角色權限管理(Role Management)
功能群:系統管理|三層 RBAC 模型與可見性規則先讀 功能群總覽 §2
事實基準:2026-07-05 從 FE
src/views/role-manage/+ BEapi/auth/routes/role_route.py+ jedi-authrole_service/role_domain_service+ routes.json / db_schema.json 掃出。變更紀錄:2026-07-09 FR-047 已知坑修復 —— 角色能力變更對「當前 session 本人」即時生效(六項#1,重拉
/user/web-menu);編輯改名補重複檢查 + 撞名改ConflictError(409)(R5,jedi-auth);ui_routes.name加 UNIQUE(SCHEMA,DEV+STG);退役notification.testcapability → 權限矩陣「TEST」空欄消失(②-bis)
1. 功能描述
角色權限管理是全系統權限治理的樞紐頁:管理員在此建立
/
維護「角色」,並透過權限矩陣把角色綁定到能力點(capabilities),能力點再經
route_capabilities
決定使用者登入後看得到哪些選單、頁內哪些按鈕可用(機制見 _overview
§2)。頁面分列表頁(role-manage)與表單頁(role-form,建立
/ 編輯共用)兩條 route。
角色分兩種:系統預設角色(tenant_id IS NULL、is_admin=1,如
Administrator,全租戶共用)與租戶自訂角色(屬某租戶)。權限矩陣以「功能樹(ui_routes)×
動作欄(capabilities 的
action)」呈現,勾選結果就是該角色擁有的 capability id 集合。
角色速覽
| 角色 | 能做什麼 |
|---|---|
| 管理員(可見本選單者) | 角色 CRUD + 權限矩陣設定 |
| root 租戶管理員 | 額外可編輯系統角色、配置平台層(is_platform)能力 |
| 一般使用者 | 無此選單(ui_routes 可見性未命中) |
1.1 功能總覽
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 角色列表 | 分頁表列所有角色(名稱 / 租戶 / 狀態 / 使用人數 / 建立更新軌跡),可多欄排序 | 列表頁 DataTable | §6.1 |
| 2 | 搜尋角色 | 依名稱關鍵字搜尋(Enter 觸發) | 列表頁工具列 | — |
| 3 | 重設搜尋 | 清空搜尋條件 | 列表頁工具列 | — |
| 4 | 建立角色 | 導向表單頁(新增模式) | 列表頁工具列 | UC-ROLE-01 |
| 5 | 編輯角色 | 導向表單頁(編輯模式),可點列名稱或「編輯」鈕 | 列表頁每列 | UC-ROLE-01 |
| 6 | 停用 / 啟用角色 | 切換
enable;系統角色(is_admin=1)隱藏此鈕 |
列表頁每列 | §6 |
| 7 | 刪除角色 | 軟刪除;系統角色隱藏此鈕 | 列表頁每列 | §6 |
| 8 | 角色基本欄位 | 名稱*(≤50)/ 租戶(下拉)/ 描述(≤250)/ 狀態 | 表單頁上半 | §6.2 |
| 9 | 權限矩陣 | TreeTable:功能樹 × 動作欄,cell / 列 tri-state 勾選 | 表單頁下半 | UC-ROLE-02 |
| 10 | 矩陣展開 / 收合 | 一鍵展開或收合功能樹全部節點 | 矩陣工具列 | — |
| 11 | 平台功能列隱藏 | 編輯非 root 租戶角色時,濾除 is_platform /
平台專屬功能列 |
表單頁矩陣 | §4 |
| 12 | 儲存角色 | 送出基本欄位 + 勾選的 capability id 陣列(全量取代) | 表單頁 | UC-ROLE-02 |
展開成 UC:UC-ROLE-01(建立 / 編輯角色主流程)、UC-ROLE-02(權限矩陣設定與儲存)。其餘為列表 CRUD 常規操作。
2. Use Case
角色速覽
| 角色 | 能做什麼 |
|---|---|
| 管理員(可見本選單者) | 角色 CRUD + 權限矩陣設定 |
| root 租戶管理員 | 額外可編輯系統角色、配置平台層(is_platform)能力 |
| 一般使用者 | 無此選單(ui_routes 可見性未命中) |
視覺慣例:判斷框(黃菱形)= 分支條件;橙框 = 擋下 / 隱藏;綠橢圓 = 成功終點。
UC-ROLE-01 建立 / 編輯角色
| 項目 | 內容 |
|---|---|
| 角色 | 管理員 |
| 前置條件 | 具本頁可見性(role 相關能力);編輯系統角色需 root
租戶角色 |
| 產出後置 | roles 新增 / 更新一列 + role_capabilities
全量改寫 |
UC-ROLE-02 權限矩陣設定與儲存
見下圖:表單載入時併行取動作欄(/capabilities/menu)與功能樹(/ui-routes),編輯模式再取現有角色能力回填
tri-state;儲存時送出全部勾選的 capability
id(非差異)。
3. 權限矩陣(操作 × FE 判定 × BE 強制)
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 進入本頁 | ui_routes web-menu 可見性(role
能力) |
無端點層強制(僅 @jwt_required) |
api/auth/routes/role_route.py:24,36,55 |
| 建立角色 | 有建立按鈕即可 | ⚠️ 僅 @jwt_required,無角色 /
能力守門 |
role_route.py:65-71 |
| 編輯角色 | 同上 | ⚠️ 僅 @jwt_required |
role_route.py:78-84 |
| 刪除角色 | FE 對 is_admin=1 隱藏刪除鈕 |
⚠️ 僅 @jwt_required,BE
未擋系統角色 |
role_route.py:90-93 |
| 停用 / 啟用 | FE 對 is_admin=1 隱藏此鈕 |
⚠️ 僅 @jwt_required,BE
未擋系統角色 |
role_route.py:101 |
| 配置平台能力 | FE 對非 root 角色隱藏 is_platform 功能列 |
⚠️ 前端過濾,BE 端點未再驗 | RoleForm.vue stripPlatformRoutes();BE
無對應 guard |
⚠️ 系統角色與平台能力的保護只在 FE:BE role 端點全部僅
@jwt_required(),jedi-authrole_service.add_role/update_role/delete_role也無角色 /is_admin/ 平台守門。直接打 API 可繞過 FE 隱藏、編輯或刪除系統角色、把平台能力配給一般租戶角色。收進 §12。與 _overview §5 一致(本群多頁共通模式)。
4. 狀態機與前置條件
角色本身狀態單純(enable 啟用 / 停用 +
is_delete 軟刪除旗標),無複雜狀態機。本頁 gate:
| 條件 | 效果 | 出處 |
|---|---|---|
| 角色名稱在同租戶內重複 | 儲存失敗 AUTH_ROLE_NAME_DUPLICATE(AUTH_409004) |
jedi-auth
role_domain_service.py:144-154(verify_role_name_duplicate) |
is_admin=1(系統角色) |
FE 隱藏停/啟用 + 刪除鈕(僅前端) | RoleManage.vue:198(v-if="data.is_admin !== 1") |
| 編輯角色非 root 租戶 | 權限矩陣濾除 is_platform / 平台專屬功能列 |
RoleForm.vue isPlatformRoute() /
stripPlatformRoutes() |
| 儲存 | role_capabilities
全量取代(非差異更新) |
jedi-auth
role_service.update_role(capability_ids
整包傳 domain) |
授權邊界(角色套用到哪個租戶 / 部門)不在本頁設定,而在使用者管理指派角色時透過
user_roles.scope/tenant_id/org_unit_id決定(見 _overview §2)。
5. UI 設計
📸 實機截圖待補(亮色模式,另有專責 session)。目前以版面骨架圖示意。待補清單:列表頁(含系統角色列隱藏操作鈕)、表單頁權限矩陣 TreeTable(tri-state cell)、非 root 角色隱藏平台列狀態。
版面骨架(列表頁 DataTable + 表單頁基本欄位/權限矩陣 TreeTable,可點跳轉 UC):
狀態呈現:矩陣 cell 三態 —— ☑
全選(該功能該動作能力已給)/ ◪ 部分(列層級:子功能部分勾選)/ ☐
未選;–
表該功能無此動作能力(route_capabilities 沒這條)。
6. API 規格
6.1 Endpoint 總清單
| 分類 | Method + Path | 說明 | 詳述 |
|---|---|---|---|
| 列表 | POST /api/1.0/roles |
分頁查詢角色(body 帶 pager / sort / filters) | §6.2 |
| 選單 | GET /api/1.0/roles/menu |
角色下拉(uid / name / tenant_id / description) | Swagger |
| 明細 | GET /api/1.0/role/<uid> |
單一角色(含 capabilities / tenant / users) | §6.2 |
| 建立 | POST /api/1.0/role |
建立角色 + 綁能力 | §6.2 |
| 更新 | PUT /api/1.0/role/<uid>(亦有
POST /api/1.0/role/<uid>) |
更新角色 + 全量改寫能力 | §6.2 |
| 刪除 | DELETE /api/1.0/role/<uid> |
軟刪除角色 | Swagger |
| 狀態 | PUT /api/1.0/role/status/<uid> |
切換啟用 / 停用(body status) |
Swagger |
| 動作欄 | GET /api/1.0/capabilities/menu |
權限矩陣的動作欄定義 | Swagger |
| 功能樹 | GET /api/1.0/ui-routes |
權限矩陣的功能列(含 route_capabilities /
is_platform) |
§6.3 |
endpoint 存在性對
routes.json核實。列表用POST /roles(body 分頁),非GET(envelope 分頁慣例,見 CLAUDE.md api-patterns)。
6.2 角色建立 / 更新(request / response)
Request(RoleRequest,api/auth/serializers/role.py:4-11):
| 欄位 | 型別 | 說明 |
|---|---|---|
name |
string(必填) | 角色名稱 |
description |
raw(必填) | 描述 |
enable |
int | 啟用 1 / 停用 0 |
is_admin |
int | 系統角色旗標(預設 0) |
tenant_id |
int(allow_none) | 所屬租戶;null = 系統角色 |
capabilities |
list[int] | 勾選的 capability id 陣列(全量取代) |
Response(RoleResponse,role.py:26-52):
| 欄位 | 型別 | 說明 |
|---|---|---|
id |
int | 角色主鍵 |
uid |
string | 角色 UID |
pid |
int | 父角色 id |
name |
string | 角色名稱 |
description |
raw | 描述 |
enable |
int | 啟用 1 / 停用 0 |
is_admin |
int | 系統角色旗標 |
is_delete |
int | 軟刪旗標 |
tenant_id |
int | 所屬租戶 id |
tenant_uid |
string | 所屬租戶 UID |
tenant |
object | Nested 租戶資訊 |
capabilities |
array | Nested CapabilityResponse(角色能力集合) |
users |
array | 使用此角色的使用者 |
user_count |
int | 使用人數 |
created_user |
string | 建立者(含 created_user_name) |
created_at |
date | 建立時間 |
updated_user |
string | 更新者(含 updated_user_name) |
updated_at |
date | 更新時間 |
BE
鏈路:RoleRoute.post/put(role_route.py:65-86)→
jedi-auth RoleService.add_role /
update_role(role_service.py:54-83)→
role_domain_service.add_role/update_role(role_entity, capability_ids)。建立時先
verify_role_name_duplicate(name, tenant_id)。capability_ids
整包傳 domain = 全量取代,非差異。
6.3 UI Routes(權限矩陣功能樹)
Response(UiRouteResponse,ui_route.py:6-27):
| 欄位 | 型別 | 說明 |
|---|---|---|
uid |
string | 功能列 UID |
id |
int | 功能列主鍵 |
pid |
int | 父功能列 id |
name |
string | 功能名稱(i18n key) |
url |
string | 路由路徑 |
icon |
string | 圖示 |
enable |
int | 啟用 1 / 停用 0 |
description |
raw | 描述 |
sort |
int | 排序 |
capabilities_count |
int | 能力數 |
is_platform |
bool | 平台專屬功能列旗標 |
route_capabilities |
array | 每項 {capability(Nested), requirement} |
children |
array | 子功能列(遞迴樹) |
矩陣以此樹為列、以 capabilities/menu 為欄,交集看
route_capabilities。
7. 前端檔案地圖
| 檔案 | 角色 |
|---|---|
compliance-manager-fe/src/views/role-manage/RoleManage.vue |
列表頁(DataTable / 搜尋 / 建立 / 每列操作) |
compliance-manager-fe/src/views/role-manage/RoleForm.vue |
表單頁(基本欄位 + 權限矩陣 TreeTable + tri-state + 平台列過濾) |
compliance-manager-fe/src/stores/menuStore.js |
fetchCapabilityMenu() / fetchUiRoutes()
供矩陣 |
compliance-manager-fe/src/stores/userStore.js |
登入後 permissions(web-menu 結果)來源 |
compliance-manager-fe/src/utils/userUtil.js |
把 permissions 樹攤平成 capability Set,供按鈕權限判定 |
compliance-manager-fe/src/config/api/api.js |
ROLES / ROLE /
CAPABILITIES_MENU / UI_ROUTES /
WEB_MENU 端點常數 |
8. 後端檔案地圖
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| 角色列表(enrich 暱稱) | api/auth/routes/role_route.py(RolesRoute) |
app/auth/service/role_app_service.py(get_roles_and_pager) |
jedi-auth role_service |
| 角色 CRUD / 狀態 | role_route.py(RoleRoute / RoleStatusRoute) |
直接委派(無主專案 app service 包裝) | jedi-auth role_service.py →
role_domain_service.py → repo |
| 功能樹 / 動作欄 | api/auth/routes/ui_route_route.py、capability_route.py |
— | jedi-auth |
DDD 提醒:角色 CRUD 的 route 直接注入 jedi-auth
RoleService、無主專案 app service 層(列表頁才有RoleAppService做暱稱 enrich)。這是外部套件擁有 domain 的情形,大半 RBAC 邏輯在 jedi-auth 套件(~/Projects/Jedicogy/module/jedi-python-package/jedi-auth/),改行為要分清邊界(CLAUDE.md 外部套件異動規範)。
9. DB
9.1 資料表總清單
| 表 | 讀寫 | 說明 | 詳述 |
|---|---|---|---|
public.roles |
讀 / 寫 | 角色本體 | §9.3 |
public.role_capabilities |
讀 / 寫(全量取代) | 角色 ↔︎ 能力關聯 | §9.3 |
public.capabilities |
讀 | 能力點(動作欄來源) | _overview §6 |
public.ui_routes |
讀 | 功能樹(矩陣列) | _overview §6 |
public.route_capabilities |
讀 | 路由 ↔︎ 能力(矩陣交集 + ALL/ANY) | _overview §6 |
public.tenants |
讀 | 租戶下拉 | _overview §6 |
public.user_roles |
讀(user_count) |
統計角色使用人數 | _overview §6 |
9.2 ER 圖
角色相關表關係見 _overview §2 RBAC
模型圖 與 §6
核心資料模型圖(本頁寫入集中在 roles +
role_capabilities)。
9.3 核心表欄位卡
public.roles(來源
db_schema.json):id / uid / pid(父角色) / name / description / enable(1啟用 0停用) / is_admin(1系統角色) / is_delete(1軟刪) / tenant_id(NULL=系統預設) / created_user / created_at / updated_user / updated_at。
public.role_capabilities:複合 PK
(role_id, capability_id),無其他欄位;儲存角色時先刪後插
= 全量取代該角色的 capability 集合。
10. 頁面邏輯與資料對應
表單載入時序:
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| 名稱 / 描述 / 狀態 / 租戶 | currRole.name/description/enable/tenant_id |
RoleRequest 同名欄位 |
| 矩陣勾選 cell | 勾選集合 → capabilities: number[] |
RoleRequest.capabilities(id 陣列) |
| 功能列 tri-state | 由該列子 capability 勾選數計算(all / some / none) | 前端計算,非 API 欄位 |
| 平台列顯隱 | isEditingRootTenantRole +
isPlatformRoute() |
讀 UiRouteResponse.is_platform |
儲存流程:勾選集合序列化成 capabilities
id 陣列 → POST /role(新增)或
PUT /role/<uid>(編輯)→ BE 全量改寫
role_capabilities → 回列表。
錯誤對應:名稱重複 →
AUTH_ROLE_NAME_DUPLICATE(AUTH_409004;⚠️ 實際 raise 的是
NotFound→HTTP 404,與 409 語意不符,見 §12);角色不存在 →
AUTH_ROLE_NOT_FOUND(AUTH_404003)。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| 通知 | 無 |
| Job / 排程 | 無 |
| Socket | 無(角色變更不即時推播;改動當前使用者本人的角色時
FE 會重拉 /user/web-menu
即時反映,改動他人在對方另一 session 仍需重登 / 重取
web-menu 才反映) |
| jedi-* 套件 | jedi-auth 擁有 role / capability / ui_route 的 service / domain / repo / ORM;主專案只有 route + 列表 enrich app service |
| 系統參數 | 無 |
權限變更生效時機(2026-07-09 部分即時化,六項#1):改動當前 session 本人持有的角色能力後,
RoleForm存檔即重拉/user/web-menu更新userStore.permissions,側邊選單(AppMenu已改 computed)與hasCap即時反映、不需重登。改動他人的角色,對方在另一 session 仍需重登 / 重取 web-menu(即時需 socket,依「別過度」未做)。此為 §12 坑 4。
12. 邊界情況與已知坑
- 系統角色 / 平台能力保護只在 FE:BE role 端點全僅
@jwt_required()、jedi-auth service 無is_admin/ 平台守門;直接打 API 可編輯 / 刪除系統角色、把is_platform能力配給一般租戶角色。本 spec 只記錄不修。 role_capabilities全量取代:儲存送整包 capability id 陣列,BE 先刪後插;漏帶已有能力 = 該能力被移除。FE 務必回填完整現有集合再送。- 編輯改名的重複檢查與錯誤型別已修(2026-07-08 FR-047
R5,jedi-auth):原本
verify_role_name_duplicate只在新增路徑呼叫、update_role內對應檢查被註解掉(改既有角色名稱撞他人不被擋);且撞名 raiseNotFound(404) 卻配碼AUTH_409004(型別 / 碼不符)。已在update_role接回 dup-check(帶tenant_id+exclude_uid排除自身,解「跨租戶誤判」與「自己撞自己」兩顆雷)、撞名改ConflictError(409)(碼AUTH_409004不變、FE i18n 不受影響);新增走同一支 verify、一改兩邊都對。 - 權限變更對本人已即時、對他人仍不即時(2026-07-09
六項#1):改動當前 session 本人持有的角色能力後 FE 重拉
web-menu 即時反映;改動他人在對方另一 session 仍需重登 / 重取
/user/web-menu(§11)。 ui_routes.name已加 UNIQUE(2026-07-08 FR-047 SCHEMA,DEV+STG 已套、POC 待部署):功能樹依賴 name 當 i18n key,原無 DB 約束、重名會顯示錯亂;已加uq_ui_routes_name UNIQUE(name)(2026-07-08-add-unique-ui-routes-name.sql),root 已封(_overview §7 坑 3)。- 跨環境 id 漂移:矩陣勾選存的是 capability id,DEV/STG/POC 的 capability id 不一致——搬角色設定不能直接複製 id(_overview §7 坑 4)。
read設 ALL 的陷阱:某功能read未給時該選單完全不可見(即使給了 write 能力),矩陣只勾 write 不勾 read 會讓使用者看不到頁(_overview §2.1)。
13. 開發與驗證
- 跑起來:BE
python main_socketio.py(port 8000);FEcompliance-manager-fenpm dev。 - 測試帳號:管理員帳號見 memory
reference_dev_login/.env(憑證不寫入本文件)。編輯系統角色需 root 租戶角色。 - 導航路徑:登入 →
側選單「系統管理」→「角色權限管理」(
/auth/role-manage)。 - 前置資料:
capabilities/ui_routes/route_capabilities需已由 migration 灌入(新功能納權限走 _overview §2 SOP)。 - 相關 FR / 文件:權限系統設計說明書
docs/system-design/permission/;納權限 SOP memoryreference_permission_system_sop。