@@ -316,19 +316,49 @@ def update_role(db: Session, role_id: str, payload: RoleUpdateRequest) -> RolePu
|
||||
|
||||
def delete_role(db: Session, role_id: str) -> bool:
|
||||
role_id = role_id.strip()
|
||||
if not role_id or role_id in PROTECTED_ROLE_IDS:
|
||||
return False
|
||||
exists = db.scalar(text("SELECT id FROM user_role WHERE id = :id"), {"id": role_id})
|
||||
if not exists:
|
||||
if not role_id:
|
||||
return False
|
||||
role_source = "legacy" if _legacy_role_table_exists(db) else "modern"
|
||||
resolved_role_id = role_id
|
||||
resolved_role_code = role_id
|
||||
if role_source == "legacy":
|
||||
if role_id in PROTECTED_ROLE_IDS:
|
||||
return False
|
||||
exists = db.scalar(text("SELECT id FROM user_role WHERE id = :id"), {"id": role_id})
|
||||
if not exists:
|
||||
return False
|
||||
else:
|
||||
role_row = db.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id::text AS id, code
|
||||
FROM roles
|
||||
WHERE id::text = :id OR code = :id
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"id": role_id},
|
||||
).mappings().first()
|
||||
if not role_row:
|
||||
return False
|
||||
resolved_role_id = str(role_row["id"])
|
||||
resolved_role_code = str(role_row.get("code") or resolved_role_id).strip() or resolved_role_id
|
||||
if resolved_role_code in PROTECTED_ROLE_IDS:
|
||||
return False
|
||||
|
||||
impacted_user_ids = _get_role_user_ids(db, role_id)
|
||||
impacted_user_ids = _get_role_user_ids(db, resolved_role_id)
|
||||
has_user_role_relation = _legacy_user_role_relation_exists(db)
|
||||
try:
|
||||
db.execute(text("DELETE FROM role_menu_rela WHERE role_id = :role_id"), {"role_id": role_id})
|
||||
if has_user_role_relation:
|
||||
db.execute(text("DELETE FROM user_role_rela WHERE role_id = :role_id"), {"role_id": role_id})
|
||||
db.execute(text("DELETE FROM user_role WHERE id = :id"), {"id": role_id})
|
||||
if role_source == "legacy":
|
||||
db.execute(text("DELETE FROM role_menu_rela WHERE role_id = :role_id"), {"role_id": resolved_role_id})
|
||||
if has_user_role_relation:
|
||||
db.execute(text("DELETE FROM user_role_rela WHERE role_id = :role_id"), {"role_id": resolved_role_id})
|
||||
db.execute(text("DELETE FROM user_role WHERE id = :id"), {"id": resolved_role_id})
|
||||
else:
|
||||
db.execute(text("DELETE FROM role_menus WHERE role_id::text = :role_id"), {"role_id": resolved_role_id})
|
||||
db.execute(text("DELETE FROM role_permissions WHERE role_id::text = :role_id"), {"role_id": resolved_role_id})
|
||||
db.execute(text("DELETE FROM user_roles WHERE role_id::text = :role_id"), {"role_id": resolved_role_id})
|
||||
db.execute(text("DELETE FROM roles WHERE id::text = :id"), {"id": resolved_role_id})
|
||||
db.commit()
|
||||
except SQLAlchemyError:
|
||||
db.rollback()
|
||||
@@ -339,18 +369,18 @@ def delete_role(db: Session, role_id: str) -> bool:
|
||||
publish_topic(
|
||||
"admin.roles",
|
||||
name="roles.changed",
|
||||
payload={"action": "deleted", "role_id": role_id, "role_code": role_id},
|
||||
payload={"action": "deleted", "role_id": resolved_role_id, "role_code": resolved_role_code},
|
||||
requires_refetch=["/api/v1/admin/roles"],
|
||||
dedupe_key=f"roles:deleted:{role_id}",
|
||||
dedupe_key=f"roles:deleted:{resolved_role_id}",
|
||||
)
|
||||
)
|
||||
_fire_and_forget(
|
||||
publish_topic(
|
||||
"admin.menus",
|
||||
name="menus.changed",
|
||||
payload={"action": "role_deleted", "role_id": role_id, "role_code": role_id},
|
||||
payload={"action": "role_deleted", "role_id": resolved_role_id, "role_code": resolved_role_code},
|
||||
requires_refetch=["/api/v1/admin/me/menus"],
|
||||
dedupe_key=f"menus:role_deleted:{role_id}",
|
||||
dedupe_key=f"menus:role_deleted:{resolved_role_id}",
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
## Work Log - 角色管理支持删除(2026-05-07)
|
||||
|
||||
- 背景:
|
||||
- Issue `FL-203` 反馈“角色管理要支持删除”。
|
||||
|
||||
- 根因:
|
||||
- `api/app/services/legacy_admin_rbac_service.py` 的 `delete_role` 只按 legacy 表(`user_role`/`role_menu_rela`)执行删除。
|
||||
- 当环境使用 modern RBAC 表(`roles`/`role_menus`/`role_permissions`/`user_roles`)时,删除接口会判定角色不存在或删除失败。
|
||||
|
||||
- 本次改动(最小闭环):
|
||||
- 文件:`api/app/services/legacy_admin_rbac_service.py`
|
||||
- 调整 `delete_role` 逻辑为与列表/更新一致的双模式处理:
|
||||
- 自动识别 `legacy`/`modern` 角色来源。
|
||||
- `modern` 模式下支持按 `id` 或 `code` 定位角色。
|
||||
- 删除时同步清理 `role_menus`、`role_permissions`、`user_roles` 关联并删除 `roles` 主记录。
|
||||
- 保护角色校验从仅 legacy `id` 扩展为 modern `code` 校验,保持 `admin/user/sys_mgr` 不可删。
|
||||
- 推送事件 `role_id/role_code` 改为使用解析后的真实值,避免前端订阅端收到错误标识。
|
||||
|
||||
- 验证:
|
||||
- 未执行编译与自动化测试(遵循任务约束:不做编译检查,不安装依赖)。
|
||||
- 通过代码链路核对,`DELETE /api/v1/admin/roles/{role_id}` 在 legacy 与 modern 两种表结构下均有完整删除路径。
|
||||
|
||||
- 风险与影响:
|
||||
- 影响面限定在角色删除服务逻辑。
|
||||
- 角色列表、创建、编辑接口行为未改动。
|
||||
Reference in New Issue
Block a user