Feat/游玩计数 #604

Merged
k88936 merged 23 commits from feat/play-count-with-cache into master 2026-10-04 13:21:04 +08:00

23 Commits

Author SHA1 Message Date
k88936 9ce43071ad Add gamePlayClientId.ts to module and test exclusions
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m14s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m54s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 6m0s
Project CI / Backend tests (pull_request) Successful in 6m54s
Project CI / Frontend tests (pull_request) Successful in 2m54s
Project CI / Native shell tests (pull_request) Successful in 6m47s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m8s
Project CI / Repository checks (pull_request) Successful in 5m9s
2026-10-04 12:53:17 +08:00
k88936 ff83b67196 Merge branch 'master' into feat/play-count-with-cache
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m7s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m20s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m28s
Project CI / Backend tests (pull_request) Successful in 7m46s
Project CI / Frontend tests (pull_request) Successful in 3m55s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m18s
Project CI / Native shell tests (pull_request) Successful in 8m37s
Project CI / Repository checks (pull_request) Failing after 6m40s
2026-10-04 12:08:32 +08:00
k88936 57e259278d Merge remote-tracking branch 'origin/master' into feat/play-count-with-cache
Project CI / Backend tests (pull_request) Failing after 36s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m28s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 6m41s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
2026-10-04 11:58:25 +08:00
k88936 08020e664b 文档:同步客户端 IP 只信 X-Real-IP
- ADR:身份与去重键节写明 IP 取 nginx 覆盖的 X-Real-IP、XFF 只取末段,并追加 2026-10-04 修订记录
- 玩法链路:去重与限流节标注 IP 来源
- decision-log:2026-10-04 条目补安全修正说明与标题
2026-10-04 11:39:00 +08:00
k88936 95705cbb11 后端:客户端 IP 只信 X-Real-IP,不再取 XFF 首段
- request_context.rs:client_ip_from_headers 改为优先 nginx 覆盖写入的 x-real-ip,x-forwarded-for 只作回退且取最后一段(nginx 用 $proxy_add_x_forwarded_for 追加的真实对端),不再信任可伪造的首段
- 影响两个调用方:公开游玩上报的匿名身份与 IP+game 限流键、微信支付下单的 payer_client_ip
- 更新测试:X-Real-IP 优先、XFF 回退取末段、空 X-Real-IP 回退与回环兜底
2026-10-04 11:38:47 +08:00
k88936 c0148dda7e 文档:同步游玩计数关停不强制 flush 与失败丢弃口径
- ADR:修订「正常关停必须 force flush」为关停不做强制 flush,并补充 flush 任一分片失败即丢弃剩余分片;追加修订记录
- 玩法链路:缓冲与延迟改为崩溃/被杀/正常关停都允许丢一个窗口;限流节补充先做内存预检;写入语义补充失败丢弃口径
- decision-log:顶部新增 2026-10-04 修订条目,并给 2026-10-03 条目标注失效项
- docs/README:ADR 摘要去掉「关停 flush」
2026-10-04 11:13:55 +08:00
k88936 28ac8a1037 后端:游玩计数 flush 失败后直接丢弃剩余分片
- game_play_counter_worker.rs:任一分片失败即停止本次 flush,不再为后续分片逐个重建连接(连接不通时每片都会各自等一次超时)
- Build(确定未发出)仍放回本批,剩余分片直接丢弃;其余错误连本批一起丢弃,并记录丢弃增量
- 补 total_delta 汇总剩余增量,模块文档写明失败即停止与丢弃口径
2026-10-04 11:10:03 +08:00
k88936 75ad087e2c 后端:关停不再强制 flush 游玩计数
- main.rs:finalize_shutdown 删除游戏游玩计数强制 flush,关停不再为它等待网络,内存里剩余增量随进程结束丢弃
- game_play_counter_worker.rs:删除 flush_game_play_counter_for_shutdown,flush_deltas 去掉 requeue_on_build 参数(关停路径已不存在),Build 仍放回等待下一轮
- game_play_counter.rs:take_pending 标记为仅测试使用,模块文档改指 take_pending_if_due
2026-10-04 11:07:02 +08:00
k88936 3f0a77e65e 后端:游玩上报先做内存限流预检再查库
- game_play_counter.rs:新增只读 is_rate_limited,不消耗额度也不改状态
- modules/game_distribution.rs:record_game_play 先做内存限流预检,超限直接 429,不再让这些请求也打一次 SpacetimeDB;真正计数时 record 仍会重新判定
- 补预检只读与固定窗口作用域测试
2026-10-04 11:04:52 +08:00
k88936 2122dc9469 后端:关停 flush 不再假承诺重试
Project CI / Backend tests (pull_request) Failing after 32s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m37s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m46s
Project CI / Repository checks (pull_request) Failing after 34s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m21s
Project CI / Frontend tests (pull_request) Successful in 3m16s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m3s
Project CI / Native shell tests (pull_request) Successful in 6m51s
- game_play_counter_worker.rs:flush_deltas 增加 requeue_on_build;worker 循环仍把 Build 失败放回下一轮,关停路径改为直接记丢失量,不再把增量放回一个不会再被 flush 的缓冲
2026-10-03 19:28:39 +08:00
k88936 ea8734b700 后端:游玩计数锁中毒时留日志
- game_play_counter.rs:lock 仍从中毒互斥锁恢复,但先 warn 一次,便于关联留下部分更新状态的 panic
2026-10-03 19:26:06 +08:00
k88936 7c3398029e 后端:游玩计数去重与限流改用元组键
- game_play_counter.rs:seen/rate 的键由 U+001F 拼接字符串改为 (identity, game_id)/(ip, game_id) 元组,clientId 携带分隔符时不再发生键碰撞
- 补分隔符碰撞回归测试
2026-10-03 19:23:37 +08:00
k88936 03f1828561 后端:游玩计数排序移出互斥锁
- game_play_counter.rs:drain 只负责在锁内取走增量,新增 sort_deltas 在释放锁后排序,避免 O(n log n) 排序阻塞并发 record
2026-10-03 19:19:57 +08:00
k88936 9de9f4b419 前端:统一游玩上报的返回类型
- gameDistributionClient.ts:recordGamePlay 标注 Promise<{ recorded?: boolean }>,空 id 分支也返回同形状对象,消除 Promise<void> 与对象 Promise 的联合类型
2026-10-03 19:17:38 +08:00
k88936 815728ab08 前端:游玩上报复用后台请求的鉴权降级策略
- gameDistributionClient.ts:删除与 BACKGROUND_AUTH_REQUEST_OPTIONS 逐字重复的 PLAY_REPORT_REQUEST_OPTIONS,改为直接复用该常量,避免两处 local/background 鉴权语义日后漂移
2026-10-03 19:15:56 +08:00
k88936 b196c1150f 后端:补充游玩上报端点与身份解析测试
- modules/game_distribution.rs:新增公开路由在未连接 SpacetimeDB 时可达、无需登录且 no-store 的断言
- 补 clientId 去空白/截断 128 字符与缺失 UA 回退的单测
2026-10-03 18:25:49 +08:00
k88936 a5e681c0a5 文档:补充游玩计数契约与决策记录
- 玩法链路:路由表新增 POST /games/{gameId}/plays,并新增「游玩计数(已实现)」小节说明触发、落点、缓冲延迟、去重限流与写入语义
- 后端架构:game_distribution_game 节补 play_count 的批量 procedure 写入路径与参数
- decision-log:记录内存去重缓冲 + 批量 procedure 落库的决策、失败语义与影响范围
2026-10-03 18:25:49 +08:00
k88936 f6cd2127ae 前端:点击开始游戏上报游玩次数
- gamePlayClientId.ts:新增匿名游玩身份,localStorage 持久并在不可用时退化为会话内存值
- gameDistributionClient.ts:新增 recordGamePlay,携带 clientId 且按后台请求处理,不刷新会话、不改全局登录态
- GamePlayPage.tsx:点击开始游戏即 fire-and-forget 上报,失败静默不阻断进入游戏
- 补 clientId 稳定性用例与点击上报断言,并把新服务文件加入 eslint 白名单
2026-10-03 18:25:49 +08:00
k88936 d12ff72425 后端:新增公开游玩上报端点
- modules/game_distribution.rs:新增 POST /api/game-distribution/games/{game_id}/plays,公开可带 bearer,非公开 404、限流 429、成功 200 {recorded}
- 身份组成:登录用 userId、匿名用 clientId、都缺失回退 IP+UA;无效 bearer 按匿名处理不影响计数
- 请求体读取原始 Bytes,空体或畸形体不阻断计数
2026-10-03 18:25:49 +08:00
k88936 dbf3adeb13 后端:接入游玩计数内存缓冲与 flush worker
- game_play_counter.rs:新增纯内存计数器(30min 身份去重、IP+game 固定窗口限流、按时/按量取增量、requeue、过期清理)与 9 个单测
- game_play_counter_worker.rs:新增 flush worker,Build 失败放回重试、Timeout/ConnectDropped 丢弃并记录丢失量,关停前强制 flush
- main.rs:HTTP 角色注册 worker,并在 finalize_shutdown 内按 outbox 超时强制落库
- state.rs/config.rs:AppState 持有计数器,新增 GENARRATIVE_GAME_PLAY_COUNTER_FLUSH_INTERVAL_MS(默认 5s)
- request_context.rs:抽出 client_ip_from_headers 并补测试,runtime_profile.rs 改为复用
2026-10-03 18:25:49 +08:00
k88936 8301b7b25f 后端:spacetime-client 新增游玩计数 facade
- game_distribution.rs:新增 GameDistributionPlayCountIncrementRecordInput 与 increment_game_distribution_game_play_counts,把批次映射为绑定输入并调用增量 procedure,错误转 procedure_failed
2026-10-03 18:25:49 +08:00
k88936 cc33c39f24 后端:新增批量游玩计数 procedure 与生成绑定
- spacetime-module/game_distribution.rs:新增 GameDistributionPlayCountIncrement(Input) 与 increment_game_distribution_game_play_counts_and_return,事务内只对 published 且有 active_version_id 的游戏 saturating_add,且不 bump updated_at
- 重新执行 npm run spacetime:generate,新增 3 个绑定并同步 module_bindings.rs 等生成产物
2026-10-03 18:04:47 +08:00
k88936 7f5ddf8f47 文档:新增游戏游玩次数计数 ADR
- 新增 docs/adr/【ADR】游戏游玩次数计数-2026-10-03.md:点开始游戏触发、api-server 纯内存 5s flush、30min 去重、IP+game 限流、批量 procedure 自增 play_count、不 bump updated_at、失败少计优于双计
- docs/README.md:登记该 ADR 到当前产品与平台
2026-10-03 17:55:36 +08:00