master #25

Merged
kdletters merged 28 commits from master into release 2026-05-18 23:19:12 +08:00
312 changed files with 43988 additions and 22766 deletions
@@ -1 +0,0 @@
C:/proj/Genarrative/.hermes/skills/behavior-driven-development
+1
View File
@@ -0,0 +1 @@
../../.hermes/skills/behavior-driven-development/
+36
View File
@@ -0,0 +1,36 @@
.git
.codex-temp
.codex-logs
.codex-runlogs
.idea
.vite
node_modules
target
dist
coverage
logs
tmp
*.log
/*.png
/*.jpg
/*.jpeg
/*.webp
.env
.env.local
.env.secrets.local
.env.secrets.*
spacetime.local.json
deploy/container/api-server.env
server-rs/target
server-rs/target-*
server-rs/.data
server-rs/.spacetimedb
public/generated-*
scripts/loadtest/data/*.local.json
scripts/loadtest/data/k6-*.log
scripts/loadtest/data/k6-*summary*.md
scripts/loadtest/data/latest-*-prefix.txt
+9 -14
View File
@@ -16,21 +16,10 @@ JWT_EXPIRES_IN="7d"
SMS_AUTH_ENABLED="true"
SMS_AUTH_PROVIDER="aliyun"
ALIYUN_SMS_ACCESS_KEY_ID="LTAI5tM6VjoixveLUNQ7x6z9"
ALIYUN_SMS_ACCESS_KEY_SECRET="w8Z8JlQKI1juGPSeirWwlvJfHp9frD"
ALIYUN_SMS_ENDPOINT="dypnsapi.aliyuncs.com"
ALIYUN_SMS_SIGN_NAME="速通互联验证码"
ALIYUN_SMS_TEMPLATE_CODE="100001"
ALIYUN_SMS_ENDPOINT="dysmsapi.aliyuncs.com"
ALIYUN_SMS_SIGN_NAME="北京亓盒网络科技"
ALIYUN_SMS_TEMPLATE_CODE="SMS_506245486"
ALIYUN_SMS_TEMPLATE_PARAM_KEY="code"
ALIYUN_SMS_COUNTRY_CODE="86"
ALIYUN_SMS_SCHEME_NAME=""
ALIYUN_SMS_CODE_LENGTH="6"
ALIYUN_SMS_CODE_TYPE="1"
ALIYUN_SMS_VALID_TIME_SECONDS="300"
ALIYUN_SMS_INTERVAL_SECONDS="60"
ALIYUN_SMS_DUPLICATE_POLICY="1"
ALIYUN_SMS_CASE_AUTH_POLICY="1"
ALIYUN_SMS_RETURN_VERIFY_CODE="false"
VITE_AUTH_ALLOW_DEV_GUEST="false"
@@ -70,3 +59,9 @@ GENARRATIVE_SPACETIME_TOKEN=""
GENARRATIVE_ADMIN_USERNAME=admin
GENARRATIVE_ADMIN_PASSWORD=123456
ADMIN_API_TARGET=http://127.0.0.1:3100
# OTLP
GENARRATIVE_OTEL_ENABLED=true
OTEL_SERVICE_NAME=genarrative-api
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=local,service.namespace=genarrative
+1
View File
@@ -33,6 +33,7 @@ temp*build*/
.worktrees/
.env.secrets.local
spacetime.local.json
deploy/container/api-server.env
# Local load-test data extracted from private migration files
scripts/loadtest/data/*.local.json
+69 -3
View File
@@ -16,6 +16,71 @@
---
## 2026-05-18 Rust 手写模块入口统一不用 mod.rs
- 背景:Rust 目录模块同时存在 `mod.rs` 与同名 `.rs` 两种入口形式,前次拆分已让 `spacetime-client/src/mapper.rs` 采用同名入口;继续新增 `mod.rs` 会让文件定位和评审口径不一致。
- 决策:手写 Rust 模块统一使用同名入口文件,例如 `puzzle.rs``match3d.rs``gameplay.rs`,子模块继续放在同名目录下;不要再为手写模块新增 `mod.rs`。SpacetimeDB CLI 生成的 bindings 也由生成脚本同步为 `module_bindings.rs``module_bindings/` 子目录,避免仓库里继续出现 `mod.rs`
- 边界:本决策只规范文件布局,不改变 module path、HTTP route、DTO、SpacetimeDB schema、生成绑定内容或运行时行为。
- 影响范围:`server-rs/crates/api-server/src/``server-rs/crates/spacetime-module/src/``server-rs/crates/spacetime-client/src/module_bindings.rs``scripts/generate-spacetime-bindings.mjs`
- 验证方式:执行 `Get-ChildItem server-rs -Recurse -Filter mod.rs` 应无结果;再执行对应 `cargo check` / 定向测试 / 编码检查。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-05-18 大文件拆分继续按聚合入口加领域子模块推进
- 背景:完成拼图 `api-server` 拆分后,`match3d.rs``spacetime-client/src/mapper.rs``PlatformEntryFlowShellImpl.tsx` 仍是后续迭代和评审的高噪音大文件。
- 决策:抓大鹅 Match3D 的 `api-server` 单文件改为同名入口 `src/match3d.rs``src/match3d/` 子模块目录,`handlers.rs``draft.rs``works.rs``item_assets.rs``runtime.rs``vector_engine_gemini.rs``mappers.rs``tags.rs``tests.rs` 分担原实现;`spacetime-client/src/mapper.rs` 改为聚合入口,具体 mapper 按领域落到 `src/mapper/*.rs`;平台入口继续以 `PlatformEntryFlowShellImpl.tsx` 为编排壳,独立 UI 片段优先拆到 `PlatformEntryFlowShellImpl/` 子目录,本次已抽出 `PuzzleOnboardingView.tsx`
- 边界:这些拆分只改变文件组织,不改变 HTTP route、DTO、error envelope、SpacetimeDB schema、生成绑定、procedure result、入口配置事实源、前端行为、VectorEngine / OSS 副作用或计费语义。后续要下沉领域规则时另行讨论并更新设计。
- 影响范围:`server-rs/crates/api-server/src/match3d/``server-rs/crates/spacetime-client/src/mapper/``src/components/platform-entry/PlatformEntryFlowShellImpl/`、后端架构文档和玩法链路文档。
- 验证方式:执行 `cargo check -p api-server --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml --no-run``cargo check -p spacetime-client --manifest-path server-rs\Cargo.toml`、前端 typecheck 或定向 tsc、`git diff --check``npm run check:encoding`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-18 api-server 拼图能力按 HTTP/BFF 子模块拆分
- 背景:`server-rs/crates/api-server/src/puzzle.rs` 已膨胀为数千行大文件,混合 Axum handler、草稿编译、图片生成、VectorEngine / OSS 持久化、DTO mapper、标签生成和测试;继续在单文件内迭代会降低定位和评审效率。
- 决策:原超大 `puzzle.rs` 改为同名入口 `server-rs/crates/api-server/src/puzzle.rs``server-rs/crates/api-server/src/puzzle/` 子模块目录。`puzzle.rs` 只保留聚合入口和 handler re-export`handlers.rs` 放 HTTP handler`draft.rs` 放表单草稿 / 编译 / snapshot helper`generation.rs` 放图片与 UI 背景生成编排;`vector_engine.rs` 放 VectorEngine、下载、OSS、asset object / binding 和错误归一;`mappers.rs` / `tags.rs` 保留映射和标签 / 错误 helper;`tests.rs` 承接原 puzzle 单测。
- 边界:本次只改变 `api-server` 内部文件组织,不改变 `/api/runtime/puzzle/*` 路由、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义或计费语义。领域规则后续仍应逐步沉到 `module-puzzle`SpacetimeDB 表、reducer、procedure 和 row shape 仍留在 `spacetime-module`
- 影响范围:`server-rs/crates/api-server/src/puzzle/``server-rs/crates/api-server/src/modules/puzzle.rs` 的 handler 引用、后端架构文档。
- 验证方式:执行 `cargo check -p api-server --manifest-path server-rs\Cargo.toml`;后续若改动 puzzle API 行为,再按对应路由补充定向测试和 `npm run dev:api-server` `/healthz` smoke。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-05-18 Windows Jenkins PowerShell 统一改为显式 powershell.exe 启动
- 背景:`Genarrative-Stdb-Module-Build` 在 Windows Jenkins 本地环境里调用裸 `powershell` step 时触发 `CreateProcess error=5, 拒绝访问`,而 `powershell.exe` 本体与 workspace ACL 都正常。
- 决策:Windows Jenkins 上凡是需要执行 PowerShell 逻辑的流水线,优先通过 `bat` 显式调用 `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File ...`,不要再依赖 Jenkins `powershell` step 的隐式启动器。
- 追加决策:`Genarrative-Stdb-Module-Build` 的 Checkout 逻辑应复用 Jenkins GitSCM 已完成的工作区状态。`COMMIT_HASH` 为空或已与当前 `HEAD` 一致时,不再额外执行 `git clean` / `git checkout`;只有需要切到指定且不同的 commit 时才补 fetch、校验和切换,避免在 Windows workspace 里二次清理触发权限拒绝。
- 影响范围:`jenkins/Jenkinsfile.production-stdb-module-build` 及后续所有同类 Windows 构建流水线。
- 验证方式:Jenkins 日志中应能看到 `[jenkins-powershell] user:``[jenkins-powershell] exe:`Checkout 阶段会打印当前 `HEAD` 与请求 commit,并在 `COMMIT_HASH` 为空或一致时直接继续;不再停在 `PipelineNodeTreeScanner... Cannot run program "powershell"` 或重复 `git clean` 的退出码 5。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md``.hermes/shared-memory/pitfalls.md`
## 2026-05-17 容器化方案只作为隔离压测与预发模拟路径
- 背景:Windows 本机直连极高 VU 压测会放大本地连接与发送缓冲行为,和线上 Linux + Nginx + systemd 拓扑不一致;需要一个更接近生产网络层的模拟方案,但不能扰动当前生产发布链路。
- 决策:新增 `deploy/container/` 容器化方案,使用 Docker Compose 组合 Linux release `api-server`、容器 Nginx、`otelcol-contrib` debug exporter 和可选 k6。该方案只用于本机或预发压测模拟,不替换当前生产 `systemd + Nginx + Jenkins` 路径。
- 隔离边界:容器方案使用独立 `deploy/container/api-server.env`、独立 Nginx 配置、独立 compose 命令和默认 `18080` 端口;真实 token 不进入镜像、不提交 Git;生产 systemd 单元、Jenkins 发布脚本和 `deploy/nginx/` 模板仍是正式线上来源。
- 影响范围:`deploy/container/``scripts/container-compose.mjs``package.json` 容器命令、开发运维文档和容器 build context 排除规则。
- 验证方式:执行 `npm run container:config` 展开 compose 配置;需要真实运行时再执行 `npm run container:build``npm run container:up``npm run container:k6`,并结合容器 Nginx log 与 OTLP debug exporter 判断瓶颈。
- 关联文档:`deploy/container/README.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-05-16 公开作品列表短期由 BFF 订阅读模型缓存
- 背景:作品列表压测和实时性讨论中,曾考虑让浏览器前端直接订阅公开作品列表,减少 HTTP 拉取和 BFF 压力。
- 决策:本轮不直接把作品列表整体交给前端订阅。短期继续由 `api-server` / BFF 通过 `spacetime-client` 长期订阅 SpacetimeDB 公开 read model 并读取本地 cache,维持首屏、排序、字段归一、权限降级和 HTTP fallback。中期可以新增或统一稳定的专用公开作品列表 read model,例如 `public_work_gallery_entry`,作为前端可选直连订阅对象。
- 边界:未来前端直订阅只允许面向稳定、低基数、公开的专用 read model。前端不得直接订阅 `puzzle_work_profile``custom_world_profile` 等领域源表,也不得在前端自行 join、聚合或执行公开权限逻辑;这些逻辑必须先沉到后端投影 / read model。
- 后续准入:若要落地前端直订阅,必须先完成并验收权限边界、字段契约、排序 / 分页、埋点和 BFF 回退策略;缺任一项时继续走 `api-server` / BFF 订阅缓存方案。
- 影响范围:发现页、推荐流、各玩法公开广场、`api-server` 公开列表缓存、SpacetimeDB public view / public 读模型设计。
- 验证方式:新增公开作品列表订阅能力时,检查前端只消费专用 public read model 或 BFF HTTP DTO;检查源表 row shape、权限判断和跨玩法聚合没有下沉到前端页面。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-05-16 api-server OpenTelemetry 统一补齐 traces metrics logs
- 背景:压测与运行观测需要把 HTTP、SpacetimeDB 调用和应用日志串起来,同时保留本地 `journalctl` / 文件日志做故障排障。
- 决策:`api-server` 通过 OTLP HTTP base endpoint 发送 traces、metrics 和 logsCollector 统一用 `otelcol-contrib``npm run otel:debug` 负责 debug 采集,`npm run otel:rider` 负责转发到 Rider;Rider 只是接收与可视化端,不直接替代 Collector。
- 日志口径:Rider Logs 面板只展示 log event 自身字段,请求完成日志需要直接携带 `request_id`、HTTP method、规范化 route、scheme、path、status、status_class、latency 和 slow_request;更完整的 request attributes 仍以 trace/span 为准。
- 影响范围:`server-rs/crates/shared-logging``server-rs/crates/api-server``scripts/run-otelcol.mjs`、压测与运维文档。
- 验证方式:`cargo test -p shared-logging --manifest-path server-rs/Cargo.toml generic_otlp_http_endpoint_expands_to_signal_paths``cargo test -p api-server --manifest-path server-rs/Cargo.toml observability_route_keeps_metrics_labels_low_cardinality``cargo test -p api-server --manifest-path server-rs/Cargo.toml resolve_request_scheme_uses_forwarded_proto_first_value``cargo check -p api-server --manifest-path server-rs/Cargo.toml`
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md``scripts/loadtest/README.md`
## 2026-05-14 创作页图像输入统一封装为图像组件
- 背景:拼图创作页已经具备“画面描述生图 / 多参考图生图 / 上传主图后 AI 重绘 / 上传主图后不重绘”四条路径,抓大鹅封面和后续创作页也会复用同一套交互;继续在页面内复制会导致参考图、预览、删除确认和重绘开关漂移。
@@ -133,7 +198,8 @@
## 2026-05-12 拼图 UI 背景图复用 levels_json 持久化
- 背景:拼图草稿结果页需要像抓大鹅一样支持 UI 背景生成,但首版只需要作品级/首关背景,不应为图片生成结果新增 SpacetimeDB 表结构。
- 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt``uiBackgroundImageSrc``uiBackgroundImageObjectKey``compile_puzzle_draft` 草稿编译阶段在首图完成后自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc``uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab,可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2-all` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。
- 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt``uiBackgroundImageSrc``uiBackgroundImageObjectKey``compile_puzzle_draft` 草稿编译阶段自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc``uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab,可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2-all` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。
- 2026-05-18 追加:为缩短首版草稿等待,`compile_puzzle_draft` 在首关命名和 `uiBackgroundPrompt` 稳定后并行启动首关关卡图生成与 UI 背景生成;上传主图且关闭 AI 重绘时,并行执行上传图持久化与 UI 背景生成。生成页预计完成时间按 5 分钟展示。
- 影响范围:拼图结果页、拼图运行态背景渲染、拼图 agent action、`module-puzzle` / `spacetime-module` / `spacetime-client` 的拼图关卡 JSON 映射、拼图流程技术文档。
- 验证方式:执行 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx``cargo test -p api-server puzzle_ui_background --manifest-path server-rs/Cargo.toml``cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run typecheck``npm run check:encoding`
- 关联文档:`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
@@ -172,7 +238,7 @@
## 2026-05-12 宝贝识物创作同时生成玩法视觉主题包
- 背景:`宝贝识物` 创作原本只根据两个关键词生成物品透明图,运行态背景、UI、礼物盒和篮子仍使用固定 CSS 绘本风,无法根据“小猪佩琪 / 奥特曼”或“苹果 / 橘子”等创作者提示词做主题化包装。
- 决策:`POST /api/creation/edutainment/baby-object-match/assets` 同一次 image-2 / VectorEngine 调用链返回两个物品图和 `visualPackage`视觉包包含 `background``ui-frame``gift-box``basket``smoke-puff` 五类资源;总风格保持寓教于乐明亮卡通绘本插画风,主题按两个物品关键词匹配,水果偏果园自然,动漫角色 / 玩具偏动漫玩具。物品图和礼物盒 / 篮子 / UI / 烟雾特效资源走透明 PNG 后处理,背景为清爽不遮挡玩法区的环境图;运行态中礼物盒按约 2 倍视觉尺寸展示、篮子按约 1.5 倍展示,礼物盒打开时使用 `smoke-puff` 弹出中央物品并移除礼盒。前端草稿保存该包,运行态消费该包;旧草稿以 `visualPackage = null` 继续使用 CSS 兜底。
- 决策:`POST /api/creation/edutainment/baby-object-match/assets` 同一次 image-2 / VectorEngine 调用链返回两个物品图和 `visualPackage`为降低调用成本,新链路只生成一张 `1024x1024``2x2` 素材 sheet 和一张 `1536x1024` 场景背景图;`2x2` sheet 固定左上物品 A、右上物品 B、左下篮子、右下礼物盒,服务端按格切图并把物品、篮子和礼物盒转透明 PNG。视觉包必需资源为 `background``gift-box``basket`;总风格保持寓教于乐明亮卡通绘本插画风,主题按两个物品关键词匹配。左右手位置指示器是运行态默认静态素材,使用项目内置第一人称半抓握手,不再随每次创作生成。运行态中礼物盒按约 2 倍视觉尺寸展示、篮子按约 1.5 倍展示,中央物品 UI 与篮子物品图标使用固定正方形槽位并等比 `contain` 缩放,礼物盒打开烟雾特效由 CSS 兜底;历史草稿中的 `ui-frame` / `smoke-puff` / `left-hand` / `right-hand` 仅兼容读取或忽略。前端草稿保存该包,运行态消费该包;旧草稿以 `visualPackage = null` 继续使用 CSS 兜底。
- 影响范围:`packages/shared/src/contracts/edutainmentBabyObject.ts``server-rs/crates/api-server/src/edutainment_baby_object.rs``src/services/edutainment-baby-object/babyObjectMatchClient.ts``src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``src/index.css`、宝贝识物 PRD 与技术方案。
- 验证方式:执行宝贝识物 service / runtime 定向测试、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml`、相关 ESLint 与编码检查;真实生图需配置 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY`
- 关联文档:`docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
@@ -197,7 +263,7 @@
## 2026-05-10 儿童动作 Demo 视觉资产统一为绘本草地舞台
- 背景:儿童动作 Demo 需要从暗色科技风切换到更适合儿童互动的卡通绘本草地风格,并且要让背景、地面、UI、地面指示环和用户轮廓使用同一套 image-2 资源口径。
- 决策:热身舞台及后续儿童动作 Demo 场景、物品、UI 资源统一采用明亮卡通绘本草地视觉语言。真实资源默认输出到 `public/child-motion-demo/`。背景沿用 `picture-book-grass-stage.png`;地面、指示环、角色轮廓和 UI 已拆分为 v2 用途专属资源:`picture-book-foreground-grass-v2.png``picture-book-ground-ring-v2.png``picture-book-character-outline-v2.png``picture-book-hud-strip-v2.png``picture-book-calibration-strip-v2.png``picture-book-start-panel-v2.png``picture-book-ui-button-v2.png`。生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 调用 VectorEngine `gpt-image-2-all`;透明资源使用品红底生成后本地去背,中间源图仅保存在 `tmp/child-motion-demo-assets/`。在缺少 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 时,只允许 dry-run 和 CSS 兜底,不伪造 live 生图结果。
- 决策:热身舞台及后续儿童动作 Demo 场景、物品、UI 资源统一采用明亮卡通绘本草地视觉语言。真实资源默认输出到 `public/child-motion-demo/`。背景沿用 `picture-book-grass-stage.png`;地面、指示环、角色指示器和 UI 已拆分为用途专属资源:`picture-book-foreground-grass-v2.png``picture-book-ground-ring-v3.png``picture-book-character-outline-v4.png``picture-book-hud-strip-v2.png``picture-book-calibration-strip-v2.png``picture-book-start-panel-v2.png``picture-book-ui-button-v2.png`其中角色指示器 v4 基于 v2 本地后处理为更细的白色描边样式,内部透明,耳朵、手指、脚趾等细节已弱化,页面显示尺寸相对上一版放大 50%。生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 调用 VectorEngine `gpt-image-2-all`;透明资源使用品红底生成后本地去背,中间源图仅保存在 `tmp/child-motion-demo-assets/`。在缺少 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 时,只允许 dry-run 和 CSS 兜底,不伪造 live 生图结果。
- 影响范围:`src/index.css``src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 的舞台视觉层、儿童动作 Demo 技术文档、后续 image-2 资产生成流程。
- 验证方式:检查 `/child-motion-demo` 舞台是否在未生成资产时仍有可用草地绘本兜底;补齐 VectorEngine 私密配置后运行 `npm run assets:child-motion-demo -- --live``--live --only <asset-id>` 应能写出对应 PNG,并确认页面静态资源返回 `image/png`。若只调整透明去背、裁切或品红边缘,可运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用源图后处理。页面接入时必须按资源原始比例等比使用,不得把方形软纸面板拉伸成 HUD、状态条或底部草坪。
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md``docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`
@@ -195,6 +195,13 @@ npm run check:server-rs-ddd
- `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`
## 生产压测与观测默认口径
- 作品列表 50 HTTP req/s 压测使用 `scripts/loadtest/README.md` 中的 K6 命令;当前脚本一次 iteration 请求两个公开列表接口,因此目标 50 HTTP req/s 对应 `PEAK_RPS=25`
- 生产 `api-server` 默认 backlog、worker threads、HTTP 并发背压、systemd 限制、Nginx upstream timing log 和 OTLP 开关以 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` 为准。
- OpenTelemetry 现阶段可选发送 traces / metrics / logs,但不会取代本地 `journalctl -u genarrative-api.service``logs/api-server/``/var/log/nginx/genarrative.*.log`
- 指标 label 不写 raw URI、userId、profileId 或 request_idrequest_id 只用于 trace/log 串联。
## 前端相关默认验证
前端修改后,应根据修改范围选择:
+125 -21
View File
@@ -44,7 +44,7 @@
- 原因:封面生成属于定向图片槽位更新;若后端复用草稿编译写回,可能按 session config 重算作品行。即使后端已修正,前端若直接把封面接口返回的整份 `item` 当成最新 profile,也可能用旧回包里的空 `generatedItemAssets` 覆盖当前页面素材。
- 处理:`POST /api/creation/match3d/works/{profileId}/cover-image` 只保存 `coverImageSrc` / `coverAssetId` 等封面字段,保留当前 `generated_item_assets_json`、难度、消除次数、题材和描述;前端收到回包后只合并 `coverImageSrc`,继续保留当前可见 `generatedItemAssets``clearCount``difficulty`
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖旧回包不覆盖物品素材和配置;`cargo test -p api-server match3d_cover --manifest-path server-rs\Cargo.toml` 覆盖封面提示词与参考图链路。
- 关联:`src/components/match3d-result/Match3DResultView.tsx``server-rs/crates/api-server/src/match3d.rs``server-rs/crates/spacetime-module/src/match3d/mod.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``server-rs/crates/api-server/src/match3d.rs``server-rs/crates/spacetime-module/src/match3d.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## OSS V4 签名时间和 bucket/object_key 兼容
@@ -83,6 +83,54 @@
- 验证:运行仓库已有编码检查;人工抽查修改文件中的中文内容。
- 关联:`AGENTS.md``npm run check:encoding`
## SpacetimeDB 运行态查询不要绕过已有索引或用 procedure JSON 回传
- 现象:运行态接口看起来只查当前用户、作品或任务,却在 `spacetime-module` 中使用 `ctx.db.<table>().iter().filter(...)` 整表遍历;或者 procedure result 返回 `items_json/run_json/work_json` 等 JSON 字符串,`spacetime-client` mapper 再反序列化成旧兼容结构。
- 原因:新增索引或 typed snapshot 后,没有同步清理旧 mapper / 测试兼容层,也没有用静态检查拦截回退写法。
- 处理:表上已有主键、unique 或 `#[index]` 覆盖查询前缀时,先用对应 accessor `.find(...)` / `.filter(...)`,只对索引无法覆盖的条件做内存残余过滤;procedure result 返回 typed snapshot / typed value,不再跨层传 `*_json: Option<String>` 作为 payload。
- 验证:执行 `npm run check:spacetime-runtime-access``npm run check:server-rs-ddd`,涉及绑定变化时先执行 `npm run spacetime:generate``npm run check:spacetime-schema`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``scripts/check-spacetime-runtime-access.mjs``server-rs/crates/spacetime-module/src/*``server-rs/crates/spacetime-client/src/mapper.rs`
## 拼图广场列表不要每次 HTTP 请求调用 SpacetimeDB procedure
- 现象:`/api/runtime/puzzle/gallery` 每个请求都走 `spacetime-client.list_puzzle_gallery()` 调用 SpacetimeDB procedure,导致 SpacetimeDB WASM 侧重复组装全量列表,客户端再映射一遍;历史实现还出现过 procedure JSON 字符串往返。
- 原因:`api-server` 的服务器端 `spacetime-client` 没有订阅可公开读取的 gallery 投影,虽然 SDK 支持 client cache,但请求路径仍把列表读取当作 procedure 调用。
- 处理:`spacetime-module` 中用 public view `puzzle_gallery_card_view` 暴露已发布拼图作品的列表卡片字段,不携带 `levels` / `anchor_pack` 等详情级载荷;`spacetime-client` 建连接后订阅 `SELECT * FROM puzzle_gallery_card_view``SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle'` 并等待 `on_applied`。HTTP gallery 通过 `PuzzleGalleryCache` 缓存最终 `PuzzleGalleryResponse` DTO`items` 返回前 10 个完整卡片,`previewRefs` 返回后 10 个作品号引用,cache miss / TTL 过期时单飞重建,后台 cleanup task 周期清理旧响应。旧 `list_puzzle_gallery` procedure 只作兼容,不再作为 HTTP gallery 主路径。
- 验证:搜索 `server-rs/crates/spacetime-client/src/puzzle.rs` 不应再出现 gallery 主路径调用 `list_puzzle_gallery_then`;搜索 `server-rs/crates/spacetime-client/src/lib.rs` 应订阅 `puzzle_gallery_card_view`;执行 `npm run spacetime:generate``cargo check --manifest-path server-rs/Cargo.toml -p spacetime-client``cargo check --manifest-path server-rs/Cargo.toml -p api-server` 和 schema/runtime access 检查。
- 关联:`server-rs/crates/spacetime-module/src/puzzle.rs``server-rs/crates/spacetime-client/src/lib.rs``server-rs/crates/spacetime-client/src/puzzle.rs``server-rs/crates/api-server/src/puzzle_gallery_cache.rs``/api/runtime/puzzle/gallery`
## Windows 本地直连高 VU 压测不要误判成业务内存泄漏
- 现象:本地 Windows release `api-server` 直连 K6 压测时,250 RPS、`PREALLOCATED_VUS=300` 能把进程 private memory 瞬时推到约 7GB;同样配置打 `/healthz` 小响应也能复现,压测结束后回落到 100MB 级。
- 原因:高水位主要来自本机直连的 K6 VU / 长连接 / Hyper 发送链路和 Windows 连接缓冲,不是 SpacetimeDB procedure、拼图 JSON 缓存或 OTEL exporter。降低到接近真实并发的 VU 后,同样 250 RPS 拼图广场 p95 约 9ms,峰值约 600MB。
- 处理:本地容量判断时让 `PREALLOCATED_VUS` / `MAX_VUS` 接近真实并发,不要把过高 VU 预分配当作默认吞吐测试;同时观察 `process.memory.*``process.windows.handle.count``genarrative.http.server.response_bodies.in_flight``genarrative.http.server.request_permits.available``genarrative.puzzle_gallery.cache.*``genarrative.spacetime.read.*`。如果内存高但 body in-flight、背压 permit、cache rebuild 和 SpacetimeDB read 都不显示积压,优先按连接 / 发送链路高水位处理。
- 验证:对照打 `/api/runtime/puzzle/gallery``/healthz`;对比 `PREALLOCATED_VUS=300 MAX_VUS=800``PREALLOCATED_VUS=20 MAX_VUS=40`;压测结束后继续采样 10 秒确认 private memory 回落。
- 关联:`scripts/loadtest/README.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md``server-rs/crates/api-server/src/process_metrics.rs``server-rs/crates/api-server/src/telemetry.rs`
## 多玩法公开广场列表优先订阅 public view / read model
- 现象:抓大鹅、方洞挑战、视觉小说、大鱼吃小鱼等公开列表如果沿用 `list_*_works` procedure,即使只读已发布作品,也会在每个 HTTP 请求里回到 SpacetimeDB WASM 侧扫描、反序列化配置并组装列表,50RPS 以上容易变成热点。
- 原因:个人作品列表和公开广场列表复用了同一套 procedure 输入,导致公开列表为了通过 owner 校验传固定占位 owner,并把可长期同步的公开读模型当成请求期查询。
- 处理:每个公开广场新增或复用专用 public view / public read model`match_3_d_gallery_view``square_hole_gallery_view``visual_novel_gallery_view``big_fish_gallery_view``spacetime-client` 建连接后订阅这些 view 和对应 `public_work_play_daily_stat` source_type 桶,HTTP gallery 只读本地 cache。个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍走原有 procedure / reducer。
- 验证:搜索 `server-rs/crates/spacetime-client/src/{match3d,square_hole,visual_novel,big_fish}.rs`,公开 gallery 主路径应读取 `connection.db().*_gallery_view()`,不应调用 `list_*_works_with_input`;执行 `npm run spacetime:generate``cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml``cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run check:spacetime-schema`
- 关联:`server-rs/crates/spacetime-module/src/match3d.rs``server-rs/crates/spacetime-module/src/square_hole.rs``server-rs/crates/spacetime-module/src/visual_novel.rs``server-rs/crates/spacetime-module/src/big_fish/session.rs``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 自定义世界广场和创作入口配置不要每次 HTTP 请求调用只读 procedure
- 现象:`/api/runtime/custom-world-gallery` 每次请求调用 `list_custom_world_gallery_entries` procedure;入口熔断中间件每个玩法请求调用 `get_creation_entry_config` procedure50RPS 以上会把 SpacetimeDB procedure 调用变成热点。
- 原因:`custom_world_gallery_entry``creation_entry_config``creation_entry_type_config` 已经是可订阅读模型或配置表,但 HTTP 路径仍按“请求到来再查 procedure”处理。
- 处理:`spacetime-client` 长连接订阅 `custom_world_gallery_entry``public_work_play_daily_stat``custom-world` 桶、`creation_entry_config``creation_entry_type_config`custom-world gallery 从本地 cache 排序并聚合 7 日播放数;入口配置优先读订阅 cache,cache 缺失时用最近一次成功内存快照,再兜底调用 `get_creation_entry_config` 完成旧库兼容。旧 `list_custom_world_gallery_entries` procedure 只允许作为旧库缺少 gallery 行时的一次性同步兜底。
- 验证:搜索 `server-rs/crates/spacetime-client/src/custom_world.rs`gallery 主路径应是 `read_after_connect` 读取 `custom_world_gallery_entry()`;搜索 `server-rs/crates/spacetime-client/src/runtime.rs``get_creation_entry_config` 应优先读取 `creation_entry_config()``creation_entry_type_config()`。执行 `cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml``cargo check -p api-server --manifest-path server-rs/Cargo.toml`
- 关联:`server-rs/crates/spacetime-client/src/lib.rs``server-rs/crates/spacetime-client/src/custom_world.rs``server-rs/crates/spacetime-client/src/runtime.rs``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 陶泥儿 logo 生图慢请求先缩短 prompt 并单张串行
- 现象:使用 VectorEngine `gpt-image-2-all` 生成陶泥儿 logo 概念图时,部分 prompt 会超过 10 分钟仍无响应,或返回 `429` / `当前分组上游负载已饱和`;同一批次里后续图片会被前面的慢请求拖住。
- 原因:复杂抽象 logo prompt 同时包含品牌解释、禁用元素、中文结构和多重隐喻时,上游排队与生成时长不稳定;并发或批量运行会放大单条慢请求的影响。
- 处理:先 `--dry-run` 看请求体;真实生成时优先短 prompt、单一造型、单张串行或小批量。失败后不要反复重试同一长 prompt,先压缩到“一个主体 + 一个负形 + 颜色 + 禁用文字/播放键/聊天气泡”再跑。联系表中的中文标签不要通过 PowerShell 管道内联 Python 写入,容易因编码链路显示为问号,可改用英文标签或脚本文件方式。
- 验证:生成文件落在 `public/branding/taonier-logo-*/`,用 Pillow 检查图片尺寸和非空;执行 `node --check scripts/generate-taonier-logo-concepts.mjs``npm run check:encoding``git diff --check`
- 关联:`scripts/generate-taonier-logo-concepts.mjs``docs/design/TAONIER_BRAND_LOGO_CONCEPTS_2026-05-13.md`
## 忘记密码后仍提示手机号或密码错误先查认证快照同步
- 现象:用户通过“忘记密码”重设密码后,接口返回成功或页面进入登录态,但再次使用新密码登录仍提示“手机号或密码错误”;重启后还可能出现 `Bearer JWT 版本已失效`,日志里的 token version 与本地快照不一致。
@@ -144,10 +192,10 @@
## 宝贝识物选篮误触发先查多套判定和残余轨迹
- 现象:`宝贝识物` 运行态打开礼物盒或反馈结束后,当前物品被连续送入左侧或右侧篮子,或硬件动作名偶发命中导致未做明确横移动作也触发选篮。
- 原因:选篮如果同时消费 `wave_left_hand` / `wave_right_hand` / `wave` 动作名和手部轨迹,或在 `correct` / `wrong` 反馈阶段继续累计手部路径,会把抓握、反馈期间残留移动或未知侧别手部误算成下一次选篮。
- 处理:宝贝识物选篮只使用明确 `leftHand` / `rightHand` 的连续横向轨迹阈值;侧别为 `unknown` 的手部轨迹不参与选篮;反馈阶段清空轨迹,不在非 `active` 阶段累计路径。进入关卡和每次正确反馈结束后自动弹出物品,不再用 `open_palm -> grab` 抓握序列激活礼物盒。
- 补充:当前本地 mocap 的 handedness 是摄像头视角,宝贝识物选篮前需要换算为用户身体视角`rightHand` 轨迹代表玩家左手并进入左篮`leftHand` 轨迹代表玩家右手并进入右篮。键鼠调试不走该换算,仍保持鼠标左键=左、右键=右
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/services/useMocapInput.test.ts`,确认动作名负向测试、未知侧别负向测试和左右手横向轨迹测试通过。
- 原因:选篮如果同时消费 `wave_left_hand` / `wave_right_hand` / `wave` 动作名、连续横向轨迹和左右手固定篮子规则,或在 `correct` / `wrong` 反馈阶段继续累计手部状态,会把反馈期间残留移动或未知侧别手部误算成下一次选篮。
- 处理:宝贝识物当前选篮只允许“手先触碰中央物品 UI,物品绑定到该手,随后拖入左侧或右侧篮子区域”这一套路径;侧别为 `unknown` 的手部不参与抓取或选篮;反馈阶段清空持有状态,不在非 `active` 阶段累计输入。进入关卡和每次正确反馈结束后自动弹出物品,不再用 `open_palm -> grab` 抓握序列激活礼物盒。
- 补充:当前本地 mocap 的 handedness 是摄像头视角,宝贝识物仍需换算为用户身体视角以展示左右手:`rightHand` 坐标代表玩家左手,`leftHand` 坐标代表玩家右手。换算不再决定只能选择哪侧篮子;任意一只手都可以拖物品到任意篮子。键鼠调试保持鼠标左键=左手位置、右键=右手位置,也必须先触碰中央物品再拖入篮子
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/services/useMocapInput.test.ts`,确认动作名负向测试、未知侧别负向测试、触碰前不能选篮和任意手拖入任意篮子用例通过。
- 关联:`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
## 宝贝爱画左右手反了先查 mocap 摄像头视角换算
@@ -161,11 +209,27 @@
## 宝贝识物创作卡在准备结果页先查长耗时 image-2 请求
- 现象:`/creation/baby-object-match` 创作生成停在“准备结果页”,约 3 分钟后显示“生成失败 / 请求超时”;后端日志可能出现同一路由 `status=502 latency_ms=231291`,或前端已失败但后端稍后返回 200。
- 原因:宝贝识物一次创作会生成 2 张物品图和 `background``ui-frame``gift-box``basket``smoke-puff` 5 张视觉包装图。旧前端只等待 180 秒并对长耗时 POST 自动重试,容易在 VectorEngine 仍在生成时先 abort,再重复发起第二次生成;上游某张图超过后端 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 或返回 5xx 时会表现为 502。
- 处理:`babyObjectMatchClient``/api/creation/edutainment/baby-object-match/assets` 使用 10 分钟超时并取消自动重试;后端并发启动物品图和视觉主题包生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,按资源类别输出开始、完成和耗时日志。
- 验证:运行 `npm run test -- src/services/edutainment-baby-object/babyObjectMatchClient.test.ts src/services/miniGameDraftGenerationProgress.test.ts``cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml` 和编码检查;真实联调时查看 `宝贝识物 image-2 资源生成完成` 耗时是否小于前端超时,若仍 502 再看 `VectorEngine 图片生成上游错误``upstreamStatus/raw_excerpt`
- 原因:宝贝识物创作属于长耗时 image-2 链路。旧前端只等待 180 秒并对长耗时 POST 自动重试,容易在 VectorEngine 仍在生成时先 abort,再重复发起第二次生成;上游某张图超过后端 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 或返回 5xx 时会表现为 502。2026-05-14 后,新链路已从“2 张物品图 + 5 张视觉包装图”收敛为“1 张 `2x2` 素材 sheet + 1 张场景背景图”,左右手位置指示器改为运行态默认静态素材,不再每次创作生成,但仍需要按长耗时链路排查。
- 处理:`babyObjectMatchClient``/api/creation/edutainment/baby-object-match/assets` 使用 10 分钟超时并取消自动重试;后端并发启动 `2x2` 素材 sheet 和场景背景生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,按资源类别输出开始、完成和耗时日志。`2x2` sheet 固定包含物品 A、物品 B、篮子和礼物盒,服务端按格切图并转透明 PNG;`ui-frame` / `smoke-puff` / `left-hand` / `right-hand` 不再作为新生成必需资源。
- 验证:运行 `npm run test -- src/services/edutainment-baby-object/babyObjectMatchClient.test.ts src/services/miniGameDraftGenerationProgress.test.ts``cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml` 和编码检查;真实联调时查看 `宝贝识物 image-2 2x2 素材 sheet 生成完成``宝贝识物 image-2 场景资源生成完成` 和整体 `宝贝识物 image-2 资源生成完成` 耗时是否小于前端超时,若仍 502 再看 `VectorEngine 图片生成上游错误``upstreamStatus/raw_excerpt`
- 关联:`src/services/edutainment-baby-object/babyObjectMatchClient.ts``src/services/miniGameDraftGenerationProgress.ts``server-rs/crates/api-server/src/edutainment_baby_object.rs``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
## 宝贝识物篮子手柄白底先查 sheet 切图后处理
- 现象:`宝贝识物` 新生成的主题篮子在左右手柄、篮口镂空或边缘处仍出现白底块或白色毛边,尤其是 2x2 sheet 背景被抠透明后,封闭镂空区域可能没有被通用边缘连通抠图清理掉。
- 原因:宝贝识物为了降低 image-2 成本,把物品 A、物品 B、篮子和礼物盒放在同一张 `2x2` sheet。通用背景透明处理主要从单格边缘连通背景开始,封闭在篮子手柄内部的近白区域不一定与边缘连通,因此会残留;如果把强力近白清理应用到物品格,又可能误伤白色物品主体。
- 处理:后端 `slice_baby_object_match_sheet` 只在 `BabyObjectMatchSheetSlot::Basket` 编码前执行近白、低饱和 matte 清理;物品格和礼物盒格继续只走通用背景透明处理。sheet prompt 同步要求篮子手柄和篮口镂空处不要留下白底描边或毛边。运行态左右篮子的物品图标和名称 UI 以篮子中心线对齐,避免素材放大后看起来偏移。
- 验证:运行 `cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml``npm run test -- src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx`;真实联调需要重新生成宝贝识物资源,旧草稿中已保存的 base64 篮子图不会自动被新后处理改写。
- 关联:`server-rs/crates/api-server/src/edutainment_baby_object.rs``src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``src/index.css``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
## 宝贝识物物品框被长条素材拉伸先查固定槽位
- 现象:用户用手机、筷子等长条关键词生成素材后,中央物品 UI 或篮子上方物品图标看起来被拉成长框,圆形 UI 失去固定比例。
- 原因:运行态如果让图片固有宽高或外层自适应内容,就会把长条透明 PNG 的主体比例传导到 UI 容器。
- 处理:中央物品 UI 和篮子物品图标都必须使用固定正方形槽位,外层尺寸由 CSS 变量控制;生成素材图片只在槽位内 `object-fit: contain` 等比缩放,不改变外层圆形 UI 框尺寸。
- 验证:用长条物品草稿进入宝贝识物运行态,中央物品框和篮子图标框仍为正圆,长条主体在框内缩小显示。
- 关联:`src/index.css``src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
## 寓教于乐作品和宝贝识物模板同时消失先查入口种子
- 现象:发现页“寓教于乐”分类下已发布的宝贝识物作品突然消失,同时创作界面模板选项中也看不到或无法正常展示 `宝贝识物`
@@ -186,10 +250,18 @@
- 现象:`/child-motion-demo` 背景风格正确,但底部草坪被拉成厚色块、顶部 HUD 或右下状态条像方形面板被横向拉伸,或旧 `picture-book-ui-panel.png` 与新资源叠在一起。
- 原因:早期资源中 `picture-book-ui-panel.png` 是接近方形画布,`picture-book-grass-floor.png` 也含大量透明边界;若 CSS 用 `background-size: 100% 100%` 把同一资源强行铺成 HUD、状态条、开始面板或底部地板,就会出现变形和层叠观感。
- 处理:使用 v2 用途专属资源:`picture-book-foreground-grass-v2.png``picture-book-ground-ring-v2.png``picture-book-character-outline-v2.png``picture-book-hud-strip-v2.png``picture-book-calibration-strip-v2.png``picture-book-start-panel-v2.png``picture-book-ui-button-v2.png`;CSS 按资源比例等比缩放,底部草坪只覆盖下沿,HUD / 状态条 / 开始托盘分别引用各自资源。若只需修透明裁切品红边,运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>`,不重新请求 image-2。
- 处理:使用用途专属资源:`picture-book-foreground-grass-v2.png``picture-book-ground-ring-v3.png``picture-book-character-outline-v4.png``picture-book-hud-strip-v2.png``picture-book-calibration-strip-v2.png``picture-book-start-panel-v2.png``picture-book-ui-button-v2.png`;CSS 按资源比例等比缩放,底部草坪只覆盖下沿,HUD / 状态条 / 开始托盘分别引用各自资源。角色指示器使用 v4 更细白色描边资源,内部透明且显示尺寸相对上一版放大 50%;若只需修透明裁切品红边或纯描边后处理,运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>`,不重新请求 image-2。
- 验证:用横屏截图检查没有新旧资源叠加、没有方形面板拉成长条、角色和地面指示环不被前景草坪埋住;同时运行 `npm run check:encoding`
- 关联:`scripts/generate-child-motion-demo-assets.mjs``src/index.css``public/child-motion-demo/``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## 儿童动作 Demo 猫咪挥手拆件错位先查动画父级和肩部挂点
- 现象:`/child-motion-demo` 打个招呼阶段的猫咪图和风格正确,但挥手时左右手臂像漂浮在身体旁边,视频里能看到肢体没有稳定接在肩膀上。
- 原因:猫咪身体和手臂如果分别做上下浮动,或手臂使用透明方形画布的默认中心/底部旋转轴,就会在摆动极值时放大肩点偏差;镜像左臂还需要把资源内部连接点换算到镜像后的坐标。
- 处理:`.child-motion-gesture-guide__wave-cat` 父级统一承接 bob 动画,身体层保持静态贴底且层级低于手臂;左右手臂作为同一父级下的兄弟层,只做旋转动画并显示在身体前方。身体使用去掉左右小圆点的 `picture-book-wave-cat-body-guide-v7.png`;手臂 v7 资源当前按身体外缘摆放,圆猫爪掌面朝向玩家;左右侧距为 `12%`,左臂使用原图层与 `60% 78%` 旋转轴,右臂使用镜像图层与 `40% 78%` 旋转轴,动画周期为 `0.47s`,左右手臂不设置错峰延迟;不要把 `scaleX(...)` 和 rotate 放在同一个手臂 wrapper 上。
- 验证:用用户录屏关键帧或离线合成预览检查摆动两端的手臂根部仍贴住肩点;再运行儿童动作 Demo 定向组件测试、ESLint 和 `npm run check:encoding`
- 关联:`src/index.css``public/child-motion-demo/picture-book-wave-cat-body-guide-v7.png``public/child-motion-demo/picture-book-wave-cat-arm-guide-v7.png``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## GPT-image-2 不再读 APIMart 图片配置
- 现象:配置了 `APIMART_BASE_URL` / `APIMART_API_KEY` 后,RPG、拼图或方洞的 GPT-image-2 生图仍返回缺配置,或请求体里还出现 `official_fallback` / `image_urls`
@@ -358,6 +430,14 @@
- 验证:请求返回 JSON,相关页面不再出现 HTML parse 错误。
- 关联:`docs/technical/PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md`
## `npm run build` 因 Vite warning 被 build-gate 判失败
- 现象:主站或后台 Vite 已经输出 `built in ...`,但根命令最后仍失败并打印 `Build gate failed because warnings were emitted`
- 原因:`scripts/build-gate.mjs` 会收集 stdout / stderr 中的 warning 行并作为硬失败;常见触发是产物 chunk 超过 `vite.config.ts``apps/admin-web/vite.config.ts``chunkSizeWarningLimit`
- 处理:先看 warning 原文确认来源。若是合理的入口级 chunk 体积增长,调整对应 Vite 配置阈值或做真实拆包;不要把这类失败按 Rust / SpacetimeDB 编译错误排查。
- 验证:重新执行 `npm run build`,主站与后台均构建完成且没有 build-gate warning 汇总。
- 关联:`scripts/build-gate.mjs``vite.config.ts``apps/admin-web/vite.config.ts`
## 反馈页清空 file input 前必须先拷贝 FileList
- 现象:点击上传凭证会打开文件选择框,但选择图片后页面没有展示预览,提交时也没有携带图片凭证。
@@ -378,8 +458,8 @@
- 现象:`POST /api/assets/hyper3d/text-to-model` 在本地返回 503,详情里提示 `HYPER3D_API_KEY 未配置`,但开发者明明已经在本地私密文件里写了 key。
- 原因:`scripts/dev-utils.mjs` 之前按 `.env.secrets.local → .env.local → .env` 合并,结果仓库里的 `.env` 空示例值会把前面已经设置好的私密 key 覆盖掉。
- 处理:`npm run dev:api-server` / `npm run dev:spacetime` / `npm run dev` 统一按“外层 shell 变量优先,其后 `.env``.env.local``.env.secrets.local` 逐层覆盖”的顺序加载;真实密钥优先放 `.env.secrets.local`
- 验证:本地加入临时测试后,`HYPER3D_API_KEY` 应能被 `.env.secrets.local` 覆盖, shell 变量仍然最高优先级。
- 处理:`npm run dev:api-server` / `npm run dev:spacetime` / `npm run dev` 统一按“外层 shell 变量优先,其后 `.env``.env.local``.env.secrets.local` 逐层覆盖”的顺序加载;真实密钥优先放 `.env.secrets.local`本地认证开关例外:`SMS_AUTH_ENABLED``SMS_AUTH_PROVIDER` 等以本地 env 文件为准,避免父进程继承的旧开关值长期压过 `.env.local`
- 验证:本地加入临时测试后,`HYPER3D_API_KEY` 应能被 `.env.secrets.local` 覆盖,真实密钥 shell 变量仍然最高优先级`mergeApiServerEnv(..., { SMS_AUTH_ENABLED: "false" })``.env.local``SMS_AUTH_ENABLED=true` 时应返回 true
- 关联:`scripts/dev-utils.mjs``server-rs/crates/api-server/src/hyper3d_generation.rs``docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`
## OSS 密钥键名不要把字母 O 写成数字 0
@@ -408,28 +488,28 @@
## 本地短信登录页签突然消失
- 现象:登录弹窗只剩密码登录,短信登录页签看起来像被删掉,但 `LoginScreen` 中手机号验证码表单仍存在。
- 原因:前端根据 `GET /api/auth/login-options` 返回的 `availableLoginMethods` 渲染页签;常见根因有两类:
- 原因:历史实现曾根据 `GET /api/auth/login-options` 返回的 `availableLoginMethods` 渲染页签;接口返回空、失败或只返回 `["password"]` 时,`AuthGate` 会降级成只显示密码。
- 本地启动脚本没有让 `.env.local` 覆盖 `.env``SMS_AUTH_ENABLED=true` 不生效,后端只返回 `["password"]`
- Rust API 直连已返回 `["phone","password"]`,但 Vite 代理目标指向未监听端口,导致 3000 域名下的 `login-options` 返回 `500``AuthGate` 降级成 `["password"]`
- 3000 端口被旧 `dev:web` 占用后,新的完整栈 Vite 自动漂移到 3001/3002;浏览器仍打开旧 3000 页面,旧页面继续代理到已经下线的端口。
- 生成页 UI 改动看起来“完全没变化”时,也要先确认当前浏览器打开的 Vite 进程正在返回最新源码;例如直接请求 `http://127.0.0.1:3000/src/components/CustomWorldGenerationView.tsx` 检查是否包含本次新增类名或关键字。
- 单独 `npm run dev:web` 启动瞬间另一个临时 API 端口可用,脚本若自动切过去,之后临时 API 停掉也会让 3000 继续代理到空端口。
- 处理:优先用 `npm run dev:api-server``npm run dev:spacetime``npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local``SMS_AUTH_ENABLED` 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启
- 验证:`http://127.0.0.1:3000/api/auth/login-options` 返回至少 `{"availableLoginMethods":["phone","password"]}`,登录弹窗会恢复短信登录页签和“获取验证码”按钮
- 关联:`scripts/dev-utils.mjs``scripts/dev.mjs``docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md`
- 处理:当前口径是登录弹窗永远展示 `短信登录``密码登录` 两个核心入口;`login-options` 只补充微信等环境相关入口,不能隐藏短信或密码页签。如果“获取验证码”点击后失败,再按短信 provider / API 代理问题排查:优先用 `npm run dev:api-server``npm run dev:spacetime``npm run dev` 启动,确认 `.env.local` 覆盖 `.env``RUST_SERVER_TARGET` 没有指向旧端口,并分别请求 3000 域名和 Rust API 目标
- 验证:即使 `/api/auth/login-options` 返回空、失败或只返回 `["password"]`,登录弹窗也应同时显示 `短信登录``密码登录``验证码` 输入和“获取验证码”按钮;短信发送真实可用性再通过 `POST /api/auth/phone/send-code` 验证
- 关联:`src/components/auth/AuthGate.tsx``src/components/auth/LoginScreen.tsx``src/components/auth/AuthGate.test.tsx``scripts/dev-utils.mjs``scripts/dev.mjs`
## 本地短信收不到验证码先查 provider
- 现象:登录弹窗可以进入短信页签,但点击“获取验证码”后,手机没有收到短信。
- 原因:本地 `.env.local` 里如果是 `SMS_AUTH_PROVIDER="mock"`,后端不会发真实短信,只会返回固定 mock 验证码;另外 `npm run dev:api-server` 过去曾让 `.env` 覆盖 `.env.local`,导致本地真实短信配置被错误压回默认值
- 处理:真实短信联调时把 `.env.local``SMS_AUTH_PROVIDER` 显式设为 `aliyun`,然后重启 `api-server`;如果只想验证 UI 和账号链路,则保留 `mock` 并使用 `SMS_AUTH_MOCK_VERIFY_CODE`
- 验证:`GET /api/auth/login-options` 返回 `["phone","password"]``api-server` 日志里 `provider=aliyun` 才说明真实短信链路已生效。
- 关联:`scripts/dev-utils.mjs``docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md``docs/technical/PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md`
- 原因:本地 `.env.local` 里如果是 `SMS_AUTH_PROVIDER="mock"`,后端不会发真实短信,只会返回固定 mock 验证码;真实阿里云链路已经改为普通短信 `SendSms`,验证码由当前 `api-server` 进程本地生成、哈希存储和校验,旧 `SendSmsVerifyCode` / `CheckSmsVerifyCode` 托管验证码参数不再参与真实校验。若接口直接返回“手机号登录暂未启用”,说明当前运行中的 `api-server` 进程内 `sms_auth_enabled=false`:常见原因是修改 `.env.local` 后没有重启后端,或外层 shell 已经设置了非空 `SMS_AUTH_ENABLED` 导致 dotenv 不覆盖。历史上 cmd 里 `set SMS_AUTH_ENABLED="true"` 会把引号也传进进程,Rust bool 解析失败后保持默认 false
- 处理:真实短信联调时把 `.env.local``SMS_AUTH_ENABLED=true``SMS_AUTH_PROVIDER=aliyun` 显式打开,并确认 `ALIYUN_SMS_ENDPOINT=dysmsapi.aliyuncs.com``ALIYUN_SMS_SIGN_NAME=北京亓盒网络科技``ALIYUN_SMS_TEMPLATE_CODE=SMS_506245486``ALIYUN_SMS_TEMPLATE_PARAM_KEY=code` 后重启 `api-server`;如果只想验证 UI 和账号链路,则保留 `mock` 并使用 `SMS_AUTH_MOCK_VERIFY_CODE`。Shell 临时覆盖时 PowerShell 用 `$env:SMS_AUTH_ENABLED="true"`cmd 用 `set SMS_AUTH_ENABLED=true`,不要把引号作为值的一部分。`api-server` 重启会清掉未校验的本地验证码
- 验证:分别请求浏览器域名和 Rust API 直连的 `/api/auth/login-options`,都应返回 `["phone","password"]``api-server` 日志里 `provider=aliyun` 才说明真实短信链路已生效。需要直接确认平台层真实调用阿里云时,配置 `ALIYUN_SMS_ACCESS_KEY_ID``ALIYUN_SMS_ACCESS_KEY_SECRET``ALIYUN_SMS_REAL_TEST_PHONE_NUMBER` 后手动执行 `cargo test -p platform-auth --manifest-path server-rs/Cargo.toml aliyun_send_sms_real_provider_sends_verify_code -- --ignored --nocapture`
- 关联:`server-rs/crates/api-server/src/config.rs``scripts/dev-utils.mjs``docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md``docs/technical/PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md`
## 手机验证码登录 500 先查短信 provider 语义
- 现象:登录弹窗手机号验证码登录失败,浏览器看到 `POST /api/auth/phone/login 500`,后端日志里同时出现阿里云短信 `UNKNOWN``biz.FREQUENCY``check frequency failed`
- 原因:真实短信 provider 的配置错误或上游失败曾被 `module-auth` 折叠成 `PhoneAuthError::Store`HTTP 层只能按内部错误返回 `500`,掩盖了 provider 失败。
- 原因:真实短信 provider 的配置错误或上游失败曾被 `module-auth` 折叠成 `PhoneAuthError::Store`HTTP 层只能按内部错误返回 `500`,掩盖了 provider 失败。当前验证码校验已经改成本地哈希校验,登录阶段的验证码错误不会再调用阿里云校验接口;若登录前的发送阶段失败,应优先看 `SendSms` 返回的 `Code/Message`
- 处理:保留 provider 错误语义,配置错误映射 `503 Service Unavailable`,上游短信失败映射 `502 Bad Gateway`;本地只验证 UI/账号链路时可用 shell 临时覆盖 `SMS_AUTH_PROVIDER=mock` 后启动 `npm run dev:api-server`
- 验证:`cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`,真实 provider 频控时接口不再返回 `500`
- 关联:`server-rs/crates/module-auth/src/errors.rs``server-rs/crates/api-server/src/phone_auth.rs``docs/technical/PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md`
@@ -766,7 +846,7 @@
- 原因:旧运行态把消除次数和类型数量绑在一起,结果页文案又同时展示“素材图片 / 局内类型”,导致前端、发布校验和 run start 口径不一致。
- 处理:统一使用 `物品种类` 口径:轻松 3、标准 9、进阶 15、硬核 21;历史 `clearCount=20` 且难度为硬核的运行态按新硬核升为 21 组三消,避免 20 组却要求 21 种素材。发布前按 `image_ready` 且有 `imageViews[]``imageSrc/imageObjectKey` 的生成素材数量阻断不足难度;试玩不阻断,但通过 `itemTypeCountOverride` 自动降到已生成 2D 素材数量。重启从已有 run 快照反推实际物品种类,保持同一局重开不变。
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx``cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`,涉及发布 reducer 时补跑 `cargo test -p spacetime-module match3d --manifest-path server-rs\Cargo.toml`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/services/match3d-runtime/match3dRuntimeClient.ts``server-rs/crates/module-match3d/src/application.rs``server-rs/crates/spacetime-module/src/match3d/mod.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/services/match3d-runtime/match3dRuntimeClient.ts``server-rs/crates/module-match3d/src/application.rs``server-rs/crates/spacetime-module/src/match3d.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅标签清洗不要把 `3D素材` 当编号剥掉
@@ -863,3 +943,27 @@
- 处理:导入 / 导出流水线在调用迁移脚本前先 `source scripts/jenkins-prepare-toolchain-env.sh`;该脚本会把 `GENARRATIVE_JENKINS_TOOL_PATHS``/var/lib/jenkins/.nvm/versions/node/v22.22.2/bin``/var/lib/jenkins/.cargo/bin``/var/lib/jenkins/.local/bin` 和系统 PATH 前缀统一补齐,并在缺少 `node` 时尽早报错。
- 验证:重新跑 `Genarrative-Database-Import``Genarrative-Database-Export`,日志应先打印 `jenkins-toolchain``node=...` 解析结果,而不是在迁移中途报 `node: command not found`
- 关联:`scripts/jenkins-prepare-toolchain-env.sh``jenkins/Jenkinsfile.production-database-import``jenkins/Jenkinsfile.production-database-export``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Windows Jenkins `powershell` step 在 Stdb module 构建里曾触发 CreateProcess error=5
- 现象:`Genarrative-Stdb-Module-Build` 在 Windows Jenkins 节点上报 `java.io.IOException: Cannot run program "powershell" (in directory "C:\\Users\\DSK\\.jenkins-local\\workspace\\Genarrative-Stdb-Module-Build"): CreateProcess error=5, 拒绝访问。`;日志里能看到 `durable-task` 已写出 `powershellWrapper.ps1`,但在真正启动裸 `powershell` 子进程时失败。
- 原因:Jenkins durable-task 的 `powershell` step 依赖一个隐式命令解析/启动路径,在这台 Windows 本地 Jenkins 环境里会被拒绝。`powershell.exe` 本体和 workspace ACL 都是正常的,问题出在 Jenkins step 的启动方式,而不是 PowerShell 脚本内容。修复后若日志能打印 `[jenkins-powershell] exe:`,但随后仅报 `拒绝访问` / `script returned exit code 5`,通常已经不是 PowerShell 启动失败,而是 Checkout 脚本内部命令在 Windows workspace 里触发权限拒绝。若 `.jenkins-*.ps1` 里中文 `throw '[stdb-build] ...'``MissingArrayIndexExpression`,则是 Windows PowerShell 5.1 用 `-File` 解析无 BOM UTF-8 脚本时按本地 ANSI 误解码。
- 处理:把 `jenkins/Jenkinsfile.production-stdb-module-build``Checkout``Build Stdb Module` 两处 `powershell` step 收口成 `runWindowsPowerShell(...)` helper,先用 `writeFile` 写出临时 `.ps1`,再用显式 `powershell.exe` 把脚本重写成 UTF-8 with BOM,最后通过 `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File ...` 执行。这个 helper 写在 Groovy GString 里时,PowerShell 的 `$path` / `$text` / `$true` 必须写成 `\$path` / `\$text` / `\$true`,否则 Jenkinsfile 会在 Groovy 编译阶段报 `unexpected token: true`。Checkout 阶段优先复用 Jenkins GitSCM 已完成的工作区结果;`COMMIT_HASH` 为空或已经等于当前 `HEAD` 时不再重复 `git fetch` / `git checkout` / `git clean`,只有确实要切到另一个指定 commit 时才补 fetch、归属校验和 checkout。
- 验证:检查 Jenkins build log 中是否出现 `[jenkins-powershell] user:``[jenkins-powershell] exe:`,以及 `[stdb-checkout] current HEAD:`。上游 Full Build 传下来的 `COMMIT_HASH` 若已等于当前 GitSCM checkout,日志应显示 `requested commit already matches Jenkins GitSCM checkout` 并继续进入构建阶段;同时确认 `builds/<n>/log` 不再停在 `PipelineNodeTreeScanner... Cannot run program "powershell"` 或 Checkout 内部 exit code 5。
- 关联:`jenkins/Jenkinsfile.production-stdb-module-build``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## QQ 浏览器发现页推荐封面全不显示先查 aspect-ratio 兜底
- 现象:发现页的“推荐”子频道作品卡标题、作者和数据正常,但所有封面图不显示,常见于 QQ 浏览器 / X5 等旧移动内核。
- 原因:公开作品卡封面内部图片是绝对铺满,容器原本主要依赖 Tailwind `aspect-video` / CSS `aspect-ratio` 撑高;旧内核不支持或实现异常时封面容器高度会坍缩为 0。若封面还是 `/generated-*` 私有资源,换签失败后没有玩法参考图兜底时会进一步表现成黑卡。
- 处理:`.platform-public-work-card__cover::before` 使用 `padding-top: 56.25%` 保留 16:9 高度,沉浸式卡片单独覆盖比例;公开作品卡通过 `resolvePlatformWorldFallbackCoverImage(...)``ResolvedAssetImage` 传入玩法参考图兜底,签名失败或图片加载失败时仍有可见封面。
- 验证:`npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``npm run typecheck``npm run check:encoding`
- 关联:`src/index.css``src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/rpg-entry/rpgEntryWorldPresentation.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 生成中草稿刷新后不要只恢复作品架遮罩
- 现象:拼图或抓大鹅草稿生成中刷新网页后,作品架卡片能显示等待遮罩,但点击卡片会走普通草稿恢复,可能进入空白结果页或未完成工作区。
- 原因:前端只把内存 notice 当作“生成中点击恢复”的判断条件,没有把后端摘要里的 `generationStatus=generating` 纳入同一路径。
- 处理:打开草稿时把持久化 `generationStatus=generating` 等同于生成中 notice,恢复对应玩法生成进度页;恢复计时使用作品摘要 `updatedAt` 推导 `startedAtMs`
- 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"`
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
@@ -0,0 +1,27 @@
# 前端直订阅公开作品列表准入待办
## 背景
未来可以考虑让前端直接订阅公开作品列表,以减少列表读取链路中的 HTTP 往返,并复用 SpacetimeDB 的实时同步能力。
## 当前结论
短期仍由 `api-server` / BFF 订阅 SpacetimeDB public read model,并从本地 cache 读取后对外提供 HTTP 列表接口。前端不直接订阅作品源表,也不把正式列表排序、分页、权限裁剪逻辑下放到 UI。
## 落地前置条件
- 建立专用、稳定、低基数的 public read model,例如 `public_work_gallery_entry`
- 明确权限边界,只暴露公开列表所需字段,不泄露作者私有信息、审核内部状态或运营字段。
- 固化字段契约,明确字段含义、默认值、兼容策略和生成绑定更新流程。
- 明确排序与分页语义,避免依赖自增 ID 顺序,优先使用时间戳或显式排序字段。
- 补齐埋点方案,能区分直订阅首屏、增量更新、分页加载和 fallback 命中。
- 保留 BFF HTTP fallback,用于低版本客户端、订阅失败、权限策略调整和灰度回滚。
- 禁止前端订阅 `puzzle_work_profile``custom_world_profile` 等作品源表。
## 建议验收
- 文档确认直订阅只面向专用 public read model,不绕过 BFF 读取源表。
- schema、绑定、字段契约、排序分页和权限说明同步更新。
- 前端具备订阅失败后的 BFF HTTP fallback。
- 自动测试覆盖公开字段裁剪、排序分页稳定性和 fallback 路径。
- 监控可观察直订阅成功率、首屏耗时、增量更新延迟和 fallback 比例。
-10
View File
@@ -1,10 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
Generated
-1
View File
@@ -1 +0,0 @@
mod.rs
-59
View File
@@ -1,59 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
-248
View File
@@ -1,248 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BackendCodeEditorSettings">
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CDeclarationWithImplicitIntType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConstevalIfIsAlwaysConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractClassWithoutSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractVirtualFunctionCallInCtor/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAccessSpecifierWithNoDeclarations/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAwaiterTypeIsNotClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBooleanIncrementExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatBadCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatLegacyCode/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatMixedArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooFewArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCompileTimeConstantCanBeReplacedWithBooleanConstant/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConceptNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConditionalExpressionCanBeSimplified/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCoroutineCallResolveError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesLocal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationSpecifierWithoutDeclarators/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorDisambiguatedAsFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorUsedBeforeInitialization/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultCaseNotHandledInSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedEntity/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedRegisterStorageClassSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDereferenceOperatorLimitExceeded/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDiscardedPostfixOperatorResult/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenSyntaxError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUndocumentedParameter/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUnresolvedReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEmptyDeclaration/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersOrder/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersPlacement/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceDoStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceForStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceFunctionDeclarationStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceIfStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceNestedNamespacesStyle/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingDestructorStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingFunctionStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceTypeAliasCodeStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceWhileStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityAssignedButNoRead/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityUsedOnlyInUnevaluatedContext/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnumeratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEqualOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEvaluationFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExplicitSpecializationInNonNamespaceScope/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExpressionWithoutSideEffects/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalFunctionInFinalClass/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalNonOverridingVirtualFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForLoopCanBeReplacedWithWhile/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForwardEnumDeclarationWithoutUnderlyingType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionDoesntReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionResultShouldBeUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionalStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHeaderHasBeenAlreadyIncluded/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHiddenFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHidingFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIdenticalOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIfCanBeReplacedByConstexprIf/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtDoubleUserConversionInCopyInit/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtNotInitializedStaticConstLocalVar/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtReinterpretCastFromNullptr/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterWideLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMustBePublicVirtualToImplementInterface/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMutableSpecifierOnReferenceMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNoDiscardExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNodiscardFunctionWithoutReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExceptionSafeResourceAcquisition/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConversionOperator/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConvertingConstructor/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineFunctionDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineVariableDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPassValueParameterByConstReference/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerConversionDropsQualifiers/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerToIntegralConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPolymorphicClassWithNonVirtualPublicDestructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyErroneousEmptyStatements/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUnintendedObjectSlicing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderIsNotIncluded/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderNotFound/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfBadFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfExtraArg/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfMissedArg/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfRiskyFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrivateSpecialMemberFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRangeBasedForIncompatibleReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedefinitionOfDefaultArgumentInOverrideFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBooleanExpressionArgument/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantCastExpression/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantComplexityInComparison/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConditionalExpression/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConstSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantControlFlowJump/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantDereferencingAndTakingAddress/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElaboratedTypeSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeyword/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeywordInsideCompoundStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyDeclaration/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantExportKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantInlineSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantLambdaParameterList/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantMemberInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantNamespaceDefinition/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantParentheses/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnMemberAllocationFunction/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnThreadLocalLocalVariable/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateArguments/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantVoidArgumentList/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantZeroInitializerInAggregateInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReinterpretCastFromVoidPtr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRemoveRedundantBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceMemsetWithZeroInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceTieWithStructuredBinding/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReturnNoValueInNonVoidFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSmartPointerVsMakeFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSomeObjectMembersMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSpecialFunctionWithoutNoexceptSpecification/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticAssertFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppThrowExpressionCanBeReplacedWithRethrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScope/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScopeInitStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTypeAliasNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedDependentBaseClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedNonStaticDataMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnionMemberOfReferenceType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAssociativeContains/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAuto/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAutoForNumeric/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseElementsView/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseEraseAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseFamiliarTemplateSyntaxForGenericLambdas/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseRangeAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStdSize/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStructuredBinding/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseTypeTraitAlias/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUserDefinedLiteralSuffixDoesNotStartWithUnderscore/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUsingResultOfAssignmentAsCondition/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVariableCanBeMadeConstexpr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionCallInsideCtor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionInFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVolatileParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWarningDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongIncludesOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongSlashesInIncludeDirective/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroConstantCanBeReplacedWithNullptr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroValuedExpressionUsedAsNullPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Genarrative.iml" filepath="$PROJECT_DIR$/.idea/Genarrative.iml" />
</modules>
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+132
View File
@@ -0,0 +1,132 @@
# Genarrative 容器化压测与隔离部署方案
本目录只服务本机或预发的容器化模拟压测,不替换当前生产 `systemd + Nginx + Jenkins` 发布路径。生产服务器仍以 `deploy/systemd/``deploy/nginx/``scripts/jenkins-*.sh``scripts/deploy/production-api-deploy.sh` 为准。
## 拓扑
```text
Docker Compose
├─ nginx :80 -> api-server:8082,负责静态站点、/admin/、/api/ 反代、upstream timing log、连接限制
├─ api-server :8082Linux release 构建,连接外部 SpacetimeDB
├─ otelcol :4317/4318debug exporter,接收 traces / metrics / logs
└─ k6 profile=loadtest 时临时启动,在 compose 网络内压 nginx
```
默认 host 端口:
- `http://127.0.0.1:18080`:容器 Nginx。
- `127.0.0.1:4317` / `127.0.0.1:4318`:容器 Collector OTLP gRPC / HTTP。
如端口冲突,可设置:
```powershell
$env:GENARRATIVE_CONTAINER_HTTP_PORT="18081"
$env:GENARRATIVE_CONTAINER_OTLP_HTTP_PORT="14318"
$env:GENARRATIVE_CONTAINER_OTLP_GRPC_PORT="14317"
```
## 初始化
```bash
npm run container:init
```
该命令会从 `deploy/container/api-server.env.example` 生成本地 `deploy/container/api-server.env`。真实 token、库名和外部服务密钥只写本地 env 文件,不提交 Git。
Docker Desktop 下默认通过 `host.docker.internal:3101` 连接宿主机上 `npm run dev` 启动的 SpacetimeDB
```env
GENARRATIVE_SPACETIME_SERVER_URL=http://host.docker.internal:3101
GENARRATIVE_SPACETIME_DATABASE=genarrative-loadtest
GENARRATIVE_SPACETIME_TOKEN=
```
Linux Docker Engine 如果不能解析 `host.docker.internal`Compose 已配置 `host-gateway`;仍不通时把 `GENARRATIVE_SPACETIME_SERVER_URL` 改成宿主机网关 IP 或同网络内的 SpacetimeDB 地址。
## 启动与验证
```bash
npm run container:config
npm run container:build
npm run container:up
npm run container:ps
curl -sS http://127.0.0.1:18080/api/runtime/puzzle/gallery
```
查看日志:
```bash
npm run container:logs -- nginx
npm run container:logs -- api-server
npm run container:logs -- otelcol
```
`npm run container:config` 默认只校验配置,不打印完整 env。排查 compose 展开结果时可临时使用:
```bash
npm run container:config -- --print
```
如果 `deploy/container/api-server.env` 已写入真实 token,不要把完整展开结果贴到公开渠道。
停止:
```bash
npm run container:down
```
如需同时清理容器卷:
```bash
npm run container:down -- -v
```
## 压测
k6 在 compose 网络内访问 `http://nginx`,避免 Windows 本机直连连接模型干扰 Linux 容器结果:
```bash
npm run container:k6
```
作品列表脚本一次 iteration 默认请求两个公开列表接口,因此目标 500 HTTP req/s 对应 `PEAK_RPS=250`
```powershell
$env:SCENARIO="spike"
$env:START_RPS="25"
$env:PEAK_RPS="250"
$env:HOLD="60s"
$env:END_RPS="25"
$env:PREALLOCATED_VUS="100"
$env:MAX_VUS="500"
$env:DETAIL_RATIO="0"
npm run container:k6
```
如果要压 1000 HTTP req/s,把 `PEAK_RPS` 调到 `500`;如果要压 5000 HTTP req/s,把 `PEAK_RPS` 调到 `2500`,并同时提高 `PREALLOCATED_VUS` / `MAX_VUS`,观察是否先被带宽、Nginx `limit_conn` 或 api-server 背压限制。
## OTLP
容器内 `otelcol` 默认使用 debug exporter。开启 api-server OTEL
```env
GENARRATIVE_OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcol:4318
```
然后重建或重启容器:
```bash
npm run container:up
npm run container:logs -- otelcol
```
Collector 日志会输出 traces / metrics / logs。接 Rider、Jaeger、Tempo、Prometheus、Grafana 或托管平台时,另建独立 Collector 配置,不直接改生产 systemd 或 Nginx 模板。
## 隔离边界
- 不改生产 systemd 单元。
- 不改 Jenkins 发布主流程。
- 不要求真实 HTTPS 证书。
- 不把真实 `.env``.env.local``.env.secrets.local``deploy/container/api-server.env` 放入 Docker build context。
- 不在容器镜像里内置 SpacetimeDB 数据或 token。
+49
View File
@@ -0,0 +1,49 @@
FROM rust:1.88-bookworm AS rust-builder
WORKDIR /workspace
COPY server-rs ./server-rs
RUN cargo build --release -p api-server --manifest-path server-rs/Cargo.toml && \
cp server-rs/target/release/api-server /tmp/api-server
FROM debian:bookworm-slim AS api-runtime
WORKDIR /srv/genarrative
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates curl && \
rm -rf /var/lib/apt/lists/* && \
useradd --system --create-home --home-dir /srv/genarrative --shell /usr/sbin/nologin genarrative
COPY --from=rust-builder /tmp/api-server /usr/local/bin/api-server
RUN mkdir -p /var/lib/genarrative/auth && \
chown -R genarrative:genarrative /srv/genarrative /var/lib/genarrative
USER genarrative
EXPOSE 8082
ENV GENARRATIVE_ENV=container \
GENARRATIVE_API_HOST=0.0.0.0 \
GENARRATIVE_API_PORT=8082 \
GENARRATIVE_AUTH_STORE_PATH=/var/lib/genarrative/auth/auth-store.json
CMD ["api-server"]
FROM node:22-bookworm-slim AS web-builder
WORKDIR /workspace
COPY package.json package-lock.json ./
COPY apps/admin-web/package.json ./apps/admin-web/package.json
RUN npm ci
COPY index.html metadata.json tsconfig.json vite.config.ts ./
COPY src ./src
COPY public ./public
COPY media ./media
COPY packages ./packages
COPY apps/admin-web ./apps/admin-web
RUN npm run build:raw && npm run admin-web:build
FROM nginx:1.27-alpine AS nginx-runtime
COPY --from=web-builder /workspace/dist /srv/genarrative/web
COPY --from=web-builder /workspace/apps/admin-web/dist /srv/genarrative/web/admin
COPY deploy/container/nginx.conf /etc/nginx/nginx.conf
+35
View File
@@ -0,0 +1,35 @@
# 复制为 deploy/container/api-server.env 后填入本机或预发值。
# 该文件只用于容器隔离方案,不参与 systemd/Jenkins 生产部署。
# 不要在这里写真实 token 后提交 Git。
GENARRATIVE_ENV=container
GENARRATIVE_API_HOST=0.0.0.0
GENARRATIVE_API_PORT=8082
GENARRATIVE_API_LOG=info,tower_http=info
GENARRATIVE_API_LISTEN_BACKLOG=1024
GENARRATIVE_API_WORKER_THREADS=4
GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512
GENARRATIVE_OTEL_ENABLED=false
OTEL_SERVICE_NAME=genarrative-api
OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcol:4318
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=container,service.namespace=genarrative
GENARRATIVE_INTERNAL_API_SECRET=CHANGE_ME_FOR_CONTAINER
GENARRATIVE_JWT_ISSUER=genarrative-container
GENARRATIVE_JWT_SECRET=CHANGE_ME_FOR_CONTAINER
AUTH_REFRESH_COOKIE_SECURE=false
GENARRATIVE_AUTH_STORE_PATH=/var/lib/genarrative/auth/auth-store.json
# Docker Desktop 下连接宿主机 npm run dev 启动的 SpacetimeDB。
# Linux Docker Engine 可改成宿主机网关 IP,或在 compose 里接入同一网络内的 SpacetimeDB。
GENARRATIVE_SPACETIME_SERVER_URL=http://host.docker.internal:3101
GENARRATIVE_SPACETIME_DATABASE=genarrative-loadtest
GENARRATIVE_SPACETIME_TOKEN=
GENARRATIVE_SPACETIME_POOL_SIZE=8
GENARRATIVE_SPACETIME_PROCEDURE_TIMEOUT_SECONDS=45
GENARRATIVE_LLM_PROVIDER=openai-compatible
GENARRATIVE_LLM_BASE_URL=
GENARRATIVE_LLM_API_KEY=
GENARRATIVE_LLM_MODEL=
@@ -0,0 +1,85 @@
name: genarrative-container-loadtest
services:
api-server:
build:
context: ../..
dockerfile: deploy/container/api-server.Dockerfile
target: api-runtime
env_file:
- ./api-server.env
environment:
GENARRATIVE_API_HOST: 0.0.0.0
GENARRATIVE_API_PORT: 8082
OTEL_EXPORTER_OTLP_ENDPOINT: http://otelcol:4318
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- api-auth-store:/var/lib/genarrative/auth
depends_on:
otelcol:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8082/healthz"]
interval: 10s
timeout: 3s
retries: 12
start_period: 20s
nginx:
build:
context: ../..
dockerfile: deploy/container/api-server.Dockerfile
target: nginx-runtime
depends_on:
api-server:
condition: service_healthy
ports:
- "${GENARRATIVE_CONTAINER_HTTP_PORT:-18080}:80"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- nginx-logs:/var/log/nginx
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/api/runtime/puzzle/gallery"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
otelcol:
image: otel/opentelemetry-collector-contrib:0.125.0
command: ["--config=/etc/otelcol/config.yaml"]
volumes:
- ./otelcol.yaml:/etc/otelcol/config.yaml:ro
ports:
- "${GENARRATIVE_CONTAINER_OTLP_GRPC_PORT:-4317}:4317"
- "${GENARRATIVE_CONTAINER_OTLP_HTTP_PORT:-4318}:4318"
k6:
image: grafana/k6:0.52.0
profiles: ["loadtest"]
depends_on:
nginx:
condition: service_healthy
environment:
BASE_URL: http://nginx
WORKS_DATA: data/works-list.sample.json
SCENARIO: ${SCENARIO:-spike}
START_RPS: ${START_RPS:-5}
PEAK_RPS: ${PEAK_RPS:-250}
HOLD: ${HOLD:-60s}
END_RPS: ${END_RPS:-5}
PREALLOCATED_VUS: ${PREALLOCATED_VUS:-100}
MAX_VUS: ${MAX_VUS:-500}
DETAIL_RATIO: ${DETAIL_RATIO:-0}
SLEEP_MIN_SECONDS: ${SLEEP_MIN_SECONDS:-0}
SLEEP_MAX_SECONDS: ${SLEEP_MAX_SECONDS:-0}
volumes:
- ../../scripts/loadtest:/scripts/loadtest:ro
working_dir: /scripts/loadtest
command: ["run", "k6-works-list.js"]
volumes:
api-auth-store:
nginx-logs:
+133
View File
@@ -0,0 +1,133 @@
worker_processes auto;
events {
worker_connections 4096;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format genarrative_upstream
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'request_time=$request_time upstream_connect_time=$upstream_connect_time '
'upstream_header_time=$upstream_header_time upstream_response_time=$upstream_response_time '
'upstream_status=$upstream_status request_id=$request_id';
upstream genarrative_api {
server api-server:8082;
keepalive 64;
}
limit_conn_zone $binary_remote_addr zone=genarrative_api_conn:10m;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml;
server {
listen 80;
server_name _;
access_log /var/log/nginx/genarrative.access.log genarrative_upstream;
error_log /var/log/nginx/genarrative.error.log warn;
limit_conn_status 429;
limit_conn_log_level warn;
root /srv/genarrative/web;
index index.html;
location ^~ /admin/api/ {
default_type application/json;
limit_conn genarrative_api_conn 64;
proxy_pass http://genarrative_api/admin/api/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-Id $request_id;
}
location = /admin {
return 301 /admin/;
}
location ^~ /admin/assets/ {
try_files $uri =404;
}
location ^~ /admin/ {
try_files $uri $uri/ /admin/index.html;
}
location ^~ /assets/ {
try_files $uri =404;
}
location ~ ^/api(?:/|$) {
default_type application/json;
limit_conn genarrative_api_conn 64;
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location ~ ^/(generated-|healthz) {
return 404;
}
location ~ ^/v1/database/[^/]+/subscribe$ {
proxy_pass http://host.docker.internal:3101;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
location ^~ /v1/identity {
proxy_pass http://host.docker.internal:3101;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
location ^~ /v1/ {
return 404;
}
location / {
try_files $uri $uri/ /index.html;
}
}
}
+23
View File
@@ -0,0 +1,23 @@
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
exporters: [debug]
metrics:
receivers: [otlp]
exporters: [debug]
logs:
receivers: [otlp]
exporters: [debug]
+10 -3
View File
@@ -5,6 +5,13 @@ GENARRATIVE_ENV=production
GENARRATIVE_API_HOST=127.0.0.1
GENARRATIVE_API_PORT=8082
GENARRATIVE_API_LOG=info,tower_http=info
GENARRATIVE_API_LISTEN_BACKLOG=1024
GENARRATIVE_API_WORKER_THREADS=4
GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512
GENARRATIVE_OTEL_ENABLED=false
OTEL_SERVICE_NAME=genarrative-api
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=genarrative
GENARRATIVE_ADMIN_USERNAME=
GENARRATIVE_ADMIN_PASSWORD=
@@ -79,9 +86,9 @@ SMS_AUTH_ENABLED=false
SMS_AUTH_PROVIDER=aliyun
ALIYUN_SMS_ACCESS_KEY_ID=
ALIYUN_SMS_ACCESS_KEY_SECRET=
ALIYUN_SMS_ENDPOINT=dypnsapi.aliyuncs.com
ALIYUN_SMS_SIGN_NAME=
ALIYUN_SMS_TEMPLATE_CODE=
ALIYUN_SMS_ENDPOINT=dysmsapi.aliyuncs.com
ALIYUN_SMS_SIGN_NAME=北京亓盒网络科技
ALIYUN_SMS_TEMPLATE_CODE=SMS_506245486
ALIYUN_SMS_TEMPLATE_PARAM_KEY=code
ALIYUN_SMS_COUNTRY_CODE=86

Some files were not shown because too many files have changed in this diff Show More