文件下载
本文旨在详细说明如何在 Java 后端应用程序中实现 PDF 和 DOCX 文件的下载功能,结合 tio-boot
框架和 hutool
工具库。
环境依赖
在开始之前,请确保您的项目已添加以下依赖:
- hutool-core:提供丰富的工具类,例如资源访问工具。
- tio-boot:作为网络框架处理 HTTP 请求和响应。
- ZXing(用于二维码生成):用于生成二维码图片。
Maven 依赖示例
<dependencies>
<!-- Hutool 核心库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.11</version>
</dependency>
<!-- Tio-Boot 框架 -->
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>tio-boot</artifactId>
<version>最新版本</version>
</dependency>
<!-- ZXing 核心库(用于二维码生成) -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.4.1</version>
</dependency>
</dependencies>
实现文件下载功能
返回字节数据
下载 PDF 文件
实现步骤
- 定义请求路径:使用
@RequestPath("/download/pdf/{id}")
注解标注方法,定义处理 PDF 文件下载的请求路径。 - 读取文件:使用
ResourceUtil.getStream("pdf/" + id + ".pdf")
根据文件 ID 动态获取 PDF 文件的输入流。 - 发送响应:将 PDF 文件内容以字节形式发送给客户端,并设置
Content-Type
为application/pdf; charset=utf-8
。
示例代码
@RequestPath("/download/pdf/{id}")
public HttpResponse downloadPdf(String id, HttpRequest request) {
log.info("id:{}", id);
InputStream inputStream = ResourceUtil.getStream("pdf/" + id + ".pdf");
int available;
try {
available = inputStream.available();
byte[] fileBytes = new byte[available];
inputStream.read(fileBytes, 0, available);
HttpResponse response = Resps.bytesWithContentType(request, fileBytes, "application/pdf; charset=utf-8");
return response;
} catch (IOException e) {
e.printStackTrace();
return Resps.json(request, RespBodyVo.fail("Error generating captcha"));
}
}
下载 DOCX 文件
实现步骤
- 定义请求路径:使用
@RequestPath("/download/docx/{id}")
注解标注方法,定义处理 DOCX 文件下载的请求路径。 - 读取文件:使用
ResourceUtil.getStream(id+".docx")
根据文件 ID 动态获取 DOCX 文件的输入流。 - 发送响应:将 DOCX 文件内容以字节形式发送给客户端,并设置
Content-Type
为application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=utf-8
。
示例代码
@RequestPath("/download/docx/{id}")
public HttpResponse download(String id, HttpRequest request) {
InputStream inputStream = ResourceUtil.getStream(id+".docx");
int available;
try {
available = inputStream.available();
byte[] fileBytes = new byte[available];
inputStream.read(fileBytes, 0, available);
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();
return Resps.json(request, RespBodyVo.fail("Error generating captcha"));
}
}
下载图片
返回验证码
利用 hutool
库生成并返回图形验证码。
示例代码
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.annotation.RequestPath;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.resp.RespBodyVo;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
@AController
@RequestPath("/captcha")
public class CaptchaController {
@RequestPath("")
public HttpResponse captcha(HttpRequest request) {
// 创建线性验证码
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// 获取验证码图片,并写入到输出流
captcha.write(outputStream);
// 将输出流转换为字节数组
byte[] captchaBytes = outputStream.toByteArray();
// 使用 Resps 工具类创建一个包含验证码图片的响应
HttpResponse response = Resps.bytesWithContentType(request, captchaBytes, "image/jpeg");
// 返回响应
return response;
} catch (IOException e) {
e.printStackTrace();
return Resps.json(request, RespBodyVo.fail("Error generating captcha"));
}
}
}
代码详解
包和导入:
package com.litongjava.tio.boot.hello.AController;
:定义类所在的包。- 导入处理 HTTP 请求和响应的类,验证码生成工具类等。
类和注解:
@RequestPath("/captcha")
:类级别的注解,设置请求路径为/captcha
。public class CaptchaController
:定义控制器类。
方法和路由:
@RequestPath("")
:方法级别的注解,指定处理/captcha
路径的请求。public HttpResponse captcha(HttpRequest request)
:定义处理 HTTP 请求的方法。
生成验证码:
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100)
:创建一个线性验证码,尺寸为 200x100 像素。captcha.write(outputStream)
:将验证码图片写入输出流。byte[] captchaBytes = outputStream.toByteArray()
:将输出流转换为字节数组。
创建响应:
Resps.bytesWithContentType(request, captchaBytes, "image/jpeg")
:创建包含验证码图片的响应,设置内容类型为image/jpeg
。
异常处理:
- 捕获并处理
IOException
异常,返回包含错误信息的 JSON 响应。
- 捕获并处理
返回二维码
利用 hutool
和 ZXing
库生成并返回二维码图片。
<!-- ZXing 核心库(用于二维码生成) -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.4.1</version>
</dependency>
示例代码
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.annotation.RequestPath;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.resp.RespBodyVo;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
@AController
@RequestPath("/qr")
public class QrController {
@RequestPath("")
public HttpResponse qr(HttpRequest request, String content) {
// 获取要生成的二维码内容
if (StrUtil.isBlank(content)) {
return Resps.json(request, RespBodyVo.fail("No content provided for QR code"));
}
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// 生成二维码图片,并写入到输出流
QrCodeUtil.generate(content, 300, 300, "png", outputStream);
// 将输出流转换为字节数组
byte[] qrCodeBytes = outputStream.toByteArray();
// 使用 Resps 工具类创建一个包含二维码图片的响应
HttpResponse response = Resps.bytesWithContentType(request, qrCodeBytes, "image/png");
// 返回响应
return response;
} catch (IOException e) {
e.printStackTrace();
return Resps.json(request, RespBodyVo.fail("Error generating QR code"));
}
}
}
代码详解
类和注解:
@RequestPath("/qr")
:类级别的注解,设置请求路径为/qr
。public class QrController
:定义控制器类。
方法和路由:
@RequestPath("")
:方法级别的注解,指定处理/qr
路径的请求。public HttpResponse qr(HttpRequest request, String content)
:定义处理 HTTP 请求的方法,接收content
参数用于生成二维码内容。
生成二维码:
- 检查传入的
content
是否为空,若为空返回错误响应。 - 使用
QrCodeUtil.generate(content, 300, 300, "png", outputStream)
生成二维码图片,尺寸为 300x300 像素,格式为 PNG,并写入输出流。
- 检查传入的
创建响应:
- 将输出流转换为字节数组。
- 使用
Resps.bytesWithContentType
创建包含二维码图片的响应,设置内容类型为image/png
。
异常处理:
- 捕获并处理
IOException
异常,返回包含错误信息的 JSON 响应。
- 捕获并处理
请求示例
访问 http://localhost/qr?content=tio-boot
将生成包含文本 “tio-boot” 的二维码图片。
通过浏览器触发文件下载
实现步骤
- 定义请求路径:创建一个控制器方法,处理文件下载请求。
- 设置响应头:设置
Content-Type
和Content-Disposition
以指示浏览器下载文件。 - 发送文件内容:将文件内容以字节形式发送给客户端。
示例代码
package com.litongjava.tio.web.hello.controller;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DocumentController {
public HttpResponse export(Long documentId) {
String downloadFilename = "my.md";
log.info("filename:{}", downloadFilename);
String contentType = "text/markdown; charset=utf-8";
HttpResponse httpResponse = TioRequestContext.getResponse();
httpResponse.setContentType(contentType);
httpResponse.setHeader("Content-Disposition", "attachment; filename=\"" + downloadFilename + "\"");
httpResponse.setString("Hi");
return httpResponse;
}
}
原理解析
浏览器触发文件下载的原理主要依赖于 HTTP 响应头的配置,尤其是 Content-Type
和 Content-Disposition
。以下是具体原理解析:
Content-Type:
- 指定返回内容的 MIME 类型,告知浏览器内容的格式。
- 例如,
application/pdf
表示 PDF 文件,application/vnd.openxmlformats-officedocument.wordprocessingml.document
表示 DOCX 文件,image/png
表示 PNG 图片等。
Content-Disposition:
- 控制内容的显示方式。
attachment
表示内容应作为附件下载,而不是直接在浏览器中显示。filename
参数指定下载文件的默认名称。
响应内容:
- 通过将文件内容作为字节流写入响应体,浏览器接收到响应后会根据
Content-Disposition
头信息提示用户下载文件。
- 通过将文件内容作为字节流写入响应体,浏览器接收到响应后会根据
详细步骤:
- 客户端请求:用户通过浏览器访问特定的下载 URL(例如
/download/pdf/123
)。 - 服务器处理:
- 服务器接收到请求后,读取对应的文件(如 PDF 文件)。
- 设置响应头:
Content-Type
:指定文件类型(例如application/pdf
)。Content-Disposition
:设置为attachment; filename="文件名.pdf"
,指示浏览器下载文件并使用指定名称。
- 将文件内容以字节流写入响应体。
- 浏览器响应:
- 根据响应头,浏览器识别出需要下载文件,并弹出下载对话框,提示用户保存文件。
示例流程:
- 用户访问
http://localhost/download/document/123
。 - 服务器读取
document_123.md
文件内容。 - 设置响应头:
Content-Type: text/markdown; charset=utf-8
Content-Disposition: attachment; filename="document_123.md"
- 将文件内容写入响应体。
- 浏览器收到响应后,提示用户下载
document_123.md
文件。
注意事项:
- 确保文件路径和命名方式符合实际项目需求。
- 对于大文件,考虑使用流式传输以避免内存占用过高。
- 处理异常情况,如文件不存在或读取失败,返回合适的错误响应。
结论
通过上述实现,您可以在 Java Web 应用中方便地添加文件下载功能,支持多种文件格式,如 PDF、DOCX、图片(验证码和二维码)等。关键在于正确设置 HTTP 响应头和处理文件的字节流传输。请根据实际项目需求调整文件路径和命名方式,确保下载功能的稳定性和安全性。