1
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# 创作中心退出登录私有缓存清理修复 2026-04-25
|
||||
|
||||
## 问题
|
||||
|
||||
点击退出登录后,页面未刷新时仍能切到创作中心,并看到上一位登录用户的作品。刷新页面后才恢复正常。
|
||||
|
||||
## 根因
|
||||
|
||||
1. `AuthGate` 的退出动作先等待 `/api/auth/logout` 完成,再通过全局鉴权事件重新 hydrate,期间前端 context 仍可能暴露旧用户。
|
||||
2. 平台创作入口里的 RPG works 会在 `canReadProtectedData=false` 时清空,但大鱼吃小鱼与拼图 works 是 `PlatformEntryFlowShellImpl` 内部 state,没有在退出登录时同步清空。
|
||||
3. 创作 Tab 会保持挂载以降低闪烁,因此私有作品数组只要留在内存里,就会继续被货架组件渲染。
|
||||
|
||||
## 修复口径
|
||||
|
||||
1. 用户触发退出当前设备或退出全部设备时,前端必须先本地收回 `user / canAccessProtectedData`,再等待后端吊销会话。
|
||||
2. `canReadProtectedData` 从 `true` 变为未登录态 `false` 时,创作中心必须清空所有私有作品缓存:
|
||||
- RPG works / library 由 `useRpgEntryBootstrap` 清空。
|
||||
- Big Fish works、Puzzle works 由 `PlatformEntryFlowShellImpl` 清空。
|
||||
- 当前创作工作区、结果页、删除忙碌态与生成态一并复位。
|
||||
3. 公开广场与分类数据不受影响,仍按匿名公开接口读取。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 点击退出登录后,不刷新页面进入创作 Tab,只能看到空作品货架,不再出现上一账号作品。
|
||||
2. 退出登录瞬间 `AuthUiContext.user` 为 `null`,`canAccessProtectedData=false`。
|
||||
3. 重新登录后按新账号重新拉取作品列表,不复用旧账号内存缓存。
|
||||
31
docs/technical/JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md
Normal file
31
docs/technical/JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Jenkins 部署环境文件 BOM 修复
|
||||
|
||||
日期:`2026-04-25`
|
||||
|
||||
## 1. 问题
|
||||
|
||||
Jenkins 部署阶段执行固定目录内的 `start.sh` 时失败:
|
||||
|
||||
```text
|
||||
/var/lib/jenkins/deploy/Genarrative/.env.local: line 1: VITE_LLM_BASE_URL=...: No such file or directory
|
||||
```
|
||||
|
||||
根因是 `.env.local` 第一行包含 UTF-8 BOM。旧版 `start.sh` 直接 `source .env.local`,BOM 会成为变量名前缀,Bash 无法按赋值语句解析,进而把整行当作命令执行。日志末尾的 sudo 提示只是 hook 执行失败后的兜底提示,不是本次失败的真实根因。
|
||||
|
||||
## 2. 修复口径
|
||||
|
||||
1. 发布包构建脚本复制 `.env`、`.env.local` 到发布目录和 `web/` 目录后,统一移除 UTF-8 BOM 与 CRLF。
|
||||
2. Jenkins 部署脚本在移动发布产物前后,再次净化发布目录和固定部署目录中的 `.env`、`.env.local`,兼容已经构建出来但尚未部署成功的旧发布包。
|
||||
3. 新生成的 `start.sh` 不再直接 `source` 环境文件,而是按 `KEY=value` 子集解析、导出合法变量,并跳过空行、注释和不合法行。
|
||||
4. `start.sh` 仍保留 `.env` 先于 `.env.local` 的加载顺序,后加载的 `.env.local` 可以覆盖默认配置。
|
||||
|
||||
## 3. 运行边界
|
||||
|
||||
1. 环境文件应保持 UTF-8 文本,允许 UTF-8 BOM 和 CRLF,但部署脚本会在发布目录中消除它们。
|
||||
2. 环境变量名必须符合 `[A-Za-z_][A-Za-z0-9_]*`。
|
||||
3. 值支持不加引号、双引号和单引号;复杂 shell 表达式不会执行,避免把环境文件变成脚本入口。
|
||||
4. 业务密钥仍通过目标服务器环境变量或发布目录 `.env.local` 管理,不写入 Jenkinsfile。
|
||||
|
||||
## 4. 失败现场恢复
|
||||
|
||||
如果 Jenkins 已经生成了失败版本,可以在拉取本次脚本修复后直接重跑部署流水线。`scripts/jenkins-deploy-release.sh` 会在执行新版本 `start.sh` 前净化已有发布目录,因此不要求手工编辑服务器上的 `.env.local`。
|
||||
@@ -99,7 +99,7 @@ scripts/jenkins-deploy-release.sh \
|
||||
|
||||
如果 `RUN_DEPLOY_HOOKS_WITH_SUDO=true`,第 1 步和第 4 步会改为 `sudo -n` 调用;这要求 Jenkins 运行用户提前配置免密 sudo,否则部署会直接失败,不会进入交互式密码提示。
|
||||
|
||||
这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `spacetimedb-data/`、`logs/`、`run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env`、`.env.local` 仍会以构建产物中的文件为准。
|
||||
这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 `spacetimedb-data/`、`logs/`、`run/` 这类运行态目录,不会因为部署被整体删除。发布白名单内的 `.env`、`.env.local` 仍会以构建产物中的文件为准;部署脚本会在启动 hook 前移除这些环境文件中的 UTF-8 BOM 与 CRLF,避免 `start.sh` 在 Bash 下把首行变量名误解析成命令。
|
||||
|
||||
### 4.3 构建并部署
|
||||
|
||||
|
||||
@@ -6,33 +6,33 @@
|
||||
|
||||
本轮在“我的”页面的“会员充值”入口落地账户充值弹窗,包含两个页签:
|
||||
|
||||
1. `积分充值`
|
||||
1. `叙世币充值`
|
||||
2. `会员卡充值`
|
||||
|
||||
前端只负责展示与发起购买,套餐、价格、赠送规则、会员权益、生效时间、钱包余额与交易流水统一由 `server-rs` 后端返回。当前没有真实支付网关,本轮采用服务端模拟支付成功:创建订单后立即写入余额或会员状态,并返回最新账户中心快照。后续接入真实支付时,只替换订单支付状态推进,不改前端套餐与账户快照 contract。
|
||||
|
||||
## 2. 产品规则
|
||||
|
||||
### 2.1 积分充值套餐
|
||||
### 2.1 叙世币充值套餐
|
||||
|
||||
| productId | 积分 | 金额分 | 徽标 | 说明 |
|
||||
| productId | 叙世币 | 金额分 | 徽标 | 说明 |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `points_10` | 10 | 100 | 首充送积分 | 首充送19积分 |
|
||||
| `points_60` | 60 | 600 | 无首充赠礼 | 无首充赠送 |
|
||||
| `points_240` | 240 | 2400 | 首充双倍 | 首充送240积分 |
|
||||
| `points_450` | 450 | 4500 | 首充双倍 | 首充送450积分 |
|
||||
| `points_950` | 950 | 9500 | 首充双倍 | 首充送950积分 |
|
||||
| `points_1980` | 1980 | 19800 | 首充双倍 | 首充送1980积分 |
|
||||
| `points_60` | 60 | 600 | 首充双倍 | 首充送60叙世币 |
|
||||
| `points_180` | 180 | 1800 | 首充双倍 | 首充送180叙世币 |
|
||||
| `points_300` | 300 | 3000 | 首充双倍 | 首充送300叙世币 |
|
||||
| `points_680` | 680 | 6800 | 首充双倍 | 首充送680叙世币 |
|
||||
| `points_1280` | 1280 | 12800 | 首充双倍 | 首充送1280叙世币 |
|
||||
| `points_3280` | 3280 | 32800 | 首充双倍 | 首充送3280叙世币 |
|
||||
|
||||
首充赠送只按用户维度判断一次:用户历史上没有 `points_recharge` 流水时,购买支持首充赠送的套餐才发放赠送积分。实际到账积分写入交易流水,余额以 SpacetimeDB projection 为准。
|
||||
叙世币充值固定为 `¥6 / ¥18 / ¥30 / ¥68 / ¥128 / ¥328` 六个档位。全部档位参与首充双倍:用户历史上没有 `points_recharge` 流水时,本次购买到账叙世币为基础叙世币与等额赠送叙世币之和;已有充值流水后只到账基础叙世币。实际到账叙世币写入交易流水,余额以 SpacetimeDB projection 为准。
|
||||
|
||||
### 2.2 会员卡套餐
|
||||
|
||||
| productId | 类型 | 天数 | 金额分 | 权益 |
|
||||
| --- | --- | ---: | ---: | --- |
|
||||
| `member_month` | 月卡 | 30 | 2800 | 免积分回合数100,每日签到加成0% |
|
||||
| `member_season` | 季卡 | 90 | 7800 | 免积分回合数100,每日签到加成100% |
|
||||
| `member_year` | 年卡 | 365 | 24800 | 免积分回合数100,每日签到加成210% |
|
||||
| `member_month` | 月卡 | 30 | 2800 | 免叙世币回合数100,每日签到加成0% |
|
||||
| `member_season` | 季卡 | 90 | 7800 | 免叙世币回合数100,每日签到加成100% |
|
||||
| `member_year` | 年卡 | 365 | 24800 | 免叙世币回合数100,每日签到加成210% |
|
||||
|
||||
购买会员时,如果当前会员仍有效,则从当前到期时间顺延;如果已过期或从未购买,则从当前服务端时间开始计算。状态只区分 `普通` 与已生效会员,前端不自行推断。
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
|
||||
需要 Bearer JWT。返回:
|
||||
|
||||
1. 当前积分余额、会员状态、到期时间
|
||||
2. 积分套餐与会员套餐
|
||||
1. 当前叙世币余额、会员状态、到期时间
|
||||
2. 叙世币套餐与会员套餐
|
||||
3. 会员权益表
|
||||
4. 最近订单摘要
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"productId": "points_240",
|
||||
"productId": "points_300",
|
||||
"paymentChannel": "mock"
|
||||
}
|
||||
```
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
1. 校验 `productId`
|
||||
2. 后端创建已支付订单
|
||||
3. 积分套餐写入钱包余额与流水
|
||||
3. 叙世币套餐写入钱包余额与流水
|
||||
4. 会员套餐写入会员状态
|
||||
5. 返回最新账户中心快照与订单摘要
|
||||
|
||||
@@ -74,14 +74,15 @@
|
||||
|
||||
1. “我的”页会员充值按钮打开独立弹窗,不在当前面板下方展开。
|
||||
2. 弹窗顶部标题为 `账户充值`,右上角关闭。
|
||||
3. 默认打开 `积分充值`,可切换到 `会员卡充值`。
|
||||
3. 默认打开 `叙世币充值`,可切换到 `会员卡充值`。
|
||||
4. 点击套餐后调用下单接口,按钮进入处理中状态,成功后刷新 `profileDashboard`。
|
||||
5. 弹窗内不写大段说明文案,只保留必要金额、积分、会员权益和状态反馈。
|
||||
5. 弹窗内不写大段说明文案,只保留必要金额、叙世币、会员权益和状态反馈。
|
||||
6. 会员卡充值区以套餐卡片优先展示周期、价格和处理状态;移动端单列,桌面端三列,权益表允许横向滚动,避免小屏挤压。
|
||||
|
||||
## 5. 验收
|
||||
|
||||
1. 普通用户打开弹窗能看到积分与会员套餐。
|
||||
2. 积分购买后余额增加,流水来源为 `points_recharge`。
|
||||
3. 首充赠送只在首次积分充值时生效。
|
||||
1. 普通用户打开弹窗能看到叙世币与会员套餐。
|
||||
2. 叙世币购买后余额增加,流水来源为 `points_recharge`。
|
||||
3. 首充赠送只在首次叙世币充值时生效。
|
||||
4. 会员购买后会员状态与到期时间立即更新。
|
||||
5. 移动端弹窗单列可滚动,桌面端接近参考图卡片网格。
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
在现有“我的”Tab 常用功能区落地三个轻量入口:
|
||||
|
||||
1. `邀请好友`:弹出面板展示当前账号绑定的邀请码。
|
||||
2. `填邀请码`:弹出面板填写邀请码,成功后邀请者与被邀请者各获得 `30` 积分。
|
||||
2. `填邀请码`:弹出面板填写邀请码,成功后邀请者与被邀请者各获得 `30` 叙世币。
|
||||
3. `玩家社区`:弹出面板展示微信群与 QQ 群二维码占位图,后续替换为正式图片。
|
||||
|
||||
## 后端边界
|
||||
|
||||
- 邀请码、邀请关系与奖励发放全部存入 `server-rs/crates/spacetime-module`。
|
||||
- Axum 只做鉴权、参数转发与响应映射,不在 API 层自行计算奖励。
|
||||
- 前端只读取后端状态与调用提交接口,不做本地加积分。
|
||||
- 前端只读取后端状态与调用提交接口,不做本地加叙世币。
|
||||
- 钱包余额继续复用 `profile_dashboard_state.wallet_balance`。
|
||||
- 奖励流水继续复用 `profile_wallet_ledger`,新增来源类型:
|
||||
- `invite_inviter_reward`
|
||||
@@ -42,7 +42,7 @@
|
||||
- 每个用户拥有一个稳定邀请码,首次进入邀请中心时自动生成。
|
||||
- 用户不能填写自己的邀请码。
|
||||
- 用户最多填写一个邀请码,成功后不可修改。
|
||||
- 被邀请者绑定成功后获得 `30` 积分。
|
||||
- 被邀请者绑定成功后获得 `30` 叙世币。
|
||||
- 邀请者每天最多获得 `10` 次邀请奖励,超过后关系仍可绑定,被邀请者仍获得奖励,邀请者当次不再加分。
|
||||
- 每次奖励都写入钱包流水,钱包余额以后端返回为准。
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
- `server-rs/crates/spacetime-module` 已新增邀请码与邀请关系表,邀请中心读取和填码绑定均通过 SpacetimeDB procedure 执行。
|
||||
- `server-rs/crates/api-server` 已挂接 `/api/runtime/profile/referrals/*` 与 `/api/profile/referrals/*` 两组路由。
|
||||
- 前端“我的”Tab 三个快捷入口均打开独立弹窗,玩家社区先使用空白二维码占位。
|
||||
- 复制邀请会复制邀请码和邀请链接;填码成功后刷新个人看板积分。
|
||||
- 复制邀请会复制邀请码和邀请链接;填码成功后刷新个人看板叙世币。
|
||||
|
||||
## 前端交互
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
2. Rust API 必须至少提供契约兼容的后端 SSE 路由,避免回退到 server-node。
|
||||
3. 任意好感度下,首次与一个 NPC 相遇都先进入 NPC 主动开场;后续再按敌对/普通分支处理。
|
||||
4. 和平相遇态 NPC 固定使用已解析相遇锚点,与主角形成面对面的右侧对称表现,并强制朝向主角。
|
||||
5. `npcInitiatesConversation=true` 表示 NPC 先手开口,不应要求玩家消息非空,也不能把内部占位文本写入 prompt 或好感结算。
|
||||
|
||||
## 代码设计
|
||||
|
||||
@@ -26,6 +27,8 @@
|
||||
- `reply_delta`:增量文本。
|
||||
- `complete`:`npcReply / affinityDelta / affinityText / suggestions / pendingQuestOffer / chatDirective`。
|
||||
3. 当前先提供后端确定性兜底回复,保证 Rust API 迁移期间链路可用;后续完整 LLM 编排应继续在 Rust API 内实现,不回接 server-node。
|
||||
4. 主动开场请求允许 `playerMessage` 为空;这一轮不是玩家发言结算,`affinityDelta` 固定为 0。
|
||||
5. 建议生成 prompt 在主动开场时明确“玩家尚未先开口”,避免把空消息或前端哨兵文本误描述为玩家台词。
|
||||
|
||||
### 前端交互
|
||||
|
||||
@@ -33,6 +36,7 @@
|
||||
|
||||
1. 首次相遇判断提前到敌对短路之前。
|
||||
2. `firstMeaningfulContactResolved` 为 false 时,无论好感度或敌对状态如何,都调用 `startNpcInitiatedOpening(...)`。
|
||||
3. NPC 主动开场调用 `streamNpcChatTurn(...)` 时传空 `playerMessage`,只依赖 `npcInitiatesConversation` 表达“由 NPC 先发言”的语义。
|
||||
|
||||
调整 `src/components/game-canvas/GameCanvasEntityLayer.tsx`:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 密码登录历史落地设计
|
||||
# 密码登录入口历史落地设计
|
||||
|
||||
> 2026-04-24 更新:当前产品策略已调整为“不开放密码注册”。新用户必须通过手机号验证码注册/登录,密码登录只面向已经设置密码的账号。密码修改与重置以 [PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md](./PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md) 为准;本文中“密码自动建号”仅保留为历史基线说明,不再作为当前落地依据。
|
||||
> 2026-04-25 更新:当前产品策略已调整为“不开放密码注册”。新用户必须通过手机号验证码注册/登录,密码登录只面向已经登录后设置过密码的手机号账号。`POST /api/auth/entry` 只接受 `phone + password`,不支持邮箱、用户名或叙世号登录,也不承担自动建号能力。本文原有“密码自动建号”内容仅作为历史背景保留,当前落地以本更新和 [PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md](./PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md) 为准。
|
||||
|
||||
日期:`2026-04-21`
|
||||
|
||||
@@ -8,17 +8,25 @@
|
||||
|
||||
这份文档用于指导 `M2` 中以下两条任务的首版落地:
|
||||
|
||||
1. `实现密码登录`
|
||||
2. `实现账号自动创建 / 幂等登录兼容策略`
|
||||
1. `实现手机号密码登录`
|
||||
2. `移除密码登录自动注册 / 自动建号语义`
|
||||
|
||||
目标是先把当前 Node 已经稳定运行的 `/api/auth/entry` 语义迁到 Rust 工作区,并冻结:
|
||||
目标是把 `/api/auth/entry` 在 Rust 工作区冻结为手机号验证码账号的补充登录方式:
|
||||
|
||||
1. `api-server` 对外暴露的最小兼容接口。
|
||||
2. `module-auth` 负责的密码登录用例边界。
|
||||
3. 自动建号与并发幂等兼容规则。
|
||||
1. `api-server` 对外只暴露 `phone + password` 的最小接口。
|
||||
2. `module-auth` 只负责已存在手机号账号的密码校验。
|
||||
3. 密码入口不创建账号,不接收邮箱、用户名或叙世号。
|
||||
4. 登录成功后与 JWT、refresh cookie 的衔接方式。
|
||||
|
||||
## 2. 当前基线
|
||||
## 1.1 当前冻结结论
|
||||
|
||||
1. 密码登录不是注册入口。
|
||||
2. 密码登录是手机号验证码登录的补充方式。
|
||||
3. 只有已存在、已绑定手机号、并已设置密码的账号可以通过密码登录。
|
||||
4. 未知手机号、未设置密码、密码错误统一返回 `401 UNAUTHORIZED`,避免通过密码入口探测账号状态。
|
||||
5. 手机号验证码登录仍是新用户注册/首次登录的唯一入口。
|
||||
|
||||
## 2. 历史基线
|
||||
|
||||
当前 Node `/api/auth/entry` 主链已经具备如下语义:
|
||||
|
||||
@@ -29,7 +37,7 @@
|
||||
5. 同时创建 refresh session,并把原始 refresh token 写入 HttpOnly cookie。
|
||||
6. 并发创建同一用户名时,后到的请求会回退为“查已存在账号并校验密码”,不因唯一键冲突直接失败。
|
||||
|
||||
这条链路既是当前前端匿名/游客恢复的基础,也是真实 `/api/auth/entry` contract 的既有事实,因此 Rust 首版必须兼容。
|
||||
这条链路曾经是前端匿名/游客恢复的基础。2026-04-25 起该历史语义已废弃,Rust 当前实现必须以“手机号账号已设置密码后登录”为准,不再兼容密码自动建号。
|
||||
|
||||
## 3. 设计输入
|
||||
|
||||
@@ -41,12 +49,12 @@
|
||||
4. [PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md](./PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md)
|
||||
5. [PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md](./PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md)
|
||||
|
||||
关键冻结点:
|
||||
当前冻结点:
|
||||
|
||||
1. `password_hash` 当前继续由 `user_account` 承担,不进入 `auth_identity`。
|
||||
2. `sub` 必须是稳定 `user_id`。
|
||||
3. 登录成功后必须继续同时生成 access token 和 refresh session。
|
||||
4. 自动建号兼容必须保留,不能因为迁到 Rust 就删除。
|
||||
4. 密码登录不再保留自动建号兼容,旧开发游客自动建号链路必须迁出 `/api/auth/entry`。
|
||||
|
||||
## 4. 首版落地范围
|
||||
|
||||
@@ -54,14 +62,14 @@
|
||||
|
||||
1. `module-auth` 中的密码登录用例。
|
||||
2. `api-server` 中的 `POST /api/auth/entry`。
|
||||
3. 用户名校验、密码哈希校验与自动建号。
|
||||
3. 手机号归一化、密码哈希校验与未设置密码拒绝。
|
||||
4. 登录成功后的 access token 与 refresh cookie 主链打通。
|
||||
|
||||
本阶段明确不包含:
|
||||
|
||||
1. SpacetimeDB 真正的 `user_account` / `refresh_session` reducer 写入。
|
||||
2. `/api/auth/me`、`/api/auth/logout`、`/api/auth/refresh` 的正式业务闭环。
|
||||
3. 手机验证码与微信登录链路。
|
||||
3. 新增邮箱登录或独立密码注册链路。
|
||||
|
||||
## 5. crate 边界
|
||||
|
||||
@@ -69,9 +77,9 @@
|
||||
|
||||
负责:
|
||||
|
||||
1. 用户名与密码的领域校验。
|
||||
1. 手机号与密码的领域校验。
|
||||
2. 密码登录主用例。
|
||||
3. 自动建号与并发幂等兼容策略。
|
||||
3. 已存在手机号账号与已设置密码约束。
|
||||
4. 输出登录成功所需的最小用户快照。
|
||||
|
||||
不负责:
|
||||
@@ -106,11 +114,11 @@
|
||||
|
||||
### 6.1 请求体
|
||||
|
||||
固定沿用当前 contract:
|
||||
当前 contract:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "guest_001",
|
||||
"phone": "13800138000",
|
||||
"password": "secret123"
|
||||
}
|
||||
```
|
||||
@@ -124,9 +132,9 @@
|
||||
"token": "<access-token>",
|
||||
"user": {
|
||||
"id": "user_xxx",
|
||||
"username": "guest_001",
|
||||
"displayName": "guest_001",
|
||||
"phoneNumberMasked": null,
|
||||
"username": "phone_xxx",
|
||||
"displayName": "138****8000",
|
||||
"phoneNumberMasked": "138****8000",
|
||||
"loginMethod": "password",
|
||||
"bindingStatus": "active",
|
||||
"wechatBound": false
|
||||
@@ -136,11 +144,11 @@
|
||||
|
||||
同时响应头必须写回 refresh cookie。
|
||||
|
||||
## 7. 用户名与密码规则
|
||||
## 7. 手机号与密码规则
|
||||
|
||||
当前阶段继续对齐 Node 基线:
|
||||
当前阶段固定:
|
||||
|
||||
1. `username` 只允许 `3` 到 `24` 位字母、数字、下划线。
|
||||
1. `phone` 只接受中国大陆手机号,服务端统一归一化为 `E.164` 后查询。
|
||||
2. `password` 长度必须在 `6` 到 `128` 位之间。
|
||||
|
||||
任一校验失败时:
|
||||
@@ -148,37 +156,30 @@
|
||||
1. 返回 `400 BAD_REQUEST`
|
||||
2. 错误文案继续保持中文
|
||||
|
||||
## 8. 自动建号与幂等兼容
|
||||
## 8. 登录校验规则
|
||||
|
||||
### 8.1 自动建号
|
||||
### 8.1 未知手机号
|
||||
|
||||
当 `username` 不存在时:
|
||||
当 `phone` 归一化后找不到账号时:
|
||||
|
||||
1. 用当前请求里的 `password` 生成密码哈希。
|
||||
2. 创建一条本地账号。
|
||||
3. `display_name = username`
|
||||
4. `login_provider = password`
|
||||
5. `account_status = active`
|
||||
6. `token_version = 1`
|
||||
1. 返回 `401 UNAUTHORIZED`。
|
||||
2. 不创建账号。
|
||||
3. 不写 `password_hash`。
|
||||
|
||||
### 8.2 已存在账号
|
||||
### 8.2 未设置密码
|
||||
|
||||
当 `username` 已存在时:
|
||||
当账号存在但 `password_login_enabled = false` 时:
|
||||
|
||||
1. 返回 `401 UNAUTHORIZED`。
|
||||
2. 不区分“未设置密码”和“密码错误”的外部文案。
|
||||
|
||||
### 8.3 已设置密码
|
||||
|
||||
当账号存在且已设置密码时:
|
||||
|
||||
1. 校验密码哈希。
|
||||
2. 校验失败返回 `401 UNAUTHORIZED`。
|
||||
3. 校验成功继续登录。
|
||||
|
||||
### 8.3 并发幂等兼容
|
||||
|
||||
若两个请求并发创建同一用户名:
|
||||
|
||||
1. 允许其中一个请求先创建成功。
|
||||
2. 后一个请求若命中唯一键冲突,不直接失败。
|
||||
3. 后一个请求必须重新查询该用户名。
|
||||
4. 若查到账号,则按“已存在账号”路径继续校验密码。
|
||||
|
||||
这保证了当前前端重复调用 `/api/auth/entry` 时可以恢复同一账号,而不是随机失败。
|
||||
3. 校验成功签发 access token 与 refresh cookie。
|
||||
|
||||
## 9. 首版存储策略
|
||||
|
||||
@@ -226,10 +227,10 @@
|
||||
|
||||
当前阶段至少覆盖:
|
||||
|
||||
1. 首次密码登录自动建号成功。
|
||||
2. 同用户名同密码可重复登录同一账号。
|
||||
3. 同用户名不同密码返回 `401`。
|
||||
4. 非法用户名返回 `400`。
|
||||
1. 未知手机号密码登录返回 `401`,且不创建账号。
|
||||
2. 已登录手机号账号设置密码后可用 `phone + password` 登录。
|
||||
3. 同手机号错误密码返回 `401`。
|
||||
4. 邮箱、用户名或叙世号作为密码登录标识返回 `400`。
|
||||
5. 登录成功时返回 access token。
|
||||
6. 登录成功时写回 refresh cookie。
|
||||
|
||||
@@ -239,7 +240,7 @@
|
||||
|
||||
1. `module-auth` 不再只是 README,占位被真实 crate 实现替换。
|
||||
2. `POST /api/auth/entry` 可在 Rust 侧独立跑通。
|
||||
3. 自动建号与幂等兼容行为可验证。
|
||||
3. 密码入口不注册、不接收邮箱/用户名的行为可验证。
|
||||
4. JWT 与 refresh cookie 登录成功主链打通。
|
||||
5. 文档、任务清单与测试同步完成。
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
沿用现有 `POST /api/auth/entry`:
|
||||
|
||||
1. 请求字段沿用 `username`、`password`,但前端固定把手机号填入 `username`。
|
||||
2. 后端优先按标准手机号归一化后查找账号,兼容历史用户名只作为开发游客兜底能力。
|
||||
1. 请求字段固定为 `phone`、`password`,前端只提交手机号。
|
||||
2. 后端只按标准手机号归一化后查找账号,不兼容邮箱、用户名、叙世号或历史开发游客标识。
|
||||
3. 手机号不存在时返回 `401`,不创建账号。
|
||||
4. 手机号存在但未设置密码时返回 `401`。
|
||||
5. 校验成功后签发 access token,并写入 refresh cookie。
|
||||
@@ -41,7 +41,7 @@
|
||||
1. 不需要 Bearer 登录态。
|
||||
2. 请求字段:`phone`、`code`、`newPassword`。
|
||||
3. 使用 `reset_password` 短信场景校验验证码。
|
||||
4. 手机号不存在时返回 `404`,避免用密码重置隐式注册账号。
|
||||
4. 手机号不存在时返回 `401`,避免用密码重置隐式注册账号,并避免泄露手机号注册状态。
|
||||
5. 重置成功后签发新的 access token,并写入 refresh cookie,便于用户直接进入登录态。
|
||||
|
||||
### 2.4 发送重置验证码
|
||||
@@ -62,7 +62,7 @@
|
||||
登录弹窗不再拆独立注册页签:
|
||||
|
||||
1. 面板直接展示手机号和密码输入,用于已设置密码账号登录。
|
||||
2. 登录按钮文本固定为 `注册/登录`,避免用户在登录和首次进入之间做页面切换。
|
||||
2. 密码登录按钮文本固定为 `登录`,不允许暗示密码入口具备注册能力。
|
||||
3. 忘记密码入口显示在登录按钮右下侧,点击后仍进入独立重置面板,不在当前表单下方展开。
|
||||
4. 同一面板保留手机号验证码注册/登录能力,用于新用户自动注册和已注册用户免密码登录。
|
||||
5. 账号设置面板提供密码修改入口;未设置密码的账号显示为设置密码。
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# 公开作品号移动端分享入口修复 2026-04-25
|
||||
|
||||
## 背景
|
||||
|
||||
公开编号设计已要求详情页和创作中心展示 `CW / PZ` 作品号,并支持通过首页搜索入口打开公开作品。但当前移动端首页只有桌面端顶部搜索框,竖屏无法输入 `SY / CW / PZ` 编号;同时首页“最新发布”和桌面趋势卡片把发布时间放在显眼 badge 位置,异常时间字符串会被误认为作品号;创作页“我的作品”卡只展示作者和游玩数,没有可复制、可搜索的公开作品号。
|
||||
|
||||
## 落地规则
|
||||
|
||||
1. 移动端首页在 Logo 下方提供紧凑搜索条,复用现有 `onSearchPublicCode` 行为,不新增页面或新系统。
|
||||
2. 首页、分类、趋势等公开外部列表不直接展示作品号,卡片 badge 展示推荐、分类或作品类型,不再用发布时间充当主 badge。
|
||||
3. RPG 与拼图详情页在已发布作品的辅助信息里展示作品号,并提供复制动作。
|
||||
4. 创作页作品卡在已发布作品上展示作品号:RPG 使用后端 `publicWorkCode`;拼图当前没有独立公开号时,使用 `PZ-` + `profileId` 后 8 位作为前端展示与复制标识,后续若补后端拼图公开号再替换来源。
|
||||
5. 作品号复制统一使用兼容复制工具:优先 Clipboard API,权限失败时降级到隐藏文本框选区复制,并在按钮内短暂显示复制结果。
|
||||
6. 作品详情返回必须恢复打开详情前的平台来源 Tab;从分类进入回分类,从首页进入回首页,从创作中心进入回创作中心。
|
||||
7. 所有入口保持轻量 UI,不写规则说明文案,不改变发布、下架、进入游戏的后端语义。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 399px 竖屏首页能直接看到并使用搜索入口。
|
||||
2. 首页公开作品卡左上角不再出现发布时间样式的疑似作品号,也不直接显示作品号。
|
||||
3. RPG 详情页能看到 `作品号 CW...` 并可复制,拼图详情页能看到 `作品号 PZ...` 并可复制。
|
||||
4. 创作页“我的作品”已发布卡能看到作品号,拼图卡不会只显示作者和游玩数。
|
||||
5. 桌面右侧趋势列表只显示排序和作品类型,不再显示 `1777110165.990127Z` 这类原始时间字符串,也不直接显示作品号。
|
||||
6. 在内嵌浏览器 Clipboard API 拒绝写入时,详情页与创作中心作品号复制仍能通过降级路径完成,并显示 `已复制` 或 `复制失败`。
|
||||
7. 打开拼图详情后点击返回,不再固定跳到创作中心,而是回到打开详情前的平台 Tab。
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md](./RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md):记录 RPG 战斗血条安全锚点、服务端战斗回包前端短表现,以及 `battle_use_skill` 指定技能兜底结算的修复口径。
|
||||
- [SPACETIMEDB_TABLE_CATALOG.md](./SPACETIMEDB_TABLE_CATALOG.md):持续维护当前 SpacetimeDB 表目录,按领域说明每张表的作用、字段结构、索引和常用 `spacetime sql` 查询模板。
|
||||
- [RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md](./RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md):记录开局场景与普通场景复用同一场景展示解析服务,修复列表幕缩略图和详情幕背景预览图片不一致的问题。
|
||||
- [RPG_WORK_DELETE_SPACETIMEDB_PROCEDURE_EXPORT_FIX_2026-04-25.md](./RPG_WORK_DELETE_SPACETIMEDB_PROCEDURE_EXPORT_FIX_2026-04-25.md):记录 RPG 作品删除时报 `No such procedure` 的根因,补齐 `delete_custom_world_agent_session` 在有效 SpacetimeDB 模块入口中的导出,并要求发布后核验 Maincloud schema。
|
||||
- [CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md](./CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md):冻结当前后端唯一落地口径,明确新功能以 `server-rs + Axum + SpacetimeDB` 为准,旧 `server-node` / Express / PostgreSQL 与 Go 方向只允许作为迁移参考。
|
||||
- [RPG_DRAFT_GENERATION_CONTINUE_AND_ETA_FIX_2026-04-25.md](./RPG_DRAFT_GENERATION_CONTINUE_AND_ETA_FIX_2026-04-25.md):记录世界草稿生成失败/中断后进度不再误到 `100%`、主按钮改为“继续生成草稿”并复用已保存底稿续跑,以及按阶段耗时模型估算预计等待时间的修复口径。
|
||||
@@ -14,6 +16,7 @@
|
||||
- [CHARACTER_VISUAL_IP_MODERATION_FALLBACK_FIX_2026-04-25.md](./CHARACTER_VISUAL_IP_MODERATION_FALLBACK_FIX_2026-04-25.md):记录角色主形象遇到 DashScope `IPInfringementSuspect` 时自动改用原创安全 prompt 兜底重试的修复口径,并保留供应商原始错误便于排查。
|
||||
- [CREATION_AGENT_IMMEDIATE_WAITING_DOTS_FIX_2026-04-25.md](./CREATION_AGENT_IMMEDIATE_WAITING_DOTS_FIX_2026-04-25.md):记录创作 Agent 用户发送消息后立刻展示三点等待动画的前端展示条件,避免首个 SSE token 到达前聊天区无反馈。
|
||||
- [CREATION_AGENT_DOCUMENT_INPUT_UPLOAD_2026-04-25.md](./CREATION_AGENT_DOCUMENT_INPUT_UPLOAD_2026-04-25.md):冻结 Agent 创作页上传文本类文档并解析为输入框内容的前后端边界、接口、支持范围和验收标准。
|
||||
- [CREATION_HUB_LOGOUT_PRIVATE_CACHE_FIX_2026-04-25.md](./CREATION_HUB_LOGOUT_PRIVATE_CACHE_FIX_2026-04-25.md):记录退出登录后创作中心仍显示上一账号作品的前端缓存根因,并冻结退出时立即收回鉴权上下文、清空三类私有作品货架缓存的修复口径。
|
||||
- [CREATION_AGENT_CLIENT_AND_FLOW_CONTROLLER_REUSE_2026-04-25.md](./CREATION_AGENT_CLIENT_AND_FLOW_CONTROLLER_REUSE_2026-04-25.md):冻结三类作品创作 Agent client 通用工厂与平台轻量流程 controller 的复用边界,明确本轮只收口 HTTP/SSE 骨架和大鱼/拼图会话流程,不合并 RPG 自动保存主链。
|
||||
- [BACKEND_CREATION_AGENT_LLM_TURN_COMMONIZATION_2026-04-25.md](./BACKEND_CREATION_AGENT_LLM_TURN_COMMONIZATION_2026-04-25.md):冻结后端创作 Agent LLM turn 公共化边界,收口模型可用性检查、流式 JSON 回复抽取、最终 JSON 解析与中文错误映射,玩法 schema 和写回逻辑继续留在各自模块。
|
||||
- [CREATION_WORK_SHELF_UNIFICATION_2026-04-25.md](./CREATION_WORK_SHELF_UNIFICATION_2026-04-25.md):冻结创作中心作品货架统一视图模型,先在前端归一 RPG、大鱼、拼图 works 的展示字段、筛选状态和卡片动作语义,不新增后端聚合接口。
|
||||
@@ -42,6 +45,7 @@
|
||||
- [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/` 覆盖策略。
|
||||
- [JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md](./JENKINS_DEPLOY_ENV_BOM_FIX_2026-04-25.md):记录 Jenkins 部署时 `.env.local` 首行 UTF-8 BOM 导致 `start.sh` 加载失败的根因,并冻结发布包构建、部署脚本和启动脚本的环境文件净化规则。
|
||||
- [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` 已挂载的 101 条 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 存储边界和文档维护门禁。
|
||||
|
||||
25
docs/technical/ROUTE_IMAGE_READY_GATE_2026-04-25.md
Normal file
25
docs/technical/ROUTE_IMAGE_READY_GATE_2026-04-25.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 路由首屏图片等待门闩 2026-04-25
|
||||
|
||||
## 背景
|
||||
|
||||
平台首页、作品详情、拼图运行态等页面会在首屏展示作品封面、角色图、场景背景图或拼图原图。此前页面主体可能先进入,图片随后分批出现,移动端尤其容易看到背景图闪入、封面占位与真实图片切换。
|
||||
|
||||
## 落地约束
|
||||
|
||||
1. 入口路由组件加载完成后,页面主体先挂载但保持不可见,继续让业务数据请求、图片请求和布局计算正常发生。
|
||||
2. 门闩扫描当前路由根节点内的 `<img>`、`src/currentSrc` 和 CSS `background-image / border-image-source` 中的 `url(...)`。
|
||||
3. 扫描到的图片统一通过 `Image()` 预加载;图片成功、失败或超时都视为 settled,避免单张异常图片阻塞整站进入。
|
||||
4. 所有已发现图片 settled 且 DOM 短暂稳定后,再一次性显示页面主体。
|
||||
5. 门闩只负责前端呈现时机,不承接作品封面选择、资产读链路、玩法逻辑或后端数据裁决。
|
||||
|
||||
## 体验规则
|
||||
|
||||
- 等待态继续复用 `RouteLoadingScreen`,只显示简短加载文案,不在 UI 中追加规则说明。
|
||||
- 页面主体隐藏时使用 `visibility: hidden`,不能用 `display: none`,否则浏览器可能不触发布局与图片加载。
|
||||
- 图片加载失败不直接改写业务 UI;后续仍由原页面的兜底图、占位图或错误态处理。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `src/routing/RouteImageReadyGate.tsx`
|
||||
- `src/routing/RouteImageReadyGate.test.ts`
|
||||
- `src/main.tsx`
|
||||
@@ -0,0 +1,27 @@
|
||||
# RPG 战斗血条与动作表现修复
|
||||
|
||||
更新时间:`2026-04-26`
|
||||
|
||||
## 背景
|
||||
|
||||
战斗运行态已经形成两条链路:
|
||||
|
||||
- `server-rs` / `module-runtime-story-compat` 负责 `battle_*`、`inventory_use` 的数值真相结算。
|
||||
- 前端 `GameCanvasRuntime`、`GameCanvasEntityLayer`、`storyChoiceRuntime` 负责把结算结果表现为血条、动作和选项反馈。
|
||||
|
||||
本次修复只处理表现层与本地兜底一致性,不把战斗数值重新搬回前端。
|
||||
|
||||
## 落地规则
|
||||
|
||||
1. 战斗中双方血条必须使用角色安全区锚点,放在角色形象上方,不再贴着 112px 容器顶部渲染。
|
||||
2. 自定义 NPC、模板角色、通用 NPC、怪物的脚底锚点和血条锚点分离维护,避免为了落地位置牺牲血条可读性。
|
||||
3. `battle_use_skill` 的本地兜底结算必须尊重 `runtimePayload.skillId`,不能重新随机挑技能。
|
||||
4. `battle_recover_breath` 的本地兜底不能伪装成攻击动作;它只做恢复、冷却推进和后续敌方压力。
|
||||
5. 服务端战斗回包如果带 `presentation.battle`,前端先播放一次短动作和血量变化,再落 `hydratedSnapshot.gameState`,避免选项点击后血量直接跳变。
|
||||
|
||||
## 验收点
|
||||
|
||||
- 玩家、同伴、NPC、怪物血条不遮挡头部或主体轮廓。
|
||||
- 点击具体技能按钮时,播放与结算使用同一个 `skillId`。
|
||||
- 点击恢复时不会出现玩家同时释放攻击技能的错位表现。
|
||||
- 走服务端 runtime action 的战斗选项仍以 server-rs 返回快照为最终状态。
|
||||
@@ -0,0 +1,24 @@
|
||||
# RPG 开局场景幕预览图片同步修复(2026-04-26)
|
||||
|
||||
## 背景
|
||||
|
||||
世界档案场景 Tab 中,开局场景卡片和点进场景详情后的幕预览曾经存在取图口径不一致:
|
||||
|
||||
1. 列表侧会先解析场景主图,再把主图作为共享图传给所有幕预览。
|
||||
2. 详情侧实际编辑的是 `sceneChapterBlueprints[].acts[].backgroundImageSrc`。
|
||||
3. 当开局场景 `camp.imageSrc` 与第二、第三幕背景不同步时,列表幕缩略图会被主图覆盖,点进详情后又显示幕自己的图。
|
||||
|
||||
## 落地规则
|
||||
|
||||
1. 开局场景和普通场景统一通过 `src/services/customWorldScenePresentation.ts` 解析展示模型。
|
||||
2. 展示模型固定输出:
|
||||
- `imageSrc`:场景主图,优先使用第一张幕背景,其次使用场景兼容图。
|
||||
- `actPreviews`:幕预览列表,优先使用当前幕 `backgroundImageSrc`,只有该幕缺图时才回退 `imageSrc`。
|
||||
3. `camp.imageSrc` 与 `landmark.imageSrc` 只作为旧数据兼容字段,不反向覆盖已有幕背景。
|
||||
4. 场景目录、开局场景详情、普通场景详情必须复用同一套展示解析服务;不得再为 `camp` 单独写一套幕预览取图逻辑。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. 开局场景列表中的第 2 幕缩略图与详情页第 2 幕背景预览一致。
|
||||
2. 普通场景仍沿用同一展示模型,列表幕缩略图不被场景主图覆盖。
|
||||
3. 保存开局场景图片时,兼容字段 `camp.imageSrc` 和多幕背景仍保持已有同步规则。
|
||||
@@ -116,9 +116,9 @@ 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. 把仓库根目录的 `.env` 与 `.env.local` 分别复制到目标目录根部和目标目录的 `web/` 下。
|
||||
6. 把仓库根目录的 `.env` 与 `.env.local` 分别复制到目标目录根部和目标目录的 `web/` 下;复制后统一移除 UTF-8 BOM 与 CRLF,避免目标服务器 Bash 加载环境文件失败。
|
||||
7. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*`、`/generated-*`、`/healthz` 反代到本包内的 `api-server`。
|
||||
8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先加载发布目录根部的 `.env`、`.env.local`,再回退到构建时通过 `--database`、`--api-port`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。
|
||||
8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先按 `KEY=value` 子集加载发布目录根部的 `.env`、`.env.local`,兼容 UTF-8 BOM 与 CRLF,再回退到构建时通过 `--database`、`--api-port`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。
|
||||
9. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/<timestamp> ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包。
|
||||
|
||||
发布包结构:
|
||||
@@ -160,10 +160,11 @@ cd build/<timestamp>
|
||||
|
||||
1. 构建脚本会把仓库根目录已有的 `.env`、`.env.local` 一并复制进发布包,因此运行前必须确认这些文件内容适合被带入目标环境。
|
||||
2. 如果仓库根目录不存在 `.env` 或 `.env.local`,脚本会打印跳过日志,但不会因此失败;此时 `start.sh` 仅使用构建时写入的默认值与运行时显式传入的环境变量。
|
||||
3. `start.sh` 默认不追加清理参数;只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,在 schema 冲突时清理旧模块数据后重发。
|
||||
4. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm;清库模式下会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。
|
||||
5. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。
|
||||
6. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。
|
||||
3. `start.sh` 只解析合法 `KEY=value` 环境行,支持不加引号、双引号和单引号;不执行复杂 shell 表达式,避免把环境文件变成脚本入口。
|
||||
4. `start.sh` 默认不追加清理参数;只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,在 schema 冲突时清理旧模块数据后重发。
|
||||
5. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm;清库模式下会追加 `-c=on-conflict`,仅在 schema 冲突时删除旧模块数据。
|
||||
6. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。
|
||||
7. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。
|
||||
|
||||
目标服务器最小要求:
|
||||
|
||||
|
||||
@@ -206,6 +206,8 @@
|
||||
|
||||
1. 密码登录仍由 `user_account.password_hash` 承担
|
||||
2. 本轮不引入 `password` provider identity
|
||||
3. 密码登录只接受已绑定手机号的账号,不支持邮箱、用户名或叙世号作为登录身份
|
||||
4. 密码登录不创建账号,新账号只由手机号验证码登录创建
|
||||
|
||||
### 9.2 `POST /api/auth/phone/login`
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
当前 Node 鉴权主链已经依赖 `users` 主表完成以下能力:
|
||||
|
||||
1. `POST /api/auth/entry`:用户名密码登录,不存在则自动创建账号
|
||||
1. `POST /api/auth/entry`:手机号密码登录,仅允许已存在且已设置密码的手机号账号登录
|
||||
2. `POST /api/auth/phone/login`:手机号验证码登录,不存在则自动创建账号
|
||||
3. `GET /api/auth/me`:读取当前账号基础信息
|
||||
4. `POST /api/auth/logout`:提升 `token_version`,让当前 access token 失效
|
||||
@@ -99,8 +99,9 @@
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `user_id` | `String` | 是 | 主键,继续沿用当前 `user_*` 前缀格式。 |
|
||||
| `username` | `String` | 是 | 当前密码登录用户名;手机号/微信创建的系统账号同样要写入唯一用户名。 |
|
||||
| `password_hash` | `String` | 是 | 密码登录校验字段;手机号/微信创建账号时继续写随机密码哈希,保持兼容。 |
|
||||
| `username` | `String` | 是 | 系统账号名;不再作为前台密码登录标识,手机号/微信创建账号时仍写入唯一系统用户名。 |
|
||||
| `password_hash` | `Option<String>` | 否 | 用户显式设置或重置密码后才写入;手机号/微信新建账号默认不可用密码登录。 |
|
||||
| `password_login_enabled` | `bool` | 是 | 是否允许密码登录;只有用户设置或重置密码后才为 `true`。 |
|
||||
| `token_version` | `u32` | 是 | access token 统一失效计数,默认 `1`。 |
|
||||
| `display_name` | `String` | 是 | 账号展示名;密码账号默认用户名,手机号账号默认脱敏手机号,微信待绑定账号默认微信昵称或“微信旅人”。 |
|
||||
| `login_provider` | `String` | 是 | 当前账号的主登录归属,枚举固定为 `password`、`phone`、`wechat`。 |
|
||||
@@ -131,9 +132,9 @@
|
||||
### 6.2 必须具备的查询索引
|
||||
|
||||
1. `username`
|
||||
作用:支撑 `POST /api/auth/entry`
|
||||
作用:系统账号唯一约束与内部排查,不作为前台密码登录入口
|
||||
2. `primary_phone_e164`
|
||||
作用:支撑 `POST /api/auth/phone/login`、`POST /api/auth/phone/change`
|
||||
作用:支撑 `POST /api/auth/entry`、`POST /api/auth/phone/login`、`POST /api/auth/phone/change`
|
||||
3. `account_status + updated_at`
|
||||
作用:后续管理端、审计排查与禁用账号扫描
|
||||
4. `merged_to_user_id`
|
||||
@@ -188,13 +189,12 @@
|
||||
|
||||
写入规则:
|
||||
|
||||
1. 先按 `username` 查询
|
||||
2. 若不存在,则创建一条 `active` 账号
|
||||
3. `login_provider = password`
|
||||
4. `display_name = username`
|
||||
5. `primary_phone_e164 = null`
|
||||
6. `phone_verified_at = null`
|
||||
7. `last_login_at = 当前时间`
|
||||
1. 只读取请求中的 `phone` 和 `password`。
|
||||
2. 先把 `phone` 归一化为 `primary_phone_e164` 后查询账号。
|
||||
3. 若手机号不存在,返回 `401`,不创建账号。
|
||||
4. 若账号存在但 `password_login_enabled = false` 或 `password_hash = null`,返回 `401`。
|
||||
5. 若账号存在且已设置密码,校验 `password_hash`。
|
||||
6. 校验成功后只更新登录会话与 `last_login_at`,不改变账号主归属。
|
||||
|
||||
### 8.2 `POST /api/auth/phone/login`
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ SELECT * FROM auth_store_snapshot WHERE snapshot_id = 'default';
|
||||
### `user_account`
|
||||
|
||||
- 作用:用户账号主表,保存用户名、公开叙世号、手机号掩码、登录方式、密码登录开关和 token 版本。
|
||||
- 结构:`user_id PK: String`, `public_user_code: String`, `username: String`, `display_name: String`, `phone_number_masked: Option<String>`, `phone_number_e164: Option<String>`, `login_method: String`, `binding_status: String`, `wechat_bound: bool`, `password_hash: String`, `password_login_enabled: bool`, `token_version: u64`。
|
||||
- 结构:`user_id PK: String`, `public_user_code: String`, `username: String`, `display_name: String`, `phone_number_masked: Option<String>`, `phone_number_e164: Option<String>`, `login_method: String`, `binding_status: String`, `wechat_bound: bool`, `password_hash: Option<String>`, `password_login_enabled: bool`, `token_version: u64`。
|
||||
- 索引:`username`, `public_user_code`。
|
||||
|
||||
```sql
|
||||
|
||||
Reference in New Issue
Block a user