package controllers import ( "encoding/json" "io" "strings" "server/models" "server/pkg/identity" "server/services" beego "github.com/beego/beego/v2/server/web" ) // PlatformWebsiteSettingController 平台官网站点设置(平台端专用) // 对应前端 views/website/setting/index.vue: // - GET /platform/website/setting/info // - POST /platform/website/setting/save // // 数据落在 yz_platform_site_setting(通用键值结构:title/value/tag/remark)。 // 该表完全数据驱动:表里插一行就是一个新的设置项,模板即可用 {yz:tag} 输出, // platform 端界面也会自动出现该项,无需改动后端代码。 type PlatformWebsiteSettingController struct { beego.Controller } func (c *PlatformWebsiteSettingController) jsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]any{"code": bizCode, "msg": msg} _ = c.ServeJSON() } // Info GET /platform/website/setting/info // 直接把表里所有设置项返回,前端按行渲染(新增设置项无需改代码): // data.list = [{ id, title, value, tag, remark }] func (c *PlatformWebsiteSettingController) Info() { if _, err := requirePlatform(&c.Controller); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } rows := models.GetPlatformSiteSettingRows() list := make([]map[string]any, 0, len(rows)) for _, r := range rows { list = append(list, map[string]any{ "id": r.ID, "title": r.Title, "value": r.Value, "tag": r.Tag, "remark": r.Remark, }) } c.Data["json"] = map[string]any{ "code": 200, "msg": "success", "data": map[string]any{ "list": list, "values": models.GetPlatformSiteTagMap(), }, } _ = c.ServeJSON() } // Save POST /platform/website/setting/save // 通用:表里已存在或新增的任意合法标签都可写入,不需要登记。 func (c *PlatformWebsiteSettingController) Save() { if _, err := requirePlatform(&c.Controller); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var payload map[string]string if uerr := json.Unmarshal(raw, &payload); uerr != nil { c.jsonErr(400, 400, "参数错误") return } if len(payload) == 0 { c.jsonErr(400, 400, "参数错误") return } if strings.TrimSpace(payload["sitename"]) == "" { c.jsonErr(400, 400, "站点名称不能为空") return } // 统一去掉首尾空格,避免模板里输出多余空白 values := make(map[string]string, len(payload)) for k, v := range payload { values[k] = strings.TrimSpace(v) } if err = models.SavePlatformSiteSettings(values); err != nil { c.jsonErr(500, 500, "保存失败: "+err.Error()) return } c.Data["json"] = map[string]any{"code": 200, "msg": "保存成功"} _ = c.ServeJSON() } // loginBrandTags 登录页品牌/登录方式配置项,存放于 yz_platform_site_setting, // 统一认证中心 /auth/login 渲染时读取。 // login_pwd_enabled / login_sms_enabled / login_qr_enabled: // 值为 "0" 表示关闭该登录方式,未配置(空)默认开启;三者全关时登录页禁止登录。 var loginBrandTags = []string{ "login_title", "login_subtitle", "login_logo", "login_bg", "login_pwd_enabled", "login_sms_enabled", "login_qr_enabled", } // LoginBrandInfo GET /platform/loginBrand // 返回登录页品牌配置(主标题 / 副标题 / Logo / 背景图)。 func (c *PlatformWebsiteSettingController) LoginBrandInfo() { if _, err := requirePlatform(&c.Controller); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } all := models.GetPlatformSiteTagMap() data := make(map[string]string, len(loginBrandTags)) for _, tag := range loginBrandTags { data[tag] = all[tag] } c.Data["json"] = map[string]any{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } // SaveLoginBrand POST /platform/saveLoginBrand // 入参 { login_title, login_subtitle, login_logo, login_bg }。 // 只接受这四个标签,避免与「站点设置」共用的 yz_platform_site_setting 被写入无关项。 func (c *PlatformWebsiteSettingController) SaveLoginBrand() { if _, err := requirePlatform(&c.Controller); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var payload map[string]string if uerr := json.Unmarshal(raw, &payload); uerr != nil { c.jsonErr(400, 400, "参数错误") return } values := make(map[string]string, len(loginBrandTags)) for _, tag := range loginBrandTags { if v, ok := payload[tag]; ok { values[tag] = strings.TrimSpace(v) } } if len(values) == 0 { c.jsonErr(400, 400, "参数错误") return } if len([]rune(values["login_title"])) > 60 { c.jsonErr(400, 400, "主标题不能超过 60 个字符") return } if len([]rune(values["login_subtitle"])) > 120 { c.jsonErr(400, 400, "副标题不能超过 120 个字符") return } // 只校验本次提交了的图片字段,避免「登录方式」等只传开关的保存把品牌图清空 for _, tag := range []string{"login_logo", "login_bg"} { raw, submitted := payload[tag] if !submitted { continue } img, ok := normalizeLoginBrandImage(raw) if !ok { c.jsonErr(400, 400, "图片地址必须是 HTTPS 或站内相对路径") return } values[tag] = img } if err = models.SavePlatformSiteSettings(values); err != nil { c.jsonErr(500, 500, "保存失败: "+err.Error()) return } c.Data["json"] = map[string]any{"code": 200, "msg": "保存成功"} _ = c.ServeJSON() } // normalizeLoginBrandImage 校验登录页图片地址: // - 站内相对路径(/uploads/...)直接放行; // - HTTPS 完整地址放行; // - HTTP 历史七牛域名尝试改写为存储配置的 HTTPS 基址,改不动则拒绝, // 否则登录页会被浏览器按 Mixed Content 拦截。 func normalizeLoginBrandImage(raw string) (string, bool) { raw = strings.TrimSpace(raw) if raw == "" { return "", true } if strings.HasPrefix(raw, "/") { return raw, true } lower := strings.ToLower(raw) if strings.HasPrefix(lower, "https://") { return raw, true } if strings.HasPrefix(lower, "http://") { rewritten := services.ResolvePublicURL("", "", raw) if strings.HasPrefix(strings.ToLower(rewritten), "https://") { return rewritten, true } return "", false } return "", false }