java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载

java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载

实现功能:zip文件上传,后台自动解压,Jstree树目录(遍历文件),editor.md预览

采用Spring+SpringMVC+Maven+Jstree+editor.md实现,主要功能:

  • zip压缩文件的上传
  • 后台自动解压
  • Jstree自动获取最上层目录,每次仅仅会获取当前层的文件或者文件夹,然后点击文件夹或者文件,通过ajax与服务器交换数据,减轻检索和数据传输压力
  • 后台通过文件路径遍历文件夹
  • 通过editor.md将文本代码高亮显示
  • 图片的解析预览

总体项目目录结构:

预览:

点击提交后:

并提供下载功能

1. 分析代码

上传压缩包的html代码,使用velocity模板渲染引擎:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传压缩项目包</title>
</head>
<body>
提示:压缩包内请勿包含中文!
<div class="uploadZipFile" id="uploadZipFile">
    <form name="zipForm" id="zipForm">
        <input type="text" id="file-name" name="file-name" placeholder="请输入项目名称"/>
        <div class="file-name-check" style="color: red"></div>
        <br>
        <input type="file"  name="file-zip" id="file-zip"/>
        <br>
        <input type="button" class="" id="upload-zip" value="提交"/>
    </form>
</div>

</body>
<script src="//cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<script type="text/javascript">
    $(window).load(function() {
        //当鼠标移出输入框
        $('#file-name').on('blur', function(){
            var fileName = document.getElementById("file-name").value;
            if(fileName==''){
                $('.file-name-check').html('');
                $('.file-name-check').append("请输入项目名!")
            }
        });

        $("#file-zip").bind("change",function(){
            var imgArr = ["zip"];
            if($(this).val() == "")
            {
                alert("请选择文件!");
            }
            else{
                var file = $(this).val();
                var len = file.length;
                var ext = file.substring(len-3,len).toLowerCase();
                if($.inArray(ext,imgArr) == -1)
                    alert("不是zip格式");
            }
        });

        $('#upload-zip').on('click', function(){
            var form = document.getElementById("zipForm");
            if(document.getElementById("file-name").value==''){		//当项目名为空时
                alert("请输入项目名!");
                return false;
            }
            if(document.getElementById("file-zip").value==''){		//当项目为空时
                alert("请上传项目!");
                return false;
            }

            var formdata = new FormData(form);
            $.ajax({
                url:"/admin/file/zip/upload",
                data: formdata,
                type:"post",
                //预期服务器返回的数据类型,自动解析json返回值,不设置的话可能要执行oResult = JSON.parse(oResult);进行解析
                dataType:"json",
                //默认值: true。默认情况下,通过data选项传递进来的数据,如果是一个对象(技术上讲只要不是字符串),
                // 都会处理转化成一个查询字符串,以配合默认内容类型 "application/x-www-form-urlencoded"。如果要发送 DOM 树信息或其它不希望转换的信息,请设置为 false。
                processData: false,
                //contentType: false,避免 JQuery 对data操作,可能失去分界符,而使服务器不能正常解析文件。
                contentType: false,
                success: function(oResult) {
//                    console.log(oResult);
                    if(oResult.success==1){
                        window.location.href="/admin/file/zip/show?file-path="+oResult.url;
                    }else{
                        alert(oResult.message);
                    }
                }
            })
//              .done(function(oResult) {   //注意done表示成功,fail表示失败,always表示不论成功还是失败,会执行的函数,
//                                          //但是在低版本jquery不兼容,是高版本jquery推荐
//                if(oResult.success==1){
//                    window.location.href="/";
//                    alert(oResult.message);
//                }else{
//                    alert(oResult.message);
//                }
//            }).fail(function () {
//                alert('出现错误,请重试');
//            });
        })
    });
</script>

</html>

预览自动解压后文件夹的html代码,使用velocity模板渲染引擎:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>项目展示</title>
    <link rel="stylesheet" type="text/css" href="/css/editormd.min.css">
    <link rel="stylesheet" type="text/css" href="/css/style.css">
