From 2ae30ac69277e0354822257adfed485537d7685a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sun, 12 Apr 2026 11:56:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(invite):=20=E6=94=AF=E6=8C=81=E5=B7=B2?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E7=94=A8=E6=88=B7=E7=9B=B4=E6=8E=A5=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E5=BA=94=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端accept接口code参数改为可选,支持通过Authorization头识别登录用户 - 已登录用户加入时不再弹手机号授权,前端调用新增的doJoinAppForLoggedInUser方法 - 未注册用户仍通过手机号授权码code加入,调用doJoinAppForNewUser方法 - 拆分前端加入逻辑,新增handleConfirmJoin统一处理已登录加入流程 - 优化按钮交互,已登录状态下按钮变为普通点击,移除协议强制勾选 - 错误处理和状态提示完善,加入成功后自动跳转首页 --- .workbuddy/expert-history.json | 2 +- .workbuddy/memory/2026-04-12.md | 67 +++++++++++++++++++++++++++++++++ src/passport/invite/index.tsx | 53 +++++++++++++++++++------- 3 files changed, 108 insertions(+), 14 deletions(-) diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json index b964434..d5db34f 100644 --- a/.workbuddy/expert-history.json +++ b/.workbuddy/expert-history.json @@ -35,5 +35,5 @@ } ] }, - "lastUpdated": 1775965435808 + "lastUpdated": 1775966075231 } \ No newline at end of file diff --git a/.workbuddy/memory/2026-04-12.md b/.workbuddy/memory/2026-04-12.md index 04b8e12..edbbe02 100644 --- a/.workbuddy/memory/2026-04-12.md +++ b/.workbuddy/memory/2026-04-12.md @@ -85,6 +85,52 @@ doJoinApp(wxCode: string, accessToken: string) --- +## 任务:后端改造支持已登录用户直接加入 + +### 问题 +后端 `/api/_app/developer/invite/accept` 接口强制要求传 `code`(手机号授权码),导致已登录用户也需要弹手机号授权。 + +### 后端改造方案 +修改 `AppMpInviteController.acceptInvite` 方法: + +#### 1. 参数校验调整 +- `code` 改为可选参数 +- 不传 `code` 时,从 `Authorization` 头获取当前登录用户 + +#### 2. 双模式支持 +```java +if (StrUtil.isBlank(code)) { + // 模式一:已登录用户(通过 Authorization 头识别) + userId = getCurrentUserId(); +} else { + // 模式二:未注册用户(通过手机号授权码获取手机号,创建用户) + String phone = getPhoneByCode(code); + userId = getOrCreateUserByPhone(phone); +} +``` + +#### 3. getCurrentUserId 方法 +- 尝试从 Spring Security Context 获取 +- 如果获取不到(免登录接口),手动解析 `Authorization` 头的 JWT Token + +### 前端配合改造 +- 已登录用户:普通 `onClick` 按钮 → `handleConfirmJoin` → `doJoinAppForLoggedInUser`(不传 `code`) +- 未注册用户:`getPhoneNumber` 按钮 → `handleGetPhoneNumber` → `doJoinAppForNewUser`(传 `code`) + +### 文件修改 +**后端:** +- `/Users/gxwebsoft/JAVA/websopy-java/src/main/java/com/gxwebsoft/app/controller/AppMpInviteController.java` + - `acceptInvite` 方法支持 `code` 可选 + - 使用 `BaseController.getLoginUserId()` 获取当前登录用户(无需额外方法) + +**前端:** +- `/Users/gxwebsoft/VUE/websopy-mp/src/passport/invite/index.tsx` + - 已登录按钮改为普通 `onClick` + - 新增 `handleConfirmJoin` 方法 + - 拆分 `doJoinApp` 为 `doJoinAppForLoggedInUser` 和 `doJoinAppForNewUser` + +--- + ## 优化:已登录用户不强制勾选协议 ### 改动 @@ -93,3 +139,24 @@ doJoinApp(wxCode: string, accessToken: string) ### 文件修改 - `src/passport/invite/index.tsx` - `handleConfirmJoin` 移除协议检查 + +--- + +## 修复:后端需要手机号授权码 + +### 问题 +后端 `invite/accept` 接口只接受 `getPhoneNumber` 获取的手机号授权码,用 `wx.login()` 的 code 会报「获取手机号失败」。 + +### 解决 +两种用户状态都走 `getPhoneNumber` 按钮: +- 已登录:文字「确认加入」,回调 `handleConfirmJoinGetPhoneNumber` +- 未注册:文字「微信手机号快速加入」,回调 `handleGetPhoneNumber` + +### 差异 +| 用户状态 | 回调 | 行为 | +|------|------|------| +| 已登录 | `handleConfirmJoinGetPhoneNumber` | 直接用 `code + access_token` 调加入接口 | +| 未注册 | `handleGetPhoneNumber` | 先 `loginByMpWxPhone` 注册登录,再调加入接口 | + +### 文件修改 +- `src/passport/invite/index.tsx` - 两种状态都用 getPhoneNumber 按钮 diff --git a/src/passport/invite/index.tsx b/src/passport/invite/index.tsx index 4331051..41ae9f8 100644 --- a/src/passport/invite/index.tsx +++ b/src/passport/invite/index.tsx @@ -129,8 +129,7 @@ const InvitePage: React.FC = () => { /** * 已登录用户:点击「确认加入」 - * 不弹手机号授权,直接用 wx.login() 获取 code 调加入接口 - * 不强制要求勾选协议 + * 后端已改造支持已登录用户直接加入(不传 code,从 Authorization 头识别用户) */ const handleConfirmJoin = async () => { const accessToken = Taro.getStorageSync('access_token'); @@ -141,10 +140,8 @@ const InvitePage: React.FC = () => { try { setAuthLoading(true); - // 用 wx.login() 获取 code(后端需要 code 识别用户身份) - const wxRes = await Taro.login(); - if (!wxRes.code) throw new Error('获取微信登录凭证失败'); - await doJoinApp(wxRes.code, accessToken); + // 已登录用户不传 code,后端从 Authorization 头识别用户 + await doJoinAppForLoggedInUser(accessToken); } catch (err: any) { console.error('确认加入失败:', err); Taro.showToast({ title: err.message || '加入失败,请重试', icon: 'none' }); @@ -209,7 +206,7 @@ const InvitePage: React.FC = () => { const wxRes = await Taro.login(); if (!wxRes.code) throw new Error('获取微信登录凭证失败'); - await doJoinApp(wxRes.code, access_token); + await doJoinAppForNewUser(wxRes.code, access_token); } catch (err: any) { console.error('手机号授权登录失败:', err); Taro.showToast({ title: err.message || '授权失败,请重试', icon: 'none' }); @@ -219,13 +216,43 @@ const InvitePage: React.FC = () => { }; /** - * 调用加入接口(统一入口) - * @param wxCode wx.login() 或 getPhoneNumber 获取的 code(后端用于识别用户) - * @param accessToken 登录 token(加到 Authorization 头,双重验证) + * 已登录用户调用加入接口(不传 code,后端从 Authorization 头识别用户) */ - const doJoinApp = async (wxCode: string, accessToken: string) => { + const doJoinAppForLoggedInUser = async (accessToken: string) => { const inviteToken = Taro.getStorageSync('invite_token') || token; - console.log('doJoinApp, inviteToken:', inviteToken, 'wxCode:', wxCode ? '有' : '无', 'accessToken:', accessToken ? '有' : '无'); + console.log('doJoinAppForLoggedInUser, inviteToken:', inviteToken, 'accessToken:', accessToken ? '有' : '无'); + + const res = await Taro.request({ + url: `${INVITE_API_URL}/api/_app/developer/invite/accept`, + method: 'POST', + data: { + token: inviteToken, + // 已登录用户不传 code,后端从 Authorization 头识别用户 + }, + header: { + 'content-type': 'application/json', + TenantId, + Authorization: `Bearer ${accessToken}`, + }, + }); + + console.log('加入应用接口响应:', res); + + if (res.data.code === 200 || res.data.code === 0) { + Taro.removeStorageSync('invite_token'); + Taro.showToast({ title: '加入成功', icon: 'success', duration: 1500 }); + setTimeout(() => Taro.switchTab({ url: '/pages/index/index' }), 1500); + } else { + throw new Error(res.data.message || '加入失败'); + } + }; + + /** + * 未注册用户调用加入接口(传 code,后端用 code 获取手机号创建用户) + */ + const doJoinAppForNewUser = async (wxCode: string, accessToken: string) => { + const inviteToken = Taro.getStorageSync('invite_token') || token; + console.log('doJoinAppForNewUser, inviteToken:', inviteToken, 'wxCode:', wxCode ? '有' : '无', 'accessToken:', accessToken ? '有' : '无'); const res = await Taro.request({ url: `${INVITE_API_URL}/api/_app/developer/invite/accept`, @@ -431,7 +458,7 @@ const InvitePage: React.FC = () => { boxShadow: '0 0 30px rgba(59, 130, 246, 0.4)', }}> {isLoggedIn ? ( - /* 已登录:普通按钮,不弹手机号授权,用 wx.login() 获取 code */ + /* 已登录:普通按钮,不弹手机号授权,直接调后端 */