Files
shanxia2.0/backend/app/crud/role.py
T
2026-05-17 08:36:37 +08:00

288 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""角色 CRUD(sys_role / sys_role_menu / sys_role_data_scope_detail)"""
from __future__ import annotations
import math
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.models import SysRole, SysRoleMenu, SysRoleDataScopeDetail
# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
def _row_to_dict(r: SysRole) -> Dict[str, Any]:
return {
"id": r.id,
"roleName": r.role_name,
"dataScope": r.data_scope or "1",
"status": r.status or "1",
"tenantNo": r.tenant_no or "",
"orderNum": r.order_num if r.order_num is not None else 0,
"remark": r.remark or "",
"createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
"updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
}
def total_pages(total: int, page_size: int) -> int:
if total <= 0:
return 0
return int(math.ceil(total / float(page_size)))
# ---------------------------------------------------------------------------
# list / get
# ---------------------------------------------------------------------------
def list_roles(
db: Session,
*,
page: int = 1,
page_size: int = 10,
keyword: Optional[str] = None,
tenant_no: Optional[str] = None,
) -> Tuple[List[Dict[str, Any]], int]:
def _filtered():
q0 = db.query(SysRole).filter(SysRole.del_flag == 0)
if tenant_no:
q0 = q0.filter(SysRole.tenant_no == tenant_no)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q0 = q0.filter(SysRole.role_name.like(like))
return q0
total = _filtered().order_by(None).with_entities(func.count(SysRole.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
rows = (
_filtered()
.order_by(SysRole.create_time.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return [_row_to_dict(x) for x in rows], int(total)
def get_role(db: Session, role_id: str) -> Optional[SysRole]:
return (
db.query(SysRole)
.filter(and_(SysRole.id == role_id, SysRole.del_flag == 0))
.first()
)
def role_name_exists(db: Session, name: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool:
q = db.query(SysRole).filter(
and_(SysRole.role_name == name.strip(), SysRole.tenant_no == tenant_no, SysRole.del_flag == 0)
)
if exclude_id:
q = q.filter(SysRole.id != exclude_id)
return q.first() is not None
# ---------------------------------------------------------------------------
# create / update / delete
# ---------------------------------------------------------------------------
def create_role(
db: Session,
*,
role_name: str,
data_scope: str = "1",
status: str = "1",
tenant_no: str = "zkxw",
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
) -> SysRole:
rn = (role_name or "").strip()
if not rn:
raise ValueError("角色名称不能为空")
if role_name_exists(db, rn, tenant_no):
raise ValueError("角色名称已存在")
obj = SysRole(
id=generate_uuid(),
role_name=rn,
data_scope=data_scope,
status=status,
tenant_no=tenant_no,
order_num=order_num if order_num is not None else 0,
remark=(remark or "").strip() or None,
create_by=user_id,
create_dept_id="",
create_time=datetime.now(),
update_by=user_id,
update_time=datetime.now(),
del_flag=0,
)
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def update_role(
db: Session,
role_id: str,
*,
role_name: Optional[str] = None,
data_scope: Optional[str] = None,
status: Optional[str] = None,
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
) -> SysRole:
obj = get_role(db, role_id)
if not obj:
raise ValueError("角色不存在")
if role_name is not None:
rn = role_name.strip()
if not rn:
raise ValueError("角色名称不能为空")
if rn != obj.role_name and role_name_exists(db, rn, obj.tenant_no, exclude_id=role_id):
raise ValueError("角色名称已存在")
obj.role_name = rn
if data_scope is not None:
obj.data_scope = data_scope
if status is not None:
obj.status = status
if order_num is not None:
obj.order_num = order_num
if remark is not None:
obj.remark = (remark or "").strip() or None
obj.update_by = user_id
obj.update_time = datetime.now()
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def soft_delete_role(db: Session, role_id: str) -> None:
obj = get_role(db, role_id)
if not obj:
raise ValueError("角色不存在")
obj.del_flag = 1
obj.update_time = datetime.now()
db.add(obj)
# cascade soft-delete role_menu and role_data_scope_detail
db.query(SysRoleMenu).filter(
and_(SysRoleMenu.role_id == role_id, SysRoleMenu.del_flag == 0)
).update({"del_flag": 1, "update_time": datetime.now()})
db.query(SysRoleDataScopeDetail).filter(
and_(SysRoleDataScopeDetail.role_id == role_id, SysRoleDataScopeDetail.del_flag == 0)
).update({"del_flag": 1, "update_time": datetime.now()})
db.commit()
# ---------------------------------------------------------------------------
# role-menu
# ---------------------------------------------------------------------------
def get_role_menu_ids(db: Session, role_id: str) -> List[str]:
rows = (
db.query(SysRoleMenu.menu_id)
.filter(and_(SysRoleMenu.role_id == role_id, SysRoleMenu.del_flag == 0))
.all()
)
return [r.menu_id for r in rows]
def update_role_menus(
db: Session,
role_id: str,
menu_ids: List[str],
tenant_no: str,
user_id: Optional[str] = None,
) -> None:
# uk_role_menu 为 (role_id, menu_id, del_flag):先全量软删再插入会在再次保存时
# 出现两条 (role_id, menu_id, del_flag=1),触发 1062。关联表采用整角色物理清理后重建。
unique_ids = list(dict.fromkeys(menu_ids or []))
db.query(SysRoleMenu).filter(SysRoleMenu.role_id == role_id).delete(synchronize_session=False)
now = datetime.now()
for mid in unique_ids:
obj = SysRoleMenu(
id=generate_uuid(),
role_id=role_id,
menu_id=mid,
tenant_no=tenant_no,
order_num=0,
create_by=user_id,
create_dept_id="",
create_time=now,
update_by=user_id,
update_time=now,
del_flag=0,
)
db.add(obj)
db.commit()
# ---------------------------------------------------------------------------
# role data-scope
# ---------------------------------------------------------------------------
def get_role_data_scope(db: Session, role_id: str) -> List[Dict[str, str]]:
rows = (
db.query(SysRoleDataScopeDetail)
.filter(and_(SysRoleDataScopeDetail.role_id == role_id, SysRoleDataScopeDetail.del_flag == 0))
.all()
)
return [{"scopeType": r.scope_type, "scopeValue": r.scope_value} for r in rows]
def update_role_data_scope(
db: Session,
role_id: str,
data_scope: str,
scope_details: Optional[List[Dict[str, str]]] = None,
tenant_no: str = "zkxw",
user_id: Optional[str] = None,
) -> None:
obj = get_role(db, role_id)
if not obj:
raise ValueError("角色不存在")
obj.data_scope = data_scope
obj.update_by = user_id
obj.update_time = datetime.now()
db.add(obj)
# soft-delete old scope details
db.query(SysRoleDataScopeDetail).filter(
and_(SysRoleDataScopeDetail.role_id == role_id, SysRoleDataScopeDetail.del_flag == 0)
).update({"del_flag": 1, "update_time": datetime.now()})
# insert new scope details (only when custom data_scope == '5')
if data_scope == "5" and scope_details:
now = datetime.now()
for detail in scope_details:
sd = SysRoleDataScopeDetail(
id=generate_uuid(),
role_id=role_id,
scope_type=detail.get("scopeType", "dept"),
scope_value=detail.get("scopeValue", ""),
tenant_no=tenant_no,
order_num=0,
create_by=user_id,
create_dept_id="",
create_time=now,
update_by=user_id,
update_time=now,
del_flag=0,
)
db.add(sd)
db.commit()