Java断点续传

说明:本文章是在Springmvc的基础上完成的(有问题可以参考之前文章Springmvc框架搭建),主要功能是断点续传,同时下载时如果是图片直接展示。

使用场景:

        ①在浏览器下载的时候可以暂停下载后继续下载。

        ②在下载某一软件时,电脑突然断网,如果是一般下载继续下载会抛出异常,如果是断点续传会继续下载。这两点是断点续传的主要表现。

下载流程:

代码实现描述:

a.文件第一次下载,下载成功,文件保存。

b.文件第一次下载,中间断网暂停,联网成功调用地址,读取断点,根据文件断点继续下载,下载成功,文件保存。

准备内容:

a.我的文件下载目录:

b.我的访问路径  localhost:8080/forAll/fileDownload?filename=2.zip&actiontype=download

                filename是要下载的文件的名称。

                actiontype:是打开文件的方式,如果download是下载,show是图片展示。

c.我的代码层次分文3个层次 Controller   Service   ServiceImpl  Utils

Controller  层

package com.zpkj.space.controller;
import com.zpkj.space.service.UpLoadService;
import com.zpkj.space.utils.UploadAllUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
/**
 * Created by 李庆伟 on
 */
@Controller
@RequestMapping("forAll")
public class UpLoadController {
    @Autowired
    private UpLoadService upLoadService;

    private String result = "";
    private static String file_path = "E:\\Linux镜像及虚拟机";
    private String page_path = "error/downLoadError";

    /**
     * 断点续传
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @RequestMapping("/fileDownload")
    public String fileDownload(HttpServletRequest request, HttpServletResponse response) throws IOException {
        StringBuffer sb = new StringBuffer();
        try {
            // 下载本地文件
            String actiontype = request.getParameter("actiontype") == null ? "show"
                    : request.getParameter("actiontype");
            if ("show".equals(actiontype)) {
                fileShow(request, response);
                return null;
            }
            sb.append("\n --下载文件信息开始--");
            String filename = request.getParameter("filename") == null ? "" : request.getParameter("filename");
            request.setAttribute("filename", filename);
            sb.append("\n actiontype=" + actiontype + " filename=" + filename);
            // 参数名称是否可用和打印地址
            String message = upLoadService.isAvailableFilename(request, filename);
            if (!"".equals(message)) {
                sb.append("\n message=" + message);
                request.setAttribute("message", message);
                return null;
            }
            sb.append("\n\r每次用于匹配文件的file_path"+file_path+"===========文件名:"+filename);
            // 文件服务器地址
            String path = readfile(file_path, filename);
            sb.append("\n\r调用readfile方法后的文件名:"+filename);
            sb.append("\n 服务器文件路径:" + path);
            File downloadFile = new File(path);
            // 文件是否可用
            message = UploadAllUtils.isAvailableFile(downloadFile);
            if (!"".equals(message)) {
                sb.append("\n message=" + message);
                request.setAttribute("message", message);
                return null;
            }
            String requestLongth = request.getHeader("RequestLongth");
            if(StringUtils.isNotBlank(requestLongth)&&requestLongth.trim().equals("1")){
                response.setHeader("Content-Length",String.valueOf(downloadFile.length()));
                return null;
            }
            // 下载成功否
            message = upLoadService.isdownLoad(request, response, downloadFile, sb);
            if (!"".equals(message)) {
                System.out.println(message);
                sb.append("\n message=" + message);
                request.setAttribute("message", message);
                return null;
            }
            sb.append("\n--下载文件信息结束--");

        } catch (Exception e) {
            request.setAttribute("message", "文件异常!" + e.getMessage());
            return null;
        }finally {
            System.out.println(sb.toString());
        }
        return null;
    }

    /**
     * 图片展示
     *
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @RequestMapping("/fileShow")
    public String fileShow(HttpServletRequest request,
                           HttpServletResponse response) throws IOException {
        try {
            // 动作类型
            String actiontype = request.getParameter("actiontype") == null ? "show"
                    : request.getParameter("actiontype");
            if ("download".equals(actiontype)) {
                fileDownload(request, response);
                return null;
            }
            // 本地文件
            String filename = request.getParameter("filename") == null ? ""
                    : request.getParameter("filename");
            request.setAttribute("filename", filename);
            // 参数名称是否可用和打印地址
            String message = upLoadService.isAvailableFilename(request,
                    filename);
            if (!"".equals(message)) {
                request.setAttribute("message", message);
                return page_path;
            }
            // 文件服务器地址
            // String path = file_path + "" + filename; // String path =
            // "E:/Install/"
            // + name;
            String path = readfile(file_path, filename);
            System.out.println("服务器文件路径:" + path);
            File showFile = new File(path);
            // 文件是否可用
            message = UploadAllUtils.isAvailableFile(showFile);
            if (!"".equals(message)) {
                request.setAttribute("message", message);
                return page_path;
            }

            // 是否是图片
            message = UploadAllUtils.isImage(showFile);
            if (!"".equals(message)) {
                request.setAttribute("message", message);
                return page_path;
            }
            // 展示成功否
            message = upLoadService.isShow(response, showFile);
            if (!"".equals(message)) {
                request.setAttribute("message", message);
                return page_path;
            }
        } catch (Exception e) {
            request.setAttribute("message", "文件异常!" + e.getMessage());
            return page_path;
        }
        return null;
    }

    public String readfile(String filepath, String fileName) {
        String resultLocal = "";
        File file = new File(filepath);
        if (!file.isDirectory()) {
            if (file.getName().equals(fileName)) {
                resultLocal = file.getAbsolutePath();

            }
        } else {
            String[] filelist = file.list();
            for (int i = 0; i < filelist.length; i++) {
                File readfile = new File(filepath + File.separator
                        + filelist[i]);
                if (!readfile.isDirectory()) {
                    if (readfile.getName().equals(fileName)) {
                        resultLocal = readfile.getAbsolutePath();
                        break;
                    }
                } else if (readfile.isDirectory()) {
                    readfile(filepath + File.separator + filelist[i], fileName);
                }
            }
        }
        return resultLocal;
    }

}

Service  层

package com.zpkj.space.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
/**
 * Created by 李庆伟
 */
public interface UpLoadService {

