287 lines
12 KiB
Java
287 lines
12 KiB
Java
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;
|
||
import java.io.File;
|
||
import java.io.IOException;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.security.MessageDigest;
|
||
import java.util.Base64;
|
||
import java.util.Formatter;
|
||
import java.util.HashMap;
|
||
import java.util.Map;
|
||
@Slf4j
|
||
@Component
|
||
public class WechatUtil {
|
||
/**
|
||
* 生成signature
|
||
**/
|
||
private static String appid = "wx9d1cbc11c8c40ba7";
|
||
private static String secret = "38e87cf6251945446e8ac091a0ba9ab2";
|
||
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 +
|
||
"×tamp=" + 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);
|
||
System.out.println("result=============="+result);
|
||
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 包含openid、session_key、unionid的JSON对象
|
||
*/
|
||
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 对象(包含 phoneNumber、purePhoneNumber 等字段)
|
||
* @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);
|
||
}
|
||
}
|
||
|
||
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";
|
||
}
|
||
}
|