feat(wx): 实现小程序码生成自动重试机制
- 在生成小程序码时增加首次失败后清理缓存并重试的逻辑 - 新增强制刷新 access_token 的方法 getAccessTokenForcibly - 优化了获取 access_token 失败的日志提示,不再在错误时清理缓存 - 移除生成小程序码接口对 token 错误时清理缓存的判断及操作 - 移除异常时清理缓存的代码,避免误删除有效缓存 - 调整二维码请求参数,移除注释的颜色配置代码
This commit is contained in:
@@ -46,5 +46,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastUpdated": 1775866842655
|
"lastUpdated": 1775868870779
|
||||||
}
|
}
|
||||||
@@ -120,6 +120,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
/**
|
/**
|
||||||
* 生成小程序码(用于PC端扫码登录)
|
* 生成小程序码(用于PC端扫码登录)
|
||||||
* 调用微信API生成无限制小程序码,返回Base64图片,扫码后直接打开小程序确认页面
|
* 调用微信API生成无限制小程序码,返回Base64图片,扫码后直接打开小程序确认页面
|
||||||
|
* 具备自动重试机制:首次失败后清理缓存并重试一次
|
||||||
*
|
*
|
||||||
* @param token 扫码登录token
|
* @param token 扫码登录token
|
||||||
* @param tenantId 租户ID
|
* @param tenantId 租户ID
|
||||||
@@ -129,13 +130,38 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
// 构建 access_token 的 Redis key(与 WxService 保持一致)
|
// 构建 access_token 的 Redis key(与 WxService 保持一致)
|
||||||
String accessTokenKey = "WX_ACCESS_TOKEN:" + (tenantId != null ? tenantId : 10048);
|
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失败,跳过生成小程序码");
|
||||||
// 获取失败时清理缓存,下次会重新获取
|
|
||||||
clearAccessTokenCache(accessTokenKey, tenantId);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,11 +176,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
params.put("env_version", "release"); // release/trial/develop
|
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)
|
||||||
@@ -174,15 +195,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
Integer errCode = errorResult.getInteger("errcode");
|
Integer errCode = errorResult.getInteger("errcode");
|
||||||
String errMsg = errorResult.getString("errmsg");
|
String errMsg = errorResult.getString("errmsg");
|
||||||
|
|
||||||
// 判断是否是 token 相关错误,需要清理缓存
|
log.error("生成小程序码API返回错误[{}:{}]", errCode, errMsg);
|
||||||
boolean shouldClearCache = isTokenRelatedError(errCode, errMsg);
|
|
||||||
|
|
||||||
if (shouldClearCache) {
|
|
||||||
log.error("生成小程序码API返回token相关错误[{}:{}],将清理缓存", errCode, errMsg);
|
|
||||||
clearAccessTokenCache(accessTokenKey, tenantId);
|
|
||||||
} else {
|
|
||||||
log.error("生成小程序码API返回错误[{}:{}],不清理缓存", errCode, errMsg);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,8 +205,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
|||||||
return "data:image/png;base64," + base64Image;
|
return "data:image/png;base64," + base64Image;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("生成小程序码异常: {}", e.getMessage(), e);
|
log.error("生成小程序码异常: {}", e.getMessage(), e);
|
||||||
// 异常时也清理缓存,以防是 token 问题
|
|
||||||
clearAccessTokenCache(accessTokenKey, tenantId);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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