    public String isAvailableFilename(HttpServletRequest request, String filename);

    public String isdownLoad(HttpServletRequest request, HttpServletResponse response, File downloadFile, StringBuffer sb);

    public String isShow(HttpServletResponse response, File showFile);



}

ServiceImpl 层

package com.zpkj.space.service.impl;
import com.zpkj.space.service.UpLoadService;
import com.zpkj.space.utils.UploadAllUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
/**
 * Created by 李庆伟 on
 */
@Service
public class UpLoadServiceImpl implements UpLoadService {
    /**
     * 判断参数是否可用
     *
     * @param request
     * @param filename
     * @return
     */
    public String isAvailableFilename(HttpServletRequest request, String filename) {
        String message = "";
        try {
            String param = request.getQueryString();
            if (param == null) {
                message = "没有传递参数";
                return message;
            }
            System.out.print("连接地址为:" + request.getRequestURL() + "?" + param);
            Assert.hasText(filename);
        } catch (IllegalArgumentException e) {
            message = "文件名参数为空!";
            return message;
        }
        return message;
    }

    /**
     * 断点续传核心方法
     * @param request
     * @param response
     * @param downloadFile
     * @param sb
     * @return
     */
    public String isdownLoad(HttpServletRequest request, HttpServletResponse response, File downloadFile, StringBuffer sb) {
        sb.append("\n 进入下载方法:");
        String message = "";
        long fileLength = downloadFile.length(); // 记录文件大小
        long pastLength = 0; // 记录已下载文件大小
        int rangeSwitch = 0; // 0:从头开始的全文下载;1:从某字节开始的下载(bytes=27000-);2:从某字节开始到某字节结束的下载(bytes=27000-39000)
        long toLength = 0; // 记录客户端需要下载的字节段的最后一个字节偏移量(比如bytes=27000-39000,则这个值是为39000)
        long contentLength = 0; // 客户端请求的字节总量
        String rangeBytes = ""; // 记录客户端传来的形如“bytes=27000-”或者“bytes=27000-39000”的内容

        String contentRange = "";// 响应
        if (request.getHeader("Range") != null) { // 客户端请求的下载的文件块的开始字节
            response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);
            rangeBytes = request.getHeader("Range").replaceAll("bytes=", "");
            sb.append("\n Request-Range:" + rangeBytes);
            if (rangeBytes.indexOf('-') == rangeBytes.length() - 1) {// bytes=969998336-
                rangeSwitch = 1;
                rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));//上传开始
                pastLength = Long.parseLong(rangeBytes.trim());//上传开始
                contentLength = fileLength - pastLength; // 客户端请求的是 969998336
                // 之后的字节
            } else { // bytes=1275856879-1275877358
                rangeSwitch = 2;
                String startPos = rangeBytes.substring(0, rangeBytes.indexOf('-'));//上传开始
                String endPos = rangeBytes.substring(
                        rangeBytes.indexOf('-') + 1, rangeBytes.length());//上传长度
                pastLength = Long.parseLong(startPos.trim()); // bytes=1275856879-1275877358,从第
                // 1275856879
                // 个字节开始下载
                toLength = Long.parseLong(endPos); // bytes=1275856879-1275877358,到第
                // 1275877358 个字节结束
                contentLength = toLength - pastLength; // 客户端请求的是
                // 1275856879-1275877358
                // 之间的字节
            }
        } else { // 从开始进行下载
            contentLength = fileLength; // 客户端要求全文下载
        }
        /**
         * 如果设设置了Content -Length,则客户端会自动进行多线程下载。如果不希望支持多线程,则不要设置这个参数。 响应的格式是:
         * Content - Length: [文件的总大小] - [客户端请求的下载的文件块的开始字节]
         * response.setHeader("Content- Length", new Long(file.length() -
         * pastLength).toString());
         */
        response.setHeader("Content-Length",
                new Long(fileLength - pastLength).toString());
        // response.reset(); // 告诉客户端允许断点续传多线程连接下载,响应的格式是:Accept-Ranges: bytes
        response.setHeader("Accept-Ranges", "bytes");// 如果是第一次下,还没有断点续传,状态是默认的
        // 200,无需显式设置;响应的格式是:HTTP/1.1
        // 200 OK
        sb.append("\n Content-Length:"
                + new Long(fileLength - pastLength).toString());
        if (pastLength != 0) {
            // 不是从最开始下载,
            // 响应的格式是:
            // Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小]
            sb.append("---不是从开始进行下载!服务器即将开始断点续传...");
            switch (rangeSwitch) {
                case 0: {
                }
                case 1: { // 针对 bytes=27000- 的请求
                    contentRange = new StringBuffer("bytes ")
                            .append(new Long(pastLength).toString()).append("-")
                            .append(new Long(fileLength - 1).toString())
                            .append("/").append(new Long(fileLength).toString())
                            .toString();
                    response.setHeader("Content-Range", contentRange);
                    break;
                }
                case 2: { // 针对 bytes=27000-39000 的请求
                    contentRange = rangeBytes + "/"
                            + new Long(fileLength).toString();
                    response.setHeader("Content-Range", contentRange);
                    break;
                }
                default: {
                    break;
                }
            }
        } else {
            // 是从开始下载
            sb.append("---是从开始进行下载!");
        }
        sb.append("\n Response-contentRange:" + contentRange);
        //进行封装下载
        //new DownloadMultiThread(response,downloadFile,contentLength,pastLength,rangeSwitch);
        RandomAccessFile raf = null; // 负责读取数据
        ServletOutputStream os = null; // 写出数据
        OutputStream out = null; // 缓冲
        byte b[] = new byte[1024]; // 暂存容器
        try {
            response.addHeader("Content-Disposition", "attachment; filename=\""
                    + downloadFile.getName() + "\"");
            response.setContentType(UploadAllUtils.setContentType(downloadFile
                    .getName())); // set the MIME type.
            os = response.getOutputStream();
            out = new BufferedOutputStream(os);
            raf = new RandomAccessFile(downloadFile, "r");
            switch (rangeSwitch) {
                case 0: { // 普通下载,或者从头开始的下载
                    // 同1
                }
                case 1: {
                    if (pastLength == 0) {
                    } else {
                        // 针对 bytes=27000- 的请求
                        raf.seek(pastLength); // 形如 bytes=969998336-
                        // 的客户端请求,跳过969998336 个字节
                    }
                    int n = 0;
                    while ((n = raf.read(b, 0, b.length)) != -1) {
                        out.write(b, 0, n);
                    }
                    break;
                }
                case 2: { // 针对 bytes=27000-39000 的请求
                    raf.seek(pastLength); // 形如 bytes=1275856879-1275877358
                    // 的客户端请求,找到第 1275856879 个字节
                    int n = 0;
                    long readLength = 0; // 记录已读字节数
                    while (readLength <= contentLength - 1024) {// 大部分字节在这里读取
                        n = raf.read(b, 0, 1024);
                        readLength += 1024;
                        out.write(b, 0, n);
                    }
                    if (readLength <= contentLength) { // 余下的不足 1024 个字节在这里读取
                        n = raf.read(b, 0, (int) (contentLength - readLength));
                        out.write(b, 0, n);
                    }
                    break;
                }
                default: {
                    break;
                }
            }

            out.flush();
            sb.append("---下载结束---");
        } catch (IOException e) {
            /**
             * 在写数据的时候, 对于 ClientAbortException 之类的异常,
             * 是因为客户端取消了下载,而服务器端继续向浏览器写入数据时, 抛出这个异常,这个是正常的。 尤其是对于迅雷这种吸血的客户端软件,
             * 明明已经有一个线程在读取 bytes=1275856879-1275877358,
             * 如果短时间内没有读取完毕,迅雷会再启第二个、第三个。。。线程来读取相同的字节段, 直到有一个线程读取完毕,迅雷会 KILL
             * 掉其他正在下载同一字节段的线程, 强行中止字节读出,造成服务器抛 ClientAbortException。
             * 所以,我们忽略这种异常
             */
            message = "#提醒# 向客户端传输时出现IO异常,但此异常是允许的,有可能客户端取消了下载,导致此异常,不用关心!"
                    + e.getMessage();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                    System.out.println(e);
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                    System.out.println(e);
                }
            }
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                    System.out.println(e);
                }
            }
        }
        return message;
    }


    /**
     * 图片展示核心代码
     *
     * @param response
     * @param showFile
     * @return
     */
    public String isShow(HttpServletResponse response, File showFile) {
        String message = "";
        try {
            response.setContentType(UploadAllUtils.setContentType(showFile
                    .getName())); // set the MIME type.
            // response.setContentType("image/jpeg; charset=GBK");
            // response.setHeader("Content-Disposition",
            // "attachment; filename="+new
            // String("temp.jpg".getBytes("GBK"),"ISO8859_1"));
            ServletOutputStream outputStream = response.getOutputStream();
            FileInputStream inputStream = new FileInputStream(showFile);
            byte[] buffer = new byte[1024];
            int i = -1;
            while ((i = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, i);
            }
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            outputStream = null;
        } catch (IOException e) {
            message = "IO异常,无法展示图片" + e.getMessage();
        }
        return message;
    }


}
Utils 中一个类

