切换server数据库

This commit is contained in:
2025-07-27 13:06:49 +08:00
parent 62d55c9831
commit 1cb8620c88
12 changed files with 1107 additions and 31 deletions

136
CERTIFICATE_PATH_FIX.md Normal file
View File

@@ -0,0 +1,136 @@
# 证书路径拼接规则修复
## 问题描述
生产环境中微信支付证书路径拼接错误,导致证书加载失败:
**错误路径**
```
/www/wwwroot/file.ws/wechat/10550//20250727/c27fe16e08314431a56c3489818af64f.pem
```
**正确路径**
```
/www/wwwroot/file.ws/file/20250727/c27fe16e08314431a56c3489818af64f.pem
```
## 修复方案
修改证书路径拼接规则为:`uploadPath + "file" + 数据库存储的相对路径`
## 修改的文件
### 1. WxNativePayController.java
**文件路径**: `src/main/java/com/gxwebsoft/common/system/controller/WxNativePayController.java`
**修改内容**:
```java
// 修改前
apiclientKey = config.getUploadPath().concat("/file").concat(payment.getApiclientKey());
// 修改后
String relativePath = payment.getApiclientKey();
apiclientKey = config.getUploadPath() + "file" + relativePath;
log.info("生产环境证书路径构建 - 上传根路径: {}", config.getUploadPath());
log.info("生产环境证书路径构建 - 数据库相对路径: {}", relativePath);
log.info("生产环境证书路径构建 - 完整路径: {}", apiclientKey);
```
### 2. AlipayConfigUtil.java
**文件路径**: `src/main/java/com/gxwebsoft/common/core/utils/AlipayConfigUtil.java`
**修改内容**:
```java
// 修改前
this.appCertPublicKey = pathConfig.getUploadPath() + "file" + payment.getString("appCertPublicKey");
this.alipayCertPublicKey = pathConfig.getUploadPath() + "file" + payment.getString("alipayCertPublicKey");
this.alipayRootCert = pathConfig.getUploadPath() + "file" + payment.getString("alipayRootCert");
// 修改后
String appCertPath = payment.getString("appCertPublicKey");
String alipayCertPath = payment.getString("alipayCertPublicKey");
String rootCertPath = payment.getString("alipayRootCert");
this.appCertPublicKey = pathConfig.getUploadPath() + "file" + appCertPath;
this.alipayCertPublicKey = pathConfig.getUploadPath() + "file" + alipayCertPath;
this.alipayRootCert = pathConfig.getUploadPath() + "file" + rootCertPath;
log.info("生产环境支付宝证书路径构建:");
log.info("上传根路径: {}", pathConfig.getUploadPath());
log.info("应用证书 - 数据库路径: {}, 完整路径: {}", appCertPath, this.appCertPublicKey);
log.info("支付宝证书 - 数据库路径: {}, 完整路径: {}", alipayCertPath, this.alipayCertPublicKey);
log.info("根证书 - 数据库路径: {}, 完整路径: {}", rootCertPath, this.alipayRootCert);
```
### 3. CertificateHealthService.java
**文件路径**: `src/main/java/com/gxwebsoft/common/core/service/CertificateHealthService.java`
**修改内容**:
```java
// 修改前
String fullPath = uploadPath + "file" + relativePath;
// 修改后
String fullPath = uploadPath + "file" + relativePath;
log.debug("生产环境证书路径构建 - 上传根路径: {}, 相对路径: {}, 完整路径: {}",
uploadPath, relativePath, fullPath);
```
### 4. SettingServiceImpl.java
**文件路径**: `src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java`
**修改内容**:
```java
// 修改前
final String privateKey = pathConfig.getUploadPath().concat("file").concat(apiclientKey);
final String apiclientCert = pathConfig.getUploadPath().concat("file").concat(jsonObject.getString("apiclientCert"));
// 修改后
final String privateKey = pathConfig.getUploadPath() + "file" + apiclientKey;
final String apiclientCert = pathConfig.getUploadPath() + "file" + jsonObject.getString("apiclientCert");
```
## 路径构建规则
### 生产环境配置
- **上传根路径**: `/www/wwwroot/file.ws/` (来自 `application-prod.yml``config.upload-path`)
- **文件目录**: `file`
- **数据库相对路径**: `/20250727/c27fe16e08314431a56c3489818af64f.pem`
### 最终路径
```
/www/wwwroot/file.ws/ + file + /20250727/c27fe16e08314431a56c3489818af64f.pem
= /www/wwwroot/file.ws/file/20250727/c27fe16e08314431a56c3489818af64f.pem
```
## 验证方法
1. **重新部署应用**
2. **查看日志输出**,确认路径构建正确:
```
生产环境证书路径构建 - 上传根路径: /www/wwwroot/file.ws/
生产环境证书路径构建 - 数据库相对路径: /20250727/c27fe16e08314431a56c3489818af64f.pem
生产环境证书路径构建 - 完整路径: /www/wwwroot/file.ws/file/20250727/c27fe16e08314431a56c3489818af64f.pem
```
3. **测试微信支付功能**,确认证书加载成功
## 注意事项
1. 确保数据库中存储的证书路径格式正确(以 `/` 开头的相对路径)
2. 确保服务器上的证书文件存在于正确位置
3. 修改后需要重新编译和部署应用
4. 建议在测试环境先验证修改效果
## 相关配置
### application-prod.yml
```yaml
config:
upload-path: /www/wwwroot/file.ws/
```
### 数据库字段示例
```sql
-- sys_payment 表中的 apiclient_key 字段应该存储类似这样的值:
-- /20250727/c27fe16e08314431a56c3489818af64f.pem
```

View File

