133 lines
6.0 KiB
Markdown
133 lines
6.0 KiB
Markdown
---
|
||
name: DailyRebateLogic重构
|
||
overview: 按 `php-code` 规范重构每日返水领取链路:`claim` 编排化、BusinessException、Model 条件更新;同步调整 Controller 异常映射。`settleDate`/`info` 仅做必要小抽取,不改业务语义。
|
||
todos:
|
||
- id: model-mark-claimed
|
||
content: DailyRebateRecordModel 增加 markClaimedIfClaimable + PHPDoc
|
||
status: pending
|
||
- id: logic-claim-refactor
|
||
content: DailyRebateLogic:claim 拆 assert/perform/format,BusinessException
|
||
status: pending
|
||
- id: logic-info-helper
|
||
content: 可选:isClaimableRecord 供 info 与 assert 复用
|
||
status: pending
|
||
- id: controller-exception
|
||
content: DailyRebateController:catch BusinessException → FAILED
|
||
status: pending
|
||
- id: verify-smoke
|
||
content: 跑 verify-slot-backend.sh;确认无 RuntimeException 新增
|
||
status: pending
|
||
isProject: false
|
||
---
|
||
|
||
# DailyRebateLogic 规范重构
|
||
|
||
## 范围(AskQuestion 中断,按推荐默认)
|
||
|
||
- **必做**:[`claim`](slot_console/app/api/logic/DailyRebateLogic.php)、[`DailyRebateController::claim`](slot_console/app/api/controller/DailyRebateController.php)、[`DailyRebateRecordModel`](slot_console/app/model/common/DailyRebateRecordModel.php)
|
||
- **轻量**:`info()` 抽取「是否可领」判断为 private,与 `assertClaimableRecord` 复用逻辑
|
||
- **不改语义**:`settleDate` / `expireDueRecords` / Redis 结算逻辑保持行为一致,仅在有重复代码时抽 1 个 private(可选)
|
||
|
||
## 现状问题(对照 php-code)
|
||
|
||
[`claim`](slot_console/app/api/logic/DailyRebateLogic.php) L92–157:
|
||
|
||
- 6 段 `RuntimeException` if 墙
|
||
- CAS 写在 Logic 内联,未沉淀 Model
|
||
- Controller `catch RuntimeException` + `PARAMS_ERROR`(verify 会拦新增行)
|
||
|
||
参照:[`FreeCreditsLogic::claim`](slot_console/app/api/logic/FreeCreditsLogic.php)(`assert*` + `BusinessException` + 状态更新)
|
||
|
||
## 目标结构
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant C as DailyRebateController
|
||
participant L as DailyRebateLogic
|
||
participant M as DailyRebateRecordModel
|
||
participant W as WalletService
|
||
|
||
C->>L: claim(uid, source, statDate)
|
||
L->>L: assertActivityEnabled
|
||
L->>L: assertUserDeposited
|
||
L->>M: findByUidAndDate
|
||
L->>L: assertClaimableRecord
|
||
L->>M: markClaimedIfClaimable
|
||
L->>W: gift(rebate, DAILY_REBATE, remark)
|
||
L-->>C: formatClaimResult
|
||
```
|
||
|
||
### 1. Model:条件更新
|
||
|
||
在 [`DailyRebateRecordModel`](slot_console/app/model/common/DailyRebateRecordModel.php) 新增:
|
||
|
||
```php
|
||
/**
|
||
* 待领取状态下标记为已领取(CAS)。
|
||
*
|
||
* @return int 影响行数,1 表示成功
|
||
*/
|
||
public static function markClaimedIfClaimable(int $id): int
|
||
```
|
||
|
||
实现:`where id` + `where status = STATUS_CLAIMABLE` → `STATUS_CLAIMED` + `claimed_at`。
|
||
|
||
### 2. Logic:`claim` 拆分为编排 + assert + perform
|
||
|
||
| 方法 | 职责 |
|
||
|------|------|
|
||
| `claim()` public | 编排 ≤20 行:`resolveSource` → 默认 `statDate` → assert → `performClaim` → 返回 |
|
||
| `assertActivityEnabled(string $source)` | 活动未开 → `BusinessException` |
|
||
| `assertUserDeposited(int $uid)` | 未充值 → `BusinessException` |
|
||
| `assertClaimableRecord(?DailyRebateRecordModel $record)` | 不存在/状态/金额/过期 → 各一条 `BusinessException`(合并原 4 条 if) |
|
||
| `performClaim(int $uid, DailyRebateRecordModel $record, string $statDate)` | 事务:`markClaimedIfClaimable` → `WalletService::gift` → commit;`affected !== 1` 或 wallet 空 → `BusinessException` |
|
||
| `formatClaimResult(...)` | 返回 `stat_date` / `rebate_amount` / `display` / `balance` |
|
||
|
||
- 使用 `support\exception\BusinessException`(与 FreeCredits 一致)
|
||
- `@throws` 改为 `BusinessException`、`\Throwable`(钱包失败)
|
||
- **钱包 biz_id**:`WalletService::gift()` 内部已 `generateOrderId`([`slot_lib/src/services/WalletService.php`](slot_lib/src/services/WalletService.php) L262–265);幂等依赖 **记录 CAS**,remark 保持 `每日返水 {statDate}` 便于对账
|
||
|
||
### 3. Logic:`info` 轻量复用(可选)
|
||
|
||
抽取 `isClaimableRecord(DailyRebateRecordModel $record): bool`(status + amount + expire),供 `info()` 的 `claimable` 块与 `assertClaimableRecord` 共用,避免两套判断漂移。
|
||
|
||
### 4. Controller:异常映射
|
||
|
||
[`DailyRebateController::claim`](slot_console/app/api/controller/DailyRebateController.php):
|
||
|
||
```php
|
||
} catch (BusinessException $e) {
|
||
return $this->errorCode(ErrorCode::FAILED, $e->getMessage());
|
||
}
|
||
```
|
||
|
||
- 对齐 [`SignController`](slot_console/app/api/controller/SignController.php)(业务失败 `FAILED` + 文案)
|
||
- **API 变更**:`code` 由 `40003`(PARAMS_ERROR)变为 `1`(FAILED);文案仍为中文业务提示。若 PWA 强依赖 `40003`,可在计划中改为保留 `PARAMS_ERROR`(实现时二选一,默认 `FAILED`)
|
||
|
||
不采用 FreeCredits「无 catch、走全局 Handler」方式,避免未捕获时变成 `SYSTEM_ERROR`(50001)。
|
||
|
||
### 5. 不动 / 谨慎
|
||
|
||
- **事务边界**:维持「先 CAS 再 gift 再 commit」;不在此 PR 改为「先 wallet 后 DB」或 Outbox
|
||
- **`settleDate`**:逻辑不变;可选抽 `settleOneUserFromRedis(...)` 降低 foreach 嵌套(非必须)
|
||
|
||
## 文件清单
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `slot_console/app/model/common/DailyRebateRecordModel.php` | +`markClaimedIfClaimable` |
|
||
| `slot_console/app/api/logic/DailyRebateLogic.php` | 重构 `claim`;可选 `isClaimableRecord` |
|
||
| `slot_console/app/api/controller/DailyRebateController.php` | `BusinessException` + `FAILED` |
|
||
|
||
## 验收
|
||
|
||
1. `docker exec -w /app/www/slot/slot_console php82 php webman dailyRebateSettle --date=...`(如有环境)结算后,已充值用户可领昨日返水
|
||
2. 重复领取 → 业务错误文案,DB 仍为 `CLAIMED`,不重复入账
|
||
3. `~/.cursor/hooks/verify-slot-backend.sh` → PASS(无新增 `RuntimeException` 业务态)
|
||
4. 最终回复含 `PHPDoc: checked`(触及符号补全 `@throws BusinessException`)
|
||
|
||
## 风险与回滚
|
||
|
||
- **PWA 错误码**:若前端按 `code===40003` 分支,需同步前端或 Controller 保留 `PARAMS_ERROR`
|
||
- 回滚:还原 3 个文件即可
|