Merge branch 'codex/backend-rewrite-spacetimedb' of http://82.157.175.59:3000/GenarrativeAI/Genarrative into codex/backend-rewrite-spacetimedb

This commit is contained in:
2026-04-23 03:51:23 +08:00
18 changed files with 1809 additions and 48 deletions

View File

@@ -0,0 +1,173 @@
# Jenkins Rust 构建与部署流水线方案
日期:`2026-04-23`
## 1. 目标
本方案为当前仓库补齐 3 条 Jenkins 流水线:
1. `构建`:只负责在仓库根目录执行 `npm run deploy:rust:remote -- --skip-upload`,生成发布包。
2. `部署`:只负责把指定发布版本部署到 `/home/ubuntu/Genarrative-deploy/`,禁止人工直接点击执行。
3. `构建并部署`:先构建,再把构建出的版本号传给 `部署` 流水线并等待部署完成。
本次只补 Jenkins 编排与本地部署脚本,不改现有 Rust 发布包构建逻辑,不恢复旧 `server-node` 部署链。
## 2. 执行约束
1. 构建产物目录统一使用 `build/<版本号>/`
2. 默认使用 Jenkins `BUILD_NUMBER` 作为版本号,避免依赖时间戳;如有需要也允许显式传 `BUILD_VERSION`
3. `部署` 流水线必须校验当前构建原因包含 `UpstreamCause`,没有上游触发则直接失败。
4. `部署` 流水线额外校验上游作业名与传入的 `EXPECTED_UPSTREAM_JOB` 一致;如配置了环境变量 `GENARRATIVE_ALLOWED_UPSTREAM_JOB`,还必须与该值一致。
5. `构建并部署` 在触发 `部署` 前先释放自己的构建节点,避免单执行器节点出现死锁。
6. `部署` 不重新构建,不重新上传,不从 Jenkins 插件仓库复制产物,直接使用上游构建节点的本地 `build/<版本号>/` 目录。
7. `部署` 流水线读取触发原因时必须使用 `currentBuild.getBuildCauses('hudson.model.Cause$UpstreamCause')` 这类白名单方法,不能直接访问 `currentBuild.rawBuild`,否则会被 Jenkins Script Security 拦截。
## 3. 节点与工作区要求
这套方案依赖“本地目录发布”,因此有两个前提:
1. `构建并部署``部署` 必须落到同一台 Ubuntu Jenkins Agent或者落到同一块共享文件系统。
2. `构建并部署` 触发 `部署` 时,必须把 `SOURCE_NODE_NAME``SOURCE_WORKSPACE_ROOT` 一并传下去。
仓库中提供的 Jenkinsfile 已按这个约束实现:
1. `构建` / `构建并部署` 在指定源码目录内 `checkout scm` 并生成 `build/<版本号>/`
2. `构建并部署` 结束构建节点占用后,再触发 `部署`
3. `部署` 优先按 `SOURCE_NODE_NAME` 调度到同名节点,再读取 `SOURCE_WORKSPACE_ROOT/build/<版本号>/`
## 4. 三条流水线定义
### 4.1 构建
脚本路径:
```text
jenkins/Jenkinsfile.build
```
核心流程:
1. 可选执行 `npm ci`
2. 在源码根目录执行:
```bash
npm run deploy:rust:remote -- --skip-upload --name <BUILD_VERSION>
```
3. 校验 `build/<BUILD_VERSION>/` 存在。
4. 归档 `build/<BUILD_VERSION>/**` 作为 Jenkins 产物。
默认版本号:
```text
BUILD_VERSION = Jenkins BUILD_NUMBER
```
### 4.2 部署
脚本路径:
```text
jenkins/Jenkinsfile.deploy
```
核心流程:
1. 校验触发原因必须是上游流水线,而不是人工点击。
2. 校验 `BUILD_VERSION``SOURCE_WORKSPACE_ROOT``DEPLOY_DIRECTORY` 非空。
3. 执行:
```bash
scripts/jenkins-deploy-release.sh \
--source-dir <SOURCE_WORKSPACE_ROOT>/build/<BUILD_VERSION> \
--deploy-dir /home/ubuntu/Genarrative-deploy
```
脚本语义:
1. 若部署目录已有旧版本且存在 `stop.sh`,先执行旧版本 `stop.sh`
2. 直接清空部署目录中的全部旧文件。
3. 将指定版本目录中的内容移动到部署目录。
4. 执行新版本 `start.sh`
这样可以满足你要求的“直接覆盖部署目录中的所有文件”。同时这也意味着部署目录内原有的 `.env``.env.local`、日志和本地 SpacetimeDB 数据都会被清掉,最终以构建产物中的文件为准。
### 4.3 构建并部署
脚本路径:
```text
jenkins/Jenkinsfile.build-and-deploy
```
核心流程:
1. 复用与 `构建` 相同的构建命令生成 `build/<BUILD_VERSION>/`
2. 归档 `build/<BUILD_VERSION>/**`
3. 记录当前 `NODE_NAME`、源码根目录、版本号。
4. 触发 `部署` 流水线,并传递:
- `BUILD_VERSION`
- `SOURCE_WORKSPACE_ROOT`
- `SOURCE_NODE_NAME`
- `DEPLOY_DIRECTORY`
- `EXPECTED_UPSTREAM_JOB`
## 5. Jenkins 参数建议
三条流水线统一建议暴露以下参数:
1. `AGENT_LABEL`:默认执行节点标签。
2. `GENARRATIVE_WORKSPACE_ROOT`:源码根目录;为空时回退到 Jenkins 当前工作区。
3. `BUILD_VERSION`:发布版本号;为空时回退到 `BUILD_NUMBER`
4. `RUN_NPM_CI`:是否在构建前执行 `npm ci`
如果当前 Jenkins 没有额外配置独立 Agent而是直接在控制器自身执行任务`AGENT_LABEL` 应填写 `built-in`
如果目标 Ubuntu 的 Jenkins `sh` 默认实际落到 `/bin/sh -> dash`,而流水线脚本又使用了 `set -euo pipefail`,则必须显式通过 `bash -lc` 执行命令,不能直接依赖 Jenkins 默认 `sh` 解释器。
其中仅 `部署` 流水线还需要:
1. `SOURCE_WORKSPACE_ROOT`
2. `SOURCE_NODE_NAME`
3. `DEPLOY_DIRECTORY`
4. `EXPECTED_UPSTREAM_JOB`
其中仅 `构建并部署` 流水线还需要:
1. `DEPLOY_JOB_NAME`
## 6. 推荐 Job 命名
建议在 Jenkins 中创建以下 3 个 Pipeline Job并分别指向仓库中的脚本路径
1. `Genarrative-Build` -> `jenkins/Jenkinsfile.build`
2. `Genarrative-Deploy` -> `jenkins/Jenkinsfile.deploy`
3. `Genarrative-Build-And-Deploy` -> `jenkins/Jenkinsfile.build-and-deploy`
同时给 `Genarrative-Deploy` 配置环境变量:
```text
GENARRATIVE_ALLOWED_UPSTREAM_JOB=Genarrative-Build-And-Deploy
```
如果 Job 在 Jenkins Folder 下,值应填写完整上游作业名,例如:
```text
game/Genarrative-Build-And-Deploy
```
## 7. 文件清单
本方案对应的仓库文件:
```text
jenkins/Jenkinsfile.build
jenkins/Jenkinsfile.deploy
jenkins/Jenkinsfile.build-and-deploy
scripts/jenkins-deploy-release.sh
```
## 8. 风险与边界
1. 该方案依赖本地目录切换,不适用于“构建节点”和“部署节点”完全隔离且不共享文件系统的 Jenkins 架构。
2. 当前 `部署` 采取的是“覆盖固定部署目录”的方式,不包含版本回滚目录管理;如需保留完整历史版本,应在后续单独补一层 release/current 软链接结构。
3. 当前 `start.sh` / `stop.sh` 仍以发布包内脚本为准,不替代 `systemd``supervisor``nginx``tls` 与日志轮转治理。

