java 实现断点续传服务

java 实现断点续传服务

一:什么是断点续传

客户端软件断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载

(将文件分片以及后续合并是一个不小的工作量,由于项目时间有限,我并没有做分片,只是实现了可断点下载)

二:实现原理

2.1 实现思路

需要前端和后端的配合,前端在请求头中 标明 下载开始的位置,后端重标记位置开始向前端输出文件剩余部分。

在简单模式下,前端不需要知道文件大小,也不许要知道文件是否已经下载完毕。当文件可以正常打开时即文件下载完毕。(若想知道文件是否下载完毕,可写个接口比较Range 值与文件大小)

一般服务请求头

GET /down.zip HTTP/1.1 
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms- 
excel, application/msword, application/vnd.ms-powerpoint, */* 
Accept-Language: zh-cn 
Accept-Encoding: gzip, deflate 
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) 
Connection: Keep-Alive

响应头

200 
Content-Length=106786028 
Accept-Ranges=bytes 
Date=Mon, 30 Apr 2001 12:56:11 GMT 
ETag=W/"02ca57e173c11:95b" 
Content-Type=application/octet-stream 
Server=Microsoft-IIS/5.0 
Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT

如果要服务器支持断点续传功能的话,需要在请求头中表明文件开始下载的位置

请求头

GET /down.zip HTTP/1.0 
User-Agent: NetFox 
RANGE: bytes=2000070- #表示文件从2000070处开始下载
# Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

响应头

206 
Content-Length=106786028 
Content-Range=bytes 2000070-106786027/106786028 
Date=Mon, 30 Apr 2001 12:55:20 GMT 
ETag=W/"02ca57e173c11:95b" 
Content-Type=application/octet-stream 
Server=Microsoft-IIS/5.0 
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT

三:java代码实现

3.1 BreakPoinService类

import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.*;

@Service
public class BreakPoinService {
    //断点续传
    public void downLoadByBreakpoint(File file, long start, long end, HttpServletResponse response){
        OutputStream stream = null;
        RandomAccessFile fif = null;
        try {
            if (end <= 0) {
                end = file.length() - 1;
            }
            stream = response.getOutputStream();
            response.reset();
            response.setStatus(206);
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment; filename=" + file.getName());
            response.setHeader("Content-Length", String.valueOf(end - start + 1));
            response.setHeader("file-size", String.valueOf(file.length()));
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("Content-Range", String.format("bytes %s-%s/%s", start, end, file.length()));
            fif = new RandomAccessFile(file, "r");
            fif.seek(start);
            long index = start;
            int d;
            byte[] buf = new byte[10240];
            while (index <= end && (d = fif.read(buf)) != -1) {
                if (index + d > end) {
                    d = (int)(end - index + 1);
                }
                index += d;
                stream.write(buf, 0, d);
            }
            stream.flush();
        } catch (Exception e) {
            try {
                if (stream != null)
                    stream.close();
                if (fif != null)
                    fif.close();
            } catch (Exception e11) {
            }
        }
    }

    //全量下载
    public void downLoadAll(File file, HttpServletResponse response){
        OutputStream stream = null;
        BufferedInputStream fif = null;
        try {
            stream = response.getOutputStream();
            response.reset();
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment; filename=" + file.getName());
            response.setHeader("Content-Length", String.valueOf(file.length()));
            fif = new BufferedInputStream(new FileInputStream(file));
            int d;
            byte[] buf = new byte[10240];
            while ((d = fif.read(buf)) != -1) {
                stream.write(buf, 0, d);
            }
            stream.flush();
        } catch (Exception e) {
            try {
                if (stream != null)
                    stream.close();
                if (fif != null)
                    fif.close();
            } catch (Exception e11) {
            }
        }
    }
}

3.2 断点续传控制类

import cn.ztuo.api.cos.QCloudStorageService;
import cn.ztuo.api.service.IBreakpointResumeService;
import cn.ztuo.api.service.impl.BreakPoinService;
import cn.ztuo.commons.annotation.PassToken;
import cn.ztuo.commons.response.CommonResult;
import cn.ztuo.mbg.entity.BreakpointResume;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 断点续传控制类
 */
@RestController
@RequestMapping("/breakpoint")
public class BreakPointController {

    @Autowired
    private IBreakpointResumeService breakpointResumeService;
    @Autowired
    private BreakPoinService breakPoinService;

    @Autowired
    private QCloudStorageService storageService;

    @PassToken
    @GetMapping(value = "resource")
    public CommonResult download(HttpServletRequest request, HttpServletResponse response, @RequestParam("key") String key) {

        LambdaQueryWrapper<BreakpointResume> brWrapper=new LambdaQueryWrapper<>();
        brWrapper.eq(BreakpointResume::getCodKey,key);
        List<BreakpointResume> list = breakpointResumeService.list(brWrapper);
        String str=null;
        //如果本地存在取本地文件
        if(list.size()>0){
            BreakpointResume breakpointResume = list.get(0);
            str=breakpointResume.getFilePath();
        }else{//本地不存在
            try{
                String download = storageService.download(key);
                BreakpointResume breakpointResume=new BreakpointResume();
                breakpointResume.setCodKey(key);
                breakpointResume.setFilePath(download);
                breakpointResume.setCreateTime(new Date());
                breakpointResume.setUpdateTime(new Date());
                boolean save = breakpointResumeService.save(breakpointResume);
                if(save){
                    str=download;
                }else{
                    return CommonResult.error();
                }
            }catch (Exception e){
                return CommonResult.error();
            }
        }
        if(str==null){
            return CommonResult.error();
        }
        File file=new File(str);
        if (file.exists()) {
            String range = request.getHeader("Range");
            if (range != null && (range = range.trim()).length() > 0) {
                Pattern rangePattern = Pattern.compile("^bytes=([0-9]+)-([0-9]+)?$");
                Matcher matcher = rangePattern.matcher(range);
                if (matcher.find()) {
                    Integer start = Integer.valueOf(matcher.group(1));
                    Integer end = 0;
                    String endStr = matcher.group(2);
                    if (endStr != null && (endStr = endStr.trim()).length() > 0)
                        end = Integer.valueOf(endStr);
                    breakPoinService.downLoadByBreakpoint(file, start, end, response);
                    return null;
                }
            }
            breakPoinService.downLoadAll(file, response);
            return null;
        }
        return CommonResult.error();
    }
}

3.3 自定义全局响应类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
    private String code;
    private String msg;
    private T data;

    public CommonResult(String code,String msg){
        this.code=code;
        this.msg=msg;
    }

    public static CommonResult success(){
        return create("200","成功");
    }
    public static <T> CommonResult success(T data){
        CommonResult result = create("200", "成功");
        result.setData(data);
        return result;
    }

    public static CommonResult error(){
        return create("500","服务器开小差了");
    }

    public static  CommonResult  create(String code,String msg){
        return new CommonResult(code,msg);
    }
}
上一篇:【web层的增删改查以及分页查询 04】


下一篇:WTF Python课堂(一)