fix: ComputeAssetHashes 并发安全 + theme 页重复加载 JS 导致双写 panic
Some checks failed
CI / Lint + Build (push) Has been cancelled

- helper.go: 用 sync.RWMutex 保护 assetHashes,构建本地 map 后原子替换
- theme.html: 删除硬编码 script 标签(footer ExtraJS 已加载),
  修复 site-settings-theme.js 执行两次 → applyTheme 触发两次 → 并发写 map panic
This commit is contained in:
2026-06-30 19:10:11 +08:00
parent d4a833f149
commit 426bedda65
2 changed files with 24 additions and 12 deletions

View File

@ -11,6 +11,7 @@ import (
"path/filepath"
"regexp"
"strconv"
"sync"
"time"
"metazone.cc/mce/internal/model"
@ -19,22 +20,31 @@ import (
)
// assetHashes 静态资源文件哈希表URL 路径 → 短哈希,文件不变哈希不变)
var assetHashes map[string]string
var (
assetHashes map[string]string
assetMu sync.RWMutex
)
// ComputeAssetHashes 遍历 static 目录,计算每个文件的 CRC32 作为版本号
// 构建本地 map 后原子替换,避免并发读写冲突
func ComputeAssetHashes(templateDir string) {
assetHashes = make(map[string]string)
mu := make(map[string]string)
// 前端主题templates/MetaLab-2026/static → URL /static/
walkStaticDir(filepath.Join(templateDir, "static"), "/static")
walkStaticDirInto(mu, filepath.Join(templateDir, "static"), "/static")
// 共享资源templates/shared/static → URL /shared/static/
walkStaticDir(filepath.Join(filepath.Dir(templateDir), "shared", "static"), "/shared/static")
walkStaticDirInto(mu, filepath.Join(filepath.Dir(templateDir), "shared", "static"), "/shared/static")
// 管理后台templates/admin/static → URL /admin/static/
walkStaticDir(filepath.Join(filepath.Dir(templateDir), "admin", "static"), "/admin/static")
log.Printf("[AssetHashes] computed %d file hashes", len(assetHashes))
walkStaticDirInto(mu, filepath.Join(filepath.Dir(templateDir), "admin", "static"), "/admin/static")
assetMu.Lock()
assetHashes = mu
assetMu.Unlock()
log.Printf("[AssetHashes] computed %d file hashes", len(mu))
}
// walkStaticDir 遍历目录,计算每个 .js/.css 的 CRC32存入 assetHashes
func walkStaticDir(dir, urlPrefix string) {
// walkStaticDirInto 遍历目录,计算每个 .js/.css 的 CRC32存入目标 map
func walkStaticDirInto(dst map[string]string, dir, urlPrefix string) {
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
@ -52,14 +62,17 @@ func walkStaticDir(dir, urlPrefix string) {
// 生成 URL 路径urlPrefix + 相对于 dir 的子路径
rel, _ := filepath.Rel(dir, path)
urlPath := urlPrefix + "/" + filepath.ToSlash(rel)
assetHashes[urlPath] = shortHash
dst[urlPath] = shortHash
return nil
})
}
// AssetV 返回指定静态资源的哈希版本号(模板函数)
// AssetV 返回指定静态资源的哈希版本号(模板函数,并发安全
func AssetV(path string) string {
if h, ok := assetHashes[path]; ok {
assetMu.RLock()
h, ok := assetHashes[path]
assetMu.RUnlock()
if ok {
return h
}
return "0"

View File

@ -41,4 +41,3 @@
</div>
</div>
{{template "admin/layout/footer.html" .}}
<script src="/admin/static/js/site-settings-theme.js?v={{assetV "/admin/static/js/site-settings-theme.js"}}" nonce="{{.CSPNonce}}"></script>