role.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. from typing import List
  2. from fastapi import APIRouter, Depends, HTTPException, status
  3. from pydantic import BaseModel, ConfigDict
  4. from pydantic.alias_generators import to_camel
  5. from sqlalchemy import or_
  6. from sqlalchemy.exc import IntegrityError
  7. from sqlalchemy.orm import Session
  8. from app.database import get_db
  9. from app.core.deps import require_roles
  10. from app.models.user import User
  11. from app.models.role import Role
  12. from app.schemas.common import ApiResponse, PagedData
  13. from app.schemas.role import RoleCreate, RoleUpdate, RoleOut
  14. from app.schemas.user import UserOut
  15. from app.services.role import RoleService
  16. class AssignMenusBody(BaseModel):
  17. """与 bx-web / bx-mobile 一致:请求体使用 camelCase(menuIds)。"""
  18. model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
  19. menu_ids: List[int]
  20. class AssignUsersBody(BaseModel):
  21. """与 bx-web / bx-mobile 一致:请求体使用 camelCase(userIds)。"""
  22. model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
  23. user_ids: List[int]
  24. router = APIRouter(prefix="/api/roles", tags=["角色管理"])
  25. def _raise_if_role_code_integrity_error(exc: IntegrityError) -> None:
  26. """将角色编码唯一约束冲突转为 400,避免未处理 IntegrityError 导致 500。"""
  27. orig = getattr(exc, "orig", None)
  28. msg = str(orig) if orig is not None else str(exc)
  29. if "uk_role_code" in msg or "role_code" in msg.lower():
  30. raise HTTPException(
  31. status_code=status.HTTP_400_BAD_REQUEST,
  32. detail="角色编码已存在,请更换其他编码",
  33. ) from exc
  34. raise HTTPException(
  35. status_code=status.HTTP_400_BAD_REQUEST,
  36. detail="保存失败:数据约束冲突",
  37. ) from exc
  38. @router.get("/", response_model=ApiResponse[PagedData[RoleOut]])
  39. def list_roles(
  40. page: int = 1,
  41. pageSize: int = 10,
  42. keyword: str = "",
  43. db: Session = Depends(get_db),
  44. _: User = Depends(require_roles("ADMIN")),
  45. ):
  46. filters = [Role.del_flag == 0]
  47. if keyword:
  48. filters.append(or_(Role.role_name.contains(keyword), Role.role_code.contains(keyword)))
  49. rows, total = RoleService.get_paged(db, page=page, page_size=pageSize, filters=filters)
  50. out_rows = [RoleOut.model_validate(r) for r in rows]
  51. paged_data = PagedData(rows=out_rows, total=total, page=page, page_size=pageSize)
  52. return ApiResponse(data=paged_data)
  53. @router.get("/{role_id}", response_model=ApiResponse[RoleOut])
  54. def get_role(role_id: int, db: Session = Depends(get_db), _: User = Depends(require_roles("ADMIN"))):
  55. role = RoleService.get_by_id(db, role_id)
  56. if not role:
  57. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="角色不存在")
  58. return ApiResponse(data=RoleOut.model_validate(role))
  59. @router.post("/", response_model=ApiResponse[RoleOut])
  60. def create_role(body: RoleCreate, db: Session = Depends(get_db), _: User = Depends(require_roles("ADMIN"))):
  61. data = body.model_dump(exclude_unset=True)
  62. code = (data.get("role_code") or "").strip()
  63. if code:
  64. data["role_code"] = code
  65. taken = db.query(Role).filter(Role.role_code == code, Role.del_flag == 0).first()
  66. if taken:
  67. raise HTTPException(
  68. status_code=status.HTTP_400_BAD_REQUEST,
  69. detail="角色编码已存在,请更换其他编码",
  70. )
  71. try:
  72. role = RoleService.create(db, data)
  73. except IntegrityError as e:
  74. _raise_if_role_code_integrity_error(e)
  75. return ApiResponse(data=RoleOut.model_validate(role))
  76. @router.put("/{role_id}", response_model=ApiResponse[RoleOut])
  77. def update_role(role_id: int, body: RoleUpdate, db: Session = Depends(get_db), _: User = Depends(require_roles("ADMIN"))):
  78. data = body.model_dump(exclude_unset=True)
  79. if "role_code" in data and data["role_code"] is not None:
  80. code = str(data["role_code"]).strip()
  81. data["role_code"] = code
  82. if code:
  83. taken = (
  84. db.query(Role)
  85. .filter(Role.role_code == code, Role.del_flag == 0, Role.id != role_id)
  86. .first()
  87. )
  88. if taken:
  89. raise HTTPException(
  90. status_code=status.HTTP_400_BAD_REQUEST,
  91. detail="角色编码已存在,请更换其他编码",
  92. )
  93. try:
  94. role = RoleService.update(db, role_id, data)
  95. except IntegrityError as e:
  96. _raise_if_role_code_integrity_error(e)
  97. if not role:
  98. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="角色不存在")
  99. return ApiResponse(data=RoleOut.model_validate(role))
  100. @router.delete("/{role_id}", response_model=ApiResponse[None])
  101. def delete_role(role_id: int, db: Session = Depends(get_db), _: User = Depends(require_roles("ADMIN"))):
  102. RoleService.soft_delete(db, role_id)
  103. return ApiResponse(data=None)
  104. @router.put("/{role_id}/menus", response_model=ApiResponse[RoleOut])
  105. def assign_menus(role_id: int, body: AssignMenusBody, db: Session = Depends(get_db), _: User = Depends(require_roles("ADMIN"))):
  106. role = RoleService.assign_menus(db, role_id, body.menu_ids)
  107. if not role:
  108. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="角色不存在")
  109. return ApiResponse(data=RoleOut.model_validate(role))
  110. @router.get("/{role_id}/users", response_model=ApiResponse[List[UserOut]])
  111. def get_role_users(role_id: int, db: Session = Depends(get_db), _: User = Depends(require_roles("ADMIN"))):
  112. users = RoleService.get_users_by_role(db, role_id)
  113. return ApiResponse(data=[UserOut.model_validate(u) for u in users])
  114. @router.put("/{role_id}/users", response_model=ApiResponse[RoleOut])
  115. def assign_users(role_id: int, body: AssignUsersBody, db: Session = Depends(get_db), _: User = Depends(require_roles("ADMIN"))):
  116. role = RoleService.assign_users(db, role_id, body.user_ids)
  117. if not role:
  118. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="角色不存在")
  119. return ApiResponse(data=RoleOut.model_validate(role))