package main import ( "embed" "io/fs" "net/http" "strings" "github.com/gin-gonic/gin" ) // frontendFS 是构建时嵌入的前端生产资源。 // Logan.Li // //go:embed all:static var frontendFS embed.FS // setupEmbeddedFrontend 为单二进制模式提供前端静态资源和 SPA 回退。 // Logan.Li func setupEmbeddedFrontend(r *gin.Engine) { staticFS, err := fs.Sub(frontendFS, "static") if err != nil { return } if _, err := fs.Stat(staticFS, "index.html"); err != nil { // 开发模式不复制 dist,后端仅提供 API。 // Logan.Li return } httpFS := http.FS(staticFS) r.NoRoute(func(c *gin.Context) { path := c.Request.URL.Path if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/ws/") { c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "接口不存在"}) return } filePath := strings.TrimPrefix(path, "/") if filePath == "" { filePath = "index.html" } if _, err := staticFS.Open(filePath); err == nil { if strings.HasSuffix(filePath, ".html") { c.Header("Cache-Control", "no-store, no-cache, must-revalidate") } c.FileFromFS(path, httpFS) return } if strings.HasPrefix(filePath, "assets/") { c.String(http.StatusNotFound, "static asset not found") return } index, err := fs.ReadFile(staticFS, "index.html") if err != nil { c.String(http.StatusInternalServerError, "前端资源不可用") return } c.Header("Cache-Control", "no-store, no-cache, must-revalidate") c.Data(http.StatusOK, "text/html; charset=utf-8", index) }) }