</head>
<!-- 禁用复制粘贴-->
<body oncontextmenu=self.event.returnValue=false onselectstart="return false">
<!-- 搜索表单-->
<form id="s" class="search">
    <input type="search" id="q" />
    <button type="submit">Search</button>
</form>
<!-- 下载按钮-->
<div class="action">
    <input type="hidden" id="filePathRem" value="$!{filePath}">
    <a href="/admin/file/zip/download?file-path=$!{filePath}">下载</a>
</div>
<!-- 放JStree目录树-->
<div id="container" class="side-nav"></div>
<!-- 放editor.md文本-->
<div id="markdown-editor" class="markdown-text"></div>
<!-- 放图片-->
<div id="image-panel" class="image-panel"></div>
</body>
<!--jstree官网https://github.com/vakata/jstree#readme-->
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.3/themes/default/style.min.css" />
<script src="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.3/jstree.min.js"></script>
<script src="/js/file-node.js"></script>

<script src="/js/editormd.min.js"></script>
##支持markdown快速解析
<script src="/lib/marked.min.js"></script>
##支持代码高亮
<script src="/lib/prettify.min.js"></script>
</html>

对应的JS文件,file-node.js:

$(function() {
    var filePath = document.getElementById("filePathRem").value;
    //注意这里面只能处理寻找文件夹的子文件或者子文件夹事件,可以把文件的读取写到 $('#container').on("changed.jstree", function (e, data)函数中
    $('#container').jstree({
        'core': {
            'data':
            //node为点击的节点,cd为输出结果的函数
                function (node, cb) {
                    var formdata = new FormData();
                    formdata.append("file-path",filePath);
                    formdata.append("id",node.id);
                    //通过表单对象上传文件或者数据,设置
                    // processData: false,表示不要对data参数进行序列化处理
                    //contentType: false,避免 JQuery 对data操作,可能失去分界符,而使服务器不能正常解析文件。
                    $.ajax({
                        //不要用get方法,因为#在浏览器中有特殊含义, 
                        // #代表网页中的一个位置。其右面的字符,就是该位置的标识符。比如,http://www.example.com/index.html#print就代表网页index.html的print位置。
                        // 浏览器读取这个URL后,会自动将print位置滚动至可视区域。
                        //并且在发送的请求中,自动忽略#,而首次打开页面的第一次请求id=#
                        //url: "/admin/file/zip/show.action?lazy&file-path=" + filePath + "&id=" + node.id,
                        url:"/admin/file/zip/show.action",
                        data:formdata,
                        type:"post",
                        dataType:"json",
                        processData: false,
                        contentType: false,
                        success: function (oResult) {
                            if (oResult.result.success == 1) {
                                cb(oResult.array);
                            } else {
                                alert(oResult.result.message);
                            }
                        }
                    })
                }
        },
        //0为文件夹,即默认,1为文件
        "types" : {
            0 : {
                "icon" : "glyphicon glyphicon-folder",
                "valid_children" : []
            },
            1 : {
                "icon" : "glyphicon glyphicon-file"
            }
        },
        //搜索功能插件和类别插件,以对文件夹和文有不同的图标
        "plugins" : ["search","types"]
    });

    //上面的表单s和本函数都用于搜索,模糊搜索,不区分大小写
    $("#s").submit(function(e) {
        e.preventDefault();
        $("#container").jstree(true).search($("#q").val());
    });

    //注意changed与click的区别,前者只要状态不变,点击多少次都加载一次,后者每次点击都重新加载
    $('#container').on("changed.jstree", function (e, data) {
        // console.log("The selected nodes are:");
        // //显示被选择节点id编号
        // console.log(data.selected);
        // //显示被选择节点的命名
        // console.log(data.node.text);
        var name=String(data.selected);
        //如果包含.则为请求文件
        if(name.search("\.")>1){
            //判断是否是图片,其他文件都是读取Json字符串的形式
            if(!isImage(name)){
                var formdata = new FormData();
                formdata.append("file-path",filePath);
                formdata.append("id",name);
                $.ajax({
                    url:"/admin/file/zip/show.action",
                    data:formdata,
                    type:"post",
                    dataType:"json",
                    processData: false,
                    contentType: false,
                    success: function (oResult) {
                        if (oResult.result.success == 1) {
                            //首先把页面中的可能存在的图片清空
                            document.getElementById("image-panel").innerHTML ='';
                            //由于editor.md每次更新内容之后都会将<textarea id="append-test" style="display:none;"></textarea>删除,那么每次更新前都需要添加
                            document.getElementById("markdown-editor").innerHTML='<textarea id="append-test" style="display:none;"></textarea>';
                            document.getElementById("append-test").value="```
"+oResult.fileContent+"
```";
                            //用于将markdown文本转化为html格式
                            editormd.markdownToHTML("markdown-editor", {
                            });
                        } else {
                            alert(oResult.result.message);
                        }
                    }
                })
            }else {    //对于图片,我们要显示为图片,而不是文本的字符流
                document.getElementById("markdown-editor").innerHTML='';
                document.getElementById("image-panel").innerHTML = '<img width="500" id="img-circle" src="">';
                document.getElementById("img-circle").src = "/admin/file/zip/image.action?file-path="+filePath+"&id="+name;
            }
        }
    });
    //判断请求文件是否是图片,仅支持常用类型
    function isImage(objFile) {
        var objtype = objFile.substring(objFile.lastIndexOf(".")).toLowerCase();
        var fileType = new Array(".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico");
        for (var i = 0; i < fileType.length; i++) {
            if (objtype == fileType[i]) {
                return true;
                break;
            }
        }
        return false;
    }
});

对应Controller层代码,FileController.java:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.demo.fileTree.model.FileHandleResponse;
import com.demo.fileTree.model.JstreeNode;
import com.demo.fileTree.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

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

/**
 * 实现项目zip压缩包的上传,自动解压,解压后的预览,包括文本和字符串,项目的压缩下载,
 * 由于java.util.zip包不支持汉字的问题,在项目压缩包内请勿包含中文文件名,但是在页面中的项目名可以起名为中文,
 * 可以用org.apache.tools.zip压缩/解压缩zip文件,解决中文乱码问题。
 *
 * @author xie
 * @version 1.0
 * @Date 2017/5/26
 */
@Controller
public class FileController {
    @Autowired
    FileService fileService;

    /**
     * 主页
     * @return
     */
    @RequestMapping(path = {"/"}, method = {RequestMethod.GET, RequestMethod.POST})
    public String index() {
        return "upload_zip";
    }

    /**
     * 上传压缩zip项目文件
     * @param file zip压缩文件
     * @param fileName 项目的命名,我们将解压缩的文件放到以项目名命名的文件夹内,为了保证项目名重复的也可以上传,项目名文件夹外部还有一个32位UUID命名的文件夹,
     *                 只不过取出项目时没有显示
     * @return 结果的json字符串
     */
    @RequestMapping(path = {"/admin/file/zip/upload"}, method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public String uploadZipFile(@RequestParam("file-zip") MultipartFile file,@RequestParam("file-name")String fileName) {
        FileHandleResponse fileHandleResponse = new FileHandleResponse();
        try {
            if(file.isEmpty()){
                fileHandleResponse.setSuccess(0);
                fileHandleResponse.setMessage("上传压缩文件为空");
                return JSON.toJSONString(fileHandleResponse);
            }
            fileHandleResponse = fileService.uploadFileZip(file,fileName);
            return JSON.toJSONString(fileHandleResponse);
        }catch (Exception e) {
            fileHandleResponse.setSuccess(0);
            fileHandleResponse.setMessage("服务器异常!");
            fileHandleResponse.setUrl(null);
            return JSON.toJSONString(fileHandleResponse);
        }
    }

    /**
     * 展示上传的zip项目解压缩后的文件结构
     * @param filePath 项目的路径,比如,C:homemyblogproject2d76c7aa844b4585a53d982d205099e2123其中123为项目名,
     * @param model
     * @return
     */
    @RequestMapping(path = {"/admin/file/zip/show"}, method = {RequestMethod.GET, RequestMethod.POST})
    public String showZipFile(@RequestParam("file-path")String filePath, Model model) {
        model.addAttribute("filePath",filePath);
        //filePath地址大概样子,C:homemyblogproject2d76c7aa844b4585a53d982d205099e2123\,windows和linux不同,
        // 包含文件名,我们提取出来,作为fileName,分隔符可能为/或或\,其中要转意为\
        String fileName = filePath.split("\|\\|/")[filePath.split("\|\\|/").length-1];
        model.addAttribute("fileName",fileName);
        return "show_zip";
    }

    /**
     * 项目展示页面
     * @param filePath 项目路径
     * @param relativePath 节点相比项目路径的相对路径,比如项目路径:
     *                     C:/home/myblog/project/dccb182a7ded477483362ce46be1eb5c/123/
     *                     那么节点路径src/main/java/表示
     *                     C:/home/myblog/project/dccb182a7ded477483362ce46be1eb5c/123/src/main/java/
     * @return 对于文件,返回字符内容的json字符串,对于文件夹,返回文件夹的下一级所有子文件和子文件夹,其实若文件是图片,我们在下面的getImage()方法中处理
     */
    @RequestMapping(path = {"/admin/file/zip/show.action"}, method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public String showZipFileDetail(@RequestParam("file-path") String filePath, @RequestParam("id") String relativePath, Model model) {
        FileHandleResponse fileHandleResponse = new FileHandleResponse();
        try {
            if (relativePath.equals("#")) {    //表示第一次打开页面的请求,relativePath为#,没什么意义,设为空字符串
                relativePath = "";
            }
            File file = new File(filePath+relativePath);

            //如果请求路径存在,即文件或者目录存在
            if (file.exists()) {
                //分为文件或者文件夹两种情况
                if (file.isFile()) {
                    BufferedReader bufferedReader;
                    try {
                        StringBuilder stringBuilder = new StringBuilder();
                        //将字节流向字符流的转换,并创建字符流缓冲区
                        bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
                        // 每次读入一行
                        String read;
                        //每读入一行,要加一个换行符
                        String lineText="
";
                        while ((read = bufferedReader.readLine()) != null) {
                            stringBuilder.append(read+lineText);
                        }
                        bufferedReader.close();
                        fileHandleResponse.setSuccess(1);
                        fileHandleResponse.setMessage("请求成功!");
                        model.addAttribute("result", fileHandleResponse);
                        model.addAttribute("fileContent", stringBuilder.toString());
                        return JSON.toJSONString(model);
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                } else {
                    List<JstreeNode> list = fileService.getAllChildrenNode(filePath,relativePath);
                    JSONArray jsonArray = new JSONArray();
                    for(JstreeNode jstreeNode : list){
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put("id", jstreeNode.getId());
                        jsonObject.put("text", jstreeNode.getText());
                        jsonObject.put("children", jstreeNode.isHasChildren());
                        jsonObject.put("type",jstreeNode.getType());
                        jsonArray.add(jsonObject);
                    }
                    fileHandleResponse.setSuccess(1);
                    fileHandleResponse.setMessage("请求成功!");
                    model.addAttribute("result", fileHandleResponse);
                    //最好不要直接传递list,前端不可以很好的解析
                    model.addAttribute("array", jsonArray);
                    return JSON.toJSONString(model);
                }
            } else {            //如果请求路径不存在
                fileHandleResponse.setSuccess(0);
                fileHandleResponse.setMessage("请求路径不存在!");
                model.addAttribute("result",fileHandleResponse);
                return JSON.toJSONString(model);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将项目压缩后以字节流的方式发送
     * @param filePath 项目路径
     * @param response
     */
    @RequestMapping(path = {"/admin/file/zip/download"}, method = {RequestMethod.GET})
    public void downloadZipFile(@RequestParam("file-path")String filePath, HttpServletResponse response) {
        FileHandleResponse fileHandleResponse;
        try {
            fileHandleResponse = fileService.downloadFileZip(filePath);
            //地址大概样子,C:homemyblogproject2d76c7aa844b4585a53d982d205099e2123.zip,windows和linux不同,
            // 包含文件名,我们提取出来,作为fileName,分隔符可能为/或或\,其中要转意为\
            String fileName = fileHandleResponse.getUrl().split("\|/|\\")[fileHandleResponse.getUrl().split("\|/|\\").length-1];
            response.setContentType("application/zip");
            response.setCharacterEncoding("UTF-8");

            response.setHeader("Content-Disposition","attachment;filename="+fileName);
            OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
            byte[] data = fileService.toByteArray(fileHandleResponse.getUrl());
            outputStream.write(data);
            outputStream.flush();
            outputStream.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 按照图片路径查找图片
     * @param filePath 项目路径
     * @param relativePath 节点相比项目路径的相对路径,比如项目路径:
     *                     C:/home/myblog/project/dccb182a7ded477483362ce46be1eb5c/123/
     *                     那么节点路径src/main/java/表示
 *                     C:/home/myblog/project/dccb182a7ded477483362ce46be1eb5c/123/src/main/java/
     * @param response
     */
    @RequestMapping(path = "/admin/file/zip/image.action")
    public void getImage(@RequestParam("file-path") String filePath,
                                  @RequestParam("id") String relativePath,
                                  HttpServletResponse response) {
        try {
            byte[] data = fileService.toByteArray(filePath+relativePath);
            response.setCharacterEncoding("UTF-8");
            OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
            outputStream.write(data);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

对应Service层代码,FileService.java:

import com.demo.fileTree.configuration.GlobalConfig;
import com.demo.fileTree.model.FileHandleResponse;
import com.demo.fileTree.model.JstreeNode;
import com.demo.fileTree.utils.ZipUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.LinkedList;
import java.util.List;

/**
 * 压缩文件上传,并且解压缩后放到服务器响应目录下,
 * 为什么不直接放压缩包,因为别人看一次,需要解压缩一次,也很浪费系统资源
 *
 * @author xie
 * @version 1.0
 * @Date 2017/5/27
 */
@Service
public class FileService {

    @Autowired
    ZipUtils zipUtils;

    /**
     * 默认上传zip压缩格式
     * @param file 上传的文件
     * @return 上传的结果UploadResponse对象
     * @throws IOException
     */
    public FileHandleResponse uploadFileZip(MultipartFile file, String fileName) throws IOException {
      FileHandleResponse fileHandleResponse;
        try {
            fileHandleResponse = zipUtils.unZipFiles(zipUtils.getZipDir(), fileName, file);
            return fileHandleResponse;
        } catch (Exception e) {
            // 请求失败时打印的异常的信息
            fileHandleResponse = new FileHandleResponse();
            fileHandleResponse.setSuccess(0);
            fileHandleResponse.setMessage("服务器异常!");
            return fileHandleResponse;
        }
    }

    /**
     *  下载压缩后的项目文件
     *
     * @param filePath 项目路径
     * @return 文件处理结果实体,其中url表示项目压缩后的路径
     * @throws IOException
     */
    public FileHandleResponse downloadFileZip(String filePath) throws IOException {
        FileHandleResponse fileHandleResponse;
        try {
            fileHandleResponse = zipUtils.zipFiles(filePath);
            return fileHandleResponse;
        } catch (Exception e) {
            // 请求失败时打印的异常的信息
            fileHandleResponse = new FileHandleResponse();
            fileHandleResponse.setSuccess(0);
            fileHandleResponse.setMessage("服务器异常!");
            return fileHandleResponse;
        }
    }

    /**
     *  返回某一结点(即文件夹)的下一级所有子节点,注意这里输入的不是具体文件或者不存在的路径,是已经判定存在的文件夹路径,
     *  如果是请求具体文件或者不存在的路径,在上一层controller层就应该将文件内容读取并返回或者返回错误信息
     *
     * @param filePath 项目路径
     * @param relativePath 节点相比项目路径的相对路径,比如项目路径:
     *                     C:/home/myblog/project/dccb182a7ded477483362ce46be1eb5c/123/
     *                     那么节点路径src/main/java/表示
     *                     C:/home/myblog/project/dccb182a7ded477483362ce46be1eb5c/123/src/main/java/
     *                     但是由于files[i].getName()只会获得abc这样的单层目录名或者abc.java这样的文件名,因此我们要设置下一级的相对路径为;
     *                     relativePath+files[i].getName()(如果是路径,还要包含/)
     *
     * @return 所有子节点的列表
     * @throws IOException
     */
    public List<JstreeNode> getAllChildrenNode(String filePath,String relativePath) throws IOException {
        File file = new File(filePath+relativePath);
        List<JstreeNode> list = new LinkedList<>();
        try {
            //对于文件夹,我们要遍历它的下一级子节点
            File[] files = file.listFiles();
            JstreeNode jstreeNode;
            for (int i = 0; i < files.length; i++) {
                //目录
                if (files[i].isDirectory()) {
                    jstreeNode = new JstreeNode();
                    jstreeNode.setId(relativePath+files[i].getName() + "/");
                    jstreeNode.setText(files[i].getName());
                    jstreeNode.setHasChildren(true);
                    jstreeNode.setType(GlobalConfig.TYPE_FLODER);
                    list.add(jstreeNode);
                }
                //文件
                else {
                    jstreeNode = new JstreeNode();
                    jstreeNode.setId(relativePath+files[i].getName());
                    jstreeNode.setText(files[i].getName());
                    jstreeNode.setHasChildren(false);
                    jstreeNode.setType(GlobalConfig.TYPE_FILE);
                    list.add(jstreeNode);
                }
            }
            return list;
        } catch (Exception e) {
            // 请求失败时打印的异常的信息
            e.printStackTrace();
        }
        return null;
    }

    /**
     * NIO方式读取file文件为byte[]
     *
     * @param filename 文件名,要求包含文件绝对路径
     * @return 文件的byte[]形式
     * @throws IOException
     */
    public byte[] toByteArray(String filename) throws IOException {
        File file = new File(filename);
        /*
        Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。
        在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。
        FileChannel实例的size()方法将返回该实例所关联文件的大小。
         */
        FileChannel channel = null;
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            channel = fileInputStream.getChannel();
            //所分配的ByteBuffer的容量
            ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
            /*
            FileChannel.read()方法。该方法将数据从FileChannel读取到Buffer中。read()方法返回的int值表示了有多少字节被读到了Buffer中。
            如果返回-1,表示到了文件末尾。
             */
            while ((channel.read(byteBuffer)) > 0) {
                // do nothing
                // System.out.println("reading");
            }
            return byteBuffer.array();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

工具类,ZipUtils.java:

import com.demo.fileTree.model.FileHandleResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 文件或者文件夹的压缩和解压缩,详细看java核心技术卷II,P27,
 * 注意,如果是更新项目,要将原来文件夹及文件夹中的内容全部删除,重新生成UUID及文件夹,在这里由于没有到数据库,就不执行这一步了
 *
 * @author xie
 * @version 1.0
 * @Date 2017/5/30
 */
@Service
public class ZipUtils {

    /** 头像图片的放置路径*/
    @Value("${zipPath.home}")
    private String ZipDir;

    /**
     * 获得图片存储路径
     * @return
     */
    public String getZipDir(){
        return ZipDir;
    }

    /**
     * 压缩文件-由于out要在递归外调用,所以封装一个方法
     * 压缩后的压缩文件的路径和命名,比如 File zipFile = new File("C:/home/myblog/project/32位UUID/test.zip"),
     *                 但注意解压缩后的文件夹的名字与压缩文件的名字不一定相同,test.zip只是压缩包的名字,
     *                 在这里我们将test.zip设为fileName.zip,放在32位UUID目录下面,和解压后的项目相同层次,
     *                 下载完成后也不删除,防止多人下载,服务器每次都要压缩文件
     *
     * @param filePath 要压缩的项目的路径
     * @throws IOException
     * @return FileHandleResponse 表示压缩结果实体对象
     */
    public static FileHandleResponse zipFiles(String filePath) throws IOException{
        FileHandleResponse fileHandleResponse = new FileHandleResponse();

        //将压缩文件和原项目放到相同目录下,并且相同命名,除了压缩文件以.zip结尾,但注意filePath以/结尾,要处理一下
        File zipFile = new File(filePath.substring(0,filePath.length()-1)+".zip");
        File noZipFile = new File(filePath);
        if(zipFile.exists()){
            fileHandleResponse.setMessage("压缩文件存在");
        }else if(!noZipFile.exists()){
            fileHandleResponse.setSuccess(0);
            fileHandleResponse.setMessage("请求文件夹或文件不存在!");
            return fileHandleResponse;
        }else{
            try {
                //创建一个将压缩数据写出到指定的OutputStream的ZipOutputStream
                ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
                zipFiles(zipOutputStream, "", noZipFile);
                zipOutputStream.close();
                System.out.println("*****************压缩完毕*******************");
                fileHandleResponse.setMessage("压缩成功");
            } catch (Exception e) {
                fileHandleResponse.setSuccess(0);
                fileHandleResponse.setMessage("服务器异常");
                e.printStackTrace();
                return fileHandleResponse;
            }
        }
        fileHandleResponse.setSuccess(1);
        fileHandleResponse.setUrl(zipFile.getAbsolutePath());
        return fileHandleResponse;
    }

    /**
     * 压缩文件,
     * 如果是目录,则对目录里的文件重新调用ZipFiles方法,一级目录一级目录的压缩
     *
     * @param zipOutputStream 压缩文件输出流
     * @param fileParentPath 压缩文件的上级目录
     * @param srcFiles 要压缩的文件,可以压缩1到多个文件,通过写数组的方式或者一个个写到参数列表里面
     */
    public static void zipFiles(ZipOutputStream zipOutputStream,String fileParentPath,File... srcFiles){
        //将目录中的1个或者多个置换为/,因为在windows目录下,以或者\为文件目录分隔符,linux却是/
        if(fileParentPath!=""){
            fileParentPath = fileParentPath.replaceAll("\+", "/");
            if(!fileParentPath.endsWith("/")){
                fileParentPath+="/";
            }
        }
        byte[] bytes = new byte[4096];
        try {
            /*
            希望放入zip文件的每一项,都应该创建一个ZipEntry对象,然后将文件名传递给ZipEntry的构造器,它将设置文件日期,解压缩方法等参数,
            并且需要调用putNextEntry方法来开始写出新文件,并将文件数据放松到zip流中,当完成时,需要调用closeEntry方法。所有文件都重复这一过程。
             */

            for(int i=0;i<srcFiles.length;i++){
                //对于目录,递归
                if(srcFiles[i].isDirectory()){
                    File[] files = srcFiles[i].listFiles();
                    String srcPath = srcFiles[i].getName();
                    srcPath = srcPath.replaceAll("\+", "/");
                    if(!srcPath.endsWith("/")){
                        srcPath+="/";
                    }
                    zipOutputStream.putNextEntry(new ZipEntry(fileParentPath+srcPath));
                    zipFiles(zipOutputStream,fileParentPath+srcPath,files);
                }
                //对于文件,发送到ZIP流中,利用4KB的缓冲区,可以考虑使用BufferedInputStream()流过滤器
                else{
                    FileInputStream fileInputStream = new FileInputStream(srcFiles[i]);
                    zipOutputStream.putNextEntry(new ZipEntry(fileParentPath + srcFiles[i].getName()));
                    int len;
                    while((len=fileInputStream.read(bytes))>0){
                        zipOutputStream.write(bytes,0,len);
                    }
                    zipOutputStream.closeEntry();
                    fileInputStream.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解压文件到指定目录
     * @param unZipPath 解压路径,比如C:\home\myblog\project\
     * @param fileName 解压后的文件名,一般命名为项目名,强制要求用户输入,并且保证不为空,
     *                 fileName的上层目录为一个随机生成的32位UUID,以保证项目名重复的依然可以保存到服务器
     * @param multipartFile 上传压缩文件
     *
     * @return FileHandleResponse 表示上传结果实体对象
     */
    @SuppressWarnings("rawtypes")
    public static FileHandleResponse unZipFiles(String unZipPath, String fileName, MultipartFile multipartFile)throws IOException{
        FileHandleResponse fileHandleResponse = new FileHandleResponse();

        String unZipRealPath = unZipPath +UUID.randomUUID().toString().replaceAll("-", "")+ "/"+fileName + "/";
        //如果保存解压缩文件的目录不存在,则进行创建,并且解压缩后的文件总是放在以fileName命名的文件夹下
        File unZipFile = new File(unZipRealPath);
        if (!unZipFile.exists()) {
            unZipFile.mkdirs();
        }
        //ZipInputStream用来读取压缩文件的输入流
        ZipInputStream zipInputStream = new ZipInputStream(multipartFile.getInputStream());
        //压缩文档中每一个项为一个zipEntry对象,可以通过getNextEntry方法获得,zipEntry可以是文件,也可以是路径,比如abc/test/路径下
        ZipEntry zipEntry;
        try {
            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                String zipEntryName = zipEntry.getName();
                //将目录中的1个或者多个置换为/,因为在windows目录下,以或者\为文件目录分隔符,linux却是/
                String outPath = (unZipRealPath + zipEntryName).replaceAll("\+", "/");
                //判断所要添加的文件所在路径或者
                // 所要添加的路径是否存在,不存在则创建文件路径
                File file = new File(outPath.substring(0, outPath.lastIndexOf('/')));
                if (!file.exists()) {
                    file.mkdirs();
                }
                //判断文件全路径是否为文件夹,如果是,在上面三行已经创建,不需要解压
                if (new File(outPath).isDirectory()) {
                    continue;
                }

                OutputStream outputStream = new FileOutputStream(outPath);
                byte[] bytes = new byte[4096];
                int len;
                //当read的返回值为-1,表示碰到当前项的结尾,而不是碰到zip文件的末尾
                while ((len = zipInputStream.read(bytes)) > 0) {
                    outputStream.write(bytes, 0, len);
                }
                outputStream.close();
                //必须调用closeEntry()方法来读入下一项
                zipInputStream.closeEntry();
            }
            zipInputStream.close();
            fileHandleResponse.setSuccess(1);
            fileHandleResponse.setMessage("解压完毕");
            fileHandleResponse.setUrl((unZipRealPath).replaceAll("\+", "/"));
            System.out.println("******************解压完毕********************");

        } catch (Exception e) {
            fileHandleResponse.setSuccess(0);
            fileHandleResponse.setMessage("服务器异常");
            e.printStackTrace();
            return fileHandleResponse;
        }
        return fileHandleResponse;
    }
}

对应的model层,FileHandleResponse.java:

/**
 * 文件处理后回显提示的实体类
 *
 * @author xie
 * @version 1.0
 * @Date 2017/5/25
 */
public class FileHandleResponse {
    /** 上传状态,0:失败,1:上传成功 */
    private int success;

    /** 图片上传提示信息,包括上传成功或上传失败及错误信息等 */
    private String message;

    /** 图片上传成功后返回的地址 */
    private String url;

    public int getSuccess() {
        return success;
    }

    public void setSuccess(int success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

}

JstreeNode.java

/**
 * Jstree节点实体
 *
 * @author xie
 * @version 1.0
 * @Date 2017/5/31
 */
public class JstreeNode {
    /** id并没有实际的意义,仅仅用于唯一标识节点,为了掌握节点之间的上下级关系,我们将id设为节点对file-path的相对路径 */
    private String id;

    /** 节点的显示名字,我们设为文件名 */
    private String text;

    /** 节点是否有孩子节点 */
    private boolean hasChildren;

    /** 节点类型,即文件还是文件夹,设置文件夹为0,文件为1 */
    private int type;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public boolean isHasChildren() {
        return hasChildren;
    }

    public void setHasChildren(boolean hasChildren) {
        this.hasChildren = hasChildren;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }
}
原文地址:https://www.cnblogs.com/xzwblog/p/6928371.html