auth.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from fastapi import APIRouter, Request
  2. from fastapi.responses import JSONResponse
  3. from database import SessionLocal
  4. from models.total import User
  5. from utils.logger import logger
  6. import bcrypt
  7. import jwt
  8. import time
  9. router = APIRouter()
  10. # 本地JWT密钥(与Go版本保持一致,可配置到config.yaml)
  11. LOCAL_JWT_SECRET = "shudao-local-jwt-secret-2024"
  12. LOCAL_JWT_EXPIRE = 24 * 3600 # 24小时
  13. def generate_local_token(user_id: int, username: str, role: str) -> str:
  14. """生成本地JWT Token"""
  15. payload = {
  16. "user_id": user_id,
  17. "username": username,
  18. "role": role,
  19. "source": "local",
  20. "exp": int(time.time()) + LOCAL_JWT_EXPIRE,
  21. "iat": int(time.time()),
  22. }
  23. return jwt.encode(payload, LOCAL_JWT_SECRET, algorithm="HS256")
  24. @router.post("/local_login")
  25. async def local_login(request: Request):
  26. """
  27. 本地用户登录接口
  28. - 支持 account + password 方式
  29. - 返回 JWT token
  30. - 路径: POST /apiv1/auth/local_login (与Go版本对齐)
  31. """
  32. try:
  33. body = await request.json()
  34. except Exception:
  35. return JSONResponse(content={"statusCode": 400, "msg": "请求参数解析失败"})
  36. username = body.get("username", "").strip()
  37. password = body.get("password", "")
  38. if not username or not password:
  39. return JSONResponse(content={"statusCode": 400, "msg": "用户名和密码不能为空"})
  40. logger.info(f"[用户登录] 用户 {username} 尝试登录")
  41. db = SessionLocal()
  42. try:
  43. user = db.query(User).filter(
  44. User.username == username,
  45. User.is_deleted == 0
  46. ).first()
  47. if not user:
  48. logger.warning(f"[用户登录] 用户不存在: {username}")
  49. return JSONResponse(content={"statusCode": 401, "msg": "用户名或密码错误"})
  50. if user.status != 1:
  51. logger.warning(f"[用户登录] 用户已被禁用: {username}")
  52. return JSONResponse(content={"statusCode": 403, "msg": "用户已被禁用"})
  53. # 验证bcrypt密码
  54. try:
  55. if not bcrypt.checkpw(password.encode("utf-8"), user.password.encode("utf-8")):
  56. logger.warning(f"[用户登录] 密码错误: {username}")
  57. return JSONResponse(content={"statusCode": 401, "msg": "用户名或密码错误"})
  58. except Exception:
  59. logger.warning(f"[用户登录] 密码验证异常: {username}")
  60. return JSONResponse(content={"statusCode": 401, "msg": "用户名或密码错误"})
  61. token = generate_local_token(user.id, user.username, user.role or "user")
  62. logger.info(f"[用户登录] 用户 {username} 登录成功")
  63. return JSONResponse(content={
  64. "statusCode": 200,
  65. "msg": "登录成功",
  66. "token": token,
  67. "userInfo": {
  68. "id": user.id,
  69. "username": user.username,
  70. "nickname": user.nickname or "",
  71. "role": user.role or "user",
  72. "email": user.email or ""
  73. }
  74. })
  75. except Exception as e:
  76. logger.error(f"[用户登录] 异常: {e}")
  77. return JSONResponse(content={"statusCode": 500, "msg": f"登录失败: {str(e)}"})
  78. finally:
  79. db.close()
  80. @router.post("/register")
  81. async def register(request: Request):
  82. """
  83. 用户注册接口
  84. - 创建新用户账号
  85. - 使用 bcrypt 加密密码
  86. - 支持 account 和 username 两个字段名(向后兼容)
  87. """
  88. try:
  89. body = await request.json()
  90. except Exception:
  91. return JSONResponse(content={"statusCode": 400, "msg": "请求参数解析失败"})
  92. # 同时支持 account 和 username 字段
  93. account = body.get("account") or body.get("username", "")
  94. account = account.strip() if account else ""
  95. password = body.get("password", "")
  96. name = body.get("name", "").strip()
  97. if not account or not password:
  98. return JSONResponse(content={"statusCode": 400, "msg": "账号和密码不能为空"})
  99. logger.info(f"[用户注册] 账号 {account} 尝试注册")
  100. db = SessionLocal()
  101. try:
  102. # 检查账号是否已存在
  103. existing_user = db.query(User).filter(
  104. User.username == account,
  105. User.is_deleted == 0
  106. ).first()
  107. if existing_user:
  108. logger.warning(f"[用户注册] 账号已存在: {account}")
  109. return JSONResponse(content={"statusCode": 400, "msg": "账号已存在"})
  110. # 加密密码
  111. hashed_password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
  112. # 创建新用户
  113. new_user = User(
  114. username=account,
  115. password=hashed_password,
  116. nickname=name or account,
  117. role="user",
  118. status=1,
  119. is_deleted=0
  120. )
  121. db.add(new_user)
  122. db.commit()
  123. db.refresh(new_user)
  124. logger.info(f"[用户注册] 用户 {account} 注册成功")
  125. return JSONResponse(content={
  126. "statusCode": 200,
  127. "msg": "注册成功",
  128. "data": {
  129. "user_id": new_user.id,
  130. "account": new_user.username
  131. }
  132. })
  133. except Exception as e:
  134. db.rollback()
  135. logger.error(f"[用户注册] 异常: {e}")
  136. return JSONResponse(content={"statusCode": 500, "msg": f"注册失败: {str(e)}"})
  137. finally:
  138. db.close()
  139. @router.get("/user/info")
  140. async def get_user_info(request: Request):
  141. """
  142. 获取用户信息接口
  143. - 需要认证
  144. - 返回当前登录用户的详细信息
  145. """
  146. user_info = request.state.user
  147. if not user_info:
  148. return JSONResponse(status_code=401, content={"statusCode": 401, "msg": "未认证"})
  149. db = SessionLocal()
  150. try:
  151. # 优先查询本地用户表
  152. user = db.query(User).filter(
  153. User.id == user_info.user_id,
  154. User.is_deleted == 0
  155. ).first()
  156. if user:
  157. # 本地用户
  158. logger.info(f"[获取用户信息] 本地用户: {user.username}")
  159. return JSONResponse(content={
  160. "statusCode": 200,
  161. "msg": "success",
  162. "data": {
  163. "user_id": user.id,
  164. "account": user.username,
  165. "name": user.nickname or user.username,
  166. "points": 0, # 本地用户默认积分为0
  167. "created_at": user.created_at or 0
  168. }
  169. })
  170. # 查询外部用户数据
  171. from models.user_data import UserData
  172. user_data = db.query(UserData).filter(
  173. UserData.accountID == user_info.account
  174. ).first()
  175. if not user_data:
  176. logger.warning(f"[获取用户信息] 用户数据不存在: {user_info.account}")
  177. return JSONResponse(content={"statusCode": 404, "msg": "用户数据不存在"})
  178. logger.info(f"[获取用户信息] 外部用户: {user_info.account}")
  179. return JSONResponse(content={
  180. "statusCode": 200,
  181. "msg": "success",
  182. "data": {
  183. "user_id": user_data.id,
  184. "account": user_data.accountID,
  185. "name": user_data.name or "",
  186. "points": user_data.points or 0,
  187. "created_at": user_data.created_at or 0
  188. }
  189. })
  190. except Exception as e:
  191. logger.error(f"[获取用户信息] 异常: {e}")
  192. return JSONResponse(content={"statusCode": 500, "msg": f"获取用户信息失败: {str(e)}"})
  193. finally:
  194. db.close()