Files
mce/internal/theme/loader.go
Victor_Jay 97adf54d6d fix: golangci-lint 零告警通过 (57→0) + gofumpt/goimports 全量格式化
## CI 修复 (P0/P1)

P0 — 编译阻塞:
  - interfaces.go: postUseCase ISP 接口移除不用的 Create/Update/Delete

P1 — 必须修复:
  - ST1000: 为 13 个包添加包注释 (common/config/model/service/...)
  - ST1005: redis_store.go 全部错误消息改为小写开头
  - errcheck (19处): defer Close()→闭包忽略, notifier.Create→_=, r.Run→检查error
  - errorlint (9处): switch-on-error→errors.Is 链, ==→errors.Is
  - ST1020/ST1022: 导出符号注释以符号名开头

P2 — 安全评审:
  - gosec (12处): G203/G301/G304/G306 添加 nolint 注释并附理由

P3 — 清理:
  - unused: 移除 hasUnicode/energyStore/current/energyAdminUseCase
  - gofumpt + goimports 全量格式化 (35+ 文件)
2026-06-22 02:27:35 +08:00

127 lines
3.7 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 theme 负责多主题模板的加载、渲染和静态内容管理。
package theme
import (
"fmt"
"html/template"
"os"
"path/filepath"
"strings"
"metazone.cc/mce/internal/common"
"metazone.cc/mce/internal/model"
)
// TemplateRoot 模板根目录配置
type TemplateRoot struct {
Dir string // 目录路径
Prefix string // 模板名前缀(如 "admin/" 表示管理后台)
}
// LoadTemplates 加载多个根目录的 .html 模板到同一模板集
// 模板名 = prefix + 相对于 root.Dir 的相对路径
func LoadTemplates(roots ...TemplateRoot) (*template.Template, error) {
funcMap := template.FuncMap{
"add": func(a, b int) int { return a + b },
"subtract": func(a, b int) int { return a - b },
"formatTTL": formatTTL,
"formatCount": formatCount,
"str": str,
"assetV": common.AssetV,
"safeHTML": func(s string) template.HTML { return template.HTML(s) }, //nolint:gosec // 模板函数,由调用方保证内容安全
"declarationLabel": func(declaration string) string {
return model.DeclarationLabels[declaration]
},
"substr": func(s string, start, length int) string {
runes := []rune(s)
if start >= len(runes) {
return ""
}
end := start + length
if end > len(runes) {
end = len(runes)
}
return string(runes[start:end])
},
// derefUint 安全解引用 *uint 指针。
// Go 1.26 的模板引擎在将 *uint 传给 printf/print 时不会自动解引用,
// 导致打印指针地址而非值。通过此函数显式解引用后传给 printf。
"derefUint": func(p *uint) uint {
if p == nil {
return 0
}
return *p
},
}
t := template.New("").Funcs(funcMap)
for _, root := range roots {
dir := filepath.Clean(root.Dir) + string(os.PathSeparator)
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || filepath.Ext(path) != ".html" {
return nil
}
content, err := os.ReadFile(path) //nolint:gosec // path 来自 filepath.Walk 遍历,非用户输入
if err != nil {
return err
}
// 模板名prefix + 相对路径
name := root.Prefix + strings.TrimPrefix(path, dir)
_, err = t.New(name).Parse(string(content))
return err
})
if err != nil {
return nil, err
}
}
return t, nil
}
// LoadContent 读取主题内容文件(准则等),返回不转义的 template.HTML。
// 后期改为查数据库时,只需修改此函数实现,调用方不变。
func LoadContent(path string) (template.HTML, error) {
content, err := os.ReadFile(path) //nolint:gosec // path 为内部主题文件路径
if err != nil {
return "", err
}
return template.HTML(content), nil //nolint:gosec // 主题内容由管理员维护,属信任输入
}
// formatTTL 将剩余分钟数格式化为人类可读的时间(如 "23 小时"、"3 天"、"15 分钟"
func formatTTL(minutes int) string {
if minutes <= 0 {
return "即将过期"
}
if minutes < 60 {
return fmt.Sprintf("%d 分钟", minutes)
}
hours := minutes / 60
if hours < 24 {
return fmt.Sprintf("%d 小时", hours)
}
days := hours / 24
remainHours := hours % 24
if remainHours == 0 {
return fmt.Sprintf("%d 天", days)
}
return fmt.Sprintf("%d 天 %d 小时", days, remainHours)
}
// formatCount 将 int64 数字格式化为简洁中文计数(如 "2.5万"、"70.3万"、"0"
func formatCount(n int64) string {
if n < 10000 {
return fmt.Sprintf("%d", n)
}
if n < 100000000 {
v := float64(n) / 10000.0
return fmt.Sprintf("%.1f万", v)
}
v := float64(n) / 100000000.0
return fmt.Sprintf("%.1f亿", v)
}
// str 将 uint 转为字符串,用于模板中与字符串比较
func str(n uint) string { return fmt.Sprintf("%d", n) }