View File

@@ -0,0 +1,163 @@
# Axum 手机验证码真实短信 Provider 接入设计
日期:`2026-04-22`
## 1. 文档目的
这份文档用于冻结 Rust `api-server + module-auth + platform-auth` 切换到真实短信链路时的最小可编码边界,解决当前 `module-auth` 仍固定使用 mock 验证码 `123456`,导致 `server-rs` 无法接入真实短信发送与真实验证码校验的问题。
## 2. 当前问题
截至 `2026-04-22`Rust 侧手机号登录存在以下状态:
1. `POST /api/auth/phone/send-code` 已存在,但 `module-auth` 内部仍写死 `123456`
2. `POST /api/auth/phone/login` 校验的是本地内存快照里的固定验证码,不是真实短信平台生成的验证码。
3. 即使把发送动作切到真实阿里云短信,如果校验仍留在本地 mock整条登录链仍然不可用。
## 3. 本次目标
本次必须达成:
1. Rust 侧短信 provider 支持 `mock``aliyun` 两种模式。
2. `send-code``aliyun` 模式下调用阿里云 `SendSmsVerifyCode`
3. `phone/login``wechat/bind-phone``aliyun` 模式下调用阿里云 `CheckSmsVerifyCode`
4. `module-auth` 不再保存验证码明文,只保存发送冷却、有效期和失败次数所需的最小快照。
5. `shared-contracts` 公开响应 contract 维持不变,仍只返回:
- `ok`
- `cooldownSeconds`
- `expiresInSeconds`
- `providerRequestId`
## 4. crate 边界
### 4.1 `platform-auth`
负责:
1. 短信 provider 配置结构。
2. `mock / aliyun` provider 实现。
3. 阿里云 RPC 请求签名、发送与校验。
4. provider 级错误归一化。
### 4.2 `module-auth`
负责:
1. 手机号归一化。
2. 发送冷却与验证码快照 TTL。
3. 校验失败次数累加与耗尽删除。
4. 手机号用户创建、复用、微信补绑归并。
### 4.3 `api-server`
负责:
1. 从环境变量读取短信 provider 配置。
2. 构建 `SmsAuthProvider` 并注入 `PhoneAuthService`
3. 把领域错误映射成 HTTP 错误。
## 5. 配置设计
新增或继续使用以下环境变量:
1. `SMS_AUTH_ENABLED`
2. `SMS_AUTH_PROVIDER`
- `mock`
- `aliyun`
3. `ALIYUN_SMS_ENDPOINT`
- 默认 `dypnsapi.aliyuncs.com`
4. `ALIYUN_SMS_ACCESS_KEY_ID`
5. `ALIYUN_SMS_ACCESS_KEY_SECRET`
6. `ALIYUN_SMS_SIGN_NAME`
7. `ALIYUN_SMS_TEMPLATE_CODE`
8. `ALIYUN_SMS_TEMPLATE_PARAM_KEY`
- 默认 `code`
9. `ALIYUN_SMS_COUNTRY_CODE`
- 默认 `86`
10. `ALIYUN_SMS_SCHEME_NAME`
11. `ALIYUN_SMS_CODE_LENGTH`
- 默认 `6`
12. `ALIYUN_SMS_CODE_TYPE`
- 默认 `1`
13. `ALIYUN_SMS_VALID_TIME_SECONDS`
- 默认 `300`
14. `ALIYUN_SMS_INTERVAL_SECONDS`
- 默认 `60`
15. `ALIYUN_SMS_DUPLICATE_POLICY`
- 默认 `1`
16. `ALIYUN_SMS_CASE_AUTH_POLICY`
- 默认 `1`
17. `ALIYUN_SMS_RETURN_VERIFY_CODE`
- 默认 `false`
18. `SMS_AUTH_MOCK_VERIFY_CODE`
- 默认 `123456`
## 6. provider 行为
### 6.1 `mock`
1. 发送验证码时不访问外部网络。
2. 返回固定 `mock-request-id`
3. 校验时使用内存中的 mock 验证码。
### 6.2 `aliyun`
1. 发送验证码调用 `SendSmsVerifyCode`
2. 校验验证码调用 `CheckSmsVerifyCode`
3. 使用阿里云 RPC 签名口径:
- `SignatureMethod=HMAC-SHA1`
- `SignatureVersion=1.0`
4. 当前仍只支持中国大陆手机号。
## 7. 状态与快照
`module-auth` 内部验证码快照保留:
1. `phone_number`
2. `scene`
3. `expires_at`
4. `last_sent_at`
5. `failed_attempts`
明确不再保留:
1. 验证码明文
2. 验证码 hash
校验流程改为:
1. 先检查是否存在活跃快照。
2. 再检查是否过期。
3. 再调用 provider 做真实验证码校验。
4. 校验失败时累加失败次数。
5. 达到上限时删除快照并返回 `429`
6. 校验成功后删除快照。
## 8. 错误语义
1. 手机号格式错误:`400`
2. 验证码格式错误:`400`
3. 验证码不存在或已过期:`400`
4. 校验失败:`400`
5. 验证码错误次数耗尽:`429`
6. 阿里云配置缺失:`500`
7. 阿里云上游失败:`502`
## 9. 测试要求
至少覆盖:
1. `mock` provider 的发送与登录仍可跑通。
2. `aliyun` provider 缺配置时会在服务初始化阶段报错。
3. 发送冷却逻辑不依赖验证码明文仍然有效。
4. 校验失败次数耗尽后会删除快照。
5. `send-code` 成功时仍返回既有 contract。
## 10. 非目标
本次明确不做:
1. 短信送达回执接口
2. `sms_auth_event` 真实持久化
3. 图形验证码
4. 更细粒度的 provider 错误码透传 DTO

