签入版本
This commit is contained in:
@@ -1,46 +1,45 @@
|
||||
# 4.3 后端分层架构
|
||||
|
||||
:::tip 概述
|
||||
后端采用"三层主干 + 三层支撑"架构:Controller → Logic → Model 构成主干链路,Validate、Service、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。
|
||||
后端采用"三层主干 + 三层支撑"架构:Controller → Service → Model 构成主干链路,FormRequest、横切服务、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。
|
||||
:::
|
||||
|
||||
## 架构全景
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 支撑层 │
|
||||
│ 支撑层 │
|
||||
│ │
|
||||
│ Validate(参数校验) Service(通用能力) Middleware(请求拦截) │
|
||||
│ app/validate/*.php app/service/*.php app/middleware/*.php │
|
||||
│ FormRequest(参数校验) 横切 Service(通用能力) Middleware(请求拦截)│
|
||||
│ Http/Requests/*.php Services/(Jwt/Dict…) Http/Middleware/ │
|
||||
└──────────────────────────────┬──────────────────────────────────────┘
|
||||
│ 被主干层调用
|
||||
│ 被主干层调用 / 前置拦截
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 主干链路 │
|
||||
│ 主干链路 │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Controller(控制器层) │ │
|
||||
│ │ 接收请求 → 获取参数 → 调用 Logic → 封装响应 │ │
|
||||
│ │ 基类:BaseController | 文件:app/controller/*.php │ │
|
||||
│ │ 注解标注 → 参数注入 → 调用 Service → 封装响应 │ │
|
||||
│ │ 基类:Controller | 文件:app/Http/Controllers/Api/*.php │ │
|
||||
│ └──────────────────────────┬──────────────────────────────────┘ │
|
||||
│ │ 调用 Logic 方法 │
|
||||
│ │ 调用 Service 方法 │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Logic(逻辑层) │ │
|
||||
│ │ 属性配置 → 参数校验 → 生命周期钩子 → 横切处理 → 调用 Model │ │
|
||||
│ │ 基类:BaseLogic / BaseTenantLogic | 文件:app/logic/*.php │ │
|
||||
│ │ Service(服务层) │ │
|
||||
│ │ 属性配置 → 唯一性校验 → 生命周期钩子 → 事务 → 调用 Model │ │
|
||||
│ │ 基类:BaseService / BaseTenantService | Services/*.php │ │
|
||||
│ └──────────────────────────┬──────────────────────────────────┘ │
|
||||
│ │ 调用 Model ORM │
|
||||
│ │ 调用 Eloquent ORM │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Model(模型层) │ │
|
||||
│ │ 表映射 → 软删除 → 审计字段 → ORM → SQL │ │
|
||||
│ │ 基类:BaseModel | 文件:app/model/*.php │ │
|
||||
│ │ 表映射 → 软删除 → 审计字段 → 关联关系 → ORM → SQL │ │
|
||||
│ │ Trait:SoftDeletesByFlag / HasAuditColumns | Models/*.php │ │
|
||||
│ └──────────────────────────┬──────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Database(数据库) │ │
|
||||
│ │ Database(五种数据库:MySQL / PG / SQL Server / Oracle / SQLite)│
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
@@ -49,117 +48,68 @@
|
||||
|
||||
### Controller 层
|
||||
|
||||
**文件**:`app/controller/{Module}Controller.php`
|
||||
**基类**:`BaseController`
|
||||
**职责**:HTTP 请求入口,只做"搬运"不做"判断"
|
||||
**文件**:`app/Http/Controllers/Api/{Module}Controller.php`
|
||||
**基类**:`App\Http\Controllers\Controller`
|
||||
**职责**:HTTP 请求入口,只做"编排"不做"判断"
|
||||
|
||||
| 职责 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| 参数获取 | 从请求中提取参数 | `$this->getJsonBody()`、`$this->getParams()` |
|
||||
| 调用 Logic | 委托业务逻辑 | `$this->logic->add($data)` |
|
||||
| 响应封装 | 统一 JSON 格式返回 | `$this->success()`、`$this->fail()` |
|
||||
| 注解标注 | 声明权限和日志 | `#[Permission]`、`#[Log]` |
|
||||
| 参数注入 | 类型注入 FormRequest 完成格式校验 | `add(ExampleRequest $request)` |
|
||||
| 调用 Service | 委托业务逻辑(快捷方法一行调用) | `$this->_add($service, $data)` |
|
||||
| 响应封装 | 统一 JSON 格式返回 | `$this->success()` / `$this->fail()` |
|
||||
| 注解标注 | 声明权限和日志 | `#[Permission]`、`#[OperationLog]` |
|
||||
|
||||
**Controller 不应该做:** 业务判断、数据库操作、文件处理、参数校验(特殊场景除外)。
|
||||
**Controller 不应该做**:业务判断、数据库操作、文件处理、重复的格式校验。
|
||||
|
||||
```php
|
||||
class ArticleController extends BaseController
|
||||
class ExampleController extends Controller
|
||||
{
|
||||
protected ArticleLogic $logic;
|
||||
public function __construct(protected ExampleService $exampleService) {}
|
||||
|
||||
protected function initialize(): void
|
||||
// 标准 CRUD —— 一行调用(构造注入,无需 initialize)
|
||||
#[OperationLog('示例管理-查询分页', OperationLog::TYPE_QUERY)]
|
||||
#[Permission('sys:example:page', '示例分页')]
|
||||
public function page(): JsonResponse
|
||||
{
|
||||
$this->logic = new ArticleLogic();
|
||||
return $this->_page($this->exampleService);
|
||||
}
|
||||
|
||||
// 标准 CRUD — 一行调用
|
||||
#[Permission('sys:article:page', '文章分页')]
|
||||
public function page(): Json
|
||||
#[OperationLog('示例管理-新增', OperationLog::TYPE_ADD, '新增示例:{name}')]
|
||||
#[Permission('sys:example:add', '添加示例')]
|
||||
public function add(ExampleRequest $request): JsonResponse
|
||||
{
|
||||
return parent::_index($this->logic);
|
||||
}
|
||||
|
||||
#[Permission('sys:article:add', '添加文章')]
|
||||
public function add(): Json
|
||||
{
|
||||
return parent::_add($this->logic, $this->getJsonBody());
|
||||
return $this->_add($this->exampleService, $request->validated());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Logic 层
|
||||
### Service 层
|
||||
|
||||
**文件**:`app/logic/{Module}Logic.php`
|
||||
**基类**:`BaseLogic`(系统共享)/ `BaseTenantLogic`(租户隔离)
|
||||
**文件**:`app/Services/{Module}Service.php`
|
||||
**基类**:`BaseService`(系统共享)/ `BaseTenantService`(租户隔离)
|
||||
**职责**:业务核心,通过"属性配置 + 生命周期钩子"声明式 CRUD
|
||||
|
||||
| 职责 | 方式 | 说明 |
|
||||
|------|------|------|
|
||||
| 行为定义 | 属性配置 | `$pageLikeFields`、`$uniqueFields`、`$validateClass` 等 |
|
||||
| 业务定制 | 生命周期钩子 | `beforeAdd`、`afterUpdate`、`beforeDelete` 等 |
|
||||
| 自动校验 | `$validateClass` | add/update 时自动调用验证器 |
|
||||
| 横切处理 | 属性驱动 | 文件字段、富文本、枚举映射、数据权限、租户隔离 |
|
||||
| 行为定义 | 属性配置 | `$pageLikeFields`、`$uniqueFields`、`$serializeMaps` 等 16 个声明式属性 |
|
||||
| 业务定制 | 生命周期钩子 | `beforeAdd`、`afterUpdate`、`beforeDelete` 等 10 个钩子 |
|
||||
| 横切处理 | Trait 组合 | 文件字段、字典翻译、数据权限、租户隔离(6 个 Concerns Trait) |
|
||||
| 数据安全 | `DB::transaction` | add/update/delete/batchDelete 主流程事务包裹 |
|
||||
|
||||
**Logic 不应该做:** 直接 HTTP 交互、直接返回 JSON 响应。
|
||||
**Service 不应该做**:直接 HTTP 交互、直接返回 JSON 响应。
|
||||
|
||||
```php
|
||||
class ArticleLogic extends BaseTenantLogic
|
||||
class ExampleService extends BaseService
|
||||
{
|
||||
/**
|
||||
* 关联的模型类
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $modelClass = Article::class;
|
||||
use HasCrudOperations, HasQueryScopes, HasSerializeMaps, HasUniqueCheck;
|
||||
|
||||
/**
|
||||
* 参数验证器类
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $validateClass = ArticleValidate::class;
|
||||
protected string $modelClass = Example::class;
|
||||
protected array $pageLikeFields = ['name'];
|
||||
protected array $pageEqFields = ['status'];
|
||||
protected array $uniqueFields = ['name'];
|
||||
protected array $serializeMaps = ['status' => 'example_status'];
|
||||
|
||||
/**
|
||||
* 需要自动处理的文件上传字段
|
||||
*
|
||||
* @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;
|
||||
@@ -170,23 +120,25 @@ class ArticleLogic extends BaseTenantLogic
|
||||
|
||||
### Model 层
|
||||
|
||||
**文件**:`app/model/{TableName}.php`
|
||||
**基类**:`BaseModel`
|
||||
**文件**:`app/Models/{TableName}.php`
|
||||
**基类**:Eloquent Model + Concerns Trait
|
||||
**职责**:数据持久化,只做表映射和自动维护
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 表映射 | `protected $name = 'article'` |
|
||||
| 软删除 | 全局查询范围自动过滤 `is_delete=1` |
|
||||
| 审计字段 | `onBeforeInsert`/`onBeforeUpdate` 自动填充 |
|
||||
| 关联关系 | `hasMany`、`belongsTo` 等 |
|
||||
| 表映射 | `protected $table = 'example'`(不含前缀,`DB_PREFIX` 自动拼接) |
|
||||
| 软删除 | `SoftDeletesByFlag` 注册全局范围自动过滤 `is_delete=1` |
|
||||
| 审计字段 | `HasAuditColumns` 的 `creating/updating` 事件自动填充 |
|
||||
| 关联关系 | `hasMany`、`belongsTo`、`belongsToMany` 等 |
|
||||
|
||||
**Model 不应该做:** 业务判断、复杂查询逻辑(交给 Logic)。
|
||||
**Model 不应该做**:业务判断、复杂查询编排(交给 Service)。
|
||||
|
||||
```php
|
||||
class Article extends BaseModel
|
||||
class Example extends Model
|
||||
{
|
||||
protected $name = 'article';
|
||||
use HasAuditColumns, SoftDeletesByFlag;
|
||||
|
||||
protected $table = 'example';
|
||||
|
||||
// 关联关系(按需定义)
|
||||
public function category()
|
||||
@@ -196,60 +148,59 @@ class Article extends BaseModel
|
||||
}
|
||||
```
|
||||
|
||||
### Validate 层
|
||||
### FormRequest 层(参数校验支撑)
|
||||
|
||||
**文件**:`app/validate/{Module}Validate.php`
|
||||
**基类**:`think\Validate`
|
||||
**职责**:参数格式校验,定义规则 + 场景
|
||||
**文件**:`app/Http/Requests/{Module}Request.php`
|
||||
**基类**:`BaseRequest`
|
||||
**职责**:参数**格式**校验(必填、类型、长度、枚举),不含业务规则
|
||||
|
||||
```php
|
||||
class ArticleValidate extends Validate
|
||||
class ExampleRequest extends BaseRequest
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require|max:200',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '文章标题不能为空',
|
||||
];
|
||||
protected $scene = [
|
||||
'add' => ['title'],
|
||||
'update' => ['title'],
|
||||
];
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'type' => 'nullable|integer|in:1,2,3,4',
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**触发方式:** Logic 配置 `$validateClass` 后,add/update 自动调用。
|
||||
**触发方式**:控制器方法类型注入,方法执行前自动校验;失败由 `failedValidation()` 返回统一 JSON(`code=1` + 字段明细)。
|
||||
|
||||
### Service 层
|
||||
### 横切 Service(通用能力支撑)
|
||||
|
||||
**文件**:`app/service/{Name}Service.php`
|
||||
**文件**:`app/Services/{Name}Service.php`(横切类)
|
||||
**职责**:跨模块通用能力,无状态、无副作用
|
||||
|
||||
| 服务 | 能力 |
|
||||
|------|------|
|
||||
| `DictService` | 字典查询(两级缓存) |
|
||||
| `PasswordService` | 密码加密(bcrypt 双重加盐) |
|
||||
| `JwtService` | Token 生成/刷新/解析 |
|
||||
| `ExcelService` | Excel 导入导出 |
|
||||
| `CaptchaService` | 图片验证码 |
|
||||
| `JwtService` | 令牌签发/刷新/解析(Redis 有状态) |
|
||||
| `DictCacheService` | 字典查询(两级缓存) |
|
||||
| `PasswordService` | 密码加密(bcrypt,成本因子 12) |
|
||||
| `ExcelService` | Excel 导入导出(表头映射、分批) |
|
||||
| `CaptchaService` | 图片验证码(IP 限流) |
|
||||
| `RequestInfoService` | IP 归属地 / OS / 浏览器解析 |
|
||||
|
||||
**Service 不应该做:** 持有状态、直接操作数据库(通过 Model 静态方法例外)。
|
||||
**横切 Service 不应该做**:持有请求状态、直接返回响应。
|
||||
|
||||
### Middleware 层
|
||||
### Middleware 层(请求拦截支撑)
|
||||
|
||||
**文件**:`app/middleware/{Name}Middleware.php`
|
||||
**文件**:`app/Http/Middleware/*.php`
|
||||
**职责**:请求拦截,在 Controller 前后执行
|
||||
|
||||
```text
|
||||
CorsMiddleware → AuthMiddleware → TenantMiddleware → DemoMiddleware → LogMiddleware → Controller
|
||||
ProxyMiddleware → Cors → ForceJsonResponse → Authenticate → ResolveTenant → LogOperation → Controller
|
||||
```
|
||||
|
||||
| 中间件 | 作用 | 注入数据 |
|
||||
|--------|------|----------|
|
||||
| `AuthMiddleware` | JWT 认证 + 权限校验 | `$request->userInfo` |
|
||||
| `TenantMiddleware` | 租户上下文 | `$request->tenantId` |
|
||||
| `DemoMiddleware` | 演示环境拦截 | — |
|
||||
| `LogMiddleware` | 操作日志记录 | — |
|
||||
| `Authenticate` | JWT 认证 + 权限校验 | `attributes['userInfo']` |
|
||||
| `ResolveTenant` | 租户上下文 | `attributes['tenantId']` / `isSuperAdmin` / `tenantInfo` |
|
||||
| `LogOperation` | 操作日志(handle 采集,terminate 落库) | — |
|
||||
|
||||
详见 [4.2 请求处理链路](/zh/architecture/request-flow.html)。
|
||||
|
||||
## 各层方法对照表
|
||||
|
||||
@@ -257,36 +208,37 @@ CorsMiddleware → AuthMiddleware → TenantMiddleware → DemoMiddleware → Lo
|
||||
|
||||
| 层 | 方法 | 输入 | 输出 |
|
||||
|----|------|------|------|
|
||||
| Controller | `_index($logic)` | HTTP 请求参数 | Json 响应 |
|
||||
| Logic | `pageList($params, $where, $with)` | 参数数组 | `['records'=>[], 'total'=>int, ...]` |
|
||||
| Model | `Model::page($current, $size)->select()` | 分页条件 | Collection |
|
||||
| Controller | `_page($service)` | HTTP 请求参数 | JsonResponse |
|
||||
| Service | `pageList($params, $where, $with)` | 参数数组 | `['records'=>[], 'total'=>int, 'current'=>int, 'size'=>int]` |
|
||||
| Model | `Model::query()->paginate(...)` | 分页条件 | LengthAwarePaginator |
|
||||
|
||||
### 新增链路
|
||||
|
||||
| 层 | 方法 | 输入 | 输出 |
|
||||
|----|------|------|------|
|
||||
| Controller | `_add($logic, $data)` 或 `$this->logic->add($data)` | Json 请求体 | Json 响应 |
|
||||
| Logic | `add($data)` | 关联数组 | `int`(新记录 ID) |
|
||||
| Controller | `_add($service, $data)` | FormRequest 校验后的数组 | JsonResponse |
|
||||
| Service | `add($data)` | snake_case 关联数组 | `int`(新记录 ID) |
|
||||
| Model | `Model::create($data)` | 关联数组 | Model 实例 |
|
||||
|
||||
**Logic::add 内部流程:**
|
||||
`beforeAdd` → `validateData` → `camel_to_snake` → `processTenantId` → `processFile` → `processContent` → `filterTableFields` → `checkUnique` → `Model::create` → `afterAdd`
|
||||
**Service::add 内部流程**(HasCrudOperations):
|
||||
|
||||
`beforeAdd` → `checkUnique` → `DB::transaction { Model::create(HasAuditColumns 审计填充)}` → `afterAdd`
|
||||
|
||||
### 修改链路
|
||||
|
||||
| 层 | 方法 | 输入 | 输出 |
|
||||
|----|------|------|------|
|
||||
| Controller | `_edit($logic, $id, $data)` | id + Json 请求体 | Json 响应 |
|
||||
| Logic | `update($id, $data)` | id + 关联数组 | `bool` |
|
||||
| Model | `Model::find($id)->save($data)` | 模型实例 + 关联数组 | `bool` |
|
||||
| Controller | `_update($service, $id, $data)` | id + 请求数据 | JsonResponse |
|
||||
| Service | `update($id, $data)` | id + 关联数组 | `bool` |
|
||||
| Model | `Model::find($id)->update($data)` | 模型 + 关联数组 | `bool` |
|
||||
|
||||
### 删除链路
|
||||
|
||||
| 层 | 方法 | 输入 | 输出 |
|
||||
|----|------|------|------|
|
||||
| Controller | `_remove($logic, $id)` | id | Json 响应 |
|
||||
| Logic | `delete($id)` | id | `bool` |
|
||||
| Model | `$model->save(['is_delete' => 1])` | 软删除标记 | `bool` |
|
||||
| Controller | `_delete($service, $id)` | id | JsonResponse |
|
||||
| Service | `delete($id)` | id | `bool` |
|
||||
| Model | `$model->softDelete()` | 软删除标记 | `bool`(`UPDATE SET is_delete=1`) |
|
||||
|
||||
## 数据流向
|
||||
|
||||
@@ -296,28 +248,26 @@ CorsMiddleware → AuthMiddleware → TenantMiddleware → DemoMiddleware → Lo
|
||||
前端请求(camelCase JSON)
|
||||
│
|
||||
▼
|
||||
BaseController::getJsonBody() ← 获取原始参数
|
||||
FormRequest::validated() ← 格式校验(camelCase 规则),原样传递
|
||||
│
|
||||
▼
|
||||
BaseLogic::beforeAdd($data) ← 钩子:预处理
|
||||
Controller::_add($service, $data) ← 编排 + 注解元数据
|
||||
│
|
||||
▼
|
||||
BaseLogic::validateData($data) ← 自动参数校验
|
||||
Service::doAdd 入口 ← 键名统一转为 snake_case
|
||||
│
|
||||
▼
|
||||
BaseLogic::convertQueryParams() ← camelCase → snake_case
|
||||
Service::beforeAdd($data) ← 钩子:预处理
|
||||
│
|
||||
▼
|
||||
BaseLogic::processFileFieldsOnSave ← 文件字段处理
|
||||
Service::checkUnique($data) ← 唯一性校验(BizException 拦截)
|
||||
│
|
||||
▼
|
||||
BaseLogic::filterTableFields() ← 过滤非数据库字段
|
||||
│
|
||||
▼
|
||||
BaseLogic::checkUnique() ← 唯一性校验
|
||||
│
|
||||
▼
|
||||
BaseModel::create() ← 审计字段自动填充
|
||||
DB::transaction {
|
||||
Service::processTenantId($data) ← 租户填充(HasDataScope 启用时)
|
||||
Service::processFileFieldsOnSave ← 文件字段处理(HasFileProcessing 启用时)
|
||||
Model::create($data) ← 审计字段自动填充(creating 事件)
|
||||
}
|
||||
│
|
||||
▼
|
||||
Database(snake_case)
|
||||
@@ -329,16 +279,15 @@ Database(snake_case)
|
||||
Database(snake_case 数据)
|
||||
│
|
||||
▼
|
||||
BaseModel ← ORM 查询
|
||||
Model ← ORM 查询(软删除全局范围生效)
|
||||
│
|
||||
▼
|
||||
BaseLogic 处理
|
||||
│ processFileFieldsOnRead() ← 文件字段补全域名
|
||||
│ processContentFieldsOnRead() ← 富文本占位符替换
|
||||
│ processSerializeMaps() ← 枚举显示名补全
|
||||
│ afterDetail() ← 钩子:补充关联数据
|
||||
Service 处理(processAfterRead 处理链)
|
||||
│ processFileFieldsOnRead() ← 文件字段补全域名(启用时)
|
||||
│ processSerializeMapsList() ← 字典显示名补全(status → statusText)
|
||||
│ afterDetail()/afterPageList() ← 钩子:补充关联数据
|
||||
▼
|
||||
Result::convertData() ← snake_case → camelCase
|
||||
Result::success() / page() ← snake_case → camelCase 转换
|
||||
│
|
||||
▼
|
||||
JSON 响应 → 前端
|
||||
@@ -350,80 +299,87 @@ JSON 响应 → 前端
|
||||
Model 层异常(如数据库连接失败)
|
||||
│
|
||||
▼ 向上抛出
|
||||
Logic 层异常(如 beforeDelete 抛出业务异常、validateData 抛出 ValidateException)
|
||||
│
|
||||
Service 层异常
|
||||
│ beforeAdd/beforeDelete 抛出 BizException(业务规则)
|
||||
│ 唯一性校验抛出 BizException
|
||||
│ DB::transaction 自动回滚
|
||||
▼ 向上抛出
|
||||
Controller 层
|
||||
│ 方式一:try-catch 捕获,返回 fail()
|
||||
│ 方式二:不捕获,由 ExceptionHandle 统一处理
|
||||
│ 方式一:_add/_update/_delete 等快捷方法捕获 BizException → fail(消息, code)
|
||||
│ 方式二:不捕获,由全局 Handler 统一处理
|
||||
▼
|
||||
ExceptionHandle::render()
|
||||
│ ValidateException → {"code":1, "msg":"参数验证失败", "data":"..."}
|
||||
│ ModelNotFoundException → {"code":1, "msg":"数据不存在"}
|
||||
│ RuntimeException(401) → {"code":1, "msg":"未授权"}
|
||||
│ 其他异常 → {"code":1, "msg":"操作失败"}
|
||||
Exceptions\Handler::render()
|
||||
│ BizException → {code:异常code(默认1), msg:"业务提示"}
|
||||
│ ValidationException → {code:422, msg:"参数验证失败", data:...}
|
||||
│ AuthenticationException → {code:401}
|
||||
│ NotFoundHttpException → {code:404}
|
||||
│ 其它异常 → {code:500, msg:调试原文/生产"操作失败"}
|
||||
▼
|
||||
JSON 响应
|
||||
```
|
||||
|
||||
**推荐做法:**
|
||||
- Controller 通过 `_add`/`_edit`/`_remove` 调用时,不捕获异常,交给 ExceptionHandle
|
||||
- Controller 直接调用 `$this->logic->add()` 时,try-catch 捕获并返回 `$this->fail()`
|
||||
- Logic 层的业务异常(如 beforeDelete 校验)直接 `throw new \Exception`
|
||||
**推荐做法**:
|
||||
|
||||
- Controller 通过 `_add` / `_update` / `_delete` 调用时,**不捕获异常**,交给快捷方法与 Handler
|
||||
- Controller 手写复杂流程时,可 try-catch 捕获 `BizException` 后返回 `$this->fail()`
|
||||
- Service 层的业务校验直接 `throw new BizException('提示')`,**不要**返回错误码让上层判断
|
||||
|
||||
## 完整请求示例
|
||||
|
||||
以"新增文章"为例,展示一个请求从进入到返回的完整链路:
|
||||
以"新增案例"为例,展示一个请求从进入到返回的完整链路:
|
||||
|
||||
```text
|
||||
POST /api/article/add
|
||||
POST /api/example/add
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <token>
|
||||
|
||||
{"title": "新文章", "categoryId": 1, "status": 0}
|
||||
{"name": "新案例", "type": 1, "status": 1}
|
||||
```
|
||||
|
||||
```text
|
||||
1. CorsMiddleware → 放行(设置跨域头)
|
||||
2. AuthMiddleware → 校验 Token → 注入 $request->userInfo
|
||||
3. TenantMiddleware → 解析租户 → 注入 $request->tenantId
|
||||
4. LogMiddleware → 记录日志(#[Log] 注解)
|
||||
1. ProxyMiddleware → 真实 IP 识别(受信代理)
|
||||
2. Cors → 非 OPTIONS,放行(响应时追加 CORS 头)
|
||||
3. ForceJsonResponse → Accept 强制 JSON
|
||||
4. Authenticate → 校验 Token → 注入 attributes['userInfo']
|
||||
→ 反射 #[Permission] → checkPermission('sys:example:add') ✓
|
||||
5. ResolveTenant → 注入 tenantId / isSuperAdmin
|
||||
6. LogOperation(handle) → 收集上下文,读 #[OperationLog]
|
||||
|
||||
5. ArticleController::add()
|
||||
│ getJsonBody() → ["title"=>"新文章", "categoryId"=>1, "status"=>0]
|
||||
│
|
||||
7. ExampleRequest
|
||||
│ rules() 校验 name/type/status ✓
|
||||
│ validated() → ["name"=>"新案例", "type"=>1, "status"=>1]
|
||||
▼
|
||||
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 唯一 ✓
|
||||
│
|
||||
8. ExampleController::add()
|
||||
│ 注解元数据已由 4/6 读取
|
||||
│ $this->_add($service, $data)
|
||||
▼
|
||||
7. Article::create($data)
|
||||
│ onBeforeInsert → 自动填充 create_user, create_time
|
||||
│
|
||||
9. ExampleService::add($data)
|
||||
│ 入口键名转换 → array_camel_to_snake()
|
||||
│ beforeAdd($data) → 默认 sort(钩子)
|
||||
│ checkUnique($data) → name 唯一 ✓
|
||||
│ DB::transaction {
|
||||
│ Example::create($data) → creating:create_user/create_time 自动填充
|
||||
│ }
|
||||
│ afterAdd($id, $data) → 无额外操作
|
||||
▼
|
||||
8. Database INSERT
|
||||
10. Database INSERT → laravel_example → 自增 ID 42
|
||||
|
||||
9. ArticleLogic::afterAdd($id, $data) → 无额外操作
|
||||
11. 返回 {"code":0, "ok":true, "msg":"添加成功", "data":{"id":42}}
|
||||
|
||||
10. 返回 {"code":0, "ok":true, "msg":"添加成功", "data":{"id":42}}
|
||||
12. LogOperation(terminate) → 写入 laravel_operation_log(响应后,不影响业务)
|
||||
```
|
||||
|
||||
## 层间通信规范
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| Controller → Logic | ✅ 允许,标准调用 |
|
||||
| Controller → Service | ✅ 允许,标准调用 |
|
||||
| Controller → Model | ❌ 禁止,越层 |
|
||||
| Logic → Model | ✅ 允许,标准调用 |
|
||||
| Logic → Service | ✅ 允许,调用通用能力 |
|
||||
| Logic → Controller | ❌ 禁止,反向依赖 |
|
||||
| Model → Logic | ❌ 禁止,反向依赖 |
|
||||
| Model → Service | ⚠️ 慎用,仅静态工具方法 |
|
||||
| Middleware → Controller | ✅ 通过 `$request` 注入数据 |
|
||||
| Validate → Logic | ❌ 禁止,只做格式校验 |
|
||||
| Controller → FormRequest | ✅ 允许,类型注入 |
|
||||
| Service → Model | ✅ 允许,标准调用 |
|
||||
| Service → 横切 Service | ✅ 允许,调用通用能力(静态/注入均可) |
|
||||
| Service → Controller | ❌ 禁止,反向依赖 |
|
||||
| Service → FormRequest | ❌ 禁止,格式校验在 HTTP 入口完成 |
|
||||
| Model → Service / Controller | ❌ 禁止,反向依赖 |
|
||||
| Middleware → 后续链路 | ✅ 通过 `request->attributes` 注入数据 |
|
||||
| FormRequest → 业务规则 | ❌ 禁止,只做格式校验(业务规则在 Service) |
|
||||
|
||||
Reference in New Issue
Block a user