# 4.3 后端分层架构 :::tip 概述 后端采用"三层主干 + 三层支撑"架构:Controller → Service → Model 构成主干链路,FormRequest、横切服务、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。 ::: ## 架构全景 ```text ┌─────────────────────────────────────────────────────────────────────┐ │ 支撑层 │ │ │ │ FormRequest(参数校验) 横切 Service(通用能力) Middleware(请求拦截)│ │ Http/Requests/*.php Services/(Jwt/Dict…) Http/Middleware/ │ └──────────────────────────────┬──────────────────────────────────────┘ │ 被主干层调用 / 前置拦截 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 主干链路 │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Controller(控制器层) │ │ │ │ 注解标注 → 参数注入 → 调用 Service → 封装响应 │ │ │ │ 基类:Controller | 文件:app/Http/Controllers/Api/*.php │ │ │ └──────────────────────────┬──────────────────────────────────┘ │ │ │ 调用 Service 方法 │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Service(服务层) │ │ │ │ 属性配置 → 唯一性校验 → 生命周期钩子 → 事务 → 调用 Model │ │ │ │ 基类:BaseService / BaseTenantService | Services/*.php │ │ │ └──────────────────────────┬──────────────────────────────────┘ │ │ │ 调用 Eloquent ORM │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Model(模型层) │ │ │ │ 表映射 → 软删除 → 审计字段 → 关联关系 → ORM → SQL │ │ │ │ Trait:SoftDeletesByFlag / HasAuditColumns | Models/*.php │ │ │ └──────────────────────────┬──────────────────────────────────┘ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Database(五种数据库:MySQL / PG / SQL Server / Oracle / SQLite)│ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ``` ## 各层职责详解 ### Controller 层 **文件**:`app/Http/Controllers/Api/{Module}Controller.php` **基类**:`App\Http\Controllers\Controller` **职责**:HTTP 请求入口,只做"编排"不做"判断" | 职责 | 说明 | 示例 | |------|------|------| | 参数注入 | 类型注入 FormRequest 完成格式校验 | `add(ExampleRequest $request)` | | 调用 Service | 委托业务逻辑(快捷方法一行调用) | `$this->_add($service, $data)` | | 响应封装 | 统一 JSON 格式返回 | `$this->success()` / `$this->fail()` | | 注解标注 | 声明权限和日志 | `#[Permission]`、`#[OperationLog]` | **Controller 不应该做**:业务判断、数据库操作、文件处理、重复的格式校验。 ```php class ExampleController extends Controller { public function __construct(protected ExampleService $exampleService) {} // 标准 CRUD —— 一行调用(构造注入,无需 initialize) #[OperationLog('示例管理-查询分页', OperationLog::TYPE_QUERY)] #[Permission('sys:example:page', '示例分页')] public function page(): JsonResponse { return $this->_page($this->exampleService); } #[OperationLog('示例管理-新增', OperationLog::TYPE_ADD, '新增示例:{name}')] #[Permission('sys:example:add', '添加示例')] public function add(ExampleRequest $request): JsonResponse { return $this->_add($this->exampleService, $request->validated()); } } ``` ### Service 层 **文件**:`app/Services/{Module}Service.php` **基类**:`BaseService`(系统共享)/ `BaseTenantService`(租户隔离) **职责**:业务核心,通过"属性配置 + 生命周期钩子"声明式 CRUD | 职责 | 方式 | 说明 | |------|------|------| | 行为定义 | 属性配置 | `$pageLikeFields`、`$uniqueFields`、`$serializeMaps` 等 16 个声明式属性 | | 业务定制 | 生命周期钩子 | `beforeAdd`、`afterUpdate`、`beforeDelete` 等 10 个钩子 | | 横切处理 | Trait 组合 | 文件字段、字典翻译、数据权限、租户隔离(6 个 Concerns Trait) | | 数据安全 | `DB::transaction` | add/update/delete/batchDelete 主流程事务包裹 | **Service 不应该做**:直接 HTTP 交互、直接返回 JSON 响应。 ```php class ExampleService extends BaseService { use HasCrudOperations, HasQueryScopes, HasSerializeMaps, HasUniqueCheck; protected string $modelClass = Example::class; protected array $pageLikeFields = ['name']; protected array $pageEqFields = ['status']; protected array $uniqueFields = ['name']; protected array $serializeMaps = ['status' => 'example_status']; // 业务定制:钩子按需重写 protected function beforeAdd(array $data): array { $data['sort'] = $data['sort'] ?? 0; return $data; } } ``` ### Model 层 **文件**:`app/Models/{TableName}.php` **基类**:Eloquent Model + Concerns Trait **职责**:数据持久化,只做表映射和自动维护 | 职责 | 说明 | |------|------| | 表映射 | `protected $table = 'example'`(不含前缀,`DB_PREFIX` 自动拼接) | | 软删除 | `SoftDeletesByFlag` 注册全局范围自动过滤 `is_delete=1` | | 审计字段 | `HasAuditColumns` 的 `creating/updating` 事件自动填充 | | 关联关系 | `hasMany`、`belongsTo`、`belongsToMany` 等 | **Model 不应该做**:业务判断、复杂查询编排(交给 Service)。 ```php class Example extends Model { use HasAuditColumns, SoftDeletesByFlag; protected $table = 'example'; // 关联关系(按需定义) public function category() { return $this->belongsTo(Category::class, 'category_id'); } } ``` ### FormRequest 层(参数校验支撑) **文件**:`app/Http/Requests/{Module}Request.php` **基类**:`BaseRequest` **职责**:参数**格式**校验(必填、类型、长度、枚举),不含业务规则 ```php class ExampleRequest extends BaseRequest { public function rules(): array { return [ 'name' => 'required|string|max:255', 'type' => 'nullable|integer|in:1,2,3,4', ]; } } ``` **触发方式**:控制器方法类型注入,方法执行前自动校验;失败由 `failedValidation()` 返回统一 JSON(`code=1` + 字段明细)。 ### 横切 Service(通用能力支撑) **文件**:`app/Services/{Name}Service.php`(横切类) **职责**:跨模块通用能力,无状态、无副作用 | 服务 | 能力 | |------|------| | `JwtService` | 令牌签发/刷新/解析(Redis 有状态) | | `DictCacheService` | 字典查询(两级缓存) | | `PasswordService` | 密码加密(bcrypt,成本因子 12) | | `ExcelService` | Excel 导入导出(表头映射、分批) | | `CaptchaService` | 图片验证码(IP 限流) | | `RequestInfoService` | IP 归属地 / OS / 浏览器解析 | **横切 Service 不应该做**:持有请求状态、直接返回响应。 ### Middleware 层(请求拦截支撑) **文件**:`app/Http/Middleware/*.php` **职责**:请求拦截,在 Controller 前后执行 ```text ProxyMiddleware → Cors → ForceJsonResponse → Authenticate → ResolveTenant → LogOperation → Controller ``` | 中间件 | 作用 | 注入数据 | |--------|------|----------| | `Authenticate` | JWT 认证 + 权限校验 | `attributes['userInfo']` | | `ResolveTenant` | 租户上下文 | `attributes['tenantId']` / `isSuperAdmin` / `tenantInfo` | | `LogOperation` | 操作日志(handle 采集,terminate 落库) | — | 详见 [4.2 请求处理链路](/zh/architecture/request-flow.html)。 ## 各层方法对照表 ### 分页查询链路 | 层 | 方法 | 输入 | 输出 | |----|------|------|------| | Controller | `_page($service)` | HTTP 请求参数 | JsonResponse | | Service | `pageList($params, $where, $with)` | 参数数组 | `['records'=>[], 'total'=>int, 'current'=>int, 'size'=>int]` | | Model | `Model::query()->paginate(...)` | 分页条件 | LengthAwarePaginator | ### 新增链路 | 层 | 方法 | 输入 | 输出 | |----|------|------|------| | Controller | `_add($service, $data)` | FormRequest 校验后的数组 | JsonResponse | | Service | `add($data)` | snake_case 关联数组 | `int`(新记录 ID) | | Model | `Model::create($data)` | 关联数组 | Model 实例 | **Service::add 内部流程**(HasCrudOperations): `beforeAdd` → `checkUnique` → `DB::transaction { Model::create(HasAuditColumns 审计填充)}` → `afterAdd` ### 修改链路 | 层 | 方法 | 输入 | 输出 | |----|------|------|------| | Controller | `_update($service, $id, $data)` | id + 请求数据 | JsonResponse | | Service | `update($id, $data)` | id + 关联数组 | `bool` | | Model | `Model::find($id)->update($data)` | 模型 + 关联数组 | `bool` | ### 删除链路 | 层 | 方法 | 输入 | 输出 | |----|------|------|------| | Controller | `_delete($service, $id)` | id | JsonResponse | | Service | `delete($id)` | id | `bool` | | Model | `$model->softDelete()` | 软删除标记 | `bool`(`UPDATE SET is_delete=1`) | ## 数据流向 ### 请求方向(前端 → 后端 → 数据库) ```text 前端请求(camelCase JSON) │ ▼ FormRequest::validated() ← 格式校验(camelCase 规则),原样传递 │ ▼ Controller::_add($service, $data) ← 编排 + 注解元数据 │ ▼ Service::doAdd 入口 ← 键名统一转为 snake_case │ ▼ Service::beforeAdd($data) ← 钩子:预处理 │ ▼ Service::checkUnique($data) ← 唯一性校验(BizException 拦截) │ ▼ DB::transaction { Service::processTenantId($data) ← 租户填充(HasDataScope 启用时) Service::processFileFieldsOnSave ← 文件字段处理(HasFileProcessing 启用时) Model::create($data) ← 审计字段自动填充(creating 事件) } │ ▼ Database(snake_case) ``` ### 响应方向(数据库 → 后端 → 前端) ```text Database(snake_case 数据) │ ▼ Model ← ORM 查询(软删除全局范围生效) │ ▼ Service 处理(processAfterRead 处理链) │ processFileFieldsOnRead() ← 文件字段补全域名(启用时) │ processSerializeMapsList() ← 字典显示名补全(status → statusText) │ afterDetail()/afterPageList() ← 钩子:补充关联数据 ▼ Result::success() / page() ← snake_case → camelCase 转换 │ ▼ JSON 响应 → 前端 ``` ## 异常处理传播 ```text Model 层异常(如数据库连接失败) │ ▼ 向上抛出 Service 层异常 │ beforeAdd/beforeDelete 抛出 BizException(业务规则) │ 唯一性校验抛出 BizException │ DB::transaction 自动回滚 ▼ 向上抛出 Controller 层 │ 方式一:_add/_update/_delete 等快捷方法捕获 BizException → fail(消息, code) │ 方式二:不捕获,由全局 Handler 统一处理 ▼ 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` / `_update` / `_delete` 调用时,**不捕获异常**,交给快捷方法与 Handler - Controller 手写复杂流程时,可 try-catch 捕获 `BizException` 后返回 `$this->fail()` - Service 层的业务校验直接 `throw new BizException('提示')`,**不要**返回错误码让上层判断 ## 完整请求示例 以"新增案例"为例,展示一个请求从进入到返回的完整链路: ```text POST /api/example/add Content-Type: application/json Authorization: Bearer {"name": "新案例", "type": 1, "status": 1} ``` ```text 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] 7. ExampleRequest │ rules() 校验 name/type/status ✓ │ validated() → ["name"=>"新案例", "type"=>1, "status"=>1] ▼ 8. ExampleController::add() │ 注解元数据已由 4/6 读取 │ $this->_add($service, $data) ▼ 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) → 无额外操作 ▼ 10. Database INSERT → laravel_example → 自增 ID 42 11. 返回 {"code":0, "ok":true, "msg":"添加成功", "data":{"id":42}} 12. LogOperation(terminate) → 写入 laravel_operation_log(响应后,不影响业务) ``` ## 层间通信规范 | 规则 | 说明 | |------|------| | Controller → Service | ✅ 允许,标准调用 | | Controller → Model | ❌ 禁止,越层 | | Controller → FormRequest | ✅ 允许,类型注入 | | Service → Model | ✅ 允许,标准调用 | | Service → 横切 Service | ✅ 允许,调用通用能力(静态/注入均可) | | Service → Controller | ❌ 禁止,反向依赖 | | Service → FormRequest | ❌ 禁止,格式校验在 HTTP 入口完成 | | Model → Service / Controller | ❌ 禁止,反向依赖 | | Middleware → 后续链路 | ✅ 通过 `request->attributes` 注入数据 | | FormRequest → 业务规则 | ❌ 禁止,只做格式校验(业务规则在 Service) |