View File

@@ -7,6 +7,7 @@
- [CUSTOM_WORLD_AGENT_LLM_REPLY_RESTORE_2026-04-22.md](./CUSTOM_WORLD_AGENT_LLM_REPLY_RESTORE_2026-04-22.md):恢复 Custom World Agent 聊天必须走大模型推理的 Rust 落地方案,冻结 submit/finalize 两阶段职责、旧 Node 提示词原样搬运、SSE 流式回复与 session 回写边界。
- [CREATION_HUB_CARD_ACTIONS_2026-04-22.md](./CREATION_HUB_CARD_ACTIONS_2026-04-22.md):冻结创作中心作品卡“体验 / 删除”入口的最小落地语义,明确 RPG 已发布作品软删除、卡片直达运行时,以及暂不扩草稿 / 拼图删除契约。
- [CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md](./CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md):记录创作中心点击类别后长时间停留在“正在开启”的根因与修复口径,收口前端创建会话启动超时、中文错误提示以及 Big Fish / 拼图代理上游超时兜底。
- [JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md](./JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md):冻结 Jenkins `构建 / 部署 / 构建并部署` 三条流水线的职责、版本号传递、上游触发门禁、本地目录部署脚本与 `/home/ubuntu/Genarrative-deploy/` 覆盖策略。
- [RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md](./RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md):冻结 Rust 本地一键联调脚本与 Ubuntu 发布包构建脚本的执行口径,覆盖 `npm run dev:rust``npm run build:rust:ubuntu`、Vite release、Linux `api-server`、SpacetimeDB wasm、启动停止脚本、默认 scp 上传和安全清库开关。
- [RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md](./RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md):记录当前 Rust `api-server` 已挂载的 96 条 Axum 路由,按 auth、assets、runtime、custom world、story、generated path 等挂载面归类,用于对照 Node 能力基线与切流 smoke 清单。
- [BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md](./BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md):冻结后端重写收口阶段的横向治理规则,覆盖 TypeScript contract 到 Rust DTO 映射、SpacetimeDB schema 演进、大对象 / workflow cache 存储边界和文档维护门禁。
@@ -19,6 +20,10 @@
- [AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md](./AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md)`/api/auth/sessions` 会话列表设计,冻结当前设备识别、多端登录字段映射、`clientLabel` 兼容策略与 Rust 首版接口边界。
- [PHONE_AUTH_AXUM_MINIMAL_FLOW_DESIGN_2026-04-21.md](./PHONE_AUTH_AXUM_MINIMAL_FLOW_DESIGN_2026-04-21.md):手机号验证码登录最小闭环设计,冻结 mock 验证码规则、`send-code` / `phone/login` contract 与 crate 边界。
- [PHONE_AUTH_AXUM_RATE_LIMIT_AND_FAILURE_DESIGN_2026-04-21.md](./PHONE_AUTH_AXUM_RATE_LIMIT_AND_FAILURE_DESIGN_2026-04-21.md):手机号验证码冷却与失败次数限制设计,冻结同手机号同场景发送冷却、错误次数耗尽、`429``Retry-After` contract。
- [PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](./PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md):冻结 Rust `api-server + module-auth + platform-auth` 接入真实阿里云短信 provider 的 crate 边界、发送与校验职责、配置项和错误语义。
- [PHONE_SMS_ALIYUN_RESPONSE_FIELD_MAPPING_FIX_2026-04-23.md](./PHONE_SMS_ALIYUN_RESPONSE_FIELD_MAPPING_FIX_2026-04-23.md):记录 Rust `platform-auth` 把阿里云 PascalCase 响应字段误判成空值的问题根因,并冻结字段映射修复与回归标准。
- [PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md](./PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md):冻结短信平台受理成功与最终送达状态的区分方式、追踪字段、送达回执接口和前端提示文案边界。
- [PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md):冻结验证清单第一项“真实短信验证码链路”的本地启动、前端操作、日志观察点、通过标准与失败排查步骤。
- [WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md](./WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md)Rust `api-server` 微信登录实现设计,冻结微信 provider 接入、系统 JWT 签发边界、`wechat/start` / `wechat/callback` / `wechat/bind-phone` 闭环,以及与后续 `SpacetimeDB` claims 透传的关系。
- [WECHAT_LOGIN_REAL_INTEGRATION_RUNBOOK_2026-04-21.md](./WECHAT_LOGIN_REAL_INTEGRATION_RUNBOOK_2026-04-21.md):微信登录从本地 mock 到真实微信开放平台联调的执行手册,覆盖环境变量、回调域名、代理头要求、验证步骤与常见失败排查。
- [PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md](./PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md):密码登录与自动建号落地设计,冻结 `/api/auth/entry`、幂等兼容策略、模块边界以及与 JWT / refresh cookie 的衔接方式。

