签入版本

This commit is contained in:
小蚂蚁云
2026-10-20 21:53:26 +08:00
parent 5cc3da657c
commit b33799aa63
427 changed files with 32547 additions and 1 deletions
@@ -0,0 +1,54 @@
# 12.1 API 响应规范
:::tip 概述
必须遵守的响应契约:统一信封四字段、code 语义、data 形态、命名驼峰(含下划线例外)。机制详解见 [8.1 统一响应格式](../api-spec/response),本节为强制规范条目。
:::
## 强制规范
```text
1. 全部接口返回 {code, ok, msg, data} 四字段信封——禁止裸数组/裸对象
2. 成功 code=0;失败 code=1(细分码在服务端语义,401 原样返回)
3. data 失败时为 {}(非 null);records 为空时为 [](非 null)
4. 分页统一 PageData[T]:{records, total, size, current, pages}
5. 新增返回 {id};修改/删除返回 {}
6. 出入参 json 一律驼峰
唯一例外:登录/令牌接口返回 access_token/refresh_token/expires_in/token_type
(OAuth2 惯例,前端按此读取,禁止"修正"为驼峰)
7. 业务错误经 gerror 上抛由包装层统一 Fail——禁止业务代码手写 JSON 响应
8. 未认证必须 code=401 且 HTTP 401(前端跳登录依赖此约定)
```
## 代码层对应(防呆)
```go
// 响应由 Bind 包装层统一写出——业务只管返回出参与 error
func (s *sExample) Add(ctx context.Context, req *v1.ExampleAddReq) (*v1.ExampleIdRes, error) {
id, err := exampleSpec.Add(ctx, req)
if err != nil {
return nil, err // 包装层 → response.Fail
}
return &v1.ExampleIdRes{Id: id}, nil // 包装层 → response.Ok
}
```
## Review 检查项
```text
□ 新接口返回类型是否用 PageData/IdRes/空结构体(而非自造形态)
□ 错误是否 gerror 上抛(而非手写 {code:1})
□ 字段 json 标签是否驼峰
□ 是否误改了登录接口的下划线字段
□ msg 是否用户可读(前端会弹出)
□ data 为 nil 的路径是否验证过(框架兜底 {} / [])
```
## 反例(禁止)
```text
❌ {"status":200, "result": {...}} 自造信封
❌ {"code":0, "data":null} data null(应 {} / [])
❌ {"code":1003,"msg":"密码错误"} 直接返回细分码 (除 401 外应降级 1,细分在 msg/日志)
❌ 裸数组 [ {...} ] 作为响应体 (应包信封)
❌ {"access_token":...} 改为 accessToken (破坏 OAuth2 惯例)
```
@@ -0,0 +1,86 @@
# 12.6 后端编码规范
:::tip 概述
后端规范汇总:分层职责、命名、错误处理、事务纪律、注释风格。多数条目在前文有机制详解,本节为速查总表。
:::
## 分层与依赖
```text
□ controller 薄层:只透传,无业务/无 dao/无手写响应
□ service 接口手维护:方法签名变更同步接口文件;禁 gf gen service 整体运行
□ logic 差异进 spec 声明与钩子;装不下就手写(逃生舱)
□ dao.Ctx 唯一查询入口;自定义 dao 方法也必须 Ctx 起手
□ 依赖红线:task→logic 单向;middleware 经 wire 接线;migrate 不走 dao
□ base.go 只依赖 dao/utility/consts;导出 ≤15
```
## 命名
```text
□ 文件:表名去 goframe_ 前缀 snake_case(config_item.go)
□ 控制器:CXxx(CConfigItem);logic 实现:sXxx
□ spec 变量:xxxSpec;字典映射:xxxSerializeMaps
□ service:IXxx 接口 / Xxx() 取用 / RegisterXxx() 注册
□ 权限码:sys:{module小驼峰}:{action}
□ 缓存键:easygoadmin: 前缀(consts/cache_key.go 统一)
□ json:驼峰(登录/令牌四字段下划线例外)
```
## 请求与响应
```text
□ 每接口一 Req/Res 结构体;内嵌 gmeta.Meta 全标签
□ 校验用 v 标签(required#文案);复杂校验在钩子
□ 分页 PageData;新增 {id};空结果 struct{}
□ 出参字段 dc 注释 + 行注释(OpenAPI 文档质量)
```
## 错误与日志
```text
□ gerror 上抛(NewCodef 语义码+消息);不吞 err
□ 钩子错误记 stage 日志后原样上抛
□ fmt.Println 禁用——用 glog(带上下文)
□ 敏感字段(password/salt/token)不进日志
□ panic 只在编程错误(mustAs 失败等);运行期错误返回 error
```
## 事务与并发
```text
□ WithTrans 包裹多表写;错误自动回滚
□ 事务内禁 HTTP/文件 IO;bcrypt 事务外预计算
□ 共享 map 加锁;goroutine 有生命周期管理
□ 大数据量用 ChunkQuery 分批;循环内禁止查库(批查)
```
## 注释风格
```text
□ 按当前功能直述(禁比较/来源痕迹/历史考古)
□ 导出函数/类型有注释(godoc 风格,首句概括)
□ 关键设计决策注释"为什么"(而非复述代码)
□ 生成物注释保留工具标记(勿删头注释)
```
## 静态门禁
```sh
go build ./... # 编译
go vet ./... # vet
test -z "$(gofmt -l .)" # 格式(CI 同款)
go test ./... # sqlite + hooks MySQL 集成测试
```
## 反例速查
```text
❌ g.DB().Table("goframe_user").Where(...) 绕过 dao
❌ .Where("del_flag", 0) 手写 应由 Ctx 注入
❌ WithTrans 内 bcrypt 拉长锁
❌ for range ids { dao.Xxx.One } N+1
❌ err 被 _ 丢弃 吞错误
❌ 接口返回 map[string]any 拼装 应有结构体契约
❌ 在钩子里 if module == "user" 基座禁止按模块分支
```
@@ -0,0 +1,72 @@
# 12.5 代码审查清单
:::tip 概述
Review 按"架构红线 → 安全 → 正确性 → 规范"四层组织。红线项(绕过 dao/反向依赖等)任何一项都应直接打回。
:::
## 架构红线(一票否决)
```text
□ 绕过 dao.Ctx 直连 g.DB() 查业务表
□ 手写 del_flag / tenant_id 条件
□ 租户级新表未登记 hooks.tenantTables
□ logic 反向 import internal/task
□ middleware 直接 import 业务包(应走 wire.go 函数变量)
□ internal/migrate 走 dao/logic(应 gdb 裸连)
□ base.go 导出函数超 15 个(应拆子包)
□ 整体运行 gf gen service 覆盖手工接口文件
□ 唯一预检范围 ≠ 唯一索引范围(Group 缺失)
□ gmeta path 带 /v1 版本段 / public 白名单三处不一致
```
## 安全 Review
```text
□ 新接口声明了 permission(或评审确认留空=登录即可)
□ 权限码符合 sys:{module小驼峰}:{action}
□ 入参有 gvalid 校验(required/范围/格式)
□ 敏感字段不出接口(password/salt)
□ 日志不打印密码/令牌(maskSecret 覆盖)
□ SQL 无字符串拼接(全参数化)
□ 文件上传走白名单+嗅探链(未自造上传口)
□ 富文本处理未破坏 [IMG_URL] 契约 / 未引入外站抓取(SSRF)
□ 新缓存键有失效点或短 TTL(对照 4.8 原则)
```
## 正确性 Review
```text
□ 事务边界正确(bcrypt/HTTP/文件 IO 不在事务内)
□ 钩子内无 append / 无 IO / 错误上抛
□ 错误处理不吞 err(不 _ = 忽略关键错误)
□ 分页走 PageData;空集 []
□ 导出走 applyFilters(导出=所见)
□ N+1 查询(循环内查库)→ 批查改造(userExpandNames 模式)
□ 并发安全(共享 map 加锁;goroutine 泄漏)
□ LastInsertId 回主键(禁雪花)
□ 前端三件套对齐(querySchemas/Req/spec)
□ 前端按钮 v-permission 对齐 gmeta
```
## 规范 Review
```text
□ 文件命名 snake_case(后端)/ 驼峰(前端)
□ 注释按当前功能直述(无比较/来源痕迹)
□ json 驼峰(下划线仅登录/令牌例外)
□ service 接口同步更新(手维护)
□ 日志标题 "{模块中文名}-{动作}"、logType 正确
□ gofmt / go vet / vue-tsc / eslint 通过
□ 文档同步(接口清单/表清单/配置项有变更时)
□ 破坏性变更走版本化说明 + 黄金样本更新
```
## Review 实践建议
```text
小 PR(<400 行)优先;大重构拆分提交
先看设计(spec 声明/钩子划分)再看细节
生成器产物看增量 diff(不逐行 review 模板产物)
测试:修复类看是否有回归测试;新功能看冒烟路径
评论给"为什么+建议",不只给"改成什么"
```
@@ -0,0 +1,80 @@
# 12.4 数据删除规范
:::tip 概述
删除一律软删(del_flag=1,由全局钩子强制改写);物理删除只在定时清理任务中出现。删除守卫保护引用完整性。本节为强制规范与操作红线。
:::
## 强制规范
```text
1. 删除接口全部软删:deleteHook 把物理 DELETE 改写为 UPDATE del_flag=1
——业务代码无需(也不允许)自己写 del_flag 更新
2. 查询一律经 dao.Ctx(自动过滤 del_flag=0)
——禁止绕过 dao 直连 g.DB()(已删数据会"复活"可见)
3. 禁止手写 del_flag 条件(手写易漏:忘 filter 或写错值)
4. 物理删除仅允许出现在清理类定时任务(LogClean/CleanTemp)
——业务代码禁止 Data/Delete 真实物理删除
5. 删除前守卫:存在子级/引用时拒绝(1011/1014),先处理关联
6. 唯一索引含 del_flag(软删行不占唯一坑位——uk 设计规范)
```
## 守卫模式(真实模块)
```text
dept:存在子部门拒绝(1011);存在用户引用拒绝
menu:存在子菜单拒绝(1011)+ 同步清理 role_menu(防悬挂授权)
role:存在用户引用拒绝(1014/1011 语义)
user:删除守卫 + 清理 user_role 关联
category:存在子分类/文章引用检查
```
spec 钩子:`BeforeDelete`(守卫)→ 基座软删 → `AfterDelete`(关联清理/缓存清理)。
## 软删的工程含义
```text
可恢复:del_flag 改回 0 即恢复(无官方恢复接口时可 DB 操作/数据修复)
存储增长:软删行永久占用——靠唯一索引含 del_flag 设计兼容
唯一性:uk 含 del_flag → 同名删除后可再建(软删行不占坑)
审计:create_user/update_user 保留最后操作人;恢复时注意盖章语义
```
## 数据清理策略
```text
日志三表(operation_log/login_log/job_log):
LogClean 任务每日 02:30 物理删除超保留期记录
保留天数 job.logRetentionDays(默认 90),任务参数 retentionDays 可覆盖
单批 5000 行循环;单表单次上限 20 万行(防长事务)
业务表软删数据:
默认不清理(可恢复性优先)
如需归档清理:新建归档任务(参考 LogClean 模式),先归档再物理删
清理前备份
```
## Review 检查项
```text
□ 删除走 spec.Delete/手写 dao.Ctx 软删(无物理 DELETE)
□ BeforeDelete 守卫齐全(子级/引用)
□ AfterDelete 关联清理(user_role/role_menu 等)
□ 唯一索引含 del_flag(新建表时)
□ 无绕过 dao 的删除路径
□ 批量删除逐条守卫(非绕过逐条检查的批量 SQL)
```
## 反例(禁止)
```text
❌ dao.Xxx.Ctx(ctx).Where(...).Delete() 未确认 deleteHook 生效(绕过 dao 时物理删除!)
❌ UPDATE goframe_xxx SET del_flag=1 手写(应交给钩子)
❌ 清理任务直接 DELETE 大表无分批(长事务锁表)
❌ 唯一索引不含 del_flag(软删后无法再建同名)
❌ 删除后不清理关联(悬挂 role_menu/user_role)
```
@@ -0,0 +1,83 @@
# 12.7 前端编码规范
:::tip 概述
前端规范汇总:三件套对齐、组件使用、权限、状态管理、类型与样式。配套工具链:ESLint + Prettier + Stylelint + vue-tsc + commitlint。
:::
## 三件套与契约(最高优先级)
```text
□ api url 对齐 gmeta path(无 /api 前缀重复、无 /v1)
□ querySchemas field ↔ PageReq json ↔ LikeFields/EqFields(三件同步)
□ columns prop ↔ Item/DetailRes json
□ edit 表单 ↔ AddReq/UpdateReq(全量提交语义)
□ 按钮 v-permission ↔ gmeta permission(复制粘贴防拼错)
□ 枚举列展示 {field}Text(不硬编码文案)
□ 登录读 data.access_token(下划线)
```
## 组件使用
```text
□ 列表用 Page 容器 + Table(columns 配置) + pagination
□ 复杂列用 render 函数(h + Element Plus 组件)
□ 筛选用 querySchemas 配置(勿手写搜索区)
□ 下拉选项优先字典驱动(getDictItemList)
□ 上传用 Upload 组件(持 URL,后端转正)
□ 图片展示用后端已补域名字段(GetFileUrl)
□ 弹窗用 Modal 封装;确认删除用 ElMessageBox
```
## 状态与逻辑
```text
□ 状态走 Pinia store(token/用户/路由/标签页);勿全局变量
□ 本地存储统一 storage 封装 + mutation-types 常量
□ HTTP 统一 http.request(勿绕过封装裸 axios)
□ 公共逻辑抽 hooks/;工具函数进 utils/
□ 类型:接口出入参定义类型;跑通 pnpm type:check
```
## 样式
```text
□ scoped 样式;颜色用 Element CSS 变量(随主题)
□ 布局用栅格组件;少 deep 覆盖
□ 样式改动过 stylelint
```
## 质量门禁
```sh
pnpm type:check # vue-tsc
pnpm lint:eslint # ESLint
pnpm lint:prettier # 格式化
pnpm lint:stylelint # 样式
pnpm build # 构建验证
```
## 反例速查
```text
❌ 直接 axios.get('/api/user/page') 绕封装 + url 重复 /api
❌ options: [{label:'正常',value:1}] 硬编码 应字典驱动
❌ status 直接展示(显示 1/2) 应展示 statusText
❌ 按钮无 v-permission 权限裸奔(UX 层缺失)
❌ 每次请求重新登录拿 token 用 refresh 轮换
❌ 组件内大量业务逻辑 抽 hooks/store
❌ 直接 localStorage.setItem('token') 统一 storage + 常量
❌ 页面硬编码颜色 #1890ff 用 var(--el-color-primary)
❌ 布尔/枚举用魔法数字散落 用 enums 常量
```
## Review 检查项
```text
□ 新页面走三件套结构(index/edit/detail/columns/querySchemas)
□ 接口函数 JSDoc 注释(生成器同款风格)
□ 列表空态/加载态/错误态处理
□ 表单校验(前端提示 + 对齐后端 v 标签)
□ 删除/危险操作二次确认
□ 权限变更后按钮正确显隐(超管/普通角色双测)
□ type:check 与 build 通过
```
@@ -0,0 +1,92 @@
# 12.3 Git 提交规范
:::tip 概述
提交遵循 Conventional Commits(前端工程内置 commitlint + husky 校验)。规范的提交信息支撑自动生成变更日志与可追溯的历史。
:::
## 提交格式(Conventional Commits)
```text
<type>(<scope>): <subject>
<body>
<footer>
```
示例:
```text
feat(user): 用户列表支持职级筛选
- querySchemas 新增 levelId 选择器
- UserPageReq 增加 levelId 字段并接入 EqFields
Closes #123
```
## type 类型
| type | 含义 |
|------|------|
| feat | 新功能 |
| fix | 缺陷修复 |
| docs | 文档 |
| style | 格式(不影响逻辑) |
| refactor | 重构(非新功能非修复) |
| perf | 性能优化 |
| test | 测试 |
| build | 构建/依赖 |
| ci | CI 配置 |
| chore | 杂项 |
| revert | 回滚 |
## scope 建议(对齐模块)
```text
后端:user/role/menu/dept/…(模块名)/ middleware/hooks/generator/migrator/base
前端:api/views/store/router/components…
其他:config/docs/ui…
```
## 提交纪律
```text
1. 一个提交一件事(功能/修复不混杂重构)
2. 提交前本地过门禁:go build/vet/gofmt + go test;pnpm type:check/eslint
3. 生成器产物与手工修改分开提交(或提交信息注明)
4. 破坏性变更(改名/删字段/改枚举值)提交信息标注 BREAKING CHANGE:
5. 敏感信息(密钥/密码/个人数据)严禁入仓
6. 中文提交信息(团队约定)subject 不超 50 字
```
## 分支与合并
```text
main 可发布主干(保护分支,PR + CI 必过 + review)
feature/* 功能(从 main 切)
fix/* 修复
hotfix/* 紧急修复(快速通道,双人确认)
release/* 发布分支(打 tag)
合并策略:PR 合并(禁止直接 push main);冲突在 feature 分支 rebase 解决
```
## hook 保障
```text
前端:husky + lint-staged(提交时 lint/格式化)+ commitlint(信息格式校验)
后端:gofmt/vet 经 CI 门禁(可加 pre-commit hook)
```
## 好的提交 vs 差的提交
```text
✅ feat(job): 任务模块支持暂停恢复接口
✅ fix(auth): 修复 refresh 轮换旧令牌未拉黑的重放风险
✅ chore(generator): overwrite 开关默认 false
❌ update (无类型无内容)
❌ 修改了一些东西 (不可追溯)
❌ fix: fix fix fix (无信息量)
❌ 一次提交跨 30 个文件混杂多主题 (无法回滚单个变更)
```
@@ -0,0 +1,68 @@
# 12.2 分页查询规范
:::tip 概述
必须遵守的查询契约:筛选对齐三件套、分页默认值与上限、导出=所见、排序白名单。机制详解见 [8.3 分页查询规范](../api-spec/pagination)。
:::
## 强制规范(三件套对齐)
```text
1. 前端 querySchemas.ts 每个筛选输入 ↔ XxxPageReq 同名驼峰字段 ↔ spec 声明
——三处任一缺失即视为筛选缺陷
2. 组件类型与后端声明对应:
字符串 Input → LikeFields(逐列模糊,列名显式声明)
Select/InputNumber → EqFields(零值跳过)
KeywordFields(req.keyword 多列 OR)能力保留在基座,模块不声明,需要时再用
3. 分页:PageReq 内嵌 common.PageReq(pageNo 默认 1;pageSize 默认 10 上限 500)
4. 导出统一走 xxxSpec.applyFilters 与列表同套筛选(导出=所见,含数据权限)
5. 排序:前端排序字段仅 OrderableFields 白名单内生效(防注入)
6. 复杂条件写 Filter 钩子——不重复声明 Like/Eq 已覆盖条件
```
## spec 声明模板(标准形态)
```go
var xxxSpec = ModuleSpec[...]{
Model: dao.Xxx.Ctx,
LikeFields: []string{"name"}, // 对应 querySchemas 的 Input
EqFields: []string{"status", "type"}, // 对应 Select/InputNumber
OrderBy: "id DESC",
// DataScope: ApplyDataScope("dept_id"), // 有部门列且需行级权限时
}
```
## Review 检查项
```text
□ querySchemas 新增输入 → Req 加字段 + spec 加 Like/Eq 声明(三件同步)
□ Req 筛选字段命名与表单 field 完全一致(驼峰)
□ 导出接口 ExportReq 携带同套筛选字段且走 applyFilters
□ 排序字段在白名单内
□ 零值语义确认(status=0 为有效筛选时的处理)
□ 分页上限未绕过(未自定义 pageSize 无上限)
```
## 反例(禁止)
```text
❌ querySchemas 有输入但 Req 无字段(提交了后端不读)
❌ Req 有字段但 spec 未声明(读了不过滤——筛选假生效)
❌ 导出另写查询条件(导出 ≠ 所见)
❌ Filter 里重复写 LikeFields 已覆盖的条件
❌ 前端传 orderField 直通 SQL(必须白名单)
❌ 手写 del_flag/tenant_id 条件混入筛选(dao.Ctx 已注入)
```
## 一致性验证方法
```text
1. 页面输入筛选值 → Network 看请求参数
2. 对照 Req 字段名(驼峰)是否接收
3. 对照 spec 声明过滤是否生效(结果集变化)
4. 导出同一筛选 → Excel 行数 = 列表 total
```