package controllers import ( "encoding/json" "io" "regexp" "strings" "time" "server/models" "server/pkg/jwtutil" "github.com/beego/beego/v2/client/orm" beego "github.com/beego/beego/v2/server/web" ) // BackendThemeController 官网模板选用(租户端只读选用,模板管理在 platform 端) type BackendThemeController struct { beego.Controller } func (c *BackendThemeController) cmsClaims() (*jwtutil.Claims, error) { return cmsBackendClaims(&c.Controller) } func (c *BackendThemeController) cmsJSONErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } var reThemeCode = regexp.MustCompile(`^[a-zA-Z0-9_\-]+$`) // List GET /backend/theme 仅返回启用中的模板(含内置 default) func (c *BackendThemeController) List() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } var rows []models.CmsFrontendTemplate _, err = models.Orm.QueryTable(new(models.CmsFrontendTemplate)). Filter("status", 1). Filter("delete_time__isnull", true). OrderBy("sort", "id"). All(&rows) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取模板列表失败") return } list := make([]map[string]interface{}, 0, len(rows)+1) hasDefault := false for _, r := range rows { if r.Code == "default" { hasDefault = true } list = append(list, map[string]interface{}{ "key": r.Code, "name": r.Name, "description": r.Description, "preview": r.Preview, "version": "1.0", "author": "", }) } // 内置 default 模板始终可选,即使未登记入库 if !hasDefault { list = append([]map[string]interface{}{{ "key": "default", "name": "默认模板", "description": "系统内置默认模板", "preview": "/themes/default/static/preview.png", "version": "1.0", "author": "", }}, list...) } current := cmsTenantTemplateCode(tid) if current == "" { current = "default" } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": map[string]interface{}{"list": list, "currentTheme": current}, } _ = c.ServeJSON() } // cmsTenantTemplateCode 读取租户当前选用的模板编码 func cmsTenantTemplateCode(tid uint64) string { var row models.TenantSiteSetting err := models.Orm.QueryTable(new(models.TenantSiteSetting)). Filter("tid", tid). Filter("delete_time__isnull", true). One(&row) if err != nil { return "" } return row.TemplateCode } // Switch POST /backend/theme/switch 切换租户使用的模板(写入 template_code) func (c *BackendThemeController) Switch() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var body struct { Tid uint64 `json:"tid"` ThemeKey string `json:"theme_key"` } if err := json.Unmarshal(raw, &body); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } if tid == 0 { tid = body.Tid } if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } code := strings.TrimSpace(body.ThemeKey) if code == "" || !reThemeCode.MatchString(code) { c.cmsJSONErr(400, 400, "模板编码不合法") return } // 非内置 default 模板必须已登记且启用中 if code != "default" { cnt, _ := models.Orm.QueryTable(new(models.CmsFrontendTemplate)). Filter("code", code). Filter("status", 1). Filter("delete_time__isnull", true). Count() if cnt == 0 { c.cmsJSONErr(400, 400, "模板不存在或已停用") return } } if err := cmsUpsertTenantTemplate(tid, code); err != nil { c.cmsJSONErr(500, 500, "切换失败") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "切换成功", "data": map[string]interface{}{"currentTheme": code}} _ = c.ServeJSON() } // cmsUpsertTenantTemplate 写入租户模板编码(站点设置不存在则创建) func cmsUpsertTenantTemplate(tid uint64, code string) error { now := time.Now() cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). Filter("tid", tid). Filter("delete_time__isnull", true). Count() if err != nil { return err } if cnt == 0 { _, err = models.Orm.Insert(&models.TenantSiteSetting{ Tid: tid, TemplateCode: code, CreateTime: now, UpdateTime: &now, }) return err } _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"template_code": code, "update_time": now}) return err } // GetData GET /backend/theme/data 返回站点设置(供模板配置展示) func (c *BackendThemeController) GetData() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } var row models.TenantSiteSetting err = models.Orm.QueryTable(new(models.TenantSiteSetting)). Filter("tid", tid). Filter("delete_time__isnull", true). One(&row) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取失败") return } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": map[string]interface{}{ "tid": tid, "sitename": row.Sitename, "logo": row.Logo, "logow": row.Logow, "ico": row.Ico, "description": row.Description, "copyright": row.Copyright, "companyname": row.Companyname, "icp": row.Icp, "companyintroduction": row.Companyintroduction, "template_code": row.TemplateCode, }, } _ = c.ServeJSON() } // SaveData POST /backend/theme/data 保存站点设置(模板配置用,白名单字段) func (c *BackendThemeController) SaveData() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var body map[string]interface{} if err := json.Unmarshal(raw, &body); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } fields := map[string]interface{}{} allowed := []string{"sitename", "logo", "logow", "ico", "description", "copyright", "companyname", "icp", "companyintroduction"} for _, k := range allowed { if v, ok := body[k]; ok { fields[k] = strings.TrimSpace(toString(v)) } } if len(fields) == 0 { c.cmsJSONErr(400, 400, "没有可保存的字段") return } fields["update_time"] = time.Now() cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). Filter("tid", tid). Filter("delete_time__isnull", true). Count() if err != nil { c.cmsJSONErr(500, 500, "保存失败") return } if cnt == 0 { now := time.Now() row := &models.TenantSiteSetting{Tid: tid, CreateTime: now, UpdateTime: &now} if v, ok := fields["sitename"]; ok { row.Sitename = v.(string) } if v, ok := fields["logo"]; ok { row.Logo = v.(string) } if v, ok := fields["logow"]; ok { row.Logow = v.(string) } if v, ok := fields["ico"]; ok { row.Ico = v.(string) } if v, ok := fields["description"]; ok { row.Description = v.(string) } if v, ok := fields["copyright"]; ok { row.Copyright = v.(string) } if v, ok := fields["companyname"]; ok { row.Companyname = v.(string) } if v, ok := fields["icp"]; ok { row.Icp = v.(string) } if v, ok := fields["companyintroduction"]; ok { row.Companyintroduction = v.(string) } if _, err := models.Orm.Insert(row); err != nil { c.cmsJSONErr(500, 500, "保存失败") return } } else { if _, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). Filter("tid", tid). Filter("delete_time__isnull", true). Update(fields); err != nil { c.cmsJSONErr(500, 500, "保存失败") return } } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} _ = c.ServeJSON() } // toString 宽松地把 JSON 值转成字符串 func toString(v interface{}) string { if v == nil { return "" } if s, ok := v.(string); ok { return s } return "" }