issue_user_tokens.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. """为已通过校验的 SysUser 签发与 web 登录一致的 JWT 会话包。"""
  2. from __future__ import annotations
  3. from typing import List, Optional, Tuple
  4. from sqlalchemy.orm import Session
  5. from app.core.auth import (
  6. create_access_token,
  7. create_refresh_token,
  8. store_refresh_token,
  9. store_session,
  10. )
  11. from app.core.config import settings
  12. from app.core.utils import generate_uuid
  13. from app.crud.user import list_post_ids_for_user
  14. from app.models.models import SysDept, SysPost, SysUser
  15. from app.services.web_auth_roles import (
  16. get_user_role_names,
  17. resolve_web_role_for_user,
  18. user_has_active_roles,
  19. )
  20. def _avatar_url_field(user: SysUser) -> Optional[str]:
  21. raw = getattr(user, "avatar", None)
  22. if not raw:
  23. return None
  24. s = str(raw).strip()
  25. return s if s else None
  26. def resolve_user_org_labels(db: Session, user: SysUser) -> Tuple[Optional[str], List[str]]:
  27. """解析用户所属部门名称与职位名称列表(保序)。"""
  28. tenant_no = (getattr(user, "tenant_no", None) or "").strip()
  29. dept_name: Optional[str] = None
  30. dept_id = (getattr(user, "dept_id", None) or "").strip()
  31. if dept_id:
  32. dept = (
  33. db.query(SysDept)
  34. .filter(
  35. SysDept.id == dept_id,
  36. SysDept.tenant_no == tenant_no,
  37. SysDept.del_flag == 0,
  38. )
  39. .first()
  40. )
  41. if dept and (dept.dept_name or "").strip():
  42. dept_name = str(dept.dept_name).strip()
  43. post_names: List[str] = []
  44. post_ids = list_post_ids_for_user(
  45. db, user.id, tenant_no=tenant_no
  46. )
  47. if post_ids:
  48. posts = (
  49. db.query(SysPost)
  50. .filter(
  51. SysPost.id.in_(post_ids),
  52. SysPost.tenant_no == tenant_no,
  53. SysPost.status == "1",
  54. SysPost.del_flag == 0,
  55. )
  56. .all()
  57. )
  58. name_map = {
  59. p.id: str(p.post_name).strip()
  60. for p in posts
  61. if p.id and (p.post_name or "").strip()
  62. }
  63. post_names = [name_map[pid] for pid in post_ids if pid in name_map]
  64. return dept_name, post_names
  65. def build_user_info_payload(
  66. user: SysUser,
  67. web_role: str,
  68. role_names: list = None,
  69. *,
  70. db: Session = None,
  71. ) -> dict:
  72. phone = (getattr(user, "phone_no", None) or "").strip() or None
  73. dept_name: Optional[str] = None
  74. post_names: List[str] = []
  75. if db is not None:
  76. dept_name, post_names = resolve_user_org_labels(db, user)
  77. return {
  78. "id": user.id,
  79. "username": user.user_no,
  80. "realName": user.nick_name,
  81. "phone": phone,
  82. "email": (getattr(user, "email", None) or "").strip() or None,
  83. "hireDate": user.hire_date.strftime("%Y-%m-%d") if getattr(user, "hire_date", None) else None,
  84. "probationMonths": getattr(user, "probation_months", None),
  85. "lastWorkDay": user.last_work_day.strftime("%Y-%m-%d")
  86. if getattr(user, "last_work_day", None)
  87. else None,
  88. "sex": getattr(user, "sex", None) or "0",
  89. "remark": (getattr(user, "remark", None) or "").strip() or None,
  90. "role": web_role,
  91. "roleNames": role_names or [],
  92. "regionId": user.dept_id,
  93. "regionName": dept_name,
  94. "deptName": dept_name,
  95. "postNames": post_names,
  96. "avatarUrl": _avatar_url_field(user),
  97. }
  98. def _jwt_user_payload(user: SysUser, web_role: str) -> dict:
  99. return {
  100. "sub": user.id,
  101. "username": user.user_no,
  102. "role": web_role,
  103. "region_id": user.dept_id,
  104. "real_name": user.nick_name,
  105. }
  106. async def issue_tokens_for_user(db: Session, user: SysUser) -> dict:
  107. """校验停用/角色后签发 token。失败抛 ValueError(中文 message)。"""
  108. if user.status != "1":
  109. raise ValueError("账号已被停用,请联系管理员")
  110. tenant_no = (getattr(user, "tenant_no", None) or "").strip() or None
  111. if not user_has_active_roles(db, user.id, tenant_no=tenant_no):
  112. raise ValueError("账号未分配角色,请联系管理员")
  113. web_role = resolve_web_role_for_user(
  114. db, user.id, tenant_no=tenant_no
  115. )
  116. role_names = get_user_role_names(db, user.id, tenant_no=tenant_no)
  117. session_id = generate_uuid()
  118. access_jti = generate_uuid()
  119. refresh_jti = generate_uuid()
  120. access_token = create_access_token(
  121. data=_jwt_user_payload(user, web_role),
  122. session_id=session_id,
  123. token_jti=access_jti,
  124. )
  125. refresh_token = create_refresh_token(
  126. data={
  127. "sub": user.id,
  128. "username": user.user_no,
  129. "role": web_role,
  130. },
  131. session_id=session_id,
  132. refresh_jti=refresh_jti,
  133. )
  134. await store_session(session_id=session_id, user_id=user.id)
  135. await store_refresh_token(refresh_jti=refresh_jti, user_id=user.id)
  136. return {
  137. "token": access_token,
  138. "refreshToken": refresh_token,
  139. "expiresIn": settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
  140. "userInfo": build_user_info_payload(user, web_role, role_names, db=db),
  141. }