- 实体类
@Data
public class FileInfo implements Serializable {
/**
* URL 路径
*/
private String urlPath;
/**
* dir 名称
*/
private String dirName;
}
- 工具类
@Slf4j
public class DownloadUtil {
/**
* 临时下载目录
*
* @return {@link String }
*/
public static String tempDownloadDir () {
String osName = System.getProperty("os.name").toLowerCase();
String downloadPath;
if (osName.contains("win")) {
// Windows 系统
downloadPath = "D:/temp/";
} else if (osName.contains("mac")) {
// macOS 系统
downloadPath = "/Users/username/temp/";
} else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) {
// Linux 系统
downloadPath = "/data/temp/";
} else {
// 默认目录
downloadPath = "./downloads";
}
return downloadPath;
}
/**
* 从 URL 中提取文件名
*
* @param url 网址
* @return {@link String }
*/
public static String extractFileNameFromUrl(String url) {
// 截取最后一个 / 之后的值
int lastSlashIndex = url.lastIndexOf('/');
if (lastSlashIndex != -1 && lastSlashIndex < url.length() - 1) {
return url.substring(lastSlashIndex + 1); // 返回最后一个 / 之后的部分
} else {
return ""; // 如果没有 / 或没有后续内容,返回空字符串
}
}
/**
* 解码文件名
*
* @param encodedFileName 编码文件名
* @return {@link String }
*/
public static String decodeFileName(String encodedFileName) {
try {
// 使用 UTF-8 编码解码文件名
return URLDecoder.decode(encodedFileName, StandardCharsets.UTF_8.name());
} catch (Exception e) {
e.printStackTrace();
return encodedFileName; // 如果解码失败,返回原始字符串
}
}
/**
* 压缩并返回文件名
*
* @param srcPath src 路径
* @return {@link String }
*/
public static String compressAndReturnTheFileName(String srcPath) {
File zip = ZipUtil.zip(srcPath);
String zipFileName = zip.getName();
return zipFileName;
}
/**
* 按组下载文件
*
* @param downloadPath 下载路径
* @param dataList 数据列表
* @return {@link String } 存储文件夹
*/
public static String downloadFilesByGroup(String downloadPath, List<FileInfo> dataList) {
String dirPath = downloadPath;
dataList.forEach(item -> {
try {
String tempDir = downloadPath + "/" + item.getDirName();
item.setDirName(tempDir);
Path directory = Paths.get(tempDir);
if (!Files.exists(directory)) {
Files.createDirectories(directory);
log.info("文件夹创建成功: {}", tempDir);
}
} catch (Exception e) {
log.error("文件夹创建失败: {}", e.getMessage());
}
});
// 创建 CompletableFuture 列表
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (FileInfo fileInfo : dataList) {
String dir = fileInfo.getDirName();
String netAddress = fileInfo.getUrlPath();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
URL url = new URL(netAddress);
URLConnection conn = url.openConnection();
InputStream inputStream = conn.getInputStream();
// 提取文件名
String fileName = netAddress.substring(netAddress.lastIndexOf('/') + 1);
String chinaFileName = URLDecoder.decode(fileName, "UTF-8");
// 创建文件输出流
File file = new File(dir + "/" + chinaFileName);
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
byte[] buffer = new byte[1024];
int byteread;
while ((byteread = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, byteread);
}
} finally {
inputStream.close();
}
System.out.println("Downloaded: " + chinaFileName);
} catch (Exception e) {
System.err.println("Error downloading file: " + netAddress);
e.printStackTrace();
}
});
futures.add(future);
}
// 等待所有任务完成
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
try {
allFutures.get(); // 阻塞主线程,等待所有任务完成
return dirPath;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return null;
}
/**
* 按文件路径下载文件
*
* @param downloadPath 下载路径
* @param fileUrls 文件 URL
* @return {@link String } 存储文件夹
*/
public static String downloadFileByFilePath(String downloadPath, List<String> fileUrls) {
String dirPath = downloadPath;
// 创建目标目录
Path directory = Paths.get(downloadPath);
try {
if (!Files.exists(directory)) {
Files.createDirectories(directory);
System.out.println();
log.info("文件夹创建成功: {} ", downloadPath);
}
} catch (Exception e) {
log.error("文件夹创建失败: {} ", downloadPath);
}
// 创建 CompletableFuture 列表
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (String netAddress : fileUrls) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
URL url = new URL(netAddress);
URLConnection conn = url.openConnection();
InputStream inputStream = conn.getInputStream();
// 提取文件名
String fileName = netAddress.substring(netAddress.lastIndexOf('/') + 1);
String chinaFileName = URLDecoder.decode(fileName, "UTF-8");
// 创建文件输出流
File file = new File(downloadPath + "/" + chinaFileName);
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
byte[] buffer = new byte[1024];
int byteread;
while ((byteread = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, byteread);
}
} finally {
inputStream.close();
}
log.info("Downloaded: {} ", netAddress);
} catch (Exception e) {
log.error("Error downloading file: {} ", netAddress);
e.printStackTrace();
}
});
futures.add(future);
}
// 等待所有任务完成
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0]));
try {
allFutures.get(); // 阻塞主线程,等待所有任务完成
return dirPath;
} catch (InterruptedException | ExecutionException e) {
log.error("文件下载错误: {} ", e.getMessage());
e.printStackTrace();
}
return null;
}
/**
* 递归删除目录及其所有内容
* @param directory 要删除的目录
* @throws IOException 如果删除过程中发生错误
*/
public static void deleteDirectory(Path directory) {
// 使用 Files.walkFileTree 遍历目录树并删除所有文件和子目录
try {
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file); // 删除文件
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir); // 删除目录
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}
}
- 下载文件到zip 中
/**
* 将文件下载到 zip
*
* @param fileUrls 文件 URL
* @param response 响应
* @return {@link ResultBody }
*/
@ApiOperation(value = "将文件下载到zip", notes = "将文件下载到zip")
@PostMapping("/downloadFileToZip")
public void downloadFileToZip(@RequestBody List<String> fileUrls, HttpServletResponse response) {
String prefixDir = DownloadUtil.tempDownloadDir();
String dataStr = DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN);
String randomStr = RandomUtil.randomString(6);
String tempDir = dataStr + "/" + randomStr;
String downloadDir = prefixDir + tempDir;
String srcPath = DownloadUtil.downloadFileByFilePath(downloadDir, fileUrls);
String zipFilePath = DownloadUtil.compressAndReturnTheFileName(srcPath);
// 删除临时文件夹
Path tempPath = Paths.get(downloadDir);
DownloadUtil.deleteDirectory(tempPath);
String downloadName = "附件文件" + "-" + randomStr + ".zip";
File zipFile = new File(prefixDir + dataStr + "/" + zipFilePath);
if (!zipFile.exists()) {
throw new IllegalArgumentException("ZIP 文件不存在: " + zipFilePath);
}
// 对文件名进行 URL 编码
String encodedFilename = null;
try {
encodedFilename = URLEncoder.encode(downloadName, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// 设置响应头
response.setContentType("application/octet-stream; charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFilename + "\"; filename*=UTF-8''" + encodedFilename);
// 设置响应头
response.setContentType("application/zip");
response.setContentLengthLong(zipFile.length()); // 设置文件大小
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFilename + "\"; filename*=UTF-8''" + encodedFilename);
// 使用缓冲流将文件内容写入响应流
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(zipFile)); BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
byte[] buffer = new byte[1024 * 8]; // 使用 8KB 缓冲区
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bos.flush();
} catch (IOException e) {
throw new RuntimeException("文件下载失败", e);
}
}
- 按分类下载并压缩成zip
/**
* 将文件下载到 zip
*
* @param fileInfoList 文件信息集合
* @param response 响应
* @return {@link ResultBody }
*/
@ApiOperation(value = "将文件下载到zip", notes = "将文件下载到zip")
@PostMapping("/downloadFileByFilePathToZip")
public void downloadFileByFilePathToZip(@RequestBody List<FileInfo> fileInfoList, HttpServletResponse response) {
String prefixDir = DownloadUtil.tempDownloadDir();
String dataStr = DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN);
String randomStr = RandomUtil.randomString(6);
String tempDir = dataStr + "/" + randomStr;
String downloadDir = prefixDir + tempDir;
String srcPath = DownloadUtil.downloadFilesByGroup(downloadDir, fileInfoList);
String zipFilePath = DownloadUtil.compressAndReturnTheFileName(srcPath);
// 删除临时文件夹
Path tempPath = Paths.get(downloadDir);
DownloadUtil.deleteDirectory(tempPath);
String downloadName = "附件文件" + "-" + randomStr + ".zip";
File zipFile = new File(prefixDir + dataStr + "/" + zipFilePath);
if (!zipFile.exists()) {
throw new IllegalArgumentException("ZIP 文件不存在: " + zipFilePath);
}
// 对文件名进行 URL 编码
String encodedFilename = null;
try {
encodedFilename = URLEncoder.encode(downloadName, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// 设置响应头
response.setContentType("application/octet-stream; charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFilename + "\"; filename*=UTF-8''" + encodedFilename);
// 设置响应头
response.setContentType("application/zip");
response.setContentLengthLong(zipFile.length()); // 设置文件大小
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFilename + "\"; filename*=UTF-8''" + encodedFilename);
// 使用缓冲流将文件内容写入响应流
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(zipFile)); BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
byte[] buffer = new byte[1024 * 8]; // 使用 8KB 缓冲区
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bos.flush();
} catch (IOException e) {
throw new RuntimeException("文件下载失败", e);
}
}