再次合并 master
合入 origin/master 最新订阅消息与计费相关更新 保留作品架 actions 收口并接入统一分享弹窗 修复创作生成泥点预检与本地余额扣减回归
This commit is contained in:
@@ -111,6 +111,9 @@ 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="doubao-1-5-pro-32k-character-250715"
|
||||
|
||||
@@ -1674,10 +1674,10 @@
|
||||
## 2026-06-07 创作入口泥点消耗改由统一契约驱动
|
||||
|
||||
- 背景:创作入口玩法卡封面右下角长期固定显示 `10-20泥点数`,无法在后台按玩法调整,也容易和真实钱包余额或活动奖池混淆。
|
||||
- 决策:`creationTypes[].unifiedCreationSpec.mudPointCost` 作为入口卡泥点消耗数量字段,旧契约缺失时后端和前端都兜底为 `10`;入口卡由前端格式化为 `X泥点数` 展示,后端和后台不保存单位文案。该字段只表达入口卡展示数量,不替代各玩法提交、生成或发布链路中的真实扣费校验。
|
||||
- 决策:`creationTypes[].unifiedCreationSpec.mudPointCost` 作为入口卡泥点消耗数量字段,旧契约缺失时后端和前端都兜底为 `10`;入口卡由前端格式化为 `X泥点数` 展示,后端和后台不保存单位文案。该字段同时作为玩法新建草稿初始生成的扣费真相源,前端余额前置校验、拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成必须读取同一份后台入口配置;结果页单图重生成、发布、道具使用和其它独立资产操作继续使用各自业务成本。
|
||||
- 决策补充:后台创作入口开关页不再直接暴露统一创作契约 JSON textarea;页面按契约结构展示为卡片和字段列表,点击“修改契约”后通过弹窗表单编辑 `title`、`mudPointCost` 和 fields,再组装回统一契约 payload 保存。`workspaceStage`、`generationStage` 和 `resultStage` 属于内部阶段标识,后台不展示也不允许编辑;保存时沿用已有契约值,新增契约时按 `playId` 的固定阶段映射自动带出。
|
||||
- 影响范围:`shared-contracts` 的 `UnifiedCreationSpecResponse`、`/api/creation-entry/config` 响应、前端入口卡派生、后台入口开关页、玩法链路文档和创作入口回归测试。
|
||||
- 验证方式:后台修改 `mudPointCost` 后保存,`GET /api/creation-entry/config` 返回同名数字字段;底部加号创作入口卡显示前端格式化后的泥点消耗;关闭态卡片仍只显示 `暂未开放`。
|
||||
- 验证方式:后台修改 `mudPointCost` 后保存,`GET /api/creation-entry/config` 返回同名数字字段;底部加号创作入口卡显示前端格式化后的泥点消耗;创作表单泥点不足提示和后端实际钱包扣费都使用该数字;关闭态卡片仍只显示 `暂未开放`。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-31 拼消消底图 prompt 与 atlas 切片提示词收口
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
- 关联:相关文件、文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 新建草稿扣费不能和入口卡泥点配置分离
|
||||
|
||||
- 现象:后台修改创作入口的 `mudPointCost` 后,入口卡和前置余额提示可能显示新数值,但用户真实钱包流水仍按代码常量扣除。
|
||||
- 原因:早期约定把 `creationTypes[].unifiedCreationSpec.mudPointCost` 只当展示字段,拼图、抓大鹅和汪汪声浪初始生成各自保留了 `2`、`10`、三次单图 `1` 的硬编码扣费路径。
|
||||
- 处理:新建草稿初始生成成本必须统一从 `GET /api/creation-entry/config` 的 `unifiedCreationSpec.mudPointCost` 解析;前端预校验、拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成同源。汪汪声浪结果页单图重新生成仍按单图资产操作成本,不套初始草稿总成本。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "mud points"`、`npm run test -- src/services/bark-battle-creation/barkBattleCreationClient.test.ts`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml resolves_mud_point_cost initial_generation_slot_cost_splits_creation_entry_total_cost -- --nocapture`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`server-rs/crates/api-server/src/creation_entry_config.rs`、`server-rs/crates/api-server/src/puzzle/handlers.rs`、`server-rs/crates/api-server/src/match3d/draft.rs`、`server-rs/crates/api-server/src/bark_battle.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## generated 图片重复下载不要改成服务端本地磁盘缓存
|
||||
|
||||
- 现象:同一张 OSS generated 图片每次展示都重新从 OSS 拉取,或者完整 OSS 私有 URL 裸请求返回 403。
|
||||
@@ -1889,7 +1897,7 @@
|
||||
|
||||
- 现象:`http://127.0.0.1:3001/` 打不开,但 `3000 / 3101 / 8082` 仍有进程;`npm run dev` 直接退出,没有把新栈拉起来。
|
||||
- 原因:旧 worktree 的 `api-server`、`spacetime-standalone` 和 Vite 还活着,或者当前 worktree 的本机 SpacetimeDB CLI 默认版本低于仓库锁定版本,`scripts/dev.mjs` 会先校验版本再启动并直接报错退出。
|
||||
- 处理:先停掉占用端口的旧进程,再执行 `spacetime version list` 和 `spacetime version use 2.3.0`,确认本机 CLI/standalone 与仓库一致后重新启动 `npm run dev -- --no-interactive --web-port 3001 --api-port 8083 --spacetime-port 3103 --admin-web-port 3104`。
|
||||
- 处理:先停掉占用端口的旧进程,再执行 `spacetime version list` 和 `spacetime version use 2.4.1`,确认本机 CLI/standalone 与仓库一致后重新启动 `npm run dev -- --no-interactive --web-port 3001 --api-port 8083 --spacetime-port 3103 --admin-web-port 3104`。
|
||||
- 验证:`http://127.0.0.1:3001/`、`http://127.0.0.1:8083/healthz`、`http://127.0.0.1:3103/v1/ping` 都返回 200,且进程命令行指向当前 worktree 路径而不是别的仓库。
|
||||
- 关联:`scripts/dev.mjs`、`.hermes/shared-memory/pitfalls.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
@@ -2098,3 +2106,19 @@
|
||||
- 处理:`UnifiedCreationPage` 根容器必须保留 `bg-[image:var(--platform-body-fill)]` 和 `overscroll-contain`,内容区必须用 `flex-1 min-h-0` 占满统一页剩余高度;移动端键盘打开时只记录 `data-mobile-keyboard-open`、隐藏底部 dock、设置键盘 inset 和浅色 `--platform-keyboard-exposed-fill`,不要再对 `.platform-viewport-shell` 做全局 `transform`;小程序 `pages/web-view` 的 `page` 和 web-view class 也要用浅色背景。不要只给某个玩法工作台单独加高度补丁。
|
||||
- 验证:`npm run test -- src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedCreationWorkspace.test.tsx src/mobileViewportKeyboardFocus.test.ts src/index.test.ts miniprogram/pages/web-view/index.style.test.js`;移动端点击拼图、敲木鱼、跳一跳输入框时,页面不应整体弹起,键盘上方应持续显示平台浅色背景。
|
||||
- 关联:`src/components/unified-creation/UnifiedCreationPage.tsx`、`src/mobileViewportKeyboardFocus.ts`、`src/index.css`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 小程序订阅消息授权不要依赖 web-view bindmessage
|
||||
|
||||
- 现象:拼图点击生成后,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 可能重新加载首页并丢失进度页状态。后端发送订阅消息仍只允许在拼图资产成功或失败终态后执行。
|
||||
- 验证:`npm run test -- src/services/wechatMiniProgramSubscribe.test.ts miniprogram/pages/subscribe-message/index.test.js`。
|
||||
- 关联:`src/services/wechatMiniProgramSubscribe.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`miniprogram/pages/subscribe-message/index.shared.js`、`miniprogram/pages/web-view/index.js`。
|
||||
|
||||
## 微信订阅消息 time 字段不能用内部时间戳
|
||||
|
||||
- 现象: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`。
|
||||
- 验证:`cargo test --manifest-path server-rs\Cargo.toml -p api-server generation_result_template -- --nocapture`;dev 日志中不应再出现 `data.time4.value invalid`。
|
||||
- 关联:`server-rs/crates/api-server/src/wechat_subscribe_message.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
@@ -34,7 +34,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
|
||||
server-rs + Axum + SpacetimeDB
|
||||
```
|
||||
|
||||
当前 SpacetimeDB crate、SDK、CLI / standalone、生成 bindings 和容器压测镜像统一按 `2.3.0` 对齐。
|
||||
当前 SpacetimeDB crate、SDK、CLI / standalone、生成 bindings 和容器压测镜像统一按 `2.4.1` 对齐。
|
||||
|
||||
职责边界:
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ Linux Docker Engine 若要从宿主机 CLI 连到容器内服务,直接用 `ht
|
||||
|
||||
## 构建工具链
|
||||
|
||||
`api-server` 容器镜像只构建 Linux release API 二进制,不构建 `spacetime-module`。当前 `api-server -> spacetime-client -> spacetimedb-sdk 2.3.0` 依赖链要求 Rust 1.93,因此 `deploy/container/api-server.Dockerfile` 的 Rust builder 固定为 `rust:1.93-bookworm`。如果本机 Docker Hub 拉取失败,可以先在本机准备同名本地 builder 镜像,但不要把临时 bootstrap 容器或私有 registry 凭据写入仓库。
|
||||
`api-server` 容器镜像只构建 Linux release API 二进制,不构建 `spacetime-module`。当前 `api-server -> spacetime-client -> spacetimedb-sdk 2.4.1` 依赖链要求 Rust 1.93,因此 `deploy/container/api-server.Dockerfile` 的 Rust builder 固定为 `rust:1.93-bookworm`。如果本机 Docker Hub 拉取失败,可以先在本机准备同名本地 builder 镜像,但不要把临时 bootstrap 容器或私有 registry 凭据写入仓库。
|
||||
|
||||
## 启动与验证
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: genarrative-container-loadtest
|
||||
|
||||
services:
|
||||
spacetimedb:
|
||||
image: clockworklabs/spacetime:v2.3.0
|
||||
image: clockworklabs/spacetime:v2.4.1
|
||||
user: root
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -16,7 +16,7 @@ server-rs + Axum + SpacetimeDB
|
||||
|
||||
`server-rs/Cargo.toml` 是 workspace 事实源。默认构建成员为 `crates/api-server`;第三方依赖版本和 workspace 内 crate path 统一放在 `[workspace.dependencies]`。
|
||||
|
||||
SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 统一锁定 `2.3.0`;本地 `spacetime` CLI / standalone、生成的 `spacetime-client` bindings 和容器压测镜像也必须与 `2.3.0` 对齐,避免 BSATN / procedure result 反序列化错配。
|
||||
SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 统一锁定 `2.4.1`;本地 `spacetime` CLI / standalone、生成的 `spacetime-client` bindings 和容器压测镜像也必须与 `2.4.1` 对齐,避免 BSATN / procedure result 反序列化错配。
|
||||
|
||||
当前主要 crate:
|
||||
|
||||
@@ -169,6 +169,12 @@ npm run check:server-rs-ddd
|
||||
7. access JWT 只携带最小设备快照 `device.client_type`、`device.client_runtime`、`device.client_platform`。充值下单按该快照拦截渠道:小程序只允许 `wechat_mp`,手机微信内网页只允许 `wechat_h5`,桌面微信内网页只允许 `wechat_native`。
|
||||
8. 所有微信真实渠道都以微信支付通知或服务端查单确认 `SUCCESS` 为到账事实;小程序、H5 跳转和 Native 二维码返回都不能直接发放泥点或会员。
|
||||
|
||||
## 创作入口泥点扣费契约
|
||||
|
||||
1. `creation_entry_type_config.unified_creation_spec_json` 内的 `mudPointCost` 是玩法新建草稿初始生成的泥点成本真相源,同时供入口卡展示和前端余额前置校验使用;旧契约缺失时允许按代码默认成本兜底。
|
||||
2. `api-server` 执行拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成时,必须通过 `GET /api/creation-entry/config` 同源配置解析对应玩法成本后再调用钱包扣费 procedure,不得继续使用前端或后端硬编码常量作为实际扣费真相。
|
||||
3. 结果页单图重生成、发布、道具使用和其它独立资产操作仍按各自业务操作成本执行;不要把初始草稿成本误套到这些单次操作上。
|
||||
|
||||
## 外部服务与资产
|
||||
|
||||
- LLM:`GENARRATIVE_LLM_*`,创意 Agent 另用 `APIMART_BASE_URL` / `APIMART_API_KEY`。
|
||||
|
||||
@@ -55,6 +55,8 @@ Linux 本机多用户并发开发时,`npm run dev` 和 `npm run dev:*` 单模
|
||||
|
||||
微信小程序虚拟支付使用 `WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_OFFER_ID`、`WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_APP_KEY`、`WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_SANDBOX_APP_KEY` 和 `WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_ENV` 配置。小程序充值统一走 `wechat_mp_virtual` / `wx.requestVirtualPayment`:泥点属于代币(`coin`),`buyQuantity` 按当前充值商品快照里的 `points_amount` 传;会员和后台新增道具类商品走 `short_series_goods`,`productId` 对应微信后台道具 ID。旧登录快照若缺 `session_key`,需要用户在小程序内重新登录后再支付;客户端成功回调不是最终到账,仍以后端通知或查询确认订单为准。详细口径见 `docs/【技术方案】微信虚拟支付接入-2026-05-26.md`。
|
||||
|
||||
微信小程序订阅消息生成结果通知使用 `WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_ENABLED`、`WECHAT_MINIPROGRAM_GENERATION_RESULT_TEMPLATE_ID` 和 `WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE` 配置。当前模板为 `AI创作生成结果通知`;H5 在拼图 `compile_puzzle_draft` 生成动作发起前先进入生成进度态并立即继续生成动作,同时非阻塞跳转到小程序原生订阅授权页尝试请求授权,用户接受、拒绝或返回都不能阻塞生成,且原生页不改写上一页 `webViewUrl`,避免返回后丢失 H5 当前进度页状态。后端只在拼图资产生成成功或失败终态后用微信登录保存的 openid 调用 `subscribeMessage.send`,发送失败只打 warning,不影响生成主链路。模板 `time4` 字段固定发送北京时间 `YYYY-MM-DD HH:mm`,不要使用内部微秒时间戳、秒级时间戳或带时区后缀的 RFC3339 字符串,否则微信会返回 `argument invalid! data.time4.value invalid`。
|
||||
|
||||
如果本地 `GET /api/creation-entry/config` 返回 `No such procedure`,或 `api-server` 日志出现 `no such table: puzzle_gallery_card_view` / `no such table: wooden_fish_gallery_card_view` 这类公开 view 缺失,通常是 `.env.local` 指向的 SpacetimeDB 库还没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库。debug 构建的 `api-server` 会临时使用后端默认入口配置兜底,避免创作作品架整块消失;正式修复仍应切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布,或用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。
|
||||
|
||||
本地排查 schema 漂移时,先用当前 dev server 显式查询目标库,例如:
|
||||
@@ -67,7 +69,7 @@ spacetime sql <database> "SELECT * FROM puzzle_gallery_card_view LIMIT 1" --serv
|
||||
|
||||
本地 `npm run dev:spacetime` 发布模块时必须显式忽略仓库根目录的 `spacetime.json`,由脚本固定追加 `--no-config` 并使用命令参数里传入的数据库名和 `--server http://127.0.0.1:3101`。否则 CLI 可能把发布目标改写到配置文件里的其他数据库,导致 `dev:spacetime` 启动后又因发布失败自动退出,浏览器随后会在 `ws://127.0.0.1:3101/v1/database/.../subscribe` 看到连接拒绝。
|
||||
|
||||
本地 `spacetime` CLI / standalone 版本必须和 `server-rs/Cargo.toml` 里锁定的 `spacetimedb` 版本一致;当前统一版本为 `2.3.0`。若版本错配,procedure 返回值可能在宿主侧触发 `Failed to BSATN deserialize procedure return value`,api-server 最终表现为敲木鱼等创作动作的 `SpacetimeDB procedure 调用超时`。排障时先运行 `spacetime --version`,再对照 `server-rs/Cargo.toml` 的 `spacetimedb = "..."`;需要切版本时执行 `spacetime version install <version> && spacetime version use <version>`,然后重新启动 `npm run dev:spacetime`。当前 `scripts/dev.mjs` 会在启动和复用本地 SpacetimeDB 前写入并校验 `dev-spacetime-tool-version`,避免把旧 standalone 继续带进新一轮创作。
|
||||
本地 `spacetime` CLI / standalone 版本必须和 `server-rs/Cargo.toml` 里锁定的 `spacetimedb` 版本一致;当前统一版本为 `2.4.1`。若版本错配,procedure 返回值可能在宿主侧触发 `Failed to BSATN deserialize procedure return value`,api-server 最终表现为敲木鱼等创作动作的 `SpacetimeDB procedure 调用超时`。排障时先运行 `spacetime --version`,再对照 `server-rs/Cargo.toml` 的 `spacetimedb = "..."`;需要切版本时执行 `spacetime version install <version> && spacetime version use <version>`,然后重新启动 `npm run dev:spacetime`。当前 `scripts/dev.mjs` 会在启动和复用本地 SpacetimeDB 前写入并校验 `dev-spacetime-tool-version`,避免把旧 standalone 继续带进新一轮创作。
|
||||
|
||||
本地 `.env`、`.env.local` 或 `.env.secrets.local` 修改后必须重启 `api-server` 才会生效;若已经通过 `npm run dev` 启动完整联调,可在该终端输入 `rs api-server`。排查 RPG / 拼图 / 抓大鹅等 VectorEngine 生图链路时,确认 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 只在本地或服务器密钥文件中配置,不能写入 Git。VectorEngine `gpt-image-2` 图片协议、URL / base64 响应解析、远端图片下载和 provider 侧结构化日志在 `server-rs/crates/platform-image`;`api-server` 只做配置、玩法编排、OSS / asset 持久化、计费和失败审计落库。开局 CG 故事板、首图、背景和图集都属于长耗时图片请求;后端默认会把 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 下限收口到 `1000000`,旧进程仍可能沿用重启前的短超时。若 VectorEngine 在 `send()` 阶段失败且日志显示 `SendRequest`,先看同一 `request_id` 的 provider 日志字段 `source`、`source_chain`、`source_chain_depth`,再查 `external_api_call_failure.metadata_json.errorSource`;当前 multipart `/v1/images/edits` 单独强制 HTTP/1.1。拼图关卡资产按 `level_scene -> ui_spritesheet -> level_background` 顺序生成,日志会带 `slot`、`asset_kind` 和 `elapsed_ms`。
|
||||
|
||||
@@ -269,7 +271,7 @@ Jenkins 按 web / api / Spacetime module / build / deploy / publish 拆分
|
||||
- `GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512` 开启应用内 HTTP 并发背压;`GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS=320`、`GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS=64`、`GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS=16` 分别限制公开列表、公开详情和后台 API 热路径。超过许可时直接返回 `429 Too Many Requests` 和 `Retry-After: 1`,`/healthz` 与 `/readyz` 不受该限制。这些值不是 RPS 限速;如果压测中 429 上升但内存和 p95 收敛,说明背压正在保护进程。直连 `api-server` 的极高 RPS 压测若出现 `connection refused`,通常已经打到 TCP 监听 / accept 层,应同时检查 backlog、Nginx upstream keepalive 和前置限流。
|
||||
- `api-server` 正常运行时 `/healthz` 返回进程存活状态,`/readyz` 返回是否仍接收新流量;收到 `SIGINT` / `SIGTERM` 后会先把 readiness 标记为不可用,再让 Axum 停止接新连接并等待已有 HTTP 请求排空。systemd 仍以 `KillSignal=SIGINT` 停服务,`TimeoutStopSec=90` 作为长请求排空上限。
|
||||
- `genarrative-api.service` 设置 `LimitNOFILE=65535`、`TasksMax=2048`;上线后用 `systemctl show genarrative-api.service -p LimitNOFILE -p TasksMax -p TimeoutStopUSec` 和 `cat /proc/$(pidof api-server)/limits` 核对。
|
||||
- Server provision 不再通过 Windows helper 下载,也不再通过 Linux build 节点中转工具包。`Prepare Provision Tools` 在目标 dev / release agent 工作区内准备 `spacetime-x86_64-unknown-linux-gnu.tar.gz` 和 `otelcol-contrib_0.151.0_linux_amd64.tar.gz` 并生成 `provision-tools/`;如果目标服务器下载需要代理,在 `PROVISION_DOWNLOAD_PROXY` 配置目标机可访问的 HTTP 代理。
|
||||
- Server provision 不再通过 Windows helper 下载,也不再通过 Linux build 节点中转工具包。`Prepare Provision Tools` 在目标 dev / release agent 工作区内准备 SpacetimeDB `2.4.1` 的 `spacetime-x86_64-unknown-linux-gnu.tar.gz` 和 `otelcol-contrib_0.151.0_linux_amd64.tar.gz` 并生成 `provision-tools/`;如果目标服务器下载需要代理,在 `PROVISION_DOWNLOAD_PROXY` 配置目标机可访问的 HTTP 代理。
|
||||
- 除 `Genarrative-Server-Provision` 外,`Genarrative-Stdb-Module-Build`、`Genarrative-Web-Build`、`Genarrative-Api-Build`、`Genarrative-*Deploy`、`Genarrative-Database-Import/Export`、`Genarrative-Full-Build-And-Deploy` 和 `Genarrative-Notify-Email` 的生产流水线现都以 Linux agent 为主,仍按各自 Jenkinsfile 的 checkout 口径执行。Server provision 不使用公网备用 Git 源。
|
||||
- `otelcol-contrib.service` 作为可选系统服务加入 provision,默认监听 `127.0.0.1:4317/4318` 并使用 `deploy/otelcol/genarrative-debug.yaml`。api-server 是否发送 OTLP 仍由 `GENARRATIVE_OTEL_ENABLED` 控制,服务 unit 见 `deploy/systemd/otelcol-contrib.service`。
|
||||
- Nginx `/api/` 与 `/admin/api/` 通过 `genarrative_api` upstream 代理到 `127.0.0.1:8082`,upstream keepalive 为 64;`limit_conn` 负责连接 / 并发保护,`limit_req` 负责入口 RPS 快拒绝。当前模板把公开 gallery list 单独放到 `genarrative_gallery_rps`,默认 `rate=5000r/s`、`burst=4096`、`limit_conn=320`;公开详情和普通 API 放到 `genarrative_api_rps`,后台 API 放到 `genarrative_admin_rps`。通用 `/api` location 设置 `client_max_body_size 64m` 是反代兜底,防止拼图入口页 / 新增关卡本地参考图 Data URL 或旧兼容请求在到达 `api-server` 前被默认 1 MiB 上限拦截;拼图本地参考图前后端统一限制 6MB,历史图片仍提交 `referenceImageAssetObjectId(s)`。若线上出现 `413 Request Entity Too Large` 且 access log 中 `request_time=0.000`、`upstream_status=-`,说明请求在 Nginx 层被拦截,先用 `nginx -T | grep client_max_body_size` 检查 release 模板是否已渲染并 reload,同时检查前端是否超出 6MB 或错误提交了未压缩大图。`limit_conn_status 429` 和 `limit_req_status 429` 必须在 HTTP 与 HTTPS server 中同时生效;若线上压测看到 `limiting connections by zone "genarrative_api_conn"` 却返回 503,优先检查 `nginx -T` 里 HTTPS server 是否缺少这些状态码,以及 `/api/runtime/puzzle/gallery` 是否误落到通用 `location ~ ^/api` 的 `limit_conn=64`。压测时看 `/var/log/nginx/genarrative.access.log` 中的 `request_time`、`upstream_connect_time`、`upstream_header_time`、`upstream_response_time`、`upstream_status`、`request_id`。
|
||||
|
||||
@@ -33,6 +33,9 @@ WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_APP_KEY=<现网 AppKey>
|
||||
WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_SANDBOX_APP_KEY=<沙箱 AppKey,可选>
|
||||
WECHAT_MINIPROGRAM_MESSAGE_TOKEN=<微信消息推送 Token>
|
||||
WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY=<微信消息推送 EncodingAESKey>
|
||||
WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_ENABLED=true
|
||||
WECHAT_MINIPROGRAM_GENERATION_RESULT_TEMPLATE_ID=m5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU
|
||||
WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE=formal
|
||||
WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_ENV=0
|
||||
```
|
||||
|
||||
@@ -69,4 +72,5 @@ npm run check:encoding
|
||||
- 沙箱或基础库失败会把微信返回的 `errCode` / `errMsg` 透传到前端失败弹窗,便于区分微信后台道具、沙箱 AppKey、签名和基础库能力问题。
|
||||
- Web 侧在拉起虚拟支付后会短时轮询 `wx_pay_result`,即使小程序 `web-view` 回写 hash 没触发浏览器 `hashchange`,也必须展示回写的微信错误内容。
|
||||
- WebView 返回但没有拿到 `wx_pay_result` 时,前端必须主动调用订单确认接口,并接入 `/api/profile/recharge/orders/{orderId}/wechat/events` 的 SSE 事件流作为服务端推送兜底;后端收到虚拟支付消息推送并入账后会发布订单更新,SSE 先推当前订单快照,再在订单结束时推 `done`。
|
||||
- 小程序订阅消息用于拼图 AI 创作生成结果通知:H5 在拼图 `compile_puzzle_draft` 生成动作发起前先把页面切到生成进度态并立即调用生成 action,同时非阻塞跳转到小程序原生订阅授权页尝试请求授权;授权接受、拒绝或页面返回都不得阻塞或取消生成。原生页不得改写上一页 `webViewUrl`,避免返回后丢失 H5 当前进度页状态。通知发送只允许发生在拼图后台首图 / UI 资产生成成功或失败终态之后,api-server 使用当前用户微信登录保存的 openid 调用微信 `subscribeMessage.send`。发送失败只记录 warning,不阻断作品生成。模板 `time4` 字段必须是北京时间 `YYYY-MM-DD HH:mm`。`WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE` 支持 `formal` / `trial` / `developer`,应与当前发布环境一致。
|
||||
- WebView 返回后,在订单状态拉取或 SSE 等待期间展示不可关闭遮罩“正在确认支付”,阻止用户离开或继续操作;只有确认到最终订单状态后才展示一次最终结果弹窗,不能先弹“正在支付/支付已提交”再二次弹成功。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -21,7 +21,7 @@ pipeline {
|
||||
string(name: 'PROVISION_DOWNLOADS_DIR', defaultValue: 'provision-tool-downloads', description: '目标服务器工作区内暂存 SpacetimeDB/otelcol 安装包的相对目录')
|
||||
string(name: 'PROVISION_TOOLS_DIR', defaultValue: 'provision-tools', description: '目标机工作区内由已下载安装包生成的工具包目录')
|
||||
string(name: 'PROVISION_DOWNLOAD_PROXY', defaultValue: '', description: '可选,目标服务器下载 SpacetimeDB 和 otelcol-contrib 时使用的代理地址,例如 http://127.0.0.1:7890;留空不设置代理')
|
||||
string(name: 'SPACETIME_DOWNLOAD_ROOT', defaultValue: 'https://github.com/clockworklabs/SpacetimeDB/releases/latest/download', description: '目标服务器使用的 SpacetimeDB Linux release tarball 根地址')
|
||||
string(name: 'SPACETIME_DOWNLOAD_ROOT', defaultValue: 'https://github.com/clockworklabs/SpacetimeDB/releases/download/v2.4.1', description: '目标服务器使用的 SpacetimeDB Linux release tarball 根地址;默认固定到项目锁定版本')
|
||||
string(name: 'SPACETIME_TARGET_HOST', defaultValue: 'x86_64-unknown-linux-gnu', description: 'SpacetimeDB 预编译包 host triple,development/release Linux amd64 使用默认值')
|
||||
string(name: 'SPACETIME_ROOT', defaultValue: '/stdb', description: 'SpacetimeDB root-dir')
|
||||
string(name: 'RELEASE_ROOT', defaultValue: '/opt/genarrative/releases', description: 'release 根目录')
|
||||
@@ -162,7 +162,7 @@ BASH
|
||||
OTELCOL_VERSION="${OTELCOL_VERSION:-0.151.0}" \
|
||||
PREPARE_OTELCOL="${ENABLE_OTELCOL:-true}" \
|
||||
PROVISION_DOWNLOAD_PROXY="${PROVISION_DOWNLOAD_PROXY:-}" \
|
||||
SPACETIME_DOWNLOAD_ROOT="${SPACETIME_DOWNLOAD_ROOT:-https://github.com/clockworklabs/SpacetimeDB/releases/latest/download}" \
|
||||
SPACETIME_DOWNLOAD_ROOT="${SPACETIME_DOWNLOAD_ROOT:-https://github.com/clockworklabs/SpacetimeDB/releases/download/v2.4.1}" \
|
||||
SPACETIME_TARGET_HOST="${SPACETIME_TARGET_HOST:-x86_64-unknown-linux-gnu}" \
|
||||
scripts/prepare-server-provision-tools.sh
|
||||
'
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"pages": ["pages/web-view/index", "pages/wechat-pay/index"],
|
||||
"pages": [
|
||||
"pages/web-view/index",
|
||||
"pages/wechat-pay/index",
|
||||
"pages/subscribe-message/index"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTitleText": "陶泥儿",
|
||||
"navigationBarBackgroundColor": "#0b0f14",
|
||||
|
||||
@@ -15,6 +15,10 @@ const MINI_PROGRAM_APP_ID = 'wx3da23ea14ca66b65';
|
||||
// 中文注释:仅作为运行时环境识别失败时的兜底;正常情况下由 wx.getAccountInfoSync 自动判断。
|
||||
const MINI_PROGRAM_ENV = 'release';
|
||||
|
||||
// 中文注释:AI 创作生成结果订阅消息模板,需与微信公众平台后台的模板 ID 保持一致。
|
||||
const GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID =
|
||||
'm5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU';
|
||||
|
||||
// 中文注释:给 H5 加一个来源标记,便于后续前端或后端识别这是微信小程序 web-view 宿主。
|
||||
const WEB_VIEW_SOURCE_QUERY = {
|
||||
clientType: 'mini_program',
|
||||
@@ -25,6 +29,7 @@ module.exports = {
|
||||
API_BASE_URL,
|
||||
DEV_API_BASE_URL,
|
||||
DEV_WEB_VIEW_ENTRY_URL,
|
||||
GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID,
|
||||
MINI_PROGRAM_APP_ID,
|
||||
MINI_PROGRAM_ENV,
|
||||
WEB_VIEW_ENTRY_URL,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/* global Page */
|
||||
|
||||
const { GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID } = require('../../config');
|
||||
const { createSubscribeMessagePage } = require('./index.shared');
|
||||
|
||||
Page(
|
||||
createSubscribeMessagePage(null, {
|
||||
templateId: GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "生成通知"
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/* global wx */
|
||||
|
||||
const SUBSCRIBE_RESULT_STORAGE_KEY = 'genarrative:wechat-subscribe-result';
|
||||
|
||||
function appendSubscribeResult(url, result) {
|
||||
const hashIndex = String(url || '').indexOf('#');
|
||||
const baseUrl =
|
||||
hashIndex >= 0 ? String(url).slice(0, hashIndex) : String(url || '');
|
||||
const rawHash = hashIndex >= 0 ? String(url).slice(hashIndex + 1) : '';
|
||||
const nextHash = rawHash
|
||||
.split('&')
|
||||
.filter((part) => part && !part.startsWith('wx_subscribe_result='))
|
||||
.concat(`wx_subscribe_result=${encodeURIComponent(result)}`)
|
||||
.join('&');
|
||||
return `${baseUrl}#${nextHash}`;
|
||||
}
|
||||
|
||||
function buildSubscribeResultValue(requestId, status, reason) {
|
||||
const segments = [requestId, status];
|
||||
if (reason) {
|
||||
segments.push(encodeURIComponent(reason));
|
||||
}
|
||||
return segments.join(':');
|
||||
}
|
||||
|
||||
function notifyPreviousWebView(requestId, status, reason) {
|
||||
const result = buildSubscribeResultValue(requestId, status, reason);
|
||||
wx.setStorageSync(SUBSCRIBE_RESULT_STORAGE_KEY, result);
|
||||
}
|
||||
|
||||
function resolveSubscribeStatus(result, templateId) {
|
||||
return result && result[templateId] === 'accept'
|
||||
? 'success'
|
||||
: 'skip';
|
||||
}
|
||||
|
||||
function createSubscribeMessagePage(pageContext, options = {}) {
|
||||
const templateId = String(options.templateId || '').trim();
|
||||
const notifyPageResult = (methodThis, status, reason) => {
|
||||
const page = pageContext ?? methodThis;
|
||||
const requestId = page.requestId || '';
|
||||
if (!requestId || page.hasNotifiedSubscribeResult) {
|
||||
return;
|
||||
}
|
||||
page.hasNotifiedSubscribeResult = true;
|
||||
notifyPreviousWebView(requestId, status, reason);
|
||||
};
|
||||
|
||||
return {
|
||||
data: {
|
||||
title: '接收生成结果通知',
|
||||
errorMessage: '',
|
||||
requesting: false,
|
||||
},
|
||||
|
||||
onLoad(query) {
|
||||
const page = pageContext ?? this;
|
||||
page.requestId = String(query.requestId || '');
|
||||
page.hasNotifiedSubscribeResult = false;
|
||||
},
|
||||
|
||||
notifyResult(status, reason) {
|
||||
notifyPageResult(this, status, reason);
|
||||
},
|
||||
|
||||
requestSubscribe() {
|
||||
const page = pageContext ?? this;
|
||||
const requestId = page.requestId || '';
|
||||
if (!requestId) {
|
||||
page.setData({
|
||||
errorMessage: '缺少订阅请求参数。',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!templateId) {
|
||||
notifyPageResult(this, 'skip', 'missing_template_id');
|
||||
wx.navigateBack();
|
||||
return;
|
||||
}
|
||||
if (typeof wx.requestSubscribeMessage !== 'function') {
|
||||
notifyPageResult(this, 'skip', 'unsupported');
|
||||
wx.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
page.setData({
|
||||
requesting: true,
|
||||
errorMessage: '',
|
||||
});
|
||||
wx.requestSubscribeMessage({
|
||||
tmplIds: [templateId],
|
||||
success(result) {
|
||||
notifyPageResult(
|
||||
page,
|
||||
resolveSubscribeStatus(result, templateId),
|
||||
'',
|
||||
);
|
||||
wx.navigateBack();
|
||||
},
|
||||
fail(error) {
|
||||
notifyPageResult(
|
||||
page,
|
||||
'skip',
|
||||
error && error.errMsg ? error.errMsg : 'failed',
|
||||
);
|
||||
wx.navigateBack();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
handleSkip() {
|
||||
notifyPageResult(this, 'skip', 'user_skip');
|
||||
wx.navigateBack();
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
notifyPageResult(this, 'skip', 'page_unload');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SUBSCRIBE_RESULT_STORAGE_KEY,
|
||||
appendSubscribeResult,
|
||||
buildSubscribeResultValue,
|
||||
createSubscribeMessagePage,
|
||||
resolveSubscribeStatus,
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import subscribeMessageBridge from './index.shared.js';
|
||||
|
||||
const TEST_TEMPLATE_ID = 'm5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU';
|
||||
|
||||
const {
|
||||
SUBSCRIBE_RESULT_STORAGE_KEY,
|
||||
appendSubscribeResult,
|
||||
buildSubscribeResultValue,
|
||||
createSubscribeMessagePage,
|
||||
} = subscribeMessageBridge;
|
||||
|
||||
describe('subscribe-message mini program bridge', () => {
|
||||
beforeEach(() => {
|
||||
globalThis.wx = {
|
||||
requestSubscribeMessage: vi.fn(),
|
||||
setStorageSync: vi.fn(),
|
||||
navigateBack: vi.fn(),
|
||||
};
|
||||
globalThis.getCurrentPages = vi.fn(() => []);
|
||||
});
|
||||
|
||||
test('requests subscribe message and stores result before returning', () => {
|
||||
const previousPage = {
|
||||
data: { webViewUrl: 'https://web.test/#tab=create' },
|
||||
setData: vi.fn(),
|
||||
};
|
||||
globalThis.getCurrentPages = vi.fn(() => [previousPage, {}]);
|
||||
globalThis.wx.requestSubscribeMessage.mockImplementationOnce((options) => {
|
||||
options.success?.({
|
||||
m5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU: 'accept',
|
||||
});
|
||||
});
|
||||
const page = createSubscribeMessagePage(
|
||||
{
|
||||
setData: vi.fn(),
|
||||
},
|
||||
{ templateId: TEST_TEMPLATE_ID },
|
||||
);
|
||||
page.onLoad({ requestId: 'request-1' });
|
||||
|
||||
page.requestSubscribe();
|
||||
|
||||
expect(globalThis.wx.requestSubscribeMessage).toHaveBeenCalledWith({
|
||||
tmplIds: [TEST_TEMPLATE_ID],
|
||||
success: expect.any(Function),
|
||||
fail: expect.any(Function),
|
||||
});
|
||||
expect(globalThis.wx.setStorageSync).toHaveBeenCalledWith(
|
||||
SUBSCRIBE_RESULT_STORAGE_KEY,
|
||||
'request-1:success',
|
||||
);
|
||||
expect(previousPage.setData).not.toHaveBeenCalled();
|
||||
expect(globalThis.wx.navigateBack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('skip action notifies previous web-view', () => {
|
||||
const previousPage = {
|
||||
data: { webViewUrl: 'https://web.test/' },
|
||||
setData: vi.fn(),
|
||||
};
|
||||
globalThis.getCurrentPages = vi.fn(() => [previousPage, {}]);
|
||||
const page = createSubscribeMessagePage(
|
||||
{
|
||||
setData: vi.fn(),
|
||||
},
|
||||
{ templateId: TEST_TEMPLATE_ID },
|
||||
);
|
||||
page.onLoad({ requestId: 'request-skip' });
|
||||
|
||||
page.handleSkip();
|
||||
|
||||
expect(globalThis.wx.setStorageSync).toHaveBeenCalledWith(
|
||||
SUBSCRIBE_RESULT_STORAGE_KEY,
|
||||
'request-skip:skip:user_skip',
|
||||
);
|
||||
expect(previousPage.setData).not.toHaveBeenCalled();
|
||||
expect(globalThis.wx.navigateBack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('appendSubscribeResult replaces stale subscribe hash', () => {
|
||||
expect(
|
||||
appendSubscribeResult(
|
||||
'https://web.test/#old=1&wx_subscribe_result=old',
|
||||
'req:skip',
|
||||
),
|
||||
).toBe('https://web.test/#old=1&wx_subscribe_result=req%3Askip');
|
||||
expect(buildSubscribeResultValue('req-1', 'skip', 'user_cancel')).toBe(
|
||||
'req-1:skip:user_cancel',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
<view class="subscribe-screen">
|
||||
<view class="subscribe-card">
|
||||
<view class="subscribe-title">{{title}}</view>
|
||||
<view wx:if="{{errorMessage}}" class="subscribe-text subscribe-text--danger">
|
||||
{{errorMessage}}
|
||||
</view>
|
||||
<button
|
||||
class="primary-button"
|
||||
loading="{{requesting}}"
|
||||
disabled="{{requesting}}"
|
||||
bindtap="requestSubscribe"
|
||||
>
|
||||
继续并接收通知
|
||||
</button>
|
||||
<button class="ghost-button" disabled="{{requesting}}" bindtap="handleSkip">
|
||||
仅继续生成
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,58 @@
|
||||
.subscribe-screen {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48rpx;
|
||||
background: #0b0f14;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.subscribe-card {
|
||||
width: 100%;
|
||||
max-width: 560rpx;
|
||||
padding: 36rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.14);
|
||||
border-radius: 12rpx;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.subscribe-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
color: #f5f7fb;
|
||||
}
|
||||
|
||||
.subscribe-text {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.55;
|
||||
color: rgba(245, 247, 251, 0.72);
|
||||
}
|
||||
|
||||
.subscribe-text--danger {
|
||||
color: #ffb4a9;
|
||||
}
|
||||
|
||||
.primary-button,
|
||||
.ghost-button {
|
||||
margin-top: 28rpx;
|
||||
width: 100%;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 2.6;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background: #f5f7fb;
|
||||
color: #0b0f14;
|
||||
}
|
||||
|
||||
.ghost-button {
|
||||
margin-top: 20rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.24);
|
||||
background: transparent;
|
||||
color: rgba(245, 247, 251, 0.86);
|
||||
}
|
||||
@@ -712,7 +712,7 @@ Page({
|
||||
},
|
||||
|
||||
handleWebViewMessage(event) {
|
||||
// 中文注释:支付由独立 native 页面承接,web-view 消息只保留调试输出。
|
||||
// 中文注释:支付和订阅消息都由独立 native 页面承接,web-view 消息只保留调试输出。
|
||||
console.info('[web-view] message', event.detail);
|
||||
},
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ export interface BarkBattleWorkPublishRequest {
|
||||
export interface BarkBattleImageAssetGenerateRequest {
|
||||
slot: BarkBattleAssetSlot;
|
||||
draftId?: string | null;
|
||||
billingPurpose?: 'initial_draft_generation' | null;
|
||||
config: BarkBattleConfigEditorPayload;
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -408,7 +408,11 @@ function readWorkspaceSpacetimeVersion() {
|
||||
if (!version) {
|
||||
throw new Error('无法从 server-rs/Cargo.toml 读取 spacetimedb 版本');
|
||||
}
|
||||
return version;
|
||||
return normalizeCargoVersionRequirement(version);
|
||||
}
|
||||
|
||||
function normalizeCargoVersionRequirement(version) {
|
||||
return version.replace(/^=/u, '');
|
||||
}
|
||||
|
||||
function parseSpacetimeToolVersion(output) {
|
||||
@@ -2130,6 +2134,7 @@ export {
|
||||
createWatchConfigs,
|
||||
isSpacetimePublishPermissionError,
|
||||
isDirectModuleExecution,
|
||||
normalizeCargoVersionRequirement,
|
||||
parseSpacetimeToolVersion,
|
||||
parseArgs,
|
||||
resolveDevStackStatePath,
|
||||
|
||||
+11
-5
@@ -15,6 +15,7 @@ import {
|
||||
createWatchConfigs,
|
||||
isDirectModuleExecution,
|
||||
isSpacetimePublishPermissionError,
|
||||
normalizeCargoVersionRequirement,
|
||||
parseSpacetimeToolVersion,
|
||||
parseArgs,
|
||||
resolveDevStackStatePath,
|
||||
@@ -34,7 +35,7 @@ function workspaceSpacetimeVersionForTest() {
|
||||
if (!match) {
|
||||
throw new Error('无法读取测试用 SpacetimeDB 版本');
|
||||
}
|
||||
return match[1];
|
||||
return normalizeCargoVersionRequirement(match[1]);
|
||||
}
|
||||
|
||||
describe('dev scheduler argument routing', () => {
|
||||
@@ -402,20 +403,25 @@ describe('dev scheduler watch routing', () => {
|
||||
});
|
||||
|
||||
describe('dev scheduler spacetime refresh', () => {
|
||||
test('解析 Cargo 精确版本要求时用于 CLI 校验的版本号不带等号', () => {
|
||||
expect(normalizeCargoVersionRequirement('=2.4.1')).toBe('2.4.1');
|
||||
expect(normalizeCargoVersionRequirement('2.4.1')).toBe('2.4.1');
|
||||
});
|
||||
|
||||
test('解析 spacetime --version 输出里的 tool version', () => {
|
||||
const version = parseSpacetimeToolVersion(`
|
||||
A new version of SpacetimeDB is available: v2.3.0 (current: v2.2.0)
|
||||
spacetimedb tool version 2.3.0; spacetimedb-lib version 2.3.0;
|
||||
A new version of SpacetimeDB is available: v2.4.1 (current: v2.4.0)
|
||||
spacetimedb tool version 2.4.1; spacetimedb-lib version 2.4.1;
|
||||
`);
|
||||
|
||||
expect(version).toBe('2.3.0');
|
||||
expect(version).toBe('2.4.1');
|
||||
});
|
||||
|
||||
test('本机 spacetime 版本和 workspace 锁定版本不一致时直接报清楚', () => {
|
||||
expect(() =>
|
||||
assertSpacetimeToolVersionMatchesWorkspace({
|
||||
toolVersion: '2.1.0',
|
||||
workspaceVersion: '2.3.0',
|
||||
workspaceVersion: '2.4.1',
|
||||
}),
|
||||
).toThrow('procedure 返回值 BSATN 反序列化失败');
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ PREPARE_OTELCOL="${PREPARE_OTELCOL:-${ENABLE_OTELCOL:-true}}"
|
||||
OTELCOL_DOWNLOAD_ROOT="${OTELCOL_DOWNLOAD_ROOT:-https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download}"
|
||||
OTELCOL_ARCHIVE_PATH="${OTELCOL_ARCHIVE_PATH:-}"
|
||||
SPACETIME_INSTALLER_URL="${SPACETIME_INSTALLER_URL:-https://install.spacetimedb.com}"
|
||||
SPACETIME_DOWNLOAD_ROOT="${SPACETIME_DOWNLOAD_ROOT:-https://github.com/clockworklabs/SpacetimeDB/releases/latest/download}"
|
||||
SPACETIME_DOWNLOAD_ROOT="${SPACETIME_DOWNLOAD_ROOT:-https://github.com/clockworklabs/SpacetimeDB/releases/download/v2.4.1}"
|
||||
SPACETIME_TARGET_HOST="${SPACETIME_TARGET_HOST:-x86_64-unknown-linux-gnu}"
|
||||
SPACETIME_ARCHIVE_PATH="${SPACETIME_ARCHIVE_PATH:-}"
|
||||
SPACETIME_INSTALLER_PATH="${SPACETIME_INSTALLER_PATH:-}"
|
||||
|
||||
Generated
+51
-38
@@ -129,6 +129,7 @@ dependencies = [
|
||||
"platform-llm",
|
||||
"platform-oss",
|
||||
"platform-speech",
|
||||
"platform-wechat",
|
||||
"reqwest 0.12.28",
|
||||
"ring",
|
||||
"serde",
|
||||
@@ -664,7 +665,7 @@ dependencies = [
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -851,7 +852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1341,7 +1342,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.3",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -2112,7 +2113,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2508,6 +2509,17 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platform-wechat"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
@@ -2659,7 +2671,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.6.3",
|
||||
"socket2 0.5.10",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -2696,9 +2708,9 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.6.3",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2990,7 +3002,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3480,9 +3492,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62aa9a940d32178e4afa7a14c9bbda76685d71d5487798905e0139b807182092"
|
||||
checksum = "536e289684a624421eae421310d2f997a12f1be70e86b3692c87b837cbbb5a33"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytemuck",
|
||||
@@ -3503,9 +3515,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-bindings-macro"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5feb5d55f04f3209764d9b94949226708a4a8578e92ac5c32abfd31dbdfc928c"
|
||||
checksum = "53256d52b684b899b92b0fbd93f3a654458feb76290893ef13d57900fd38cfd5"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"humantime",
|
||||
@@ -3517,18 +3529,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-bindings-sys"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28f9c900c9371fd7e84d34b8cb2bf90562060dc2473ae9c44e970d4026e7d7d9"
|
||||
checksum = "dba2d0109f7f2aa4cf6f349b8145268b214d0348b8e409005452d65b61139080"
|
||||
dependencies = [
|
||||
"spacetimedb-primitives",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-client-api-messages"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349296ad43e6ecdced74ad8b3fd2c6abbdbb40cdbd06ac329c0726c6b911fa73"
|
||||
checksum = "014a905d52635c0dcb4fde3092fcc001e770d0d34c2e406b837d81196a630423"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytestring",
|
||||
@@ -3548,9 +3560,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-data-structures"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b86ed7c567d723378a405317e30413293cc0bc9e2aac2f7843580d744b43c31"
|
||||
checksum = "823d3b3ecac3e8e948f254ee69e3fb848c8c0b4e6f92568bcfdeead1c98c4ff4"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"crossbeam-queue",
|
||||
@@ -3563,13 +3575,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-lib"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15a98adf6f030e8188df4b038e140a54771e6eeb50ad05a6c3e46939b8de853"
|
||||
checksum = "7aecb06dc09f1e964a30f9de87404f21470a10f4f988ef668494b8ea53a4f920"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.11.1",
|
||||
"blake3",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"derive_more",
|
||||
"enum-as-inner",
|
||||
@@ -3587,9 +3600,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-memory-usage"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c68aa8ed30c15a1d665bf3a8c689955508ce75ca784068ec0232b4cdd511b4c8"
|
||||
checksum = "9ce5f8d17fe9432e0d6b6e04f46001ce0459ef70236e193bd5b17b3f71dd7731"
|
||||
dependencies = [
|
||||
"decorum",
|
||||
"ethnum",
|
||||
@@ -3597,9 +3610,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-metrics"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8455b0a92dd632757f7e7c22d5e438aa33da5f48d14483e5ee79dfc5468a4db"
|
||||
checksum = "4d178e28a736a326574c39753107b64a13cac7c04a57948f80caab8cadc1b7d8"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"itertools",
|
||||
@@ -3609,9 +3622,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-primitives"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b20cc4bf97377f1dce9e75b2f6ce94bc5c7c2a243040a7a2016ac5cdb002793d"
|
||||
checksum = "2be40c852541973b8faf8c74957ade687579cfc5badd09b1060fb83e9a4fbec8"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"either",
|
||||
@@ -3623,18 +3636,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-query-builder"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afa17878dbc23b4bfc06a45165c7afd34c8d29bba6dfde81625840c11380abce"
|
||||
checksum = "857603c65a283e190b7e0a8bb62c8ff3fbd88cf97b0ae34454862e0caf2a30b7"
|
||||
dependencies = [
|
||||
"spacetimedb-lib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-sats"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74216db354eab5cefad9572a350654761495968478e83e51ef2c530cdf6cb1d4"
|
||||
checksum = "0290133e753457920bc975872edbb78559cf2003c78d3f2a8e3f2ecc288229d3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec",
|
||||
@@ -3665,9 +3678,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-schema"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff3ff36f901f6875907ff1df2b610fc396937b88f6793dfa04b0d9f298d74946"
|
||||
checksum = "c54cac9350fe39d35002089af31417d28c85d44eca66646a1515f99117db03e0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -3696,9 +3709,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-sdk"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26897e31aa58acd6cd0bcf12cf56a4ecd0cb4fc48053478626729e868f042e54"
|
||||
checksum = "82ac34a6f244a0b7114ae52c94db0b85ef3e4bddcfa54adcfc8198e0143b638a"
|
||||
dependencies = [
|
||||
"anymap3",
|
||||
"base64 0.21.7",
|
||||
@@ -3728,9 +3741,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spacetimedb-sql-parser"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19428d4ddc8cf1eb34e58715ed512820ee9a3de187a89f88f80e18e914a086ae"
|
||||
checksum = "2fc9150d2dba445942d1c82b3d6e56f003ca024069eb43d1ccd06c1129ee1294"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"spacetimedb-lib",
|
||||
@@ -3855,7 +3868,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4626,7 +4639,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user