feat(auth): M2 POST /wechat/conflict/rebind(换绑=注销X重建Y,单事务)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
guke
2026-07-14 17:46:58 +08:00
parent 80652ca78d
commit 1567a9ac74
3 changed files with 161 additions and 8 deletions
+38
View File
@@ -402,6 +402,44 @@ def wechat_conflict_continue(
)
@router.post(
"/wechat/conflict/rebind",
response_model=WechatBindResultResponse,
summary="微信占用冲突·换绑(注销老账号+用该号重建全新账号)",
)
def wechat_conflict_rebind(
req: WechatConflictRebindRequest, request: Request, db: DbSession
) -> WechatBindResultResponse:
from app.core.config import settings # 局部 import,避免循环
try:
claims = decode_conflict_ticket(req.conflict_ticket)
except TokenError as e:
raise HTTPException(status_code=401, detail="操作超时,请重新用微信登录") from e
enforce_rate_limit(
request, scope="wechat-conflict-device", subject=req.device_id,
limit=SMS_LOGIN_MAX_PER_HOUR, window_sec=3600, detail="操作过于频繁,请稍后再试",
)
phone = claims["phone"]
if rebind_repo.rebound_within_days(db, phone, settings.PHONE_REBIND_LIMIT_DAYS):
days = rebind_repo.remaining_block_days(db, phone, settings.PHONE_REBIND_LIMIT_DAYS)
raise HTTPException(status_code=409, detail=f"该手机号 {days} 天内已换绑过,暂不能再次换绑")
user = user_repo.rebind_account(
db, phone=phone, openid=claims["openid"],
wechat_nickname=claims["wnk"], wechat_avatar_url=claims["wav"],
)
completed = onboarding_repo.is_completed(db, user_id=user.id, device_id=req.device_id)
logger.info("wechat conflict rebind new_user_id=%d phone=%s openid=%s***",
user.id, mask_phone(phone), claims["openid"][:6])
return WechatBindResultResponse(
status="logged_in",
token=_login_response(user, onboarding_completed=completed),
)
# ===================== Refresh =====================
@router.post("/refresh", response_model=TokenPair, summary="用 refresh_token 换新 token 对")