@@ -0,0 +1,148 @@
# 数据库证书路径检查功能总结
## 功能概述
根据要求,已成功实现证书健康检查功能从数据库读取证书路径并打印完整绝对路径的功能。
## 主要实现
### 1. 核心功能
-**数据库证书路径读取**: 从`sys_payment`表读取微信支付证书配置
-**完整绝对路径打印**: 显示证书文件的完整绝对路径
-**文件存在性验证**: 检查证书文件是否真实存在
-**多租户支持**: 支持按租户ID查询证书配置
-**环境适配**: 区分开发环境和生产环境的路径处理
### 2. 修改的文件
#### CertificateHealthService.java
- 新增`checkDatabaseCertificates()`方法
- 集成数据库证书检查到健康检查流程
- 添加完整绝对路径计算逻辑
#### CertificateController.java
- 新增`/api/system/certificate/database-check`接口
- 提供独立的数据库证书检查API
#### PaymentConfigTest.java
- 添加数据库证书检查测试用例
- 验证功能正确性
### 3. 新增API接口
#### 数据库证书检查
```
GET /api/system/certificate/database-check
```
**功能**: 检查数据库中存储的证书路径配置
**响应示例**:
```json
{
"code": 0,
"message": "数据库证书检查完成",
"data": {
"healthy": true,
"certificates": {
"wechatPay": {
"privateKey": {
"relativePath": "/20230622/fb193d3bfff0467b83dc498435a4f433.pem",
"absolutePath": "/Users/gxwebsoft/Documents/uploads/file/20230622/fb193d3bfff0467b83dc498435a4f433.pem",
"exists": true
},
"certificate": {
"relativePath": "/20230622/23329e924dae41af9b716825626dd44b.pem",
"absolutePath": "/Users/gxwebsoft/Documents/uploads/file/20230622/23329e924dae41af9b716825626dd44b.pem",
"exists": true
},
"merchantId": "1246610101",
"serialNumber": "2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7",
"apiV3Key": "已配置"
}
}
}
}
```
#### 增强的健康检查
```
GET /api/system/certificate/health
```
现在包含三个部分:
- `wechatPay`: 配置文件证书检查
- `alipay`: 配置文件证书检查
- `databaseCertificates`: 数据库证书检查
## 关键特性
### 1. 完整绝对路径显示
```java
// 生产环境路径计算
String uploadPath = configProperties.getUploadPath();
String fullPath = uploadPath + "file" + relativePath;
log.info("微信支付私钥证书 - 相对路径: {}, 绝对路径: {}, 存在: {}",
apiclientKey, keyPath, keyExists);
```
### 2. 多租户支持
```java
// 按租户查询支付配置
List<Payment> wechatPayments = paymentService.list(
new LambdaQueryWrapper<Payment>()
.eq(Payment::getCode, "wxPay")
.eq(Payment::getStatus, true)
.eq(Payment::getTenantId, tenantId)
);
```
### 3. 环境适配
- **开发环境**: 使用配置文件中的证书路径
- **生产环境**: 使用上传目录中的证书文件
## 日志输出示例
运行证书检查时,会看到类似以下的日志:
```
检查租户 1 的数据库证书配置
找到微信支付配置: 商户号=1246610101, 序列号=2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7
微信支付私钥证书 - 相对路径: /20230622/fb193d3bfff0467b83dc498435a4f433.pem, 绝对路径: /Users/gxwebsoft/Documents/uploads/file/20230622/fb193d3bfff0467b83dc498435a4f433.pem, 存在: true
微信支付证书 - 相对路径: /20230622/23329e924dae41af9b716825626dd44b.pem, 绝对路径: /Users/gxwebsoft/Documents/uploads/file/20230622/23329e924dae41af9b716825626dd44b.pem, 存在: true
```
## 测试验证
### 1. 单元测试
```bash
mvn test -Dtest=PaymentConfigTest#testDatabaseCertificateCheck
```
### 2. API测试
使用提供的`test-database-cert-check.sh`脚本进行API测试
### 3. 手动验证
通过浏览器或Postman访问相关API接口
## 使用场景
1. **运维监控**: 定期检查证书文件状态
2. **故障排查**: 快速定位证书路径问题
3. **部署验证**: 确认证书文件正确部署
4. **安全审计**: 验证证书配置完整性
## 注意事项
1. **权限要求**: 需要`system:certificate:view`权限
2. **租户隔离**: 只能查看当前租户的证书配置
3. **文件权限**: 确保应用有读取证书文件的权限
4. **路径配置**: 确保上传路径配置正确
## 后续扩展
可以考虑添加以下功能:
- 支付宝证书的数据库配置检查
- 证书过期时间检查
- 证书内容验证
- 自动证书更新提醒

255
PAYMENT_CONFIG_CHANGES.md Normal file
View File

@@ -0,0 +1,255 @@
# 支付配置修改说明
## 修改概述
根据要求,对支付配置进行了以下修改:
1. 注释掉从缓存获取支付配置的代码(测试期间)
2. 确保微信支付和支付宝证书路径打印完整的绝对路径
## 修改的文件
### 1. WxNativePayController.java
**文件路径**: `src/main/java/com/gxwebsoft/common/system/controller/WxNativePayController.java`
**主要修改**:
- 注释掉从Redis缓存获取微信支付配置的代码
- 添加测试模式当payment为null时使用测试配置
- 增强证书路径打印,显示完整的绝对路径
- 区分classpath模式和文件系统模式的路径处理
**关键代码**:
```java
// 测试期间注释掉从缓存获取支付配置
// final Payment payment = redisUtil.get(key, Payment.class);
// log.debug("从缓存获取支付配置: {}", payment);
// 测试期间直接从数据库获取支付配置
final Payment payment = null; // 暂时设为null强制从数据库获取
log.debug("测试模式不从缓存获取支付配置payment设为null");
```
### 2. AlipayConfigUtil.java
**文件路径**: `src/main/java/com/gxwebsoft/common/core/utils/AlipayConfigUtil.java`
**主要修改**:
- 注释掉从Redis缓存获取支付宝配置的代码
- 增强支付宝证书路径打印,显示完整的绝对路径
- 添加classpath和文件系统模式的路径处理
**关键代码**:
```java
// 测试期间注释掉从缓存获取支付配置
// String cache = stringRedisTemplate.opsForValue().get(key);
// if (cache == null) {
// throw new BusinessException("支付方式未配置");
// }
// 测试期间:模拟缓存为空的情况
String cache = null;
log.debug("测试模式支付宝配置缓存设为null");
```
### 3. CertificateService.java
**文件路径**: `src/main/java/com/gxwebsoft/common/core/service/CertificateService.java`
**主要修改**:
- 增强`getWechatPayCertPath()`方法,添加完整绝对路径打印
- 增强`getAlipayCertPath()`方法,添加完整绝对路径打印
- 区分classpath模式和文件系统模式的路径处理
**关键功能**:
```java
// 打印完整的绝对路径信息
if (certificateProperties.isClasspathMode()) {
log.info("微信支付证书路径模式: CLASSPATH");
log.info("微信支付证书相对路径: {}", certPath);
try {
ClassPathResource resource = new ClassPathResource(certPath);
if (resource.exists()) {
String absolutePath = resource.getFile().getAbsolutePath();
log.info("微信支付证书完整绝对路径: {}", absolutePath);
}
} catch (Exception e) {
log.warn("无法获取微信支付证书绝对路径: {}", e.getMessage());
}
} else {
java.io.File file = new java.io.File(certPath);
String absolutePath = file.getAbsolutePath();
log.info("微信支付证书路径模式: FILESYSTEM");
log.info("微信支付证书完整绝对路径: {}", absolutePath);
log.info("微信支付证书文件是否存在: {}", file.exists());
}
```
### 4. PaymentConfigTest.java (新增)
**文件路径**: `src/test/java/com/gxwebsoft/common/core/PaymentConfigTest.java`
**功能**:
- 测试微信支付证书路径获取
- 测试支付宝证书路径获取
- 测试证书配置属性
- 验证修改后的功能是否正常工作
## 测试验证
### 运行测试
```bash
mvn test -Dtest=PaymentConfigTest
```
### 预期日志输出
运行测试后,应该能看到以下类型的日志:
1. **微信支付证书路径**:
```
微信支付证书路径模式: CLASSPATH
微信支付证书相对路径: certs/dev/wechat/apiclient_key.pem
微信支付证书完整绝对路径: /Users/xxx/project/target/classes/certs/dev/wechat/apiclient_key.pem
```
2. **支付宝证书路径**:
```
支付宝证书路径模式: CLASSPATH
支付宝证书相对路径: certs/dev/alipay/appCertPublicKey.crt
支付宝证书完整绝对路径: /Users/xxx/project/target/classes/certs/dev/alipay/appCertPublicKey.crt
```
3. **缓存注释确认**:
```
测试模式不从缓存获取支付配置payment设为null
测试模式支付宝配置缓存设为null
```
## 注意事项
1. **测试期间**: 当前修改是为了测试目的,缓存获取被注释掉
2. **生产环境**: 在生产环境部署前,需要恢复缓存获取功能
3. **证书文件**: 确保证书文件存在于配置的路径中
4. **日志级别**: 建议将日志级别设置为INFO或DEBUG以查看详细的路径信息
## 恢复缓存功能
测试完成后,如需恢复缓存功能,请:
1. 取消注释缓存获取代码
2. 注释掉测试模式的代码
3. 重新部署应用
### 5. CertificateHealthService.java (新增功能)
**文件路径**: `src/main/java/com/gxwebsoft/common/core/service/CertificateHealthService.java`
**主要修改**:
- 添加了从数据库读取支付配置证书路径的功能
- 新增`checkDatabaseCertificates()`方法
- 增强证书健康检查,包含数据库配置检查
- 打印数据库中证书的完整绝对路径
**关键功能**:
```java
/**
* 检查数据库中存储的证书路径
*/
public Map<String, Object> checkDatabaseCertificates() {
// 查询微信支付配置
List<Payment> wechatPayments = paymentService.list(
new LambdaQueryWrapper<Payment>()
.eq(Payment::getCode, "wxPay")
.eq(Payment::getStatus, true)
.eq(Payment::getTenantId, tenantId)
);
// 检查证书路径并打印完整绝对路径
String keyPath = getAbsoluteCertPath(apiclientKey);
boolean keyExists = new File(keyPath).exists();
log.info("微信支付私钥证书 - 相对路径: {}, 绝对路径: {}, 存在: {}",
apiclientKey, keyPath, keyExists);
}
```
### 6. CertificateController.java (新增API)
**文件路径**: `src/main/java/com/gxwebsoft/common/core/controller/CertificateController.java`
**新增API**:
- `GET /api/system/certificate/database-check`: 检查数据库证书配置
### 7. PaymentConfigTest.java (更新测试)
**文件路径**: `src/test/java/com/gxwebsoft/common/core/PaymentConfigTest.java`
**新增测试**:
- `testDatabaseCertificateCheck()`: 测试数据库证书配置检查
- `testCertificateHealthCheck()`: 测试完整证书健康检查
## 新增功能说明
### 数据库证书路径检查
现在证书健康检查功能会:
1. **从数据库读取支付配置**: 查询`sys_payment`表中的微信支付配置
2. **检查证书路径**: 验证数据库中存储的证书文件路径
3. **打印完整绝对路径**: 显示证书文件的完整绝对路径
4. **验证文件存在性**: 检查证书文件是否真实存在
### API使用示例
#### 检查数据库证书配置
```bash
GET /api/system/certificate/database-check
```
**响应示例**:
```json
{
"code": 0,
"message": "数据库证书检查完成",
"data": {
"healthy": true,
"certificates": {
"wechatPay": {
"privateKey": {
"relativePath": "/20230622/fb193d3bfff0467b83dc498435a4f433.pem",
"absolutePath": "/Users/gxwebsoft/Documents/uploads/file/20230622/fb193d3bfff0467b83dc498435a4f433.pem",
"exists": true
},
"certificate": {
"relativePath": "/20230622/23329e924dae41af9b716825626dd44b.pem",
"absolutePath": "/Users/gxwebsoft/Documents/uploads/file/20230622/23329e924dae41af9b716825626dd44b.pem",
"exists": true
},
"merchantId": "1246610101",
"serialNumber": "2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7",
"apiV3Key": "已配置"
},
"alipay": {
"status": "未找到支付宝配置"
}
}
}
}
```
#### 完整健康检查
```bash
GET /api/system/certificate/health
```
现在会包含三个部分的检查:
- `wechatPay`: 配置文件中的微信支付证书
- `alipay`: 配置文件中的支付宝证书
- `databaseCertificates`: 数据库中的证书配置
## 配置文件
确保`application.yml`中的证书配置正确:
```yaml
certificate:
load-mode: CLASSPATH
dev-cert-path: certs/dev
wechat-pay:
dev:
api-v3-key: "your-api-v3-key"
private-key-file: "apiclient_key.pem"
cert-dir: "wechat"
alipay:
cert-dir: "alipay"
app-cert-public-key-file: "appCertPublicKey.crt"
alipay-cert-public-key-file: "alipayCertPublicKey.crt"
alipay-root-cert-file: "alipayRootCert.crt"
```

