# 013-backend.mdc (Deep Reference) > 该文件为原始详细规范归档,供 Tier 3 按需读取。 --- # 🖥️ PHP Hyperf + Swoole Backend Standards 参考文档: @docs/architecture/api-contracts.md @docs/architecture/system-design.md ## 分层架构 ``` Request → Middleware → Controller → Service → Repository → Model → DB ↓ Event → Listener (异步) ``` | 层 | 职责 | 禁止 | |---|---|---| | **Controller** | 接收请求、参数验证、调用 Service、返回响应 | 包含业务逻辑 | | **Service** | 核心业务逻辑、事务管理、调用 Repository | 直接操作数据库 | | **Repository** | 数据访问、查询构建、数据权限过滤 | 包含业务判断 | | **Model** | 数据模型、关联定义、类型转换 | 包含业务方法 | ## PHP 编码规范 (PSR-12) ```php orderRepository->create($data); // trigger event event(new OrderCreated($order)); return $order; }); } } ``` **命名规范**: | 类型 | 规范 | 示例 | |------|------|------| | 类 | PascalCase | `OrderService` | | 方法 | camelCase | `createOrder()` | | 变量 | camelCase | `$orderData` | | 常量 | SCREAMING_SNAKE | `MAX_RETRY_COUNT` | | 数据库字段 | snake_case | `created_at` | | 路由 | kebab-case 复数 | `/admin/production-orders` | ## API 设计 - RESTful 命名: 复数名词 (`/admin/users`, `/admin/production-orders`) - 版本控制: `/admin/v1/...`(可选) - 统一响应格式: ```php // 成功 { "code": 200, "message": "ok", "data": T } // 列表(带分页) { "code": 200, "message": "ok", "data": { "items": [], "total": 100 } } // 错误 { "code": 422, "message": "Validation failed", "data": { "errors": {} } } ``` ## 输入验证 ```php // app/Request/Production/CreateOrderRequest.php 'required|integer|exists:customers,id', 'platform_id' => 'required|integer|exists:platforms,id', 'order_type' => 'required|integer|in:1,2,3', 'remark' => 'nullable|string|max:500', ]; } } ``` ## 认证/授权 - JWT 双 Token 机制: Access Token (2h) + Refresh Token (7d) - Token 存储在 Redis,支持强制失效 - 密码: `password_hash()` + `PASSWORD_BCRYPT` - 速率限制: `#[RateLimit]` 注解 ## 中间件链 ``` Request → ErrorLogMiddleware # 异常捕获 → AccessTokenMiddleware # JWT 验证 → PermissionMiddleware # RBAC 权限 → DataPermissionMiddleware # 数据权限过滤 → OperationMiddleware # 操作日志 → Controller ``` ## 异常处理器链 异常处理器按优先级顺序注册,每个 Handler 只处理对应的异常类型: ```php // config/autoload/exceptions.php return [ 'handler' => [ 'http' => [ // Priority: higher number = higher priority ValidationExceptionHandler::class, // 验证异常 → 422 AuthExceptionHandler::class, // 认证异常 → 401 PermissionExceptionHandler::class, // 权限异常 → 403 BusinessExceptionHandler::class, // 业务异常 → 自定义 code RateLimitExceptionHandler::class, // 限流异常 → 429 ModelNotFoundExceptionHandler::class, // 模型未找到 → 404 QueryExceptionHandler::class, // 数据库异常 → 500(隐藏细节) AppExceptionHandler::class, // 兜底异常 → 500 ], ], ]; ``` ```php // app/Exception/BusinessException.php namespace App\Exception; use Hyperf\Server\Exception\ServerException; class BusinessException extends ServerException { public function __construct( int $code = 500, string $message = '', ?\Throwable $previous = null, ) { parent::__construct($message, $code, $previous); } } // app/Exception/Handler/BusinessExceptionHandler.php class BusinessExceptionHandler extends ExceptionHandler { public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface { $this->stopPropagation(); return $response->withStatus(200)->withBody(new SwooleStream(json_encode([ 'code' => $throwable->getCode(), 'message' => $throwable->getMessage(), 'data' => null, ], JSON_UNESCAPED_UNICODE))); } public function isValid(Throwable $throwable): bool { return $throwable instanceof BusinessException; } } ``` ## 事件系统 Event + Listener 解耦异步逻辑,避免 Service 膨胀: | 事件 | 触发时机 | 监听器 | |------|---------|--------| | `OrderCreated` | 订单创建后 | 发送通知、记录日志、同步第三方 | | `OrderStatusChanged` | 状态变更后 | 更新统计、通知相关人 | | `PaymentReceived` | 收款确认后 | 更新订单状态、触发生产 | | `UserLoggedIn` | 登录成功 | 记录登录日志、更新最后登录时间 | | `UserRegistered` | 注册成功 | 发送欢迎邮件、初始化默认数据 | | `WorkflowApproved` | 审批通过 | 推进流程、通知下一节点 | | `WorkflowRejected` | 审批驳回 | 通知发起人、记录原因 | | `FileUploaded` | 文件上传完成 | 生成缩略图、同步到 OSS | | `NotificationSent` | 通知发出 | WebSocket 推送、记录日志 | | `CacheInvalidated` | 缓存失效 | 预热缓存、记录日志 | ```php // app/Event/OrderCreated.php class OrderCreated { public function __construct(public readonly ProductionOrder $order) {} } // app/Listener/SendOrderNotificationListener.php #[Listener] class SendOrderNotificationListener implements ListenerInterface { public function listen(): array { return [OrderCreated::class]; } public function process(object $event): void { /** @var OrderCreated $event */ $this->notificationService->send( userId: $event->order->created_by, type: 'order_created', data: ['order_no' => $event->order->order_no], ); } } ``` ## 数据权限过滤 5 级 DATA_SCOPE 控制用户可见数据范围,在 Repository 层自动过滤: | 级别 | 常量 | 说明 | |------|------|------| | 全部 | `DATA_SCOPE_ALL` | 管理员,无限制 | | 自定义 | `DATA_SCOPE_CUSTOM` | 指定部门集合 | | 本部门 | `DATA_SCOPE_DEPT` | 仅本部门数据 | | 本部门及下级 | `DATA_SCOPE_DEPT_AND_CHILD` | 本部门 + 子部门 | | 仅本人 | `DATA_SCOPE_SELF` | 仅自己创建的数据 | ```php // app/Repository/Concern/DataPermissionTrait.php trait DataPermissionTrait { protected function applyDataScope(Builder $query): Builder { $user = Context::get('current_user'); if (!$user) return $query; return match ($user->data_scope) { DataScope::ALL => $query, DataScope::CUSTOM => $query->whereIn('dept_id', $user->custom_dept_ids), DataScope::DEPT => $query->where('dept_id', $user->dept_id), DataScope::DEPT_AND_CHILD => $query->whereIn( 'dept_id', $this->deptService->getChildDeptIds($user->dept_id), ), DataScope::SELF => $query->where('created_by', $user->id), }; } } ``` ## 分布式锁模式 使用 Redis 分布式锁防止并发操作导致数据不一致: ```php // 订单锁定场景 class OrderLockService { private const LOCK_PREFIX = 'order_lock:'; private const LOCK_TTL = 30; private const LOCK_TYPES = [ 'edit' => '编辑锁', 'process' => '处理锁', 'payment_pending' => '待收款锁', 'payment_missing' => '缺款锁', ]; public function acquireLock(int $orderId, string $type, int $userId): bool { $key = self::LOCK_PREFIX . "{$type}:{$orderId}"; return $this->redis->set($key, $userId, ['NX', 'EX' => self::LOCK_TTL]); } public function releaseLock(int $orderId, string $type, int $userId): bool { $key = self::LOCK_PREFIX . "{$type}:{$orderId}"; // Lua script for atomic check-and-delete $script = <<<'LUA' if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end LUA; return (bool) $this->redis->eval($script, [$key, (string) $userId], 1); } } ``` ## Model Traits 常用 Model Trait 复用数据库行为: | Trait | 职责 | |-------|------| | `HasCreator` | 自动填充 `created_by` / `updated_by` | | `SoftDeletes` | 软删除 (`deleted_at`) | | `HasOperationLog` | 操作日志记录 | | `HasDataPermission` | 查询自动加数据权限条件 | | `HasSortable` | 拖拽排序 (`sort_order`) | ```php // app/Model/Concern/HasCreator.php trait HasCreator { public static function bootHasCreator(): void { static::creating(function (Model $model) { $user = Context::get('current_user'); if ($user) { $model->created_by = $user->id; $model->updated_by = $user->id; } }); static::updating(function (Model $model) { $user = Context::get('current_user'); if ($user) { $model->updated_by = $user->id; } }); } } ``` ## 定时任务 ```php // app/Crontab/DailyStatisticsCrontab.php #[Crontab( name: 'daily_statistics', rule: '0 2 * * *', singleton: true, onOneServer: true, memo: '每日凌晨 2 点统计报表' )] class DailyStatisticsCrontab { #[Inject] protected StatisticsService $statisticsService; public function execute(): void { $this->statisticsService->generateDailyReport( Carbon::yesterday() ); } } ``` **注解说明**: - `singleton: true` — 同一进程内不重复执行 - `onOneServer: true` — 多实例部署时只在一台执行 ## 请求签名验证 前端签名 + 后端验签,防止请求篡改: ```php // app/Middleware/RequestSignMiddleware.php class RequestSignMiddleware implements MiddlewareInterface { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $timestamp = $request->getHeaderLine('X-Timestamp'); $sign = $request->getHeaderLine('X-Sign'); $nonce = $request->getHeaderLine('X-Nonce'); // Step 1: 时间窗口校验(5 分钟内有效) if (abs(time() - (int) $timestamp) > 300) { throw new BusinessException(403, 'Request expired'); } // Step 2: Nonce 防重放 if (!$this->redis->set("nonce:{$nonce}", 1, ['NX', 'EX' => 300])) { throw new BusinessException(403, 'Duplicate request'); } // Step 3: 签名校验 $body = (string) $request->getBody(); $expectedSign = hash_hmac('sha256', "{$timestamp}{$nonce}{$body}", $this->appSecret); if (!hash_equals($expectedSign, $sign)) { throw new BusinessException(403, 'Invalid signature'); } return $handler->handle($request); } } ``` ## 依赖注入 ```php // ✅ 构造函数注入(推荐) public function __construct( protected readonly OrderRepository $orderRepo, protected readonly CacheInterface $cache, ) {} // ✅ 属性注入 #[Inject] protected OrderService $orderService; // ❌ 禁止手动 new 或 make() $service = new OrderService(); // WRONG ``` ## 禁止事项 - 禁止在 Controller 中直接操作数据库 - 禁止在 Swoole 环境使用阻塞 I/O(`file_get_contents`, `sleep`) - 禁止使用全局变量 / 全局静态属性存储请求数据 - 禁止 `dd()` / `var_dump()` 残留在代码中 - 禁止 `SELECT *`,明确列名 - 禁止在循环中执行 SQL(N+1 问题) - 禁止在事件监听器中抛出异常阻塞主流程 - 禁止在定时任务中未加 `onOneServer` 导致多实例重复执行