最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實現(xiàn)多種來源的Zip多層目錄打包下載

 更新時間:2025年04月30日 10:51:40   作者:頗有幾分姿色  
這篇文章主要為大家詳細介紹了SpringBoot如何實現(xiàn)多種來源的?Zip?多層目錄打包下載,包括本地文件和HTTP混合,感興趣的小伙伴可以跟隨小編一起學習一下

需要將一批文件(可能分布在不同目錄、不同來源)打包成Zip格式,按目錄結(jié)構(gòu)導出給用戶下載。

1. 核心思路

支持將本地服務器上的文件(如/data/upload/xxx.jpg)打包進Zip,保持原有目錄結(jié)構(gòu)。

支持通過HTTP下載遠程文件寫入Zip。

所有寫入Zip的目錄名、文件名均需安全處理。

統(tǒng)一使用流式IO,適合大文件/大量文件導出,防止內(nèi)存溢出。

目錄下無文件時寫入empty.txt標識。

2. 代碼實現(xiàn)

2.1 工具類:本地&HTTP兩種方式寫入Zip

package com.example.xiaoshitou.utils;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;

/***
 * @title
 * @author shijiangyong
 * @date 2025/4/28 16:34
 **/
public class ZipDownloadUtils {


    private static final String SUFFIX_ZIP = ".zip";
    private static final String UNNAMED = "未命名";
    /**
     * 安全處理文件名/目錄名
     * @param name
     * @return
     */
    public static String safeName(String name) {
        if (name == null) return "null";
        return name.replaceAll("[\\\\/:*?\"<>|]", "_");
    }

    /**
     * HTTP下載寫入Zip
     * @param zipOut
     * @param fileUrl
     * @param zipEntryName
     * @throws IOException
     */
    public static void writeHttpFileToZip(ZipArchiveOutputStream zipOut, String fileUrl, String zipEntryName) throws IOException {
        ZipArchiveEntry entry = new ZipArchiveEntry(zipEntryName);
        zipOut.putArchiveEntry(entry);
        try (InputStream in = openHttpStream(fileUrl, 8000, 20000)) {
            byte[] buffer = new byte[4096];
            int len;
            while ((len = in.read(buffer)) != -1) {
                zipOut.write(buffer, 0, len);
            }
        } catch (Exception e) {
            zipOut.write(("下載失敗: " + fileUrl).getBytes(StandardCharsets.UTF_8));
        }
        zipOut.closeArchiveEntry();
    }

