Merge remote-tracking branch 'origin/master' into fix/canva-preview-mismatch
This commit is contained in:
@@ -92,11 +92,11 @@
|
||||
- `POST /api/editor/assets`:批量或单个创建账号级素材,登录态上传必须写入 OSS / asset object 引用和 `/<objectKey>` 轻量路径,不允许把 Data URL / signed URL 写入素材库。
|
||||
- `PATCH /api/editor/assets/{assetId}`:重命名素材或移动素材到文件夹。
|
||||
- `DELETE /api/editor/assets/{assetId}`:删除素材。已放入画布的 project resource 不被级联删除,避免旧画布丢图。
|
||||
- `POST /api/editor/images/generations`:按提示词调用 VectorEngine 生成图片。带 `model / aspectRatio / imageSize` 的用户生成必须把当前 K 档对应的真实像素直接传给 provider,前端占位与该请求尺寸使用同一映射;不得先请求固定 1K 再放大为 2K。普通图片的 provider 回图先留在内存,尺寸变换成功后只上传变换结果,变换失败则只上传 provider 原图,主结果只写一次 OSS 且不额外创建“原始输出”。角色生成可携带 `model`、`screenColor`、`segModel`、`aspectRatio`、`imageSize` 和 `referenceImageSrcs`;父流程先保存带纯色背景源图,随后只以 object key 向唯一 loopback `bgfilter-worker` 发起一次内部 HTTP RPC;子 worker 在每次真实 provider attempt 前签发短期 OSS URL,并向 BgFilter 传入 `screen_color=<screenColor>`、`seg_model=<segModel>`。父流程不直连 BgFilter、不签发该 URL,也不重试已被 worker 接收的内部 RPC(连接从未建立时按调度方案 §5.1 有界重连);透明处理成功时生成透明 PNG,最终失败时按前述多产物降级规则以原图主结果和通用 `warning` 收口。角色、图标图集和 UI 图集的透明处理正常成功但返回尺寸与 provider 原图不同时,只重采样透明图的 alpha 蒙版并应用回 provider 原图的原始分辨率 RGB,不放大低分辨率后处理成品。宣发素材携带 `kind: "publication-material"` 时固定归一为 `gpt-image-2`,不支持 `nanobanana2`,并继续按固定交付像素处理。`nanobanana2` 参考图作为 `inline_data` 进入 `generateContent`,`gpt-image-2` 参考图进入 edits;`nanobanana2` 的 `512 / 1024 / 2K` 是标量清晰度档位,后端保留 provider 输出几何尺寸,不按 `宽x高` 解析。从既有图层重新打开生成器且没有仍存活的对话框快照时,前端按该图层真实 `originalWidth / originalHeight` 恢复比例和清晰度,不得回落到新建面板的 1K 默认值。普通重绘继续走该接口并把当前图层图片作为参考图;图片快速编辑不走该接口。请求可携带 `projectId`、`assetFolderId`、`assetKind`、`generationInputs` 和 `sourceResourceId`,后端生成完成后在响应中返回实际产物的 project / resource / asset 快照。
|
||||
- `POST /api/editor/images/generations`:按提示词调用 VectorEngine 生成图片。带 `model / aspectRatio / imageSize` 的用户生成以统一业务像素矩阵创建前端占位和最终画布资源,例如两种图片模型的 `2K·16:9` 都交付 `2048x1152`;不得先请求固定 1K 再放大为 2K。`gpt-image-2` 在 provider 边界使用其接口支持的对齐请求尺寸,该尺寸不是业务交付尺寸;`nanobanana2` 仍把比例和清晰度档位写入 `generateContent`。provider 回图大于业务目标且比例偏差在允许范围内时,在内存中缩小并轻微裁切到业务尺寸后只上传最终结果。任意一边小于业务目标或比例偏差过大时禁止放大或大幅裁切,只上传 provider 实际回图,以实际尺寸写入结果并通过通用 `warning` 提示用户。主结果只写一次 OSS 且不额外创建“原始输出”。角色生成可携带 `model`、`screenColor`、`segModel`、`aspectRatio`、`imageSize` 和 `referenceImageSrcs`;父流程先按 provider 原始分辨率保存带纯色背景源图,随后只以 object key 向唯一 loopback `bgfilter-worker` 发起一次内部 HTTP RPC;子 worker 在每次真实 provider attempt 前签发短期 OSS URL,并向 BgFilter 传入 `screen_color=<screenColor>`、`seg_model=<segModel>`。父流程不直连 BgFilter、不签发该 URL,也不重试已被 worker 接收的内部 RPC(连接从未建立时按调度方案 §5.1 有界重连);透明处理成功时只重采样透明图的 alpha 蒙版并应用回 provider 原图 RGB,最后把透明主结果归一到统一业务像素;最终失败时按前述多产物降级规则以原图主结果和通用 `warning` 收口。图标图集和 UI 图集的透明处理正常成功但返回尺寸与 provider 原图不同时,同样只重采样 alpha 蒙版并应用回 provider 原图,不放大低分辨率后处理成品。宣发素材携带 `kind: "publication-material"` 时固定归一为 `gpt-image-2`,不支持 `nanobanana2`,并继续按固定交付像素处理。从既有图层重新打开生成器且没有仍存活的对话框快照时,前端按该图层真实 `originalWidth / originalHeight` 恢复比例和清晰度,不得回落到新建面板的 1K 默认值。普通重绘继续走该接口并把当前图层图片作为参考图;图片快速编辑不走该接口。请求可携带 `projectId`、`assetFolderId`、`assetKind`、`generationInputs` 和 `sourceResourceId`,后端生成完成后在响应中返回实际产物的 project / resource / asset 快照。
|
||||
- `POST /api/editor/images/background-removals`:接收当前图片的 `objectKey`、`resourceId` 或 `assetId` 候选引用,登录态和稳定引用入口校验通过后创建外部生成任务,响应只返回 `queueState`。父 `external-generation-worker` 负责把候选引用解析为已登记、已校验当前账号归属的私有 OSS object key,只向唯一 `bgfilter-worker` 发起一次内部 HTTP RPC,传递 object key、`maxQueueWaitMs`、公式化 `callBudgetMs` 以及固定的 `background_mode=complex + seg_model=birefnet + cross_check=off`;父侧不下载原图、不签发 URL,也不发送 `file` 或 `screen_color`。子 worker 在每次真实 provider attempt 前签发 600 秒 OSS URL,以默认 `Q=2048` admission 保险丝和 provider 并发 `N=16` 限流,取得 provider permit 后才启动 `callBudgetMs`,并对同一次逻辑调用最多执行两次顺序 provider attempt;成功图片以内部 HTTP 二进制 body 返回父流程,父侧不重试已被 worker 接收的内部 RPC(连接从未建立时按调度方案 §5.1 有界重连)。complex 任意最终失败都直接使父任务失败,不进入阿里云或本地键色 fallback。请求可携带 `projectId`、`targetLayerId`、`assetFolderId`、`assetLabel`、`sourceResourceId` 和 `canvasCompletion`;成功后仍由父流程完成最终 OSS / project resource 持久化,有 `canvasCompletion` 时按生成占位写入结果图层,否则沿用旧的目标图层替换路径。provider 令牌只在子 worker 服务端通过 `GENARRATIVE_EDITOR_BGFILTER_TOKEN` 注入,未配置时兼容回退旧 `GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_TOKEN`;父子内部调用另使用独立内部 Token。
|
||||
- `POST /api/editor/icon-spritesheets/generations`:按图标规范图和素材描述数组生成 spritesheet;api-server 先保存带纯色背景 spritesheet 源图,透明处理成功后再保存透明 spritesheet 并尝试拆分。请求支持 `model`、`screenColor`、`segModel`、`aspectRatio`、`imageSize`、`priceMudPoints`、`projectId`、`assetFolderId` 和 `generationInputs`;`priceMudPoints` 必须来自编辑器生成计费配置中对应生图模型的尺寸档位(如 `nanobanana2` 的 `0.5K / 1K / 2K` 或 `gpt-image-2` 的 `1K / 2K`),后端用 `editor_generation_config` 校验后才调用上游;`nanobanana2` 走原生 `generateContent` 并写入 `generationConfig.imageConfig.aspectRatio/imageSize`,`0.5K` 传 `"512"`;`gpt-image-2` 走 `/v1/images/edits`。透明处理最终失败时只保存并返回原图主结果,不生成透明图或切片;透明图成功但拆分失败时保留整张透明图并返回 `sliceWarning`。响应只返回实际产物对应的 project / resource / asset 快照及可选通用 `warning`。
|
||||
- `POST /api/editor/ui-designs/assets/extractions`:前端把红色框选轮廓绘入本地临时图后,先将该图上传 OSS 并确认 asset object,再以返回的 `objectKey` 作为参考图入队;Data URL / Blob URL 只允许停留在上传前的浏览器临时态。接口固定 `gpt-image-2` 和自动决策纯色背景素材提取提示词生成素材 spritesheet;api-server 先保存带纯色背景 spritesheet 源图,透明处理成功后再保存透明 spritesheet 并按连通域尝试拆分为 `素材 1..N`,返回结构复用图标 spritesheet 响应。请求必须携带 `screenColor`、`segModel`、`aspectRatio: "1:1"`、`imageSize: "1K" | "2K"` 和 `priceMudPoints`;框选数量不超过 6 个时前端按 `1:1·1K` 与 gpt-image-2 1K 价格提交,超过 6 个时按 `1:1·2K` 与 2K 价格提交。后端必须在调用上游前校验比例、尺寸和泥点价格,只允许 `1:1 / 1K / 2K`。透明处理最终失败时只保存并返回原图主结果,不生成透明图或切片;透明图成功但拆分失败时保留整张透明图并返回 `sliceWarning`。请求可携带 `projectId`、`assetFolderId`、`generationInputs` 和 `spritesheetLabel`,响应只返回实际产物对应的 project / resource / asset 快照及可选通用 `warning`;前端按后端快照落画布,不补造缺失产物。
|
||||
- `POST /api/editor/images/edits`:按提示词和当前图片的已登记 `objectKey` / `resourceId` 修改图片,返回新的生成图片元数据;图片快速编辑当前只提交 `sourceImageSrc`,不提交隐藏的 `referenceImageSrcs`,并随用户当前选择提交 `model / aspectRatio / imageSize / size`。api-server 必须先归一模型再选择 VectorEngine 协议:`nanobanana2` 调用 `/v1beta/models/{model}:generateContent` 并把原图作为 `inline_data`、比例和清晰度写入 `generationConfig.imageConfig`;`gpt-image-2` 调用 `/v1/images/edits` multipart。gpt-image-2 路径在 provider 边界把目标尺寸和所有 multipart 参考图临时补齐到 16 的倍数,回图后在内存恢复业务目标尺寸;nanobanana2 路径保留 provider 按比例和清晰度返回的几何尺寸。成功时只上传最终结果,尺寸恢复失败时只上传 provider 原图;无论是否发生尺寸恢复都只创建一个 project resource / 账号素材,不显示重复“原始输出”。16 对齐尺寸不得泄漏到正常完成的最终响应、资源或图层 Resolution;变换失败降级时以实际 provider 原图尺寸为准。本地红框标记图必须先上传再提交 objectKey;请求携带 project / asset 上下文时由后端创建新 resource / asset,前端只消费响应快照。
|
||||
- `POST /api/editor/images/edits`:按提示词和当前图片的已登记 `objectKey` / `resourceId` 修改图片,返回新的生成图片元数据;图片快速编辑当前只提交 `sourceImageSrc`,不提交隐藏的 `referenceImageSrcs`,并随用户当前选择提交 `model / aspectRatio / imageSize / size`。api-server 必须先归一模型再选择 VectorEngine 协议:`nanobanana2` 调用 `/v1beta/models/{model}:generateContent` 并把原图作为 `inline_data`、比例和清晰度写入 `generationConfig.imageConfig`;`gpt-image-2` 调用 `/v1/images/edits` multipart。gpt-image-2 路径在 provider 边界把目标尺寸和所有 multipart 参考图临时补齐到 16 的倍数;nanobanana2 路径保留 provider 的比例 / 清晰度请求,但两条路径回图后都以统一业务目标尺寸尝试归一。只允许缩小和轻微裁切;回图任意一边小于目标或比例偏差过大时保留 provider 实际回图及尺寸,并返回通用 `warning`,不得放大伪造所选档位。无论是否发生尺寸恢复都只创建一个 project resource / 账号素材,不显示重复“原始输出”。provider 对齐尺寸或原生 K 档像素不得泄漏到正常完成的最终响应、资源或图层 Resolution;变换失败降级时以实际 provider 原图尺寸为准。本地红框标记图必须先上传再提交 objectKey;请求携带 project / asset 上下文时由后端创建新 resource / asset,前端只消费响应快照。
|
||||
- `POST /api/editor/videos/generations`:按视频描述、模型、比例、时长、分辨率、模式、声音、默认联网搜索标记和泥点价格生成视频。前端可选模型为 `seedance2.0-fast`、`seedance2.0`、`kling3.0`、`kling3.0-omni`,默认 `seedance2.0-fast`;后端必须将 `seedance2.0-fast` 映射到 `doubao-seedance-2-0-fast-260128`,将 `seedance2.0` 映射到 `doubao-seedance-2-0-260128`,两者不得混用。后端允许 6 类比例、4 到 15 秒整数、`480p / 720p / 1080p`,并拒绝 `seedance2.0-fast + 1080p`;`sound=on/off` 映射 Ark `generate_audio=true/false`。后端复用 Ark / VectorEngine content generation task 轮询链路,下载最终视频并持久化到 OSS;请求携带 `projectId` / `assetFolderId` 时同步创建 project resource / 账号素材并返回 `project` / `asset` 快照,基础响应返回 `videoSrc`、尺寸、prompt、model、provider、taskId、durationSeconds、resolution 和 `priceMudPoints`。
|
||||
- `POST /api/editor/audios/sound-effects/generations` 与 `POST /api/editor/audios/background-music/generations`:按音效 / 背景音乐参数生成音频并持久化到 OSS;请求携带 `projectId` / `assetFolderId` 时同步创建 project resource / 账号素材并返回 `project` / `resource` / `asset` 快照,基础响应返回 `audioSrc`、prompt、model、provider、taskId、duration、歌词和 `priceMudPoints`。
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 后台管理多账号与 Tab 访问权限方案
|
||||
|
||||
更新时间:`2026-07-23`
|
||||
更新时间:`2026-07-24`
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
本文定义陶泥儿后台从单一环境变量管理员扩展为“1 个 owner 引导账号 + 多个 member 持久账号”的编码契约,并为每个一级 Tab 建立前后端一致的访问权限。
|
||||
|
||||
本次只增加后台管理员账号与整页访问权限,不引入页面内按钮级、字段级或只读权限。正式实现必须同时完成前端导航过滤和后端 API 鉴权;前端过滤只改善体验,不能作为安全边界。
|
||||
后台权限默认仍以整页 Tab 为粒度;只有会触发权威钱包全量扫描的“手动对账用户历史花费”作为明确例外,使用独立操作权限,不随任何 Tab 自动授予。正式实现必须同时完成前端按钮过滤和后端 API 鉴权;前端过滤只改善体验,不能作为安全边界。
|
||||
|
||||
## 2. 当前基线与目标
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
1. 现有环境变量账号升级为 `owner`,仍由部署环境提供,不迁移、不复制到 SpacetimeDB。
|
||||
2. owner 始终拥有全部 15 个业务 Tab 权限,并独占“账号管理”Tab 和账号管理 API。
|
||||
3. owner 可以创建、修改、启停 member;member 保存在 SpacetimeDB 私有表 `admin_account`。
|
||||
4. member 按一级 Tab 分配权限;获得一个 Tab 权限即获得该页面内全部读写能力,页面内部二级 Tab、弹窗和操作区继承一级权限。
|
||||
5. member JWT 每次请求都重新读取当前账号并校验 `enabled`、`token_version` 和实时权限,权限、密码或启停变更应立即让旧 JWT 失效。
|
||||
4. member 按一级 Tab 分配常规权限;获得一个 Tab 权限即获得该页面内常规读写能力。历史花费手动对账必须另行授予 `profile-wallet-consumption-reconcile`,任何 Tab 都不隐式包含。
|
||||
5. member JWT 每次请求都重新读取当前账号并校验 `enabled`、`token_version`、实时 Tab 权限和独立操作权限,权限、密码或启停变更应立即让旧 JWT 失效。
|
||||
|
||||
## 3. 角色与不可变规则
|
||||
|
||||
@@ -26,18 +26,18 @@
|
||||
|
||||
- owner 用户名和密码继续读取 `GENARRATIVE_ADMIN_USERNAME`、`GENARRATIVE_ADMIN_PASSWORD`。
|
||||
- owner 是环境变量构造的虚拟账号,不写入 `admin_account`,不允许通过后台改名、改密、禁用或删除。
|
||||
- owner 始终拥有本文列出的全部 15 个可分配权限,不能在前端取消,也不从数据库加载权限。
|
||||
- “账号管理”是 owner-only 能力。它可以作为新增一级路由 `accounts` / `#accounts` 展示,但 `accounts` 不进入 `ADMIN_TAB_PERMISSIONS`,不能写入 member 的 `permissions_json`。
|
||||
- owner 始终拥有本文列出的全部 15 个 Tab 权限和全部独立操作权限,不能在前端取消,也不从数据库加载权限。
|
||||
- “账号管理”是 owner-only 能力。它可以作为新增一级路由 `accounts` / `#accounts` 展示,但 `accounts` 不进入 `ADMIN_TAB_PERMISSIONS` 或 `ADMIN_ACTION_PERMISSIONS`,不能写入 member 的权限 JSON。
|
||||
- owner 会话返回 `accountRole = "owner"`、`roles = ["admin", "owner"]`;账号管理权限必须根据服务端确认的 `accountRole` 判断,不能只相信前端角色字符串。
|
||||
- owner 配置缺失时,后台整体保持未启用状态;不能依赖数据库中的 member 绕过 owner 引导配置启动后台。
|
||||
|
||||
### 3.2 member
|
||||
|
||||
- member 只来自 `admin_account`,不新增第二套环境变量账号。
|
||||
- member 会话返回 `accountRole = "member"`、`roles = ["admin", "member"]` 和当前实时 `tabPermissions`。
|
||||
- member 会话返回 `accountRole = "member"`、`roles = ["admin", "member"]`、当前实时 `tabPermissions` 和 `actionPermissions`。
|
||||
- member 永远不能访问账号管理页面或账号管理 API,也不能给自己或他人分配 `accounts`。
|
||||
- member 的一个一级 Tab 权限覆盖该页面的查询、创建、修改、启停、退款等全部现有操作,不拆成 `read` / `write`。
|
||||
- 页面内二级 Tab、筛选视图、抽屉、弹窗和共享详情弹窗继承触发它的一级 Tab 权限,不另设 permission id。
|
||||
- member 的一个一级 Tab 权限覆盖该页面的常规查询、创建、修改、启停、退款等操作,不拆成通用 `read` / `write`。
|
||||
- 页面内二级 Tab、筛选视图、抽屉、弹窗和共享详情弹窗默认继承触发它的一级 Tab 权限;历史花费手动对账是唯一独立高风险操作例外,无权限时共享用户详情不展示按钮,直接请求仍由后端返回 403。
|
||||
|
||||
## 4. 权限标识
|
||||
|
||||
@@ -61,9 +61,15 @@
|
||||
| `editor-showcase` | 精选审核 | `#editor-showcase` |
|
||||
| `editor-assets` | 素材查询 | `#editor-assets` |
|
||||
|
||||
权限数组必须去重并按上表顺序规范化后保存。保存时拒绝未知值和 `accounts`;读取旧数据时遇到未知值应忽略并记录告警,绝不能将未知值解释为全权限。空数组合法,表示 member 可以登录但没有业务页面权限。
|
||||
Tab 权限数组必须去重并按上表顺序规范化后保存。保存时拒绝未知值和 `accounts`;读取旧数据时遇到未知值应忽略并记录告警,绝不能将未知值解释为全权限。空数组合法,表示 member 可以登录但没有业务页面权限。
|
||||
|
||||
后续新增一级 Tab 时,必须在同一次改动中更新:
|
||||
`ADMIN_ACTION_PERMISSIONS` 是独立操作权限闭合集合,当前只有:
|
||||
|
||||
| permission id | 操作 | 授权边界 |
|
||||
| --- | --- | --- |
|
||||
| `profile-wallet-consumption-reconcile` | 手动对账用户历史花费 | owner 默认拥有;member 必须在账号管理中单独勾选,不要求同时持有特定 Tab |
|
||||
|
||||
独立操作权限保存在 `action_permissions_json`,响应为 `actionPermissions`;未知值必须拒绝。后续新增一级 Tab 时,必须在同一次改动中更新:
|
||||
|
||||
- shared-contracts 的 `ADMIN_TAB_PERMISSIONS`。
|
||||
- admin-web 的路由定义、权限标签和第一可访问项顺序。
|
||||
@@ -80,13 +86,14 @@
|
||||
| `username` | `String` | `unique`;登录名,创建后不可修改;按 `trim + ASCII lowercase` 规范化 |
|
||||
| `display_name` | `String` | 展示名,去除首尾空白后 1 至 64 字符 |
|
||||
| `password_hash` | `String` | Argon2id PHC 字符串;只在内部登录查询中返回给 api-server,永不进入 HTTP DTO、日志或前端状态 |
|
||||
| `permissions_json` | `String` | 规范化后的 Tab permission JSON;只允许第 4 节 15 个值,空数组为 `[]` |
|
||||
| `tab_permissions_json` | `String` | 规范化后的 Tab permission JSON;只允许第 4 节 15 个值,空数组为 `[]` |
|
||||
| `enabled` | `bool` | 是否允许登录和继续使用现有 JWT |
|
||||
| `token_version` | `u64` | 初始为 `1`;权限、密码或启停状态发生有效变化时加 `1` |
|
||||
| `created_by` | `String` | 创建者后台 subject;当前只能是 owner subject |
|
||||
| `updated_by` | `String` | 最近更新者后台 subject;当前只能是 owner subject |
|
||||
| `created_at` | `Timestamp` | 创建时间,使用 `ctx.timestamp` |
|
||||
| `updated_at` | `Timestamp` | 最近更新时间,使用 `ctx.timestamp` |
|
||||
| `action_permissions_json` | `Option<String>` | 既有表末尾追加;旧行默认 `None` 并按 `[]` 读取,只允许第 4 节独立操作权限 |
|
||||
|
||||
账号规则:
|
||||
|
||||
@@ -94,7 +101,7 @@
|
||||
- owner 用户名属于保留名称。创建 member 时必须同时与当前规范化后的 owner 用户名比较并拒绝冲突,不能只依赖 `admin_account.username` 唯一索引。
|
||||
- 密码明文只存在于登录、创建和改密请求生命周期内;限制为 6 至 128 个字符,并复用 `platform-auth` 的 Argon2id 哈希与校验能力。Argon2id 必须在 blocking 任务中执行,api-server 通过有界信号量限制同时 hash / verify 数量,不得占用 Tokio worker 或无界堆积高成本任务。
|
||||
- 不提供物理删除 API。离职或停用通过 `enabled = false` 完成,以保留 `created_by`、`updated_by` 和账号标识。
|
||||
- `display_name` 单独变化只更新 `updated_by`、`updated_at`,不要求递增 `token_version`;权限、密码、`enabled` 任一有效变化必须在同一事务中递增版本。
|
||||
- `display_name` 单独变化只更新 `updated_by`、`updated_at`,不要求递增 `token_version`;Tab 权限、独立操作权限、密码、`enabled` 任一有效变化必须在同一事务中递增版本。
|
||||
- `u64` 版本到达上限时更新失败关闭,不能回绕。
|
||||
|
||||
## 6. SpacetimeDB 与 facade 边界
|
||||
@@ -158,9 +165,10 @@ owner 优先既保持原账号行为,也防止数据库同名记录遮蔽或
|
||||
```text
|
||||
accountRole: "owner" | "member"
|
||||
tabPermissions: string[]
|
||||
actionPermissions: string[]
|
||||
```
|
||||
|
||||
owner 返回全部 15 个 permission id;member 返回数据库中的实时规范化数组。`GET /admin/api/me` 同样执行逐请求校验并返回实时权限,供刷新页面后恢复导航。
|
||||
owner 返回全部 15 个 Tab permission id 和全部独立操作权限;member 返回数据库中的两组实时规范化数组。`GET /admin/api/me` 同样执行逐请求校验并返回实时权限,供刷新页面后恢复导航和操作按钮。
|
||||
|
||||
后台所有面向运营展示的管理员身份统一使用 `displayName`。审计表继续保存稳定 subject,例如 owner subject 或 `admin-account-<uuid>`;api-server 在返回兑换码、邀请码等操作记录时,按 owner 运行态和 `admin_account` 批量解析显示名称,同时兼容历史用户名记录。已无法解析的历史主体统一展示“已停用管理员”,前端不得直接渲染 `operatorUserId`、账号 ID 或登录用户名代替显示名称。对写接口,显示名目录必须在主事务前加载,或在主事务成功后降级为占位文案;不得因二次读取失败把已提交写入伪装成失败。
|
||||
|
||||
@@ -168,14 +176,15 @@ owner 返回全部 15 个 permission id;member 返回数据库中的实时规
|
||||
|
||||
在统一 `require_admin_auth` 之后增加可复用的权限守卫,支持:
|
||||
|
||||
- `require_admin_permission(permission)`:owner 自动通过;member 必须包含该 permission。
|
||||
- `require_any_admin_permission([permission...])`:owner 自动通过;member 至少包含一个,用于共享 API。
|
||||
- `require_admin_tab_permission(permission)`:owner 自动通过;member 必须包含该 Tab permission。
|
||||
- `require_any_admin_tab_permission([permission...])`:owner 自动通过;member 至少包含一个,用于共享读取 API。
|
||||
- `require_admin_action_permission(permission)`:owner 自动通过;member 必须包含该独立操作 permission。
|
||||
- `require_admin_owner`:只接受服务端确认的 owner。
|
||||
|
||||
返回语义统一如下:
|
||||
|
||||
- `401 Unauthorized`:token 缺失、无效、过期,member 不存在、被停用或 `token_version` 过期。
|
||||
- `403 Forbidden`:会话有效但缺少目标 Tab 权限,或 member 请求 owner-only API。
|
||||
- `403 Forbidden`:会话有效但缺少目标 Tab / 独立操作权限,或 member 请求 owner-only API。
|
||||
- 前端收到 `401` 清除本地 token 并回到登录页;收到 `403` 不应伪装成掉线,应刷新 `/me` 权限并跳转到第一可访问项或零权限空态。
|
||||
|
||||
## 9. API-to-Tab 权限矩阵
|
||||
@@ -223,6 +232,8 @@ owner 返回全部 15 个 permission id;member 返回数据库中的实时规
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/register` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/manual-review/resolve` | `recharge-orders` |
|
||||
| `GET` | `/admin/api/profile/users/detail` | `tables OR tracking OR recharge-orders OR editor-showcase OR editor-assets` |
|
||||
| `POST` | `/admin/api/profile/users/reconcile-consumption` | 独立操作权限 `profile-wallet-consumption-reconcile` |
|
||||
| `POST` | `/admin/api/profile/users/initialize-consumption-projections` | owner-only 维护窗口操作 |
|
||||
| `POST` | `/admin/api/profile/wallet-restriction` | `recharge-orders` |
|
||||
| `GET` | `/admin/api/accounts` | owner-only |
|
||||
| `POST` | `/admin/api/accounts` | owner-only |
|
||||
@@ -249,6 +260,7 @@ accounts: Array<{
|
||||
username,
|
||||
displayName,
|
||||
tabPermissions,
|
||||
actionPermissions,
|
||||
enabled,
|
||||
tokenVersion,
|
||||
createdBy,
|
||||
@@ -270,6 +282,7 @@ accounts: Array<{
|
||||
displayName: string,
|
||||
password: string,
|
||||
tabPermissions: string[],
|
||||
actionPermissions: string[],
|
||||
enabled?: boolean
|
||||
}
|
||||
```
|
||||
@@ -285,11 +298,12 @@ accounts: Array<{
|
||||
displayName: string,
|
||||
password?: string,
|
||||
tabPermissions: string[],
|
||||
actionPermissions: string[],
|
||||
enabled: boolean
|
||||
}
|
||||
```
|
||||
|
||||
`username` 和 `account_id` 不可修改。更新请求完整提交显示名称、Tab 权限和启停状态;密码省略表示不修改,空字符串密码作为非法参数拒绝。api-server 只在提供新密码时生成新 hash。procedure 比较有效变化,在权限、密码或启停任一变化时只递增一次 `token_version`,并在同一事务写入账号字段、`updated_by`、`updated_at`。响应仍不返回密码或 hash。
|
||||
`username` 和 `account_id` 不可修改。更新请求完整提交显示名称、Tab 权限、独立操作权限和启停状态;密码省略表示不修改,空字符串密码作为非法参数拒绝。api-server 只在提供新密码时生成新 hash。procedure 比较有效变化,在权限、密码或启停任一变化时只递增一次 `token_version`,并在同一事务写入账号字段、`updated_by`、`updated_at`。响应仍不返回密码或 hash。
|
||||
|
||||
## 11. admin-web 行为
|
||||
|
||||
@@ -313,7 +327,7 @@ accounts: Array<{
|
||||
|
||||
### 11.3 账号管理页
|
||||
|
||||
- 权限编辑器展示 15 个明确的 checkbox,每项使用现有 Tab 中文名称;不能展示或提交 `accounts`。
|
||||
- 权限编辑器分为 15 个 Tab checkbox 和独立操作权限区;当前独立区只显示“手动对账用户历史花费”。不能展示或提交 `accounts`。
|
||||
- 创建和编辑使用独立弹窗或抽屉,不在列表下方追加表单。
|
||||
- 编辑时密码字段默认空,空表示请求中省略 `password`;页面永不展示现有密码或 hash。
|
||||
- 停用使用开关并二次确认。保存成功后以 API 返回 account snapshot 更新列表。
|
||||
@@ -323,17 +337,17 @@ accounts: Array<{
|
||||
|
||||
建议按以下边界落地,避免在前端或 `api-server` 重新发明持久化规则:
|
||||
|
||||
- `shared-contracts`:`ADMIN_TAB_PERMISSIONS`、扩展后的 `AdminSessionPayload`、账号管理 request/response DTO。
|
||||
- `shared-contracts`:`ADMIN_TAB_PERMISSIONS`、`ADMIN_ACTION_PERMISSIONS`、扩展后的 `AdminSessionPayload`、账号管理 request/response DTO。
|
||||
- `spacetime-module`:私有表、输入类型、typed procedures、唯一性与版本递增事务。
|
||||
- `spacetime-client`:生成绑定、row mapper、登录查询与账号管理 facade。
|
||||
- `api-server`:owner/member 登录编排、Argon2id、逐请求账号解析、权限 middleware、账号管理 handlers。
|
||||
- `apps/admin-web`:权限感知路由、hash 回落、零权限空态、owner-only 账号管理页。
|
||||
|
||||
不能将 `permissions_json` 的解析与授权只放在前端;不能让 admin-web 直连 SpacetimeDB;不能用进程内 member 列表替代 `admin_account`。
|
||||
不能将 Tab / 独立操作权限 JSON 的解析与授权只放在前端;不能让 admin-web 直连 SpacetimeDB;不能用进程内 member 列表替代 `admin_account`。
|
||||
|
||||
## 13. 迁移、绑定与发布顺序
|
||||
|
||||
`admin_account` 是新增私有表,没有旧数据回填。原环境变量 owner 不入表,因此迁移不创建 owner 行。
|
||||
`admin_account` 已是私有表;本次只在表结构体最后追加带 `None` 默认值的 `action_permissions_json`,旧 member 自动按空独立权限读取。原环境变量 owner 不入表,并始终由 api-server 合成全部权限。
|
||||
|
||||
实现 schema 后必须:
|
||||
|
||||
@@ -380,16 +394,17 @@ spacetime publish <database> \
|
||||
- member 私表不能被普通 SpacetimeDB identity 查询或调用 procedure;只有 runtime service identity 可读写。
|
||||
- 创建重复规范化用户名、owner 保留用户名、未知权限或 `accounts` 权限均失败。
|
||||
- GET/POST/PUT 账号 API 任何响应和日志都不包含明文密码或 `password_hash`。
|
||||
- 权限、密码、启停更新各自会递增 `token_version`;同一次请求修改多项只递增一次;仅改展示名不递增。
|
||||
- Tab 权限、独立操作权限、密码、启停更新各自会递增 `token_version`;同一次请求修改多项只递增一次;仅改展示名不递增。
|
||||
- member 被停用、改密或改权限后,旧 JWT 下一次请求返回 401;重新登录后获得实时权限。
|
||||
- API-to-Tab 矩阵逐路由覆盖 `modules/admin.rs`,每条路由至少测试 owner 成功、具备权限的 member 成功、缺权限 member 返回 403。
|
||||
- 两个共享读取接口分别覆盖每个允许 permission 的成功用例,以及无关 permission 的 403 用例。
|
||||
- owner-only 账号 API 对任意 member 都返回 403,即使其 `permissions_json` 被污染为包含 `accounts`。
|
||||
- 历史花费手动对账对仅持有任意 Tab 的 member 返回 403;只持有独立操作权限时允许调用;全量投影初始化始终 owner-only。
|
||||
- owner-only 账号 API 对任意 member 都返回 403,即使其 Tab 或独立权限 JSON 被污染为包含 `accounts`。
|
||||
|
||||
### 14.2 前端
|
||||
|
||||
- owner 看到 15 个业务 Tab 和账号管理;member 只看到被分配的业务 Tab。
|
||||
- 每个一级 Tab 内的二级 Tab、弹窗和写操作继承一级权限并正常使用,不出现“页面可见但内部 API 403”的错误映射。
|
||||
- 常规二级 Tab、弹窗和写操作继承一级权限;历史花费对账按钮只在用户详情返回 `canReconcileConsumption=true` 时显示。
|
||||
- 直接输入无权限 hash 自动替换为第一可访问项,不短暂挂载无权限页面。
|
||||
- 当前 Tab 权限被 owner 收回后,下一请求触发重新登录;新会话恢复后落到第一可访问项。
|
||||
- 零权限 member 登录后显示空态,不回落 Dashboard、不发送 Dashboard 或其它业务请求,并可正常退出。
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
- SpacetimeDB schema guard 比较当前工作树与基线提交时,两侧都必须分别读取各自 `Cargo.toml` 的 `lib.path`,再沿 `mod` / `#[path]` 只扫描该快照 crate root 可达的 schema;不得递归扫描整个 `src/`,否则原位保留的旧源码会与现役历史数据壳产生假 accessor 重复。
|
||||
- `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-agent` 及其专属 `langchainrust` 依赖同样退出 workspace 与 `api-server` 依赖图;现役编辑器 Agent 仅需的模型常量收口到 `platform-llm`,不再通过旧拼图 Phase 1 / Creative Agent 执行器 crate 复用。后续抽出的 `platform-agent-harness` 是无旧玩法依赖的通用 JSON function-calling 底座,不得依赖、复用或重新挂回本条退役 crate。
|
||||
- `platform-auth` 不再编译 runtime guest token;`platform-wechat` 不再编译旧生成结果订阅服务,只保留现役认证和支付协议。
|
||||
|
||||
## 验收
|
||||
|
||||
Reference in New Issue
Block a user