feat(wx-login): 添加微信登录接口租户支持
- 新增支持指定租户ID的微信登录相关接口 - 添加获取AccessToken和登录接口的重载版本 - 更新文档,增加租户支持相关说明 - 修改内部方法,支持指定租户ID参数
This commit is contained in:
119
docs/WX_LOGIN_TENANT_SUPPORT.md
Normal file
119
docs/WX_LOGIN_TENANT_SUPPORT.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# 微信登录接口租户支持文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
为了支持多租户场景,我们为微信登录相关接口添加了支持指定租户ID的重载版本。这样可以在跨租户场景下灵活使用微信登录功能。
|
||||||
|
|
||||||
|
## 新增接口
|
||||||
|
|
||||||
|
### 1. 获取AccessToken(支持租户ID)
|
||||||
|
|
||||||
|
**接口地址:** `POST /api/wx-login/getAccessToken/{tenantId}`
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `tenantId`:租户ID(路径参数)
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/wx-login/getAccessToken/123"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 微信授权手机号码并登录(支持租户ID)
|
||||||
|
|
||||||
|
**接口地址:** `POST /api/wx-login/loginByMpWxPhone/{tenantId}`
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `tenantId`:租户ID(路径参数)
|
||||||
|
- 请求体:UserParam对象
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/wx-login/loginByMpWxPhone/123" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"code": "登录凭证code",
|
||||||
|
"authCode": "手机号授权code",
|
||||||
|
"openid": "微信openid(可选)"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 兼容性
|
||||||
|
|
||||||
|
### 原有接口保持不变
|
||||||
|
|
||||||
|
所有原有的接口都保持不变,确保向后兼容:
|
||||||
|
|
||||||
|
- `POST /api/wx-login/getAccessToken` - 使用当前租户ID
|
||||||
|
- `POST /api/wx-login/loginByMpWxPhone` - 使用当前租户ID
|
||||||
|
|
||||||
|
### 内部方法重载
|
||||||
|
|
||||||
|
我们添加了以下内部方法的重载版本:
|
||||||
|
|
||||||
|
1. `getAccessToken(Integer tenantId)` - 支持指定租户ID获取AccessToken
|
||||||
|
2. `getPhoneByCode(UserParam userParam, Integer tenantId)` - 支持指定租户ID获取手机号
|
||||||
|
3. `addUserWithTenant(UserParam userParam, Integer tenantId)` - 支持指定租户ID注册用户
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
### 场景1:管理员代理操作
|
||||||
|
|
||||||
|
管理员需要为特定租户的用户进行微信登录操作:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 前端代码
|
||||||
|
wx.login({
|
||||||
|
success: (loginRes) => {
|
||||||
|
wx.getPhoneNumber({
|
||||||
|
success: (phoneRes) => {
|
||||||
|
wx.request({
|
||||||
|
url: '/api/wx-login/loginByMpWxPhone/123', // 指定租户ID
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
code: loginRes.code,
|
||||||
|
authCode: phoneRes.code
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景2:跨租户数据同步
|
||||||
|
|
||||||
|
在数据同步或迁移场景下,需要为不同租户获取AccessToken:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 后端代码示例
|
||||||
|
String tenant1Token = getAccessToken(123);
|
||||||
|
String tenant2Token = getAccessToken(456);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **租户ID验证**:系统会验证传入的租户ID是否有效
|
||||||
|
2. **权限控制**:确保调用者有权限操作指定租户的数据
|
||||||
|
3. **缓存隔离**:不同租户的AccessToken会分别缓存,互不影响
|
||||||
|
4. **错误处理**:如果指定的租户ID无效,会返回相应的错误信息
|
||||||
|
|
||||||
|
## 错误码
|
||||||
|
|
||||||
|
| 错误码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| 40001 | 租户ID无效 |
|
||||||
|
| 40002 | 无权限操作指定租户 |
|
||||||
|
| 40029 | 微信授权码无效或过期 |
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **前端处理**:建议前端分别获取登录code和手机号code,避免code重复使用
|
||||||
|
2. **错误重试**:当遇到40029错误时,提示用户重新授权
|
||||||
|
3. **日志记录**:系统会记录详细的操作日志,便于问题排查
|
||||||
|
4. **租户隔离**:确保不同租户的数据完全隔离
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
- 2024-12-19:添加租户支持功能
|
||||||
|
- 2024-12-19:修复openid获取失败的问题
|
||||||
|
- 2024-12-19:增强错误处理和日志记录
|
||||||
@@ -77,6 +77,13 @@ public class WxLoginController extends BaseController {
|
|||||||
return success("操作成功", getAccessToken());
|
return success("操作成功", getAccessToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation("获取微信AccessToken(支持指定租户ID)")
|
||||||
|
@Transactional(rollbackFor = {Exception.class})
|
||||||
|
@PostMapping("/getAccessToken/{tenantId}")
|
||||||
|
public ApiResult<String> getMpAccessToken(@PathVariable("tenantId") Integer tenantId) {
|
||||||
|
return success("操作成功", getAccessToken(tenantId));
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOperation("清理并统一AccessToken存储格式")
|
@ApiOperation("清理并统一AccessToken存储格式")
|
||||||
@PostMapping("/cleanAccessTokenFormat")
|
@PostMapping("/cleanAccessTokenFormat")
|
||||||
public ApiResult<String> cleanAccessTokenFormat() {
|
public ApiResult<String> cleanAccessTokenFormat() {
|
||||||
@@ -262,6 +269,152 @@ public class WxLoginController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation("微信授权手机号码并登录(支持指定租户ID)")
|
||||||
|
@Transactional(rollbackFor = {Exception.class})
|
||||||
|
@PostMapping("/loginByMpWxPhone/{tenantId}")
|
||||||
|
public ApiResult<LoginResult> loginByMpWxPhoneWithTenant(@RequestBody UserParam userParam, HttpServletRequest request, @PathVariable("tenantId") Integer tenantId) {
|
||||||
|
System.out.println("接收到的参数: code=" + userParam.getCode() + ", authCode=" + userParam.getAuthCode() + ", openid=" + userParam.getOpenid() + ", tenantId=" + tenantId);
|
||||||
|
|
||||||
|
// 获取手机号码(使用指定的tenantId)
|
||||||
|
String phone = getPhoneByCode(userParam, tenantId);
|
||||||
|
if (phone == null) {
|
||||||
|
String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString());
|
||||||
|
redisTemplate.delete(key);
|
||||||
|
throw new BusinessException("授权失败,请重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = null;
|
||||||
|
|
||||||
|
// 超级管理员验证
|
||||||
|
if (userParam.getIsSuperAdmin() != null) {
|
||||||
|
final LoginParam loginParam = new LoginParam();
|
||||||
|
loginParam.setIsAdmin(true);
|
||||||
|
loginParam.setPhone(phone);
|
||||||
|
final List<User> adminsByPhone = userService.getAdminsByPhone(loginParam);
|
||||||
|
if (!CollectionUtils.isEmpty(adminsByPhone)) {
|
||||||
|
user = adminsByPhone.get(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 先查询管理员用户
|
||||||
|
user = userService.getAdminByPhone(userParam);
|
||||||
|
|
||||||
|
// 如果不是管理员,再查询普通用户(使用指定的tenantId)
|
||||||
|
if (user == null) {
|
||||||
|
user = userService.getByPhone(phone, tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果用户存在,直接登录
|
||||||
|
if (user != null) {
|
||||||
|
System.out.println("用户已存在,直接登录: " + user.getPhone());
|
||||||
|
|
||||||
|
// 检查绑定上级关系
|
||||||
|
if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) {
|
||||||
|
UserReferee check = userRefereeService.check(user.getUserId(), userParam.getRefereeId());
|
||||||
|
if (check == null) {
|
||||||
|
UserReferee userReferee = new UserReferee();
|
||||||
|
userReferee.setDealerId(userParam.getRefereeId());
|
||||||
|
userReferee.setUserId(user.getUserId());
|
||||||
|
userRefereeService.save(userReferee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 签发token
|
||||||
|
String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
|
||||||
|
configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
||||||
|
loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
|
||||||
|
|
||||||
|
return success("登录成功", new LoginResult(access_token, user));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户不存在,注册新用户
|
||||||
|
System.out.println("用户不存在,注册新用户: " + phone);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 确保获取openid - 优先使用已有的openid,否则通过code获取
|
||||||
|
if (StrUtil.isBlank(userParam.getOpenid())) {
|
||||||
|
String codeToUse = null;
|
||||||
|
// 优先使用authCode,如果没有则使用code
|
||||||
|
if (StrUtil.isNotBlank(userParam.getAuthCode())) {
|
||||||
|
codeToUse = userParam.getAuthCode();
|
||||||
|
} else if (StrUtil.isNotBlank(userParam.getCode())) {
|
||||||
|
codeToUse = userParam.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isNotBlank(codeToUse)) {
|
||||||
|
try {
|
||||||
|
System.out.println("尝试使用code获取openid: " + codeToUse);
|
||||||
|
UserParam userParam2 = new UserParam();
|
||||||
|
userParam2.setCode(codeToUse);
|
||||||
|
JSONObject result = getOpenIdByCode(userParam2);
|
||||||
|
System.out.println("获取openid结果: " + result);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
// 检查微信接口是否返回错误
|
||||||
|
Object errcode = result.get("errcode");
|
||||||
|
if (errcode != null && !errcode.equals(0)) {
|
||||||
|
String errmsg = result.getString("errmsg");
|
||||||
|
System.err.println("微信获取openid失败: errcode=" + errcode + ", errmsg=" + errmsg);
|
||||||
|
|
||||||
|
// 如果是code无效错误,提示前端重新获取
|
||||||
|
if (errcode.equals(40029)) {
|
||||||
|
System.err.println("授权码已过期或无效,建议前端重新调用wx.login()获取新的code");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 成功获取openid
|
||||||
|
String openid = result.getString("openid");
|
||||||
|
String unionid = result.getString("unionid");
|
||||||
|
if (StrUtil.isNotBlank(openid)) {
|
||||||
|
userParam.setOpenid(openid);
|
||||||
|
System.out.println("成功获取openid: " + openid);
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(unionid)) {
|
||||||
|
userParam.setUnionid(unionid);
|
||||||
|
System.out.println("成功获取unionid: " + unionid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("获取openid异常,但继续注册流程: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
// 不抛出异常,允许没有openid的情况下继续注册
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("警告:没有提供code或authCode,无法获取openid,将创建没有openid的用户");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("使用已有的openid: " + userParam.getOpenid());
|
||||||
|
}
|
||||||
|
|
||||||
|
userParam.setPhone(phone);
|
||||||
|
// 设置租户ID
|
||||||
|
userParam.setTenantId(tenantId);
|
||||||
|
user = addUserWithTenant(userParam, tenantId);
|
||||||
|
user.setRecommend(1);
|
||||||
|
|
||||||
|
// 签发token
|
||||||
|
String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
|
||||||
|
configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
||||||
|
loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REGISTER, null, user.getTenantId(), request);
|
||||||
|
|
||||||
|
return success("注册并登录成功", new LoginResult(access_token, user));
|
||||||
|
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
// 如果注册时提示手机号已存在,说明存在并发情况,重新查询用户并登录
|
||||||
|
if (e.getMessage().contains("手机号已存在")) {
|
||||||
|
System.out.println("注册时发现手机号已存在,重新查询用户进行登录");
|
||||||
|
user = userService.getByPhone(phone, tenantId);
|
||||||
|
if (user != null) {
|
||||||
|
String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
|
||||||
|
configProperties.getTokenExpireTime(), configProperties.getTokenKey());
|
||||||
|
loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
|
||||||
|
return success("登录成功", new LoginResult(access_token, user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOperation("微信授权手机号码并更新")
|
@ApiOperation("微信授权手机号码并更新")
|
||||||
@Transactional(rollbackFor = {Exception.class})
|
@Transactional(rollbackFor = {Exception.class})
|
||||||
@PostMapping("/updatePhoneByMpWx")
|
@PostMapping("/updatePhoneByMpWx")
|
||||||
@@ -282,6 +435,13 @@ public class WxLoginController extends BaseController {
|
|||||||
* 新用户注册
|
* 新用户注册
|
||||||
*/
|
*/
|
||||||
private User addUser(UserParam userParam) {
|
private User addUser(UserParam userParam) {
|
||||||
|
return addUserWithTenant(userParam, getTenantId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新用户注册(支持指定租户ID)
|
||||||
|
*/
|
||||||
|
private User addUserWithTenant(UserParam userParam, Integer tenantId) {
|
||||||
User addUser = new User();
|
User addUser = new User();
|
||||||
// 注册用户
|
// 注册用户
|
||||||
addUser.setStatus(0);
|
addUser.setStatus(0);
|
||||||
@@ -302,7 +462,7 @@ public class WxLoginController extends BaseController {
|
|||||||
addUser.setUnionid(userParam.getUnionid());
|
addUser.setUnionid(userParam.getUnionid());
|
||||||
}
|
}
|
||||||
addUser.setPassword(userService.encodePassword(CommonUtil.randomUUID16()));
|
addUser.setPassword(userService.encodePassword(CommonUtil.randomUUID16()));
|
||||||
addUser.setTenantId(getTenantId());
|
addUser.setTenantId(tenantId != null ? tenantId : getTenantId());
|
||||||
addUser.setRecommend(1);
|
addUser.setRecommend(1);
|
||||||
Role role = roleService.getOne(new QueryWrapper<Role>().eq("role_code", "user"), false);
|
Role role = roleService.getOne(new QueryWrapper<Role>().eq("role_code", "user"), false);
|
||||||
addUser.setRoleId(role.getRoleId());
|
addUser.setRoleId(role.getRoleId());
|
||||||
@@ -364,6 +524,19 @@ public class WxLoginController extends BaseController {
|
|||||||
* @param userParam 需要传微信凭证code或encryptedData等参数
|
* @param userParam 需要传微信凭证code或encryptedData等参数
|
||||||
*/
|
*/
|
||||||
private String getPhoneByCode(UserParam userParam) {
|
private String getPhoneByCode(UserParam userParam) {
|
||||||
|
return getPhoneByCode(userParam, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信手机号码(支持指定租户ID)
|
||||||
|
* 支持两种方式:
|
||||||
|
* 1. 新版API方式:使用code直接获取手机号
|
||||||
|
* 2. 旧版解密方式:使用encryptedData、iv、sessionKey解密获取手机号
|
||||||
|
*
|
||||||
|
* @param userParam 需要传微信凭证code或encryptedData等参数
|
||||||
|
* @param tenantId 租户ID,为null时使用当前租户ID
|
||||||
|
*/
|
||||||
|
private String getPhoneByCode(UserParam userParam, Integer tenantId) {
|
||||||
// 方式1:如果有encryptedData,使用解密方式获取手机号
|
// 方式1:如果有encryptedData,使用解密方式获取手机号
|
||||||
if (StrUtil.isNotBlank(userParam.getEncryptedData()) &&
|
if (StrUtil.isNotBlank(userParam.getEncryptedData()) &&
|
||||||
StrUtil.isNotBlank(userParam.getIv()) &&
|
StrUtil.isNotBlank(userParam.getIv()) &&
|
||||||
@@ -383,7 +556,7 @@ public class WxLoginController extends BaseController {
|
|||||||
// 方式2:使用新版API方式获取手机号
|
// 方式2:使用新版API方式获取手机号
|
||||||
if (StrUtil.isNotBlank(userParam.getCode())) {
|
if (StrUtil.isNotBlank(userParam.getCode())) {
|
||||||
try {
|
try {
|
||||||
String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken();
|
String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(tenantId);
|
||||||
HashMap<String, Object> paramMap = new HashMap<>();
|
HashMap<String, Object> paramMap = new HashMap<>();
|
||||||
paramMap.put("code", userParam.getCode());
|
paramMap.put("code", userParam.getCode());
|
||||||
// 执行post请求
|
// 执行post请求
|
||||||
@@ -464,7 +637,22 @@ public class WxLoginController extends BaseController {
|
|||||||
* <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">...</a>
|
* <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">...</a>
|
||||||
*/
|
*/
|
||||||
private String getAccessToken() {
|
private String getAccessToken() {
|
||||||
String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString());
|
return getAccessToken(getTenantId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取接口调用凭据AccessToken(支持指定租户ID)
|
||||||
|
* <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">...</a>
|
||||||
|
*
|
||||||
|
* @param tenantId 租户ID
|
||||||
|
* @return access_token
|
||||||
|
*/
|
||||||
|
|
||||||
|
private String getAccessToken(Integer tenantId) {
|
||||||
|
if (tenantId == null) {
|
||||||
|
tenantId = getTenantId();
|
||||||
|
}
|
||||||
|
String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString());
|
||||||
// 获取微信小程序配置信息
|
// 获取微信小程序配置信息
|
||||||
JSONObject setting = settingService.getBySettingKey("mp-weixin");
|
JSONObject setting = settingService.getBySettingKey("mp-weixin");
|
||||||
if (setting == null) {
|
if (setting == null) {
|
||||||
@@ -672,7 +860,11 @@ public class WxLoginController extends BaseController {
|
|||||||
@ApiOperation("获取微信小程序码-订单核销码-数量极多的业务场景")
|
@ApiOperation("获取微信小程序码-订单核销码-数量极多的业务场景")
|
||||||
@GetMapping("/getOrderQRCodeUnlimited/{orderNo}")
|
@GetMapping("/getOrderQRCodeUnlimited/{orderNo}")
|
||||||
public ApiResult<?> getOrderQRCodeUnlimited(@PathVariable("orderNo") String orderNo) {
|
public ApiResult<?> getOrderQRCodeUnlimited(@PathVariable("orderNo") String orderNo) {
|
||||||
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken();
|
final User loginUser = getLoginUser();
|
||||||
|
if(loginUser == null){
|
||||||
|
return fail("请先登录");
|
||||||
|
}
|
||||||
|
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(loginUser.getTenantId());
|
||||||
final HashMap<String, Object> map = new HashMap<>();
|
final HashMap<String, Object> map = new HashMap<>();
|
||||||
map.put("scene", "orderNo=".concat(orderNo));
|
map.put("scene", "orderNo=".concat(orderNo));
|
||||||
map.put("page", "package/admin/order-scan");
|
map.put("page", "package/admin/order-scan");
|
||||||
|
|||||||
Reference in New Issue
Block a user