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

摘要:
$.load(function(){//当鼠标移出输入框$('#fileName').on('blur',function()){varfileName=document.getElementById(”fileName“).value;if(fileName==''){$('.fileName-check').html('');$('fileName-check').append(”请输入项目名称!“);}否则{varfile=$.val();varlen=file.length;varext=file.substring.toLowerCase();ifalert;}});$。在('click',function(){varform=document.getElementById(“zipForm”);if(document.getElement ById(”file name“).value==''){//当项目名称为空时,发出警告(“请输入项目名称!”);returnfalse;}如果{//当项目为空时发出警报(“请上载项目!”);returnfalse;}varformdata=newFormData;$。Ajax({url:“/admin/file/zip/upload”,data:formdata,type:“post”,//预期服务器返回的数据类型将自动解析json的返回值。如果未设置,则可能执行oResult=json.parse(oResult);parse dataType:“json”,//默认值为true。默认情况下,通过数据选项传入的数据(如果是对象)//将被处理并转换为查询字符串,以匹配默认内容类型“application/x-www-form-urlencoded”file path=“+oResult.url;}else{alert;}})//。完成。失败;})});预览自动解压缩文件夹的html代码。使用速度模板渲染引擎:˂!
java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载

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

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

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

总体项目目录结构:
java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载第1张

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

点击提交后:
java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载第3张
并提供下载功能

1. 分析代码

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

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

</body>
<script src="http://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="http://t.zoukankan.com/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="http://t.zoukankan.com/";
//                    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="http://t.zoukankan.com/css/editormd.min.css">
    <link rel="stylesheet" type="text/css" href="http://t.zoukankan.com/css/style.css">
</head>
<!-- 禁用复制粘贴-->
<body oncontextmenu=self.event.returnValue=false onselectstart="return false">
<!-- 搜索表单-->
<form   class="search">
    <input type="search"   />
    <button type="submit">Search</button>
</form>
<!-- 下载按钮-->
<div class="action">
    <input type="hidden"   value="$!{filePath}">
    <a href="http://t.zoukankan.com/admin/file/zip/download?file-path=$!{filePath}">下载</a>
</div>
<!-- 放JStree目录树-->
<div   class="side-nav"></div>
<!-- 放editor.md文本-->
<div   class="markdown-text"></div>
<!-- 放图片-->
<div   class="image-panel"></div>
</body>
<!--jstree官网https://github.com/vakata/jstree#readme-->
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.3/themes/default/style.min.css" />
<script src="http://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.3/jstree.min.js"></script>
<script src="http://t.zoukankan.com/js/file-node.js"></script>

<script src="http://t.zoukankan.com/js/editormd.min.js"></script>
##支持markdown快速解析
<script src="http://t.zoukankan.com/lib/marked.min.js"></script>
##支持代码高亮
<script src="http://t.zoukankan.com/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 + "& /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   style="display:none;"></textarea>删除,那么每次更新前都需要添加
                            document.getElementById("markdown-editor").innerHTML='<textarea   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     src="https://tool.4xseo.com/article/231203.html">';
                document.getElementById("img-circle").src = "http://t.zoukankan.com/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;
    }
}

免责声明:文章转载自《java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇在a:hover中改变子标签内样式的注意使用phpMyAdmin批量修改Mysql数据表前缀的方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android 上的 10 款 Web 开发工具推荐

作为一个狂热的 Android 用户,我一直在寻找适合Web开发人员使用的一些应用程序。经过搜索发现能够找到Web开发几乎每个方面的工具,涉及从Web服务器本身到FTP客户端再到一个源代码查看器,以及网站分析工具等等。以下推荐10款非常实用的Android端的Web开发工具。1.AndFTPAndFTP 是 Android 手机上的支持 FTP、SFTP、...

jsTree通过AJAX从后台获取数据

页面代码: <div id="MenuTree"></div> javascript代码: $(document).ready(function($) { InitMenuTree(); }); functionInitMenuTree() { $('#MenuTree').data('jstree', false);...

vue-quill-editor 封装成组件;图片文件流上传;同一页面多个编辑器样式异常解决办法

使用方法: 引入并注册组件,然后直接使用; @getcode是同步获取编辑器内容的;:contentDefault是编辑器的默认内容; 注意:如果同一个页面多个编辑器,参数id不能相同,否则只有第一个编辑器有样式; 封装组件: 工具栏可以自己添加配置,看自己项目需求 <template> <div class="quill_bo...

vue 富文本编辑器 项目实战用法

1.挑个富文本编辑器 首先针对自己项目的类型,确定自己要用啥编辑器。 1.1 wangeditor 如果一般类似博客这种项目不需要花里胡哨的,功能也不要求贼多的,推荐一下wangeditor(点击跳转)。能覆盖基本上所有的常见操作,轻量化,开源,有中文文档。 ▽wangeditor效果图  1.2 tinyMCE 如果需要复杂的编辑器,推荐tinyMCE...

viscode 使用 格式的配置

viscode 现在也越来越适用于 python 开发使用的 IDEA ,慢慢不逊色于 pycharm 。下面是关于使用的 格式【字体颜色,背景之类的配置】 1. 2. 如果是设置的 中文显示,在界面上是一个个 点击配置的选项, viscode 是采取 settings.json 配置文件进行格式配置的。 点击 此图标,转到 文件格式。 具体想配置什么...

VSCode拓展插件推荐

大牛VSCode拓展插件推荐 https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2FvarHarrie%2FDawn-Blossoms%2Fissues%2F10   1、配置:个人的VSCode首选项配置(文件-首选项-设置)     { "editor.tabSize": 2, "file...