SpringBoot整合MinIO

122 阅读7分钟

原文:www.r2coding.vip/articles/20…

MinIo安装及启动

1. 通过 docker 安装 MinIo

# 搜索是否有 minio 镜像 docker search minio # 有则拉取镜像 docker pull minio/minio

2. 启动 及配置 MinIo

# 先创建minio 文件存放的位置 mkdir -p /opt/docker/minio/data # 启动并指定端口 docker run \ -p 5000:9000 \ -p 5001:5001 \ --name minio \ -v /opt/docker/minio/data:/data \ -e "MINIO_ROOT_USER=Jonny" \ -e "MINIO_ROOT_PASSWORD=minioadmin" \ -d minio/minio server /data --console-address ":5001" # 设置为和 docker 绑定启动,docker 启动则 minio 就启动 docker update --restart=always

这里解释一下 docker 里面的几个参数

-p: 指定端口映射,格式为:主机(宿主)端口:容器端口

-e "MINIO_ROOT_USER=Jonny": 设置环境变量;

-d: 后台运行容器,并返回容器ID;

-v: 绑定一个卷



3.其他OPTIONS解释

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

OPTIONS参数说明:
-a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
-d: 后台运行容器,并返回容器ID;
-i: 以交互模式运行容器,通常与 -t 同时使用;
-P: 随机端口映射,容器内部端口随机映射到主机的端口
-p: 指定端口映射,格式为:主机(宿主)端口:容器端口
-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
name="nginx-lb": 为容器指定一个名称;
dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
-h "mars": 指定容器的hostname;
-e username="ritchie": 设置环境变量;
env-file=[]: 从指定文件读入环境变量;
cpuset="0-2" or cpuset="0,1,2": 绑定容器到指定CPU运行;
-m :设置容器使用内存最大值;
net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
link=[]: 添加链接到另一个容器;
expose=[]: 开放一个端口或一组端口;
volume , -v: 绑定一个卷
例如,使用docker镜像nginx:latest以后台模式启动一个容器,并将容器命名为mynginx:
docker run name mynginx -d nginx:latest
使用镜像nginx:latest以后台模式启动一个容器,并将容器的80端口映射到主机随机端口:
docker run -P -d nginx:latest
使用镜像 nginx:latest,以后台模式启动一个容器,将容器的 80 端口映射到主机的 80 端口,主机的目录 /data 映射到容器的 /data:
docker run -p 80:80 -v /data:/data -d nginx:latest
绑定容器的 8080 端口,并将其映射到本地主机 127.0.0.1 的 80 端口上:
docker run -p 127.0.0.1:80:8080/tcp ubuntu bash
使用镜像nginx:latest以交互模式启动一个容器,在容器内执行/bin/bash命令:
docker run -it nginx:latest /bin/bash

4. 登录minio控制台页面

image.png

image.png

image.png

image.png

SpringBoot 整合 MinIO

1.导入依赖

<!-- https://mvnrepository.com/artifact/io.minio/minio --> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.4.0</version> </dependency>

2.application.yml 配置信息

minio: endpoint: http://127.0.0.1:9000 #Minio服务所在地址 bucketName: tulaoda #存储桶名称 accessKey: Jonny #访问的key secretKey: minioadmin #访问的秘钥

3.MinioConfig.class配置类

@Data @Configuration @ConfigurationProperties(prefix = "minio") public class MinioConfig { private String endpoint; private String accessKey; private String secretKey; private String bucketName; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } }

4.minio工具类

