服务端
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
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
This commit is contained in:
96
server/.ai-specs/coding-specs/api-auth-control.md
Normal file
96
server/.ai-specs/coding-specs/api-auth-control.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# API 鉴权与权限修改规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
- 新增接口时,需要判定它是“公开访问”“仅登录可访问”还是“登录且有权限才可访问”时,使用本文。
|
||||
- 已有接口在“不需要登录 / 需要登录 / 需要权限”之间切换时,使用本文。
|
||||
|
||||
## 现有鉴权基线
|
||||
|
||||
- `initialize/router.go` 当前只创建两类顶层路由组:
|
||||
- `PublicGroup`:不挂鉴权中间件。
|
||||
- `PrivateGroup`:统一挂 `middleware.JWTAuth()` 和 `middleware.CasbinHandler()`。
|
||||
- `JWTAuth` 定义在 `middleware/jwt.go`。
|
||||
- `CasbinHandler` 定义在 `middleware/casbin_rbac.go`。
|
||||
- 结论:
|
||||
- 挂到 `PublicGroup` = 不需要登录。
|
||||
- 挂到 `PrivateGroup` = 需要登录,且需要角色权限。
|
||||
- 当前仓库没有默认内置的“只登录、不校验权限”公共组。
|
||||
- 不存在“只校验权限、不登录”的合法模式;`CasbinHandler` 依赖 `JWTAuth` 写入的用户 claims。
|
||||
|
||||
## 权限模式判定
|
||||
|
||||
| 模式 | 路由挂载方式 | JWT | Casbin | 后续要求 |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| 不需要登录 | `PublicGroup` / `PublicRouter` | 否 | 否 | 默认不作为角色权限点长期维护 |
|
||||
| 需要登录,不需要权限 | 基于 `PublicRouter` 单独加 `middleware.JWTAuth()` | 是 | 否 | 默认不进入角色权限维护 |
|
||||
| 需要登录且需要权限 | `PrivateGroup` / `Router` | 是 | 是 | 必须进入 `sys_apis` 并分配角色 |
|
||||
|
||||
## 强制规则
|
||||
|
||||
- 同一个 `path + method` 只能落一种鉴权模式;禁止同时注册到公开组和鉴权组。
|
||||
- `middleware.OperationRecord()` 只负责操作审计,不改变登录与权限判定。
|
||||
- 鉴权逻辑统一落在 `router` 层;禁止在 `API` 或 `Service` 中靠 `if token != ""`、`if authorityId == ...` 兜底模拟权限。
|
||||
- 修改接口鉴权时,Swagger 注解必须同步:
|
||||
- 不需要登录:移除 `@Security ApiKeyAuth`
|
||||
- 需要登录:保留 `@Security ApiKeyAuth`
|
||||
- 业务路由统一在 `router/<module>` 调整;业务模块接入点统一在 `initialize/router_biz.go` 注册。
|
||||
|
||||
## 改成不需要登录
|
||||
|
||||
- 把路由挂载从 `PrivateGroup` 或“仅登录组”移到 `PublicRouter`。
|
||||
- 如果只是业务模块内部路由调整,不要改 `API`、`Service` 方法签名。
|
||||
- 该接口如果不再作为后台角色权限点维护,必须把对应 `path + method` 从权限维护中移走,二选一即可:
|
||||
- 删除对应 `sys_apis` 记录。
|
||||
- 加入 `sys_ignore_apis`,避免后续 `SyncApi` 再次纳入权限页。
|
||||
- 若历史上已经给该接口分配过角色,必须同步清理对应 Casbin 规则,避免后台权限配置出现“看起来要授权、实际上公开可调”的假象。
|
||||
|
||||
## 改成需要登录,不需要权限
|
||||
|
||||
- 当前项目没有全局现成的 `LoginGroup`;少量接口优先在对应 router 中基于 `PublicRouter` 单独挂 `JWTAuth`,不要为了一两个接口改全局入口。
|
||||
- 推荐写法:
|
||||
|
||||
```go
|
||||
func (r *BookRouter) InitBookRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
bookLoginRouter := PublicRouter.Group("book").Use(middleware.JWTAuth())
|
||||
{
|
||||
bookLoginRouter.GET("profile", bookApi.GetProfile)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 同一模块里如果“仅登录接口”很多,再考虑在 `initialize/router.go` 新增单独的 `LoginGroup`,并把它显式传入对应 router 初始化函数。
|
||||
- 这类接口禁止挂 `middleware.CasbinHandler()`。
|
||||
- 这类接口默认不进入 `sys_apis` 的角色权限维护;如果历史上已经进入,必须删除或加入 `sys_ignore_apis`,不要继续让后台把它当作“需要分配角色”的接口。
|
||||
|
||||
## 改成需要登录且需要权限
|
||||
|
||||
- 把路由挂到 `PrivateGroup` 或业务 router 的 `Router` 参数上。
|
||||
- 只把路由挪进 `PrivateGroup` 还不够;`CasbinHandler` 按 `path + method` 校验权限,没有角色策略就会直接返回“权限不足”。
|
||||
- 因此必须同时完成以下动作:
|
||||
- 确保该 `path + method` 存在于 `sys_apis`。
|
||||
- 确保该接口已分配允许访问的角色。
|
||||
- 刷新 Casbin 策略缓存。
|
||||
- 系统现成接口的完整路径 = `<router-prefix>/api/...`;如果 `router-prefix` 为空,则完整路径就是 `/api/...`。不要把是否存在额外前缀写死成项目事实。
|
||||
- 常用管理接口:
|
||||
- `GET <router-prefix>/api/syncApi`:对比内存路由和 `sys_apis`
|
||||
- `POST <router-prefix>/api/enterSyncApi`:确认把新增路由写入 `sys_apis`
|
||||
- `POST <router-prefix>/api/setApiRoles`:全量覆盖某个接口的角色列表
|
||||
- `GET <router-prefix>/api/freshCasbin`:刷新 Casbin 策略缓存
|
||||
- 若接口此前在 `sys_ignore_apis` 中,改成“需要权限”前必须先取消 ignore,再同步到 `sys_apis`。
|
||||
- 若不给任何角色分配该接口,则结果不是“仅登录可访问”,而是“所有已登录用户都权限不足”。
|
||||
|
||||
## 验证要求
|
||||
|
||||
- 修改完成后,至少验证下面三类请求结果:
|
||||
- 公开接口:不带 token 可访问成功。
|
||||
- 仅登录接口:不带 token 返回未登录;带有效 token 可访问成功。
|
||||
- 权限接口:不带 token 返回未登录;带无权角色 token 返回“权限不足”;带有权角色 token 可访问成功。
|
||||
- 如果改了接口鉴权方式,同时该接口出现在 Swagger、前端权限配置页、角色配置页,也必须同步核对展示结果是否一致。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 禁止只改 `sys_apis` 或 `casbin_rule`,却不改真实 router 挂载。
|
||||
- 禁止只把路由从 `PublicGroup` 挪到 `PrivateGroup`,却不补 `sys_apis` 和角色策略。
|
||||
- 禁止把“仅登录接口”偷懒挂到 `PrivateGroup`,再靠给所有角色放权来模拟“无权限限制”。
|
||||
- 禁止让公开接口长期保留在角色权限维护页里,造成权限含义失真。
|
||||
42
server/.ai-specs/coding-specs/module-admin-app-split.md
Normal file
42
server/.ai-specs/coding-specs/module-admin-app-split.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 同模块 admin/app 接口分层规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
- 同一业务模块同时存在 `admin` 管理端接口和 `app` 用户端接口时,使用本文。
|
||||
|
||||
## 强制规则
|
||||
|
||||
- 顶层目录一律按业务模块落点;禁止因为有 `app` 端,就单独新建顶层 `router/app`、`api/v1/app`、`service/app`、`model/app` 来承载 `book`、`order`、`author` 这类业务。
|
||||
- 同一业务模块的实体仍统一放在 `model/<module>`;只有接口入参、出参和流程按 `admin` / `app` 分开。
|
||||
- `router/<module>`、`api/v1/<module>`、`service/<module>` 内,`admin` 与 `app` 接口必须分文件或分承载结构体;禁止长期混写在同一个大文件里。
|
||||
- 可复用逻辑优先下沉到 `service/<module>` 的公共方法;公共逻辑只抽业务共性,不要把 `admin` / `app` 的鉴权、返回、分页口径硬揉成一套。
|
||||
|
||||
## 推荐落点
|
||||
|
||||
- 推荐文件名:`<module>_admin.go`、`<module>_app.go`、`enter.go`。
|
||||
- 示例:
|
||||
- `router/book/book_admin.go`
|
||||
- `router/book/book_app.go`
|
||||
- `api/v1/book/book_admin.go`
|
||||
- `api/v1/book/book_app.go`
|
||||
- `service/book/book_admin.go`
|
||||
- `service/book/book_app.go`
|
||||
- `service/book/book_common.go`
|
||||
- `model/book/book.go`
|
||||
- `model/book/request/book_admin.go`
|
||||
- `model/book/request/book_app.go`
|
||||
- `model/book/response/book_admin.go`
|
||||
- `model/book/response/book_app.go`
|
||||
|
||||
## 路由与鉴权
|
||||
|
||||
- `admin` 接口默认挂业务 router 的 `PrivateGroup`,走后台 `JWT + Casbin`。
|
||||
- `app` 公开接口挂 `PublicGroup`。
|
||||
- `app` 仅登录接口,基于 `PublicGroup` 单独加 `middleware.JWTAuth()`;不要直接挂 `PrivateGroup`。
|
||||
- 如果 `app` 用户体系不等于 `sys_users`,禁止直接复用后台登录、claims、角色权限链路;必须单独实现用户端认证链路。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 禁止把“同一业务模块的 app 端接口”误建成独立业务模块。
|
||||
- 禁止为了省事,把所有 `app` 接口都挂到 `PrivateGroup`,再用后台角色权限去模拟用户端登录态。
|
||||
- 禁止同一个业务实体在 `model` 层拆出两套重复表结构,仅因接口面向 `admin` / `app` 不同。
|
||||
54
server/.ai-specs/coding-specs/module-admin-crud-default.md
Normal file
54
server/.ai-specs/coding-specs/module-admin-crud-default.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 业务 admin 端默认 CRUD 接口规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
- 新增业务模块的 `admin` 端管理接口时,使用本文。
|
||||
- 判断某个业务模块默认应该提供哪些后台 CRUD 接口时,使用本文。
|
||||
- 业务模块没有明确说明“只读”“无详情”“禁止批量删除”等例外时,默认按本文落地。
|
||||
|
||||
## 默认接口基线
|
||||
|
||||
- 默认 CRUD = `创建`、`单删`、`批量删除`、`更新`、`详情`、`分页列表` 6 个接口。
|
||||
- 完整路径 = `<router-prefix>/<abbreviation>/...`;如果 `router-prefix` 为空,则完整路径不带额外前缀。禁止把 `/api` 写死为项目事实。
|
||||
- `admin` 接口默认挂 `PrivateGroup`,统一走后台 `JWT + Casbin`。
|
||||
- 写操作默认挂 `middleware.OperationRecord()`;读操作默认不挂操作审计。
|
||||
|
||||
| 动作 | Method | 默认路径 | API 方法名 | Service 方法名 |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| 创建 | `POST` | `/<abbreviation>/create<StructName>` | `Create<StructName>` | `Create<StructName>` |
|
||||
| 单删 | `DELETE` | `/<abbreviation>/delete<StructName>` | `Delete<StructName>` | `Delete<StructName>` |
|
||||
| 批量删除 | `DELETE` | `/<abbreviation>/delete<StructName>ByIds` | `Delete<StructName>ByIds` | `Delete<StructName>ByIds` |
|
||||
| 更新 | `PUT` | `/<abbreviation>/update<StructName>` | `Update<StructName>` | `Update<StructName>` |
|
||||
| 详情 | `GET` | `/<abbreviation>/find<StructName>` | `Find<StructName>` | `Get<StructName>` |
|
||||
| 分页列表 | `GET` | `/<abbreviation>/get<StructName>List` | `Get<StructName>List` | `Get<StructName>InfoList` |
|
||||
|
||||
## 参数约定
|
||||
|
||||
- `Create`、`Update` 默认使用 `body` 传实体或业务 `request`。
|
||||
- `Delete` 默认用主键 query 参数删除;主键名必须和实体主键字段保持一致。
|
||||
- `DeleteByIds` 默认用主键数组 query 参数删除,格式统一为 `<primaryField>s[]`。
|
||||
- `Find` 默认用主键 query 参数查询详情。
|
||||
- `GetList` 默认接收 `model/<module>/request` 下的搜索结构;普通列表返回分页结果,树形列表可不接分页参数,但接口名仍保持 `get<StructName>List`。
|
||||
|
||||
## 强制规则
|
||||
|
||||
- 新增业务 `admin` 模块时,如无明确例外,先提供这 6 个接口,再叠加业务特有接口。
|
||||
- 路由统一放在 `router/<module>`,接口统一放在 `api/v1/<module>`,业务统一放在 `service/<module>`,模型统一放在 `model/<module>`。
|
||||
- 新增业务路由后,必须同步在 `initialize/router_biz.go` 注册。
|
||||
- 同一模块如果同时有 `admin/app` 两套接口,目录仍按业务模块落点,`admin` 与 `app` 必须分文件或分承载结构体,不能长期混写。
|
||||
- Swagger 注解里的 `@Router`、`@Security ApiKeyAuth`、`Method` 必须和真实 router 挂载一致。
|
||||
|
||||
## 允许例外
|
||||
|
||||
- 业务天然只读时,可以不做 `Create`、`Update`、`Delete`、`DeleteByIds`,但必须在对应业务文档或接口文档中明确说明。
|
||||
- 业务明确禁止批量删除时,可以去掉 `DeleteByIds`,但 `router`、`api`、`service`、前端调用、权限点必须同步移除,不能只删单层。
|
||||
- 树形实体的列表接口可以返回整棵树,不强制分页;但接口名仍保持 `get<StructName>List`,不要随意改成 `tree`、`all`、`queryList`。
|
||||
- 配置型场景如果本质不是独立业务实体,优先复用已有能力,例如 `sys_params`;不要为了凑 CRUD 强行建完整业务模块。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 禁止把默认 `admin` CRUD 直接挂到 `PublicGroup`。
|
||||
- 禁止同一批后台 CRUD 同时出现 `createXxx`、`addXxx`、`saveXxx` 多套命名。
|
||||
- 禁止把列表接口随意命名成 `page`、`list`、`query`,导致不同模块接口风格漂移。
|
||||
- 禁止只新增 `API` 或只新增 `Router`,不补齐 `Service`、`Model`、注册入口。
|
||||
- 禁止因为有 `app` 端,就把 `admin` 端默认 CRUD 改落到 `router/app`、`api/v1/app` 这类顶层目录。
|
||||
158
server/.ai-specs/coding-specs/sys-params.md
Normal file
158
server/.ai-specs/coding-specs/sys-params.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# 系统参数(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 配置直接裸透传为字符串给业务页面,除非该页面本身就是通用参数管理页。
|
||||
54
server/.ai-specs/coding-specs/vo-model-request-response.md
Normal file
54
server/.ai-specs/coding-specs/vo-model-request-response.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Model / Request / Response 组织规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
- 判断项目中的 `vo`、实体、接口入参、接口出参应该落在哪一层时,使用本文。
|
||||
- 新增或修改业务模块的 `model/<module>`、`model/<module>/request`、`model/<module>/response` 时,使用本文。
|
||||
- 判断某个 API 是直接复用实体,还是新增 `request/response` 结构时,使用本文。
|
||||
|
||||
## 结论基线
|
||||
|
||||
- 本项目不单独维护顶层 `vo` 目录。
|
||||
- 项目里的数据载体统一归到 `model` 体系,不额外拆一套平行 `vo` 层。
|
||||
- 标准落点如下:
|
||||
|
||||
| 场景 | 落点 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| 数据库实体 / 业务实体 | `model/<module>` | 承载表结构映射、业务实体字段 |
|
||||
| API 入参 | `model/<module>/request` | 承载查询条件、分页条件、保存参数等 |
|
||||
| API 出参 | `model/<module>/response` | 承载详情包装、列表项包装、聚合展示结构等 |
|
||||
| 跨模块通用入参 | `model/common/request` | 例如 `PageInfo`、`GetById`、`IdsReq` |
|
||||
| 跨模块通用出参 | `model/common/response` | 例如统一响应壳、分页结果 |
|
||||
|
||||
## 判定规则
|
||||
|
||||
- 请求参数如果就是业务实体本身,且不会引入多余字段、敏感字段或语义歧义,可以直接复用 `model/<module>` 实体。
|
||||
- 请求参数如果只是“分页 + 条件筛选 + 排序”这类接口 contract,应定义到 `model/<module>/request`。
|
||||
- 返回结果如果只是直接返回实体本身,可以直接返回实体或列表,不强制为了“像 VO”再包一层空结构。
|
||||
- 返回结果如果需要额外包装、聚合字段、嵌套结构、展示字段转换,应定义到 `model/<module>/response`。
|
||||
- 多个 API 只要 contract 一致,可以共用同一个 `request` 或 `response` 结构;不是每个 API 都必须单独建一份。
|
||||
|
||||
## 强制规则
|
||||
|
||||
- 新增业务模块时,实体统一放 `model/<module>`;禁止把实体落到 `api`、`service`、`router`。
|
||||
- `API` 层入参、出参需要独立结构时,统一放 `model/<module>/request`、`model/<module>/response`;禁止在 `API` 文件里长期维护临时匿名结构体当正式 contract。
|
||||
- 跨模块都能稳定复用的分页、主键、统一响应、分页结果等结构,统一复用 `model/common/request`、`model/common/response`;不要每个模块各复制一份。
|
||||
- 同一业务模块如果同时存在 `admin/app` 两套接口,实体仍统一放 `model/<module>`;只有 `request/response` 按 `admin` / `app` 分文件区分。
|
||||
- 新增 `request/response` 前,先检查同模块现有结构是否可复用;只有接口字段、校验语义、返回语义明显不同,才新增结构。
|
||||
- 改实体字段时,必须同步检查 `service` 查询/写入、`API` 绑定/返回、`doc-sql` 是否仍一致。
|
||||
- 改 `request/response` 时,必须同步检查 `API` 绑定、`Service` 方法签名、`doc-api` 是否仍一致。
|
||||
|
||||
## 推荐做法
|
||||
|
||||
- `Create`、`Update` 这类保存接口,如果直接面向实体字段,可优先复用实体。
|
||||
- `GetList`、搜索、筛选、排序接口,优先使用 `request` 结构,不要把分页筛选字段硬塞进实体。
|
||||
- `Find`、详情、聚合展示、树结构、联表结果等返回,优先使用 `response` 结构,不要把纯展示字段反向塞进数据库实体。
|
||||
- `response` 结构命名优先体现业务语义,例如 `BookDetailResponse`、`BookListItem`、`AuthorOption`;不要机械统一叫 `XxxVO`。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 禁止单独新建顶层 `vo` 目录,与 `model` 并行维护两套数据结构体系。
|
||||
- 禁止为了“每个 API 都有专属 VO”而机械性给每个接口复制一份几乎相同的 `request/response`。
|
||||
- 禁止把分页、排序、筛选字段直接加进数据库实体,只为了省掉 `request` 结构。
|
||||
- 禁止把纯展示字段、聚合字段、临时返回字段长期塞进实体,只为了省掉 `response` 结构。
|
||||
- 禁止 `API`、`Service` 长期返回 `map[string]interface{}`、`gin.H` 充当正式业务出参,导致 contract 漂移。
|
||||
9
server/.ai-specs/doc-dict/book_author_status.md
Normal file
9
server/.ai-specs/doc-dict/book_author_status.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 作者状态
|
||||
|
||||
- 模块:book
|
||||
- 字典编码:`book_author_status`
|
||||
|
||||
| Label | Value | Sort | Status | Desc |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| 启用 | `enabled` | 10 | true | 作者可正常展示,并可被书籍继续关联 |
|
||||
| 禁用 | `disabled` | 20 | true | 作者不再对外展示,且不应再被新增书籍关联 |
|
||||
9
server/.ai-specs/doc-dict/book_completion_status.md
Normal file
9
server/.ai-specs/doc-dict/book_completion_status.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 书籍完结状态
|
||||
|
||||
- 模块:book
|
||||
- 字典编码:`book_completion_status`
|
||||
|
||||
| Label | Value | Sort | Status | Desc |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| 完结 | `completed` | 10 | true | 书籍内容已全部发布完成,不再持续新增章节 |
|
||||
| 连载 | `serializing` | 20 | true | 书籍内容仍在持续发布,后续还会新增章节 |
|
||||
39
server/.ai-specs/doc-sql/book_author.md
Normal file
39
server/.ai-specs/doc-sql/book_author.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 书籍作者表
|
||||
|
||||
## 基本信息
|
||||
|
||||
- 模块:book
|
||||
- 表名:`book_author`
|
||||
- 模型:`model/book/book_author.go`
|
||||
- 迁移接入:`initialize/gorm_biz.go`
|
||||
- 状态字典:`book_author_status`
|
||||
- 职责:承载书籍作者主体信息,用于作者资料展示、书籍作者关联和后台作者管理。
|
||||
|
||||
## 建议 SQL
|
||||
|
||||
> 以下 SQL 以当前项目 PostgreSQL 为准,主要表达字段、约束和索引语义;实际落库以 `GORM Model` 和 `initialize/gorm_biz.go` 为准。
|
||||
|
||||
```sql
|
||||
CREATE TABLE book_author (
|
||||
id bigserial PRIMARY KEY,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
name varchar(128) NOT NULL,
|
||||
author_status varchar(32) NOT NULL DEFAULT 'enabled',
|
||||
intro text,
|
||||
cover_url varchar(500)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE book_author IS '书籍作者表';
|
||||
COMMENT ON COLUMN book_author.id IS '主键';
|
||||
COMMENT ON COLUMN book_author.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN book_author.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN book_author.name IS '作者名称';
|
||||
COMMENT ON COLUMN book_author.author_status IS '作者状态字典值,对应 book_author_status';
|
||||
COMMENT ON COLUMN book_author.intro IS '作者简介';
|
||||
COMMENT ON COLUMN book_author.cover_url IS '作者封面图片 URL';
|
||||
|
||||
CREATE UNIQUE INDEX uk_book_author_name ON book_author (name);
|
||||
CREATE INDEX idx_book_author_author_status ON book_author (author_status);
|
||||
CREATE INDEX idx_book_author_created_at ON book_author (created_at);
|
||||
```
|
||||
47
server/.ai-specs/logic-specs/book.md
Normal file
47
server/.ai-specs/logic-specs/book.md
Normal file
@@ -0,0 +1,47 @@
|
||||
书名:
|
||||
子标题
|
||||
书籍类型:字典
|
||||
书籍标签 : 字典 ,标签是多个 要考虑用标签搜索书籍的性能问题
|
||||
书有系列:第一部 第二部, 上部 下部 , 所以有排序功能
|
||||
章节名: 章节是否能观看
|
||||
文件路径:oss url
|
||||
封面图片: url
|
||||
作者名(非id): 多个名字用,隔开
|
||||
出版社:
|
||||
出版时间:
|
||||
简介:
|
||||
热度:
|
||||
评分: 0-10分
|
||||
点评数:
|
||||
字数:
|
||||
书籍完结状态: 字典, 完结/连载
|
||||
- 书籍系列Id
|
||||
|
||||
# 书籍系列表
|
||||
- 名字
|
||||
- 封面图片
|
||||
|
||||
# 书籍作者表
|
||||
- 名字
|
||||
- 状态:字典
|
||||
- 简介
|
||||
- 封面图片: url
|
||||
|
||||
# 用户观看书籍历史表
|
||||
- 会员用户Id
|
||||
- 观看书籍进度 : 前端显示 50%
|
||||
- 观看章节Id
|
||||
- 观看章节行数
|
||||
|
||||
|
||||
# 用户收藏书籍历
|
||||
- 会员用户Id
|
||||
- 书籍Id
|
||||
|
||||
# 书籍评论表
|
||||
- 评论书籍id
|
||||
- 评论章节id
|
||||
- 评论章节行数 : 比如第二章第5行
|
||||
- 评论内容
|
||||
- 评论点赞数
|
||||
- 作者是否点赞
|
||||
53
server/.ai-specs/sys-specs/business-dictionary-spec.md
Normal file
53
server/.ai-specs/sys-specs/business-dictionary-spec.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 业务字典规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
- 当任务是新增业务状态、类型、级别、来源、模式、分类等值域时,先读本文件。
|
||||
- 本文件只规定新增业务字典怎么写,不重复说明系统字典实现细节。
|
||||
|
||||
## 强制规则
|
||||
|
||||
- 业务枚举就是业务字典。禁止在代码中脱离字典单独发明枚举值域。
|
||||
- 新增业务值域时,先写字典文档,再写表结构、接口、校验和前端展示。
|
||||
- 具体业务字典必须写到 `.ai-specs/doc-dict/` 下。
|
||||
- 一个 `.md` 文件只能写一个字典。
|
||||
- 字典文件名必须等于字典编码,推荐路径:`.ai-specs/doc-dict/<dict-code>.md`。
|
||||
- 字典编码使用 `snake_case`,固定格式 `<module>_<field>`,例如 `device_status`。
|
||||
- `<dict-code>` 就是 `<module>_<field>`。
|
||||
- 字典项 `Value` 使用稳定 machine value;代码常量值必须与字典项 `Value` 完全一致。
|
||||
- 代码判断统一使用字典项 `Value`;禁止使用 `Label` 做逻辑分支。
|
||||
- 已上线字典的编码和字典项 `Value` 默认不可变;下线优先禁用,不直接删除。
|
||||
- 禁止出现数据库存 `1/2/3`,但没有对应字典文档说明语义。
|
||||
- 禁止代码新增枚举值,但未同步字典文档和字典数据。
|
||||
|
||||
## MD 模板
|
||||
|
||||
.ai-specs/doc-dict/<dict-code>.md
|
||||
```md
|
||||
# <字典中文名>
|
||||
|
||||
- 模块:<module>
|
||||
- 字典编码:`<module>_<field>`
|
||||
|
||||
| Label | Value | Sort | Status | Desc |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| <中文名> | `<machine_value>` | 10 | true | <说明> |
|
||||
```
|
||||
|
||||
## 代码模板
|
||||
|
||||
```go
|
||||
package <module>
|
||||
|
||||
type <TypeName> string
|
||||
|
||||
const (
|
||||
<TypeName><Item1> <TypeName> = "<value_1>"
|
||||
<TypeName><Item2> <TypeName> = "<value_2>"
|
||||
)
|
||||
```
|
||||
|
||||
## 与 SQL 的关系
|
||||
|
||||
- 值域字段先有字典定义,再进入表设计。
|
||||
- 值域字段如何落库,按 `.ai-specs/sys-specs/business-table-spec.md` 执行。
|
||||
77
server/.ai-specs/sys-specs/business-table-spec.md
Normal file
77
server/.ai-specs/sys-specs/business-table-spec.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 业务表 SQL 规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
- 当任务是新增/修改 `.ai-specs/doc-sql/*.md` 时,先读本文件。
|
||||
- 当前项目关系型数据库默认是 `PostgreSQL`,`doc-sql` 的 `建议 SQL` 默认输出 `PostgreSQL` 写法。
|
||||
- 涉及状态、类型、级别、来源、模式、分类等值域字段时,必须先读 `.ai-specs/sys-specs/business-dictionary-spec.md`,并先补 `.ai-specs/doc-dict/<dict-code>.md`。
|
||||
|
||||
## 强制输出
|
||||
|
||||
- 文档路径固定:`.ai-specs/doc-sql/<table_name>.md`
|
||||
- 文档结构固定只保留:`# 标题`、`## 基本信息`、`## 建议 SQL`
|
||||
- `## 基本信息` 固定顺序:`模块` → `表名` → `模型` → `迁移接入` → 按需补 `字典` 行 → `职责`
|
||||
- 有字典字段时,每个字典单独占一行:`- <字段中文名>字典:\`<dict-code>\``
|
||||
- `## 建议 SQL` 前的说明固定写为:`> 以下 SQL 以当前项目 PostgreSQL 为准,主要表达字段、约束和索引语义;实际落库以 \`GORM Model\` 和 \`initialize/gorm_biz.go\` 为准。`
|
||||
- `SQL` 代码块固定按这个顺序组织:`CREATE TABLE` → `COMMENT ON TABLE` → `COMMENT ON COLUMN` → `CREATE UNIQUE INDEX` / `CREATE INDEX`
|
||||
- 表名、字段名、索引名统一使用 `snake_case`
|
||||
- 唯一索引命名固定:`uk_<table_name>_<field>`
|
||||
- 普通索引命名固定:`idx_<table_name>_<field>`
|
||||
- 字典字段只存字典项 `Value`,禁止存 `Label`
|
||||
- 文档只写已确认字段、约束、索引;禁止再补 `字段设计`、`索引设计`、`关联关系`、`删除与兼容`、`自检` 等重复章节
|
||||
|
||||
## PostgreSQL 写法
|
||||
|
||||
- 主键自增优先写 `bigserial PRIMARY KEY`
|
||||
- 时间字段优先写 `timestamp with time zone`
|
||||
- 注释统一写 `COMMENT ON TABLE`、`COMMENT ON COLUMN`
|
||||
- 索引统一单独写 `CREATE UNIQUE INDEX`、`CREATE INDEX`
|
||||
- 禁止混入 `AUTO_INCREMENT`、反引号、`ENGINE=InnoDB`、行内 `COMMENT`、`UNIQUE KEY`、`KEY ...` 等 `MySQL` 方言
|
||||
|
||||
## 输出模板
|
||||
|
||||
````md
|
||||
# <表中文名>
|
||||
|
||||
## 基本信息
|
||||
|
||||
- 模块:<module>
|
||||
- 表名:`<table_name>`
|
||||
- 模型:`model/<module>/<file>.go`
|
||||
- 迁移接入:`initialize/gorm_biz.go`
|
||||
- <字段中文名>字典:`<dict-code>`
|
||||
- 职责:<一句话职责>
|
||||
|
||||
## 建议 SQL
|
||||
|
||||
> 以下 SQL 以当前项目 PostgreSQL 为准,主要表达字段、约束和索引语义;实际落库以 `GORM Model` 和 `initialize/gorm_biz.go` 为准。
|
||||
|
||||
```sql
|
||||
CREATE TABLE <table_name> (
|
||||
id bigserial PRIMARY KEY,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
<field_1> <type> <constraint>,
|
||||
<field_2> <type>
|
||||
);
|
||||
|
||||
COMMENT ON TABLE <table_name> IS '<表中文名>';
|
||||
COMMENT ON COLUMN <table_name>.id IS '主键';
|
||||
COMMENT ON COLUMN <table_name>.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN <table_name>.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN <table_name>.<field_1> IS '<字段说明>';
|
||||
COMMENT ON COLUMN <table_name>.<field_2> IS '<字段说明>';
|
||||
|
||||
CREATE UNIQUE INDEX uk_<table_name>_<field_1> ON <table_name> (<field_1>);
|
||||
CREATE INDEX idx_<table_name>_<field_2> ON <table_name> (<field_2>);
|
||||
```
|
||||
````
|
||||
|
||||
- 没有字典字段,就删除对应“字典”行
|
||||
- 没有唯一索引或普通索引,就删除对应 SQL 行
|
||||
- 如果字段默认值来自字典,直接写字典项 `Value`,例如 `DEFAULT 'enabled'`
|
||||
|
||||
## 复刻目标
|
||||
|
||||
- 只看本文件,应能直接写出类似 `.ai-specs/doc-sql/book_author.md` 的文档
|
||||
- 先定字典,再写 `doc-sql`,再进入 `Model`、迁移和业务代码
|
||||
20
server/.ai-specs/sys-specs/module-naming-spec.md
Normal file
20
server/.ai-specs/sys-specs/module-naming-spec.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 模块命名规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
- 当任务涉及新增/修改业务模块命名、中英文对照、目录命名、表前缀、字典编码时,先读本文件。
|
||||
|
||||
## 强制规则
|
||||
|
||||
- 模块中文名与英文名的映射统一登记在本文件;禁止同一业务出现多个英文命名。
|
||||
- 同一业务模块在 `router`、`api`、`service`、`model`、表名、字典编码、接口路径中必须使用同一英文词根;禁止混用近义词或复数变体。
|
||||
- 模块英文名统一使用小写登记值;需要拼接时,按目标位置使用既有规范展开,不单独发明新缩写。
|
||||
- 新增模块词汇时,必须先更新本文件,再进入代码实现。
|
||||
- 已登记模块命名默认视为稳定约束;如需改名,必须先更新本文件并评估受影响目录、表、接口、字典和文档。
|
||||
|
||||
## 模块词汇
|
||||
|
||||
| 中文 | 英文 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| 书籍 | book | 书籍与章节相关业务模块 |
|
||||
| 设备 | device | 设备管理模块 |
|
||||
Reference in New Issue
Block a user