go使用embed嵌入静态资源(使用hertz)
构建vue
将vue项目建在 go 项目内的ui文件夹内,例如在ui文件夹内,pnpm build
生成的生产文件内容位于 ui/web/db-compare-ui/dist
文件夹内。
代码
// vue 项目构建的目录及所有内容都使用 embed.FS 内嵌到 go 程序体内
//go:embed ui/web/db-compare-ui/dist/**
//go:embed ui/web/db-compare-ui/dist/assets/**
var vue embed.FS
// 初始化 vue 路由
// h hertz的路由器
// contextPath 访问URL地址的根路径,通常是 /
// fs 已内嵌的 embed.FS 虚拟文件系统
// rootDir vue静态文件在 embed.FS 的根路径,通常是 xxx/xxx/dist 这样的相对路径
func InitVueRouter(h *server.Hertz, contextPath string, fs embed.FS, rootDir string) {
h.StaticFS(contextPath, &app.FS{
Root: rootDir, // 根目录
IndexNames: []string{"index.html"}, // 索引文件
GenerateIndexPages: true, // 生成索引页面
Compress: false, // 压缩
AcceptByteRange: false, // 接受字节范围
PathRewrite: nil, // 路径重写
PathNotFound: func(ctx context.Context, c *app.RequestContext) {
path := string(c.Path())
// 这个函数是路径找不到绘执行这个,例如 /login /home
// css js 文件会在 Root 文件 里面找
// 下面匹配路径
switch {
case strings.HasSuffix(path, ".js"):
return
case strings.HasSuffix(path, ".css"):
return
default:
// 必须有这一步 react vue 项目 不是 '/'路径 刷新后找不到 报 404
// 上面匹配 js css 可以不要,样式文件 会在 Root 文件 里面找,找不到会执行上面的函数
data, err := fs.ReadFile(rootDir + "/index.html") // 读取react vue 项目的 index.html
if err != nil {
return
}
c.Data(200, "text/html; charset=utf-8", data)
}
}, // 路径未找到
CacheDuration: 0, // 缓存持续时间
CompressedFileSuffix: "", // 压缩文件后缀
})
}
func main() {
h := server.Default(server.WithHostPorts("0.0.0.0:8080"))
log.Println("Starting server at port 8080")
// 构建 vue 路由
InitVueRouter(h, "/", vue, "ui/web/db-compare-ui/dist")
h.Spin()
}
补充更新
实测发现 h.StaticFS
更多的还是用于本地文件系统,而非内存嵌入的 embed.FS
。
实际上使用 h.NoRoute
更为理想,即参考的第二个链接。我使用 h.NoRoute
方式进行了改造,新的代码如下:
//go:embed ui/web/db-compare-ui/dist/**
var vueFS embed.FS
func InitVueRouter(h *server.Hertz, contextPath string) {
path := contextPath
if path == "" {
path = "/"
}
processVueRouter(h, path, vueFS, "ui/web/db-compare-ui/dist")
}
func processVueRouter(h *server.Hertz, contextPath string, vfs embed.FS, dir string) {
h.NoRoute(func(ctx context.Context, c *app.RequestContext) {
path := string(c.Path())
// 如果设置了 contextPath 的话,需要将该路径去除后得到根目录下的真实路径
if contextPath != "" && contextPath != "/" {
path = strings.Replace(path, contextPath, "", -1)
}
log.Printf("Request path: %s", path)
if path == "" || strings.HasSuffix(path, "/") {
// 对于vue应用通常使用index.html作为入口
homePage := RootDir + "/index.html"
data, err := vfs.ReadFile(homePage) // 读取react vueFS 项目的 index.html
if err != nil {
log.Printf("Error reading %s: %v", homePage, err)
return
}
c.Data(200, "text/html; charset=utf-8", data)
} else {
// 请求的是其他文件
f := RootDir + path
data, err := vfs.ReadFile(f) // 读取embed.FS内的文件
if err != nil {
log.Printf("Error reading %s: %v", f, err)
return
}
// 获取文件扩展名
ext := filepath.Ext(path)
// ext = .js .css 等文件扩展名
// GetContentType是一个根据文件扩展名返回 http 的 Content-Type 的函数
contentType := GetContentType(ext)
if err != nil {
log.Printf("Error reading %s: %v", ext, err)
return
}
if len(contentType) == 0 {
// 如果是未识别的 Content-Type 类型 返回默认的 application/octet-stream
c.Data(200, "application/octet-stream", data)
} else {
c.Data(200, contentType, data)
}
}
})
}