部署异步下载服务

异步下载

一、背景

  • 目前系统对于大文件的下载慢、导出慢、大量的接口占用服务器带宽等问题,严重影响用户的体验,基于此背景,开发并实现了异步下载功能。

二、项目结构

  • 脑图思路

三、环境准备

  • 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章