MatchMaster/assets/coin_madness/README.md

301 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CoinMadness(黄金矿工)活动前端说明
核对日期:2026-09-30,客户端基准 `7e14f661`。本文描述实际实现,不代表完整兼容仓库后端 V1.7 或已完成线上验收。[文档入口](../../docs/CoinMadness前端开发说明.md) · [接入差异与验证状态](../../docs/GoldMiner接入说明.md)。请求示例省略真实账号和 token。
活动预制体、图片和面板脚本在 coin_madness 分包;Host、GoldMinerService、BrownLoadingSpinner 在主包侧,入口图在 resources。游戏测试包、活动测试面板及演示场景已移除。普通 Creator 预览不启动真实活动;独立预览夹具仍在 tools/coin-madness。
## 1. 代码分工
| 文件 | 职责 |
| --- | --- |
| [script/CoinMadnessPanel.ts](script/CoinMadnessPanel.ts) | 活动主面板、购买和领取按钮、倒计时、金币动画 |
| [script/CoinMadnessSettlement.ts](script/CoinMadnessSettlement.ts) | 补发到账通知窗口 |
| [script/CoinMadnessDigits.ts](script/CoinMadnessDigits.ts) | 图片数字显示 |
| [../Script/coin_madness/CoinMadnessHost.ts](../Script/coin_madness/CoinMadnessHost.ts) | 首页入口、同步时机、分包加载释放、主面板/说明/结算窗口切换 |
| [../Script/coin_madness/GoldMinerService.ts](../Script/coin_madness/GoldMinerService.ts) | 活动接口、支付订单、领奖与补发、交付记录和恢复流程 |
| [../Script/module/Pay/Utils.ts](../Script/module/Pay/Utils.ts) | POSTJSON、服务器选择、金币保存队列、通关保存请求 |
| [../Script/module/Tool/GameTool.ts](../Script/module/Tool/GameTool.ts) | addLevel 中接入真实通关事件 |
| [../Script/GameManager.ts](../Script/GameManager.ts) | startGame 中提前获取首页活动状态 |
| [../Script/JiaZai.ts](../Script/JiaZai.ts) | 挂载 Host、首页左侧活动补位、金币数字刷新 |
通常调用关系:
```text
CoinMadnessPanel(用户操作)
→ CoinMadnessHost.requestActivity
→ GoldMinerService(组织业务)
→ Utils.POSTJSON(发送 HTTP)
→ 后端
```
真实通关另走 GameTool.addLevel → Utils.setUserLevel;活动胜利事件附在原有关卡保存请求里。
## 2. 环境与接口清单
活动请求使用 POST JSON。Utils.serverUrl 根据微信版本选择服务器:开发版/体验版使用 testHttpip,其余使用 httpip。具体地址以 Utils.ts 为准,不在本文重复硬编码。
当前两个地址相同:合入 main 后,开发版/体验版也会请求同一正式服。账本隔离依据实际 URL 和 uid,不依据版本名称。
GoldMinerService.post 为认证请求补充 uid;Utils.POSTJSON 补充 token。activityConfig/list 使用非认证请求。
共涉及 8 个主要后端地址。goldMiner/index 是同一个地址,通过 action 区分业务。
| 地址 | action | 用途 | 直接调用位置 |
| --- | --- | --- | --- |
| activityConfig/list | 无 | 活动日程、服务器时间,辅助判断通关所属期次 | GoldMinerService.readSchedule |
| goldMiner/index | info | 玩家活动状态、价格、进度、任务和领取状态 | GoldMinerService.readInfo |
| goldMiner/index | claim | 检查资格,返回本档奖励授权与金额 | GoldMinerService.deliver |
| goldMiner/index | confirm_delivery | 确认普通奖励已保存到账 | GoldMinerService.deliver |
| goldMiner/index | settlements | 查询往期补发清单 | GoldMinerService.settle |
| goldMiner/index | confirm_settlement_delivery | 确认补发奖励已保存到账 | GoldMinerService.deliver |
| userLevel | save | 保存关卡,并携带 goldMiner 胜利事件 | Utils.setUserLevel |
| userCoin | save | 保存领取/补发后的金币总余额 | GoldMinerService.deliver |
| wx/orderPaySig | 无 | Android 原生支付下单,返回支付参数 | GoldMinerService.purchase |
| wx/iosorderPaySig | 无 | iOS 原生支付下单,返回支付参数 | GoldMinerService.purchase |
| wx/getPayInfo | 无 | 查询 Android 订单支付和权益结果 | GoldMinerService.queryOrder |
| wx/iosgetPayInfo | 无 | 查询 iOS 订单支付和权益结果 | GoldMinerService.queryOrder |
Network 中多个 index 请求不一定重复:必须查看 Payload.action。HTTP 200 只代表获得 HTTP 响应,业务成功仍要检查响应 code 和内部业务结果。
## 3. 首页、入口和完整刷新
### 触发时机
- 首次登录:GameManager.startGame 在加载画面期间调用 readForDisplay,和商店分包加载并行;拿到首份活动状态或请求失败后继续进入首页。
- 首页 Host 启动、游戏返回前台:调用 sync。
- 打开活动面板:先显示 service.view 的已有数据,再请求首份最新信息。
- 本地检测到活动时间阶段变化:触发同步,包括到期。
readForDisplay 会启动/复用 refresh,但只等待第一次 info。后续恢复仍继续执行,并不是只请求 info。
### refresh 的真实执行顺序
```text
info ← 首份数据返回后即可显示入口/主面板
→ activityConfig/list
→ 遍历未完成订单,逐笔 queryOrder
→ 恢复未完成的普通领奖和 issuing 奖励
→ settlements,处理补发
→ info ← 获取最终任务状态
```
没有待恢复订单、没有奖励、没有分页时,通常仍有 4 次请求:info → list → settlements → info。
同一客户端的在途 refresh 会复用 Promise;前一轮完成后再次触发会新开一轮。恢复与购买、领奖使用同一串行队列,避免并发交付,但后面的操作可能等待前面的恢复任务。
### 入口显示与布局
- 有补发到账通知或可领取/发放中的奖励时,入口可显示红点。
- 普通展示还要求 canIos 开关允许,且 info.status 为 purchasable、purchase_disabled 或 unlocked。
- locked、unavailable 默认不显示入口,奖励通知是例外。
- 使用 HomeScene 的 Load/Top/CoinMadnessEntry;找不到时才创建。
- JiaZai.refreshHomeActivityEntryLayout 统一排列左侧入口,参与首页渐隐控制;不固定悬浮在另一套坐标上。
## 4. 倒计时、到期与重试
前端用服务器时间与本地时间差校准:
```text
offset = serverTime - Date.now()
剩余时间 = endsAt - (Date.now() + offset)
```
面板每秒重新计算显示,Host 每 0.5 秒检查本地时间阶段/红点。这些本地检查本身不发请求。当前没有固定每 30 秒的全量轮询。
到期时活动面板退出:已有到账通知则切换结算窗口;否则关闭并触发同步,补发处理成功后再展示通知。
settlements 返回 settling、补发失败或查单失败会设置重试标记。Host 的重试间隔依次为 2、3、5、7、10、15、25、30、50 秒,当前计数未重置时最多追加 9 次。回前台等 resumeSync 会重置计数。
恢复标记触发 retryRecovery,只查未完成订单与补发;订单有变化或通知金额改变时才读取 info,不重读日程或重放普通领奖。完整同步抛错则重试完整 refresh。重新进入首页、回前台或期次边界可触发新一轮;重试次数不是整个会话的总请求上限。
已知边界:
- 后端临时开启一个前端未知的活动,玩家一直停在首页时,当前实现没有固定轮询自动发现,要等下次同步。
- settlements 返回空列表且未标记 settling 时,前端按本次无记录处理,不会仅因空列表重试。
- 后端持续 settling 超过有限重试次数,需要后续同步或检查服务端结算任务。
## 5. 真实通关流程
```text
GameTool.addLevel
→ 本地关卡推进
→ GoldMinerService.victory 生成活动事件
→ Utils.setUserLevel
→ userLevel / save(附带 goldMiner)
→ GoldMinerService.victoryResult 处理活动结果
```
主线上报示例(省略认证字段):
```json
{
"action": "save",
"levelAmount": 91,
"goldMiner": {
"periodId": "实际活动期ID",
"eventId": "本次胜利唯一ID",
"outcome": "win",
"mode": "main",
"clearedMainLevel": 91
}
}
```
- 主线省略 isWuXian;无尽使用字符串 "true",goldMiner.mode 为 endless 并携带 endlessSequence。
- 前端需识别有效活动期和符合条件的真实胜利,才附带 goldMiner;配置补关等不作为真实胜利计数。
- 不是再单独请求 index 给活动加进度。
- 检查 data.goldMiner.code、activityCounted、duplicate、progressWins。外层 code=1 不等于活动计数一定成功。
- victoryResult 更新本地活动累计次数;完整任务领取状态由后续 info 获取。
- 本地关卡已前进不代表服务器保存成功。userLevel 整体不是纯活动幂等接口,不应自动无限重放失败请求。
## 6. 购买解锁流程
```text
Panel.run('purchase')
→ Host.requestActivity
→ Service.request → purchase
→ info:校验活动期、购买状态与价格
→ 下单:返回 outTradeNo、支付参数
→ 微信客户端拉起支付
→ queryOrder:查询后端订单权益
→ info:刷新解锁状态
→ 面板更新,成功解锁后自动打开一次“?”说明
```
| 渠道 | 下单地址 | 微信客户端 API | 查单地址 |
| --- | --- | --- | --- |
| Android 原生 | wx/orderPaySig | wx.requestMidasPaymentGameItem | wx/getPayInfo |
| iOS 原生 | wx/iosorderPaySig | wx.requestMidasPaymentGameItem | wx/iosgetPayInfo |
| 当前 iOS 客服 | Utils.GoKEFu 本地生成订单及会话参数 | wx.openCustomerServiceConversation | wx/iosgetPayInfo |
微信支付 API 与 wx.showToast 是客户端 API,不是本项目后端地址。支付窗口关闭不等于后端确认成功。
queryOrder 验证 pay_state=2、rewardDelivery='goldMiner.claim' 和 goldMiner 权益数据。支付客户端调用成功后最多尝试查单四次,间隔 1.5、3、4.5 秒;PAYMENT_PENDING 和 NETWORK_UNKNOWN 可快速重试,其他异常提前退出。仍未确认时提示“支付结果确认中”。每次有效购买生成新 createRequestId;iOS 原生明确返回错误码 16 且非取消时回退 GoKEFu。当前客服链路不同于后端 V1.7 的 create_order 签名票据,见接入差异。
提示使用 wx.showToast:购买成功、已取消支付、购买失败或支付结果确认中。购买只解锁权益,不自动发放全部任务金币。跨期承接权益可能分配给下一期,不等于当前期立即解锁。
## 7. 点击领取一档任务奖励
```text
Panel.run('claim')
→ Host.requestActivity
→ Service.request → deliver
→ claim:取得奖励授权
→ userCoin/save:保存增加后的总余额
→ onSaved:立即启动金币动画,飞到顶部后释放数字显示等待
→ confirm_delivery:确认该档到账(不等待动画)
→ info:读取最新任务状态
```
以余额 1000、奖励 300 为例,userCoin/save 上传 coinAmount=1300,不能上传 300。
正常首次领取通常四次请求。金币保存确认成功后就播放动画,无须等待 confirm_delivery 或最后一次 info;服务端确认失败时仍保留恢复凭证。网络和串行队列可能造成保存前等待。
金币余额实际保存早于动画,只有数字显示被延迟。当前每档动画产生 16 枚金币,约 0.935 秒后释放显示等待,再过 0.24 秒清理动画层;并发动画全部到达后刷新顶部数字。关闭面板也会恢复显示。
已保存但未确认的奖励恢复时只重试确认;已确认奖励不重复加金币。具体取决于本地交付记录和后端响应。
## 8. 活动结束后的补发
```text
Service.refresh → settle
→ settlements:获得补发清单
→ 对每档 reward 调用 deliver(直接使用清单中的授权)
→ userCoin/save
→ confirm_settlement_delivery
→ refresh 末尾 info
→ Host.showSettlementNotice
→ 显示补发到账窗口
```
补发清单包含奖励授权,不再对过期任务调用 claim。
一档 1000 金币待补发的结构示例:
```json
{
"code": 1,
"data": {
"items": [{
"settlementId": "实际结算ID",
"periodId": "实际活动期ID",
"rewards": [{
"taskId": "实际任务ID",
"grantId": "实际奖励凭证ID",
"items": [{ "type": "coin", "count": 1000 }]
}]
}],
"nextCursor": null
},
"msg": "成功"
}
```
同一奖励的 ID 应稳定,多个任务分别列出各自的 reward。不要仅返回总金额,也不要在可处理清单上继续标记 settling。
confirm_settlement_delivery 请求携带 settlementId、grantIds;当前逐档调用,grantIds 每次包含一档的 grantId。客户端验证响应中的 settlementId 和 confirmedGrantIds。
如果一页有三档待补发奖励,核心流程通常是:查询一次 + 保存三次 + 确认三次,完整 refresh 还包含其他查询。
客户端仍发送 limit=100,但 V1.7 已改为服务器控制页大小,不能依赖这个参数决定返回数量。非 settling 响应中,nextCursor 非空时用 afterId 翻页,即使 items 为空也继续。当前遇 settling 会立即退出,即使有 nextCursor;这与 V1.7 要求继续扫描其他期的规则不同,尚待对齐。no_pending_rewards 的空列表、空游标可自然结束本轮。
返回 settling 时表示尚未生成可处理清单,不表示没有奖励,也不表示到账。
离线结束的活动在下次进入首页时同样查询,不依赖打开活动面板。仅有符合补发条件、实际处理成功的金额才形成到账通知,不是所有玩家到期都弹结算。
关闭结算窗口只调用 dismissNotice 清除本地通知,不再发币、不再调用确认接口。若到账后未关闭窗口就退出,下次可能再次显示通知,但不应再次发币。
补发通知直接加载 CoinMadnessSettlement,不先实例化活动主面板。关闭通知后立即更新入口及左侧补位;过期任务的残留红点不保留入口。如果已有新一期可参与活动,则保留新一期入口。
## 9. 标识关联与本地记录
| 字段 | 含义及用途 |
| --- | --- |
| periodId | 关联某一期活动的通关、权益、任务和补发 |
| eventId | 一次胜利的唯一事件标识,用于活动事件去重 |
| outTradeNo | 下单返回的订单号,查单使用 |
| taskId | 一档活动任务 |
| grantId | 具体奖励授权,确认与交付恢复使用 |
| settlementId | 一笔补发结算,补发确认使用 |
本地账本按服务器和账号隔离,包含 orders、grants、events、notices 等数据。
确认接口存在的原因:查询到授权不等于金币已保存。保存金币成功后,确认接口才告诉活动后端“这档已完成”。
但当前 userCoin/save 是总余额覆盖,与确认接口不是同一个事务;仅靠 grantId 不能保证重装、多设备等情况下严格只发一次。余额保存结果不明时,代码会阻止直接重复加币,需要核对,不能只因仍是 issuing 就再加一次。
## 10. 为什么 getPayInfo 经常出现
每次完整 refresh 都遍历本地订单:有订单号、未标记 done、且不是 cancelled 的订单都会查。因此历史未付款或失败订单可能反复出现。
查单失败设置 refreshNeedsRetry,下一轮恢复重试会再查这些订单。多个期次订单意味着一轮多个 getPayInfo。这是查单,不是重复下单或扣款;同一期本地只保留最近一次购买记录,旧记录被替换后不再由该账本恢复。
完整 refresh 包括首屏、订单、领奖恢复和补发;retryRecovery 已单独用于恢复重试。首屏提前返回第一次 info,后台完整刷新仍会继续。
## 11. 排查与验证
2026-09-30 清理:已删除游戏测试分包、活动测试面板、首页测试入口及两个演示场景。保留活动正式流程、支付诊断日志和离线回归检查。
- 进度不涨:查看真实通关那条 userLevel 的请求 goldMiner 与响应 data.goldMiner,再对比 info 的 periodId/progressWins/tasks。
- 未弹补发:查看 settlements 是否 settling、有无 items、后续 userCoin/save 和 confirm_settlement_delivery 是否成功。
- 领取慢:检查串行队列中的前序恢复,以及 claim → 保存 → 确认 → info 各次耗时。
- “网络结果未知”:检查 transportReason、HTTP 状态。空响应、非 JSON、超时、连接失败需分别定位。
- 在浏览器地址栏打开接口是 GET,不等于游戏实际 POST;应查看 Network 中同一条请求的 Response。
仓库根目录可运行以下离线检查(不操作真实支付和余额):
```text
node tools/coin-madness/check-api.cjs
node tools/coin-madness/check-first-display.cjs
node tools/coin-madness/check-expiry-sync.cjs
node tools/coin-madness/check-expired-panel.cjs
node tools/coin-madness/check-release-ui.cjs
node tools/coin-madness/check-home-entry-ready.cjs
node tools/coin-madness/check-settlement-entry.cjs
node tools/coin-madness/check-types.cjs
```
上面列出的是可运行的现有脚本,不代表全部已通过。上次 check-types 报 Utils.ts 的 wx 声明问题,check-release-ui 的夹具及断言待维护;通过记录和真机待验项见接入说明。类型检查以 HEAD 为基线,提交后基线随之改变,“无新增”不等于零错误。
最新仓库协议:[后端 V1.7](../../server/laf-cloud/functions/goldMiner/FRONTEND-API.md);当前兼容边界:[接入说明](../../docs/GoldMiner接入说明.md)。V1.3/V1.5 只作历史参考,不能据此认定客户端已完成 V1.7 接入。