部署異步下載服務

異步下載

一、背景

  • 目前系統對於大文件的下載慢、導出慢、大量的接口占用服務器帶寬等問題,嚴重影響用戶的體驗,基於此背景,開發並實現了異步下載功能。

二、項目結構

  • 腦圖思路

三、環境準備

  • 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

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