Files
go_typecho/utils/templateRenderer.go
T

156 lines
3.6 KiB
Go

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
}