Become a sponsor

概述
后端采用"三层主干 + 三层支撑"架构:Controller → Logic → Model 构成主干链路,Validate、Service、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。
┌─────────────────────────────────────────────────────────────────────┐
│ 支撑层 │
│ │
│ Validate(参数校验) Service(通用能力) Middleware(请求拦截) │
│ app/validate/*.php app/service/*.php app/middleware/*.php │
└──────────────────────────────┬──────────────────────────────────────┘
│ 被主干层调用
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 主干链路 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Controller(控制器层) │ │
│ │ 接收请求 → 获取参数 → 调用 Logic → 封装响应 │ │
│ │ 基类:BaseController | 文件:app/controller/*.php │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ 调用 Logic 方法 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Logic(逻辑层) │ │
│ │ 属性配置 → 参数校验 → 生命周期钩子 → 横切处理 → 调用 Model │ │
│ │ 基类:BaseLogic / BaseTenantLogic | 文件:app/logic/*.php │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ 调用 Model ORM │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Model(模型层) │ │
│ │ 表映射 → 软删除 → 审计字段 → ORM → SQL │ │
│ │ 基类:BaseModel | 文件:app/model/*.php │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Database(数据库) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘文件:app/controller/{Module}Controller.php基类:BaseController职责:HTTP 请求入口,只做"搬运"不做"判断"
| 职责 | 说明 | 示例 |
|---|---|---|
| 参数获取 | 从请求中提取参数 | $this->getJsonBody()、$this->getParams() |
| 调用 Logic | 委托业务逻辑 | $this->logic->add($data) |
| 响应封装 | 统一 JSON 格式返回 | $this->success()、$this->fail() |
| 注解标注 | 声明权限和日志 | #[Permission]、#[Log] |
Controller 不应该做: 业务判断、数据库操作、文件处理、参数校验(特殊场景除外)。
class ArticleController extends BaseController
{
protected ArticleLogic $logic;
protected function initialize(): void
{
$this->logic = new ArticleLogic();
}
// 标准 CRUD — 一行调用
#[Permission('sys:article:page', '文章分页')]
public function page(): Json
{
return parent::_index($this->logic);
}
#[Permission('sys:article:add', '添加文章')]
public function add(): Json
{
return parent::_add($this->logic, $this->getJsonBody());
}
}文件:app/logic/{Module}Logic.php基类:BaseLogic(系统共享)/ BaseTenantLogic(租户隔离) 职责:业务核心,通过"属性配置 + 生命周期钩子"声明式 CRUD
| 职责 | 方式 | 说明 |
|---|---|---|
| 行为定义 | 属性配置 | $pageLikeFields、$uniqueFields、$validateClass 等 |
| 业务定制 | 生命周期钩子 | beforeAdd、afterUpdate、beforeDelete 等 |
| 自动校验 | $validateClass | add/update 时自动调用验证器 |
| 横切处理 | 属性驱动 | 文件字段、富文本、枚举映射、数据权限、租户隔离 |
Logic 不应该做: 直接 HTTP 交互、直接返回 JSON 响应。
class ArticleLogic extends BaseTenantLogic
{
/**
* 关联的模型类
*
* @var string
*/
protected string $modelClass = Article::class;
/**
* 参数验证器类
*
* @var string
*/
protected string $validateClass = ArticleValidate::class;
/**
* 需要自动处理的文件上传字段
*
* @var array
*/
protected array $fileFields = ['cover'];
/**
* LIKE 模糊匹配字段
*
* @var array
*/
protected array $pageLikeFields = ['title'];
/**
* 精确匹配字段
*
* @var array
*/
protected array $pageEqFields = ['status', 'category_id'];
/**
* 唯一性校验字段
*
* @var array
*/
protected array $uniqueFields = ['title'];
/**
* 枚举显示名映射
*
* @var array
*/
protected array $serializeMaps = ['status' => 'article_status'];
/**
* 新增前处理:设置默认排序值
*
* @param array $data 待新增的数据
* @return array 处理后的数据
*/
protected function beforeAdd(array $data): array
{
$data['sort'] = $data['sort'] ?? 0;
return $data;
}
}文件:app/model/{TableName}.php基类:BaseModel职责:数据持久化,只做表映射和自动维护
| 职责 | 说明 |
|---|---|
| 表映射 | protected $name = 'article' |
| 软删除 | 全局查询范围自动过滤 is_delete=1 |
| 审计字段 | onBeforeInsert/onBeforeUpdate 自动填充 |
| 关联关系 | hasMany、belongsTo 等 |
Model 不应该做: 业务判断、复杂查询逻辑(交给 Logic)。
class Article extends BaseModel
{
protected $name = 'article';
// 关联关系(按需定义)
public function category()
{
return $this->belongsTo(Category::class, 'category_id');
}
}文件:app/validate/{Module}Validate.php基类:think\Validate职责:参数格式校验,定义规则 + 场景
class ArticleValidate extends Validate
{
protected $rule = [
'title' => 'require|max:200',
];
protected $message = [
'title.require' => '文章标题不能为空',
];
protected $scene = [
'add' => ['title'],
'update' => ['title'],
];
}触发方式: Logic 配置 $validateClass 后,add/update 自动调用。
文件:app/service/{Name}Service.php职责:跨模块通用能力,无状态、无副作用
| 服务 | 能力 |
|---|---|
DictService | 字典查询(两级缓存) |
PasswordService | 密码加密(bcrypt 双重加盐) |
JwtService | Token 生成/刷新/解析 |
ExcelService | Excel 导入导出 |
CaptchaService | 图片验证码 |
Service 不应该做: 持有状态、直接操作数据库(通过 Model 静态方法例外)。
文件:app/middleware/{Name}Middleware.php职责:请求拦截,在 Controller 前后执行
CorsMiddleware → AuthMiddleware → TenantMiddleware → DemoMiddleware → LogMiddleware → Controller| 中间件 | 作用 | 注入数据 |
|---|---|---|
AuthMiddleware | JWT 认证 + 权限校验 | $request->userInfo |
TenantMiddleware | 租户上下文 | $request->tenantId |
DemoMiddleware | 演示环境拦截 | — |
LogMiddleware | 操作日志记录 | — |
| 层 | 方法 | 输入 | 输出 |
|---|---|---|---|
| Controller | _index($logic) | HTTP 请求参数 | Json 响应 |
| Logic | pageList($params, $where, $with) | 参数数组 | ['records'=>[], 'total'=>int, ...] |
| Model | Model::page($current, $size)->select() | 分页条件 | Collection |
| 层 | 方法 | 输入 | 输出 |
|---|---|---|---|
| Controller | _add($logic, $data) 或 $this->logic->add($data) | Json 请求体 | Json 响应 |
| Logic | add($data) | 关联数组 | int(新记录 ID) |
| Model | Model::create($data) | 关联数组 | Model 实例 |
Logic::add 内部流程:beforeAdd → validateData → camel_to_snake → processTenantId → processFile → processContent → filterTableFields → checkUnique → Model::create → afterAdd
| 层 | 方法 | 输入 | 输出 |
|---|---|---|---|
| Controller | _edit($logic, $id, $data) | id + Json 请求体 | Json 响应 |
| Logic | update($id, $data) | id + 关联数组 | bool |
| Model | Model::find($id)->save($data) | 模型实例 + 关联数组 | bool |
| 层 | 方法 | 输入 | 输出 |
|---|---|---|---|
| Controller | _remove($logic, $id) | id | Json 响应 |
| Logic | delete($id) | id | bool |
| Model | $model->save(['is_delete' => 1]) | 软删除标记 | bool |
前端请求(camelCase JSON)
│
▼
BaseController::getJsonBody() ← 获取原始参数
│
▼
BaseLogic::beforeAdd($data) ← 钩子:预处理
│
▼
BaseLogic::validateData($data) ← 自动参数校验
│
▼
BaseLogic::convertQueryParams() ← camelCase → snake_case
│
▼
BaseLogic::processFileFieldsOnSave ← 文件字段处理
│
▼
BaseLogic::filterTableFields() ← 过滤非数据库字段
│
▼
BaseLogic::checkUnique() ← 唯一性校验
│
▼
BaseModel::create() ← 审计字段自动填充
│
▼
Database(snake_case)Database(snake_case 数据)
│
▼
BaseModel ← ORM 查询
│
▼
BaseLogic 处理
│ processFileFieldsOnRead() ← 文件字段补全域名
│ processContentFieldsOnRead() ← 富文本占位符替换
│ processSerializeMaps() ← 枚举显示名补全
│ afterDetail() ← 钩子:补充关联数据
▼
Result::convertData() ← snake_case → camelCase
│
▼
JSON 响应 → 前端Model 层异常(如数据库连接失败)
│
▼ 向上抛出
Logic 层异常(如 beforeDelete 抛出业务异常、validateData 抛出 ValidateException)
│
▼ 向上抛出
Controller 层
│ 方式一:try-catch 捕获,返回 fail()
│ 方式二:不捕获,由 ExceptionHandle 统一处理
▼
ExceptionHandle::render()
│ ValidateException → {"code":1, "msg":"参数验证失败", "data":"..."}
│ ModelNotFoundException → {"code":1, "msg":"数据不存在"}
│ RuntimeException(401) → {"code":1, "msg":"未授权"}
│ 其他异常 → {"code":1, "msg":"操作失败"}
▼
JSON 响应推荐做法:
_add/_edit/_remove 调用时,不捕获异常,交给 ExceptionHandle$this->logic->add() 时,try-catch 捕获并返回 $this->fail()throw new \Exception以"新增文章"为例,展示一个请求从进入到返回的完整链路:
POST /api/article/add
Content-Type: application/json
Authorization: Bearer <token>
{"title": "新文章", "categoryId": 1, "status": 0}1. CorsMiddleware → 放行(设置跨域头)
2. AuthMiddleware → 校验 Token → 注入 $request->userInfo
3. TenantMiddleware → 解析租户 → 注入 $request->tenantId
4. LogMiddleware → 记录日志(#[Log] 注解)
5. ArticleController::add()
│ getJsonBody() → ["title"=>"新文章", "categoryId"=>1, "status"=>0]
│
▼
6. ArticleLogic::add($data)
│ beforeAdd($data) → 设置默认 sort=0
│ validateData($data, 'add') → 校验 title 必填 ✓
│ camel_to_snake($data) → ["title"=>"新文章", "category_id"=>1, "status"=>0]
│ processTenantId($data) → 加入 tenant_id
│ processFileFieldsOnSave → cover 无值,跳过
│ filterTableFields($data) → 过滤非表字段
│ checkUnique($data) → title 唯一 ✓
│
▼
7. Article::create($data)
│ onBeforeInsert → 自动填充 create_user, create_time
│
▼
8. Database INSERT
9. ArticleLogic::afterAdd($id, $data) → 无额外操作
10. 返回 {"code":0, "ok":true, "msg":"添加成功", "data":{"id":42}}| 规则 | 说明 |
|---|---|
| Controller → Logic | ✅ 允许,标准调用 |
| Controller → Model | ❌ 禁止,越层 |
| Logic → Model | ✅ 允许,标准调用 |
| Logic → Service | ✅ 允许,调用通用能力 |
| Logic → Controller | ❌ 禁止,反向依赖 |
| Model → Logic | ❌ 禁止,反向依赖 |
| Model → Service | ⚠️ 慎用,仅静态工具方法 |
| Middleware → Controller | ✅ 通过 $request 注入数据 |
| Validate → Logic | ❌ 禁止,只做格式校验 |