Compare commits
23 Commits
f382df7976
...
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 |
@@ -33,7 +33,18 @@
|
||||
"usedAt": 1775720823455,
|
||||
"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": 1775724367075
|
||||
"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端扫码登录)
|
||||
* 调用微信API生成无限制小程序码,返回Base64图片,扫码后直接打开小程序确认页面
|
||||
* 具备自动重试机制:首次失败后清理缓存并重试一次
|
||||
*
|
||||
* @param token 扫码登录token
|
||||
* @param tenantId 租户ID
|
||||
* @return 小程序码图片Base64字符串
|
||||
*/
|
||||
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 {
|
||||
// 获取小程序access_token
|
||||
String accessToken = wxService.getAccessToken(tenantId);
|
||||
String accessToken = forceRefresh
|
||||
? wxService.getAccessTokenForcibly(tenantId) // 强制从微信获取新token
|
||||
: wxService.getAccessToken(tenantId);
|
||||
|
||||
if (StrUtil.isBlank(accessToken)) {
|
||||
log.warn("获取小程序access_token失败,跳过生成小程序码");
|
||||
return null;
|
||||
@@ -142,14 +173,9 @@ public class QrLoginServiceImpl implements QrLoginService {
|
||||
// 小程序端通过 router.params.scene 获取此 token
|
||||
params.put("scene", token);
|
||||
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("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)
|
||||
@@ -166,7 +192,10 @@ public class QrLoginServiceImpl implements QrLoginService {
|
||||
// 检查是否返回JSON错误(微信API错误时会返回JSON)
|
||||
if (imageBytes.length < 100 && new String(imageBytes).startsWith("{")) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
public QrLoginStatusResponse checkQrLoginStatus(String token) {
|
||||
if (StrUtil.isBlank(token)) {
|
||||
|
||||
@@ -628,7 +628,8 @@ public class MainController extends BaseController {
|
||||
|
||||
// 超级管理员验证
|
||||
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 = "验证码不正确";
|
||||
return fail(message, null);
|
||||
}
|
||||
@@ -657,7 +658,9 @@ public class MainController extends BaseController {
|
||||
if(tenantId == 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 = "验证码不正确";
|
||||
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId, request);
|
||||
return fail(message, null);
|
||||
@@ -751,14 +754,8 @@ public class MainController extends BaseController {
|
||||
if (!StrUtil.equals(code, cacheClient.get(phone, String.class))) {
|
||||
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)) {
|
||||
@@ -792,11 +789,12 @@ public class MainController extends BaseController {
|
||||
company.setShortName(tenantName);
|
||||
company.setTenantId(tenant.getTenantId());
|
||||
company.setTemplateId(user.getTemplateId());
|
||||
tenantService.initialization(company);
|
||||
final Company addCompany = tenantService.initialization(company);
|
||||
final UserParam userParam = new UserParam();
|
||||
userParam.setIsAdmin(true);
|
||||
userParam.setPhone(phone);
|
||||
userParam.setTemplateId(user.getTemplateId());
|
||||
userParam.setTenantId(addCompany.getTenantId()); // 使用新创建的租户ID
|
||||
final User adminByPhone = userService.getAdminByPhone(userParam);
|
||||
|
||||
// 设置过期时间
|
||||
@@ -866,7 +864,9 @@ public class MainController extends BaseController {
|
||||
public ApiResult<LoginResult> superAdminRegister(@RequestBody User user) {
|
||||
// 验证签名
|
||||
String tenantName = user.getCompanyName(); // 应用名称
|
||||
String phone = user.getPhone(); // 手机号码
|
||||
// 自动使用当前登录用户的手机号
|
||||
User loginUser = getLoginUser();
|
||||
String phone = loginUser != null ? loginUser.getPhone() : user.getPhone();
|
||||
String password = user.getPassword(); // 密码
|
||||
String code = user.getCode(); // 短信验证码
|
||||
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))) {
|
||||
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)) {
|
||||
@@ -948,6 +942,7 @@ public class MainController extends BaseController {
|
||||
tenant.setPhone(phone);
|
||||
tenant.setTenantCode(CommonUtil.randomUUID16());
|
||||
tenant.setSortNumber(100);
|
||||
tenant.setUserId(getLoginUserId()); // 保存当前登录用户ID
|
||||
tenantService.save(tenant);
|
||||
|
||||
// 租户初始化
|
||||
@@ -979,6 +974,7 @@ public class MainController extends BaseController {
|
||||
userParam1.setIsAdmin(true);
|
||||
userParam1.setPhone(phone);
|
||||
userParam1.setTemplateId(user.getTemplateId());
|
||||
userParam1.setTenantId(addCompany.getTenantId()); // 使用新创建的租户ID
|
||||
final User adminByPhone = userService.getAdminByPhone(userParam1);
|
||||
|
||||
// 设置过期时间
|
||||
|
||||
@@ -53,7 +53,25 @@ public class TenantController extends BaseController {
|
||||
@Operation(summary = "分页查询租户")
|
||||
@GetMapping("/page")
|
||||
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')")
|
||||
@@ -64,7 +82,6 @@ public class TenantController extends BaseController {
|
||||
return success(tenantService.listRel(param));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('sys:tenant:list')")
|
||||
@Operation(summary = "根据id查询租户")
|
||||
@GetMapping("/{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"));
|
||||
// 微信用户的手机号码
|
||||
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;
|
||||
} else {
|
||||
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);
|
||||
|
||||
// 判断是否是 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);
|
||||
}
|
||||
} catch (BusinessException be) {
|
||||
@@ -627,6 +640,25 @@ public class WxLoginController extends BaseController {
|
||||
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)
|
||||
private Object date;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
@TableField(exist = false)
|
||||
private String username;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
@TableField(exist = false)
|
||||
private String phone;
|
||||
@@ -112,7 +116,21 @@ public class Tenant implements Serializable {
|
||||
@TableField(exist = false)
|
||||
private String freeDomain;
|
||||
|
||||
/**
|
||||
* 是否脱敏手机号,默认true脱敏
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "手机号是否脱敏,默认true")
|
||||
private boolean phoneMasked = true;
|
||||
|
||||
public String getPhone(){
|
||||
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;
|
||||
|
||||
@Schema(description = "是否超级管理员")
|
||||
@TableField(exist = false)
|
||||
private Boolean isSuperAdmin;
|
||||
|
||||
@Schema(description = "租户管理员ID")
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
<!-- 关联查询sql -->
|
||||
<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
|
||||
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>
|
||||
<if test="param.tenantId != null">
|
||||
AND a.tenant_id = #{param.tenantId}
|
||||
|
||||
@@ -316,18 +316,15 @@
|
||||
WHERE user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<!-- 根据手机号码查询 -->
|
||||
<!-- 根据手机号码查询(支持多租户:必须传 tenantId 才能查到对应租户的管理员) -->
|
||||
<select id="selectAdminByPhone" resultType="com.gxwebsoft.common.system.entity.User">
|
||||
SELECT a.*
|
||||
FROM sys_user a
|
||||
<where>
|
||||
AND a.deleted = 0
|
||||
AND a.phone = #{param.phone}
|
||||
AND a.template_id = #{param.templateId}
|
||||
AND (a.username = 'superAdmin' OR a.username = 'admin' OR a.is_admin = 1)
|
||||
<if test="param.tenantId">
|
||||
AND a.tenant_id = #{param.tenantId}
|
||||
</if>
|
||||
LIMIT 1
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
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.gxwebsoft.common.core.annotation.QueryField;
|
||||
import com.gxwebsoft.common.core.annotation.QueryType;
|
||||
import com.gxwebsoft.common.core.web.BaseParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@@ -52,4 +49,11 @@ public class TenantParam extends BaseParam {
|
||||
@QueryField(type = QueryType.EQ)
|
||||
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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user