退役旧创作模板业务并保留数据壳
保留 SpacetimeDB 历史表、迁移白名单与最小兼容读取定义 移除旧创作前后端、worker、业务过程及纯业务 crate 的编译依赖 恢复现役创作、项目、我的入口及桌面移动导航 收紧 Vite、TypeScript、ESLint、Vitest 与静态资源退役边界 补齐开发栈、网关、原生壳和文档退役约束
This commit is contained in:
@@ -114,10 +114,6 @@ WECHAT_MOCK_DISPLAY_NAME="微信旅人"
|
||||
WECHAT_MOCK_AVATAR_URL=""
|
||||
WECHAT_MINIPROGRAM_MESSAGE_TOKEN=""
|
||||
WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY=""
|
||||
WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_ENABLED="true"
|
||||
WECHAT_MINIPROGRAM_GENERATION_RESULT_TEMPLATE_ID="m5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU"
|
||||
WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE="formal"
|
||||
|
||||
# Model name for chat completions.
|
||||
VITE_LLM_MODEL="gpt-5.4-mini"
|
||||
GENARRATIVE_LLM_PROVIDER="openai-compatible"
|
||||
@@ -125,9 +121,6 @@ GENARRATIVE_LLM_BASE_URL="https://api.vectorengine.cn/v1"
|
||||
GENARRATIVE_LLM_API_KEY=""
|
||||
GENARRATIVE_LLM_MODEL="gpt-5.4-mini"
|
||||
|
||||
# Optional: enable upstream web search for RPG story text generation.
|
||||
RPG_LLM_WEB_SEARCH_ENABLED="true"
|
||||
|
||||
# Server-side DashScope endpoint and API key used by the local scene-image proxy.
|
||||
DASHSCOPE_BASE_URL="https://dashscope.aliyuncs.com/api/v1"
|
||||
DASHSCOPE_API_KEY="YOUR_DASHSCOPE_API_KEY"
|
||||
|
||||
@@ -171,6 +171,45 @@ module.exports = {
|
||||
'src/services/appTitle.ts',
|
||||
'src/services/appTitle.test.ts',
|
||||
'src/components/CharacterAnimator.tsx',
|
||||
'src/components/*.ts',
|
||||
'src/components/*.tsx',
|
||||
'!src/components/ResolvedAssetImage.tsx',
|
||||
'!src/components/ResolvedAssetVideo.tsx',
|
||||
'src/hooks/*.ts',
|
||||
'src/hooks/*.tsx',
|
||||
'!src/hooks/useGameSettings.ts',
|
||||
'!src/hooks/useHostNavigationCanGoBack.ts',
|
||||
'!src/hooks/useHostNavigationCanGoBack.test.tsx',
|
||||
'!src/hooks/useResolvedAssetReadUrl.ts',
|
||||
'!src/hooks/useResolvedAssetReadUrl.test.tsx',
|
||||
'src/persistence/*.ts',
|
||||
'src/persistence/*.tsx',
|
||||
'!src/persistence/gameSettingsStorage.ts',
|
||||
'!src/persistence/gameSettingsStorage.test.ts',
|
||||
'!src/persistence/storage.ts',
|
||||
'!src/persistence/storage.test.ts',
|
||||
'src/routing/*.ts',
|
||||
'src/routing/*.tsx',
|
||||
'!src/routing/activeAppPageRoutes.ts',
|
||||
'!src/routing/activeAppPageRoutes.test.ts',
|
||||
'!src/routing/activeAppRoutes.tsx',
|
||||
'!src/routing/activeAppRoutes.test.ts',
|
||||
'src/services/*.ts',
|
||||
'src/services/*.tsx',
|
||||
'!src/services/activeAppTitle.ts',
|
||||
'!src/services/activeAppTitle.test.ts',
|
||||
'!src/services/apiClient.ts',
|
||||
'!src/services/apiClient.test.ts',
|
||||
'!src/services/assetReadUrlService.ts',
|
||||
'!src/services/assetReadUrlService.test.ts',
|
||||
'!src/services/authService.ts',
|
||||
'!src/services/authService.test.ts',
|
||||
'!src/services/clipboard.ts',
|
||||
'!src/services/clipboard.test.ts',
|
||||
'!src/services/frontendRuntimeConfigService.ts',
|
||||
'!src/services/frontendRuntimeConfigService.test.ts',
|
||||
'!src/services/sseStream.ts',
|
||||
'!src/services/sseStream.test.ts',
|
||||
'src/AdventurePanel.tsx',
|
||||
'src/AdventureEntityModal.tsx',
|
||||
'src/App.tsx',
|
||||
@@ -265,11 +304,23 @@ module.exports = {
|
||||
'src/hooks/rpg-runtime-story/**',
|
||||
'src/hooks/rpg-session/**',
|
||||
'src/hooks/combat/**',
|
||||
'src/hooks/useCombatFlow.ts',
|
||||
'src/hooks/useStoryOptions.ts',
|
||||
'src/prompts/customWorldPrompts.ts',
|
||||
'src/services/ai.ts',
|
||||
'src/services/appTitle.ts',
|
||||
'src/services/appTitle.test.ts',
|
||||
'src/services/miniGameDraftGenerationProgress.ts',
|
||||
'src/services/creationEntryConfigService.ts',
|
||||
'src/services/creationUrlState*',
|
||||
'src/services/customWorld*',
|
||||
'src/services/input-devices/**',
|
||||
'src/services/publicWorkCode.ts',
|
||||
'src/services/runtimeGuestAuth.ts',
|
||||
'src/services/runtimeRequest*',
|
||||
'src/services/runtimeAudioFeedback.ts',
|
||||
'src/services/useMocapInput*',
|
||||
'src/services/wechatMiniProgramSubscribe*',
|
||||
'src/services/bark-battle-creation/**',
|
||||
'src/services/bark-battle-runtime/**',
|
||||
'src/services/big-fish-creation/**',
|
||||
@@ -304,6 +355,10 @@ module.exports = {
|
||||
'src/services/visual-novel-runtime/**',
|
||||
'src/services/visual-novel-works/**',
|
||||
'src/services/wooden-fish/**',
|
||||
'src/types.ts',
|
||||
'src/types/**',
|
||||
'src/uiAssets.ts',
|
||||
'scripts/loadtest/**',
|
||||
'packages/shared/src/contracts/jumpHop.ts',
|
||||
'packages/shared/src/contracts/match3dAgent.ts',
|
||||
'packages/shared/src/contracts/match3dRuntime.ts',
|
||||
|
||||
@@ -10,11 +10,10 @@ Docker Compose
|
||||
├─ nginx :80 -> api-server:8082,负责静态站点、/admin/、/api/ 反代、upstream timing log、连接限制
|
||||
├─ api-server :8082,Linux release 构建,连接 compose 内 SpacetimeDB
|
||||
├─ external-generation-worker,独立 worker 进程,消费 external_generation_job 队列
|
||||
├─ otelcol :4317/4318,debug exporter,接收 traces / metrics / logs
|
||||
└─ k6 profile=loadtest 时临时启动,在 compose 网络内压 nginx
|
||||
└─ otelcol :4317/4318,debug exporter,接收 traces / metrics / logs
|
||||
```
|
||||
|
||||
当前容器模拟参数按 `genarrative-release` 服务器采样值收口为 2 vCPU / 2 GiB RAM / 4096 soft nofile / 768 worker_connections,并已在 compose 里落实到 `spacetimedb cpus=1.0 mem_limit=896m`、`api-server cpus=2.0 mem_limit=1g`、`external-generation-worker cpus=2.0 mem_limit=1g`、`nginx cpus=0.5 mem_limit=128m`、`otelcol cpus=0.25 mem_limit=128m`、`k6 cpus=1.0 mem_limit=512m`。SpacetimeDB 同时设置 `--page_pool_max_size=402653184`,给 reducer、订阅与运行时保留更多非 page pool 内存。
|
||||
当前容器模拟参数按 `genarrative-release` 服务器采样值收口为 2 vCPU / 2 GiB RAM / 4096 soft nofile / 768 worker_connections,并已在 compose 里落实到 `spacetimedb cpus=1.0 mem_limit=896m`、`api-server cpus=2.0 mem_limit=1g`、`external-generation-worker cpus=2.0 mem_limit=1g`、`nginx cpus=0.5 mem_limit=128m`、`otelcol cpus=0.25 mem_limit=128m`。SpacetimeDB 同时设置 `--page_pool_max_size=402653184`,给 reducer、订阅与运行时保留更多非 page pool 内存。
|
||||
容器 `api-server` 默认 `GENARRATIVE_API_WORKER_THREADS=4`,用于让 Tokio 在 2 vCPU 配额内有更多 I/O 调度 worker;该值不会突破 compose 里的 `cpus=2.0` CPU 上限。
|
||||
容器默认 `GENARRATIVE_EXTERNAL_GENERATION_MODE=queue`,用于验证 `api-server -> external_generation_job -> external-generation-worker` 链路;如只想本地同步排查 provider/OSS/SpacetimeDB 写回,可在本机 env 临时改为 `inline`,但该模式不会覆盖 worker 动态扩缩容验证。
|
||||
Collector 镜像使用 `otel/opentelemetry-collector-contrib:0.151.0`。
|
||||
@@ -168,42 +167,9 @@ npm run container:down
|
||||
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
|
||||
```
|
||||
|
||||
容器内 `api-server` 资源上限与 Nginx 连接模型已经按 `genarrative-release` 的 2C / 2G / `nofile=4096` / `worker_connections=768` 收口;如果你要改成别的机器,就先重新采样再改这里。
|
||||
|
||||
SpacetimeDB 容器默认只提供运行时,不自动发布模块。首次启动或清理 `spacetime-data` 卷后,先只启动 `spacetimedb` 服务,再发布模块:
|
||||
|
||||
```bash
|
||||
npm run container:up -- spacetimedb
|
||||
spacetime publish genarrative-loadtest --server http://127.0.0.1:13101 --module-path server-rs/crates/spacetime-module --yes --build-options="--debug"
|
||||
```
|
||||
|
||||
发布完成后再执行 `npm run container:up` 和 `npm run container:k6`。如果 `deploy/container/api-server.env` 里的 `GENARRATIVE_SPACETIME_DATABASE` 改成了别的库名,发布命令里的库名也要同步修改。
|
||||
|
||||
如果要压 1000 HTTP req/s,把 `PEAK_RPS` 调到 `500`;如果要压 5000 HTTP req/s,把 `PEAK_RPS` 调到 `2500`,并同时提高 `PREALLOCATED_VUS` / `MAX_VUS`,观察是否先被带宽、Nginx `limit_conn` / `limit_req` 或 api-server 分组背压限制。当前容器 Nginx 对公开 gallery list 使用 `genarrative_gallery_rps`,公开详情和普通 API 使用 `genarrative_api_rps`,后台 API 使用 `genarrative_admin_rps`;api-server 侧对应 `GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS`、`GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS` 和 `GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS`。
|
||||
|
||||
2026-05-19 的 2C / 2G 容器压测结论:公开 gallery list 的 `limit_conn=320`、`limit_req rate=5000r/s burst=4096` 与 `GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS=320` 是当前发布口径。用宿主机 k6 打 `http://127.0.0.1:18080`,`PEAK_RPS=2500` 等价于约 5000 HTTP req/s 的两接口组合压测;连续 10 轮不重启 SpacetimeDB 的平均实际吞吐约 `4219 HTTP req/s`,总计 `1,897,357` 个 200、`212,542` 个 429、`0` 个 5xx,200 请求平均 `p95=123ms`、`p99=234ms`。该档会让 SpacetimeDB 内存从约 `366MiB` 累积到约 `885MiB / 896MiB`,下游内存先到危险区。当前不要为了降低“剩余 CPU”继续抬公开列表并发;下一步应减少成功列表请求后的 SpacetimeDB tracking 写入或优化下游连接 / 订阅状态,而不是放大入口并发。
|
||||
旧作品列表 / gallery 的 compose `k6` profile 与 `container:k6` 命令已经随模板业务退役。`scripts/loadtest/` 仅保留历史脚本和脱敏样例,不进入当前运行入口;新的容量验收必须针对现役编辑器、项目或素材接口单独设计,不能复用旧作品接口结论。
|
||||
|
||||
### 内存采样
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ GENARRATIVE_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS=600
|
||||
GENARRATIVE_EXTERNAL_GENERATION_WORKER_JOB_TIMEOUT_SECONDS=900
|
||||
GENARRATIVE_EXTERNAL_GENERATION_WORKER_LONG_JOB_TIMEOUT_SECONDS=1800
|
||||
GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512
|
||||
GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS=320
|
||||
GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS=64
|
||||
GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS=16
|
||||
GENARRATIVE_TRACKING_OUTBOX_ENABLED=true
|
||||
GENARRATIVE_TRACKING_OUTBOX_DIR=/var/lib/genarrative/tracking-outbox
|
||||
|
||||
@@ -139,32 +139,6 @@ services:
|
||||
- "${GENARRATIVE_CONTAINER_OTLP_GRPC_PORT:-4317}:4317"
|
||||
- "${GENARRATIVE_CONTAINER_OTLP_HTTP_PORT:-4318}:4318"
|
||||
|
||||
k6:
|
||||
image: grafana/k6:0.52.0
|
||||
profiles: ["loadtest"]
|
||||
cpus: "1.0"
|
||||
mem_limit: 512m
|
||||
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:
|
||||
spacetime-data:
|
||||
api-tracking-outbox:
|
||||
|
||||
@@ -21,7 +21,6 @@ http {
|
||||
}
|
||||
|
||||
limit_conn_zone $binary_remote_addr zone=genarrative_api_conn:10m;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_gallery_rps:10m rate=5000r/s;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_api_rps:10m rate=300r/s;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_admin_rps:10m rate=30r/s;
|
||||
|
||||
@@ -141,7 +140,7 @@ http {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:creation|editor/canvas|project)/?$" {
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project)/?$" {
|
||||
try_files $uri /index.html =404;
|
||||
}
|
||||
# END GENARRATIVE MAIN SPA ROUTES
|
||||
|
||||
Vendored
-5
@@ -18,8 +18,6 @@ GENARRATIVE_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS=600
|
||||
GENARRATIVE_EXTERNAL_GENERATION_WORKER_JOB_TIMEOUT_SECONDS=900
|
||||
GENARRATIVE_EXTERNAL_GENERATION_WORKER_LONG_JOB_TIMEOUT_SECONDS=1800
|
||||
GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512
|
||||
GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS=320
|
||||
GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS=64
|
||||
GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS=16
|
||||
GENARRATIVE_API_SHUTDOWN_OUTBOX_FLUSH_TIMEOUT_MS=5000
|
||||
GENARRATIVE_TRACKING_OUTBOX_ENABLED=true
|
||||
@@ -77,9 +75,6 @@ GENARRATIVE_LLM_PROVIDER=openai-compatible
|
||||
GENARRATIVE_LLM_BASE_URL=https://api.vectorengine.cn/v1
|
||||
GENARRATIVE_LLM_API_KEY=
|
||||
GENARRATIVE_LLM_MODEL=gpt-5.4-mini
|
||||
GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED=false
|
||||
GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=false
|
||||
|
||||
VECTOR_ENGINE_BASE_URL=https://api.vectorengine.cn
|
||||
VECTOR_ENGINE_API_KEY=
|
||||
VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000
|
||||
|
||||
@@ -14,7 +14,6 @@ upstream genarrative_api {
|
||||
}
|
||||
|
||||
limit_conn_zone $binary_remote_addr zone=genarrative_api_conn:10m;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_gallery_rps:10m rate=5000r/s;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_api_rps:10m rate=300r/s;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_admin_rps:10m rate=30r/s;
|
||||
|
||||
@@ -190,7 +189,7 @@ server {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:creation|editor/canvas|project)/?$" {
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project)/?$" {
|
||||
error_page 503 /maintenance.html;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
|
||||
@@ -12,7 +12,6 @@ upstream genarrative_api {
|
||||
}
|
||||
|
||||
limit_conn_zone $binary_remote_addr zone=genarrative_api_conn:10m;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_gallery_rps:10m rate=5000r/s;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_api_rps:10m rate=300r/s;
|
||||
limit_req_zone $binary_remote_addr zone=genarrative_admin_rps:10m rate=30r/s;
|
||||
|
||||
@@ -210,7 +209,7 @@ server {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:creation|editor/canvas|project)/?$" {
|
||||
location ~* "^/(?:creation|editor/canvas|profile|project)/?$" {
|
||||
error_page 503 /maintenance.html;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
|
||||
@@ -333,6 +333,29 @@
|
||||
"失败回退 `/index.html`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "profile_spa_fallback",
|
||||
"samplePath": "/profile",
|
||||
"expect": {
|
||||
"kind": "static",
|
||||
"root": "web",
|
||||
"mode": "spa_fallback"
|
||||
},
|
||||
"nginx": {
|
||||
"production": [
|
||||
"# BEGIN GENARRATIVE MAIN SPA ROUTES",
|
||||
"try_files $uri /index.html =404;"
|
||||
],
|
||||
"development": [
|
||||
"# BEGIN GENARRATIVE MAIN SPA ROUTES",
|
||||
"try_files $uri /index.html =404;"
|
||||
]
|
||||
},
|
||||
"docs": [
|
||||
"主站 SPA allowlist",
|
||||
"失败回退 `/index.html`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "web_spa_case_trailing_slash",
|
||||
"samplePath": "/PROJECT/",
|
||||
|
||||
@@ -62,12 +62,6 @@ GENARRATIVE_PINGORA_GATEWAY_PROTECTION_ENABLED=true
|
||||
GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_MAX_CONCURRENT=64
|
||||
GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_RATE_PER_SECOND=30
|
||||
GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_BURST=16
|
||||
GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_MAX_CONCURRENT=320
|
||||
GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_RATE_PER_SECOND=5000
|
||||
GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_BURST=4096
|
||||
GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_MAX_CONCURRENT=32
|
||||
GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_RATE_PER_SECOND=300
|
||||
GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_BURST=32
|
||||
GENARRATIVE_PINGORA_GATEWAY_API_MAX_CONCURRENT=64
|
||||
GENARRATIVE_PINGORA_GATEWAY_API_RATE_PER_SECOND=300
|
||||
GENARRATIVE_PINGORA_GATEWAY_API_BURST=64
|
||||
|
||||
@@ -653,6 +653,7 @@
|
||||
- 2026-06-20 桌面能力清单单测边界:Tauri `capabilities.rs` 必须用 Rust 单测同时覆盖桌面 runtime capability 清单顺序、无重复、真实桌面能力完整包含,并显式排除 `auth.requestLogin`、`payment.request`、`file.captureImage`、`scanner.scanQrCode` 和 `haptics.impact` 等未接入能力;桌面单端配置检查会反查该测试边界,避免只靠方案文档或共享 profile 发现桌面壳能力伪声明。
|
||||
- 2026-06-20 桌面本地通知契约镜像:Tauri `notification.showLocal` 的 title / body 归一化、长度上限和成功结果 action 必须镜像共享 HostBridge 契约;Rust 侧常量使用 `HOST_BRIDGE_LOCAL_NOTIFICATION_TITLE_MAX_LENGTH`、`HOST_BRIDGE_LOCAL_NOTIFICATION_BODY_MAX_LENGTH` 和 `HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_ACTION` 命名,桌面单端配置检查会与 `packages/shared/src/contracts/hostBridge.ts` 比对数值并反查成功结果由该 action 常量组装,避免通知 payload 边界变成桌面壳本地规则。
|
||||
- 2026-06-19 桌面壳外链打开 helper 共用:Tauri WebView 外域拦截和 HostBridge `app.openExternalUrl` 都必须复用 `open_normalized_desktop_external_url` 执行系统外链打开动作;HostBridge 分支仍先用 `normalize_external_url` 保留 payload 错误语义并把 opener 错误回传给 H5,WebView 拦截保持 best-effort 静默处理。桌面壳配置检查会拒绝 `dispatch.rs` 直接调用 `app.opener().open_url` 绕过该 helper,避免两条离壳路径漂移。
|
||||
> 2026-07-18 覆盖说明:本段后续关于微信 `navigation.openNativePage`、生成结果订阅页、`[subscribe-message]` 日志和订阅页路由门禁的 2026-06 决策均已由旧创作模板退役决策废止,只作为历史记录。Expo / Tauri 的同源 H5 受控导航及微信登录、支付、分享能力继续有效。
|
||||
- 2026-06-20 H5 原生导航预校验:`navigateHostNativePage()` 在 `native_app` 下发送 `navigation.openNativePage` 前必须先拒绝空值、控制字符、协议相对 URL、外域绝对 URL 和非 `http:` / `https:` 协议目标;同源绝对 URL、`/path` 和保留给桌面壳兼容的相对 route 继续交给 Expo / Tauri 壳二次归一并补写宿主上下文。微信小程序分支仍按小程序页面 URL 语义走 `wx.miniProgram.navigateTo`,不套原生 App 同源 H5 预校验。根级 `npm run check:native-shells` 会反查 H5 facade 仍使用 `normalizeNativeAppPageUrl(...)` 且发送归一后的 URL,避免明显不安全目标触达原生壳。
|
||||
- 2026-06-20 微信受控原生页能力声明:微信小程序壳真实 capability profile 声明 `navigation.openNativePage`,用于承接已经登记并测试的小程序原生页 flow;当前订阅生成结果通知页通过 H5 `requestGenerationResultSubscribePermission()` 调用 `navigateHostNativePage()` 打开 `/pages/subscribe-message/index`,小程序页再调用真实 `wx.requestSubscribeMessage` 并按既有结果协议回灌。根级 `npm run check:native-shells` 必须把该能力反查到共享 profile、微信 `WECHAT_HOST_CAPABILITIES` 镜像、订阅页协议常量、H5 入口、小程序 host-bridge / shell / page 文件和相关测试;该能力不代表开放任意小程序页面跳转。
|
||||
- 2026-06-18 能力声明收紧:`packages/shared/src/contracts/hostBridge.ts` 提供 HostBridge method / capability 白名单,H5 的 `getHostRuntime()` 会解析并过滤 `hostCapabilities`;`openHostShare`、`writeHostClipboardText`、`requestHostHapticsImpact`、`setHostAppTitle`、`exportHostTextFile` 等 native 能力只在宿主声明对应 capability 后调用。发布分享弹窗只有声明 `share.open` 时才显示受控分享动作,并按 `hostShell` 区分 Expo 系统分享面板和 Tauri 剪贴板复制表达,避免旧壳或裁剪壳露出不可用入口。
|
||||
@@ -781,6 +782,8 @@
|
||||
|
||||
## 2026-06-17 H5 宿主壳能力统一走 HostBridge
|
||||
|
||||
> 2026-07-18 覆盖说明:以下订阅授权、订阅页和旧玩法导航部分已退役;登录、支付、分享、九宫切图与通用 HostBridge 分层仍有效。
|
||||
|
||||
- 背景:主站同时运行在普通浏览器、微信小程序 `web-view` 和未来可能出现的原生 App WebView 中;登录、支付、分享、订阅授权和运行态分享目标同步曾散落在业务组件与服务文件里,后续新增宿主壳会导致同一业务重复分叉。
|
||||
- 决策:前端宿主运行态识别、微信小程序 JS SDK 加载、原生页跳转、支付跳转、登录跳转、九宫切图和 `postMessage` 统一收口到 `src/services/host-bridge/hostBridge.ts`,业务层优先调用 `getHostRuntime`、`requestHostLogin`、`requestHostPayment`、`navigateHostNativePage`、`setHostShareTarget` 和 `openHostShareGrid`。`authService`、分享服务、订阅授权和个人中心充值可保留兼容导出或业务编排,但不再自行加载微信 JS SDK 或直接判断 `wx.miniProgram`。固定内置玩法不走代码包下载流程;AI 生成 H5 沙箱后续单独定义受限 `GameBridge`,不得直接暴露完整 `HostBridge`。
|
||||
- 影响范围:`src/services/host-bridge/`、`src/services/authService.ts`、`src/services/payment/paymentPlatform.ts`、`src/services/wechatMiniProgramShareGrid.ts`、`src/services/wechatMiniProgramShareTarget.ts`、`src/services/wechatMiniProgramSubscribe.ts`、`src/components/platform-entry/usePlatformProfileCenterController.ts`、微信小程序壳和未来原生 App 壳接入。
|
||||
@@ -4222,4 +4225,14 @@
|
||||
- 背景:旧创作模板退役时误把新版 `/creation`、桌面公共侧边栏和“我的”完整资料页一起缩减;只恢复视觉后,现役 profile client 又经 `rpg-entry` barrel 把旧作品库、旧 runtime request 和展示模型重新带入 Vite 与 TypeScript 图。
|
||||
- 决策:桌面端继续使用原平台公共结构,一级导航固定为 `创作 / 项目 / 我的`;顶栏保留编辑器项目 / 素材搜索、泥点入口和账号胶囊;“我的”全宽保留资料编辑、陶泥号、三项统计、充值、兑换码、社区、反馈、通用设置、API Key 和法律信息。搜索只面向编辑器项目与公开编辑器素材,不恢复旧公开作品搜索。
|
||||
- 依赖边界:公共 dashboard、钱包、充值、兑换码、邀请码、API Key 和设置请求迁入 `services/platform-entry`,公共账单展示迁入现役 profile model。Vite 新增退役模块 graph 门禁,ESLint 对现役源码禁止导入旧目录;目录 watch ignore、Tailwind source、tsconfig include 和 tree-shaking 都不能作为依赖隔离证明。
|
||||
- 影响范围:`PlatformEntryActiveFlowShell`、`PlatformActiveProfileView`、编辑器 / 项目搜索、平台 profile clients、Vite / ESLint 门禁及旧业务退役方案。
|
||||
- 路由与响应式边界:`/creation`、`/project`、`/profile` 都是可刷新、可前进 / 后退的稳定路由;桌面端使用侧边栏,移动端必须提供同样 `创作 / 项目 / 我的` 的三项底部 dock,不得因隐藏桌面侧边栏而丢失移动导航。
|
||||
- 公共设置边界:`runtime_setting` 保持原表结构与历史数据,但它是音乐音量和平台主题的现役账号级公共能力,不归入旧玩法数据壳。鉴权后的 `GET/PUT /api/runtime/settings` 必须经 `spacetime-client` 调用 `get_runtime_setting_or_default` / `upsert_runtime_setting_and_return`;保留该路由不构成恢复旧 runtime API 的先例。
|
||||
- 编译门禁:除旧业务目录外,`src/uiAssets.ts`、`src/types.ts`、`src/types/**`、`src/services/runtimeAudioFeedback.ts` 和 `src/services/publicWorkCode.ts` 也是顶层退役 module,必须同时退出 Vite module graph、TypeScript、ESLint 和 Vitest;`/audio/**`、`/chat.png`、`/fusion-pixel.ttf` 及旧 pixel / story-tab / 玩法 CSS 不得进入 dev 服务或生产产物。验收时必须同时检查 `tsc --listFilesOnly`、Vite 依赖图 / 产物和退役资产路径,不能只依赖 tree-shaking。
|
||||
- Rust 产物边界:`module-runtime` 继续承载账号、钱包、公共设置、追踪和 feature gate,但 `CreationEntry*`、旧公开作品、存档、浏览历史与游玩统计 DTO / command / mapper / 规则必须退出实际 rlib;只保留历史表需要的 `RuntimeBrowseHistoryThemeMode`、完整保序的钱包流水来源枚举等持久化 ABI。`check:server-rs-ddd` 必须执行 `check:module-runtime-artifact`,同时验证旧符号和字面量为零、必要 ABI 仍存在,不能以源码存在 `#[cfg(any())]` 或路由未挂载代替产物证明。
|
||||
- 外围编译边界:`platform-auth` 不再编译 runtime guest token,`platform-wechat` 不再编译旧玩法生成结果订阅消息,小程序不再注册订阅授权页;旧公开作品资产授权 view 退出 SpacetimeDB module,匿名素材读取只保留现役 editor showcase 派生授权。
|
||||
- 历史队列边界:现役 external generation worker 只领取 `source_module = editor-canvas` 的任务,历史旧玩法 pending / running 行保持原状态,不得被新 worker 领取后改写为失败。
|
||||
- Agent crate 边界:`platform-agent` 的执行器、工具注册表、回调和拼图 Phase 1 输入均属于已退役 Creative Agent 业务,不得因现役编辑器 Agent 共用一个模型名常量而留在 workspace 或 `api-server` 依赖图。该常量收口到 `platform-llm`,`platform-agent` 与仅由它引入的 `langchainrust` 退出在运 Cargo resolve graph,源码目录继续仅作历史追溯。
|
||||
- 防回流补充:顶层 `creationEntryConfigService`、`creationUrlState`、`customWorld*`、`runtimeGuestAuth`、`runtimeRequest`、`input-devices`、`useCombatFlow`、`useStoryOptions`、`useMocapInput` 和微信生成订阅 facade 同样属于退役前端模块;Vite dev 对旧 `/api/creation*` 与 `/api/public-works*` 前缀直接返回 404,不能回落 SPA HTML。
|
||||
- Vite 全量边界补充:`src/games/**`、`src/data/**`、`src/prompts/**`、旧顶层 App / Playground、旧路由和 `services/ai.ts` 必须由 pre-transform 门禁直接拒绝;所有同源 `/generated-*` 裸读在 dev 与生产统一为空 `404`,历史对象只经现役签名读取接口兼容,不允许 SPA fallback 伪装成资产成功响应。
|
||||
- 前端混合根目录补充:`src/components`、`src/hooks`、`src/persistence`、`src/routing`、`src/services` 的根级文件实行现役白名单,Vite 与 ESLint 使用同一口径阻断旧 RPG / 玩法根文件;子目录仍按现役目录和退役目录分别管理,新增公共根文件必须显式登记。
|
||||
- 影响范围:`PlatformEntryActiveFlowShell`、`PlatformActiveProfileView`、编辑器 / 项目搜索、平台 profile clients、`module-runtime`、`platform-llm`、Cargo workspace / resolve graph、Vite / ESLint / Rust 产物门禁及旧业务退役方案。
|
||||
|
||||
@@ -186,7 +186,7 @@ npm run check:rustfmt
|
||||
cargo fmt --all --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
- 后端通用用户行为埋点统一通过 `record_tracking_event_and_return` procedure、`SpacetimeRuntimeClient::record_tracking_event(...)` 与 api-server `tracking` 中间件写入 `tracking_event` / `tracking_daily_stat`;后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 默认排除;作品级游玩埋点统一使用 `work_play_start`,详细事件清单见 `docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md`。
|
||||
- 后端通用用户行为埋点统一通过 `record_tracking_event_and_return` procedure、`SpacetimeRuntimeClient::record_tracking_event(...)` 与 api-server `tracking` 中间件写入 `tracking_event` / `tracking_daily_stat`;现役账号、钱包、编辑器、项目、精选素材和公共设置路由按 `tracking.rs` 的显式静态路由表记录,后台路由默认排除。旧玩法、公开作品和专属运行态路由已经退役,不再维护作品级游玩埋点覆盖。
|
||||
|
||||
编码检查:
|
||||
|
||||
@@ -231,23 +231,15 @@ npm run check:native-shells
|
||||
```
|
||||
|
||||
该命令会覆盖 H5 HostBridge 关键测试、微信 / Expo / Tauri 三端桥接层文件结构门禁、完整相对路径文档反查、微信 capability 到真实 WebView / 支付 / 分享页面流程和测试清单的映射门禁、H5 HostBridge 事件订阅双能力门控反查、H5 `navigation.canGoBack` 消费 hook 与直达二级页返回锚点测试、移动端和桌面端单端源码清单门禁、Expo 壳 typecheck / test / EAS build config smoke / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面壳 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描,确认 Expo managed config、移动端 EAS 原生包构建 profile、移动端 iOS / Android production bundle、打包 H5 资产、Tauri release 入口、H5 页面内导航保留完整原生宿主上下文和 H5 HostBridge 真实调用链没有漂移;扫描范围包含微信小程序壳生产 `.js`、Tauri `Info.plist`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件,但不扫描 Expo export、Tauri `target/`、Cargo / Metro 缓存或 release 构建产物。移动壳配置检查必须反查 EAS 生产 profile、文本 / 文档 / 图片 / 音频导入边界都来自共享 HostBridge 契约。登录与支付外链跳转必须保持在该调用链扫描内,`src/services/authService.ts` 和 `src/services/payment/paymentRedirect.ts` 是必扫文件;`AuthGate` 的登录成功、退出登录、身份边界刷新和登录状态异常重试都必须通过 `app.reloadWebView` 优先路径,并由 `src/components/auth/AuthGate.test.tsx` 进入该门禁。壳源码和配置继续严格禁止 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时;H5 业务调用链允许正常表单 `placeholder` 属性、业务占位图文案和真实兼容 / 故障语义中的“未实现”“临时”表述,但仍禁止 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。
|
||||
创作 Agent 原生壳文档导入优先走 `file.importDocument`,旧壳只声明 `file.importText` 时才回退文本导入;相关变更必须让根级和单端门禁覆盖共享 method、capability profile、文档 MIME / 5 MiB 上限、读取前 size 校验,以及 H5 base64 转 `File` 后继续走后端文档解析的链路。
|
||||
创作 Agent 参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后继续交给既有 `onReferenceImageChange` 校验链路;用户取消原生选择不应再连带弹出浏览器文件输入,普通浏览器、小程序和未声明能力的裁剪壳才使用原隐藏文件输入。
|
||||
创作 Agent 轻输入 composer 的参考图按钮在原生壳声明 `file.importImage` 时必须优先走宿主图片导入;移动壳声明 `file.captureImage` 时才显示拍摄参考图入口,并把宿主图片同样转为 `File` 后复用 `readPuzzleReferenceImageAsDataUrl` 的类型、大小、压缩和预览链路。
|
||||
反馈页上传凭证在原生壳声明 `file.importImage` 时必须优先走宿主图片导入;移动壳声明 `file.captureImage` 时才显示拍摄凭证入口,并把拍摄图片同样转为 `File` 后复用反馈页原有数量、大小、MIME、data URL 预览和提交 payload 校验。
|
||||
固定内置 H5 体验入口在原生壳声明 `navigation.openNativePage` 时必须优先走 `navigateHostNativePage()`;例如儿童动作热身 Demo 从平台首页进入 `/child-motion-demo` 时应由 HostBridge 发出 `navigation.openNativePage`,宿主不可用时才回退浏览器跳转。
|
||||
Expo / Tauri 声明 `navigation.openNativePage` 时,只用于现役同源 H5 路由的受控导航和宿主上下文续接;微信小程序不再声明该能力。旧儿童动作 Demo、模板工作台、生成页、结果页和运行态不得作为 HostBridge 导航验收入口。
|
||||
H5 支付链接跳转在原生壳声明 `app.openExternalUrl` 时必须优先走宿主系统浏览器;原生壳未接真实支付 SDK 前不得声明 `payment.request`,也不得把外部 H5 支付跳转伪装成原生支付成功。
|
||||
微信 OAuth 登录授权 URL 在原生壳声明 `app.openExternalUrl` 时必须优先走宿主系统浏览器;原生壳未接真实登录 SDK 前不得声明 `auth.requestLogin`,也不得把网页登录跳转伪装成原生登录成功。
|
||||
汪汪声浪结果页玩家 / 对手 / UI 背景三图槽位上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后继续交给 `uploadBarkBattleAsset` 与当前槽位写回链路;用户取消原生选择不应再连带弹出浏览器文件输入,普通浏览器、小程序和未声明能力的裁剪壳才使用原隐藏文件输入。
|
||||
抓大鹅结果页发布封面图和封面参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有封面 data URL 读取、AI 重绘开关、参考图集合和封面生成 payload 链路;用户取消原生选择不应再连带弹出浏览器文件输入。
|
||||
RPG 角色资产工作室的角色参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有 `readFileAsDataUrl` 参考图集合和角色形象生成 payload 链路;用户取消原生选择不应再连带弹出浏览器文件输入。
|
||||
RPG 作品封面上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有 10 MiB 校验、图片尺寸读取、16:9 裁剪和 `uploadCustomWorldCoverImage` 保存链路;用户取消原生选择不应再连带弹出浏览器文件输入。
|
||||
RPG 作品封面参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有 `readImageFileAsDataUrl` 读取、预览和 `generateCustomWorldCoverImage` payload 链路;用户取消原生选择不应再连带弹出浏览器文件输入。
|
||||
RPG 场景图片参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有 `readImageFileAsDataUrl` 读取、预览和 `rpgCreationAssetClient.generateSceneImage` payload 链路;用户取消原生选择不应再连带弹出浏览器文件输入。
|
||||
现役编辑器与个人中心新增文件导入能力时,继续复用共享 `file.importDocument`、`file.importImage`、`file.captureImage` 和 MIME / 大小门禁,不得把已退役模板的上传 service、玩法 DTO 或页面测试重新纳入原生壳门禁。
|
||||
视觉小说结果页封面 / 角色 / 场景图片和音乐 / 环境音上传在原生壳声明 `file.importImage` / `file.importAudio` 时必须优先走宿主受控导入,并把 H5 base64 转 `File` 后继续交给 `uploadVisualNovelAsset` 与当前素材字段写回链路;用户取消原生选择不应再连带弹出浏览器文件输入,历史素材选择和 AI 图片生成保持原链路。
|
||||
该命令会反查微信小程序 `WECHAT_HOST_CAPABILITIES` 与共享 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES` 一致;小程序生产代码继续保留 CommonJS 运行时镜像,不直接 import TypeScript shared 包。
|
||||
该命令同时会运行微信小程序 `miniprogram/host-bridge/`、`miniprogram/shell/`、`pages/web-view` 样式和 `scripts/miniprogram-web-view-auth.test.ts` 的壳层测试,保证微信桥接层拆分后的支付、订阅消息、九宫切图、分享目标和 WebView 登录 / 分享入口行为与 Expo、Tauri 壳一起验收。
|
||||
该命令还会反查微信小程序 `app.json.pages` 与 `host-bridge/protocol.js` 页面 URL、H5 小程序页面常量、H5 订阅授权页面常量、WebView 分享入口、分享目标消息类型、`WEB_VIEW_SOURCE_QUERY`、微信请求头运行时标记、H5 runtime parser、H5 路由保留字段和 H5 / API base URL 格式,避免页面路由、来源标记、宿主上下文 query 或域名配置在微信壳、H5 HostBridge 与运行时配置之间分叉。生产 / 开发 H5 与 API 域名都必须显式配置为纯 HTTPS domain,运行时开发域名回退生产域名只作为异常兜底。
|
||||
该命令还会反查微信小程序 `app.json.pages` 与 `host-bridge/protocol.js` 现役页面 URL、H5 小程序页面常量、WebView 分享入口、分享目标消息类型、`WEB_VIEW_SOURCE_QUERY`、微信请求头运行时标记、H5 runtime parser、H5 路由保留字段和 H5 / API base URL 格式,避免页面路由、来源标记、宿主上下文 query 或域名配置在微信壳、H5 HostBridge 与运行时配置之间分叉。旧生成结果订阅授权页和对应 H5 service 不再进入清单。生产 / 开发 H5 与 API 域名都必须显式配置为纯 HTTPS domain,运行时开发域名回退生产域名只作为异常兜底。
|
||||
|
||||
内容检查:
|
||||
|
||||
@@ -314,8 +306,8 @@ npm run check:server-rs-ddd
|
||||
|
||||
- 移动端优先,再兼容网页端。
|
||||
- 页面只展示后端返回的状态,不自行计算结论型业务状态。
|
||||
- 创作中心入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;前端只在 `platformEntryCreationTypes.ts` 做展示派生,api-server 路由熔断也使用同一份配置,禁止恢复前端硬编码入口配置文件。底部加号创作入口页公告位也跟随后端 `eventBanners` 配置,前端只做展示和轮播;后台公告用表单维护标题与 HTML 内容,保存时再序列化为后端 `eventBannersJson` 传输字段。`最近创作` 不属于模板分类,不能作为分类缺失兜底;生成中和生成失败的真实草稿摘要都应进入最近创作。
|
||||
- 一期统一创作页字段 spec 同样跟随 `GET /api/creation-entry/config`,由 `creationTypes[].unifiedCreationSpec` 下发;拼图、抓大鹅、敲木鱼之外的模板不接入该扩展位,前端只保留旧后端缺字段时的兜底默认。
|
||||
- 现役一级入口为 `/creation`、`/project`、`/profile`,桌面侧边栏和移动端底部 dock 都固定显示“创作 / 项目 / 我的”。`/creation` 只读取图片编辑器项目与 `GET /api/editor/showcase/resources`,不得重新接入旧模板入口配置、旧作品架或专属运行态。
|
||||
- 旧创作模板目录和顶层旧业务模块必须持续退出 Vite、TypeScript、ESLint 与 Vitest;旧 `/api/creation-entry/config`、模板 API、公开作品详情和运行态 API 必须保持未挂载。SpacetimeDB 历史表、迁移白名单与必要兼容类型只作为数据壳保留,不得据此恢复业务逻辑。
|
||||
- 优先复用现有面板、抽屉、弹窗,不新建独立大系统。
|
||||
- 不在 UI 中默认写功能说明类文本。
|
||||
- 弹出独立面板的交互不要实现成在当前面板下方追加内容。
|
||||
@@ -329,7 +321,7 @@ npm run check:server-rs-ddd
|
||||
|
||||
## 提交前建议让 Agent 执行
|
||||
|
||||
涉及拼图、抓大鹅、敲木鱼统一创作 / 生成链路、Phase 2 之后的跨玩法回归或本地 dev 栈时,先按 `quality-gates/README.md`、`quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md` 和对应单项门禁文档执行自动脚本与体验检查。
|
||||
涉及现役编辑器、项目、精选素材、账号、钱包、公共设置或本地 dev 栈时,按对应定向测试、类型检查、Rust / SpacetimeDB 门禁、真实浏览器 smoke 与本文件当前命令验收。旧跨玩法回归和单玩法质量门禁只作历史记录,不再作为提交前现役检查入口。
|
||||
|
||||
```text
|
||||
请检查当前 git diff,指出:
|
||||
|
||||
@@ -2929,6 +2929,8 @@
|
||||
|
||||
## 小程序订阅消息授权不要依赖 web-view bindmessage
|
||||
|
||||
> 2026-07-18:本节及下一节只作为历史记录。生成结果订阅页、H5 service、HostBridge capability 和后端发送链路已随旧创作模板业务退役,不得按这些排障步骤恢复。
|
||||
|
||||
- 现象:拼图点击生成后,H5 以为已经请求了生成结果订阅授权,但小程序没有弹出 `wx.requestSubscribeMessage` 授权框。
|
||||
- 原因:`web-view bindmessage` / `wx.miniProgram.postMessage` 不适合承接“当前用户点击后立刻请求授权”的时序,消息可能等到 web-view 后退、分享或销毁时才派发,导致授权请求没有发生在 `compile_puzzle_draft` 前。
|
||||
- 处理:不要在原生页 `onLoad` 自动触发 `wx.requestSubscribeMessage`,真机会闪页返回且不弹授权框。H5 在 `compile_puzzle_draft` 前应先进入生成进度态并立即发起生成 action,再通过微信 JS SDK `miniProgram.navigateTo` 非阻塞跳转到小程序原生订阅页尝试请求授权;用户接受、拒绝或返回都不能阻塞生成。原生页不要改写上一页 `webViewUrl`,否则 web-view 可能重新加载首页并丢失进度页状态。后端发送订阅消息仍只允许在拼图资产成功或失败终态后执行。
|
||||
@@ -2937,6 +2939,8 @@
|
||||
|
||||
## 微信订阅消息 time 字段不能用内部时间戳
|
||||
|
||||
> 2026-07-18:该能力已退役,本节不再作为现役排障入口。
|
||||
|
||||
- 现象:dev 服务器拼图资产生成终态后已经调用订阅消息发送,但日志出现 `微信订阅消息发送失败:argument invalid! data.time4.value invalid`,用户收不到生成结果通知。
|
||||
- 原因:微信模板 `time` 字段不接受内部微秒时间戳、秒级时间戳或带 `Z` / 时区后缀的字符串;发送 `1713686401.234567Z` 或类似 `2026-06-08 08:09:18Z` 会被微信拒绝。
|
||||
- 处理:`api-server` 构造生成结果订阅消息时,`time4` 固定格式化为北京时间 `YYYY-MM-DD HH:mm`;不要复用 `shared_kernel::format_timestamp_micros`。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Genarrative 项目共享概览
|
||||
|
||||
更新时间:`2026-07-17`
|
||||
更新时间:`2026-07-18`
|
||||
|
||||
## 一句话定位
|
||||
|
||||
@@ -21,7 +21,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
|
||||
- 小程序 WebView 外壳:`miniprogram/`。
|
||||
- 法律文本:`media/files/user_agreement.md`、`media/files/privacy_policy.md`、`media/files/disclaimer.md`。
|
||||
|
||||
移动端一级 Tab:`推荐 / 发现 / 我的`。桌面端导航保留 `创作 / 项目 / 我的`,其中 `/creation` 是独立创作工具主页,`/project` 是画布项目入口,“我的”继续承载账号、钱包、统计和通用设置等平台公共能力。
|
||||
桌面端侧边栏和移动端底部 dock 的一级入口统一为 `创作 / 项目 / 我的`。`/creation` 是独立创作工具主页,`/project` 是画布项目入口,`/profile` 是“我的”稳定路由,继续承载账号、钱包、统计和通用设置等平台公共能力;刷新及浏览器前进 / 后退必须保持当前入口与选中态一致。
|
||||
|
||||
## 当前后端路线
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 【前端架构】Platform Selection Stage Model 收口计划
|
||||
|
||||
> 2026-07-18 退役覆盖:本文涉及旧玩法 stage、生成页、结果页、公开详情和运行态的规则仅作为历史设计记录,不再进入现役前端编译链。当前稳定入口为 `/creation`、`/project` 与 `/profile`。
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 在受保护数据失效后会清空当前用户的私有作品、运行态、草稿 notice 和生成状态。清理完成后,壳层还要判断当前 `SelectionStage` 是否还能继续展示:公开首页、公开详情、工作台入口等阶段可保留;结果页、生成页、运行态、个人反馈等依赖私有数据或运行态快照的阶段必须回到首页。
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 外部生成 Worker 化方案
|
||||
|
||||
> 2026-07-18 退役覆盖:旧创作模板 job 类型、玩法写回和玩法恢复链路均已退出现役 worker。当前 worker 只领取 `source_module = editor-canvas` 的任务;本文涉及拼图、跳一跳、拼消消、敲木鱼等玩法的内容仅作为历史设计记录,历史队列行不得被领取或改写。
|
||||
|
||||
更新时间:`2026-07-15`
|
||||
|
||||
## 背景
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 统一公开作品 ReadModel 设计
|
||||
|
||||
> 2026-07-18 退役覆盖:统一公开作品 BFF、read model、逐玩法 source view 和互动链路均已退出现役编译与路由。相关历史表只作为 schema 数据壳保留;新版 `/creation` 只读取编辑器精选素材。本文其余内容仅作为历史设计记录。
|
||||
|
||||
更新时间:`2026-05-26`
|
||||
|
||||
## 背景
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
- 代理路径上的上游连接失败会返回统一 JSON;`502` 使用 `GATEWAY_UPSTREAM_ERROR`,`504` 使用 `GATEWAY_UPSTREAM_TIMEOUT`,避免 shadow / canary 阶段把框架默认错误页透给前端或巡检。
|
||||
- 上游超时显式配置在 Pingora peer 上:连接超时默认 `3000ms`,没有 Nginx 显式长超时的代理路由读取默认 `60s`,通用 `/api`、公开列表 / 详情和 SpacetimeDB subscribe 读取默认 `3600s`,写入超时默认 `3600s`。读 / 写 / 连接超时统一映射为 JSON `504 GATEWAY_UPSTREAM_TIMEOUT`。
|
||||
- 默认开启 gzip 响应压缩,`GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5` 和 `GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 对齐当前 Nginx `gzip_comp_level 5` / `gzip_min_length 1024`;`GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=false` 时禁用。`GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip` 是当前唯一允许的压缩算法白名单;网关会在进入 Pingora compression 模块前把 `Accept-Encoding` 收敛为 gzip,避免未验收的 `br` / `zstd` 被隐式打开。压缩能力由 `check:pingora-gateway-smoke` 用小响应不压缩、图片资源不压缩、大响应 `Accept-Encoding: gzip`、`Accept-Encoding: br, gzip`、`Content-Encoding: gzip`、`Vary: Accept-Encoding` 和解压后的响应体一起验证。Brotli 不进入当前 Pingora 正式化口径,仍由 Nginx / 前置代理能力探测承担;直连 Pingora 时不把 Brotli parity 作为切换门禁。
|
||||
- 默认开启单进程内接流保护,按客户端 IP 与路由组执行并发上限、RPS 和 burst 限制。保护组默认对齐当前 Nginx:`admin_api=64/30rps/16burst`、`gallery_list=320/5000rps/4096burst`、`gallery_detail=32/300rps/32burst`、`api=64/300rps/64burst`、`spacetime=256/1000rps/256burst`。超限返回 JSON `429` 并带 `Retry-After: 1`。
|
||||
- 默认开启单进程内接流保护,按客户端 IP 与路由组执行并发上限、RPS 和 burst 限制。保护组默认对齐当前 Nginx:`admin_api=64/30rps/16burst`、`api=64/300rps/64burst`、`spacetime=256/1000rps/256burst`。超限返回 JSON `429` 并带 `Retry-After: 1`。
|
||||
- 当前接流保护的默认正式口径是单 Pingora 实例。`GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT` 默认 `1`;若 `GENARRATIVE_PINGORA_GATEWAY_PROTECTION_ENABLED=true` 且 `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1`,必须先落地共享限流 / 共享并发保护层,并显式设置 `GENARRATIVE_PINGORA_GATEWAY_SHARED_PROTECTION_CONFIRMED=true`,否则网关启动和目标机 direct preflight 都会失败。若关闭网关保护后横向多实例运行,则全局接流保护必须由前置 Nginx / LB 承担。
|
||||
- 默认以 TCP 对端 IP 作为限流 client key;只有在 Pingora 前置代理已经清洗 `X-Forwarded-For` 时,才允许开启 `GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR=true` 使用首个转发 IP。开启时必须同时设置 `GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED=true`,否则网关会拒绝启动。若 Pingora 直接监听公网地址,`TRUST_X_FORWARDED_FOR` 必须保持 `false`,目标机 direct preflight 会在看到公网监听加该开关时失败。
|
||||
- 可选配置 `GENARRATIVE_PINGORA_GATEWAY_GITEA_HOSTS` 和 `GENARRATIVE_PINGORA_GATEWAY_GITEA_UPSTREAM` 后,网关会先按 `Host` 归一化匹配 Gitea 域名,命中时整站代理到 Gitea 上游。Gitea 路由不走应用维护页、API 请求体上限或网关接流保护,避免影响 git clone / push。用于同一公网 IP 同时承载 `dev.genarrative.world` 与 `git.genarrative.world` 的直连切换时,TLS 证书必须同时覆盖两个域名;当前单 listener 配置只加载一组 cert/key。
|
||||
@@ -70,7 +70,7 @@ npm run check:pingora-release-readiness
|
||||
|
||||
`check:pingora-canary-docker` 会启动 mock `api-server`、mock SpacetimeDB、真实 `pingora-gateway` 和 Docker Nginx,把前缀 canary 与真实路径 canary 两份 snippet 都渲染到临时 Nginx 中,再复用 `check:pingora-canary-live` 验证 Nginx -> Pingora -> 上游的 handoff 链路。临时 Nginx 使用生产同口径 `genarrative_upstream` access log,live smoke 后会继续调用 `scripts/check-pingora-canary-access-log-parity.mjs`,按同一 `request_id` 对账 canary healthz、代表性 API、SpacetimeDB identity 和静态资源路径;前缀模式确认 Nginx rewrite 后路径与 Pingora access log 一致,真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。默认不拉取镜像;缺少 Docker daemon 或 `nginx:1.27-alpine` 镜像时跳过。CI / 目标 agent 上需要把它作为硬门禁时执行 `node scripts/check-pingora-canary-docker.mjs --require-docker --pull`。
|
||||
|
||||
`check:pingora-canary-access-log-parity` 会烟测只读脚本 `scripts/check-pingora-canary-access-log-parity.mjs`,用于目标机 canary 后按 `request_id` 对照 Nginx handoff access log 和 Pingora tab-separated access log。真实目标机前缀 canary 执行时使用 `/opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --nginx-log-file /var/log/nginx/genarrative.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_canary/healthz --path /__genarrative_pingora_canary/api/creation-entry/config`;真实路径 canary 执行时追加 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/creation-entry/config --path /v1/identity --path /assets/app.js`。脚本只读日志,不修改日志、不 reload Nginx 或 Pingora。
|
||||
`check:pingora-canary-access-log-parity` 会烟测只读脚本 `scripts/check-pingora-canary-access-log-parity.mjs`,用于目标机 canary 后按 `request_id` 对照 Nginx handoff access log 和 Pingora tab-separated access log。真实目标机前缀 canary 执行时使用 `/opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --nginx-log-file /var/log/nginx/genarrative.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_canary/healthz --path /__genarrative_pingora_canary/api/editor/showcase/resources`;真实路径 canary 执行时追加 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/editor/showcase/resources --path /v1/identity --path /assets/app.js`。脚本只读日志,不修改日志、不 reload Nginx 或 Pingora。
|
||||
|
||||
`check:pingora-direct-preflight` 默认只检查仓库内主 service、direct-entry drop-in 和 env 示例,适合本机提交前护栏。目标机直连切换窗口必须提供真实 env 并打开现场检查:
|
||||
|
||||
@@ -351,8 +351,8 @@ node -- /opt/genarrative/current/scripts/deploy/pingora-health-patrol-env-switch
|
||||
4. 在 `server {}` 内人工 include 该 snippet,并保持 `allow 127.0.0.1; allow ::1; deny all;` 或改成当次可信来源。
|
||||
5. 执行 `npm run check:nginx-pingora-canary`;目标机或 CI 有 Nginx 时执行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,再执行 `nginx -t && nginx -s reload`。
|
||||
6. 执行 `GENARRATIVE_PINGORA_CANARY_BASE_URL=http://127.0.0.1 GENARRATIVE_PINGORA_CANARY_HOST=<域名> npm run check:pingora-canary-live`,确认 healthz、API、SpacetimeDB identity、静态资源和拒绝入口都带 `X-Genarrative-Nginx-Handoff: pingora-canary`。
|
||||
7. 必要时再用同一前缀访问其它代表性路由,例如 `/__genarrative_pingora_canary/api/creation-entry/config`、`/__genarrative_pingora_canary/v1/identity` 和 `/__genarrative_pingora_canary/assets/app.js`,并对照直连 Nginx 正常入口和 Pingora shadow 日志。
|
||||
8. 执行 current release 随包 `scripts/check-pingora-canary-access-log-parity.mjs --nginx-log-file /var/log/nginx/genarrative.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_canary/healthz --path /__genarrative_pingora_canary/api/creation-entry/config`,确认同一 `request_id`、`path`、`status` 和 `proxy_target` 与 Nginx access log 可对齐。对账脚本的日志路径、canary prefix、必需路径和 `--since-lines` / `GENARRATIVE_PINGORA_CANARY_ACCESS_LOG_SINCE_LINES` 都不能包含换行或 NUL;若 access log 行里解析出的 URI / path 含控制字符,脚本也会把对应行记为失败,避免污染值进入 JSON 对账输出。
|
||||
7. 必要时再用同一前缀访问其它代表性路由,例如 `/__genarrative_pingora_canary/api/editor/showcase/resources`、`/__genarrative_pingora_canary/v1/identity` 和 `/__genarrative_pingora_canary/assets/app.js`,并对照直连 Nginx 正常入口和 Pingora shadow 日志。
|
||||
8. 执行 current release 随包 `scripts/check-pingora-canary-access-log-parity.mjs --nginx-log-file /var/log/nginx/genarrative.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_canary/healthz --path /__genarrative_pingora_canary/api/editor/showcase/resources`,确认同一 `request_id`、`path`、`status` 和 `proxy_target` 与 Nginx access log 可对齐。对账脚本的日志路径、canary prefix、必需路径和 `--since-lines` / `GENARRATIVE_PINGORA_CANARY_ACCESS_LOG_SINCE_LINES` 都不能包含换行或 NUL;若 access log 行里解析出的 URI / path 含控制字符,脚本也会把对应行记为失败,避免污染值进入 JSON 对账输出。
|
||||
9. 验证结束后移除 include 并 reload Nginx;不要把该前缀入口当作正式公网 URL。
|
||||
|
||||
## dev shadow service 验收记录
|
||||
@@ -499,7 +499,7 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_CONNECT_TIMEOUT_MS` | `3000` | 连接上游的超时,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_DEFAULT_READ_TIMEOUT_SECONDS` | `60` | 没有 Nginx 显式长超时的代理路由读取超时,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_API_READ_TIMEOUT_SECONDS` | `3600` | 通用 `/api` 路由读取超时,对齐当前 Nginx `proxy_read_timeout 3600s`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_LONG_READ_TIMEOUT_SECONDS` | `3600` | 公开列表 / 详情和 SpacetimeDB subscribe 长连接读取超时。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_LONG_READ_TIMEOUT_SECONDS` | `3600` | SpacetimeDB subscribe 长连接读取超时。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_WRITE_TIMEOUT_SECONDS` | `3600` | 写上游请求头 / 请求体超时,对齐当前 Nginx `proxy_send_timeout 3600s` 口径。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR` | `false` | 是否用 `X-Forwarded-For` 首个 IP 作为接流保护 client key;公网直连 Pingora 时必须保持 `false`,direct preflight 会阻断公网监听误开启。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED` | `false` | 开启 `TRUST_X_FORWARDED_FOR` 时必须显式设为 `true`,表示前置代理会清洗 `X-Forwarded-For`。 |
|
||||
@@ -509,12 +509,6 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_MAX_CONCURRENT` | `64` | `/admin/api/*` 每 client 并发上限;`0` 表示不限制并发。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_RATE_PER_SECOND` | `30` | `/admin/api/*` 每 client token bucket 回填速率;`0` 表示不限制 RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_BURST` | `16` | `/admin/api/*` 每 client 额外 burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_MAX_CONCURRENT` | `320` | 公开列表路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_RATE_PER_SECOND` | `5000` | 公开列表路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_LIST_BURST` | `4096` | 公开列表路由每 client burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_MAX_CONCURRENT` | `32` | 公开详情兼容路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_RATE_PER_SECOND` | `300` | 公开详情兼容路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GALLERY_DETAIL_BURST` | `32` | 公开详情兼容路由每 client burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_MAX_CONCURRENT` | `64` | 通用 `/api` 路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_RATE_PER_SECOND` | `300` | 通用 `/api` 路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_BURST` | `64` | 通用 `/api` 路由每 client burst。 |
|
||||
@@ -536,13 +530,11 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
| `/admin/assets/*` | 从 Web 根目录精确读取静态文件;带 Vite 指纹的文件默认长期缓存,其它文件默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/admin/*` | 先读取静态文件或目录 index,失败回退 `/admin/index.html`,HTML 默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/assets/*` | 从 Web 根目录精确读取静态文件;带 Vite 指纹的文件默认长期缓存,其它文件默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/api/runtime/puzzle/gallery`、`/api/runtime/custom-world-gallery` | 转发到 `api-server`。 |
|
||||
| `/api/runtime/puzzle/gallery/{id}`、`/api/runtime/custom-world-gallery/{profile}/{owner}` | 转发到 `api-server`。 |
|
||||
| `/api`、`/api/*` | 转发到 `api-server`,按配置执行 `Content-Length` 与流式 body 累计上限检查。 |
|
||||
| `/v1/database/{db}/subscribe`、`/v1/identity*` | 转发到 SpacetimeDB,保留 WebSocket Upgrade 头。 |
|
||||
| `/__genarrative_pingora/healthz` | 仅在携带 `X-Genarrative-Pingora-Probe` 且匹配配置 token 时返回 shadow JSON,否则 404。 |
|
||||
| `/v1/*`、`/generated-*`、`/healthz*`、`/readyz*` | 返回 404,保持生产公网不暴露口径。 |
|
||||
| 主站 SPA allowlist | 只对当前前端完整路由及兼容恢复路径 `/creation/rpg/agent` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
|
||||
| 主站 SPA allowlist | 只对 `/`、`/creation`、`/project`、`/profile` 与 `/editor/canvas` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
|
||||
| 其它 Web 路径 | 只读取真实静态文件或目录 index,缺失时返回真实 404;`/creation/not-exist`、`/runtime/not-exist`、`/puzzle/not-exist` 不进入 SPA fallback。 |
|
||||
|
||||
维护模式下,公网 API-like 路由返回 JSON `503`;公网 Web 静态路由先读取 `GENARRATIVE_PINGORA_GATEWAY_MAINTENANCE_PAGE_FILE` 指向的 release 外运行态公告,缺失时回退 `GENARRATIVE_PINGORA_GATEWAY_WEB_ROOT/maintenance.html`,两者都不存在时返回纯文本 `503`。版本化默认页不得包含日期或具体时段,临时公告由 `maintenance-on.sh --page-file` 安装并在 `maintenance-off.sh` 时清理。IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local 来源绕过整站维护闸,主站页面与静态资源、普通 API、后台页面与后台 API、SpacetimeDB 路由均按非维护状态继续处理;应用层登录、管理员鉴权和其它业务鉴权保持不变。Pingora 直连按 TCP peer 判定来源;仅当 peer 是 loopback 的同机 Nginx 时才接受 Nginx 强制覆盖的 `X-Real-IP`,绝不使用客户端可伪造的 `X-Forwarded-For` 做维护放行。该放行只绕过网关维护响应;若 `pause-after-stdb` 已停止 api-server,内网普通 API 和后台 API 仍不可用。
|
||||
@@ -556,7 +548,7 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
2. 涉及 Nginx 模板、Pingora 路由、限流分组或路由文档时,同步更新 `deploy/pingora/nginx-route-parity.matrix.json`,并运行 `npm run check:pingora-route-parity` 与 `cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml matches_nginx_route_parity_matrix`。
|
||||
3. 容器内使用同一份 Web 产物、同一组真实上游地址跑 Pingora smoke,并继续对照 `deploy/nginx/genarrative.conf` 扩展真实上游路由 parity 自动测试。
|
||||
4. 使用 `deploy/nginx/snippets/genarrative-pingora-canary.conf` 做 Nginx 前缀 canary;启用前先跑 `npm run check:nginx-pingora-canary` 和 `npm run check:pingora-canary-docker`,有 Nginx 或 Docker 的目标环境分别强制跑 `node scripts/check-nginx-pingora-canary.mjs --require-nginx` 与 `node scripts/check-pingora-canary-docker.mjs --require-docker --pull`,其中 Docker handoff 会自动对账临时 Nginx 与 Pingora access log。启用后跑 `npm run check:pingora-canary-live`,再用 current release 随包 access log parity 脚本对账目标机 Nginx 与 Pingora access log。canary live 的 base URL、prefix、Host、额外 path 和 timeout 不能包含换行或 NUL;canary live timeout 和 access log `since-lines` 必须是正整数,非法值直接失败。
|
||||
5. 前缀 canary 通过后,再使用 current release 随包 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083` 启用真实路径 canary;它会把 `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 渲染成独立本机 `server`,写入 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,不能 include 到生产 `443` server 内。该片段使用 `access_log ... genarrative_upstream`,文件名必须保证晚于定义 `log_format genarrative_upstream` 的主站配置加载;否则 `nginx -t` 会报 `unknown log format "genarrative_upstream"`。启用脚本会先执行 `nginx -t`、reload Nginx,再默认运行 realpath live smoke,任一阶段失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会在 `nginx -t` 或 reload 失败时恢复删除前配置。启用后跑 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再用 current release 随包 access log parity 脚本传 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log` 对账 `/api/creation-entry/config`、`/v1/identity` 和 `/assets/app.js` 等真实路径。
|
||||
5. 前缀 canary 通过后,再使用 current release 随包 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083` 启用真实路径 canary;它会把 `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 渲染成独立本机 `server`,写入 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,不能 include 到生产 `443` server 内。该片段使用 `access_log ... genarrative_upstream`,文件名必须保证晚于定义 `log_format genarrative_upstream` 的主站配置加载;否则 `nginx -t` 会报 `unknown log format "genarrative_upstream"`。启用脚本会先执行 `nginx -t`、reload Nginx,再默认运行 realpath live smoke,任一阶段失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会在 `nginx -t` 或 reload 失败时恢复删除前配置。启用后跑 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再用 current release 随包 access log parity 脚本传 `--realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log` 对账 `/api/editor/showcase/resources`、`/v1/identity` 和 `/assets/app.js` 等真实路径。
|
||||
6. 目标机 canary include 后必须跑正式切换聚合门禁,并按现场已启用的 canary 入口选择参数:前缀 canary 已启用时,源码 checkout / CI / 构建环境执行 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`,目标机 current release 执行 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 已启用时,追加或单独使用 `--require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。如果现场只启用了真实路径 canary,不要同时传 `--require-live`;缺少 Host 会直接失败,避免 live canary 误测默认 vhost。live smoke 后还会按 `request_id` 对账 Nginx 与 Pingora access log,缺少同一请求的 Pingora 日志、状态码、方法或 path 漂移都会失败。
|
||||
7. 如需评估 Pingora 直连公网入口,必须显式配置 `TLS_LISTEN`、证书、私钥和 `HTTP_REDIRECT_LISTEN`;Certbot 证书先用随包 `scripts/deploy/pingora-tls-cert-sync.mjs` 同步到 `/etc/genarrative/pingora-tls/<域名>/`,不要直接 chmod Let’s Encrypt live/archive 原路径;同一 IP 上还有 Gitea 域名时,还必须配置 `GITEA_HOSTS` / `GITEA_UPSTREAM` 并确认 TLS 证书覆盖所有由 Pingora 直连接管的 Host。绑定 `80/443` 时还必须人工启用 `genarrative-pingora-gateway-direct-entry.conf` drop-in 授予 `CAP_NET_BIND_SERVICE`。随后用 `npm run check:pingora-gateway-smoke` 覆盖 TLS / HTTP/2 ALPN / redirect / WSS subscribe / Gitea Host 分流;目标机必须先跑 `npm run check:pingora-direct-preflight -- --env-file /etc/genarrative/pingora-gateway.env --require-live-env --systemd-cat --check-cert-readable --check-service-env-file --check-service-user-cert-readable --check-service-binary-executable --check-ports-free`,再跑 `npm run check:pingora-direct-live` 或 release readiness 的 `--require-direct`,且 `--require-direct` 必须带 direct HTTPS base URL、direct HTTP base URL、正式域名 Host/SNI、redirect Location host、Pingora access log 文件、health patrol env 文件、direct preflight env 文件、systemd drop-in 生效检查、service EnvironmentFile 一致性检查、当前用户和 systemd 服务用户证书可读检查、service 二进制可执行检查、显式 SpacetimeDB 数据库名,并会拒绝 `--skip-wss`。高端口 rehearsal 使用 `https://127.0.0.1:<高端口>` 打入但期望 HTTP redirect Location 指向正式域名默认 HTTPS 入口时,额外传 `--direct-redirect-base-url https://<域名>`;`--direct-redirect-host` 仍必须保留,用于 runbook Host 一致性约束。direct live 会用生成的 `request_id` 反查 Pingora access log;缺少对应日志、method 漂移、path 漂移或 status 漂移都算直连门禁失败。direct preflight 会拒绝开启网关保护但未确认共享保护层的 `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1` 配置;`--env-file`、`--systemd-service`、服务用户和 env 中的 listen / cert / key 值都不能包含换行或 NUL,执行 `systemctl cat` 或 `sudo -u <serviceUser> test -r <file>` 前还会复核子命令参数,避免污染参数进入目标机预检命令;direct live 的 URL、Host、redirect base URL、probe token、额外 path、数据库名、access log 路径、timeout 和布尔 env 也不能包含换行或 NUL,且会在发起请求前失败;direct live timeout 必须是正整数,直连相关布尔 env 只接受 `true/false`、`1/0`、`yes/no`、`on/off` 或空值,非法值直接失败。证书申请与续期仍由 Certbot / 外部自动化承担,网关只读取现有文件。
|
||||
8. 正式直连 runbook 的启用前基础门禁和启用后 `--require-direct` 复核必须调用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only`。该脚本、`scripts/check-pingora-canary-live.mjs`、`scripts/ops/pingora-direct-rehearsal-status.mjs`、realpath canary 启停脚本和 `deploy/nginx/` 必须进入生产 API release、Jenkins API Build 归档、Jenkins API Deploy 复制清单和目标机 current release;缺失时部署应 fail-fast,切换窗口不能依赖源码 checkout 或 Jenkins workspace。
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
- `api-server` 中上述业务的 router、handler、service、生成与发布编排、公开详情、专属 runtime 和 worker 启动点。
|
||||
- `spacetime-client` 的模板业务 facade、mapper、reducer/procedure 调用和业务 DTO 依赖。
|
||||
- `spacetime-module` 的模板 reducer、procedure、业务 view、初始化逻辑和领域规则调用。
|
||||
- 所有纯模板 crate 及 RPG 专属运行态 crate 的 workspace/default 目标和在运依赖边。
|
||||
- 所有纯模板 crate、RPG 专属运行态 crate 及旧 Creative Agent 的 `platform-agent` crate 的 workspace/default 目标和在运依赖边。
|
||||
- 后台及主站中只服务于旧创作入口的配置、灰度、展示、搜索、启动、追踪和运维门禁。
|
||||
|
||||
### 保留
|
||||
@@ -25,7 +25,8 @@
|
||||
- `migration.rs` 中相关表的迁移白名单、表名兼容和字段目录。
|
||||
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
|
||||
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
|
||||
- 新版 `/creation` 创作工具主页、`creation-home` 展示组件与静态资产,以及桌面端“创作 / 项目 / 我的”公共侧边栏;“我的”保留头像 / 昵称编辑、陶泥号复制、钱包与账单、统计、充值、兑换码、玩家社区、反馈、通用设置、开发者 API Key 和法律信息,不恢复旧模板入口、旧作品架或生成队列。
|
||||
- 新版 `/creation` 创作工具主页、`/project` 项目入口、稳定的 `/profile` 个人页路由、`creation-home` 展示组件与现役静态资产。桌面端保留“创作 / 项目 / 我的”公共侧边栏,移动端保留同样三项的底部 dock;“我的”保留头像 / 昵称编辑、陶泥号复制、钱包与账单、统计、充值、兑换码、玩家社区、反馈、通用设置、开发者 API Key 和法律信息,不恢复旧模板入口、旧作品架或生成队列。
|
||||
- `runtime_setting` 是账号级公共设置事实,不属于旧模板运行态。原表结构和数据不变,继续由鉴权后的 `GET/PUT /api/runtime/settings`、`get_runtime_setting_or_default` 与 `upsert_runtime_setting_and_return` procedure 支撑音乐音量和平台主题读写。
|
||||
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
|
||||
|
||||
## 编译边界
|
||||
@@ -38,26 +39,42 @@
|
||||
- 平台公共 profile 请求与展示模型必须位于 `services/platform-entry/` 和现役 `platform-entry` 文件,不得因为沿用账号、钱包或设置能力而继续 import `services/rpg-entry`、`services/rpg-runtime` 或 `components/rpg-entry`。Vite 对退役模块实行实际 module graph 门禁,命中即中止 dev/build;ESLint restricted imports 作为更早的源码反馈。
|
||||
- 原 `main.tsx`、`App.tsx`、旧路由、旧标题映射、`PlatformEntryFlowShellImpl.tsx` 与旧入口类型保持原样;正式链路由 `active-main.tsx`、`ActiveApp.tsx`、`activeApp*`、`PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts` 承载,只包含新版创作主页、项目、编辑器、账号、设置与钱包公共能力。`retired/legacy-creation-templates/frontend/original/` 另保留逐文件原样快照。
|
||||
- 退役 CSS 的源码过滤必须在 Tailwind/Vite 转换前执行,产物过滤留在 `generateBundle`;禁止在 `post` transform 中把 Vite 已生成的 JavaScript 样式模块重新交给 PostCSS 解析。
|
||||
- `/creation` 保持新版创作工具主页;生产网关对旧子路径返回 404,客户端若收到未知旧路径则回落当前平台公共首页,不再提供旧业务详情页或兼容壳。
|
||||
- 顶层退役 module 也必须受编译门禁约束:`src/uiAssets.ts`、`src/types.ts`、`src/types/**`、`src/services/runtimeAudioFeedback.ts`、`src/services/publicWorkCode.ts`、`creationEntryConfigService`、`creationUrlState`、`customWorld*`、`runtimeGuestAuth`、`runtimeRequest`、`input-devices/**`、`useMocapInput`、`wechatMiniProgramSubscribe`、`useCombatFlow` 和 `useStoryOptions` 不得进入 Vite module graph、TypeScript、ESLint 或 Vitest,现役公共品牌资产应从独立公共定义导入。
|
||||
- `src/games/**`、`src/data/**`、`src/prompts/**`、旧 `App.tsx` / `main.tsx` / `RpgRuntimeApp.tsx` / `*PlaygroundApp.tsx`、旧 `appRoutes` / `appPageRoutes` 和 `services/ai.ts` 同属退役源码边界;Vite 必须在 pre-transform 阶段拒绝直接请求,不能只依赖现役入口未 import 或构建 tree-shaking。
|
||||
- `src/components`、`src/hooks`、`src/persistence`、`src/routing` 和 `src/services` 根级文件是新旧混合区,Vite 与 ESLint 必须使用显式现役白名单。当前只放行正式入口实际依赖的根级公共模块;新增根级公共模块时必须同步登记,未登记文件按退役源码处理。各现役子目录继续按独立目录边界放行。
|
||||
- 退役静态资产 `/audio/**`、`/chat.png` 和 `/fusion-pixel.ttf` 不得由 Vite dev server 提供,也不得进入生产产物;旧 pixel / story-tab / 玩法 CSS 仅能在历史源码中存在。
|
||||
- 所有同源 `/generated-*` 裸读路径在 Vite、Nginx、Pingora 和 `api-server` 均返回空 `404`;历史对象 key 只允许作为 `legacyPublicPath` 进入 `/api/assets/read-url` 等现役签名读取链,不恢复旧生成资产代理。
|
||||
- `/creation`、`/project` 和 `/profile` 是现役稳定路由,刷新及浏览器前进 / 后退必须保持当前页签。生产网关对旧子路径返回 404,客户端若收到未知旧页面路径则回落当前平台公共首页;Vite dev 对旧 `/api/creation*` 与 `/api/public-works*` 直接返回 404,不能回落为 SPA HTML。小程序不再注册旧生成结果订阅授权页。
|
||||
|
||||
### Rust
|
||||
|
||||
- `api-server` 不声明模板模块,不挂模板路由,不保留模板 worker 启动点。
|
||||
- 现役 external generation worker 只领取 `source_module = editor-canvas` 的任务,历史旧玩法 pending / running 行不得被领取或改写。
|
||||
- `spacetime-client` 不保留模板业务 facade 和 mutation 调用;历史表生成绑定只允许服务必要兼容读取。
|
||||
- `spacetime-module` 对旧模板只编译历史表结构,不导出旧 reducer、procedure、业务 view 或领域规则。
|
||||
- 旧 `public_work_asset_read_grant` view 与十类旧作品授权计算退出 module;匿名资产读取只保留现役 editor showcase 授权。
|
||||
- `spacetime-module` 与 `spacetime-client` 的 Cargo `lib.path` 固定指向各自的 `src/active.rs`;原 `src/lib.rs` 及旧业务源码继续原位保留,但不再作为 crate 根参与编译。
|
||||
- 历史表最小定义集中在 `spacetime-module/src/legacy_schema/` 与 `spacetime-module/src/runtime/legacy_schema/`,混合 profile 表的在运数据壳位于 `spacetime-module/src/runtime/active/profile.rs`;这些目录只允许 schema 和必要兼容读取定义。
|
||||
- `module-runtime` 仍是账号、钱包、公共设置、追踪和 feature gate 的现役领域 crate;其混合源码中的 `CreationEntry*`、旧公开作品、旧存档 / 浏览历史 / 游玩统计 DTO、command、mapper 和规则必须以编译条件退出,且不再依赖只为旧创作契约存在的 `shared-contracts`。历史 schema 只继续编译 `RuntimeBrowseHistoryThemeMode` 六个变体和完整保序的 `RuntimeProfileWalletLedgerSourceType` 等持久化 ABI,不保留围绕这些类型的旧业务实现。
|
||||
- 纯模板 crate 和专属运行态 crate 不属于 workspace members、default members 或任何在运 crate 的依赖图;源码目录保持原样。
|
||||
- `platform-agent` 及其专属 `langchainrust` 依赖同样退出 workspace 与 `api-server` 依赖图;现役编辑器 Agent 仅需的模型常量收口到 `platform-llm`,不再通过旧拼图 Phase 1 / Creative Agent 执行器 crate 复用。
|
||||
- `platform-auth` 不再编译 runtime guest token;`platform-wechat` 不再编译旧生成结果订阅服务,只保留现役认证和支付协议。
|
||||
|
||||
## 验收
|
||||
|
||||
- 旧 URL 不再命中旧页面或后端路由。
|
||||
- `/creation` 与 `/project` 显示“创作 / 项目 / 我的”公共侧边栏,新创作主页只调用编辑器项目和公开素材接口;顶栏保持现役搜索、公共泥点入口与账号胶囊,不重新拼装平行账号按钮组。
|
||||
- `/creation`、`/project` 与 `/profile` 在桌面端显示“创作 / 项目 / 我的”公共侧边栏,在 `390x844` 等移动视口显示同样三项的底部 dock;点击、刷新及浏览器前进 / 后退均保持路由与选中态一致。新创作主页只调用编辑器项目和公开编辑器素材接口;顶栏保持现役搜索、公共泥点入口与账号胶囊,不重新拼装平行账号按钮组。
|
||||
- “我的”桌面布局按原平台公共资料页全宽展示四个常用入口、两行设置和法律栏;头像、昵称、复制、充值、兑换码、社区、反馈、API Key 等入口可用,但不发起旧模板、旧公开作品或旧运行态请求。
|
||||
- `tsc --listFilesOnly` 与 Vite 干净加载均不得出现 `src/components/rpg-entry/**`、`src/services/rpg-entry/**` 或 `src/services/rpg-runtime/**`。
|
||||
- Vite 构建产物和依赖图不包含旧前端业务目录。
|
||||
- `cargo tree` 中不存在纯模板 crate 或专属运行态 crate。
|
||||
- 鉴权访问 `GET/PUT /api/runtime/settings` 不得返回 404,读写必须经 `spacetime-client` 调用现役 settings procedure;未鉴权请求返回 401,不恢复任何旧运行态设置路由。
|
||||
- `tsc --listFilesOnly` 与 Vite 干净加载均不得出现旧业务目录、上述顶层退役 module 或小程序旧订阅授权实现。
|
||||
- 直接请求代表性的旧 `games` / `data` / `prompts` / 顶层 App 模块必须被 Vite module graph 门禁拒绝;现役 `creation-home`、项目、profile 与 editor 模块仍正常转换。
|
||||
- 根级旧组件、hook、persistence、routing 和 service 必须同时被 Vite 拒绝并被 ESLint 忽略;现役根级图片解析、设置、路由和 API client 文件必须继续参与两套门禁。
|
||||
- Vite 构建产物和依赖图不包含旧前端业务目录、Fusion Pixel / pixel 业务样式或旧 runtime 声音签名;产物中不存在 `dist/audio/**`、`dist/chat.png` 或 `dist/fusion-pixel.ttf`。
|
||||
- 旧生成资产前缀和现役 editor 对象前缀的同源裸读都必须返回空 `404`,不能返回 `index.html` 形成 soft 404;编辑器真实资产读取继续走签名 URL。
|
||||
- `cargo tree` 中不存在纯模板 crate、专属运行态 crate、`platform-agent` 或 `langchainrust`。
|
||||
- `npm run check:module-runtime-artifact` 对实际 `module_runtime.rlib` 的 object 成员执行负向扫描:旧创作、存档、浏览与游玩符号和字面量必须为零,同时 `RuntimeBrowseHistoryThemeMode`、`RuntimeProfileWalletLedgerSourceType` 与 `RuntimeSettingSnapshot` 等兼容 ABI 必须仍存在。
|
||||
- `spacetime-module` 编译结果仍包含全部历史表,但不包含任何旧模板 reducer、procedure 和业务 view。
|
||||
- `platform_auth.rlib` 不包含 runtime guest token 符号,`platform_wechat.rlib` 不包含订阅消息发送符号;历史旧队列行不满足现役 worker claim 条件。
|
||||
- `npm run check:spacetime-schema`、定向 Rust 检查、前端类型检查、`npm run check:encoding`、`git diff --check` 通过。
|
||||
|
||||
## 历史记录兼容
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -56,7 +56,7 @@ RAG 默认不安装运行时依赖,也不把 LanceDB、Transformers.js 或本
|
||||
- 移动端优先,同时保证桌面端体验完整。
|
||||
- 弹出独立面板的交互使用弹窗、抽屉、popover 或页面级 portal,不在当前面板下面追加内容。
|
||||
- 页面展示以后端返回状态为准,不在前端自行计算结论型业务状态。
|
||||
- 创作入口事实源来自 SpacetimeDB,经 `/api/creation-entry/config` 下发;前端只做展示派生。
|
||||
- 现役平台入口固定为 `/creation`、`/project`、`/profile`:创作主页只读取图片编辑器项目与公开编辑器素材,个人页只复用账号、钱包和公共设置能力。旧 `/api/creation-entry/config` 及模板工作台、公开作品和专属运行态已经退役,不得因历史表仍在而恢复前端入口或后端接口。
|
||||
- 优先扩展现有公共组件,例如平台弹窗、图片输入、媒体预览、状态提示和动作按钮,不在业务页复制通用逻辑。
|
||||
|
||||
## 后端与数据真相
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user