support RequestRange for system file download

This commit is contained in:
Timi
2025-07-15 11:44:57 +08:00
parent 329b6cb23d
commit 2de29cb3a8
2 changed files with 28 additions and 8 deletions

1
.idea/.gitignore generated vendored
View File

@ -1,3 +1,4 @@
# Default ignored files # Default ignored files
/shelf/ /shelf/
/workspace.xml /workspace.xml
CopilotChatHistory.xml

View File

@ -23,12 +23,15 @@ import com.imyeyu.server.modules.system.task.async.FileUnZipAsyncTask;
import com.imyeyu.server.modules.system.task.async.FileZipAsyncTask; import com.imyeyu.server.modules.system.task.async.FileZipAsyncTask;
import com.imyeyu.server.modules.system.util.ResourceHandler; import com.imyeyu.server.modules.system.util.ResourceHandler;
import com.imyeyu.server.modules.system.vo.ListFileToRequest; import com.imyeyu.server.modules.system.vo.ListFileToRequest;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.IgnoreGlobalReturn; import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.RequestRange;
import com.imyeyu.utils.OS; import com.imyeyu.utils.OS;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails; import net.coobird.thumbnailator.Thumbnails;
@ -47,6 +50,7 @@ import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -195,6 +199,7 @@ public class FileController implements TimiJava, OS.FileSystem {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return; return;
} }
resp.setContentLengthLong(Files.size(filePath));
String mimeType = new Tika().detect(filePath); String mimeType = new Tika().detect(filePath);
if (TimiJava.isNotEmpty(mimeType)) { if (TimiJava.isNotEmpty(mimeType)) {
resp.setContentType(mimeType); resp.setContentType(mimeType);
@ -452,27 +457,41 @@ public class FileController implements TimiJava, OS.FileSystem {
/** /**
* 下载文件 * 下载文件
* *
* @param req 请求对象 * @param resp 返回对象
* @param resp 回调对象
*/ */
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@IgnoreGlobalReturn @IgnoreGlobalReturn
@RequestMapping("/download/**") @RequestMapping("/download/**")
public void download(HttpServletRequest req, HttpServletResponse resp) { public void download(HttpServletResponse resp) {
try { try {
String path = req.getServletPath().substring("/system/file/download".length()); String path = TimiSpring.cutURIStartAt("/system/file/download");
path = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE) + path; path = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE) + path;
File file = new File(path); File file = new File(path);
service.checkAccessPermission(file.getAbsolutePath()); service.checkAccessPermission(file.getAbsolutePath());
String mimeType = new Tika().detect(file); String mimeType = new Tika().detect(file);
resp.setContentType(mimeType); resp.setContentType(mimeType);
resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(file.getName())); resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(file.getName()));
resp.setHeader("Content-Range", String.valueOf(file.length() - 1));
resp.setHeader("Accept-Ranges", "bytes"); resp.setHeader("Accept-Ranges", "bytes");
RequestRange range = TimiSpring.requestRange(file.length());
if (range == null) {
// 完整文件
resp.setContentLengthLong(file.length()); resp.setContentLengthLong(file.length());
resp.setStatus(HttpServletResponse.SC_OK);
IO.toOutputStream(resp.getOutputStream(), file); IO.toOutputStream(resp.getOutputStream(), file);
resp.flushBuffer(); } else {
// 分片文件
resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
resp.setHeader("Content-Range", "bytes %s-%s/%s".formatted(range.getStart(), range.getEnd(), file.length()));
resp.setContentLengthLong(range.getLength());
@Cleanup
RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(range.getStart());
IO.toOutputStream(resp.getOutputStream(), raf, range.getStart(), range.getLength());
}
} catch (Exception e) { } catch (Exception e) {
log.error("download error", e); log.error("download error", e);
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);