refactor: 静态资源缓存破坏改为文件级哈希,重启不刷新未修改文件的缓存
- 启动时计算每个 JS/CSS 的 SHA-1 前 8 位作为版本号 - 文件不变则哈希不变,浏览器继续用缓存,重启零压力 - 替换全局 AssetVersion 为 assetV 模板函数,按文件路径查询哈希 - 移除 BuildPageData 中的全局版本号注入
This commit is contained in:
@ -1,23 +1,105 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"metazone.cc/metalab/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AssetVersion 静态资源版本号(服务启动时生成,重启即刷新缓存)
|
||||
var AssetVersion string
|
||||
// assetHashes 静态资源文件哈希表(URL 路径 → 短哈希,文件不变哈希不变)
|
||||
var (
|
||||
assetHashes map[string]string
|
||||
assetOnce sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
AssetVersion = strconv.FormatInt(time.Now().Unix(), 36)
|
||||
// ComputeAssetHashes 遍历 static 目录,计算每个文件的 SHA-1 前 8 位作为版本号
|
||||
func ComputeAssetHashes(templateDir string) {
|
||||
assetHashes = make(map[string]string)
|
||||
staticDir := filepath.Join(templateDir, "static")
|
||||
filepath.WalkDir(staticDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
// 只处理 .js 和 .css
|
||||
ext := filepath.Ext(path)
|
||||
if ext != ".js" && ext != ".css" {
|
||||
return nil
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
h := sha1.Sum(data)
|
||||
shortHash := hex.EncodeToString(h[:])[:8]
|
||||
// 生成 URL 路径:/static/...
|
||||
rel, _ := filepath.Rel(filepath.Dir(templateDir), path)
|
||||
urlPath := "/" + filepath.ToSlash(rel)
|
||||
assetHashes[urlPath] = shortHash
|
||||
return nil
|
||||
})
|
||||
// 也处理 shared/static 目录
|
||||
sharedDir := filepath.Join(filepath.Dir(templateDir), "shared", "static")
|
||||
filepath.WalkDir(sharedDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
if ext != ".js" && ext != ".css" {
|
||||
return nil
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
h := sha1.Sum(data)
|
||||
shortHash := hex.EncodeToString(h[:])[:8]
|
||||
rel, _ := filepath.Rel(filepath.Dir(templateDir), path)
|
||||
urlPath := "/" + filepath.ToSlash(rel)
|
||||
assetHashes[urlPath] = shortHash
|
||||
return nil
|
||||
})
|
||||
// 也处理 admin/static 目录
|
||||
adminDir := filepath.Join(filepath.Dir(templateDir), "admin", "static")
|
||||
filepath.WalkDir(adminDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
if ext != ".js" && ext != ".css" {
|
||||
return nil
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
h := sha1.Sum(data)
|
||||
shortHash := hex.EncodeToString(h[:])[:8]
|
||||
rel, _ := filepath.Rel(filepath.Dir(templateDir), path)
|
||||
urlPath := "/" + filepath.ToSlash(rel)
|
||||
assetHashes[urlPath] = shortHash
|
||||
return nil
|
||||
})
|
||||
log.Printf("[AssetHashes] computed %d file hashes", len(assetHashes))
|
||||
}
|
||||
|
||||
// AssetV 返回指定静态资源的哈希版本号(模板函数)
|
||||
func AssetV(path string) string {
|
||||
if h, ok := assetHashes[path]; ok {
|
||||
return h
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// mdImageRe 匹配 Markdown 图片语法 
|
||||
@ -57,8 +139,6 @@ func BuildPageData(c *gin.Context, extra gin.H) gin.H {
|
||||
if isMaintenance, exists := c.Get("is_maintenance"); exists {
|
||||
data["IsMaintenance"] = isMaintenance
|
||||
}
|
||||
// 注入静态资源版本号
|
||||
data["V"] = AssetVersion
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"metazone.cc/metalab/internal/common"
|
||||
)
|
||||
|
||||
// TemplateRoot 模板根目录配置
|
||||
@ -21,6 +23,7 @@ func LoadTemplates(roots ...TemplateRoot) (*template.Template, error) {
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"subtract": func(a, b int) int { return a - b },
|
||||
"formatTTL": formatTTL,
|
||||
"assetV": common.AssetV,
|
||||
}
|
||||
t := template.New("").Funcs(funcMap)
|
||||
for _, root := range roots {
|
||||
|
||||
Reference in New Issue
Block a user