在一个 Java 的 web 应用中,借助 Spring Boot 框架,可以很简单地将应用的网页部分和服务端逻辑(一般称为“后端”)部分打包成一个独立的jar
文件。通过向用户提供该 jar 包,即可在安装了 JRE 的受支持的终端上运行此应用。
使用 Go 语言时,可借助 go-bindata
( homebrew 、主分支)或 embed
包( Go 1.16
及之后)等方案,实现将静态文件打包至二进制可执行程序中。
在我的一个应用创建之初, Go 1.16
版本还未发布,当时选择 go-bindata
将网页部分代码(由 Vue
创建的单页应用)作为静态文件,打包并提供给 Gin
框架。随着 1.16
版本的发布,我将这部分代码改为由 embed
实现,迁移的过程非常容易,只需要涉及少量的修改。迁移后发现后者生成的二进制代码明显更大,但考虑到二者的发展前景,选择仍然使用 embed
打包静态文件。
代码结构
简化后的目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| $ tree . . ├── frontend │ ├── dist │ │ ├── favicon.ico │ │ └── index.html │ ├── node_modules │ └── package.json ├── go.mod ├── go.sum └── main.go
3 directories, 6 files
|
其中, frontend/
目录用于保存前端相关的内容, frontend/dist/
中保存编译后、待发布的静态内容。main.go
作为程序的主要入口,兼具提供 API
与提供静态网页之用途。
Web 页面部分
根据项目的需要选择 React/Vue等框架进行开发,本节使用一个简单 HTML
页面举例。
页面 frontend/dist/index.html
中内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello World</title> </head> <body> <div id="hello"></div> <script> setInterval(() => { fetch('/api/hello').then(res => res.json()).then(data => { document.getElementById('hello').innerHTML = data.message; }) }, 1000) </script> </body> </html>
|
其中的 /api/hello
由 Gin
的后端提供
Gin 部分
程序的入口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
| package main
import ( "embed" "fmt" "github.com/gin-gonic/gin" "mime" "strings" "time" )
var static embed.FS
func main() { r := gin.Default() r.GET("/api/hello", func(c *gin.Context) { timeFormat := "2006-01-02 15:04:05" msg := fmt.Sprintf("Hello, current time is %s", time.Now().Format(timeFormat)) c.JSON(200, gin.H{ "message": msg, }) }) r.NoRoute(func(c *gin.Context) { path := c.Request.URL.Path s := strings.Split(path, ".") prefix := "frontend/dist" if data, err := static.ReadFile(prefix + path); err != nil { if data, err = static.ReadFile(prefix + "/index.html"); err != nil { c.JSON(404, gin.H{ "err": err, }) } else { c.Data(200, mime.TypeByExtension(".html"), data) } } else { c.Data(200, mime.TypeByExtension(fmt.Sprintf(".%s", s[len(s)-1])), data) } }) _ = r.Run(":8080") }
|
完整的例子已托管于 Gitlab