package pongo import ( "fmt" "net/http" "sync" "github.com/flosch/pongo2/v6" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/render" "github.com/sirupsen/logrus" ) type Renderer struct { mu sync.RWMutex globalContext pongo2.Context tplSet *pongo2.TemplateSet logger *logrus.Logger } func New(directory string, isDebug bool, logger *logrus.Logger) *Renderer { loader := pongo2.MustNewLocalFileSystemLoader(directory) tplSet := pongo2.NewSet("gin-pongo-templates", loader) tplSet.Debug = isDebug return &Renderer{ globalContext: make(pongo2.Context), tplSet: tplSet, logger: logger, } } // SetGlobalContext 线程安全地设置全局上下文 func (p *Renderer) SetGlobalContext(key string, value interface{}) { p.mu.Lock() defer p.mu.Unlock() p.globalContext[key] = value } // Warmup 预加载模板 func (p *Renderer) Warmup(templateNames ...string) error { for _, name := range templateNames { if _, err := p.tplSet.FromCache(name); err != nil { return fmt.Errorf("failed to warmup template '%s': %w", name, err) } } p.logger.WithField("count", len(templateNames)).Info("Templates warmed up") return nil } func (p *Renderer) Instance(name string, data interface{}) render.Render { // 安全读取全局上下文 p.mu.RLock() glob := make(pongo2.Context, len(p.globalContext)) for k, v := range p.globalContext { glob[k] = v } p.mu.RUnlock() // 解析请求数据 var context pongo2.Context if data != nil { switch v := data.(type) { case gin.H: context = pongo2.Context(v) case pongo2.Context: context = v case map[string]interface{}: context = v default: context = make(pongo2.Context) } } else { context = make(pongo2.Context) } // 合并上下文(请求数据优先) for k, v := range glob { if _, exists := context[k]; !exists { context[k] = v } } // 加载模板 tpl, err := p.tplSet.FromCache(name) if err != nil { p.logger.WithError(err).WithField("template", name).Error("Failed to load template") return &ErrorHTML{ StatusCode: http.StatusInternalServerError, Error: fmt.Errorf("template load error: %s", name), } } return &HTML{ Template: tpl, Name: name, Data: context, } } type HTML struct { Template *pongo2.Template Name string Data pongo2.Context } func (h *HTML) Render(w http.ResponseWriter) error { h.WriteContentType(w) bytes, err := h.Template.ExecuteBytes(h.Data) if err != nil { return err } _, err = w.Write(bytes) return err } func (h *HTML) WriteContentType(w http.ResponseWriter) { if w.Header().Get("Content-Type") == "" { w.Header().Set("Content-Type", "text/html; charset=utf-8") } } // ErrorHTML 错误渲染器 type ErrorHTML struct { StatusCode int Error error } func (e *ErrorHTML) Render(w http.ResponseWriter) error { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(e.StatusCode) _, err := w.Write([]byte(e.Error.Error())) return err } func (e *ErrorHTML) WriteContentType(w http.ResponseWriter) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") } // C 获取或创建 pongo2 上下文 func C(ctx *gin.Context) pongo2.Context { if p, exists := ctx.Get("pongo2"); exists { if pCtx, ok := p.(pongo2.Context); ok { return pCtx } } pCtx := make(pongo2.Context) ctx.Set("pongo2", pCtx) return pCtx }