文件打成壓縮包通過(guò)上傳OSS給前端下載功能實(shí)現(xiàn)

1. 需求描述

前端通過(guò)正則識(shí)別出音頻文件URL傳給后端葫盼,后端打成zip文件給前端下載,需要考慮穩(wěn)定性和下載速度瓦灶。

2. 實(shí)現(xiàn)一:直接通過(guò)流讀取壓縮和返回前端

直接通過(guò)流接受并轉(zhuǎn)為 zipOutStream 流寫(xiě)到 response 里給前端鸠删。

/**
    * @param downloadFilename 下載壓縮文件的名稱(chēng)
     * @param downloads        要下載的音頻集合
     * @param response         response
     * @description: 壓縮包文件流下載
     */
public void downloadZip(String downloadFilename, List<AudioDownloadDto> downloads, HttpServletResponse response) {
    if (ListUtils.isEmpty(downloads)) {
        return;
    }
    dealRepeatFileName(downloads);
    ZipOutputStream zos = null;
    try {
        downloadFilename = URLEncoder.encode(downloadFilename, "UTF-8");
        // 指明response的返回對(duì)象是文件流
        response.setContentType("application/octet-stream");
        // 設(shè)置在下載框默認(rèn)顯示的文件名
        response.setHeader("Content-Disposition", "attachment;filename=" + downloadFilename);
        zos = new ZipOutputStream(response.getOutputStream());
        for (AudioDownloadDto download : downloads) {
            packageZipOutPutStream(zos, download);
        }
        zos.finish();
    } catch (Exception e) {
        log.error("下載音頻壓縮包失敗 :{}", e.getMessage());
        throw new ServiceException("下載音頻壓縮包失敗");
    } finally {
        try {
            if (zos != null) {
                zos.close();
            }
        } catch (IOException e) {
            log.error("ZipOutputStream close fail :{}", e.getMessage());
            throw new ServiceException("ZipOutputStream close fail");
        }
    }
}

packageZipOutPutStream 方法:

/**
     * @param zos
     * @param download
     * @return
     * @throws IOException
     * @description: 轉(zhuǎn)為壓縮流
     */
private boolean packageZipOutPutStream(ZipOutputStream zos, AudioDownloadDto download) throws IOException {
    String fileUrl = download.getUrl();
    ZipEntry zipEntry = new ZipEntry(getFileName(download.getQueId(), fileUrl));
    zos.putNextEntry(zipEntry);
    InputStream fis = FileUtils.getInputStreamByFileUrl(fileUrl);
    // 因?yàn)?URL 為 OSS 地址,故此處也可以直接從通過(guò) OSS API 獲取要下載的內(nèi)容(走內(nèi)網(wǎng)的話(huà)其兩種方式的效率差不多)
    /*OSSClient ossClient=  ossUtil.getOSSClient();
    OSSObject ossObject = ossClient.getObject(ossPropResource.getUserStorageOssBucketName(), StringUtils.substringAfterLast(fileUrl, ".com/"));
    InputStream fis = ossObject.getObjectContent();*/
    if (fis == null) {
        return true;
    }
    byte[] buffer = new byte[2048];
    int r;
    while ((r = fis.read(buffer)) != -1) {
        zos.write(buffer, 0, r);
    }
    fis.close();
    //  注意寫(xiě)完一個(gè)文件倚搬,需要關(guān)閉這個(gè)文件冶共,再寫(xiě)下一個(gè)
    zos.closeEntry();
    zos.flush();
    return false;
}

3. 實(shí)現(xiàn)二:先上傳 OSS,把返回的壓縮文件地址給前端

中間不需要落地文件每界,直接通過(guò)流寫(xiě)入 OSS 壓縮文件捅僵,返回oss地址給前端下載,后續(xù)通過(guò)定時(shí)任務(wù)把生成的oss 壓縮文件刪除眨层,節(jié)約oss空間資源

/**
     * @param downloadFilename 下載壓縮文件的名稱(chēng)
     * @param downloads        要下載的音頻集合
     * @return
     * @description: 獲取壓縮包oss地址下載
     */
