模板

目标

  • 实现静态资源服务。
  • 支持HTML模板渲染。

服务端渲染

模板可以理解为事先定义好的HTML文档文件,模板渲染的作用机制可以简单理解为文本替换操作–使用相应的数据去替换HTML文档中事先准备好的标记。

前后端分离的开发模式:即 Web 后端提供 RESTful 接口,返回结构化的数据(通常为 JSON 或者 XML)。前端使用 AJAX 技术请求到所需的数据,利用 JavaScript 进行渲染。

前后端分离开发模式的优势前后端解耦,前端注于界面设计实现,只需要考虑拿到数据后如何渲染即可。后端专心解决资源利用,并发,数据库等问题,只需要考虑数据如何生成。同一套后端服务能够同时支撑小程序、移动APP、PC端 Web 页面,以及对外提供的接口。

设计思路

处理静态文件

将所有的静态文件放在/usr/web目录下,映射到真实的文件后,将文件返回。

返回:通过net/http库解析请求的地址,映射到服务器上文件的真实地址,交给http.FileServer处理。

gee.go

Static这个方法:用户可以将磁盘上的某个文件夹root映射到路由relativePath

1
2
3
4
5
r := gee.New()
r.Static("/assets", "/usr/jpc/blog/static")
// 或相对路径 r.Static("/assets", "./static")
r.Run(":9999")
// 用户访问localhost:9999/assets/xxx.js,最终返回/usr/jpc/blog/static/xxx.js。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建静态处理程序
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
absolutePath := path.Join(group.prefix, relativePath)
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
return func(c *Context) {
file := c.Param("filepath")
// 检查文件是否存在或者我们是否有权限访问它
if _, err := fs.Open(file); err != nil {
c.Status(http.StatusNotFound)
return
}

fileServer.ServeHTTP(c.Writer, c.Req)
}
}

// Static 提供静态文件
func (group *RouterGroup) Static(relativePath string, root string) {
handler := group.createStaticHandler(relativePath, http.Dir(root))
urlPattern := path.Join(relativePath, "/*filepath")
// 注册GET处理程序
group.GET(urlPattern, handler)
}

HTML 模板渲染

go语言提供了html/template模板库,为 HTML 提供了较为完整的支持。包括普通变量渲染、列表渲染、对象渲染等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Engine struct {
*RouterGroup
router *router
groups []*RouterGroup // 存储所有路由组
htmlTemplates *template.Template // html render
funcMap template.FuncMap // html render
}

// 自定义模板渲染函数
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
engine.funcMap = funcMap
}

// 将所有模板加载进内存
func (engine *Engine) LoadHTMLGlob(pattern string) {
engine.htmlTemplates = template.Must(template.New("").Funcs(engine.funcMap).ParseGlob(pattern))
}

context.go支持根据模板文件名选择模板进行渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Context struct {
// ...
// engine pointer
engine *Engine
}

func (c *Context) HTML(code int, name string, data interface{}) {
c.SetHeader("Content-Type", "text/html")
c.Status(code)
if err := c.engine.htmlTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {
c.Fail(500, err.Error())
}
}

gee.go实例化 Context 时,还需要给 c.engine 赋值。

1
2
3
4
5
6
7
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// ...
c := newContext(w, req)
c.handlers = middlewares
c.engine = engine
engine.router.handle(c)
}

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main


import (
"fmt"
"html/template"
"net/http"
"time"

"gee"
)

type student struct {
Name string
Age int8
}

func FormatAsDate(t time.Time) string {
year, month, day := t.Date()
return fmt.Sprintf("%d-%02d-%02d", year, month, day)
}

func main() {
r := gee.New()
r.Use(gee.Logger())
r.SetFuncMap(template.FuncMap{
"FormatAsDate": FormatAsDate,
})
r.LoadHTMLGlob("templates/*")
r.Static("/assets", "./static")

stu1 := &student{Name: "jpc", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "css.tmpl", nil)
})
r.GET("/students", func(c *gee.Context) {
c.HTML(http.StatusOK, "arr.tmpl", gee.H{
"title": "gee",
"stuArr": [2]*student{stu1, stu2},
})
})

r.GET("/date", func(c *gee.Context) {
c.HTML(http.StatusOK, "custom_func.tmpl", gee.H{
"title": "gee",
"now": time.Date(2019, 8, 17, 0, 0, 0, 0, time.UTC),
})
})

r.Run(":9999")
}

访问页面

image-20221213193944336

项目地址

项目地址