Skip to content

文件下载 ​

本文详细介绍了如何在 tio-boot 后端应用程序中实现文件下载功能,涵盖任意文件下载、浏览器触发下载、以及 DOCX/PDF 文件下载等多种场景,同时还展示了如何生成二维码并通过浏览器下载二维码图片。内容包括完整的代码示例和详细的原理解析,适用于希望扩展或自定义文件下载功能的开发者。


1. 任意文件下载 ​

在一些场景下,我们可能需要实现一个通用的文件下载接口,该接口能够根据传入的参数动态读取文件并返回给客户端。下面的示例代码展示了如何读取指定文件,并将文件字节流以合适的 MIME 类型返回给浏览器。

java
package nexus.io.test.controller;

import java.io.File;

import nexus.io.annotation.RequestPath;
import nexus.io.media.NativeMedia;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.model.upload.UploadFile;
import nexus.io.tio.http.server.util.Resps;
import nexus.io.tio.utils.http.ContentTypeUtils;
import nexus.io.tio.utils.hutool.FileUtil;
import nexus.io.tio.utils.hutool.FilenameUtils;

@RequestPath("/media")
public class MediaController {

  public HttpResponse toMp3(HttpRequest request) {
    // 指定要下载的文件名称(这里以 mp3 文件为例)
    String result = "a.mp3";
    // 根据文件后缀获取正确的 MIME 类型
    String contentType = ContentTypeUtils.getContentType(FilenameUtils.getSuffix(result));
    // 将文件读取为字节数组
    byte[] fileBytes = FileUtil.readBytes(new File(result));
    HttpResponse response = TioRequestContext.getResponse();
    // 使用工具类将字节流和 MIME 类型设置到响应中
    Resps.bytesWithContentType(response, fileBytes, contentType);
    return response;
  }
}

原理说明 ​

  • 文件读取:使用 FileUtil.readBytes() 将文件转换为字节数组,适用于文件较小的场景。
  • ContentType:通过 ContentTypeUtils 根据文件后缀设置 MIME 类型,确保浏览器正确解析文件内容。
  • 响应构建:利用 Resps.bytesWithContentType 工具类,封装文件字节流和 MIME 类型,返回一个 HTTP 响应。

2. 通过浏览器触发文件下载 ​

在大多数 Web 应用中,我们希望通过浏览器的下载功能直接下载文件。为此,需要在 HTTP 响应中设置特定的响应头,提示浏览器将内容作为附件下载。

示例代码 ​

java
package nexus.io.linux.handler;

import java.io.File;

import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.utils.http.ContentTypeUtils;
import nexus.io.tio.utils.hutool.FileUtil;
import nexus.io.tio.utils.hutool.FilenameUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DownloadHandler {

  public HttpResponse donwload(HttpRequest httpRequest) {

    HttpResponse httpResponse = TioRequestContext.getResponse();
    String downloadFilename = "readme.md";
    File file = new File(downloadFilename);

    String suffix = FilenameUtils.getSuffix(downloadFilename);
    // 设置响应内容类型,此处使用 Markdown 格式
    String contentType = ContentTypeUtils.getContentType(suffix);
    log.info("filename:{},{}", downloadFilename, contentType);
    
    
    // 设置 Content-Type 响应头,确保浏览器以正确格式解析文件内容
    httpResponse.setContentType(contentType);
    // 设置 Content-Disposition 响应头,告知浏览器将内容作为附件下载,并指定默认文件名
    httpResponse.setAttachmentFilename(downloadFilename);
    // 设置响应体内容,实际项目中可替换为文件的字节流
    httpResponse.setBody(FileUtil.readBytes(file));

    return httpResponse;
  }
}

原理说明 ​

  • Content-Disposition:设置为 attachment 告知浏览器将文件作为附件下载,同时 filename 参数指定了默认的文件名称。
  • Content-Type:确保返回的内容格式与文件实际类型相匹配,从而使下载后的文件能够被正确识别。

http响应头

content-disposition:attachment; filename="readme.md"
content-type:application/octet-stream

3. 文档下载 ​

针对常见的文档文件(如 DOCX 和 PDF),可以根据文件 ID 动态获取文件内容,并返回给客户端。以下分别介绍 DOCX 和 PDF 文件下载的实现方法。

3.1 下载 DOCX 文件 ​

示例代码 ​

java
@RequestPath("/download/docx/{id}")
public HttpResponse download(String id, HttpRequest request) {
  // 根据传入的 id 动态获取 DOCX 文件的输入流
  InputStream inputStream = ResourceUtil.getStream(id+".docx");
  int available;
  try {
    available = inputStream.available();
    byte[] fileBytes = new byte[available];
    // 将文件内容读取到字节数组中
    inputStream.read(fileBytes, 0, available);
    // 设置 DOCX 文件的 MIME 类型
    String contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=utf-8";
    HttpResponse response = Resps.bytesWithContentType(request, fileBytes, contentType);
    return response;
  } catch (IOException e) {
    e.printStackTrace();
    // 文件读取异常时返回错误 JSON 响应
    return Resps.json(request, RespBodyVo.fail("Error generating captcha"));
  }
}

3.2 下载 PDF 文件 ​

示例代码 ​

java
@RequestPath("/download/pdf/{id}")
public HttpResponse downloadPdf(String id, HttpRequest request) {
  log.info("id:{}", id);
  // 从资源路径中动态读取 PDF 文件(假设文件存放在 pdf 目录下)
  InputStream inputStream = ResourceUtil.getStream("pdf/" + id + ".pdf");
  int available;
  try {
    available = inputStream.available();
    byte[] fileBytes = new byte[available];
    // 将文件内容读取到字节数组中
    inputStream.read(fileBytes, 0, available);
    // 设置 PDF 文件的 MIME 类型
    HttpResponse response = Resps.bytesWithContentType(request, fileBytes, "application/pdf; charset=utf-8");
    return response;
  } catch (IOException e) {
    e.printStackTrace();
    // 文件读取异常时返回错误 JSON 响应
    return Resps.json(request, RespBodyVo.fail("Error generating captcha"));
  }
}

