如何通过Spring Boot REST API提供存储在S3上的PDF文件?
我看你现在的代码在处理S3上的PDF文件时踩了个常见的坑——用字符流(Reader)来读取二进制的PDF文件,这会直接破坏文件的字节结构,导致返回的PDF无法正常打开。咱们来修正这个问题,给出正确的Spring Boot REST API实现方式:
先说说原代码的核心问题
PDF是二进制文件,而BufferedReader、char[]这类工具是专门用来处理文本的。用字符流读取二进制流时,会因为编码转换丢失关键字节,直接导致文件损坏,这是绝对不能犯的错误。正确的做法是直接用字节流读取S3对象内容,再通过Spring Boot正确返回二进制响应。
方案一:使用InputStreamResource返回PDF(适合中小型文件)
这种方式会把文件流封装成Spring的资源对象,框架会帮我们处理流的关闭和响应输出:
import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.S3Object; import java.io.InputStream; @RestController public class PdfController { private final S3Client s3Client; // 构造注入S3Client(Spring Boot推荐的依赖注入方式) public PdfController(S3Client s3Client) { this.s3Client = s3Client; } @GetMapping("/get-pdf") public ResponseEntity<InputStreamResource> fetchPdfFromS3() { String bucketName = "S3_BUCKET_NAME"; String pdfObjectKey = "prefixUrl"; // 替换成你的PDF在S3中的实际key // 获取S3对象的字节流 S3Object s3Object = s3Client.getObject(GetObjectRequest.builder() .bucket(bucketName) .key(pdfObjectKey) .build()); InputStream pdfInputStream = s3Object.response().inputStream(); InputStreamResource resource = new InputStreamResource(pdfInputStream); // 设置响应头,告诉浏览器这是PDF文件 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_PDF); // 可选:控制浏览器行为——attachment是下载,inline是在线预览 headers.setContentDispositionFormData("attachment", "sample.pdf"); return ResponseEntity.status(HttpStatus.OK) .headers(headers) .body(resource); } }
方案二:使用StreamingResponseBody(适合大文件,避免内存溢出)
如果你的PDF文件很大,不想把整个文件加载到内存里,可以用流式输出的方式,边读边写:
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.S3Object; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @RestController public class PdfController { private final S3Client s3Client; public PdfController(S3Client s3Client) { this.s3Client = s3Client; } @GetMapping("/stream-pdf") public ResponseEntity<StreamingResponseBody> streamPdfFromS3() { String bucketName = "S3_BUCKET_NAME"; String pdfObjectKey = "prefixUrl"; S3Object s3Object = s3Client.getObject(GetObjectRequest.builder() .bucket(bucketName) .key(pdfObjectKey) .build()); InputStream pdfInputStream = s3Object.response().inputStream(); // 定义流式输出逻辑 StreamingResponseBody responseBody = outputStream -> { byte[] buffer = new byte[8192]; // 8KB缓冲区 int bytesRead; while ((bytesRead = pdfInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } pdfInputStream.close(); // 手动关闭输入流,也可以用try-with-resources }; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_PDF); headers.setContentDispositionFormData("inline", "large-sample.pdf"); return ResponseEntity.status(HttpStatus.OK) .headers(headers) .body(responseBody); } }
关键注意事项
- 永远用字节流处理二进制文件:别再用
Reader、StringBuilder这类文本工具处理PDF、图片这类二进制资源了。 - 资源安全关闭:可以用
try-with-resources包裹输入流,确保即使发生异常也能正确关闭资源,避免连接泄漏。 - S3Client配置:确保你的Spring Boot项目已经正确配置了S3Client(比如通过AWS SDK v2的starter,配置access key、secret key、region等)。
- 响应头要准确:
Content-Type必须设为application/pdf,否则浏览器可能无法正确识别文件类型;Content-Disposition可以控制文件是下载还是在线预览。
内容的提问来源于stack exchange,提问作者Shivkumar Mallesappa
相关产品推荐
相关产品推荐

