结果集

该项目采取统一结果集返回,所以这里我就先贴出了结果集Result的代码

/**
 * 响应结果封装类:
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Result {

    /**
     * 状态码常量:
     */
    // 成功
    public static final int CODE_OK = 200;
    // 失败
    public static final int CODE_ERR = 400;

    // 成员属性
    private int code;       // 状态码
    private boolean success; // 成功响应为true,失败响应为false
    private String message; // 响应信息
    private Object data;    // 响应数据

    // 成功响应的方法 -- 返回的Result中只封装了成功状态码
    public static Result ok() {
        return new Result(CODE_OK, true, null, null);
    }

    // 成功响应的方法 -- 返回的Result中封装了成功状态码和响应信息
    public static Result ok(String message) {
        return new Result(CODE_OK, true, message, null);
    }

    // 成功响应的方法 -- 返回的Result中封装了成功状态码和响应数据
    public static Result ok(Object data) {
        return new Result(CODE_OK, true, null, data);
    }

    // 成功响应的方法 -- 返回的Result中封装了成功状态码和响应信息和响应数据
    public static Result ok(String message, Object data) {
        return new Result(CODE_OK, true, message, data);
    }

    // 失败响应的方法 -- 返回的Result中封装了失败状态码和响应信息
    public static Result err(String message) {
        return new Result(CODE_ERR, false, message, null);
    }

    // 失败响应的方法 -- 返回的Result中封装了失败状态码和响应信息
    public static Result err(int errCode, String message) {
        return new Result(errCode, false, message, null);
    }

    // 失败响应的方法 -- 返回的Result中封装了失败状态码和响应信息和响应数据
    public static Result err(int errCode, String message, Object data) {
        return new Result(errCode, false, message, data);
    }
}

yaml文件

这里我采取的是通过yaml文件统一管理上传文件的大小,类型以及存储路径

spring:
  servlet:
    multipart:
      max-file-size: 5MB
      max-request-size: 5MB
server:
  port: 7801
file:
  upload:
    location: D:\kaifa\imgAddr
    max-size: 5MB   # 单位: MB
    allowed-types:
      - jpeg
      - png

location:文件上传的位置

max-size:允许上传文件的最大大小

allowed-type:可以自定义能够上传的文件类型

Spring Boot 有内置的文件上传限制,默认情况下,这些限制可能会影响到上传文件的大小,即使你已经在自定义属性中设置了 max-size。所以我们要在 application.yml 中添加max-file-size以及 max-request-size配置来确保 Spring 的文件上传限制与自定义的 max-size 一致

Properties处理配置文件

FileUploadProperties

@Component
@ConfigurationProperties(prefix = "file.upload")
public class FileUploadProperties {
    private String location;
    private DataSize maxSize;
    private List<String> allowedTypes;

    // Getters and Setters
    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public DataSize getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(DataSize maxSize) {
        this.maxSize = maxSize;
    }

    public List<String> getAllowedTypes() {
        return allowedTypes;
    }

    public void setAllowedTypes(List<String> allowedTypes) {
        this.allowedTypes = allowedTypes;
    }
}

这个 FileUploadProperties 类是一个用于存储文件上传配置的组件类。它的作用是将配置文件(如 application.ymlapplication.properties)中的文件上传相关的配置值注入到这个类的实例中,从而方便在代码中使用这些配置。

具体用途

  1. 存储文件上传路径 (location):

    • location 字段用来存储文件上传的目录路径。在 application.yml 中配置的路径将会注入到这个字段中。

  2. 存储上传文件的最大大小 (maxSize):

    • maxSize 字段用来存储允许上传的文件的最大大小限制。它使用 DataSize 类型,允许像 5MB 这样的配置值。这是用来限制用户上传的文件大小。

  3. 存储允许上传的文件类型 (allowedTypes):

    • allowedTypes 字段用来存储一个允许的文件类型列表。这个列表中的值通常是文件的扩展名或 MIME 类型,用来限制用户只能上传指定类型的文件。

注入配置

@ConfigurationProperties(prefix = "file.upload") 注解表示这个类中的属性将与配置文件中 file.upload 前缀的配置项绑定,而在之前的yaml文件中我们已经设置过了这些数据,所以这里可以直接获取到我们所设定的和文件相关的设置,注意到是这里的字段要和yaml文件里的一样

这样做的好处是配置与代码解耦,使得代码更加灵活且易于维护。

ServerProperties

@Configuration
public class ServerProperties {