View File

@@ -172,6 +172,19 @@ public class CertificateController extends BaseController {
}
}
@ApiOperation("检查数据库证书配置")
@GetMapping("/database-check")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<Map<String, Object>> checkDatabaseCertificates() {
try {
Map<String, Object> result = certificateHealthService.checkDatabaseCertificates();
return success("数据库证书检查完成", result);
} catch (Exception e) {
log.error("检查数据库证书配置失败", e);
return new ApiResult<>(1, "检查数据库证书配置失败: " + e.getMessage());
}
}
@ApiOperation("刷新证书缓存")
@PostMapping("/refresh")
@PreAuthorize("hasAuthority('system:certificate:manage')")

View File

@@ -1,10 +1,20 @@
package com.gxwebsoft.common.core.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.common.core.config.CertificateProperties;
import com.gxwebsoft.common.core.config.ConfigProperties;
import com.gxwebsoft.common.system.entity.Payment;
import com.gxwebsoft.common.system.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -21,12 +31,36 @@ public class CertificateHealthService {
private final CertificateService certificateService;
private final CertificateProperties certificateProperties;
@Resource
private PaymentService paymentService;
@Resource
private ConfigProperties configProperties;
@Value("${spring.profiles.active:dev}")
private String active;
public CertificateHealthService(CertificateService certificateService,
CertificateProperties certificateProperties) {
this.certificateService = certificateService;
this.certificateProperties = certificateProperties;
}
/**
* 获取当前租户ID
*/
private Integer getCurrentTenantId() {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof com.gxwebsoft.common.system.entity.User) {
return ((com.gxwebsoft.common.system.entity.User) authentication.getPrincipal()).getTenantId();
}
} catch (Exception e) {
log.warn("获取当前租户ID失败: {}", e.getMessage());
}
return 1; // 默认租户ID
}
/**
* 自定义健康检查结果类
*/
@@ -61,23 +95,31 @@ public class CertificateHealthService {
Map<String, Object> details = new HashMap<>();
boolean allHealthy = true;
// 检查微信支付证书
// 检查微信支付证书(配置文件模式)
Map<String, Object> wechatHealth = checkWechatPayCertificates();
details.put("wechatPay", wechatHealth);
if (!(Boolean) wechatHealth.get("healthy")) {
allHealthy = false;
}
// 检查支付宝证书
// 检查支付宝证书(配置文件模式)
Map<String, Object> alipayHealth = checkAlipayCertificates();
details.put("alipay", alipayHealth);
if (!(Boolean) alipayHealth.get("healthy")) {
allHealthy = false;
}
// 检查数据库中的支付配置证书路径
Map<String, Object> databaseCertHealth = checkDatabaseCertificates();
details.put("databaseCertificates", databaseCertHealth);
if (!(Boolean) databaseCertHealth.get("healthy")) {
allHealthy = false;
}
// 添加系统信息
details.put("loadMode", certificateProperties.getLoadMode());
details.put("certRootPath", certificateProperties.getCertRootPath());
details.put("currentEnvironment", active);
if (allHealthy) {
return HealthResult.up(details);
@@ -250,4 +292,132 @@ public class CertificateHealthService {
return result;
}
/**
* 检查数据库中存储的证书路径
*/
public Map<String, Object> checkDatabaseCertificates() {
Map<String, Object> health = new HashMap<>();
boolean healthy = true;
Map<String, Object> certificates = new HashMap<>();
try {
Integer tenantId = getCurrentTenantId();
log.info("检查租户 {} 的数据库证书配置", tenantId);
// 查询微信支付配置
List<Payment> wechatPayments = paymentService.list(
new LambdaQueryWrapper<Payment>()
.eq(Payment::getCode, "wxPay")
.eq(Payment::getStatus, true)
.eq(Payment::getTenantId, tenantId)
);
Map<String, Object> wechatDbCerts = new HashMap<>();
if (!wechatPayments.isEmpty()) {
Payment wechatPayment = wechatPayments.get(0);
log.info("找到微信支付配置: 商户号={}, 序列号={}", wechatPayment.getMchId(), wechatPayment.getMerchantSerialNumber());
// 检查微信支付证书路径
String apiclientKey = wechatPayment.getApiclientKey();
String apiclientCert = wechatPayment.getApiclientCert();
if (apiclientKey != null && !apiclientKey.isEmpty()) {
String keyPath = getAbsoluteCertPath(apiclientKey);
boolean keyExists = new File(keyPath).exists();
wechatDbCerts.put("privateKey", Map.of(
"relativePath", apiclientKey,
"absolutePath", keyPath,
"exists", keyExists
));
log.info("微信支付私钥证书 - 相对路径: {}, 绝对路径: {}, 存在: {}", apiclientKey, keyPath, keyExists);
if (!keyExists) healthy = false;
} else {
wechatDbCerts.put("privateKey", Map.of("error", "私钥路径未配置"));
healthy = false;
}
if (apiclientCert != null && !apiclientCert.isEmpty()) {
String certPath = getAbsoluteCertPath(apiclientCert);
boolean certExists = new File(certPath).exists();
wechatDbCerts.put("certificate", Map.of(
"relativePath", apiclientCert,
"absolutePath", certPath,
"exists", certExists
));
log.info("微信支付证书 - 相对路径: {}, 绝对路径: {}, 存在: {}", apiclientCert, certPath, certExists);
if (!certExists) healthy = false;
} else {
wechatDbCerts.put("certificate", Map.of("error", "证书路径未配置"));
healthy = false;
}
wechatDbCerts.put("merchantId", wechatPayment.getMchId());
wechatDbCerts.put("serialNumber", wechatPayment.getMerchantSerialNumber());
wechatDbCerts.put("apiV3Key", wechatPayment.getApiKey() != null ? "已配置" : "未配置");
} else {
wechatDbCerts.put("error", "未找到微信支付配置");
healthy = false;
log.warn("租户 {} 未找到微信支付配置", tenantId);
}
certificates.put("wechatPay", wechatDbCerts);
// 查询支付宝配置(如果有的话)
List<Payment> alipayPayments = paymentService.list(
new LambdaQueryWrapper<Payment>()
.eq(Payment::getCode, "alipay")
.eq(Payment::getStatus, true)
.eq(Payment::getTenantId, tenantId)
);
Map<String, Object> alipayDbCerts = new HashMap<>();
if (!alipayPayments.isEmpty()) {
Payment alipayPayment = alipayPayments.get(0);
log.info("找到支付宝配置: 应用ID={}", alipayPayment.getAppId());
// 这里可以添加支付宝证书路径检查逻辑
alipayDbCerts.put("appId", alipayPayment.getAppId());
alipayDbCerts.put("status", "支付宝配置存在,但证书路径检查需要根据具体字段实现");
} else {
alipayDbCerts.put("status", "未找到支付宝配置");
log.info("租户 {} 未找到支付宝配置", tenantId);
}
certificates.put("alipay", alipayDbCerts);
} catch (Exception e) {
log.error("检查数据库证书配置失败", e);
certificates.put("error", e.getMessage());
healthy = false;
}
health.put("healthy", healthy);
health.put("certificates", certificates);
return health;
}
/**
* 获取证书的完整绝对路径
*/
private String getAbsoluteCertPath(String relativePath) {
if (relativePath == null || relativePath.isEmpty()) {
return "";
}
// 如果是生产环境,证书存储在上传目录
if (!"dev".equals(active)) {
String uploadPath = configProperties.getUploadPath();
// 修改路径拼接规则uploadPath + "file" + 数据库存储的相对路径
String fullPath = uploadPath + "file" + relativePath;
log.debug("生产环境证书路径构建 - 上传根路径: {}, 相对路径: {}, 完整路径: {}",
uploadPath, relativePath, fullPath);
return fullPath;
} else {
// 开发环境,可能需要不同的处理逻辑
log.debug("开发环境证书路径: {}", relativePath);
return relativePath;
}
}
}

View File

@@ -116,7 +116,33 @@ public class CertificateService {
* @return 证书路径
*/
public String getWechatPayCertPath(String fileName) {
return certificateProperties.getWechatPayCertPath(fileName);
String certPath = certificateProperties.getWechatPayCertPath(fileName);
log.debug("获取微信支付证书路径 - 文件名: {}, 路径: {}", fileName, certPath);
// 打印完整的绝对路径信息
if (certificateProperties.isClasspathMode()) {
log.info("微信支付证书路径模式: CLASSPATH");
log.info("微信支付证书相对路径: {}", certPath);
try {
ClassPathResource resource = new ClassPathResource(certPath);
if (resource.exists()) {
String absolutePath = resource.getFile().getAbsolutePath();
log.info("微信支付证书完整绝对路径: {}", absolutePath);
} else {
log.warn("微信支付证书文件不存在于classpath: {}", certPath);
}
} catch (Exception e) {
log.warn("无法获取微信支付证书绝对路径: {}", e.getMessage());
}
} else {
java.io.File file = new java.io.File(certPath);
String absolutePath = file.getAbsolutePath();
log.info("微信支付证书路径模式: FILESYSTEM");
log.info("微信支付证书完整绝对路径: {}", absolutePath);
log.info("微信支付证书文件是否存在: {}", file.exists());
}
return certPath;
}
/**
@@ -126,7 +152,33 @@ public class CertificateService {
* @return 证书路径
*/
public String getAlipayCertPath(String fileName) {
return certificateProperties.getAlipayCertPath(fileName);
String certPath = certificateProperties.getAlipayCertPath(fileName);
log.debug("获取支付宝证书路径 - 文件名: {}, 路径: {}", fileName, certPath);
// 打印完整的绝对路径信息
if (certificateProperties.isClasspathMode()) {
log.info("支付宝证书路径模式: CLASSPATH");
log.info("支付宝证书相对路径: {}", certPath);
try {
ClassPathResource resource = new ClassPathResource(certPath);
if (resource.exists()) {
String absolutePath = resource.getFile().getAbsolutePath();
log.info("支付宝证书完整绝对路径: {}", absolutePath);
} else {
log.warn("支付宝证书文件不存在于classpath: {}", certPath);
}
} catch (Exception e) {
log.warn("无法获取支付宝证书绝对路径: {}", e.getMessage());
}
} else {
java.io.File file = new java.io.File(certPath);
String absolutePath = file.getAbsolutePath();
log.info("支付宝证书路径模式: FILESYSTEM");
log.info("支付宝证书完整绝对路径: {}", absolutePath);
log.info("支付宝证书文件是否存在: {}", file.exists());
}
return certPath;
}
/**

View File

@@ -82,9 +82,18 @@ public class AlipayConfigUtil {
log.debug("获取支付宝配置租户ID: {}", tenantId);
String key = "cache".concat(tenantId.toString()).concat(":setting:payment");
log.debug("Redis缓存key: {}", key);
String cache = stringRedisTemplate.opsForValue().get(key);
// 测试期间注释掉从缓存获取支付配置
// String cache = stringRedisTemplate.opsForValue().get(key);
// if (cache == null) {
// throw new BusinessException("支付方式未配置");
// }
// 测试期间:模拟缓存为空的情况
String cache = null;
log.debug("测试模式支付宝配置缓存设为null");
if (cache == null) {
throw new BusinessException("支付方式未配置");
throw new BusinessException("支付方式未配置(测试模式:缓存已注释)");
}
// 解析json数据
@@ -102,9 +111,36 @@ public class AlipayConfigUtil {
this.alipayRootCert = certificateService.getAlipayCertPath(alipayConfig.getAlipayRootCertFile());
log.info("开发环境支付宝证书路径:");
log.info("应用证书: {}", this.appCertPublicKey);
log.info("支付宝证书: {}", this.alipayCertPublicKey);
log.info("根证书: {}", this.alipayRootCert);
log.info("应用证书相对路径: {}", this.appCertPublicKey);
log.info("支付宝证书相对路径: {}", this.alipayCertPublicKey);
log.info("根证书相对路径: {}", this.alipayRootCert);
// 打印完整的绝对路径
try {
if (certificateProperties.isClasspathMode()) {
log.info("支付宝证书加载模式: CLASSPATH");
org.springframework.core.io.ClassPathResource appCertResource = new org.springframework.core.io.ClassPathResource(this.appCertPublicKey);
org.springframework.core.io.ClassPathResource alipayCertResource = new org.springframework.core.io.ClassPathResource(this.alipayCertPublicKey);
org.springframework.core.io.ClassPathResource rootCertResource = new org.springframework.core.io.ClassPathResource(this.alipayRootCert);
if (appCertResource.exists()) {
log.info("应用证书完整绝对路径: {}", appCertResource.getFile().getAbsolutePath());
}
if (alipayCertResource.exists()) {
log.info("支付宝证书完整绝对路径: {}", alipayCertResource.getFile().getAbsolutePath());
}
if (rootCertResource.exists()) {
log.info("根证书完整绝对路径: {}", rootCertResource.getFile().getAbsolutePath());
}
} else {
log.info("支付宝证书加载模式: FILESYSTEM");
log.info("应用证书完整绝对路径: {}", new java.io.File(this.appCertPublicKey).getAbsolutePath());
log.info("支付宝证书完整绝对路径: {}", new java.io.File(this.alipayCertPublicKey).getAbsolutePath());
log.info("根证书完整绝对路径: {}", new java.io.File(this.alipayRootCert).getAbsolutePath());
}
} catch (Exception e) {
log.warn("获取支付宝证书绝对路径失败: {}", e.getMessage());
}
// 检查证书文件是否存在
if (!certificateService.certificateExists("alipay", alipayConfig.getAppCertPublicKeyFile())) {
@@ -118,14 +154,20 @@ public class AlipayConfigUtil {
}
} else {
// 生产环境:使用上传的证书文件
this.appCertPublicKey = pathConfig.getUploadPath() + "file" + payment.getString("appCertPublicKey");
this.alipayCertPublicKey = pathConfig.getUploadPath() + "file" + payment.getString("alipayCertPublicKey");
this.alipayRootCert = pathConfig.getUploadPath() + "file" + payment.getString("alipayRootCert");
// 修改路径拼接规则uploadPath + "file" + 数据库存储的相对路径
String appCertPath = payment.getString("appCertPublicKey");
String alipayCertPath = payment.getString("alipayCertPublicKey");
String rootCertPath = payment.getString("alipayRootCert");
log.info("生产环境支付宝证书路径:");
log.info("应用证书: {}", this.appCertPublicKey);
log.info("支付宝证书: {}", this.alipayCertPublicKey);
log.info("根证书: {}", this.alipayRootCert);
this.appCertPublicKey = pathConfig.getUploadPath() + "file" + appCertPath;
this.alipayCertPublicKey = pathConfig.getUploadPath() + "file" + alipayCertPath;
this.alipayRootCert = pathConfig.getUploadPath() + "file" + rootCertPath;
log.info("生产环境支付宝证书路径构建:");
log.info("上传根路径: {}", pathConfig.getUploadPath());
log.info("应用证书 - 数据库路径: {}, 完整路径: {}", appCertPath, this.appCertPublicKey);
log.info("支付宝证书 - 数据库路径: {}, 完整路径: {}", alipayCertPath, this.alipayCertPublicKey);
log.info("根证书 - 数据库路径: {}, 完整路径: {}", rootCertPath, this.alipayRootCert);
}
} catch (Exception e) {
log.error("配置支付宝证书路径失败: {}", e.getMessage(), e);

View File

@@ -26,6 +26,7 @@ import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -116,18 +117,44 @@ public class WxNativePayController extends BaseController {
return build;
}
String key = "Payment:wxPay:".concat(tenantId.toString());
final Payment payment = redisUtil.get(key, Payment.class);
log.debug("获取支付配置: {}", payment);
// String key = "Payment:wxPay:".concat(tenantId.toString());
// 测试期间注释掉从缓存获取支付配置
// final Payment payment = redisUtil.get(key, Payment.class);
// log.debug("从缓存获取支付配置: {}", payment);
// 测试期间直接从数据库获取支付配置
final Payment payment = null; // 暂时设为null强制从数据库获取
log.debug("测试模式不从缓存获取支付配置payment设为null");
String apiclientKey;
try {
if (active.equals("dev")) {
// 开发环境:使用证书服务获取证书路径
apiclientKey = certificateService.getWechatPayCertPath(
String relativePath = certificateService.getWechatPayCertPath(
certificateProperties.getWechatPay().getDev().getPrivateKeyFile()
);
log.info("开发环境使用证书路径: {}", apiclientKey);
// 获取完整的绝对路径
if (certificateProperties.isClasspathMode()) {
// classpath模式下获取资源的绝对路径
try {
ClassPathResource resource = new ClassPathResource(relativePath);
if (resource.exists()) {
apiclientKey = resource.getFile().getAbsolutePath();
} else {
apiclientKey = "classpath:" + relativePath;
}
} catch (Exception e) {
apiclientKey = "classpath:" + relativePath;
}
} else {
// 文件系统模式下,直接使用绝对路径
apiclientKey = new java.io.File(relativePath).getAbsolutePath();
}
log.info("开发环境微信支付证书完整绝对路径: {}", apiclientKey);
log.info("证书相对路径: {}", relativePath);
log.info("证书加载模式: {}", certificateProperties.getLoadMode());
// 检查证书文件是否存在
if (!certificateService.certificateExists("wechat",
@@ -135,17 +162,52 @@ public class WxNativePayController extends BaseController {
throw new RuntimeException("微信支付私钥证书文件不存在");
}
} else {
// 生产环境:需要从数据库获取支付配置
if (payment == null) {
throw new RuntimeException("测试模式:支付配置为空,无法获取证书路径");
}
// 生产环境:使用上传的证书文件
apiclientKey = config.getUploadPath().concat("/file").concat(payment.getApiclientKey());
log.info("生产环境使用证书路径: {}", apiclientKey);
// 修改路径拼接规则uploadPath + "file" + 数据库存储的相对路径
String relativePath = payment.getApiclientKey();
apiclientKey = config.getUploadPath() + "file" + relativePath;
log.info("生产环境证书路径构建 - 上传根路径: {}", config.getUploadPath());
log.info("生产环境证书路径构建 - 数据库相对路径: {}", relativePath);
log.info("生产环境证书路径构建 - 完整路径: {}", apiclientKey);
}
Config wxConfig = new RSAAutoCertificateConfig.Builder()
Config wxConfig;
if (active.equals("dev") && payment == null) {
// 开发环境测试配置 - 使用测试商户号和序列号
log.info("开发环境测试模式:使用测试微信支付参数");
log.warn("注意:当前使用的是测试配置,请确保证书文件与测试商户号匹配");
// 测试用的商户号和序列号(需要根据实际测试环境调整)
String testMerchantId = "1246610101"; // 测试商户号
String testMerchantSerialNumber = "2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7"; // 测试序列号
String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key();
log.info("测试商户号: {}", testMerchantId);
log.info("测试序列号: {}", testMerchantSerialNumber);
log.info("APIv3密钥: {}", testApiV3Key != null ? "已配置" : "未配置");
wxConfig = new RSAAutoCertificateConfig.Builder()
.merchantId(testMerchantId)
.privateKeyFromPath(apiclientKey)
.merchantSerialNumber(testMerchantSerialNumber)
.apiV3Key(testApiV3Key)
.build();
} else if (payment != null) {
// 正常模式:使用数据库配置
log.info("正常模式:使用数据库中的微信支付配置");
wxConfig = new RSAAutoCertificateConfig.Builder()
.merchantId(payment.getMchId())
.privateKeyFromPath(apiclientKey)
.merchantSerialNumber(payment.getMerchantSerialNumber())
.apiV3Key(payment.getApiKey())
.build();
} else {
throw new RuntimeException("支付配置不可用payment为null且非开发环境");
}
log.info("微信支付配置创建成功");
WxNativeUtil.addConfig(tenantId, wxConfig);

View File

@@ -121,8 +121,9 @@ public class SettingServiceImpl extends ServiceImpl<SettingMapper, Setting> impl
final JSONObject jsonObject = JSONObject.parseObject(data.getContent());
final String mchId = jsonObject.getString("mchId");
final String apiclientKey = jsonObject.getString("apiclientKey");
final String privateKey = pathConfig.getUploadPath().concat("file").concat(apiclientKey);
final String apiclientCert = pathConfig.getUploadPath().concat("file").concat(jsonObject.getString("apiclientCert"));
// 修改路径拼接规则uploadPath + "file" + 数据库存储的相对路径
final String privateKey = pathConfig.getUploadPath() + "file" + apiclientKey;
final String apiclientCert = pathConfig.getUploadPath() + "file" + jsonObject.getString("apiclientCert");
final String merchantSerialNumber = jsonObject.getString("merchantSerialNumber");
final String apiV3key = jsonObject.getString("wechatApiKey");
if(config == null){

View File

@@ -9,6 +9,11 @@ spring:
password: jdj7HYEdYHnYEFBy
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
redis:
database: 0
host: 1Panel-redis-Q1LE
port: 6379
password: redis_WSDb88
# 日志配置
logging:

View File

@@ -0,0 +1,139 @@
package com.gxwebsoft.common.core;
import com.gxwebsoft.common.core.service.CertificateService;
import com.gxwebsoft.common.core.service.CertificateHealthService;
import com.gxwebsoft.common.core.config.CertificateProperties;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import java.util.Map;
/**
* 支付配置测试类
* 用于测试支付配置缓存注释和证书路径打印功能
*/
@Slf4j
@SpringBootTest
@ActiveProfiles("dev")
public class PaymentConfigTest {
@Autowired
private CertificateService certificateService;
@Autowired
private CertificateProperties certificateProperties;
@Autowired
private CertificateHealthService certificateHealthService;
@Test
public void testWechatPayCertPath() {
log.info("=== 测试微信支付证书路径获取 ===");
try {
String privateKeyFile = certificateProperties.getWechatPay().getDev().getPrivateKeyFile();
log.info("配置的私钥文件名: {}", privateKeyFile);
String certPath = certificateService.getWechatPayCertPath(privateKeyFile);
log.info("获取到的证书路径: {}", certPath);
// 测试证书是否存在
boolean exists = certificateService.certificateExists("wechat", privateKeyFile);
log.info("证书文件是否存在: {}", exists);
} catch (Exception e) {
log.error("测试微信支付证书路径失败: {}", e.getMessage(), e);
}
}
@Test
public void testAlipayCertPath() {
log.info("=== 测试支付宝证书路径获取 ===");
try {
CertificateProperties.AlipayConfig alipayConfig = certificateProperties.getAlipay();
String appCertFile = alipayConfig.getAppCertPublicKeyFile();
String alipayCertFile = alipayConfig.getAlipayCertPublicKeyFile();
String rootCertFile = alipayConfig.getAlipayRootCertFile();
log.info("配置的应用证书文件名: {}", appCertFile);
log.info("配置的支付宝证书文件名: {}", alipayCertFile);
log.info("配置的根证书文件名: {}", rootCertFile);
String appCertPath = certificateService.getAlipayCertPath(appCertFile);
String alipayCertPath = certificateService.getAlipayCertPath(alipayCertFile);
String rootCertPath = certificateService.getAlipayCertPath(rootCertFile);
log.info("获取到的应用证书路径: {}", appCertPath);
log.info("获取到的支付宝证书路径: {}", alipayCertPath);
log.info("获取到的根证书路径: {}", rootCertPath);
} catch (Exception e) {
log.error("测试支付宝证书路径失败: {}", e.getMessage(), e);
}
}
@Test
public void testCertificateProperties() {
log.info("=== 测试证书配置属性 ===");
log.info("证书加载模式: {}", certificateProperties.getLoadMode());
log.info("证书根路径: {}", certificateProperties.getCertRootPath());
log.info("开发环境证书路径: {}", certificateProperties.getDevCertPath());
log.info("是否为classpath模式: {}", certificateProperties.isClasspathMode());
// 微信支付配置
CertificateProperties.WechatPayConfig wechatPay = certificateProperties.getWechatPay();
log.info("微信支付证书目录: {}", wechatPay.getCertDir());
log.info("微信支付APIv3密钥: {}", wechatPay.getDev().getApiV3Key() != null ? "已配置" : "未配置");
log.info("微信支付私钥文件: {}", wechatPay.getDev().getPrivateKeyFile());
// 支付宝配置
CertificateProperties.AlipayConfig alipay = certificateProperties.getAlipay();
log.info("支付宝证书目录: {}", alipay.getCertDir());
log.info("支付宝应用证书文件: {}", alipay.getAppCertPublicKeyFile());
}
@Test
public void testDatabaseCertificateCheck() {
log.info("=== 测试数据库证书配置检查 ===");
try {
Map<String, Object> result = certificateHealthService.checkDatabaseCertificates();
log.info("数据库证书检查结果: {}", result);
Boolean healthy = (Boolean) result.get("healthy");
log.info("证书健康状态: {}", healthy ? "健康" : "不健康");
@SuppressWarnings("unchecked")
Map<String, Object> certificates = (Map<String, Object>) result.get("certificates");
if (certificates != null) {
log.info("证书详细信息:");
certificates.forEach((key, value) -> {
log.info(" {}: {}", key, value);
});
}
} catch (Exception e) {
log.error("测试数据库证书检查失败: {}", e.getMessage(), e);
}
}
@Test
public void testCertificateHealthCheck() {
log.info("=== 测试完整证书健康检查 ===");
try {
CertificateHealthService.HealthResult healthResult = certificateHealthService.health();
log.info("证书健康检查状态: {}", healthResult.getStatus());
log.info("证书健康检查详情: {}", healthResult.getDetails());
} catch (Exception e) {
log.error("测试证书健康检查失败: {}", e.getMessage(), e);
}
}
}

53
test-database-cert-check.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
# 数据库证书检查测试脚本
# 用于测试新增的数据库证书路径检查功能
echo "=== 数据库证书配置检查测试 ==="
# 设置基础URL根据实际部署环境调整
BASE_URL="http://localhost:8080"
# 设置认证头(根据实际认证方式调整)
AUTH_HEADER="Authorization: Bearer your-token-here"
TENANT_HEADER="tenantId: 1"
echo "1. 测试数据库证书配置检查..."
curl -X GET \
"${BASE_URL}/api/system/certificate/database-check" \
-H "${AUTH_HEADER}" \
-H "${TENANT_HEADER}" \
-H "Content-Type: application/json" \
| jq '.'
echo -e "\n2. 测试完整证书健康检查..."
curl -X GET \
"${BASE_URL}/api/system/certificate/health" \
-H "${AUTH_HEADER}" \
-H "${TENANT_HEADER}" \
-H "Content-Type: application/json" \
| jq '.'
echo -e "\n3. 测试证书诊断信息..."
curl -X GET \
"${BASE_URL}/api/system/certificate/diagnostic" \
-H "${AUTH_HEADER}" \
-H "${TENANT_HEADER}" \
-H "Content-Type: application/json" \
| jq '.'
echo -e "\n=== 测试完成 ==="
# 使用说明
echo -e "\n使用说明"
echo "1. 请根据实际环境修改 BASE_URL"
echo "2. 请根据实际认证方式修改 AUTH_HEADER"
echo "3. 请根据实际租户ID修改 TENANT_HEADER"
echo "4. 确保已安装 jq 工具用于格式化JSON输出"
echo "5. 如果没有jq可以去掉 '| jq \".\"' 部分"
# 预期输出说明
echo -e "\n预期输出"
echo "- database-check: 显示数据库中证书的相对路径、绝对路径和存在状态"
echo "- health: 显示配置文件证书和数据库证书的综合健康状态"
echo "- diagnostic: 显示详细的证书诊断信息"