diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/similarasin/controller/SimilarAsinController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/similarasin/controller/SimilarAsinController.java index 824e74db..e6ed6172 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/similarasin/controller/SimilarAsinController.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/similarasin/controller/SimilarAsinController.java @@ -1,7 +1,6 @@ package com.nanri.aiimage.modules.similarasin.controller; import com.nanri.aiimage.common.api.ApiResponse; -import com.nanri.aiimage.common.util.DownloadHeaderUtil; import com.nanri.aiimage.modules.similarasin.model.dto.SimilarAsinFilterConditionAddRequest; import com.nanri.aiimage.modules.similarasin.model.dto.SimilarAsinParseRequest; import com.nanri.aiimage.modules.similarasin.model.dto.SimilarAsinParsedPayloadDto; @@ -23,6 +22,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -33,7 +33,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; -import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.List; @@ -181,38 +180,23 @@ public class SimilarAsinController { @GetMapping("/results/{resultId}/download") @Operation(summary = "下载相似 ASIN 检测结果文件") - public void downloadResult( + public ResponseEntity downloadResult( @Parameter(description = "结果记录 ID", required = true, example = "1001") @PathVariable Long resultId, @Parameter(description = "当前用户 ID", required = true, example = "1") - @RequestParam("user_id") Long userId, - jakarta.servlet.http.HttpServletResponse response) { + @RequestParam("user_id") Long userId) { ResultDownloadInfo download = service.resolveResultDownloadInfo(resultId, userId); if (download.url() == null || download.url().isBlank()) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "暂无可下载结果"); } - try { - response.setContentType(download.contentType()); - if (download.size() > 0) { - response.setContentLengthLong(download.size()); - } - DownloadHeaderUtil.setAttachment(response, download.filename()); - long written = 0L; - try (InputStream in = URI.create(download.url()).toURL().openStream()) { - byte[] buffer = new byte[65536]; - int read; - while ((read = in.read(buffer)) != -1) { - response.getOutputStream().write(buffer, 0, read); - written += read; - } - response.getOutputStream().flush(); - } - if (download.size() > 0 && written != download.size()) { - log.warn("[相似ASIN] 下载文件大小不一致 结果ID={} 预期字节数={} 实际写出字节数={} 文件名={}", - resultId, download.size(), written, download.filename()); - } - } catch (Exception ex) { - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "下载失败"); - } + // 结果文件常见百 MB 级(货源结果含图片,单文件可达 130MB+): + // 原先在此流式代理整个对象,请求线程会被下载时长占满,并发时拖垮线程池。 + // 改为 302 重定向到对象存储直链:① 立即释放 Tomcat 线程; + // ② 直链支持 Range,客户端断点续传才真正生效(代理只回 200 全量,续传实际失效)。 + log.info("[相似ASIN] 结果文件下载重定向 结果ID={} 用户ID={} 文件名={} 字节数={}", + resultId, userId, download.filename(), download.size()); + return ResponseEntity.status(HttpStatus.FOUND) + .location(URI.create(download.url())) + .build(); } }