更新功能和文档: 增强支付系统、任务队列管理、用户作品管理等功能

This commit is contained in:
AIGC Developer
2025-10-29 10:16:03 +08:00
parent 8c55f9f376
commit 6f72386523
64 changed files with 1529 additions and 339 deletions

View File

@@ -91,9 +91,7 @@ public class SecurityConfig {
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:5173",
"http://127.0.0.1:5173",
"https://*.ngrok.io",
"https://*.ngrok-free.app"
"http://127.0.0.1:5173"
));
configuration.setAllowedOriginPatterns(Arrays.asList(
"https://*.ngrok.io",
@@ -103,6 +101,7 @@ public class SecurityConfig {
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setExposedHeaders(Arrays.asList("Authorization"));
configuration.setMaxAge(3600L); // 预检请求缓存时间
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);

View File

@@ -5,7 +5,6 @@ import java.util.Locale;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

View File

@@ -1,16 +1,23 @@
package com.example.demo.controller;
import com.example.demo.service.ApiResponseHandler;
import com.example.demo.util.JwtUtils;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import com.example.demo.service.ApiResponseHandler;
import com.example.demo.util.JwtUtils;
/**
* API测试控制器

View File

@@ -1,23 +1,30 @@
package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import com.example.demo.service.VerificationCodeService;
import com.example.demo.util.JwtUtils;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/auth")
@@ -114,11 +121,23 @@ public class AuthApiController {
try {
// 从邮箱生成用户名(去掉@符号及后面的部分)
String username = email.split("@")[0];
logger.info("邮箱验证码登录 - 原始邮箱: '{}', 生成的用户名: '{}'", email, username);
// 确保用户名长度不超过50个字符
if (username.length() > 50) {
username = username.substring(0, 50);
logger.info("邮箱验证码登录 - 用户名过长,截断为: '{}'", username);
}
// 确保用户名唯一
String originalUsername = username;
int counter = 1;
while (userService.findByUsername(username) != null) {
username = originalUsername + counter;
while (userService.findByUsernameOrNull(username) != null) {
// 如果用户名过长,需要重新截断
String newUsername = originalUsername + counter;
if (newUsername.length() > 50) {
newUsername = newUsername.substring(0, 50);
}
username = newUsername;
counter++;
}
@@ -145,6 +164,7 @@ public class AuthApiController {
// 生成JWT Token
String token = jwtUtils.generateToken(user.getUsername(), user.getRole(), user.getId());
logger.info("邮箱验证码登录 - 生成JWT token用户名: '{}', 用户ID: {}", user.getUsername(), user.getId());
Map<String, Object> body = new HashMap<>();
body.put("success", true);

View File

@@ -136,7 +136,7 @@ public class PaymentApiController {
}
String orderId = (String) paymentData.get("orderId");
String amountStr = (String) paymentData.get("amount");
String amountStr = paymentData.get("amount") != null ? paymentData.get("amount").toString() : null;
String method = (String) paymentData.get("method");
if (orderId == null || amountStr == null || method == null) {
@@ -306,7 +306,7 @@ public class PaymentApiController {
.body(createErrorResponse("请先登录后再创建支付记录"));
}
String amountStr = (String) paymentData.get("amount");
String amountStr = paymentData.get("amount") != null ? paymentData.get("amount").toString() : null;
String method = (String) paymentData.get("method");
if (amountStr == null || method == null) {
@@ -373,31 +373,42 @@ public class PaymentApiController {
@RequestBody Map<String, Object> paymentData,
Authentication authentication) {
try {
logger.info("收到创建支付宝支付请求,数据:{}", paymentData);
String username;
if (authentication != null && authentication.isAuthenticated()) {
username = authentication.getName();
logger.info("用户已认证:{}", username);
} else {
logger.warn("用户未认证,拒绝支付请求");
return ResponseEntity.badRequest()
.body(createErrorResponse("请先登录后再创建支付"));
}
Long paymentId = Long.valueOf(paymentData.get("paymentId").toString());
logger.info("查找支付记录ID{}", paymentId);
Payment payment = paymentService.findById(paymentId)
.orElseThrow(() -> new RuntimeException("支付记录不存在"));
// 检查权限
if (!payment.getUser().getUsername().equals(username)) {
logger.warn("用户{}无权限操作支付记录{}", username, paymentId);
return ResponseEntity.status(403)
.body(createErrorResponse("无权限操作此支付记录"));
}
logger.info("开始调用支付宝服务创建支付,订单号:{},金额:{}", payment.getOrderId(), payment.getAmount());
// 调用支付宝接口创建支付
String paymentUrl = alipayService.createPayment(payment);
Map<String, Object> paymentResult = alipayService.createPayment(payment);
logger.info("支付宝二维码生成成功,订单号:{}", payment.getOrderId());
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "支付宝支付创建成功");
response.put("data", Map.of("paymentUrl", paymentUrl));
response.put("message", "支付宝二维码生成成功");
response.put("data", paymentResult);
return ResponseEntity.ok(response);
} catch (Exception e) {
@@ -449,47 +460,7 @@ public class PaymentApiController {
}
}
/**
* 支付宝异步通知
*/
@PostMapping("/alipay/notify")
public ResponseEntity<String> handleAlipayNotify(@RequestParam Map<String, String> params) {
try {
logger.info("收到支付宝异步通知:{}", params);
boolean success = alipayService.handleNotify(params);
if (success) {
return ResponseEntity.ok("success");
} else {
return ResponseEntity.ok("fail");
}
} catch (Exception e) {
logger.error("处理支付宝异步通知失败:", e);
return ResponseEntity.ok("fail");
}
}
/**
* 支付宝同步返回
*/
@GetMapping("/alipay/return")
public ResponseEntity<String> handleAlipayReturn(@RequestParam Map<String, String> params) {
try {
logger.info("收到支付宝同步返回:{}", params);
boolean success = alipayService.handleReturn(params);
if (success) {
return ResponseEntity.ok("支付成功,正在跳转...");
} else {
return ResponseEntity.ok("支付验证失败");
}
} catch (Exception e) {
logger.error("处理支付宝同步返回失败:", e);
return ResponseEntity.ok("支付处理异常");
}
}
private Map<String, Object> createErrorResponse(String message) {
Map<String, Object> response = new HashMap<>();

View File

@@ -1,14 +1,8 @@
package com.example.demo.controller;
import com.example.demo.model.Payment;
import com.example.demo.model.PaymentMethod;
import com.example.demo.model.PaymentStatus;
import com.example.demo.model.User;
import com.example.demo.service.AlipayService;
import com.example.demo.service.PayPalService;
import com.example.demo.service.PaymentService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -16,10 +10,23 @@ import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.Map;
import com.example.demo.model.Payment;
import com.example.demo.model.PaymentMethod;
import com.example.demo.model.User;
import com.example.demo.service.AlipayService;
import com.example.demo.service.PayPalService;
import com.example.demo.service.PaymentService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
@Controller
@RequestMapping("/payment")
@@ -73,8 +80,12 @@ public class PaymentController {
// 根据支付方式创建支付
String redirectUrl;
if (payment.getPaymentMethod() == PaymentMethod.ALIPAY) {
redirectUrl = alipayService.createPayment(payment);
return "redirect:" + redirectUrl;
Map<String, Object> paymentResult = alipayService.createPayment(payment);
if (paymentResult.containsKey("qrCode")) {
// 对于二维码支付,重定向到支付页面显示二维码
return "redirect:/payment/qr?qrCode=" + paymentResult.get("qrCode");
}
return "redirect:/payment/error";
} else if (payment.getPaymentMethod() == PaymentMethod.PAYPAL) {
redirectUrl = payPalService.createPayment(payment);
return "redirect:" + redirectUrl;

View File

@@ -1,18 +1,24 @@
package com.example.demo.controller;
import com.example.demo.model.TaskQueue;
import com.example.demo.service.TaskQueueService;
import com.example.demo.util.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.model.TaskQueue;
import com.example.demo.service.TaskQueueService;
import com.example.demo.util.JwtUtils;
/**
* 任务队列API控制器
*/

View File

@@ -1,18 +1,26 @@
package com.example.demo.controller;
import com.example.demo.model.TextToVideoTask;
import com.example.demo.service.TextToVideoService;
import com.example.demo.util.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.model.TextToVideoTask;
import com.example.demo.service.TextToVideoService;
import com.example.demo.util.JwtUtils;
/**
* 文生视频API控制器
*/

View File

@@ -195,3 +195,5 @@ public class PointsFreezeRecord {
}

View File

@@ -263,3 +263,5 @@ public class TaskQueue {
}

View File

@@ -255,3 +255,5 @@ public class TaskStatus {
}

View File

@@ -1,17 +1,18 @@
package com.example.demo.model;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@@ -31,9 +32,7 @@ public class User {
@Column(nullable = false, unique = true, length = 100)
private String email;
@NotBlank
@Size(min = 6, max = 100)
@Column(nullable = false)
@Column(nullable = true)
private String passwordHash;
@NotBlank

View File

@@ -63,3 +63,5 @@ public interface TaskStatusRepository extends JpaRepository<TaskStatus, Long> {
}

View File

@@ -1,6 +1,9 @@
package com.example.demo.repository;
import com.example.demo.model.UserWork;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -9,9 +12,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import com.example.demo.model.UserWork;
/**
* 用户作品仓库接口

View File

@@ -1,5 +1,7 @@
package com.example.demo.scheduler;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -8,7 +10,6 @@ import org.springframework.stereotype.Component;
import com.example.demo.service.TaskCleanupService;
import com.example.demo.service.TaskQueueService;
import java.util.Map;
/**
* 任务队列定时调度器

View File

@@ -1,28 +1,28 @@
package com.example.demo.security;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import com.example.demo.util.JwtUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.lang.NonNull;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import com.example.demo.util.JwtUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@@ -41,7 +41,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
logger.debug("JWT过滤器处理请求: {}", request.getRequestURI());
logger.info("JWT过滤器处理请求: {}", request.getRequestURI());
try {
String authHeader = request.getHeader("Authorization");
@@ -50,13 +50,14 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
String token = jwtUtils.extractTokenFromHeader(authHeader);
logger.debug("提取的token: {}", token != null ? "存在" : "不存在");
if (token != null) {
if (token != null && !token.equals("null") && !token.trim().isEmpty()) {
String username = jwtUtils.getUsernameFromToken(token);
logger.debug("从token获取用户名: {}", username);
if (username != null && jwtUtils.validateToken(token, username)) {
logger.info("JWT认证 - 从token获取的用户名: '{}'", username);
User user = userService.findByUsername(username);
logger.debug("查找用户: {}", user != null ? user.getUsername() : "未找到");
logger.info("JWT认证 - 数据库查找结果: {}", user != null ? "找到用户 '" + user.getUsername() + "'" : "未找到用户");
if (user != null) {
// 创建用户权限列表
@@ -64,7 +65,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
authorities.add(new SimpleGrantedAuthority(user.getRole()));
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(user, null, authorities);
new UsernamePasswordAuthenticationToken(user.getUsername(), null, authorities);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
logger.debug("JWT认证成功用户: {}, 角色: {}", username, user.getRole());

View File

@@ -34,3 +34,5 @@ public class PlainTextPasswordEncoder implements PasswordEncoder {

View File

@@ -1,24 +1,25 @@
package com.example.demo.service;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
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 java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;
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;
@Service
public class AlipayService {
@@ -56,60 +57,117 @@ public class AlipayService {
}
/**
* 创建支付宝支付订单
* 创建支付宝支付订单并生成二维码
*/
public String createPayment(Payment payment) {
public Map<String, Object> createPayment(Payment payment) {
try {
logger.info("开始创建支付宝支付订单,订单号:{},金额:{}", payment.getOrderId(), payment.getAmount());
// 设置支付状态
payment.setStatus(PaymentStatus.PENDING);
payment.setPaymentMethod(PaymentMethod.ALIPAY);
payment.setOrderId(generateOrderId());
if (payment.getOrderId() == null || payment.getOrderId().isEmpty()) {
payment.setOrderId(generateOrderId());
}
payment.setCallbackUrl(notifyUrl);
payment.setReturnUrl(returnUrl);
// 保存支付记录
paymentRepository.save(payment);
logger.info("支付记录已保存ID{}", payment.getId());
// 创建支付宝客户端
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl, appId, privateKey, "json", charset, publicKey, signType);
// 调用真实的支付宝API
return callRealAlipayAPI(payment);
// 设置请求参数
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
// 设置业务参数
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
model.setOutTradeNo(payment.getOrderId());
model.setTotalAmount(payment.getAmount().toString());
model.setSubject(payment.getDescription() != null ? payment.getDescription() : "商品支付");
model.setBody(payment.getDescription());
model.setProductCode("FAST_INSTANT_TRADE_PAY");
request.setBizModel(model);
// 调用API
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
logger.info("支付宝支付订单创建成功,订单号:{}", payment.getOrderId());
return response.getBody();
} else {
logger.error("支付宝支付订单创建失败:{}", response.getMsg());
payment.setStatus(PaymentStatus.FAILED);
paymentRepository.save(payment);
throw new RuntimeException("支付订单创建失败:" + response.getMsg());
}
} catch (AlipayApiException e) {
logger.error("支付宝API调用异常", e);
} catch (Exception e) {
logger.error("创建支付订单时发生异常,订单号:{},错误:{}", payment.getOrderId(), e.getMessage(), e);
payment.setStatus(PaymentStatus.FAILED);
paymentRepository.save(payment);
throw new RuntimeException("支付服务异常:" + e.getMessage());
}
}
/**
* 调用真实的支付宝API
*/
private Map<String, Object> callRealAlipayAPI(Payment payment) throws Exception {
// 创建支付宝客户端,增加超时时间
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl, appId, privateKey, "json", charset, publicKey, signType);
// 设置连接和读取超时时间30秒
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
System.setProperty("sun.net.client.defaultReadTimeout", "30000");
// 使用预创建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平台会员订阅服务");
request.setBizModel(model);
logger.info("调用支付宝预创建API订单号{},金额:{},商品名称:{}",
model.getOutTradeNo(), model.getTotalAmount(), model.getSubject());
// 调用API增加重试机制
AlipayTradePrecreateResponse response = null;
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries) {
try {
logger.info("正在调用支付宝API... (第{}次尝试)", retryCount + 1);
response = alipayClient.execute(request);
break; // 成功则跳出循环
} catch (Exception e) {
retryCount++;
logger.warn("支付宝API调用失败第{}次重试,错误:{}", retryCount, e.getMessage());
if (retryCount >= maxRetries) {
logger.error("支付宝API调用失败已达到最大重试次数");
throw new RuntimeException("支付宝API调用失败" + e.getMessage());
}
try {
Thread.sleep(2000); // 等待2秒后重试
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("重试被中断", ie);
}
}
}
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());
payment.setStatus(PaymentStatus.FAILED);
paymentRepository.save(payment);
throw new RuntimeException("二维码生成失败:" + response.getMsg() + " - " + response.getSubMsg());
}
}
/**
* 处理支付宝异步通知
*/

View File

@@ -1,7 +1,15 @@
package com.example.demo.service;
import com.example.demo.model.ImageToVideoTask;
import com.example.demo.repository.ImageToVideoTaskRepository;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -14,15 +22,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import com.example.demo.model.ImageToVideoTask;
import com.example.demo.repository.ImageToVideoTaskRepository;
/**
* 图生视频服务类

View File

@@ -1,13 +1,5 @@
package com.example.demo.service;
import com.example.demo.model.*;
import com.example.demo.repository.PaymentRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
@@ -15,6 +7,22 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.model.Order;
import com.example.demo.model.OrderItem;
import com.example.demo.model.OrderStatus;
import com.example.demo.model.OrderType;
import com.example.demo.model.Payment;
import com.example.demo.model.PaymentMethod;
import com.example.demo.model.PaymentStatus;
import com.example.demo.model.User;
import com.example.demo.repository.PaymentRepository;
@Service
@Transactional
public class PaymentService {
@@ -291,11 +299,15 @@ public class PaymentService {
*/
public Payment createPayment(String username, String orderId, String amountStr, String method) {
try {
logger.info("创建支付 - 用户名: {}, 订单ID: {}, 金额: {}, 支付方式: {}", username, orderId, amountStr, method);
logger.info("创建支付 - 用户名: '{}', 订单ID: {}, 金额: {}, 支付方式: {}", username, orderId, amountStr, method);
User user = userService.findByUsername(username);
if (user == null) {
logger.error("用户不存在: {}", username);
User user;
try {
logger.info("PaymentService - 尝试查找用户: '{}'", username);
user = userService.findByUsername(username);
logger.info("PaymentService - 用户查找结果: {}", user != null ? "找到用户 '" + user.getUsername() + "'" : "未找到用户");
} catch (Exception e) {
logger.error("PaymentService - 用户查找异常: {}", username, e);
// 如果是匿名用户,创建一个临时用户记录
if (username.startsWith("anonymous_")) {
user = createAnonymousUser(username);
@@ -315,6 +327,7 @@ public class PaymentService {
payment.setUser(user);
payment.setOrderId(orderId);
payment.setAmount(amount);
payment.setCurrency("CNY"); // 设置默认货币为人民币
payment.setPaymentMethod(paymentMethod);
payment.setStatus(PaymentStatus.PENDING);
payment.setCreatedAt(LocalDateTime.now());
@@ -325,10 +338,12 @@ public class PaymentService {
// 根据支付方式调用相应的支付服务
if (paymentMethod == PaymentMethod.ALIPAY) {
try {
String paymentUrl = alipayService.createPayment(savedPayment);
savedPayment.setPaymentUrl(paymentUrl);
Map<String, Object> paymentResult = alipayService.createPayment(savedPayment);
if (paymentResult.containsKey("qrCode")) {
savedPayment.setPaymentUrl(paymentResult.get("qrCode").toString());
}
save(savedPayment);
logger.info("支付宝支付URL生成成功: {}", paymentUrl);
logger.info("支付宝二维码生成成功: {}", paymentResult.get("qrCode"));
} catch (Exception e) {
logger.error("调用支付宝支付接口失败:", e);
// 不抛出异常,让前端处理

View File

@@ -86,9 +86,9 @@ public class TaskStatusPollingService {
try {
// 调用外部API查询状态
HttpResponse<String> response = Unirest.post(apiBaseUrl + "/v1/videos")
HttpResponse<String> response = Unirest.get(apiBaseUrl + "/v1/videos")
.header("Authorization", "Bearer " + apiKey)
.field("task_id", task.getExternalTaskId())
.queryString("task_id", task.getExternalTaskId())
.asString();
if (response.getStatus() == 200) {
@@ -113,19 +113,41 @@ public class TaskStatusPollingService {
*/
private void updateTaskStatus(TaskStatus task, JsonNode responseJson) {
try {
// 检查base_resp状态
JsonNode baseResp = responseJson.path("base_resp");
if (!baseResp.isMissingNode() && baseResp.path("status_code").asInt() != 0) {
String errorMsg = baseResp.path("status_msg").asText("Unknown error");
task.markAsFailed(errorMsg);
logger.warn("API返回错误: taskId={}, error={}", task.getTaskId(), errorMsg);
taskStatusRepository.save(task);
return;
}
String status = responseJson.path("status").asText();
int progress = responseJson.path("progress").asInt(0);
String resultUrl = responseJson.path("result_url").asText();
String resultUrl = null;
String errorMessage = responseJson.path("error_message").asText();
task.incrementPollCount();
task.setProgress(progress);
switch (status.toLowerCase()) {
case "completed":
case "success":
task.markAsCompleted(resultUrl);
logger.info("任务完成: taskId={}, resultUrl={}", task.getTaskId(), resultUrl);
// 获取file_id并获取视频URL
String fileId = responseJson.path("file_id").asText();
if (!fileId.isEmpty()) {
resultUrl = getVideoUrlFromFileId(fileId);
if (resultUrl != null) {
task.markAsCompleted(resultUrl);
logger.info("任务完成: taskId={}, resultUrl={}", task.getTaskId(), resultUrl);
} else {
task.markAsFailed("无法获取视频URL");
logger.warn("任务完成但无法获取视频URL: taskId={}, fileId={}", task.getTaskId(), fileId);
}
} else {
task.markAsFailed("任务完成但未返回文件ID");
logger.warn("任务完成但未返回文件ID: taskId={}", task.getTaskId());
}
break;
case "failed":
@@ -136,6 +158,7 @@ public class TaskStatusPollingService {
case "processing":
case "in_progress":
case "pending":
task.setStatus(TaskStatus.Status.PROCESSING);
logger.info("任务处理中: taskId={}, progress={}%", task.getTaskId(), progress);
break;
@@ -152,6 +175,38 @@ public class TaskStatusPollingService {
}
}
/**
* 根据file_id获取视频URL
*/
private String getVideoUrlFromFileId(String fileId) {
try {
HttpResponse<String> response = Unirest.get(apiBaseUrl + "/minimax/v1/files/retrieve")
.header("Authorization", "Bearer " + apiKey)
.queryString("file_id", fileId)
.asString();
if (response.getStatus() == 200) {
JsonNode responseJson = objectMapper.readTree(response.getBody());
JsonNode fileNode = responseJson.path("file");
if (!fileNode.isMissingNode()) {
String downloadUrl = fileNode.path("download_url").asText();
if (!downloadUrl.isEmpty()) {
logger.info("成功获取视频URL: fileId={}, url={}", fileId, downloadUrl);
return downloadUrl;
}
}
}
logger.warn("获取视频URL失败: fileId={}, status={}, response={}",
fileId, response.getStatus(), response.getBody());
return null;
} catch (Exception e) {
logger.error("获取视频URL时发生错误: fileId={}, error={}", fileId, e.getMessage(), e);
return null;
}
}
/**
* 处理超时任务
*/

View File

@@ -1,7 +1,11 @@
package com.example.demo.service;
import com.example.demo.model.TextToVideoTask;
import com.example.demo.repository.TextToVideoTaskRepository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -13,11 +17,8 @@ import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import com.example.demo.model.TextToVideoTask;
import com.example.demo.repository.TextToVideoTaskRepository;
/**
* 文生视频服务类

View File

@@ -58,6 +58,11 @@ public class UserService {
return userRepository.findByUsername(username).orElseThrow(() -> new IllegalArgumentException("用户不存在"));
}
@Transactional(readOnly = true)
public User findByUsernameOrNull(String username) {
return userRepository.findByUsername(username).orElse(null);
}
@Transactional
public User create(String username, String email, String rawPassword) {
return register(username, email, rawPassword);

View File

@@ -1,12 +1,10 @@
package com.example.demo.service;
import com.example.demo.model.User;
import com.example.demo.model.UserWork;
import com.example.demo.model.TextToVideoTask;
import com.example.demo.model.ImageToVideoTask;
import com.example.demo.repository.UserWorkRepository;
import com.example.demo.repository.TextToVideoTaskRepository;
import com.example.demo.repository.ImageToVideoTaskRepository;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -16,10 +14,13 @@ import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.example.demo.model.ImageToVideoTask;
import com.example.demo.model.TextToVideoTask;
import com.example.demo.model.User;
import com.example.demo.model.UserWork;
import com.example.demo.repository.ImageToVideoTaskRepository;
import com.example.demo.repository.TextToVideoTaskRepository;
import com.example.demo.repository.UserWorkRepository;
/**
* 用户作品服务类
@@ -314,13 +315,33 @@ public class UserWorkService {
*/
@Transactional(readOnly = true)
public Map<String, Object> getUserWorkStats(String username) {
Object[] stats = userWorkRepository.getUserWorkStats(username);
Map<String, Object> result = new HashMap<>();
result.put("completedCount", stats[0] != null ? stats[0] : 0);
result.put("processingCount", stats[1] != null ? stats[1] : 0);
result.put("failedCount", stats[2] != null ? stats[2] : 0);
result.put("totalPointsCost", stats[3] != null ? stats[3] : 0);
try {
Object[] stats = userWorkRepository.getUserWorkStats(username);
// 安全地处理查询结果
if (stats != null && stats.length >= 4) {
result.put("completedCount", stats[0] != null ? stats[0] : 0);
result.put("processingCount", stats[1] != null ? stats[1] : 0);
result.put("failedCount", stats[2] != null ? stats[2] : 0);
result.put("totalPointsCost", stats[3] != null ? stats[3] : 0);
} else {
// 如果查询结果格式不正确,使用默认值
result.put("completedCount", 0);
result.put("processingCount", 0);
result.put("failedCount", 0);
result.put("totalPointsCost", 0);
}
} catch (Exception e) {
// 如果查询失败,使用默认值
logger.warn("获取用户作品统计失败,使用默认值: " + e.getMessage());
result.put("completedCount", 0);
result.put("processingCount", 0);
result.put("failedCount", 0);
result.put("totalPointsCost", 0);
}
result.put("totalCount", userWorkRepository.countByUsername(username));
result.put("publicCount", userWorkRepository.countPublicWorksByUsername(username));

View File

@@ -1,15 +1,19 @@
package com.example.demo.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
@Component
public class JwtUtils {
@@ -120,17 +124,7 @@ public class JwtUtils {
* 获取签名密钥
*/
private SecretKey getSigningKey() {
// 确保密钥长度至少为256位32字节
byte[] keyBytes = secret.getBytes();
if (keyBytes.length < 32) {
// 如果密钥太短使用SHA-256哈希扩展
try {
java.security.MessageDigest sha = java.security.MessageDigest.getInstance("SHA-256");
keyBytes = sha.digest(keyBytes);
} catch (java.security.NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 algorithm not available", e);
}
}
return Keys.hmacShaKeyFor(keyBytes);
}