From f7d36528780b244f31f210bf4c18553abf06bd94 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Mon, 5 Oct 2026 18:12:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=B8=B8=E6=88=8F=E4=B9=B0?= =?UTF-8?q?=E6=96=AD=E5=88=B6=E6=B3=A5=E7=82=B9=E4=BB=98=E8=B4=B9=E4=B8=8E?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E9=89=B4=E6=9D=83=E6=96=87=E6=A1=A3=E9=97=AD?= =?UTF-8?q?=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主规范路由表新增 POST /games/{gameId}/purchase 行,写明幂等键、请求体与余额不足/作者自购/管理员令牌/价格变化错误口径 主规范路由表新增 POST /games/{gameId}/play-session 行,写明付费需登录、免费回公开入口与 { playUrl, expiresAt } 响应 主规范路由表新增 GET /game-distribution/play-sessions/{token}[/{assetPath}] 行,写明凭令牌、不读 Cookie、no-store、失效一律 404 主规范路由表更新 GET /games/{gameId} 行,补充 priceMudPoints 与 purchased,付费未购买时 currentVersion.entryUrl 为 null 主规范路由表更新发行网关行,当前公开版本 price_mud_points > 0 时统一 404 主规范合同 Status 由 proposed 改为 implemented(本地验证通过,待用户验收;未部署) 主规范收口价格上限 1_000_000 与免购买范围(作者本人 + 后台管理员令牌),删除正文悬置措辞并注明日期 主规范补充播放会话进程内会话、始终解析当前公开版本两条实现约束,记录后台待审预览前缀未清 Cookie 的同类风险 里程碑 Status 由 proposed 改为 implemented-awaiting-runtime-acceptance docs/README.md 该合同/里程碑/实施计划条目描述改为已实现、本机真实栈 E2E 通过、待用户验收、未部署 --- docs/README.md | 2 +- ...¢‘】游戏买断制泥点付费与播放鉴权-2026-10-05.md | 2 +- ...�玩法创作】平台入口与玩法链路-2026-05-15.md | 25 +++++++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/docs/README.md b/docs/README.md index d91dda5a0..deaf7040d 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):proposed;作者可选买断制泥点付费,购买后永久可玩,后台审核可见价格且审核员不限次试用;未实现。 +- [游戏买断制泥点付费与播放鉴权合同](./【玩法创作】平台入口与玩法链路-2026-05-15.md#游戏买断制泥点付费与播放鉴权合同)、[里程碑](./project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md)与[实施计划](./project-memory/plans/【实施计划】游戏买断制泥点付费与播放鉴权-2026-10-05.md):已实现,本机真实栈 E2E 通过,待用户验收;未部署;作者可选买断制泥点付费,购买后永久可玩,后台审核可见价格且审核员不限次试用。 - [游戏游玩次数计数](./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 index 506c2ec93..896b46fee 100644 --- a/docs/project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md +++ b/docs/project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md @@ -3,7 +3,7 @@ | 字段 | 值 | | --- | --- | | Version | 1.0 | -| Status | proposed | +| Status | implemented-awaiting-runtime-acceptance | | Date | 2026-10-05 | | Parent Spec | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`(「游戏买断制泥点付费与播放鉴权合同」章) | diff --git a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md index ff27cc0dd..e3697862b 100644 --- a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md +++ b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md @@ -122,9 +122,12 @@ | 方法与路径 | 身份 | 行为 | | --- | --- | --- | | `GET /games` | 游客 | **已实现**:关键词与分类筛选,最多 48 项;仅公开可玩版本 | -| `GET /games/{gameId}` | 游客 | **已实现**:当前公开资料与 `currentVersion.entryUrl`;不可见时 404 | +| `GET /games/{gameId}` | 游客/登录 | **已实现**:当前公开资料、`priceMudPoints` 与查看者 `purchased`(匿名、坏令牌与读取失败按未购买,内容访问失败关闭);付费作品对未购买且非作者/管理员的查看者把 `currentVersion.entryUrl` 置为 `null`,资料与价格仍可见;不可见时 404 | | `POST /games/{gameId}/plays` | 游客/登录 | **已实现**:上报一次「开始游戏」;可选 Bearer,非公开 404、超限 429,成功返回 `{recorded}`;只进 api-server 内存缓冲,失败不影响游玩 | -| `GET /game-distribution/releases/{gameId}[/{assetPath}]` | 游客 | **已实现**:根路径等价于 `index.html`;发行网关只服务当前已公开版本包内文件,按扩展名白名单设内容类型,未知扩展名 404,带 Cookie 的请求 403;游玩页的入口来自详情投影的 `currentVersion.entryUrl` | +| `POST /games/{gameId}/purchase` | 登录用户 | **已实现**:买断制购买,必填 `Idempotency-Key`,体 `{ expectedPriceMudPoints }`;扣费与购买记录同事务,重复购买幂等返回既有记录不再扣费;余额不足 400 `INSUFFICIENT_MUD_POINTS`、作者自购 400 `GAME_PURCHASE_OWNER_EXEMPT`、后台管理员令牌 403 `GAME_PURCHASE_ADMIN_NOT_ALLOWED`、价格已被新版本改变 409;免费游戏按 400 拒绝 | +| `POST /games/{gameId}/play-session` | 游客/登录 | **已实现**:付费作品需登录,仅已购买、作者本人或后台管理员可签发,响应 `{ playUrl, expiresAt }`;免费作品不校验登录也不签发令牌,直接返回现有公开入口 `/games/{gameId}/`;未登录 401、不可见 404、未购买且非作者/管理员 403 | +| `GET /game-distribution/releases/{gameId}[/{assetPath}]` | 游客 | **已实现**:根路径等价于 `index.html`;发行网关只服务当前已公开版本包内文件,按扩展名白名单设内容类型,未知扩展名 404,带 Cookie 的请求 403;当前公开版本 `price_mud_points > 0` 时统一 404(付费作品只经播放会话开放,不能靠猜测 `gameId` 绕过购买);免费作品的游玩页入口来自详情投影的 `currentVersion.entryUrl` | +| `GET /game-distribution/play-sessions/{token}[/{assetPath}]` | 凭播放令牌 | **已实现**:凭令牌读取当前公开版本包内文件,不读 Cookie(带可解析平台 refresh Cookie 的请求一律 403,边缘/dev 按该前缀清 Cookie),`no-store`;令牌不存在或过期、游戏下架/封禁、无有效公开版本一律 404 | | `GET /my-games` | 登录作者 | **已实现**:当前账号游戏、最近版本状态与驳回理由;owner 只从认证主体派生,单次最多 48 项 | | `GET /my-games/{gameId}` | 登录作者 | **已实现**:作者读自己名下单个游戏的详情,条目与 `GET /my-games` 同形(含全部版本私有状态、驳回理由与已公开版本的 `entryUrl`)。作者要能打开「审核中 / 被驳回 / 已下架 / 已撤回」的作品,公开详情只服务已公开投影,所以作者视角必须走这条 owner 作用域路由;游戏不存在或不属于当前主体都返回 404 | | `PATCH /my-games/{gameId}` | 登录作者 | **已实现**:作者编辑自己名下游戏的展示资料(标题/简介/详介/分类/标签/封面/截图/设备/输入模式/方向),立即生效并落 `tracking_event` 审计;要求 `Idempotency-Key` 与 `expectedPublicationRevision` CAS,随版本冻结的包摘要与资料快照不受影响,缺封面/截图归属不符仍按创建口径拒绝 | @@ -556,7 +559,7 @@ | 字段 | 值 | | --- | --- | | Version | 1.0 | -| Status | proposed | +| Status | implemented(本地验证通过,待用户验收;未部署) | | Date | 2026-10-05 | | 适用边界 | 主站游戏分发(`/games`)的作者买断制定价、泥点购买、付费游玩鉴权与后台审核价格展示;不含创作者结算 | | 里程碑 | [游戏买断制泥点付费与播放鉴权](./project-memory/plans/【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md) | @@ -586,7 +589,7 @@ #### 定价与冻结 - 价格是版本冻结资料的一部分:作者提交新版本时携带价格,与封面/截图一样在审核通过时整体生效到游戏行;未通过审核的价格不改变当前公开价格。 -- 价格是整数泥点数,`0` 表示免费,取值域由服务端约束(上限待评审,初值 `1_000_000`);免费与付费共用同一条发布与审核链路。 +- 价格是整数泥点数,`0` 表示免费,取值域由服务端约束为 `0..=1_000_000`(上限于 2026-10-05 定稿);免费与付费共用同一条发布与审核链路。 - 展示资料编辑(`PATCH /my-games/{gameId}`)不得直接变更公开价格;调整价格必须走新版本审核。 #### 购买与所有权 @@ -618,6 +621,12 @@ - 并发或重复点击只产生一条购买记录、只扣一次。 - 服务端不可用时购买失败关闭,不出现“未扣费却已解锁”或“已扣费却未解锁”的中间态。 +#### 实现约束与已知风险 + +- 播放会话是 `api-server` 进程内短期会话,不持久化到 SpacetimeDB:多副本部署需要粘性路由,进程重启即全部失效,与既有后台待审预览会话同构。 +- 播放始终解析“当前公开版本”:作者发布并审核通过新版本后,旧会话继续玩到的是新版本包,与买断制永久所有权随 `gameId` 而非版本的口径一致。 +- 已知同类风险(本次只记录、不修):后台待审预览入口 `/api/game-distribution/admin-previews/{token}/` 与播放会话同属 `/api` 前缀,`api-server` 预览网关同样对带可解析平台 refresh Cookie 的请求返回 403;本次只对播放会话前缀做了边缘/dev 清 Cookie 加固,预览前缀未加固。 + ### 契约与迁移 - SpacetimeDB:`game_distribution_game` 末尾追加 `price_mud_points: u64`(明确默认 0);新增私有 `game_distribution_purchase`(`purchase_id` 主键,含 `game_id`、`user_id`、成交价快照、钱包流水 ID 与创建时间,`(user_id, game_id)` 唯一);同步 `migration.rs`、后端表目录与生成绑定,并运行 `npm run check:spacetime-schema`。 @@ -639,7 +648,9 @@ | 免费回归 | 免费游戏发布/详情/游玩/计数无行为变化 | 待补 | | 工程门禁 | schema/绑定、两端类型、定向测试、编码与 diff 检查通过 | 待补 | -### 待评审决策 +### 已收口决策(2026-10-05) -1. 价格上限取值,以及是否允许作者在作品已公开后通过新版本随意调价(对已购买用户不再收费的口径已确定)。 -2. “免购买游玩”的判定范围:仅限作者本人与现有管理员身份,还是需要单独运营角色。 +1. 价格上限取值 `1_000_000`,与 `0` 一起构成唯一定价约束;作者调整公开价格必须走新版本提交与审核,不设独立价格运营审批流,已购买用户继续保有所有权、不再扣费。 +2. “免购买游玩”判定范围为作者本人与后台管理员:作者由游戏行的 `ownerUserId` 与当前登录主体比对;管理员必须携带后台管理员令牌,主站登录态与后台账号之间没有身份关联,普通登录不会被当作管理员。 + +本章当前没有待评审决策。