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;
}
}