controller.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. from typing import Annotated
  2. from fastapi import APIRouter, Depends, Request
  3. from fastapi.responses import JSONResponse
  4. from redis.asyncio.client import Redis
  5. from sqlalchemy.ext.asyncio import AsyncSession
  6. from app.common.response import ErrorResponse, SuccessResponse
  7. from app.config.setting import settings
  8. from app.core.dependencies import db_getter, get_current_user, redis_getter
  9. from app.core.logger import log
  10. from app.core.router_class import OperationLogRoute
  11. from app.core.security import CustomOAuth2PasswordRequestForm
  12. from .schema import (
  13. AutoLoginTokenSchema,
  14. AutoLoginUserSchema,
  15. CaptchaOutSchema,
  16. JWTOutSchema,
  17. LoginMiniRequestSchema,
  18. LogoutPayloadSchema,
  19. RefreshTokenPayloadSchema,
  20. SmsCodeSchema,
  21. SmsLoginRequestSchema,
  22. )
  23. from .service import AutoLoginService, CaptchaService, LoginService, SmsCodeService
  24. AuthRouter = APIRouter(route_class=OperationLogRoute, prefix="/auth", tags=["认证授权"])
  25. @AuthRouter.post(
  26. '/sms-code',
  27. summary="发送短信验证码",
  28. description="发送短信验证码",
  29. )
  30. async def sms_code_controller(
  31. sms_code: SmsCodeSchema,
  32. redis: Annotated[Redis, Depends(redis_getter)],
  33. ) -> JSONResponse:
  34. """
  35. 发送短信验证码
  36. 参数:
  37. - smsCode (SmsCodeSchema): 短信验证码请求模型
  38. - redis (Redis): Redis 客户端对象
  39. - db (AsyncSession): 数据库会话对象
  40. 返回:
  41. - JSONResponse | dict: 包含短信验证码的响应模型
  42. 异常:
  43. - CustomException: 验证码发送失败时抛出异常。
  44. """
  45. await SmsCodeService.send_sms_code_service(
  46. sms_code=sms_code, redis=redis
  47. )
  48. return SuccessResponse(data={}, msg="短信验证码发送成功")
  49. @AuthRouter.post(
  50. "/login/mini",
  51. summary="小程序登录",
  52. description="小程序登录",
  53. response_model=JWTOutSchema,
  54. )
  55. async def login_mini_controller(
  56. request: Request,
  57. redis: Annotated[Redis, Depends(redis_getter)],
  58. login_form: LoginMiniRequestSchema,
  59. db: Annotated[AsyncSession, Depends(db_getter)],
  60. ) -> JSONResponse | dict:
  61. login_token = await LoginService.authenticate_mini_user_service(
  62. request=request, redis=redis, login_form=login_form, db=db
  63. )
  64. return SuccessResponse(data=login_token.model_dump(), msg="登录成功")
  65. @AuthRouter.post(
  66. "/login/sms",
  67. summary="短信登录",
  68. description="使用手机号和验证码登录",
  69. response_model=JWTOutSchema,
  70. )
  71. async def login_sms_controller(
  72. request: Request,
  73. login_form: SmsLoginRequestSchema,
  74. redis: Annotated[Redis, Depends(redis_getter)],
  75. db: Annotated[AsyncSession, Depends(db_getter)],
  76. ) -> JSONResponse:
  77. """
  78. 短信登录
  79. 参数:
  80. - request (Request): FastAPI请求对象
  81. - login_form (SmsLoginRequestSchema): 短信登录表单(手机号和验证码)
  82. - redis (Redis): Redis客户端对象
  83. - db (AsyncSession): 数据库会话对象
  84. 返回:
  85. - JWTOutSchema: 包含访问令牌和刷新令牌的响应模型
  86. 异常:
  87. - CustomException: 认证失败时抛出异常。
  88. """
  89. login_token = await LoginService.authenticate_sms_user_service(
  90. request=request, redis=redis, login_form=login_form, db=db
  91. )
  92. log.info(f"用户{login_form.mobile}短信登录成功")
  93. return SuccessResponse(data=login_token.model_dump(), msg="登录成功")
  94. @AuthRouter.post(
  95. "/login",
  96. summary="登录",
  97. description="登录",
  98. response_model=JWTOutSchema,
  99. )
  100. async def login_for_access_token_controller(
  101. request: Request,
  102. redis: Annotated[Redis, Depends(redis_getter)],
  103. login_form: Annotated[CustomOAuth2PasswordRequestForm, Depends()],
  104. db: Annotated[AsyncSession, Depends(db_getter)],
  105. ) -> JSONResponse | dict:
  106. """
  107. 用户登录
  108. 参数:
  109. - request (Request): FastAPI请求对象
  110. - redis (Redis): Redis 客户端对象
  111. - login_form (CustomOAuth2PasswordRequestForm): 登录表单数据
  112. - db (AsyncSession): 数据库会话对象
  113. 返回:
  114. - JWTOutSchema: 包含访问令牌和刷新令牌的响应模型
  115. 异常:
  116. - CustomException: 认证失败时抛出异常。
  117. """
  118. login_token = await LoginService.authenticate_user_service(
  119. request=request, redis=redis, login_form=login_form, db=db
  120. )
  121. log.info(f"用户{login_form.username}登录成功")
  122. # 如果是文档请求,则不记录日志:http://localhost:8000/api/v1/docs
  123. if settings.DOCS_URL in request.headers.get("referer", ""):
  124. return login_token.model_dump()
  125. return SuccessResponse(data=login_token.model_dump(), msg="登录成功")
  126. @AuthRouter.post(
  127. "/token/refresh",
  128. summary="刷新token",
  129. description="刷新token",
  130. response_model=JWTOutSchema,
  131. dependencies=[Depends(get_current_user)],
  132. )
  133. async def get_new_token_controller(
  134. request: Request,
  135. payload: RefreshTokenPayloadSchema,
  136. db: Annotated[AsyncSession, Depends(db_getter)],
  137. redis: Annotated[Redis, Depends(redis_getter)],
  138. ) -> JSONResponse:
  139. """
  140. 刷新token
  141. 参数:
  142. - request (Request): FastAPI请求对象
  143. - payload (RefreshTokenPayloadSchema): 刷新令牌负载模型
  144. - db (AsyncSession): 数据库会话对象
  145. - redis (Redis): Redis 客户端对象
  146. 返回:
  147. - JWTOutSchema: 包含新的访问令牌和刷新令牌的响应模型
  148. 异常:
  149. - CustomException: 刷新令牌失败时抛出异常。
  150. """
  151. # 解析当前的访问Token以获取用户名
  152. new_token = await LoginService.refresh_token_service(
  153. db=db, request=request, redis=redis, refresh_token=payload
  154. )
  155. token_dict = new_token.model_dump()
  156. log.info(f"刷新token成功: {token_dict}")
  157. return SuccessResponse(data=token_dict, msg="刷新成功")
  158. @AuthRouter.get(
  159. "/captcha/get",
  160. summary="获取验证码",
  161. description="获取登录验证码",
  162. response_model=CaptchaOutSchema,
  163. )
  164. async def get_captcha_for_login_controller(
  165. redis: Annotated[Redis, Depends(redis_getter)],
  166. ) -> JSONResponse:
  167. """
  168. 获取登录验证码
  169. 参数:
  170. - redis (Redis): Redis客户端对象
  171. 返回:
  172. - CaptchaOutSchema: 包含验证码图片和key的响应模型
  173. 异常:
  174. - CustomException: 获取验证码失败时抛出异常。
  175. """
  176. # 获取验证码
  177. captcha = await CaptchaService.get_captcha_service(redis=redis)
  178. log.info("获取验证码成功")
  179. return SuccessResponse(data=captcha, msg="获取验证码成功")
  180. @AuthRouter.post(
  181. "/logout",
  182. summary="退出登录",
  183. description="退出登录",
  184. dependencies=[Depends(get_current_user)],
  185. )
  186. async def logout_controller(
  187. payload: LogoutPayloadSchema,
  188. redis: Annotated[Redis, Depends(redis_getter)],
  189. ) -> JSONResponse:
  190. """
  191. 退出登录
  192. 参数:
  193. - payload (LogoutPayloadSchema): 退出登录负载模型
  194. - redis (Redis): Redis客户端对象
  195. 返回:
  196. - JSONResponse: 包含退出登录结果的响应模型
  197. 异常:
  198. - CustomException: 退出登录失败时抛出异常。
  199. """
  200. if await LoginService.logout_service(redis=redis, token=payload):
  201. log.info("退出成功")
  202. return SuccessResponse(msg="退出成功")
  203. return ErrorResponse(msg="退出失败")
  204. @AuthRouter.get(
  205. "/auto-login/users",
  206. summary="获取免登录用户列表",
  207. description="获取可用于免登录快速登录的用户列表",
  208. response_model=list[AutoLoginUserSchema],
  209. )
  210. async def get_auto_login_users_controller(
  211. db: Annotated[AsyncSession, Depends(db_getter)],
  212. ) -> JSONResponse:
  213. """
  214. 获取免登录用户列表
  215. 参数:
  216. - db (AsyncSession): 数据库会话对象
  217. 返回:
  218. - list[AutoLoginUserSchema]: 免登录用户列表
  219. """
  220. users = await AutoLoginService.get_auto_login_users_service(db=db)
  221. return SuccessResponse(data=users, msg="获取成功")
  222. @AuthRouter.post(
  223. "/auto-login/token",
  224. summary="获取免登录Token",
  225. description="根据用户ID生成免登录Token",
  226. response_model=AutoLoginTokenSchema,
  227. )
  228. async def get_auto_login_token_controller(
  229. redis: Annotated[Redis, Depends(redis_getter)],
  230. db: Annotated[AsyncSession, Depends(db_getter)],
  231. user_id: int,
  232. ) -> JSONResponse:
  233. """
  234. 获取免登录Token
  235. 参数:
  236. - redis (Redis): Redis客户端对象
  237. - db (AsyncSession): 数据库会话对象
  238. - user_id (int): 用户ID
  239. 返回:
  240. - AutoLoginTokenSchema: 免登录Token和用户信息
  241. """
  242. result = await AutoLoginService.create_auto_login_token_service(
  243. redis=redis, db=db, user_id=user_id
  244. )
  245. return SuccessResponse(data=result, msg="获取成功")
  246. @AuthRouter.post(
  247. "/auto-login",
  248. summary="免登录",
  249. description="使用免登录Token快速登录",
  250. response_model=JWTOutSchema,
  251. )
  252. async def auto_login_controller(
  253. request: Request,
  254. redis: Annotated[Redis, Depends(redis_getter)],
  255. db: Annotated[AsyncSession, Depends(db_getter)],
  256. token: str,
  257. ) -> JSONResponse:
  258. """
  259. 免登录
  260. 参数:
  261. - request (Request): FastAPI请求对象
  262. - redis (Redis): Redis客户端对象
  263. - db (AsyncSession): 数据库会话对象
  264. - token (str): 免登录Token
  265. 返回:
  266. - JWTOutSchema: JWT令牌信息
  267. """
  268. login_token = await AutoLoginService.auto_login_service(
  269. request=request, redis=redis, db=db, token=token
  270. )
  271. log.info("用户免登录成功")
  272. return SuccessResponse(data=login_token.model_dump(), msg="登录成功")