f502829fde
Project CI / AI game creator shell Rust crates (push) Successful in 1m28s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m2s
Project CI / Backend tests (push) Successful in 5m19s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m59s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m19s
Project CI / Native shell tests (push) Successful in 6m48s
Project CI / Frontend tests (push) Successful in 2m54s
Project CI / Repository checks (push) Successful in 2m58s
Project CI / AI game creator shell web tests (push) Successful in 2m34s
## 解决什么 你反馈的两个现象都是真问题,这个 PR 一次修掉: 1. **没开灰度也能看到发布按钮**:发布开关此前是「无 gate 行 = 默认开放」,所以没配灰度时后端把 `gameDistributionPublishEnabled` 判成 true,客户端就渲染了发布入口。 2. **按钮点了没反应**:发布动作的提示原先只写 `workspaceStatus`,而普通项目走 `DirectProjectChatView` 时并不渲染工作台状态行,于是「先打开一个项目再发布」「构建失败…」这类信息全都看不见。 3. **后台看不到 key**:预设项此前在未合并的另一个 PR 里,而且后台只列「已创建」的 gate 行。 ## 改成什么 - `is_game_distribution_publish_enabled_for_user` 要求 gate 行存在且 `enabled=true`:**灰度默认关闭**,未登录/无行/`enabled=false` 一律不开放;只有白名单 / 灰度比例 / 用户标签命中才开放。作者写入与新版本激活都按这条判据。 - 新增与作者无关的总开关 `is_game_distribution_publish_open`:管理员审核激活新版本只看「灰度是否开启」,不会因为审核员不在作者白名单里而被挡住(这是合入后立刻发现的回归,已修 + 有用例)。 - AGC 新增 `announcePublishMessage`:发布相关提示同时写工作台状态与 DirectProject 对话,点「发布到游戏广场」不会再像没反应。 - 后台「灰度发布配置」新增「可配置开关」列表:`game-distribution:publish` 等预设即使还没创建行也会显示,点「配置」按默认关闭填表,运营开启后填白名单/比例即可放量。 ## 验证 - `npm run check:game-distribution-media-e2e`(真实 Phaser 构建产物)现在覆盖:灰度关闭 → 作者拿不到入口 + 写入口 503 → 开启灰度 → 真实封面/截图直传 → 创建游戏/版本/上传/送审 → 管理员审核通过 → 网关 index.html 与 bundle 200 → nosniff;全流程通过。 - 后端:`cargo test -p api-server game_distribution` 19 passed(含新增 `game_distribution_publish_open_ignores_author_allowlist_for_activation`)、`frontend_runtime_config` 6 passed。 - 前端:admin 灰度页 10 用例(含「未创建的预设开关可见并可一键配置」)、AGC 聊天头 3 用例、网页发布页 15 用例;三端 typecheck、`check:encoding`、`check:doc-index`、`check:rustfmt`、全量 `npm test`(400 文件 / 4441 用例)与 `check:repository-ci -- origin/master HEAD` 全绿。 ## 上线注意 灰度默认关闭是**刻意**的:合入后到运营在后台把 `game-distribution:publish` 配置并开启之前,作者看不到发布入口、写入口 503;已公开游戏、目录、详情、发行网关、安全下架不受影响。 Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/482 Co-authored-by: kdletters <kdletters@qq.com> Co-committed-by: kdletters <kdletters@qq.com>
api-server 主工程 crate 占位说明
日期:2026-04-20
1. crate 职责
api-server 是新后端的 Axum 主工程 crate,后续负责:
main.rs启动入口Router装配with_state共享状态注入- 中间件挂载
/healthz、/api/*、SSE 与静态资源兼容层装配- 由
../../scripts/dev.ps1与../../scripts/dev.sh驱动的本地开发启动链路 - 由
../../scripts/test.ps1与../../scripts/test.sh驱动的本地测试链路 - 由
../../scripts/check.ps1与../../scripts/check.sh驱动的本地统一检查链路 - 由
../../scripts/smoke.ps1与../../scripts/smoke.sh驱动的本地启动与协议冒烟链路
2. 当前阶段说明
当前目录已经完成以下基础骨架:
- 目录占位
Cargo.tomlsrc/main.rssrc/app.rssrc/state.rssrc/config.rs- 基础
TraceLayer挂载 - 接入
shared-logging完成tracing subscriber初始化 - 接入
POST /api/auth/entry首版密码登录链路 - 接入
POST /api/auth/password/change登录后修改密码链路 - 接入
POST /api/auth/password/reset手机验证码重置密码链路 - 接入
POST /api/assets/direct-upload-tickets直传票据接口 - 接入
GET /api/auth/me当前用户查询链路 - 接入
POST /api/auth/refreshrefresh token 轮换链路 - 接入
POST /api/auth/logout当前设备退出链路 - 接入
POST /api/assets/objects/confirm上传完成确认链路 - 接入
GET /api/auth/login-options登录方式探测链路 - 接入
POST /api/auth/phone/send-code手机验证码发送链路 - 接入
POST /api/auth/phone/login手机验证码登录链路 - 接入
GET /api/auth/wechat/start微信授权起跳链路 - 接入
GET /api/auth/wechat/callback微信回调换取系统登录态链路 - 接入
POST /api/auth/wechat/bind-phone微信待绑定账号补绑手机号链路 - 接入
POST /api/assets/objects/bind已确认对象绑定业务实体槽位链路 - 接入
POST /api/assets/sts-upload-credentials禁用式 STS 写权限 contract - 接入
custom-world-library、custom-world-gallery与 agentpublish_world首批 Axum facade - 接入 custom world agent
session create / session snapshotAxum facade - 旧
runtime story兼容接口已下线并保持未挂载;运行时故事写链路改为:POST /api/story/sessions/runtimeGET /api/story/sessions/{story_session_id}/runtime-projectionPOST /api/story/sessions/{story_session_id}/actions/resolve
- 接入
POST /api/assets/character-visual/generate - 接入
GET /api/assets/character-visual/jobs/{task_id} - 接入
POST /api/assets/character-visual/publish - 接入
GET /api/assets/character-animation/templates - 接入
POST /api/assets/character-animation/import-video - 接入
GET /api/assets/character-workflow-cache/{character_id} - 接入
POST /api/assets/character-workflow-cache - 接入
POST /api/assets/character-animation/generate - 接入
GET /api/assets/character-animation/jobs/{task_id} - 接入
POST /api/assets/character-animation/publish - 下线旧
/generated-*资产直读代理;生成资产读取统一走/api/assets/read-url或 asset object projection - 下线旧
/api/custom-world/*非 runtime 前缀和/api/runtime/puzzle/runs/local-next-level,并用路由回归测试确认这些旧入口保持404
后续与本 crate 直接相关的任务包括:
- 接入统一日志与 tracing
- 接入
request_id - 接入统一错误处理中间件
- 接入 response envelope
- 接入
/healthz - 接入
/api/auth/entry - 接入
/api/assets/direct-upload-tickets - 接入
/api/auth/me - 接入
/api/auth/refresh - 接入
/api/auth/logout - 接入
/api/assets/objects/confirm - 接入
/api/auth/login-options - 接入
/api/auth/phone/send-code - 接入
/api/auth/phone/login - 接入
/api/auth/wechat/start - 接入
/api/auth/wechat/callback - 接入
/api/auth/wechat/bind-phone - 接入
/api/assets/objects/bind - 接入
/api/assets/sts-upload-credentials - 接入
custom world library / gallery / publish_world首批 facade - 接入
custom world agent session create / snapshotfacade - 下线旧
runtime storycompat facade,并接入 story session scoped runtime story 写读入口 - 接入
character-visual generate / jobs / publish第一批 OSS 主链兼容 facade - 接入
character-animation templates / import-video第一批 OSS 草稿兼容 facade - 接入
character-workflow-cache get / save第一批 OSS JSON 草稿兼容 facade - 接入
character-animation generate / jobs / publish第一批 OSS 主链兼容 facade - 下线旧
/generated-*路径 OSS 私有读同源代理 - 下线旧
/api/custom-world/*非 runtime 前缀和 Puzzlelocal-next-level兼容入口
当前 tracing 约定:
- 进程启动时通过
shared-logging统一初始化tracing subscriber。 - 默认日志过滤器来自
GENARRATIVE_API_LOG,未提供时回落到info,tower_http=info。 - HTTP 访问日志统一通过 Axum 路由层的
TraceLayer输出,后续request_id、响应头与错误中间件继续在同一层扩展。 - 本地启动器
npm run dev:api-server和完整联调入口npm run dev会在保留终端实时输出的同时,把同一份cargo/api-server输出持久化到logs/api-server/。如需固定文件或目录,可设置GENARRATIVE_API_SERVER_LOG_FILE或GENARRATIVE_API_SERVER_LOG_DIR。
当前 request context 约定:
- 中间件优先读取来访
x-request-id,未提供时生成新的 UUID。 request_id会统一写入请求extensions与请求头,供 tracing、错误处理中间件和响应头层复用。- 最终响应会回写同一个
x-request-id,保证调用方、日志链路和后续 envelopemeta.requestId可对齐。
当前错误处理中间件约定:
- 对 Axum 默认产生的空
4xx / 5xx响应,统一归一化为 legacy 兼容 JSON 错误体:{ error: { code, message, details? } }。 - 已经带
content-type的业务错误响应不会被覆盖,避免抢走后续 response envelope 的职责。 - 统一错误日志会复用当前请求的
request_id,便于后续和响应头、envelope 元信息串联。
当前 response envelope 约定:
RequestContext已记录request_id、请求开始时间、默认operation与 envelope 协商结果。json_success_body(...)/json_error_body(...)会根据x-genarrative-response-envelope自动在“裸数据 / 标准 envelope / legacy error + meta”之间切换。meta.apiVersion、meta.requestId、meta.routeVersion、meta.operation、meta.latencyMs、meta.timestamp已按当前前端契约生成,响应头回写仍留给后续独立任务。
当前基础响应头约定:
- 所有响应都会回写
x-request-id。 - 所有响应都会回写固定的
x-api-version,值来自shared_contracts::api::API_VERSION,当前为2026-06-16,并与 bodymeta.apiVersion保持一致。 - 所有响应都会回写
x-route-version,当前阶段默认与x-api-version保持一致,后续再按路由粒度细分。 - 所有响应都会回写
x-response-time-ms,值来源于RequestContext内记录的请求开始时间。
当前 /healthz 约定:
- 路径固定为
/healthz。 - 裸响应继续返回
{ ok: true, service: "genarrative-node-server" },保持与当前 Node 工程兼容。 - 当请求携带
x-genarrative-response-envelope时,/healthz会返回标准 success envelope。 x-request-id、x-api-version、x-route-version、x-response-time-ms会在/healthz响应中一并回写。
当前本地检查链路约定:
../../scripts/check.ps1与../../scripts/check.sh统一串联cargo fmt --all --check、cargo clippy、cargo check、cargo test。- 默认检查整个
server-rsworkspace,确保后续多 crate 扩容时仍然保持统一口径。 - 当只需聚焦单个 crate 时,可通过
-Package或SERVER_RS_CHECK_PACKAGE收窄clippy / check / test目标。 cargo fmt --all --check仍固定覆盖整个 workspace,避免多 crate 下格式基线漂移。
当前本地 smoke 链路约定:
../../scripts/smoke.ps1与../../scripts/smoke.sh会先构建api-server,再拉起临时本地进程完成冒烟验证。- smoke 当前固定校验
/healthz的 raw 响应、envelope 响应以及x-request-id、x-api-version、x-route-version、x-response-time-ms头。 - smoke 通过后,可作为“Axum 服务可独立启动且基础 contract 可联通”的本地自动化证据。
3. 边界约束
api-server负责 HTTP、SSE、Cookie、Header、路由与协议装配。- 业务逻辑优先通过独立模块 crate 暴露能力,再由主工程组合。
- 外部副作用通过
platform-auth、platform-oss、platform-llm与各模块 crate 的应用层完成。 - 不把领域规则直接堆在 handler 中。
- 当前密码登录由
module-auth负责用例编排,api-server只负责请求解析、JWT 签发与 refresh cookie 写回。 - 当前
/api/auth/me复用现有 Bearer JWT 中间件与module-auth用户快照查询,不直接绕过模块边界读取内部状态。 - 当前
/api/auth/refresh复用module-auth的 refresh session 轮换能力,api-server负责 refresh cookie 读取、失败清理与 access token 重签。 - 当前
/api/auth/logout复用module-auth的当前会话吊销与用户版本递增能力,api-server负责 Bearer JWT、refresh cookie 读取与清理 cookie 回写。 - 当前
/api/assets/objects/confirm先由platform-oss完成私有HEAD Object校验,再通过spacetime-client调用spacetime-module的对象确认持久化入口。 - 当前
/api/assets/objects/bind只绑定已确认对象到业务实体槽位,不访问 OSS,不创建悬空asset_object_id。 - 当前手机号登录与微信登录都复用
module-auth的进程内认证仓储,api-server负责请求解析、场景判定、系统 JWT 签发与 refresh cookie 写回。 - 当前微信回调不会把第三方 token 直接透传给前端或 SpacetimeDB,而是统一换成系统签发的 JWT。
- 当前
/api/assets/sts-upload-credentials按“服务器上传、Web 只下载”口径固定返回403,不向浏览器下发 OSS 写权限。 - 当前
/api/runtime/custom-world/agent/sessions与/api/runtime/custom-world/agent/sessions/{session_id}只提供 deterministic session 骨架与 snapshot 读取,不承诺 message submit、operation query、card detail 的完整能力。 - 当前
/api/runtime/story/*不再挂载;runtime story 开局、读取和动作结算通过/api/story/sessions/runtime、/api/story/sessions/{story_session_id}/runtime-projection与/api/story/sessions/{story_session_id}/actions/resolve进入 BFF,并由module-runtime-story、story session 和 runtime snapshot 投影共同闭合。 - 当前
/api/assets/character-visual/*第一批只保证旧接口 contract、OSS 草稿/正式对象、asset_object与asset_entity_binding主链可用;真实图片模型、workflow cache 与本地角色覆盖写回仍在后续阶段。 - 当前
/api/assets/character-animation/import-video第一批只接受data:video/*;base64,...并写入 OSS 草稿区,不读取旧本地public/路径,也不创建正式asset_object。 - 当前
/api/assets/character-workflow-cache/*第一批只把工作流 JSON 草稿写入 OSS,不迁移历史本地缓存,也不创建正式asset_object。 - 当前
/api/assets/character-animation/generate第一批只用 Rust 占位产物打通AiTaskService + OSS草稿链;image-sequence写 SVG 帧,视频类策略优先复用参考视频或仓库内可播放占位视频,不代表真实上游视频模型已完成迁移。 - 当前
/api/assets/character-animation/publish会把前端提交帧、动作级 manifest 与总 manifest 写入 OSS,并只把总 manifest 确认为asset_object后绑定到character / animation_set。 - 当前旧
/generated-*直读兼容层已下线;/generated-*只作为legacyPublicPath/ OSS object key 标识,读取必须通过/api/assets/read-url或业务投影中的签名读 URL。 - 当前旧
/api/custom-world/entity、/api/custom-world/scene-npc、/api/custom-world/scene-image、/api/custom-world/cover-image、/api/custom-world/cover-upload不再挂载,RPG 创作资产入口统一使用/api/runtime/custom-world/*。 - 当前旧
/api/runtime/puzzle/runs/local-next-level不再挂载,正式 Puzzle 运行态只通过/api/runtime/puzzle/runs/{run_id}/next-level进入后端真相源。