diff --git a/cmd/server/main.go b/cmd/server/main.go
index c571ebd..9d4c35a 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -3,6 +3,7 @@ package main
import (
"log"
+ "metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/config"
"metazone.cc/metalab/internal/model"
"metazone.cc/metalab/internal/router"
@@ -48,6 +49,9 @@ func main() {
r := gin.Default()
+ // 计算静态资源哈希(用于缓存破坏,文件不变哈希不变)
+ common.ComputeAssetHashes("templates/MetaLab-2026")
+
// 模板加载(前端主题 + 管理后台)
tmpl, err := theme.LoadTemplates(
theme.TemplateRoot{Dir: "templates/MetaLab-2026/html", Prefix: ""},
diff --git a/internal/common/helper.go b/internal/common/helper.go
index 96377c0..fd85e94 100644
--- a/internal/common/helper.go
+++ b/internal/common/helper.go
@@ -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
}
diff --git a/internal/theme/loader.go b/internal/theme/loader.go
index 564c89b..f1e8fc5 100644
--- a/internal/theme/loader.go
+++ b/internal/theme/loader.go
@@ -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 {
diff --git a/templates/MetaLab-2026/html/auth/login.html b/templates/MetaLab-2026/html/auth/login.html
index ce5aaa2..f3249fa 100644
--- a/templates/MetaLab-2026/html/auth/login.html
+++ b/templates/MetaLab-2026/html/auth/login.html
@@ -42,7 +42,7 @@
-
-
+
+