• 总结常用9种下载(限速、多线程加速、ZIP、导Excel)


    公众号:赵侠客

    原文:https://mp.weixin.qq.com/s/i49mqqZ9km22gk2A6umaQQ

           一、前言         

    下载文件在我们项目很常见,有下载视频、文件、图片、附件、导出Excel、导出Zip压缩文件等等,这里我对常见的下载做个简单的总结,主要有文件下载、限速下载、多文件打包下载、URL文件打包下载、Excel导出下载、Excel批量导出Zip包下载、多线程加速下载。

           二、搭建Spring Boot项目         

    搭建个SpringBoot Web项目,引用常用依赖,commons-io作常用IO操作,hutool-all、poi-ooxml做导出Excel操作,commons-compress做多线程压缩。

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-webartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.apache.commonsgroupId>
    7. <artifactId>commons-ioartifactId>
    8. <version>1.3.2version>
    9. dependency>
    10. <dependency>
    11. <groupId>cn.hutoolgroupId>
    12. <artifactId>hutool-allartifactId>
    13. <version>5.8.21version>
    14. dependency>
    15. <dependency>
    16. <groupId>org.apache.poigroupId>
    17. <artifactId>poi-ooxmlartifactId>
    18. <version>4.1.2version>
    19. dependency>
    20. <dependency>
    21. <groupId>org.apache.commonsgroupId>
    22. <artifactId>commons-compressartifactId>
    23. <version>1.20version>
    24. dependency>

      三、文件下载   

    3.1 单文件下载

    最简单的下载就是提供一个文件下载接口,浏览器请求接口后预览或者下载文件,这里以下载一个1.2G的视频为例,直接看 /download接口

    1. @GetMapping("/download")
    2. public void download(HttpServletResponse response) throws IOException {
    3. File file = new File("/Users/zxk/Movies/1.2G.mp4");
    4. response.setContentType("video/mp4;charset=utf8");
    5. //设置下载文件名
    6. response.setHeader("Content-Disposition", "attachment;filename=" + file.getName());
    7. //中文乱码处理
    8. //response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8") );
    9. //网页直接播放
    10. //response.setHeader("Content-Disposition", "inline");
    11. //下载进度
    12. response.setContentLengthLong(file.length());
    13. try (InputStream inputStream = new FileInputStream(file);
    14. OutputStream outputStream = response.getOutputStream()
    15. ) {
    16. IOUtils.copy(inputStream, outputStream);
    17. }
    18. }
     
    

    这里有以下几点需要注意:

    • 1. response.setContentType设置文件的类型

    • 2. Content-Disposition设置文件下载时显示的文件名,如果有中文乱码,需要URLEncode,如果希望浏览器直接打开可以设置"inline"

    • 3. response.setContentLengthLong(file.length()),设置Http body长度可以在下载时显示进度

    • 4. 下载完成需要关闭流,这里使用try-with-resource自动关闭流

    3.2限速下载

    使用第一种下载速度会非常快,可能瞬间就将你的服务器带宽占满了,所以就需要限制下载速度。某盘开会员和不开会员下载速度相差非常大,就是针对不用同步给限制了不同的下载速度

    1. @GetMapping("/limitSpeed")
    2. public void limitSpeed(@RequestParam(value = "speed", defaultValue = "1024") int speed, HttpServletResponse response) throws IOException {
    3. File path = new File("/Users/zxk/Movies/1.2G.mp4");
    4. response.setContentType("video/mp4;charset=utf8");
    5. response.setHeader("Content-Disposition", "attachment;filename=" + path.getName());
    6. response.setContentLengthLong(path.length());
    7. try (
    8. InputStream inputStream = new FileInputStream(path);
    9. OutputStream outputStream = response.getOutputStream()
    10. ) {
    11. byte[] buffer = new byte[1024];
    12. int length;
    13. SpeedLimiter speedLimiter = new SpeedLimiter(speed);
    14. while ((length = inputStream.read(buffer)) != -1) {
    15. outputStream.write(buffer, 0, length);
    16. speedLimiter.delayNextBytes(length);
    17. }
    18. }
    19. }
    20. public class SpeedLimiter {
    21. /** 速度上限(KB/s), 0=不限速 */
    22. private int maxRate = 1024;
    23. private long getMaxRateBytes(){
    24. return this.maxRate * 1024L;
    25. }
    26. private long getLessCountBytes() {
    27. long lcb = getMaxRateBytes() / 10;
    28. if (lcb < 10240) lcb = 10240;
    29. return lcb;
    30. }
    31. public SpeedLimiter(int maxRate) {
    32. this.setMaxRate(maxRate);
    33. }
    34. public synchronized void setMaxRate(int maxRate){
    35. this.maxRate = Math.max(maxRate, 0);
    36. }
    37. private long totalBytes = 0;
    38. private long tmpCountBytes = 0;
    39. private final long lastTime = System.currentTimeMillis();
    40. public synchronized void delayNextBytes(int len) {
    41. if (maxRate <= 0) return;
    42. totalBytes += len;
    43. tmpCountBytes += len;
    44. //未达到指定字节数跳过...
    45. if (tmpCountBytes < getLessCountBytes()) {
    46. return;
    47. }
    48. long nowTime = System.currentTimeMillis();
    49. long sendTime = nowTime - lastTime;
    50. long workTime = (totalBytes * 1000) / getMaxRateBytes();
    51. long delayTime = workTime - sendTime;
    52. if (delayTime > 0) {
    53. try {
    54. Thread.sleep(delayTime);
    55. } catch (InterruptedException e) {
    56. e.printStackTrace();
    57. }
    58. tmpCountBytes = 0;
    59. }
    60. }
    61. }
     
    

    3.3多文件打成ZIP包下载

    有了单文件下载,肯定就用多文件下载,一般浏览器下载多个文件是将多个文件打包成一个Zip文件下载。

    1. @GetMapping("/zip")
    2. public void zip(HttpServletResponse response) throws IOException {
    3. File file1 = new File("/Users/zxk/Movies/2.mp4");
    4. File file2 = new File("/Users/zxk/Movies/2.mp4");
    5. List files = Arrays.asList(file2, file1);
    6. response.setContentType("application/zip");
    7. response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
    8. try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
    9. zipOutputStream.setLevel(0);
    10. files.forEach(f -> {
    11. try (FileInputStream inputStream = new FileInputStream(f)) {
    12. zipOutputStream.putNextEntry(new ZipEntry(f.getName()));
    13. IOUtils.copy(inputStream, zipOutputStream);
    14. zipOutputStream.closeEntry();
    15. } catch (Exception e) {
    16. e.printStackTrace();
    17. }
    18. });
    19. zipOutputStream.flush();
    20. zipOutputStream.finish();
    21. }
    22. }
     
    

    多文件打成Zip包注意事项:

    • 1. zipOutputStream.setLevel(0)设置压缩等级,0为不压缩,这样可以提升下载速度

    • 2. 多个文件打包下载时并不是所有文件压缩完成后才开始下载,而是边压缩边下载,这样用户点了下载后,下载任务会直接进入浏览器下载列表

    3.4整个文件夹下载

    有时需要递归将整个文件夹打包下载下来

    1. @GetMapping("/dir")
    2. public void dir(HttpServletResponse response) throws IOException {
    3. File dir = new File("/Users/zxk/Movies/download");
    4. response.setContentType("application/zip");
    5. response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
    6. try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
    7. zipOutputStream.setLevel(0);
    8. zip(zipOutputStream, "", dir);
    9. zipOutputStream.flush();
    10. zipOutputStream.finish();
    11. }
    12. }
    13. public void zip(ZipOutputStream zipOutputStream, String parentPath, File file) throws IOException {
    14. if (file.isDirectory()) {
    15. File[] subFiles = file.listFiles();
    16. if (subFiles != null) {
    17. for (File f : subFiles) {
    18. zip(zipOutputStream, parentPath + file.getName() + "/", f);
    19. }
    20. }
    21. } else {
    22. try (FileInputStream fileInputStream = new FileInputStream(file)) {
    23. zipOutputStream.putNextEntry(new ZipEntry(parentPath + file.getName()));
    24. IOUtils.copy(fileInputStream, zipOutputStream);
    25. }
    26. }
    27. }

    注意事项:

    • 1. 会递归整个文件夹,文件夹文件不能太多

    3.5通过URL打包下载

    有时我们的文件可能是存在云存储上,数据库里存的是文件的ULR,下载时我们就需要通过URL将多个文件打包下载

    1. @GetMapping("/urlZip")
    2. public void urlZip(HttpServletResponse response) throws IOException {
    3. List urls = Arrays.asList("https://demo.com/11666832527556.jpeg",
    4. "https://demo.com/11666831385156.jpeg",
    5. "https://demo.com/11666829917700.jpeg",
    6. "https://demo.com/11666762702021.png",
    7. "https://demo.com/11666762702020.webp",
    8. "https://demo.com/11666549651972.jpg",
    9. "https://demo.com/11666524497476.jpeg",
    10. "https://demo.com/11666507113092.jpg");
    11. response.setContentType("application/zip");
    12. response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
    13. try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
    14. zipOutputStream.setLevel(0);
    15. urls.forEach(f -> {
    16. try {
    17. zipOutputStream.putNextEntry(new ZipEntry(StringUtils.getFilename(f)));
    18. HttpUtil.download(f, zipOutputStream, false);
    19. zipOutputStream.closeEntry();
    20. } catch (Exception e) {
    21. e.printStackTrace();
    22. }
    23. });
    24. zipOutputStream.flush();
    25. zipOutputStream.finish();
    26. }
    27. }

    3.6导出Excel下载

    有些下载的文件并不存在,而是先从数据库中查出数据,再动态生成文件,再提供给用户下载,这里我们以导出单个Excel文件为例:

    1. @GetMapping("/excel")
    2. public void excel(HttpServletResponse response) throws IOException {
    3. List> rows = new ArrayList<>();
    4. for (int i = 0; i < 10000; i++) {
    5. rows.add(IntStream.range(i, i + 100).boxed().collect(Collectors.toList()));
    6. }
    7. response.setContentType("application/vnd.ms-excel;charset=utf-8");
    8. response.setHeader("Content-Disposition", "attachment;filename=test.xls");
    9. try (OutputStream out = response.getOutputStream();
    10. ExcelWriter writer = ExcelUtil.getWriter()) {
    11. writer.write(rows);
    12. writer.flush(out, true);
    13. }
    14. }

    3.7批量导出Excel打包下载

    很多业务需要一次性导出多个Excel,这里我们可以将多个Excel压缩成一个Zip文件下载下来,这里以动态生成10个Excel主例:

    1. @GetMapping("/excelZip")
    2. public void excelZip(HttpServletResponse response) throws IOException {
    3. response.setContentType("application/zip");
    4. response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
    5. try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
    6. zipOutputStream.setLevel(0);
    7. for (int i = 0; i < 10; i++) {
    8. zipOutputStream.putNextEntry(new ZipEntry(String.format("%s.xls", i)));
    9. try (ExcelWriter writer = ExcelUtil.getWriter()) {
    10. writer.write(generateData());
    11. writer.flush(zipOutputStream);
    12. }
    13. }
    14. zipOutputStream.flush();
    15. zipOutputStream.finish();
    16. }
    17. }
    18. private ListgenerateData() {
    19. List> rows = new ArrayList<>();
    20. for (int i = 0; i < 10000; i++) {
    21. rows.add(IntStream.range(i, i + 100).boxed().collect(Collectors.toList()));
    22. }
    23. return rows;
    24. }

    3.8多线程加速下载

    有时下载数据量比较多,单线程打包会比较慢,这里我们就需要使用多线程并发打包提高打包速度,这里我以多线程下载多个URL文件为例使用commons-compress的ParallelScatterZipCreator多线程并发打包

    1. public static final ThreadFactory factory = new ThreadFactoryBuilder().setNamePrefix("compressFileList-pool-").build();
    2.   public static final ExecutorService executor = new ThreadPoolExecutor(101060, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), factory);
    3. @GetMapping("/parallelZip")
    4. public void excelZipThread(HttpServletResponse response) throws IOException {
    5. response.setContentType("application/zip");
    6. response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
    7. List urls = Arrays.asList("https://demo.com/11671291835144.png",
    8. "https://demo.com/11671291834824.png",
    9. "https://demo.com/11671291833928.png",
    10. "https://demo.com/11671291833800.png",
    11. "https://demo.com/11671291833480.png",
    12. "https://demo.com/11671291828232.png",
    13. "https://demo.com/11671291827528.png",
    14. "https://demo.com/11671291825737.png",
    15. "https://demo.com/11671291825736.png");
    16. ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);
    17. try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(response.getOutputStream())) {
    18. zipArchiveOutputStream.setLevel(0);
    19. urls.forEach(x -> {
    20. ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(StringUtils.getFilename(x));
    21. zipArchiveEntry.setMethod(ZipArchiveEntry.STORED);
    22. InputStreamSupplier inputStreamSupplier = () -> URLUtil.getStream(URLUtil.url(x));
    23. parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
    24. });
    25. parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
    26. } catch (Exception e) {
    27. e.printStackTrace();
    28. }
    29. }

    3.9多线程批量导Excel打包下载

    这种是比较复杂的,动态生成多个Excel文件后,使用多线程打成ZIP包下载

    1. @GetMapping("/parallelexcelZip")
    2. public void parallelexcelZip(HttpServletResponse response) throws IOException {
    3. response.setContentType("application/zip");
    4. response.setHeader("Content-Disposition", "attachment;filename=demo.zip");
    5. ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);
    6. try (ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(response.getOutputStream())) {
    7. zipArchiveOutputStream.setLevel(0);
    8. IntStream.range(1,10).forEach(x -> {
    9. InputStreamSupplier inputStreamSupplier = () ->{
    10. ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
    11. try(ExcelWriter writer=ExcelUtil.getWriter()) {
    12. writer.write(generateData());
    13. writer.flush(outputStream);
    14. }
    15. return new ByteArrayInputStream(outputStream.toByteArray());
    16. };
    17. ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(String.format("%s.xls",x));
    18. zipArchiveEntry.setMethod(ZipArchiveEntry.STORED);
    19. parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier);
    20. });
    21. parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
    22. } catch (Exception e) {
    23. e.printStackTrace();
    24. }
    25. }


    ​​

    ​   四、总结   

    本文主要总结了常用9种常见的文件下载操作,并提供对应的演示代码,当然还有一些没有总结到的,如分片下载、断点结续下、分布式下载限速等,这些高级下载在普通项目中用到的不太多所以没有总结。

  • 相关阅读:
    抖音矩阵系统,抖音矩阵系统源码。抖音SEO源码。
    centos 安装ngnix mysql php
    小型功率放大器的设计与制作——功率放大器的设计方法
    Sui主网升级至V1.11.2版本
    LeetCode-17-电话号码的字母组合
    插画、插图网站,免费(商用)
    民安智库(北京第三方窗口测评)开展汽车消费者焦点小组座谈会调查
    孙卫琴的《精通Vue.js》读书笔记-路由管理器的基本用法
    洛谷千题详解 | P1014 [NOIP1999 普及组] Cantor 表【C++、Java语言】
    隐私计算助力数据的安全流通与共享
  • 原文地址:https://blog.csdn.net/whzhaochao/article/details/132861431