初始化

This commit is contained in:
2026-03-05 21:27:11 +08:00
commit 130de0fd5d
140 changed files with 21972 additions and 0 deletions

View 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 *`,明确列名
- 禁止在循环中执行 SQLN+1 问题)
- 禁止在事件监听器中抛出异常阻塞主流程
- 禁止在定时任务中未加 `onOneServer` 导致多实例重复执行