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 1b3d80e5..3dc70417 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 @@ -63,7 +63,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { //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/save", "/api/global-award/contestants/by-email", "/api/global-award/checkEmail", "/api/global-award/checkCode","/api/global-award/contestants/export" ); @Override diff --git a/src/main/java/com/ai/da/controller/GlobalAwardController.java b/src/main/java/com/ai/da/controller/GlobalAwardController.java index 10879652..2206dc4d 100644 --- a/src/main/java/com/ai/da/controller/GlobalAwardController.java +++ b/src/main/java/com/ai/da/controller/GlobalAwardController.java @@ -11,6 +11,7 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -163,6 +164,17 @@ public class GlobalAwardController { return Response.success(globalAwardService.checkCode(email, code)); } + @GetMapping("/contestants/export") + @ApiOperation(value = "导出参赛者列表为Excel", notes = "导出所有参赛者信息为xlsx并触发下载") + public void exportContestants(HttpServletResponse response) throws Exception { + byte[] data = globalAwardService.exportContestants(); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=\"contestants.xlsx\""); + response.setContentLength(data.length); + response.getOutputStream().write(data); + response.getOutputStream().flush(); + } + } 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 8c79b51f..0e7241bb 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 @@ -26,6 +26,9 @@ public class Contestant { private String email; + @TableField("contestant_number") + private Integer contestantNumber; + @TableField("first_name") private String firstName; @@ -56,6 +59,15 @@ public class Contestant { @TableField("video_path") private String videoPath; + @TableField("video_duration") + private Integer videoDuration; + + @TableField("video_size") + private Long videoSize; + + @TableField("pdf_size") + private Long pdfSize; + @TableField("created_at") private LocalDateTime createdAt; diff --git a/src/main/java/com/ai/da/model/dto/ContestantDTO.java b/src/main/java/com/ai/da/model/dto/ContestantDTO.java index 57342903..1390c819 100644 --- a/src/main/java/com/ai/da/model/dto/ContestantDTO.java +++ b/src/main/java/com/ai/da/model/dto/ContestantDTO.java @@ -49,6 +49,15 @@ public class ContestantDTO { @ApiModelProperty(value = "视频文件路径", required = false, example = "contestants/user@example.com/2024/01/video_1234567890.mp4") private String videoPath; + + @ApiModelProperty(value = "视频时长(秒)", required = false, example = "120") + private Integer videoDuration; + + @ApiModelProperty(value = "视频大小(字节)", required = false, example = "10485760") + private Long videoSize; + + @ApiModelProperty(value = "PDF 文件大小(字节)", required = false, example = "524288") + private Long pdfSize; // /** // * 是否确认覆盖已存在记录(false 表示发现已有记录时仅返回 existingRecord,不覆盖) @@ -58,6 +67,8 @@ public class ContestantDTO { @NotBlank private String secureToken; + + } diff --git a/src/main/java/com/ai/da/python/PythonService.java b/src/main/java/com/ai/da/python/PythonService.java index feb02dfe..5ef07e16 100644 --- a/src/main/java/com/ai/da/python/PythonService.java +++ b/src/main/java/com/ai/da/python/PythonService.java @@ -2851,13 +2851,17 @@ public class PythonService { gradientString = JSONObject.toJSONString(designSingleItem.getGradient()); } - PrintToPython printToPython = resolveDesignSinglePrint(designSingleItem.getPrintObject().getPrints(), + PrintToPython printToPython; + printToPython = resolveDesignSinglePrint(designSingleItem.getPrintObject().getPrints(), designSingleItem.getPartialDesign().getPartialDesignMinioPath()); - resolveDesignElement(designSingleItem.getTrims(), printToPython); + /*PrintToPython printToPython = resolveDesignSinglePrint(designSingleItem.getPrintObject().getPrints(), + designSingleItem.getPartialDesign().getPartialDesignMinioPath());*/ +// resolveDesignElement(designSingleItem.getTrims(), printToPython); log.info("组装参数【服装:{}的maskUrl: {}】",designSingleItem.getType(), designSingleItem.getMaskUrl()); - String mergeImagePath = !StringUtil.isNullOrEmpty(printToPython.getPartial()) - ? printToPython.getPartial() : designSingleItem.getPath(); + String partialDesign = designSingleItem.getPartialDesign().getPartialDesignMinioPath(); + String mergeImagePath = !StringUtil.isNullOrEmpty(partialDesign) + ? partialDesign : designSingleItem.getPath(); response.add(new DesignPythonItem( designSingleItem.getType(), designSingleItem.getPath(), @@ -2896,9 +2900,9 @@ public class PythonService { private PrintToPython resolveDesignSinglePrint(List printObject, String partialDesign) { PrintToPython printToPython = new PrintToPython(); - DesignPythonItemPrint printSingle = new DesignPythonItemPrint(); +// DesignPythonItemPrint printSingle = new DesignPythonItemPrint(); DesignPythonItemPrint printOverall = new DesignPythonItemPrint(); - printToPython.setSingle(printSingle); +// printToPython.setSingle(printSingle); printToPython.setOverall(printOverall); printToPython.setPartial(StringUtil.isNullOrEmpty(partialDesign) ? null : partialDesign); if (Objects.isNull(printObject) || printObject.isEmpty()){ @@ -2945,14 +2949,14 @@ public class PythonService { } // log.info("本次print打点locations###{}###fileVO{}", p.getLocation(), JSON.toJSONString(fileVO)); }); - locationS.removeAll(Collections.singleton(null)); + /*locationS.removeAll(Collections.singleton(null)); scaleS.removeAll(Collections.singleton(null)); angleS.removeAll(Collections.singleton(null)); pathsS.removeAll(Collections.singleton(null)); printSingle.setLocation(locationS); printSingle.setPrint_scale_list(scaleS); printSingle.setPrint_angle_list(angleS); - printSingle.setPrint_path_list(pathsS); + printSingle.setPrint_path_list(pathsS);*/ locationO.removeAll(Collections.singleton(null)); scaleO.removeAll(Collections.singleton(null)); diff --git a/src/main/java/com/ai/da/service/GlobalAwardService.java b/src/main/java/com/ai/da/service/GlobalAwardService.java index 583cf321..b21cfc36 100644 --- a/src/main/java/com/ai/da/service/GlobalAwardService.java +++ b/src/main/java/com/ai/da/service/GlobalAwardService.java @@ -20,6 +20,17 @@ public interface GlobalAwardService { CheckOTPVO checkCode(String email, String otp); void checkSecurityToken(String email, String securityToken); + + /** + * 导出参赛者列表为 Excel(二进制) + * @return Excel 文件的字节数组 + */ + byte[] exportContestants() throws Exception; + + /** + * 将参赛者列表导出并保存到服务端本地目录(使用服务配置的 uploadDir/exports) + */ + void saveContestantsToLocal() throws Exception; } diff --git a/src/main/java/com/ai/da/service/impl/ConvenientInquiryServiceImpl.java b/src/main/java/com/ai/da/service/impl/ConvenientInquiryServiceImpl.java index 7d5011a8..05b5b960 100644 --- a/src/main/java/com/ai/da/service/impl/ConvenientInquiryServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/ConvenientInquiryServiceImpl.java @@ -33,7 +33,6 @@ import io.netty.util.internal.StringUtil; import lombok.extern.slf4j.Slf4j; import org.apache.poi.xssf.usermodel.*; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -705,14 +704,19 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl saveContestant(ContestantDTO request) { Map resp = new HashMap<>(); if (request.getEmail() == null) { @@ -142,6 +158,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { LocalDateTime now = LocalDateTime.now(); if (existing == null) { + // 由数据库自增分配 contestant_number(请确保数据库列为 AUTO_INCREMENT) Contestant toInsert = Contestant.builder() .email(request.getEmail()) .firstName(request.getFirstName()) @@ -155,11 +172,18 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { .designDescription(request.getDesignDescription()) .pdfPath(request.getPdfPath()) .videoPath(request.getVideoPath()) + .videoDuration(request.getVideoDuration()) + .videoSize(request.getVideoSize()) + .pdfSize(request.getPdfSize()) .createdAt(now) .updatedAt(now) .build(); contestantMapper.insert(toInsert); + // 重新查询以获取数据库分配的 contestant_number + Contestant saved = contestantMapper.selectById(toInsert.getId()); + Integer assignedNumber = (saved == null) ? null : saved.getContestantNumber(); resp.put("success", true); + resp.put("contestant_number", assignedNumber); sendSiteMsg(toInsert.getId(), toInsert.getEmail()); return resp; } else { @@ -175,13 +199,145 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { existing.setDesignDescription(request.getDesignDescription()); existing.setPdfPath(request.getPdfPath()); existing.setVideoPath(request.getVideoPath()); + existing.setVideoDuration(request.getVideoDuration()); + existing.setVideoSize(request.getVideoSize()); + existing.setPdfSize(request.getPdfSize()); existing.setUpdatedAt(now); contestantMapper.updateById(existing); resp.put("success", true); + resp.put("contestant_number", existing.getContestantNumber()); return resp; } } + @Override + public byte[] exportContestants() throws Exception { + List list = contestantMapper.selectList(new QueryWrapper<>()); + try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { + Sheet sheet = workbook.createSheet("contestants"); + int rowIdx = 0; + Row header = sheet.createRow(rowIdx++); + String[] headers = new String[] { + "contestantNumber", "email", "firstName", "lastName", "gender", "occupation", + "age", "countryRegionCity", "phoneNumber", "designTitle", "designDescription", + "pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "createdAt", "updatedAt" + }; + for (int i = 0; i < headers.length; i++) { + Cell c = header.createCell(i); + c.setCellValue(headers[i]); + } + + for (Contestant cst : list) { + Row r = sheet.createRow(rowIdx++); + int ci = 0; + r.createCell(ci++).setCellValue(cst.getContestantNumber() == null ? "" : cst.getContestantNumber().toString()); + r.createCell(ci++).setCellValue(cst.getEmail() == null ? "" : cst.getEmail()); + r.createCell(ci++).setCellValue(cst.getFirstName() == null ? "" : cst.getFirstName()); + r.createCell(ci++).setCellValue(cst.getLastName() == null ? "" : cst.getLastName()); + r.createCell(ci++).setCellValue(cst.getGender() == null ? "" : cst.getGender()); + r.createCell(ci++).setCellValue(cst.getOccupation() == null ? "" : cst.getOccupation()); + r.createCell(ci++).setCellValue(cst.getAge() == null ? "" : cst.getAge().toString()); + r.createCell(ci++).setCellValue(cst.getCountryRegionCity() == null ? "" : cst.getCountryRegionCity()); + 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.getVideoDuration() == null ? "" : cst.getVideoDuration().toString()); + // 视频大小、PDF 大小:以 MB 导出,保留两位小数 + if (cst.getVideoSize() == null) { + r.createCell(ci++).setCellValue(""); + } else { + double vMb = cst.getVideoSize() / 1024.0 / 1024.0; + r.createCell(ci++).setCellValue(String.format("%.2f", vMb)); + } + if (cst.getPdfSize() == null) { + r.createCell(ci++).setCellValue(""); + } else { + double pMb = cst.getPdfSize() / 1024.0 / 1024.0; + r.createCell(ci++).setCellValue(String.format("%.2f", pMb)); + } + r.createCell(ci++).setCellValue(cst.getCreatedAt() == null ? "" : cst.getCreatedAt().toString()); + r.createCell(ci++).setCellValue(cst.getUpdatedAt() == null ? "" : cst.getUpdatedAt().toString()); + } + + workbook.write(out); + out.flush(); + return out.toByteArray(); + } catch (IOException e) { + log.error("export contestants failed", e); + throw e; + } + } + + @Override + public void saveContestantsToLocal() throws Exception { + List list = contestantMapper.selectList(new QueryWrapper<>()); + DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); + String ts = LocalDateTime.now().format(fmt); + Path exportDir = Paths.get(uploadDir == null ? "uploads" : uploadDir, "exports"); + Files.createDirectories(exportDir); + Path outPath = exportDir.resolve("contestants_" + ts + ".xlsx"); + + try (XSSFWorkbook workbook = new XSSFWorkbook(); FileOutputStream fos = new FileOutputStream(outPath.toFile())) { + Sheet sheet = workbook.createSheet("contestants"); + int rowIdx = 0; + Row header = sheet.createRow(rowIdx++); + String[] headers = new String[] { + "contestantNumber", "email", "firstName", "lastName", "gender", "occupation", + "age", "countryRegionCity", "phoneNumber", "designTitle", "designDescription", + "pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "createdAt", "updatedAt" + }; + for (int i = 0; i < headers.length; i++) { + Cell c = header.createCell(i); + c.setCellValue(headers[i]); + } + + for (Contestant cst : list) { + Row r = sheet.createRow(rowIdx++); + int ci = 0; + r.createCell(ci++).setCellValue(cst.getContestantNumber() == null ? "" : cst.getContestantNumber().toString()); + r.createCell(ci++).setCellValue(cst.getEmail() == null ? "" : cst.getEmail()); + r.createCell(ci++).setCellValue(cst.getFirstName() == null ? "" : cst.getFirstName()); + r.createCell(ci++).setCellValue(cst.getLastName() == null ? "" : cst.getLastName()); + r.createCell(ci++).setCellValue(cst.getGender() == null ? "" : cst.getGender()); + r.createCell(ci++).setCellValue(cst.getOccupation() == null ? "" : cst.getOccupation()); + r.createCell(ci++).setCellValue(cst.getAge() == null ? "" : cst.getAge().toString()); + r.createCell(ci++).setCellValue(cst.getCountryRegionCity() == null ? "" : cst.getCountryRegionCity()); + 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.getVideoDuration() == null ? "" : cst.getVideoDuration().toString()); + // 视频大小、PDF 大小:以 MB 导出,保留两位小数 + if (cst.getVideoSize() == null) { + r.createCell(ci++).setCellValue(""); + } else { + double vMb = cst.getVideoSize() / 1024.0 / 1024.0; + r.createCell(ci++).setCellValue(String.format("%.2f", vMb)); + } + if (cst.getPdfSize() == null) { + r.createCell(ci++).setCellValue(""); + } else { + double pMb = cst.getPdfSize() / 1024.0 / 1024.0; + r.createCell(ci++).setCellValue(String.format("%.2f", pMb)); + } + r.createCell(ci++).setCellValue(cst.getCreatedAt() == null ? "" : cst.getCreatedAt().toString()); + r.createCell(ci++).setCellValue(cst.getUpdatedAt() == null ? "" : cst.getUpdatedAt().toString()); + } + + workbook.write(fos); + fos.flush(); + log.info("Exported contestants to local file: {}", outPath.toString()); + } catch (IOException e) { + log.error("save contestants to local failed", e); + throw e; + } + } + @Override public ContestantDTO getContestantByID(String id) { if (id == null) { diff --git a/src/main/java/com/ai/da/service/impl/LibraryModelPointServiceImpl.java b/src/main/java/com/ai/da/service/impl/LibraryModelPointServiceImpl.java index 2966be9e..863d1cfe 100644 --- a/src/main/java/com/ai/da/service/impl/LibraryModelPointServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/LibraryModelPointServiceImpl.java @@ -28,6 +28,7 @@ import io.netty.util.internal.StringUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -53,7 +54,11 @@ public class LibraryModelPointServiceImpl extends ServiceImpl imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(url); libModel.setWidth(imagesWidthAndHeight.get(0)); libModel.setHigh(imagesWidthAndHeight.get(1)); @@ -104,25 +110,10 @@ public class LibraryModelPointServiceImpl extends ServiceImpl imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(url); - saveAsModel.setWidth(imagesWidthAndHeight.get(0)); - saveAsModel.setHigh(imagesWidthAndHeight.get(1)); - saveAsModel.setCreateDate(DateUtil.getByTimeZone(libraryModelPointDTO.getTimeZone())); - libraryService.save(saveAsModel); - // 更新新的模特在library中的id,用于后面新建模特点位信息用 - libraryModelPointDTO.setLibraryId(saveAsModel.getId()); + Library saveAsModel = createNewLibraryCopy(libModel, libraryModelPointDTO); // 新增模特点位信息 + libraryModelPointDTO.setLibraryId(saveAsModel.getId()); // 更新libraryId为新创建的模型ID LibraryModelPoint libraryModelPoint = resolvePoint(libraryModelPointDTO); libraryModelPoint.setModelType("Library"); libraryModelPoint.setCreateDate(DateUtil.getByTimeZone(libraryModelPointDTO.getTimeZone())); @@ -130,22 +121,50 @@ public class LibraryModelPointServiceImpl extends ServiceImpl imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(libraryModelPointDTO.getModelPath()); + saveAsModel.setWidth(imagesWidthAndHeight.get(0)); + saveAsModel.setHigh(imagesWidthAndHeight.get(1)); + saveAsModel.setCreateDate(DateUtil.getByTimeZone(libraryModelPointDTO.getTimeZone())); + libraryService.save(saveAsModel); + return saveAsModel; + } + @Override public LibraryModelPointVO saveOrEditTemplatePointOld(LibraryModelPointDTO libraryModelPointDTO) { // Library library = libraryService.getById(libraryModelPointDTO.getLibraryId()); diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 3214b137..da4dfdcb 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -125,9 +125,9 @@ orderList.link=https://develop.aida.com.hk/home/homePage?order= # 0 不发送邮件通知 1 发送邮件通知 stripe.webhook.fail.reminder=0 # kim test -stripe.paymentMethodConfiguration=pmc_1LywTWH7nPZ8bkrN6FvdCUWG +#stripe.paymentMethodConfiguration=pmc_1LywTWH7nPZ8bkrN6FvdCUWG # developer test -#stripe.paymentMethodConfiguration=pmc_1QIKyq02n1TEydyNKVEYvhW7 +stripe.paymentMethodConfiguration=pmc_1QIKyq02n1TEydyNKVEYvhW7 #thymelea模板配置 #控制 Thymeleaf 是否启用模板缓存 生产环境用true,以提高性能 spring.thymeleaf.cache=false diff --git a/src/main/resources/mapper/primary/WorkspaceRelStyleMapper.xml b/src/main/resources/mapper/primary/WorkspaceRelStyleMapper.xml index e65cd83b..03fbe225 100644 --- a/src/main/resources/mapper/primary/WorkspaceRelStyleMapper.xml +++ b/src/main/resources/mapper/primary/WorkspaceRelStyleMapper.xml @@ -17,3 +17,4 @@ +