llms.txt: machine-readable documentation index. Markdown version of this page is also available.

IntelliJ IDEA 2026.3 Help

Spring Boot REST API 教程,第 3 部分:上传和下载文件

在 Spring Boot REST API 教程,第 2 部分:添加 GET 和 DELETE 端点 中,API 使用照片标识符和文件名称。 它可以描述照片,但不保存图像。 在本部分中,添加一个用于上传文件的页面,将文件字节存储在服务中,并通过下载端点返回这些字节。

创建用于文件上传的 HTML 页面

要上传图像,浏览器需要一种选择文件并将其发送到 API 的方式。 此页面提供文件选择器和上传按钮,然后在警报中显示服务器响应。

  • 在 src/main/resources/static 中,创建 upload.html ,内容如下:

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Photo Upload</title> </head> <body> <h2>Photo Upload</h2> <input id="fileupload" type="file" name="fileupload"/> <button id="upload-button" onclick="uploadFile()">Upload</button> <script> async function uploadFile() { let formData = new FormData(); formData.append("data", fileupload.files[0]); await fetch("http://localhost:8080/photos", { method: "POST", body: formData }).then(result => result.text()) .then(text => alert(text)); } </script> </body> </html>

    Spring Boot 会直接提供此目录中的文件,因此无需控制器即可通过 /upload.html 访问该页面。

    FormData 会让浏览器发送 multipart 请求,该请求会同时携带文件字节、文件名称和内容类型。 每个部分都有一个名称:这里是 data。 上传端点使用同一名称在请求中查找文件。 那里的不匹配是上传看起来没有任何反应的常见原因。

将文件数据添加到模型

现有的 Photo 类包含文件名称,但没有地方保存图像。 添加字节和内容类型,例如 image/jpeg ,这样下载端点稍后就可以告诉浏览器它返回的是哪种文件。

  1. 将内容类型和文件字节的字段添加到 Photo:

    private String contentType; @JsonIgnore private byte[] data;

    照片列表只需要元数据。 包含字节会在 JSON 响应中的每张照片里添加一条很长的 base64 字符串。 @JsonIgnore 会告知 Jackson JSON 转换器跳过此字段,只保留标识符、文件名称和内容类型。 字节仍会被存储,以便下载端点单独返回。

  2. 为这两个新字段生成访问器:按 Alt+Insert ,选择 Getter 和 Setter ,然后选择 contentType 和 data。

  3. 从 PhotosService 中移除添加 hello.jpg 的构造函数:

    public PhotosService() { db.put("1", new Photo("1", "hello.jpg")); }

    实际上传会替换该示例,而该示例没有图像数据。 此外,从 Photo 中移除含两个实参的构造函数:

    public Photo(String id, String fileName) { this.id = id; this.fileName = fileName; }

    保留无实参构造函数。

处理 multipart 文件上传

  1. 在 PhotosService 中,添加一个根据文件数据构建并存储照片的方法:

    public Photo save(String fileName, String contentType, byte[] data) { Photo photo = new Photo(); photo.setId(UUID.randomUUID().toString()); photo.setFileName(fileName); photo.setContentType(contentType); photo.setData(data); db.put(photo.getId(), photo); return photo; }

    服务器会分配一个 UUID 来标识照片,并返回存储的对象,以便客户端接收该标识符用于后续请求。 照片会保留在内存中,直到应用程序停止;第 4 部分会用数据库替换该 map。

  2. 在 PhotosController 中,添加上传端点:

    @PostMapping("/photos") public Photo create(@RequestPart("data") MultipartFile file) throws IOException { return photosService.save(file.getOriginalFilename(), file.getContentType(), file.getBytes()); }

    @RequestPart("data") 会选择页面发送的文件。 MultipartFile 会提供其原始名称、内容类型和字节。

    控制器会提取这些值,因此服务不依赖于 HTTP 特定的 MultipartFile 类型。 同一个服务方法可以从其他来源接受文件名称、内容类型和字节,而无需 HTTP 请求。 读取字节可能会抛出 IOException ,该方法对此进行了声明。

提高上传大小限制

默认文件大小限制为 1 MB。 如果上传更大的文件,请求会失败, 运行 工具窗口会报告 MaxUploadSizeExceededException。 在上传更大的照片之前提高限制。 此设置应位于 application.properties 中,可在其中配置应用程序行为,而无需更改 Java 编码。

  • 打开 src/main/resources/application.properties 并开始输入 spring.servlet.multipart。 IntelliJ IDEA 会补全配置密钥并显示其描述。 设置这两个限制:

    spring.servlet.multipart.max-file-size=100MB spring.servlet.multipart.max-request-size=100MB

    max-file-size 限制每个文件; max-request-size 限制整个请求,包括 multipart 开销。

添加文件下载端点

API 现在可以存储图像,但其 JSON 响应会有意省略字节。 添加一个单独的下载控制器。 此端点会返回图像字节并设置页眉,告知浏览器收到的是哪种文件以及应将其称作什么。

  1. 在 web 软件包中,创建名称为 DownloadController 的类并 inject 服务:

    @RestController public class DownloadController { private final PhotosService photosService; public DownloadController(PhotosService photosService) { this.photosService = photosService; } }

    两个控制器都会接收同一个服务实例,因此它们使用相同的已存储照片。

  2. 在 DownloadController 中,添加一个将照片字节作为文件下载返回的方法:

    @GetMapping("/download/{id}") public ResponseEntity<byte[]> download(@PathVariable String id) { Photo photo = photosService.get(id); if (photo == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND); } HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.valueOf(photo.getContentType())); headers.setContentDisposition( ContentDisposition.builder("attachment") .filename(photo.getFileName()) .build()); return new ResponseEntity<>(photo.getData(), headers, HttpStatus.OK); }

    ResponseEntity 会组合字节、页眉和状态。 Content-Type 会标识文件格式,而 Content-Disposition 会要求浏览器以其原始名称下载文件。

测试文件上传和下载

  1. 重新启动应用程序,打开 http://localhost:8080/upload.html ,选择小于 100 MB 的图像,然后点击 上传。

    警报会显示标识符、文件名称和内容类型。 从警报中复制标识符。

  2. 打开 http://localhost:8080/photos。 JSON 会列出标识符、文件名称和内容类型,不包含图像字节。

  3. 打开 http://localhost:8080/download/<id> ,将 <id> 替换为复制的标识符。 浏览器会以图像的原始名称下载该图像。

    浏览器下载历史记录显示 ij.png,在使用照片标识符打开 /download 端点后,该文件已按其原始名称保存。
  4. 请求一个不存在的标识符。 下载端点会返回 404,与 JSON 端点相同,因为两者都会询问服务,并且都以相同方式处理 null。

下一部分

2026年 11月 3日