GlobalAward下载到浏览器
This commit is contained in:
@@ -176,10 +176,19 @@ public class GlobalAwardController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/contestants/export/files")
|
@PostMapping("/contestants/export/files")
|
||||||
@ApiOperation(value = "导出参赛者文件到本地", notes = "根据参赛者编号范围导出PDF和视频文件到本地temp/uploads/contestants目录")
|
@ApiOperation(value = "导出参赛者文件为ZIP", notes = "根据参赛者编号范围导出PDF、视频和信息文件为ZIP,直接响应给浏览器")
|
||||||
public Response<Integer> exportContestantFiles(@ApiParam(value = "参赛者文件导出请求", required = true) @RequestBody ContestantExportRequest request) throws Exception {
|
public void exportContestantFiles(@ApiParam(value = "参赛者文件导出请求", required = true) @RequestBody ContestantExportRequest request, HttpServletResponse response) throws Exception {
|
||||||
int exportedCount = globalAwardService.exportContestantFiles(request.getMinContestantNumber(), request.getMaxContestantNumber());
|
byte[] zipData = globalAwardService.exportContestantFilesAsZip(request.getMinContestantNumber(), request.getMaxContestantNumber());
|
||||||
return Response.success(exportedCount);
|
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")
|
@GetMapping("/contestants/count")
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ public interface GlobalAwardService {
|
|||||||
void saveContestantsToLocal() throws Exception;
|
void saveContestantsToLocal() throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据参赛者编号范围导出参赛者文件到本地目录
|
* 将参赛者文件打包为 ZIP 并返回字节数组(不落盘,直接响应给浏览器)
|
||||||
* @param minContestantNumber 最小参赛者编号
|
* @param minContestantNumber 最小参赛者编号
|
||||||
* @param maxContestantNumber 最大参赛者编号
|
* @param maxContestantNumber 最大参赛者编号
|
||||||
* @return 导出的参赛者数量
|
* @return ZIP 文件的字节数组
|
||||||
*/
|
*/
|
||||||
int exportContestantFiles(Integer minContestantNumber, Integer maxContestantNumber) throws Exception;
|
byte[] exportContestantFilesAsZip(Integer minContestantNumber, Integer maxContestantNumber) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询参赛者总数
|
* 查询参赛者总数
|
||||||
|
|||||||
@@ -26,24 +26,22 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
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.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.util.zip.ZipEntry;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
import org.apache.poi.ss.usermodel.Row;
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
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
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -476,7 +474,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int exportContestantFiles(Integer minContestantNumber, Integer maxContestantNumber) throws Exception {
|
public byte[] exportContestantFilesAsZip(Integer minContestantNumber, Integer maxContestantNumber) throws Exception {
|
||||||
if (minContestantNumber == null || maxContestantNumber == null) {
|
if (minContestantNumber == null || maxContestantNumber == null) {
|
||||||
throw new BusinessException("minContestantNumber and maxContestantNumber are required.");
|
throw new BusinessException("minContestantNumber and maxContestantNumber are required.");
|
||||||
}
|
}
|
||||||
@@ -484,7 +482,7 @@ public class GlobalAwardServiceImpl implements GlobalAwardService {
|
|||||||
throw new BusinessException("minContestantNumber cannot be greater than maxContestantNumber.");
|
throw new BusinessException("minContestantNumber cannot be greater than maxContestantNumber.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 根据contestantNumber范围查询参赛者
|
// 1. 根据 contestantNumber 范围查询参赛者
|
||||||
QueryWrapper<Contestant> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<Contestant> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.lambda()
|
queryWrapper.lambda()
|
||||||
.ge(Contestant::getContestantNumber, minContestantNumber)
|
.ge(Contestant::getContestantNumber, minContestantNumber)
|
||||||
@@ -494,59 +492,37 @@ public class GlobalAwardServiceImpl implements GlobalAwardService {
|
|||||||
|
|
||||||
if (contestants.isEmpty()) {
|
if (contestants.isEmpty()) {
|
||||||
log.info("No contestants found in range [{}, {}]", minContestantNumber, maxContestantNumber);
|
log.info("No contestants found in range [{}, {}]", minContestantNumber, maxContestantNumber);
|
||||||
return 0;
|
return new byte[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 创建基础目录
|
// 2. 在内存中构建 ZIP
|
||||||
String baseDir = uploadDir + "/contestants";
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
Path basePath = Paths.get(baseDir).toAbsolutePath();
|
java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(baos)) {
|
||||||
Files.createDirectories(basePath);
|
|
||||||
log.info("Base directory created: {}", basePath);
|
|
||||||
|
|
||||||
int exportedCount = 0;
|
for (Contestant contestant : contestants) {
|
||||||
|
Integer contestantNumber = contestant.getContestantNumber();
|
||||||
// 3. 遍历每个参赛者,下载文件
|
if (contestantNumber == null) {
|
||||||
for (Contestant contestant : contestants) {
|
log.warn("Contestant {} has no contestantNumber, skipping", contestant.getId());
|
||||||
Integer contestantNumber = contestant.getContestantNumber();
|
continue;
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 下载视频文件
|
String dirPrefix = contestantNumber + "/";
|
||||||
String videoPath = contestant.getVideoPath();
|
|
||||||
if (StringUtils.isNotBlank(videoPath)) {
|
// 添加 PDF 文件
|
||||||
try {
|
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("/") ?
|
String fileName = videoPath.contains("/") ?
|
||||||
videoPath.substring(videoPath.lastIndexOf("/") + 1) : "video.mp4";
|
videoPath.substring(videoPath.lastIndexOf("/") + 1) : "video.mp4";
|
||||||
downloadFileFromMinio(videoPath, contestantPath.toString(), fileName);
|
addMinioFileToZip(zos, videoPath, dirPrefix + fileName);
|
||||||
log.info("Downloaded video for contestant {}", contestantNumber);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to download video for contestant {}: {}", contestantNumber, e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 生成参赛者信息 txt 文件
|
// 添加参赛者信息 txt 文件
|
||||||
try {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("=== Contestant Information ===\n\n");
|
sb.append("=== Contestant Information ===\n\n");
|
||||||
sb.append("ID: ").append(nullSafe(contestant.getId())).append("\n");
|
sb.append("ID: ").append(nullSafe(contestant.getId())).append("\n");
|
||||||
@@ -561,54 +537,58 @@ public class GlobalAwardServiceImpl implements GlobalAwardService {
|
|||||||
sb.append("Phone Number: ").append(nullSafe(contestant.getPhoneNumber())).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 Title: ").append(nullSafe(contestant.getDesignTitle())).append("\n");
|
||||||
sb.append("Design Description: ").append(nullSafe(contestant.getDesignDescription())).append("\n");
|
sb.append("Design Description: ").append(nullSafe(contestant.getDesignDescription())).append("\n");
|
||||||
sb.append("PDF Path: ").append(nullSafe(contestant.getPdfPath())).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("PDF Size (bytes): ").append(contestant.getPdfSize() != null ? contestant.getPdfSize() : "N/A").append("\n");
|
||||||
sb.append("Video Path: ").append(nullSafe(contestant.getVideoPath())).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 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("Video Size (bytes): ").append(contestant.getVideoSize() != null ? contestant.getVideoSize() : "N/A").append("\n");
|
||||||
sb.append("Created At: ").append(contestant.getCreatedAt() != null ? contestant.getCreatedAt() : "N/A").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");
|
sb.append("Updated At: ").append(contestant.getUpdatedAt() != null ? contestant.getUpdatedAt() : "N/A").append("\n");
|
||||||
|
|
||||||
String infoFilePath = contestantPath.resolve("contestant_info.txt").toString();
|
ZipEntry infoEntry = new ZipEntry(dirPrefix + "contestant_info.txt");
|
||||||
Files.write(Paths.get(infoFilePath), sb.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
zos.putNextEntry(infoEntry);
|
||||||
log.info("Generated info file for contestant {}", contestantNumber);
|
zos.write(sb.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||||
} catch (Exception e) {
|
zos.closeEntry();
|
||||||
log.error("Failed to generate info file for contestant {}: {}", contestantNumber, e.getMessage());
|
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下载文件到本地
|
* 将 MinIO 文件流式写入 ZIP,不落盘
|
||||||
* @param minioPath MinIO路径 (格式: bucketName/objectPath)
|
* @param zos ZIP 输出流
|
||||||
* @param localDir 本地目录
|
* @param minioPath MinIO 路径(格式: bucketName/objectPath)
|
||||||
* @param fileName 本地文件名
|
* @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)) {
|
if (StringUtils.isBlank(minioPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从路径中提取bucket名称和对象名称
|
|
||||||
int index = minioPath.indexOf("/");
|
int index = minioPath.indexOf("/");
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
log.warn("Invalid MinIO path: {}", minioPath);
|
log.warn("Invalid MinIO path: {}", minioPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String bucketName = minioPath.substring(0, index);
|
String bucketName = minioPath.substring(0, index);
|
||||||
String objectName = minioPath.substring(index + 1);
|
String objectName = minioPath.substring(index + 1);
|
||||||
|
|
||||||
// 构建本地文件完整路径
|
try (InputStream in = minioUtil.download(bucketName, objectName)) {
|
||||||
Path localFilePath = Paths.get(localDir, fileName);
|
ZipEntry entry = new ZipEntry(entryName);
|
||||||
|
zos.putNextEntry(entry);
|
||||||
// 下载文件
|
byte[] buffer = new byte[8192];
|
||||||
minioUtil.downloadMinioObjectToLocal(bucketName, objectName, localFilePath.toString());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user