承接目录治理:上轮只压缩内容未减文件数,闭线工作记录仍平铺致目录看着仍一堆(创始人指出)。本次执行 B2 归档。 64 个 ≤06-15 闭线档(25 压缩桩 + 闭线 review/report/纪要/edit-plan)git mv 入 docs/agent-specs/_archive/(文件名不变、仍 git 跟踪可查)。热目录 ≤06-15 仅留 13 活档(决策/纲领/SoT/活spike)+13 个 06-16 在飞。 活资产 20 处旧路径引用(.agents/docs/memory/_index)同步改 _archive/,引用断裂复测=0;_index 活地图 + 治理档状态收口。约束:0 个 06-16 被移、orchestrator 等未跟踪在飞档零误纳。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
159 lines
14 KiB
Markdown
159 lines
14 KiB
Markdown
# B4 四链路(广告/收益/分享/互动)staging 烟测登记
|
||
|
||
> 日期:2026-06-09 | 执行:测试工程师 subagent | 目标环境:staging(mini-desktop, http://100.64.0.7:48080)
|
||
> 目的:给 MVP 总账一份「真实/桩」的诚实清册,**不修代码、不下结论性承诺**。
|
||
> 判据:HTTP 200 ∧ body.code==0 ∧ 结构合理 = 通;受控业务错误码精确命中 = 链路活但按设计拒绝。
|
||
> 写库纪律:全程零业务写入,唯一例外为 2 条 telemetry 测试事件(eventId 前缀 `b4smoke-`,已留清理 SQL,见 §6)。
|
||
|
||
---
|
||
|
||
## 1. 结论速览(总表)
|
||
|
||
| 链路 | 判定 | 一句话 |
|
||
|---|---|---|
|
||
| 广告 | **部分真实** | 后端计费链(幂等/归因/落账/合规闸门/T+1 对账锚点)真实,但计价与激励校验是 mock SPI、staging 0 个广告位、**前端完全未接后端计费 API**(宿主桩弹层自产自销) |
|
||
| 收益 | **部分真实** | 账户原子账本/提现状态机/80% 分成结算代码真实且幂等,但打款是 mock 状态机、**结算 Job 在 staging 被关闭(xxl.job.enabled=false)永不运行**、前端无收益页入口、全链路 0 数据 |
|
||
| 分享 | **部分真实** | 端点活:发布门禁真实(project.status==4)、utm_source 渠道归因真实、versionId 回填真实;但 **OG 标题/封面/描述=空串桩**、分享域名写死占位 `https://wanxiang.ai/s/` |
|
||
| 互动 | **部分真实**(排序权重路径=**已证**,走 telemetry) | interact 幂等落库+聚合计数回显真实、前端 InteractBar 已接线;但 **interact 信号未接排序权重**(排序的 like/share 权重实际来自 telemetry 事件,B2 已证+本次复证)、举报转 compliance 是占位日志、互动态回显恒 false |
|
||
|
||
staging 数据现状(只读核查,烟测前后均如此,证明探针零污染):
|
||
|
||
```
|
||
ad_slot=0 ad_revenue=0 trade_account=0 trade_income=0 trade_withdraw=0 interact_log=0
|
||
```
|
||
|
||
即:**四条链路在 staging 从未被真实使用过**(对比:feed/telemetry 主链路 B1/B2 已有数据)。
|
||
|
||
---
|
||
|
||
## 2. 路径发现方法
|
||
|
||
- 模块定位:`game-cloud/game-module-ad`(广告)、`game-module-trade`(收益/分成/提现)、`game-module-feed`(分享+互动)、`game-module-telemetry`(互动遥测口径)。
|
||
- app 侧前缀:`/app-api` 由框架按包名 `controller.app.*` 自动添加(各 controller 头注释自述,如 AdController.java:28;并经本次 curl 实测证实路径可达)。
|
||
- Flyway V6(ad)/V7(trade)/V10 均无 INSERT 种子(grep 实证)→ 广告位等运营数据需人工配置,staging 没人配过。
|
||
|
||
---
|
||
|
||
## 3. 各链路详情
|
||
|
||
### 3.1 广告链路(game-module-ad)
|
||
|
||
**端点烟测表**
|
||
|
||
| # | 路径 | 方法 | HTTP | code | 判定 | 证据 |
|
||
|---|---|---|---|---|---|---|
|
||
| 1 | `/app-api/ad/slot/list-enabled` | GET | 200 | 0 | 真实(但空配置) | 实测 `{"code":0,"data":[]}`;DB `game_ad_slot`=0 行;AdSlotService 真查库(AdController.java:48-51) |
|
||
| 2 | `/app-api/ad/report/impression` | POST(假 slotId 探针,零写) | 200 | 1111002000 | 部分真实 | 实测精确命中 `AD_REPORT_SLOT_DISABLED`(ErrorCodeConstants.java:27),证明 路由→VO 校验→Service→slot 查库 全链活;计费核心见下 |
|
||
| 3 | `/app-api/ad/report/reward` | POST(同上探针) | 200 | 1111002000 | 部分真实 | 同上,同走 `bill()` 计费核心 |
|
||
|
||
**真伪判读(源码亲核)**
|
||
|
||
- 真实部分:计费核心 `bill()` = slot 校验→合规闸门→(traceId,eventType) 幂等先查+DuplicateKeyException 并发兜底→gameId 经 ProjectApi 反查创作者归因→落 `game_ad_revenue` 台账(settle_status=0,statDate=今日)——AdRevenueServiceImpl.java:108-171。结算回标幂等 0→1(同文件 179-188)。
|
||
- 桩部分:
|
||
- **计价 mock**:唯一 provider 是 `MockAdProvider`,`calcRevenue = ecpmFloor/1000` 整除(MockAdProvider.java:29-35),`verifyReward()` 恒 true(同文件 38-41);工厂对未注册 provider(csj/gdt)一律降级 mock(AdProviderFactory.java:41-44)。
|
||
- **合规桩**:`isMinor()` 恒 false、`currentSessionCount()` 恒 0(AdComplianceChecker.java:60-75,注释自述「MVP 对接点」)。
|
||
- **前端断链(亲核)**:game-studio 无 `src/api/ad.ts`(api 目录仅 aigc/feed/project/runtime/telemetry);宿主侧广告是桩弹层——GamePlayer.vue:12「ad/pay 桩弹层:点击模拟激励视频/支付,回调 rewarded:true」、:315-336 模拟看完直接回 `rewarded:true`,**不调 `/ad/report/*` 也不拉 `/ad/slot/list-enabled`**。即后端计费 API 真实可用但当前无任何调用方。
|
||
|
||
### 3.2 收益链路(game-module-trade)
|
||
|
||
**端点烟测表**
|
||
|
||
| # | 路径 | 方法 | HTTP | code | 判定 | 证据 |
|
||
|---|---|---|---|---|---|---|
|
||
| 4 | `/app-api/trade/income/page` | GET | 200 | 0 | 真实(0 数据) | 实测 `{"code":0,"data":{"total":0,"list":[]}}`;真实分页查 `game_trade_income`(IncomeServiceImpl.java:94-96,userId 边界强制) |
|
||
| 5 | `/app-api/trade/withdraw/page` | GET | 200 | 0 | 真实(0 数据) | 实测同上结构;WithdrawServiceImpl.java:161-163 |
|
||
| 6 | `/app-api/trade/withdraw/apply` | POST(amount=1 门槛探针,零写) | 200 | 1106002000 | 真实 | 精确命中 `TRADE_WITHDRAW_BELOW_MIN`;门槛校验先于一切写入(WithdrawServiceImpl.java:70-72,Nacos `trade.withdraw-min` 默认 500 分),实证未写库 |
|
||
| 7 | `/app-api/trade/account/mine` | GET | 未实测 | — | 真实(代码判读) | **有写副作用故未实测**:首查即建零值账户(AccountServiceImpl.java:33-58 getOrInitAccount),为守住「不写库」纪律跳过;同 controller 其余端点已证路由与鉴权通 |
|
||
|
||
**真伪判读(源码亲核)**
|
||
|
||
- 真实部分:
|
||
- 账户原子账本:余额增减全部下沉行级 UPDATE(`SET balance=balance±? WHERE balance>=?`),0 行=余额不足防超扣(AccountServiceImpl.java 头注释 17-21 + freeze 72-78)。
|
||
- 提现状态机:insert(uk_biz_no 幂等)+freeze 同事务、审核 CAS 流转防二次发钱/退款(WithdrawServiceImpl.java:66-106、109-158)。
|
||
- 分成结算:T+1 拉 ad 未结算台账→逐笔 `uk_source` 幂等入账(net=gross×Nacos `trade.creator-share` 默认 0.80,BigDecimal 向下取整)→先入账后回标+补偿(SettlementServiceImpl.java:54-89;IncomeServiceImpl.java:40-91)。
|
||
- 桩/休眠部分:
|
||
- **打款 mock**:审核通过即 1→2「已打款」纯状态翻转,channel=`mock`,无真实支付渠道(WithdrawServiceImpl.java:44-45、126-135)。
|
||
- **结算调度休眠**:唯一触发器是 XXL-Job `tradeSettlementJob`(SettlementJob.java:39),admin 控制器无手动结算端点(TradeAdminController 仅 withdraw/audit/income/report 四端点,grep 实证);而 staging `xxl.job.enabled: false`(application-staging.yaml:90-92)且 docker 仅 mysql+redis 两容器(实测 `docker ps`)→ **即使广告计了费,钱也永远到不了创作者账户**。
|
||
- **前端无入口**:game-studio 无 `src/api/trade.ts`,无收益/提现页面。
|
||
|
||
### 3.3 分享链路(game-module-feed · share)
|
||
|
||
**端点烟测表**
|
||
|
||
| # | 路径 | 方法 | HTTP | code | 判定 | 证据 |
|
||
|---|---|---|---|---|---|---|
|
||
| 8 | `/app-api/feed/share/9001?channel=wechat` | GET | 200 | 0 | 部分真实 | 实测 `data={gameId:9001, shareUrl:"https://wanxiang.ai/s/9001?utm_source=wechat", ogTitle:"", ogImage:"", ogDescription:"", channel:"wechat", versionId:9001}` —— OG 三字段空串是**桩的活体证据** |
|
||
| 9 | `/app-api/feed/share/999999` | GET(未发布探针) | 200 | 1103002001 | 真实 | 精确命中 `FEED_SHARE_GAME_NOT_PUBLISHED`,发布门禁生效 |
|
||
|
||
**真伪判读(源码亲核)**
|
||
|
||
- 真实部分:发布门禁经 ProjectApi 读 status 仅 PUBLISHED(4) 放行(FeedServiceImpl.java:477-482);utm_source 渠道归因拼接(:462-466);versionId 经 ProjectApi 回填供落地页直达即玩(:261-264、278-284)。
|
||
- 桩部分:`ogTitle/ogImage/ogDescription` 写死空串,TODO 待对接 project 本体元信息(FeedServiceImpl.java:257-260);分享域名写死占位 `https://wanxiang.ai/s/`,TODO 待 Nacos 化(:463-464),该域名可达性/备案未验证。
|
||
- 前端入口:已接线(game-studio `src/api/feed.ts:73-79` getShareMeta + `src/views/share/Share.vue`),属 B2′ 已跑通的前端面。
|
||
- 与 B2 的关系:B2 实证的是「share **遥测事件** → share_count → quality_score」;本链路是「分享**落地页元数据**生成」,两者独立、互不替代。
|
||
|
||
### 3.4 互动链路(game-module-feed · interact + telemetry 口径)
|
||
|
||
**端点烟测表**
|
||
|
||
| # | 路径 | 方法 | HTTP | code | 判定 | 证据 |
|
||
|---|---|---|---|---|---|---|
|
||
| 10 | `/app-api/feed/interact` | POST(action=99 非法探针,零写) | 200 | 1103001000 | 部分真实 | 精确命中 `FEED_INTERACT_ACTION_INVALID`(校验先于写入,FeedServiceImpl.java:195-198),链路活且实证未写库(interact_log 测后仍 0) |
|
||
| 11 | `/app-api/telemetry/events/batch` | POST(b4smoke- like+share 各 1 条,任务豁免写入) | 200 | 0 | **已证** | 实测 `{"code":0,"data":{"accepted":2,"rejected":0}}`;落库核验:`game_telemetry_event` 两行 process_status=1;`game_telemetry_game_stat` gameId=9002 当日 like_count=1、share_count=1 —— B2 链路复证成功 |
|
||
|
||
**真伪判读(源码亲核)**
|
||
|
||
- 真实部分:interact = 动作枚举校验(1 赞 2 藏 3 享 4 报,FeedActionEnum.java:22-25)→ 幂等 upsert `game_feed_interact_log`(命中唯一键翻转 active,可取消语义正确)→ 聚合计数回显(FeedServiceImpl.java:193-241)。前端已接线:`src/api/feed.ts:60-65` + `src/components/InteractBar.vue`。
|
||
- 桩/缺失部分:
|
||
- **未接排序权重**:`FeedInteractLogMapper` 仅 feed 模块内部使用(全仓 grep 实证,消费方只有 FeedServiceImpl/FeedConvert);controller 宣称「点赞/收藏/分享/举报 → 排序权重」(AppFeedController.java:64)**名不副实**——排序的 like/share 权重实际来自 telemetry 事件聚合回灌(EventIngestServiceImpl.java:185-194 计数 → :150-162 QUALITY_REFRESH 回灌 feed_rank)。
|
||
- 举报转 compliance = 占位日志(FeedServiceImpl.java:227-232,TODO 事务内禁远程调用,待 MQ/Feign)。
|
||
- 匿名互动 anon_id = 空串 TODO(:207);feed 流互动态 liked/favorited 回显恒 false(:118 TODO + FeedConvert.java:38)。
|
||
- 「已证」的边界说明:B2 已实证 + 本次 b4smoke 复证的是 **telemetry 路径**(like/share 事件 → game_stat 计数 → quality_score → feed_rank.sort_score 重排);而 **interact 路径**(按钮点击 → interact_log → 计数回显)落库真实但只到「回显」为止,不参与排序。staging `game_feed_interact_log`=0 行也佐证 B2′ 实玩未触发过 interact 写入。
|
||
|
||
---
|
||
|
||
## 4. 缺口清单(MVP 上线前各链路还缺什么)
|
||
|
||
**广告(上线即赚钱的硬前提全缺)**
|
||
1. 前后端断链:game-studio 无 ad API 接线,宿主桩弹层自回调 rewarded:true,不拉广告位、不上报计费 → 后端台账永远 0。
|
||
2. 无真实广告联盟 SPI(穿山甲/优量汇),MockAdProvider 计价=ecpmFloor/1000、激励校验直通——真实接入还有联盟审核闸门(日历闸门)。
|
||
3. 广告位运营数据为零(Flyway 无种子、staging ad_slot=0),需 admin 端配置流程跑一遍。
|
||
4. 未成年识别、单会话频控均为恒放行桩(合规风险,上线前必须接真实信号)。
|
||
|
||
**收益(钱的管道有管无水)**
|
||
5. T+1 结算调度在 staging 关闭(xxl.job.enabled=false 且无 xxl-job-admin 容器),且无手动触发端点 → 广告收入到创作者账户的链路从未端到端跑通过一次。
|
||
6. 打款是 mock 状态机,未接微信/支付宝企业付款(涉及支付资质日历闸门)。
|
||
7. 前端无收益/提现页(无 trade.ts),创作者「看见钱」的入口缺失。
|
||
8. `/trade/account/mine` 首查建账的写副作用本次未实测,待一次受控验证。
|
||
|
||
**分享(能分享但分享卡片是白板)**
|
||
9. OG 标题/封面/描述空串 → 微信/QQ 分享卡片无图无题,传播转化必伤;需对接 project 元信息。
|
||
10. 分享域名写死占位 `https://wanxiang.ai/s/`,未 Nacos 化,域名可达性/备案未验证。
|
||
|
||
**互动(信号进了库但不进排序)**
|
||
11. interact 信号未接入排序权重,与 controller 文档宣称不符;需决策:要么接通(interact→telemetry 事件或直刷 rank),要么改文档口径承认排序只认 telemetry。
|
||
12. 举报→compliance 受理链路未接(占位日志),举报功能存在合规缺口。
|
||
13. feed 流互动态回显恒 false:用户刷新后看不到自己点过赞/收藏,体验断点。
|
||
14. 匿名互动 anon_id 未透传(未登录用户互动会归到 userId=0)。
|
||
|
||
---
|
||
|
||
## 5. 烟测覆盖与局限
|
||
|
||
- 覆盖:11 个探测中 10 个实测(4 GET + 5 POST + 1 落库核验),1 个因写副作用主动跳过(标注于 §3.2 #7)。
|
||
- 局限:错误路径只探了首个闸门(如 ad 探针停在 slot 校验,未实测归因失败/会话超限分支——已由源码判读补位);admin 侧端点(withdraw/audit、slot 配置)未实测(需 admin 会话且涉写库)。
|
||
|
||
## 6. 测试残留物(可清理)
|
||
|
||
仅 2 条 telemetry 事件 + 1 行当日聚合增量(gameId=9002,2026-06-10 行 like_count/share_count 各 +1):
|
||
|
||
```sql
|
||
-- 清理(如需):
|
||
DELETE FROM `ruoyi-vue-pro`.game_telemetry_event WHERE event_id LIKE 'b4smoke-%';
|
||
-- 聚合行修正(或整行删除当日 9002 行,下次事件会重建):
|
||
UPDATE `ruoyi-vue-pro`.game_telemetry_game_stat SET like_count=like_count-1, share_count=share_count-1
|
||
WHERE game_id=9002 AND stat_date='2026-06-10';
|
||
```
|
||
|
||
其余六张链路表(ad_slot/ad_revenue/trade_account/trade_income/trade_withdraw/interact_log)测前测后均为 0 行,零污染。
|