diff --git a/docs/README.md b/docs/README.md index deaf7040d..0a3a4c48f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,7 +24,7 @@ - [后台游戏评价管理合同](./【玩法创作】平台入口与玩法链路-2026-05-15.md#后台游戏评价管理合同):查找、分页、隐藏/恢复/删除、必填原因、统计与个人状态联动;已实现并通过本地验证,待用户验收,未部署。 - [后台游戏评价管理里程碑](./project-memory/plans/【里程碑】后台游戏评价管理-2026-10-01.md)与[实施计划](./project-memory/plans/【实施计划】后台游戏评价管理-2026-10-01.md):单里程碑范围、接口/schema 边界及验收要求;本地证据已回写主规范。 - [游戏广场评分展示合同](./【玩法创作】平台入口与玩法链路-2026-05-15.md#游戏广场评分展示合同)、[里程碑](./project-memory/plans/【里程碑】游戏广场评分展示-2026-10-01.md)与[实施计划](./project-memory/plans/【实施计划】游戏广场评分展示-2026-10-01.md):已实现并通过本地定向验证,待用户验收,未部署;公开列表/详情携带真实摘要,卡片显示一位小数均分与人数,复用有效评价统计。 -- [游戏买断制泥点付费与播放鉴权合同](./【玩法创作】平台入口与玩法链路-2026-05-15.md#游戏买断制泥点付费与播放鉴权合同)、[里程碑](./project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md)与[实施计划](./project-memory/plans/【实施计划】游戏买断制泥点付费与播放鉴权-2026-10-05.md):已实现,本机真实栈 E2E 通过,待用户验收;未部署;作者可选买断制泥点付费,购买后永久可玩,后台审核可见价格且审核员不限次试用。 +- [游戏买断制泥点付费与播放鉴权合同](./【玩法创作】平台入口与玩法链路-2026-05-15.md#游戏买断制泥点付费与播放鉴权合同)与[里程碑](./project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md):已实现,本机真实栈 E2E 76 PASS,待用户验收;未部署;人工验收脚本 `check:game-distribution-purchase-e2e` 不在 CI 自动门禁内;作者可选买断制泥点付费,购买后永久可玩,后台审核可见价格且审核员不限次试用。 - [游戏游玩次数计数](./adr/【ADR】游戏游玩次数计数-2026-10-03.md):点「开始游戏」前端上报一次游玩,api-server 纯内存聚合(5s flush、30min 去重、`IP+game` 限流、关停不强制 flush),批量 procedure 自增现有 `game_distribution_game.play_count`,不 bump `updated_at`。 - [外部 OpenAPI 与 API Key 接入方案](./【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md) - [外部 MCP 语义工具说明与参数设计](./technical/【技术方案】外部MCP语义工具说明与参数设计-2026-09-23.md):15 个新增语义工具与全部原工具并存,复用现有 External API;包含工具说明、action、参数、幂等和兼容合同。 diff --git a/docs/project-memory/plans/【实施计划】游戏买断制泥点付费与播放鉴权-2026-10-05.md b/docs/project-memory/plans/【实施计划】游戏买断制泥点付费与播放鉴权-2026-10-05.md deleted file mode 100644 index 9e8c77922..000000000 --- a/docs/project-memory/plans/【实施计划】游戏买断制泥点付费与播放鉴权-2026-10-05.md +++ /dev/null @@ -1,48 +0,0 @@ -# 【实施计划】游戏买断制泥点付费与播放鉴权 - -| 字段 | 值 | -| --- | --- | -| Milestone | `docs/project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md` | -| Status | ready | -| Owner | Genarrative Agent | - -## 修改边界 - -- 允许修改: - - `server-rs/crates/module-game-distribution`:定价、购买与播放授权的领域规则与纯内存测试。 - - `server-rs/crates/module-runtime`:新增 `game_purchase` 流水来源类型。 - - `server-rs/crates/spacetime-module`:游戏表价格字段、购买表、相关 procedure、`migration.rs`。 - - `server-rs/crates/spacetime-client`:facade 与 row mapper。 - - `server-rs/crates/api-server`:创建/更新版本接受价格、购买接口、播放会话接口、公开详情与发行网关鉴权、admin 展示。 - - `server-rs/crates/shared-contracts`、`packages/shared`:价格、购买态、购买与播放会话 DTO 及钱包文案。 - - 主站 `src/components/game-distribution`、`src/services/gameDistributionClient.ts` 与发布/详情/游玩/广场相关组件。 - - `apps/admin-web`:审核详情与待审列表价格展示。 - - 文档、后端表目录、生成绑定与定向测试。 -- 明确不修改:`/api/external/v1` 及其 OpenAPI;AGC 发布面板;评分/评价;免费游戏公开链路行为。 - -## 实现顺序 - -1. 主规范与里程碑评审确认;主规范已解除“不做交易”排除并新增合同章。 -2. 领域与 schema:游戏表末尾追加 `price_mud_points`;新增 `game_distribution_purchase`;`module-game-distribution` 定价/购买/授权规则与纯内存测试;同步 `migration.rs`、表目录、生成绑定并运行 schema 检查。 -3. 钱包:`RuntimeProfileWalletLedgerSourceType` 追加 `game_purchase`;购买时原子扣费并写流水;同步 Rust/TS 契约与前端/后台文案。 -4. API 与鉴权:创建/更新版本接受并校验价格;购买接口(`Idempotency-Key` + `(user_id, game_id)` 唯一);播放会话接口;公开详情返回价格与购买态并按需隐藏入口;发行网关对付费游戏拒绝公开路径;admin 详情与列表展示价格。 -5. 前端:发布页定价字段与校验;详情页价格与购买(复用泥点确认弹窗与钱包 store);游玩页经播放会话挂载 iframe;广场角标(可选)。 -6. 后台:审核详情与待审列表价格展示。 -7. 回写主规范、产出真实栈 smoke 与验收证据矩阵,删除本实施计划。 - -## 验证命令 - -1. `cargo test -p module-game-distribution` -2. `cargo test -p api-server game_distribution` -3. `npm run spacetime:generate && npm run check:spacetime-schema` -4. `npm run typecheck` -5. 定向 `npx vitest run src/components/game-distribution` 与 `apps/admin-web` 相关用例 -6. `npm run check:encoding`、`git diff --check` -7. 真实本地栈 smoke(见里程碑“证据要求”) - -## 风险与回滚点 - -- schema:游戏表只末尾追加带默认值字段,购买为新独立表;如迁移不可用,回滚点是不开放定价选项,保留领域与接口的非持久化部分。 -- 付费鉴权:播放会话未就绪时,回滚点是付费游戏只做“购买态 + 平台页隐藏入口”,但不得对外宣称已具备防绕过能力。 -- 钱包:扣费与购买记录必须同事务;无法原子时回滚到不开放购买,只保留价格展示。 -- 免费链路:若改动影响免费游戏,优先回滚该分支,保证免费回归为绿。 diff --git a/docs/project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md b/docs/project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md index 896b46fee..136069a30 100644 --- a/docs/project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md +++ b/docs/project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md @@ -35,18 +35,25 @@ ## 验收标准 -- [ ] 作者网页发布可选「免费」或「买断制 N 泥点」;负数/超上限/非整数被前后端同时拦截。 -- [ ] 后台审核详情展示该版本价格;无价格的历史版本按免费展示。 -- [ ] 审核员不限次数试玩待审付费版本,不校验购买、不扣泥点。 -- [ ] 未购买用户详情可见资料但无可播放入口;直连 `/games//` 返回 404。 -- [ ] 余额不足购买失败,余额、账单与购买记录都不变。 -- [ ] 购买成功:余额减少 N、账单出现 `game_purchase` 流水、购买记录唯一、可反复游玩不再扣费。 -- [ ] 双击、并发与响应丢失重试只扣一次。 -- [ ] 作者本人与管理员免购买即可游玩付费作品且不扣费。 -- [ ] 免费游戏的发布、详情、游玩与计数链路无行为变化。 +- [x] 作者网页发布可选「免费」或「买断制 N 泥点」;负数/超上限/非整数被前后端同时拦截。证据:真实本地栈 E2E 价格 `1000001` → 400 且不落版本,`0` / `1000000` → 200;`cargo test -p module-game-distribution` 29 passed、`cargo test -p api-server game_distribution` 53 passed 覆盖定价校验,网页发布表单定价定向 Vitest 覆盖前端拦截。 +- [x] 后台审核详情展示该版本价格;无价格的历史版本按免费展示。证据:`cargo test -p api-server game_distribution` 53 passed(`private_version_payload` / `version_detail_payload` 输出 `priceMudPoints`,历史无价格版本按 0);admin-web 审核价格展示定向 Vitest。 +- [x] 审核员不限次数试玩待审付费版本,不校验购买、不扣泥点。证据:真实本地栈 E2E 审核员两次试玩待审付费版本均 200、两次包内字节一致,作者与审核员余额/流水均不变。 +- [x] 未购买用户详情可见资料但无可播放入口;直连 `/games//` 返回 404。证据:真实本地栈 E2E 详情 `priceMudPoints=30` / `purchased=false` / `entryUrl=null`,发行网关与平台同源均 404,创建会话 403,未登录 401。 +- [x] 余额不足购买失败,余额、账单与购买记录都不变。证据:真实本地栈 E2E 返回 400 `INSUFFICIENT_MUD_POINTS`,余额与流水不变。 +- [x] 购买成功:余额减少 N、账单出现 `game_purchase` 流水、购买记录唯一、可反复游玩不再扣费。证据:真实本地栈 E2E 扣 30(120→90),`game_purchase` 流水与购买行各 1 条,重复进入不再扣费。 +- [x] 双击、并发与响应丢失重试只扣一次。证据:真实本地栈 E2E 同 key 重放、换 key 重复购买均不再扣费;两路不同 key 真并发返回 200/200、只有一个 `replayed=false`、余额只扣一次、购买记录 1 条。 +- [x] 作者本人与管理员免购买即可游玩付费作品且不扣费。证据:真实本地栈 E2E 作者(自购 400 `GAME_PURCHASE_OWNER_EXEMPT`、余额/流水不变、播放会话 200)与管理员(管理员令牌创建播放会话 200、入口 200);管理员免购买经 play-session 管理员分支实现。 +- [x] 免费游戏的发布、详情、游玩与计数链路无行为变化。证据:真实本地栈 E2E 免费游戏详情保留入口、发行网关 200、匿名创建会话 200 且不签发令牌。 + +## 未覆盖边界(保持未勾选) + +- [ ] 管理员安全下架后的播放会话失效:本轮未单独运行,与作者下架共用同一 404 关闭路径。 +- [ ] 多账号 / 多游戏混合并发购买:本轮只覆盖单游戏、单账号的两路真并发。 ## 证据要求 - 自动化:Rust 定向测试(定价校验、购买幂等与并发、授权判定、状态机)、Vitest(发布表单定价、详情购买态、游玩页会话挂载、审核价格展示)、schema/绑定与编码门禁。 - 运行时:真实本地栈跑通「发布付费游戏 → 后台审核可见价格并试玩 → 通过 → 未购买访问失败 → 购买 → 游玩 → 重复游玩不扣费」。 - 边界:并发购买、余额不足、下架/封禁后播放会话失效、猜测 URL 直连、免费游戏回归。 + +已执行(2026-10-05,人工验收):`npm run check:game-distribution-purchase-e2e` 在本机真实 SpacetimeDB / api-server / OSS 一次跑通 76 PASS / 0 FAIL / 1 WARN,exit 0,24.1s,teardown 无残留。唯一 WARN 为「管理员令牌购买」在当前产品形态不可达(管理员免购买经 play-session 管理员分支实现,购买路由的 403 `GAME_PURCHASE_ADMIN_NOT_ALLOWED` 分支作为纵深防御保留)。该脚本不在 CI 自动门禁内,CI 绿不代表该链路已验证;本里程碑结论最高到“本机真实栈验证通过,待用户验收,未部署”。 diff --git a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md index e3697862b..a863c47e2 100644 --- a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md +++ b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md @@ -563,7 +563,6 @@ | Date | 2026-10-05 | | 适用边界 | 主站游戏分发(`/games`)的作者买断制定价、泥点购买、付费游玩鉴权与后台审核价格展示;不含创作者结算 | | 里程碑 | [游戏买断制泥点付费与播放鉴权](./project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md) | -| 实施计划 | [实施计划](./project-memory/plans/【实施计划】游戏买断制泥点付费与播放鉴权-2026-10-05.md) | ### 目标 @@ -626,6 +625,8 @@ - 播放会话是 `api-server` 进程内短期会话,不持久化到 SpacetimeDB:多副本部署需要粘性路由,进程重启即全部失效,与既有后台待审预览会话同构。 - 播放始终解析“当前公开版本”:作者发布并审核通过新版本后,旧会话继续玩到的是新版本包,与买断制永久所有权随 `gameId` 而非版本的口径一致。 - 已知同类风险(本次只记录、不修):后台待审预览入口 `/api/game-distribution/admin-previews/{token}/` 与播放会话同属 `/api` 前缀,`api-server` 预览网关同样对带可解析平台 refresh Cookie 的请求返回 403;本次只对播放会话前缀做了边缘/dev 清 Cookie 加固,预览前缀未加固。 +- `scripts/check-game-distribution-purchase-e2e.mjs`(`npm run check:game-distribution-purchase-e2e`)是人工验收脚本,需要 `E2E_ADMIN_USER` / `E2E_ADMIN_PASSWORD` 与真实 SpacetimeDB / api-server / OSS,不在 CI 自动门禁内;CI 绿不代表付费链路的运行时行为已验证,发布前必须人工运行并保留结果。 +- 上线前需用 `E2E_REQUIRE_PRIVATE_BUCKET=1` 复核生产桶不允许匿名直取发行包(既有要求,非本轮新增)。 ### 契约与迁移 @@ -638,15 +639,17 @@ | 条款 | 验收方式 | 证据 | | --- | --- | --- | -| 定价 | 网页发布可选「免费/买断制」并拦截非法值,前后端口径一致 | 待补 | -| 后台可见 | 审核详情与待审列表显示该版本价格 | 待补 | -| 审核试玩 | 审核员不限次试玩待审付费版本,不校验购买、不扣泥点 | 待补 | -| 购买 | 余额不足失败且不变更;成功扣 N、账单出现 `game_purchase` 流水、购买记录唯一 | 待补 | -| 幂等 | 双击、并发、响应丢失重试只扣一次 | 待补 | -| 播放鉴权 | 未购买直连公开入口 404;购买后可反复游玩且不再扣费 | 待补 | -| 免购买游玩 | 作者与管理员可游玩付费作品且不扣费 | 待补 | -| 免费回归 | 免费游戏发布/详情/游玩/计数无行为变化 | 待补 | -| 工程门禁 | schema/绑定、两端类型、定向测试、编码与 diff 检查通过 | 待补 | +| 定价 | 网页发布可选「免费/买断制」并拦截非法值,前后端口径一致 | 真实本地栈 E2E:价格 `1000001` → 400 且不落版本,`0` 与 `1000000` → 200;`cargo test -p module-game-distribution` 29 passed(`normalize_game_price_mud_points` 上限)、`cargo test -p api-server game_distribution` 53 passed(`validate_version_declaration` 定价校验);网页发布表单定价定向 Vitest | +| 后台可见 | 审核详情与待审列表显示该版本价格 | `cargo test -p api-server game_distribution` 53 passed(`private_version_payload` / `version_detail_payload` 输出 `priceMudPoints`,历史无价格版本按 0,不回退游戏行当前价);admin-web 审核价格展示定向 Vitest | +| 审核试玩 | 审核员不限次试玩待审付费版本,不校验购买、不扣泥点 | 真实本地栈 E2E:审核员两次试玩待审付费版本均 200、两次包内字节一致,作者与审核员余额/流水均不变 | +| 购买 | 余额不足失败且不变更;成功扣 N、账单出现 `game_purchase` 流水、购买记录唯一 | 真实本地栈 E2E:余额不足 400 `INSUFFICIENT_MUD_POINTS` 且余额与流水不变;成功购买扣 30(120→90),`game_purchase` 流水与购买行各 1 条;同 key 重放与换 key 重复购买都不再扣费 | +| 幂等 | 双击、并发、响应丢失重试只扣一次 | 真实本地栈 E2E:同 key 重放、换 key 重复购买均不再扣费;两路不同 key 真并发返回 200/200,只有一个 `replayed=false`,余额只扣一次、购买记录 1 条。多账号/多游戏混合并发未覆盖 | +| 播放鉴权 | 未购买直连公开入口 404;购买后可反复游玩且不再扣费 | 真实本地栈 E2E:未购买时详情 `priceMudPoints=30` / `purchased=false` / `entryUrl=null`,直连发行网关与平台同源均 404,创建会话 403,未登录 401;购买后可玩且重复进入不再扣费;伪造令牌 404;直连带平台 Cookie 403、经 dev 代理(该前缀清 Cookie)200;作者下架后旧会话入口与资源均 404。管理员安全下架后的会话失效未单独跑(与作者下架共用同一 404 关闭路径) | +| 免购买游玩 | 作者与管理员可游玩付费作品且不扣费 | 真实本地栈 E2E:作者免购买(详情 `purchased=false` 但有 `entryUrl`,自购 400 `GAME_PURCHASE_OWNER_EXEMPT` 且余额/流水不变,播放会话 200);管理员免购买(管理员令牌创建播放会话 200、入口 200)。管理员免购买经 play-session 管理员分支实现;购买路由的 403 `GAME_PURCHASE_ADMIN_NOT_ALLOWED` 分支在当前产品形态不可达(管理员令牌在用户路由先被 `require_bearer_auth` 判 401),作为纵深防御保留并有单元测试 | +| 免费回归 | 免费游戏发布/详情/游玩/计数无行为变化 | 真实本地栈 E2E:免费游戏详情保留入口、发行网关 200、匿名创建会话 200 且不签发令牌;`cargo test -p api-server game_distribution` 免费详情 `priceMudPoints=0` / `purchased=false` / 入口保持 | +| 工程门禁 | schema/绑定、两端类型、定向测试、编码与 diff 检查通过 | `cargo test -p api-server game_distribution` 53 passed、`cargo test -p module-game-distribution` 29 passed、`cargo test -p spacetime-module game_distribution` 7 passed、`cargo test -p pingora-gateway` 42 passed;`check:game-distribution-dto-parity`、`check:pingora-route-parity`、`check:nginx-spa-routes`、`check:pingora-gateway-smoke`、`check:spacetime-schema`、`check:server-rs-ddd`、两端 typecheck 与定向 Vitest、`check:encoding`、`git diff --check` 全绿 | + +上表运行时证据来自人工验收脚本 `scripts/check-game-distribution-purchase-e2e.mjs`(`npm run check:game-distribution-purchase-e2e`):本机真实 SpacetimeDB / api-server / OSS 一次跑通 76 PASS / 0 FAIL / 1 WARN,exit 0,24.1s,teardown 无残留。该脚本不在 CI 自动门禁内,CI 绿不代表该链路已验证;本表结论最高到“本机真实栈验证通过,待用户验收,未部署”。 ### 已收口决策(2026-10-05)