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: "常用于
和页头 | 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)
}