改造支付证书管理模块
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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("异步通知")
|
||||
|
||||
Reference in New Issue
Block a user