@@ -316,19 +316,49 @@ def update_role(db: Session, role_id: str, payload: RoleUpdateRequest) -> RolePu
|
|||||||
|
|
||||||
def delete_role(db: Session, role_id: str) -> bool:
|
def delete_role(db: Session, role_id: str) -> bool:
|
||||||
role_id = role_id.strip()
|
role_id = role_id.strip()
|
||||||
if not role_id or role_id in PROTECTED_ROLE_IDS:
|
if not role_id:
|
||||||
return False
|
|
||||||
exists = db.scalar(text("SELECT id FROM user_role WHERE id = :id"), {"id": role_id})
|
|
||||||
if not exists:
|
|
||||||
return False
|
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)
|
has_user_role_relation = _legacy_user_role_relation_exists(db)
|
||||||
try:
|
try:
|
||||||
db.execute(text("DELETE FROM role_menu_rela WHERE role_id = :role_id"), {"role_id": role_id})
|
if role_source == "legacy":
|
||||||
if has_user_role_relation:
|
db.execute(text("DELETE FROM role_menu_rela WHERE role_id = :role_id"), {"role_id": resolved_role_id})
|
||||||
db.execute(text("DELETE FROM user_role_rela WHERE role_id = :role_id"), {"role_id": role_id})
|
if has_user_role_relation:
|
||||||
db.execute(text("DELETE FROM user_role WHERE id = :id"), {"id": role_id})
|
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()
|
db.commit()
|
||||||
except SQLAlchemyError:
|
except SQLAlchemyError:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
@@ -339,18 +369,18 @@ def delete_role(db: Session, role_id: str) -> bool:
|
|||||||
publish_topic(
|
publish_topic(
|
||||||
"admin.roles",
|
"admin.roles",
|
||||||
name="roles.changed",
|
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"],
|
requires_refetch=["/api/v1/admin/roles"],
|
||||||
dedupe_key=f"roles:deleted:{role_id}",
|
dedupe_key=f"roles:deleted:{resolved_role_id}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_fire_and_forget(
|
_fire_and_forget(
|
||||||
publish_topic(
|
publish_topic(
|
||||||
"admin.menus",
|
"admin.menus",
|
||||||
name="menus.changed",
|
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"],
|
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
|
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