mini_auth.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """小程序鉴权:微信手机号快捷登录。"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from fastapi import APIRouter, Depends
  5. from sqlalchemy.orm import Session
  6. from app.core.database import get_db
  7. from app.core.response import error_response, success_response
  8. from app.services import wx_qr_session
  9. from app.services.web_login_tokens import issue_web_login_tokens
  10. from app.services.wechat_login_service import resolve_wechat_login_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. @router.post("/wechat-login", response_model=dict)
  18. def wechat_login(body: dict, db: Session = Depends(get_db)):
  19. """手机号快捷登录:须 loginCode + phoneCode;匹配 phone_no 并绑定 wx_openid。"""
  20. login_code = (body.get("loginCode") or "").strip()
  21. phone_code = (body.get("phoneCode") or "").strip()
  22. if not phone_code:
  23. return error_response(400, "请授权手机号登录,或使用账号密码登录")
  24. try:
  25. sess = code2session(login_code)
  26. except WechatMiniAuthError as exc:
  27. return error_response(400, str(exc))
  28. try:
  29. phone = get_phone_number(phone_code)
  30. except WechatMiniAuthError as exc:
  31. return error_response(400, str(exc))
  32. user, err_msg, err_code = resolve_wechat_login_user(
  33. db, openid=sess["openid"], phone=phone
  34. )
  35. if err_msg:
  36. return error_response(err_code or 400, err_msg)
  37. unionid = (sess.get("unionid") or "").strip()
  38. if unionid and not (getattr(user, "wx_unionid", None) or "").strip():
  39. user.wx_unionid = unionid
  40. user.update_time = datetime.now()
  41. db.add(user)
  42. db.commit()
  43. db.refresh(user)
  44. try:
  45. data = issue_web_login_tokens(db, user)
  46. except ValueError as exc:
  47. return error_response(403, str(exc))
  48. return success_response(data)
  49. @router.post("/web-qr-scanned", response_model=dict)
  50. def web_qr_scanned(body: dict):
  51. raw = (body.get("sessionId") or body.get("scene") or "").strip()
  52. sid = wx_qr_session.resolve_session_id(raw) or raw
  53. data = wx_qr_session.get_session(sid)
  54. if not data or data.get("status") in ("expired", "consumed"):
  55. return error_response(400, "二维码已过期,请在电脑上刷新后重扫")
  56. wx_qr_session.mark_scanned(sid)
  57. return success_response({"ok": True})
  58. @router.post("/web-qr-confirm", response_model=dict)
  59. def web_qr_confirm(body: dict, db: Session = Depends(get_db)):
  60. raw = (body.get("sessionId") or body.get("scene") or "").strip()
  61. sid = wx_qr_session.resolve_session_id(raw) or raw
  62. data = wx_qr_session.get_session(sid)
  63. if not data or data.get("status") in ("expired", "consumed", "failed"):
  64. return error_response(400, "二维码已过期,请在电脑上刷新后重扫")
  65. if data.get("status") == "success":
  66. return success_response({"ok": True, "message": "电脑端即将登录"})
  67. wx_qr_session.mark_scanned(sid)
  68. login_code = (body.get("loginCode") or "").strip()
  69. phone_code = (body.get("phoneCode") or "").strip()
  70. if not login_code or not phone_code:
  71. wx_qr_session.mark_failed(sid, "需要授权手机号才能完成扫码登录")
  72. return error_response(400, "需要授权手机号才能完成扫码登录")
  73. try:
  74. sess = code2session(login_code)
  75. phone = get_phone_number(phone_code)
  76. except WechatMiniAuthError as exc:
  77. wx_qr_session.mark_failed(sid, str(exc))
  78. return error_response(400, str(exc))
  79. user, err_msg, err_code = resolve_wechat_login_user(
  80. db, openid=sess["openid"], phone=phone
  81. )
  82. if err_msg:
  83. wx_qr_session.mark_failed(sid, err_msg)
  84. return error_response(err_code or 400, err_msg)
  85. unionid = (sess.get("unionid") or "").strip()
  86. if unionid and not (getattr(user, "wx_unionid", None) or "").strip():
  87. user.wx_unionid = unionid
  88. user.update_time = datetime.now()
  89. db.add(user)
  90. db.commit()
  91. db.refresh(user)
  92. try:
  93. tokens = issue_web_login_tokens(db, user)
  94. except ValueError as exc:
  95. wx_qr_session.mark_failed(sid, str(exc))
  96. return error_response(403, str(exc))
  97. if not wx_qr_session.mark_success(sid, tokens):
  98. return error_response(400, "二维码已过期,请在电脑上刷新后重扫")
  99. return success_response({"ok": True, "message": "电脑端即将登录,可关闭本页"})