异步下载
一、背景
- 目前系统对于大文件的下载慢、导出慢、大量的接口占用服务器带宽等问题,严重影响用户的体验,基于此背景,开发并实现了异步下载功能。
二、项目结构
- 脑图思路
三、环境准备
- maven依赖
<!-- zip加密 -->
<dependency>
<groupId>net.lingala.zip4j</groupId>
<artifactId>zip4j</artifactId>
<version>2.5.2</version>
</dependency>
<!-- 谷歌图片压缩 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.11</version>
</dependency>
四、具体实现
- 异步下载源码
/**
* 异步下载:下载文件到新建文件夹中-->压缩文件夹-->删除文件夹
*
* @author mc
* @date 2020-05-18 17:50
**/
@ResponseBody
@PostMapping("/asyDown")
@ApiOperation(value = "异步下载", notes = "下载文件到新建文件夹中-->压缩文件夹-->删除文件夹")
public Result<String> asynchronousDownload(@RequestBody List<String> list) throws InterruptedException{
if (CollectionUtils.isEmpty(list)) {
return Result.genFailResult("获取文件下载地址为空!");
}
// 每次请求输出一个新文件夹 例:20200520052021
String folder = DateUtil.dateToStr(new Date(), DateUtil.TIMEFORMAT3) + RandomUtil.randomNumbers(2);
// 生产服务器文件目录
String[] filePath = {path + folder + "/"};
File file = new File(filePath[0]);
// 获取oss下载token
String token = ossUtil.getOssToken();
list.forEach(x -> {
// 利用线程池批量下载
threadPoolExecutor.execute(() -> {
log.warn("开始执行 --> {}", Thread.currentThread().getName());
downloadFile(token,x,file,filePath);
});
});
// 压缩文件夹(随机生产4位数密码)---->zip文件
String pw = RandomUtil.randomString(4);
String zipPath = filePath[0].substring(0, filePath[0].length() - 1) + ".zip";
threadPoolExecutor.shutdown();
while(true){
// 所有的子线程都结束进行文件压缩
if(threadPoolExecutor.isTerminated()){
FileUtils.encryptZip(file, zipPath, pw);
// 删除文件夹
FileUtils.delete(file);
break;
}
Thread.sleep(1000);
}
return Result.genSuccessResult("保存成功!", pw);
}
/**
* 循环下载文件体
* @param token 请求oss token
* @param x 下载地址
* @param file 文件夹目录
* @param filePath 文件目录
*/
private void downloadFile(String token, String x, File file, String[] filePath) {
try {
// 从oss服务器单张下载图片到Linux服务器端
Result<byte[]> ossResult = ossUtil.downLoadFile(token, x);
if (!ossResult.getFlag()) {
return;
}
// 重新定义文件名称 例:17213943.jpg
String fileName = DateUtil.dateToStr(new Date(), DateUtil.TIMEFORMATCl) + RandomUtil.randomNumbers(2) + x.substring(x.lastIndexOf("."));
// 判断文件夹是否存在,如果文件夹不存在,则创建新的的文件夹
FileUtil.mkdir(file);
// 写入到文件(注意文件保存路径的后面一定要加上文件的名称)
FileOutputStream fileOut = new FileOutputStream(filePath[0] + fileName);
BufferedOutputStream bos = new BufferedOutputStream(fileOut);
// 保存文件
bos.write(ossResult.getData());
fileOut.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
- Zip加密压缩
/**
* 将指定路径下的文件压缩至指定zip文件,并以指定密码加密,若密码为空,则不进行加密保护
*
* @param file 待压缩文件夹
* @param pathFile 压缩后路径+文件名
* @param passWord 加密密码
* @author mc
* @date 2020-05-20 17:21
**/
public static void encryptZip(File file, String pathFile, String passWord) {
try {
ZipFile zipFile = new ZipFile(pathFile);
ZipParameters parameters = new ZipParameters();
// 密码不为空设置加密
if (StringUtils.isNotBlank(passWord)) {
// 设置密码
zipFile.setPassword(passWord.toCharArray());
// 加密方式
parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
parameters.setEncryptFiles(true);
}
zipFile.addFolder(file, parameters);
} catch (ZipException e) {
log.error(e.getMessage());
throw new RuntimeException("failed to compress file, specific reason" + e.getMessage(), e);
}
}
- 文件夹递归删除
/**
* 清空文件夹
*
* @param directory 文件夹
* @return boolean
*/
private static boolean clean(File directory) throws IORuntimeException {
if (directory == null || !directory.exists() || !directory.isDirectory()) {
return true;
}
// 获取所有子文件目录
final File[] files = directory.listFiles();
if (null == files) {
return true;
}
// 遍历所有子文件
for (File childFile : files) {
// 删除一个出错则本次删除任务失败
if (!childFile.delete()) {
return false;
}
}
return true;
}
/**
* 删除文件/某个文件删除失败会终止删除操作
*
* @param file 文件对象
* @return boolean
*/
public static boolean delete(File file) throws IORuntimeException {
// 如果文件不存在或已被删除,此处返回true表示删除成功
if (file == null || !file.exists()) {
return true;
}
if (file.isDirectory()) {
// 清空目录下所有文件和目录
if (!clean(file)) {
return false;
}
}
// 删除文件或清空后的目录
return file.delete();
}
- 图片流压缩
/**
* 按照scale比例压缩图片
* @author mc
* @date 2020-05-21 09:21
* @param picByte 图片输入流
* @param suffix 图片后缀默认jpg
* @return 图片输出流
**/
private static byte[] decompressPicByte(byte[] picByte, String suffix) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(picByte);
Thumbnails.Builder<? extends InputStream> builder = Thumbnails.of(inputStream).scale(0.5);
try {
BufferedImage bufferedImage = builder.asBufferedImage();
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, suffix, bao);
return bao.toByteArray();
} catch (IOException e) {
log.error(e.getMessage());
}
return picByte;
}
- 零拷贝
五、服务器
- Nginx配置
server {
listen 80;
server_name local;
add_header 'Access-Control-Allow-Origin' *;
add_header 'Access-Control-Allow-Credentials' true;
location / {
root /文件存放地址/;
}
#error_page 404 /404.html;
}
- Cronntab定时任务
# 删除2小时之前的文件
find /文件地址/ -type f -mmin +120 -exec rm {} ;
# 创建shell脚本
touch remove.sh
vim remove.sh
chmod 777 remove.sh
# 编辑crontab脚本 1小时执行一次
crontab -e
0 */1 * * * sh /shell路径地址/remove.sh > /dev/null 2>&1
# 查看crontab脚本列表
crontab -l
五、注意事项
-
包路径规范: `com.公司名称.部门名称.项目名称
包路径一定要改掉!一定改掉!不要跟别的项目重名!
-
配置文件编码 必须为 UTF-8
来源:oschina
链接:https://my.oschina.net/u/3775800/blog/4291838