public String queryDownloadZipOSSUrl(String downloadFilename, List<AudioDownloadDto> downloads) {
    if (ListUtils.isEmpty(downloads)) {
        return null;
    }
    dealRepeatFileName(downloads);
    ZipOutputStream zos = null;
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try {
        zos = new ZipOutputStream(bos);
        for (AudioDownloadDto download : downloads) {
            packageZipOutPutStream(zos, download);
        }
        zos.finish();
        InputStream inputStream = new ByteArrayInputStream(bos.toByteArray());
        String key = UUID.randomUUID().toString().replace("-", "") + Constants.FileSuffix.ZIP;
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType("application/octet-stream");
        metadata.setContentDisposition("attachment;filename=" + downloadFilename);
        ossService.pubObjectStream(ossPropResource.getTiKuBucketName(), SUB_BUCKET_NAME + key, inputStream, metadata);
        // 放入redis隊(duì)列等待定時(shí)任務(wù)刪除
        redisDao.putListObject(Constants.Cache.AUDIO_ZIP_OSS_KEY_LIST, key);
        return ossPropResource.getTiKuBucketEndpoint() + "/" + SUB_BUCKET_NAME + key;
    } catch (Exception e) {
        log.error("獲取音頻壓縮文件的OSS地址失敗 :{}", e.getMessage());
        throw new ServiceException("獲取音頻壓縮文件的OSS地址失敗");
    } finally {
        try {
            if (zos != null) {
                zos.close();
            }
        } catch (IOException e) {
            log.error("ZipOutputStream close fail :{}", e.getMessage());
            throw new ServiceException("ZipOutputStream close fail");
        }
    }
}

涉及到的一些方法:

/**
     * @description: 文件名重復(fù)問(wèn)題
     * @author: zcq
     * @date: 2020/11/27 4:44 下午
     */
public static void dealRepeatFileName(List<AudioDownloadDto> downloads) {
    Set<String> queIdSet = new HashSet();
    for (AudioDownloadDto download : downloads) {
        if (!queIdSet.add(download.getQueId())) {
            // 試題重復(fù)庙楚,通過(guò)增加時(shí)間戳來(lái)去重
            download.setQueId(download.getQueId() + "-" + UUID.randomUUID().toString().replace("-", ""));
        }
    }
}

/**
     * @description: 根據(jù)文件路徑獲取文件的擴(kuò)展名
     * @author: zcq
     * @date: 2020/11/26 6:18 下午
     */
private static String getFileName(String queId, String url) {
    return queId + Constants.Decollator.POINT_DECOLLATOR + StringUtils.substringAfterLast(url, Constants.Decollator.POINT_DECOLLATOR);
}

/**
     * @description: 刪除OSS文件
     * @author: zcq
     * @date: 2020/12/3 3:33 下午
     */
public void delOssFile(final String ossKey) {
    ossService.deleteObject(ossPropResource.getTiKuBucketName(), SUB_BUCKET_NAME + ossKey);
}

4. 總結(jié)

第二種方式為更優(yōu)的選擇,oss通過(guò)內(nèi)網(wǎng)上傳效率很高趴樱,節(jié)省了流給前端傳輸?shù)臅r(shí)間馒闷,并且穩(wěn)定性也更好。其次叁征,下載壓力從服務(wù)器端移到了阿里云oss纳账,降低了服務(wù)器的壓力。

其實(shí)還可以通過(guò)隊(duì)列異步處理下載壓縮請(qǐng)求捺疼。前端請(qǐng)求下載傳url集合疏虫,后端放到隊(duì)列之后直接返回成功(每一次下載任務(wù)生成唯一ID返給前端)。然后后端隊(duì)列任務(wù)異步處理啤呼,前端可以輪詢(xún)獲取拿著唯一ID獲取處理成功返回的URL再去下載卧秘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市官扣,隨后出現(xiàn)的幾起案子翅敌,更是在濱河造成了極大的恐慌,老刑警劉巖惕蹄,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚯涮,死亡現(xiàn)場(chǎng)離奇詭異治专,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)遭顶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)看靠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人液肌,你說(shuō)我怎么就攤上這事∨副酰” “怎么了嗦哆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)婿滓。 經(jīng)常有香客問(wèn)我老速,道長(zhǎng),這世上最難降的妖魔是什么凸主? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任橘券,我火速辦了婚禮,結(jié)果婚禮上卿吐,老公的妹妹穿的比我還像新娘旁舰。我一直安慰自己,他們只是感情好嗡官,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布箭窜。 她就那樣靜靜地躺著,像睡著了一般衍腥。 火紅的嫁衣襯著肌膚如雪磺樱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天婆咸,我揣著相機(jī)與錄音竹捉,去河邊找鬼。 笑死尚骄,一個(gè)胖子當(dāng)著我的面吹牛块差,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乖仇,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼憾儒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了乃沙?” 一聲冷哼從身側(cè)響起起趾,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎警儒,沒(méi)想到半個(gè)月后训裆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體眶根,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年边琉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了属百。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡变姨,死狀恐怖族扰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情定欧,我是刑警寧澤渔呵,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站砍鸠,受9級(jí)特大地震影響扩氢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爷辱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一录豺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饭弓,春花似錦双饥、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至夫嗓,卻和暖如春迟螺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舍咖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工矩父, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人排霉。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓窍株,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親攻柠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子球订,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容