Access key length should be at least 3, and secret key length at least 8 characters
为了更接近实际应用,本文专门解析了一个域名到 Minio 服务器:oss.springboot.io
。本文接下来就会使用这个域名来进行资源访问、后台管理和 API 调用!
你如果没有域名,直接使用 ip 也是没任何问题的。
登录控制台
安装就绪后,使用浏览器访问登录页:http://oss.springboot.io:9090/login
。然后使用安装时设置的用户名和密码进行登录。
创建 Bucket
进入管理页面后,点击左侧 “Buckets” 按钮,进入 Bucket 管理面板。
创建一个名为 “images” 的 Bucket。
Bucket 就是存储对象资源的基本单位,你可以简单理解为系统中的 “文件夹”。
为匿名用户设置只读权限
接着,点击刚创建的 Bucket,进入 “Anonymous” 配置。为匿名用户添加 “readonly” 权限。
Prefix 是资源的前缀匹配。
默认情况下,Bucket 是私有的,匿名用户无法访问其中的资源。
创建 Access Key
点击左侧 “Access Keys” 菜单,生成一个新的 Access Key。
生成后,点击 “Create” 保存 Access Key。
千万要注意保存这俩 Key,因为这是你最后一次可以看到 Secret Key 的值了。
创建 Spring Boot 项目
添加 Minio 官方的 SDK 依赖 minio
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version>
</dependency>
配置上传信息
在 application.yaml
中配置 Access Key 等信息:
app:
minio:
# 访问资源的 URL
base-url: "http://oss.springboot.io:9000/"
# API 端点
endpoint: "http://oss.springboot.io:9000/"
# 上传的 Bucket
bucket: images
# Access Key
access-key: Umt2UtK5vp7njhM4BFjP
# Secret Key
secret-key: S3ZJayIxxv3AZfkyCitmrksugzrABbYGJQ4v8OGB
如上,在配置文件中指定了访问文件的URL、API 端点地址、要上传到哪个 Bucket 以及在 Minio 控制台生成的 Access Key 和 Secret Key。
UploadController
创建 UploadController
,实现 /upload
API。
接收客户端上传的资源文件,进行基本的校验后通过 Minio SDK 上传到 Minio 服务器,最后返回访问地址。
package cn.springdoc.demo.web.controller;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
@RestController
@RequestMapping("/upload")
public class UploadController {
static final Logger log = LoggerFactory.getLogger(UploadController.class);
// 日期格式化
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("/yyy/MM/dd/");
// 资源的 访问 URL
@Value("${app.minio.base-url}")
private String baseUrl;
// API 端点
@Value("${app.minio.endpoint}")
private String endpoint;
// Bucket 存储桶
@Value("${app.minio.bucket}")
private String bucket;
// Acess Key
@Value("${app.minio.access-key}")
private String accessKey;
// Secret Key
@Value("${app.minio.secret-key}")
private String secretKey;
* 上传文件到 Minio 服务器,返回访问地址
* @param file
* @return
* @throws Exception
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) throws Exception{
// 文件大小
long size = file.getSize();
if (size == 0) {
return ResponseEntity.badRequest().body("禁止上传空文件");
// 文件名称
String fileName = file.getOriginalFilename();
// 文件后缀
String ext = "";
int index = fileName.lastIndexOf(".");
if (index ==-1) {
return ResponseEntity.badRequest().body("禁止上传无后缀的文件");
ext = fileName.substring(index);
// 文件类型
String contentType = file.getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
// 根据日期打散目录,使用 UUID 重命名文件
String filePath = formatter.format(LocalDate.now()) +
UUID.randomUUID().toString().replace("-", "") +
log.info("文件名称:{}", fileName);
log.info("文件大小:{}", size);
log.info("文件类型:{}", contentType);
log.info("文件路径:{}", filePath);
// 实例化客户端
MinioClient client = MinioClient.builder()
.endpoint(this.endpoint)
.credentials(this.accessKey, this.secretKey)
.build();
// 上传文件到客户端
try (InputStream inputStream = file.getInputStream()){
client.putObject(PutObjectArgs.builder()
.bucket(this.bucket) // 指定 Bucket
.contentType(contentType) // 指定 Content Type
.object(filePath) // 指定文件的路径
.stream(inputStream, size, -1) // 文件的 Inputstream 流
.build());
// 返回最终的访问路径
return ResponseEntity.ok(this.baseUrl + this.bucket + filePath);
通过 @Value
注解,把配置文件中的属性值注入到 Controller
成员变量中。
在上传方法中,首先校验了上传的文件。不允许上大小为 0 和无后缀的文件。然后根据日期 /yyy/MM/dd/
格式打散目录,并且使用 UUID
重命名文件防止同名文件覆盖。
然后使用端点 API 地址、accessKey 和 secretKey 参数构建 MinioClient
实例。
调用 MinioClient
的 putObject
方法进行上传,通过 PutObjectArgs
Builder 构建上传参数,其中指定了要上传的 Bucket、文件的媒体类型、文件的保存路径以及文件的 InputStream
。
如果没有发生异常,则上传成功。最后,拼接完整的访问路径,返回给客户端(文件的访问路径包含了 Bucket 名称)。
启动服务器,使用 Postman 上传图片文件(图片文件是 “Spring 中文网” 的 Logo - 512 x 512):
上传成功,返回资源的访问地址如下:
http://oss.springboot.io:9000/images/2023/11/13/090c2b7daa20457c8cfdfbb1cc32009b.png
接着,尝试在浏览器中访问上传的资源。
一切 OK。
最后附上客户端的请求日志:
POST /upload HTTP/1.1
Accept-Language: en_US
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 7f213edc-7506-4652-bdc5-f783823fc978
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: multipart/form-data; boundary=--------------------------815963622984394675083411
Content-Length: 20029
----------------------------815963622984394675083411
Content-Disposition: form-data; name="file"; filename="512.png"
<512.png>
----------------------------815963622984394675083411--
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
Content-Length: 84
Date: Mon, 13 Nov 2023 10:30:09 GMT
Keep-Alive: timeout=60
Connection: keep-alive
http://oss.springboot.io:9000/images/2023/11/13/090c2b7daa20457c8cfdfbb1cc32009b.png
以及服务端的日志: