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..1e8eb00b 100644 --- a/src/main/java/com/ai/da/controller/GlobalAwardController.java +++ b/src/main/java/com/ai/da/controller/GlobalAwardController.java @@ -163,6 +163,14 @@ public class GlobalAwardController { return Response.success(globalAwardService.checkCode(email, code)); } + @GetMapping("/contestants/export") + @ApiOperation(value = "导出参赛者列表为Excel", notes = "导出所有参赛者信息为xlsx并触发下载") + public Response exportContestants() throws Exception { + // 将文件保存到服务端本地目录(uploadDir/exports),不返回文件内容给客户端 + globalAwardService.saveContestantsToLocal(); + return Response.success(); + } + } 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/GlobalAwardServiceImpl.java b/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java index 8fbc5e38..d29d4d9d 100644 --- a/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import org.springframework.transaction.annotation.Transactional; import jakarta.annotation.Resource; @@ -29,6 +30,20 @@ import java.io.IOException; 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 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 @@ -128,6 +143,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { } @Override + @Transactional(rollbackFor = Exception.class) public Map saveContestant(ContestantDTO request) { Map resp = new HashMap<>(); if (request.getEmail() == null) { @@ -142,7 +158,14 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { LocalDateTime now = LocalDateTime.now(); if (existing == null) { + // 生成唯一的参赛选手编号(从10000开始),使用数据库行锁保证并发安全与原子性 + QueryWrapper numQw = new QueryWrapper<>(); + numQw.isNotNull("contestant_number").orderByDesc("contestant_number").last("FOR UPDATE"); + Contestant maxRow = contestantMapper.selectOne(numQw); + Integer nextContestantNumber = (maxRow == null || maxRow.getContestantNumber() == null) ? 10000 : maxRow.getContestantNumber() + 1; + Contestant toInsert = Contestant.builder() + .contestantNumber(nextContestantNumber) .email(request.getEmail()) .firstName(request.getFirstName()) .lastName(request.getLastName()) @@ -160,6 +183,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { .build(); contestantMapper.insert(toInsert); resp.put("success", true); + resp.put("contestant_number", nextContestantNumber); sendSiteMsg(toInsert.getId(), toInsert.getEmail()); return resp; } else { @@ -178,10 +202,109 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { 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", "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.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", "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.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/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 @@ +