文件批量下载,根据文件类型名称及结构创建文件夹,最终转成压缩包以供下载
需求:单独下载:先将文章转为word后,与附件同时下载,下载后按照该文件的分类,创建相应的文件夹,(如果有多个顶级文件夹)最后将同类顶级文件夹共同存放在一个根文件夹下,再压缩成一个压缩包下载
批量下载:选中多个文件后,一键下载所有word文档与附件,所有的文件按照分类目录结构放置,最终下载一个压缩包
一、分析
实现上述需求,我们需要考虑一下几个必要的因素
1、正文转成word文档
2、创建和删除文件夹
3、文件和文件夹的复制粘贴
4、打压缩包
5、java的下载方法
6、路径!路径!路径!(很容易把自己绕晕)
二、代码实现
为了方便,也为了代码规范,先将正文转word文档、文件和文件夹的复制粘贴、删除文件夹等方法封装成util工具。话不多说上代码~~
1、正文转word的方法
package com.chixin.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
public class ToDocUtil {
//创建.doc后缀的word
public static void main(String[] args) {
String path = "E:\\AAAAA";
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
String fileName = "哎呦不错哦.doc";
File f = new File(path+"\\"+fileName);
if (!f.exists()) {
try {
f.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//createWord(path,fileName);
//writeDataDocx
String data = "123";
String filePath = path+"\\"+fileName;
writeDataDocx(filePath,data,true,12);
}
public static void createWord(String path, String fileName) {
//判断目录是否存在
File file = new File(path);
//exists()测试此抽象路径名表示的文件或目录是否存在。
//mkdir()创建此抽象路径名指定的目录。
//mkdirs()创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
if (!file.exists()) file.mkdirs();
//因为HWPFDocument并没有提供公共的构造方法 所以没有办法构造word
//这里使用word2007及以上的XWPFDocument来进行构造word
@SuppressWarnings("resource")
XWPFDocument document = new XWPFDocument();
OutputStream stream = null;
try {
stream = new FileOutputStream(new File(file, fileName));
document.write(stream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (stream != null) ;
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//向word中写入数据
/**
* 有些方法需要传特殊类型的参数的时候,一般可以用★静态的接口.参数★来传参
*
* @param path
* @param data
*/
public static void writeDataDocx(String path, String data, boolean jiacu, int size) {
InputStream istream = null;
OutputStream ostream = null;
try {
istream = new FileInputStream(path);
ostream = new FileOutputStream(path);
@SuppressWarnings("resource")
XWPFDocument document = new XWPFDocument();
//添加一个段落
XWPFParagraph p1 = document.createParagraph();
XWPFRun r1 = p1.createRun();//p1.createRun()将一个新运行追加到这一段
r1.setText(data);
r1.setBold(jiacu);//---"加黑加粗"
r1.setFontSize(size);//---字体大小
document.write(ostream);
System.out.println("创建word成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (istream != null) {
try {
istream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ostream != null) {
try {
ostream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//向word中写入数据
// public static void writeDataDoc(String path,String data){
// OutputStream ostream=null;
// try {
// ostream = new FileOutputStream(path);
// ostream.write(data.getBytes());
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// } catch (IOException e) {
// e.printStackTrace();
// }finally{
// if(ostream != null){
// try {
// ostream.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }
// }
//读取数据 docx
public static String readDataDocx(String filePath) {
String content = "";
InputStream istream = null;
try {
istream = new FileInputStream(filePath);
@SuppressWarnings("resource")
XWPFDocument document = new XWPFDocument(istream);
//getLastParagraph()返回包含页眉或页脚的文本的段落
//getText()返回文档所有文本
content = document.getLastParagraph().getText();//★★★★★
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (istream != null) {
}
}
return content;
}
}
2、文件和文件夹的复制粘贴
package com.chixin.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
public class copyFilesUtil {
/**
* 复制单个文件
* @param oldPath String 原文件路径 如:c:/fqf.txt
* @param newPath String 复制后路径 如:f:/fqf.txt
* @return boolean
*/
public void copyFile(String oldPath, String newPath) {
try {
int bytesum = 0;
int byteread = 0;
File oldfile = new File(oldPath);
if (oldfile.exists()) { //文件存在时
InputStream inStream = new FileInputStream(oldPath); //读入原文件
FileOutputStream fs = new FileOutputStream(newPath);
byte[] buffer = new byte[1444];
int length;
while ( (byteread = inStream.read(buffer)) != -1) {
bytesum += byteread; //字节数 文件大小
System.out.println(bytesum);
fs.write(buffer, 0, byteread);
}
inStream.close();
}
}
catch (Exception e) {
System.out.println("复制单个文件操作出错");
e.printStackTrace();
}
}
/**
* 复制整个文件夹内容
* @param oldPath String 原文件路径 如:c:/fqf
* @param newPath String 复制后路径 如:f:/fqf/ff
* @return boolean
*/
public void copyFolder(String oldPath, String newPath) {
try {
(new File(newPath)).mkdirs(); //如果文件夹不存在 则建立新文件夹
File a=new File(oldPath);
String[] file=a.list();
File temp=null;
for (int i = 0; i < file.length; i++) {
if(oldPath.endsWith(File.separator)){
temp=new File(oldPath+file[i]);
}
else{
temp=new File(oldPath+File.separator+file[i]);
}
if(temp.isFile()){
FileInputStream input = new FileInputStream(temp);
FileOutputStream output = new FileOutputStream(newPath + "/" +
(temp.getName()).toString());
byte[] b = new byte[1024 * 5];
int len;
while ( (len = input.read(b)) != -1) {
output.write(b, 0, len);
}
output.flush();
output.close();
input.close();
}
if(temp.isDirectory()){//如果是子文件夹
copyFolder(oldPath+"/"+file[i],newPath+"/"+file[i]);
}
}
}
catch (Exception e) {
System.out.println("复制整个文件夹内容操作出错");
e.printStackTrace();
}
}
public static void main(String[] args)throws Exception {
// //这是你的源文件,本身是存在的
// File beforefile = new File("C:/Users/Administrator/Desktop/Untitled-2.html");
//
// //这是你要保存之后的文件,是自定义的,本身不存在
// File afterfile = new File("C:/Users/Administrator/Desktop/jiekou0/Untitled-2.html");
//
// //定义文件输入流,用来读取beforefile文件
// FileInputStream fis = new FileInputStream(beforefile);
//
// //定义文件输出流,用来把信息写入afterfile文件中
// FileOutputStream fos = new FileOutputStream(afterfile);
//
// //文件缓存区
// byte[] b = new byte[1024];
// //将文件流信息读取文件缓存区,如果读取结果不为-1就代表文件没有读取完毕,反之已经读取完毕
// while(fis.read(b)!=-1){
// //将缓存区中的内容写到afterfile文件中
// fos.write(b);
// fos.flush();
// }
String oldPath="E:\\厨房电器\\";
String newPath="E:\\chixinDownload\\厨房电器2";
copyFilesUtil t=new copyFilesUtil();
t.copyFolder(oldPath, newPath);
}
}
3、删除文件夹
package com.chixin.util;
import java.io.File;
public class FileDleteUtil {
public static void main(String[] args) {
FileDleteUtil fdu = new FileDleteUtil();
fdu.deleteDir("E:\\123");
}
public static boolean deleteDir(String dirPath){
File file = new File(dirPath);
if(file.isFile()){
file.delete();
return true;
}else{
File[] files = file.listFiles();
if(files == null){
file.delete();
return true;
}else{
for (int i = 0; i < files.length; i++){
boolean deleteDir = deleteDir(files[i].getAbsolutePath());
}
file.delete();
return true;
}
}
}
}
4、打压缩包
package com.chixin.util;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipUtils {
private static final int BUFFER_SIZE = 2 * 1024;
/**
* 压缩成ZIP 方法 * @param srcDir 压缩文件夹路径
* @param out 压缩文件输出流
* @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(String srcDir, OutputStream out, boolean KeepDirStructure)
throws RuntimeException{
long start = System.currentTimeMillis();
ZipOutputStream zos = null ;
try {
zos = new ZipOutputStream(out);
File sourceFile = new File(srcDir);
compress(sourceFile,zos,sourceFile.getName(),KeepDirStructure);
long end = System.currentTimeMillis();
System.out.println("压缩完成,耗时:" + (end - start) +" ms");
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtils",e);
}finally{
if(zos != null){
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 压缩成ZIP 方法 * @param srcFiles 需要压缩的文件列表
* @param out 压缩文件输出流
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(List<File> srcFiles , OutputStream out)throws RuntimeException {
long start = System.currentTimeMillis();
ZipOutputStream zos = null ;
try {
zos = new ZipOutputStream(out);
for (File srcFile : srcFiles) {
byte[] buf = new byte[BUFFER_SIZE];
zos.putNextEntry(new ZipEntry(srcFile.getName()));
int len;
FileInputStream in = new FileInputStream(srcFile);
while ((len = in.read(buf)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
}
long end = System.currentTimeMillis();
System.out.println("压缩完成,耗时:" + (end - start) +" ms");
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtils",e);
}finally{
if(zos != null){
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 递归压缩方法
* @param sourceFile 源文件
* @param zos zip输出流
* @param name 压缩后的名称
* @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws Exception
*/
private static void compress(File sourceFile, ZipOutputStream zos, String name,
boolean KeepDirStructure) throws Exception{
byte[] buf = new byte[BUFFER_SIZE];
if(sourceFile.isFile()){
// 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
zos.putNextEntry(new ZipEntry(name));
// copy文件到zip输出流中
int len;
FileInputStream in = new FileInputStream(sourceFile);
while ((len = in.read(buf)) != -1){
zos.write(buf, 0, len);
}
// Complete the entry
zos.closeEntry();
in.close();
} else {
//是文件夹
File[] listFiles = sourceFile.listFiles();
if(listFiles == null || listFiles.length == 0){
// 需要保留原来的文件结构时,需要对空文件夹进行处理
if(KeepDirStructure){
// 空文件夹的处理
zos.putNextEntry(new ZipEntry(name + "/"));
// 没有文件,不需要文件的copy
zos.closeEntry();
}
}else {
for (File file : listFiles) {
// 判断是否需要保留原来的文件结构
if (KeepDirStructure) {
// 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
// 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
compress(file, zos, name + "/" + file.getName(),KeepDirStructure);
} else {
compress(file, zos, file.getName(),KeepDirStructure);
}
}
}
}
}
public static void main(String[] args) throws Exception {
/** 测试压缩方法 */
FileOutputStream fos1= new FileOutputStream(new File("E:/testZip123.zip"));
ZipUtils.toZip("E:/2", fos1,true);
/** 测试压缩方法 */
// List<File> fileList = new ArrayList<>();
// fileList.add(new File("D:/Java/jdk1.7.0_45_64bit/bin/jar.exe"));
// fileList.add(new File("D:/Java/jdk1.7.0_45_64bit/bin/java.exe"));
// FileOutputStream fos2= new FileOutputStream(new File("c:/mytest02.zip"));
// ZipUtils.toZip(fileList, fos2);
}
}
上述四个工具类,直接复制到自己的项目中改个包名即可,里面均附有main()方法,可测试完再去调用。
5、下载
我们的目的是下载一个压缩包,那么从后往前推:
最终下载的压缩包–>最终下载的文件夹–>父文件夹–>子文件夹–>doc文档+附件
捋清楚层级关系后,我们需要一个文章的id来查询正文,需要一个附件的path(路径)来进行附件下载,或者叫复制附件。当然,这是针对单独操作时需要的,那么,如果需要批量下载,怎么办呢,其实也很简单,将我们需要的id和path,用逗号隔开传过来即可。详细过程请看代码:
/**
* 文件(二进制数据)下载
* @param paths 文件路径 多个path之间用逗号隔开
* @param ids 文章id 多个id之间用逗号隔开
* ResponseEntity<byte[]>
* @return
* @throws FileNotFoundException
*/
@ResponseBody
@RequestMapping(value = "/downloadFile" , method = RequestMethod.GET)
public ResponseEntity<byte[]> downloadFile(String ids,String paths,HttpServletRequest request) throws FileNotFoundException{
//文件将保存在contentpath路径之下
paths = "E:\\123.png,E:\\456.png";
String num = UUID.randomUUID().toString().replace("-", "");
String contentpath = ContentUtil.DOWNLOAD_URL+num.substring(0,10);
File fil = new File(contentpath);
if (!fil.exists()) {
fil.mkdirs();
}
System.out.println("最终下载的文件夹已创建成功");
//遍历ids
String str[] = ids.split(",");
String affix[] = paths.split(",");
for (int i = 0; i < str.length; i++) {
String uuid = UUID.randomUUID().toString().replace("-", "");
String url = ContentUtil.DOWNLOAD_URL+uuid.substring(11, 16);
File fi = new File(url);
if (!fi.exists()) {
fi.mkdirs();
}
System.out.println("最终下载的文件夹已创建成功");
// path = "E:\\123.png";
KnowledgeContent bean = service.findById(str[i]);
copyFilesUtil copyutil = new copyFilesUtil();
String title = bean.getTitle();
String fileName = title + uuid.substring(0, 6)+".doc";
//根据id查询正文
String data = bean.getMycontent();
ToDocUtil toDocUtil = new ToDocUtil();
String foo = "";
//根据文件类型创建文件夹放置转好的word文件
KnowledgeContentType tbean = typeservice.findById(bean.getMytype());
String rd = uuid.substring(6,10);
if (!"0".equals(tbean.getSuperid())) {
//如果该文件的分类有父级分类,先用子集的名称创建一个文件夹
String childfolder = url+"\\"+tbean.getName();
File fold = new File(childfolder);
fold.mkdirs();
System.out.println("子文件夹创建成功");
//创建一个新的文件名称用来存放正文
File f = new File(childfolder+"\\"+fileName);
if (!f.exists()) {
try {
f.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//createWord(path,fileName);
//writeDataDocx
String filePath = childfolder+"\\"+fileName;
//将正文写入一个word文档中
toDocUtil.writeDataDocx(filePath,data,true,12);
System.out.println("新的word文件创建成功");
//将新生成的word文件复制到子文件夹下
//copyutil.copyFile(filePath, "E:\\"+tbean.getName()+"\\"fileName);
//将需要下载的附件从path复制在子目录路径之下
copyutil.copyFile(affix[i], childfolder+"\\"+affix[i].substring(affix[i].lastIndexOf("\\")+1));
System.out.println("附件已经成功复制到指定目录");
//在用父类的名称创建一个文件夹,将子集的文件夹复制到新的文件夹中去
String supername = typeservice.findById(tbean.getSuperid()).getName();
System.out.println("supername:"+supername);
foo = rd +"-"+ supername ;
File folds = new File(url+"\\"+foo);
folds.mkdirs();
System.out.println("父文件夹创建成功");
copyutil.copyFolder(childfolder, url+"\\"+foo+"\\"+tbean.getName());
System.out.println("成功将子文件夹复制到父文件夹中了");
//再将父文件夹复制到contentpath路径之下
copyutil.copyFolder(url+"\\"+foo, contentpath+"\\"+foo);
System.out.println("成功所有文件复制到contentpath路径下");
}else {
foo = rd +"-"+ tbean.getName();
File fold = new File(url+"\\"+foo);
fold.mkdirs();
System.out.println("单层文件夹创建成功");
//创建一个新的文件名称用来存放正文
File f = new File(url+"\\"+foo+"\\"+fileName);
if (!f.exists()) {
try {
f.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//createWord(path,fileName);
//writeDataDocx
String filePath = url+"\\"+foo+"\\"+fileName;
//将正文写入一个word文档中
toDocUtil.writeDataDocx(filePath,data,true,12);
System.out.println("新的word文件创建成功");
//将需要下载的附件从path复制在子集文件夹之下
copyutil.copyFile(affix[i], url+"\\"+foo+"\\"+affix[i].substring(affix[i].lastIndexOf("\\")+1));
System.out.println("word文件已经成功复制到指定目录");
//将父文件夹复制到contentpath路径之下
copyutil.copyFolder(url+"\\"+foo, contentpath+"\\"+foo);
}
}
//将contentpath文件夹压缩成zip
ZipUtils ziputil = new ZipUtils();
String newpath = contentpath+".zip";
FileOutputStream fos1= new FileOutputStream(new File(newpath));
ziputil.toZip(contentpath, fos1, true);
System.out.println("最终下载的文件夹已成功打成zip压缩包");
HttpHeaders headers = new HttpHeaders();
ResponseEntity<byte[]> entity = null;
InputStream in=null;
try {
in=new FileInputStream(new File(newpath));
byte[] bytes = new byte[in.available()];
// path是指欲下载的文件的路径。
File file = new File(newpath);
// 取得文件名。
String filename = file.getName();
System.out.println("filename:"+filename);
// 截取文件名。
// String imageName = filename.substring(filename.lastIndexOf("/")+1).toUpperCase();
// System.out.println("imageName:"+imageName);
//处理IE下载文件的中文名称乱码的问题
String header = request.getHeader("User-Agent").toUpperCase();
if (header.contains("MSIE") || header.contains("TRIDENT") || header.contains("EDGE")) {
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", "%20"); //IE下载文件名空格变+号问题
} else {
filename = new String(filename.getBytes(), "iso-8859-1");
}
in.read(bytes);
headers.add("Content-Disposition", "attachment;filename="+filename);
entity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(in!=null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return entity;
}
/*
* 删除容器中指定路径下的文件夹
*/
@RequestMapping(value = "/filedelete" , method = RequestMethod.GET)
public Map<String,Object> filedelete(){
FileDleteUtil fdu = new FileDleteUtil();
fdu.deleteDir(ContentUtil.DOWNLOAD_URL.substring(0, ContentUtil.DOWNLOAD_URL.lastIndexOf("\\")));
return OutgoingUtils.OutgoingParameter("200", "删除成功");
}
以上方法用到很多流的东西,需要import的包很多,这些是本人使用过程中用到的一些包,供大家参考:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
还有需要注意的一个坑,就是缺少jar包的问题~
有三个不太引人注意但还必不可少的jar包如下:
commons-math3-3.6.1.jar
commons-collections4-4.1.jar
commons-compress-1.20.jar
都可去官网免费下载http://commons.apache.org/proper/commons-compress/download_compress.cgi
希望对新猿们有所帮助!