## 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+ 文件)
127 lines
3.7 KiB
Go
127 lines
3.7 KiB
Go
// 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) }
|