初始化
This commit is contained in:
445
.cursor/rules/references/013-backend-deep.md
Normal file
445
.cursor/rules/references/013-backend-deep.md
Normal file
@@ -0,0 +1,445 @@
|
||||
# 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 *`,明确列名
|
||||
- 禁止在循环中执行 SQL(N+1 问题)
|
||||
- 禁止在事件监听器中抛出异常阻塞主流程
|
||||
- 禁止在定时任务中未加 `onOneServer` 导致多实例重复执行
|
||||
Reference in New Issue
Block a user