签入版本

This commit is contained in:
小蚂蚁云
2026-10-25 17:24:27 +08:00
parent 48911cc08f
commit fdbeae092a
1010 changed files with 30682 additions and 95274 deletions
@@ -1,135 +1,47 @@
# 12.1 API 响应规范
:::tip 概述
所有接口返回统一的 JSON 结构,前端可据此判断请求结果。成功响应 `code=0`,失败响应 `code=1`(或 401/403/404/422)。
:::tip 必须遵守
所有接口一律返回统一 JSON 信封,对外码值只有 0 / 1 / 401 三值语义。业务代码禁止自造响应形态。
:::
## 统一格式
```typescript
interface ApiResponse {
code: number; // 状态码:0=成功,其他=失败
ok: boolean; // 是否成功
msg: string; // 提示信息
data: any; // 响应数据
}
```
## 成功响应
## 信封
```json
{
"code": 0,
"ok": true,
"msg": "操作成功",
"data": {
"id": 1,
"username": "admin",
"createTime": "2026-01-01 00:00:00"
}
}
{ "code": 0, "ok": true, "msg": "操作成功", "data": {} }
```
## 分页响应
| 码值 | 语义 | 前端行为 |
|---|---|---|
| 0 | 成功 | 取 data |
| 1 | 失败 | 弹 msg |
| 401 | 未认证 | 跳登录(或刷新令牌) |
```json
{
"code": 0,
"ok": true,
"msg": "操作成功",
"data": {
"records": [
{ "id": 1, "username": "admin" }
],
"total": 100,
"size": 20,
"current": 1,
"pages": 5
}
}
```
内部细分码(400/403/429/1xxx)只用于服务端日志与文档,对外统一收敛(401 除外)。
## 失败响应
## 构造规约
```json
{
"code": 1,
"ok": false,
"msg": "用户名已存在",
"data": null
}
```
| 场景 | 写法 |
|---|---|
| 成功带数据 | `response.Ok(data)` |
| 成功自定义文案 | `response.OkMsg(msg, data)` |
| 分页 | `dto.NewPageData(...)` 经信封返回 |
| 业务失败 | `return nil, consts.CodeXxx` / `apperr.Newf(...)` |
| 禁止 | handler 手写 JSON、自定义 code 集、裸写 HTTP 状态 |
## 字段说明
## 禁则
| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | number | 状态码,0=成功 |
| `ok` | boolean | 是否成功 |
| `msg` | string | 提示信息 |
| `data` | any | 响应数据,失败时通常为 null |
| `data.records` | array | 分页数据列表 |
| `data.total` | number | 总记录数 |
| `data.size` | number | 每页条数 |
| `data.current` | number | 当前页码 |
| `data.pages` | number | 总页数 |
- 禁止返回 HTML 错误页(NoRoute/panic 已保证 JSON 兜底)
- 禁止 data 内嵌套第二套 code/msg
- 禁止吞错后返回"成功"空数据
- 禁止在业务层直接 `response.Write`(信封由绑定层与中间件统一负责)
## 驼峰自动转换
## 检查点
响应数据自动从 snake_case 转为 camelCase:
- 新接口响应经信封且 code 语义正确
- 前端只按 0/1/401 三分支处理
- 错误 msg 是面向用户的中文提示(不含技术栈细节)
| 数据库字段 | 响应字段 |
|-----------|---------|
| `create_time` | `createTime` |
| `user_name` | `userName` |
| `is_delete` | `isDelete` |
## 相关
可通过 `config/api.php` 的 `camel_snake_convert` 配置关闭。
## 后端构造方式
```php
// Controller 中
return $this->success($data, '操作成功'); // 成功
return $this->success(['id' => $id], '添加成功'); // 成功(带数据)
return $this->fail('用户名已存在'); // 失败
return $this->_page($result); // 分页
```
```php
// Result 类直接调用
Result::success($data, $msg);
Result::page($records, $total, $current, $size);
Result::fail($msg, $code);
Result::unauthorized($msg); // 401
Result::forbidden($msg); // 403
Result::notFound($msg); // 404
Result::validateError($errors); // 422 参数验证失败
```
### validateError 示例
`Result::validateError()` 用于参数验证失败场景,HTTP 状态码为 422,`data` 字段返回具体错误明细,便于前端逐字段提示:
```php
// 直接传字符串
return Result::validateError('用户名不能为空');
// 传数组,包含多个字段错误
return Result::validateError([
'username' => '用户名不能为空',
'email' => '邮箱格式不正确',
]);
```
```json
{
"code": 422,
"ok": false,
"msg": "参数验证失败",
"data": {
"username": "用户名不能为空",
"email": "邮箱格式不正确"
}
}
```
- [8.1 统一响应格式](/zh/api-spec/response.html)
- [8.2 错误码对照表](/zh/api-spec/error-code.html)
@@ -1,120 +1,64 @@
# 12.6 后端编码规范
# 12.8 后端编码规范
:::tip 概述
本节定义后端 PHP 代码的编写规范,包括 Model、Logic、Controller、Validate 各层的命名和结构规范,确保团队代码风格统一。
:::tip 建议遵守
在数据访问铁律与模块规约之上,后端编码的通用风格约定:命名、注释、错误、并发、依赖方向。
:::
## Model 层规范
## 命名
```php
// 继承 BaseModel
class Example extends BaseModel
{
/**
* 对应数据库表名(不含前缀)
*
* @var string
*/
protected $name = 'example';
}
| 对象 | 规约 | 示例 |
|---|---|---|
| 模块文件 | 小写下划线,与表名对应 | user.go、dict_item.go |
| 控制器 | C + 模块名 Pascal | CUser |
| 服务 | s + 模块名 | sUser |
| 仓储 | 模块名 + Repo | UserRepo |
| 声明变量 | 模块名 + Spec | userSpec |
| 常量 | 语义化大驼峰 | CodeRecordNotFound |
| 缓存键 | consts 键函数生成 | CacheKeyCaptcha(uuid) |
## 注释
- 包注释一句说明模块职责;文件头不加横幅
- 导出函数注释以函数名开头,说明行为/约束/降级
- 反直觉处(顺序依赖、红线原因)必须注释"为什么"
- 注释直述现状,不写改造过程与来源对比
## 错误处理
- 业务失败用哨兵/apperr,**原样上抛不 wrap**
- 不吞错只记日志;日志与返回二选一时优先返回
- panic 仅限编程错误(如 ToData 类型不匹配)——运行时输入不 panic
## 并发
- service 单例方法必须并发安全(无请求态字段)
- ctx 贯穿一切(身份/事务/追踪),不用全局变量传请求态
- 缓存读写经 utility/cache(自带降级),不直连 go-redis
## 依赖方向
```text
handler → service → repository → db
utility 被任意层引用;utility 不 import internal
task → service(单向);service 不反向依赖 task
```
## Logic 层规范
禁止反向与横向环依赖;跨模块协作经同包直调或显式接口。
```php
// 系统共享模块继承 BaseLogic
class ExampleLogic extends BaseLogic
{
/**
* 关联的模型类
*
* @var string
*/
protected string $modelClass = Example::class;
## 性能习惯
/**
* LIKE 模糊匹配字段
*
* @var array
*/
protected array $pageLikeFields = ['name'];
- 查询指定列优先(大表 SELECT * 慎用)
- 循环内不做 N+1 查询(批量 IN)
- 大结果集导出走分批,不一次载入
- 热点读经缓存(键走 consts 函数)
/**
* 精确匹配字段
*
* @var array
*/
protected array $pageEqFields = ['status'];
## 提交前
/**
* 新增前处理
*
* @param array $data 待新增的数据
* @return array 处理后的数据
*/
protected function beforeAdd(array $data): array
{
return $data;
}
}
// 租户模块继承 BaseTenantLogic
class UserLogic extends BaseTenantLogic { ... }
```bash
make check # fmt + vet + test-short + check-config
```
## Controller 层规范
## 相关
```php
class ExampleController extends BaseController
{
protected ExampleLogic $logic;
protected function initialize(): void
{
$this->logic = new ExampleLogic();
}
// 使用注解
#[Log('模块-操作', Log::TYPE_ADD)]
#[Permission('sys:example:add', '添加')]
public function add(): Json
{
return parent::_add($this->logic, $this->getJsonBody());
}
}
```
## Validate 层规范
```php
class ExampleValidate extends Validate
{
protected $rule = [
'name' => 'require',
'categoryId' => 'require', // ✅ 驼峰命名
// 'category_id' => 'require', // ❌ 禁止下划线
];
protected $message = [
'name.require' => '名称不能为空',
'categoryId.require' => '分类不能为空',
];
protected $scene = [
'add' => ['name', 'categoryId'],
'update' => ['name'],
];
}
```
:::warning 字段名必须使用驼峰
Validate 中的字段名必须使用 camelCase(驼峰),与前端传参一致。
因为校验发生在 `BaseLogic::beforeAdd` 之后、`array_camel_to_snake` 转换之前,
此时数据键名还是驼峰形式。使用下划线会导致校验规则匹配不到字段。
:::
## 注解规范
- 所有写操作(POST/PUT/DELETE)必须标注 `#[Log]`
- 所有需要权限控制的接口必须标注 `#[Permission]`
- 权限码格式:`sys:{模块名}:{操作}`
- [12.4 模块开发规约](/zh/standards/module-code.html)
- [12.3 数据访问铁律](/zh/standards/data-access.html)
@@ -1,80 +1,48 @@
# 12.5 代码审查清单
# 12.7 代码审查清单
:::tip 概述
代码审查(Code Review)是保证代码质量的重要环节。以下清单涵盖后端和前端的审查要点。
:::tip 建议遵守
审查按清单走:安全红线 → 契约影响 → 分层纪律 → 规约符合度。前四项任一不过即阻塞。
:::
## 后端审查清单
## 阻塞项(必须过)
### Model 层
- [ ] **数据访问**:无裸 `*gorm.DB`;删除走软删;Raw 有场景注释([12.3](/zh/standards/data-access.html))
- [ ] **契约稳定**:dto json 标签名未意外变更;RouteSpec 路径/权限码变更已同步前端与菜单
- [ ] **错误处理**:业务错误原样上抛(无 wrap)、无吞错、无 handler 写响应
- [ ] **安全**:新接口有权限码声明;敏感信息(密码/Token)不入日志;无硬编码密钥
- [ ] **配置**:新增配置键同步 .env.example(check-config 可过)
- [ ] 继承 `BaseModel`
- [ ] 表名 `$name` 配置正确
- [ ] 关联关系定义正确(如有)
- [ ] 无硬编码的数据库前缀
## 规约项
### Logic 层
- [ ] 模块四件套齐全、命名符合 [12.4](/zh/standards/module-code.html)
- [ ] handler 只有委托;逻辑在 service;差异进 ModuleSpec 声明/钩子
- [ ] 钩子无 HTTP/文件 IO、无 append
- [ ] 事务用 WithTrans 且回调内用入参 ctx
- [ ] 分页符合 [12.2](/zh/standards/pagination.html);响应信封符合 [12.1](/zh/standards/api-response.html)
- [ ] 字典/枚举显示走 SerializeMaps 或字典接口,无硬编码文案映射
- [ ] 注释按当前功能直述(无改造过程表述)
- [ ] 继承正确的基类(`BaseLogic` 或 `BaseTenantLogic`)
- [ ] `$modelClass` 配置正确
- [ ] 查询条件配置合理(`pageLikeFields` / `pageEqFields`)
- [ ] 排序配置合理(`pageOrderBy`)
- [ ] 唯一性校验配置(`uniqueFields`)
- [ ] 枚举映射配置(`serializeMaps`)
- [ ] 文件字段配置(`fileFields` / `fileSaveDir`)
- [ ] 钩子方法中无 SQL 注入风险
- [ ] 事务使用正确(`startTrans` / `commit` / `rollback`)
## 前端项
### Controller 层
- [ ] api 模块 URL 不带 /api 前缀,命名与后端动作对应
- [ ] 按钮权限用 v-perm/v-perms,码值与菜单节点一致
- [ ] 表单字段与 dto json 名一致(全量提交语义)
- [ ] 未绕过 http 封装直接用 axios
- [ ] 继承 `BaseController`
- [ ] `initialize()` 中实例化 Logic
- [ ] 所有接口标注 `#[Permission]` 注解
- [ ] 所有写操作标注 `#[Log]` 注解
- [ ] 权限码格式正确(`sys:模块名:操作`)
- [ ] 异常处理完善(try-catch)
- [ ] 参数校验充分
## 测试与工程
### 安全
- [ ] 新逻辑有配套测试(或说明为何不测)
- [ ] `make check` / 前端 type+lint 本地通过
- [ ] 生成器产物与模板改动同提交
- [ ] 提交信息符合 [12.5](/zh/standards/git.html)
- [ ] 无 SQL 注入风险(使用 ORM 参数绑定)
- [ ] 无 XSS 风险(输出转义)
- [ ] 敏感数据不暴露(password、salt)
- [ ] 文件上传有类型和大小限制
- [ ] 权限校验覆盖所有接口
## 审查沟通原则
## 前端审查清单
- 挑刺对代码不对人;给出理由与替代方案
- 优先级:正确性 > 安全 > 契约 > 可读性 > 风格
- 争议收敛到规约文档——这次讨论的结论应能写进本章某页
### API 模块
## 相关
- [ ] 函数命名规范(`getXxxPage` / `xxxAdd` / `xxxUpdate` / `xxxDelete`)
- [ ] HTTP 方法正确(GET/POST/PUT/DELETE)
- [ ] 参数传递方式正确(params / data)
- [ ] 错误处理完善
### 页面组件
- [ ] 使用 `<script setup lang="ts">` 语法
- [ ] Props 有类型定义
- [ ] 表单有验证规则
- [ ] 删除操作有确认弹窗
- [ ] 加载状态处理(loading)
- [ ] 空数据处理
### 权限
- [ ] 按钮级权限使用 `v-perm` 指令
- [ ] 权限码与后端一致
### 性能
- [ ] 列表数据分页加载
- [ ] 图片懒加载
- [ ] 组件按需引入
## Git 提交审查
- [ ] commit message 格式正确
- [ ] 一个 commit 只做一件事
- [ ] 无调试代码残留(console.log、dump)
- [ ] 无敏感信息泄露(密码、密钥)
- [12.8 后端编码规范](/zh/standards/backend-code.html)
- [12.9 前端编码规范](/zh/standards/frontend-code.html)
@@ -0,0 +1,71 @@
# 12.3 数据访问铁律
:::tip 必须遵守
数据访问的四条红线:**禁止裸用 `*gorm.DB`、一切删除走软删除、Raw 受限使用、事务统一 WithTrans**。违反任何一条都会破坏租户隔离或审计完整性。
:::
## 铁律一:禁止裸用 *gorm.DB
```go
// 正确
repository.NewUserRepo().Ctx(ctx).Where(...).Find(...)
// 禁止
db.DB.Where(...) // 全局句柄直用
gorm.Open(...) // 业务层自建连接
```
`Ctx(ctx)` 是唯一常规入口——软删过滤与租户隔离在此注入,绕过即裸奔。
## 铁律二:一切删除走软删除
```go
repo.SoftDelete(ctx, "id", id) // UPDATE del_flag=1
repo.SoftDeleteByID(ctx, id)
```
物理 DELETE 被全局钩子拦截。确需物理清理(定时任务清超期日志):
```go
ctx = hooks.WithPhysicalDelete(ctx) // 显式放行,作用域仅此链路
```
## 铁律三:Raw 仅限明确场景
`Raw()` 不注入任何 Scope(已删数据可见、跨租户可见),仅限:
- 导入(批量落库)
- 字典管理(平台级维护)
- 登录校验(公开路由无租户身份)
使用处必须注释说明场景正当性,且**条件完整性由使用者自行保证**。
## 铁律四:事务统一 WithTrans
```go
db.WithTrans(ctx, func(ctx context.Context) error {
// 回调内一律用入参 ctx(携带事务连接)
// 返回非 nil 错误整体回滚
})
```
跨模块写操作必须同事务;钩子在事务内同样生效(审计盖章一致)。
## 常见违反与后果
| 违反 | 后果 |
|---|---|
| 裸 DB 查询 | 查到已删数据、跨租户数据泄露 |
| 业务代码物理删除 | 审计链断裂,无法追责 |
| Raw 当常规用 | 同裸 DB,且逐渐扩散 |
| 事务用外层 ctx | 事务内查询落回连接池,回滚不生效 |
## 审查抓手
PR 审查 grep 三板斧:`gorm.DB`(层内裸用)、`\.Delete(`(物理删除)、`Raw(`(受限入口)。
## 相关
- [3.4 数据访问层(repository)](/zh/guide/repository.html)
- [4.8 横切机制](/zh/architecture/cross-cutting.html)
- [12.6 数据删除规范](/zh/standards/deletion.html)
+34 -136
View File
@@ -1,156 +1,54 @@
# 12.4 数据删除规范
# 12.6 数据删除规范
## 软删除约定
:::tip 核心原则
所有业务数据统一使用软删除机制,通过 `is_delete` 字段标记记录状态,不物理删除数据。
:::tip 建议遵守
删除的默认形态是软删除;物理删除是受控例外。删除前做引用守卫,删除后可追溯。
:::
| is_delete 值 | 含义 |
|--------------|------|
| `0` | 正常记录(默认值) |
| `1` | 已删除记录 |
## 默认:软删除
所有查询方法自动过滤 `is_delete = 0`,业务层无需关心已删除数据。
```go
// repository 标准入口
repo.SoftDelete(ctx, "id", id)
repo.SoftDeleteByID(ctx, id)
## 单条删除
### URL 格式
```text
DELETE /api/{module}/delete/{id}
// 批量:基座 BatchDelete(逐条软删)
spec.BatchDelete(ctx, ids)
```
### Controller 示例
软删后对查询不可见(Ctx 注入 del_flag=0),数据保留可审计、可恢复(运维按需 UPDATE 回 0)。
```php
#[Log('案例演示-删除记录', Log::TYPE_DELETE, '删除案例演示ID:{id}')]
#[Permission('sys:example:delete', '删除案例演示')]
public function delete(int $id): Json
{
return parent::_remove($this->logic, $id);
}
## 例外:物理删除
仅限**数据本身有保留期语义**的场景(超期日志、临时文件索引):
```go
ctx = hooks.WithPhysicalDelete(ctx) // 显式放行
```
### 内部流程
放行要收敛在最小函数作用域,注释说明依据(如"超过 job.logRetentionDays")。
```text
Controller::delete($id)
│
▼
BaseController::_remove($logic, $id)
│
▼
BaseLogic::delete($id)
│
├─ beforeDelete($id) 钩子(抛异常可拦截)
│
├─ $model->find($id)
│ └─ 不存在 → return false
│
├─ $model->save(['is_delete' => 1])
│
└─ afterDelete($id) 钩子
```
## 删除守卫
## 批量删除
删除被引用数据前做存在性检查,冲突时上抛守卫错误:
### 请求格式
| 错误码 | 场景 |
|---|---|
| CodeHasChildren | 树形表存在下级(部门/菜单/分类) |
| CodeInUse | 关键记录仍被引用(用户在角色中等) |
```text
DELETE /api/{module}/batchDelete
Content-Type: application/json
守卫检查用 `ExistsByCond`(软删/租户已过滤)。
{ "ids": [1, 2, 3] }
```
## 前端交互
### Controller 示例
- 危险删除(批量、不可逆)必须二次确认
- 删除失败展示后端 msg(守卫原因)
- 不做"假删除"(前端移除行但不调接口)
```php
#[Log('案例演示-批量删除', Log::TYPE_DELETE)]
#[Permission('sys:example:delete', '删除案例演示')]
public function batchDelete(): Json
{
$ids = $this->getJsonBody()['ids'] ?? [];
return parent::_batchRemove($this->logic, $ids);
}
```
## 审计要求
### 内部流程
删除操作经 RouteSpec Log/LogType=delete 留操作日志——谁删了什么可追溯。物理删除场景本身不落业务表,但任务日志留有执行记录。
```text
Controller::batchDelete()
│
▼
BaseController::_batchRemove($logic, $ids)
│
├─ ids 为空 → 返回失败 "请选择要删除的数据"
│
▼
BaseLogic::batchDelete($ids)
│
├─ beforeBatchDelete($ids) 钩子(整体校验)
│
├─ 逐条 beforeDelete($id) 校验
│
├─ Model::batchSoftDelete($ids)
│ └─ UPDATE SET is_delete = 1 WHERE id IN (...)
│
└─ afterBatchDelete($ids) 钩子
```
## 相关
## 删除前校验(beforeDelete 钩子)
在 Logic 中重写 `beforeDelete` 方法,抛异常可拦截删除:
```php
class UserLogic extends BaseTenantLogic
{
protected function beforeDelete(int $id): void
{
// 检查是否有子级
$children = Dept::where('parent_id', $id)->where('is_delete', 0)->count();
if ($children > 0) {
throw new \Exception('存在子部门,不可删除');
}
// 检查是否被引用
$userCount = User::where('dept_id', $id)->where('is_delete', 0)->count();
if ($userCount > 0) {
throw new \Exception('该部门下有用户,不可删除');
}
}
}
```
## 何时可物理删除
| 场景 | 方式 | 说明 |
|------|------|------|
| 日志数据清理 | 定时任务 | CleanLogTask 清理 N 天前的日志 |
| 临时数据 | 定时任务 | 清理过期的临时文件 |
| 其它 | 审批 | 需经过管理员审批 |
## 前端删除规范
### 单条删除
```vue
<el-popconfirm title="确认删除该记录?" @confirm="handleDelete(row.id)">
<template #reference>
<el-button type="danger" size="small">删除</el-button>
</template>
</el-popconfirm>
```
### 批量删除
```vue
<el-button
type="danger"
:disabled="!selectedIds.length"
@click="handleBatchDelete"
>
批量删除 ({{ selectedIds.length }})
</el-button>
```
- [12.3 数据访问铁律](/zh/standards/data-access.html)
- [4.8 横切机制](/zh/architecture/cross-cutting.html)
@@ -1,142 +1,64 @@
# 12.7 前端编码规范
# 12.9 前端编码规范
:::tip 概述
前端编码规范涵盖 Vue 组件、TypeScript、API 模块、样式和代码风格等方面。
:::tip 建议遵守
前端通用风格约定:分层隔离、命名一致、组件复用、类型渐进。配合 `pnpm type:check` 与 `pnpm lint:eslint` 门禁。
:::
## Vue 组件规范
## 分层纪律
```vue
<!-- 推荐的组件结构 -->
<template>
<!-- 模板内容 -->
</template>
| 层 | 禁止 |
|---|---|
| views 页面 | 直接 axios、写信封判断、放公共组件 |
| api 模块 | UI 逻辑、持 Token、catch 兜底(拦截器已做) |
| components 公共组件 | 耦合具体业务模块 |
| store | 存可从接口推导的数据 |
| utils | import api/views/store(环依赖) |
<script setup lang="ts">
// 使用 <script setup lang="ts"> 语法
import { ref, onMounted } from 'vue';
## 命名
// Props 定义
interface Props {
id: number;
name?: string;
}
const props = withDefaults(defineProps<Props>(), {
name: '',
});
| 对象 | 规约 | 示例 |
|---|---|---|
| api 文件 | 域/模块.ts | system/user.ts |
| api 函数 | 模块+动作 | userPage、userAdd |
| 页面目录 | 域/模块/ | views/system/user/ |
| 组件 | PascalCase | UserTable.vue |
| hooks | use-*.ts | use-async.ts |
| store | camelCase 模块 | asyncRoute.ts |
| 权限码 | 与后端逐字一致 | sys:user:add |
// 响应式数据
const loading = ref(false);
const tableData = ref<any[]>([]);
## 组件实践
// 方法
const fetchData = async () => {
loading.value = true;
try {
const res = await getExamplePage({ pageNo: 1, pageSize: 20 });
tableData.value = res.records;
} finally {
loading.value = false;
}
};
- 公共组件优先(Table/Page/Form/Select/Upload...),不重复造
- 页面私有组件放 views 下 components/
- props 显式声明 + 类型(vue-types/propTypes)
- 事件命名 `update:xxx` / `change`,不滥用全局事件总线
// 生命周期
onMounted(() => fetchData());
</script>
## 状态与数据
<style scoped>
/* 组件样式 */
</style>
```
- 本地状态 ref/reactive;跨页面才进 Pinia
- storage 键走 mutation-types 常量,不散裸字符串
- 字典数据走字典接口,不硬编码 options
## TypeScript 规范
## 样式
```typescript
// 接口定义
interface User {
id: number;
username: string;
realname: string;
status: number;
}
- 颜色/间距用变量,不硬编码色值
- 组件样式 scoped;全局样式进 styles/ 统一管理
- 避免 `!important`;层级用变量
// 枚举定义
enum UserStatus {
ENABLE = 1,
DISABLE = 2,
}
## 类型
// API 响应类型
interface PageResult<T> {
records: T[];
total: number;
size: number;
current: number;
pages: number;
}
- 渐进式补类型:api 泛型 → props → 页面
- 枚举用 as const + 类型导出
- 临时 any 需注释原因,不扩散
// Props 类型
interface TableProps {
data: any[];
loading?: boolean;
columns: Column[];
}
```
## API 模块规范
```typescript
// ui/src/api/system/user.ts
import { http } from '@/utils/http/axios';
// 查询用 GET + params
export function getUserPage(params?: Record<string, any>) {
return http.request({ url: '/user/page', method: 'GET', params });
}
// 新增用 POST + data
export function userAdd(data: any) {
return http.request({ url: '/user/add', method: 'POST', data });
}
// 修改用 PUT + data
export function userUpdate(data: any) {
return http.request({ url: '/user/update', method: 'PUT', data });
}
// 删除用 DELETE
export function userDelete(id: number) {
return http.request({ url: '/user/delete/' + id, method: 'DELETE' });
}
```
## 命名规范
| 类型 | 规范 | 示例 |
|------|------|------|
| 组件文件 | PascalCase | `BasicTable.vue` |
| API 文件 | camelCase | `user.ts` |
| API 函数 | camelCase | `getUserPage` |
| Store 文件 | camelCase | `user.ts` |
| 常量 | UPPER_SNAKE_CASE | `ACCESS_TOKEN` |
| CSS 类 | kebab-case | `page-container` |
## 代码风格
## 提交前
```bash
# ESLint 检查
pnpm type:check
pnpm lint:eslint
# Prettier 格式化
pnpm lint:prettier
# Stylelint 检查
pnpm lint:stylelint
```
:::warning 禁止事项
- 不要使用 `any` 类型(除非确实无法确定类型)
- 不要使用 `console.log`(生产环境)
- 不要在模板中写复杂逻辑(提取为 computed 或方法)
- 不要在 `setup` 外使用 Composition API
:::
## 相关
- [9.14 API 模块编写规范](/zh/frontend/api-module.html)
- [12.7 代码审查清单](/zh/standards/code-review.html)
+44 -99
View File
@@ -1,117 +1,62 @@
# 12.3 Git 提交规范
# 12.5 Git 提交规范
:::tip 概述
统一的 Git 提交规范有助于代码审查、版本管理和自动化工具集成。
:::tip 必须遵守
提交信息采用约定式格式:`类型(范围): 摘要`。一个提交一个主题,提交前本地过 `make check`。
:::
## Commit Message 格式
## 格式
```html
<type>(<scope>): <subject>
```text
<类型>(<范围>): <摘要,祈使句,不带句号>
<body>
<可选正文:动机与取舍>
<可选脚注:关联需求/破坏性说明>
```
## Type 类型
示例:
| type | 说明 | 示例 |
|------|------|------|
| `feat` | 新功能 | `feat(user): 新增用户导入导出功能` |
| `fix` | 修复 Bug | `fix(login): 修复验证码过期后仍可使用的问题` |
| `docs` | 文档 | `docs: 更新 API 接口文档` |
| `style` | 代码格式(不影响功能) | `style: 统一代码缩进` |
| `refactor` | 重构 | `refactor(logic): 优化分页查询性能` |
| `perf` | 性能优化 | `perf(dict): 增加字典缓存减少数据库查询` |
| `test` | 测试 | `test: 添加用户模块单元测试` |
| `chore` | 构建/工具 | `chore: 更新 Composer 依赖` |
## Scope 范围
| scope | 说明 |
|-------|------|
| `user` | 用户模块 |
| `role` | 角色模块 |
| `menu` | 菜单模块 |
| `dict` | 字典模块 |
| `job` | 定时任务 |
| `auth` | 认证授权 |
| `upload` | 文件上传 |
| `generator` | 代码生成器 |
| `config` | 配置 |
| `deploy` | 部署 |
## 示例
```bash
# -----------------------------------------------------------------------------
# 新功能(feat)
# -----------------------------------------------------------------------------
# -m → 指定提交信息(单行简短描述)
git commit -m "feat(article): 新增文章管理模块"
# -----------------------------------------------------------------------------
# 修复 Bug(fix)
# -----------------------------------------------------------------------------
git commit -m "fix(login): 修复密码错误时未记录登录日志的问题"
# -----------------------------------------------------------------------------
# 文档更新(docs)
# -----------------------------------------------------------------------------
git commit -m "docs: 补充多数据库部署文档"
# -----------------------------------------------------------------------------
# 重构(refactor)
# -----------------------------------------------------------------------------
git commit -m "refactor(logic): 统一删除前校验逻辑"
# -----------------------------------------------------------------------------
# 性能优化(perf)
# -----------------------------------------------------------------------------
git commit -m "perf(dict): 增加请求级缓存减少重复查询"
```text
feat(user): 用户导入增加手机号唯一校验
fix(dict): 字典项变更未删除缓存导致显示名滞后
refactor(repo): 基座 SoftDelete 条件参数成对传入
docs(quickstart): 补充 Windows make.bat 说明
```
## 注意事项
## 类型
:::warning 禁止事项
- 不要提交调试代码(`console.log`、`dump`、`var_dump`)
- 不要提交敏感信息(密码、密钥、Token)
- 不要提交 IDE 配置文件(`.idea/`、`.vscode/`)
- 一个 commit 只做一件事
:::
| 类型 | 含义 |
|---|---|
| feat | 新功能 |
| fix | 缺陷修复 |
| refactor | 重构(不改行为) |
| perf | 性能 |
| docs | 文档 |
| test | 测试 |
| build / ci | 构建与流水线 |
| chore | 杂项 |
## .gitignore
## 范围
```gitignore
# =============================================================================
# .gitignore — Git 版本控制忽略规则
# =============================================================================
# 匹配的文件不会被 git track(不会提交到仓库)
# =============================================================================
按模块/域命名:user、role、dict、job、upload、router、generator、frontend、deploy...
# -----------------------------------------------------------------------------
# 运行时目录(ThinkPHP 缓存、日志、编译模板 — 自动生成,无需版本控制)
# -----------------------------------------------------------------------------
runtime/
vendor/
## 纪律
# -----------------------------------------------------------------------------
# 环境配置(包含数据库密码等敏感信息 — 绝不能提交)
# -----------------------------------------------------------------------------
.env
- **一个提交一个主题**:功能与格式化提交分离(禁大杂烩 diff)
- **提交前**:`make check`(后端改动)/ `pnpm type:check + lint`(前端改动)
- **不提交**:log/、upload/ 运行产物、.env、IDE 配置
- **生成器产物**:与模板改动同一提交(保持可追溯)
- **破坏性变更**(API 契约调整):提交正文显式标注并同步前端
# -----------------------------------------------------------------------------
# IDE 配置文件(每个开发者的 IDE 设置不同 — 不应互相影响)
# -----------------------------------------------------------------------------
.idea/
.vscode/
## 分支建议
# -----------------------------------------------------------------------------
# 临时上传文件(用户上传的临时文件 — 不属于代码)
# -----------------------------------------------------------------------------
uploads/temp/
# -----------------------------------------------------------------------------
# 前端依赖(node_modules 体积庞大 — 通过 pnpm install 重新安装即可)
# -----------------------------------------------------------------------------
ui/node_modules/
```text
main 生产就绪
develop 集成分支
feat/* 功能
fix/* 修复
```
## 相关
- [11.11 CI/CD 与质量门禁](/zh/deploy/ci-cd.html)
- [12.7 代码审查清单](/zh/standards/code-review.html)
@@ -0,0 +1,52 @@
# 12.4 模块开发规约
:::tip 必须遵守
一个模块固定四个文件(dto / repository / service / handler),对外 JSON 契约(json 标签、字段名、路径、权限码、binding 标签)以表结构与前端约定为准,保持稳定。本页是规约全文要点。
:::
## dto — internal/model/dto/{module}.go
- 结构体按职责命名(UserItem、UserPageReq…),json 标签即对外 API 字段名
- 每字段补 `form:"同json名"` 标签;binding/msg 与 dc 注释按校验需求书写
- 时间字段统一 `*LocalTime`;文件字段 `*multipart.FileHeader` + UnmarshalBody
- 分页公共字段内嵌 PageReq;批量删除用 `Ids []int64`
- 类型别名:`type XxxListRes = []*XxxItem`、`type XxxPageRes = PageData[XxxItem]`
- 路由声明在 router.go 该控制器的 RouteSpec 表(字段 Method/Path/Summary/Permission/Log/LogType/Public),新增接口补一条,写法与既有条目逐字一致
## repository — internal/repository/{module}.go
```go
type XxxRepo struct{ Base }
func NewXxxRepo() *XxxRepo { return &XxxRepo{Base: NewBase("gin_xxx")} }
func (r *XxxRepo) Ctx(ctx context.Context) *gorm.DB { return r.Base.Ctx(ctx) }
```
表名 = gin_ + 模块名;关联表同样建仓储。铁律见 [12.3 数据访问铁律](/zh/standards/data-access.html)。
## service — internal/service/{module}.go
- 三段式:`type sXxx struct{}` + init 注册 + `var Xxx *sXxx`
- 标准七方法委托 ModuleSpec;复杂逻辑(导入导出、登录、分配菜单)手写方法
- 跨模块同包直调(无需 import);dict 相关模块 init 调 SetDictLookup
- 数据权限需要的模块声明 `DataScope: ApplyDataScope("dept_id")`
- 事务:`db.WithTrans(ctx, func(ctx context.Context) error { ... })`
- 业务错误:`consts.CodeXxx` 哨兵或 `apperr.Newf(consts.CodeXxx.Code(), "fmt", a...)`
- 钩子禁 HTTP/文件 IO、禁 append;错误原样上抛
## handler — internal/handler/{module}.go
- 极薄委托:`type CXxx struct{}` + 与 dto 一一对应的方法
- 签名固定 `func (c *CXxx) Method(ctx context.Context, req *dto.XxxReq) (*dto.XxxRes, error)`
- 非标准方法(导入/导出/重置密码/分配菜单等)保留为 handler 方法并注释入参/返回/副作用
## 通用禁则
- 注释一律按当前功能直述
- 文件头不加版权横幅,仅包注释一句说明模块职责
- binding/msg 标签逐字保持(本项目 validator 读取该标签)
- 新增配置键必须同步 .env.example
## 相关
- [第 3 章 模块开发实战](/zh/guide/overview.html)
- [3.11 完整代码清单](/zh/guide/code-list.html)
@@ -1,65 +1,41 @@
# 12.2 分页查询规范
:::tip 概述
所有分页接口遵循统一的参数命名和返回结构,确保前后端一致性。
:::tip 必须遵守
列表查询一律用分页契约:请求内嵌 PageReq,响应为 PageData 五字段。禁止自造分页参数名或响应结构。
:::
## 请求参数
## 契约
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `pageNo` | number | 1 | 页码(最小为 1) |
| `pageSize` | number | 20 | 每页条数(1~100) |
| `orderField` | string | - | 排序字段(camelCase,如 `createTime`) |
| `orderType` | string | `desc` | 排序方向(`asc` / `desc`) |
| `fields` | string | - | 返回字段(逗号分隔,如 `id,username,status`) |
| 其他参数 | - | - | 按 Logic 中 pageLikeFields / pageEqFields 自动匹配 |
## 请求示例
```bash
# 基本分页
GET /api/example/page?pageNo=1&pageSize=10
# 带模糊搜索
GET /api/example/page?pageNo=1&pageSize=10&name=测试
# 带精确过滤
GET /api/example/page?pageNo=1&pageSize=10&status=1&type=2
# 自定义排序
GET /api/example/page?pageNo=1&pageSize=10&orderField=sort&orderType=asc
# 指定返回字段
GET /api/example/page?pageNo=1&pageSize=10&fields=id,name,status
```text
请求:pageNo(默认 1)/ pageSize(默认 10,上限 500)
响应:records / total / size / current / pages
```
## 返回结构
## 服务端规约
```json
{
"code": 0,
"ok": true,
"msg": "操作成功",
"data": {
"records": [
{ "id": 1, "name": "测试", "status": 1, "statusText": "启用" }
],
"total": 100,
"size": 10,
"current": 1,
"pages": 10
}
}
```
- 请求结构内嵌 `dto.PageReq`,不手写 pageNo/pageSize 字段
- 分页经 ModuleSpec.Page 委托(自动兜底与组壳)
- records 空时输出 `[]`(NewPageData 保证)
- 深翻页由 pageSize 上限兜底;特殊大导出走 Export 而非超大分页
## 参数转换
## 前端规约
前端使用 camelCase,后端自动转换为 snake_case。排除项(pageNo、pageSize、orderField、orderType、fields)不参与转换。详见 [横切机制 - 驼峰/下划线转换](/zh/architecture/cross-cutting.html)。
- 分页组件绑定 pageNo/pageSize,变更即重查
- 筛选变化回到第 1 页
- 判空按 `records.length`(不判 null)
## 排序字段
## List(全量)的边界
| 前端传参 | 后端处理 | SQL |
|---------|---------|-----|
| `orderField=createTime&orderType=desc` | 转为 `create_time` | `ORDER BY create_time DESC` |
| 不传 | 使用 Logic 默认排序 | 按 `pageOrderBy` 配置 |
下拉数据源等小数据量用 List + limit 上限;**任何面向页面表格的数据必须走 Page**。导出用 Export(服务端流式写出),不以"大 pageSize 分页"替代。
## 反例
| 反例 | 问题 |
|---|---|
| 自造参数 offset/limit/size 混用 | 与基座、生成器、前端组件不兼容 |
| 响应只回数组不回 total | 前端无法渲染分页条 |
| pageSize 不设上限 | 一次拉爆内存 |
## 相关
- [8.3 分页查询规范](/zh/api-spec/pagination.html)