原理说明 ​

  • 资源获取:利用 ResourceUtil.getStream() 方法根据文件名或路径获取文件的输入流,适用于存储在项目资源中的文件。
  • 字节流读取:调用 inputStream.available() 获取可读取的字节数,并一次性读取文件内容。对于大文件,应考虑使用流式传输以避免内存压力。
  • 错误处理:在读取过程中捕获异常,并返回格式化的错误响应,确保接口的健壮性。

3.3 用文档类型方法直接下载 ​

Resps 为常用文档类型准备了快捷方法:一行写完 Content-Type 与附件文件名,不需要自己拼字符串。文件名里的中文会按 UTF-8 编码写入响应头:

java
import java.io.InputStream;

import nexus.io.annotation.Post;
import nexus.io.annotation.RequestPath;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
import nexus.io.tio.utils.hutool.ResourceUtil;

@RequestPath("/download")
public class DocumentDownloadController {

  @Post("/docx/{id}")
  public HttpResponse docx(String id) {
    byte[] bytes = read(ResourceUtil.getStream(id + ".docx"));
    return Resps.docx(TioRequestContext.getResponse(), bytes, id + ".docx");
  }

  @Post("/pdf/{id}")
  public HttpResponse pdf(String id) {
    byte[] bytes = read(ResourceUtil.getStream("pdf/" + id + ".pdf"));
    return Resps.pdf(TioRequestContext.getResponse(), bytes, id + ".pdf");
  }

  private byte[] read(InputStream inputStream) {
    try (InputStream in = inputStream) {
      return in.readAllBytes();
    } catch (java.io.IOException e) {
      throw new IllegalStateException("读取文件失败", e);
    }
  }
}

同一组方法还有 excel、excelXls、pptx、ppt、image,以及通用的 download(response, bytes, contentType, filename);完整清单见 Resps。


4. 生成二维码并下载 ​

除了常规的文件下载外,有时还需要动态生成图片(例如二维码)并返回给客户端。下面以二维码生成为例,展示如何使用 ZXing 库生成二维码图片,并通过 HTTP 响应返回。

前置准备 ​

请确保项目中已添加以下依赖:

Maven 依赖示例 ​

xml
<dependencies>
    <!-- ZXing 核心库(用于二维码生成) -->
    <dependency>
      <groupId>com.google.zxing</groupId>
      <artifactId>core</artifactId>
      <version>3.4.1</version>
    </dependency>
    <dependency>
      <groupId>com.google.zxing</groupId>
      <artifactId>javase</artifactId>
      <version>3.4.1</version>
    </dependency>

    <!-- Tio-Boot 框架 -->
  <dependency>
    <groupId>nexus.io</groupId>
    <artifactId>tio-boot</artifactId>
    <version>最新版本</version>
  </dependency>
</dependencies>

QrController 实现 ​

示例代码 ​

java
package nexus.io.test.controller;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import nexus.io.annotation.AController;
import nexus.io.annotation.RequestPath;
import nexus.io.model.body.RespBodyVo;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
import nexus.io.tio.utils.hutool.StrUtil;
import nexus.io.tio.utils.qrcode.QrCodeUtils;

@AController
@RequestPath("/qr")
public class QrController {

  @RequestPath("/gen")
  public HttpResponse qr(String content) {
    HttpResponse response = TioRequestContext.getResponse();
    // 验证二维码内容不能为空
    if (StrUtil.isBlank(content)) {
      return Resps.json(response, RespBodyVo.fail("No content provided for QR code"));
    }

    try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
      // 生成二维码图片,并写入到输出流中,二维码尺寸为 300x300 像素
      QrCodeUtils.generateQRCode(content, 300, 300, outputStream);

      // 将输出流转换为字节数组
      byte[] qrCodeBytes = outputStream.toByteArray();

      // 使用工具类返回包含二维码图片字节流的响应,设置 MIME 类型为 image/png
      return Resps.bytesWithContentType(response, qrCodeBytes, "image/png");

    } catch (IOException e) {
      e.printStackTrace();
      return Resps.json(response, RespBodyVo.fail("Error generating QR code"));
    }
  }
}

原理说明 ​

  • 二维码生成:利用 ZXing 库生成二维码图片,通过 QrCodeUtils.generateQRCode() 方法将二维码内容写入输出流中。
  • 流转换:将 ByteArrayOutputStream 中的内容转换为字节数组,方便后续通过 HTTP 响应返回给客户端。
  • 响应设置:设置正确的 MIME 类型 "image/png",确保浏览器能够识别并显示图片内容。

5. 总结 ​

通过以上示例,我们可以看到在 Java Web 应用中实现文件下载功能的关键步骤:

  1. 定义请求路径:通过注解或路由配置定义对应的 URL 接口。
  2. 读取文件:根据实际需求读取服务器中的文件,支持任意文件、DOCX、PDF、二维码图片等。
  3. 设置响应头:重点在于设置 Content-Type 和 Content-Disposition 响应头,确保浏览器能够正确识别文件类型并触发下载。
  4. 返回响应内容:将读取的字节流写入 HTTP 响应体中,完成文件传输。
  5. 错误处理:合理捕获异常并返回友好的错误提示,提升接口的健壮性和用户体验。

通过正确的设置和处理,开发者可以在项目中灵活扩展文件下载功能,同时确保安全性和性能。希望本文能为您提供清晰的实现思路和实践参考。

MIT Licensed