签入版本

This commit is contained in:
小蚂蚁云
2026-10-20 21:19:44 +08:00
commit 7e70ed6942
2943 changed files with 498573 additions and 0 deletions
@@ -0,0 +1,135 @@
# 12.1 API 响应规范
:::tip 概述
所有接口返回统一的 JSON 结构,前端可据此判断请求结果。成功响应 `code=0`,失败响应 `code=1`(或 401/403/404/422)。
:::
## 统一格式
```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"
}
}
```
## 分页响应
```json
{
"code": 0,
"ok": true,
"msg": "操作成功",
"data": {
"records": [
{ "id": 1, "username": "admin" }
],
"total": 100,
"size": 20,
"current": 1,
"pages": 5
}
}
```
## 失败响应
```json
{
"code": 1,
"ok": false,
"msg": "用户名已存在",
"data": null
}
```
## 字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `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 | 总页数 |
## 驼峰自动转换
响应数据自动从 snake_case 转为 camelCase:
| 数据库字段 | 响应字段 |
|-----------|---------|
| `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": "邮箱格式不正确"
}
}
```
@@ -0,0 +1,120 @@
# 12.6 后端编码规范
:::tip 概述
本节定义后端 PHP 代码的编写规范,包括 Model、Logic、Controller、Validate 各层的命名和结构规范,确保团队代码风格统一。
:::
## Model 层规范
```php
// 继承 BaseModel
class Example extends BaseModel
{
/**
* 对应数据库表名(不含前缀)
*
* @var string
*/
protected $name = 'example';
}
```
## Logic 层规范
```php
// 系统共享模块继承 BaseLogic
class ExampleLogic extends BaseLogic
{
/**
* 关联的模型类
*
* @var string
*/
protected string $modelClass = Example::class;
/**
* LIKE 模糊匹配字段
*
* @var array
*/
protected array $pageLikeFields = ['name'];
/**
* 精确匹配字段
*
* @var array
*/
protected array $pageEqFields = ['status'];
/**
* 新增前处理
*
* @param array $data 待新增的数据
* @return array 处理后的数据
*/
protected function beforeAdd(array $data): array
{
return $data;
}
}
// 租户模块继承 BaseTenantLogic
class UserLogic extends BaseTenantLogic { ... }
```
## 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:{模块名}:{操作}`
@@ -0,0 +1,80 @@
# 12.5 代码审查清单
:::tip 概述
代码审查(Code Review)是保证代码质量的重要环节。以下清单涵盖后端和前端的审查要点。
:::
## 后端审查清单
### Model 层
- [ ] 继承 `BaseModel`
- [ ] 表名 `$name` 配置正确
- [ ] 关联关系定义正确(如有)
- [ ] 无硬编码的数据库前缀
### Logic 层
- [ ] 继承正确的基类(`BaseLogic` 或 `BaseTenantLogic`)
- [ ] `$modelClass` 配置正确
- [ ] 查询条件配置合理(`pageLikeFields` / `pageEqFields`)
- [ ] 排序配置合理(`pageOrderBy`)
- [ ] 唯一性校验配置(`uniqueFields`)
- [ ] 枚举映射配置(`serializeMaps`)
- [ ] 文件字段配置(`fileFields` / `fileSaveDir`)
- [ ] 钩子方法中无 SQL 注入风险
- [ ] 事务使用正确(`startTrans` / `commit` / `rollback`)
### Controller 层
- [ ] 继承 `BaseController`
- [ ] `initialize()` 中实例化 Logic
- [ ] 所有接口标注 `#[Permission]` 注解
- [ ] 所有写操作标注 `#[Log]` 注解
- [ ] 权限码格式正确(`sys:模块名:操作`)
- [ ] 异常处理完善(try-catch)
- [ ] 参数校验充分
### 安全
- [ ] 无 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)
- [ ] 无敏感信息泄露(密码、密钥)
@@ -0,0 +1,156 @@
# 12.4 数据删除规范
## 软删除约定
:::tip 核心原则
所有业务数据统一使用软删除机制,通过 `is_delete` 字段标记记录状态,不物理删除数据。
:::
| is_delete 值 | 含义 |
|--------------|------|
| `0` | 正常记录(默认值) |
| `1` | 已删除记录 |
所有查询方法自动过滤 `is_delete = 0`,业务层无需关心已删除数据。
## 单条删除
### URL 格式
```text
DELETE /api/{module}/delete/{id}
```
### Controller 示例
```php
#[Log('案例演示-删除记录', Log::TYPE_DELETE, '删除案例演示ID:{id}')]
#[Permission('sys:example:delete', '删除案例演示')]
public function delete(int $id): Json
{
return parent::_remove($this->logic, $id);
}
```
### 内部流程
```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) 钩子
```
## 批量删除
### 请求格式
```text
DELETE /api/{module}/batchDelete
Content-Type: application/json
{ "ids": [1, 2, 3] }
```
### Controller 示例
```php
#[Log('案例演示-批量删除', Log::TYPE_DELETE)]
#[Permission('sys:example:delete', '删除案例演示')]
public function batchDelete(): Json
{
$ids = $this->getJsonBody()['ids'] ?? [];
return parent::_batchRemove($this->logic, $ids);
}
```
### 内部流程
```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>
```
@@ -0,0 +1,142 @@
# 12.7 前端编码规范
:::tip 概述
前端编码规范涵盖 Vue 组件、TypeScript、API 模块、样式和代码风格等方面。
:::
## Vue 组件规范
```vue
<!-- 推荐的组件结构 -->
<template>
<!-- 模板内容 -->
</template>
<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: '',
});
// 响应式数据
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;
}
};
// 生命周期
onMounted(() => fetchData());
</script>
<style scoped>
/* 组件样式 */
</style>
```
## TypeScript 规范
```typescript
// 接口定义
interface User {
id: number;
username: string;
realname: string;
status: number;
}
// 枚举定义
enum UserStatus {
ENABLE = 1,
DISABLE = 2,
}
// API 响应类型
interface PageResult<T> {
records: T[];
total: number;
size: number;
current: number;
pages: number;
}
// 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 lint:eslint
# Prettier 格式化
pnpm lint:prettier
# Stylelint 检查
pnpm lint:stylelint
```
:::warning 禁止事项
- 不要使用 `any` 类型(除非确实无法确定类型)
- 不要使用 `console.log`(生产环境)
- 不要在模板中写复杂逻辑(提取为 computed 或方法)
- 不要在 `setup` 外使用 Composition API
:::
@@ -0,0 +1,117 @@
# 12.3 Git 提交规范
:::tip 概述
统一的 Git 提交规范有助于代码审查、版本管理和自动化工具集成。
:::
## Commit Message 格式
```html
<type>(<scope>): <subject>
<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): 增加请求级缓存减少重复查询"
```
## 注意事项
:::warning 禁止事项
- 不要提交调试代码(`console.log`、`dump`、`var_dump`)
- 不要提交敏感信息(密码、密钥、Token)
- 不要提交 IDE 配置文件(`.idea/`、`.vscode/`)
- 一个 commit 只做一件事
:::
## .gitignore
```gitignore
# =============================================================================
# .gitignore — Git 版本控制忽略规则
# =============================================================================
# 匹配的文件不会被 git track(不会提交到仓库)
# =============================================================================
# -----------------------------------------------------------------------------
# 运行时目录(ThinkPHP 缓存、日志、编译模板 — 自动生成,无需版本控制)
# -----------------------------------------------------------------------------
runtime/
vendor/
# -----------------------------------------------------------------------------
# 环境配置(包含数据库密码等敏感信息 — 绝不能提交)
# -----------------------------------------------------------------------------
.env
# -----------------------------------------------------------------------------
# IDE 配置文件(每个开发者的 IDE 设置不同 — 不应互相影响)
# -----------------------------------------------------------------------------
.idea/
.vscode/
# -----------------------------------------------------------------------------
# 临时上传文件(用户上传的临时文件 — 不属于代码)
# -----------------------------------------------------------------------------
uploads/temp/
# -----------------------------------------------------------------------------
# 前端依赖(node_modules 体积庞大 — 通过 pnpm install 重新安装即可)
# -----------------------------------------------------------------------------
ui/node_modules/
```
@@ -0,0 +1,65 @@
# 12.2 分页查询规范
:::tip 概述
所有分页接口遵循统一的参数命名和返回结构,确保前后端一致性。
:::
## 请求参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `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
```
## 返回结构
```json
{
"code": 0,
"ok": true,
"msg": "操作成功",
"data": {
"records": [
{ "id": 1, "name": "测试", "status": 1, "statusText": "启用" }
],
"total": 100,
"size": 10,
"current": 1,
"pages": 10
}
}
```
## 参数转换
前端使用 camelCase,后端自动转换为 snake_case。排除项(pageNo、pageSize、orderField、orderType、fields)不参与转换。详见 [横切机制 - 驼峰/下划线转换](/zh/architecture/cross-cutting.html)。
## 排序字段
| 前端传参 | 后端处理 | SQL |
|---------|---------|-----|
| `orderField=createTime&orderType=desc` | 转为 `create_time` | `ORDER BY create_time DESC` |
| 不传 | 使用 Logic 默认排序 | 按 `pageOrderBy` 配置 |