欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

文件分片续传及文件类型判断

程序员文章站 2022-03-10 22:02:15
...

写在最前面

为了实现一个文件分片上传方法,找了很久的demo,但是一直没有找到,最后找到了一个plupload插件,可以支持文件分片。但是,第一次接触这个插件,有很多不会用的地方,网上的许多文章毕竟是他们做的,拿来当然会有许多坑,一步一步调试再理解插件的作用,最后成功。这个插件中,有许多功能,但我最主要的是用了插件的分片的功能,文件类型验证全部由后端验证。

关于plupload插件的使用方法

plupload插件的使用方法在csdn、博客园、开源中国论坛都有很多讲的,在这里给一个我觉得介绍挺完整的:https://www.cnblogs.com/2050/p/3913184.html
下面这个是官方文档:http://www.phpin.net/tools/plupload/

一、jsp页面

<body>

<div id="uploader"></div>

<a href="listFile.jsp">下载</a>
<script type="text/javascript">
    // Convert divs to queue widgets when the DOM is ready
    $(function() {
        var uploader = $("#uploader").plupload({
            runtimes: 'gears,flash,silverlight,browserplus,html5', 
            url : 'file/pluploadUpload',
            multipart: true,
            upload_tmp_dir:"d:\\temp",//设置临时文件位置

            init:{
                BeforeUpload: function(up, file) {
                    up.settings.multipart_params = {
                        filename : file.name
                    };

                },
            },
            multipart_params: {
                'key': '${filename}', // use filename as a key
                'Filename': '${filename}' // adding this to keep consistency across the runtimes
            },
            max_file_size: '800mb', // 文件上传最大限制。
            chunk_size: '10mb', // 上传分块每块的大小
           // max_retries: 1,
            multiple_queues :true,
            unique_names: false, // 上传的文件名是否唯一
            // 是否生成缩略图(仅对图片文件有效)
            resize: {
                width: 1000,
                crop: false,
                preserve_headers: true
            },
            file_data_name: 'file',
            rename: false,//可重命名
            sortable: true,//可排序
            dragdrop: false,//可拖拽
            filters : [//文件类型在此处设置,但我放后台验证了
                    {title: "*", extensions: "*"}
            ],
            views: {
                list: true,
                thumbs: true, // Show thumbs
                active: 'thumbs'
            },
            // Flash settings
            flash_swf_url : 'js/Moxie.swf',
            // Silverlight settings
            silverlight_xap_url : 'js/Moxie.xap'
        });
    });
</script>
</body>

二、FileUpload类

  File dir = new File(request.getSession().getServletContext().getRealPath("/") + FileDir+"/"+userId);
                if(!dir.exists()){
                    //可创建多级目录,而mkdir()只能创建一级目录
                    dir.mkdirs();
                }
                //开始上传文件
                PluploadService.upload(plupload,fileName,dir);
                    System.out.println("上传成功,路径为:"+dir);
        }catch (Exception e){
            e.printStackTrace();
        }
        return "message";

三、PluloadService类

