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="```\n"+oResult.fileContent+"\n```";
//用于将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:\home\myblog\project\2d76c7aa844b4585a53d982d205099e2\123\其中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:\home\myblog\project\2d76c7aa844b4585a53d982d205099e2\123\,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="\n";
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:\home\myblog\project\2d76c7aa844b4585a53d982d205099e2\123.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;
}
}