@Component @Slf4j public class MinioUtil { @Autowired private MinioConfig prop; @Resource private MinioClient minioClient; @Autowired private CodeService codeService; /** * 查看存储bucket是否存在 * @return boolean */ public Boolean bucketExists(String bucketName) { Boolean found; try { found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); } catch (Exception e) { e.printStackTrace(); return false; } return found; } /** * 创建存储bucket * @return Boolean */ public Boolean makeBucket(String bucketName) { try { minioClient.makeBucket(MakeBucketArgs.builder() .bucket(bucketName) .build()); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 删除存储bucket * @return Boolean */ public Boolean removeBucket(String bucketName) { try { minioClient.removeBucket(RemoveBucketArgs.builder() .bucket(bucketName) .build()); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 获取全部bucket */ public List<Bucket> getAllBuckets() { try { List<Bucket> buckets = minioClient.listBuckets(); return buckets; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 文件上传 * * @param file 文件 * @return Boolean */ public String upload(MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (StringUtils.isBlank(originalFilename)){ throw new RuntimeException(); } String fileName = UuidUtils.generateUuid() + originalFilename.substring(originalFilename.lastIndexOf(".")); String objectName = CommUtils.getNowDateLongStr("yyyy-MM/dd") + "/" + fileName; try { PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName) .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build(); //文件名称相同会覆盖 minioClient.putObject(objectArgs); } catch (Exception e) { e.printStackTrace(); return null; } return objectName; } /** * 预览图片 * @param fileName * @return */ public String preview(String fileName){ // 查看文件地址 GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(prop.getBucketName()).object(fileName).method(Method.GET).build(); try { String url = minioClient.getPresignedObjectUrl(build); return url; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 文件下载 * @param fileName 文件名称 * @param res response * @return Boolean */ public void download(String fileName, HttpServletResponse res) { GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName()) .object(fileName).build(); try (GetObjectResponse response = minioClient.getObject(objectArgs)){ byte[] buf = new byte[1024]; int len; try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){ while ((len=response.read(buf))!=-1){ os.write(buf,0,len); } os.flush(); byte[] bytes = os.toByteArray(); res.setCharacterEncoding("utf-8"); // 设置强制下载不打开 // res.setContentType("application/force-download"); res.addHeader("Content-Disposition", "attachment;fileName=" + fileName); try (ServletOutputStream stream = res.getOutputStream()){ stream.write(bytes); stream.flush(); } } } catch (Exception e) { e.printStackTrace(); } } /** * 查看文件对象 * @return 存储bucket内文件对象信息 */ public List<Item> listObjects() { Iterable<Result<Item>> results = minioClient.listObjects( ListObjectsArgs.builder().bucket(prop.getBucketName()).build()); List<Item> items = new ArrayList<>(); try { for (Result<Item> result : results) { items.add(result.get()); } } catch (Exception e) { e.printStackTrace(); return null; } return items; } /** * 删除 * @param fileName * @return * @throws Exception */ public boolean remove(String fileName){ try { minioClient.removeObject( RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build()); }catch (Exception e){ return false; } return true; } }

5.文件处理接口

@Api(tags = "文件相关接口") @Slf4j @RestController @RequestMapping(value = "product/file") public class FileController { @Autowired private MinioUtil minioUtil; @Autowired private MinioConfig prop; @ApiOperation(value = "查看存储bucket是否存在") @GetMapping("/bucketExists") public R bucketExists(@RequestParam("bucketName") String bucketName) { return R.ok().put("bucketName",minioUtil.bucketExists(bucketName)); } @ApiOperation(value = "创建存储bucket") @GetMapping("/makeBucket") public R makeBucket(String bucketName) { return R.ok().put("bucketName",minioUtil.makeBucket(bucketName)); } @ApiOperation(value = "删除存储bucket") @GetMapping("/removeBucket") public R removeBucket(String bucketName) { return R.ok().put("bucketName",minioUtil.removeBucket(bucketName)); } @ApiOperation(value = "获取全部bucket") @GetMapping("/getAllBuckets") public R getAllBuckets() { List<Bucket> allBuckets = minioUtil.getAllBuckets(); return R.ok().put("allBuckets",allBuckets); } @ApiOperation(value = "文件上传返回url") @PostMapping("/upload") public R upload(@RequestParam("file") MultipartFile file) { String objectName = minioUtil.upload(file); if (null != objectName) { return R.ok().put("url",(prop.getEndpoint() + "/" + prop.getBucketName() + "/" + objectName)); } return R.error(); } @ApiOperation(value = "图片/视频预览") @GetMapping("/preview") public R preview(@RequestParam("fileName") String fileName) { return R.ok().put("filleName",minioUtil.preview(fileName)); } @ApiOperation(value = "文件下载") @GetMapping("/download") public R download(@RequestParam("fileName") String fileName, HttpServletResponse res) { minioUtil.download(fileName,res); return R.ok(); } @ApiOperation(value = "删除文件", notes = "根据url地址删除文件") @PostMapping("/delete") public R remove(String url) { String objName = url.substring(url.lastIndexOf(prop.getBucketName()+"/") + prop.getBucketName().length()+1); minioUtil.remove(objName); return R.ok().put("objName",objName); } }

验证结果

上传

注意这里的 file 要选择一下类型用 File 而不是用 text

image-20220513000159452

删除

image-20220513000245741

预览

image-20220513001539704


这一块的接口慢慢试,主要用的几个接口在上面


报错解决

image-20220512220445285

错误原因:
minio版本太新需要更换成8.2.2的版本

<!-- https://mvnrepository.com/artifact/io.minio/minio --> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.2.2</version> </dependency>
<span>error</span> <span>occurred</span>
<span>ErrorResponse(code</span> <span>=</span> <span>RequestTimeTooSkewed,</span> <span>message</span> <span>=</span> <span>The</span> <span>difference</span> <span>between</span> <span>the</span> <span>request</span> <span>time</span> <span>and</span> <span>the</span> <span>server's</span> <span>time</span> <span>is</span> <span>too</span> <span>large.,</span> <span>bucketName</span> <span>=</span> <span>null</span><span>,</span> <span>objectName</span> <span>=</span> <span>null</span><span>,</span> <span>resource</span> <span>=</span> <span>/gulimall-product,</span> <span>requestId</span> <span>=</span> <span>null</span><span>,</span> <span>hostId</span> <span>=</span> <span>bffd91de-450a-47ac-b4b2-3d3b45ddd054)</span>
<span>request={method=GET,</span> <span>url=http://192.168.1.17:5000/gulimall-product?location=,</span> <span>headers=Host:</span> <span>192.168</span><span>.1</span><span>.17</span><span>:5000</span>
<span>Accept-Encoding:</span> <span>identity</span>
<span>User-Agent:</span> <span>MinIO</span> <span>(Mac</span> <span>OS</span> <span>X;</span> <span>x86_64)</span> <span>minio-java/8.2.2</span>
<span>Content-MD5:</span> <span>1B2M2Y8AsgTpgAmY7PhCfg==</span>
<span>x-amz-content-sha256:</span> <span>e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855</span>
<span>x-amz-date:</span> <span>20220512T140028Z</span>
<span>Authorization:</span> <span>AWS4-HMAC-SHA256</span> <span>Credential=*REDACTED*/20220512/us-east-1/s3/aws4_request,</span> <span>SignedHeaders=content-md5;host;x-amz-content-sha256;x-amz-date,</span> <span>Signature=*REDACTED*</span>
<span>}</span>
<span>response={code=403,</span> <span>headers=Accept-Ranges:</span> <span>bytes</span>
<span>Content-Length:</span> <span>299</span>
<span>Content-Type:</span> <span>application/xml</span>
<span>Server:</span> <span>MinIO</span>
<span>Vary:</span> <span>Origin</span>
<span>Date:</span> <span>Thu,</span> <span>12</span> <span>May</span> <span>2022 13:32:53 </span><span>GMT</span>
<span>}</span>
<span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span>

主要是因为 : 系统时区与硬件时区不一致导致的

解决步骤如下

  1. 安装ntp ntpdate
    yum -y install ntp ntpdate
  2. 与时间服务器同步时间
    ntpdate 0.asia.pool.ntp.org
  3. 将系统时间写入硬件时间
    hwclock --systohc

image-20220512222616705

报错是因为没有给buckts配置权限策略导致。

解决步骤如下:

方法一 :客户端操作

image-20220512223454181

方法二: 在创建 Bucket 的时候直接设置权限策略[后端修改]

将 MinIOUtil 工具类的创建 Bucket 的方法优化一下

/** * 创建存储bucket * @return Boolean */ public Boolean makeBucket(String bucketName) { try { if (!bucketExists(bucketName)) { minioClient.makeBucket(MakeBucketArgs.builder() .bucket(bucketName) .build()); String policyJson = "{\n" + "\t\"Version\": \""+new SimpleDateFormat("yyyy-mm-dd").format(System.currentTimeMillis())+"\",\n" + "\t\"Statement\": [{\n" + "\t\t\"Effect\": \"Allow\",\n" + "\t\t\"Principal\": {\n" + "\t\t\t\"AWS\": [\"*\"]\n" + "\t\t},\n" + "\t\t\"Action\": [\"s3:GetBucketLocation\", \"s3:ListBucket\", \"s3:ListBucketMultipartUploads\"],\n" + "\t\t\"Resource\": [\"arn:aws:s3:::" + bucketName + "\"]\n" + "\t}, {\n" + "\t\t\"Effect\": \"Allow\",\n" + "\t\t\"Principal\": {\n" + "\t\t\t\"AWS\": [\"*\"]\n" + "\t\t},\n" + "\t\t\"Action\": [\"s3:AbortMultipartUpload\", \"s3:DeleteObject\", \"s3:GetObject\", \"s3:ListMultipartUploadParts\", \"s3:PutObject\"],\n" + "\t\t\"Resource\": [\"arn:aws:s3:::" + bucketName + "/*\"]\n" + "\t}]\n" + "}\n"; minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyJson).build()); log.info("buckets:【{}】,创建[readwrite]策略成功!", bucketName); } else { log.info("minio bucket->>>【{}】already exists", bucketName); } } catch (Exception e) { e.printStackTrace(); return false; } return true; }

image-20220512233535741

这一块报错是因为文件名写错了🤣,注意在将文件上传到服务器的过程中修改了文件名:

文件名的格式为:yyyy-mm/dd/+uuid+文件后缀