131 lines
4.4 KiB
Python
131 lines
4.4 KiB
Python
"""小程序鉴权:微信登录 / 绑定 openid。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.auth import get_current_user_by_token
|
|
from app.core.database import get_db
|
|
from app.core.response import error_response, success_response
|
|
from app.crud.user import user_crud
|
|
from app.services.issue_user_tokens import issue_tokens_for_user
|
|
from app.services.wechat_mini_auth import (
|
|
WechatMiniAuthError,
|
|
code2session,
|
|
get_phone_number,
|
|
)
|
|
|
|
router = APIRouter(prefix="/mini/auth", tags=["小程序鉴权"])
|
|
|
|
|
|
async def _resolve_user_id(request: Request, db: Session) -> str:
|
|
authorization = request.headers.get("Authorization", "")
|
|
if not authorization.startswith("Bearer "):
|
|
raise HTTPException(status_code=401, detail="未授权")
|
|
token = authorization.replace("Bearer ", "")
|
|
current = await get_current_user_by_token(token=token, db=db)
|
|
user_id = current.get("user_id") or current.get("sub")
|
|
if not user_id:
|
|
raise HTTPException(status_code=401, detail="未授权")
|
|
return user_id
|
|
|
|
|
|
@router.post("/wechat-login", response_model=dict)
|
|
async def wechat_login(body: dict, db: Session = Depends(get_db)):
|
|
"""微信授权登录:优先 openid;未绑定则用手机号匹配员工并绑定。"""
|
|
login_code = (body.get("loginCode") or "").strip()
|
|
phone_code = (body.get("phoneCode") or "").strip() or None
|
|
|
|
try:
|
|
sess = code2session(login_code)
|
|
except WechatMiniAuthError as exc:
|
|
return error_response(400, str(exc))
|
|
|
|
openid = sess["openid"]
|
|
user = user_crud.get_by_wx_openid(db, openid)
|
|
if user:
|
|
try:
|
|
data = await issue_tokens_for_user(db, user)
|
|
except ValueError as exc:
|
|
return error_response(403, str(exc))
|
|
return success_response(data)
|
|
|
|
if not phone_code:
|
|
return error_response(400, "请授权手机号登录,或使用账号密码登录")
|
|
|
|
try:
|
|
phone = get_phone_number(phone_code)
|
|
except WechatMiniAuthError as exc:
|
|
return error_response(400, str(exc))
|
|
|
|
users = user_crud.list_active_by_phone_no(db, phone)
|
|
if not users:
|
|
return error_response(
|
|
401, "未找到与该手机号匹配的员工账号,请使用账号密码登录"
|
|
)
|
|
if len(users) > 1:
|
|
return error_response(400, "手机号对应多个账号,请使用账号密码登录")
|
|
|
|
user = users[0]
|
|
other = user_crud.get_by_wx_openid(db, openid)
|
|
if other and other.id != user.id:
|
|
return error_response(409, "该微信已绑定其他账号")
|
|
existing_oid = (getattr(user, "wx_openid", None) or "").strip()
|
|
if existing_oid and existing_oid != openid:
|
|
return error_response(409, "该账号已绑定其他微信,请联系管理员")
|
|
|
|
user.wx_openid = openid
|
|
user.update_time = datetime.now()
|
|
db.add(user)
|
|
db.commit()
|
|
db.refresh(user)
|
|
|
|
try:
|
|
data = await issue_tokens_for_user(db, user)
|
|
except ValueError as exc:
|
|
return error_response(403, str(exc))
|
|
return success_response(data)
|
|
|
|
|
|
@router.post("/wechat-bind", response_model=dict)
|
|
async def wechat_bind(
|
|
request: Request,
|
|
body: dict,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""密码登录后绑定当前微信 openid(需 Bearer)。"""
|
|
try:
|
|
user_id = await _resolve_user_id(request, db)
|
|
except HTTPException:
|
|
return error_response(401, "未授权")
|
|
|
|
login_code = (body.get("loginCode") or "").strip()
|
|
try:
|
|
sess = code2session(login_code)
|
|
except WechatMiniAuthError as exc:
|
|
return error_response(400, str(exc))
|
|
|
|
openid = sess["openid"]
|
|
user = user_crud.get(db, user_id)
|
|
if not user or user.del_flag != 0:
|
|
return error_response(401, "用户不存在")
|
|
|
|
other = user_crud.get_by_wx_openid(db, openid)
|
|
if other and other.id != user.id:
|
|
return error_response(409, "该微信已绑定其他账号")
|
|
|
|
existing_oid = (getattr(user, "wx_openid", None) or "").strip()
|
|
if existing_oid == openid:
|
|
return success_response({"bound": True, "message": "已绑定"})
|
|
|
|
if existing_oid and existing_oid != openid:
|
|
return error_response(409, "该账号已绑定其他微信,请联系管理员")
|
|
|
|
user.wx_openid = openid
|
|
user.update_time = datetime.now()
|
|
db.add(user)
|
|
db.commit()
|
|
return success_response({"bound": True, "message": "绑定成功"})
|