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端扫码登录)
|
||||
* 调用微信API生成无限制小程序码,返回Base64图片,扫码后直接打开小程序确认页面
|
||||
* 具备自动重试机制:首次失败后清理缓存并重试一次
|
||||
*
|
||||
* @param token 扫码登录token
|
||||
* @param tenantId 租户ID
|
||||
@@ -129,13 +130,38 @@ public class QrLoginServiceImpl implements QrLoginService {
|
||||
// 构建 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失败,跳过生成小程序码,将清理缓存");
|
||||
// 获取失败时清理缓存,下次会重新获取
|
||||
clearAccessTokenCache(accessTokenKey, tenantId);
|
||||
log.warn("获取小程序access_token失败,跳过生成小程序码");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -150,11 +176,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
||||
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)
|
||||
@@ -174,15 +195,7 @@ public class QrLoginServiceImpl implements QrLoginService {
|
||||
Integer errCode = errorResult.getInteger("errcode");
|
||||
String errMsg = errorResult.getString("errmsg");
|
||||
|
||||
// 判断是否是 token 相关错误,需要清理缓存
|
||||
boolean shouldClearCache = isTokenRelatedError(errCode, errMsg);
|
||||
|
||||
if (shouldClearCache) {
|
||||
log.error("生成小程序码API返回token相关错误[{}:{}],将清理缓存", errCode, errMsg);
|
||||
clearAccessTokenCache(accessTokenKey, tenantId);
|
||||
} else {
|
||||
log.error("生成小程序码API返回错误[{}:{}],不清理缓存", errCode, errMsg);
|
||||
}
|
||||
log.error("生成小程序码API返回错误[{}:{}]", errCode, errMsg);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -192,8 +205,6 @@ public class QrLoginServiceImpl implements QrLoginService {
|
||||
return "data:image/png;base64," + base64Image;
|
||||
} catch (Exception e) {
|
||||
log.error("生成小程序码异常: {}", e.getMessage(), e);
|
||||
// 异常时也清理缓存,以防是 token 问题
|
||||
clearAccessTokenCache(accessTokenKey, tenantId);
|
||||
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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user