Files
vibe_coding/.cursor/rules/references/013-backend-deep.md
2026-03-05 21:27:11 +08:00

446 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
<?php
declare(strict_types=1);
namespace App\Service\Production;
use App\Model\Production\ProductionOrder;
use App\Repository\Production\OrderRepository;
use Hyperf\Di\Annotation\Inject;
use Hyperf\DbConnection\Db;
class OrderService
{
#[Inject]
protected OrderRepository $orderRepository;
public function create(array $data): ProductionOrder
{
return Db::transaction(function () use ($data) {
$order = $this->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
<?php
declare(strict_types=1);
namespace App\Request\Production;
use Hyperf\Validation\Request\FormRequest;
class CreateOrderRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'customer_id' => '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 *`,明确列名
- 禁止在循环中执行 SQLN+1 问题)
- 禁止在事件监听器中抛出异常阻塞主流程
- 禁止在定时任务中未加 `onOneServer` 导致多实例重复执行