View File

@@ -7,7 +7,7 @@
本方案补齐 `server-rs` 在 M7 切流前需要的两类工程脚本:
1. 本地一键联调脚本:同时启动本地 SpacetimeDB、Rust `api-server` 与 Web 前端,并通过现有 Vite 代理开关把运行时 API 指向 Rust。
2. Ubuntu 发布包构建脚本:在仓库根目录生成 `build/<当前时间>/` 发布目录,内含前端 release、Linux `api-server`、SpacetimeDB wasm、启动脚本停止脚本,并默认通过 `scp` 上传到目标服务器。
2. Ubuntu 发布包构建脚本:在仓库根目录生成 `build/<当前时间>/` 发布目录,内含前端 release、Linux `api-server`、SpacetimeDB wasm、启动脚本停止脚本,以及从仓库根目录复制的 `.env` / `.env.local`并默认通过 `scp` 上传到目标服务器。
脚本只做部署与联调编排,不改变 HTTP contract、SpacetimeDB schema 命名、对象存储键规划和前端默认 Node 开发入口。
@@ -102,15 +102,20 @@ npm run deploy:rust:remote
3. 使用 Vite 构建前端 release 到目标目录的 `web/`
4. 执行 `cargo build -p api-server --release --target x86_64-unknown-linux-gnu --manifest-path server-rs/Cargo.toml`,并把 `api-server` 复制到目标目录。
5. 执行 `cargo build -p spacetime-module --release --target wasm32-unknown-unknown --manifest-path server-rs/Cargo.toml`,并把 `spacetime_module.wasm` 复制到目标目录。
6. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*``/generated-*``/healthz` 反代到本包内的 `api-server`
7. 在目标目录写入 `start.sh``stop.sh`
8. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/<timestamp> ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包
6. 把仓库根目录的 `.env``.env.local` 分别复制到目标目录根部和目标目录的 `web/`
7. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*``/generated-*``/healthz` 反代到本包内的 `api-server`
8. 在目标目录写入 `start.sh``stop.sh`
9. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/<timestamp> ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包。
发布包结构:
```text
build/<timestamp>/
├─ .env
├─ .env.local
├─ web/
│ ├─ .env
│ └─ .env.local
├─ api-server
├─ spacetime_module.wasm
├─ web-server.mjs
@@ -137,8 +142,8 @@ cd build/<timestamp>
安全边界:
1. 构建脚本不读取、不传输、不打印生产密钥
2. 目标服务器 `.env``.env.local` 或进程环境仍由服务器本身维护
1. 构建脚本会把仓库根目录已有的 `.env``.env.local` 一并复制进发布包,因此运行前必须确认这些文件内容适合被带入目标环境
2. 如果仓库根目录不存在 `.env``.env.local`,脚本会打印跳过日志,但不会因此失败
3. `start.sh` 默认不清空 SpacetimeDB只有显式执行 `./start.sh --clear-database` 才允许清库重发。
4. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm。
5. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。