权限菜单(一):GoAuto 模块写入系统菜单并支持角色配置 #137

Closed
opened 2026-08-28 17:41:59 +08:00 by ila · 3 comments
Owner

所属与来源

  • 关联工单:#62 AI 匹配设置(明文 API Key 例外)、#127 采购规则落库(新增模块时同样适用本机制)。
  • 来源:用户于 2026-08-28 反馈「Admin 采集采购相关模块没有加入系统菜单管理,角色编辑时不能选择菜单,需要把后面加的模块分成采购员用的和管理员看到全部的」;随后确认采购员可见模块需要支持后期在界面配置。
  • 类型:Server / 系统菜单入库与角色菜单绑定。
  • 设计证据:本工单不涉及前端界面改动,无需原型。前端渲染改造由后续工单实施。
  • 工具回退说明:本工单通过 Gitea API 创建;当前会话未提供 Gitea MCP 工具,按 AGENTS.md「Gitea 交互与工单最小读取」记录回退原因。

当前事实(提交 30d8238 复核)

  1. API 权限已实现:server/app/goauto/access/purchaser.go 是一份审阅过的权限矩阵(AdminAPIs 与 PurchaserAPIs());迁移 server/cmd/migrate/migration/version-local/1786701700000_purchaser_role.go 会创建「采购员」角色、把全部 GoAuto API 写入 sys_api,并把采购员可用的写入 casbin_rule。该迁移对采购员的 casbin 策略执行全量重建(先按 ptype='p' AND v0=purchaser 删除再重建),因此代码中减权可自动生效。
  2. 菜单从未入库:sys_menu 中没有任何 GoAuto 模块记录,因此角色编辑页的菜单树里选不到这些模块。
  3. 前端菜单是静态且不受权限控制:GoAuto 全部模块位于 web/src/router/index.js 的 constantRoutes(第 38~245 行,asyncRoutes 自 246 行起)。constantRoutes 对所有登录用户可见、不做角色过滤、也不读后端菜单。
  4. 由此产生的实际状况:采购员当前能看到全部 GoAuto 菜单(含 AI 匹配设置、SYB 店铺管理等管理员专属模块),只是点击后部分 API 被 casbin 拒绝。属体验割裂与信息暴露,但不构成越权。
  5. sys_menu 与 sys_api 之间通过 sys_menu_api_rule 多对多关联(app/admin/models/sys_menu.go:22)。

目标

  1. GoAuto 各模块写入 sys_menu,使角色编辑页可以勾选。
  2. 采购员的菜单可见范围支持后期在界面配置,人工调整不被后续迁移覆盖。
  3. 菜单可见性与 API 权限保持单一事实源,避免出现「菜单可见但接口被拒」或「菜单不可见但接口可调」的不一致。
  4. API 权限的安全强度不降低。

非目标

  • 不修改前端路由与菜单渲染(后续工单)。
  • 不放开 API 权限的界面配置:casbin_rule 仍由代码矩阵维护并全量重建。
  • 不改变既有 AdminAPIs 矩阵中任何一条的 Purchaser 取值。
  • 不改动用户自建的菜单与角色。
  • 不涉及采集、采购业务逻辑与 Agent。

关键设计决定

一、菜单可配、API 权限不可配(分层)

