diff --git a/src/main/java/com/ai/da/common/config/MyTaskScheduler.java b/src/main/java/com/ai/da/common/config/MyTaskScheduler.java index f7c10bcc..806082cf 100644 --- a/src/main/java/com/ai/da/common/config/MyTaskScheduler.java +++ b/src/main/java/com/ai/da/common/config/MyTaskScheduler.java @@ -202,7 +202,7 @@ public class MyTaskScheduler { } } - // @Scheduled(cron = "0 0 9 * * ?") +// @Scheduled(cron = "0 0 9 * * ?") public void sendTrialOrderExcelToManagements() { // 获取前一天日期 LocalDate yesterday = LocalDate.now().minusDays(1); diff --git a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java index ec24bfad..14027e39 100644 --- a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java +++ b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java @@ -61,10 +61,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { , "/api/account/schoolLogin", "/api/account/enterpriseLogin", "/api/account/organizationNameSearch", "/api/llm/stream", //GlobalAwardController - "/api/global-award/uploads/pdf/init", "/api/global-award/uploads/pdf/chunk", "/api/global-award/uploads/pdf/complete", "/api/global-award/uploads/pdf/status", - "/api/global-award/uploads/video/init", "/api/global-award/uploads/video/chunk", "/api/global-award/uploads/video/complete", "/api/global-award/uploads/video/status", - "/api/global-award/contestants/save", "/api/global-award/contestants/by-email", "/api/global-award/checkEmail", "/api/global-award/checkCode","/api/global-award/contestants/export", - "/api/global-award/contestants/export/files" + "/api/global-award" ); @Override diff --git a/src/main/java/com/ai/da/common/task/AccountTask.java b/src/main/java/com/ai/da/common/task/AccountTask.java index e0d90480..2fff300b 100644 --- a/src/main/java/com/ai/da/common/task/AccountTask.java +++ b/src/main/java/com/ai/da/common/task/AccountTask.java @@ -34,7 +34,7 @@ public class AccountTask { accountService.refreshCreditsMonthly(); } - // @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes +// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes public void getPaidUser() { // 获取code-create 表中 指定日期之后 订单状态为wc-processing的订单 accountService.extendValidityForCC(); diff --git a/src/main/java/com/ai/da/common/task/PaymentTask.java b/src/main/java/com/ai/da/common/task/PaymentTask.java index e63e9388..f1e47dd7 100644 --- a/src/main/java/com/ai/da/common/task/PaymentTask.java +++ b/src/main/java/com/ai/da/common/task/PaymentTask.java @@ -45,7 +45,7 @@ public class PaymentTask { @Resource private PayPalCheckoutService payPalCheckoutService; - // @Scheduled(cron = "0/30 * * * * ?") +// @Scheduled(cron = "0/30 * * * * ?") public void orderConfirmForPaypal() throws SerializeException { // log.info("PayPal orderConfirm 被执行......"); @@ -97,7 +97,7 @@ public class PaymentTask { // } - // @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes + @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes public void updateAffiliateInfoWithPayment(){ // log.info("佣金计算定时器"); affiliateService.updateAffiliateInfoWithPayment(); @@ -109,7 +109,7 @@ public class PaymentTask { affiliateService.syncLinkViewCountToDB(); } - // @Scheduled(cron = "0 0 8 28-31 * ?") +// @Scheduled(cron = "0 0 8 28-31 * ?") public void commissionSummaryReminder(){ // 每个月末的最后一天的早上八点执行 LocalDate today = LocalDate.now(); diff --git a/src/main/java/com/ai/da/common/task/SubscriptionReminderTask.java b/src/main/java/com/ai/da/common/task/SubscriptionReminderTask.java index 26478270..8195d7e7 100644 --- a/src/main/java/com/ai/da/common/task/SubscriptionReminderTask.java +++ b/src/main/java/com/ai/da/common/task/SubscriptionReminderTask.java @@ -40,7 +40,7 @@ public class SubscriptionReminderTask { REMINDER_DAYS_CONFIG.put("year", 14); } - // @Scheduled(cron = "0 0 9 * * ?") +// @Scheduled(cron = "0 0 9 * * ?") public void subscriptionReminder() { // 获取所有需要通知的订阅 List subscriptionInfos = getDueSubscriptions(); @@ -97,7 +97,7 @@ public class SubscriptionReminderTask { return subscriptionInfoMapper.selectList(qw); } - // @Scheduled(cron = "0 0 9 * * ?") +// @Scheduled(cron = "0 0 9 * * ?") public void trialReminder() { // 今天的 00:00:00 和 23:59:59 LocalDateTime startOfDay = LocalDateTime.now().toLocalDate().atStartOfDay(); diff --git a/src/main/java/com/ai/da/controller/GlobalAwardController.java b/src/main/java/com/ai/da/controller/GlobalAwardController.java index e36a7b77..a63d566b 100644 --- a/src/main/java/com/ai/da/controller/GlobalAwardController.java +++ b/src/main/java/com/ai/da/controller/GlobalAwardController.java @@ -4,6 +4,7 @@ import com.ai.da.common.response.Response; import com.ai.da.model.dto.*; import com.ai.da.model.dto.ContestantDTO; import com.ai.da.model.vo.CheckOTPVO; +import com.ai.da.model.vo.ContestantCountVO; import com.ai.da.service.GlobalAwardService; import com.ai.da.service.upload.UploadService; import com.ai.da.service.upload.UploadTask; @@ -176,10 +177,25 @@ public class GlobalAwardController { } @PostMapping("/contestants/export/files") - @ApiOperation(value = "导出参赛者文件到本地", notes = "根据参赛者编号范围导出PDF和视频文件到本地temp/uploads/contestants目录") - public Response exportContestantFiles(@ApiParam(value = "参赛者文件导出请求", required = true) @RequestBody ContestantExportRequest request) throws Exception { - int exportedCount = globalAwardService.exportContestantFiles(request.getMinContestantNumber(), request.getMaxContestantNumber()); - return Response.success(exportedCount); + @ApiOperation(value = "导出参赛者文件为ZIP", notes = "根据参赛者编号范围导出PDF、视频和信息文件为ZIP,直接响应给浏览器") + public void exportContestantFiles(@ApiParam(value = "参赛者文件导出请求", required = true) @RequestBody ContestantExportRequest request, HttpServletResponse response) throws Exception { + byte[] zipData = globalAwardService.exportContestantFilesAsZip(request.getMinContestantNumber(), request.getMaxContestantNumber()); + if (zipData.length == 0) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.getWriter().write("No contestants found in the specified range."); + return; + } + response.setContentType("application/zip"); + response.setHeader("Content-Disposition", "attachment; filename=\"contestants.zip\""); + response.setContentLength(zipData.length); + response.getOutputStream().write(zipData); + response.getOutputStream().flush(); + } + + @GetMapping("/contestants/count") + @ApiOperation(value = "查询参赛者总数", notes = "查询数据库中参赛者的总数量和最大参赛者编号") + public Response getContestantCount() { + return Response.success(globalAwardService.getContestantCount()); } } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/Contestant.java b/src/main/java/com/ai/da/mapper/primary/entity/Contestant.java index 0e7241bb..6592df21 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/Contestant.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/Contestant.java @@ -68,6 +68,9 @@ public class Contestant { @TableField("pdf_size") private Long pdfSize; + @TableField("portfolio_url") + private String portfolioUrl; + @TableField("created_at") private LocalDateTime createdAt; diff --git a/src/main/java/com/ai/da/model/vo/ContestantCountVO.java b/src/main/java/com/ai/da/model/vo/ContestantCountVO.java new file mode 100644 index 00000000..4495049f --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/ContestantCountVO.java @@ -0,0 +1,17 @@ +package com.ai.da.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ContestantCountVO { + + private Long count; + + private Integer maxContestantNumber; +} diff --git a/src/main/java/com/ai/da/model/vo/QueryUserConditionsVO.java b/src/main/java/com/ai/da/model/vo/QueryUserConditionsVO.java index 852ca839..2f0b0b13 100644 --- a/src/main/java/com/ai/da/model/vo/QueryUserConditionsVO.java +++ b/src/main/java/com/ai/da/model/vo/QueryUserConditionsVO.java @@ -34,4 +34,8 @@ public class QueryUserConditionsVO extends PageQueryBaseVo { private Integer systemUser; + private Long subscriptionPlanId; + + private Long organizationId; + } diff --git a/src/main/java/com/ai/da/service/GlobalAwardService.java b/src/main/java/com/ai/da/service/GlobalAwardService.java index 5dcde74f..76232238 100644 --- a/src/main/java/com/ai/da/service/GlobalAwardService.java +++ b/src/main/java/com/ai/da/service/GlobalAwardService.java @@ -2,6 +2,7 @@ package com.ai.da.service; import com.ai.da.model.dto.ContestantDTO; import com.ai.da.model.vo.CheckOTPVO; +import com.ai.da.model.vo.ContestantCountVO; import org.springframework.web.multipart.MultipartFile; import java.util.Map; @@ -33,12 +34,18 @@ public interface GlobalAwardService { void saveContestantsToLocal() throws Exception; /** - * 根据参赛者编号范围导出参赛者文件到本地目录 + * 将参赛者文件打包为 ZIP 并返回字节数组(不落盘,直接响应给浏览器) * @param minContestantNumber 最小参赛者编号 * @param maxContestantNumber 最大参赛者编号 - * @return 导出的参赛者数量 + * @return ZIP 文件的字节数组 */ - int exportContestantFiles(Integer minContestantNumber, Integer maxContestantNumber) throws Exception; + byte[] exportContestantFilesAsZip(Integer minContestantNumber, Integer maxContestantNumber) throws Exception; + + /** + * 查询参赛者总数和最大参赛者编号 + * @return 参赛者数量和最大参赛者编号 + */ + ContestantCountVO getContestantCount(); } diff --git a/src/main/java/com/ai/da/service/impl/CollectionElementServiceImpl.java b/src/main/java/com/ai/da/service/impl/CollectionElementServiceImpl.java index 87a246e5..1f34afc7 100644 --- a/src/main/java/com/ai/da/service/impl/CollectionElementServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/CollectionElementServiceImpl.java @@ -524,7 +524,7 @@ public class CollectionElementServiceImpl extends ServiceImpl impleme print.setPosition("[0.0,0.0]"); // print.setScale(1d); // todo mark 将print默认scale置为0.3 - print.setScale(Arrays.toString(new Float[]{0.3f, 0.3f})); + print.setScale(Arrays.toString(new Float[]{1.0f, 1.0f})); print.setAngle(0.0); print.setPriority(1); QueryWrapper getPrintboardLevel2TypeQw = new QueryWrapper<>(); diff --git a/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java b/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java index d23c7b6b..bbf5111d 100644 --- a/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java @@ -3934,11 +3934,48 @@ public class GenerateServiceImpl extends ServiceImpl i } public byte[] downloadVideoOrImage(String url) { - try (CloseableHttpClient client = HttpClients.createDefault(); - InputStream in = client.execute(new HttpGet(url)).getEntity().getContent()) { - return IOUtils.toByteArray(in); - } catch (IOException e) { - throw new RuntimeException(e); + int maxRetries = 3; + int retryDelayMs = 1000; + IOException lastException = null; + + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + return downloadWithTimeout(url, 30000, 60000); + } catch (IOException e) { + lastException = e; + log.warn("下载失败 (尝试 {}/{}): {}", attempt, maxRetries, e.getMessage()); + + if (attempt < maxRetries) { + try { + Thread.sleep((long) retryDelayMs * attempt); // 递增延迟 + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } + } + } + } + + throw new RuntimeException("下载失败,已重试 " + maxRetries + " 次", lastException); + } + + private byte[] downloadWithTimeout(String url, int connectTimeout, int socketTimeout) throws IOException { + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(connectTimeout) + .setSocketTimeout(socketTimeout) + .setConnectionRequestTimeout(connectTimeout) + .build(); + + try (CloseableHttpClient client = HttpClients.custom() + .setDefaultRequestConfig(requestConfig) + .build(); + CloseableHttpResponse response = client.execute(new HttpGet(url))) { + + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != 200) { + throw new IOException("HTTP Error: " + statusCode); + } + return IOUtils.toByteArray(response.getEntity().getContent()); } } diff --git a/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java b/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java index 210da7de..804c10cd 100644 --- a/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java @@ -12,6 +12,7 @@ import com.ai.da.mapper.primary.entity.Notification; import com.ai.da.model.dto.ContestantDTO; import com.ai.da.model.dto.PublishSysNotificationDTO; import com.ai.da.model.vo.CheckOTPVO; +import com.ai.da.model.vo.ContestantCountVO; import com.ai.da.service.GlobalAwardService; import com.ai.da.service.MessageCenterService; import com.alibaba.fastjson.JSON; @@ -26,24 +27,22 @@ import org.springframework.transaction.annotation.Transactional; import jakarta.annotation.Resource; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; 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.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import jakarta.servlet.http.HttpServletResponse; +import java.util.zip.ZipEntry; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.io.FileOutputStream; -import java.time.format.DateTimeFormatter; @Service @Slf4j @@ -187,6 +186,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { .videoSize(request.getVideoSize()) .pdfSize(request.getPdfSize()) .contestantNumber(nextNumber) + .portfolioUrl(request.getPortfolioUrl()) .createdAt(now) .updatedAt(now) .build(); @@ -227,6 +227,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { existing.setVideoDuration(request.getVideoDuration()); existing.setVideoSize(request.getVideoSize()); existing.setPdfSize(request.getPdfSize()); + existing.setPortfolioUrl(request.getPortfolioUrl()); existing.setUpdatedAt(now); contestantMapper.updateById(existing); resp.put("success", true); @@ -244,7 +245,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { String[] headers = new String[] { "contestantNumber", "email", "firstName", "lastName", "gender", "occupation", "age", "countryRegionCity", "phoneNumber", "designTitle", "designDescription", - "pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "createdAt", "updatedAt" + "pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "portfolioUrl", "createdAt", "updatedAt" }; for (int i = 0; i < headers.length; i++) { Cell c = header.createCell(i); @@ -265,11 +266,9 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { r.createCell(ci++).setCellValue(cst.getPhoneNumber() == null ? "" : cst.getPhoneNumber()); r.createCell(ci++).setCellValue(cst.getDesignTitle() == null ? "" : cst.getDesignTitle()); r.createCell(ci++).setCellValue(cst.getDesignDescription() == null ? "" : cst.getDesignDescription()); -// r.createCell(ci++).setCellValue(cst.getPdfPath() == null ? "" : cst.getPdfPath()); -// r.createCell(ci++).setCellValue(cst.getVideoPath() == null ? "" : cst.getVideoPath()); - // 视频时长(秒) + r.createCell(ci++).setCellValue(cst.getPdfPath() == null ? "" : cst.getPdfPath()); + r.createCell(ci++).setCellValue(cst.getVideoPath() == null ? "" : cst.getVideoPath()); r.createCell(ci++).setCellValue(cst.getVideoDuration() == null ? "" : cst.getVideoDuration().toString()); - // 视频大小、PDF 大小:以 MB 导出,保留两位小数 if (cst.getVideoSize() == null) { r.createCell(ci++).setCellValue(""); } else { @@ -282,6 +281,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { double pMb = cst.getPdfSize() / 1024.0 / 1024.0; r.createCell(ci++).setCellValue(String.format("%.2f", pMb)); } + r.createCell(ci++).setCellValue(cst.getPortfolioUrl() == null ? "" : cst.getPortfolioUrl()); r.createCell(ci++).setCellValue(cst.getCreatedAt() == null ? "" : cst.getCreatedAt().toString()); r.createCell(ci++).setCellValue(cst.getUpdatedAt() == null ? "" : cst.getUpdatedAt().toString()); } @@ -311,7 +311,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { String[] headers = new String[] { "contestantNumber", "email", "firstName", "lastName", "gender", "occupation", "age", "countryRegionCity", "phoneNumber", "designTitle", "designDescription", - "pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "createdAt", "updatedAt" + "pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "portfolioUrl", "createdAt", "updatedAt" }; for (int i = 0; i < headers.length; i++) { Cell c = header.createCell(i); @@ -332,11 +332,9 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { r.createCell(ci++).setCellValue(cst.getPhoneNumber() == null ? "" : cst.getPhoneNumber()); r.createCell(ci++).setCellValue(cst.getDesignTitle() == null ? "" : cst.getDesignTitle()); r.createCell(ci++).setCellValue(cst.getDesignDescription() == null ? "" : cst.getDesignDescription()); -// r.createCell(ci++).setCellValue(cst.getPdfPath() == null ? "" : cst.getPdfPath()); -// r.createCell(ci++).setCellValue(cst.getVideoPath() == null ? "" : cst.getVideoPath()); - // 视频时长(秒) + r.createCell(ci++).setCellValue(cst.getPdfPath() == null ? "" : cst.getPdfPath()); + r.createCell(ci++).setCellValue(cst.getVideoPath() == null ? "" : cst.getVideoPath()); r.createCell(ci++).setCellValue(cst.getVideoDuration() == null ? "" : cst.getVideoDuration().toString()); - // 视频大小、PDF 大小:以 MB 导出,保留两位小数 if (cst.getVideoSize() == null) { r.createCell(ci++).setCellValue(""); } else { @@ -349,6 +347,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { double pMb = cst.getPdfSize() / 1024.0 / 1024.0; r.createCell(ci++).setCellValue(String.format("%.2f", pMb)); } + r.createCell(ci++).setCellValue(cst.getPortfolioUrl() == null ? "" : cst.getPortfolioUrl()); r.createCell(ci++).setCellValue(cst.getCreatedAt() == null ? "" : cst.getCreatedAt().toString()); r.createCell(ci++).setCellValue(cst.getUpdatedAt() == null ? "" : cst.getUpdatedAt().toString()); } @@ -387,6 +386,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { dto.setVideoDuration(existing.getVideoDuration()); dto.setPdfSize(existing.getPdfSize()); dto.setVideoSize(existing.getVideoSize()); + dto.setPortfolioUrl(existing.getPortfolioUrl()); return dto; } @@ -480,7 +480,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { } @Override - public int exportContestantFiles(Integer minContestantNumber, Integer maxContestantNumber) throws Exception { + public byte[] exportContestantFilesAsZip(Integer minContestantNumber, Integer maxContestantNumber) throws Exception { if (minContestantNumber == null || maxContestantNumber == null) { throw new BusinessException("minContestantNumber and maxContestantNumber are required."); } @@ -488,7 +488,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { throw new BusinessException("minContestantNumber cannot be greater than maxContestantNumber."); } - // 1. 根据contestantNumber范围查询参赛者 + // 1. 根据 contestantNumber 范围查询参赛者 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.lambda() .ge(Contestant::getContestantNumber, minContestantNumber) @@ -498,90 +498,126 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { if (contestants.isEmpty()) { log.info("No contestants found in range [{}, {}]", minContestantNumber, maxContestantNumber); - return 0; + return new byte[0]; } - // 2. 创建基础目录 - String baseDir = uploadDir + "/contestants"; - Path basePath = Paths.get(baseDir).toAbsolutePath(); - Files.createDirectories(basePath); - log.info("Base directory created: {}", basePath); + // 2. 在内存中构建 ZIP + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(baos)) { - int exportedCount = 0; - - // 3. 遍历每个参赛者,下载文件 - for (Contestant contestant : contestants) { - Integer contestantNumber = contestant.getContestantNumber(); - if (contestantNumber == null) { - log.warn("Contestant {} has no contestantNumber, skipping", contestant.getId()); - continue; - } - - // 创建参赛者文件夹 - String contestantDir = baseDir + "/" + contestantNumber; - Path contestantPath = Paths.get(contestantDir); - Files.createDirectories(contestantPath); - - // 下载PDF文件 - String pdfPath = contestant.getPdfPath(); - if (StringUtils.isNotBlank(pdfPath)) { - try { - String fileName = pdfPath.contains("/") ? - pdfPath.substring(pdfPath.lastIndexOf("/") + 1) : "design.pdf"; - downloadFileFromMinio(pdfPath, contestantPath.toString(), "design.pdf"); - log.info("Downloaded PDF for contestant {}", fileName); - } catch (Exception e) { - log.error("Failed to download PDF for contestant {}: {}", contestantNumber, e.getMessage()); + for (Contestant contestant : contestants) { + Integer contestantNumber = contestant.getContestantNumber(); + if (contestantNumber == null) { + log.warn("Contestant {} has no contestantNumber, skipping", contestant.getId()); + continue; } - } - // 下载视频文件 - String videoPath = contestant.getVideoPath(); - if (StringUtils.isNotBlank(videoPath)) { - try { - // 根据路径判断视频格式 + String dirPrefix = contestantNumber + "/"; + + // 添加 PDF 文件 + String pdfPath = contestant.getPdfPath(); + if (StringUtils.isNotBlank(pdfPath)) { + addMinioFileToZip(zos, pdfPath, dirPrefix + "design.pdf"); + } + + // 添加视频文件 + String videoPath = contestant.getVideoPath(); + if (StringUtils.isNotBlank(videoPath)) { String fileName = videoPath.contains("/") ? videoPath.substring(videoPath.lastIndexOf("/") + 1) : "video.mp4"; - downloadFileFromMinio(videoPath, contestantPath.toString(), fileName); - log.info("Downloaded video for contestant {}", contestantNumber); - } catch (Exception e) { - log.error("Failed to download video for contestant {}: {}", contestantNumber, e.getMessage()); + addMinioFileToZip(zos, videoPath, dirPrefix + fileName); } + + // 添加参赛者信息 txt 文件 + StringBuilder sb = new StringBuilder(); + sb.append("=== Contestant Information ===\n\n"); + sb.append("ID: ").append(nullSafe(contestant.getId())).append("\n"); + sb.append("Email: ").append(nullSafe(contestant.getEmail())).append("\n"); + sb.append("Contestant Number: ").append(contestantNumber).append("\n"); + sb.append("First Name: ").append(nullSafe(contestant.getFirstName())).append("\n"); + sb.append("Last Name: ").append(nullSafe(contestant.getLastName())).append("\n"); + sb.append("Gender: ").append(nullSafe(contestant.getGender())).append("\n"); + sb.append("Occupation: ").append(nullSafe(contestant.getOccupation())).append("\n"); + sb.append("Age: ").append(contestant.getAge() != null ? contestant.getAge() : "N/A").append("\n"); + sb.append("Country/Region/City: ").append(nullSafe(contestant.getCountryRegionCity())).append("\n"); + sb.append("Phone Number: ").append(nullSafe(contestant.getPhoneNumber())).append("\n"); + sb.append("Design Title: ").append(nullSafe(contestant.getDesignTitle())).append("\n"); + sb.append("Design Description: ").append(nullSafe(contestant.getDesignDescription())).append("\n"); + sb.append("PDF Path: ").append(nullSafe(pdfPath)).append("\n"); + sb.append("PDF Size (bytes): ").append(contestant.getPdfSize() != null ? contestant.getPdfSize() : "N/A").append("\n"); + sb.append("Video Path: ").append(nullSafe(videoPath)).append("\n"); + sb.append("Video Duration (seconds): ").append(contestant.getVideoDuration() != null ? contestant.getVideoDuration() : "N/A").append("\n"); + sb.append("Video Size (bytes): ").append(contestant.getVideoSize() != null ? contestant.getVideoSize() : "N/A").append("\n"); + sb.append("Portfolio URL: ").append(nullSafe(contestant.getPortfolioUrl())).append("\n"); + sb.append("Created At: ").append(contestant.getCreatedAt() != null ? contestant.getCreatedAt() : "N/A").append("\n"); + sb.append("Updated At: ").append(contestant.getUpdatedAt() != null ? contestant.getUpdatedAt() : "N/A").append("\n"); + + ZipEntry infoEntry = new ZipEntry(dirPrefix + "contestant_info.txt"); + zos.putNextEntry(infoEntry); + zos.write(sb.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8)); + zos.closeEntry(); + log.info("Added contestant {} info to zip", contestantNumber); } - exportedCount++; + zos.finish(); + log.info("ZIP built for {} contestants, size: {} bytes", contestants.size(), baos.size()); + return baos.toByteArray(); } - - log.info("Exported {} contestants' files to {}", exportedCount, basePath); - return exportedCount; } /** - * 从MinIO下载文件到本地 - * @param minioPath MinIO路径 (格式: bucketName/objectPath) - * @param localDir 本地目录 - * @param fileName 本地文件名 + * 将 MinIO 文件流式写入 ZIP,不落盘 + * @param zos ZIP 输出流 + * @param minioPath MinIO 路径(格式: bucketName/objectPath) + * @param entryName ZIP 条目名称 */ - private void downloadFileFromMinio(String minioPath, String localDir, String fileName) { + private void addMinioFileToZip(java.util.zip.ZipOutputStream zos, String minioPath, String entryName) { if (StringUtils.isBlank(minioPath)) { return; } - - // 从路径中提取bucket名称和对象名称 int index = minioPath.indexOf("/"); if (index == -1) { log.warn("Invalid MinIO path: {}", minioPath); return; } - String bucketName = minioPath.substring(0, index); String objectName = minioPath.substring(index + 1); - // 构建本地文件完整路径 - Path localFilePath = Paths.get(localDir, fileName); + try (InputStream in = minioUtil.download(bucketName, objectName)) { + ZipEntry entry = new ZipEntry(entryName); + zos.putNextEntry(entry); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + zos.write(buffer, 0, bytesRead); + } + zos.closeEntry(); + log.info("Added {} to zip ({} bytes)", entryName, entry.getSize()); + } catch (Exception e) { + log.error("Failed to add {} to zip: {}", entryName, e.getMessage()); + } + } - // 下载文件 - minioUtil.downloadMinioObjectToLocal(bucketName, objectName, localFilePath.toString()); + @Override + public ContestantCountVO getContestantCount() { + long count = contestantMapper.selectCount(null); + Integer maxContestantNumber = null; + QueryWrapper qMax = new QueryWrapper<>(); + qMax.isNotNull("contestant_number"); + qMax.orderByDesc("contestant_number"); + qMax.last("LIMIT 1"); + Contestant last = contestantMapper.selectOne(qMax); + if (last != null) { + maxContestantNumber = last.getContestantNumber(); + } + return ContestantCountVO.builder() + .count(count) + .maxContestantNumber(maxContestantNumber) + .build(); + } + + private String nullSafe(String value) { + return value != null ? value : "N/A"; } } diff --git a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java index 86ea5be3..f402a043 100644 --- a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java @@ -451,11 +451,12 @@ public class StripeServiceImpl implements StripeService { String periodEnd = DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_yyyy_MM_dd_HH_mm_ss); qwPI.lambda().eq(PaymentInfo::getOrderNo, subscriptionInfo.getOrderNo()) + .eq(PaymentInfo::getTradeState, "paid") .between(PaymentInfo::getCreateTime, periodStart, periodEnd) .orderByDesc(PaymentInfo::getId); List paymentInfos = paymentInfoMapper.selectList(qwPI); if (paymentInfos.isEmpty()) { - log.info("不发送邮件,原因:【根据order_no:{},查询到的paymentInfos为空】", orderNo); + log.info("不发送邮件,原因:【根据order_no:{},查询到的成功的paymentInfos为空】", orderNo); return false; } PaymentInfo paymentInfo = paymentInfos.get(0); diff --git a/src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java b/src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java index 47e8b587..7758064d 100644 --- a/src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java @@ -905,15 +905,15 @@ public class UserLikeGroupServiceImpl extends ServiceImpl childSortList = collectionSortMapper.selectList(childCollectionQw); List childList = new ArrayList<>(); + // 收集需要删除的失败记录ID,用于后续统一清理并重新排序 + List failedSortIds = new ArrayList<>(); for (CollectionSort userLikeSort : childSortList) { if (userLikeSort.getRelationType().equals(CollectionType.TO_PRODUCT_IMAGE.getValue())) { ToProductImageResult toProductImageResult = toProductImageResultMapper.selectById(userLikeSort.getRelationId()); if (isGenerateTaskFailed(toProductImageResult.getStatus(), toProductImageResult.getCreateTime())) { + failedSortIds.add(userLikeSort.getId()); + log.info("【获取内容】TO_PRODUCT_IMAGE结果失败,relationId={},即将从collection_sort中删除", userLikeSort.getRelationId()); continue; } toProductImageResult.setUrl(getMinioUrl(toProductImageResult.getUrl())); @@ -2238,6 +2246,8 @@ public class UserLikeGroupServiceImpl extends ServiceImpl(); + for (CollectionSort userLikeSort : childSortList) { + if (userLikeSort.getRelationType().equals(CollectionType.TO_PRODUCT_IMAGE.getValue())) { + ToProductImageResult toProductImageResult = toProductImageResultMapper.selectById(userLikeSort.getRelationId()); + if (isGenerateTaskFailed(toProductImageResult.getStatus(), toProductImageResult.getCreateTime())) { + continue; + } + toProductImageResult.setUrl(getMinioUrl(toProductImageResult.getUrl())); + ToProductImageResultVO toProductImageResultVO = CopyUtil.copyObject(toProductImageResult, ToProductImageResultVO.class); + + ToProductImageRecord toProductImageRecord = toProductImageRecordMapper.selectById(toProductImageResult.getToProductImageRecordId()); + if (Objects.isNull(toProductImageRecord)) { + continue; + } + toProductImageResultVO.setPrompt(toProductImageRecord.getPrompt()); + + if (toProductImageResultVO.getElementType().equals("ProductElement")) { + ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageResultVO.getElementId()); + toProductImageResultVO.setSourceUrl(getMinioUrl(toProductElement.getUrl())); + } else if ((toProductImageResultVO.getElementType().equals("DesignOutfit"))) { + TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageResultVO.getElementId()); + toProductImageResultVO.setSourceUrl(getMinioUrl(tDesignPythonOutfit.getDesignUrl())); + } else { + ToProductImageResult toProductImageResult1 = toProductImageResultMapper.selectById(toProductImageResultVO.getElementId()); + toProductImageResultVO.setSourceUrl(getMinioUrl(toProductImageResult1.getUrl())); + } + toProductImageResultVO.setCollectionType(CollectionType.TO_PRODUCT_IMAGE.getValue()); + toProductImageResultVO.setSort(userLikeSort.getSort()); + toProductImageResultVO.setUserLikeSortId(userLikeSort.getId()); + toProductImageResultVO.setRelationType(userLikeSort.getRelationType()); + toProductImageResultVO.setParentId(userLikeSort.getParentId()); + childList.add(toProductImageResultVO); + } else if (userLikeSort.getRelationType().equals(CollectionType.RELIGHT.getValue())) { + ToProductImageResult toProductImageResult = toProductImageResultMapper.selectById(userLikeSort.getRelationId()); + if (isGenerateTaskFailed(toProductImageResult.getStatus(), toProductImageResult.getCreateTime())) { + continue; + } + toProductImageResult.setUrl(getMinioUrl(toProductImageResult.getUrl())); + ToProductImageResultVO toProductImageResultVO = CopyUtil.copyObject(toProductImageResult, ToProductImageResultVO.class); + + ToProductImageRecord toProductImageRecord = toProductImageRecordMapper.selectById(toProductImageResult.getToProductImageRecordId()); + if (Objects.isNull(toProductImageRecord)) { + continue; + } + toProductImageResultVO.setPrompt(toProductImageRecord.getPrompt()); + + if (toProductImageResultVO.getElementType().equals("ProductElement")) { + ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageResultVO.getElementId()); + toProductImageResultVO.setSourceUrl(getMinioUrl(toProductElement.getUrl())); + } else if ((toProductImageResultVO.getElementType().equals("DesignOutfit"))) { + TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageResultVO.getElementId()); + toProductImageResultVO.setSourceUrl(getMinioUrl(tDesignPythonOutfit.getDesignUrl())); + } else { + ToProductImageResult toProductImageResult1 = toProductImageResultMapper.selectById(toProductImageResultVO.getElementId()); + toProductImageResultVO.setSourceUrl(getMinioUrl(toProductImageResult1.getUrl())); + } + toProductImageResultVO.setCollectionType(CollectionType.RELIGHT.getValue()); + toProductImageResultVO.setSort(userLikeSort.getSort()); + toProductImageResultVO.setUserLikeSortId(userLikeSort.getId()); + toProductImageResultVO.setRelationType(userLikeSort.getRelationType()); + toProductImageResultVO.setParentId(userLikeSort.getParentId()); + childList.add(toProductImageResultVO); + } else if (userLikeSort.getRelationType().equals(CollectionType.POSE_TRANSFORM.getValue())) { + PoseTransformation item = poseTransformationMapper.selectById(userLikeSort.getRelationId()); + if (isGenerateTaskFailed(item.getTaskStatus(), item.getCreateTime())) { + continue; + } + PoseTransformationVO poseTransformationVO = new PoseTransformationVO(); + poseTransformationVO.setId(item.getId()); + poseTransformationVO.setTaskId(item.getUniqueId()); + poseTransformationVO.setProductImage(getMinioUrl(item.getProductImage())); + poseTransformationVO.setLastFrameProductImage(getMinioUrl(item.getLastFrameProductImage())); + poseTransformationVO.setPrompt(item.getPrompt()); + poseTransformationVO.setGifUrl(getMinioUrl(item.getGifUrl())); + poseTransformationVO.setVideoUrl(getMinioUrl(item.getVideoUrl())); + poseTransformationVO.setFirstFrameUrl(getMinioUrl(item.getFirstFrameUrl())); + poseTransformationVO.setIsLiked(item.getIsLiked()); + poseTransformationVO.setCollectionType(CollectionType.POSE_TRANSFORM.getValue()); + poseTransformationVO.setSort(userLikeSort.getSort()); + poseTransformationVO.setUserLikeSortId(userLikeSort.getId()); + poseTransformationVO.setRelationType(userLikeSort.getRelationType()); + poseTransformationVO.setResultType(CollectionType.POSE_TRANSFORM.getValue()); + poseTransformationVO.setParentId(userLikeSort.getParentId()); + poseTransformationVO.setModelName(item.getModelName()); + poseTransformationVO.setPoseId(item.getPoseId()); + poseTransformationVO.setStatus(item.getTaskStatus()); + childList.add(poseTransformationVO); + } + } + log.info("【获取内容】失败记录清理完成,重新排序后childList.size={}", childList.size()); + } o.setChildList(childList); list.add(o); diff --git a/src/main/resources/payment.properties b/src/main/resources/payment.properties index 0f2800d6..efda4ceb 100644 --- a/src/main/resources/payment.properties +++ b/src/main/resources/payment.properties @@ -11,18 +11,18 @@ #paypal.webhook_id=51V87014T6406322F # aida-sandbox-kim -#paypal.client-id=AbDDH8jnTrKqjnWLFgEu6LogYzVz2ZLuirE4W54t1M4lrofrP5OzXfhbxqktLLFB-rAO9KeYQVYFJ_tO -#paypal.client-secret=EOOoiIAe_dyR2YhY7qCIqWipZvYXCDrmBlFYchphuvkPFms1spsBGTlStlrx580y4hN-EukWwF9m_LAs -#paypal.receiver.email=sb-4xe8i29784722@business.example.com -#paypal.mode=sandbox -#paypal.webhook_id=1WH327112B602422N +paypal.client-id=AbDDH8jnTrKqjnWLFgEu6LogYzVz2ZLuirE4W54t1M4lrofrP5OzXfhbxqktLLFB-rAO9KeYQVYFJ_tO +paypal.client-secret=EOOoiIAe_dyR2YhY7qCIqWipZvYXCDrmBlFYchphuvkPFms1spsBGTlStlrx580y4hN-EukWwF9m_LAs +paypal.receiver.email=sb-4xe8i29784722@business.example.com +paypal.mode=sandbox +paypal.webhook_id=1WH327112B602422N # aida-live-kim -paypal.client-id=ASWSIZ3MXJU5w5VOeOHeigWcSw6iinl30ZCipruziKpHclxP0ryf8-7VKG1Ba2VwZwa2DMvGEzTfCTgz -paypal.client-secret=EHQg_K5PSqmp4FJlzEcOEH_kFkmq4aBzaI7jridw53L6cOQRULBAnfv2KakRfrsqaU1PDSkO4Co9Vyxc -paypal.receiver.email=kimwong@code-create.com.hk -paypal.mode=live -paypal.webhook_id=1D107312EX592781K +#paypal.client-id=ASWSIZ3MXJU5w5VOeOHeigWcSw6iinl30ZCipruziKpHclxP0ryf8-7VKG1Ba2VwZwa2DMvGEzTfCTgz +#paypal.client-secret=EHQg_K5PSqmp4FJlzEcOEH_kFkmq4aBzaI7jridw53L6cOQRULBAnfv2KakRfrsqaU1PDSkO4Co9Vyxc +#paypal.receiver.email=kimwong@code-create.com.hk +#paypal.mode=live +#paypal.webhook_id=1D107312EX592781K ##### Stripe