改造支付证书管理模块

This commit is contained in:
2025-07-26 20:47:43 +08:00
parent 9d61f9dc38
commit 2d2b913eb3
23 changed files with 2305 additions and 21 deletions

View File

@@ -0,0 +1,197 @@
package com.gxwebsoft.common.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 证书配置属性类
* 支持开发环境从classpath加载证书生产环境从Docker挂载卷加载证书
*
* @author 科技小王子
* @since 2024-07-26
*/
@Data
@Component
@ConfigurationProperties(prefix = "certificate")
public class CertificateProperties {
/**
* 证书加载模式
* CLASSPATH: 从classpath加载开发环境
* FILESYSTEM: 从文件系统加载(生产环境)
* VOLUME: 从Docker挂载卷加载容器环境
*/
private LoadMode loadMode = LoadMode.CLASSPATH;
/**
* Docker挂载卷证书根路径
*/
private String certRootPath = "/app/certs";
/**
* 开发环境证书路径前缀
*/
private String devCertPath = "certs/dev";
/**
* 微信支付证书配置
*/
private WechatPayConfig wechatPay = new WechatPayConfig();
/**
* 支付宝证书配置
*/
private AlipayConfig alipay = new AlipayConfig();
/**
* 证书加载模式枚举
*/
public enum LoadMode {
CLASSPATH, // 从classpath加载
FILESYSTEM, // 从文件系统加载
VOLUME // 从Docker挂载卷加载
}
/**
* 微信支付证书配置
*/
@Data
public static class WechatPayConfig {
/**
* 开发环境配置
*/
private DevConfig dev = new DevConfig();
/**
* 生产环境基础路径
*/
private String prodBasePath = "/file";
/**
* 微信支付证书目录名
*/
private String certDir = "wechat";
@Data
public static class DevConfig {
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* 商户私钥证书文件名
*/
private String privateKeyFile = "apiclient_key.pem";
/**
* 商户证书文件名
*/
private String apiclientCertFile = "apiclient_cert.pem";
/**
* 微信支付平台证书文件名
*/
private String wechatpayCertFile = "wechatpay_cert.pem";
}
}
/**
* 支付宝证书配置
*/
@Data
public static class AlipayConfig {
/**
* 支付宝证书目录名
*/
private String certDir = "alipay";
/**
* 应用私钥文件名
*/
private String appPrivateKeyFile = "app_private_key.pem";
/**
* 应用公钥证书文件名
*/
private String appCertPublicKeyFile = "appCertPublicKey.crt";
/**
* 支付宝公钥证书文件名
*/
private String alipayCertPublicKeyFile = "alipayCertPublicKey.crt";
/**
* 支付宝根证书文件名
*/
private String alipayRootCertFile = "alipayRootCert.crt";
}
/**
* 获取证书文件的完整路径
*
* @param certType 证书类型wechat/alipay
* @param fileName 文件名
* @return 完整路径
*/
public String getCertificatePath(String certType, String fileName) {
switch (loadMode) {
case CLASSPATH:
return devCertPath + "/" + certType + "/" + fileName;
case FILESYSTEM:
return System.getProperty("user.dir") + "/certs/" + certType + "/" + fileName;
case VOLUME:
return certRootPath + "/" + certType + "/" + fileName;
default:
throw new IllegalArgumentException("不支持的证书加载模式: " + loadMode);
}
}
/**
* 获取微信支付证书路径
*
* @param fileName 文件名
* @return 完整路径
*/
public String getWechatPayCertPath(String fileName) {
return getCertificatePath(wechatPay.getCertDir(), fileName);
}
/**
* 获取支付宝证书路径
*
* @param fileName 文件名
* @return 完整路径
*/
public String getAlipayCertPath(String fileName) {
return getCertificatePath(alipay.getCertDir(), fileName);
}
/**
* 检查证书加载模式是否为classpath模式
*
* @return true if classpath mode
*/
public boolean isClasspathMode() {
return LoadMode.CLASSPATH.equals(loadMode);
}
/**
* 检查证书加载模式是否为文件系统模式
*
* @return true if filesystem mode
*/
public boolean isFilesystemMode() {
return LoadMode.FILESYSTEM.equals(loadMode);
}
/**
* 检查证书加载模式是否为挂载卷模式
*
* @return true if volume mode
*/
public boolean isVolumeMode() {
return LoadMode.VOLUME.equals(loadMode);
}
}

