无闻Web2.基础模板用法

在上一节中,我们学习了如何使用Go语言编写HTTP服务器的N种写法。但是,总是响应固定的字符串对用户和开发者而言都会显得非常无趣。因此,这节我们来学习如何使用Go语言标准库的text/template包来向客户端(即浏览器或终端)响应动态的内容。

初识文本模板引擎

标准库中的text/template包是Go语言内置的文本模板引擎,虽然在灵活性上不如其它语言中第三方框架自带的模板引擎(如Django、Ruby on Rails等等),但功能依旧十分强大。根据标准库给出的定义,它的主要特性如下:

  1. 将模板应用于给定的数据结构来执行模板,模板的编程与Go语言源代码文件相同,需为UTF-8编码
  2. 模板中的注释(Annotation)会根据数据结构中的元素来执行并派生具体的显示结构,这些元素一般指结构体中的字段或map中的键名
  3. 模板的执行逻辑会依据点(Dot,".")操作符来设定当前的执行位置,并按序完成所有逻辑的执行
  4. 模板中的行为(Action)包括数据评估(Data Evaluation)和控制逻辑,且需要使用双层大括号({{}})包裹。除行为以外的任何内容都会原样输出不做修改
  5. 模板解析完成后,从设计上可并发的进行渲染,但要注意被渲染对象的并发安全性。例如,一个模板可以同时为多个客户端的响应进行渲染,因为输出对象(Writer)是相互独立的,但是被渲染的对象可能有各自的状态和时效性

接下来,让我们结合上节课所学的知识,从一个简单的例子开始学习使用Go语言中的文本模板引擎。简单起见,我们依旧从输出"Hello World!"字符串开始。

package mian

import (
	"fmt"
	"log"
	"net/http"
	"text/template"
)

func main() {
	http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request) {
		// 创建模板对象并解析模板内容
		tmpl, err := template.New("test").Parse("Helo World!")
		if err != nil {
			fmt.Fprintf(w,"Parse:%v",err)
			return
		}
		
		// 调用模板对象的渲染方法
		err = tmpl.Execute(w,nil)
		if err != nil {
			fmt.Fprintf(w,"Execute:%v",err)
			return
		}
	})
	
	log.Println("Starting HTTP server...")
    log.Fatal(http.ListenAndServe("localhost:4000", nil))
}

如果运行上面的代码,就会发现和上节课的输出毫无区别,并且在作为处理器的匿名函数中增加了更多的逻辑。

相比之前多出来的这部分逻辑便是创建、解析和渲染模板的必要步骤:

  1. template.New的作用就是根据用户给定的名称创建一个模板对象,本例中我们使用了"test"字符串作为这个模板对象的名称。另外,由于template.New函数会直接返回一个*template.Template对象,因此可以直接链式操作调用该对象的Parse方法。
  2. template.Parse方法接受一个string类型的参数,即文本模板的内容,然后对内容进行解析并返回解析过程中发生的任何错误。本例中,我们使用了没有任何模板语法的"Hello World!"字符串,同时获得了两个返回值。第一个返回值依旧是一个*template.Template对象,此时该对象已经包含了模板解析后的数据结构。第二个返回值便是在解析过程中可能出现的错误,这要求我们对该错误进行检查判断
  3. 如果模板解析过程没有产生任何错误则表示模板可以被用于渲染了,template.Execute就是用于渲染模板的方法,该方法接受两个参数:输出对象和指定数据对象(或根对象)。简单起见,本例中我们只使用到了第一个参数,即输出对象。凡是实现了io.Writer接口的实例均可以作为输出对象,这在Go语言中是非常常见的一种编码模式

在模板中渲染变量

学会了模板渲染的基本操作之后,我们就可以开始向模板中输出一些动态的内容了。首先,我们来快速了解一下怎么获取HTTP协议中GET请求的URL查询参数(即问号"?“之后的内容)。例如,我们想要获取”/?val=123"中的"val"的值,并返回给客户端

package main

import (
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(r.URL.Query().Get("val")))
    })

    log.Println("Starting HTTP server...")
    log.Fatal(http.ListenAndServe("localhost:4000", nil))
}

我们这里用到的方法就是*http.Request对象的URL.Query().Get方法。通过终端执行可以获得如下结果,你还可以尝试赋予"val"其它的值,服务端也会输出对应的内容。

curl http://localhost:4000/?val="123"
123

现在,我们可以结合模板语法,将这个"val"的值进行渲染了。

package main

import (
	"fmt"
	"log"
	"net/http"
	"text/template"
)

