上节我们说了Minio怎么大文件上传,我们是进行了分段上传,然后合并处理,感兴趣的可以去这篇文章,
那么今天的主题就是大文件下载,再大文件就需要分段下载,也就需要前端给下载的范围,就是下面的range的参数,我们为了好测试将此字段放入了参数了,实际你可以放入header头部。
下载的Controller类:
@Slf4j
@RestController
public class DownloadController {
@Resource
private IDownloadProcess downloadProcess;
// http://localhost:8082/download?filename=a9500aa2091875f3d02a9b84ae1ab712.mp4&range=bytes=0-52428800
// 分段下载的化,支持断点下载,暂停下载,断网恢复下载等。
// 我测试就采取这种方式RequestParam,大家真实场景可以放到header里 @RequestHeader(name = "Range", required = false) String range,
@GetMapping("/download")
public ResponseEntity downloadFile(@RequestParam String filename,
@RequestParam(required = false) String range,
HttpServletRequest request, HttpServletResponse response) {
try {
return downloadProcess.downloadFile(filename, range, request, response);
} catch (Exception e) {
log.error("下载异常|参数:{},{}|{}", filename, range, e);
return new ResponseEntity<byte[]>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
IDownloadProcess:定义下载接口
public interface IDownloadProcess {
ResponseEntity downloadFile(String filename, String range, HttpServletRequest request, HttpServletResponse response) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException, Exception;
}
DownloadProcessImpl:下载实现类,
我们可以传range参数来处理要下载的kb数范围,当然也可以不传递就是下载全部,
1.首先就是获取桶里文件信息,文件大小什么的都能获取
2.查看是范围下载还是全部下载
3.设置响应下载的类型和请求头
4.获取minio的流文件
5.将流文件遍历读取放入缓冲中
6.然后写入到OutputStream流中,然后刷新就可以啦。
@Slf4j
@Service
public class DownloadProcessImpl implements IDownloadProcess {
@Resource
private MinioClient minioClient;
@Resource
private MinioConfig minioConfig;
// 完整文件与分片文件下载
@Override
public ResponseEntity downloadFile(String filename, String range, HttpServletRequest request, HttpServletResponse response) throws Exception {
ResponseEntity<byte[]> responseEntity = null;
BufferedOutputStream os = null;
GetObjectResponse stream = null;
if (StringUtils.isNotBlank(filename)) {
log.info("要下载的文件:{}", filename);
//String range = request.getHeader("Range");
log.info("current request rang:{}", range);
// 获取桶里文件信息
StatObjectResponse statObjectResponse = minioClient.statObject(
StatObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.object(filename)
.build());
//开始下载位置
long startByte = 0;
//结束下载位置
long endByte = statObjectResponse.size() - 1;
log.info("文件开始位置:{},文件结束位置:{},文件总长度:{}", startByte, endByte, statObjectResponse.size());
// 有range的话,需要根据前端下载长度进行下载,也就是分段下载
// 例如:range=bytes=0-52428800
if (StringUtils.isNotBlank(range) && range.contains("bytes=") && range.contains("-")) {
range = range.substring(range.lastIndexOf("=") + 1).trim();
String[] ranges = range.split("-");
//判断range的类型
if (ranges.length == 1) {
//类型一:bytes=-2343
if (range.startsWith("-")) endByte = Long.parseLong(ranges[0]);
//类型二:bytes=2343-
if (range.endsWith("-")) startByte = Long.parseLong(ranges[0]);
}
//类型三:bytes=22-2343
else if (ranges.length == 2) {
startByte = Long.parseLong(ranges[0]);
endByte = Long.parseLong(ranges[1]);
}
}
//要下载的长度
long contentLength = endByte - startByte + 1;
//文件类型
String contentType = request.getServletContext().getMimeType(filename);
//解决下载文件时文件名乱码问题
byte[] fileNameBytes = filename.getBytes(StandardCharsets.UTF_8);
filename = new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);
//各种响应头设置---------------------------------------------------------------------------------------------
//支持断点续传,获取部分字节内容:
response.setHeader("Accept-Ranges", "bytes");
//http状态码要为206:表示获取部分内容,SC_PARTIAL_CONTENT,部分浏览器不支持,所以改成SC_OK
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(contentType);
response.setHeader("Last-Modified", statObjectResponse.lastModified().toString());
//inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
response.setHeader("Content-Length", String.valueOf(contentLength));
//Content-Range,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]
response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + statObjectResponse.size());
response.setHeader("ETag", "\"".concat(statObjectResponse.etag()).concat("\""));
response.setContentType("application/octect-stream;charset=UTF-8");
try {
// 获取文件流
stream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(statObjectResponse.bucket())
.object(statObjectResponse.object())
.offset(startByte)
.length(contentLength)
.build());
os = new BufferedOutputStream(response.getOutputStream());
// 将读取的文件写入到OutputStream
byte[] buffer = new byte[1024];
long bytesWritten = 0;
int bytesRead = -1;
while ((bytesRead = stream.read(buffer)) != -1) {
if (bytesWritten + bytesRead > contentLength) {
os.write(buffer, 0, (int) (contentLength - bytesWritten));
break;
} else {
os.write(buffer, 0, bytesRead);
bytesWritten += bytesRead;
}
}
os.flush();
response.flushBuffer();
log.info("下载完毕");
// 返回对应http状态
responseEntity = new ResponseEntity<byte[]>(buffer, HttpStatus.OK);
} finally {
if (os != null) os.close();
if (stream != null) stream.close();
}
}
return responseEntity;
}
}
测试链接:
下50M的情况
http://localhost:8082/download?filename=a9500aa2091875f3d02a9b84ae1ab712.mp4&range=bytes=0-52428800
?从50m再次下载50m
http://localhost:8082/download?filename=a9500aa2091875f3d02a9b84ae1ab712.mp4&range=bytes=52428800-104857600
都下载到了前端本地以后由客户端进行合并操作就好了。