no message
This commit is contained in:
21
src/main/java/com/sczx/pay/utils/AlipayApiCallback.java
Normal file
21
src/main/java/com/sczx/pay/utils/AlipayApiCallback.java
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Alipay.com Inc.
|
||||
* Copyright (c) 2004-2024 All Rights Reserved.
|
||||
*/
|
||||
package com.sczx.pay.utils;
|
||||
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.AlipayResponse;
|
||||
|
||||
/**
|
||||
* @author jishupei.jsp
|
||||
* @version : AlipayApiCallback, v0.1 2024年03月27日 5:46 下午 jishupei.jsp Exp $
|
||||
*/
|
||||
public interface AlipayApiCallback<T, R extends AlipayResponse> {
|
||||
|
||||
R process() throws AlipayApiException;
|
||||
|
||||
T getData(R response);
|
||||
|
||||
String getApiName();
|
||||
}
|
||||
50
src/main/java/com/sczx/pay/utils/AlipayApiTemplate.java
Normal file
50
src/main/java/com/sczx/pay/utils/AlipayApiTemplate.java
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Alipay.com Inc.
|
||||
* Copyright (c) 2004-2024 All Rights Reserved.
|
||||
*/
|
||||
package com.sczx.pay.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.AlipayResponse;
|
||||
import com.sczx.pay.alipay.vo.OpenResponse;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* @author jishupei.jsp
|
||||
* @version : AlipayApiTemplate, v0.1 2024年03月27日 5:48 下午 jishupei.jsp Exp $
|
||||
*/
|
||||
public class AlipayApiTemplate {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(AlipayApiTemplate.class);
|
||||
|
||||
public static <T, R extends AlipayResponse> OpenResponse<T> execute(AlipayApiCallback<T, R> callback) {
|
||||
try {
|
||||
//执行
|
||||
R response = callback.process();
|
||||
OpenResponse<T> tOpenResponse = new OpenResponse<>(response);
|
||||
if (response.isSuccess()) {
|
||||
//获取data
|
||||
T data = callback.getData(response);
|
||||
tOpenResponse.setData(data);
|
||||
logger.info(callback.getApiName() + "调用成功:" + response.getBody());
|
||||
} else {
|
||||
logger.error(callback.getApiName() + "调用失败:" + JSON.toJSONString(response));
|
||||
}
|
||||
return tOpenResponse;
|
||||
} catch (AlipayApiException e) {
|
||||
//异常处理
|
||||
logger.error(callback.getApiName() + "调用失败", e);
|
||||
return new OpenResponse<>(e);
|
||||
} catch (Exception e) {
|
||||
//异常处理
|
||||
logger.error(callback.getApiName() + "调用失败", e);
|
||||
OpenResponse<T> response = new OpenResponse<>();
|
||||
response.setCode("SYSTEM_ERROR");
|
||||
response.setMsg("系统错误");
|
||||
response.setSubMsg(e.getMessage());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
242
src/main/java/com/sczx/pay/utils/AlipaySdkUtil.java
Normal file
242
src/main/java/com/sczx/pay/utils/AlipaySdkUtil.java
Normal file
@ -0,0 +1,242 @@
|
||||
package com.sczx.pay.utils;
|
||||
|
||||
import com.alipay.api.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Configuration
|
||||
public class AlipaySdkUtil {
|
||||
|
||||
private AlipayClient alipayClient;
|
||||
|
||||
@Value("${alipay.appid}")
|
||||
private String appId;
|
||||
|
||||
// 应用私钥
|
||||
@Value("${alipay.privatekey}")
|
||||
private String privateKey;
|
||||
|
||||
// 商户ID(alipay.pid)
|
||||
@Value("${alipay.pid}")
|
||||
private String pid;
|
||||
|
||||
// 是否使用OpenId(alipay.use_open_id)
|
||||
@Value("${alipay.use_open_id}")
|
||||
private boolean useOpenId;
|
||||
|
||||
// 使用OpenId时必填(alipay.openid)
|
||||
@Value("${alipay.openid}")
|
||||
private String openid;
|
||||
|
||||
// 用户ID,和OpenId二选一(alipay.userid)
|
||||
@Value("${alipay.userid}")
|
||||
private String userid;
|
||||
|
||||
// 是否是服务商(alipay.is_isv)
|
||||
@Value("${alipay.is_isv}")
|
||||
private boolean isIsv;
|
||||
|
||||
// 服务商必填(alipay.app_auth_token)
|
||||
@Value("${alipay.app_auth_token}")
|
||||
private String appAuthToken;
|
||||
|
||||
@Value("${alipay.alipay-public-cert-path}")
|
||||
private String alipayPublicCertPath;
|
||||
|
||||
@Value("${alipay.ali-public-cert-path}")
|
||||
private String aliPublicCertPath;
|
||||
|
||||
@Value("${alipay.alipay-root-cert-path}")
|
||||
private String alipayRootCertPath;
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws AlipayApiException {
|
||||
// 初始化v2 SDK
|
||||
this.alipayClient = new DefaultAlipayClient(getAlipayConfig());
|
||||
}
|
||||
|
||||
public <T extends AlipayResponse> T execute(AlipayRequest<T> request) throws AlipayApiException {
|
||||
if (isIsv) {
|
||||
return alipayClient.certificateExecute(request, null, appAuthToken);
|
||||
}
|
||||
return alipayClient.certificateExecute(request);
|
||||
}
|
||||
|
||||
private AlipayConfig getAlipayConfig() {
|
||||
AlipayConfig alipayConfig = new AlipayConfig();
|
||||
alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
|
||||
alipayConfig.setAppId(appId);
|
||||
alipayConfig.setPrivateKey(privateKey);
|
||||
//设置应用公钥证书路径
|
||||
alipayConfig.setAppCertPath(alipayPublicCertPath);
|
||||
//设置支付宝公钥证书路径
|
||||
alipayConfig.setAlipayPublicCertPath(aliPublicCertPath);
|
||||
//设置支付宝根证书路径
|
||||
alipayConfig.setRootCertPath(alipayRootCertPath);
|
||||
alipayConfig.setFormat("json");
|
||||
alipayConfig.setCharset("UTF-8");
|
||||
alipayConfig.setSignType("RSA2");
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("alipay-sdk-demo", "app-item-0.0.1");
|
||||
alipayConfig.setCustomHeaders(headers);
|
||||
|
||||
|
||||
|
||||
return alipayConfig;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>pid</tt>.
|
||||
*
|
||||
* @return property value of pid
|
||||
*/
|
||||
public String getPid() {
|
||||
return pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>pid</tt>.
|
||||
*
|
||||
* @param pid value to be assigned to property pid
|
||||
*/
|
||||
public void setPid(String pid) {
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>appId</tt>.
|
||||
*
|
||||
* @return property value of appId
|
||||
*/
|
||||
public String getAppId() {
|
||||
return appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>appId</tt>.
|
||||
*
|
||||
* @param appId value to be assigned to property appId
|
||||
*/
|
||||
public void setAppId(String appId) {
|
||||
this.appId = appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>openid</tt>.
|
||||
*
|
||||
* @return property value of openid
|
||||
*/
|
||||
public String getOpenid() {
|
||||
return openid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>openid</tt>.
|
||||
*
|
||||
* @param openid value to be assigned to property openid
|
||||
*/
|
||||
public void setOpenid(String openid) {
|
||||
this.openid = openid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>userid</tt>.
|
||||
*
|
||||
* @return property value of userid
|
||||
*/
|
||||
public String getUserid() {
|
||||
return userid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>userid</tt>.
|
||||
*
|
||||
* @param userid value to be assigned to property userid
|
||||
*/
|
||||
public void setUserid(String userid) {
|
||||
this.userid = userid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>useOpenId</tt>.
|
||||
*
|
||||
* @return property value of useOpenId
|
||||
*/
|
||||
public boolean isUseOpenId() {
|
||||
return useOpenId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>useOpenId</tt>.
|
||||
*
|
||||
* @param useOpenId value to be assigned to property useOpenId
|
||||
*/
|
||||
public void setUseOpenId(boolean useOpenId) {
|
||||
this.useOpenId = useOpenId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>isIsv</tt>.
|
||||
*
|
||||
* @return property value of isIsv
|
||||
*/
|
||||
public boolean isv() {
|
||||
return isIsv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>isIsv</tt>.
|
||||
*
|
||||
* @param isv value to be assigned to property isIsv
|
||||
*/
|
||||
public void setIsv(boolean isv) {
|
||||
isIsv = isv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>appAuthToken</tt>.
|
||||
*
|
||||
* @return property value of appAuthToken
|
||||
*/
|
||||
public String getAppAuthToken() {
|
||||
return appAuthToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>appAuthToken</tt>.
|
||||
*
|
||||
* @param appAuthToken value to be assigned to property appAuthToken
|
||||
*/
|
||||
public void setAppAuthToken(String appAuthToken) {
|
||||
this.appAuthToken = appAuthToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>privateKey</tt>.
|
||||
*
|
||||
* @return property value of privateKey
|
||||
*/
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>privateKey</tt>.
|
||||
*
|
||||
* @param privateKey value to be assigned to property privateKey
|
||||
*/
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
159
src/main/java/com/sczx/pay/utils/ComputerInfo.java
Normal file
159
src/main/java/com/sczx/pay/utils/ComputerInfo.java
Normal file
@ -0,0 +1,159 @@
|
||||
package com.sczx.pay.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/*
|
||||
* <取网卡物理地址--
|
||||
* 1.在Windows,Linux系统下均可用;
|
||||
* 2.通过ipconifg,ifconfig获得计算机信息;
|
||||
* 3.再用模式匹配方式查找MAC地址,与操作系统的语言无关>
|
||||
*
|
||||
* //* Description: <取计算机名--从环境变量中取>
|
||||
* abstract 限制继承/创建实例
|
||||
*/
|
||||
public abstract class ComputerInfo {
|
||||
private static String macAddressStr = null;
|
||||
private static String computerName = System.getenv().get("COMPUTERNAME");
|
||||
|
||||
private static final String[] windowsCommand = { "ipconfig", "/all" };
|
||||
private static final String[] linuxCommand = { "/sbin/ifconfig", "-a" };
|
||||
private static final String[] macCommand = { "ifconfig", "-a" };
|
||||
private static final Pattern macPattern = Pattern.compile(".*((:?[0-9a-f]{2}[-:]){5}[0-9a-f]{2}).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* 获取多个网卡地址
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private final static List<String> getMacAddressList() throws IOException {
|
||||
final ArrayList<String> macAddressList = new ArrayList<String>();
|
||||
final String os = System.getProperty("os.name");
|
||||
final String command[];
|
||||
|
||||
if (os.startsWith("Windows")) {
|
||||
command = windowsCommand;
|
||||
} else if (os.startsWith("Linux")) {
|
||||
command = linuxCommand;
|
||||
} else if (os.startsWith("Mac")){
|
||||
command = macCommand;
|
||||
}
|
||||
else {
|
||||
throw new IOException("Unknow operating system:" + os);
|
||||
}
|
||||
// 执行命令
|
||||
final Process process = Runtime.getRuntime().exec(command);
|
||||
|
||||
BufferedReader bufReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
for (String line = null; (line = bufReader.readLine()) != null;) {
|
||||
Matcher matcher = macPattern.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
macAddressList.add(matcher.group(1));
|
||||
// macAddressList.add(matcher.group(1).replaceAll("[-:]",
|
||||
// ""));//去掉MAC中的“-”
|
||||
}
|
||||
}
|
||||
|
||||
process.destroy();
|
||||
bufReader.close();
|
||||
return macAddressList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个网卡地址(多个网卡时从中获取一个)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getMacAddress() {
|
||||
if (macAddressStr == null || macAddressStr.equals("")) {
|
||||
StringBuffer sb = new StringBuffer(); // 存放多个网卡地址用,目前只取一个非0000000000E0隧道的值
|
||||
try {
|
||||
List<String> macList = getMacAddressList();
|
||||
for (Iterator<String> iter = macList.iterator(); iter.hasNext();) {
|
||||
String amac = iter.next();
|
||||
if (!"0000000000E0".equals(amac)) {
|
||||
sb.append(amac);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
macAddressStr = sb.toString();
|
||||
|
||||
}
|
||||
|
||||
return macAddressStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取电脑名
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getComputerName() {
|
||||
if (computerName == null || computerName.equals("")) {
|
||||
computerName = System.getenv().get("COMPUTERNAME");
|
||||
}
|
||||
return computerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getIpAddrAndName() throws IOException {
|
||||
return InetAddress.getLocalHost().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getIpAddr() throws IOException {
|
||||
return InetAddress.getLocalHost().getHostAddress().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取电脑唯一标识
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getComputerID() {
|
||||
String id = getMacAddress();
|
||||
if (id == null || id.equals("")) {
|
||||
try {
|
||||
id = getIpAddrAndName();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return computerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制创建实例
|
||||
*/
|
||||
private ComputerInfo() {
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.out.println(ComputerInfo.getMacAddress());
|
||||
System.out.println(ComputerInfo.getComputerName());
|
||||
System.out.println(ComputerInfo.getIpAddr());
|
||||
System.out.println(ComputerInfo.getIpAddrAndName());
|
||||
}
|
||||
}
|
||||
135
src/main/java/com/sczx/pay/utils/IPUtils.java
Normal file
135
src/main/java/com/sczx/pay/utils/IPUtils.java
Normal file
@ -0,0 +1,135 @@
|
||||
package com.sczx.pay.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* IP地址工具类
|
||||
*/
|
||||
public class IPUtils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(IPUtils.class);
|
||||
|
||||
// 多个IP查询服务,以防某个服务不可用
|
||||
private static final String[] IP_SERVICES = {
|
||||
"https://api.ipify.org",
|
||||
"https://icanhazip.com",
|
||||
"https://ident.me"
|
||||
};
|
||||
|
||||
private static String cachedPublicIP = null;
|
||||
private static long lastFetchTime = 0;
|
||||
private static final long CACHE_DURATION = 30 * 60 * 1000; // 30分钟缓存
|
||||
|
||||
/**
|
||||
* 获取服务器公网IP地址
|
||||
* @return 公网IP地址
|
||||
*/
|
||||
public static String getServerPublicIP() {
|
||||
// 检查缓存
|
||||
if (cachedPublicIP != null && (System.currentTimeMillis() - lastFetchTime) < CACHE_DURATION) {
|
||||
return cachedPublicIP;
|
||||
}
|
||||
|
||||
// 首先尝试从外部服务获取公网IP
|
||||
String publicIP = fetchPublicIPFromExternalService();
|
||||
if (publicIP != null && !publicIP.isEmpty()) {
|
||||
cachedPublicIP = publicIP;
|
||||
lastFetchTime = System.currentTimeMillis();
|
||||
return publicIP;
|
||||
}
|
||||
|
||||
// 如果外部服务不可用,则获取本地IP
|
||||
try {
|
||||
String localIP = InetAddress.getLocalHost().getHostAddress();
|
||||
logger.warn("无法获取公网IP,使用本地IP: {}", localIP);
|
||||
return localIP;
|
||||
} catch (UnknownHostException e) {
|
||||
logger.error("获取本地IP失败", e);
|
||||
return "127.0.0.1";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从外部服务获取公网IP
|
||||
* @return 公网IP地址
|
||||
*/
|
||||
private static String fetchPublicIPFromExternalService() {
|
||||
for (String serviceUrl : IP_SERVICES) {
|
||||
try {
|
||||
String ip = fetchIPFromService(serviceUrl);
|
||||
if (isValidIP(ip)) {
|
||||
logger.info("成功从 {} 获取公网IP: {}", serviceUrl, ip);
|
||||
return ip;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("从服务 {} 获取IP失败: {}", serviceUrl, e.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定服务获取IP
|
||||
* @param serviceUrl 服务URL
|
||||
* @return IP地址
|
||||
* @throws Exception 网络异常
|
||||
*/
|
||||
private static String fetchIPFromService(String serviceUrl) throws Exception {
|
||||
URL url = new URL(serviceUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(connection.getInputStream()))) {
|
||||
return reader.readLine().trim();
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IP地址是否有效
|
||||
* @param ip IP地址
|
||||
* @return 是否有效
|
||||
*/
|
||||
private static boolean isValidIP(String ip) {
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 简单的IP格式验证
|
||||
String[] parts = ip.split("\\.");
|
||||
if (parts.length != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String part : parts) {
|
||||
try {
|
||||
int num = Integer.parseInt(part);
|
||||
if (num < 0 || num > 255) {
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 排除私有IP和回环地址
|
||||
if (ip.startsWith("127.") || ip.startsWith("10.") ||
|
||||
ip.startsWith("192.168.") || ip.startsWith("172.")) {
|
||||
// 私有地址也可以接受,因为可能在内网环境中使用
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
243
src/main/java/com/sczx/pay/utils/WXPayUtil.java
Normal file
243
src/main/java/com/sczx/pay/utils/WXPayUtil.java
Normal file
@ -0,0 +1,243 @@
|
||||
package com.sczx.pay.utils;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import java.io.StringReader;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import java.security.Security;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
|
||||
public class WXPayUtil {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WXPayUtil.class);
|
||||
private static final String CHARSET = "UTF-8";
|
||||
private static final SecureRandom random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
*/
|
||||
public static String generateNonceStr() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*/
|
||||
public static String generateSignature(Map<String, String> data, String key) throws Exception {
|
||||
return generateSignature(data, key, "MD5");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*/
|
||||
public static String generateSignature(Map<String, String> data, String key, String signType) throws Exception {
|
||||
Set<String> keySet = data.keySet();
|
||||
String[] keyArray = keySet.toArray(new String[0]);
|
||||
Arrays.sort(keyArray);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String k : keyArray) {
|
||||
if (k.equals("sign")) {
|
||||
continue;
|
||||
}
|
||||
String value = data.get(k);
|
||||
if (value != null && value.trim().length() > 0) {
|
||||
sb.append(k).append("=").append(value.trim()).append("&");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("key=").append(key);
|
||||
|
||||
if ("MD5".equals(signType)) {
|
||||
return DigestUtils.md5Hex(sb.toString()).toUpperCase();
|
||||
} else if ("HMAC-SHA256".equals(signType)) {
|
||||
return HMACSHA256(sb.toString(), key);
|
||||
} else {
|
||||
throw new Exception("Invalid sign_type: " + signType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HMAC-SHA256签名
|
||||
*/
|
||||
private static String HMACSHA256(String data, String key) throws Exception {
|
||||
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(CHARSET), "HmacSHA256");
|
||||
sha256_HMAC.init(secret_key);
|
||||
byte[] array = sha256_HMAC.doFinal(data.getBytes(CHARSET));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte item : array) {
|
||||
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
|
||||
}
|
||||
return sb.toString().toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
*/
|
||||
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
|
||||
if (!data.containsKey("sign")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String sign = data.get("sign");
|
||||
String calculatedSign = generateSignature(data, key);
|
||||
return sign.equals(calculatedSign);
|
||||
}
|
||||
|
||||
/**
|
||||
* XML字符串转换为Map
|
||||
*/
|
||||
public static Map<String, String> xmlToMap(String strXML) throws Exception {
|
||||
Map<String, String> data = new HashMap<>();
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document doc = documentBuilder.parse(new ByteArrayInputStream(strXML.getBytes(CHARSET)));
|
||||
|
||||
doc.getDocumentElement().normalize();
|
||||
NodeList nodeList = doc.getDocumentElement().getChildNodes();
|
||||
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||
Element element = (Element) node;
|
||||
data.put(element.getNodeName(), element.getTextContent());
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map转换为XML字符串
|
||||
*/
|
||||
public static String mapToXml(Map<String, String> data) throws Exception {
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
Document document = documentBuilder.newDocument();
|
||||
|
||||
Element root = document.createElement("xml");
|
||||
document.appendChild(root);
|
||||
|
||||
for (String key : data.keySet()) {
|
||||
String value = data.get(key);
|
||||
if (value == null) {
|
||||
value = "";
|
||||
}
|
||||
|
||||
Element filed = document.createElement(key);
|
||||
filed.appendChild(document.createTextNode(value));
|
||||
root.appendChild(filed);
|
||||
}
|
||||
|
||||
TransformerFactory tf = TransformerFactory.newInstance();
|
||||
Transformer transformer = tf.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
DOMSource source = new DOMSource(document);
|
||||
StringWriter writer = new StringWriter();
|
||||
StreamResult result = new StreamResult(writer);
|
||||
transformer.transform(source, result);
|
||||
|
||||
return writer.getBuffer().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证微信支付通知签名
|
||||
*/
|
||||
public static boolean isSignatureValid(String xmlString, String key) throws Exception {
|
||||
Map<String, String> data = xmlToMap(xmlString);
|
||||
return isSignatureValid(data, key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将XML字符串转换为指定类型的实体对象
|
||||
*
|
||||
* @param xmlString XML字符串
|
||||
* @param clazz 实体类Class
|
||||
* @param <T> 实体类型
|
||||
* @return 实体对象
|
||||
* @throws Exception 转换异常
|
||||
*/
|
||||
public static <T> T xmlToEntity(String xmlString, Class<T> clazz) throws Exception {
|
||||
JAXBContext context = JAXBContext.newInstance(clazz);
|
||||
Unmarshaller unmarshaller = context.createUnmarshaller();
|
||||
return clazz.cast(unmarshaller.unmarshal(new StringReader(xmlString)));
|
||||
}
|
||||
|
||||
|
||||
public static String decryptReqInfo(String reqInfo, String apiKey) throws Exception {
|
||||
try {
|
||||
// 步骤1: 对加密串A做base64解码,得到加密串B
|
||||
byte[] encryptedData = Base64.decodeBase64(reqInfo);
|
||||
|
||||
// 步骤2: 对商户key做md5,得到32位小写key*
|
||||
String key = DigestUtils.md5Hex(apiKey).toLowerCase();
|
||||
|
||||
// 步骤3: 用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
|
||||
byte[] decryptedData = decrypt(encryptedData, key);
|
||||
|
||||
return new String(decryptedData, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
logger.error("解密微信退款通知req_info失败", e);
|
||||
throw new Exception("解密失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密方法
|
||||
*
|
||||
* @param encryptedData 加密数据
|
||||
* @param key 解密密钥
|
||||
* @return 解密后的字节数组
|
||||
* @throws Exception 解密异常
|
||||
*/
|
||||
private static byte[] decrypt(byte[] encryptedData, String key) throws Exception {
|
||||
try {
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
|
||||
return cipher.doFinal(encryptedData);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
|
||||
IllegalBlockSizeException | BadPaddingException e) {
|
||||
logger.error("AES解密失败", e);
|
||||
throw new Exception("AES解密失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user