    @Value("${server.port}")
    private int port;

    public int getPort() {
        return port;
    }
}

ServerProperties 类是一个用于存储和提供服务器配置属性的配置类。在这个例子中,它专门用于获取和存储服务器运行的端口号。

具体用途

  1. 获取服务器端口 (port):

    • 这个类中的 port 字段通过 @Value("${server.port}") 注解来注入配置文件(如 application.ymlapplication.properties)中的服务器端口号配置。

    • @Value("${server.port}") 表示从配置文件中读取 server.port 配置项的值,并将其赋值给 port 字段。

  2. 提供服务器端口号:

    • getPort() 方法用于获取这个端口号。在应用程序的其他部分需要知道服务器的端口号时,可以通过调用这个方法来获取。

注解说明

  • @Configuration:这是一个配置类的标注,告诉 Spring 容器这个类包含了一个或多个 @Bean 方法,这些方法将会被 Spring 容器管理。但在这个例子中,它仅用于将端口号作为配置提供,因此 @Configuration 并非必要,可以去掉。

  • @Value("${server.port}"):这个注解用来从配置文件中读取 server.port 的值。

因为我们在yaml中已经设置过端口号,所以这里便可以直接获取到我们的端口号7801,以便我们后续的使用

文件静态资源配置config文件

Spring Boot 默认不提供静态资源映射到文件系统的功能,你需要添加配置来使 /uploads/ 路径指向文件系统中的目录。我们可以使用 Spring Boot 的静态资源配置来实现这一点。

package com.weida.superhomepage.config;

import com.weida.superhomepage.properties.FileUploadProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.io.File;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final FileUploadProperties fileUploadProperties;

    @Autowired
    public WebConfig(FileUploadProperties fileUploadProperties) {
        this.fileUploadProperties = fileUploadProperties;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 从配置中获取上传路径
        String uploadLocation = fileUploadProperties.getLocation();

        // 映射 /uploads/ 路径到文件系统的目录
        registry.addResourceHandler("/uploads/**")
                .addResourceLocations("file:" + uploadLocation + File.separator);
    }
}

这个配置类用于配置文件上传功能的静态资源映射。具体来说,它设置了一个资源处理器,将文件系统中的某个目录映射到 Web 应用程序的一个 URL 路径,以便能够通过 HTTP 访问这些文件。

  • @Configuration 注解:

    • 这个注解表示该类是一个配置类,Spring 容器会将其作为配置类进行处理。它通常用于定义 Bean 和配置类,Spring 容器会加载这些配置。

  • 实现 WebMvcConfigurer 接口:

    • WebMvcConfigurer 接口允许自定义 Spring MVC 的配置,如添加资源处理器、视图解析器等。通过实现这个接口,可以对 Spring MVC 的默认行为进行修改或扩展。

  • 注入 FileUploadProperties:

    • 通过构造函数注入 FileUploadProperties 类,这个类包含了文件上传相关的配置,如上传路径等。Spring 会自动将配置文件中的属性注入到 FileUploadProperties 实例中。

  • addResourceHandlers 方法:

    • 这个方法用于添加资源处理器。资源处理器用于映射 URL 路径到本地文件系统中的资源路径。

  1. uploadLocation:

    • FileUploadProperties 中获取配置的文件上传路径。

  2. addResourceHandler("/uploads/**"):

    • 定义 URL 路径模式 /uploads/**,表示所有以 /uploads/ 开头的请求都会被映射到本地文件系统的目录中。

  3. addResourceLocations("file:" + uploadLocation + File.separator):

    • file: 协议与上传路径拼接起来,形成文件系统的路径。file: 表示这是一个本地文件系统路径。

  4. File.separator:

    • 是系统默认的文件分隔符,用于确保路径在不同操作系统下都能正确处理。

其实 File.separator 的作用相当于 ' \ ',在 windows 中 文件文件分隔符 用 ' \ ' 或者 ' / ' 都可以,但是在 Linux 中,是不识别 ' \ ' 的,而 File.separator 是系统默认的文件分隔符号,在 UNIX 系统上,此字段的值为 ' / ',在 Microsoft Windows 系统上,它为 ' \ ' 屏蔽了这些系统的区别。所以用 File.separator 保证了在任何系统下不会出错。

此外 File 类还有:

  1. separatorChar:与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符

  2. pathSeparatorChar:与系统有关的路径分隔符,为了方便,它被表示为一个字符串

  3. pathSeparator:此字符用于分隔以路径列表形式给定的文件序列中的文件名,在 UNIX 系统上此字段为 ' : ' ,在 Microsoft Windows 系统上,它为 ' ; '

实际效果

  1. 文件访问:

    • 通过访问 http://localhost:7801/uploads/yourfile.jpg 可以直接访问到文件系统中的 D:\kaifa\imgAddr\yourfile.jpg 文件。

  2. 资源映射:

    • 这个配置使得 Web 应用能够服务于上传的文件,使用户可以通过浏览器访问这些文件而不必直接暴露文件系统的路径。

异常捕获

@RestControllerAdvice
public class GlobalExceptionHandler {

    private final FileUploadProperties fileUploadProperties;

    @Autowired
    public GlobalExceptionHandler(FileUploadProperties fileUploadProperties) {
        this.fileUploadProperties = fileUploadProperties;
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public Result handleMaxSizeException(MaxUploadSizeExceededException exc) {
        DataSize maxSize = fileUploadProperties.getMaxSize();
        String message = "上传文件大小超过限制,最大允许上传的文件大小为 " + maxSize.toMegabytes() + " MB。请上传更小的文件。";
        return Result.err(message);
    }
}

这个文件很有必要,因为我试过了,如果不加这个全局异常捕获,只写在controller层,程序是无法准确的将错误信息返回,会直接抛异常,所以我们需要定义该类来全局捕获此异常,在全局异常处理中覆盖可能遗漏的情况。

接口实现

/**
 * 文件上传控制器
 * <p>
 * 处理文件上传请求,包括文件类型校验、文件保存和生成文件访问路径等功能。
 * </p>
 */
