Golang模板处理中遇到的常见问题解析

Golang模板处理中遇到的常见问题解析 我尝试将多个变量传递到HTML模板中,但无法实现。

模板如下:

<html>

<head>

<title>Current IP Check</title>

</head>

<body>

<div>当前IP地址:{{.injectIP}}</div>

<div>当前位置:{{.injectCity}}</div>

</body>

</html>

Go文件如下:

package main

// 模块要求
import (
	"bytes"
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
)

type Htmltemp struct {
	injectIP   string
	injectCity string
}

// 用于解析JSON并传递给HTML的模板
type Geo struct {
	City string `json:"city"`
}

// 主函数,在80端口创建Web服务器
func main() {

	http.HandleFunc("/", ReadUserIP)
	http.ListenAndServe(":80", nil)
}

func ReadUserIP(w http.ResponseWriter, r *http.Request) {

	//检查X-forwarded-for头部
	IPAddress := ""
	if IPAddress == "" {
		//IPAddress = r.Header.Get("x-forwarded-for")
		IPAddress = "134.201.250.155, 23:24:3:5" // -----------待删除,测试用硬编码
	}
	// 从请求中打印远程地址
	if IPAddress == "" {
		IPAddress = r.RemoteAddr
	}
	// 遍历字符串获取所需值

	ipadd := strings.Split(IPAddress, ",") //---- 我将:改为,
	cip1 := ipadd[0]
	//t.Execute(w, cip1)

	// 通过插入定位的IP创建地理定位API调用的端点
	buffer := bytes.Buffer{}
	buffer.WriteString("http://api.ipstack.com/")
	buffer.WriteString(cip1)
	buffer.WriteString("?access_key=2dd2ca2a9f39638d4f61533ecb1b337b&fields=city") //请在该网站注册并获取新密钥,我更改了我的密钥,另外 - 购买付费版本以使用此API获取时区数据
	url := buffer.String()

	// 进行API调用以接收IP相关数据
	response, err := http.Get(url)
	if err != nil {
		fmt.Print(err.Error())
		os.Exit(1)
	}
	responseData, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatal(err)
	}
	//fmt.Println(string(responseData)) // 用于查看响应

	//JSON解析以及位置和时区插入输出文件"t"待处理
	// - 解组JSON
	// 然后将它们插入HTML模板中
	var geo Geo

	err = json.Unmarshal(responseData, &geo)
	if err != nil {
		panic(err)
	}
	fmt.Println(geo.City)

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	t, err := template.ParseFiles("index.html")
	if err != nil {
		fmt.Fprintf(w, "无法加载模板")
	}
	inject := Htmltemp{injectIP: cip1, injectCity: geo.City}
	t.Execute(w, inject)

}

更多关于Golang模板处理中遇到的常见问题解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

感谢您分享这段经历,这是我用Go语言编写的第一个"Hello World"类型的程序,虽然遇到了挫折但很有意义。现在我已经开始学习教程了。让我惊讶的是,用Go语言完成相对复杂的任务时,代码量看起来非常少。这真是太棒了!

更多关于Golang模板处理中遇到的常见问题解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


KodeSaga: 导出的变量是否必须以大写字母开头?

是的,以大写字母开头的标识符会从包中导出,小写字母开头的则不会导出,仅在包内可用。

这在教程中甚至在你编写任何有意义的代码之前就已经涵盖了:

https://tour.golang.org/basics/3

你看到了什么错误?如果你没有看到错误,请确保不要忽略 t.Execute(…) 的结果。


PS 并编辑

我费了些功夫在我的环境中运行了你的代码,并打印了 t.Execute(…) 的结果:

template: index.html:9:33: executing "index.html" at <.injectIP>: injectIP is an unexported field of struct type main.Htmltemp

由于模板是通过反射来渲染的,模板中引用的所有字段都需要从上下文中导出。你不能在模板中使用私有字段。

我已经尝试按照您看到的方式导出它们

type Htmltemp struct {
	injectIP   string
	injectCity string
}

这简直太神奇了,我唯一做的改变就是在代码中将下面字段的首字母改为大写,现在它完美工作了

&amp;lt;div&amp;gt;Current IP Address: {{.InjectIP}}&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;Current Location: {{.InjectCity}}&amp;lt;/div&amp;gt;

并且在Go程序中的导出模板中也做了相应更改

type Htmltemp struct {
	InjectIP   string
	InjectCity string
}

这是否意味着导出的变量必须以大写字母开头?

在Go模板处理中传递多个变量时,最常见的问题是结构体字段的可见性。在您的代码中,Htmltemp结构体的字段是小写字母开头,这意味着它们是未导出的(私有字段),模板引擎无法访问这些字段。

以下是修正后的代码:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
)

// 修正:将结构体字段改为大写开头,使其可导出
type Htmltemp struct {
	InjectIP   string
	InjectCity string
}

type Geo struct {
	City string `json:"city"`
}

func main() {
	http.HandleFunc("/", ReadUserIP)
	http.ListenAndServe(":80", nil)
}

func ReadUserIP(w http.ResponseWriter, r *http.Request) {
	IPAddress := ""
	if IPAddress == "" {
		IPAddress = "134.201.250.155, 23:24:3:5"
	}
	
	if IPAddress == "" {
		IPAddress = r.RemoteAddr
	}

	ipadd := strings.Split(IPAddress, ",")
	cip1 := ipadd[0]

	buffer := bytes.Buffer{}
	buffer.WriteString("http://api.ipstack.com/")
	buffer.WriteString(cip1)
	buffer.WriteString("?access_key=2dd2ca2a9f39638d4f61533ecb1b337b&fields=city")
	url := buffer.String()

	response, err := http.Get(url)
	if err != nil {
		fmt.Print(err.Error())
		os.Exit(1)
	}
	responseData, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatal(err)
	}

	var geo Geo
	err = json.Unmarshal(responseData, &geo)
	if err != nil {
		panic(err)
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	t, err := template.ParseFiles("index.html")
	if err != nil {
		fmt.Fprintf(w, "无法加载模板")
		return
	}
	
	// 使用导出的字段名
	inject := Htmltemp{InjectIP: cip1, InjectCity: geo.City}
	err = t.Execute(w, inject)
	if err != nil {
		fmt.Fprintf(w, "模板执行错误: %v", err)
	}
}

对应的HTML模板也需要更新字段名:

<html>
<head>
<title>Current IP Check</title>
</head>
<body>
<div>当前IP地址:{{.InjectIP}}</div>
<div>当前位置:{{.InjectCity}}</div>
</body>
</html>

另一个替代方案是使用map来传递变量:

func ReadUserIP(w http.ResponseWriter, r *http.Request) {
	// ... 前面的代码保持不变
	
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	t, err := template.ParseFiles("index.html")
	if err != nil {
		fmt.Fprintf(w, "无法加载模板")
		return
	}
	
	// 使用map传递变量
	data := map[string]interface{}{
		"injectIP":   cip1,
		"injectCity": geo.City,
	}
	
	err = t.Execute(w, data)
	if err != nil {
		fmt.Fprintf(w, "模板执行错误: %v", err)
	}
}

使用map方式时,HTML模板可以保持原样不变。主要问题在于Go的可见性规则:只有首字母大写的字段才能被外部包(包括模板引擎)访问。

回到顶部