feat(wx-login): 添加微信登录接口租户支持

- 新增支持指定租户ID的微信登录相关接口
- 添加获取AccessToken和登录接口的重载版本
- 更新文档,增加租户支持相关说明
- 修改内部方法,支持指定租户ID参数
This commit is contained in:
2025-09-05 16:05:28 +08:00
parent 8551386af6
commit 7a332ccd13
2 changed files with 315 additions and 4 deletions

View 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增强错误处理和日志记录

View File

@@ -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");