17 KiB
4.3 后端分层架构
:::tip 概述 后端采用"三层主干 + 三层支撑"架构:Controller → Service → Model 构成主干链路,FormRequest、横切服务、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。 :::
架构全景
┌─────────────────────────────────────────────────────────────────────┐
│ 支撑层 │
│ │
│ 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 不应该做:业务判断、数据库操作、文件处理、重复的格式校验。
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 响应。
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)。
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
职责:参数格式校验(必填、类型、长度、枚举),不含业务规则
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 前后执行
ProxyMiddleware → Cors → ForceJsonResponse → Authenticate → ResolveTenant → LogOperation → Controller
| 中间件 | 作用 | 注入数据 |
|---|---|---|
Authenticate |
JWT 认证 + 权限校验 | attributes['userInfo'] |
ResolveTenant |
租户上下文 | attributes['tenantId'] / isSuperAdmin / tenantInfo |
LogOperation |
操作日志(handle 采集,terminate 落库) | — |
详见 4.2 请求处理链路。
各层方法对照表
分页查询链路
| 层 | 方法 | 输入 | 输出 |
|---|---|---|---|
| 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) |
数据流向
请求方向(前端 → 后端 → 数据库)
前端请求(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)
响应方向(数据库 → 后端 → 前端)
Database(snake_case 数据)
│
▼
Model ← ORM 查询(软删除全局范围生效)
│
▼
Service 处理(processAfterRead 处理链)
│ processFileFieldsOnRead() ← 文件字段补全域名(启用时)
│ processSerializeMapsList() ← 字典显示名补全(status → statusText)
│ afterDetail()/afterPageList() ← 钩子:补充关联数据
▼
Result::success() / page() ← snake_case → camelCase 转换
│
▼
JSON 响应 → 前端
异常处理传播
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('提示'),不要返回错误码让上层判断
完整请求示例
以"新增案例"为例,展示一个请求从进入到返回的完整链路:
POST /api/example/add
Content-Type: application/json
Authorization: Bearer <token>
{"name": "新案例", "type": 1, "status": 1}
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) |