    /**
     * 本地文件寫入Zip
     * @param zipOut
     * @param localFilePath
     * @param zipEntryName
     * @throws IOException
     */
    public static void writeLocalFileToZip(ZipArchiveOutputStream zipOut, String localFilePath, String zipEntryName) throws IOException {
        File file = new File(localFilePath);
        if (!file.exists() || file.isDirectory()) {
            writeTextToZip(zipOut, zipEntryName + "_empty.txt", "文件不存在或是目錄: " + localFilePath);
            return;
        }

        ZipArchiveEntry entry = new ZipArchiveEntry(zipEntryName);
        zipOut.putArchiveEntry(entry);
        try (InputStream fis = new FileInputStream(file)) {
            byte[] buffer = new byte[4096];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                zipOut.write(buffer, 0, len);
            }
        }
        zipOut.closeArchiveEntry();
    }

    /**
     * 寫入文本文件到Zip(如empty.txt)
     * @param zipOut
     * @param zipEntryName
     * @param content
     * @throws IOException
     */
    public static void writeTextToZip(ZipArchiveOutputStream zipOut, String zipEntryName, String content) throws IOException {
        ZipArchiveEntry entry = new ZipArchiveEntry(zipEntryName);
        zipOut.putArchiveEntry(entry);
        zipOut.write(content.getBytes(StandardCharsets.UTF_8));
        zipOut.closeArchiveEntry();
    }

    /**
     * 打開HTTP文件流
     * @param url
     * @param connectTimeout
     * @param readTimeout
     * @return
     * @throws IOException
     */
    public static InputStream openHttpStream(String url, int connectTimeout, int readTimeout) throws IOException {
        URLConnection conn = new URL(url).openConnection();
        conn.setConnectTimeout(connectTimeout);
        conn.setReadTimeout(readTimeout);
        return conn.getInputStream();
    }

    /**
     * 從url獲取文件名
     * @param url
     * @return
     * @throws IOException
     */
    public static String getFileName(String url)  {
        return url.substring(url.lastIndexOf('/')+1);
    }

    /**
     * 設置response
     * @param request
     * @param response
     * @param fileName
     * @throws UnsupportedEncodingException
     */
    public static void setResponse(HttpServletRequest request, HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
        if (!StringUtils.hasText(fileName)) {
            fileName = LocalDate.now() + UNNAMED;
        }
        if (!fileName.endsWith(SUFFIX_ZIP)) {
            fileName = fileName + SUFFIX_ZIP;
        }
        response.setHeader("Connection", "close");
        response.setHeader("Content-Type", "application/octet-stream;charset=UTF-8");
        String filename = encodeFileName(request, fileName);
        response.setHeader("Content-Disposition", "attachment;filename=" + filename);
    }

    /**
     * 文件名在不同瀏覽器兼容處理
     * @param request 請求信息
     * @param fileName 文件名
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String encodeFileName(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {
        String userAgent = request.getHeader("USER-AGENT");
        // 火狐瀏覽器
        if (userAgent.contains("Firefox") || userAgent.contains("firefox")) {
            fileName = new String(fileName.getBytes(), "ISO8859-1");
        } else {
            // 其他瀏覽器
            fileName = URLEncoder.encode(fileName, "UTF-8");
        }
        return fileName;
    }
}

2.2 Controller 示例:按本地目錄結(jié)構(gòu)批量導出

假設有如下導出結(jié)構(gòu):

用戶A/
    身份證/
        xxx.jpg (本地)
        xxx.png (本地)
    頭像/
        xxx.jpg (HTTP)
用戶B/
    empty.txt

模擬數(shù)據(jù)結(jié)構(gòu):

zipGroup:

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.List;

/***
 * @title
 * @author shijiangyong
 * @date 2025/4/28 16:36
 **/
@Data
@AllArgsConstructor
public class ZipGroup {
    /**
     * 用戶名、文件名
     */
    private String dirName;
    private List<ZipSubDir> subDirs;
}

zipGroupDir:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/***
 * @title
 * @author shijiangyong
 * @date 2025/4/28 16:37
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ZipSubDir {
    /**
     * 子目錄
     */
    private String subDirName;
    private List<ZipFileRef> fileRefs;
}

ZipFileRef:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/***
 * @title
 * @author shijiangyong
 * @date 2025/4/28 16:38
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ZipFileRef {
    /**
     * 文件名
     */
    private String name;
    /**
     * 本地路徑
     */
    private String localPath;
    /**
     * http路徑
     */
    private String httpUrl;
}

Controller通用代碼:

package com.example.xiaoshitou.controller;

import com.example.xiaoshitou.service.ZipService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/***
 * @title
 * @author shijiangyong
 * @date 2025/4/28 16:50
 **/
@RestController
@RequestMapping("/zip")
@AllArgsConstructor
public class ZipController {

    private final ZipService zipService;

    /**
     *  打包下載
     * @param response
     */
    @GetMapping("/download")
    public void downloadZip(HttpServletRequest request, HttpServletResponse response) {
        zipService.downloadZip(request,response);
    }
}

Service 層代碼:

package com.example.xiaoshitou.service.impl;

import com.example.xiaoshitou.entity.ZipFileRef;
import com.example.xiaoshitou.entity.ZipGroup;
import com.example.xiaoshitou.entity.ZipSubDir;
import com.example.xiaoshitou.service.ZipService;
import com.example.xiaoshitou.utils.ZipDownloadUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.zip.Deflater;

/***
 * @title
 * @author shijiangyong
 * @date 2025/4/28 16:43
 **/
