refactor(utils): 修复密码哈希比较逻辑错误 feat(user): 新增按状态筛选优惠券接口 docs: 添加虚拟发货与任务中心相关文档 fix(wechat): 修正Code2Session上下文传递问题 test: 补充订单折扣与积分转换测试用例 build: 更新配置文件与构建脚本 style: 清理多余的空行与注释
170 lines
6.7 KiB
Markdown
170 lines
6.7 KiB
Markdown
## 约束与对齐
|
||
- 管理端仅支持“定时到具体时间点”的开奖,不支持设置“定时 N 分钟后开奖”。
|
||
- 计划包含:玩法策略、哈希可验证抽奖、退款+赠券流程、数据一致性、完整 API 与参数。
|
||
|
||
## 玩法与流程
|
||
- 一番赏
|
||
- 定时开奖:必填 `scheduled_time`(绝对时间戳) 与 `min_participants`(最低人数)。到时若人数 < N:全额退款+赠券。≥N:统一开奖。
|
||
- 即时开奖:用户支付成功后立即抽奖并返回凭证。
|
||
- 无限赏/对对碰/爬塔:标准抽奖流程(即时抽奖),策略差异体现在权重、过滤规则、后置效果。
|
||
|
||
## 抽奖与可验证性
|
||
- 种子:服务端使用加密安全 `crypto/rand` 生成 32 字节 `seed`,按期(issue)维度保存。
|
||
- 哈希签名:`signature = HMAC-SHA256(seed, user_id|issue_id|timestamp|nonce)`;`nonce` 为 16 字节随机串,`timestamp` 精确到毫秒。
|
||
- 客户端校验:本地按相同算法计算签名并比对;凭证包含必要输入。
|
||
|
||
## 数据模型(核心字段)
|
||
- `activities`:`id`、`name`、`play_type`(ichiban/infinite/match/tower)、`draw_mode`(scheduled/instant)、`min_participants`、`scheduled_time`、`refund_coupon_type`、`refund_coupon_amount`、`status`(draft/active/closed)。
|
||
- `issues`:`id`、`activity_id`、`seed`、`seed_version`、`original_qty`、`quantity`、`status`(pending/drawing/done/failed)。
|
||
- `rewards`:`id`、`issue_id`、`name`、`weight`、`original_qty`、`quantity`、`type`、`meta`。
|
||
- `draw_logs`:`id`、`issue_id`、`activity_id`、`user_id`、`reward_id`、`signature`、`timestamp`、`nonce`、`receipt_json`。
|
||
- `lottery_refund_logs`:`id`、`issue_id`、`order_id`、`user_id`、`amount`、`coupon_type`、`coupon_amount`、`reason`、`created_at`、`status`(success/failed/retry)。
|
||
|
||
## 管理端 API 与参数
|
||
1) 创建活动
|
||
- `POST /api/admin/lottery/activities`
|
||
- Body
|
||
```
|
||
{
|
||
"name": "一番赏·七月",
|
||
"play_type": "ichiban", // 枚举: ichiban | infinite | match | tower
|
||
"draw_mode": "scheduled", // 枚举: scheduled | instant
|
||
"min_participants": 100, // 定时玩法必填
|
||
"scheduled_time": "2025-12-05T20:00:00Z", // 绝对时间戳,禁止传相对分钟
|
||
"refund_coupon_type": "cash", // 枚举: cash | discount | gift
|
||
"refund_coupon_amount": 20.0, // 金额或面额
|
||
"description": "..."
|
||
}
|
||
```
|
||
- Response
|
||
```
|
||
{ "activity_id": 123 }
|
||
```
|
||
- 校验:当 `draw_mode`==`scheduled` 时必须存在 `scheduled_time`(>= 当前时间+最小提前量) 与 `min_participants`;拒绝包含“分钟”相对参数。
|
||
|
||
2) 更新活动
|
||
- `PATCH /api/admin/lottery/activities/:activity_id`
|
||
- Body(同创建,可部分字段)
|
||
- 禁止将 `scheduled_time` 更新为相对分钟;仅允许更新为更晚的绝对时间。
|
||
|
||
3) 创建活动期(含奖池与种子)
|
||
- `POST /api/admin/lottery/activities/:activity_id/issues`
|
||
- Body
|
||
```
|
||
{
|
||
"rewards": [
|
||
{ "name": "SSR皮肤", "weight": 1, "original_qty": 1, "type": "virtual", "meta": {"skin_id": 88} },
|
||
{ "name": "SR皮肤", "weight": 10, "original_qty": 50, "type": "virtual", "meta": {"skin_id": 77} }
|
||
]
|
||
}
|
||
```
|
||
- Response
|
||
```
|
||
{
|
||
"issue_id": 456,
|
||
"seed_version": 1
|
||
}
|
||
```
|
||
|
||
4) 活动列表/详情
|
||
- `GET /api/admin/lottery/activities?play_type=&draw_mode=&status=&page=&size=`
|
||
- `GET /api/admin/lottery/activities/:activity_id`
|
||
|
||
5) 期列表/详情
|
||
- `GET /api/admin/lottery/activities/:activity_id/issues?page=&size=`
|
||
- `GET /api/admin/lottery/issues/:issue_id`
|
||
|
||
6) 手动关闭活动/期
|
||
- `POST /api/admin/lottery/activities/:activity_id/close`
|
||
- `POST /api/admin/lottery/issues/:issue_id/close`
|
||
|
||
## APP 端 API 与参数
|
||
1) 参与抽奖(创建订单/入场)
|
||
- `POST /api/app/lottery/join`
|
||
- Body
|
||
```
|
||
{
|
||
"activity_id": 123,
|
||
"issue_id": 456,
|
||
"channel": "wechat", // 支付渠道
|
||
"client_nonce": "base64..." // 可选,客户端随机串
|
||
}
|
||
```
|
||
- Response(即时模式可能包含抽奖结果;定时模式仅返回入场信息)
|
||
```
|
||
{
|
||
"join_id": "J202512030001",
|
||
"order_id": "O202512030009",
|
||
"pay_params": { /* JSAPI 等 */ },
|
||
"queued": true, // 定时玩法
|
||
"draw_mode": "scheduled"
|
||
}
|
||
```
|
||
|
||
2) 即时抽奖(支付成功后自动触发,若需要主动查询)
|
||
- `GET /api/app/lottery/result?join_id=J202512030001`
|
||
- Response(包含可验证凭证)
|
||
```
|
||
{
|
||
"result": {
|
||
"reward_id": 789,
|
||
"reward_name": "SR皮肤"
|
||
},
|
||
"receipt": {
|
||
"issue_id": 456,
|
||
"seed_version": 1,
|
||
"timestamp": 1733251200123,
|
||
"nonce": "rB1A...==",
|
||
"signature": "hmacSha256Base64...",
|
||
"algorithm": "HMAC-SHA256",
|
||
"inputs": {
|
||
"user_id": 10086,
|
||
"issue_id": 456,
|
||
"timestamp": 1733251200123,
|
||
"nonce": "rB1A...=="
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
3) 定时开奖结果查询(到点统一结算后)
|
||
- `GET /api/app/lottery/scheduled/result?issue_id=456&user_id=10086`
|
||
- Response 同上(若未达人数,返回退款与赠券信息)
|
||
```
|
||
{
|
||
"refunded": true,
|
||
"refund": { "order_id": "O202512030009", "amount": 29.9 },
|
||
"coupon": { "type": "cash", "amount": 20.0, "coupon_id": 33001 }
|
||
}
|
||
```
|
||
|
||
4) 客户端校验助手(可选)
|
||
- `GET /api/app/lottery/issue/:issue_id/seed_version`
|
||
- Response `{ "seed_version": 1 }`
|
||
|
||
## 支付/退款/赠券
|
||
- 支付回调:沿用 `/api/pay/wechat_notify`(落库订单状态,触发即时抽奖或定时入场)。
|
||
- 定时结算器:服务端按 `scheduled_time` 扫描:
|
||
- 人数 < N:批量退款,并为每位用户发放预设优惠券(`system_coupons`)。
|
||
- 人数 ≥ N:统一抽奖与发放,写入 `draw_logs` 与事务扣减奖励。
|
||
- 退款接口(内部调用):对每个订单创建退款,记录 `lottery_refund_logs` 并保证幂等。
|
||
|
||
## 一致性与异常处理
|
||
- 事务:入场/库存扣减/日志写入使用同一事务;失败回滚。
|
||
- 幂等:`join_id` 与 `order_id` 作为幂等键,退款/赠券均防重复。
|
||
- 定时任务:定期扫描 + 重试机制;失败写入日志,告警通知。
|
||
- 网络中断:客户端可凭 `join_id` 查询结果;服务端保证结算原子性。
|
||
|
||
## 错误码(示例)
|
||
- `400`:活动未开始/已结束/库存不足/限频/参数错误
|
||
- `402`:未支付
|
||
- `409`:重复参与/幂等冲突
|
||
- `500`:系统错误/结算失败(可重试)
|
||
|
||
## 测试与验收
|
||
- 单元:HMAC 随机选择、拒绝采样无偏、事务一致性、幂等与重试。
|
||
- 集成:支付→即时报奖→收据校验;定时人数不足→退款+送券。
|
||
- 文档:Swagger 补充所有请求/响应体;明确“禁止 N 分钟相对定时”,仅允许绝对时间戳。
|
||
|
||
## 交付清单
|
||
- 策略模块、管理端与 APP 端 API、哈希凭证、定时结算器、退款日志与发券流程、测试与文档。 |