mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-30 12:45:32 +08:00
- ulthon-controller-response-throw 规则:揭露 success/error/result/redirect 都是 throw HttpResponseException 的陷阱,含两种正确范式(try 外 / 放行 HttpResponseException)与 grep 自查方法 - ulthon-system-config 规则:sysconfig 完整机制(存储/读取/保存/视图扩展),含新增配置项两种方式(加入已有组 / 新建独立 Tab) - AGENTS.md 索引同步:零散规则表 +2 行,工作流列表 +2 行(含上一 commit 的技能)
277 lines
11 KiB
Markdown
277 lines
11 KiB
Markdown
# 系统配置(sysconfig)机制与扩展规范
|
||
|
||
> 来源:框架内置(ulthon-)
|
||
> 作用域:需要通过后台界面管理的可变配置项(域名、密钥、开关等)
|
||
> 触发条件:新增/修改后台可变配置、读取 `sysconfig()`、覆盖 `system/config/*` 视图时加载
|
||
|
||
## 一、核心机制
|
||
|
||
### 1.1 数据存储
|
||
|
||
配置存储在 `system_config` 表,结构为 `group + name + value` 的键值对:
|
||
|
||
| 字段 | 说明 | 示例 |
|
||
|------|------|------|
|
||
| `group` | 配置组(Tab 页粒度) | `site`、`upload`、`wechat` |
|
||
| `name` | 配置键名 | `site_domain`、`upload_type` |
|
||
| `value` | 配置值(字符串) | `http://example.com`、`local_public` |
|
||
| `remark` | 后台显示的说明文字 | `站点域名` |
|
||
| `sort` | 排序 | 0 |
|
||
|
||
### 1.2 读取:sysconfig() 辅助函数
|
||
|
||
定义位置:`extend/base/helper.php:133`。
|
||
|
||
```php
|
||
// 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 秒
|
||
- 单值缓存 key:`sysconfig_{group}_{name}`
|
||
- 整组缓存 key:`sysconfig_{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_config`(group + 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 容器)
|
||
|
||
```html
|
||
<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 为例)
|
||
|
||
```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 常见表单控件写法
|
||
|
||
**文本输入**:
|
||
```html
|
||
<input type="text" name="键名" class="layui-input"
|
||
value="{:sysconfig('组名','键名')}">
|
||
```
|
||
|
||
**文本域**:
|
||
```html
|
||
<textarea name="键名" class="layui-textarea">{:sysconfig('组名','键名')}</textarea>
|
||
```
|
||
|
||
**单选(radio)**:
|
||
```html
|
||
{foreach ['值1'=>'标签1','值2'=>'标签2'] as $key=>$val}
|
||
<input type="radio" name="键名" value="{$key}" title="{$val}"
|
||
{if $key==sysconfig('组名','键名')}checked=""{/if}>
|
||
{/foreach}
|
||
```
|
||
|
||
**带上传组件**(图片/文件):
|
||
```html
|
||
<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:加入已有配置组(推荐)
|
||
|
||
在已有的 Tab(如 `site`)中增加字段,**无需改 `index.html`**。
|
||
|
||
**步骤**:
|
||
|
||
1. 覆盖对应的 include 模板到 `app/admin/view/system/config/` 下(如 `site.html`),复制原内容并增加新字段
|
||
2. 首次保存时 `ConfigBase::save()` 会自动在 `system_config` 表插入新记录(group=site, name=新键名)
|
||
3. 代码中用 `sysconfig('site', '新键名')` 读取
|
||
|
||
**示例**:在"网站设置"Tab 中增加「扫码域名」:
|
||
|
||
```html
|
||
<!-- 在 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.html` 在 `extend/base/` 不可改,需整体覆盖到 `app/admin/view/`。
|
||
|
||
**步骤**:
|
||
|
||
1. 复制 `extend/base/admin/view/system/config/index.html` 到 `app/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:
|
||
|
||
```html
|
||
<!-- app/admin/view/system/config/index.html 中增加 -->
|
||
<li>业务配置</li>
|
||
<!-- ... -->
|
||
<div class="layui-tab-item">
|
||
{include file="system/config/box" /}
|
||
</div>
|
||
```
|
||
|
||
```html
|
||
<!-- 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](./ulthon-file-override-mechanism.md)。
|
||
|
||
**覆盖规则**:
|
||
- 只需在 `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. **缓存 TTL**:sysconfig 缓存 3600 秒。如果直接改 DB(不走后台保存),需手动清缓存:`Cache::tag('sysconfig')->clear()`
|
||
|
||
4. **覆盖 `index.html` 的维护成本**:框架更新时如果 `index.html` 有变化(新增 Tab 等),覆盖版本不会自动同步。覆盖后需在框架升级时手动 diff 合并(`php think admin:update`)
|
||
|
||
## 相关文件
|
||
|
||
- `extend/base/helper.php`(`sysconfig()` 定义,第 133 行)
|
||
- `extend/base/admin/controller/system/ConfigBase.php`(配置控制器)
|
||
- `extend/base/admin/service/TriggerServiceBase.php`(`updateSysconfig()` 清缓存,第 45 行)
|
||
- `extend/base/admin/view/system/config/`(框架默认视图:index/site/logo/upload)
|
||
- `app/admin/model/SystemConfig.php`(配置模型)
|
||
|
||
## 相关规则与技能
|
||
|
||
- [ulthon-file-override-mechanism](./ulthon-file-override-mechanism.md)(视图覆盖机制,本规则依赖它实现配置视图覆盖)
|
||
- [ulthon-base-app-architecture](../skills/ulthon-base-app-architecture/SKILL.md)(Base/App 双层架构,决定 `extend/base/admin/view/` 不可改、`app/admin/view/` 可改)
|
||
- [ulthon-update-workflow](../skills/ulthon-update-workflow/SKILL.md)(框架更新时如何处理覆盖的 `index.html`)
|