12 KiB
上小节中,我们已经在本地搭建好了 Minio 对象存储的环境。本小节中,我们就来为后端服务添加一个 —— 文件上传接口。
1. 添加 Minio 依赖
首先,在父项目的 pom.xml 文件中添加 Minio 版本管理声明:
<!-- 版本号统一管理 -->
<properties>
// 省略...
<minio.version>8.2.1</minio.version>
</properties>
<!-- 统一依赖管理 -->
<dependencyManagement>
<dependencies>
// 省略...
<!-- 对象存储 Minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
然后,在 weblog-module-admin 模块中的 pom.xml 文件中,添加该依赖:
<!-- 对象存储 Minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
2. 添加 Minio 配置
编辑 applicaiton-dev.yml 配置文件,添加 minio 相关的配置项:
#=================================================================
# minio
#=================================================================
minio:
endpoint: http://127.0.0.1:9000
accessKey: quanxiaoha
secretKey: quanxiaoha
bucketName: weblog
解释一下这些配置都是干啥的:
endpoint: http://127.0.0.1:9000:指定 MinIO 服务器的地址。实际部署时,您需要将它替换为您 MinIO 服务器的地址。accessKey: quanxiaoha:运行容器时,指定的接入key。secretKey: quanxiaoha:运行容器时,指定的秘钥key。bucketName: weblog:存储桶(bucket)的名称。
3. 新增 Minio 配置类
接着,在 weblog-module-admin 子模块的 /config 包下,创建一个 MinioProperties 配置类,用来读取刚刚手动配置的 Minio 选项:
@ConfigurationProperties(prefix = "minio")
@Component
@Data
public class MinioProperties {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;
}
⚠️ 注意:配置类的前缀需要指定为
minio, 且字段名要与配置项名称保持一致。
4. 新增 Minio 客户端配置
继续在 /config 包中,创建一个 MinioConfig 客户端配置类:
@Configuration
public class MinioConfig {
@Autowired
private MinioProperties minioProperties;
@Bean
public MinioClient minioClient() {
// 构建 Minio 客户端
return MinioClient.builder()
.endpoint(minioProperties.getEndpoint())
.credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
.build();
}
}
上述代码中,我们构建了一个 MinioClient 客户端,并使用 @Bean 注解注入到了 Spring 容器中。
5. 封装图片上传工具类
初始化好 Minio 客户端后,在 weblog-module-admin 子模块中,新增一个 /utils 工具包,然后,新建 MinioUtil 工具类,并添加一个上传文件的方法,代码如下:
@Component
@Slf4j
public class MinioUtil {
@Autowired
private MinioProperties minioProperties;
@Autowired
private MinioClient minioClient;
/**
* 上传文件
* @param file
* @return
* @throws Exception
*/
public String uploadFile(MultipartFile file) throws Exception {
// 判断文件是否为空
if (file == null || file.getSize() == 0) {
log.error("==> 上传文件异常:文件大小为空 ...");
throw new RuntimeException("文件大小不能为空");
}
// 文件的原始名称
String originalFileName = file.getOriginalFilename();
// 文件的 Content-Type
String contentType = file.getContentType();
// 生成存储对象的名称(将 UUID 字符串中的 - 替换成空字符串)
String key = UUID.randomUUID().toString().replace("-", "");
// 获取文件的后缀,如 .jpg
String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
// 拼接上文件后缀,即为要存储的文件名
String objectName = String.format("%s%s", key, suffix);
log.info("==> 开始上传文件至 Minio, ObjectName: {}", objectName);
// 上传文件至 Minio
minioClient.putObject(PutObjectArgs.builder()
.bucket(minioProperties.getBucketName())
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(contentType)
.build());
// 返回文件的访问链接
String url = String.format("%s/%s/%s", minioProperties.getEndpoint(), minioProperties.getBucketName(), objectName);
log.info("==> 上传文件至 Minio 成功,访问路径: {}", url);
return url;
}
}
上述代码中,我们首先对 MultipartFile 进行了判空,防止上传空的文件。然后拿到了该文件的相关属性值,如原始文件名、Content-Type ,通过对原始文件名截取出文件后缀,以及通过 UUID 生成一个随机的文件名,最终通过 minioClient 客户端的 putObject 方法来上传文件,上传成功后,将文件的访问链接拼接好并返回。
6. 接口出入参格式
前置工作都完成后,我们准备开始开发文件上传接口。
6.1 接口地址
POST /admin/file/upload 表单格式提交
6.2 入参
| 字段名 | 描述 |
|---|---|
file |
选择一个本地文件 |
6.3 出参
{
"success": true,
"message": null,
"errorCode": null,
"data": {
"url": "http://127.0.0.1:9000/weblog/d73ec5ffb1074aacb07c0899663068dd.jpg" // 文件的访问地址
}
}
7. 新增上传文件返参 VO
在 weblog-module-admin 模块中的 /model/vo 包下,新增名为 /file 的包,用于放置文件模块相关的 VO 类,然后,创建 UploadFileRspVO 文件上传返参实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UploadFileRspVO {
/**
* 文件的访问链接
*/
private String url;
}
8. 新增上传文件 service
上传文件的工具类封装好后,我们在 weblog-module-admin 模块中的 service 包中添加 AdminFileService 接口,并在其中定义一个文件上传方法:
public interface AdminFileService {
/**
* 上传文件
* @param file
* @return
*/
Response uploadFile(MultipartFile file);
}
然后,在 /impl 包中,创建该接口的实现类 AdminFileServiceImpl , 代码如下:
@Service
@Slf4j
public class AdminFileServiceImpl implements AdminFileService {
@Autowired
private MinioUtil minioUtil;
/**
* 上传文件
*
* @param file
* @return
*/
@Override
public Response uploadFile(MultipartFile file) {
try {
// 上传文件
String url = minioUtil.uploadFile(file);
// 构建成功返参,将图片的访问链接返回
return Response.success(UploadFileRspVO.builder().url(url).build());
} catch (Exception e) {
log.error("==> 上传文件至 Minio 错误: ", e);
// 手动抛出业务异常,提示 “文件上传失败”
throw new BizException(ResponseCodeEnum.FILE_UPLOAD_FAILED);
}
}
}
代码比较简单,我们将 MinioUtil 注入后,直接调用工具方法中的 uploadFile() 方法,并构建成功返参,将图片的访问链接返回。
8.1 添加文件上传失败全局枚举
上述代码中,当上传文件捕获到异常时,则手动抛出了上传文件失败异常。这里,我们还需要编辑 weblog-module-common 模块中的 ResponseCodeEnum 全局枚举类,添加对应的枚举值,如下:
FILE_UPLOAD_FAILED("20008", "文件上传失败!"),
9. 新增 Controller 接口
接着,在 /controller 包下,创建 AdminFileController 控制器,并新增文件上传接口 /amdin/file/upload, 代码如下:
@RestController
@RequestMapping("/admin")
@Api(tags = "Admin 文件模块")
public class AdminFileController {
@Autowired
private AdminFileService fileService;
@PostMapping("/file/upload")
@ApiOperation(value = "文件上传")
@ApiOperationLog(description = "文件上传")
public Response uploadFile(@RequestParam MultipartFile file) {
return fileService.uploadFile(file);
}
}
注意,文件上传接口并非使用 JSON 格式进行提交,而是使用 Form 表达来提交的,所以入参的注解使用的 @RequestParam , 并使用了 MultipartFile 类接收上传的文件。
10. 测试看看
完成以上工作后,文件上传接口就开发完毕了。接下来,我们通过浏览器访问 localhost:8080/doc.html , 调试一波此接口,看看功能是否正常:
选择一张本地图片后,点击发送,可以看到后端响参成功,并返回了图片的访问地址。复制该图片的访问链接,直接在浏览器中,访问该图片地址,看看能否正常访问:
OK,图片访问没有任何问题,表明文件上传接口功能正常。
11. 大文件上传失败问题
当你尝试上传一个体积较大的文件时,你可能会在控制台中看到如下异常信息:
Caused by: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field file exceeds its maximum permitted size of 1048576 bytes.
at org.apache.tomcat.util.http.fileupload.impl.FileItemStreamImpl$1.raiseError(FileItemStreamImpl.java:117) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.checkLimit(LimitedInputStream.java:76) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.read(LimitedInputStream.java:135) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at java.io.FilterInputStream.read(FilterInputStream.java:107) ~[na:1.8.0_311]
at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:97) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:288) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.connector.Request.parseParts(Request.java:2932) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
... 96 common frames omitted
这个错误表明在尝试上传文件时,文件的大小超过了 Spring Boot 设置的最大允许大小限制。具体而言,错误消息中的 "The field file exceeds its maximum permitted size of 1048576 bytes" 意味着上传的文件大小超过了 1048576 字节(1MB)。为了解决这个问题,可以编辑 application.yml 文件,添加如下配置, 来手动设置上传文件的大小:
spring:
servlet:
multipart:
max-file-size: 10MB # 限制单个上传文件的最大大小为 10MB。如果上传的文件大小超过这个值,将会被拒绝上传。
max-request-size: 10MB # 限制整个上传请求的最大大小为 10MB。这包括所有上传文件的大小之和。如果请求总大小超过这个值,将会被拒绝。
12. 本小节对应源码下载
13. 结语
本小节中,小哈带着大家手动封装了一个 Minio 的文件上传工具类,并新增了一个文件上传接口,最后,通过 Knife4j 调试了该接口,选择了一张本地图片进行上传,接口调试通过。