sys_login.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # -*- coding: utf-8 -*-
  2. import datetime
  3. from flask import Request, request
  4. from ruoyi_common.constant import Constants
  5. from ruoyi_common.domain.vo import LoginBody
  6. from ruoyi_common.utils import AddressUtil, IpUtil, MessageUtil
  7. from ruoyi_common.utils import security_util as SecurityUtil
  8. from ruoyi_common.domain.entity import LoginUser, SysUser
  9. from ruoyi_common.domain.enum import UserStatus
  10. from ruoyi_common.exception import ServiceException
  11. from ruoyi_common.utils.base import UserAgentUtil
  12. from ruoyi_framework.asyncsched.manager import TaskManager
  13. from ruoyi_framework.asyncsched.task import record_logininfor
  14. from ruoyi_system.domain.entity import SysLogininfor
  15. from ruoyi_system.service import SysConfigService,SysUserService
  16. from ruoyi_system.service.sys_post import SysPostService
  17. from ruoyi_admin.ext import lm,redis_cache
  18. from .token import TokenService
  19. from .sys_permission import SysPermissionService
  20. class LoginService:
  21. @classmethod
  22. def login(cls, login: LoginBody) -> str:
  23. """
  24. 登录
  25. Args:
  26. login (LoginBody): 登录参数
  27. Raises:
  28. ServiceException: 登录失败
  29. ServiceException: 验证码错误
  30. Returns:
  31. str: 登录token
  32. """
  33. captcha_on_off = SysConfigService.select_captcha_on_off()
  34. if captcha_on_off:
  35. cls.validate_captcha(login.username, login.code, login.uuid)
  36. sysuser = SysUserService.select_user_by_user_name(login.username)
  37. if not sysuser:
  38. logininfor = cls.build_logininfor(
  39. login.username,
  40. Constants.LOGIN_FAIL,
  41. MessageUtil.message(code="user.password.not.match")
  42. )
  43. TaskManager.execute(record_logininfor,logininfor)
  44. raise ServiceException(f"登录用户:{login.username} 不存在")
  45. if not SecurityUtil.matches_password(login.password.get_secret_value(), sysuser.password):
  46. logininfor = cls.build_logininfor(
  47. login.username,
  48. Constants.LOGIN_FAIL,
  49. MessageUtil.message(code="user.password.not.match")
  50. )
  51. TaskManager.execute(record_logininfor,logininfor)
  52. raise ServiceException("用户名或密码不匹配")
  53. logininfor = cls.build_logininfor(
  54. login.username,
  55. Constants.LOGIN_SUCCESS,
  56. MessageUtil.message(code="user.login.success")
  57. )
  58. TaskManager.execute(record_logininfor,logininfor)
  59. login_user = cls.load_user(sysuser)
  60. cls.record_sysuser(login_user.user_id)
  61. return TokenService.create_token(login_user)
  62. @classmethod
  63. def load_user(cls, user:SysUser) -> LoginUser:
  64. """
  65. 加载登录用户
  66. Args:
  67. user (SysUser): 用户信息
  68. Raises:
  69. ServiceException: 用户已删除
  70. ServiceException: 用户已停用
  71. Returns:
  72. LoginUser: 登录用户
  73. """
  74. if user.del_flag == UserStatus.DELETED.value:
  75. raise ServiceException(f"对不起,您的账号:{user.user_name} 已删除")
  76. if user.status == UserStatus.DISABLE.value:
  77. raise ServiceException(f"对不起,您的账号:{user.user_name} 已停用")
  78. if not user.role_ids:
  79. dedup_role_ids = []
  80. seen_role_ids = set()
  81. for role in user.roles:
  82. role_id = getattr(role, "role_id", None)
  83. if role_id and role_id not in seen_role_ids:
  84. seen_role_ids.add(role_id)
  85. dedup_role_ids.append(role_id)
  86. user.role_ids = dedup_role_ids
  87. if not user.role_id and user.role_ids:
  88. user.role_id = user.role_ids[0]
  89. if not user.post_ids:
  90. user.post_ids = SysPostService.select_post_list_by_user_id(user.user_id)
  91. login_user = LoginUser(
  92. user_id=user.user_id,
  93. dept_id=user.dept_id,
  94. permissions=SysPermissionService.get_menu_permission(user),
  95. user=user,
  96. )
  97. return login_user
  98. @classmethod
  99. def validate_captcha(cls, username:str, code:str, uuid:str):
  100. """
  101. 验证码校验
  102. Args:
  103. username (str): 用户名
  104. code (str): 验证码
  105. uuid (str): 验证码唯一标识
  106. Raises:
  107. ServiceException: 验证码无效
  108. ServiceException: 验证码过期
  109. """
  110. verify_key = Constants.CAPTCHA_CODE_KEY + str(uuid)
  111. captcha:bytes = redis_cache.get(verify_key)
  112. if not captcha:
  113. logininfor = cls.build_logininfor(
  114. username,
  115. Constants.LOGIN_FAIL,
  116. MessageUtil.message("user.jcaptcha.expire")
  117. )
  118. TaskManager.execute(record_logininfor,logininfor)
  119. raise ServiceException("验证码无效")
  120. redis_cache.delete(verify_key)
  121. if captcha.decode("utf-8").lower() != code.lower():
  122. logininfor = cls.build_logininfor(
  123. username,
  124. Constants.LOGIN_FAIL,
  125. MessageUtil.message("user.jcaptcha.error")
  126. )
  127. TaskManager.execute(record_logininfor,logininfor)
  128. raise ServiceException("验证码错误")
  129. @classmethod
  130. def record_sysuser(cls, id: int):
  131. """
  132. 记录登录信息
  133. Args:
  134. id (int): 用户id
  135. """
  136. sysuser = SysUser(
  137. user_id=id,
  138. login_ip=IpUtil.get_ip(),
  139. login_date=datetime.datetime.now()
  140. )
  141. SysUserService.update_user_login_info(sysuser)
  142. @classmethod
  143. def build_logininfor(cls, username:str,status:str,message:str) -> SysLogininfor:
  144. """
  145. 构建登录信息
  146. Args:
  147. username (str): 用户名
  148. status (str): 登录状态
  149. message (str): 登录信息
  150. Returns:
  151. SysLogininfor: 登录信息
  152. """
  153. ip = IpUtil.get_ip()
  154. address = AddressUtil.get_address(ip)
  155. browser = UserAgentUtil.browser()
  156. os = UserAgentUtil.os()
  157. logininfor = SysLogininfor(
  158. user_name=username,
  159. ipaddr=ip,
  160. login_location=address,
  161. browser=browser,
  162. os=os,
  163. msg=message,
  164. login_time=datetime.datetime.now()
  165. )
  166. if status in [Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER]:
  167. logininfor.status = Constants.SUCCESS
  168. else:
  169. logininfor.status = Constants.FAIL
  170. return logininfor
  171. @classmethod
  172. def logout(cls) -> bool:
  173. '''
  174. 退出登录
  175. Returns:
  176. bool: 是否退出成功
  177. '''
  178. login_user:LoginUser = TokenService.get_login_user(request=request)
  179. if login_user:
  180. TokenService.del_login_user(token=login_user.token.hex)
  181. logininfor = cls.build_logininfor(
  182. login_user.user_name,
  183. Constants.LOGIN_SUCCESS,
  184. "退出成功")
  185. TaskManager.execute(record_logininfor,logininfor)
  186. return True
  187. @lm.unauthorized_handler
  188. def unauthorized_handler() -> tuple[str, int]:
  189. """
  190. 未授权处理
  191. Returns:
  192. tuple[str, int]: 返回401状态码
  193. """
  194. return "Unauthorized", 401
  195. @lm.request_loader
  196. def load_request(request:Request) -> LoginUser:
  197. """
  198. 加载请求
  199. Args:
  200. request (Request): 请求
  201. Returns:
  202. LoginUser: 登录用户
  203. """
  204. login_user = TokenService.get_login_user(request)
  205. return login_user