在 Spring Boot 中,资源文件的访问与管理是常见的操作需求,比如加载配置文件、读取静态文件或从外部文件系统读取文件。Spring 提供了多种方式来处理资源文件访问,包括通过 ResourceLoader
、@Value
注解以及 ApplicationContext
获取资源。下面我们详细介绍这几种常见的文件资源访问与管理方法。
ResourceLoader
加载资源ResourceLoader
是 Spring 提供的一个接口,用于加载各种类型的资源文件。它支持从多种来源加载资源,包括类路径、文件系统、URL 等。你可以通过 ResourceLoader
来轻松访问文件资源。
你可以通过 ResourceLoader
来加载类路径中的资源文件(例如 src/main/resources
下的文件)。以下示例展示如何加载类路径中的文件:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@Service
public class FileResourceService {
@Autowired
private ResourceLoader resourceLoader;
public String loadClasspathFile() throws IOException {
// 加载类路径下的资源文件
Resource resource = resourceLoader.getResource("classpath:data/example.txt");
// 读取文件内容
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
return reader.lines().reduce("", (acc, line) -> acc + line + "\n");
}
}
}
ResourceLoader.getResource("classpath:...")
:用于加载类路径下的文件资源。classpath:
前缀通常用于读取位于 src/main/resources
中的文件 (相当于替代了 src/main/resources
这个路径)除了类路径,ResourceLoader
还可以加载文件系统中的资源文件。
public String loadFileSystemFile() throws IOException {
// 加载文件系统中的文件资源
Resource resource = resourceLoader.getResource("file:/path/to/your/file.txt");
// 读取文件内容
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
return reader.lines().reduce("", (acc, line) -> acc + line + "\n");
}
}
ResourceLoader.getResource("file:...")
:用于从文件系统加载文件资源。ResourceLoader
还可以加载远程 URL 资源,比如从网络上加载文件:
public String loadUrlFile() throws IOException {
// 加载 URL 资源
Resource resource = resourceLoader.getResource("https://example.com/file.txt");
// 读取文件内容
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
return reader.lines().reduce("", (acc, line) -> acc + line + "\n");
}
}
ResourceLoader.getResource("https://...")
:用于从网络 URL 加载资源文件。@Value
注入文件路径@Value
注解可以从配置文件中注入资源文件路径,这是一种简化的方式,特别是在需要配置外部资源时非常有用。@Value
注解不仅可以用于注入配置文件中的字符串,还可以直接注入 Resource
对象。
通过 @Value
可以直接注入文件路径作为字符串,并使用传统的 IO 来读取文件:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
public class FilePathService {
// 注入文件路径
@Value("${file.path}")
private String filePath;
public String readFileContent() throws IOException {
Path path = Paths.get(filePath);
return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
}
}
@Value("${file.path}")
:可以从 application.properties
或 application.yml
文件中注入文件路径。Resource
你还可以直接通过 @Value
注入 Resource
对象,然后使用 Resource
对象的 getInputStream()
方法读取文件内容。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@Service
public class ResourceService {
// 注入类路径下的资源文件
@Value("classpath:data/example.txt")
private Resource resource;
public String loadFileContent() throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
return reader.lines().reduce("", (acc, line) -> acc + line + "\n");
}
}
}
@Value("classpath:...")
:注入类路径下的资源文件,直接注入为 Resource
类型。ApplicationContext
获取资源ApplicationContext
是 Spring 容器的核心接口,它不仅可以管理 Bean,还提供了资源加载功能。通过 ApplicationContext.getResource()
方法,可以访问与 ResourceLoader
相同的功能。
ApplicationContext
加载资源你可以通过 ApplicationContext.getResource()
方式来加载各种资源文件。
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@Service
public class AppContextResourceService {
private final ApplicationContext applicationContext;
public AppContextResourceService(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public String loadFileFromClasspath() throws IOException {
// 通过 ApplicationContext 获取类路径下的文件
Resource resource = applicationContext.getResource("classpath:data/example.txt");
// 读取文件内容
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
return reader.lines().reduce("", (acc, line) -> acc + line + "\n");
}
}
public String loadFileFromFileSystem() throws IOException {
// 通过 ApplicationContext 获取文件系统中的文件
Resource resource = applicationContext.getResource("file:/path/to/your/file.txt");
// 读取文件内容
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
return reader.lines().reduce("", (acc, line) -> acc + line + "\n");
}
}
}
ApplicationContext
使用的注入方式是构造器注入 。在 Spring Boot 中,访问和管理文件资源有多种方式:
ResourceLoader
:可以加载类路径、文件系统和 URL 等多种来源的文件资源,非常灵活。@Value
注入:可以直接注入文件路径或者 Resource
对象,适合简化资源加载。ApplicationContext.getResource()
:通过 Spring 的 ApplicationContext
获取资源,提供与 ResourceLoader
类似的功能。在 Spring Boot 中,文件的上传与下载是常见的操作,特别是在处理 Web 应用时。Spring Boot 提供了 MultipartFile
来处理文件上传,同时通过 ResponseEntity
可以简便地实现文件下载功能。处理大文件时,常常需要考虑性能和资源管理的问题,如文件大小限制、分块上传和流式下载等。
MultipartFile
处理文件上传请求Spring Boot 内置了对文件上传的支持,使用 MultipartFile
来处理上传的文件。可以通过 @RequestParam
或 @ModelAttribute
来接收文件上传请求,并将文件保存到指定路径。
示例:
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
public class FileUploadController {
private static final String UPLOAD_DIR = "uploads/";
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "上传文件为空";
}
try {
// 确保上传目录存在
Path uploadPath = Paths.get(UPLOAD_DIR);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 保存文件到目标目录
Path filePath = uploadPath.resolve(file.getOriginalFilename());
file.transferTo(filePath.toFile());
return "文件上传成功: " + file.getOriginalFilename();
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败";
}
}
}
用户上传文件:
/upload
接口。MultipartFile
接收。保存文件:
Path
和 Files
确保上传目录存在。Path.resolve()
拼接文件名,生成目标路径。MultipartFile.transferTo()
保存文件到目标路径。返回结果:
MultipartFile
的作用:
transferTo()
方法用于保存文件。Path
的作用:
MultipartFile
MultipartFile
是 Spring 提供的接口,用于在处理 multipart/form-data
类型的表单时,代表客户端上传的文件。
或 AJAX 的 multipart/form-data
请求上传文件,后端用 MultipartFile
接收。(1)常用属性和方法
方法 | 返回类型 | 描述 | 示例代码 |
---|---|---|---|
getName() |
String |
获取前端表单中字段的名称(name 属性值)。 |
String fieldName = file.getName(); |
getOriginalFilename() |
String |
获取用户上传时的文件名(包括扩展名)。 | String filename = file.getOriginalFilename(); |
getContentType() |
String |
获取文件的 MIME 类型(例如 image/png 、application/pdf )。 |
String contentType = file.getContentType(); |
isEmpty() |
boolean |
判断文件是否为空(文件不存在或大小为 0 时返回 true )。 |
boolean empty = file.isEmpty(); |
getSize() |
long |
获取文件大小(以字节为单位)。 | long size = file.getSize(); |
getBytes() |
byte[] |
获取文件内容为字节数组(可能占用大量内存,不适合处理大文件)。 | byte[] content = file.getBytes(); |
getInputStream() |
InputStream |
获取文件内容的输入流,用于流式读取文件内容(适合处理大文件)。 | InputStream stream = file.getInputStream(); |
transferTo(File dest) |
void |
将文件内容保存到指定的 File 对象(复制或移动文件)。 |
file.transferTo(new File("uploads/example.txt")); |
示例用法
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "文件为空";
}
String filename = file.getOriginalFilename();
System.out.println("文件名:" + filename);
System.out.println("文件大小:" + file.getSize());
System.out.println("文件类型:" + file.getContentType());
try {
// 保存文件
file.transferTo(new File("uploads/" + filename));
} catch (IOException e) {
e.printStackTrace();
return "上传失败";
}
return "上传成功:" + filename;
}
(2)MultipartFile
在 Spring Boot 项目中的作用与职责
MultipartFile
在 Spring Boot 项目中用于处理上传文件,主要负责:
接收前端通过 HTTP 请求上传的文件:
@RequestParam
注解将前端表单中的文件绑定到 MultipartFile
对象。解析上传文件的元信息:
临时文件存储的中间层
MultipartFile
提供访问这些临时数据的方法。保存文件到磁盘或其他存储位置:
transferTo()
方法将文件保存到服务器的文件系统或其他存储介质(如云存储)。
MultipartFile
的transferTo(File dest)
方法它将文件的内容从临时存储位置移动或复制到目标File
对象指定的位置,从而真正保存文件到磁盘。
(3) 注意事项
文件大小限制:
1MB
10MB
application.properties
中调整限制: spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB
文件安全性:
../../etc/passwd
)。临时存储路径:
java.io.tmpdir
),需要及时保存到其他位置。java.nio.file.Path
Path
是 Java NIO 提供的类,用于表示文件路径和目录路径。它是处理文件路径的关键类,支持复杂的路径操作。
Files
的静态方法提供目标路径。与其他类的联系:
Paths.get("uploads")
获取 Path
,然后 resolve(file.getOriginalFilename())
拼接文件名。File
对象:path.toFile()
在需要老式 IO 时使用。创建 Path
对象
方法 | 引入版本 | 优势 | 示例 |
---|---|---|---|
Path.of() |
Java 11 | 推荐使用,语义清晰,现代化方法;支持路径拼接。 | Path.of("uploads/example.txt") |
Paths.get() |
Java 7 | 功能等价于 Path.of() ,支持低版本 Java。 |
Paths.get("uploads/example.txt") |
File.toPath() |
Java 7 | 将已有的 File 转换为 Path ,适合兼容旧代码的场景。 |
file.toPath() |
Paths.get(URI uri) |
Java 7 | 用于网络或文件 URI 的场景,例如本地文件路径或远程路径。 | Paths.get(URI.create("file:///path")) |
常用属性和方法
方法 | 返回类型 | 说明 | 用法示例 |
---|---|---|---|
getFileName() |
Path |
获取路径中的文件名部分。 | Path fileName = path.getFileName(); |
getParent() |
Path |
获取当前路径的父路径。 | Path parent = path.getParent(); |
getRoot() |
Path |
获取路径的根部分(如 C:/ 或 / )。 |
Path root = path.getRoot(); |
toAbsolutePath() |
Path |
转换为绝对路径。 | Path absolute = path.toAbsolutePath(); |
normalize() |
Path |
规范化路径(去掉冗余部分,如 ./ 和 ../ )。 |
Path normalized = path.normalize(); |
resolve(String other) |
Path |
将当前路径与另一个路径拼接。 | Path newPath = path.resolve("subdir/file.txt"); |
resolveSibling(String other) |
Path |
将当前路径的兄弟路径拼接(同目录下的另一个路径)。 | Path siblingPath = path.resolveSibling("other.txt"); |
relativize(Path other) |
Path |
获取两个路径之间的相对路径。 | Path relative = path.relativize(otherPath); |
toUri() |
URI |
转换为 URI 表示形式。 | URI uri = path.toUri(); |
toFile() |
File |
将路径转换为 File 对象。 |
File file = path.toFile(); |
示例用法
Path uploadPath = Paths.get("uploads");
// 获取文件名部分
Path fileName = uploadPath.getFileName();
System.out.println("文件名:" + fileName);
// 转换为绝对路径
Path absolutePath = uploadPath.toAbsolutePath();
System.out.println("绝对路径:" + absolutePath);
// 拼接路径
Path newPath = uploadPath.resolve("example.txt");
System.out.println("新路径:" + newPath);
正斜杠(
/
):
- 通用的路径分隔符,广泛用于URL、Unix/Linux系统和Java内部。
- 在Java字符串中无需转义,直接使用即可。
反斜杠(
\
):
- Windows系统的路径分隔符。
- 在Java字符串中,反斜杠是转义字符(例如,
\n
表示换行),因此需要使用双反斜杠(\\
)来表示一个实际的反斜杠。使用path.of()创建一个path对象的时候,如果传入的是Java中使用的正斜杠(
/
),那它会根据你的操作系统来转换(在Windows系统下会自动将正斜杠/
转换为反斜杠\,
此Linux系统中则保持不变)它是单向转换的,只会把Java中的正斜杠(
/
)转换为操作系统所对应的斜杠,而不会将操作系统中的路径转换为Java中的路径所以如果希望将操作系统中的文件路径转换为Java中的路径就得手动操作,通过以下方式:
// 将反斜杠替换为正斜杠 String fileUrl = resource.getFileUrl().replace("\\", "/");
String 拼接与 Path.resolve 的主要区别
(1)适配性与跨平台支持
String 拼接:
使用 +
只是简单地将两个字符串合并,不会考虑平台的路径分隔符。在 Windows 平台下,路径分隔符通常是 \
,而在 Unix/Linux 平台下是 /
。
如果 UPLOAD_DIR
和 fileName
拼接时没有正确处理分隔符(例如 UPLOAD_DIR
末尾没有 /
或 \
),可能导致生成的路径不正确。
Path.resolve:
Path.resolve()
是 java.nio.file.Path
提供的方法,会根据操作系统的路径规则自动调整路径分隔符,确保生成的路径在跨平台场景下是正确的。
Path uploadDir = Paths.get("C:\\uploads");
Path filePath = uploadDir.resolve("file.txt");
System.out.println(filePath); // 输出 C:\uploads\file.txt
(2) 路径规范化
String 拼接:
不会自动规范化路径。如果路径中出现冗余的分隔符(如 //
或 ..
),需要手动处理。
String filePath = "C:/uploads/" + "../file.txt";
System.out.println(filePath); // 输出 C:/uploads/../file.txt
Path.resolve:
..
或多余的分隔符,它会尝试简化路径:Path uploadDir = Paths.get("C:/uploads");
Path filePath = uploadDir.resolve("../file.txt");
System.out.println(filePath.normalize()); // 输出 C:\file.txt
(3) 错误处理
String 拼接:
不具备路径校验功能。如果 UPLOAD_DIR
或 fileName
不符合路径规则,拼接后的路径仍然是一个无效的路径。
例如,UPLOAD_DIR
是 null
时,会抛出 NullPointerException
。
Path.resolve:
提供了更安全的路径处理逻辑。若传入无效路径,可以及时发现并处理异常。
(4) 功能扩展
String 拼接:
只能用于简单的字符串拼接,无法进一步操作路径。
如果需要获取文件名、父目录等信息,必须通过额外的工具类来解析路径。
Path.resolve:
Path
提供了许多方便的方法,比如获取文件名(getFileName
)、父目录(getParent
)、路径元素等,方便对路径进行进一步操作。
Path filePath = Paths.get("C:/uploads").resolve("file.txt");
System.out.println(filePath.getParent()); // 输出 C:\uploads
System.out.println(filePath.getFileName()); // 输出 file.txt
(5) 代码可读性
String 拼接:
代码简短但缺乏语义性,可能会让读者误解这只是字符串处理,而非路径处理。
Path.resolve:
代码更具语义性,明确表明是路径拼接,方便理解和维护。
java.io.File
java.io.File
是 Java 提供的一个类,用于表示文件和目录的抽象路径。它是操作文件系统的基础类,主要用于创建、删除、检查文件或目录的属性。
(1)File
的属性
File
类的属性是基于文件路径的元信息,以下是其关键属性:
path
:
"C:/uploads/example.txt"
或 "uploads/example.txt"
。name
:
uploads/example.txt
,文件名为 example.txt
。parent
:
uploads/example.txt
,父路径为 uploads/
。(2) File
的构造函数
构造函数 | 描述 |
---|---|
File(String pathname) |
使用文件或目录的路径名创建一个 File 实例。 |
File(String parent, String child) |
使用父路径和子路径创建一个 File 实例。 |
File(File parent, String child) |
使用父 File 对象和子路径创建一个 File 实例。 |
示例:
// 直接使用路径创建文件
File file1 = new File("uploads/example.txt");
// 使用父路径和子路径创建文件
File file2 = new File("/var/uploads", "example.txt");
// 使用父 File 对象和子路径创建文件
File file3 = new File(new File("/var/uploads"), "example.txt");
(3)File
的常用方法
方法 | 返回类型 | 描述 | 示例 |
---|---|---|---|
exists() |
boolean |
判断文件或目录是否存在。 | file.exists(); |
isDirectory() |
boolean |
判断当前 File 是否是一个目录。 |
file.isDirectory(); |
isFile() |
boolean |
判断当前 File 是否是一个文件。 |
file.isFile(); |
createNewFile() |
boolean |
如果文件不存在,则创建一个新文件(已存在则返回 false )。 |
file.createNewFile(); |
mkdir() |
boolean |
创建目录(如果父目录不存在,则创建失败)。 | file.mkdir(); |
mkdirs() |
boolean |
创建目录(包括不存在的父目录)。 | file.mkdirs(); |
delete() |
boolean |
删除文件或目录(目录必须为空)。 | file.delete(); |
getName() |
String |
获取文件名或目录名。 | file.getName(); |
getParent() |
String |
获取文件或目录的父路径。 | file.getParent(); |
getAbsolutePath() |
String |
获取文件的绝对路径。 | file.getAbsolutePath(); |
length() |
long |
获取文件大小(以字节为单位)。 | file.length(); |
renameTo(File dest) |
boolean |
重命名文件或移动文件到目标路径。 | file.renameTo(new File("new_path/example.txt")); |
(4) File
在 Spring Boot 后端项目中的作用与职责
File
类是 Spring Boot 项目中处理文件的基础,主要用于表示和操作服务器上的文件或目录。
1. 文件路径的定义
在文件上传、下载或管理场景中,File
用于表示目标文件或目录的位置。
路径可以是相对路径(相对于项目运行目录)或绝对路径(系统中的具体路径)。
// 定义一个相对路径的文件
File file = new File("uploads/example.txt");
// 定义一个绝对路径的文件
File absoluteFile = new File("/var/uploads/example.txt");
2. 文件或目录的创建
在处理文件上传之前,需要确保目标目录存在。如果目录不存在,可以使用 mkdirs()
方法创建。
File uploadDir = new File("uploads/");
if (!uploadDir.exists()) {
uploadDir.mkdirs(); // 创建上传目录
}
3. 文件的保存
在文件上传中,File
用于表示保存文件的目标位置,并配合 MultipartFile.transferTo()
完成文件存储。
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "文件为空";
}
try {
File dest = new File("uploads/" + file.getOriginalFilename());
file.transferTo(dest); // 保存文件到目标位置
return "文件已保存到: " + dest.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
return "文件保存失败";
}
}
4. 文件的管理
可以通过 File
对象删除、重命名或检查文件。
示例:删除过期文件
File file = new File("uploads/old_file.txt");
if (file.exists()) {
file.delete(); // 删除文件
}
示例:重命名文件
File oldFile = new File("uploads/example.txt");
File newFile = new File("uploads/renamed_example.txt");
if (oldFile.exists()) {
oldFile.renameTo(newFile);
}
5. 文件的属性操作
获取文件的大小、文件名、父路径等属性。
File file = new File("uploads/example.txt");
if (file.exists()) {
System.out.println("文件名: " + file.getName());
System.out.println("文件大小: " + file.length() + " 字节");
System.out.println("父路径: " + file.getParent());
}
(5)注意事项
路径安全性
在保存或管理文件时,应避免路径穿越漏洞(如 ../../etc/passwd
)。
可使用正则表达式校验文件名。
文件操作权限
确保运行项目的用户对目标路径有读写权限,否则会导致文件操作失败。
相对路径与绝对路径
相对路径适合开发环境(相对于项目运行目录)。
绝对路径更适合生产环境,避免因运行目录变化导致路径不一致。
File
是 Java I/O 中的早期设计;对于更复杂或高性能的操作,多与 NIO 的 Path
/ Files
结合使用。2.1.5.java.nio.file.Files
Files
是 Java NIO 提供的一个工具类(static methods),专门用于对文件或目录进行高效的读写、创建、删除、拷贝、移动等操作。Files
自身没有属性和构造函数,所有方法都是 static
静态方法。常用方法
方法 | 描述 | 示例 |
---|---|---|
createDirectories(Path) |
创建目录,包括必要的父目录。 | Files.createDirectories(path); |
exists(Path) |
判断文件或目录是否存在。 | Files.exists(path); |
copy(InputStream, Path) |
将输入流的内容复制到指定路径文件。 | Files.copy(inputStream, path); |
delete(Path) |
删除文件或目录。 | Files.delete(path); |
readAllBytes(Path) |
读取文件内容为字节数组。 | Files.readAllBytes(path); |
在 Spring Boot 项目中的职责
确保目录存在:
Files.createDirectories()
确保目录存在。 Path uploadDir = Paths.get("uploads/");
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir);
}
高效文件存储:
MultipartFile.getInputStream()
,使用 Files.copy()
将上传文件保存到本地。 Files.copy(file.getInputStream(), uploadPath.resolve(file.getOriginalFilename()));
Spring Boot 提供了配置属性来限制文件上传的大小和请求体的大小,从而避免过大文件上传造成的服务器压力。你可以在 application.properties
中配置这些参数。
配置示例:
# 单个文件最大上传大小,单位为字节,10MB
spring.servlet.multipart.max-file-size=10MB
# 请求体最大大小,单位为字节,50MB
spring.servlet.multipart.max-request-size=50MB
# 配置文件上传临时路径
spring.servlet.multipart.location=/tmp/upload
Spring Boot 提供了通过 ResponseEntity
返回文件下载响应的能力。你可以设置 Content-Type
和 Content-Disposition
头,指定文件名和下载方式。
ResponseEntity
响应文件下载请求ResponseEntity
可以用于向客户端返回文件,客户端可以根据响应头下载文件。
示例:
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
public class FileDownloadController {
private static final String DOWNLOAD_DIR = "uploads/";
@GetMapping("/download/{filename}")
public ResponseEntity downloadFile(@PathVariable String filename) {
try {
Path filePath = Paths.get(DOWNLOAD_DIR).resolve(filename).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} else {
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.internalServerError().build();
}
}
}
解释代码:
Path filePath = Paths.get(DOWNLOAD_DIR).resolve(filename).normalize();
Resource resource = new UrlResource(filePath.toUri());
Paths.get(DOWNLOAD_DIR)
:Paths.get()
是一个用于构造路径对象的静态方法,它返回一个 Path
实例。这里的 DOWNLOAD_DIR
是一个字符串变量,表示文件的存储目录,例如 "uploads/"
。Paths.get(DOWNLOAD_DIR)
相当于从文件系统中的这个路径创建一个 Path
对象。
.resolve(filename)
:resolve()
方法用于将一个相对路径追加到已有的路径中。filename
是客户端请求中传入的文件名,这样会将 filename
追加到 DOWNLOAD_DIR
路径中。例如,如果 DOWNLOAD_DIR
是 "uploads/"
,filename
是 "test.txt"
,那么执行后 filePath
会指向 "uploads/test.txt"
。
.normalize()
:normalize()
方法会清理路径中的冗余部分,例如去除多余的 .
和 ..
。它用于确保路径是标准的和安全的。
new UrlResource(filePath.toUri())
:UrlResource
是 Spring 的 Resource
接口的一个实现类。filePath.toUri()
将文件系统的路径转换为 URI
,UrlResource
通过这个 URI
来定位资源文件。
Content-Disposition
头部信息Content-Disposition
头部信息用于告诉浏览器以附件(attachment
)的形式下载文件,并指定文件名。通过 ResponseEntity
的 header()
方法可以设置这个信息。
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
ResponseEntity.ok()
:创建一个 ResponseEntity
对象,表示 HTTP 响应的状态是 200 OK。 HttpHeaders.CONTENT_DISPOSITION
:这个头部的值用于告诉浏览器如何处理响应内容。Content-Disposition
是 HTTP 响应头中的一个属性,它可以控制浏览器如何显示文件。这里的 attachment
指定了文件应该以下载的方式处理,而不是直接在浏览器中展示。
attachment;
:attachment
表示告诉浏览器下载这个文件而不是直接打开。filename="..."
指定了浏览器下载时建议的文件名。在这里,resource.getFilename()
返回资源的实际文件名。通过这种方式,下载时浏览器会显示文件名并提示用户保存文件。
filename="..."
:指定的是下载文件的显示名称,filename=\"
后面只能跟文件名,不能跟文件路径。
body(resource)
:将文件的内容(resource
对象)作为 HTTP 响应的主体,返回给客户端。
ResponseEntity
响应的响应体部分(文件主体)在 Spring Boot 中,如果你的目标是让前端浏览器以附件下载的方式获取文件,那么 ResponseEntity
的 body(...)
部分必须放置真正的文件内容(而不是文件路径或其他字符串)。常用的几种做法主要取决于你的文件大小、性能需求等,以下为最常见的三种:
本地文件下载:
- 优先使用
FileSystemResource
,它更直接、高效。动态生成文件内容(非文件系统资源):
- 使用
InputStreamResource
,因为它可以基于流生成动态内容。远程文件下载(或动态 URL 资源加载):
- 使用
UrlResource
,尤其适用于需要从网络资源中加载的场景。
(1) 使用 byte[]
适用场景:小文件或文件体积不大的场景,后端可以直接将整个文件读取到内存,然后用 byte[]
直接返回。
示例:
@GetMapping("/download1")
public ResponseEntity downloadFileAsByteArray(@RequestParam String fileurl) throws IOException {
Path filePath = Paths.get(fileurl);
String filename = filePath.getFileName().toString();
// 读取文件为字节数组
byte[] fileBytes = Files.readAllBytes(filePath);
// 设置响应头,指定下载文件名
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(fileBytes);
}
优点:简单直接,容易编码。
缺点:如果文件过大,会占用大量内存,不适合大文件下载。
(2)使用InputStreamResource
(Resource
接口的实现类)
适用场景:大文件下载或不想一次性将整个文件读入内存时,使用流的方式可以节省内存。
基于现有的
InputStream
创建资源。不直接绑定到文件路径,而是绑定到数据流。
示例:
@GetMapping("/download2")
public ResponseEntity downloadFileAsStream(@RequestParam String fileurl) throws IOException {
Path filePath = Paths.get(fileurl);
String filename = filePath.getFileName().toString();
InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath));
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}
(3) 使用 FileSystemResource
(Resource
接口的实现类)
适用场景:与 InputStreamResource
类似,也是一种 Resource 类型,通过它可以让 Spring 处理文件流。
专门用于表示文件系统中的资源。
直接以
File
对象或文件路径为基础创建资源。默认提供文件的直接访问(支持流式处理)。
示例:
@GetMapping("/download3")
public ResponseEntity downloadFileAsResource(@RequestParam String fileurl) {
Path filePath = Paths.get(fileurl);
String filename = filePath.getFileName().toString();
Resource resource = new FileSystemResource(filePath.toFile());
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}
(4)使用UrlResource
(Resource
接口的实现类)
基于 URL 表示的资源,可以是本地文件(file://
协议)、HTTP 资源(http://
协议)或其他协议的资源。 当你使用 UrlResource(filePath.toUri())
时,实际上通过一个 URL(在这种情况下是 file://
协议对应的本地文件路径)来创建一个 Resource
对象。
Path filePath = Paths.get("C:/path/to/file.txt");
Resource resource = new UrlResource(filePath.toUri());
优点:
支持多种协议:支持 file://
、http://
、ftp://
等多种协议,不仅局限于本地文件,适合加载远程资源。
与网络资源的集成能力:可以方便地从远程 URL 加载文件,例如从 CDN 或 REST API 提供的下载链接直接传递到前端。
缺点:
对本地文件访问性能较低:如果只是访问本地文件,UrlResource
会先将路径转换为 file://
协议的 URL 进行解析,增加了额外的开销。对于纯粹的本地文件访问,FileSystemResource
更高效。
依赖网络环境:如果 URL 指向网络资源,可能受到网络延迟或连接问题的影响。
其他注意事项
编码与中文文件名
这样可以提升不同浏览器对中文文件名的兼容性。
String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
// 或者使用 filename*=UTF-8''xxxx 形式
.header("Content-Disposition", "attachment; filename=\"" + encodedFilename + "\"; filename*=UTF-8''" + encodedFilename)
Content-Type
application/octet-stream
作为下载文件的通用 MIME 类型。MediaType.APPLICATION_PDF
或 MediaType.IMAGE_JPEG
,这样浏览器在下载时也会有更准确的类型提示。大文件下载性能
InputStreamResource
或 FileSystemResource
)来避免占用过多内存。安全考虑
处理大文件时,内存的使用和服务器的负载是重要的考虑因素。对于上传来说,分块上传是常用方案;而对于下载来说,流式下载可以有效避免内存溢出问题。
大文件的上传可以通过分块上传来实现,即客户端将文件分成多个部分,逐个上传。后端接收每个块并将其合并,完成文件上传。
实现思路:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=50MB
spring.servlet.multipart.location=/tmp/upload
在前端,文件被分割成多个小块,每个块可以单独上传。前端将通过循环或并发请求将这些块传给服务器。每个块包含元数据信息,比如块编号、总块数等。
// 这是一个基本的前端分块上传的示例代码
const file = document.getElementById("fileInput").files[0];
const chunkSize = 1 * 1024 * 1024; // 每块大小1MB
const totalChunks = Math.ceil(file.size / chunkSize);
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
const start = chunkIndex * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append("file", chunk);
formData.append("chunkIndex", chunkIndex);
formData.append("totalChunks", totalChunks);
formData.append("fileName", file.name);
fetch("/uploadChunk", {
method: "POST",
body: formData
}).then(response => {
console.log(`Chunk ${chunkIndex + 1}/${totalChunks} uploaded`);
});
}
file.slice(start, end)
:将文件从 start
到 end
位置切片,生成每个块的数据,确保每块大小相同,最后一块可能小于指定的 chunkSize
。const formData = new FormData()
:构造 FormData
对象,用于包装分块数据和元数据信息,便于发送到服务器。formData.append("file", chunk)
:将当前块的数据添加到 FormData
中,chunk
是从文件切片得到的数据。formData.append("chunkIndex", chunkIndex)
:将当前块的索引(块编号)添加到 FormData
中,便于后端按顺序处理文件块。formData.append("totalChunks", totalChunks)
:将总块数添加到 FormData
,使后端知道文件被分成多少块,便于合并。formData.append("fileName", file.name)
:将文件名添加到 FormData
,确保后端能够识别是哪一个文件的块。fetch("/uploadChunk", { method: "POST", body: formData })
:通过 fetch
API 发送 POST 请求,将当前文件块和相关信息上传到服务器。.then(response => { ... })
:在上传每块成功后执行回调函数,打印上传进度。后端负责接收这些块,并且根据块编号将它们暂时存储,等所有块上传完成后再合并。
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
public class ChunkUploadController {
private static final String UPLOAD_DIR = "uploads/";
@PostMapping("/uploadChunk")
public String uploadChunk(@RequestParam("file") MultipartFile file,
@RequestParam("chunkIndex") int chunkIndex,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("fileName") String fileName) {
try {
// 创建文件临时存储目录
Path tempDir = Paths.get(UPLOAD_DIR, "temp", fileName);
if (!tempDir.toFile().exists()) {
tempDir.toFile().mkdirs();
}
// 将每一块临时存储到文件系统中
File chunkFile = new File(tempDir + "/" + fileName + ".part" + chunkIndex);
try (FileOutputStream out = new FileOutputStream(chunkFile)) {
out.write(file.getBytes());
}
// 如果是最后一个块,开始合并所有文件
if (chunkIndex == totalChunks - 1) {
mergeChunks(fileName, totalChunks);
}
return "Chunk uploaded successfully";
} catch (IOException e) {
e.printStackTrace();
return "Failed to upload chunk";
}
}
private void mergeChunks(String fileName, int totalChunks) throws IOException {
Path tempDir = Paths.get(UPLOAD_DIR, "temp", fileName);
Path mergedFile = Paths.get(UPLOAD_DIR, fileName);
try (FileOutputStream out = new FileOutputStream(mergedFile.toFile())) {
for (int i = 0; i < totalChunks; i++) {
File chunkFile = tempDir.resolve(fileName + ".part" + i).toFile();
byte[] bytes = java.nio.file.Files.readAllBytes(chunkFile.toPath());
out.write(bytes);
chunkFile.delete(); // 合并后删除临时块文件
}
}
tempDir.toFile().delete(); // 删除临时目录
}
}
@RequestParam("file") MultipartFile file
:接收前端传来的文件块,MultipartFile
是 Spring 提供的用于处理文件上传的类。Path tempDir = Paths.get(UPLOAD_DIR, "temp", fileName)
:生成临时存储目录,用于保存每个块文件,确保每个文件的块存储在一个独立的文件夹中。if (!tempDir.toFile().exists()) { tempDir.toFile().mkdirs(); }
:检查并创建临时目录,如果不存在,则创建一个用于存储块文件的目录。File chunkFile = new File(tempDir + "/" + fileName + ".part" + chunkIndex)
:为每个块生成一个唯一的临时文件名,块文件的命名格式为 fileName.partN
,其中 N 是块的索引。FileOutputStream out = new FileOutputStream(chunkFile)
:创建文件输出流,将当前文件块的数据写入临时文件。out.write(file.getBytes())
:将当前块的数据写入到临时文件中。if (chunkIndex == totalChunks - 1)
:检查是否是最后一个块,如果是最后一个块,则触发文件合并操作。mergeChunks(fileName, totalChunks)
:调用合并方法,将所有块文件合并为一个完整的文件。for (int i = 0; i < totalChunks; i++)
:遍历所有块文件,按顺序读取它们的内容并写入到最终的合并文件中。byte[] bytes = java.nio.file.Files.readAllBytes(chunkFile.toPath())
:读取每个块文件的字节数据。out.write(bytes)
:将读取到的块文件内容写入到最终合并的文件中。chunkFile.delete()
:合并完成后,删除临时块文件,释放存储空间。tempDir.toFile().delete()
:在所有块文件合并完成并删除后,删除用于存放临时文件的目录。对于大文件的下载,可以使用流式下载,即文件内容按块读取并逐步写入响应流,避免将整个文件加载到内存中。
示例:
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@RestController
public class LargeFileDownloadController {
private static final String DOWNLOAD_DIR = "uploads/";
@GetMapping("/stream-download/{filename}")
public ResponseEntity downloadLargeFile(@PathVariable String filename) throws FileNotFoundException {
File file = new File(DOWNLOAD_DIR + filename);
if (!file.exists()) {
return ResponseEntity.notFound().build();
}
// 创建文件流
FileInputStream fileInputStream = new FileInputStream(file);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"")
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(fileInputStream));
}
}
InputStreamResource
:用于将输入流作为响应体,支持流式下载。contentLength(file.length())
:告知客户端文件的大小,帮助客户端预估下载进度。绝对路径是从根目录开始的完整路径,有两种主要形式:
(1)完全限定的系统路径
C:/your/absolute/path/uploads
/home/username/uploads
(2)完整的 URL 路径
http://example.com/uploads
相对路径是相对于当前位置的路径,分为两种:
(1)不以 /
开头的相对路径
uploads/
:相对于当前目录../uploads/
:返回上一级目录后的 uploads(2)以 /
开头的相对路径
/uploads/
:在 Web 应用中,这是相对于 Web 应用的上下文根(context root)2.4.3./uploads/
的具体含义在 Web 应用中,/uploads/
是一个特殊的相对路径:
示例说明
假设 Web 应用部署路径是 http://localhost:8080/myapp/
uploads/
会解析为 http://localhost:8080/myapp/uploads/
/uploads/
会解析为 http://localhost:8080/uploads/
String userDir = System.getProperty("user.dir");
Path uploadDir = Paths.get(userDir, "uploads");
System.getProperty("user.dir")
: 这个方法返回当前 Java 程序的工作目录路径。通常情况下,工作目录是你启动 Java 程序时的目录。比如,如果你从命令行启动应用程序,工作目录就是命令行当前所在的目录;如果是通过 IDE 启动,则是 IDE 配置的工作目录。Paths.get(userDir, "uploads")
: 这个方法通过将工作目录路径与 "uploads"
目录名连接,创建一个 Path
对象,指向工作目录下的 uploads
目录。Paths.get
是 java.nio.file.Path
类的方法,适用于跨平台的路径操作。Path uploadDir = Paths.get("C:/your/absolute/path/uploads");
绝对路径: 这是指定存储位置的直接方法,路径的开始部分包括了完整的文件系统路径。例如,C:/your/absolute/path/uploads
表示在 C
盘下的 your/absolute/path
目录中存储上传的文件。
Paths.get
: 这个方法将字符串类型的绝对路径转换为 Path
对象,供程序使用。
1. 文件上传:使用 MultipartFile
来接收文件,并配置 application.properties
来限制文件大小和请求体大小。
spring.servlet.multipart.max-file-size
和 spring.servlet.multipart.max-request-size
来设置文件上传限制。2. 文件下载:使用 ResponseEntity
来响应下载请求,配置 Content-Disposition
头部信息确保文件作为附件下载。
3. 大文件的上传与下载:
InputStreamResource
和流式下载,避免将大文件完全加载到内存中。文件读取可以通过多种方式完成,具体取决于文件的大小、内容格式以及读取的需求。Java NIO 提供了高效的文件读取方法,适合处理各种类型的文件。
Files.readAllBytes()
Files.readAllBytes()
方法用于一次性将文件的所有内容读取为一个字节数组。适用于小型文件,因为一次性读取会将所有内容加载到内存中。
示例:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
public class FileReadExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("example.txt");
byte[] fileBytes = Files.readAllBytes(path); // 读取整个文件为字节数组
System.out.println(new String(fileBytes)); // 打印文件内容
}
}
Files.readAllBytes(path)
将文件内容全部加载到内存中,因此不适合处理大文件。path
是文件的路径对象。Files.readString()
Files.readString()
方法直接将文件的内容读取为一个字符串。它是 Java 11 引入的方法,适用于小型文本文件读取。
示例:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
public class FileReadExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("example.txt");
String content = Files.readString(path); // 读取文件为字符串
System.out.println(content); // 打印文件内容
}
}
String
。适合处理小型文本文件。Files.readString(path)
直接将整个文件作为字符串返回,非常方便处理文本文件。从 Java 11 开始支持。Files.readAllLines()
Files.readAllLines()
方法将文件的所有行读取为 List
,适合逐行读取文本内容。
示例:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.List;
public class FileReadExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("example.txt");
List lines = Files.readAllLines(path); // 读取文件为行列表
lines.forEach(System.out::println); // 逐行打印文件内容
}
}
Files.readAllLines(path)
逐行读取文件内容,并将每一行存储为String,用List集合存储这些String。适合处理需要分行读取的文本文件。由于将所有行加载到内存中,不适合非常大的文件。BufferedReader
逐行读取大文件当文件非常大时,不适合一次性加载到内存,可以使用 BufferedReader
进行逐行读取。
示例:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class LargeFileReadExample {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("largefile.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line); // 逐行处理文件内容
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
new FileWriter("largefile.txt")
FileWriter
是一个基础类,用于将文本数据写入到文件中。这里 "largefile.txt"
是要写入的文件名。FileWriter
是一个不带缓冲的写入流,直接操作文件系统的 I/O 操作。FileWriter
本身不具备缓冲区功能,每次写入都会直接触发 I/O 操作,这样可能导致频繁的磁盘访问,降低性能,尤其在处理大量数据时。new BufferedWriter(new FileWriter("largefile.txt"))
BufferedWriter
是对 FileWriter
的增强,增加了缓冲区。使用缓冲区可以减少直接与磁盘的交互,通过缓冲区将小块数据累积起来,只有在缓冲区满了或者关闭 BufferedWriter
时才一次性写入磁盘。BufferedWriter
内部会将数据临时存储在内存中,只有当缓冲区满或者显式调用 flush()
时才会触发写入磁盘的操作。这种设计减少了频繁的磁盘 I/O,显著提升了写入效率。new BufferedWriter(new FileWriter())
,可以给现有的流(FileWriter
)增加额外功能(缓冲功能),而无需改变 FileWriter
本身。文件写入在 Java 中也有多种方式,适用于写入文本、二进制文件等。
Files.write()
进行文件写入Files.write()
方法可以将字节数组或字符串写入到文件中。如果文件不存在,它会自动创建文件。
示例:写入字符串到文件
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
public class FileWriteExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("output.txt");
String content = "This is a test.";
Files.write(path, content.getBytes()); // 将字符串写入文件
}
}
Files.write(path, content.getBytes())
将字符串转换为字节后写入文件。简单高效,适合写入小文件。BufferedWriter
处理大文件写入BufferedWriter
通过使用缓冲区提高了文件写入的效率,尤其适合处理大文件写入。
示例:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class LargeFileWriteExample {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("largefile.txt"))) {
for (int i = 0; i < 1000; i++) {
writer.write("This is line " + i); // 写入行数据
writer.newLine(); // 写入换行符
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedWriter.write()
逐行写入数据,通过 BufferedWriter.newLine()
插入换行符。通过缓冲机制减少 I/O 操作,提高性能。对于 JSON 和 XML 格式文件,通常需要使用第三方库来进行解析和写入。Spring Boot 提供了对 Jackson
库的良好支持,用于处理 JSON,而 XML 可以使用 Jackson
或其他库如 JAXB
处理。
Jackson
处理 JSON 文件Jackson
是处理 JSON 文件的最常用库。它提供了序列化和反序列化功能,可以将对象转换为 JSON 文件,或将 JSON 文件解析为对象。
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
public class JsonReadExample {
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(new File("user.json"), User.class); // 读取 JSON 文件为对象
System.out.println(user);
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
public class JsonWriteExample {
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
User user = new User("John", "Doe");
objectMapper.writeValue(new File("user.json"), user); // 将对象写入 JSON 文件
}
}
ObjectMapper.writeValue
用来将 Java 对象序列化为 JSON 并写入到文件。这里 new File("user.json")
指定要写入的目标文件,而 user
是需要被序列化的 Java 对象。 可以使用 Jackson
的 XmlMapper
或者 JAXB
来处理 XML 文件。XmlMapper
类似于 ObjectMapper
,适用于 XML 格式。
XmlMapper
读取 XML 文件import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.File;
import java.io.IOException;
public class XmlReadExample {
public static void main(String[] args) throws IOException {
XmlMapper xmlMapper = new XmlMapper();
User user = xmlMapper.readValue(new File("user.xml"), User.class); // 读取 XML 文件为对象
System.out.println(user);
}
}
XmlMapper
写入 XML 文件import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.File;
import java.io.IOException;
public class XmlWriteExample {
public static void main(String[] args) throws IOException {
XmlMapper xmlMapper = new XmlMapper();
User user = new User("John", "Doe");
xmlMapper.writeValue(new File("user.xml"), user); // 将对象写入 XML 文件
}
}
1. 文件读取:
Files.readAllBytes()
、Files.readString()
、Files.readAllLines()
适合小型文件。BufferedReader
逐行读取适合大文件。2. 文件写入:
Files.write()
写入字节或字符串到文件。BufferedWriter
逐行写入适合处理大文件。3. JSON/XML 文件处理:
Jackson
处理 JSON 文件,可以轻松将对象与 JSON 互相转换。Jackson
的 XmlMapper
处理 XML 文件,方法类似。 在 Spring Boot 中,日志记录是应用程序监控和调试的重要手段。SLF4J
(Simple Logging Facade for Java)是一个通用的日志框架抽象层,它允许开发者使用统一的日志 API,而底层可以灵活绑定不同的日志实现(如 Logback
、Log4j2
)。Spring Boot 默认使用 SLF4J
与 Logback
组合来进行日志管理。
通过 SLF4J
,开发者可以记录不同级别的日志信息,例如 TRACE
、DEBUG
、INFO
、WARN
和 ERROR
,帮助更好地跟踪应用程序的状态和错误信息,尤其在生产环境中,日志是排查问题的核心工具。
@Slf4j
记录日志@Slf4j
是由 Lombok
提供的注解,它可以简化日志记录器的创建。在 Spring Boot 中,通过添加 @Slf4j
,开发者无需手动创建日志记录器,Lombok 会自动生成一个 log
对象。然后,开发者可以使用 log
对象记录不同级别的日志。
@Slf4j
的工作原理当在类上使用 @Slf4j
注解时,Lombok 自动生成了一个名为 log
的日志记录器实例。开发者可以通过 log.info()
、log.debug()
、log.warn()
、log.error()
、log.trace()
等方法,在不同的场景下记录相应级别的日志信息。
log
对象的日志级别log
对象提供了多种日志级别,每个日志级别代表不同的日志重要性和记录目的。Spring Boot 默认支持以下日志级别(从高到低):
ERROR
:表示严重错误,通常是应用程序无法继续运行的异常情况。WARN
:表示可能存在问题,但程序可以继续运行。INFO
:表示应用程序的正常运行状态,可以帮助跟踪应用程序的主要流程。DEBUG
:表示详细的调试信息,通常用于开发和调试环境,不建议在生产环境中启用。TRACE
:表示非常详细的跟踪信息,通常用于极度精细的调试场景。以下是一个使用 @Slf4j
注解的 Spring Boot 控制器示例,展示了如何记录不同级别的日志信息。
示例代码:
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j // Lombok 自动生成 log 对象
public class LogController {
@GetMapping("/log")
public String logExample() {
log.info("Info level log message"); // 记录 INFO 级别日志
log.debug("Debug level log message"); // 记录 DEBUG 级别日志
log.warn("Warn level log message"); // 记录 WARN 级别日志
log.error("Error level log message"); // 记录 ERROR 级别日志
return "Log example completed";
}
}
log.trace()
:用于记录非常详细的追踪信息,一般用于调试时追踪程序执行的最细节步骤。通常仅在开发环境使用,生产环境中几乎不会启用。
log.debug()
:用于记录调试信息,帮助开发者了解应用程序内部的运行逻辑。常用于开发和测试环境,生产环境中也很少启用。
log.info()
:用于记录系统的常规操作状态,比如系统启动、请求处理、用户登录等。适用于生产环境,帮助运维团队了解应用程序的正常运行情况。
log.warn()
:用于记录警告信息,表明应用程序可能出现非预期的情况,但程序仍可以继续运行。这通常用于标记可能需要注意的问题。
log.error()
:用于记录严重错误信息,表明应用程序遇到了无法正常继续运行的错误。开发者可以通过这些日志快速定位问题的根源,帮助修复错误。
日志级别的输出可以通过配置文件来控制。在 Spring Boot 中,日志输出通常由 application.properties
或 logback-spring.xml
配置文件来管理。如果日志级别设置为 INFO
,则 DEBUG
和 TRACE
级别的日志将不会输出。通过配置文件,开发者可以灵活控制不同环境下的日志输出。
INFO
# 全局日志级别设置为 INFO,忽略 DEBUG 和 TRACE 级别日志
logging.level.root=INFO
# 对特定包启用 DEBUG 级别日志
logging.level.com.example=DEBUG
logging.level.root=INFO
:设置全局日志级别为 INFO
,表示只输出 INFO
级别及以上的日志(即 INFO
、WARN
、ERROR
),忽略 DEBUG
和 TRACE
级别日志。logging.level.com.example=DEBUG
:对指定包 com.example
启用 DEBUG
级别日志,适合对特定模块进行详细调试。Spring Boot 使用 Logback
作为默认的日志框架,提供了灵活的日志文件管理功能。通过配置文件,开发者可以控制日志的输出路径、日志文件的大小限制、日志保存时间等。
在 application.properties
文件中,开发者可以自定义日志文件的输出路径、文件名、文件大小和日志保存历史等参数。
示例:
# 配置日志文件的路径和文件名
logging.file.name=logs/myapp.log
# 设置日志文件的最大大小,文件超过 10MB 后滚动生成新文件
logging.file.max-size=10MB
# 设置日志文件的最大保留天数,最多保留 7 个历史文件
logging.file.max-history=7
logging.file.name
:指定日志文件的存储路径和文件名。如果没有指定,日志将输出到控制台。logging.file.max-size
:设置日志文件的最大大小,超过此大小后,日志系统会滚动生成新文件。logging.file.max-history
:设置保留的历史日志文件数量,超过这个数量的旧日志文件将被删除。当需要更复杂的日志管理(如按时间滚动、文件压缩、删除旧日志等),可以通过 logback-spring.xml
文件进行详细配置。Logback
提供了丰富的日志管理功能,可以按时间或文件大小滚动日志文件,并支持自动压缩和删除旧日志。
示例:
logs/myapp.log
logs/myapp-%d{yyyy-MM-dd}.%i.log
10MB
7
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
RollingFileAppender
:定义日志滚动的策略,使用时间和文件大小进行日志文件的切换。TimeBasedRollingPolicy
:基于时间(如按天)和文件大小(如 10MB)进行日志滚动。maxFileSize
:每个日志文件的最大大小,超过此大小后会创建新的文件。maxHistory
:最大日志保存天数,超过此时间的旧日志文件会被删除。pattern
:定义日志的输出格式,如时间戳、线程名、日志级别和日志消息。在处理文件操作时,异常是不可避免的。Java 提供了多种异常类来处理文件操作过程中的问题,如 IOException
、FileNotFoundException
等。为了保证应用程序的稳定性,开发者需要对这些异常进行适当的处理,尤其是在生产环境中,确保异常不会导致程序崩溃。Spring Boot 也提供了全局异常处理机制,可以通过 @ControllerAdvice
进行统一处理文件操作中的异常。
文件操作涉及的异常通常与 I/O(输入/输出)相关。以下是文件操作中常见的异常及其处理方式。
IOException
IOException
是文件操作过程中最常见的异常之一。它是 Java 中的通用 I/O 异常类,表示在读、写、关闭流或文件时出现的任何输入输出问题。
示例:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FileIOExceptionExample {
public void writeFile(String content, String path) {
try (FileWriter writer = new FileWriter(new File(path))) {
writer.write(content);
} catch (IOException e) {
// 处理 IOException
System.err.println("An I/O error occurred: " + e.getMessage());
e.printStackTrace();
}
}
}
try-catch
块捕获异常并记录错误日志,避免应用程序崩溃。代码解释:
System.err
:标准错误流,通常用于输出错误信息,默认情况下也会输出到控制台。和 System.out
类似,但专门用于显示错误或异常信息。常见的使用场景是将异常信息输出到控制台。 e.printStackTrace()
会打印异常发生时的调用链,开发者可以通过这条链追踪到错误的源头,快速调试程序。FileNotFoundException
FileNotFoundException
是 IOException
的子类,表示程序尝试打开的文件不存在或文件路径无效。通常在读取文件时可能发生。
示例:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class FileNotFoundExceptionExample {
public void readFile(String path) {
try (Scanner scanner = new Scanner(new File(path))) {
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
// 处理 FileNotFoundException
System.err.println("File not found: " + e.getMessage());
}
}
}
SecurityException
:当应用程序没有访问文件的权限时,会抛出此异常。EOFException
:表示文件结束异常,通常在读取超出文件内容时发生。try-with-resources
语法在处理文件操作时,使用 try-with-resources
是一种最佳实践。这种语法确保在完成文件操作后,资源(如文件流)会自动关闭,避免资源泄漏问题。
示例:
public void readFileWithResources(String path) {
try (FileReader fileReader = new FileReader(new File(path))) {
// 读取文件
} catch (IOException e) {
// 处理 IOException 和其子类
System.err.println("Error occurred: " + e.getMessage());
}
}
close()
方法,资源会自动关闭,避免了可能的资源泄漏。在实际开发中,单独为每个文件操作编写 try-catch
代码会导致代码冗余。Spring Boot 提供了全局异常处理机制,允许开发者通过 @ControllerAdvice
统一处理应用程序中的异常,包括文件操作异常。
@ControllerAdvice
处理文件操作中的异常@ControllerAdvice
是 Spring 提供的全局异常处理注解,能够集中处理应用中的所有异常,包括文件操作异常。结合 @ExceptionHandler
注解,可以为特定的异常类型提供处理逻辑。
示例:全局异常处理文件操作
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.http.ResponseEntity;
import java.io.FileNotFoundException;
import java.io.IOException;
@ControllerAdvice
public class GlobalExceptionHandler {
// 处理 FileNotFoundException 异常
@ExceptionHandler(FileNotFoundException.class)
public ResponseEntity handleFileNotFound(FileNotFoundException e) {
// 返回自定义的响应和状态码
return ResponseEntity.status(404).body("File not found: " + e.getMessage());
}
// 处理 IOException 异常
@ExceptionHandler(IOException.class)
public ResponseEntity handleIOException(IOException e) {
// 返回自定义的响应和状态码
return ResponseEntity.status(500).body("I/O error occurred: " + e.getMessage());
}
}
@ExceptionHandler(FileNotFoundException.class)
:捕获所有 FileNotFoundException
异常,并返回自定义的 404 状态码和错误消息。@ExceptionHandler(IOException.class)
:捕获所有 IOException
异常,并返回自定义的 500 状态码,表示服务器发生 I/O 错误。当你定义了 @ExceptionHandler(FileNotFoundException.class)
方法后,Spring 会自动捕获所有抛出的 FileNotFoundException
异常,并通过该方法处理异常响应。你无需在具体的方法中手动捕获该异常,而是可以将异常的处理逻辑统一到全局异常处理类中。
示例:在控制器中抛出异常
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
@RestController
public class FileController {
@GetMapping("/read-file")
public String readFile() throws FileNotFoundException {
File file = new File("nonexistentfile.txt");
Scanner scanner = new Scanner(file); // 如果文件不存在,会抛出 FileNotFoundException
return "File read successfully";
}
}
文件操作的常见异常:
IOException
、FileNotFoundException
等异常,通过 try-catch
块进行捕获和处理,确保程序在文件操作失败时不会崩溃。try-with-resources
可以自动关闭资源,避免资源泄漏问题。全局异常处理:
@ControllerAdvice
和 @ExceptionHandler
处理文件操作中的异常,实现统一的异常处理逻辑。在处理文件上传、下载等操作时,确保文件访问的安全性和权限控制是非常重要的。Spring Boot 提供了强大的 Spring Security
框架,用于实现访问控制和权限验证。同时,开发者还需要注意处理文件路径,以防止目录遍历攻击等安全问题。
通过 Spring Security
,可以控制用户对文件上传、下载等操作的权限,确保只有授权用户才能执行这些敏感操作。这种权限控制可以基于角色、用户、URL、方法等多种方式进行配置。
在实际应用中,文件上传和下载功能需要根据用户的权限进行控制。例如,只有管理员或特定用户组可以上传文件,而普通用户只能查看或下载文件。
配置示例:使用 Spring Security
控制文件上传与下载
首先,在 SecurityConfig
类中配置权限控制逻辑。确保文件上传和下载的 URL 仅对有特定权限的用户开放。
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 配置上传文件的权限:需要 ADMIN 角色
.antMatchers("/upload").hasRole("ADMIN")
// 配置下载文件的权限:所有经过认证的用户都可以下载
.antMatchers("/download/**").authenticated()
// 其他所有请求允许匿名访问
.anyRequest().permitAll()
.and()
.formLogin()
.and()
.logout().permitAll();
}
}
除了基于 URL 的权限控制,Spring Security
还支持基于方法的权限控制。通过 @PreAuthorize
注解,可以在文件处理的具体方法上设置权限。
示例:在文件上传方法上设置权限
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FileController {
@PostMapping("/upload")
@PreAuthorize("hasRole('ADMIN')") // 仅 ADMIN 角色可以上传文件
public String uploadFile(@RequestParam("file") MultipartFile file) {
// 上传文件逻辑
return "File uploaded successfully!";
}
@GetMapping("/download/{filename}")
@PreAuthorize("isAuthenticated()") // 仅经过认证的用户可以下载文件
public ResponseEntity downloadFile(@PathVariable String filename) {
// 文件下载逻辑
}
}
@PreAuthorize
注解:用于方法级别的权限控制,hasRole('ADMIN')
表示只有拥有 ADMIN
角色的用户才能调用该方法。目录遍历攻击(Directory Traversal Attack)是一种安全漏洞,攻击者通过操控输入的文件路径,试图访问应用程序未授权的文件或目录。通常,攻击者通过构造相对路径符号(如 ../
)来绕过系统的目录访问限制,从而访问系统中的敏感文件或资源。为了防止这种攻击,开发者需要采取安全的路径处理和验证方法。
目录遍历攻击的目标是通过操控文件路径让应用程序访问到超出预期范围的文件或目录。攻击者利用相对路径中的 ../
(父目录符号)跳出合法目录,访问敏感文件,例如系统配置文件、密码文件等。
示例:
假设应用程序允许下载文件,并且文件存储在 uploads/
目录中。
正常请求:
http://example.com/download?file=myfile.txt
uploads/myfile.txt
攻击请求:
http://example.com/download?file=../../etc/passwd
/etc/passwd
(Linux 系统的用户密码文件)。为了防止目录遍历攻击,开发者可以通过使用 Java NIO 提供的 Paths.get()
、resolve()
和 normalize()
方法来确保路径的合法性,并在操作前对路径进行验证。
Paths.get()
和 resolve()
方法构建安全路径Paths.get()
:用于安全地将字符串路径转换为 Path
对象,避免直接拼接字符串带来的安全问题。手动拼接路径可能导致安全隐患,而 Paths.get()
能确保路径的正确解析。
resolve()
:用于将相对路径与基准路径拼接,确保路径合法。resolve()
能够避免通过相对路径符号(如 ../
)跳出指定目录的攻击手段。
示例:
Path basePath = Paths.get("mysource", "uploads");
Path filePath = basePath.resolve("myfile.txt"); // 结果是 "mysource/uploads/myfile.txt"
解释:
Paths.get()
可以用来生成一个目录路径或包含文件名的完整路径。所以Paths.get()中如果将路径与路径拼接,则文件名的拼接交给resolve处理,如果Paths.get()将路径与文件名进行了拼接,则不需要resolve。resolve()
用来将文件名拼接到已有的路径对象上。- 一般情况下,使用
Paths.get()
来构建一个路径对象,路径中可以是多级目录,再通过resolve()
拼接文件名。
normalize()
方法标准化路径normalize()
方法用于清理路径中的冗余部分,特别是移除相对路径中的 ../
,从而防止目录遍历攻击。通过标准化路径,可以消除恶意用户通过相对路径符号绕过目录限制的企图。示例:
Path filePath = Paths.get("uploads").resolve("../etc/passwd").normalize();
// 结果是标准化路径,可以通过进一步的路径验证确保安全
../
来访问上层目录,调用 normalize()
后,路径会被标准化,去掉非法部分,确保最终路径安全。在处理文件路径时,需要验证用户输入的路径是否仍然在合法的目录范围内。通过检查路径是否以指定的合法目录开头,防止目录遍历攻击。
示例:
if (!filePath.startsWith(Paths.get("uploads"))) {
throw new SecurityException("Illegal file access attempt!");
}
解释:
filePath.startsWith()
:此方法用于检查一个路径是否以特定的根路径(如 uploads
目录)开头。它返回 true
或 false
,根据路径是否在指定的基础目录中决定。Paths.get("uploads")
:这是你应用程序中允许访问的合法上传目录。例如,uploads
可能是存储用户上传文件的根目录。filePath.startsWith(Paths.get("uploads"))
检查 filePath
是否位于 uploads
目录中。如果返回 false
,则说明用户可能尝试通过目录遍历攻击访问系统中的其他敏感文件。通过 Paths.get()
、resolve()
、normalize()
和路径验证,可以防止目录遍历攻击,确保文件路径在合法范围内。
示例代码:
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
public class FileController {
private static final String UPLOAD_DIR = "uploads/";
@GetMapping("/download/{filename}")
public ResponseEntity downloadFile(@PathVariable String filename) {
try {
// 构建文件路径,并对用户输入的文件名进行标准化处理
Path filePath = Paths.get(UPLOAD_DIR).resolve(filename).normalize();
// 验证路径是否仍然在合法的目录范围内,防止目录遍历攻击
if (!filePath.startsWith(Paths.get(UPLOAD_DIR))) {
throw new SecurityException("Illegal file access attempt!");
}
// 加载并返回文件资源
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} else {
return ResponseEntity.notFound().build();
}
} catch (InvalidPathException | IOException e) {
return ResponseEntity.internalServerError().build();
}
}
}
关键步骤:
Paths.get()
和 resolve()
安全构建路径,避免直接使用字符串拼接。normalize()
移除路径中的 ../
等不合法符号,防止目录遍历攻击。1. 文件访问的权限控制:
Spring Security
控制文件上传和下载权限,确保只有授权用户能够执行这些操作。@PreAuthorize
)的方式实现权限控制。2. 防止目录遍历攻击:
Paths.get()
、normalize()
),确保用户输入的文件路径不会导致非法访问。在现代应用程序中,将文件存储在云端而不是本地存储已经成为常见的做法。通过集成云存储服务,可以实现大规模、安全、便捷的文件管理。常见的云存储服务包括阿里云 OSS(对象存储服务)和腾讯云 COS(对象存储服务)。这些服务不仅提供了文件上传和下载的基础功能,还支持文件管理、备份、访问控制等功能。
阿里云 OSS 和腾讯云 COS 是常用的云存储服务,分别提供了用于文件管理的 API 接口,允许开发者将文件上传到云端,或从云端下载、管理文件。
在阿里云或腾讯云上注册账号,进入云存储服务(OSS 或 COS)控制台,创建存储空间(Bucket),并获取 API 密钥和相关配置。
使用官方提供的 SDK,可以轻松集成 OSS 或 COS 的功能。
阿里云 OSS SDK:https://help.aliyun.com/document_detail/32008.html
腾讯云 COS SDK:https://cloud.tencent.com/document/product/436/10199
在 Spring Boot 项目中,将云存储服务的 API 密钥、存储空间名称等配置放入 application.properties
文件中。
示例:阿里云 OSS 配置
# 阿里云 OSS 配置
aliyun.oss.endpoint=oss-cn-hangzhou.aliyuncs.com
aliyun.oss.access-key-id=
aliyun.oss.access-key-secret=
aliyun.oss.bucket-name=
示例:腾讯云 COS 配置
# 腾讯云 COS 配置
tencent.cos.region=ap-guangzhou
tencent.cos.secret-id=
tencent.cos.secret-key=
tencent.cos.bucket-name=
在 Spring Boot 项目中,通过 Maven 引入相应的云存储 SDK。
阿里云 OSS SDK 依赖
com.aliyun.oss
aliyun-sdk-oss
3.13.0
腾讯云 COS SDK 依赖
com.qcloud
cos_api
5.6.10
在 Spring Boot 中,需要通过配置类或服务类来初始化云存储服务的客户端。
阿里云 OSS 客户端初始化示例
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OssConfig {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.access-key-id}")
private String accessKeyId;
@Value("${aliyun.oss.access-key-secret}")
private String accessKeySecret;
@Bean
public OSS ossClient() {
return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
}
腾讯云 COS 客户端初始化示例
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.region.Region;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CosConfig {
@Value("${tencent.cos.secret-id}")
private String secretId;
@Value("${tencent.cos.secret-key}")
private String secretKey;
@Value("${tencent.cos.region}")
private String region;
@Bean
public COSClient cosClient() {
BasicCOSCredentials credentials = new BasicCOSCredentials(secretId, secretKey);
ClientConfig clientConfig = new ClientConfig(new Region(region));
return new COSClient(credentials, clientConfig);
}
}
文件的云端管理包括文件上传、下载和删除等基本操作。在集成了云存储服务(如阿里云 OSS 或腾讯云 COS)之后,开发者可以通过 SDK 或 REST API 对文件进行云端管理操作。这不仅能有效管理大量文件,还能确保文件的高可用性、安全性和可扩展性。下面详细讲解如何通过阿里云 OSS 和腾讯云 COS 进行文件的上传、下载和删除。
阿里云 OSS 文件上传示例
在阿里云 OSS 中,文件上传通过 putObject()
方法实现。你需要提供存储空间的 bucket
名称、文件的名称以及文件的输入流。
import com.aliyun.oss.OSS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RestController
public class OssController {
@Autowired
private OSS ossClient;
@Value("${aliyun.oss.bucket-name}")
private String bucketName;
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
String fileName = file.getOriginalFilename();
ossClient.putObject(bucketName, fileName, file.getInputStream()); // 上传文件到云端
return "File uploaded successfully!";
}
}
MultipartFile
:Spring MVC 提供的文件类型,代表从前端上传的文件。ossClient.putObject()
:用于将文件上传到阿里云 OSS。传入参数包括存储桶名称、文件名和文件输入流。腾讯云 COS 文件上传示例
腾讯云 COS 的文件上传通过 putObject()
方法实现,类似于阿里云 OSS。
import com.qcloud.cos.COSClient;
import com.qcloud.cos.model.PutObjectRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@RestController
public class CosController {
@Autowired
private COSClient cosClient;
@Value("${tencent.cos.bucket-name}")
private String bucketName;
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
File localFile = convertMultipartFileToFile(file); // 将文件转换为本地文件
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, file.getOriginalFilename(), localFile);
cosClient.putObject(putObjectRequest); // 上传文件到腾讯云 COS
return "File uploaded successfully!";
}
// 将 MultipartFile 转换为本地文件
private File convertMultipartFileToFile(MultipartFile file) throws IOException {
File convFile = new File(System.getProperty("java.io.tmpdir") + "/" + file.getOriginalFilename());
file.transferTo(convFile);
return convFile;
}
}
MultipartFile
:前端上传的文件。PutObjectRequest
:封装文件上传请求。上传时需指定存储桶名称、文件名和本地文件路径。MultipartFile
转换为 File
,再通过 putObject()
上传。文件下载是通过云存储服务提供的 getObject()
方法获取文件,并将文件流返回给客户端。
阿里云 OSS 文件下载示例
import com.aliyun.oss.model.OSSObject;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
@RestController
public class OssDownloadController {
@Autowired
private OSS ossClient;
@Value("${aliyun.oss.bucket-name}")
private String bucketName;
@GetMapping("/download/{fileName}")
public void downloadFile(@PathVariable String fileName, HttpServletResponse response) throws IOException {
// 从 OSS 获取文件
OSSObject ossObject = ossClient.getObject(bucketName, fileName);
try (InputStream inputStream = ossObject.getObjectContent();
OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length); // 将文件内容写入响应
}
}
}
}
ossClient.getObject()
:用于从 OSS 获取指定文件。response.getOutputStream()
:通过 HTTP 响应流将文件内容返回给客户端,支持文件下载功能。腾讯云 COS 文件下载示例
import com.qcloud.cos.model.COSObject;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
@RestController
public class CosDownloadController {
@Autowired
private COSClient cosClient;
@Value("${tencent.cos.bucket-name}")
private String bucketName;
@GetMapping("/download/{fileName}")
public void downloadFile(@PathVariable String fileName, HttpServletResponse response) throws IOException {
// 从 COS 获取文件
COSObject cosObject = cosClient.getObject(bucketName, fileName);
try (InputStream inputStream = cosObject.getObjectContent();
OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length); // 将文件内容写入响应
}
}
}
}
cosClient.getObject()
:用于从 COS 获取指定文件。response.getOutputStream()
,客户端通过 HTTP 请求下载文件。文件删除操作使用 deleteObject()
方法来删除云端存储的文件。
阿里云 OSS 文件删除示例
import org.springframework.web.bind.annotation.*;
@RestController
public class OssDeleteController {
@Autowired
private OSS ossClient;
@Value("${aliyun.oss.bucket-name}")
private String bucketName;
@DeleteMapping("/delete/{fileName}")
public String deleteFile(@PathVariable String fileName) {
ossClient.deleteObject(bucketName, fileName); // 删除 OSS 上的文件
return "File deleted successfully!";
}
}
ossClient.deleteObject()
:用于删除 OSS 中的文件。提供存储桶名称和文件名即可删除指定文件。腾讯云 COS 文件删除示例
import org.springframework.web.bind.annotation.*;
@RestController
public class CosDeleteController {
@Autowired
private COSClient cosClient;
@Value("${tencent.cos.bucket-name}")
private String bucketName;
@DeleteMapping("/delete/{fileName}")
public String deleteFile(@PathVariable String fileName) {
cosClient.deleteObject(bucketName, fileName); // 删除 COS 上的文件
return "File deleted successfully!";
}
}
cosClient.deleteObject()
:用于删除 COS 中的文件。传入存储桶名称和文件名,即可删除指定文件。在 Java 中进行文件操作时,处理大文件或频繁的文件读写操作可能会引发性能瓶颈。通过优化文件的 I/O(输入/输出)操作,可以显著提高程序的性能。性能优化的方式主要包括使用缓冲流和 Java NIO(New I/O)来减少阻塞,提高文件读写效率,尤其在处理大文件时,这些技术尤为关键。
Java I/O 包中的 缓冲流 提供了一种高效的文件读写方式。默认情况下,直接使用 FileReader
、FileWriter
等类进行读写操作时,每次读写操作都会访问磁盘,这可能导致性能下降。通过使用缓冲流(BufferedReader
和 BufferedWriter
),可以显著减少对磁盘的直接访问,提升文件 I/O 的效率。
BufferedReader
:用于缓冲字符输入流,提供更高效的读取操作,尤其是逐行读取文件时。它减少了磁盘访问次数,通过内存缓冲区来提升读取速度。
BufferedWriter
:用于缓冲字符输出流,减少每次写操作对磁盘的访问,尤其是在频繁写入的情况下。BufferedWriter
通过将数据存储到内存缓冲区,再统一写入磁盘,提升写入性能。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderExample {
public void readFile(String filePath) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}
BufferedReader
通过内部缓冲机制减少对文件的逐行读取操作,每次读取较大的块,而不是每次直接从文件读取一行。这样可以大大提高性能,尤其是在读取大文件时。import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterExample {
public void writeFile(String filePath, String content) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
writer.write(content);
}
}
}
BufferedWriter
通过在内存中收集一定量的数据后再一次性写入文件,避免了每次写操作都直接访问磁盘,从而提高了写入性能。减少 I/O 操作:每次 I/O 操作(如读取或写入)都涉及到操作系统和硬件层的交互,而这些操作通常是性能瓶颈。使用缓冲流可以通过批量操作来减少这种交互次数,从而提高整体性能。
内存缓冲区:通过使用缓冲区,将小块数据暂存在内存中,只有当缓冲区满时才写入文件或从文件中读取,避免频繁访问磁盘。
Java NIO(New Input/Output)引入了一种更高效的文件操作方式,尤其是在处理大文件时,NIO 提供的 FileChannel
和 MappedByteBuffer
等类能够显著提升性能。通过这些类,程序可以直接操作文件的字节,而不是像传统 IO 一样必须经过缓慢的流式读取和写入。尤其在处理大文件时,NIO 的非阻塞特性可以减少文件操作的阻塞等待,提高整体响应性能。
Java NIO(New I/O)引入了一种高效的文件处理机制,特别是对于大文件。NIO 的核心是通道(Channel)和缓冲区(Buffer)的模型,这比传统的流式 I/O 更加高效和灵活,尤其适用于非阻塞 I/O 操作。
FileChannel 是专门用于操作文件的通道类,支持对文件进行高效的读写操作,并且允许随机访问文件的任何位置。它不直接与文件交互,而是通过 ByteBuffer
作为缓冲区来操作数据。
RandomAccessFile
创建 FileChannel
FileChannel
可以通过 RandomAccessFile
的 getChannel()
方法获取。RandomAccessFile
允许读写文件的任意位置,因此配合 FileChannel
可以实现随机访问。
RandomAccessFile file = new RandomAccessFile("example.txt", "rw");
FileChannel fileChannel = file.getChannel();
FileInputStream
或 FileOutputStream
获取如果只需要文件的读取或写入功能,可以通过 FileInputStream
或 FileOutputStream
的 getChannel()
方法获取 FileChannel
。
FileInputStream fis = new FileInputStream("example.txt");
FileChannel readChannel = fis.getChannel();
FileOutputStream fos = new FileOutputStream("example.txt");
FileChannel writeChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配缓冲区
int bytesRead = fileChannel.read(buffer); // 读取数据到缓冲区
buffer.flip(); // 准备读取缓冲区内容
FileChannel.read(ByteBuffer)
方法可以将数据从文件通道读取到缓冲区中。ByteBuffer buffer = ByteBuffer.wrap("Hello World!".getBytes());
fileChannel.write(buffer); // 将缓冲区内容写入文件
FileChannel.write(ByteBuffer)
方法可以将缓冲区中的数据写入文件。fileChannel.position(100); // 跳到文件的第100字节处
fileChannel.read(buffer); // 从该位置读取数据
position(long newPosition)
方法,FileChannel
支持文件的随机读写。可以跳转到文件中的任意位置进行操作。fileChannel.truncate(1024); // 将文件大小截断为1024字节
truncate(long size)
方法用于截断文件,文件的大小将被调整为给定的大小。超出该大小的部分将被丢弃。fileChannel.force(true); // 强制将文件内容和元数据写入磁盘
force(boolean metaData)
方法用于确保所有文件数据(以及文件的元数据)被同步写入到磁盘,避免数据丢失。缓冲区(Buffer)是 Java NIO 中用于处理数据的核心部分。它是一块内存区域,提供了存储数据的能力,特别适合 I/O 操作。缓冲区将数据从通道中读入或从程序中写出到通道。缓冲区不仅在文件操作中使用,还可以用于网络传输等场景。
根据存储的数据类型不同,Java NIO 提供了多种缓冲区类型。每种缓冲区对应不同的数据类型,它们继承自 Buffer
类。常见的缓冲区包括:
ByteBuffer
:字节缓冲区,处理字节数据。CharBuffer
:字符缓冲区,处理字符数据。IntBuffer
、FloatBuffer
、DoubleBuffer
等:处理不同基本数据类型的缓冲区。每个缓冲区都有一些重要的属性,这些属性决定了缓冲区在读写数据时的状态和行为。这些属性包括 capacity
、position
、limit
和 mark
。
1.capacity
(容量)缓冲区的容量表示它最多能容纳的数据元素个数。容量是创建缓冲区时确定的,并且不能改变。
2.position
(位置)缓冲区的当前位置,用于指示下一个要读或写的元素的索引。缓冲区的 position
会随着数据的读写操作自动更新。
3.limit
(限制)缓冲区的限制是指在写模式下,限制你能写入的数据量。在读模式下,限制是缓冲区中可以读取的数据的数量。通常当缓冲区从写模式切换到读模式时,会将 limit
设置为数据的实际大小。
4.mark
(标记)标记用于记录缓冲区中的某个特定位置。你可以在稍后调用 reset()
方法恢复到该位置。mark
通常用于在某个地方做标记以便后续回退到这个位置。
capacity
:缓冲区的总大小,不能改变。position
:当前正在处理的位置,指向缓冲区的某个索引。limit
:限制当前能读写的最大位置。mark
:做标记,可以用来回退到某个位置。缓冲区的使用状态主要有两种:
position
表示下一个写入的位置,limit
通常等于 capacity
。flip()
之后),position
被重置为 0,limit
设置为缓冲区的写入位置,表示可以读取的有效数据范围。1.flip()
buffer.flip(); // 切换到读模式
将缓冲区从写模式切换到读模式。调用 flip()
后,limit
被设置为当前的 position
,而 position
被重置为 0,表示从缓冲区的头开始读取数据。
2.clear()
buffer.clear(); // 清除缓冲区,准备重新写入数据
清除缓冲区,准备再次写入数据。clear()
并不会清除缓冲区中的数据,只是将 position
置为 0,limit
置为 capacity
,表示可以重新写入数据。
3.rewind()
buffer.rewind(); // 重置 position 为 0,准备重新读取
重置 position
为 0,允许重新读取缓冲区中的数据,而不改变 limit
。
4.compact()
buffer.compact(); // 压缩缓冲区,保留未读取的数据
将未读的数据复制到缓冲区的起始位置,然后将 position
设置到未读数据的后面,准备写入新的数据。
5.hasRemaining()
if (buffer.hasRemaining()) {
// 还有未读数据
}
检查缓冲区是否还有未读的数据(即 position
< limit
)。
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
import java.io.IOException;
public class FileChannelExample {
public void readFile(String filePath) throws IOException {
// 使用 RandomAccessFile 打开文件,"r" 表示以只读模式打开
try (RandomAccessFile file = new RandomAccessFile(filePath, "r");
FileChannel fileChannel = file.getChannel()) { // 获取 FileChannel
// 分配一个 1024 字节大小的 ByteBuffer 作为缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从通道中读取数据到缓冲区
while (fileChannel.read(buffer) > 0) {
buffer.flip(); // 将缓冲区从写模式切换为读模式
// 逐字节读取缓冲区中的内容
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get()); // 输出字符
}
buffer.clear(); // 清空缓冲区,准备下一次读操作
}
}
}
}
代码解析:
RandomAccessFile file = new RandomAccessFile(filePath, "r")
:
RandomAccessFile
用于打开文件,模式 "r"
表示以只读方式打开。与普通 FileInputStream
不同,RandomAccessFile
支持随机访问文件中的任意位置。FileChannel fileChannel = file.getChannel()
:
getChannel()
方法获取该文件的 FileChannel
。通过 FileChannel
,我们可以直接操作文件的数据,支持高效的文件读写。ByteBuffer buffer = ByteBuffer.allocate(1024)
:
ByteBuffer
,这是内存中的缓冲区。数据会从 FileChannel
读入到这个缓冲区中。fileChannel.read(buffer)
:
ByteBuffer
缓冲区中,返回值表示读取的字节数。如果读取的字节数大于 0,说明文件中还有数据没有读完。buffer.flip()
:
flip()
将缓冲区从写模式切换为读模式,准备从缓冲区读取数据。buffer.hasRemaining()
:
hasRemaining()
方法检查缓冲区中是否还有未读取的数据。如果有,则逐字节输出缓冲区中的内容。buffer.clear()
:
clear()
清空缓冲区,为下一次读操作做准备。MappedByteBuffer
是 Java NIO 中的一个特殊类,它允许将文件的某个部分映射到内存中进行直接访问。这种内存映射文件方式使得文件读写操作非常高效,尤其适用于处理大文件时,可以避免频繁的磁盘 I/O 操作。
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.io.IOException;
public class MappedByteBufferExample {
public void processLargeFile(String filePath) throws IOException {
// 以读写模式打开文件
try (RandomAccessFile file = new RandomAccessFile(filePath, "rw");
FileChannel fileChannel = file.getChannel()) {
// 将文件的前1MB映射到内存,MapMode.READ_WRITE 表示可读可写
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024);
// 修改映射到内存中的文件内容
buffer.put(0, (byte) 65); // 将第一个字节修改为 'A' (ASCII 65)
// 读取映射文件的部分内容
byte firstByte = buffer.get(0);
System.out.println("First byte of the file: " + (char) firstByte);
}
}
}
代码解析:
RandomAccessFile file = new RandomAccessFile(filePath, "rw")
:
"rw"
表示允许读写操作。FileChannel fileChannel = file.getChannel()
:
FileChannel
,通过它来对文件进行操作。MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024)
:
map()
方法将文件的前 1MB 映射到内存中,MapMode.READ_WRITE
表示映射的内容可以读写。参数 0
表示从文件开头开始映射,1024 * 1024
表示映射 1MB 数据。buffer.put(0, (byte) 65)
:
65
,即字符 'A'
的 ASCII 码值。byte firstByte = buffer.get(0)
:
内存同步:
MappedByteBuffer
中的数据实际上就等于修改了文件。注意事项:
为避免将整个文件映射到内存引发的内存不足问题,通常使用分块映射策略。分块映射可以将文件按部分映射到内存,每次只操作文件的一部分,而不是整个文件。
在文件上传和下载功能开发完成后,进行充分的测试是确保代码质量的关键步骤。在 Spring Boot 中,我们可以使用 单元测试 和 集成测试 来验证这些功能。单元测试主要是验证各个组件(如服务层、控制器层)的功能是否正常,而集成测试则是确保整个应用程序的各个部分能够协同工作。
单元测试的重点是确保业务逻辑和方法功能的正确性。在文件上传和下载功能中,可以使用 MockMultipartFile
来模拟文件上传,验证控制器和服务层的行为。
MockMultipartFile
的使用MockMultipartFile
是 Spring 提供的一个模拟文件上传的类,可以在单元测试中使用,模拟客户端上传的文件,而无需真正地上传文件。
示例:模拟文件上传的单元测试
假设我们有一个文件上传的服务,负责将上传的文件保存到本地文件系统中,服务代码如下:
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Service
public class FileUploadService {
public String uploadFile(MultipartFile file) throws IOException {
String filePath = "uploads/" + file.getOriginalFilename();
file.transferTo(new File(filePath)); // 将文件保存到指定路径
return filePath;
}
}
我们可以通过 MockMultipartFile
模拟上传文件的单元测试:
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
public class FileUploadServiceTest {
@Autowired
private FileUploadService fileUploadService;
@Test
public void testFileUpload() throws Exception {
// 创建一个 MockMultipartFile 模拟上传文件
MockMultipartFile mockFile = new MockMultipartFile(
"file", // 参数名
"testfile.txt", // 文件名
"text/plain", // 文件类型
"Hello, World!".getBytes()); // 文件内容
// 调用上传服务,验证结果
String filePath = fileUploadService.uploadFile(mockFile);
assertEquals("uploads/testfile.txt", filePath); // 验证文件路径
}
}
代码解析:
MockMultipartFile
:创建模拟的上传文件。在这里,我们模拟了一个名为 testfile.txt
的文本文件,内容为 "Hello, World!"
。
fileUploadService.uploadFile(mockFile)
:调用上传文件的服务,模拟文件的上传。
assertEquals()
:验证文件是否被上传到了指定路径。
文件下载也可以通过 MockMvc
或其他手段来验证。假设我们有一个简单的文件下载服务:
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
public class FileDownloadService {
public Resource downloadFile(String fileName) throws Exception {
Path filePath = Paths.get("uploads").resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return resource;
} else {
throw new Exception("File not found");
}
}
}
文件下载的单元测试可以模拟调用该服务:
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
public class FileDownloadServiceTest {
@Autowired
private FileDownloadService fileDownloadService;
@Test
public void testFileDownload() throws Exception {
// 假设 uploads 文件夹中有一个文件 testfile.txt
Resource resource = fileDownloadService.downloadFile("testfile.txt");
assertTrue(resource.exists()); // 验证文件是否存在
}
}
代码解析:
downloadFile("testfile.txt")
:调用下载服务,模拟下载 testfile.txt
文件。
assertTrue(resource.exists())
:验证文件是否存在于指定路径。
集成测试是为了确保应用程序的各个部分能够协同工作。使用 MockMvc
可以模拟对控制器的 HTTP 请求,测试文件上传和下载 API 的整体功能。MockMvc
是 Spring 提供的一个工具,可以模拟 Web 请求,并验证响应内容。
MockMvc
测试文件上传 API假设我们有一个文件上传的控制器:
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.beans.factory.annotation.Autowired;
@RestController
@RequestMapping("/files")
public class FileController {
@Autowired
private FileUploadService fileUploadService;
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) throws Exception {
return fileUploadService.uploadFile(file); // 调用服务上传文件
}
}
使用 MockMvc
来测试该控制器的文件上传功能:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
public class FileUploadControllerTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Test
public void testFileUpload() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
MockMultipartFile mockFile = new MockMultipartFile(
"file", "testfile.txt", "text/plain", "Hello, World!".getBytes());
mockMvc.perform(multipart("/files/upload") // 模拟文件上传请求
.file(mockFile)
.contentType(MediaType.MULTIPART_FORM_DATA))
.andExpect(status().isOk()); // 验证返回状态
}
}
代码解析:
MockMvcBuilders.webAppContextSetup()
:设置 MockMvc
上下文。
multipart("/files/upload")
:模拟对 /files/upload
的文件上传请求。
andExpect(status().isOk())
:验证上传操作是否返回了 200 OK 状态。
MockMvc
测试文件下载 API文件下载的控制器如下:
import org.springframework.web.bind.annotation.*;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.beans.factory.annotation.Autowired;
@RestController
@RequestMapping("/files")
public class FileDownloadController {
@Autowired
private FileDownloadService fileDownloadService;
@GetMapping("/download/{fileName}")
public ResponseEntity downloadFile(@PathVariable String fileName) throws Exception {
Resource resource = fileDownloadService.downloadFile(fileName);
return ResponseEntity.ok()
.body(resource);
}
}
使用 MockMvc
测试文件下载功能:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
public class FileDownloadControllerTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Test
public void testFileDownload() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(get("/files/download/testfile.txt")) // 模拟下载请求
.andExpect(status().isOk()); // 验证返回状态
}
}
代码解析:
get("/files/download/testfile.txt")
:模拟 GET 请求,下载 testfile.txt
文件。
andExpect(status().isOk())
:验证文件下载操作是否成功,返回 200 OK 状态。