From 6ad4cccb2a43015b10c30ee7934588df0aea71aa Mon Sep 17 00:00:00 2001 From: quyixiao <2621048238@qq.com> Date: Fri, 7 Nov 2025 13:17:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/heyu/api/jsapi/JsapiPrepay.java | 14 ++ .../java/com/heyu/api/jsapi/dto/WxPayVO.java | 20 ++ .../java/com/heyu/api/jsapi/utils/IOUtil.java | 60 +++++ .../com/heyu/api/jsapi/utils/PemUtil.java | 227 ++++++++++++++++++ .../api/controller/vv/AppOrderController.java | 44 +++- 5 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 api-third/src/main/java/com/heyu/api/jsapi/dto/WxPayVO.java create mode 100644 api-third/src/main/java/com/heyu/api/jsapi/utils/IOUtil.java create mode 100644 api-third/src/main/java/com/heyu/api/jsapi/utils/PemUtil.java diff --git a/api-third/src/main/java/com/heyu/api/jsapi/JsapiPrepay.java b/api-third/src/main/java/com/heyu/api/jsapi/JsapiPrepay.java index 0ad4f15..536f3d9 100644 --- a/api-third/src/main/java/com/heyu/api/jsapi/JsapiPrepay.java +++ b/api-third/src/main/java/com/heyu/api/jsapi/JsapiPrepay.java @@ -6,16 +6,21 @@ import com.heyu.api.jsapi.dto.CommonAmountInfo; import com.heyu.api.jsapi.dto.DirectAPIv3JsapiPrepayRequest; import com.heyu.api.jsapi.dto.DirectAPIv3JsapiPrepayResponse; import com.heyu.api.jsapi.dto.JsapiReqPayerInfo; +import com.heyu.api.jsapi.utils.PemUtil; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.springframework.util.Base64Utils; import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.Signature; /** * JSAPI下单 @@ -59,6 +64,15 @@ public class JsapiPrepay { this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); } + @SneakyThrows + public static String getSign(String signatureStr, String privateKey) { + String replace = privateKey.replace("\\n", "\n"); + PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromPath(replace); + Signature sign = Signature.getInstance("SHA256withRSA"); + sign.initSign(merchantPrivateKey); + sign.update(signatureStr.getBytes(StandardCharsets.UTF_8)); + return Base64Utils.encodeToString(sign.sign()); + } public DirectAPIv3JsapiPrepayResponse prePay(Long tradeOrderId, diff --git a/api-third/src/main/java/com/heyu/api/jsapi/dto/WxPayVO.java b/api-third/src/main/java/com/heyu/api/jsapi/dto/WxPayVO.java new file mode 100644 index 0000000..d09f7cd --- /dev/null +++ b/api-third/src/main/java/com/heyu/api/jsapi/dto/WxPayVO.java @@ -0,0 +1,20 @@ +package com.heyu.api.jsapi.dto; + +import lombok.Data; + +@Data +public class WxPayVO { + // 预支付交易会话标识小程序下单接口返回的prepay_id参数值 + private String prepayId; + // 随机字符串 + private String nonceStr; + // 时间戳 + private Long timeStamp; + // 签名 + private String paySign; + // 商户号 + private String mchid; + +} + + diff --git a/api-third/src/main/java/com/heyu/api/jsapi/utils/IOUtil.java b/api-third/src/main/java/com/heyu/api/jsapi/utils/IOUtil.java new file mode 100644 index 0000000..6ae6de6 --- /dev/null +++ b/api-third/src/main/java/com/heyu/api/jsapi/utils/IOUtil.java @@ -0,0 +1,60 @@ + +package com.heyu.api.jsapi.utils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** I/O工具 */ +public class IOUtil { + + private static final int DEFAULT_BUFFER_SIZE = 8192; + + private IOUtil() {} + + /** + * 转换输入流为字节数组 + * + * @param inputStream 输入流 + * @return 字节数组 + * @throws IOException 读取字节失败、关闭流失败等 + */ + public static byte[] toByteArray(InputStream inputStream) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int nRead; + byte[] data = new byte[DEFAULT_BUFFER_SIZE]; + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + + return buffer.toByteArray(); + } + + /** + * 转换输入流为字符串 + * + * @param inputStream 输入流 + * @return UTF-8编码的字符串 + * @throws IOException 读取字节失败、关闭流失败等 + */ + public static String toString(InputStream inputStream) throws IOException { + return new String(toByteArray(inputStream), StandardCharsets.UTF_8); + } + + /** + * 从文件路径中读取字符串 + * + * @param path 文件路径 + * @return UTF-8编码的字符串 + * @throws IOException 读取字节失败、关闭流失败等 + */ + public static String loadStringFromPath(String path) throws IOException { + try (InputStream inputStream = Files.newInputStream(Paths.get(path))) { + return toString(inputStream); + } + } +} diff --git a/api-third/src/main/java/com/heyu/api/jsapi/utils/PemUtil.java b/api-third/src/main/java/com/heyu/api/jsapi/utils/PemUtil.java new file mode 100644 index 0000000..266cb5f --- /dev/null +++ b/api-third/src/main/java/com/heyu/api/jsapi/utils/PemUtil.java @@ -0,0 +1,227 @@ +package com.heyu.api.jsapi.utils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + + +/** PEM工具 */ +public class PemUtil { + + + public static final int HEX = 16; + + private PemUtil() {} + + /** + * 从字符串中加载RSA私钥。 + * + * @param keyString 私钥字符串 + * @return RSA私钥 + */ + public static PrivateKey loadPrivateKeyFromString(String keyString) { + try { + keyString = + keyString + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + return KeyFactory.getInstance("RSA") + .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString))); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException(e); + } catch (InvalidKeySpecException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * 从字符串中加载指定算法的私钥 + * + * @param keyString 私钥字符串 + * @param algorithm 私钥算法 + * @param provider the provider + * @return 私钥 + */ + public static PrivateKey loadPrivateKeyFromString( + String keyString, String algorithm, String provider) { + try { + keyString = + keyString + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + return KeyFactory.getInstance(algorithm, provider) + .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString))); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException(e); + } catch (InvalidKeySpecException | NoSuchProviderException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * 从字符串中加载RSA公钥。 + * + * @param keyString 公钥字符串 + * @return RSA公钥 + */ + public static PublicKey loadPublicKeyFromString(String keyString) { + try { + keyString = + keyString + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s+", ""); + return KeyFactory.getInstance("RSA") + .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(keyString))); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException(e); + } catch (InvalidKeySpecException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * 从文件路径加载RSA私钥 + * + * @param keyPath 私钥路径 + * @return RSA私钥 + */ + public static PrivateKey loadPrivateKeyFromPath(String keyPath) { + return loadPrivateKeyFromString(readKeyStringFromPath(keyPath)); + } + + /** + * 从文件路径加载指定算法的私钥 + * + * @param keyPath 私钥路径 + * @param algorithm 私钥算法 + * @param provider the provider + * @return 私钥 + */ + public static PrivateKey loadPrivateKeyFromPath( + String keyPath, String algorithm, String provider) { + return loadPrivateKeyFromString(readKeyStringFromPath(keyPath), algorithm, provider); + } + + /** + * 从文件路径加载RSA公钥 + * + * @param keyPath 公钥路径 + * @return RSA公钥 + */ + public static PublicKey loadPublicKeyFromPath(String keyPath) { + return loadPublicKeyFromString(readKeyStringFromPath(keyPath)); + } + + private static String readKeyStringFromPath(String keyPath) { + try (FileInputStream inputStream = new FileInputStream(keyPath)) { + return IOUtil.toString(inputStream); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * 从输入流加载X.509证书 + * + * @param inputStream 私钥输入流 + * @return X.509证书 + */ + public static X509Certificate loadX509FromStream(InputStream inputStream) { + try { + return (X509Certificate) + CertificateFactory.getInstance("X.509").generateCertificate(inputStream); + } catch (CertificateException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * 从输入流加载X.509证书 + * + * @param inputStream 私钥输入流 + * @param provider the provider + * @return X.509证书 + */ + public static X509Certificate loadX509FromStream(InputStream inputStream, String provider) { + try { + return (X509Certificate) + CertificateFactory.getInstance("X.509", provider).generateCertificate(inputStream); + } catch (CertificateException | NoSuchProviderException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * 从文件路径加载X.509证书 + * + * @param certificatePath 证书文件路径 + * @return X.509证书 + */ + public static X509Certificate loadX509FromPath(String certificatePath) { + try (FileInputStream inputStream = new FileInputStream(certificatePath)) { + return loadX509FromStream(inputStream); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * 从文件路径加载X.509证书 + * + * @param certificatePath 证书文件路径 + * @param provider the provider + * @return X.509证书 + */ + public static X509Certificate loadX509FromPath(String certificatePath, String provider) { + try (FileInputStream inputStream = new FileInputStream(certificatePath)) { + return loadX509FromStream(inputStream, provider); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * 从字符串加载X.509证书 + * + * @param certificateString 证书字符串 + * @return X.509证书 + */ + public static X509Certificate loadX509FromString(String certificateString) { + try (ByteArrayInputStream inputStream = + new ByteArrayInputStream(certificateString.getBytes(StandardCharsets.UTF_8))) { + return loadX509FromStream(inputStream); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * 从字符串加载X.509证书 + * + * @param certificateString 证书字符串 + * @param provider the provider + * @return X.509证书 + */ + public static X509Certificate loadX509FromString(String certificateString, String provider) { + try (ByteArrayInputStream inputStream = + new ByteArrayInputStream(certificateString.getBytes(StandardCharsets.UTF_8))) { + return loadX509FromStream(inputStream, provider); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static String getSerialNumber(X509Certificate certificate) { + return certificate.getSerialNumber().toString(HEX).toUpperCase(); + } +} diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppOrderController.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppOrderController.java index 9493165..7b5ed8b 100644 --- a/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppOrderController.java +++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/vv/AppOrderController.java @@ -17,6 +17,7 @@ import com.heyu.api.data.enums.RoleEnums; import com.heyu.api.data.utils.*; import com.heyu.api.jsapi.JsapiPrepay; import com.heyu.api.jsapi.dto.DirectAPIv3JsapiPrepayResponse; +import com.heyu.api.jsapi.dto.WxPayVO; import com.heyu.api.utils.ISelect; import com.heyu.api.utils.PPageUtils; import lombok.extern.slf4j.Slf4j; @@ -31,6 +32,8 @@ import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Slf4j @@ -78,6 +81,19 @@ public class AppOrderController { @Value("${eb.config.rabbitQueue.delayExchangeName}") private String delayExchangeName; + @Value("${eb.config.weixin.pay.appid}") + private String appid; + + @Value("${eb.config.weixin.pay.mchid}") + private String mchid; + + + @Value("${eb.config.weixin.pay.privateKeyFilePath}") + private String privateKey; + + + + /*** * https://api.1024api.com/api-interface/app/order/list @@ -347,12 +363,34 @@ public class AppOrderController { }); } - Map map = new HashMap<>(); - map.put("prepay_id",prepay_id); - return R.ok().setData(map); + WxPayVO vo = new WxPayVO(); + Long timeStamp = System.currentTimeMillis() / 1000; + String nonceStr = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); + + String signatureStr = Stream.of(appid, String.valueOf(timeStamp), nonceStr, "prepay_id=" + prepay_id) + .collect(Collectors.joining("\n", "", "\n")); + + // "timeStamp": "1414561699", + // "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS", + // "package": "prepay_id=wx201410272009395522657a690389285100", + // "signType": "RSA", + // "paySign": "oR9d", + + String sign = JsapiPrepay.getSign(signatureStr, privateKey); + vo.setNonceStr(nonceStr); + vo.setTimeStamp(timeStamp); + vo.setMchid(mchid); + vo.setPaySign(sign); + vo.setPrepayId("prepay_id=" + prepay_id); + + return R.ok().setData(vo); } + + + + @Describe("删除订单") @RequestMapping("/delete") public R add(@RequestBody VvTradeOrderDeleteDTO vvOrderRequest) {