Some checks failed
CI / init (push) Has been cancelled
CI / Frontend node 18.16.0 (push) Has been cancelled
CI / Backend go (1.22) (push) Has been cancelled
CI / devops-test (1.22, 18.16.0) (push) Has been cancelled
CI / release-pr (push) Has been cancelled
CI / release-please (push) Has been cancelled
CI / devops-prod (1.22, 18.x) (push) Has been cancelled
CI / docker (push) Has been cancelled
159 lines
6.6 KiB
Markdown
159 lines
6.6 KiB
Markdown
# 系统参数(SysParams)使用规范
|
||
|
||
## 适用范围
|
||
|
||
- 需要读取或写入 `sys_params` 表中的系统参数时,使用本文。
|
||
- 需要给某一个固定参数提供独立业务 API,但底层仍保存到 `sys_params` 时,使用本文。
|
||
|
||
## 现有落点
|
||
|
||
- 表结构:`model/system/sys_params.go`
|
||
- Service:`service/system/sys_params.go`
|
||
- API:`api/v1/system/sys_params.go`
|
||
- Router:`router/system/sys_params.go`
|
||
|
||
## 现有接口
|
||
|
||
- 实际完整路径 = `<router-prefix>/sysParams/...`。
|
||
- 如果环境把 `router-prefix` 配成 `/api`,则完整路径就是 `/api/sysParams/...`。
|
||
- 当前仓库 `config.yaml` 中 `router-prefix` 默认值为空字符串;不要把 `/api` 写死为项目事实。
|
||
- 这组路由在 `initialize/router.go` 中挂到 `PrivateGroup`,默认需要鉴权。
|
||
- `GET /sysParams/getSysParam?key=xxx`:按 `key` 读取单条参数。
|
||
- `GET /sysParams/getSysParamsList`:分页读取参数列表。
|
||
- `POST /sysParams/createSysParams`:创建参数。
|
||
- `PUT /sysParams/updateSysParams`:按 `ID` 更新参数。
|
||
- `DELETE /sysParams/deleteSysParams`:按 `ID` 删除参数。
|
||
- 结论:这组接口适合“通用参数管理页”,不适合业务围绕某一个固定参数直接做长期读写。
|
||
|
||
## 强制规则
|
||
|
||
- 后端代码禁止通过 HTTP 反调自己的 `/sysParams` 接口;统一在 `Service` 层直接调用 `service.ServiceGroupApp.SystemServiceGroup.SysParamsService`。
|
||
- `API` 层禁止直接操作 `global.GVA_DB`。
|
||
- `Value` 是字符串存储;简单值可直接存字符串,结构化配置必须由业务 `Service` 负责 `json.Marshal` / `json.Unmarshal`。
|
||
- 不要把 `sys_params` 的 `ID` 暴露给前端,作为某个固定配置的长期主键。
|
||
- 固定配置统一按 `key` 识别;`key` 必须稳定、唯一、可读,建议使用 `<module>.<scene>`,例如 `device.runtimeConfig`。
|
||
- 参数不存在时必须显式处理:返回业务错误、初始化默认值、或创建后再返回;禁止静默吞掉。
|
||
- 通用参数管理页面可以直接调用 `/sysParams`;具体业务页面优先调用该业务自己的独立 API。
|
||
- 参数属于某个业务模块时,落点放到该业务模块;只有平台级通用参数才继续放在 `system` 模块。
|
||
|
||
## 代码内直接读取
|
||
|
||
- 读取动作统一放在 `service/<module>`。
|
||
- 调用入口:
|
||
|
||
```go
|
||
param, err := service.ServiceGroupApp.SystemServiceGroup.SysParamsService.GetSysParam("device.runtimeConfig")
|
||
```
|
||
|
||
- `param.Value` 就是最终存储值。
|
||
- 如果 `Value` 存的是 JSON,必须先反序列化为业务结构体,再继续使用。
|
||
|
||
```go
|
||
package device
|
||
|
||
import (
|
||
"encoding/json"
|
||
|
||
"github.com/flipped-aurora/gin-vue-admin/server/service"
|
||
)
|
||
|
||
type RuntimeConfig struct {
|
||
Enable bool `json:"enable"`
|
||
Mode string `json:"mode"`
|
||
}
|
||
|
||
func (s *DeviceService) GetRuntimeConfig() (RuntimeConfig, error) {
|
||
param, err := service.ServiceGroupApp.SystemServiceGroup.SysParamsService.GetSysParam("device.runtimeConfig")
|
||
if err != nil {
|
||
return RuntimeConfig{}, err
|
||
}
|
||
|
||
var cfg RuntimeConfig
|
||
if err = json.Unmarshal([]byte(param.Value), &cfg); err != nil {
|
||
return RuntimeConfig{}, err
|
||
}
|
||
|
||
return cfg, nil
|
||
}
|
||
```
|
||
|
||
## 代码内直接写入
|
||
|
||
- 当前 `SysParamsService` 没有“按 `key` 直接写入”的现成能力。
|
||
- 现在只有 `CreateSysParams`、`UpdateSysParams`、`GetSysParam`。
|
||
- 这意味着:业务写入固定参数时,必须先按 `key` 查询,再决定 `create` 还是 `update`。
|
||
- 这段逻辑必须收口到业务 `Service`,不要放在 `API`,也不要交给前端拼 `ID`。
|
||
|
||
```go
|
||
package device
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
|
||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||
"github.com/flipped-aurora/gin-vue-admin/server/service"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
const runtimeConfigKey = "device.runtimeConfig"
|
||
|
||
func (s *DeviceService) SaveRuntimeConfig(cfg RuntimeConfig) error {
|
||
valueBytes, err := json.Marshal(cfg)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
sysParamsSvc := service.ServiceGroupApp.SystemServiceGroup.SysParamsService
|
||
param, err := sysParamsSvc.GetSysParam(runtimeConfigKey)
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return sysParamsSvc.CreateSysParams(&system.SysParams{
|
||
Name: "设备运行配置",
|
||
Key: runtimeConfigKey,
|
||
Value: string(valueBytes),
|
||
Desc: "device 模块运行配置",
|
||
})
|
||
}
|
||
return err
|
||
}
|
||
|
||
param.Value = string(valueBytes)
|
||
param.Name = "设备运行配置"
|
||
param.Desc = "device 模块运行配置"
|
||
return sysParamsSvc.UpdateSysParams(param)
|
||
}
|
||
```
|
||
|
||
## 什么时候必须写独立 API
|
||
|
||
- 前端只关心某一个固定参数,不应该先查列表、拿 `ID`、再更新时。
|
||
- 参数值是 JSON,需要强类型请求体和响应体时。
|
||
- 这个参数有明确业务语义,例如“设备运行配置”“首页配置”“第三方回调配置”时。
|
||
- 该参数需要单独做权限控制、默认值处理、字段校验、审计说明时。
|
||
|
||
## 独立 API 规范
|
||
|
||
- 独立 API 仍按 `Router → API → Service → GORM → Database` 分层实现。
|
||
- 底层存储仍使用 `sys_params`;不要因为一个配置项就单独建表,除非它已经演变成独立业务实体。
|
||
- 对外接口暴露业务语义,不暴露 `sys_params` 的 `ID`、通用 CRUD 细节。
|
||
- `Service` 负责固定 `key`、处理 JSON 转换、决定 create/update、处理不存在时的策略。
|
||
- 参数属于业务模块时,文件落点统一为 `model/<module>/request`、`model/<module>/response`、`service/<module>`、`api/v1/<module>`、`router/<module>`。
|
||
- 新增业务路由统一注册到 `initialize/router_biz.go`。
|
||
|
||
## 推荐落地方式
|
||
|
||
- 对外提供两个独立接口即可:一个“获取配置”,一个“保存配置”。
|
||
- 接口名使用业务语义,不使用 `sysParams` 命名。
|
||
- 示例接口:`GET <router-prefix>/deviceConfig/getRuntimeConfig`、`PUT <router-prefix>/deviceConfig/updateRuntimeConfig`。
|
||
- `GET` 接口返回业务结构体。
|
||
- `PUT` 接口接收业务结构体。
|
||
- 底层统一在 `service/device` 中转成 `sys_params.value` 的 JSON 字符串。
|
||
|
||
## 禁止事项
|
||
|
||
- 禁止在业务代码里直接写 SQL 操作 `sys_params`,绕开现有 `Service` 分层。
|
||
- 禁止在业务 `API` 中直接处理 `sys_params` 的 `ID`。
|
||
- 禁止让前端把一个固定配置当成“参数管理列表中的某一行”去长期维护。
|
||
- 禁止把结构化 JSON 配置直接裸透传为字符串给业务页面,除非该页面本身就是通用参数管理页。
|