""" 微信账号绑定/解绑 Use Case(已登录用户场景) 与 wechat_sync_use_case(登录/注册,系统级)不同: - bind:把微信 openid/unionid 绑定到【当前登录账号】,不创建新用户; 微信身份若已绑定其他账号则冲突(409)。 - unbind:解除当前账号的微信绑定;若账号没有其他登录方式(手机/邮箱/密码), 解绑后将无法登录,因此拒绝解绑。 """ from __future__ import annotations from typing import Optional from packages.domain.entities import User class WechatBindRequest: """微信绑定请求""" def __init__(self, user_id: str, openid: str, unionid: str = ""): self.user_id = user_id self.openid = (openid or "").strip() self.unionid = (unionid or "").strip() class WechatBindResult: """微信绑定/解绑结果""" def __init__(self, user: User): self.user = user class WechatBindUseCase: """已登录用户绑定微信用例""" def __init__(self, user_repository): self.user_repository = user_repository def bind(self, request: WechatBindRequest) -> tuple[Optional[WechatBindResult], Optional[str], int]: """ 绑定微信到当前登录账号。 Returns: (结果, 错误信息, http状态码) - 成功时错误信息为 None、状态码为 200; 冲突返回 409,客户端/服务端错误返回 400/404。 """ if not request.openid: return None, "缺少微信 openid", 400 user = self.user_repository.find_by_id(request.user_id) if user is None: return None, "当前用户不存在", 404 # 已绑定同一个微信:幂等成功 if user.wechat_openid == request.openid: return WechatBindResult(user=user), None, 200 # 当前账号已绑定其他微信 if user.wechat_openid: return None, "当前账号已绑定微信,请先解绑", 409 # openid 已被其他账号占用 existing = self.user_repository.find_by_wechat_openid(request.openid) if existing is not None and existing.id != user.id: return None, "该微信已绑定其他账号,请先在原账号解绑", 409 # unionid 冲突:同主体微信已绑其他账号 if request.unionid: existing_union = self.user_repository.find_by_wechat_unionid(request.unionid) if existing_union is not None and existing_union.id != user.id: return None, "该微信主体已绑定其他账号,请先在原账号解绑", 409 user.wechat_openid = request.openid if request.unionid and not user.wechat_unionid: user.wechat_unionid = request.unionid self.user_repository.save(user) return WechatBindResult(user=user), None, 200 class WechatUnbindUseCase: """已登录用户解绑微信用例""" def __init__(self, user_repository): self.user_repository = user_repository def unbind(self, user_id: str) -> tuple[Optional[WechatBindResult], Optional[str], int]: """ 解除当前账号的微信绑定。 解绑前置条件:账号必须还有其他登录方式(密码 / 已验证手机 / 真实邮箱), 否则解绑后将永远无法登录。 """ user = self.user_repository.find_by_id(user_id) if user is None: return None, "当前用户不存在", 404 if not user.wechat_openid: return None, "当前账号未绑定微信", 400 # 守卫:解绑后账号必须仍有可实际使用的登录方式。 # 注意:微信注册用户带的是【随机密码】(用户不知道、无法用密码登录, # 且 @wechat.local 占位邮箱收不到重置邮件),故 password_hash 不作为兜底依据, # 口径与 /auth/me 的 binding_complete 一致。 has_phone = bool(user.phone and user.phone_verified) has_real_email = bool(user.email and user.email_verified and "@wechat.local" not in user.email) if not (has_phone or has_real_email): return None, "账号需要至少一种其他登录方式(已验证手机或真实邮箱)后才能解绑微信", 400 user.wechat_openid = None user.wechat_unionid = None self.user_repository.save(user) return WechatBindResult(user=user), None, 200