Files
ks/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/WechatUtil.java

287 lines
12 KiB
Java
Raw Normal View History

2025-09-22 17:06:47 +08:00
package com.ruoyi.cms.util;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
2025-09-22 17:06:47 +08:00
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
2025-09-22 17:06:47 +08:00
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class WechatUtil {
/**
* 生成signature
**/
2025-10-20 15:14:44 +08:00
private static String appid = "wx9d1cbc11c8c40ba7";
private static String secret = "38e87cf6251945446e8ac091a0ba9ab2";
2025-09-22 17:06:47 +08:00
public AppWechatEntity sign(String url) {
Map<String, String> ret = new HashMap();
String nonceStr = create_nonce_str();
String timestamp = Long.toString(create_timestamp());
String string1;
String signature = "";
//获取jsapi_ticket和过期时间
AppWechatEntity appWechatEntity = getJsapiTicket();
String jsapiTicket = appWechatEntity.getJsapi_ticket();
Long expireTime = appWechatEntity.getExpire_time();
//注意这里参数名必须全部小写,且必须有序
string1 = "jsapi_ticket=" + jsapiTicket +
"&noncestr=" + nonceStr +
"&timestamp=" + timestamp +
"&url=" + url;
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes(StandardCharsets.UTF_8));
signature = byteToHex(crypt.digest());
} catch (Exception e) {
e.printStackTrace();
}
appWechatEntity.setAppId(appid);
appWechatEntity.setExpire_time(expireTime);
appWechatEntity.setNonceStr(nonceStr);
appWechatEntity.setTimestamp(timestamp);
appWechatEntity.setSignature(signature);
return appWechatEntity;
}
/**
* 获取jsapi_ticket
**/
public AppWechatEntity getJsapiTicket() {
//logger.debug("--------------开始执行getJsapiTicket方法--------------");
//定义过期时间
AppWechatEntity appWechatEntity = new AppWechatEntity();
String accessTokenString = "";
String jsapiTicketString = "";
String jsapi_ticket = "";
String access_token = "";
jsapiTicketString = readWechatTokenFile(getJsapiTicketFilePath());
if (!StringUtils.isEmpty(jsapiTicketString)) {
appWechatEntity = JSONObject.parseObject(jsapiTicketString, AppWechatEntity.class);
long expireTime = appWechatEntity.getExpire_time();
long curTime = create_timestamp();
if (expireTime >= curTime && StrUtil.isNotEmpty(appWechatEntity.getJsapi_ticket())) {
//logger.debug("已有的jsapi_ticket=" + jsapi_ticket);
return appWechatEntity;
}
}
long timestamp = create_timestamp() + 7000;//过期时间是2小时(7200s)
access_token =
getAccessTokenData("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid+ "&secret=" + secret);
Map accessTokenMap = new HashMap();
accessTokenMap.put("expire_time", timestamp);
accessTokenMap.put("access_token", access_token);
accessTokenString = JSONObject.toJSONString(accessTokenMap);
jsapi_ticket = getJsapiTicketData("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi");
Map jsapiTicketMap = new HashMap();
jsapiTicketMap.put("expire_time", timestamp);
jsapiTicketMap.put("jsapi_ticket", jsapi_ticket);
jsapiTicketString = JSONObject.toJSONString(jsapiTicketMap);
// 写文件
try {
FileUtils.writeStringToFile(new File(getAccessTokenFilePath()), accessTokenString, CharsetUtil.CHARSET_UTF_8);
FileUtils.writeStringToFile(new File(getJsapiTicketFilePath()), jsapiTicketString, CharsetUtil.CHARSET_UTF_8);
//logger.debug("写入文件成功");
} catch (IOException e) {
log.debug("写文件异常:" + e.getMessage());
e.printStackTrace();
}
appWechatEntity.setJsapi_ticket(jsapi_ticket);
appWechatEntity.setExpire_time(timestamp);
appWechatEntity.setAccess_token(access_token);
//logger.debug("--------------结束执行getJsapiTicket方法--------------");
return appWechatEntity;
}
public String getAccessToken() {
//logger.debug("--------------开始执行getAccessToken方法--------------");
String access_token = "";
String accessTokenString = "";
AppWechatEntity appWechatEntity = new AppWechatEntity();
accessTokenString = readWechatTokenFile(getAccessTokenFilePath());
if (StringUtils.isNotEmpty(accessTokenString)) {
appWechatEntity = JSONObject.parseObject(accessTokenString, AppWechatEntity.class);
access_token = appWechatEntity.getAccess_token();
long expireTime = appWechatEntity.getExpire_time();
long curTime = create_timestamp();
if (expireTime >= curTime) {
//logger.debug("已有的access_token=" + access_token);
return access_token;
}
}
long timestamp = create_timestamp() + 6000;//过期时间是2小时但是可以提前进行更新防止前端正好过期
access_token =
getAccessTokenData("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret);
Map accessTokenMap = new HashMap();
accessTokenMap.put("expire_time", timestamp);
accessTokenMap.put("access_token", access_token);
accessTokenString = JSONObject.toJSONString(accessTokenMap);
// 写文件
try {
FileUtils.writeStringToFile(new File(getAccessTokenFilePath()), accessTokenString, CharsetUtil.CHARSET_UTF_8);
} catch (IOException e) {
log.debug("写文件异常:" + e.getMessage());
e.printStackTrace();
}
//logger.debug("新的access_token=" + access_token);
//logger.debug("--------------结束执行getAccessToken方法--------------");
return access_token;
}
private String readWechatTokenFile(String filePath) {
String content = "";
try {
if (new File(filePath).exists()) {
FileReader fileReader = new FileReader(filePath, CharsetUtil.CHARSET_UTF_8);
content = fileReader.readString();
} else {
new File(filePath).createNewFile();
}
} catch (IOException e) {
log.error("读文件异常:" + e.getMessage());
e.printStackTrace();
}
return content;
}
private String getAccessTokenData(String url) {
String str = "";
String result = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8);
2025-10-20 15:14:44 +08:00
System.out.println("result=============="+result);
2025-09-22 17:06:47 +08:00
if (StringUtils.isEmpty(result))
return str;
str = parseData("access_token", "expires_in", result);
return str;
}
private String getJsapiTicketData(String url) {
String str = "";
String result = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8);
if (StringUtils.isEmpty(result))
return str;
str = parseData("ticket", "expires_in", result);
return str;
}
private String parseData(String tokenName, String expiresInName, String data) {
String tokenConent = "";
JSONObject jsonObject = JSONObject.parseObject(data);
try {
tokenConent = jsonObject.get(tokenName).toString();
if (StringUtils.isEmpty(tokenConent)) {
log.error("token获取失败,获取结果" + data);
return tokenConent;
}
} catch (Exception e) {
log.error("token 结果解析失败token参数名称: " + tokenName + "有效期参数名称:" + expiresInName + "token请求结果:" + data);
e.printStackTrace();
return tokenConent;
}
return tokenConent;
}
private String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
/**
* 通过code获取微信用户的openid和session_key
*
* @param code 登录凭证code
* @return 包含openidsession_keyunionid的JSON对象
*/
2025-10-20 15:14:44 +08:00
public JSONObject code2Session(String code) {
try {
String response = getAccessTokenData("https://api.weixin.qq.com/sns/jscode2session?appid="+appid+"&secret="+secret+"&js_code="+code+"&grant_type=authorization_code");
JSONObject result = JSONObject.parseObject(response);
// 微信返回错误码处理
if (result.containsKey("errcode") && result.getInteger("errcode") != 0) {
throw new RuntimeException("微信授权失败:" + result.getString("errmsg"));
}
return result;
} catch (Exception e) {
throw new RuntimeException("调用微信接口失败:" + e.getMessage());
}
}
/**
* 解密微信用户手机号用户通过 getPhoneNumber 组件授权后返回的加密数据
* @param encryptedData 微信返回的加密手机号数据
* @param sessionKey code2Session 接口获取的会话密钥
* @param iv 微信返回的加密向量 encryptedData 配套
* @return 解密后的 JSON 对象包含 phoneNumberpurePhoneNumber 等字段
* @throws RuntimeException 解密失败时抛出
*/
public JSONObject decryptPhoneNumber(String encryptedData, String sessionKey, String iv) {
try {
// 1. Base64 解码encryptedData、sessionKey、iv 均为 Base64 编码)
byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedData);
byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey);
byte[] ivBytes = Base64.getDecoder().decode(iv);
// 2. 初始化 AES-128-CBC 解密器(微信固定加密算法)
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec keySpec = new SecretKeySpec(sessionKeyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// 3. 执行解密并转换为字符串
byte[] decryptedBytes = cipher.doFinal(encryptedDataBytes);
String decryptedStr = new String(decryptedBytes, StandardCharsets.UTF_8);
// 4. 解析为 JSON 并返回(包含手机号等信息)
return JSONObject.parseObject(decryptedStr);
} catch (Exception e) {
throw new RuntimeException("解密用户手机号失败:" + e.getMessage(), e);
}
}
2025-09-22 17:06:47 +08:00
private String create_nonce_str() {
return IdUtil.simpleUUID();
}
private Long create_timestamp() {
return System.currentTimeMillis() / 1000;
}
private String getJsapiTicketFilePath() {
return "/data/wechat" + "/" +appid + "_jsapiTicket.txt";
}
private String getAccessTokenFilePath() {
return "/data/wechat" + "/" + appid + "_accessToken.txt";
}
}