From a57eb804eb832f2c6c3b437b3d854fe15e7f7cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sat, 11 Apr 2026 08:56:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(wx):=20=E5=AE=9E=E7=8E=B0=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=A0=81=E7=94=9F=E6=88=90=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在生成小程序码时增加首次失败后清理缓存并重试的逻辑 - 新增强制刷新 access_token 的方法 getAccessTokenForcibly - 优化了获取 access_token 失败的日志提示,不再在错误时清理缓存 - 移除生成小程序码接口对 token 错误时清理缓存的判断及操作 - 移除异常时清理缓存的代码,避免误删除有效缓存 - 调整二维码请求参数,移除注释的颜色配置代码 --- .workbuddy/expert-history.json | 2 +- .../auto/service/impl/QrLoginServiceImpl.java | 51 ++++++++++------- .../common/system/service/WxService.java | 55 +++++++++++++++++++ 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json index 0766328..32de93b 100644 --- a/.workbuddy/expert-history.json +++ b/.workbuddy/expert-history.json @@ -46,5 +46,5 @@ } ] }, - "lastUpdated": 1775866842655 + "lastUpdated": 1775868870779 } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java index 2abb00d..4cd3e76 100644 --- a/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java +++ b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java @@ -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 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; } } diff --git a/src/main/java/com/gxwebsoft/common/system/service/WxService.java b/src/main/java/com/gxwebsoft/common/system/service/WxService.java index eb3e9f9..d112b88 100644 --- a/src/main/java/com/gxwebsoft/common/system/service/WxService.java +++ b/src/main/java/com/gxwebsoft/common/system/service/WxService.java @@ -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 */