| | |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.web.client.RestTemplate; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | |
| | | |
| | | private static final Logger log = LoggerFactory.getLogger(WechatUtils.class); |
| | | |
| | | private static final String WECHAT_API_BASE_URL = "https://api.weixin.qq.com"; |
| | | private static final String WECHAT_API_BASE_URL = "https://open.weixin.qq.com"; |
| | | private static final String WECHAT_API_BASE_URL_SERVER = "https://api.weixin.qq.com"; |
| | | |
| | | /** |
| | | * 获取微信Access Token |
| | |
| | | */ |
| | | public static String getAccessToken(String appId, String appSecret) { |
| | | try { |
| | | String url = WECHAT_API_BASE_URL + "/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret; |
| | | String url = WECHAT_API_BASE_URL_SERVER + "/cgi-bin/token"; |
| | | String param = "grant_type=client_credential&appid=" + appId + "&secret=" + appSecret; |
| | | |
| | | RestTemplate restTemplate = new RestTemplate(); |
| | | String response = restTemplate.getForObject(url, String.class); |
| | | String response = HttpUtils.sendGet(url, param); |
| | | |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject.containsKey("access_token")) { |
| | |
| | | */ |
| | | public static JSONObject getWechatUserInfo(String accessToken, String openid) { |
| | | try { |
| | | String url = WECHAT_API_BASE_URL + "/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN"; |
| | | String url = WECHAT_API_BASE_URL_SERVER + "/cgi-bin/user/info"; |
| | | String param = "access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN"; |
| | | |
| | | RestTemplate restTemplate = new RestTemplate(); |
| | | String response = restTemplate.getForObject(url, String.class); |
| | | String response = HttpUtils.sendGet(url, param); |
| | | |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject.containsKey("openid")) { |
| | |
| | | */ |
| | | public static JSONObject getWebAccessToken(String appId, String appSecret, String code) { |
| | | try { |
| | | String url = WECHAT_API_BASE_URL + "/sns/oauth2/access_token?appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code"; |
| | | String url = WECHAT_API_BASE_URL_SERVER + "/sns/oauth2/access_token"; |
| | | String param = "appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code"; |
| | | |
| | | RestTemplate restTemplate = new RestTemplate(); |
| | | String response = restTemplate.getForObject(url, String.class); |
| | | String response = HttpUtils.sendGet(url, param); |
| | | |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject.containsKey("access_token")) { |
| | |
| | | */ |
| | | public static JSONObject getWebUserInfo(String accessToken, String openid) { |
| | | try { |
| | | String url = WECHAT_API_BASE_URL + "/sns/userinfo?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN"; |
| | | String url = WECHAT_API_BASE_URL_SERVER + "/sns/userinfo"; |
| | | String param = "access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN"; |
| | | |
| | | RestTemplate restTemplate = new RestTemplate(); |
| | | String response = restTemplate.getForObject(url, String.class); |
| | | String response = HttpUtils.sendGet(url, param); |
| | | |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject.containsKey("openid")) { |
| | |
| | | */ |
| | | public static String generateAuthUrl(String appId, String redirectUri, String scope, String state) { |
| | | try { |
| | | String encodedRedirectUri = java.net.URLEncoder.encode(redirectUri, "UTF-8"); |
| | | return WECHAT_API_BASE_URL + "/connect/oauth2/authorize?appid=" + appId + "&redirect_uri=" + encodedRedirectUri + "&response_type=code&scope=" + scope + "&state=" + state + "#wechat_redirect"; |
| | | // 清理和验证redirectUri |
| | | String cleanRedirectUri = cleanRedirectUri(redirectUri); |
| | | |
| | | // URL编码 |
| | | String encodedRedirectUri = java.net.URLEncoder.encode(cleanRedirectUri, "UTF-8"); |
| | | |
| | | return WECHAT_API_BASE_URL + "/connect/oauth2/authorize?appid=" + appId + |
| | | "&redirect_uri=" + encodedRedirectUri + |
| | | "&response_type=code&scope=" + scope + |
| | | "&state=" + state + "#wechat_redirect"; |
| | | } catch (Exception e) { |
| | | log.error("生成微信授权URL异常: {}", e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 清理和验证redirectUri |
| | | * |
| | | * @param redirectUri 原始回调地址 |
| | | * @return 清理后的回调地址 |
| | | */ |
| | | private static String cleanRedirectUri(String redirectUri) { |
| | | if (StringUtils.isEmpty(redirectUri)) { |
| | | return redirectUri; |
| | | } |
| | | |
| | | try { |
| | | // 移除端口号(微信授权不支持端口号) |
| | | if (redirectUri.contains(":81") || redirectUri.contains(":8080") || redirectUri.contains(":3000")) { |
| | | redirectUri = redirectUri.replaceAll(":(81|8080|3000|8081|8082|8083|8084|8085|8086|8087|8088|8089|8090)", ""); |
| | | log.warn("检测到端口号,已自动移除: {}", redirectUri); |
| | | } |
| | | |
| | | // 确保使用HTTPS(生产环境) |
| | | if (redirectUri.startsWith("http://") && !redirectUri.contains("localhost") && !redirectUri.contains("127.0.0.1")) { |
| | | redirectUri = redirectUri.replace("http://", "https://"); |
| | | log.warn("生产环境建议使用HTTPS,已自动转换: {}", redirectUri); |
| | | } |
| | | |
| | | return redirectUri; |
| | | } catch (Exception e) { |
| | | log.error("清理redirectUri异常: {}", e.getMessage()); |
| | | return redirectUri; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | return userAgent.toLowerCase().contains("micromessenger"); |
| | | } |
| | | |
| | | /** |
| | | * 截断thing类型字段,微信订阅消息thing类型最长20个字符 |
| | | * |
| | | * @param value 原始字符串 |
| | | * @return 截断后的字符串(最长20字符) |
| | | */ |
| | | public static String truncateThingValue(String value) { |
| | | if (StringUtils.isEmpty(value)) { |
| | | return value; |
| | | } |
| | | |
| | | // 微信thing类型最长20个字符 |
| | | final int MAX_THING_LENGTH = 20; |
| | | |
| | | if (value.length() <= MAX_THING_LENGTH) { |
| | | return value; |
| | | } |
| | | |
| | | // 截断并添加省略号,确保总长度不超过20 |
| | | return value.substring(0, MAX_THING_LENGTH - 1) + "…"; |
| | | } |
| | | |
| | | /** |
| | | * 发送小程序订阅消息 |
| | | * |
| | | * @param accessToken 微信接口调用凭证 |
| | | * @param touser 接收人openId |
| | | * @param templateId 模板ID |
| | | * @param page 小程序跳转页面 |
| | | * @param data 模板数据,key为字段名(如thing1、time27),value为字段值 |
| | | * @return 微信返回结果JSON |
| | | */ |
| | | public static JSONObject sendSubscribeMessage(String accessToken, |
| | | String touser, |
| | | String templateId, |
| | | String page, |
| | | Map<String, String> data) { |
| | | try { |
| | | if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(touser) || StringUtils.isEmpty(templateId)) { |
| | | log.error("发送订阅消息参数不完整,accessToken={}, touser={}, templateId={}", accessToken, touser, templateId); |
| | | return null; |
| | | } |
| | | |
| | | String url = WECHAT_API_BASE_URL_SERVER + "/cgi-bin/message/subscribe/send?access_token=" + accessToken; |
| | | |
| | | Map<String, Object> body = new HashMap<>(); |
| | | body.put("touser", touser); |
| | | body.put("template_id", templateId); |
| | | if (StringUtils.isNotEmpty(page)) { |
| | | body.put("page", page); |
| | | } |
| | | body.put("miniprogram_state", "formal"); |
| | | |
| | | Map<String, Object> dataNode = new HashMap<>(); |
| | | if (data != null && !data.isEmpty()) { |
| | | for (Map.Entry<String, String> entry : data.entrySet()) { |
| | | Map<String, String> valueNode = new HashMap<>(); |
| | | valueNode.put("value", entry.getValue()); |
| | | dataNode.put(entry.getKey(), valueNode); |
| | | } |
| | | } |
| | | body.put("data", dataNode); |
| | | |
| | | String jsonBody = JSON.toJSONString(body); |
| | | String response = HttpUtils.sendPost(url, jsonBody); |
| | | |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject == null) { |
| | | log.error("发送订阅消息返回为空"); |
| | | return null; |
| | | } |
| | | if (jsonObject.getIntValue("errcode") != 0) { |
| | | log.error("发送订阅消息失败: {}", jsonObject.toJSONString()); |
| | | } else { |
| | | log.info("发送订阅消息成功,touser={}, templateId={}", touser, templateId); |
| | | } |
| | | return jsonObject; |
| | | } catch (Exception e) { |
| | | log.error("发送订阅消息异常: {}", e.getMessage(), e); |
| | | return null; |
| | | } |
| | | } |
| | | } |