Compare commits
25 Commits
bf12ed397c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f7e3cad931 | |||
| 6a48299e12 | |||
| ed9d500e5d | |||
| 64e9674d0e | |||
| 6804a0a824 | |||
| a3c4b74d33 | |||
| c3bd90f234 | |||
| 5579f7494e | |||
| e9532ae4d7 | |||
| 2d012dbd7f | |||
| 5637690424 | |||
| 6cb23a8eee | |||
| e2520001c9 | |||
| f894c53184 | |||
| 5f253695c4 | |||
| 05c67811ed | |||
| 7d562db19c | |||
| 5e66c4c65b | |||
| 5b3363d1ae | |||
| a57eb804eb | |||
| 789b8ddeca | |||
| 7aaf25c1ac | |||
| 1d5b65bcc0 | |||
| f382df7976 | |||
| 9353fb6b85 |
@@ -33,7 +33,18 @@
|
|||||||
"usedAt": 1775720823455,
|
"usedAt": 1775720823455,
|
||||||
"industryId": "all"
|
"industryId": "all"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"11ef16ee251d4624968d1e84c0fb1de9": [
|
||||||
|
{
|
||||||
|
"expertId": "SeniorDeveloper",
|
||||||
|
"name": "Will",
|
||||||
|
"profession": "高级开发工程师",
|
||||||
|
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||||
|
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||||
|
"usedAt": 1775866025894,
|
||||||
|
"industryId": "all"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastUpdated": 1775724063193
|
"lastUpdated": 1775868870779
|
||||||
}
|
}
|
||||||
33
.workbuddy/memory/2026-04-11.md
Normal file
33
.workbuddy/memory/2026-04-11.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 2026-04-11 工作日志
|
||||||
|
|
||||||
|
## 扫码登录 access_token 自动恢复机制
|
||||||
|
|
||||||
|
### 问题背景
|
||||||
|
- `WX_ACCESS_TOKEN:{tenantId}` 缓存过期后,微信 API 返回 40001/42001 等错误
|
||||||
|
- 之前需要手动删除 Redis 缓存才能恢复
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
实现了 access_token 自动清理和重试机制:
|
||||||
|
|
||||||
|
#### 1. QrLoginServiceImpl 改动
|
||||||
|
- `generateMiniprogramQrCode()` 添加重试逻辑
|
||||||
|
- 首次失败 → 清理缓存 → 重试
|
||||||
|
- 新增 `doGenerateMiniprogramQrCode()` 私有方法
|
||||||
|
|
||||||
|
#### 2. WxService 新增方法
|
||||||
|
- `getAccessTokenForcibly(tenantId)` - 强制刷新 token
|
||||||
|
- 先删除 Redis 缓存
|
||||||
|
- 直接从微信 API 获取新 token
|
||||||
|
|
||||||
|
#### 3. WxLoginController 改动
|
||||||
|
- `getPhoneByCode()` 检测 token 相关错误时自动清理缓存
|
||||||
|
- 新增 `isTokenRelatedError()` 方法识别 40001/42001 等错误码
|
||||||
|
|
||||||
|
### 关键文件
|
||||||
|
- `QrLoginServiceImpl.java` - 扫码登录服务
|
||||||
|
- `WxService.java` - 微信公共服务
|
||||||
|
- `WxLoginController.java` - 微信登录控制器
|
||||||
|
|
||||||
|
### 影响范围
|
||||||
|
- 扫码登录生成小程序码 ✅
|
||||||
|
- 小程序手机号授权登录 ✅
|
||||||
6
.workbuddy/memory/2026-04-21.md
Normal file
6
.workbuddy/memory/2026-04-21.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# 2026-04-21 工作日志
|
||||||
|
|
||||||
|
## loginBySms 租户10519特例
|
||||||
|
- 文件:`MainController.java` → `loginBySms` 接口
|
||||||
|
- 变更:普通用户登录时,租户ID=10519 使用硬编码万能验证码 `170083`,跳过从 Redis 读取 `CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS`
|
||||||
|
- 超级管理员路径无需此特例(超管不区分租户)
|
||||||
@@ -120,15 +120,46 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
/**
|
/**
|
||||||
* 生成小程序码(用于PC端扫码登录)
|
* 生成小程序码(用于PC端扫码登录)
|
||||||
* 调用微信API生成无限制小程序码,返回Base64图片,扫码后直接打开小程序确认页面
|
* 调用微信API生成无限制小程序码,返回Base64图片,扫码后直接打开小程序确认页面
|
||||||
|
* 具备自动重试机制:首次失败后清理缓存并重试一次
|
||||||
*
|
*
|
||||||
* @param token 扫码登录token
|
* @param token 扫码登录token
|
||||||
* @param tenantId 租户ID
|
* @param tenantId 租户ID
|
||||||
* @return 小程序码图片Base64字符串
|
* @return 小程序码图片Base64字符串
|
||||||
*/
|
*/
|
||||||
private String generateMiniprogramQrCode(String token, Integer tenantId) {
|
private String generateMiniprogramQrCode(String token, Integer tenantId) {
|
||||||
|
// 构建 access_token 的 Redis key(与 WxService 保持一致)
|
||||||
|
String accessTokenKey = "WX_ACCESS_TOKEN:" + (tenantId != null ? tenantId : 10048);
|
||||||
|
|
||||||
|
// 第一次尝试生成
|
||||||
|
String result = doGenerateMiniprogramQrCode(token, tenantId, accessTokenKey, false);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一次失败,清理缓存并重试(确保下次能拿到最新的 access_token)
|
||||||
|
log.info("小程序码首次生成失败,清理缓存后重试...");
|
||||||
|
clearAccessTokenCache(accessTokenKey, tenantId);
|
||||||
|
|
||||||
|
// 第二次尝试生成(强制刷新 token)
|
||||||
|
return doGenerateMiniprogramQrCode(token, tenantId, accessTokenKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行小程序码生成
|
||||||
|
*
|
||||||
|
* @param token 扫码登录token
|
||||||
|
* @param tenantId 租户ID
|
||||||
|
* @param accessTokenKey access_token 的 Redis key
|
||||||
|
* @param forceRefresh 是否强制刷新 access_token
|
||||||
|
* @return 小程序码 Base64 字符串,失败返回 null
|
||||||
|
*/
|
||||||
|
private String doGenerateMiniprogramQrCode(String token, Integer tenantId, String accessTokenKey, boolean forceRefresh) {
|
||||||
try {
|
try {
|
||||||
// 获取小程序access_token
|
// 获取小程序access_token
|
||||||
String accessToken = wxService.getAccessToken(tenantId);
|
String accessToken = forceRefresh
|
||||||
|
? wxService.getAccessTokenForcibly(tenantId) // 强制从微信获取新token
|
||||||
|
: wxService.getAccessToken(tenantId);
|
||||||
|
|
||||||
if (StrUtil.isBlank(accessToken)) {
|
if (StrUtil.isBlank(accessToken)) {
|
||||||
log.warn("获取小程序access_token失败,跳过生成小程序码");
|
log.warn("获取小程序access_token失败,跳过生成小程序码");
|
||||||
return null;
|
return null;
|
||||||
@@ -142,14 +173,9 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
// 小程序端通过 router.params.scene 获取此 token
|
// 小程序端通过 router.params.scene 获取此 token
|
||||||
params.put("scene", token);
|
params.put("scene", token);
|
||||||
params.put("page", "passport/qr-confirm/index"); // 小程序确认页面路径(子包)
|
params.put("page", "passport/qr-confirm/index"); // 小程序确认页面路径(子包)
|
||||||
params.put("env_version", "develop"); // 正式版小程序 release
|
params.put("env_version", "release"); // release/trial/develop
|
||||||
params.put("width", 280); // 二维码宽度
|
params.put("width", 280); // 二维码宽度
|
||||||
params.put("auto_color", false); // 不自动配置颜色
|
params.put("auto_color", false); // 不自动配置颜色
|
||||||
// HashMap<String, Object> lineColor = new HashMap<>();
|
|
||||||
// lineColor.put("r", 0);
|
|
||||||
// lineColor.put("g", 122);
|
|
||||||
// lineColor.put("b", 255);
|
|
||||||
// params.put("line_color", lineColor); // 二维码颜色
|
|
||||||
|
|
||||||
// 发送请求并获取二进制响应
|
// 发送请求并获取二进制响应
|
||||||
byte[] imageBytes = HttpRequest.post(apiUrl)
|
byte[] imageBytes = HttpRequest.post(apiUrl)
|
||||||
@@ -166,7 +192,10 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
// 检查是否返回JSON错误(微信API错误时会返回JSON)
|
// 检查是否返回JSON错误(微信API错误时会返回JSON)
|
||||||
if (imageBytes.length < 100 && new String(imageBytes).startsWith("{")) {
|
if (imageBytes.length < 100 && new String(imageBytes).startsWith("{")) {
|
||||||
JSONObject errorResult = JSON.parseObject(new String(imageBytes));
|
JSONObject errorResult = JSON.parseObject(new String(imageBytes));
|
||||||
log.error("生成小程序码API返回错误: {}", errorResult);
|
Integer errCode = errorResult.getInteger("errcode");
|
||||||
|
String errMsg = errorResult.getString("errmsg");
|
||||||
|
|
||||||
|
log.error("生成小程序码API返回错误[{}:{}]", errCode, errMsg);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +209,49 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是 token 相关的错误码,需要清理缓存
|
||||||
|
* 常见微信 API 错误码:
|
||||||
|
* - 40001: 获取access_token时AppSecret错误
|
||||||
|
* - 40013: appid无效
|
||||||
|
* - 40125: appsecret无效
|
||||||
|
* - 42001: access_token超时
|
||||||
|
* - 42002: refresh_token超时
|
||||||
|
* - 42003: code超时
|
||||||
|
* - 44002: post body太长
|
||||||
|
* - 44003: 图片太大
|
||||||
|
* - 41002: appid不正确
|
||||||
|
* - 41008: 缺少access_token参数
|
||||||
|
*/
|
||||||
|
private boolean isTokenRelatedError(Integer errCode, String errMsg) {
|
||||||
|
if (errCode == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// token 相关错误码
|
||||||
|
return errCode == 40001 // AppSecret错误
|
||||||
|
|| errCode == 40013 // appid无效
|
||||||
|
|| errCode == 40125 // appsecret无效
|
||||||
|
|| errCode == 42001 // access_token超时
|
||||||
|
|| errCode == 42002 // refresh_token超时
|
||||||
|
|| errCode == 42003 // code超时
|
||||||
|
|| errCode == 41002 // appid不正确
|
||||||
|
|| errCode == 41008 // 缺少access_token参数
|
||||||
|
|| errCode == 40014 // 不合法的access_token
|
||||||
|
|| errCode == 40097; // invalid page
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理 access_token 缓存
|
||||||
|
*/
|
||||||
|
private void clearAccessTokenCache(String accessTokenKey, Integer tenantId) {
|
||||||
|
try {
|
||||||
|
redisUtil.delete(accessTokenKey);
|
||||||
|
log.info("清理微信access_token缓存[{}], tenantId={}", accessTokenKey, tenantId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("清理access_token缓存失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QrLoginStatusResponse checkQrLoginStatus(String token) {
|
public QrLoginStatusResponse checkQrLoginStatus(String token) {
|
||||||
if (StrUtil.isBlank(token)) {
|
if (StrUtil.isBlank(token)) {
|
||||||
|
|||||||
@@ -628,7 +628,8 @@ public class MainController extends BaseController {
|
|||||||
|
|
||||||
// 超级管理员验证
|
// 超级管理员验证
|
||||||
if(isSuperAdmin != null){
|
if(isSuperAdmin != null){
|
||||||
if (!code.equals(redisUtil.get(key)) && !redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS).equals(code)) {
|
String devSmsCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
|
||||||
|
if (!code.equals(redisUtil.get(key)) && !devSmsCode.equals(code)) {
|
||||||
String message = "验证码不正确";
|
String message = "验证码不正确";
|
||||||
return fail(message, null);
|
return fail(message, null);
|
||||||
}
|
}
|
||||||
@@ -657,7 +658,9 @@ public class MainController extends BaseController {
|
|||||||
if(tenantId == null){
|
if(tenantId == null){
|
||||||
return fail("用户不存在",null);
|
return fail("用户不存在",null);
|
||||||
}
|
}
|
||||||
if (!code.equals(redisUtil.get(key)) && !redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS).equals(code)) {
|
// 租户10519特例:使用硬编码万能验证码170083
|
||||||
|
String effectiveDevSmsCode = Integer.valueOf(10519).equals(tenantId) ? "170083" : redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
|
||||||
|
if (!code.equals(redisUtil.get(key)) && !effectiveDevSmsCode.equals(code)) {
|
||||||
String message = "验证码不正确";
|
String message = "验证码不正确";
|
||||||
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId, request);
|
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId, request);
|
||||||
return fail(message, null);
|
return fail(message, null);
|
||||||
@@ -751,14 +754,8 @@ public class MainController extends BaseController {
|
|||||||
if (!StrUtil.equals(code, cacheClient.get(phone, String.class))) {
|
if (!StrUtil.equals(code, cacheClient.get(phone, String.class))) {
|
||||||
throw new BusinessException("验证码不正确");
|
throw new BusinessException("验证码不正确");
|
||||||
}
|
}
|
||||||
// 注册管理员
|
// 注册管理员(已去掉手机号唯一限制,同一手机号可创建多个租户)
|
||||||
final UserParam param = new UserParam();
|
// 重复注册的检查由数据库唯一约束处理
|
||||||
param.setPhone(phone);
|
|
||||||
param.setTemplateId(user.getTemplateId());
|
|
||||||
param.setIsAdmin(true);
|
|
||||||
if (userService.getAdminByPhone(param) != null) {
|
|
||||||
throw new BusinessException("该手机号码已注册");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证租户名称是否重复
|
// 验证租户名称是否重复
|
||||||
if (StrUtil.isNotBlank(tenantName)) {
|
if (StrUtil.isNotBlank(tenantName)) {
|
||||||
@@ -792,11 +789,12 @@ public class MainController extends BaseController {
|
|||||||
company.setShortName(tenantName);
|
company.setShortName(tenantName);
|
||||||
company.setTenantId(tenant.getTenantId());
|
company.setTenantId(tenant.getTenantId());
|
||||||
company.setTemplateId(user.getTemplateId());
|
company.setTemplateId(user.getTemplateId());
|
||||||
tenantService.initialization(company);
|
final Company addCompany = tenantService.initialization(company);
|
||||||
final UserParam userParam = new UserParam();
|
final UserParam userParam = new UserParam();
|
||||||
userParam.setIsAdmin(true);
|
userParam.setIsAdmin(true);
|
||||||
userParam.setPhone(phone);
|
userParam.setPhone(phone);
|
||||||
userParam.setTemplateId(user.getTemplateId());
|
userParam.setTemplateId(user.getTemplateId());
|
||||||
|
userParam.setTenantId(addCompany.getTenantId()); // 使用新创建的租户ID
|
||||||
final User adminByPhone = userService.getAdminByPhone(userParam);
|
final User adminByPhone = userService.getAdminByPhone(userParam);
|
||||||
|
|
||||||
// 设置过期时间
|
// 设置过期时间
|
||||||
@@ -866,7 +864,9 @@ public class MainController extends BaseController {
|
|||||||
public ApiResult<LoginResult> superAdminRegister(@RequestBody User user) {
|
public ApiResult<LoginResult> superAdminRegister(@RequestBody User user) {
|
||||||
// 验证签名
|
// 验证签名
|
||||||
String tenantName = user.getCompanyName(); // 应用名称
|
String tenantName = user.getCompanyName(); // 应用名称
|
||||||
String phone = user.getPhone(); // 手机号码
|
// 自动使用当前登录用户的手机号
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
String phone = loginUser != null ? loginUser.getPhone() : user.getPhone();
|
||||||
String password = user.getPassword(); // 密码
|
String password = user.getPassword(); // 密码
|
||||||
String code = user.getCode(); // 短信验证码
|
String code = user.getCode(); // 短信验证码
|
||||||
String email = user.getEmail(); // 邮箱
|
String email = user.getEmail(); // 邮箱
|
||||||
@@ -919,14 +919,8 @@ public class MainController extends BaseController {
|
|||||||
if (!StrUtil.equals(code, cacheClient.get(phone, String.class)) && !StrUtil.equals(code, redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS))) {
|
if (!StrUtil.equals(code, cacheClient.get(phone, String.class)) && !StrUtil.equals(code, redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS))) {
|
||||||
throw new BusinessException("验证码不正确");
|
throw new BusinessException("验证码不正确");
|
||||||
}
|
}
|
||||||
// 注册管理员
|
// 注册管理员(已去掉手机号唯一限制,同一手机号可创建多个租户)
|
||||||
final UserParam param = new UserParam();
|
// 重复注册的检查由数据库唯一约束处理
|
||||||
param.setPhone(phone);
|
|
||||||
param.setIsAdmin(true);
|
|
||||||
param.setTemplateId(user.getTemplateId());
|
|
||||||
if (userService.getAdminByPhone(param) != null) {
|
|
||||||
throw new BusinessException("该手机号码已注册");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证租户名称是否重复
|
// 验证租户名称是否重复
|
||||||
if (StrUtil.isNotBlank(tenantName)) {
|
if (StrUtil.isNotBlank(tenantName)) {
|
||||||
@@ -948,6 +942,7 @@ public class MainController extends BaseController {
|
|||||||
tenant.setPhone(phone);
|
tenant.setPhone(phone);
|
||||||
tenant.setTenantCode(CommonUtil.randomUUID16());
|
tenant.setTenantCode(CommonUtil.randomUUID16());
|
||||||
tenant.setSortNumber(100);
|
tenant.setSortNumber(100);
|
||||||
|
tenant.setUserId(getLoginUserId()); // 保存当前登录用户ID
|
||||||
tenantService.save(tenant);
|
tenantService.save(tenant);
|
||||||
|
|
||||||
// 租户初始化
|
// 租户初始化
|
||||||
@@ -979,6 +974,7 @@ public class MainController extends BaseController {
|
|||||||
userParam1.setIsAdmin(true);
|
userParam1.setIsAdmin(true);
|
||||||
userParam1.setPhone(phone);
|
userParam1.setPhone(phone);
|
||||||
userParam1.setTemplateId(user.getTemplateId());
|
userParam1.setTemplateId(user.getTemplateId());
|
||||||
|
userParam1.setTenantId(addCompany.getTenantId()); // 使用新创建的租户ID
|
||||||
final User adminByPhone = userService.getAdminByPhone(userParam1);
|
final User adminByPhone = userService.getAdminByPhone(userParam1);
|
||||||
|
|
||||||
// 设置过期时间
|
// 设置过期时间
|
||||||
|
|||||||
@@ -53,7 +53,25 @@ public class TenantController extends BaseController {
|
|||||||
@Operation(summary = "分页查询租户")
|
@Operation(summary = "分页查询租户")
|
||||||
@GetMapping("/page")
|
@GetMapping("/page")
|
||||||
public ApiResult<PageResult<Tenant>> page(TenantParam param) {
|
public ApiResult<PageResult<Tenant>> page(TenantParam param) {
|
||||||
return success(tenantService.pageRel(param));
|
// 如果传了 all=true,查询全部租户;否则自动用当前登录用户的 userId
|
||||||
|
if (param.getAll() == null || !param.getAll()) {
|
||||||
|
if (param.getUserId() == null) {
|
||||||
|
final User loginUser = getLoginUser();
|
||||||
|
if (loginUser != null && loginUser.getUserId() != null) {
|
||||||
|
param.setUserId(loginUser.getUserId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PageResult<Tenant> result = tenantService.pageRel(param);
|
||||||
|
// 如果传入 mask=false,设置不脱敏
|
||||||
|
if (param.getMask() != null && !param.getMask()) {
|
||||||
|
if (result.getList() != null) {
|
||||||
|
for (Tenant tenant : result.getList()) {
|
||||||
|
tenant.setPhoneMasked(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasAuthority('sys:tenant:list')")
|
@PreAuthorize("hasAuthority('sys:tenant:list')")
|
||||||
@@ -64,7 +82,6 @@ public class TenantController extends BaseController {
|
|||||||
return success(tenantService.listRel(param));
|
return success(tenantService.listRel(param));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasAuthority('sys:tenant:list')")
|
|
||||||
@Operation(summary = "根据id查询租户")
|
@Operation(summary = "根据id查询租户")
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public ApiResult<Tenant> get(@PathVariable("id") Integer id) {
|
public ApiResult<Tenant> get(@PathVariable("id") Integer id) {
|
||||||
|
|||||||
@@ -602,16 +602,29 @@ public class WxLoginController extends BaseController {
|
|||||||
JSONObject phoneInfo = JSON.parseObject(json.getString("phone_info"));
|
JSONObject phoneInfo = JSON.parseObject(json.getString("phone_info"));
|
||||||
// 微信用户的手机号码
|
// 微信用户的手机号码
|
||||||
final String phoneNumber = phoneInfo.getString("phoneNumber");
|
final String phoneNumber = phoneInfo.getString("phoneNumber");
|
||||||
// 验证手机号码
|
|
||||||
// if (userParam.getNotVerifyPhone() == null && !Validator.isMobile(phoneNumber)) {
|
|
||||||
// String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString());
|
|
||||||
// redisTemplate.delete(key);
|
|
||||||
// throw new BusinessException("手机号码格式不正确");
|
|
||||||
// }
|
|
||||||
return phoneNumber;
|
return phoneNumber;
|
||||||
} else {
|
} else {
|
||||||
String errorMsg = json.getString("errmsg");
|
String errorMsg = json.getString("errmsg");
|
||||||
|
Integer errCodeInt = null;
|
||||||
|
if (errcode instanceof Integer) {
|
||||||
|
errCodeInt = (Integer) errcode;
|
||||||
|
} else if (errcode instanceof Long) {
|
||||||
|
errCodeInt = ((Long) errcode).intValue();
|
||||||
|
} else if (errcode instanceof String) {
|
||||||
|
try {
|
||||||
|
errCodeInt = Integer.parseInt((String) errcode);
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
System.err.println("微信获取手机号失败: errcode=" + errcode + ", errmsg=" + errorMsg);
|
System.err.println("微信获取手机号失败: errcode=" + errcode + ", errmsg=" + errorMsg);
|
||||||
|
|
||||||
|
// 判断是否是 token 相关错误,如果是则清理缓存
|
||||||
|
if (isTokenRelatedError(errCodeInt, errorMsg)) {
|
||||||
|
String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId != null ? tenantId.toString() : getTenantId().toString());
|
||||||
|
redisTemplate.delete(key);
|
||||||
|
System.err.println("已清理access_token缓存,key=" + key);
|
||||||
|
}
|
||||||
|
|
||||||
throw new BusinessException("获取手机号失败:" + errorMsg);
|
throw new BusinessException("获取手机号失败:" + errorMsg);
|
||||||
}
|
}
|
||||||
} catch (BusinessException be) {
|
} catch (BusinessException be) {
|
||||||
@@ -627,6 +640,25 @@ public class WxLoginController extends BaseController {
|
|||||||
throw new BusinessException("获取手机号失败,请检查参数");
|
throw new BusinessException("获取手机号失败,请检查参数");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是 token 相关的错误码,需要清理缓存
|
||||||
|
*/
|
||||||
|
private boolean isTokenRelatedError(Integer errCode, String errMsg) {
|
||||||
|
if (errCode == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// token 相关错误码
|
||||||
|
return errCode == 40001 // AppSecret错误
|
||||||
|
|| errCode == 40013 // appid无效
|
||||||
|
|| errCode == 40125 // appsecret无效
|
||||||
|
|| errCode == 42001 // access_token超时
|
||||||
|
|| errCode == 42002 // refresh_token超时
|
||||||
|
|| errCode == 42003 // code超时
|
||||||
|
|| errCode == 41002 // appid不正确
|
||||||
|
|| errCode == 41008 // 缺少access_token参数
|
||||||
|
|| errCode == 40014; // 不合法的access_token
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成随机账号
|
* 生成随机账号
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ public class Tenant implements Serializable {
|
|||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private Object date;
|
private Object date;
|
||||||
|
|
||||||
|
@Schema(description = "用户名")
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String username;
|
||||||
|
|
||||||
@Schema(description = "手机号码")
|
@Schema(description = "手机号码")
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private String phone;
|
private String phone;
|
||||||
@@ -112,7 +116,21 @@ public class Tenant implements Serializable {
|
|||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private String freeDomain;
|
private String freeDomain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否脱敏手机号,默认true脱敏
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "手机号是否脱敏,默认true")
|
||||||
|
private boolean phoneMasked = true;
|
||||||
|
|
||||||
public String getPhone(){
|
public String getPhone(){
|
||||||
return DesensitizedUtil.mobilePhone(this.phone);
|
if (phoneMasked) {
|
||||||
|
return DesensitizedUtil.mobilePhone(this.phone);
|
||||||
|
}
|
||||||
|
return this.phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhoneMasked(boolean masked) {
|
||||||
|
this.phoneMasked = masked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,7 +197,6 @@ public class User implements UserDetails {
|
|||||||
private Integer isOrganizationAdmin;
|
private Integer isOrganizationAdmin;
|
||||||
|
|
||||||
@Schema(description = "是否超级管理员")
|
@Schema(description = "是否超级管理员")
|
||||||
@TableField(exist = false)
|
|
||||||
private Boolean isSuperAdmin;
|
private Boolean isSuperAdmin;
|
||||||
|
|
||||||
@Schema(description = "租户管理员ID")
|
@Schema(description = "租户管理员ID")
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
<!-- 关联查询sql -->
|
<!-- 关联查询sql -->
|
||||||
<sql id="selectSql">
|
<sql id="selectSql">
|
||||||
SELECT a.*,b.company_name,b.company_logo as logo,b.admin_url,b.domain,b.free_domain
|
SELECT a.*,b.company_name,b.company_logo as logo,b.admin_url,b.domain,b.free_domain,
|
||||||
|
u.phone,u.username
|
||||||
FROM sys_tenant a
|
FROM sys_tenant a
|
||||||
LEFT JOIN sys_company b ON a.tenant_id = b.tenant_id
|
LEFT JOIN sys_company b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT JOIN gxwebsoft_core.sys_user u ON u.tenant_id = a.tenant_id AND u.is_super_admin = 1 AND u.deleted = 0
|
||||||
<where>
|
<where>
|
||||||
<if test="param.tenantId != null">
|
<if test="param.tenantId != null">
|
||||||
AND a.tenant_id = #{param.tenantId}
|
AND a.tenant_id = #{param.tenantId}
|
||||||
|
|||||||
@@ -316,18 +316,15 @@
|
|||||||
WHERE user_id = #{userId}
|
WHERE user_id = #{userId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 根据手机号码查询 -->
|
<!-- 根据手机号码查询(支持多租户:必须传 tenantId 才能查到对应租户的管理员) -->
|
||||||
<select id="selectAdminByPhone" resultType="com.gxwebsoft.common.system.entity.User">
|
<select id="selectAdminByPhone" resultType="com.gxwebsoft.common.system.entity.User">
|
||||||
SELECT a.*
|
SELECT a.*
|
||||||
FROM sys_user a
|
FROM sys_user a
|
||||||
<where>
|
<where>
|
||||||
AND a.deleted = 0
|
AND a.deleted = 0
|
||||||
AND a.phone = #{param.phone}
|
AND a.phone = #{param.phone}
|
||||||
AND a.template_id = #{param.templateId}
|
|
||||||
AND (a.username = 'superAdmin' OR a.username = 'admin' OR a.is_admin = 1)
|
AND (a.username = 'superAdmin' OR a.username = 'admin' OR a.is_admin = 1)
|
||||||
<if test="param.tenantId">
|
AND a.tenant_id = #{param.tenantId}
|
||||||
AND a.tenant_id = #{param.tenantId}
|
|
||||||
</if>
|
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</where>
|
</where>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
package com.gxwebsoft.common.system.param;
|
package com.gxwebsoft.common.system.param;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.gxwebsoft.common.core.annotation.QueryField;
|
import com.gxwebsoft.common.core.annotation.QueryField;
|
||||||
import com.gxwebsoft.common.core.annotation.QueryType;
|
import com.gxwebsoft.common.core.annotation.QueryType;
|
||||||
import com.gxwebsoft.common.core.web.BaseParam;
|
import com.gxwebsoft.common.core.web.BaseParam;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@@ -52,4 +49,11 @@ public class TenantParam extends BaseParam {
|
|||||||
@QueryField(type = QueryType.EQ)
|
@QueryField(type = QueryType.EQ)
|
||||||
private Integer tenantId;
|
private Integer tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "手机号是否脱敏,默认true")
|
||||||
|
@QueryField(type = QueryType.EQ)
|
||||||
|
private Boolean mask;
|
||||||
|
|
||||||
|
@Schema(description = "查询全部租户,true时忽略userId条件")
|
||||||
|
private Boolean all;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,61 @@ public class WxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制刷新微信AccessToken(先删除缓存,再重新获取)
|
||||||
|
* 用于当 token 过期或失效后,需要强制获取新 token 的场景
|
||||||
|
*
|
||||||
|
* @param tenantId 租户ID,为null时使用默认值
|
||||||
|
* @return access_token
|
||||||
|
*/
|
||||||
|
public String getAccessTokenForcibly(Integer tenantId) {
|
||||||
|
if (tenantId == null) {
|
||||||
|
tenantId = 10048;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = ACCESS_TOKEN_KEY + ":" + tenantId;
|
||||||
|
|
||||||
|
// 先删除缓存
|
||||||
|
redisTemplate.delete(key);
|
||||||
|
log.info("强制刷新access_token,已删除缓存: {}", key);
|
||||||
|
|
||||||
|
// 直接从微信API获取新token(不再检查缓存)
|
||||||
|
try {
|
||||||
|
JSONObject setting = settingService.getBySettingKey("mp-weixin");
|
||||||
|
if (setting == null) {
|
||||||
|
throw new RuntimeException("请先配置微信小程序");
|
||||||
|
}
|
||||||
|
|
||||||
|
String appId = setting.getString("appId");
|
||||||
|
String appSecret = setting.getString("appSecret");
|
||||||
|
if (StrUtil.isBlank(appId) || StrUtil.isBlank(appSecret)) {
|
||||||
|
throw new RuntimeException("微信小程序配置不完整");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用微信API获取AccessToken
|
||||||
|
String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
|
||||||
|
+ appId + "&secret=" + appSecret;
|
||||||
|
String response = HttpRequest.get(apiUrl).execute().body();
|
||||||
|
|
||||||
|
JSONObject result = JSON.parseObject(response);
|
||||||
|
String accessToken = result.getString("access_token");
|
||||||
|
if (StrUtil.isNotBlank(accessToken)) {
|
||||||
|
// 存入缓存
|
||||||
|
JSONObject tokenData = new JSONObject();
|
||||||
|
tokenData.put("access_token", accessToken);
|
||||||
|
tokenData.put("expires_in", result.get("expires_in"));
|
||||||
|
redisTemplate.opsForValue().set(key, tokenData.toJSONString(), 7000L, TimeUnit.SECONDS);
|
||||||
|
log.info("强制刷新access_token成功: {}", accessToken);
|
||||||
|
return accessToken;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("获取AccessToken失败: " + response);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("强制刷新微信AccessToken失败: {}", e.getMessage(), e);
|
||||||
|
throw new RuntimeException("获取微信AccessToken失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取微信公众号 AppID
|
* 获取微信公众号 AppID
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user