package com.ruoyi.system.service.impl; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.service.IQyWechatAccessTokenService; import com.ruoyi.system.service.ISysConfigService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * 企业微信AccessToken服务实现类 * * @author ruoyi * @date 2025-12-11 */ @Service public class QyWechatAccessTokenServiceImpl implements IQyWechatAccessTokenService { private static final Logger log = LoggerFactory.getLogger(QyWechatAccessTokenServiceImpl.class); @Autowired private ISysConfigService configService; /** * 企业微信获取access_token的URL */ private static final String GET_ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"; /** * 获取企业微信应用的AccessToken * * @param corpId 企业ID * @param secret 应用密钥或小程序密钥 * @return AccessToken */ @Override public String getAppAccessToken(String corpId, String secret) { try { // 参数校验 if (StringUtils.isEmpty(corpId) || StringUtils.isEmpty(secret)) { log.warn("企业微信配置参数不完整,corpId或secret为空"); return null; } // 检查服务是否启用 if (!isEnabled()) { log.info("企业微信服务已禁用,无法获取AccessToken"); return null; } // 构建配置键名 String tokenKey = "qy_wechat.access_token." + corpId; String expiresKey = "qy_wechat.access_token_expires." + corpId; // 从配置中获取Token和过期时间 String accessToken = configService.selectConfigByKey(tokenKey); String expiresStr = configService.selectConfigByKey(expiresKey); // 检查Token是否存在且未过期 if (StringUtils.isNotEmpty(accessToken) && StringUtils.isNotEmpty(expiresStr)) { try { long expiresTime = Long.parseLong(expiresStr); long currentTime = System.currentTimeMillis(); // 预留60秒安全边界,避免临界点过期 if (currentTime < expiresTime - 60000) { log.debug("使用缓存的企业微信AccessToken,剩余有效时间: {}秒", (expiresTime - currentTime) / 1000); return accessToken; } else { log.info("企业微信AccessToken已过期或即将过期,需要刷新"); } } catch (NumberFormatException e) { log.warn("解析企业微信AccessToken过期时间失败: {}", expiresStr); } } // Token不存在或已过期,刷新Token return refreshAppAccessToken(corpId, secret); } catch (Exception e) { log.error("获取企业微信AccessToken失败", e); return null; } } /** * 刷新企业微信应用的AccessToken * * @param corpId 企业ID * @param secret 应用密钥或小程序密钥 * @return 新的AccessToken */ @Override public String refreshAppAccessToken(String corpId, String secret) { try { log.info("开始刷新企业微信AccessToken"); // 构建请求URL String url = GET_ACCESS_TOKEN_URL + "?corpid=" + corpId + "&corpsecret=" + secret; // 发送HTTP请求获取Token String response = sendHttpGetRequest(url); if (StringUtils.isEmpty(response)) { log.error("获取企业微信AccessToken失败,响应为空"); return null; } // 解析响应 QyWechatTokenResponse tokenResponse = parseTokenResponse(response); if (tokenResponse == null || StringUtils.isEmpty(tokenResponse.getAccessToken())) { log.error("解析企业微信AccessToken响应失败: {}", response); return null; } // 计算过期时间(当前时间 + 有效期 - 60秒安全边界) long expiresTime = System.currentTimeMillis() + (tokenResponse.getExpiresIn() * 1000L) - 60000L; // 构建配置键名 String tokenKey = "qy_wechat.access_token." + corpId; String expiresKey = "qy_wechat.access_token_expires." + corpId; // 保存到系统配置表 configService.updateConfigValue(tokenKey, tokenResponse.getAccessToken()); configService.updateConfigValue(expiresKey, String.valueOf(expiresTime)); log.info("企业微信AccessToken刷新成功,有效期: {}秒", tokenResponse.getExpiresIn()); return tokenResponse.getAccessToken(); } catch (Exception e) { log.error("刷新企业微信AccessToken失败", e); return null; } } /** * 获取企业微信小程序的AccessToken * * @param corpId 企业ID * @param corpSecret 小程序密钥 * @return AccessToken */ @Override public String getQyMiniAccessToken(String corpId, String corpSecret) { try { // 参数校验 if (StringUtils.isEmpty(corpId) || StringUtils.isEmpty(corpSecret)) { log.warn("企业微信小程序配置参数不完整,corpId或corpSecret为空"); return null; } // 检查服务是否启用 if (!isEnabled()) { log.info("企业微信服务已禁用,无法获取小程序AccessToken"); return null; } // 构建配置键名(使用不同的键名以区分普通应用和小程序) String tokenKey = "qy_wechat.mini_access_token." + corpId; String expiresKey = "qy_wechat.mini_access_token_expires." + corpId; // 从配置中获取Token和过期时间 String accessToken = configService.selectConfigByKey(tokenKey); String expiresStr = configService.selectConfigByKey(expiresKey); // 检查Token是否存在且未过期 if (StringUtils.isNotEmpty(accessToken) && StringUtils.isNotEmpty(expiresStr)) { try { long expiresTime = Long.parseLong(expiresStr); long currentTime = System.currentTimeMillis(); // 预留60秒安全边界,避免临界点过期 if (currentTime < expiresTime - 60000) { log.debug("使用缓存的企业微信小程序AccessToken,剩余有效时间: {}秒", (expiresTime - currentTime) / 1000); return accessToken; } else { log.info("企业微信小程序AccessToken已过期或即将过期,需要刷新"); } } catch (NumberFormatException e) { log.warn("解析企业微信小程序AccessToken过期时间失败: {}", expiresStr); } } // Token不存在或已过期,刷新Token return refreshQyMiniAccessToken(corpId, corpSecret); } catch (Exception e) { log.error("获取企业微信小程序AccessToken失败", e); return null; } } /** * 刷新企业微信小程序的AccessToken * * @param corpId 企业ID * @param corpSecret 小程序密钥 * @return 新的AccessToken */ public String refreshQyMiniAccessToken(String corpId, String corpSecret) { try { log.info("开始刷新企业微信小程序AccessToken"); // 构建请求URL String url = GET_ACCESS_TOKEN_URL + "?corpid=" + corpId + "&corpsecret=" + corpSecret; // 发送HTTP请求获取Token String response = sendHttpGetRequest(url); if (StringUtils.isEmpty(response)) { log.error("获取企业微信小程序AccessToken失败,响应为空"); return null; } // 解析响应 QyWechatTokenResponse tokenResponse = parseTokenResponse(response); if (tokenResponse == null || StringUtils.isEmpty(tokenResponse.getAccessToken())) { log.error("解析企业微信小程序AccessToken响应失败: {}", response); return null; } // 计算过期时间(当前时间 + 有效期 - 60秒安全边界) long expiresTime = System.currentTimeMillis() + (tokenResponse.getExpiresIn() * 1000L) - 60000L; // 构建配置键名(使用不同的键名以区分普通应用和小程序) String tokenKey = "qy_wechat.mini_access_token." + corpId; String expiresKey = "qy_wechat.mini_access_token_expires." + corpId; // 保存到系统配置表 configService.updateConfigValue(tokenKey, tokenResponse.getAccessToken()); configService.updateConfigValue(expiresKey, String.valueOf(expiresTime)); log.info("企业微信小程序AccessToken刷新成功,有效期: {}秒", tokenResponse.getExpiresIn()); return tokenResponse.getAccessToken(); } catch (Exception e) { log.error("刷新企业微信小程序AccessToken失败", e); return null; } } /** * 检查企业微信服务是否启用 * * @return true-启用,false-禁用 */ @Override public boolean isEnabled() { try { String enabled = configService.selectConfigByKey("qy_wechat.enable"); return !"false".equals(enabled); // 默认启用 } catch (Exception e) { log.warn("获取企业微信服务启用状态失败,使用默认值(true)", e); return true; } } /** * 发送HTTP GET请求 * * @param url 请求URL * @return 响应内容 */ private String sendHttpGetRequest(String url) { try { java.net.HttpURLConnection conn = (java.net.HttpURLConnection) new java.net.URL(url).openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); int responseCode = conn.getResponseCode(); if (responseCode == 200) { java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader(conn.getInputStream(), "UTF-8")); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); return response.toString(); } else { log.error("HTTP请求失败,响应码: {}", responseCode); return null; } } catch (Exception e) { log.error("发送HTTP请求失败", e); return null; } } /** * 解析企业微信Token响应 * * @param response 响应JSON * @return Token响应对象 */ private QyWechatTokenResponse parseTokenResponse(String response) { try { // 简单JSON解析(实际项目中建议使用Jackson或Gson) // 示例响应: {"errcode":0,"errmsg":"ok","access_token":"xxxxxx","expires_in":7200} // 移除花括号 String content = response.substring(1, response.length() - 1); // 按逗号分割 String[] pairs = content.split(","); QyWechatTokenResponse tokenResponse = new QyWechatTokenResponse(); for (String pair : pairs) { String[] keyValue = pair.split(":"); if (keyValue.length == 2) { String key = keyValue[0].trim().replaceAll("\"", ""); String value = keyValue[1].trim().replaceAll("\"", ""); switch (key) { case "errcode": tokenResponse.setErrcode(Integer.parseInt(value)); break; case "errmsg": tokenResponse.setErrmsg(value); break; case "access_token": tokenResponse.setAccessToken(value); break; case "expires_in": tokenResponse.setExpiresIn(Integer.parseInt(value)); break; } } } // 检查是否有错误 if (tokenResponse.getErrcode() != 0) { log.error("获取企业微信AccessToken失败,错误码: {}, 错误信息: {}", tokenResponse.getErrcode(), tokenResponse.getErrmsg()); return null; } return tokenResponse; } catch (Exception e) { log.error("解析企业微信Token响应失败: {}", response, e); return null; } } /** * 企业微信Token响应内部类 */ private static class QyWechatTokenResponse { private int errcode; private String errmsg; private String accessToken; private int expiresIn; // Getters and Setters public int getErrcode() { return errcode; } public void setErrcode(int errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } public String getAccessToken() { return accessToken; } public void setAccessToken(String accessToken) { this.accessToken = accessToken; } public int getExpiresIn() { return expiresIn; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } } }