package utils import ( "fmt" "html/template" "net/http" "path/filepath" "sync" "github.com/gin-gonic/gin/render" ) // templateFuncs 定义模板函数 func templateFuncs() template.FuncMap { return template.FuncMap{ "add": func(a, b int) int { return a + b }, "sub": func(a, b int) int { return a - b }, "html": func(s string) template.HTML { return template.HTML(s) }, "markdown": RenderMarkdown, } } // TemplateRenderer 自定义模板渲染器,支持多模板和主题切换 type TemplateRenderer struct { templates map[string]*template.Template mu sync.RWMutex // 读写锁,支持运行时重新加载 } // Lookup 查找指定名称的模板 func (r *TemplateRenderer) Lookup(name string) *template.Template { r.mu.RLock() defer r.mu.RUnlock() return r.templates[name] } // Instance 创建渲染实例 func (r *TemplateRenderer) Instance(name string, data any) render.Render { return &htmlRender{ tmpl: r.Lookup(name), data: data, name: name, } } // htmlRender HTML渲染器 type htmlRender struct { tmpl *template.Template data any name string } // Render 执行模板渲染 func (r *htmlRender) Render(w http.ResponseWriter) error { r.WriteContentType(w) return r.tmpl.ExecuteTemplate(w, "base.html", r.data) } // WriteContentType 设置内容类型 func (r *htmlRender) WriteContentType(w http.ResponseWriter) { w.Header().Set("Content-Type", "text/html; charset=utf-8") } // LoadTemplates 加载指定主题的模板 func LoadTemplates(theme string) (*TemplateRenderer, error) { r := &TemplateRenderer{ templates: make(map[string]*template.Template), } if err := r.reload(theme); err != nil { return nil, err } return r, nil } // Reload 重新加载主题模板(线程安全) func (r *TemplateRenderer) Reload(theme string) { r.mu.Lock() defer r.mu.Unlock() r.reload(theme) } // reload 内部重载方法(需要持有锁) func (r *TemplateRenderer) reload(theme string) error { templateDir := "templates/" + theme // 需要独立渲染的页面模板 pages := []string{"index", "post", "page"} // 收集主题目录下的公共局部模板 partials := []string{} matches, err := filepath.Glob(templateDir + "/*.html") if err != nil { return fmt.Errorf("扫描主题目录失败: %w", err) } for _, f := range matches { base := filepath.Base(f) if base == "base.html" { continue } name := base[:len(base)-len(".html")] isPage := false for _, p := range pages { if p == name { isPage = true break } } if !isPage { partials = append(partials, f) } } // 为每个页面创建独立的模板组合 newTemplates := make(map[string]*template.Template) for _, pageName := range pages { // 加载 base.html、对应页面模板以及所有公共局部模板 allFiles := make([]string, 0, 2+len(partials)) allFiles = append(allFiles, templateDir+"/base.html", templateDir+"/"+pageName+".html", ) allFiles = append(allFiles, partials...) // 解析模板文件,遇到错误立即返回 tmpl, err := template.New("").Funcs(templateFuncs()).ParseFiles(allFiles...) if err != nil { return fmt.Errorf("解析页面模板 %s 失败: %w", pageName, err) } newTemplates[pageName] = tmpl } // 所有页面模板解析成功后才更新映射 r.templates = newTemplates return nil } // 全局模板渲染器实例 var globalRenderer *TemplateRenderer // SetGlobalRenderer 设置全局渲染器 func SetGlobalRenderer(r *TemplateRenderer) { globalRenderer = r } // GetGlobalRenderer 获取全局渲染器 func GetGlobalRenderer() *TemplateRenderer { return globalRenderer }