Files
jiang13-forum/embed_static/embed.go
freefire 37d14a22d5 fix: 发版后软刷新检测入口壳并在 chunk 404 时硬刷新
避免 Logo/下拉刷新仍跑旧 index.js,以及站内消息预取失败弹出英文 toast。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 07:53:39 +08:00

119 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package embed_static
import (
"embed"
"io/fs"
"net/http"
"regexp"
"strings"
"github.com/gin-gonic/gin"
)
//go:embed static/*
var staticFS embed.FS
var (
spaTitleRe = regexp.MustCompile(`(?s)<title>.*?</title>`)
spaBrandTitleFn func() string
spaBrandJSONFn func() []byte // 站点品牌 JSON注入 window.__J13_BRANDING__
spaBrandFaviconFn func() string // 站点 Favicon URL注入 <link rel="icon">
)
// SetSPADocumentTitle 注册站点标题提供者ServeSPA 会注入到入口 HTML避免刷新闪烁
func SetSPADocumentTitle(fn func() string) {
spaBrandTitleFn = fn
}
// SetSPABrandingJSON 注册品牌 JSON 提供者(须为合法 JSON 对象),供前端首屏同步读入
func SetSPABrandingJSON(fn func() []byte) {
spaBrandJSONFn = fn
}
// SetSPAFaviconURL 注册 Favicon URL 提供者,注入到入口 HTML 的 <link rel="icon">
func SetSPAFaviconURL(fn func() string) {
spaBrandFaviconFn = fn
}
// SetupEmbed 配置内嵌资源React SPA 静态资源
func SetupEmbed(r *gin.Engine) error {
if sub, err := fs.Sub(staticFS, "static/spa/assets"); err == nil {
fileServer := http.StripPrefix("/assets", http.FileServer(http.FS(sub)))
r.GET("/assets/*filepath", func(c *gin.Context) {
// 仅 200 写 immutable避免中间层把旧 chunk 的 404 长期缓存
w := &cacheOnOKWriter{ResponseWriter: c.Writer, cacheControl: "public, max-age=31536000, immutable"}
c.Writer = w
fileServer.ServeHTTP(c.Writer, c.Request)
})
}
if sub, err := fs.Sub(staticFS, "static/spa/stickers"); err == nil {
fileServer := http.StripPrefix("/stickers", http.FileServer(http.FS(sub)))
r.GET("/stickers/*filepath", func(c *gin.Context) {
// stickers 不含哈希指纹,设置适中的缓存
c.Header("Cache-Control", "public, max-age=86400")
fileServer.ServeHTTP(c.Writer, c.Request)
})
}
return nil
}
// cacheOnOKWriter 仅在最终状态码为 200 时写入长期 Cache-Control
type cacheOnOKWriter struct {
gin.ResponseWriter
cacheControl string
wroteHeader bool
}
func (w *cacheOnOKWriter) WriteHeader(code int) {
if !w.wroteHeader {
w.wroteHeader = true
if code == http.StatusOK {
w.Header().Set("Cache-Control", w.cacheControl)
}
}
w.ResponseWriter.WriteHeader(code)
}
func (w *cacheOnOKWriter) Write(b []byte) (int, error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
return w.ResponseWriter.Write(b)
}
// ServeSPA 返回 React SPA 入口(仅注入站点默认标题)
func ServeSPA(c *gin.Context) {
ServeSPAWithMeta(c, nil)
}
// ServeSPANoIndex 返回带 noindex 的 SPA登录/后台等私密页)
func ServeSPANoIndex(c *gin.Context) {
title := ""
if spaBrandTitleFn != nil {
title = strings.TrimSpace(spaBrandTitleFn())
}
ServeSPAWithMeta(c, &SPAPageMeta{
Title: title,
Robots: "noindex,nofollow",
})
}
// IsSPARoute 判断是否应由 SPA 处理
func IsSPARoute(path string) bool {
if path == "/health" || path == "/robots.txt" || path == "/sitemap.xml" {
return false
}
if strings.HasPrefix(path, "/api") ||
strings.HasPrefix(path, "/admin") ||
strings.HasPrefix(path, "/uploads") ||
strings.HasPrefix(path, "/media") ||
strings.HasPrefix(path, "/assets") ||
strings.HasPrefix(path, "/stickers") ||
strings.HasPrefix(path, "/oauth") ||
strings.HasPrefix(path, "/.well-known") {
return false
}
return true
}