Go 1.16 新特性embed在gin中使用
在golang1.16加入之前,编译出来的二进制文件默认是不包括非代码文件的,那么开发一个web网站,则需要将html模板文件、配置文件和静态资源文件(css、js)始终按照开发时的目录结构放在一起,部署起来很麻烦。
embed是在Go 1.16中新加包。它通过//go:embed
指令,可以在编译阶段将静态资源文件打包进编译好的程序中,并提供访问这些文件的能力。
1.使用gin框架构建一个简单的静态资源服务器
使用gin构建一个web服务非常简单,使用embed加入静态资源也非常简单,代码如下:
package main
import (
"embed"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
//go:embed statics/* templates/*
var Assets embed.FS
func main() {
router := gin.Default()
router.StaticFS("/", http.FS(Assets))
err := router.Run("0.0.0.0:5000")
if err != nil {
fmt.Println(err)
return
}
}
文件目录结构如下:
启动之后我们访问http://127.0.0.1:5000
即可看到如下界面:
2.在模板语言中使用静态资源
加入embed之后,gin使用模板引擎主要通过自带的方法SetHTMLTemplate来加载从embed中解析的模板文件, 接下来对项目结构进行以下修改以避免路由冲突,然后来渲染一个简单的页面。
此时项目目录结构如下:
新增的statics.go
主要是为了加载静态资源时保证路由的名称合理,其内容如下
package statics
import "embed"
//go:embed css/* js/*
var Statics embed.FS
main.go
文件内容修改后为:
package main
import (
"embed"
"fmt"
"gin_embed/statics"
"github.com/gin-gonic/gin"
"html/template"
"net/http"
)
//go:embed templates/*
var Templates embed.FS
func main() {
router := gin.Default()
router.StaticFS("/statics", http.FS(statics.Statics))
tmpl, _ := template.ParseFS(Templates, "templates/*")
router.GET("/layout", index)
router.SetHTMLTemplate(tmpl)
err := router.Run("0.0.0.0:5000")
if err != nil {
fmt.Println(err)
return
}
}
func index(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "layouts.tmpl", gin.H{"title": "布局页面"})
}
layouts.tmpl
文件内容为:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>{{.title}}</title>
<link href="/statics/css/semantic.css" rel="stylesheet" type="text/css">
<script src="/statics/js/jquery-3.6.0.min.js"></script>
<script src="/statics/js/semantic.min.js"></script>
</head>
<body>
<div class="ui secondary pointing menu">
<a href="first" class="item first-menu-item active">
第一页
</a>
<a class="item" href="second">
第二页
</a>
</div>
<div class="ui container">
<p>我是基础页面</p>
<button class="ui button primary">按钮</button>
</div>
</body>
</html>
此时进入http://127.0.0.1:/5000/layouts
我们将能看到:
到此,加载静态资源和简单的模板渲染就可以实现了。
3.多模板渲染
gin本身是不支持多模板渲染的,要进行多模板渲染,可以查看示例代码
但在引入embed过后,我们需要对官方示例做一些调整来保证正常运行。
首先在templates/文件目录下新建两个文件first.tmpl、second.tmpl, 内容分别如下:
{{/*first.tmpl*/}}
{{template "layouts" .}}
{{define "content"}}
<p>我是第一页</p>
{{end}}
{{/*second.tmpl*/}}
{{template "layouts" .}}
{{define "content"}}
<p>我是第二页</p>
{{end}}
修改layouts.tmpl,加入block块来实现继承
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>{{.title}}</title>
<link href="/statics/css/semantic.css" rel="stylesheet" type="text/css">
<script src="/statics/js/jquery-3.6.0.min.js"></script>
<script src="/statics/js/semantic.min.js"></script>
</head>
<body>
<div class="ui secondary pointing menu">
<a href="first" class="item first-menu-item active">
第一页
</a>
<a class="item" href="second">
第二页
</a>
</div>
<div class="ui container">
<p>我是基础页面</p>
<button class="ui button primary">按钮</button>
{{block "content" .}}{{end}}
</div>
</body>
</html>
main.go修改如下
package main
import (
"embed"
"fmt"
"gin_embed/statics"
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
"html/template"
"net/http"
)
//go:embed templates/*
var Templates embed.FS
func main() {
router := gin.Default()
router.StaticFS("/statics", http.FS(statics.Statics))
router.GET("/first", first)
router.GET("/second", second)
router.HTMLRender = CreateMyRender()
err := router.Run("0.0.0.0:5000")
if err != nil {
fmt.Println(err)
return
}
}
type Render struct {
render multitemplate.Render
assets embed.FS
}
func (r *Render) AddFromEmbed (name string, patterns ...string) {
tmpl, err := template.ParseFS(r.assets, patterns...)
if err != nil {
fmt.Println(err)
panic(err)
}
r.render.Add(name, tmpl)
}
func CreateMyRender() multitemplate.Renderer {
var render Render
render = Render{
render: multitemplate.New(),
assets: Templates,
}
render.AddFromEmbed("layouts", "templates/layouts.tmpl")
render.AddFromEmbed("first", "templates/layouts.tmpl", "templates/first.tmpl")
render.AddFromEmbed("second", "templates/layouts.tmpl", "templates/second.tmpl")
return render.render
}
func first(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "first", gin.H{"title": "第一页"})
}
func second(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "second", gin.H{"title": "第二页"})
}
再次启动后进入http://127.0.0.1/first
后界面如下:
最终项目结构如下:
完整代码可前往我的GitHub项目查看.