增加租户官网模块

This commit is contained in:
2026-08-19 23:34:28 +08:00
parent ec5cf96d0b
commit 14f2e8b5c1
28 changed files with 3812 additions and 7 deletions
+339
View File
@@ -0,0 +1,339 @@
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 ""
}