Browse Source

修复支付证书的路径拼接规则

main
科技小王子 2 weeks ago
parent
commit
4623b6f5d1
  1. 219
      docs/CERTIFICATE_PATH_FIX_SUMMARY.md
  2. 91
      src/main/java/com/gxwebsoft/common/core/utils/WechatCertAutoConfig.java
  3. 45
      src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java
  4. 2
      src/main/resources/application-dev.yml

219
docs/CERTIFICATE_PATH_FIX_SUMMARY.md

@ -0,0 +1,219 @@
# 微信支付证书路径修复总结
## 问题描述
用户反馈本地开发环境的支付证书路径配置不正确,需要修复为使用配置文件的 `upload-path` 拼接证书路径。
**拼接规则**:配置文件 `upload-path` + `dev/wechat/` + 租户ID
**示例路径**:
```
配置文件upload-path: /Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/
拼接后证书路径: /Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10550/
```
## 修复原则
- **开发环境**: 使用固定的本地证书路径,便于开发调试
- **生产环境**: 使用数据库存储的证书路径,支持灵活配置和多租户
## 修复内容
### 1. 修复 SettingServiceImpl
**文件**: `src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java`
**修复内容**:
- 添加环境变量注入 `@Value("${spring.profiles.active:prod}")`
- 修改 `initConfig` 方法,根据环境选择不同的证书路径配置
- 开发环境使用本地固定路径
- 生产环境使用数据库配置的路径
**修复前**:
```java
config = new RSAConfig.Builder()
.merchantId("1246610101")
.privateKeyFromPath("/Users/gxwebsoft/cert/1246610101_20221225_cert/01ac632fea184e248d0375e9917063a4.pem")
.merchantSerialNumber("2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7")
.wechatPayCertificatesFromPath("/Users/gxwebsoft/cert/1246610101_20221225_cert/bac91dfb3ef143328dde489004c6d002.pem")
.build();
```
**修复后**:
```java
if ("dev".equals(activeProfile)) {
// 开发环境:使用配置文件的upload-path拼接证书路径
String uploadPath = pathConfig.getUploadPath(); // 获取配置的upload-path
String tenantId = "10550"; // 租户ID
String certBasePath = uploadPath + "dev/wechat/" + tenantId + "/";
String devPrivateKeyPath = certBasePath + "apiclient_key.pem";
String devCertPath = certBasePath + "apiclient_cert.pem";
config = new RSAConfig.Builder()
.merchantId("1246610101")
.privateKeyFromPath(devPrivateKeyPath)
.merchantSerialNumber("2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7")
.wechatPayCertificatesFromPath(devCertPath)
.build();
} else {
// 生产环境:使用数据库存储的路径
config = new RSAConfig.Builder()
.merchantId(mchId)
.privateKeyFromPath(privateKey)
.merchantSerialNumber(merchantSerialNumber)
.wechatPayCertificatesFromPath(apiclientCert)
.build();
}
```
### 2. 修复配置文件
**文件**: `src/main/resources/application-dev.yml`
**修复内容**:
- 修改 `upload-path` 配置,指向项目资源目录
**修复前**:
```yaml
config:
upload-path: /Users/gxwebsoft/Documents/uploads/ # window(D:\Temp)
```
**修复后**:
```yaml
config:
upload-path: /Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/ # 项目资源目录
```
### 3. 修复 WechatCertAutoConfig
**文件**: `src/main/java/com/gxwebsoft/common/core/utils/WechatCertAutoConfig.java`
**修复内容**:
- 添加环境变量注入
- 修改 `createDefaultDevConfig` 方法,根据环境选择证书路径
**修复后**:
```java
public Config createDefaultDevConfig() {
String merchantId = "1723321338";
String privateKeyPath;
if ("dev".equals(activeProfile)) {
// 开发环境:使用配置文件upload-path拼接证书路径
String uploadPath = configProperties.getUploadPath(); // 配置文件路径
String tenantId = "10550"; // 租户ID
String certPath = uploadPath + "dev/wechat/" + tenantId + "/";
privateKeyPath = certPath + "apiclient_key.pem";
} else {
// 生产环境:使用相对路径
privateKeyPath = "src/main/resources/certs/dev/wechat/apiclient_key.pem";
}
return createAutoConfig(merchantId, privateKeyPath, merchantSerialNumber, apiV3Key);
}
```
## 路径拼接规则
### 开发环境路径拼接
```
最终路径 = 配置文件upload-path + "dev/wechat/" + 租户ID + "/"
```
**示例**:
- 配置文件upload-path: `/Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/`
- 租户ID: `10550`
- 最终证书路径: `/Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10550/`
- 私钥文件: `/Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10550/apiclient_key.pem`
- 证书文件: `/Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10550/apiclient_cert.pem`
### 生产环境路径拼接
```
最终路径 = 配置文件upload-path + "file/" + 数据库相对路径
```
**示例**:
- 配置文件upload-path: `/www/wwwroot/file.ws/`
- 数据库相对路径: `wechat/10550/apiclient_key.pem`
- 最终证书路径: `/www/wwwroot/file.ws/file/wechat/10550/apiclient_key.pem`
## 证书文件验证
### 证书目录结构
```
/Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10550/
├── apiclient_cert.p12 # PKCS12格式证书 (2.8K)
├── apiclient_cert.pem # 商户证书 (1.5K)
└── apiclient_key.pem # 商户私钥 (1.7K)
```
### 证书文件格式验证
- ✅ 私钥文件格式正确: `-----BEGIN PRIVATE KEY-----`
- ✅ 证书文件格式正确: `-----BEGIN CERTIFICATE-----`
## 环境配置说明
### 开发环境 (dev)
- **证书路径**: 配置文件upload-path拼接路径
- **拼接规则**: `upload-path` + `dev/wechat/` + 租户ID
- **配置方式**: 通过配置文件设置upload-path
- **优点**: 灵活配置,便于不同开发环境
- **适用场景**: 本地开发、测试
### 生产环境 (prod)
- **证书路径**: 数据库配置的相对路径
- **配置方式**: 通过数据库 `payment` 表配置
- **优点**: 灵活配置,支持多租户
- **适用场景**: 生产部署、多租户环境
## 测试验证
### 测试文件
1. `src/test/java/com/gxwebsoft/test/CertificatePathFixTest.java`
- 验证证书文件存在性和格式
- 验证证书目录结构
2. `src/test/java/com/gxwebsoft/test/EnvironmentBasedCertificateTest.java`
- 验证环境判断逻辑
- 验证不同环境的证书路径配置
3. `src/test/java/com/gxwebsoft/test/CertificatePathConcatenationTest.java`
- 验证路径拼接逻辑
- 验证配置文件upload-path的使用
- 验证多租户路径拼接
### 运行测试
```bash
# 开发环境测试
mvn test -Dtest=EnvironmentBasedCertificateTest -Dspring.profiles.active=dev
# 生产环境测试
mvn test -Dtest=EnvironmentBasedCertificateTest -Dspring.profiles.active=prod
```
## 部署说明
### 开发环境部署
1. 确保证书文件存在于指定路径
2. 设置环境变量: `spring.profiles.active=dev`
3. 重启应用
### 生产环境部署
1. 上传证书文件到服务器指定目录
2. 在数据库 `payment` 表中配置正确的相对路径
3. 设置环境变量: `spring.profiles.active=prod`
4. 重启应用
## 注意事项
1. **路径安全**: 开发环境的硬编码路径仅适用于特定开发机器
2. **证书安全**: 确保证书文件权限设置正确,避免泄露
3. **环境隔离**: 开发和生产环境使用不同的证书配置策略
4. **多租户支持**: 生产环境支持多租户证书配置
## 相关文档
- [微信支付证书配置指南](WECHAT_PAY_CERTIFICATE_FIX.md)
- [微信支付公钥模式配置](WECHAT_PAY_PUBLIC_KEY_CONFIG.md)
- [证书问题修复总结](CERTIFICATE_FIX_SUMMARY.md)

