功能说明
已经下载一部分,网络断开等原因导致没有下载完,问题消失后还可以继续下载,不用从头开始下载
实现
说明,前端要传过来文件总大小和已经下载的大小,不同的文件服务器的断点下载的api不同;返回给前端的时候根据是否是断点下载返回不同的Content-Length和Content-Range即可。
@ApiOperation(value = "断点下载",notes = "断点下载需要传请求头Size,Range")
@GetMapping(path ="/keepOnDownload", name = "断点下载")
public void keepOnDownload(String fileName, @RequestHeader HttpHeaders headers , HttpServletResponse response) {
fileService.keepOnDownload(fileName,headers,response);
}
public void keepOnDownload(String fileName, HttpHeaders headers, HttpServletResponse response) {
log.info(JSONUtil.toJsonStr(headers));
List<String> fileSize = headers.get("Size");//文件总大小
// List<String> offset = headers.get("Offset");//已下载大小
List<String> ranges = headers.get("Range");//已下载大小
String offsetStr =null;
String rangeStr =null;
if(CollectionUtil.isEmpty(fileSize)){
throw new GlobalDefaultException(ResponseCode.METHOD_ARGUMENT_NOT_VALID.getCode(),"请求头参数异常");
}
String contentLength = fileSize.get(0);
String end=null;
if(CollectionUtil.isNotEmpty(ranges)){
rangeStr=ranges.get(0);
offsetStr=rangeStr;
String[] split = offsetStr.split("-");
if(split.length>1){
end =split[1];
}
offsetStr = rangeStr.replace("bytes=", "");
offsetStr = offsetStr.substring(0, offsetStr.indexOf('-'));
}
log.info("Size:{},Offset:{}",contentLength,offsetStr);
Long range =StrUtil.isBlank(offsetStr)?null:Long.parseLong(offsetStr);
Long rangeEnd=StrUtil.isBlank(end)?Long.parseLong(contentLength)-1:Long.parseLong(end);
InputStream fis=null;
if(range==null || range <= 0){
if("bytes=0-0".equals(rangeStr) ){
response.setHeader("Accept-Ranges","bytes");
response.setHeader("Content-Length", "0" );
response.setHeader("Content-Range", "bytes 0-0/"+contentLength );
fis=getFileStreamKeepOn(fileName,0L,0L);
}else{//0-获取所有数据
response.setHeader("Accept-Ranges","bytes");
response.setHeader("Content-Length", rangeEnd+1+"");
response.setHeader("Content-Range", "bytes 0-"+(rangeEnd)+"/"+contentLength );
fis=getFileStreamKeepOn(fileName,range,rangeEnd);
}
}else{
response.setHeader("Accept-Ranges","bytes");
response.setHeader("Content-Length", rangeEnd-Long.parseLong(offsetStr)+1+"" );
response.setHeader("Content-Range", "bytes "+range+"-"+rangeEnd+"/"+contentLength );
fis=getFileStreamKeepOn(fileName,range,rangeEnd);
}
try {
// 文件名可以任意指定
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName,"UTF-8"));
response.setContentType(OssFileUtil.getContentType(fileName));
if(fis!=null){
int size = FileUtil.copy(fis, response.getOutputStream());
log.info("下载大小:{}",size);
}else{
log.info("下载大小:{}",0);
}
} catch (Exception e) {
log.error("下载出错了:{}",e.getMessage());
}
}
public InputStream getFileStreamKeepOn(String fileName, Long begin,Long end) {
InputStream input = null;
if("minio".equals(uploadType)){
input = minioFileService.getInputStream(fileName,begin,end);
}else if("oss".equals(uploadType)){
input = ossFileService.getInputStream(fileName,begin,end);
}else if("s3oss".equals(uploadType)){
input = s3OssFileUtil.getInputStream(fileName,begin,end);
}
return input;
}
minioFileService
public InputStream getInputStream(String fileName, Long begin,Long end) {
return minioFileUtil.getInputStream(fileName,begin,end-begin+1);
}
public InputStream getInputStream(String fileName,Long offset,Long length) {
String bucketName ="bucketName";
fileName = "file/"+fileName;
try {
return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).offset(offset).length(length).build());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
ossFileServie
public InputStream getInputStream(String fileName,Long begin,Long end){
String bucketName = "bucketName";
String key = "file/" + fileName;
GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, key );
Date expiration = new Date(new Date().getTime() + 3600 * 1000);
// 设置过期时间。
req.setExpiration(expiration);
URL signedUrl = ossClient.generatePresignedUrl(req);
Map<String,String> customHeaders = new HashMap<>();
// + end
String strEnd=end==null?"":end+"";
customHeaders.put("Range","bytes="+begin+"-"+strEnd);
OSSObject object = ossClient.getObject(signedUrl, customHeaders);
return object.getObjectContent();
}
s3ossService
public InputStream getInputStream(String fileName,Long begin,Long end){
String bucketName = s3OSSConfig.getBucketName();
String key = s3OSSConfig.getFolder() + fileName;
try {
GetObjectRequest getObjectRequest=new GetObjectRequest(bucketName,key);
getObjectRequest.withRange(begin,end);
return s3Client.getObject(getObjectRequest).getObjectContent();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Description: 判断OSS服务文件上传时文件的contentType
* @param fileName 文件名
* @return String
*/
public static String getContentType(String fileName) {
// 文件的后缀名
String fileExtension = fileName.substring(fileName.lastIndexOf("."));
if (".bmp".equalsIgnoreCase(fileExtension)) {
return "image/bmp";
}else if (".gif".equalsIgnoreCase(fileExtension)) {
return "image/gif";
}else if (".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension)
|| ".png".equalsIgnoreCase(fileExtension)) {
return "image/jpg";
}
// else if (".png".equalsIgnoreCase(fileExtension)) {
// return "image/png";
// }
else if (".html".equalsIgnoreCase(fileExtension)) {
return "text/html";
}else if (".txt".equalsIgnoreCase(fileExtension)) {
return "text/plain";
}else if (".vsd".equalsIgnoreCase(fileExtension)) {
return "application/vnd.visio";
}else if (".ppt".equalsIgnoreCase(fileExtension) || ".pptx".equalsIgnoreCase(fileExtension)) {
return "application/vnd.ms-powerpoint";
}else if (".doc".equalsIgnoreCase(fileExtension) || ".docx".equalsIgnoreCase(fileExtension)) {
return "application/msword";
}else if (".xml".equalsIgnoreCase(fileExtension)) {
return "text/xml";
}else if (".pdf".equalsIgnoreCase(fileExtension)) {
return "application/pdf";
}else if(".apk".equalsIgnoreCase(fileExtension)){
return "application/vnd.android.package-archive";
}else if(".xlsx".equalsIgnoreCase(fileExtension)){
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
}
// 默认返回类型
return "image/jpg";
}
/**
* byte 转file
*/
public static File byte2File(byte[] buf, File file) throws IOException {
BufferedOutputStream bos = null;
FileOutputStream fos = null;
try{
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(buf);
}finally{
if (bos != null)
bos.close();
if (fos != null)
fos.close();
}
return file;
}
public static int copy(InputStream in, OutputStream out) throws Exception {
Assert.notNull(in, "No InputStream specified");
Assert.notNull(out, "No OutputStream specified");
int byteCount = 0;
try {
//大多数文件系统使用4096或8192的块大小。
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {//将输入流数据读取到byte[]中
out.write(buffer, 0, bytesRead);//将byte[]中数据写入到输出流中
byteCount += bytesRead;
}
out.flush();//当输出流是BufferedOutputStream时才有效,用于主动触发写入磁盘,而不必等待byte[]满了以后
return byteCount;
}catch (Exception e){
log.error("下载过程中出错,已下载大小:{}",byteCount,e);
throw e;
}
finally {
try {
in.close();
}catch (Exception e){
log.error("关闭输入流出错了",e.getMessage());
// e.printStackTrace();
}
try{
out.close();
}catch (Exception e){
log.error("关闭输出流出错了",e.getMessage());
// e.printStackTrace();
}
}
}