feat(invite): 支持已登录用户直接加入应用
- 后端accept接口code参数改为可选,支持通过Authorization头识别登录用户 - 已登录用户加入时不再弹手机号授权,前端调用新增的doJoinAppForLoggedInUser方法 - 未注册用户仍通过手机号授权码code加入,调用doJoinAppForNewUser方法 - 拆分前端加入逻辑,新增handleConfirmJoin统一处理已登录加入流程 - 优化按钮交互,已登录状态下按钮变为普通点击,移除协议强制勾选 - 错误处理和状态提示完善,加入成功后自动跳转首页
This commit is contained in:
@@ -35,5 +35,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastUpdated": 1775965435808
|
"lastUpdated": 1775966075231
|
||||||
}
|
}
|
||||||
@@ -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` 移除协议检查
|
- `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 按钮
|
||||||
|
|||||||
@@ -129,8 +129,7 @@ const InvitePage: React.FC = () => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 已登录用户:点击「确认加入」
|
* 已登录用户:点击「确认加入」
|
||||||
* 不弹手机号授权,直接用 wx.login() 获取 code 调加入接口
|
* 后端已改造支持已登录用户直接加入(不传 code,从 Authorization 头识别用户)
|
||||||
* 不强制要求勾选协议
|
|
||||||
*/
|
*/
|
||||||
const handleConfirmJoin = async () => {
|
const handleConfirmJoin = async () => {
|
||||||
const accessToken = Taro.getStorageSync('access_token');
|
const accessToken = Taro.getStorageSync('access_token');
|
||||||
@@ -141,10 +140,8 @@ const InvitePage: React.FC = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setAuthLoading(true);
|
setAuthLoading(true);
|
||||||
// 用 wx.login() 获取 code(后端需要 code 识别用户身份)
|
// 已登录用户不传 code,后端从 Authorization 头识别用户
|
||||||
const wxRes = await Taro.login();
|
await doJoinAppForLoggedInUser(accessToken);
|
||||||
if (!wxRes.code) throw new Error('获取微信登录凭证失败');
|
|
||||||
await doJoinApp(wxRes.code, accessToken);
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('确认加入失败:', err);
|
console.error('确认加入失败:', err);
|
||||||
Taro.showToast({ title: err.message || '加入失败,请重试', icon: 'none' });
|
Taro.showToast({ title: err.message || '加入失败,请重试', icon: 'none' });
|
||||||
@@ -209,7 +206,7 @@ const InvitePage: React.FC = () => {
|
|||||||
const wxRes = await Taro.login();
|
const wxRes = await Taro.login();
|
||||||
if (!wxRes.code) throw new Error('获取微信登录凭证失败');
|
if (!wxRes.code) throw new Error('获取微信登录凭证失败');
|
||||||
|
|
||||||
await doJoinApp(wxRes.code, access_token);
|
await doJoinAppForNewUser(wxRes.code, access_token);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('手机号授权登录失败:', err);
|
console.error('手机号授权登录失败:', err);
|
||||||
Taro.showToast({ title: err.message || '授权失败,请重试', icon: 'none' });
|
Taro.showToast({ title: err.message || '授权失败,请重试', icon: 'none' });
|
||||||
@@ -219,13 +216,43 @@ const InvitePage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用加入接口(统一入口)
|
* 已登录用户调用加入接口(不传 code,后端从 Authorization 头识别用户)
|
||||||
* @param wxCode wx.login() 或 getPhoneNumber 获取的 code(后端用于识别用户)
|
|
||||||
* @param accessToken 登录 token(加到 Authorization 头,双重验证)
|
|
||||||
*/
|
*/
|
||||||
const doJoinApp = async (wxCode: string, accessToken: string) => {
|
const doJoinAppForLoggedInUser = async (accessToken: string) => {
|
||||||
const inviteToken = Taro.getStorageSync('invite_token') || token;
|
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({
|
const res = await Taro.request({
|
||||||
url: `${INVITE_API_URL}/api/_app/developer/invite/accept`,
|
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)',
|
boxShadow: '0 0 30px rgba(59, 130, 246, 0.4)',
|
||||||
}}>
|
}}>
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
/* 已登录:普通按钮,不弹手机号授权,用 wx.login() 获取 code */
|
/* 已登录:普通按钮,不弹手机号授权,直接调后端 */
|
||||||
<Button
|
<Button
|
||||||
onClick={handleConfirmJoin}
|
onClick={handleConfirmJoin}
|
||||||
disabled={authLoading}
|
disabled={authLoading}
|
||||||
|
|||||||
Reference in New Issue
Block a user