Compare commits

...

2 Commits

Author SHA1 Message Date
0e97812c03 Merge branch 'claude/nice-heyrovsky-5b0cf0' 2026-06-06 22:05:29 +08:00
a4ac682ad0 feat: 密码登录后自动绑定 open_id,为微信消息通知做准备
- 新增 /api/auth/bind-openid-by-code 接口,用 wx.login code 绑定 open_id
- 密码登录成功后自动调用绑定接口,无需用户手动操作
- 修复 auth.py 文档注释中的参数描述

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 22:04:17 +08:00
4 changed files with 74 additions and 2 deletions

View File

@ -26,6 +26,11 @@ class BindOpenIdRequest(BaseModel):
open_id: str
class BindOpenIdByCodeRequest(BaseModel):
"""通过 wx.login code 绑定微信 OpenID"""
code: str
@router.post("/login")
def login(
payload: LoginRequest, # 登录请求体,包含用户名、密码、角色类型
@ -51,7 +56,7 @@ def wechat_login(
"""微信手机号一键登录接口
用途通过微信手机号授权登录自动匹配系统用户并绑定 open_id
请求参数WechatLoginRequestcodeencrypted_dataiv
请求参数WechatLoginRequestcodephone_code
返回值登录结果包含 token 等认证信息
权限要求无需认证公开接口
"""
@ -103,3 +108,20 @@ def bind_openid(
"""
wechat_notification_service.bind_open_id(session, current_user["user_id"], payload.open_id)
return success_payload({"success": True})
@router.post("/bind-openid-by-code")
def bind_openid_by_code(
payload: BindOpenIdByCodeRequest, # 通过 code 绑定
auth_service: AuthService = Depends(get_auth_service), # 注入认证服务
session: Session = Depends(get_db_session), # 注入数据库会话
current_user: dict = Depends(get_current_user), # 当前登录用户
) -> dict:
"""通过 wx.login code 绑定微信 OpenID
用途密码登录后调用 code 换取 open_id 并绑定到当前用户
请求参数BindOpenIdByCodeRequestcode - wx.login 获取的临时凭证
返回值绑定结果 {"success": true}
权限要求必须已登录
"""
return success_payload(auth_service.bind_openid_by_code(current_user["user_id"], payload.code, session))

View File

@ -273,6 +273,40 @@ class AuthService:
pass
return ""
def bind_openid_by_code(self, user_id: int, code: str, session: Session | None = None) -> dict:
"""通过 wx.login code 绑定 open_id 到指定用户。
用于密码登录后自动绑定 open_id为微信订阅消息通知做准备
Args:
user_id: 用户 ID
code: wx.login() 获取的临时凭证
session: 数据库会话
Returns:
{"success": True}
被调用路由: auth.py - POST /auth/bind-openid-by-code
"""
if session is None:
raise AppException(code=ErrorCode.INTERNAL_ERROR, message="数据库连接不可用", status_code=500)
settings = self.settings
if not settings.wechat_app_id or not settings.wechat_app_secret:
return {"success": True, "skipped": True}
_, open_id = self._code2session(code, settings)
if not open_id:
return {"success": True, "skipped": True}
user = self.repository.get_user(session, user_id)
if user is not None:
user.open_id = open_id
session.add(user)
session.commit()
return {"success": True}
def get_me(self, token: str, session: Session | None = None) -> dict | None:
"""根据 Token 获取当前用户信息(无需重新登录)。

View File

@ -36,6 +36,21 @@ Page({
auth.setToken(data.token);
auth.setUser(data);
// 密码登录成功后自动绑定 open_id为微信消息通知做准备
try {
var loginCode = await new Promise(function (resolve, reject) {
wx.login({ success: resolve, fail: reject });
});
await app.request({
url: "/api/auth/bind-openid-by-code",
method: "POST",
data: { code: loginCode.code },
});
} catch (bindErr) {
// 绑定失败不影响登录
console.log("open_id 绑定跳过:", bindErr.message);
}
wx.showToast({
title: "欢迎," + (data.real_name || data.username),
icon: "success",

View File

@ -11,8 +11,9 @@ Page({
},
handleGetPhoneNumber: async function (e) {
console.log("[DEBUG] getPhoneNumber event:", JSON.stringify(e.detail));
if (e.detail.errMsg !== "getPhoneNumber:ok") {
this.setData({ message: "您拒绝了手机号授权" });
this.setData({ message: "授权失败:" + (e.detail.errMsg || JSON.stringify(e.detail)) });
return;
}
this.setData({ loading: true, message: "" });