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);
    }
}
  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java 中,我们可以使用 Apache Commons Net 库来实现 FTP 断点续传功能。具体实现步骤如下: 1. 创建 FTPClient 对象,并连接到 FTP 服务器: ```java FTPClient ftpClient = new FTPClient(); ftpClient.connect(server, port); ftpClient.login(user, password); ``` 2. 进入到 FTP 服务器上的需要上传或下载的目录: ```java ftpClient.changeWorkingDirectory(remoteDir); ``` 3. 在上传或下载文件前,记录上一次传输完成的位置: ```java long restartPosition = ftpClient.getRestartOffset(); ``` 4. 上传或下载文件时,指定 REST 命令参数即可实现断点续传: ```java // 上传文件 try (InputStream inputStream = new FileInputStream(localFile)) { ftpClient.setRestartOffset(restartPosition); ftpClient.storeFile(remoteFile, inputStream); } // 下载文件 try (OutputStream outputStream = new FileOutputStream(localFile, true)) { ftpClient.setRestartOffset(restartPosition); ftpClient.retrieveFile(remoteFile, outputStream); } ``` 需要注意的是,FTP 服务器可能不支持断点续传功能,因此在实现时需要判断服务器的支持情况。 完整的示例代码如下: ```java import java.io.*; import org.apache.commons.net.ftp.*; public class FtpResume { public static void main(String[] args) throws Exception { String server = "ftp.example.com"; int port = 21; String user = "username"; String password = "password"; String remoteDir = "/remote/dir/"; String remoteFile = "test.txt"; String localFile = "test.txt"; FTPClient ftpClient = new FTPClient(); ftpClient.connect(server, port); ftpClient.login(user, password); ftpClient.changeWorkingDirectory(remoteDir); long restartPosition = ftpClient.getRestartOffset(); // 上传文件 try (InputStream inputStream = new FileInputStream(localFile)) { ftpClient.setRestartOffset(restartPosition); ftpClient.storeFile(remoteFile, inputStream); } // 下载文件 try (OutputStream outputStream = new FileOutputStream(localFile, true)) { ftpClient.setRestartOffset(restartPosition); ftpClient.retrieveFile(remoteFile, outputStream); } ftpClient.disconnect(); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值