@Slf4j
@Service
public class ZipServiceImpl implements ZipService {
    @Override
    public void downloadZip(HttpServletRequest request, HttpServletResponse response) {
        // ==== 示例數(shù)據(jù) ====
        List<ZipGroup> data = Arrays.asList(
                new ZipGroup("小明", Arrays.asList(
                        new ZipSubDir("身份證(本地)", Arrays.asList(
                                new ZipFileRef("","E:/software/test/1.png",""),
                                new ZipFileRef("","E:/software/test/2.png","")
                        )),
                        new ZipSubDir("頭像(http)", Arrays.asList(
                                // 百度隨便找的
                                new ZipFileRef("","","https://pic4.zhimg.com/v2-4d9e9f936b9968f53be22b594aafa74f_r.jpg")
                        ))
                )),
                new ZipGroup("小敏", Collections.emptyList())
        );

        try (BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
             ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(bos)) {
            String fileName = "資料打包_" + System.currentTimeMillis() + ".zip";
            ZipDownloadUtils.setResponse(request,response, fileName);
            // 快速壓縮
            zipOut.setLevel(Deflater.BEST_SPEED);
            for (ZipGroup group : data) {
                String groupDir = ZipDownloadUtils.safeName(group.getDirName()) + "/";
                List<ZipSubDir> subDirs = group.getSubDirs();
                if (subDirs == null || subDirs.isEmpty()) {
                    groupDir = ZipDownloadUtils.safeName(group.getDirName()) + "(無資料)/";
                    ZipDownloadUtils.writeTextToZip(zipOut, groupDir + "empty.txt", "該目錄無任何資料");
                    continue;
                }
                for (ZipSubDir subDir : subDirs) {
                    String subDirPath = groupDir + ZipDownloadUtils.safeName(subDir.getSubDirName()) + "/";
                    List<ZipFileRef> fileRefs = subDir.getFileRefs();
                    if (fileRefs == null || fileRefs.isEmpty()) {
                        subDirPath = groupDir + ZipDownloadUtils.safeName(subDir.getSubDirName()) + "(empty)/";
                        ZipDownloadUtils.writeTextToZip(zipOut, subDirPath + "empty.txt", "該類型無資料");
                        continue;
                    }
                    for (ZipFileRef fileRef : fileRefs) {
                        if (fileRef.getLocalPath() != null && !fileRef.getLocalPath().isEmpty()) {
                            String name = ZipDownloadUtils.getFileName(fileRef.getLocalPath());
                            fileRef.setName(name);
                            ZipDownloadUtils.writeLocalFileToZip(zipOut, fileRef.getLocalPath(), subDirPath + ZipDownloadUtils.safeName(fileRef.getName()));
                        } else if (fileRef.getHttpUrl() != null && !fileRef.getHttpUrl().isEmpty()) {
                            String name = ZipDownloadUtils.getFileName(fileRef.getHttpUrl());
                            fileRef.setName(name);
                            ZipDownloadUtils.writeHttpFileToZip(zipOut, fileRef.getHttpUrl(), subDirPath + ZipDownloadUtils.safeName(fileRef.getName()));
                        }
                    }
                }
            }
            zipOut.finish();
            zipOut.flush();
            response.flushBuffer();
        } catch (Exception e) {
            throw new RuntimeException("打包下載失敗", e);
        }
    }
}

3. 常見問題及安全建議

防路徑穿越(Zip Slip):所有目錄/文件名務必用safeName過濾特殊字符

大文件/大批量:建議分頁、分批處理

空目錄寫入:統(tǒng)一寫empty.txt標識空目錄

本地文件不存在:Zip包內(nèi)寫入提示信息

HTTP下載失敗:Zip包內(nèi)寫入“下載失敗”提示

避免泄露服務器絕對路徑:僅在日志中記錄本地路徑,Zip內(nèi)不暴露

權(quán)限校驗:實際生產(chǎn)需驗證用戶是否有權(quán)訪問指定文件

4. 總結(jié)

這里介紹了如何從本地服務器路徑和HTTP混合讀取文件并Zip打包下載,目錄結(jié)構(gòu)靈活可控??筛鶕?jù)實際需求擴展更多來源類型(如數(shù)據(jù)庫、對象存儲等)。

