150 lines
3.3 KiB
Go
150 lines
3.3 KiB
Go
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
|
|
}
|