|
|
@@ -9,13 +9,14 @@ import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.dromara.system.domain.SysConfig;
|
|
|
import org.dromara.system.mapper.SysConfigMapper;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
import java.io.File;
|
|
|
+import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.util.List;
|
|
|
|
|
|
@@ -29,8 +30,25 @@ public class PaymentConfigServiceImpl implements IPaymentConfigService {
|
|
|
|
|
|
private final SysConfigMapper sysConfigMapper;
|
|
|
|
|
|
- @Value("${ruoyi.profile:/data/upload}")
|
|
|
- private String uploadPath;
|
|
|
+ /** 证书保存目录(模块resources/cert下) */
|
|
|
+ private String getCertDir() {
|
|
|
+ try {
|
|
|
+ // 获取当前模块的resources目录
|
|
|
+ String resourcePath = this.getClass().getClassLoader().getResource("").getPath();
|
|
|
+ // Windows路径处理
|
|
|
+ if (resourcePath.startsWith("/")) {
|
|
|
+ resourcePath = resourcePath.substring(1);
|
|
|
+ }
|
|
|
+ // 开发环境:target/classes -> src/main/resources
|
|
|
+ if (resourcePath.contains("target/classes")) {
|
|
|
+ resourcePath = resourcePath.replace("target/classes", "src/main/resources");
|
|
|
+ }
|
|
|
+ return resourcePath + "cert";
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取resources路径失败", e);
|
|
|
+ throw new RuntimeException("获取证书目录失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
// 配置key
|
|
|
private static final String SHORT_PRICE_KEY = "payment.short.price";
|
|
|
@@ -40,6 +58,8 @@ public class PaymentConfigServiceImpl implements IPaymentConfigService {
|
|
|
private static final String NOTIFY_URL_KEY = "payment.notify.url";
|
|
|
private static final String PRIVATE_KEY_PATH_KEY = "payment.private.key.path";
|
|
|
private static final String CERT_PATH_KEY = "payment.cert.path";
|
|
|
+ private static final String PUBLIC_KEY_ID_KEY = "payment.public.key.id";
|
|
|
+ private static final String PUBLIC_KEY_PATH_KEY = "payment.public.key.path";
|
|
|
|
|
|
@Override
|
|
|
public PaymentConfigVo getConfig() {
|
|
|
@@ -50,6 +70,13 @@ public class PaymentConfigServiceImpl implements IPaymentConfigService {
|
|
|
vo.setMchId(getConfigString(MCH_ID_KEY));
|
|
|
vo.setApiV3Key(getConfigString(API_V3_KEY));
|
|
|
vo.setNotifyUrl(getConfigString(NOTIFY_URL_KEY));
|
|
|
+
|
|
|
+ // 微信支付公钥配置
|
|
|
+ vo.setPublicKeyId(getConfigString(PUBLIC_KEY_ID_KEY));
|
|
|
+
|
|
|
+ // 检查公钥是否已上传
|
|
|
+ String publicKeyPath = getConfigString(PUBLIC_KEY_PATH_KEY);
|
|
|
+ vo.setPublicKeyUploaded(publicKeyPath != null && !publicKeyPath.isEmpty() && new File(publicKeyPath).exists());
|
|
|
|
|
|
// 检查私钥是否已上传
|
|
|
String privateKeyPath = getConfigString(PRIVATE_KEY_PATH_KEY);
|
|
|
@@ -80,21 +107,35 @@ public class PaymentConfigServiceImpl implements IPaymentConfigService {
|
|
|
if (bo.getNotifyUrl() != null) {
|
|
|
updateOrInsertConfig(NOTIFY_URL_KEY, bo.getNotifyUrl(), "支付回调地址");
|
|
|
}
|
|
|
+ if (bo.getPublicKeyId() != null) {
|
|
|
+ updateOrInsertConfig(PUBLIC_KEY_ID_KEY, bo.getPublicKeyId(), "微信支付公钥ID");
|
|
|
+ }
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean savePrivateKey(MultipartFile file) {
|
|
|
try {
|
|
|
- // 保存到cert目录
|
|
|
- String certDir = uploadPath + "/cert";
|
|
|
+ // 保存到resources/cert目录
|
|
|
+ String certDir = getCertDir();
|
|
|
File dir = new File(certDir);
|
|
|
if (!dir.exists()) {
|
|
|
- dir.mkdirs();
|
|
|
+ boolean created = dir.mkdirs();
|
|
|
+ log.info("创建目录 {}: {}", certDir, created);
|
|
|
}
|
|
|
|
|
|
String filePath = certDir + "/apiclient_key.pem";
|
|
|
- file.transferTo(new File(filePath));
|
|
|
+ File destFile = new File(filePath);
|
|
|
+
|
|
|
+ // 使用 InputStream 写入文件,避免 transferTo 在 Windows 上的问题
|
|
|
+ try (InputStream is = file.getInputStream();
|
|
|
+ FileOutputStream fos = new FileOutputStream(destFile)) {
|
|
|
+ byte[] buffer = new byte[1024];
|
|
|
+ int len;
|
|
|
+ while ((len = is.read(buffer)) > 0) {
|
|
|
+ fos.write(buffer, 0, len);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
// 保存路径到配置
|
|
|
updateOrInsertConfig(PRIVATE_KEY_PATH_KEY, filePath, "商户私钥文件路径");
|
|
|
@@ -110,15 +151,26 @@ public class PaymentConfigServiceImpl implements IPaymentConfigService {
|
|
|
@Override
|
|
|
public boolean saveCert(MultipartFile file) {
|
|
|
try {
|
|
|
- // 保存到cert目录
|
|
|
- String certDir = uploadPath + "/cert";
|
|
|
+ // 保存到resources/cert目录
|
|
|
+ String certDir = getCertDir();
|
|
|
File dir = new File(certDir);
|
|
|
if (!dir.exists()) {
|
|
|
- dir.mkdirs();
|
|
|
+ boolean created = dir.mkdirs();
|
|
|
+ log.info("创建目录 {}: {}", certDir, created);
|
|
|
}
|
|
|
|
|
|
String filePath = certDir + "/apiclient_cert.pem";
|
|
|
- file.transferTo(new File(filePath));
|
|
|
+ File destFile = new File(filePath);
|
|
|
+
|
|
|
+ // 使用 InputStream 写入文件,避免 transferTo 在 Windows 上的问题
|
|
|
+ try (InputStream is = file.getInputStream();
|
|
|
+ FileOutputStream fos = new FileOutputStream(destFile)) {
|
|
|
+ byte[] buffer = new byte[1024];
|
|
|
+ int len;
|
|
|
+ while ((len = is.read(buffer)) > 0) {
|
|
|
+ fos.write(buffer, 0, len);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
// 保存路径到配置
|
|
|
updateOrInsertConfig(CERT_PATH_KEY, filePath, "支付证书文件路径");
|
|
|
@@ -131,6 +183,41 @@ public class PaymentConfigServiceImpl implements IPaymentConfigService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public boolean savePublicKey(MultipartFile file) {
|
|
|
+ try {
|
|
|
+ // 保存到resources/cert目录
|
|
|
+ String certDir = getCertDir();
|
|
|
+ File dir = new File(certDir);
|
|
|
+ if (!dir.exists()) {
|
|
|
+ boolean created = dir.mkdirs();
|
|
|
+ log.info("创建目录 {}: {}", certDir, created);
|
|
|
+ }
|
|
|
+
|
|
|
+ String filePath = certDir + "/pub_key.pem";
|
|
|
+ File destFile = new File(filePath);
|
|
|
+
|
|
|
+ // 使用 InputStream 写入文件
|
|
|
+ try (InputStream is = file.getInputStream();
|
|
|
+ FileOutputStream fos = new FileOutputStream(destFile)) {
|
|
|
+ byte[] buffer = new byte[1024];
|
|
|
+ int len;
|
|
|
+ while ((len = is.read(buffer)) > 0) {
|
|
|
+ fos.write(buffer, 0, len);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存路径到配置
|
|
|
+ updateOrInsertConfig(PUBLIC_KEY_PATH_KEY, filePath, "微信支付公钥文件路径");
|
|
|
+
|
|
|
+ log.info("公钥文件保存成功: {}", filePath);
|
|
|
+ return true;
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("保存公钥文件失败", e);
|
|
|
+ throw new RuntimeException("保存公钥文件失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private BigDecimal getConfigValue(String key, String defaultValue) {
|
|
|
List<SysConfig> list = sysConfigMapper.selectList(
|
|
|
new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getConfigKey, key).last("LIMIT 1"));
|