@@ -0,0 +1,56 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"oci-portal/internal/webui"
|
||||
)
|
||||
|
||||
// registerWebUI 挂载嵌入的前端产物。dev 模式走 vite devServer,不经此路径。
|
||||
func registerWebUI(r *gin.Engine) error {
|
||||
sub, err := webui.Dist()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mountSPA(r, sub)
|
||||
return nil
|
||||
}
|
||||
|
||||
// mountSPA 注册 NoRoute 兜底:命中真实文件直出,API 前缀保持 JSON 404,
|
||||
// 其余路径回 index.html 交给 SPA 路由。
|
||||
func mountSPA(r *gin.Engine, sub fs.FS) {
|
||||
fileServer := http.FileServer(http.FS(sub))
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
p := c.Request.URL.Path
|
||||
if strings.HasPrefix(p, "/api/") || strings.HasPrefix(p, "/ai/") {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
if name := strings.TrimPrefix(path.Clean(p), "/"); name != "" {
|
||||
if _, err := fs.Stat(sub, name); err == nil {
|
||||
setStaticCacheHeader(c, p)
|
||||
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||
return
|
||||
}
|
||||
}
|
||||
// SPA 路由统一回 index.html,禁缓存保证发版后入口即时更新
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Request.URL.Path = "/"
|
||||
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
}
|
||||
|
||||
// setStaticCacheHeader 按路径设缓存策略:vite 产物带内容 hash,/assets/* 可永久缓存;
|
||||
// 其余真实文件(favicon 等)不带 hash,禁强缓存。
|
||||
func setStaticCacheHeader(c *gin.Context, p string) {
|
||||
if strings.HasPrefix(p, "/assets/") {
|
||||
c.Header("Cache-Control", "public, max-age=31536000, immutable")
|
||||
return
|
||||
}
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
}
|
||||
Reference in New Issue
Block a user