func main() {
	http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request) {
		// 创建模板对象并解析模板内容
		tmpl,err := template.New("test").Parse("The value is:{{.}}")
		if err != nil {
			fmt.Fprintf(w,"Parse:%v",err)
			return
		}
		
		// 获取URL参数的值
		val := r.URL.Query().Get("val")
		// 调用模板对象的渲染方法
		err = tmpl.Execuate(w,val)
		if err != nil {
			fmt.Fprintf(w,"Execuate:%v",err)
			return
		}
	})

	log.Println("Starting HTTP server...")
    log.Fatal(http.ListenAndServe("localhost:4000", nil))
}

在上面的代码中,你可以注意到模板的内容被替换为了The value is:{{.}},即使用了分隔符将点操作符包裹起来。在Go语言的标准库模板引擎中,点操作符默认指向的是根对象,即我们在调用template.Execuate方法时传入的第二个参数。本例中,我们传入的根对象是一个单纯的string类型的变量val,那么点操作符的渲染对象就是变量val

尝试运行以上代码可以在终端获得以下结果:

curl http://localhost:4000/?val=666
The value is: 666

在模板中渲染复杂对象

你是否也正在思考,除了简单类型的变量,根对象还可以是什么类型呢?细心的你可能已经发现,template.Execuate方法的第二个参数类型为interface{},也就是说可以传入任何类型。这代表text/template包提供的文本模板引擎会根据所提供的根对象进行底层类型分析,然后自动判断应该以什么样的形式去理解模板中的语法。

让我们来创建一个名为Inventory的复合类型,然后通过URL查询参数的值创建一个实例,最后通过模板渲染出各个字段的值:

package main

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"strconv"
)

type Inventory struct {
	SKU       string
	Name      string
	UnitPrice float64
	Quantity  int64
}

func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		// 创建模板对象并解析模板内容
		tmpl, err := template.New("test").Parse(`Inventory
			SKU:{{.SKU}}
			Name:{{.Name}}
			UnitPrice:{{.UnitPrice}}
			Quantity:{{.Quantity}}
		`)
		if err != nil {
			fmt.Fprintf(writer,"Parse:%v",err)
			return
		}
		// 根据URL查询参数的值创建Inventory实例
		inventory := &Inventory{
			SKU:  request.URL.Query().Get("sku"),
			Name: request.URL.Query().Get("name"),
		}

		// 注意:为了简化代码逻辑,这里并没有进行错误处理
		inventory.UnitPrice, _= strconv.ParseFloat(request.URL.Query().Get("unitPrice"), 64)
		inventory.Quantity,_ = strconv.ParseInt(request.URL.Query().Get("quantity"),10,64)

		// 调用模板对象的渲染方法
		err = tmpl.Execute(writer, inventory)
		if err != nil {
			fmt.Fprintf(writer,"Execute:%v",err)
			return
		}
	})

	log.Println("Starting HTTP server...")
	log.Fatal(http.ListenAndServe("localhost:4000", nil))
}

可以注意到,使用分隔符包裹起来的内容和Inventory类型中的字段名称是一一对应的,且大小写保持一致(Go语言是一门大小写敏感的语言)。模板中使用了点操作符指代根对象inventory,即通过URL查询参数的值所创建的一个变量。这里用到了strconv包中的ParseFloatParseInt函数,主要作用为解析字符串为浮点型和整型数字。

尝试运行以上代码可以在终端获得以下结果:

curl http://localhost:4000/?sku=1122334&name=phone&unitPrice=649.99&quantity=833
Inventory
SKU: 1122334
Name: phone
UnitPrice: 649.99
Quantity: 833

在模板中调用结构的方法

我们已经讲解了如何在模板中显示具体对象的字段值,那么,是不是也可以使用同样的方式来调用对象具有的方法呢?答案当然是肯定的。

我们需要先为Inventory类型添加一个方法,称为Subtotal,即根据该商品的单价和数量来显示当前库存所具有的价值。

// Subtotal 根据单价和数量计算出总价值
func (i *Inventory) Subtotal() float64 {
	return i.UnitPrice * float64(i.Quantity)
}

然后在模板中添加相关的内容,使得计算结果能够通过模板渲染展示给客户端

// 创建模板对象并解析模板内容
tmpl, err := template.New("test").Parse(`Inventory
	SKU:{{.SKU}}
	Name:{{.Name}}
	UnitPrice:{{.UnitPrice}}
	Quantity:{{.Quantity}}
	Subtotal:{{.Subtotal}}`)

可以注意到,在text/template包提供的文本模板引擎中,显示方法调用结果的值和字段的值的语法是完全相同的,即不需要在方法名称后使用括号表示调用。该模板引擎会在渲染时自动识别所调用对象的具体类型,然后做出相应的操作。

尝试运行以上代码可以在终端获得以下结果:

curl http://localhost:4000/?sku=1122334&name=phone&unitPrice=649.99&quantity=833
Inventory
SKU: 1122334
Name: phone
UnitPrice: 649.99
Quantity: 833
Subtotal: 541441.67

