63 lines
5.6 KiB
Markdown
63 lines
5.6 KiB
Markdown
# 任务中心领取逻辑风险审查与测试计划
|
||
|
||
## 背景与目标
|
||
- **风险点**:用户可直接领取、或依赖任务上线前的历史数据领取新的任务奖励,尤其是在 `window` 未配置或默认 `lifetime` 的档位上。
|
||
- **目标**:明确代码审查要点、需要补强的校验以及自动化/手工测试,确保任务中心的进度统计与领取逻辑默认受限于任务 `StartTime ~ EndTime`。
|
||
|
||
## 现状速览
|
||
- **窗口处理**:`normalizeWindow` + `computeTimeWindow` 对非法窗口统一回退 `lifetime`,`WindowActivityPeriod` 才会套用任务起止时间(`internal/service/task_center/service.go:200-525`)。未配置或设置 `lifetime` 的档位默认统计全部历史。
|
||
- **进度统计**:`GetUserProgress` 按 `(window, activity_id)` 分组统计 `TierProgressMap`,并回退到全局订单/邀请(`service.go:528-668`)。
|
||
- **领取校验**:`ClaimTier` 先读取 `TierProgressMap`,再按 metric/operator 判断;含活动的档位使用 Redis `tc:claim_lock:{user_id}:{activity_id}` 加锁,并做跨任务阈值校验(`service.go:670-939`)。全局档位没有锁,任务级限额只在总量层面控制。
|
||
|
||
## 审查与增强要点
|
||
### 1. 窗口与时间范围
|
||
1. 将“任务设置了 `StartTime`/`EndTime` 但 window=空或 `lifetime`”纳入巡检:建议在 `normalizeWindow` or `GetUserProgress` 中,当 `task.StartTime` 不为空时自动将窗口裁剪到 `[StartTime, EndTime]`,并在单测覆盖空/NULL 窗口场景。
|
||
2. 明确 `since_registration` 行为:若不允许历史数据,应在 `computeTimeWindow` 中将起点设为 `max(user.RegistrationTime, task.StartTime)`。
|
||
3. 验证 `TierProgressMap`、`SubProgress` 是否正确过滤 `start_time`/`end_time`:针对 `daily/weekly/activity_period/lifetime` 构造订单在边界日/周的案例,确保窗口重置与任务期重叠判断符合预期。
|
||
|
||
### 2. 领取校验扩展
|
||
1. 在 `ClaimTier` 中新增“窗口开始 < task.StartTime”判断:若 `tierProgress` 返回数据完全来自任务上线前,需拒绝并提示“任务尚未开始”或“需重新累计”。
|
||
2. 复核跨任务阈值 `calculateCrossTaskConsumedThreshold`:目前按任务创建时间 + 窗口重叠过滤,但若 `siblingRows` 包含旧任务的 `lifetime` 档位仍可能消耗全部历史,需要配合第 1 点的窗口裁剪。
|
||
3. Redis 锁范围:对于 `activity_id=0` 的档位,可增加 `tc:claim_lock_task:{user_id}:{task_id}`,防止并发重复领取。
|
||
|
||
### 3. 配置与数据守护
|
||
1. **巡检 SQL**(示例):
|
||
```sql
|
||
SELECT tiers.id, tiers.task_id, tiers.window, tasks.start_time, tasks.end_time
|
||
FROM task_center_task_tiers tiers
|
||
JOIN task_center_tasks tasks ON tasks.id = tiers.task_id
|
||
WHERE (tiers.window IS NULL OR tiers.window = '' OR tiers.window = 'lifetime')
|
||
AND tasks.start_time IS NOT NULL;
|
||
```
|
||
对结果逐条评估是否需改成 `activity_period` 或自定义窗口。
|
||
2. 若需让运营显式配置“允许历史数据”,可在 `task_center_task_tiers` 增加 `allow_legacy_data TINYINT`,并在 `admin` Upsert 接口透出。当前阶段以代码默认裁剪为主。
|
||
|
||
## 自动化测试计划
|
||
| 编号 | 场景 | 步骤 | 预期 |
|
||
| --- | --- | --- | --- |
|
||
| UT-1 | `lifetime` + StartTime 剪裁 | 任务 StartTime=T0,插入 T0-1/T0+1 订单,调用 `GetUserProgress` | 仅统计 T0+1 数据;`TierProgressMap` 中的数量=1 |
|
||
| UT-2 | 空 Window | 档位 `window=''`,任务 StartTime=T0,同上 | 行为等同 UT-1 |
|
||
| UT-3 | `since_registration` | 构造用户注册时间 Treg < T0,验证窗口起点为 `max(Treg, T0)` | 统计以 `T0` 为准 |
|
||
| IT-1 | 历史数据领取阻断 | 先插入历史订单,使 `TierProgressMap` 达标;上线任务后 `ClaimTier` | 返回“任务条件未达成” |
|
||
| IT-2 | 新订单后可领 | 在 IT-1 基础上插入新订单超过阈值再 `ClaimTier` | 领取成功 |
|
||
| IT-3 | 跨任务占用 | 旧任务(已领 50 单) + 新任务阈值 60 单,新订单 15 单 | `ClaimTier` 拒绝,日志输出 `cross-task threshold`;再补 10 单 → 成功 |
|
||
| IT-4 | Redis 锁 | 并发触发 `ClaimTier`;activityID>0 与 activityID=0 场景分别验证 | 仅一次成功,其余提示“操作频繁”或“已领取” |
|
||
|
||
> 自动化测试可基于现有 SQLite Repo(`mysql.NewSQLiteRepoForTest`)快速构造数据,参考 `service_test.go`、`invite_logic_test.go` 的写法。
|
||
|
||
## 手工/灰度验证
|
||
1. **SQL 巡检**:执行上文 SQL,导出需要修正的档位,配合运营确认并批量更新 `window`。
|
||
2. **模拟接口回放**:通过 `/admin/task_center/simulate/order_paid`、`/simulate/invite_success` 重放旧流水,再调整任务时间并调用 `/tasks/{id}/claim/{user}`,观察日志(`ClaimTier: cross-task threshold...`、`任务尚未开始` 等)。
|
||
3. **小程序体验**:发布新任务后,用老用户登录 `pages-user/tasks`,确认显示进度清零、领取按钮禁用;完成新订单后刷新 → 按预期解锁。
|
||
|
||
## 风险与假设
|
||
- 默认业务需求为“任务上线前的历史数据不可复用”,如需白名单例外需另开配置。
|
||
- Redis/数据库资源允许新增少量锁与巡检脚本,不影响现有性能。
|
||
- 若需要对现网数据批量改 `window`,需评估是否会影响已经配置为 `lifetime` 的任务,并提前同步运营。
|
||
|
||
## 下一步
|
||
1. 根据本计划完成代码 PoC(窗口剪裁、领取校验、锁扩展)。
|
||
2. 提交自动化测试用例,覆盖表格中的 UT/IT 场景。
|
||
3. 运行 SQL 巡检 + 手工验证,记录整改项。
|
||
4. 如需引入“允许历史数据”配置,评估 schema 与前端/运营端改造影响,再单独立项。
|