package com.zpkj.space.utils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
 * Created by lqw on 
 */
public class UploadAllUtils {

    /**
     * 判断文件是否可用
     * @param file
     * @return
     */
    public static String isAvailableFile(File file){
        String message="";
        if (file.exists()) {
            if (file.isFile()) {
                if (file.length()==0){
                    message="文件是一个空文件";
                }
                if (!file.canRead()) {
                    message="文件不是一个可读的文件";
                }
            } else {
                message="文件是一个文件夹";
            }
        } else {
            message="文件不存在!";
        }
        return message;
    }

    public static String setContentType(String returnFileName) {
        String contentType = "application/octet-stream";
        if ( returnFileName.lastIndexOf(".") < 0)
            return contentType;
        returnFileName = returnFileName .toLowerCase();
        returnFileName = returnFileName.substring(returnFileName .lastIndexOf("." ) + 1);

        if ( returnFileName.equals("html") || returnFileName.equals("htm") || returnFileName .equals("shtml" )) {
            contentType = "text/html";
        } else if ( returnFileName.equals("apk")) {
            contentType = "application/vnd.android.package-archive" ;
        } else if ( returnFileName.equals("sis")) {
            contentType = "application/vnd.symbian.install";
        } else if ( returnFileName.equals("sisx")) {
            contentType = "application/vnd.symbian.install";
        } else if ( returnFileName.equals("exe")) {
            contentType = "application/x-msdownload";
        } else if ( returnFileName.equals("msi")) {
            contentType = "application/x-msdownload";
        } else if ( returnFileName.equals("css")) {
            contentType = "text/css";
        } else if ( returnFileName.equals("xml")) {
            contentType = "text/xml";
        } else if ( returnFileName.equals("gif")) {
            contentType = "image/gif";
        } else if ( returnFileName.equals("jpeg") || returnFileName.equals("jpg")) {
            contentType = "image/jpeg";
        } else if ( returnFileName.equals("js")) {
            contentType = "application/x-javascript";
        } else if ( returnFileName.equals("atom")) {
            contentType = "application/atom+xml";
        } else if ( returnFileName.equals("rss")) {
            contentType = "application/rss+xml";
        } else if ( returnFileName.equals("mml")) {
            contentType = "text/mathml";
        } else if ( returnFileName.equals("txt")) {
            contentType = "text/plain";
        } else if ( returnFileName.equals("jad")) {
            contentType = "text/vnd.sun.j2me.app-descriptor" ;
        } else if ( returnFileName.equals("wml")) {
            contentType = "text/vnd.wap.wml";
        } else if ( returnFileName.equals("htc")) {
            contentType = "text/x-component";
        } else if ( returnFileName.equals("png")) {
            contentType = "image/png";
        } else if ( returnFileName.equals("tif") || returnFileName.equals("tiff")) {
            contentType = "image/tiff";
        } else if ( returnFileName.equals("wbmp")) {
            contentType = "image/vnd.wap.wbmp";
        } else if ( returnFileName.equals("ico")) {
            contentType = "image/x-icon";
        } else if ( returnFileName.equals("jng")) {
            contentType = "image/x-jng";
        } else if ( returnFileName.equals("bmp")) {
            contentType = "image/x-ms-bmp";
        } else if ( returnFileName.equals("svg")) {
            contentType = "image/svg+xml";
        } else if ( returnFileName.equals("jar") || returnFileName.equals("var") || returnFileName .equals("ear" )) {
            contentType = "application/java-archive";
        } else if ( returnFileName.equals("doc")) {
            contentType = "application/msword";
        } else if ( returnFileName.equals("pdf")) {
            contentType = "application/pdf";
        } else if ( returnFileName.equals("rtf")) {
            contentType = "application/rtf";
        } else if ( returnFileName.equals("xls")) {
            contentType = "application/vnd.ms-excel";
        } else if ( returnFileName.equals("ppt")) {
            contentType = "application/vnd.ms-powerpoint";
        } else if ( returnFileName.equals("7z")) {
            contentType = "application/x-7z-compressed";
        } else if ( returnFileName.equals("rar")) {
            contentType = "application/x-rar-compressed";
        } else if ( returnFileName.equals("swf")) {
            contentType = "application/x-shockwave-flash";
        } else if ( returnFileName.equals("rpm")) {
            contentType = "application/x-redhat-package-manager" ;
        } else if ( returnFileName.equals("der") || returnFileName.equals("pem") || returnFileName .equals("crt" )) {
            contentType = "application/x-x509-ca-cert";
        } else if ( returnFileName.equals("xhtml")) {
            contentType = "application/xhtml+xml";
        } else if ( returnFileName.equals("zip")) {
            contentType = "application/zip";
        } else if ( returnFileName.equals("mid") || returnFileName.equals("midi") || returnFileName .equals("kar" )) {
            contentType = "audio/midi";
        } else if ( returnFileName.equals("mp3")) {
            contentType = "audio/mpeg";
        } else if ( returnFileName.equals("ogg")) {
            contentType = "audio/ogg";
        } else if ( returnFileName.equals("m4a")) {
            contentType = "audio/x-m4a";
        } else if ( returnFileName.equals("ra")) {
            contentType = "audio/x-realaudio";
        } else if ( returnFileName.equals("3gpp") || returnFileName.equals("3gp")) {
            contentType = "video/3gpp";
        } else if ( returnFileName.equals("mp4")) {
            contentType = "video/mp4";
        } else if ( returnFileName.equals("mpeg") || returnFileName.equals("mpg")) {
            contentType = "video/mpeg";
        } else if ( returnFileName.equals("mov")) {
            contentType = "video/quicktime";
        } else if ( returnFileName.equals("flv")) {
            contentType = "video/x-flv";
        } else if ( returnFileName.equals("m4v")) {
            contentType = "video/x-m4v";
        } else if ( returnFileName.equals("mng")) {
            contentType = "video/x-mng";
        } else if ( returnFileName.equals("asx") || returnFileName.equals("asf")) {
            contentType = "video/x-ms-asf";
        } else if ( returnFileName.equals("wmv")) {
            contentType = "video/x-ms-wmv";
        } else if ( returnFileName.equals("avi")) {
            contentType = "video/x-msvideo";
        }

        return contentType;
    }

    /**
     * 判断文件是否是图片
     * @param file
     * @return
     */
    public static String isImage(File file) {
        String message="";
        BufferedImage bi=null;
        try {
            bi = ImageIO.read(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(bi == null){
            message="文件不是图片!";
        }
        return message;
    }

}

想要看效果可以随便找个Springmvc框架,把代码扔进去,按照之前的访问路径访问就可以了

到此断点续传功能就OK了。










猜你喜欢

转载自blog.csdn.net/liqingwei168/article/details/79813801