View File

@@ -0,0 +1,188 @@
package com.gxwebsoft.common.core.controller;
import com.gxwebsoft.common.core.service.CertificateHealthService;
import com.gxwebsoft.common.core.service.CertificateService;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.health.Health;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Map;
/**
* 证书管理控制器
* 提供证书状态查询、健康检查等功能
*
* @author 科技小王子
* @since 2024-07-26
*/
@Slf4j
@Api(tags = "证书管理")
@RestController
@RequestMapping("/api/system/certificate")
public class CertificateController extends BaseController {
@Resource
private CertificateService certificateService;
@Resource
private CertificateHealthService certificateHealthService;
@ApiOperation("获取所有证书状态")
@GetMapping("/status")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<Map<String, Object>> getCertificateStatus() {
try {
Map<String, Object> status = certificateService.getAllCertificateStatus();
return success("获取证书状态成功", status);
} catch (Exception e) {
log.error("获取证书状态失败", e);
return fail("获取证书状态失败: " + e.getMessage());
}
}
@ApiOperation("证书健康检查")
@GetMapping("/health")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<Map<String, Object>> healthCheck() {
try {
Health health = certificateHealthService.health();
Map<String, Object> result = Map.of(
"status", health.getStatus().getCode(),
"details", health.getDetails()
);
return success("证书健康检查完成", result);
} catch (Exception e) {
log.error("证书健康检查失败", e);
return fail("证书健康检查失败: " + e.getMessage());
}
}
@ApiOperation("获取证书诊断信息")
@GetMapping("/diagnostic")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<Map<String, Object>> getDiagnosticInfo() {
try {
Map<String, Object> diagnostic = certificateHealthService.getDiagnosticInfo();
return success("获取证书诊断信息成功", diagnostic);
} catch (Exception e) {
log.error("获取证书诊断信息失败", e);
return fail("获取证书诊断信息失败: " + e.getMessage());
}
}
@ApiOperation("检查特定证书")
@GetMapping("/check/{certType}/{fileName}")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<Map<String, Object>> checkSpecificCertificate(
@ApiParam(value = "证书类型", example = "wechat") @PathVariable String certType,
@ApiParam(value = "文件名", example = "apiclient_key.pem") @PathVariable String fileName) {
try {
Map<String, Object> result = certificateHealthService.checkSpecificCertificate(certType, fileName);
return success("检查证书完成", result);
} catch (Exception e) {
log.error("检查证书失败: {}/{}", certType, fileName, e);
return fail("检查证书失败: " + e.getMessage());
}
}
@ApiOperation("验证证书文件")
@GetMapping("/validate/{certType}/{fileName}")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<CertificateService.CertificateInfo> validateCertificate(
@ApiParam(value = "证书类型", example = "wechat") @PathVariable String certType,
@ApiParam(value = "文件名", example = "apiclient_cert.pem") @PathVariable String fileName) {
try {
CertificateService.CertificateInfo certInfo =
certificateService.validateX509Certificate(certType, fileName);
if (certInfo != null) {
return success("证书验证成功", certInfo);
} else {
return fail("证书验证失败可能不是有效的X509证书");
}
} catch (Exception e) {
log.error("验证证书失败: {}/{}", certType, fileName, e);
return fail("验证证书失败: " + e.getMessage());
}
}
@ApiOperation("检查证书文件是否存在")
@GetMapping("/exists/{certType}/{fileName}")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<Boolean> checkCertificateExists(
@ApiParam(value = "证书类型", example = "alipay") @PathVariable String certType,
@ApiParam(value = "文件名", example = "appCertPublicKey.crt") @PathVariable String fileName) {
try {
boolean exists = certificateService.certificateExists(certType, fileName);
String message = exists ? "证书文件存在" : "证书文件不存在";
return success(message, exists);
} catch (Exception e) {
log.error("检查证书文件存在性失败: {}/{}", certType, fileName, e);
return fail("检查证书文件存在性失败: " + e.getMessage());
}
}
@ApiOperation("获取证书文件路径")
@GetMapping("/path/{certType}/{fileName}")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<String> getCertificatePath(
@ApiParam(value = "证书类型", example = "wechat") @PathVariable String certType,
@ApiParam(value = "文件名", example = "wechatpay_cert.pem") @PathVariable String fileName) {
try {
String path = certificateService.getCertificateFilePath(certType, fileName);
return success("获取证书路径成功", path);
} catch (Exception e) {
log.error("获取证书路径失败: {}/{}", certType, fileName, e);
return fail("获取证书路径失败: " + e.getMessage());
}
}
@ApiOperation("获取微信支付证书路径")
@GetMapping("/wechat-path/{fileName}")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<String> getWechatPayCertPath(
@ApiParam(value = "文件名", example = "apiclient_key.pem") @PathVariable String fileName) {
try {
String path = certificateService.getWechatPayCertPath(fileName);
return success("获取微信支付证书路径成功", path);
} catch (Exception e) {
log.error("获取微信支付证书路径失败: {}", fileName, e);
return fail("获取微信支付证书路径失败: " + e.getMessage());
}
}
@ApiOperation("获取支付宝证书路径")
@GetMapping("/alipay-path/{fileName}")
@PreAuthorize("hasAuthority('system:certificate:view')")
public ApiResult<String> getAlipayCertPath(
@ApiParam(value = "文件名", example = "appCertPublicKey.crt") @PathVariable String fileName) {
try {
String path = certificateService.getAlipayCertPath(fileName);
return success("获取支付宝证书路径成功", path);
} catch (Exception e) {
log.error("获取支付宝证书路径失败: {}", fileName, e);
return fail("获取支付宝证书路径失败: " + e.getMessage());
}
}
@ApiOperation("刷新证书缓存")
@PostMapping("/refresh")
@PreAuthorize("hasAuthority('system:certificate:manage')")
public ApiResult<String> refreshCertificateCache() {
try {
// 这里可以添加刷新证书缓存的逻辑
log.info("证书缓存刷新请求,操作用户: {}", getLoginUser().getUsername());
return success("证书缓存刷新成功");
} catch (Exception e) {
log.error("刷新证书缓存失败", e);
return fail("刷新证书缓存失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,227 @@
package com.gxwebsoft.common.core.service;
import com.gxwebsoft.common.core.config.CertificateProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 证书健康检查服务
* 实现Spring Boot Actuator健康检查接口
*
* @author 科技小王子
* @since 2024-07-26
*/
@Slf4j
@Service
public class CertificateHealthService implements HealthIndicator {
private final CertificateService certificateService;
private final CertificateProperties certificateProperties;
public CertificateHealthService(CertificateService certificateService,
CertificateProperties certificateProperties) {
this.certificateService = certificateService;
this.certificateProperties = certificateProperties;
}
@Override
public Health health() {
try {
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;
}
// 添加系统信息
details.put("loadMode", certificateProperties.getLoadMode());
details.put("certRootPath", certificateProperties.getCertRootPath());
if (allHealthy) {
return Health.up().withDetails(details).build();
} else {
return Health.down().withDetails(details).build();
}
} catch (Exception e) {
log.error("证书健康检查失败", e);
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
/**
* 检查微信支付证书健康状态
*/
private Map<String, Object> checkWechatPayCertificates() {
Map<String, Object> health = new HashMap<>();
boolean healthy = true;
Map<String, Object> certificates = new HashMap<>();
CertificateProperties.WechatPayConfig wechatConfig = certificateProperties.getWechatPay();
// 检查私钥证书
String privateKeyFile = wechatConfig.getDev().getPrivateKeyFile();
boolean privateKeyExists = certificateService.certificateExists("wechat", privateKeyFile);
certificates.put("privateKey", Map.of(
"file", privateKeyFile,
"exists", privateKeyExists,
"path", certificateService.getWechatPayCertPath(privateKeyFile)
));
if (!privateKeyExists) healthy = false;
// 检查商户证书
String apiclientCertFile = wechatConfig.getDev().getApiclientCertFile();
boolean apiclientCertExists = certificateService.certificateExists("wechat", apiclientCertFile);
certificates.put("apiclientCert", Map.of(
"file", apiclientCertFile,
"exists", apiclientCertExists,
"path", certificateService.getWechatPayCertPath(apiclientCertFile)
));
if (!apiclientCertExists) healthy = false;
// 检查微信支付平台证书
String wechatpayCertFile = wechatConfig.getDev().getWechatpayCertFile();
boolean wechatpayCertExists = certificateService.certificateExists("wechat", wechatpayCertFile);
certificates.put("wechatpayCert", Map.of(
"file", wechatpayCertFile,
"exists", wechatpayCertExists,
"path", certificateService.getWechatPayCertPath(wechatpayCertFile)
));
if (!wechatpayCertExists) healthy = false;
health.put("healthy", healthy);
health.put("certificates", certificates);
return health;
}
/**
* 检查支付宝证书健康状态
*/
private Map<String, Object> checkAlipayCertificates() {
Map<String, Object> health = new HashMap<>();
boolean healthy = true;
Map<String, Object> certificates = new HashMap<>();
CertificateProperties.AlipayConfig alipayConfig = certificateProperties.getAlipay();
// 检查应用私钥
String appPrivateKeyFile = alipayConfig.getAppPrivateKeyFile();
boolean appPrivateKeyExists = certificateService.certificateExists("alipay", appPrivateKeyFile);
certificates.put("appPrivateKey", Map.of(
"file", appPrivateKeyFile,
"exists", appPrivateKeyExists,
"path", certificateService.getAlipayCertPath(appPrivateKeyFile)
));
if (!appPrivateKeyExists) healthy = false;
// 检查应用公钥证书
String appCertPublicKeyFile = alipayConfig.getAppCertPublicKeyFile();
boolean appCertExists = certificateService.certificateExists("alipay", appCertPublicKeyFile);
certificates.put("appCertPublicKey", Map.of(
"file", appCertPublicKeyFile,
"exists", appCertExists,
"path", certificateService.getAlipayCertPath(appCertPublicKeyFile)
));
if (!appCertExists) healthy = false;
// 检查支付宝公钥证书
String alipayCertPublicKeyFile = alipayConfig.getAlipayCertPublicKeyFile();
boolean alipayCertExists = certificateService.certificateExists("alipay", alipayCertPublicKeyFile);
certificates.put("alipayCertPublicKey", Map.of(
"file", alipayCertPublicKeyFile,
"exists", alipayCertExists,
"path", certificateService.getAlipayCertPath(alipayCertPublicKeyFile)
));
if (!alipayCertExists) healthy = false;
// 检查支付宝根证书
String alipayRootCertFile = alipayConfig.getAlipayRootCertFile();
boolean rootCertExists = certificateService.certificateExists("alipay", alipayRootCertFile);
certificates.put("alipayRootCert", Map.of(
"file", alipayRootCertFile,
"exists", rootCertExists,
"path", certificateService.getAlipayCertPath(alipayRootCertFile)
));
if (!rootCertExists) healthy = false;
health.put("healthy", healthy);
health.put("certificates", certificates);
return health;
}
/**
* 获取详细的证书诊断信息
*/
public Map<String, Object> getDiagnosticInfo() {
Map<String, Object> diagnostic = new HashMap<>();
try {
// 基本系统信息
diagnostic.put("loadMode", certificateProperties.getLoadMode());
diagnostic.put("certRootPath", certificateProperties.getCertRootPath());
diagnostic.put("devCertPath", certificateProperties.getDevCertPath());
// 获取所有证书状态
diagnostic.put("certificateStatus", certificateService.getAllCertificateStatus());
// 健康检查结果
Health health = health();
diagnostic.put("healthStatus", health.getStatus().getCode());
diagnostic.put("healthDetails", health.getDetails());
} catch (Exception e) {
log.error("获取证书诊断信息失败", e);
diagnostic.put("error", e.getMessage());
}
return diagnostic;
}
/**
* 检查特定证书的详细信息
*/
public Map<String, Object> checkSpecificCertificate(String certType, String fileName) {
Map<String, Object> result = new HashMap<>();
try {
boolean exists = certificateService.certificateExists(certType, fileName);
String path = certificateService.getCertificateFilePath(certType, fileName);
result.put("certType", certType);
result.put("fileName", fileName);
result.put("exists", exists);
result.put("path", path);
if (exists && (fileName.endsWith(".crt") || fileName.endsWith(".pem"))) {
// 尝试验证证书
CertificateService.CertificateInfo certInfo =
certificateService.validateX509Certificate(certType, fileName);
result.put("certificateInfo", certInfo);
}
} catch (Exception e) {
log.error("检查证书失败: {}/{}", certType, fileName, e);
result.put("error", e.getMessage());
}
return result;
}
}

View File

@@ -0,0 +1,272 @@
package com.gxwebsoft.common.core.service;
import com.gxwebsoft.common.core.config.CertificateProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 证书管理服务
* 负责处理不同环境下的证书加载、验证和管理
*
* @author 科技小王子
* @since 2024-07-26
*/
@Slf4j
@Service
public class CertificateService {
private final CertificateProperties certificateProperties;
public CertificateService(CertificateProperties certificateProperties) {
this.certificateProperties = certificateProperties;
}
@PostConstruct
public void init() {
log.info("证书服务初始化,当前加载模式: {}", certificateProperties.getLoadMode());
log.info("证书根路径: {}", certificateProperties.getCertRootPath());
// 检查证书目录和文件
checkCertificateDirectories();
}
/**
* 获取证书文件的输入流
*
* @param certType 证书类型wechat/alipay
* @param fileName 文件名
* @return 输入流
* @throws IOException 文件读取异常
*/
public InputStream getCertificateInputStream(String certType, String fileName) throws IOException {
String certPath = certificateProperties.getCertificatePath(certType, fileName);
if (certificateProperties.isClasspathMode()) {
// 从classpath加载
Resource resource = new ClassPathResource(certPath);
if (!resource.exists()) {
throw new IOException("证书文件不存在: " + certPath);
}
log.debug("从classpath加载证书: {}", certPath);
return resource.getInputStream();
} else {
// 从文件系统加载
File file = new File(certPath);
if (!file.exists()) {
throw new IOException("证书文件不存在: " + certPath);
}
log.debug("从文件系统加载证书: {}", certPath);
return Files.newInputStream(file.toPath());
}
}
/**
* 获取证书文件路径
*
* @param certType 证书类型
* @param fileName 文件名
* @return 文件路径
*/
public String getCertificateFilePath(String certType, String fileName) {
return certificateProperties.getCertificatePath(certType, fileName);
}
/**
* 检查证书文件是否存在
*
* @param certType 证书类型
* @param fileName 文件名
* @return 是否存在
*/
public boolean certificateExists(String certType, String fileName) {
try {
String certPath = certificateProperties.getCertificatePath(certType, fileName);
if (certificateProperties.isClasspathMode()) {
Resource resource = new ClassPathResource(certPath);
return resource.exists();
} else {
File file = new File(certPath);
return file.exists() && file.isFile();
}
} catch (Exception e) {
log.error("检查证书文件存在性时出错: {}", e.getMessage());
return false;
}
}
/**
* 获取微信支付证书路径
*
* @param fileName 文件名
* @return 证书路径
*/
public String getWechatPayCertPath(String fileName) {
return certificateProperties.getWechatPayCertPath(fileName);
}
/**
* 获取支付宝证书路径
*
* @param fileName 文件名
* @return 证书路径
*/
public String getAlipayCertPath(String fileName) {
return certificateProperties.getAlipayCertPath(fileName);
}
/**
* 验证X509证书
*
* @param certType 证书类型
* @param fileName 文件名
* @return 证书信息
*/
public CertificateInfo validateX509Certificate(String certType, String fileName) {
try (InputStream inputStream = getCertificateInputStream(certType, fileName)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
CertificateInfo info = new CertificateInfo();
info.setSubject(cert.getSubjectDN().toString());
info.setIssuer(cert.getIssuerDN().toString());
info.setNotBefore(cert.getNotBefore());
info.setNotAfter(cert.getNotAfter());
info.setSerialNumber(cert.getSerialNumber().toString());
info.setValid(isValidDate(cert.getNotBefore(), cert.getNotAfter()));
return info;
} catch (Exception e) {
log.error("验证证书失败: {}/{}, 错误: {}", certType, fileName, e.getMessage());
return null;
}
}
/**
* 检查证书目录结构
*/
private void checkCertificateDirectories() {
String[] certTypes = {"wechat", "alipay"};
for (String certType : certTypes) {
if (!certificateProperties.isClasspathMode()) {
// 检查文件系统目录
String dirPath = certificateProperties.getCertificatePath(certType, "");
File dir = new File(dirPath);
if (!dir.exists()) {
log.warn("证书目录不存在: {}", dirPath);
} else {
log.info("证书目录存在: {}", dirPath);
}
}
}
}
/**
* 获取所有证书状态
*
* @return 证书状态映射
*/
public Map<String, Object> getAllCertificateStatus() {
Map<String, Object> status = new HashMap<>();
// 微信支付证书状态
Map<String, Object> wechatStatus = new HashMap<>();
CertificateProperties.WechatPayConfig wechatConfig = certificateProperties.getWechatPay();
wechatStatus.put("privateKey", getCertStatus("wechat", wechatConfig.getDev().getPrivateKeyFile()));
wechatStatus.put("apiclientCert", getCertStatus("wechat", wechatConfig.getDev().getApiclientCertFile()));
wechatStatus.put("wechatpayCert", getCertStatus("wechat", wechatConfig.getDev().getWechatpayCertFile()));
status.put("wechat", wechatStatus);
// 支付宝证书状态
Map<String, Object> alipayStatus = new HashMap<>();
CertificateProperties.AlipayConfig alipayConfig = certificateProperties.getAlipay();
alipayStatus.put("appPrivateKey", getCertStatus("alipay", alipayConfig.getAppPrivateKeyFile()));
alipayStatus.put("appCertPublicKey", getCertStatus("alipay", alipayConfig.getAppCertPublicKeyFile()));
alipayStatus.put("alipayCertPublicKey", getCertStatus("alipay", alipayConfig.getAlipayCertPublicKeyFile()));
alipayStatus.put("alipayRootCert", getCertStatus("alipay", alipayConfig.getAlipayRootCertFile()));
status.put("alipay", alipayStatus);
// 系统信息
Map<String, Object> systemInfo = new HashMap<>();
systemInfo.put("loadMode", certificateProperties.getLoadMode());
systemInfo.put("certRootPath", certificateProperties.getCertRootPath());
systemInfo.put("devCertPath", certificateProperties.getDevCertPath());
status.put("system", systemInfo);
return status;
}
/**
* 获取单个证书状态
*/
private Map<String, Object> getCertStatus(String certType, String fileName) {
Map<String, Object> status = new HashMap<>();
status.put("fileName", fileName);
status.put("exists", certificateExists(certType, fileName));
status.put("path", getCertificateFilePath(certType, fileName));
// 如果是.crt或.pem文件尝试验证证书
if (fileName.endsWith(".crt") || fileName.endsWith(".pem")) {
CertificateInfo certInfo = validateX509Certificate(certType, fileName);
status.put("certificateInfo", certInfo);
}
return status;
}
/**
* 检查日期是否有效
*/
private boolean isValidDate(Date notBefore, Date notAfter) {
Date now = new Date();
return now.after(notBefore) && now.before(notAfter);
}
/**
* 证书信息类
*/
public static class CertificateInfo {
private String subject;
private String issuer;
private Date notBefore;
private Date notAfter;
private String serialNumber;
private boolean valid;
// Getters and Setters
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getIssuer() { return issuer; }
public void setIssuer(String issuer) { this.issuer = issuer; }
public Date getNotBefore() { return notBefore; }
public void setNotBefore(Date notBefore) { this.notBefore = notBefore; }
public Date getNotAfter() { return notAfter; }
public void setNotAfter(Date notAfter) { this.notAfter = notAfter; }
public String getSerialNumber() { return serialNumber; }
public void setSerialNumber(String serialNumber) { this.serialNumber = serialNumber; }
public boolean isValid() { return valid; }
public void setValid(boolean valid) { this.valid = valid; }
}
}

View File

@@ -7,7 +7,11 @@ import com.alipay.api.AlipayConstants;
import com.alipay.api.CertAlipayRequest;
import com.alipay.api.DefaultAlipayClient;
import com.gxwebsoft.common.core.config.ConfigProperties;
import com.gxwebsoft.common.core.config.CertificateProperties;
import com.gxwebsoft.common.core.service.CertificateService;
import com.gxwebsoft.common.core.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@@ -15,9 +19,11 @@ import javax.annotation.Resource;
/**
* 支付宝工具类
* 支持新的证书管理系统
* @author leng
*
*/
@Slf4j
@Component
public class AlipayConfigUtil {
private final StringRedisTemplate stringRedisTemplate;
@@ -30,8 +36,15 @@ public class AlipayConfigUtil {
public String alipayCertPublicKey;
public String alipayRootCert;
@Value("${spring.profiles.active}")
private String active;
@Resource
private ConfigProperties pathConfig;
@Resource
private CertificateService certificateService;
@Resource
private CertificateProperties certificateProperties;
public AlipayConfigUtil(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
@@ -66,21 +79,59 @@ public class AlipayConfigUtil {
* 获取支付宝秘钥
*/
public JSONObject payment(Integer tenantId) {
System.out.println("tenantId = " + tenantId);
log.debug("获取支付宝配置租户ID: {}", tenantId);
String key = "cache".concat(tenantId.toString()).concat(":setting:payment");
System.out.println("key = " + key);
log.debug("Redis缓存key: {}", key);
String cache = stringRedisTemplate.opsForValue().get(key);
if (cache == null) {
throw new BusinessException("支付方式未配置");
}
// 解析json数据
JSONObject payment = JSON.parseObject(cache.getBytes());
this.config = payment;
this.appId = payment.getString("alipayAppId");
this.privateKey = payment.getString("privateKey");
this.appCertPublicKey = pathConfig.getUploadPath() + "file" + payment.getString("appCertPublicKey");
this.alipayCertPublicKey = pathConfig.getUploadPath() + "file" + payment.getString("alipayCertPublicKey");
this.alipayRootCert = pathConfig.getUploadPath() + "file" + payment.getString("alipayRootCert");
try {
if (active.equals("dev")) {
// 开发环境:使用证书服务获取证书路径
CertificateProperties.AlipayConfig alipayConfig = certificateProperties.getAlipay();
this.appCertPublicKey = certificateService.getAlipayCertPath(alipayConfig.getAppCertPublicKeyFile());
this.alipayCertPublicKey = certificateService.getAlipayCertPath(alipayConfig.getAlipayCertPublicKeyFile());
this.alipayRootCert = certificateService.getAlipayCertPath(alipayConfig.getAlipayRootCertFile());
log.info("开发环境支付宝证书路径:");
log.info("应用证书: {}", this.appCertPublicKey);
log.info("支付宝证书: {}", this.alipayCertPublicKey);
log.info("根证书: {}", this.alipayRootCert);
// 检查证书文件是否存在
if (!certificateService.certificateExists("alipay", alipayConfig.getAppCertPublicKeyFile())) {
throw new RuntimeException("支付宝应用证书文件不存在");
}
if (!certificateService.certificateExists("alipay", alipayConfig.getAlipayCertPublicKeyFile())) {
throw new RuntimeException("支付宝公钥证书文件不存在");
}
if (!certificateService.certificateExists("alipay", alipayConfig.getAlipayRootCertFile())) {
throw new RuntimeException("支付宝根证书文件不存在");
}
} 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");
log.info("生产环境支付宝证书路径:");
log.info("应用证书: {}", this.appCertPublicKey);
log.info("支付宝证书: {}", this.alipayCertPublicKey);
log.info("根证书: {}", this.alipayRootCert);
}
} catch (Exception e) {
log.error("配置支付宝证书路径失败: {}", e.getMessage(), e);
throw new RuntimeException("支付宝证书配置失败: " + e.getMessage());
}
return payment;
}

View File

@@ -2,6 +2,8 @@ package com.gxwebsoft.common.system.controller;
import com.alibaba.fastjson.JSONObject;
import com.gxwebsoft.common.core.config.ConfigProperties;
import com.gxwebsoft.common.core.config.CertificateProperties;
import com.gxwebsoft.common.core.service.CertificateService;
import com.gxwebsoft.common.core.utils.CommonUtil;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.common.core.utils.RequestUtil;
@@ -22,6 +24,7 @@ import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import io.swagger.annotations.Api;
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.web.bind.annotation.*;
@@ -31,19 +34,21 @@ import java.util.Map;
import java.util.Set;
@Slf4j
@Api(tags = "微信Native支付接口")
@RestController
@RequestMapping("/api/system/wx-native-pay")
public class WxNativePayController extends BaseController {
@Value("${spring.profiles.active}")
String active;
// 保留用于兼容性的静态配置(开发环境备用)
public static String merchantId = "1246610101";
public static String privateKeyPath = "/Users/gxwebsoft/JAVA/com.gxwebsoft.core/src/main/resources/cert/apiclient_key.pem";
public static String merchantSerialNumber = "48749613B40AA8F1D768583FC352358E13EB5AF0";
public static String apiV3Key = "zGufUcqa7ovgxRL0kF5OlPr482EZwtn9";
@Resource
private RedisUtil redisUtil;
private Config wxPayConfig;
@Resource
private ConfigProperties config;
@Resource
@@ -52,6 +57,10 @@ public class WxNativePayController extends BaseController {
private RequestUtil requestUtil;
@Resource
private OrderService orderService;
@Resource
private CertificateService certificateService;
@Resource
private CertificateProperties certificateProperties;
@ApiOperation("生成付款码")
@@ -106,23 +115,46 @@ public class WxNativePayController extends BaseController {
if (build != null) {
return build;
}
String key = "Payment:wxPay:".concat(tenantId.toString());
final Payment payment = redisUtil.get(key, Payment.class);
System.out.println("payment = " + payment);
String apiclientKey = config.getUploadPath().concat("/file").concat(payment.getApiclientKey());
// 开发环境配置
if(active.equals("dev")){
apiclientKey = privateKeyPath;
log.debug("获取支付配置: {}", payment);
String apiclientKey;
try {
if (active.equals("dev")) {
// 开发环境:使用证书服务获取证书路径
apiclientKey = certificateService.getWechatPayCertPath(
certificateProperties.getWechatPay().getDev().getPrivateKeyFile()
);
log.info("开发环境使用证书路径: {}", apiclientKey);
// 检查证书文件是否存在
if (!certificateService.certificateExists("wechat",
certificateProperties.getWechatPay().getDev().getPrivateKeyFile())) {
throw new RuntimeException("微信支付私钥证书文件不存在");
}
} else {
// 生产环境:使用上传的证书文件
apiclientKey = config.getUploadPath().concat("/file").concat(payment.getApiclientKey());
log.info("生产环境使用证书路径: {}", apiclientKey);
}
Config wxConfig = new RSAAutoCertificateConfig.Builder()
.merchantId(payment.getMchId())
.privateKeyFromPath(apiclientKey)
.merchantSerialNumber(payment.getMerchantSerialNumber())
.apiV3Key(payment.getApiKey())
.build();
log.info("微信支付配置创建成功");
WxNativeUtil.addConfig(tenantId, wxConfig);
return wxConfig;
} catch (Exception e) {
log.error("创建微信支付配置失败: {}", e.getMessage(), e);
throw new RuntimeException("微信支付配置失败: " + e.getMessage());
}
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(payment.getMchId())
.privateKeyFromPath(apiclientKey)
.merchantSerialNumber(payment.getMerchantSerialNumber())
.apiV3Key(payment.getApiKey())
.build();
System.out.println("config = " + config);
WxNativeUtil.addConfig(tenantId, config);
return config;
}
@ApiModelProperty("异步通知")