Files
ulthon_admin/.agents/rules/ulthon-system-config.md
augushong 4a4155070d docs(agents): 新增控制器响应陷阱与 sysconfig 机制规则
- ulthon-controller-response-throw 规则:揭露 success/error/result/redirect 都是 throw HttpResponseException 的陷阱,含两种正确范式(try 外 / 放行 HttpResponseException)与 grep 自查方法
- ulthon-system-config 规则:sysconfig 完整机制(存储/读取/保存/视图扩展),含新增配置项两种方式(加入已有组 / 新建独立 Tab)
- AGENTS.md 索引同步:零散规则表 +2 行,工作流列表 +2 行(含上一 commit 的技能)
2026-07-19 12:03:22 +08:00

11 KiB
Raw Blame History

系统配置sysconfig机制与扩展规范

来源框架内置ulthon- 作用域:需要通过后台界面管理的可变配置项(域名、密钥、开关等) 触发条件:新增/修改后台可变配置、读取 sysconfig()、覆盖 system/config/* 视图时加载

一、核心机制

1.1 数据存储

配置存储在 system_config 表,结构为 group + name + value 的键值对:

字段 说明 示例
group 配置组Tab 页粒度) siteuploadwechat
name 配置键名 site_domainupload_type
value 配置值(字符串) http://example.comlocal_public
remark 后台显示的说明文字 站点域名
sort 排序 0

1.2 读取sysconfig() 辅助函数

定义位置:extend/base/helper.php:133

// 1. 读单个配置项(推荐)
$value = sysconfig('site', 'site_domain');

// 2. 读整组配置(返回 [name => value] 数组)
$all = sysconfig('site');
// $all['site_domain'], $all['site_name'] ...

// 3. 提供默认值(值为 null 时返回默认)
$value = sysconfig('site', 'site_domain', 'https://default.com');

// 4. 跨组快捷查找($name 传 true$group 参数当字段名用)
$value = sysconfig('site_domain', true);

缓存机制

  • Cache::tag('sysconfig') 缓存TTL 3600 秒
  • 单值缓存 keysysconfig_{group}_{name}
  • 整组缓存 keysysconfig_{group}
  • 保存配置时 TriggerService::updateSysconfig() 自动清除整个 sysconfig 标签的缓存

1.3 保存ConfigBase::save()

POST 到 system.config/save,控制器逻辑位于 extend/base/admin/controller/system/ConfigBase.php

  1. group_name 隐藏域确定配置组
  2. 遍历 POST 的其余字段,逐个 upsert 到 system_configgroup + name 存在则更新,不存在则创建)
  3. 调用 TriggerService::updateSysconfig()extend/base/admin/service/TriggerServiceBase.php:45)清缓存

关键group_name 不存在时按 name 全局匹配更新(无 group 限定);有 group_name 时严格按 group + name 匹配。每个配置表单必须包含 <input type="hidden" name="group_name">

二、配置视图结构

2.1 文件层级

配置页面入口(框架内核,可覆盖):
  extend/base/admin/view/system/config/index.html   ← Tab 容器

各 Tab 内容include可覆盖
  extend/base/admin/view/system/config/site.html    ← 网站设置
  extend/base/admin/view/system/config/logo.html    ← LOGO 配置
  extend/base/admin/view/system/config/upload.html  ← 上传配置

2.2 index.html 结构Tab 容器)

<div class="layui-tab layui-tab-brief">
    <ul class="layui-tab-title">
        <li class="layui-this">网站设置</li>
        <li>LOGO 配置</li>
        <li>上传配置</li>
    </ul>
    <div class="layui-tab-content">
        <div class="layui-tab-item layui-show">
            {include file="system/config/site" /}
        </div>
        <!-- 更多 Tab -->
    </div>
</div>

2.3 单个配置 Tab 模板(以 site.html 为例)

<form id="app-form" class="layui-form layuimini-form">
    <!-- 1. 隐藏域:声明配置组名(必传) -->
    <input type="hidden" name="group_name" value="site">

    <!-- 2. 配置项name=键名value=当前值 -->
    <div class="layui-form-item">
        <label class="layui-form-label">站点域名</label>
        <div class="layui-input-block">
            <input type="text" name="site_domain"
                   class="layui-input" lay-verify="required"
                   value="{:sysconfig('site','site_domain')}">
            <tip>填写说明文字</tip>
        </div>
    </div>

    <!-- 3. 提交按钮:固定写法 -->
    <div class="hr-line"></div>
    <div class="layui-form-item text-center">
        <button type="submit" class="layui-btn layui-btn-normal layui-btn-sm"
                lay-submit="system.config/save" data-refresh="false">确认</button>
        <button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
    </div>
</form>

2.4 常见表单控件写法

文本输入

<input type="text" name="键名" class="layui-input"
       value="{:sysconfig('组名','键名')}">

文本域

<textarea name="键名" class="layui-textarea">{:sysconfig('组名','键名')}</textarea>

单选radio

{foreach ['值1'=>'标签1','值2'=>'标签2'] as $key=>$val}
<input type="radio" name="键名" value="{$key}" title="{$val}"
       {if $key==sysconfig('组名','键名')}checked=""{/if}>
{/foreach}

带上传组件(图片/文件):

<div class="layui-input-block layuimini-upload">
    <input name="键名" class="layui-input layui-col-xs6"
           value="{:sysconfig('组名','键名')}">
    <div class="layuimini-upload-btn">
        <a class="layui-btn" data-upload="键名" data-upload-number="one" data-upload-exts="*image">
            <i class="fa fa-upload"></i> 上传
        </a>
        <a class="layui-btn layui-btn-normal" data-upload-select="键名" data-upload-number="one">
            <i class="fa fa-list"></i> 选择
        </a>
    </div>
</div>

三、新增配置项

方式 A加入已有配置组推荐

在已有的 Tabsite)中增加字段,无需改 index.html

步骤

  1. 覆盖对应的 include 模板到 app/admin/view/system/config/ 下(如 site.html),复制原内容并增加新字段
  2. 首次保存时 ConfigBase::save() 会自动在 system_config 表插入新记录group=site, name=新键名)
  3. 代码中用 sysconfig('site', '新键名') 读取

示例:在"网站设置"Tab 中增加「扫码域名」:

<!-- 在 app/admin/view/system/config/site.html 中追加 -->
<div class="layui-form-item">
    <label class="layui-form-label">扫码域名</label>
    <div class="layui-input-block">
        <input type="text" name="box_scan_url" class="layui-input"
               value="{:sysconfig('site','box_scan_url')}">
        <tip>二维码扫码访问使用的域名,以 http:// 或 https:// 开头</tip>
    </div>
</div>

读取:sysconfig('site', 'box_scan_url')

方式 B新建独立配置组新 Tab

需要修改 index.html 增加新 Tab。由于 index.htmlextend/base/ 不可改,需整体覆盖到 app/admin/view/

步骤

  1. 复制 extend/base/admin/view/system/config/index.htmlapp/admin/view/system/config/index.html
  2. 在覆盖的 index.html 中增加新 Tab 的 <li><div class="layui-tab-item">
  3. 创建新 Tab 的 include 模板 app/admin/view/system/config/新组名.html
  4. 首次保存后,配置数据自动写入 system_config

示例新增「业务配置」Tab

<!-- app/admin/view/system/config/index.html 中增加 -->
<li>业务配置</li>
<!-- ... -->
<div class="layui-tab-item">
    {include file="system/config/box" /}
</div>
<!-- app/admin/view/system/config/box.html -->
<form id="app-form" class="layui-form layuimini-form">
    <input type="hidden" name="group_name" value="box">
    <div class="layui-form-item">
        <label class="layui-form-label">扫码域名</label>
        <div class="layui-input-block">
            <input type="text" name="scan_url" class="layui-input"
                   value="{:sysconfig('box','scan_url')}">
            <tip>扫码访问域名</tip>
        </div>
    </div>
    <div class="hr-line"></div>
    <div class="layui-form-item text-center">
        <button type="submit" class="layui-btn layui-btn-normal layui-btn-sm"
                lay-submit="system.config/save" data-refresh="false">确认</button>
        <button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
    </div>
</form>

读取:sysconfig('box', 'scan_url')

四、视图覆盖优先级

ThinkPHP 模板解析顺序(先找到先使用):

1. app/admin/view/system/config/xxx.html          ← 应用层覆盖(优先)
2. extend/base/admin/view/system/config/xxx.html  ← 框架内核

详细机制见规则 ulthon-file-override-mechanism

覆盖规则

  • 只需在 app/admin/view/system/config/ 下创建同名文件即可覆盖
  • {include file="system/config/xxx" /} 的解析也遵循此优先级
  • 覆盖 index.html 时,必须保留原有 Tab 的 include 引用,否则丢失功能

五、sysconfig vs .env 的选择

维度 sysconfig .env
修改方式 后台界面修改,实时生效 手动编辑文件,需重启服务
适用场景 业务可变配置(域名、密钥、开关) 环境固定配置(数据库连接、调试开关)
缓存 Cache::tag('sysconfig'),保存自动清除 无缓存opcache 级别)
多环境 各环境共享代码,各自配置值不同 各环境各自文件
命令行 正常读取(有 DB 连接即可) 正常读取

判断标准:如果部署后可能需要修改 → sysconfig;如果绑定环境不会变 → .env

六、注意事项

  1. group_name 必传:每个配置表单必须包含 <input type="hidden" name="group_name" value="组名">,否则保存逻辑走全局 name 匹配,可能误更新其他组的同名字段

  2. 首次保存自动建记录ConfigBase::save() 在 group+name 不存在时会自动 create(),无需手动插入 DB 种子数据。但建议通过 DB 预置默认值,避免首次读取时返回 null

  3. 缓存 TTLsysconfig 缓存 3600 秒。如果直接改 DB不走后台保存需手动清缓存Cache::tag('sysconfig')->clear()

  4. 覆盖 index.html 的维护成本:框架更新时如果 index.html 有变化(新增 Tab 等),覆盖版本不会自动同步。覆盖后需在框架升级时手动 diff 合并(php think admin:update

相关文件

  • extend/base/helper.phpsysconfig() 定义,第 133 行)
  • extend/base/admin/controller/system/ConfigBase.php(配置控制器)
  • extend/base/admin/service/TriggerServiceBase.phpupdateSysconfig() 清缓存,第 45 行)
  • extend/base/admin/view/system/config/框架默认视图index/site/logo/upload
  • app/admin/model/SystemConfig.php(配置模型)

相关规则与技能