91
src/main/java/com/gxwebsoft/common/core/utils/WechatCertAutoConfig.java

@ -3,12 +3,16 @@ package com.gxwebsoft.common.core.utils;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.gxwebsoft.common.core.config.ConfigProperties;
import javax.annotation.Resource;
/**
* 微信支付证书自动配置工具类
* 使用RSAAutoCertificateConfig实现证书自动管理
*
*
* @author 科技小王子
* @since 2024-07-26
*/
@ -16,38 +20,47 @@ import org.springframework.stereotype.Component;
@Component
public class WechatCertAutoConfig {
@Value("${spring.profiles.active:prod}")
private String activeProfile;
@Resource
private ConfigProperties configProperties;
@Resource
private ConfigProperties configProperties;
/**
* 创建微信支付自动证书配置
*
*
* @param merchantId 商户号
* @param privateKeyPath 私钥文件路径
* @param merchantSerialNumber 商户证书序列号
* @param apiV3Key APIv3密钥
* @return 微信支付配置对象
*/
public Config createAutoConfig(String merchantId, String privateKeyPath,
public Config createAutoConfig(String merchantId, String privateKeyPath,
String merchantSerialNumber, String apiV3Key) {
try {
log.info("创建微信支付自动证书配置...");
log.info("商户号: {}", merchantId);
log.info("私钥路径: {}", privateKeyPath);
log.info("证书序列号: {}", merchantSerialNumber);
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
log.info("✅ 微信支付自动证书配置创建成功");
log.info("🔄 系统将自动管理平台证书的下载和更新");
return config;
} catch (Exception e) {
log.error("❌ 创建微信支付自动证书配置失败: {}", e.getMessage(), e);
// 提供详细的错误诊断信息
log.error("🔍 错误诊断:");
log.error("1. 请检查商户平台是否已开启API安全功能");
@ -55,28 +68,48 @@ public class WechatCertAutoConfig {
log.error("3. 请验证APIv3密钥和证书序列号是否正确");
log.error("4. 请检查网络连接是否正常");
log.error("5. 请确认私钥文件路径是否正确: {}", privateKeyPath);
throw new RuntimeException("微信支付自动证书配置失败: " + e.getMessage(), e);
}
}
/**
* 使用默认开发环境配置创建自动证书配置
*
* 根据当前环境自动选择证书路径
* 开发环境拼接规则配置文件upload-path + dev/wechat/ + 租户ID
*
* @return 微信支付配置对象
*/
public Config createDefaultDevConfig() {
String merchantId = "1723321338";
String privateKeyPath = "src/main/resources/certs/dev/wechat/apiclient_key.pem";
String privateKeyPath;
String merchantSerialNumber = "2B933F7C35014A1C363642623E4A62364B34C4EB";
String apiV3Key = "0kF5OlPr482EZwtn9zGufUcqa7ovgxRL";
// 根据环境选择证书路径
if ("dev".equals(activeProfile)) {
// 开发环境:使用配置文件upload-path拼接证书路径
String uploadPath = configProperties.getUploadPath(); // 配置文件路径
String tenantId = "10550"; // 租户ID
String certPath = uploadPath + "dev/wechat/" + tenantId + "/";
privateKeyPath = certPath + "apiclient_key.pem";
log.info("开发环境:使用配置文件upload-path拼接证书路径");
log.info("配置文件upload-path: {}", uploadPath);
log.info("证书基础路径: {}", certPath);
log.info("私钥文件路径: {}", privateKeyPath);
} else {
// 生产环境:使用相对路径,由系统动态解析
privateKeyPath = "src/main/resources/certs/dev/wechat/apiclient_key.pem";
log.info("生产环境:使用相对证书路径 - {}", privateKeyPath);
}
return createAutoConfig(merchantId, privateKeyPath, merchantSerialNumber, apiV3Key);
}
/**
* 测试证书配置是否正常
*
*
* @param config 微信支付配置
* @return 是否配置成功
*/
@ -84,56 +117,56 @@ public class WechatCertAutoConfig {
try {
// 这里可以添加一些基本的配置验证逻辑
log.info("🧪 测试微信支付证书配置...");
if (config == null) {
log.error("配置对象为空");
return false;
}
log.info("✅ 证书配置测试通过");
return true;
} catch (Exception e) {
log.error("❌ 证书配置测试失败: {}", e.getMessage(), e);
return false;
}
}
/**
* 获取配置使用说明
*
*
* @return 使用说明
*/
public String getUsageInstructions() {
return """
🚀 微信支付自动证书配置使用说明
================================
优势:
1. 自动下载微信支付平台证书
2. 证书过期时自动更新
3. 无需手动管理 wechatpay_cert.pem 文件
4. 符合微信支付官方最佳实践
📝 使用方法:
// 方法1: 使用默认开发环境配置
Config config = wechatCertAutoConfig.createDefaultDevConfig();
// 方法2: 自定义配置
Config config = wechatCertAutoConfig.createAutoConfig(
"商户号",
"私钥路径",
"证书序列号",
"商户号",
"私钥路径",
"证书序列号",
"APIv3密钥"
);
🔧 前置条件:
1. 微信商户平台已开启API安全功能
2. 已申请使用微信支付公钥
3. 私钥文件存在且路径正确
4. 网络连接正常
📚 更多信息:
https://pay.weixin.qq.com/doc/v3/merchant/4012153196
""";

45
src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java

@ -16,6 +16,7 @@ import com.gxwebsoft.common.system.service.SettingService;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@ -41,6 +42,9 @@ public class SettingServiceImpl extends ServiceImpl<SettingMapper, Setting> impl
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${spring.profiles.active:prod}")
private String activeProfile;
@Override
public PageResult<Setting> pageRel(SettingParam param) {
PageParam<Setting, SettingParam> page = new PageParam<>(param);
@ -120,20 +124,41 @@ public class SettingServiceImpl extends ServiceImpl<SettingMapper, Setting> impl
final String merchantSerialNumber = jsonObject.getString("merchantSerialNumber");
final String apiV3key = jsonObject.getString("wechatApiKey");
if(config == null){
// config = new RSAAutoCertificateConfig.Builder()
// .merchantId(mchId)
// .privateKeyFromPath("/Users/gxwebsoft/Documents/uploads/file/20230622/fb193d3bfff0467b83dc498435a4f433.pem")
// .merchantSerialNumber(merchantSerialNumber)
// .apiV3Key(apiV3key)
// .build();
config =
new RSAConfig.Builder()
// 根据环境选择不同的证书路径配置
if ("dev".equals(activeProfile)) {
// 开发环境:使用配置文件的upload-path拼接证书路径 - 租户ID 10550
System.out.println("=== 开发环境:使用配置文件upload-path拼接证书路径 ===");
String uploadPath = pathConfig.getUploadPath(); // 获取配置的upload-path
String tenantId = "10550"; // 租户ID
String certBasePath = uploadPath + "dev/wechat/" + tenantId + "/";
String devPrivateKeyPath = certBasePath + "apiclient_key.pem";
String devCertPath = certBasePath + "apiclient_cert.pem";
System.out.println("配置的upload-path: " + uploadPath);
System.out.println("证书基础路径: " + certBasePath);
System.out.println("私钥文件路径: " + devPrivateKeyPath);
System.out.println("证书文件路径: " + devCertPath);
config = new RSAConfig.Builder()
.merchantId("1246610101")
.privateKeyFromPath("/Users/gxwebsoft/cert/1246610101_20221225_cert/01ac632fea184e248d0375e9917063a4.pem")
.privateKeyFromPath(devPrivateKeyPath)
.merchantSerialNumber("2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7")
.wechatPayCertificatesFromPath("/Users/gxwebsoft/cert/1246610101_20221225_cert/bac91dfb3ef143328dde489004c6d002.pem")
.wechatPayCertificatesFromPath(devCertPath)
.build();
System.out.println("开发环境证书路径配置完成");
} else {
// 生产环境:使用数据库存储的路径
System.out.println("=== 生产环境:使用数据库存储的证书路径 ===");
config = new RSAConfig.Builder()
.merchantId(mchId)
.privateKeyFromPath(privateKey)
.merchantSerialNumber(merchantSerialNumber)
.wechatPayCertificatesFromPath(apiclientCert)
.build();
System.out.println("生产环境证书路径: " + privateKey);
}
configMap.put(data.getTenantId().toString(),config);
System.out.println("当前环境: " + activeProfile);
System.out.println("config = " + config);
}
if (service == null) {

2
src/main/resources/application-dev.yml

@ -54,7 +54,7 @@ mqtt:
config:
# 开发环境接口
server-url: https://server.websoft.top/api
upload-path: /Users/gxwebsoft/Documents/uploads/ # window(D:\Temp)
upload-path: /Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/ # 项目资源目录
# 开发环境证书配置
certificate:

Loading…
Cancel
Save