package com.ruoyi.common.utils;
|
|
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSONObject;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import com.ruoyi.common.utils.http.HttpUtils;
|
|
import java.util.HashMap;
|
import java.util.Map;
|
|
/**
|
* 微信工具类
|
*
|
* @author ruoyi
|
*/
|
public class WechatUtils {
|
|
private static final Logger log = LoggerFactory.getLogger(WechatUtils.class);
|
|
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
|
*
|
* @param appId 微信AppID
|
* @param appSecret 微信AppSecret
|
* @return Access Token
|
*/
|
public static String getAccessToken(String appId, String appSecret) {
|
try {
|
String url = WECHAT_API_BASE_URL_SERVER + "/cgi-bin/token";
|
String param = "grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
|
|
String response = HttpUtils.sendGet(url, param);
|
|
JSONObject jsonObject = JSON.parseObject(response);
|
if (jsonObject.containsKey("access_token")) {
|
return jsonObject.getString("access_token");
|
} else {
|
log.error("获取微信Access Token失败: {}", response);
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("获取微信Access Token异常: {}", e.getMessage());
|
return null;
|
}
|
}
|
|
/**
|
* 获取微信用户信息
|
*
|
* @param accessToken Access Token
|
* @param openid 用户OpenID
|
* @return 用户信息
|
*/
|
public static JSONObject getWechatUserInfo(String accessToken, String openid) {
|
try {
|
String url = WECHAT_API_BASE_URL_SERVER + "/cgi-bin/user/info";
|
String param = "access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
|
|
String response = HttpUtils.sendGet(url, param);
|
|
JSONObject jsonObject = JSON.parseObject(response);
|
if (jsonObject.containsKey("openid")) {
|
return jsonObject;
|
} else {
|
log.error("获取微信用户信息失败: {}", response);
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("获取微信用户信息异常: {}", e.getMessage());
|
return null;
|
}
|
}
|
|
/**
|
* 获取微信网页授权Access Token
|
*
|
* @param appId 微信AppID
|
* @param appSecret 微信AppSecret
|
* @param code 授权码
|
* @return 网页授权Access Token信息
|
*/
|
public static JSONObject getWebAccessToken(String appId, String appSecret, String code) {
|
try {
|
String url = WECHAT_API_BASE_URL_SERVER + "/sns/oauth2/access_token";
|
String param = "appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code";
|
|
String response = HttpUtils.sendGet(url, param);
|
|
JSONObject jsonObject = JSON.parseObject(response);
|
if (jsonObject.containsKey("access_token")) {
|
return jsonObject;
|
} else {
|
log.error("获取微信网页授权Access Token失败: {}", response);
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("获取微信网页授权Access Token异常: {}", e.getMessage());
|
return null;
|
}
|
}
|
|
/**
|
* 获取微信网页授权用户信息
|
*
|
* @param accessToken 网页授权Access Token
|
* @param openid 用户OpenID
|
* @return 用户信息
|
*/
|
public static JSONObject getWebUserInfo(String accessToken, String openid) {
|
try {
|
String url = WECHAT_API_BASE_URL_SERVER + "/sns/userinfo";
|
String param = "access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
|
|
String response = HttpUtils.sendGet(url, param);
|
|
JSONObject jsonObject = JSON.parseObject(response);
|
if (jsonObject.containsKey("openid")) {
|
return jsonObject;
|
} else {
|
log.error("获取微信网页授权用户信息失败: {}", response);
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("获取微信网页授权用户信息异常: {}", e.getMessage());
|
return null;
|
}
|
}
|
|
/**
|
* 生成微信网页授权URL
|
*
|
* @param appId 微信AppID
|
* @param redirectUri 回调地址
|
* @param scope 授权范围 (snsapi_base 或 snsapi_userinfo)
|
* @param state 状态参数
|
* @return 授权URL
|
*/
|
public static String generateAuthUrl(String appId, String redirectUri, String scope, String state) {
|
try {
|
// 清理和验证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;
|
}
|
}
|
|
/**
|
* 判断是否为微信浏览器
|
*
|
* @param userAgent 用户代理字符串
|
* @return 是否为微信浏览器
|
*/
|
public static boolean isWechatBrowser(String userAgent) {
|
if (StringUtils.isEmpty(userAgent)) {
|
return false;
|
}
|
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;
|
}
|
}
|
}
|