Gin 打包vue或react项目输出文件到程序二进制文件
- 背景
- 解决方案
- 1. 示例目录结构
- 2. 有如下问题要解决:
- 3. 方案探索
- 效果
背景
前后端分离
已成为行业主流,vue
或react
等项目生成的文件独立在一个单独目录,与后端项目无关。
实际部署中,通常前面套一个nginx,根据请求返回静态资源或者代理到后端go服务上。
安装配置一套环境繁琐,加上有时需要部署在windows上,希望借助go的夸平台编译运行+embed嵌入文件能力,实现单个文件部署即可。
nginx配置样例
server {
listen 80;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/index.html /index.html;
}
location /api {
proxy_pass http://localhost/to-go-app-server;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}
解决方案
1. 示例目录结构
app.go
为代码文件,同dist
一个目录app-server
为编译后的单个exe
.
├── app.go
├── app-server
└── dist
├── assets
│ ├── index-43d6e8d0.css
│ └── index-f5e49ae2.js
├── CNAME
├── element-plus-logo-small.svg
├── favicon.svg
├── index.html
└── vite.svg
2. 有如下问题要解决:
http://exmpale.com/
跟路由如何定向到dist/index.html
http://exmpale.com/xx.svg
以及http://exmpale.com/assets/xxxxx.js
这些动态路由如何生成http://exmpale.com/正常业务路由
与上面静态文件路由冲突如何处理
3. 方案探索
经过参考gin官方
github.com/gin-contrib/static
的插件,找出以下简单有效的解决方案
- 使用
embed
将整个文件夹嵌入- 所有请求增加一个中间件, 判断
embed.FS
总是否存在url中路径的文件- 存在使用http.fileserver处理,并中断处理链
- 不存在处理正常的逻辑
package main
import (
"embed"
"io/fs"
"net/http"
"github.com/gin-gonic/gin"
)
//go:embed dist
var dist embed.FS
func main() {
r := gin.Default()
r.Use(ServerStatic("dist", dist))
r.GET("/ping", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "pong")
})
r.Run("localhost:81")
}
/*
假设vue/react项目输出文件夹名字为dist,拷贝到该go文件所在目录下
注意"dist"前后不能有 /
r.Use(ServerStatic("dist", dist))
*/
func ServerStatic(prefix string, embedFs embed.FS) gin.HandlerFunc {
return func(ctx *gin.Context) {
// 去掉前缀
fsys, err := fs.Sub(embedFs, prefix)
if err != nil {
panic(err)
}
fs2 := http.FS(fsys)
f, err := fs2.Open(ctx.Request.URL.Path)
if err != nil {
// 判断文件不存在,退出交给其他路由函数
ctx.Next()
return
}
defer f.Close()
http.FileServer(fs2).ServeHTTP(ctx.Writer, ctx.Request)
ctx.Abort()
}
}
效果
- 访问
http://localhost:81/
返回的是vue页面- 访问
http://localhost:81/ping
返回的是逻辑处理结果pong