更新代码:添加支付功能、任务队列系统和相关文档

This commit is contained in:
AIGC Developer
2025-11-04 18:18:49 +08:00
parent 6d834d3385
commit 0b0ad442a0
41 changed files with 904 additions and 85 deletions

View File

@@ -11,6 +11,12 @@ import org.springframework.scheduling.annotation.EnableScheduling;
public class DemoApplication {
public static void main(String[] args) {
// 在应用启动时立即设置HTTP超时时间支付宝API调用可能需要更长时间
// 连接超时30秒读取超时120秒
// 必须在SpringApplication.run()之前设置确保在所有HTTP客户端创建之前生效
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
System.setProperty("sun.net.client.defaultReadTimeout", "120000");
SpringApplication.run(DemoApplication.class, args);
}

View File

@@ -1,17 +1,27 @@
package com.example.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.lang.NonNull;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
/**
* 支付配置类
* 集成IJPay支付模块配置
*
* 初始化IJPay的AliPayApiConfigKit确保AliPayApi可以正常使用
*/
@Configuration
@PropertySource("classpath:payment.properties")
public class PaymentConfig {
public class PaymentConfig implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger logger = LoggerFactory.getLogger(PaymentConfig.class);
@Bean
@ConfigurationProperties(prefix = "alipay")
@@ -19,6 +29,263 @@ public class PaymentConfig {
return new AliPayConfig();
}
/**
* 初始化IJPay配置
* 在应用上下文完全初始化后执行确保所有bean都已创建完成
*/
@Override
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
// 确保只在根上下文触发时初始化,避免子上下文重复触发
if (event.getApplicationContext().getParent() != null) {
return;
}
// 从ApplicationContext获取已创建的bean此时所有bean都已创建完成
AliPayConfig aliPayConfig;
try {
aliPayConfig = event.getApplicationContext().getBean(AliPayConfig.class);
} catch (Exception e) {
logger.error("无法从ApplicationContext获取AliPayConfig bean", e);
return;
}
try {
logger.info("=== 初始化IJPay配置 ===");
// 详细的配置验证
boolean configValid = validateAlipayConfig(aliPayConfig);
if (!configValid) {
logger.error("支付宝配置验证失败,请检查配置");
return;
}
logger.info("应用ID: {}", aliPayConfig.getAppId());
logger.info("网关地址: {}", aliPayConfig.getServerUrl());
logger.info("字符集: {}", aliPayConfig.getCharset());
logger.info("签名类型: {}", aliPayConfig.getSignType());
logger.info("通知URL: {}", aliPayConfig.getNotifyUrl());
logger.info("返回URL: {}", aliPayConfig.getReturnUrl());
// HTTP超时时间已在应用启动时设置DemoApplication.main方法中
// 连接超时30秒读取超时120秒
logger.info("HTTP超时设置连接超时30秒读取超时120秒已在应用启动时设置");
// 构建AliPayApiConfig对象
// 注意DefaultAlipayClient会在build()方法中创建,此时会使用上面设置的系统属性
AliPayApiConfig apiConfig = AliPayApiConfig.builder()
.setAppId(aliPayConfig.getAppId())
.setPrivateKey(aliPayConfig.getPrivateKey())
.setAliPayPublicKey(aliPayConfig.getPublicKey())
.setServiceUrl(aliPayConfig.getServerUrl())
.setCharset(aliPayConfig.getCharset() != null ? aliPayConfig.getCharset() : "UTF-8")
.setSignType(aliPayConfig.getSignType() != null ? aliPayConfig.getSignType() : "RSA2")
.build();
// 验证配置中的serviceUrl是否正确
logger.info("=== 验证IJPay配置中的网关地址 ===");
logger.info("配置中的serviceUrl: {}", apiConfig.getServiceUrl());
logger.info("配置中的appId: {}", apiConfig.getAppId());
// 保存配置到静态变量供AlipayService使用备用方案
AliPayApiConfigHolder.setConfig(apiConfig);
// 设置到AliPayApiConfigKit中供AliPayApi使用
// 根据IJPay源码正确的方法是 putApiConfig
try {
AliPayApiConfigKit.putApiConfig(apiConfig);
logger.info("IJPay配置已成功设置到AliPayApiConfigKit");
// 验证从AliPayApiConfigKit获取的配置
AliPayApiConfig retrievedConfig = AliPayApiConfigKit.getAliPayApiConfig();
logger.info("从AliPayApiConfigKit获取的serviceUrl: {}", retrievedConfig.getServiceUrl());
logger.info("从AliPayApiConfigKit获取的appId: {}", retrievedConfig.getAppId());
logger.info("IJPay配置初始化完成");
} catch (Exception e) {
logger.error("设置IJPay配置到AliPayApiConfigKit时发生异常", e);
// 不抛出异常,允许应用启动,配置将在调用时动态设置
logger.warn("IJPay配置设置失败但应用将继续启动配置将在调用时动态设置");
}
} catch (Exception e) {
logger.error("IJPay配置初始化失败", e);
// 不抛出异常,允许应用启动,配置将在调用时动态设置
logger.warn("IJPay配置初始化失败但应用将继续启动配置将在调用时动态设置");
}
}
/**
* 验证支付宝配置
* @param config 支付宝配置
* @return 验证结果
*/
private boolean validateAlipayConfig(AliPayConfig config) {
logger.info("=== 开始验证支付宝配置 ===");
boolean isValid = true;
StringBuilder errors = new StringBuilder();
// 1. 验证应用ID
if (config.getAppId() == null || config.getAppId().isEmpty()) {
logger.error("❌ 应用ID (app-id) 为空");
errors.append("应用ID为空; ");
isValid = false;
} else {
// 验证appId格式应该是16位数字
String appId = config.getAppId().trim();
if (!appId.matches("^\\d{16}$")) {
logger.warn("⚠️ 应用ID格式可能不正确: {} (应该是16位数字)", appId);
} else {
logger.info("✅ 应用ID格式正确: {}", appId);
}
}
// 2. 验证私钥
if (config.getPrivateKey() == null || config.getPrivateKey().isEmpty()) {
logger.error("❌ 应用私钥 (private-key) 为空");
errors.append("应用私钥为空; ");
isValid = false;
} else {
String privateKey = config.getPrivateKey().trim();
// 验证私钥格式RSA私钥通常以BEGIN PRIVATE KEY或BEGIN RSA PRIVATE KEY开头
if (!privateKey.startsWith("MII") && !privateKey.contains("BEGIN")) {
logger.warn("⚠️ 私钥格式可能不正确应该以MII开头或包含BEGIN PRIVATE KEY");
logger.debug("私钥前20个字符: {}", privateKey.substring(0, Math.min(20, privateKey.length())));
}
// 验证私钥长度RSA私钥通常至少1000字符
if (privateKey.length() < 500) {
logger.error("❌ 私钥长度过短: {} 字符RSA私钥通常至少500字符", privateKey.length());
errors.append("私钥长度过短; ");
isValid = false;
} else {
logger.info("✅ 私钥长度: {} 字符", privateKey.length());
// 隐藏敏感信息,只显示前后部分
String maskedKey = privateKey.substring(0, 20) + "..." + privateKey.substring(privateKey.length() - 20);
logger.debug("私钥摘要: {}", maskedKey);
}
}
// 3. 验证公钥
if (config.getPublicKey() == null || config.getPublicKey().isEmpty()) {
logger.error("❌ 支付宝公钥 (public-key) 为空");
errors.append("支付宝公钥为空; ");
isValid = false;
} else {
String publicKey = config.getPublicKey().trim();
// 验证公钥格式RSA公钥通常以MII开头
if (!publicKey.startsWith("MII") && !publicKey.contains("BEGIN")) {
logger.warn("⚠️ 公钥格式可能不正确应该以MII开头或包含BEGIN PUBLIC KEY");
}
// 验证公钥长度RSA公钥通常至少200字符
if (publicKey.length() < 200) {
logger.error("❌ 公钥长度过短: {} 字符RSA公钥通常至少200字符", publicKey.length());
errors.append("公钥长度过短; ");
isValid = false;
} else {
logger.info("✅ 公钥长度: {} 字符", publicKey.length());
// 隐藏敏感信息,只显示前后部分
String maskedKey = publicKey.substring(0, 20) + "..." + publicKey.substring(publicKey.length() - 20);
logger.debug("公钥摘要: {}", maskedKey);
}
}
// 4. 验证网关地址
if (config.getServerUrl() == null || config.getServerUrl().isEmpty()) {
logger.error("❌ 网关地址 (server-url) 为空");
errors.append("网关地址为空; ");
isValid = false;
} else {
String serverUrl = config.getServerUrl().trim();
if (!serverUrl.startsWith("https://")) {
logger.error("❌ 网关地址格式不正确: {} (应该以https://开头)", serverUrl);
errors.append("网关地址格式不正确; ");
isValid = false;
} else if (!serverUrl.contains("alipay")) {
logger.warn("⚠️ 网关地址可能不正确: {} (应该包含alipay)", serverUrl);
} else {
logger.info("✅ 网关地址: {}", serverUrl);
}
}
// 5. 验证字符集
String charset = config.getCharset();
if (charset == null || charset.isEmpty()) {
logger.warn("⚠️ 字符集未设置,将使用默认值: UTF-8");
} else if (!charset.equalsIgnoreCase("UTF-8")) {
logger.warn("⚠️ 字符集不是UTF-8: {} (建议使用UTF-8)", charset);
} else {
logger.info("✅ 字符集: {}", charset);
}
// 6. 验证签名类型
String signType = config.getSignType();
if (signType == null || signType.isEmpty()) {
logger.warn("⚠️ 签名类型未设置,将使用默认值: RSA2");
} else if (!signType.equalsIgnoreCase("RSA2")) {
logger.warn("⚠️ 签名类型不是RSA2: {} (建议使用RSA2)", signType);
} else {
logger.info("✅ 签名类型: {}", signType);
}
// 7. 验证通知URL
if (config.getNotifyUrl() == null || config.getNotifyUrl().isEmpty()) {
logger.error("❌ 通知URL (notify-url) 为空");
errors.append("通知URL为空; ");
isValid = false;
} else {
String notifyUrl = config.getNotifyUrl().trim();
if (!notifyUrl.startsWith("https://")) {
logger.error("❌ 通知URL格式不正确: {} (应该以https://开头)", notifyUrl);
errors.append("通知URL格式不正确; ");
isValid = false;
} else {
logger.info("✅ 通知URL: {}", notifyUrl);
}
}
// 8. 验证返回URL
if (config.getReturnUrl() == null || config.getReturnUrl().isEmpty()) {
logger.error("❌ 返回URL (return-url) 为空");
errors.append("返回URL为空; ");
isValid = false;
} else {
String returnUrl = config.getReturnUrl().trim();
if (!returnUrl.startsWith("https://")) {
logger.error("❌ 返回URL格式不正确: {} (应该以https://开头)", returnUrl);
errors.append("返回URL格式不正确; ");
isValid = false;
} else {
logger.info("✅ 返回URL: {}", returnUrl);
}
}
// 总结验证结果
if (isValid) {
logger.info("=== 支付宝配置验证通过 ===");
} else {
logger.error("=== 支付宝配置验证失败 ===");
logger.error("错误详情: {}", errors.toString());
logger.error("请检查 application-dev.properties 或 payment.properties 文件中的支付宝配置");
}
return isValid;
}
/**
* IJPay配置持有者
* 用于保存配置供AlipayService在调用时动态设置
*/
public static class AliPayApiConfigHolder {
private static AliPayApiConfig config;
public static void setConfig(AliPayApiConfig config) {
AliPayApiConfigHolder.config = config;
}
public static AliPayApiConfig getConfig() {
return config;
}
}
/**
* 支付宝配置
*/
@@ -27,7 +294,12 @@ public class PaymentConfig {
private String privateKey;
private String publicKey;
private String serverUrl;
private String gatewayUrl;
private String domain;
private String charset;
private String signType;
private String notifyUrl;
private String returnUrl;
private String appCertPath;
private String aliPayCertPath;
private String aliPayRootCertPath;
@@ -45,9 +317,24 @@ public class PaymentConfig {
public String getServerUrl() { return serverUrl; }
public void setServerUrl(String serverUrl) { this.serverUrl = serverUrl; }
public String getGatewayUrl() { return gatewayUrl; }
public void setGatewayUrl(String gatewayUrl) { this.gatewayUrl = gatewayUrl; }
public String getDomain() { return domain; }
public void setDomain(String domain) { this.domain = domain; }
public String getCharset() { return charset; }
public void setCharset(String charset) { this.charset = charset; }
public String getSignType() { return signType; }
public void setSignType(String signType) { this.signType = signType; }
public String getNotifyUrl() { return notifyUrl; }
public void setNotifyUrl(String notifyUrl) { this.notifyUrl = notifyUrl; }
public String getReturnUrl() { return returnUrl; }
public void setReturnUrl(String returnUrl) { this.returnUrl = returnUrl; }
public String getAppCertPath() { return appCertPath; }
public void setAppCertPath(String appCertPath) { this.appCertPath = appCertPath; }

View File

@@ -45,7 +45,8 @@ public class SecurityConfig {
.requestMatchers("/login", "/register", "/api/public/**", "/api/auth/**", "/api/verification/**", "/api/email/**", "/api/tencent/**", "/api/test/**", "/api/polling/**", "/api/diagnostic/**", "/api/polling-diagnostic/**", "/api/monitor/**", "/css/**", "/js/**", "/h2-console/**").permitAll()
.requestMatchers("/api/orders/stats").permitAll() // 统计接口允许匿名访问
.requestMatchers("/api/orders/**").authenticated() // 订单接口需要认证
.requestMatchers("/api/payments/**").authenticated() // 支付接口需要认证
.requestMatchers("/api/payments/alipay/notify", "/api/payments/alipay/return").permitAll() // 支付宝回调接口允许匿名访问(外部调用)
.requestMatchers("/api/payments/**").authenticated() // 其他支付接口需要认证
.requestMatchers("/api/image-to-video/**").authenticated() // 图生视频接口需要认证
.requestMatchers("/api/text-to-video/**").authenticated() // 文生视频接口需要认证
.requestMatchers("/api/dashboard/**").hasRole("ADMIN") // 仪表盘API需要管理员权限

View File

@@ -101,11 +101,55 @@ public class PaymentController {
@ResponseBody
public String alipayNotify(HttpServletRequest request) {
try {
Map<String, String> params = request.getParameterMap().entrySet().stream()
.collect(java.util.stream.Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue()[0]
));
// 支付宝异步通知参数获取
// 注意IJPay的AliPayApi.toMap()使用javax.servlet但Spring Boot 3使用jakarta.servlet
// 所以手动获取参数
Map<String, String> params = new java.util.HashMap<>();
// 获取URL参数
request.getParameterMap().forEach((key, values) -> {
if (values != null && values.length > 0) {
params.put(key, values[0]);
}
});
// 如果是POST请求尝试从请求体获取参数
if ("POST".equalsIgnoreCase(request.getMethod())) {
try {
// 读取请求体
StringBuilder body = new StringBuilder();
try (java.io.BufferedReader reader = request.getReader()) {
String line;
while ((line = reader.readLine()) != null) {
body.append(line);
}
}
// 解析请求体参数(如果存在)
if (body.length() > 0) {
String bodyStr = body.toString();
// 支付宝可能使用form-urlencoded格式
if (bodyStr.contains("=")) {
String[] pairs = bodyStr.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=", 2);
if (keyValue.length == 2) {
try {
params.put(
java.net.URLDecoder.decode(keyValue[0], "UTF-8"),
java.net.URLDecoder.decode(keyValue[1], "UTF-8")
);
} catch (Exception e) {
logger.warn("解析参数失败: {}", pair, e);
}
}
}
}
}
} catch (Exception e) {
logger.warn("读取请求体失败", e);
}
}
boolean success = alipayService.handleNotify(params);
return success ? "success" : "fail";
@@ -122,11 +166,13 @@ public class PaymentController {
@GetMapping("/alipay/return")
public String alipayReturn(HttpServletRequest request, Model model) {
try {
Map<String, String> params = request.getParameterMap().entrySet().stream()
.collect(java.util.stream.Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue()[0]
));
// 支付宝同步返回参数获取GET请求从URL参数获取
Map<String, String> params = new java.util.HashMap<>();
request.getParameterMap().forEach((key, values) -> {
if (values != null && values.length > 0) {
params.put(key, values[0]);
}
});
boolean success = alipayService.handleReturn(params);

View File

@@ -68,3 +68,4 @@ public class MailMessage {

View File

@@ -202,3 +202,4 @@ public class PointsFreezeRecord {

View File

@@ -270,3 +270,4 @@ public class TaskQueue {

View File

@@ -262,3 +262,4 @@ public class TaskStatus {

View File

@@ -70,3 +70,4 @@ public interface TaskStatusRepository extends JpaRepository<TaskStatus, Long> {

View File

@@ -39,5 +39,6 @@ public class PlainTextPasswordEncoder implements PasswordEncoder {

View File

@@ -10,16 +10,18 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.example.demo.model.Payment;
import com.example.demo.model.PaymentMethod;
import com.example.demo.model.PaymentStatus;
import com.example.demo.repository.PaymentRepository;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import com.example.demo.config.PaymentConfig.AliPayApiConfigHolder;
@Service
public class AlipayService {
@@ -52,8 +54,11 @@ public class AlipayService {
@Value("${alipay.return-url}")
private String returnUrl;
private final ObjectMapper objectMapper;
public AlipayService(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
this.objectMapper = new ObjectMapper();
}
/**
@@ -88,11 +93,36 @@ public class AlipayService {
}
/**
* 调用真实的支付宝API
* 确保AliPayApiConfigKit中已设置配置
* 如果未设置从AliPayApiConfigHolder获取并设置
* 根据IJPay源码正确的方法是 putApiConfig
*/
private void ensureAliPayConfigSet() {
try {
// 从AliPayApiConfigHolder获取配置
AliPayApiConfig config = AliPayApiConfigHolder.getConfig();
if (config != null) {
// 根据IJPay源码使用 putApiConfig 方法设置配置
try {
AliPayApiConfigKit.putApiConfig(config);
logger.debug("IJPay配置已动态设置到AliPayApiConfigKit");
} catch (Exception e) {
logger.warn("动态设置IJPay配置到AliPayApiConfigKit时发生异常: {}", e.getMessage());
}
} else {
logger.warn("AliPayApiConfigHolder中没有配置IJPay配置可能未初始化");
}
} catch (Exception e) {
logger.warn("动态设置IJPay配置时发生异常: {}", e.getMessage());
}
}
/**
* 调用真实的支付宝API使用IJPay
*/
private Map<String, Object> callRealAlipayAPI(Payment payment) throws Exception {
// 记录配置信息
logger.info("=== 支付宝API配置信息 ===");
logger.info("=== 使用IJPay调用支付宝API ===");
logger.info("网关地址: {}", gatewayUrl);
logger.info("应用ID: {}", appId);
logger.info("字符集: {}", charset);
@@ -100,64 +130,112 @@ public class AlipayService {
logger.info("通知URL: {}", notifyUrl);
logger.info("返回URL: {}", returnUrl);
// 设置连接和读取超时时间60秒需要在创建客户端之前设置
System.setProperty("sun.net.client.defaultConnectTimeout", "60000");
System.setProperty("sun.net.client.defaultReadTimeout", "60000");
logger.info("超时配置: 连接超时=60秒, 读取超时=60秒");
// 创建支付宝客户端
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl, appId, privateKey, "json", charset, publicKey, signType);
// 使用预创建API生成二维码
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl(notifyUrl);
// 设置业务参数
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setOutTradeNo(payment.getOrderId());
model.setTotalAmount(payment.getAmount().toString());
model.setSubject(payment.getDescription() != null ? payment.getDescription() : "AIGC会员订阅");
model.setBody(payment.getDescription() != null ? payment.getDescription() : "AIGC平台会员订阅服务");
model.setTimeoutExpress("5m");
request.setBizModel(model);
logger.info("调用支付宝预创建API订单号{},金额:{},商品名称:{}",
logger.info("调用支付宝预创建APIIJPay订单号{},金额:{},商品名称:{}",
model.getOutTradeNo(), model.getTotalAmount(), model.getSubject());
// 调用API增加重试机制
AlipayTradePrecreateResponse response = null;
int maxRetries = 3;
// 使用IJPay调用API增加重试机制
String qrCode = null;
int maxRetries = 5; // 最大重试次数
int retryCount = 0;
// 在调用前确保配置已设置
ensureAliPayConfigSet();
// 验证配置中的网关地址
try {
AliPayApiConfig config = AliPayApiConfigKit.getAliPayApiConfig();
logger.info("=== API调用前配置验证 ===");
logger.info("配置中的serviceUrl: {}", config.getServiceUrl());
logger.info("配置中的appId: {}", config.getAppId());
logger.info("配置中的charset: {}", config.getCharset());
logger.info("配置中的signType: {}", config.getSignType());
} catch (Exception e) {
logger.warn("获取IJPay配置时发生异常: {}", e.getMessage());
}
while (retryCount < maxRetries) {
try {
logger.info("正在调用支付宝API... (第{}次尝试)", retryCount + 1);
response = alipayClient.execute(request);
break; // 成功则跳出循环
logger.info("正在调用支付宝APIIJPay... (第{}次尝试,共{}次)", retryCount + 1, maxRetries);
logger.info("API方法: alipay.trade.precreate (由AlipayTradePrecreateRequest自动设置)");
logger.info("通知URL: {}", notifyUrl);
// 使用IJPay的AliPayApi调用预创建接口
// AlipayTradePrecreateRequest会自动设置method参数为"alipay.trade.precreate"
String responseBody = AliPayApi.tradePrecreatePayToResponse(model, notifyUrl).getBody();
if (responseBody == null || responseBody.isEmpty()) {
throw new RuntimeException("IJPay API响应为空");
}
logger.info("IJPay API响应: {}", responseBody);
// 解析JSON响应
// IJPay返回的响应体是JSON字符串格式为: {"alipay_trade_precreate_response":{"code":"10000","msg":"Success","qr_code":"..."},"sign":"..."}
try {
Map<String, Object> responseMap = objectMapper.readValue(
responseBody,
new TypeReference<Map<String, Object>>() {}
);
Map<String, Object> precreateResponse = (Map<String, Object>) responseMap.get("alipay_trade_precreate_response");
if (precreateResponse != null) {
String code = (String) precreateResponse.get("code");
String msg = (String) precreateResponse.get("msg");
if ("10000".equals(code)) {
qrCode = (String) precreateResponse.get("qr_code");
if (qrCode != null && !qrCode.isEmpty()) {
logger.info("支付宝二维码生成成功IJPay订单号{},二维码:{}", payment.getOrderId(), qrCode);
break; // 成功则跳出循环
} else {
throw new RuntimeException("二维码为空,响应消息:" + msg);
}
} else {
String subCode = (String) precreateResponse.get("sub_code");
String subMsg = (String) precreateResponse.get("sub_msg");
throw new RuntimeException("二维码生成失败:" + msg + (subMsg != null ? " - " + subMsg : "") + " (code: " + code + (subCode != null ? ", sub_code: " + subCode : "") + ")");
}
} else {
throw new RuntimeException("无法解析响应,响应体:" + responseBody);
}
} catch (com.fasterxml.jackson.core.JsonProcessingException e) {
logger.error("JSON解析失败", e);
throw new RuntimeException("JSON解析失败" + e.getMessage() + ",响应体:" + responseBody);
}
} catch (Exception e) {
retryCount++;
String errorType = e.getClass().getSimpleName();
String errorMessage = e.getMessage();
logger.warn("支付宝API调用失败第{}次重试", retryCount);
logger.warn("支付宝API调用失败IJPay,第{}次尝试失败", retryCount);
logger.warn("错误类型: {}", errorType);
logger.warn("错误信息: {}", errorMessage);
// 根据错误类型提供诊断建议
// 根据错误类型提供诊断建议和决定重试策略
boolean isTimeoutError = false;
if (errorMessage != null) {
if (errorMessage.contains("Read timed out") || errorMessage.contains("timeout")) {
if (errorMessage.contains("Read timed out") || errorMessage.contains("timeout")
|| errorMessage.contains("SocketTimeoutException")) {
isTimeoutError = true;
logger.error("=== 网络超时错误诊断 ===");
logger.error("可能的原因:");
logger.error("1. 网络连接不稳定或延迟过高");
logger.error("2. 支付宝沙箱环境响应慢openapi.alipaydev.com");
logger.error("2. 支付宝沙箱环境响应慢openapi-sandbox.dl.alipaydev.com");
logger.error("3. 防火墙或代理服务器阻止连接");
logger.error("4. ngrok隧道可能已过期或不可用");
logger.error("解决方案:");
logger.error("1. 检查网络连接尝试ping openapi.alipaydev.com");
logger.error("1. 检查网络连接尝试ping openapi-sandbox.dl.alipaydev.com");
logger.error("2. 检查ngrok是否正常运行: {}", notifyUrl);
logger.error("3. 考虑使用代理服务器或VPN");
logger.error("4. 增加超时时间或重试次数");
} else if (errorMessage.contains("Connection refused") || errorMessage.contains("ConnectException")) {
logger.error("=== 连接拒绝错误诊断 ===");
logger.error("无法连接到支付宝服务器: {}", gatewayUrl);
@@ -170,12 +248,16 @@ public class AlipayService {
}
if (retryCount >= maxRetries) {
logger.error("支付宝API调用失败已达到最大重试次数{}次)", maxRetries);
logger.error("支付宝API调用失败IJPay,已达到最大重试次数({}次)", maxRetries);
logger.error("最终失败原因: {}", errorMessage);
throw new RuntimeException("支付宝API调用失败" + errorMessage);
throw new RuntimeException("支付宝API调用失败IJPay已重试" + maxRetries + "" + errorMessage);
}
// 根据错误类型决定等待时间:超时错误等待更长时间
int waitTime = isTimeoutError ? 5000 : 3000; // 超时错误等待5秒其他错误等待3秒
logger.info("等待{}秒后重试...", waitTime / 1000);
try {
Thread.sleep(2000); // 等待2秒后重试
Thread.sleep(waitTime);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("重试被中断", ie);
@@ -183,29 +265,20 @@ public class AlipayService {
}
}
logger.info("支付宝API响应状态{}", response.isSuccess());
logger.info("支付宝API响应码{}", response.getCode());
logger.info("支付宝API响应消息:{}", response.getMsg());
logger.info("支付宝API响应子码{}", response.getSubCode());
logger.info("支付宝API响应子消息{}", response.getSubMsg());
if (response.isSuccess()) {
String qrCode = response.getQrCode();
logger.info("支付宝二维码生成成功,订单号:{},二维码:{}", payment.getOrderId(), qrCode);
Map<String, Object> result = new HashMap<>();
result.put("qrCode", qrCode);
result.put("outTradeNo", response.getOutTradeNo());
result.put("success", true);
return result;
} else {
logger.error("支付宝二维码生成失败,订单号:{},错误信息:{},子码:{},子消息:{}",
payment.getOrderId(), response.getMsg(), response.getSubCode(), response.getSubMsg());
// 检查二维码是否为空
if (qrCode == null || qrCode.isEmpty()) {
logger.error("支付宝API调用失败IJPay二维码为空");
payment.setStatus(PaymentStatus.FAILED);
paymentRepository.save(payment);
throw new RuntimeException("二维码生成失败:" + response.getMsg() + " - " + response.getSubMsg());
throw new RuntimeException("支付宝API调用失败IJPay二维码为空");
}
Map<String, Object> result = new HashMap<>();
result.put("qrCode", qrCode);
result.put("outTradeNo", payment.getOrderId());
result.put("success", true);
return result;
}

View File

@@ -46,7 +46,8 @@ ai.image.api.key=sk-jp1O4h5lfN3WWZReF48SDa2osm0o9alC9qetkgq3M7XUjJ4R
alipay.app-id=9021000157616562
alipay.private-key=MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCH7wPeptkJlJuoKwDqxvfJJLTOAWVkHa/TLh+wiy1tEtmwcrOwEU3GuqfkUlhij71WJIZi8KBytCwbax1QGZA/oLXvTCGJJrYrsEL624X5gGCCPKWwHRDhewsQ5W8jFxaaMXxth8GKlSW61PZD2cOQClRVEm2xnWFZ+6/7WBI7082g7ayzGCD2eowXsJyWyuEBCUSbHXkSgxVhqj5wUGIXhr8ly+pdUlJmDX5K8UG2rjJYx+0AU5UZJbOAND7d3iyDsOulHDvth50t8MOWDnDCVJ2aAgUB5FZKtOFxOmzNTsMjvzYldFztF0khbypeeMVL2cxgioIgTvjBkUwd55hZAgMBAAECggEAUjk3pARUoEDt7skkYt87nsW/QCUECY0Tf7AUpxtovON8Hgkju8qbuyvIxokwwV2k72hkiZB33Soyy9r8/iiYYoR5yGfKmUV7R+30df03ivYmamD48BCE138v8GZ31Ufv+hEY7MADSCpzihGrbNtaOdSlslfVVmyWKHHfvy9EyD6yHJGYswLpHXC/QX1TuLRRxk6Uup8qENOG/6zjGWMfxoRZFwTt80ml1mKy32YZGyJqDaQpJcdYwAHOPcnJl1emw4E+oVjiLyksl643npuTkgnZXs1iWcWSS8ojF1w/0kVDzcNh9toLg+HDuQlIHOis01VQ7lYcG4oiMOnhX1QHIQKBgQC9fgBuILjBhuCI9fHvLRdzoNC9heD54YK7xGvEV/mv90k8xcmNx+Yg5C57ASaMRtOq3b7muPiCv5wOtMT4tUCcMIwSrTNlcBM6EoTagnaGfpzOMaHGMXO4vbaw+MIynHnvXFj1rZjG1lzkV/9K36LAaHD9ZKVJaBQ9mK+0CIq/3QKBgQC3pL5GbvXj6/4ahTraXzNDQQpPGVgbHxcOioEXL4ibaOPC58puTW8HDbRvVuhl/4EEOBRVX81BSgkN8XHwTSiZdih2iOqByg+o9kixs7nlFn3Iw9BBP2/g+Wqiyi2N+9g17kfWXXVOKYz/eMXLBeOo4KhQE9wqNGyZldYzX2ywrQKBgApJmvBfqmgnUG1fHOFlS06lvm9ro0ktqxFSmp8wP4gEHt/DxSuDXMUQXk2jRFp9ReSS4VhZVnSSvoA15DO0c2uHXzNsX8v0B7cxZjEOwCyRFyZCn4vJB4VSF2cIOlLRF/Wcx9+eqxqwbJ6hAGUqOwXDJc879ZVEp0So03EsvYupAoGAAnI+Wp/VxLB7FQ1bSFdmTmoKYh1bUBks7HOp3o4yiqduCUWfK7L6XKSxF56Xv+wUYuMAWlbJXCpJTpc9xk6w0MKDLXkLbqkrZjvJohxbyJJxIICDQKtAqUWJRxvcWXzWV3mSGWfrTRw+lZSdReQRMUm01EQ/dYx3OeCGFu8Zeo0CgYAlH5YSYdJxZSoDCJeoTrkxUlFoOg8UQ7SrsaLYLwpwcwpuiWJaTrg6jwFocj+XhjQ9RtRbSBHz2wKSLdl+pXbTbqECKk85zMFl6zG3etXtTJU/dD750Ty4i8zt3+JGhvglPrQBY1CfItgml2oXa/VUVMnLCUS0WSZuPRmPYZD8dg==
alipay.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAksEwzuR3ASrKtTzaANqdQYKOoD44itA1TWG/6onvQr8PHNEMgcguLuJNrdeuT2PDg23byzZ9qKfEM2D5U4zbpt0/uCYLfZQyAnAWWyMvnKPoSIgrtBjnxYK6HE6fuQV3geJTcZxvP/z8dGZB0V0s6a53rzbKSLh0p4w0hWfVXlQihq3Xh4vSKB+ojdhEkIblhpWPT42NPbjVNdwPzIhUGpRy3/nsgNqVBu+ZacQ5/rCvzXU1RE0allBbjcvjymKQTS7bAE0i1Mgo1eX8njvElsfQUv5P7xQdrvZagqtIuTdP19cmsSNGdIC9Z5Po3j0z3KWPR7MrKgDuJfzkWtJR4wIDAQAB
alipay.gateway-url=https://openapi.alipaydev.com/gateway.do
alipay.server-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
alipay.gateway-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
alipay.charset=UTF-8
alipay.sign-type=RSA2
alipay.notify-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/notify

View File

@@ -29,3 +29,4 @@ CREATE TABLE IF NOT EXISTS task_queue (

View File

@@ -28,3 +28,4 @@ CREATE TABLE IF NOT EXISTS points_freeze_records (

View File

@@ -31,3 +31,4 @@ CREATE TABLE task_status (

View File

@@ -3,16 +3,13 @@
alipay.app-id=您的APPID
alipay.private-key=您的应用私钥
alipay.public-key=支付宝公钥
alipay.server-url=https://openapi.alipaydev.com/gateway.do
alipay.domain=http://您的域名:8080
alipay.server-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
alipay.gateway-url=https://openapi-sandbox.dl.alipaydev.com/gateway.do
alipay.domain=https://curtly-aphorismatic-ginger.ngrok-free.dev
alipay.charset=UTF-8
alipay.sign-type=RSA2
alipay.notify-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/notify
alipay.return-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/return
alipay.app-cert-path=classpath:cert/alipay/appCertPublicKey.crt
alipay.ali-pay-cert-path=classpath:cert/alipay/alipayCertPublicKey_RSA2.crt
alipay.ali-pay-root-cert-path=classpath:cert/alipay/alipayRootCert.crt
# PayPal支付配置
paypal.client-id=your_paypal_client_id_here
paypal.client-secret=your_paypal_client_secret_here
paypal.mode=sandbox
paypal.return-url=http://localhost:8080/api/payments/paypal/return
paypal.cancel-url=http://localhost:8080/api/payments/paypal/cancel
paypal.domain=http://localhost:8080

View File

@@ -575,5 +575,6 @@

View File

@@ -491,5 +491,6 @@

View File

@@ -530,5 +530,6 @@