229 lines
6.7 KiB
Go
229 lines
6.7 KiB
Go
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 }]
|
||
// data.values = { "sitename": "云泽科技", ... }(key 即标签名,方便直接使用)
|
||
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
|
||
// 入参 { "sitename": "云泽科技", "subtitle": "...", ... },key 为标签名,按标签逐项 upsert。
|
||
// 通用:表里已存在或新增的任意合法标签都可写入,不需要登记。
|
||
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
|
||
}
|