使用map类型作为模板根对象

我想你应该已经意识到将某个具体类型作为模板对象的局限性,因为不论想要展示什么内容,都需要通过修改添加类型的字段或方法才能实现,在操作上非常的不灵活。但是,如果你还记得根对象的参数类型为interface{}的话,应该就不难理解通过利用一个map[string]interface{}类型的根对象,可以实现灵活的向模板中添加需要被渲染的子对象。

这种方案可行的根本原因是因为在Go语言中,当interface{}类型作为参数时,调用者可以传入任意类型的值,效果类似Java中的Object类型。

接下来,让我们通过使用map[string]interface{}类型作为根对象,实现之前展示Inventory类型字段值的效果:

package main

import (
    "fmt"
    "log"
    "net/http"
    "strconv"
    "text/template"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 创建模板对象并解析模板内容
        tmpl, err := template.New("test").Parse(`Inventory
SKU: {{.SKU}}
Name: {{.Name}}
UnitPrice: {{.UnitPrice}}
Quantity: {{.Quantity}}
`)
        if err != nil {
            fmt.Fprintf(w, "Parse: %v", err)
            return
        }

        // 获取 URL 查询参数的值
        // 注意:为了简化代码逻辑,这里并没有进行错误处理
        sku := r.URL.Query().Get("sku")
        name := r.URL.Query().Get("name")
        unitPrice, _ := strconv.ParseFloat(r.URL.Query().Get("unitPrice"), 64)
        quantity, _ := strconv.ParseInt(r.URL.Query().Get("quantity"), 10, 64)

        // 调用模板对象的渲染方法,并创建一个 map[string]interface{} 类型的临时变量作为根对象
        err = tmpl.Execute(w, map[string]interface{}{
            "SKU":       sku,
            "Name":      name,
            "UnitPrice": unitPrice,
            "Quantity":  quantity,
        })
        if err != nil {
            fmt.Fprintf(w, "Execute: %v", err)
            return
        }
    })

    log.Println("Starting HTTP server...")
    log.Fatal(http.ListenAndServe("localhost:4000", nil))
}

在以上代码中,我们将URL查询的参数赋值给多个变量,然后将所有变量以键值对的形式生成一个map[string]interface{}类型的临时对象作为模板的根对象。相比之前需要先定义一个Inventory类型而言,这种方式可以更加灵活便利的将对象放置到模板中用于渲染。

curl http://localhost:4000/?sku=1122334&name=phone&unitPrice=649.99&quantity=833
Inventory
SKU: 1122334
Name: phone
UnitPrice: 649.99
Quantity: 833

许多Web框架的实现都是基于这个小技巧,如果之前还不明所以的话,现在应该知道其中的原理了吧?

在模板中使用注释

虽然目前我们所使用的模板文本还都非常简单,但当模板内容变多、逻辑更加复杂的时候就会想要使用注释来进行辅助理解,便于后期的维护和开发。

注释语法和Go语言程序代码中的块注释语法相同,即使用/**/将注释内容包裹起来,例如:{{/*这是注释的内容*/}}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图像识别技术在病虫害检测中的应用是一个快速发展的领域,它结合了计算机视觉和机器学习算法来自动识别和分类植物上的病虫害。以下是这一技术的一些关键步骤和组成部分: 1. **数据收集**:首先需要收集大量的植物图像数据,这些数据包括健康植物的图像以及受不同病虫害影响的植物图像。 2. **图像预处理**:对收集到的图像进行处理,以提高后续分析的准确性。这可能包括调整亮度、对比度、去噪、裁剪、缩放等。 3. **特征提取**:从图像中提取有助于识别病虫害的特征。这些特征可能包括颜色、纹理、形状、边缘等。 4. **模型训练**:使用机器学习算法(如支持向量机、随机森林、卷积神经网络等)来训练模型。训练过程中,算法会学习如何根据提取的特征来识别不同的病虫害。 5. **模型验证和测试**:在独立的测试集上验证模型的性能,以确保其准确性和泛化能力。 6. **部署和应用**:将训练好的模型部署到实际的病虫害检测系统中,可以是移动应用、网页服务或集成到智能农业设备中。 7. **实时监测**:在实际应用中,系统可以实时接收植物图像,并快速给出病虫害的检测结果。 8. **持续学习**:随着时间的推移,系统可以不断学习新的病虫害样本,以提高其识别能力。 9. **用户界面**:为了方便用户使用,通常会有一个用户友好的界面,显示检测结果,并提供进一步的指导或建议。 这项技术的优势在于它可以快速、准确地识别出病虫害,甚至在早期阶段就能发现问题,从而及时采取措施。此外,它还可以减少对化学农药的依赖,支持可持续农业发展。随着技术的不断进步,图像识别在病虫害检测中的应用将越来越广泛。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值