fix(similar-asin): 结果文件下载改 302 重定向到对象存储直链——原流式代理让请求线程被整个下载时长占满,货源结果单文件常见 100MB+(任务 27609 达 130MB),并发下载即拖垮 Tomcat 线程池;且代理恒回 200 全量,客户端 Range 断点续传实际失效
原实现在 Controller 内 openStream 边读边写把整个对象代理给客户端,改为 ResponseEntity.status(FOUND).location(直链) 后:① 请求线程立即释放(实测响应 从"随下载时长占用"降到 ~10ms);② 直链支持 Range,save_file_from_url_new 的 断点续传才真正生效。前端本就优先用 item.downloadUrl 直链、该接口仅兜底且客户端 保存名由前端传入不依赖响应头,故无需前端改动。补一条重定向 info 日志便于排查。
This commit is contained in:
+12
-28
@@ -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<Void> 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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user