到此這篇關于SpringBoot實現(xiàn)多種來源的Zip多層目錄打包下載的文章就介紹到這了,更多相關SpringBoot多來源Zip打包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • idea搭建SSM框架遇踩的坑(附完整過程)

    idea搭建SSM框架遇踩的坑(附完整過程)

    最近準備搭建一個SSM框架,由于很久沒有搭建了,一來就遇到各種問題,折騰了一天終于搞定了,特此記錄一下遇到的問題,下面這篇文章主要給大家介紹了關于idea搭建SSM框架遇踩的坑,文中還附完整過程,需要的朋友可以參考下
    2023-04-04
  • SpringBoot讀取Resource目錄下文件的四種方式總結(jié)

    SpringBoot讀取Resource目錄下文件的四種方式總結(jié)

    在Spring?Boot項目中,經(jīng)常需要獲取resources目錄下的文件,這些文件可以包括配置文件、模板文件、靜態(tài)資源等,本文將介紹四種常用的方法來獲取resources目錄下的文件,需要的朋友可以參考下
    2023-08-08
  • Java關鍵字之this用法詳解

    Java關鍵字之this用法詳解

    這篇文章將為大家詳細介紹一下Java關鍵字this的用法,文中有相關的代碼示例,希望對大家的學習或工作有一定的幫助,感興趣的同學可以參考下
    2023-05-05
  • Swing拆分窗格控件JSplitPane使用詳解

    Swing拆分窗格控件JSplitPane使用詳解

    這篇文章主要為大家詳細介紹了Swing拆分窗格控件JSplitPane的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Netty分布式高性能工具類FastThreadLocal和Recycler分析

    Netty分布式高性能工具類FastThreadLocal和Recycler分析

    這篇文章主要為大家介紹了Netty分布式高性能工具類FastThreadLocal和Recycler分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • Java8中日期時間API的避坑指南與最佳實踐指南

    Java8中日期時間API的避坑指南與最佳實踐指南

    在Java 8之前,處理日期時間我們常用Date、Calendar和SimpleDateFormat,本文將通過實際案例,剖析老API的典型陷阱,并展示如何使用新API優(yōu)雅避坑,希望對大家有所幫助
    2026-04-04
  • 使用Java實現(xiàn)文件夾的遍歷操作指南

    使用Java實現(xiàn)文件夾的遍歷操作指南

    網(wǎng)上大多采用java遞歸的方式遍歷文件夾下的文件,這里我不太喜歡遞歸的風格,這篇文章主要給大家介紹了關于使用Java實現(xiàn)文件夾的遍歷操作的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-05-05
  • springboot根據(jù)實體類生成表的實現(xiàn)方法

    springboot根據(jù)實體類生成表的實現(xiàn)方法

    本文介紹了如何通過SpringBoot工程引入SpringDataJPA,并通過實體類自動生成數(shù)據(jù)庫表的過程,包括常見問題解決方法,感興趣的可以了解一下
    2024-09-09
  • jdk17+springboot使用webservice的踩坑實戰(zhàn)記錄

    jdk17+springboot使用webservice的踩坑實戰(zhàn)記錄

    這篇文章主要給大家介紹了關于jdk17+springboot使用webservice踩坑的相關資料,網(wǎng)上很多教程是基于jdk8的,所以很多在17上面跑不起來,折騰兩天,直接給答案,需要的朋友可以參考下
    2024-01-01
  • Springboot?JPA如何使用distinct返回對象

    Springboot?JPA如何使用distinct返回對象

    這篇文章主要介紹了Springboot?JPA如何使用distinct返回對象,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02

最新評論

莱阳市| 仁怀市| 英超| 中山市| 朝阳区| 农安县| 上虞市| 丹江口市| 胶州市| 纳雍县| 安康市| 永新县| 宜昌市| 台北市| 满洲里市| 康平县| 辽阳市| 探索| 视频| 专栏| 玛纳斯县| 武平县| 磴口县| 平山县| 台中县| 方正县| 禄劝| 辽源市| 合作市| 河东区| 北流市| 沾益县| 内黄县| 阿拉善右旗| 蒲城县| 贡嘎县| 留坝县| 大田县| 定结县| 海林市| 巴楚县|