This commit is contained in:
aaron 2026-05-23 14:52:29 +08:00
parent 02c35c146d
commit 69e6338722
3 changed files with 42 additions and 7 deletions

View File

@ -5,7 +5,7 @@ from sqlalchemy.orm import selectinload
from app.core.deps import get_current_user from app.core.deps import get_current_user
from app.db.database import get_db from app.db.database import get_db
from app.db.models import ClassMembership, User from app.db.models import ClassMembership, Class_, User
from app.schemas.user import TokenResponse, build_user_out from app.schemas.user import TokenResponse, build_user_out
from app.schemas.wechat import ( from app.schemas.wechat import (
WeChatBindRequest, WeChatBindRequest,
@ -28,6 +28,31 @@ from app.services.wechat_service import (
router = APIRouter(prefix="/api/wechat", tags=["wechat"]) router = APIRouter(prefix="/api/wechat", tags=["wechat"])
async def _find_approved_member_for_wechat_bind(
db: AsyncSession,
invite_code: str,
student_id: str,
) -> tuple[User, int] | None:
result = await db.execute(
select(User, ClassMembership.class_id)
.join(ClassMembership, ClassMembership.user_id == User.id)
.join(Class_, Class_.id == ClassMembership.class_id)
.options(
selectinload(User.memberships),
selectinload(User.memberships).selectinload(ClassMembership.class_),
)
.where(
Class_.invite_code == invite_code,
User.student_id == student_id,
User.status == "approved",
)
)
row = result.first()
if row is None:
return None
return row[0], row[1]
@router.post("/login", response_model=WeChatLoginResponse) @router.post("/login", response_model=WeChatLoginResponse)
async def wechat_login(req: WeChatLoginRequest, db: AsyncSession = Depends(get_db)): async def wechat_login(req: WeChatLoginRequest, db: AsyncSession = Depends(get_db)):
session = await exchange_code_for_session(req.code) session = await exchange_code_for_session(req.code)
@ -60,12 +85,21 @@ async def wechat_bind(req: WeChatBindRequest, db: AsyncSession = Depends(get_db)
req.invite_code.strip().upper(), req.invite_code.strip().upper(),
req.student_id.strip(), req.student_id.strip(),
) )
approved_target = None
if activation_target is None: if activation_target is None:
raise HTTPException(status_code=400, detail="邀请码或学号无效,或账号已激活") approved_target = await _find_approved_member_for_wechat_bind(
db,
req.invite_code.strip().upper(),
req.student_id.strip(),
)
if activation_target is None and approved_target is None:
raise HTTPException(status_code=400, detail="邀请码或学号无效")
user, class_id = activation_target user, class_id = activation_target or approved_target
if existing is not None and existing.id != user.id: if existing is not None and existing.id != user.id:
raise HTTPException(status_code=400, detail="该微信已绑定其他账号") raise HTTPException(status_code=400, detail="该微信已绑定其他账号")
if user.wechat_openid and user.wechat_openid != openid:
raise HTTPException(status_code=400, detail="该账号已绑定其他微信")
if user.phone and user.phone != phone: if user.phone and user.phone != phone:
raise HTTPException(status_code=400, detail="手机号与预留手机号不一致") raise HTTPException(status_code=400, detail="手机号与预留手机号不一致")

View File

@ -126,7 +126,8 @@ Page({
phone_code: phoneCode phone_code: phoneCode
}); });
saveSession(res.token, res.user); saveSession(res.token, res.user);
wx.switchTab({ url: "/pages/home/index" }); wx.showToast({ title: "绑定成功", icon: "success" });
setTimeout(() => wx.switchTab({ url: "/pages/home/index" }), 500);
} catch (error) { } catch (error) {
wx.showToast({ title: error.message || "绑定失败", icon: "none" }); wx.showToast({ title: error.message || "绑定失败", icon: "none" });
} finally { } finally {

View File

@ -15,7 +15,7 @@
<view wx:if="{{isWechatLogin}}" class="login-method"> <view wx:if="{{isWechatLogin}}" class="login-method">
<view class="wechat-symbol">微</view> <view class="wechat-symbol">微</view>
<view class="login-title">微信快捷登录</view> <view class="login-title">微信快捷登录</view>
<view class="login-copy">已绑定微信的同学可直接进入班级空间;未绑定账号会进入激活流程。</view> <view class="login-copy">已绑定微信的同学可直接进入班级空间;未绑定微信的账号会进入绑定流程。</view>
<button class="button login-primary" loading="{{loading}}" bindtap="loginWithWechatTap">使用微信登录</button> <button class="button login-primary" loading="{{loading}}" bindtap="loginWithWechatTap">使用微信登录</button>
</view> </view>
@ -37,13 +37,13 @@
<view wx:elif="{{isActivateMode}}" class="section"> <view wx:elif="{{isActivateMode}}" class="section">
<view class="section-head"> <view class="section-head">
<view class="section-title">激活账号</view> <view class="section-title">绑定班级账号</view>
<view class="section-action" bindtap="backToLogin">返回登录</view> <view class="section-action" bindtap="backToLogin">返回登录</view>
</view> </view>
<view class="card"> <view class="card">
<view class="muted">说明</view> <view class="muted">说明</view>
<view class="card-title">没有找到已绑定的微信账号</view> <view class="card-title">没有找到已绑定的微信账号</view>
<view class="muted">请用班级邀请码、学号和微信手机号激活你的班级账号。</view> <view class="muted">请用班级邀请码、学号和微信手机号绑定你的班级账号;已在网站激活的账号也可以直接绑定并登录。</view>
</view> </view>
<view class="card"> <view class="card">
<view class="muted">邀请码</view> <view class="muted">邀请码</view>