package template import ( "fmt" "os" "path/filepath" ) type TemplateValidator struct { config TemplateConfig } type ValidationResult struct { Valid bool `json:"valid"` Errors []string `json:"errors,omitempty"` Warnings []string `json:"warnings,omitempty"` Template string `json:"template"` Table string `json:"table"` Type string `json:"type"` } func NewTemplateValidator(config TemplateConfig) *TemplateValidator { return &TemplateValidator{ config: config, } } func (tv *TemplateValidator) ValidateTemplate(templateType, tableName string) ValidationResult { result := ValidationResult{ Valid: true, Template: templateType, Table: tableName, Type: templateType, } // 验证模板文件存在性 paths := tv.getTemplatePaths(templateType, tableName) found := false for _, path := range paths { if _, err := os.Stat(path); err == nil { found = true break } } if !found { result.Valid = false result.Errors = append(result.Errors, fmt.Sprintf("未找到模板文件: %s", templateType)) return result } // 验证模板语法 loader := NewTemplateLoader(tv.config) _, err := loader.LoadTemplate(templateType, tableName) if err != nil { result.Valid = false result.Errors = append(result.Errors, fmt.Sprintf("模板语法错误: %v", err)) } // 验证模板结构 tv.validateTemplateStructure(&result, templateType, tableName) return result } func (tv *TemplateValidator) ValidateAllTemplates() []ValidationResult { var results []ValidationResult // 验证所有内置模板 builtinTemplates := []string{"list", "card", "timeline"} for _, templateType := range builtinTemplates { result := tv.ValidateTemplate(templateType, "_default") results = append(results, result) } // 验证自定义目录下的模板 if entries, err := os.ReadDir(tv.config.CustomPath); err == nil { for _, entry := range entries { if entry.IsDir() && entry.Name() != "_default" { tableName := entry.Name() for _, templateType := range builtinTemplates { result := tv.ValidateTemplate(templateType, tableName) results = append(results, result) } } } } return results } func (tv *TemplateValidator) getTemplatePaths(templateType, tableName string) []string { return []string{ // 自定义模板 filepath.Join(tv.config.CustomPath, tableName, fmt.Sprintf("%s.html", templateType)), // 默认自定义模板 filepath.Join(tv.config.CustomPath, "_default", fmt.Sprintf("%s.html", templateType)), // 内置模板 filepath.Join(tv.config.BuiltinPath, fmt.Sprintf("%s.html", templateType)), } } func (tv *TemplateValidator) validateTemplateStructure(result *ValidationResult, templateType, tableName string) { // 验证必需的文件结构 requiredFiles := []string{ "layout.html", } for _, file := range requiredFiles { path := filepath.Join(tv.config.BuiltinPath, file) if _, err := os.Stat(path); err != nil { result.Valid = false result.Errors = append(result.Errors, fmt.Sprintf("缺少必需文件: %s", file)) } } // 验证字段模板 fieldPath := filepath.Join(tv.config.BuiltinPath, "field") if _, err := os.Stat(fieldPath); err == nil { requiredFieldTemplates := []string{"raw.html", "time.html", "tag.html", "markdown.html", "category.html"} for _, fieldTpl := range requiredFieldTemplates { fieldPath := filepath.Join(fieldPath, fieldTpl) if _, err := os.Stat(fieldPath); err != nil { result.Warnings = append(result.Warnings, fmt.Sprintf("缺少字段模板: %s", fieldTpl)) } } } // 验证自定义模板目录结构 if tableName != "_default" { customTablePath := filepath.Join(tv.config.CustomPath, tableName) if _, err := os.Stat(customTablePath); err == nil { // 检查是否有list或card模板 hasList := false hasCard := false if _, err := os.Stat(filepath.Join(customTablePath, "list.html")); err == nil { hasList = true } if _, err := os.Stat(filepath.Join(customTablePath, "card.html")); err == nil { hasCard = true } if !hasList && !hasCard { result.Warnings = append(result.Warnings, "自定义模板目录存在但缺少list.html或card.html") } } } } // 模板健康检查 func (tv *TemplateValidator) HealthCheck() map[string]interface{} { results := tv.ValidateAllTemplates() total := len(results) valid := 0 errors := 0 warnings := 0 for _, result := range results { if result.Valid { valid++ } if len(result.Errors) > 0 { errors += len(result.Errors) } if len(result.Warnings) > 0 { warnings += len(result.Warnings) } } return map[string]interface{}{ "total": total, "valid": valid, "errors": errors, "warnings": warnings, "details": results, } }