# 系统参数（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 配置直接裸透传为字符串给业务页面，除非该页面本身就是通用参数管理页。
