公開アカウント: Zhao Xiake
I.はじめに
私たちのプロジェクトでは、ビデオ、ファイル、写真、添付ファイルのダウンロード、Excel のエクスポート、Zip 圧縮ファイルのエクスポートなど、ファイルのダウンロードが非常に一般的です。ここでは、主にファイルのダウンロード、速度制限されたダウンロード、ファイル パッケージのダウンロード、URL ファイル パッケージのダウンロード、Excel エクスポートのダウンロード、Excel バッチ エクスポート Zip パッケージのダウンロード、マルチスレッド高速ダウンロードなど。
2. Spring Boot プロジェクトをビルドする
SpringBoot Web プロジェクトを構築し、共通の依存関係を参照します。commons-io は一般的な IO 操作に使用され、hutool-all と poi-ooxml は Excel 操作のエクスポートに使用され、commons-compress はマルチスレッド圧縮に使用されます。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.21</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.20</version>
</dependency>
3. ファイルのダウンロード
3.1 単一ファイルのダウンロード
最も単純なダウンロードは、ファイル ダウンロード インターフェイスを提供することです。ブラウザはインターフェイスを要求し、ファイルをプレビューまたはダウンロードします。ここでは 1.2G ビデオのダウンロードを例として、/download インターフェイスを直接見ていきます。
@GetMapping("/download")
public void download(HttpServletResponse response) throws IOException {
File file = new File("/Users/zxk/Movies/1.2G.mp4");
response.setContentType("video/mp4;charset=utf8");
//设置下载文件名
response.setHeader("Content-Disposition", "attachment;filename=" + file.getName());
//中文乱码处理
//response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8") );
//网页直接播放
//response.setHeader("Content-Disposition", "inline");
//下载进度
response.setContentLengthLong(file.length());
try (InputStream inputStream = new FileInputStream(file);
OutputStream outputStream = response.getOutputStream()
) {
IOUtils.copy(inputStream, outputStream);
}
}
以下の点に注意してください。
-
1.response.setContentType はファイルタイプを設定します
-
2. Content-Dispositionはファイルダウンロード時に表示されるファイル名を設定します 中国語文字化けがある場合はURLEncodeが必要です ブラウザで直接開けたい場合は「inline」を設定します
-
3.response.setContentLengthLong(file.length())、HTTP 本文の長さを設定すると、ダウンロード中に進行状況を表示できます
-
4. ダウンロードが完了したらストリームを閉じる必要がありますが、ここでは try-with-resource を使用してストリームを自動的に閉じます。
3.2 速度制限のあるダウンロード
最初の方法を使用したダウンロード速度は非常に速く、サーバーの帯域幅がすぐにいっぱいになる可能性があるため、ダウンロード速度を制限する必要があります。あるディスクのダウンロード速度がメンバーシップを持っている人とメンバーシップを持っていない人で大きく異なるのは、同期する必要のないディスクではダウンロード速度に制限があるためです。
@GetMapping("/limitSpeed")
public void limitSpeed(@RequestParam(value = "speed", defaultValue = "1024") int speed, HttpServletResponse response) throws IOException {
File path = new File("/Users/zxk/Movies/1.2G.mp4");
response.setContentType("video/mp4;charset=utf8");
response.setHeader("Content-Disposition", "attachment;filename=" + path.getName());
response.setContentLengthLong(path.length());
try (
InputStream inputStream = new FileInputStream(path);
OutputStream outputStream = response.getOutputStream()
) {
byte[] buffer = new byte[1024];
int length;
SpeedLimiter speedLimiter = new SpeedLimiter(speed);
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
speedLimiter.delayNextBytes(length);
}
}
}
public class SpeedLimiter {
/** 速度上限(KB/s), 0=不限速 */
private int maxRate = 1024;
private long getMaxRateBytes(){
return this.maxRate * 1024L;
}
private long getLessCountBytes() {
long lcb = getMaxRateBytes() / 10;
if (lcb < 10240) lcb = 10240;
return lcb;
}
public SpeedLimiter(int maxRate) {
this.setMaxRate(maxRate);
}
public synchronized void setMaxRate(int maxRate){
this.maxRate = Math.max(maxRate, 0);
}
private long totalBytes = 0;
private long tmpCountBytes = 0;
private final long lastTime = System.currentTimeMillis();
public synchronized void delayNextBytes(int len) {
if (maxRate <= 0) return;
totalBytes += len;
tmpCountBytes += len;
//未达到指定字节数跳过...
if (tmpCountBytes < getLessCountBytes()) {
return;
}
long nowTime = System.currentTimeMillis();
long sendTime = nowTime - lastTime;
long workTime = (totalBytes * 1000) / getMaxRateBytes();
long delayTime = workTime - sendTime;
if (delayTime > 0) {
try {
Thread.sleep(delayTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmpCountBytes = 0;
}
}
}
3.3 複数のファイルを ZIP パッケージにダウンロードする
単一ファイルのダウンロードでは、複数ファイルのダウンロードを使用する必要があります。通常、ブラウザは、ダウンロード用に複数のファイルを Zip ファイルにパッケージ化して複数のファイルをダウンロードします。
@GetMapping("/zip")
public void zip(HttpServletResponse response) throws IOException {
File file1 = new File("/Users/zxk/Movies/2.mp4");
File file2 = new File("/Users/zxk/Movies/2.mp4");
List<File> files = Arrays.asList(file2, file1);
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
zipOutputStream.setLevel(0);
files.forEach(f -> {
try (FileInputStream inputStream = new FileInputStream(f)) {
zipOutputStream.putNextEntry(new ZipEntry(f.getName()));
IOUtils.copy(inputStream, zipOutputStream);
zipOutputStream.closeEntry();
} catch (Exception e) {
e.printStackTrace();
}
});
zipOutputStream.flush();
zipOutputStream.finish();
}
}
複数のファイルを Zip パッケージにパッケージ化する場合の注意事項:
-
1. zipOutputStream.setLevel(0) は圧縮レベルを設定します。0 は圧縮なしを意味し、ダウンロード速度を向上させることができます。
-
2. 複数のファイルをパッケージ化してダウンロードする場合、すべてのファイルが圧縮されてからダウンロードが開始されるのではなく、圧縮しながらダウンロードされるため、ユーザーがダウンロードをクリックすると、ダウンロード タスクがブラウザのダウンロード リストに直接入力されます。
3.4 フォルダー全体をダウンロードする
場合によっては、フォルダー全体を再帰的にパッケージ化してダウンロードする必要がある
@GetMapping("/dir")
public void dir(HttpServletResponse response) throws IOException {
File dir = new File("/Users/zxk/Movies/download");
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
zipOutputStream.setLevel(0);
zip(zipOutputStream, "", dir);
zipOutputStream.flush();
zipOutputStream.finish();
}
}
public void zip(ZipOutputStream zipOutputStream, String parentPath, File file) throws IOException {
if (file.isDirectory()) {
File[] subFiles = file.listFiles();
if (subFiles != null) {
for (File f : subFiles) {
zip(zipOutputStream, parentPath + file.getName() + "/", f);
}
}
} else {
try (FileInputStream fileInputStream = new FileInputStream(file)) {
zipOutputStream.putNextEntry(new ZipEntry(parentPath + file.getName()));
IOUtils.copy(fileInputStream, zipOutputStream);
}
}
}
予防:
-
1. フォルダー全体が再帰的に実行され、フォルダーに多すぎるファイルを含めることはできません。
3.5 URL によるパッケージのダウンロード
ファイルがクラウド ストレージに保存され、ファイルの ULR がデータベースに保存される場合があります。ダウンロードする場合は、URL を介して複数のファイルをパッケージ化してダウンロードする必要があります。
@GetMapping("/urlZip")
public void urlZip(HttpServletResponse response) throws IOException {
List<String> urls = Arrays.asList("https://demo.com/11666832527556.jpeg",
"https://demo.com/11666831385156.jpeg",
"https://demo.com/11666829917700.jpeg",
"https://demo.com/11666762702021.png",
"https://demo.com/11666762702020.webp",
"https://demo.com/11666549651972.jpg",
"https://demo.com/11666524497476.jpeg",
"https://demo.com/11666507113092.jpg");
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
zipOutputStream.setLevel(0);
urls.forEach(f -> {
try {
zipOutputStream.putNextEntry(new ZipEntry(StringUtils.getFilename(f)));
HttpUtil.download(f, zipOutputStream, false);
zipOutputStream.closeEntry();
} catch (Exception e) {
e.printStackTrace();
}
});
zipOutputStream.flush();
zipOutputStream.finish();
}
}
3.6 Excelダウンロードのエクスポート
ダウンロードされた一部のファイルは存在しません。代わりに、データは最初にデータベースから取得され、次にファイルが動的に生成され、ダウンロードのためにユーザーに提供されます。ここでは例として 1 つの Excel ファイルをエクスポートします。
@GetMapping("/excel")
public void excel(HttpServletResponse response) throws IOException {
List<List<Integer>> rows = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
rows.add(IntStream.range(i, i + 100).boxed().collect(Collectors.toList()));
}
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename=test.xls");
try (OutputStream out = response.getOutputStream();
ExcelWriter writer = ExcelUtil.getWriter()) {
writer.write(rows);
writer.flush(out, true);
}
}
3.7 一括エクスポートExcelパッケージダウンロード
多くの企業では、一度に複数の Excel をエクスポートする必要があります。ここでは、複数の Excel を Zip ファイルに圧縮してダウンロードできます。10 個の Excel を動的に生成する主な例を次に示します。
@GetMapping("/excelZip")
public void excelZip(HttpServletResponse response) throws IOException {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
zipOutputStream.setLevel(0);
for (int i = 0; i < 10; i++) {
zipOutputStream.putNextEntry(new ZipEntry(String.format("%s.xls", i)));
try (ExcelWriter writer = ExcelUtil.getWriter()) {
writer.write(generateData());
writer.flush(zipOutputStream);
}
}
zipOutputStream.flush();
zipOutputStream.finish();
}
}
private List<List<Integer>> generateData() {
List<List<Integer>> rows = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
rows.add(IntStream.range(i, i + 100).boxed().collect(Collectors.toList()));
}
return rows;
}
3.8 マルチスレッドの高速ダウンロード
ダウンロードされるデータの量が大きい場合があり、シングルスレッドのパッケージングは遅くなります。ここでは、マルチスレッドの同時パッケージングを使用してパッケージングの速度を向上させる必要があります。ここでは、複数の URL ファイルのマルチスレッドのダウンロードを例として使用します。 commons-compress の ParallelScatterZipCreator マルチスレッド同時パッケージ化。
public static final ThreadFactory factory = new ThreadFactoryBuilder().setNamePrefix("compressFileList-pool-").build();
public static final ExecutorService executor = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), factory);
@GetMapping("/parallelZip")
public void excelZipThread(HttpServletResponse response) throws IOException {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
List<String> urls = Arrays.asList("https://demo.com/11671291835144.png",
"https://demo.com/11671291834824.png",
"https://demo.com/11671291833928.png",
"https://demo.com/11671291833800.png",
"https://demo.com/11671291833480.png",
"https://demo.com/11671291828232.png",
"https://demo.com/11671291827528.png",
"https://demo.com/11671291825737.png",
"https://demo.com/11671291825736.png");
ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);
try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(response.getOutputStream())) {
zipArchiveOutputStream.setLevel(0);
urls.forEach(x -> {
ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(StringUtils.getFilename(x));
zipArchiveEntry.setMethod(ZipArchiveEntry.STORED);
InputStreamSupplier inputStreamSupplier = () -> URLUtil.getStream(URLUtil.url(x));
parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
});
parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
3.9 マルチスレッド一括インポート Excelパッケージダウンロード
これはさらに複雑で、複数の Excel ファイルを動的に生成した後、マルチスレッドを使用してそれらのファイルを ZIP パッケージにダウンロードします。
@GetMapping("/parallelexcelZip")
public void parallelexcelZip(HttpServletResponse response) throws IOException {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);
try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(response.getOutputStream())) {
zipArchiveOutputStream.setLevel(0);
IntStream.range(1,10).forEach(x -> {
InputStreamSupplier inputStreamSupplier = () ->{
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
try(ExcelWriter writer=ExcelUtil.getWriter()) {
writer.write(generateData());
writer.flush(outputStream);
}
return new ByteArrayInputStream(outputStream.toByteArray());
};
ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(String.format("%s.xls",x));
zipArchiveEntry.setMethod(ZipArchiveEntry.STORED);
parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
});
parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
4. まとめ
この記事では主に 9 つの一般的なファイル ダウンロード操作を要約し、対応するデモ コードを提供します。もちろん、分割ダウンロード、ブレークポイント レジューム、分散ダウンロード速度制限など、まとめられていないものもあります。これらの高度なダウンロードも使用されていません通常のプロジェクトで多くのことが行われるため、要約はありません。