在这个类中我进行了文件类型判断,由于文件是分片上传的,服务器得到的文件是一块一块的,所以很难进行截取完整的文件头。因此,我多加了一个判断,文件是第一块的时候,截取文件头,获取文件类型从而进行判断。如果文件类型不允许,则直接返回。

 private static long UPLOAD_MAXSIZE = 800 * 1024 * 1024;

    public static void upload(Plupload plupload,String fileName, File pluploadDir) throws ServletException, IOException {
        String name = "" + System.currentTimeMillis()+fileName;
        uploads(plupload,name, pluploadDir);
    }

    /**
     * 上传方法
     * @param plupload  上传实体对象
     * @param pluploadDir 上传的文件夹名称
     * @param fileName 保存的文件名
     */
    private static void uploads(Plupload plupload,String fileName, File pluploadDir) {
        boolean flag=false;
        System.out.println(plupload);
        //用户上传文件被分隔的总块数
        int chunks = plupload.getChunks();
        //当前块,从0开始
        int nowChunk = plupload.getChunk();
        System.out.println("当前块:"+nowChunk+"\n");

        //这里Request请求类型的强制转换可能出错,配置文件中向SpringIOC容器引入multipartResolver对象即可。
        MultipartHttpServletRequest mulRequest = (MultipartHttpServletRequest) plupload.getRequest();
        //map中只有一个键值对
        MultiValueMap<String, MultipartFile> map = mulRequest.getMultiFileMap();
        if (map != null) {
            try {
                Iterator<String> iterator = map.keySet().iterator();
                while (iterator.hasNext()) {
                    String key = iterator.next();
                    List<MultipartFile> multipartFileList = map.get(key);
                    //循环只进行一次
                    for (MultipartFile multipartFile : multipartFileList) {
                        //手动向Plupload对象传入MultipartFile属性值
                        plupload.setMultipartFile(multipartFile);
                        //新建目标文件,只有被流写入时才会真正存在
                        File targetFile = new File(pluploadDir + "/" + fileName);
                        //当前块为首块时,截取文件头进行判断
                        if(nowChunk==0){
                            if (!creatCheckFile(multipartFile, plupload.getRequest(),fileName)){
                                return;
                            }
                        }
                        //上传资料总块数大于1,要进行合并
                        if (chunks >1) {
                            File tempFile = new File(pluploadDir.getPath() + "/" + multipartFile.getName());
                            //第一块直接从头写入,不用从末端写入
                            savePluploadFile(multipartFile.getInputStream(), tempFile, nowChunk == 0 ? false : true);
                            //全部块已经上传完毕,此时targetFile因为有被流写入而存在,要改文件名字
                            if (chunks - nowChunk == 1) {
                                flag=true;
                                tempFile.renameTo(targetFile);
                            }
                        }else {
                            multipartFile.transferTo(targetFile);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ServletException e) {
                e.printStackTrace();
            }
        }
    }
    public static boolean creatCheckFile(MultipartFile file, HttpServletRequest request,String fileName) throws ServletException, IOException {
        boolean flag = false;
        //获取文件名
        String message = "";
        //判断文件不为空
        if (file.getSize() != 0 && !file.isEmpty()) {
            flag = true;
            //判断文件大小
            if (file.getSize() <= UPLOAD_MAXSIZE) {
                flag = true;
                //文件类型判断
                if (CheckFileType.getCheckFile(file,fileName)) {
                    flag = true;
                } else {
                    flag = false;
                    System.out.println("文件格式不符合!");
                    message = "文件格式不符合";
                }
            } else {
                flag = false;
                System.out.println("文件大小超出范围!");
                message = "文件大小超出范围!";
            }
        } else {
            flag = false;
            System.out.println("文件为空!");
            message = "文件为空!";
        }
        return flag;
    }

    /**
     * 保存文件
     * @param is 文件输入流
     * @param tempFile 临时文件
     * @param flag 操作标识
     */
    private static void savePluploadFile(InputStream is, File tempFile, boolean flag){
        OutputStream os=null;
        try{
            //从末端写入
            if(tempFile.exists()){
                os=new BufferedOutputStream(new FileOutputStream(tempFile,true));
            }else {//从头写入
                os=new BufferedOutputStream(new FileOutputStream(tempFile));
            }
            byte []bytes=new byte[1024*1024*20];
            int len=0;
            while ((len=(is.read(bytes)))>0){
                os.write(bytes,0,len);
            }
            os.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try{
                os.close();
                is.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

四、文件类型判断

截取八位以上文件头,文件头会发生一点改变,截取八位文件头,部分文件的文件头是一样的。在这里截取的是前八位文件头。

/**
     * 判断上传的文件是否合法
     * 1.检查文件的扩展名
     * 2.检查文件的MIME类型
     *
     * @param file
     * @return boolean
     */
    public final static boolean getCheckFile(MultipartFile file, String fileName) {
        //为真表示符合上传条件,为假表标不符合
        boolean upflag = false;
        String sname = fileName.substring(fileName.lastIndexOf(".") + 1);
        //转换成小写
        sname = sname.toLowerCase();
        System.out.println("上传文件后缀:" + sname);
        //获取上传附件的文件头,判断属于哪种类型,并获取其扩展名
        byte[] b = new byte[4];
        try {
            InputStream is = file.getInputStream();
            is.read(b, 0, b.length);
            String fileHeader = getFileHeader(b);
            System.out.println("上传文件的文件头为:" + fileHeader);
            if (FILE_TYPE_MAP.containsKey(fileHeader)) {
                upflag = true;
                //检查文件扩展名
                String va = FILE_TYPE_MAP.get(fileHeader);
                System.out.println("文件的扩展名应为:" + va);
                if (va.equals(sname)) {
                    upflag = true;
                } else {
                //特殊情况校验,有的文件头前八位
                    if (elseCheck(sname,fileHeader)){
                        upflag=true;
                    }
                    //特殊情况校验,如果用户上传的扩展名为文本文件,则允许上传
                    if (sname.indexOf("txt") != -1) {
                        return true;
                    } else {
                        return false;
                    }

                }
                is.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return upflag;
    }

    /**
     * 获取上传文件类型
     *
     * @param b
     * @return
     */
    public final static String getFileHeader(byte[] b) {
        String value = getFileHexString(b);
        return value;
    }

    /**
     * 读取文件头,将字节数组的前四位转换成十六进制
     *
     * @param b 读取文件头信息的文件的byte数组
     * @return 文件头信息
     */
    public final static String getFileHexString(byte[] b) {
        StringBuilder builder = new StringBuilder();
        if (b == null || b.length <=0) {
            return null;
        }
        for (int i = 0; i < b.length; i++) {
            int v = b[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                builder.append(0);
            }
            builder.append(hv);
        }
        return builder.toString();
    }

    private static boolean elseCheck(String suffix,String fileHeader){
        boolean check=false;
        //扩展名为"wav"与"avi"的前八位文件头相同
        if ("wav".equals(suffix)&& "52494646".equals(fileHeader)){
            return true;
        }
        //扩展名为"f4v"与"flv"的文件头相同
        if ("f4v".equals(suffix) && "464c5601".equals(fileHeader)){
            return true;
        }
        //扩展名为"rmvb"与"rm"的文件头相同
        if ("rm".equals(suffix)&& "2e524d46".equals(fileHeader)){
            return true;
        }
        //扩展名为"asf"与"wmv"的文件头相同
        if ("asf".equals(suffix)&&"3026b275".equals(fileHeader)){
            return true;
        }
        //jar与zip只是前八位相同
        for (String s:wpsSame) {
            if ( suffix.equals(s)&& "504b0304".equals(fileHeader)){
                return true;
            }
        }
        for (String s:docSame) {
            if ( suffix.equals(s)&&"d0cf11e0".equals(fileHeader)){
                return true;
            }
        }
        return check;
    }

五、Plupload类

Plupload类名不能改变,除非在plupload源码插件中改变。在这里,没有给出允许文件类型的集合,这个可以自己定义。

public class Plupload {
    /**文件原名*/
    private String name;
    /**用户上传资料被分解总块数*/
    private int chunks = -1;
    /**当前块数(从0开始计数)*/
    private int chunk = -1;
    /**HttpServletRequest对象,不会自动赋值,需要手动传入*/
    private HttpServletRequest request;
    private HttpServletResponse response;
    /**保存文件上传信息,不会自动赋值,需要手动传入*/
    private MultipartFile multipartFile;