👨🎓作者:bug菌 ✏️博客:CSDN、掘金等 💌公众号:猿圈奇妙屋 🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。 🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。
一、前言🔥
接下来的这几期,bug菌想跟大家分享一下自己昨天刚接到一个临时的需求,热乎着呢,想分享一下自己是如何面对临时需求并制定整个开发周期,其中包括从梳理业务到创建业务表再到实现业务逻辑形成闭环再到与前端对接,其中会穿插一些业务拓展及功能性拓展,这一条龙流程在线与大家一起见证,分享给刚入门的小伙伴,希望对你们有所帮助。
环境说明:idea2019.3 + springboot2.3.1.REALSE + mybati-plus3.2.0 + mysql5.6 + jdk1.8
二、需求描述🔥
此需求完全是针对管理员个人而开放的,需求方要求能将所有人的反馈文件导出到一个指定的文件夹中,并且最好是能导出一个.zip的压缩包,这样就方便它挨个挨个浏览查阅,也方便运维人员针对文件进行备份存档。
我一听,这其实也是io操作的一种,虽然不是很常用,但是基本想实现该需求,也是简单的为此,我还是基于文件流的写法来逐一实现如何将批量实现文件的zip压缩,如果你也遇到的了这个需求并且没有啥思路,不用担心,你接下来只需要根据我写的实现逻辑,即可轻松带你解决你的需求问题,如果你是想接触了解,我写的也是非常详细,实现及测试,就地解决你的一切阅读所带来的不便。
接下来,废话不多说,直接上代码。
三、代码实现🔥
1️⃣定义Controller请求
首先我们先定义个接口请求,子路径名顾名思义,就是最好定义为能够见名知意的接口路径名,比如我这该需求是直接将图片导出,那我直接定义为export-questions-images即可。
/**
* 所有问题反馈截图导出成zip(压缩包)
*/
@GetMapping("/export-questions-images")
@ApiOperation(value = "所有问题反馈截图导出成zip(压缩包)", notes = "所有问题反馈截图导出成zip(压缩包)")
public void exportQuestionsImages(HttpServletResponse response)
2️⃣定义接口方法solveQuestion()
/**
* 所有问题反馈截图导出成zip(压缩包)
*/
void exportQuestions(HttpServletResponse response);
3️⃣实现exportQuestions()方法
如下是核心实现方法,具体实现思路就是,进行了两次文件压缩,具体操作就是:先是对完整的个人文件夹进行分类,然后将对于子文件的文件添加进子文件夹中,然后遍历对每一个子文件夹进行压缩,然后再将所有的压缩包存放到一个父文件夹中,接着对父文件夹进行压缩,最后将父压缩包导出即可。
涉及所有代码,若是有不清楚的地方,麻烦多研究几遍,我相信,你一定能学的明白的。
/**
* 所有问题反馈截图导出成zip(压缩包)
*/
@Override
public void exportQuestions(HttpServletResponse response){
//1、判断父级目录是否存在
File rootPath = new File(questionRootPath);
if (!rootPath.exists()) {
//创建父目录文件夹
rootPath.mkdirs();
}
//2、查询问题反馈表中有上传过截图的数据
List<UserQuestionsEntity> questions = userQuestionsService.getQuestions();
//3、一次压缩
//先将所有人的截图放到同一文件夹中并压缩
questions.forEach(p -> {
//子文件夹命名
String userId = p.getCreatorAccountId();
//获取截图父级目录
String parentPath = questionImg + userId;
//将多路径转成图片数组
String[] paths = p.getFilePaths().split(",");
// 创建临时路径,存放压缩文件
String zipFilePath = rootPath + "/" + userId + ".zip";
//压缩输出流,包装流,将临时文件输出流包装成压缩流,将所有文件输出到这里,打成zip包
try {
ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath));
// 循环调用压缩文件方法,将一个一个需要下载的文件打入压缩文件包
for (String path : paths) {
//拼接文件全路径
String imagePath = parentPath + "/" + path;
//判断文件是否存在
File file = new File(imagePath);
if (!file.exists()) {
continue;
}
try {
//将文件添加到指定的压缩包中
uploader.fileToZip(imagePath, zipOut);
} catch (IOException e) {
e.printStackTrace();
}
}
// 压缩完成后,关闭压缩流
zipOut.close();
} catch (IOException e) {
e.printStackTrace();
}
});
//4、二次压缩
//二次压缩(压缩父级目录)
String downloadPath = rootPath + "/" + "问题反馈截图.zip";
try {
ZipOutputStream zipOut1 = new ZipOutputStream(new FileOutputStream(downloadPath));
//遍历获取所有的父级目录
questions.forEach(p -> {
//拼接父级目录
String path = rootPath + "/" + p.getCreatorAccountId() + ".zip";
//获取存放文件的跟路径
try {
uploader.fileToZip(path, zipOut1);
} catch (IOException e) {
e.printStackTrace();
}
});
// 压缩完成后,关闭压缩流
zipOut1.close();
//拼接下载默认名称并转为uft-8格式
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("在线问题反馈截图.zip", StandardCharsets.UTF_8.name()));
//该流不可以手动关闭,手动关闭下载会出问题,下载完成后会自动关闭
ServletOutputStream outputStream = response.getOutputStream();
FileInputStream inputStream = new FileInputStream(downloadPath);
// copy方法为文件复制,在这里直接实现了下载效果
IOUtils.copy(inputStream, outputStream);
} catch (IOException e) {
e.printStackTrace();
}
//下载完成之后,本地删掉这个zip
File fileTempZip = new File(downloadPath);
fileTempZip.delete();
}
4️⃣实现getQuestions()方法
如下是确定数据来源,将图片地址不为空的数据查询出来。
/**
* 查询已上传过截图的问题反馈
*/
List<UserQuestionsEntity> getQuestions();
/**
* 查询已上传过截图的问题反馈
*/
@Override
public List<UserQuestionsEntity> getQuestions() {
LambdaQueryWrapper<UserQuestionsEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.isNotNull(UserQuestionsEntity::getFilePaths);//只查询截图路径不为空的数据
return this.list(wrapper);
}
5️⃣实现文件写入压缩包方法fileToZip()
如下是实现单个文件被压缩成zip的功能方法。
/**
* 将一个文件写入压缩包(一次只压缩一次)
*
* @param filePath 文件路径
* @param
public void fileToZip(String filePath, ZipOutputStream zipOut) throws IOException {
// 需要压缩的文件
File file = new File(filePath);
// 获取文件名称,如果有特殊命名需求,可以将参数列表拓展,传fileName
String fileName = file.getName();
FileInputStream fileInput = new FileInputStream(filePath);
// 缓冲
byte[] bufferArea = new byte[1024 * 10];
BufferedInputStream bufferStream = new BufferedInputStream(fileInput, 1024 * 10);
// 将当前文件作为一个zip实体写入压缩流,fileName代表压缩文件中的文件名称
zipOut.putNextEntry(new ZipEntry(fileName));
int length = 0;
// 最常规IO操作,不必紧张
while ((length = bufferStream.read(bufferArea, 0, 1024 * 10)) != -1) {
zipOut.write(bufferArea, 0, length);
}
//关闭流
fileInput.close();
// 需要注意的是缓冲流必须要关闭流,否则输出无效
bufferStream.close();
// 压缩流不必关闭,使用完后再关
6️⃣定义全局路径配置
yaml文件配置:
#自定义配置项
review:
#文件存放地址
file:
question-zip-path: ./template/question-zip/
question-img: ./template/question-img/
获取方式:
@Value("${review.file.question-zip-path}")
private String questionRootPath;
@Value("${review.file.question-img}")
private
7️⃣涉及存放地址展示
如下是我直接存放在项目根目录下的template文件夹下。
大概你们可以参考一下,观摩我的代码然后对照该目录结构,这样你们方便理解。
四、测试🔥
接下来,我们就对该接口进行测试,由于我是将该接口加入了token白名单,所以我们就不需要通过设置接口请求头了。我们只需要在浏览器输入完整访问地址即可,
比如如下演示:
输入地址后,我们直接浏览器回车,我们可以看看到浏览器左下角会弹出一个xxx.zip的压缩包下载,这就证明我们起码成功了一半。
接下来,我们再检查一下,具体的文件夹子个数及子文件夹具体images数量,核实一下是否与数据库数据一致?经我查验,都是完整导出完好无损的。
正常给大家看下我后台查询数据所存储数据库的原本记录格式吧。也方便大家核对子文件压缩包数量是否一致。
具体给大家看一眼,对于admin该条记录而言,该用户是共上传了两个截图,所以在我们的导出包中对于admin.zip目录里应该就是对于的这两xxx.jpg图片才是,我给大家打开核实一下。
大家请看:
最后看下控制台,是否有导出异常信息?很正常,除了查询接口sql打印无其他打印内容,证明代码导出不存在显性问题,大家可以正常拿去使用借鉴啦。
好啦,以上就是这期的所有内容啦,你们学废了么?