## 执行逻辑(目标态) ### 参与下单(仅创建订单,不开奖) - 接口:`POST /api/app/lottery/join` - 入参:`activity_id`、`issue_id`、`count`(默认1,≤N)、可选 `client_nonce` - 流程: - 查询活动单价 `price_draw`;计算 `total_amount = price_draw * count` - 查询积分余额;计算需积分 `needPts = ceil(total_amount / 10)`;实际扣减 `usePts = min(balance, needPts)` - 写订单:`draw_count = count`、`PointsAmount = usePts * 10`、`ActualAmount = total_amount - PointsAmount`、`Status=1` - 当 `ActualAmount == 0` → 将订单置为已支付(`Status=2`、`PaidAt=now`),但不开奖 - 返回:`order_no`、`draw_mode`、`count`、金额与抵扣细节、`queued` ### 预下单(微信 JSAPI) - 接口:`POST /api/app/pay/wechat/jsapi/preorder` - 校验:订单属于当前用户且 `Status=1` - 返回:`wx.requestPayment` 参数;记录 `PaymentPreorders` ### 支付回调(入账) - 接口:`POST /api/pay/wechat/notify` - 行为:验签→记录交易→更新订单 `Status=2(PAID)`、`PaidAt`;幂等事件处理 - 触发:即时模式下调用 `DrawProcessor`;计划模式下等待调度 ### 抽奖处理器(按订单维度执行) - 输入:`order_id/order_no/activity_id/issue_id/draw_mode/draw_count` - 即时模式: - 读取已抽次数 `n = count(ActivityDrawLogs where order_id)` - 循环 `i=n+1..draw_count`: `SelectItem`→`GrantReward`→`Create(ActivityDrawLogs{draw_index=i})` - `completed == draw_count` → 订单标记 `SETTLED` - 计划模式: - 到 `scheduled_time` 统一处理;参与不足自动退款(先积分、后微信金额),保留现有逻辑 - 幂等:以上循环按“每订单已存在日志条数”补齐,事务化执行 ### 统一结果轮询(基于 order_no) - 接口:`GET /api/app/lottery/result?order_no=...` - 返回: - `status`: `pending|paid_waiting|settled|refunded` - `draw_mode`、`count`、`completed`、`results[{reward_id,reward_name,level,draw_index}]` - `receipt`:`issue_id/seed_version/timestamp/nonce/signature/algorithm` - `nextPollMs`(建议2s-5s) ## 改造点与文件定位 - 修改 `JoinLottery`:internal/api/activity/lottery_app.go - 增加 `count` 入参;仅下单与积分抵扣;免支付不开奖 - 保持 `WechatJSAPIPreorder` 不变:internal/api/user/pay_wechat_app.go - 扩展回调:在 `WechatNotify` 支付入账后触发即时模式订单的 `DrawProcessor`:internal/api/pay/wechat_notify.go - 定时调度按订单 `draw_count` 抽取或退款:internal/service/activity/scheduler.go - 新增统一查询接口 `GET /api/app/lottery/result`:新增 handler(internal/api/activity/lottery_result_app.go),挂载到 APP 认证组(internal/router/router.go) ## 数据库变更 - `orders` 新增:`draw_count INT NOT NULL DEFAULT 1`(可选:`draw_mode` 冗余) - `activity_draw_logs` 可选新增:`draw_index INT NULL`(记录第几次抽取) - 迁移默认值:历史订单填充 `draw_count = 1` ## 边界与异常 - 积分充足:免支付→订单直接 `PAID`;由处理器负责开奖 - 积分不够/无积分:需支付→入账后再开奖 - 计划不足:自动退款(积分与金额),订单 `REFUNDED` - 并发与幂等:以 `order_id` + 已有日志条数控制,所有扣减与发奖在事务中执行 ## 前端调用契约 - 流程:`join` → (可选)`preorder` → 支付 → 轮询 `result` 至 `settled|refunded` - 显示:用 `results` 数组展示 N 次抽奖结果;根据 `status` 渲染进度与状态 ## 测试与验收 - 单元:积分计算(充足/不够/为0)、N 抽即时/计划模式日志与发奖一致 - 集成:支付回调幂等、计划不足退款、统一查询状态流转 - 验收:同一 `order_no` 在两模式下的查询端点返回一致结构与正确状态机;库存扣减准确;退款日志完整 ## 注意事项 - `client_nonce`(可选):用于防重复参与提交的客户端幂等键;不影响支付与开奖 - 安全:JWT校验、签名凭证不泄露种子;所有金额与积分变更审计入库