两者性质不同:

  • 菜单可见性属体验与信息暴露。勾多了,采购员只是看到一个点进去被拒的菜单,不会越权——API 层兜底。
  • API 权限是安全边界。若交由界面勾选,一次误操作即可能把「保存 AI 匹配设置」这类管理员专属接口开给采购员,而其中存有明文 API Key(#62 例外)。

因此:安全由 API 层兜底,可见性放开配置。

二、迁移的三种语义必须区分

对象 迁移行为 后果
casbin_rule(API 权限) 全量重建(现状不变) 代码减权自动生效;人工改动被还原
sys_menu 记录本身 有则更新、无则创建 菜单标题/路径/图标跟随代码
采购员的菜单绑定 只增不删 新增模块默认对采购员可见;界面上取消的勾选永久生效,不被迁移勾回

「只增不删」同时满足两个需求:新增模块无需每次手工勾;人工取消的勾选不被覆盖。代价是批量减少菜单只能人工操作,代码改默认值对已有角色不生效——对内部系统可接受,且安全线在 API 层不受影响。

三、单一事实源

在 access 包新增模块级定义,从现有 AdminAPIs 矩阵派生,每个模块包含:菜单标题、前端路径、图标、排序、所含 API 列表、采购员默认可见与否。

迁移据此同时生成 sys_menu、sys_api、casbin_rule 与 sys_menu_api_rule。「采购员能看什么」只有一处定义。

实施方案

  1. 在 server/app/goauto/access 新增模块定义,覆盖现有全部 GoAuto 前端模块:设备、PDD 商品、虾皮商品、SYB 商品、SYB 店铺、SYB 同步记录、档口入库码、采集规则、采集任务、采购任务、AI 匹配设置。路径与 web/src/router/index.js 中的既有路由严格一致,否则前端改造时会出现菜单点不动。
  2. 采购员默认可见范围由 AdminAPIs 中 Purchaser=true 的 API 所属模块推导。
  3. 硬约束:AI 匹配设置模块默认且始终不对采购员可见,不纳入「可后期配置」范围——该模块存有明文 API Key(#62 例外条款),不得通过界面勾选开放。实现上应在绑定逻辑中显式排除,而非仅依赖默认值。
  4. 扩展现有迁移(或新增一条迁移),按第二节的三种语义分别处理 sys_menu、sys_api、casbin_rule 与角色菜单绑定。
  5. 迁移必须幂等,且只处理 GoAuto 自身的菜单与角色绑定,不得删除或改动用户自建菜单、其他角色的绑定。
  6. 迁移在空库与既有库两种前提下均可执行;既有库上不得因重复执行而产生重复菜单。

安全边界

  • API 权限矩阵与全量重建策略保持不变,安全强度不降低。
  • AI 匹配设置对采购员的不可见性为硬约束,不可配置。
  • 迁移不触碰用户自建菜单与其他角色。
  • 明文 API Key 不得出现在菜单、日志、工单与 Wiki 中。
  • 数据库迁移属高风险改动,实施前需再次人工确认。

验收标准

  • 角色编辑页的菜单树中可看到并勾选全部 GoAuto 模块。
  • 采购员角色默认绑定的菜单与 AdminAPIs 中 Purchaser=true 的模块一致。
  • AI 匹配设置不在采购员默认绑定中,且通过界面无法为采购员勾选(或勾选后被拒绝/不生效,实现方式在工单说明)。
  • 在界面上取消采购员某个菜单的勾选后,重新执行迁移不会将其勾回。
  • 在代码中新增一个模块并执行迁移,该模块自动出现在采购员的菜单绑定中。
  • 代码中减少某 API 的采购员权限并执行迁移,casbin_rule 中对应策略被移除(全量重建行为未被破坏)。
  • 菜单的 path 与 web/src/router/index.js 中既有路由逐条一致。
  • 迁移幂等:连续执行两次不产生重复菜单、不改动用户自建菜单与其他角色。
  • 既有采购员 API 权限行为无任何变化。

验证方式

  • go test ./app/goauto/access/... ./cmd/migrate/...
  • 迁移在空库与既有库两种前提下各执行一次,并连续执行两次验证幂等。
  • 手工验证:取消勾选 → 重跑迁移 → 确认未被勾回;新增模块 → 重跑迁移 → 确认自动出现。
  • 菜单 path 与前端路由的一致性用测试或脚本逐条比对,不靠人工核对。
  • 未覆盖的数据库类型与部署环境如实回写。

依赖、并行与风险

  • 无前置依赖;是前端菜单渲染改造工单的前置。
  • 风险:菜单 path 与前端路由不一致会导致菜单点不动或白屏。缓解:第 1 项要求严格一致,验收含逐条比对。
  • 风险:「只增不删」意味着代码中减少默认可见模块对已有角色不生效。这是已确认的取舍,需写入文档,避免误以为改代码即可收回菜单。
  • 回退:还原提交并保留已写入的菜单记录即可;API 权限不受影响。

文档影响

  • Wiki Architecture-and-Code-Map:模块定义作为菜单与权限的单一事实源,及迁移的三种语义。
  • Wiki Business-Rules-and-Glossary:采购员与管理员的可见范围边界,AI 匹配设置的硬约束,以及「菜单可后期配置、API 权限由代码维护」的分层原则。
  • 按 Wiki-first 门禁:先改线上页面并回读 revision,再执行一轮 sync 与一轮 sync --check,把页面与 revision 写回本工单。

状态

待实施(数据库迁移需实施前再次人工确认)。

## 所属与来源 - 关联工单:#62 AI 匹配设置(明文 API Key 例外)、#127 采购规则落库(新增模块时同样适用本机制)。 - 来源:用户于 2026-08-28 反馈「Admin 采集采购相关模块没有加入系统菜单管理,角色编辑时不能选择菜单,需要把后面加的模块分成采购员用的和管理员看到全部的」;随后确认采购员可见模块**需要支持后期在界面配置**。 - 类型:Server / 系统菜单入库与角色菜单绑定。 - 设计证据:本工单不涉及前端界面改动,无需原型。前端渲染改造由后续工单实施。 - 工具回退说明:本工单通过 Gitea API 创建;当前会话未提供 Gitea MCP 工具,按 `AGENTS.md`「Gitea 交互与工单最小读取」记录回退原因。 ## 当前事实(提交 30d8238 复核) 1. **API 权限已实现**:`server/app/goauto/access/purchaser.go` 是一份审阅过的权限矩阵(`AdminAPIs` 与 `PurchaserAPIs()`);迁移 `server/cmd/migrate/migration/version-local/1786701700000_purchaser_role.go` 会创建「采购员」角色、把全部 GoAuto API 写入 `sys_api`,并把采购员可用的写入 `casbin_rule`。该迁移对采购员的 casbin 策略执行**全量重建**(先按 `ptype='p' AND v0=purchaser` 删除再重建),因此代码中减权可自动生效。 2. **菜单从未入库**:`sys_menu` 中没有任何 GoAuto 模块记录,因此角色编辑页的菜单树里选不到这些模块。 3. **前端菜单是静态且不受权限控制**:GoAuto 全部模块位于 `web/src/router/index.js` 的 `constantRoutes`(第 38~245 行,`asyncRoutes` 自 246 行起)。`constantRoutes` 对所有登录用户可见、不做角色过滤、也不读后端菜单。 4. 由此产生的实际状况:**采购员当前能看到全部 GoAuto 菜单**(含 AI 匹配设置、SYB 店铺管理等管理员专属模块),只是点击后部分 API 被 casbin 拒绝。属体验割裂与信息暴露,但不构成越权。 5. `sys_menu` 与 `sys_api` 之间通过 `sys_menu_api_rule` 多对多关联(`app/admin/models/sys_menu.go:22`)。 ## 目标 1. GoAuto 各模块写入 `sys_menu`,使角色编辑页可以勾选。 2. 采购员的菜单可见范围**支持后期在界面配置**,人工调整不被后续迁移覆盖。 3. 菜单可见性与 API 权限保持**单一事实源**,避免出现「菜单可见但接口被拒」或「菜单不可见但接口可调」的不一致。 4. API 权限的安全强度不降低。 ## 非目标 - 不修改前端路由与菜单渲染(后续工单)。 - 不放开 API 权限的界面配置:`casbin_rule` 仍由代码矩阵维护并全量重建。 - 不改变既有 `AdminAPIs` 矩阵中任何一条的 `Purchaser` 取值。 - 不改动用户自建的菜单与角色。 - 不涉及采集、采购业务逻辑与 Agent。 ## 关键设计决定 ### 一、菜单可配、API 权限不可配(分层) 两者性质不同: - **菜单可见性**属体验与信息暴露。勾多了,采购员只是看到一个点进去被拒的菜单,**不会越权**——API 层兜底。 - **API 权限**是安全边界。若交由界面勾选,一次误操作即可能把「保存 AI 匹配设置」这类管理员专属接口开给采购员,而其中存有明文 API Key(#62 例外)。 因此:**安全由 API 层兜底,可见性放开配置。** ### 二、迁移的三种语义必须区分 | 对象 | 迁移行为 | 后果 | |---|---|---| | `casbin_rule`(API 权限) | **全量重建**(现状不变) | 代码减权自动生效;人工改动被还原 | | `sys_menu` 记录本身 | 有则更新、无则创建 | 菜单标题/路径/图标跟随代码 | | 采购员的**菜单绑定** | **只增不删** | 新增模块默认对采购员可见;界面上取消的勾选**永久生效,不被迁移勾回** | 「只增不删」同时满足两个需求:新增模块无需每次手工勾;人工取消的勾选不被覆盖。代价是批量减少菜单只能人工操作,代码改默认值对已有角色不生效——对内部系统可接受,且安全线在 API 层不受影响。 ### 三、单一事实源 在 `access` 包新增模块级定义,从现有 `AdminAPIs` 矩阵派生,每个模块包含:菜单标题、前端路径、图标、排序、所含 API 列表、采购员默认可见与否。 迁移据此同时生成 `sys_menu`、`sys_api`、`casbin_rule` 与 `sys_menu_api_rule`。「采购员能看什么」只有一处定义。 ## 实施方案 1. 在 `server/app/goauto/access` 新增模块定义,覆盖现有全部 GoAuto 前端模块:设备、PDD 商品、虾皮商品、SYB 商品、SYB 店铺、SYB 同步记录、档口入库码、采集规则、采集任务、采购任务、AI 匹配设置。路径与 `web/src/router/index.js` 中的既有路由**严格一致**,否则前端改造时会出现菜单点不动。 2. 采购员默认可见范围由 `AdminAPIs` 中 `Purchaser=true` 的 API 所属模块推导。 3. **硬约束**:AI 匹配设置模块**默认且始终不对采购员可见**,不纳入「可后期配置」范围——该模块存有明文 API Key(#62 例外条款),不得通过界面勾选开放。实现上应在绑定逻辑中显式排除,而非仅依赖默认值。 4. 扩展现有迁移(或新增一条迁移),按第二节的三种语义分别处理 `sys_menu`、`sys_api`、`casbin_rule` 与角色菜单绑定。 5. 迁移必须幂等,且**只处理 GoAuto 自身的菜单与角色绑定**,不得删除或改动用户自建菜单、其他角色的绑定。 6. 迁移在空库与既有库两种前提下均可执行;既有库上不得因重复执行而产生重复菜单。 ## 安全边界 - API 权限矩阵与全量重建策略保持不变,安全强度不降低。 - AI 匹配设置对采购员的不可见性为硬约束,不可配置。 - 迁移不触碰用户自建菜单与其他角色。 - 明文 API Key 不得出现在菜单、日志、工单与 Wiki 中。 - 数据库迁移属高风险改动,实施前需再次人工确认。 ## 验收标准 - [ ] 角色编辑页的菜单树中可看到并勾选全部 GoAuto 模块。 - [ ] 采购员角色默认绑定的菜单与 `AdminAPIs` 中 `Purchaser=true` 的模块一致。 - [ ] AI 匹配设置不在采购员默认绑定中,且通过界面**无法**为采购员勾选(或勾选后被拒绝/不生效,实现方式在工单说明)。 - [ ] 在界面上取消采购员某个菜单的勾选后,**重新执行迁移不会将其勾回**。 - [ ] 在代码中新增一个模块并执行迁移,该模块**自动出现在**采购员的菜单绑定中。 - [ ] 代码中减少某 API 的采购员权限并执行迁移,`casbin_rule` 中对应策略被移除(全量重建行为未被破坏)。 - [ ] 菜单的 `path` 与 `web/src/router/index.js` 中既有路由逐条一致。 - [ ] 迁移幂等:连续执行两次不产生重复菜单、不改动用户自建菜单与其他角色。 - [ ] 既有采购员 API 权限行为无任何变化。 ## 验证方式 - `go test ./app/goauto/access/... ./cmd/migrate/...` - 迁移在空库与既有库两种前提下各执行一次,并连续执行两次验证幂等。 - 手工验证:取消勾选 → 重跑迁移 → 确认未被勾回;新增模块 → 重跑迁移 → 确认自动出现。 - 菜单 `path` 与前端路由的一致性用测试或脚本逐条比对,不靠人工核对。 - 未覆盖的数据库类型与部署环境如实回写。 ## 依赖、并行与风险 - 无前置依赖;是前端菜单渲染改造工单的前置。 - 风险:菜单 `path` 与前端路由不一致会导致菜单点不动或白屏。缓解:第 1 项要求严格一致,验收含逐条比对。 - 风险:「只增不删」意味着代码中减少默认可见模块对已有角色不生效。这是已确认的取舍,需写入文档,避免误以为改代码即可收回菜单。 - 回退:还原提交并保留已写入的菜单记录即可;API 权限不受影响。 ## 文档影响 - Wiki `Architecture-and-Code-Map`:模块定义作为菜单与权限的单一事实源,及迁移的三种语义。 - Wiki `Business-Rules-and-Glossary`:采购员与管理员的可见范围边界,AI 匹配设置的硬约束,以及「菜单可后期配置、API 权限由代码维护」的分层原则。 - 按 Wiki-first 门禁:先改线上页面并回读 revision,再执行一轮 `sync` 与一轮 `sync --check`,把页面与 revision 写回本工单。 ## 状态 待实施(数据库迁移需实施前再次人工确认)。
Author
Owner

实施完成,待验收

已按工单方案完成并推送:72f54f7 feat(goauto): add configurable module menus (#137)。

实现

  • 在 server/app/goauto/access 建立 11 个 GoAuto 模块定义,统一维护标题、路径、组件、图标、排序、模块 API 与采购员默认可见/硬排除标记;增加与当前 Web 静态路由的一致性测试。
  • 新增迁移 1787885400000_goauto_menus.go,幂等维护模块目录/页面菜单、sys_menu_api_rule、采购员 Casbin 固定白名单和采购员菜单绑定。
  • 采购员菜单绑定遵循“只增不覆盖”:首次引入的默认模块自动加入;已存在模块被人工取消后,重复迁移不会重新勾选;AI 规格匹配父/子菜单始终移出采购员绑定。
  • 角色保存时,采购员 API 权限仍从 AdminAPIs/PurchaserAPIs() 固定矩阵全量生成,不能由菜单勾选扩大;AI 菜单选中请求会返回明确错误。工单要求的既有 API 权限值未改变,采购员仍只可读取 AI 是否启用。
  • 测试覆盖空库初始化、既有库、重复执行、人工取消保持、新模块加入、AI 菜单硬排除、旧 Casbin 策略重建、用户自定义菜单不受影响、菜单/API 关联与路由一致性。

验证

  • go test ./...:通过。
  • scripts/verify.ps1 -Component all:通过;Server 测试/构建、Web lint/生产构建、Android 单测/debug APK 均通过。Web lint 为仓库既有 30 warnings、0 errors。
  • python dev_scripts/harness.py check --strict:通过。
  • python dev_scripts/harness.py sync --check:通过。
  • Wiki 已在线更新并回读:Architecture-and-Code-Map@267a75be046b2faedddbad86232e2c78b08ba92c、Business-Rules-and-Glossary@92a67263229d3f98861b2526422d12ce13bb81c1。

边界与未验证项

  • 本会话无 Gitea MCP,按仓库规则回退 Gitea API;凭据仅由 Git Credential Manager 读取,未输出或落库。
  • 仅在 SQLite 测试库执行迁移逻辑;未连接、未写入正式/部署数据库,也未执行发布。
  • 工单保持开启,等待数据库环境迁移与角色页面人工验收。
## 实施完成,待验收 已按工单方案完成并推送:`72f54f7 feat(goauto): add configurable module menus (#137)`。 ### 实现 - 在 `server/app/goauto/access` 建立 11 个 GoAuto 模块定义,统一维护标题、路径、组件、图标、排序、模块 API 与采购员默认可见/硬排除标记;增加与当前 Web 静态路由的一致性测试。 - 新增迁移 `1787885400000_goauto_menus.go`,幂等维护模块目录/页面菜单、`sys_menu_api_rule`、采购员 Casbin 固定白名单和采购员菜单绑定。 - 采购员菜单绑定遵循“只增不覆盖”:首次引入的默认模块自动加入;已存在模块被人工取消后,重复迁移不会重新勾选;AI 规格匹配父/子菜单始终移出采购员绑定。 - 角色保存时,采购员 API 权限仍从 `AdminAPIs/PurchaserAPIs()` 固定矩阵全量生成,不能由菜单勾选扩大;AI 菜单选中请求会返回明确错误。工单要求的既有 API 权限值未改变,采购员仍只可读取 AI 是否启用。 - 测试覆盖空库初始化、既有库、重复执行、人工取消保持、新模块加入、AI 菜单硬排除、旧 Casbin 策略重建、用户自定义菜单不受影响、菜单/API 关联与路由一致性。 ### 验证 - `go test ./...`:通过。 - `scripts/verify.ps1 -Component all`:通过;Server 测试/构建、Web lint/生产构建、Android 单测/debug APK 均通过。Web lint 为仓库既有 30 warnings、0 errors。 - `python dev_scripts/harness.py check --strict`:通过。 - `python dev_scripts/harness.py sync --check`:通过。 - Wiki 已在线更新并回读:`Architecture-and-Code-Map@267a75be046b2faedddbad86232e2c78b08ba92c`、`Business-Rules-and-Glossary@92a67263229d3f98861b2526422d12ce13bb81c1`。 ### 边界与未验证项 - 本会话无 Gitea MCP,按仓库规则回退 Gitea API;凭据仅由 Git Credential Manager 读取,未输出或落库。 - 仅在 SQLite 测试库执行迁移逻辑;**未连接、未写入正式/部署数据库,也未执行发布**。 - 工单保持开启,等待数据库环境迁移与角色页面人工验收。
Author
Owner

本机迁移状态更新

在 #140/#141 获授权的 Admin 重启中,#137 迁移版本 1787885400000 已成功执行:GoAuto 父子菜单 22 条、根菜单排序 50~60;采购员默认绑定 10 个模块,AI 规格匹配菜单保持 0 绑定。Admin 已进入工作台并显示全部 11 个模块。#137 仍等待用户业务验收。

## 本机迁移状态更新 在 #140/#141 获授权的 Admin 重启中,#137 迁移版本 `1787885400000` 已成功执行:GoAuto 父子菜单 22 条、根菜单排序 50~60;采购员默认绑定 10 个模块,AI 规格匹配菜单保持 0 绑定。Admin 已进入工作台并显示全部 11 个模块。#137 仍等待用户业务验收。
Author
Owner

用户于 2026-08-29 明确确认本工单通过验收。验收结论已记录,现关闭工单。没有新的长期事实变化,本次不重复同步 Wiki。

用户于 2026-08-29 明确确认本工单通过验收。验收结论已记录,现关闭工单。没有新的长期事实变化,本次不重复同步 Wiki。
ila closed this issue 2026-08-29 20:43:56 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#137