package themes import ( "embed" "errors" "fmt" "html/template" "io/fs" "os" "path/filepath" "strings" "sync" "github.com/gin-gonic/gin" ) // 主题元数据结构 type ThemeMeta struct { Name string `yaml:"name"` Author string `yaml:"author"` Version string `yaml:"version"` Description string `yaml:"description"` } // 主题管理器 type ThemeManager struct { mu sync.RWMutex currentTheme string templates map[string]*template.Template baseTemplates embed.FS // 可选:嵌入基础模板 themesDir string } // 创建新主题管理器 func NewManager(themesDir string) *ThemeManager { return &ThemeManager{ templates: make(map[string]*template.Template), themesDir: themesDir, } } // 核心方法:加载主题 func (m *ThemeManager) LoadTheme(themeName string) error { m.mu.Lock() defer m.mu.Unlock() // 1. 验证主题目录结构 themePath := filepath.Join(m.themesDir, themeName) if !isValidTheme(themePath) { return fmt.Errorf("invalid theme structure: %s", themeName) } // 2. 加载模板文件 tpls, err := m.parseTemplates(themePath) if err != nil { return fmt.Errorf("template parsing failed: %w", err) } // 3. 更新当前主题 m.currentTheme = themeName m.templates = tpls return nil } // 获取当前主题名称 func (m *ThemeManager) CurrentTheme() string { m.mu.RLock() defer m.mu.RUnlock() return m.currentTheme } // 获取编译后的模板 func (m *ThemeManager) GetTemplate(name string) *template.Template { m.mu.RLock() defer m.mu.RUnlock() return m.templates[name] } // 注册静态文件路由(Gin框架) func (m *ThemeManager) RegisterStaticRoutes(router *gin.Engine) { staticPath := filepath.Join(m.themesDir, m.currentTheme, "static") router.StaticFS("/theme/static", gin.Dir(staticPath, false)) } // 校验主题完整性 func isValidTheme(themePath string) bool { requiredFiles := []string{ "theme.yaml", "templates/home.html", "templates/post.html", } for _, f := range requiredFiles { if _, err := os.Stat(filepath.Join(themePath, f)); os.IsNotExist(err) { return false } } return true } // 解析模板文件(支持布局继承) func (m *ThemeManager) parseTemplates(themePath string) (map[string]*template.Template, error) { templates := make(map[string]*template.Template) // 加载公共基础模板(可选) baseTpl := template.New("base").Funcs(template.FuncMap{ "safeHTML": func(s string) template.HTML { return template.HTML(s) }, }) // 从主题目录加载模板 tplDir := filepath.Join(themePath, "templates") err := filepath.WalkDir(tplDir, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() || !strings.HasSuffix(path, ".html") { return nil } // 读取模板内容 content, err := os.ReadFile(path) if err != nil { return err } // 生成模板名称(相对路径) name := strings.TrimPrefix(path, tplDir+string(filepath.Separator)) name = strings.TrimSuffix(name, filepath.Ext(name)) // 克隆基础模板并解析 tpl := template.Must(baseTpl.Clone()) tpl, err = tpl.Parse(string(content)) if err != nil { return fmt.Errorf("parse error in %s: %w", name, err) } templates[name] = tpl return nil }) if err != nil { return nil, err } if len(templates) == 0 { return nil, errors.New("no valid templates found") } return templates, nil } // 获取可用主题列表 func (m *ThemeManager) ListThemes() ([]string, error) { dirs, err := os.ReadDir(m.themesDir) if err != nil { return nil, err } var themes []string for _, d := range dirs { if d.IsDir() && isValidTheme(filepath.Join(m.themesDir, d.Name())) { themes = append(themes, d.Name()) } } return themes, nil }