mini_auth.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """小程序鉴权:微信登录 / 绑定 openid。"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from fastapi import APIRouter, Depends, HTTPException, Request
  5. from sqlalchemy.orm import Session
  6. from app.core.auth import get_current_user_by_token
  7. from app.core.database import get_db
  8. from app.core.response import error_response, success_response
  9. from app.crud.user import user_crud
  10. from app.services.issue_user_tokens import issue_tokens_for_user
  11. from app.services.wechat_mini_auth import (
  12. WechatMiniAuthError,
  13. code2session,
  14. get_phone_number,
  15. )
  16. router = APIRouter(prefix="/mini/auth", tags=["小程序鉴权"])
  17. async def _resolve_user_id(request: Request, db: Session) -> str:
  18. authorization = request.headers.get("Authorization", "")
  19. if not authorization.startswith("Bearer "):
  20. raise HTTPException(status_code=401, detail="未授权")
  21. token = authorization.replace("Bearer ", "")
  22. current = await get_current_user_by_token(token=token, db=db)
  23. user_id = current.get("user_id") or current.get("sub")
  24. if not user_id:
  25. raise HTTPException(status_code=401, detail="未授权")
  26. return user_id
  27. @router.post("/wechat-login", response_model=dict)
  28. async def wechat_login(body: dict, db: Session = Depends(get_db)):
  29. """微信授权登录:优先 openid;未绑定则用手机号匹配员工并绑定。"""
  30. login_code = (body.get("loginCode") or "").strip()
  31. phone_code = (body.get("phoneCode") or "").strip() or None
  32. try:
  33. sess = code2session(login_code)
  34. except WechatMiniAuthError as exc:
  35. return error_response(400, str(exc))
  36. openid = sess["openid"]
  37. user = user_crud.get_by_wx_openid(db, openid)
  38. if user:
  39. try:
  40. data = await issue_tokens_for_user(db, user)
  41. except ValueError as exc:
  42. return error_response(403, str(exc))
  43. return success_response(data)
  44. if not phone_code:
  45. return error_response(400, "请授权手机号登录,或使用账号密码登录")
  46. try:
  47. phone = get_phone_number(phone_code)
  48. except WechatMiniAuthError as exc:
  49. return error_response(400, str(exc))
  50. users = user_crud.list_active_by_phone_no(db, phone)
  51. if not users:
  52. return error_response(
  53. 401, "未找到与该手机号匹配的员工账号,请使用账号密码登录"
  54. )
  55. if len(users) > 1:
  56. return error_response(400, "手机号对应多个账号,请使用账号密码登录")
  57. user = users[0]
  58. other = user_crud.get_by_wx_openid(db, openid)
  59. if other and other.id != user.id:
  60. return error_response(409, "该微信已绑定其他账号")
  61. existing_oid = (getattr(user, "wx_openid", None) or "").strip()
  62. if existing_oid and existing_oid != openid:
  63. return error_response(409, "该账号已绑定其他微信,请联系管理员")
  64. user.wx_openid = openid
  65. user.update_time = datetime.now()
  66. db.add(user)
  67. db.commit()
  68. db.refresh(user)
  69. try:
  70. data = await issue_tokens_for_user(db, user)
  71. except ValueError as exc:
  72. return error_response(403, str(exc))
  73. return success_response(data)
  74. @router.post("/wechat-bind", response_model=dict)
  75. async def wechat_bind(
  76. request: Request,
  77. body: dict,
  78. db: Session = Depends(get_db),
  79. ):
  80. """密码登录后绑定当前微信 openid(需 Bearer)。"""
  81. try:
  82. user_id = await _resolve_user_id(request, db)
  83. except HTTPException:
  84. return error_response(401, "未授权")
  85. login_code = (body.get("loginCode") or "").strip()
  86. try:
  87. sess = code2session(login_code)
  88. except WechatMiniAuthError as exc:
  89. return error_response(400, str(exc))
  90. openid = sess["openid"]
  91. user = user_crud.get(db, user_id)
  92. if not user or user.del_flag != 0:
  93. return error_response(401, "用户不存在")
  94. other = user_crud.get_by_wx_openid(db, openid)
  95. if other and other.id != user.id:
  96. return error_response(409, "该微信已绑定其他账号")
  97. existing_oid = (getattr(user, "wx_openid", None) or "").strip()
  98. if existing_oid == openid:
  99. return success_response({"bound": True, "message": "已绑定"})
  100. if existing_oid and existing_oid != openid:
  101. return error_response(409, "该账号已绑定其他微信,请联系管理员")
  102. user.wx_openid = openid
  103. user.update_time = datetime.now()
  104. db.add(user)
  105. db.commit()
  106. return success_response({"bound": True, "message": "绑定成功"})