@Api(tags = "文件上传接口")
@RestController
public class FileUploadController {

    private final FileUploadProperties fileProperties;
    private final ServerProperties serverProperties;

    @Autowired
    public FileUploadController(FileUploadProperties fileProperties, ServerProperties serverProperties) {
        this.fileProperties = fileProperties;
        this.serverProperties = serverProperties;
    }

    /**
     * 上传文件接口
     * <p>
     * 处理文件上传请求,包括校验文件类型、生成唯一文件名、保存文件,并返回文件的访问路径。
     * </p>
     *
     * @param file 上传的文件
     * @return 上传结果,包括文件的访问路径
     */
    @ApiOperation(value = "上传文件", notes = "处理文件上传请求,包括文件类型校验和生成文件访问路径。")
    @PostMapping("/upload")
    public Result uploadFile(
            @ApiParam(value = "上传的文件", required = true) @RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return Result.err("上传文件不能为空");
        }

        // 获取文件类型的扩展部分
        String fileType = file.getContentType();
        String fileTypeExtension = fileType != null ? fileType.substring(fileType.indexOf('/') + 1) : "";

        // 校验文件类型
        if (!fileProperties.getAllowedTypes().contains(fileTypeExtension)) {
            return Result.err("不支持的文件类型:" + fileTypeExtension);
        }

        try {
            // 获取服务器 IP 地址
            String ipAddress = InetAddress.getLocalHost().getHostAddress();
            // 获取端口号
            String port = String.valueOf(serverProperties.getPort());
            // 获取文件上传路径
            String uploadDir = fileProperties.getLocation();

            // 获取原文件名和文件扩展名
            String originalFilename = file.getOriginalFilename();
            String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));

            // 生成唯一的文件名后缀
            String uniqueSuffix = UUID.randomUUID().toString();
            // 生成新的文件名
            String newFilename = originalFilename.substring(0, originalFilename.lastIndexOf("."))
                    + "_" + uniqueSuffix + fileExtension;
            // 拼接文件路径
            String filePath = uploadDir + File.separator + newFilename;

            // 创建目录
            File directory = new File(uploadDir);
            if (!directory.exists()) {
                directory.mkdirs();
            }

            // 保存文件
            file.transferTo(new File(filePath));

            // 拼接文件访问路径
            String fileUrl = "http://" + ipAddress + ":" + port + "/uploads/" + newFilename;

            // 返回文件访问路径
            Map<String, String> result = new HashMap<>();
            result.put("path", fileUrl);
            return Result.ok("上传成功", result);
        } catch (UnknownHostException e) {
            return Result.err("获取服务器 IP 地址失败");
        } catch (IOException e) {
            e.printStackTrace();
            return Result.err("文件上传失败");
        }
    }
}

以上就是整个上传文件功能的完成了。