Files
yunzerwebsiteallinone/go/models/platform_site_setting.go
T
2026-09-15 00:08:36 +08:00

290 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package models
import (
"regexp"
"strings"
"time"
)
// PlatformSiteSetting 平台官网站点设置表 yz_platform_site_setting(通用键值结构)
//
// 一行一个设置项:title 名称 / value 值 / tag 标签名 / remark 说明。
// 模板里用 {yz:tag} 即可输出对应 value,例如 tag=sitename → {yz:sitename}。
// 新增设置项只需在 PlatformSiteSettingDefs 里登记一条(或直接在表里插一行),
// 无需再加表字段,因此该表对所有站点级设置通用。
type PlatformSiteSetting struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Title string `orm:"column(title);size(128);default('')" json:"title"` // 设置项名称
Value string `orm:"column(value);type(text);null" json:"value"` // 设置项的值
Tag string `orm:"column(tag);size(64);default('')" json:"tag"` // 标签名,模板中写作 {yz:tag}
Remark string `orm:"column(remark);size(255);default('')" json:"remark"` // 备注/填写说明
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add;null" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *PlatformSiteSetting) TableName() string {
return "yz_platform_site_setting"
}
// PlatformSiteSettingDef 站点设置项定义(顺序即 platform 端界面展示顺序)
type PlatformSiteSettingDef struct {
Tag string // 标签名,模板中 {yz:tag}
Title string // 设置项名称
Remark string // 填写说明
}
// PlatformSiteSettingDefs 站点设置项定义清单(仅作初始化种子,不限制可新增的设置项)
//
// 该表现在完全由数据驱动:直接在表里插一行即成为一个新设置项,无需改动任何代码:
// - tag 即模板标签名,表里写了什么标签,模板就能用 {yz:tag}
// - remark 支持可选标记,用 | 分隔,未识别的片段当作说明文字展示:
// group:分组名 决定 platform 端界面落在哪个 tab(缺省归入「基本设置」)
// type:控件类型 text(默认,单行输入) / textarea(多行) / image(图片上传)
// 例:深色 Logo,用于浅色背景 | group:基本设置 | type:image
var PlatformSiteSettingDefs = []PlatformSiteSettingDef{
// 基本设置
{Tag: "sitename", Title: "站点名称", Remark: "常用于 <title> 和页头 | group:基本设置"},
{Tag: "subtitle", Title: "副标题", Remark: "站点名称下方的补充说明,常用于页头/首页 | group:基本设置"},
{Tag: "logo", Title: "Logo(深色)", Remark: "深色 Logo,用于浅色背景(如页头) | group:基本设置 | type:image"},
{Tag: "logow", Title: "Logo(浅色)", Remark: "浅色 Logo,用于深色背景(如页脚) | group:基本设置 | type:image"},
// 公司信息
{Tag: "companyname", Title: "公司名称", Remark: "公司全称,常用于页脚与「关于我们」 | group:公司信息"},
{Tag: "phone", Title: "联系电话", Remark: "服务热线,常用于页头与页脚 | group:公司信息"},
{Tag: "email", Title: "电子邮箱", Remark: "商务合作邮箱,可配合 mailto: 使用 | group:公司信息"},
{Tag: "address", Title: "公司地址", Remark: "公司所在地址,展示于页脚/联系页面 | group:公司信息"},
{Tag: "worktime", Title: "工作时间", Remark: "如:工作日 9:00 - 18:00 | group:公司信息"},
// 备案信息
{Tag: "icp", Title: "ICP 备案号", Remark: "如:苏ICP备2026000000号-1,常用于页脚 | group:备案信息"},
{Tag: "policeicp", Title: "公安备案", Remark: "如:苏公网安备32010000000000号,常用于页脚 | group:备案信息"},
{Tag: "copyright", Title: "版权信息", Remark: "如:© 2026 公司名称 版权所有 | group:备案信息"},
// 政策条款
{Tag: "privacy", Title: "隐私政策", Remark: "隐私政策链接,如 /page/privacy | group:政策条款"},
{Tag: "terms", Title: "使用条款", Remark: "使用条款链接,如 /page/terms | group:政策条款"},
}
// rePlatformSiteTag 标签名校验:与 tagengine 全局标签正则保持一致
var rePlatformSiteTag = regexp.MustCompile(`^[a-zA-Z_]\w*$`)
// platformSiteSettingDef 按 tag 取定义;未登记时返回空定义
func platformSiteSettingDef(tag string) PlatformSiteSettingDef {
for _, d := range PlatformSiteSettingDefs {
if d.Tag == tag {
return d
}
}
return PlatformSiteSettingDef{Tag: tag, Title: tag, Remark: ""}
}
// GetPlatformSiteTagMap 返回 标签名 -> 值。
// 已登记的定义项必定出现在返回结果中(未配置时为 ""),保证调用方不会缺 key。
func GetPlatformSiteTagMap() map[string]string {
out := make(map[string]string, len(PlatformSiteSettingDefs))
for _, d := range PlatformSiteSettingDefs {
out[d.Tag] = ""
}
var rows []PlatformSiteSetting
if _, err := Orm.QueryTable(new(PlatformSiteSetting)).
Filter("delete_time__isnull", true).
All(&rows); err != nil {
return out
}
for _, r := range rows {
if r.Tag != "" {
out[r.Tag] = r.Value
}
}
return out
}
// SavePlatformSiteSettings 按标签名批量写入设置项(tag -> value)。
// 已存在则更新 value,不存在则按定义插入;标签名非法的项直接忽略。
func SavePlatformSiteSettings(values map[string]string) error {
now := time.Now()
for tag, val := range values {
tag = strings.TrimSpace(tag)
if !rePlatformSiteTag.MatchString(tag) {
continue
}
cnt, err := Orm.QueryTable(new(PlatformSiteSetting)).
Filter("tag", tag).
Filter("delete_time__isnull", true).
Count()
if err != nil {
return err
}
if cnt == 0 {
def := platformSiteSettingDef(tag)
if _, err = Orm.Insert(&PlatformSiteSetting{
Title: def.Title,
Tag: tag,
Value: val,
Remark: def.Remark,
UpdateTime: &now,
}); err != nil {
return err
}
continue
}
if _, err = Orm.QueryTable(new(PlatformSiteSetting)).
Filter("tag", tag).
Filter("delete_time__isnull", true).
Update(map[string]any{"value": val, "update_time": now}); err != nil {
return err
}
}
return nil
}
// GetPlatformSiteSettingRows 返回库中实际存在的设置行(不做默认值补齐)
func GetPlatformSiteSettingRows() []PlatformSiteSetting {
var rows []PlatformSiteSetting
if _, err := Orm.QueryTable(new(PlatformSiteSetting)).
Filter("delete_time__isnull", true).
OrderBy("id").
All(&rows); err != nil {
return nil
}
return rows
}
// EnsurePlatformSiteSettingDefaults 补齐已登记但未入库的设置项(缺失时插入空值行)
// 注意:须用库中实际行判断,不能用 GetPlatformSiteTagMap —— 它预置了所有默认 tag 的 key,
// 会导致「已存在」判断永远成立、一行都插不进去。
func EnsurePlatformSiteSettingDefaults() {
exist := map[string]bool{}
for _, r := range GetPlatformSiteSettingRows() {
if r.Tag != "" {
exist[r.Tag] = true
}
}
now := time.Now()
for _, d := range PlatformSiteSettingDefs {
if exist[d.Tag] {
continue
}
if _, err := Orm.Insert(&PlatformSiteSetting{
Title: d.Title,
Tag: d.Tag,
Value: "",
Remark: d.Remark,
UpdateTime: &now,
}); err != nil {
continue
}
exist[d.Tag] = true
}
}
// EnsurePlatformSiteSettingTable 建表(通用键值结构,CREATE TABLE IF NOT EXISTS,可重复执行)。
// 若检测到旧宽表结构(含 logo_dark 列),先把历史数据迁移为键值行,再补齐默认项。
func EnsurePlatformSiteSettingTable() {
table := new(PlatformSiteSetting).TableName()
sql := `CREATE TABLE IF NOT EXISTS ` + table + ` (
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
title varchar(128) NOT NULL DEFAULT '' COMMENT '设置项名称',
value text COMMENT '设置项的值',
tag varchar(64) NOT NULL DEFAULT '' COMMENT '标签名,模板中写作 {yz:tag}',
remark varchar(255) NOT NULL DEFAULT '' COMMENT '备注/填写说明',
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time datetime DEFAULT NULL COMMENT '更新时间',
delete_time datetime DEFAULT NULL COMMENT '删除时间(软删除)',
PRIMARY KEY (id),
UNIQUE KEY uk_tag (tag, delete_time),
KEY idx_delete_time (delete_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台官网站点设置表(通用键值结构)'`
if _, err := Orm.Raw(sql).Exec(); err != nil {
return
}
// 旧宽表升级场景:CREATE TABLE IF NOT EXISTS 不会改已有表,需补齐通用结构列
ensurePlatformSiteSettingColumns(table)
if hasTableColumn(table, "logo_dark") {
migrateLegacyPlatformSiteSetting(table)
}
EnsurePlatformSiteSettingDefaults()
}
// ensurePlatformSiteSettingColumns 补齐通用结构列(列缺失时新增,已存在则忽略)
func ensurePlatformSiteSettingColumns(table string) {
cols := []struct{ name, def string }{
{"title", "varchar(128) NOT NULL DEFAULT '' COMMENT '设置项名称'"},
{"value", "text COMMENT '设置项的值'"},
{"tag", "varchar(64) NOT NULL DEFAULT '' COMMENT '标签名,模板中写作 {yz:tag}'"},
{"remark", "varchar(255) NOT NULL DEFAULT '' COMMENT '备注/填写说明'"},
}
for _, c := range cols {
if hasTableColumn(table, c.name) {
continue
}
_, _ = Orm.Raw("ALTER TABLE " + table + " ADD COLUMN " + c.def).Exec()
}
}
// hasTableColumn 判断表是否存在指定列(表不存在或查询异常时返回 false)
func hasTableColumn(table, column string) bool {
var cnt int64
err := Orm.Raw(
"SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?",
table, column,
).QueryRow(&cnt)
return err == nil && cnt > 0
}
// migrateLegacyPlatformSiteSetting 旧宽表结构迁移:
// 旧列为 sitename/logo_dark/logo_light/icp/police_icp/copyright/privacy_policy/terms_of_service,
// 迁移时仅在目标标签行为空的情况下写入,避免覆盖新配置。
func migrateLegacyPlatformSiteSetting(table string) {
type legacyRow struct {
Sitename string
LogoDark string
LogoLight string
Icp string
PoliceIcp string
Copyright string
Privacy string
Terms string
Companyname string
}
var rows []legacyRow
if _, err := Orm.Raw(
"SELECT IFNULL(sitename,'') sitename, IFNULL(logo_dark,'') logo_dark, IFNULL(logo_light,'') logo_light," +
" IFNULL(icp,'') icp, IFNULL(police_icp,'') police_icp, IFNULL(copyright,'') copyright," +
" IFNULL(privacy_policy,'') privacy_policy, IFNULL(terms_of_service,'') terms_of_service" +
" FROM " + table + " WHERE delete_time IS NULL ORDER BY id LIMIT 1").QueryRows(&rows); err != nil {
return
}
if len(rows) == 0 {
return
}
old := rows[0]
current := GetPlatformSiteTagMap()
pending := map[string]string{
"sitename": old.Sitename,
"logo": old.LogoDark,
"logow": old.LogoLight,
"icp": old.Icp,
"policeicp": old.PoliceIcp,
"copyright": old.Copyright,
"privacy": old.Privacy,
"terms": old.Terms,
}
for tag, val := range pending {
if val == "" || strings.TrimSpace(current[tag]) != "" {
continue
}
current[tag] = val
}
_ = SavePlatformSiteSettings(pending)
}