Minio文件存储服务使用入门

概念

优秀的轻量的对象存储服务

Minio是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

使用

配置

yml配置

配置:

min:
  io:
    endpoint: http://192.168.1.13:9009
    accessKey: minio
    secretKey: minio@123
    bucket: excelreport

网址:

在这里插入图片描述

pom文件
<!--调用minio服务器-->
<dependency>
    <groupId>me.tongfei</groupId>
    <artifactId>progressbar</artifactId>
    <version>0.5.3</version>
</dependency>

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.8.1</version>
</dependency>

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.1</version>
    <exclusions>
        <exclusion>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!--避免idea后端配置类报红-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
获取配置
@Data
@Component
@ConfigurationProperties(prefix = "min.io")
public class MinIoProperties {

    /**
     * Minio 服务端ip
     */
    private String endpoint;

    private String accessKey;

    private String secretKey;

    private String bucket;

    @Bean
    public MinioClient getMinioClient() {
        return MinioClient.builder()
                .endpoint(endpoint).credentials(accessKey, secretKey).build();
    }
}

工具类

MinIoUtils

存储、取出、删除等操作工具类

@Configuration
@EnableConfigurationProperties({MinIoProperties.class})
public class MinIoUtils {

    @Resource
    private MinioClient instance;

    private static final String SEPARATOR_DOT = ".";

    private static final String SEPARATOR_ACROSS = "-";

    private static final String SEPARATOR_STR = "";

    // 存储桶名称
    private static final String chunkBucKet = "miniobucket";

    /**
     * 不排序
     */
    public final static boolean NOT_SORT = false;

    /**
     * 排序
     */
    public final static boolean SORT = true;

    /**
     * 默认过期时间(分钟)
     */
    private final static Integer DEFAULT_EXPIRY = 60;

    /**
     * 删除分片
     */
    public final static boolean DELETE_CHUNK_OBJECT = true;
    /**
     * 不删除分片
     */
    public final static boolean NOT_DELETE_CHUNK_OBJECT = false;

    /**
     * 判断桶是否存在
     * @param bucketName 桶名
     * @return boolean
     * @author exe.wangtaotao
     * @date 2020/10/21 16:33
     */
    public boolean bucketExists(String bucketName) {
        try {
            return instance.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 创建存储桶
     * 创建 bucket
     *
     * @param bucketName 桶名
     */
    public void makeBucket(String bucketName) {
        try {
            boolean isExist = bucketExists(bucketName);
            if (!isExist) {
                instance.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @return java.util.List<java.lang.String>
     * @Description 获取文件存储服务的所有存储桶名称
     * @author exe.wangtaotao
     * @date 2020/10/21 16:35
     */
    public List<String> listBucketNames() {
        List<Bucket> bucketList = listBuckets();
        List<String> bucketListName = new ArrayList<>();
        for (Bucket bucket : bucketList) {
            bucketListName.add(bucket.name());
        }
        return bucketListName;
    }

    /**
     * @return java.util.List<io.minio.messages.Bucket>
     * @Description 列出所有存储桶
     */
    @SneakyThrows
    private List<Bucket> listBuckets() {
        return instance.listBuckets();
    }


    /**
     * 获取对象文件名称列表
     *
     * @param bucketName 存储桶名称
     * @param prefix     对象名称前缀(文件夹 /xx/xx/xxx.jpg 中的 /xx/xx/)
     * @return objectNames
     */
    public List<String> listObjectNames(String bucketName, String prefix) {
        return listObjectNames(bucketName, prefix, NOT_SORT);
    }


    /**
     * 获取对象文件名称列表
     *
     * @param bucketName 存储桶名称
     * @param prefix     对象名称前缀(文件夹 /xx/xx/xxx.jpg 中的 /xx/xx/)
     * @param sort       是否排序(升序)
     * @return objectNames
     */
    @SneakyThrows
    public List<String> listObjectNames(String bucketName, String prefix, Boolean sort) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            ListObjectsArgs listObjectsArgs;
            if (null == prefix) {
                listObjectsArgs = ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .recursive(true)
                        .build();
            } else {
                listObjectsArgs = ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(prefix)
                        .recursive(true)
                        .build();
            }
            Iterable<Result<Item>> chunks = instance.listObjects(listObjectsArgs);
            List<String> chunkPaths = new ArrayList<>();
            for (Result<Item> item : chunks) {
                chunkPaths.add(item.get().objectName());
            }
            if (sort) {
                chunkPaths.sort(new Str2IntComparator(false));
            }
            return chunkPaths;
        }
        return new ArrayList<>();
    }

    /**
     * 在桶下创建文件夹,文件夹层级结构根据参数决定
     *
     * @param bucket 桶名称
     * @param WotDir 格式为 xxx/xxx/xxx/
     */
    @SneakyThrows
    public String createDirectory(String bucket, String WotDir) {
        if (!this.bucketExists(bucket)) {
            return null;
        }
        instance.putObject(PutObjectArgs.builder().bucket(bucket).object(WotDir).stream(
                        new ByteArrayInputStream(new byte[]{}), 0, -1)
                .build());
        return WotDir;
    }


    /**
     * 删除一个文件
     *
     * @param bucketName 桶名称
     * @param objectName /xx/xx/xxx.jpg
     */
    @SneakyThrows
    public boolean removeObject(String bucketName, String objectName) {

        if (!bucketExists(bucketName)) {
            return false;
        }
        instance.removeObject(
                RemoveObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
        return true;
    }

    /**
     * @param bucketName 桶名称
     * @param objectNames /xx/xx/xxx.jpg
     * @return java.util.List<java.lang.String>
     * @Description 删除指定桶的多个文件对象, 返回删除错误的对象列表,全部删除成功,返回空列表
     * @author exe.wangtaotao
     * @date 2020/10/21 16:43
     */
    @SneakyThrows
    public List<String> removeObjects(String bucketName, List<String> objectNames) {
        if (!bucketExists(bucketName)) {
            return new ArrayList<>();
        }
        List<DeleteObject> deleteObjects = new ArrayList<>(objectNames.size());
        for (String objectName : objectNames) {
            deleteObjects.add(new DeleteObject(objectName));
        }
        List<String> deleteErrorNames = new ArrayList<>();
        Iterable<Result<DeleteError>> results = instance.removeObjects(
                RemoveObjectsArgs.builder()
                        .bucket(bucketName)
                        .objects(deleteObjects)
                        .build());
        for (Result<DeleteError> result : results) {
            DeleteError error = result.get();
            deleteErrorNames.add(error.objectName());
        }
        return deleteErrorNames;
    }


    /**
     * 获取访问对象的外链地址
     * 获取文件的下载url
     *
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param expiry     过期时间(分钟) 最大为7天 超过7天则默认最大值
     * @return viewUrl
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName, Integer expiry) {
        expiry = expiryHandle(expiry);
        return instance.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(expiry)
                        .build()
        );
    }


    /**
     * 创建上传文件对象的外链
     *
     * @param bucketName 存储桶名称
     * @param objectName 欲上传文件对象的名称
     * @return uploadUrl
     */
    public String createUploadUrl(String bucketName, String objectName) {
        return createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY);
    }

    /**
     * 创建上传文件对象的外链
     *
     * @param bucketName 存储桶名称
     * @param objectName 欲上传文件对象的名称
     * @param expiry     过期时间(分钟) 最大为7天 超过7天则默认最大值
     * @return uploadUrl
     */
    @SneakyThrows
    public String createUploadUrl(String bucketName, String objectName, Integer expiry) {
        expiry = expiryHandle(expiry);
        return instance.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(expiry)
                        .build()
        );
    }


//    /**
//     * 批量下载
//     *
//     * @param directory
//     * @return
//     */
//    @SneakyThrows
//    public List<String> downLoadMore(String bucket, String directory) {
//        Iterable<io.minio.Result<Item>> objs = instance.listObjects(ListObjectsArgs.builder().bucket(bucket).prefix(directory).useUrlEncodingType(false).build());
//        List<String> list = new ArrayList<>();
//        for (io.minio.Result<Item> result : objs) {
//            String objectName = null;
//            objectName = result.get().objectName();
//            ObjectStat statObject = instance.statObject(StatObjectArgs.builder().bucket(bucket).object(objectName).build());
//            if (statObject != null && statObject.length() > 0) {
//                String fileurl = instance.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(statObject.name()).method(Method.GET).build());
//                list.add(fileurl);
//            }
//        }
//        return list;
//    }
//

    /**
     * @param multipartFile 文件
     * @param bucketName 桶名
     * @param directory     image/
     * @return java.lang.String
     * @Description 文件上传
     * @author exe.wangtaotao
     * @date 2020/10/21 13:45
     */
    public MinIoUploadResDTO upload(MultipartFile multipartFile, String bucketName, String directory) throws Exception {
        if (!this.bucketExists(bucketName)) {
            this.makeBucket(bucketName);
        }
        InputStream inputStream = multipartFile.getInputStream();
        directory = Optional.ofNullable(directory).orElse("");
        String minFileName = directory + minFileName(multipartFile.getOriginalFilename());
        //上传文件到指定目录
        instance.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(minFileName)
                .contentType(multipartFile.getContentType())
                .stream(inputStream, inputStream.available(), -1)
                .build());
        inputStream.close();
        // 返回生成文件名、访问路径
        return new MinIoUploadResDTO(minFileName, getObjectUrl(bucketName, minFileName, DEFAULT_EXPIRY));
    }

    /**
     * @param response
     * @return java.lang.String
     * @Description 下载文件
     * @author exe.wangtaotao
     * @date 2020/10/21 15:18
     */
    public void download(HttpServletResponse response, String bucketName, String minFileName) throws Exception {
        InputStream fileInputStream = instance.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(minFileName).build());
        response.setHeader("Content-Disposition", "attachment;filename=" + minFileName);
        response.setContentType("application/force-download");
        response.setCharacterEncoding("UTF-8");
        IOUtils.copy(fileInputStream, response.getOutputStream());
    }


    /**
     * 批量创建分片上传外链
     *
     * @param bucketName 存储桶名称
     * @param objectMD5  欲上传分片文件主文件的MD5
     * @param chunkCount 分片数量
     * @return uploadChunkUrls
     */
    public List<String> createUploadChunkUrlList(String bucketName, String objectMD5, Integer chunkCount) {
        if (null == bucketName) {
            bucketName = chunkBucKet;
        }
        if (null == objectMD5) {
            return null;
        }
        objectMD5 += "/";
        if (null == chunkCount || 0 == chunkCount) {
            return null;
        }
        List<String> urlList = new ArrayList<>(chunkCount);
        for (int i = 1; i <= chunkCount; i++) {
            String objectName = objectMD5 + i + ".chunk";
            urlList.add(createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY));
        }
        return urlList;
    }

    /**
     * 创建指定序号的分片文件上传外链
     *
     * @param bucketName 存储桶名称
     * @param objectMD5  欲上传分片文件主文件的MD5
     * @param partNumber 分片序号
     * @return uploadChunkUrl
     */
    public String createUploadChunkUrl(String bucketName, String objectMD5, Integer partNumber) {
        if (null == bucketName) {
            bucketName = chunkBucKet;
        }
        if (null == objectMD5) {
            return null;
        }
        objectMD5 += "/" + partNumber + ".chunk";
        return createUploadUrl(bucketName, objectMD5, DEFAULT_EXPIRY);
    }


    /**
     * 获取分片文件名称列表
     *
     * @param bucketName 存储桶名称
     * @param ObjectMd5  对象Md5
     * @return objectChunkNames
     */
    public List<String> listChunkObjectNames(String bucketName, String ObjectMd5) {
        if (null == bucketName) {
            bucketName = chunkBucKet;
        }
        if (null == ObjectMd5) {
            return null;
        }
        return listObjectNames(bucketName, ObjectMd5, SORT);
    }

    /**
     * 获取分片名称地址HashMap key=分片序号 value=分片文件地址
     *
     * @param bucketName 存储桶名称
     * @param ObjectMd5  对象Md5
     * @return objectChunkNameMap
     */
    public Map<Integer, String> mapChunkObjectNames(String bucketName, String ObjectMd5) {
        if (null == bucketName) {
            bucketName = chunkBucKet;
        }
        if (null == ObjectMd5) {
            return null;
        }
        List<String> chunkPaths = listObjectNames(bucketName, ObjectMd5);
        if (null == chunkPaths || chunkPaths.size() == 0) {
            return null;
        }
        Map<Integer, String> chunkMap = new HashMap<>(chunkPaths.size());
        for (String chunkName : chunkPaths) {
            Integer partNumber = Integer.parseInt(chunkName.substring(chunkName.indexOf("/") + 1, chunkName.lastIndexOf(".")));
            chunkMap.put(partNumber, chunkName);
        }
        return chunkMap;
    }


    /**
     * 合并分片文件成对象文件
     *
     * @param chunkBucKetName   分片文件所在存储桶名称
     * @param composeBucketName 合并后的对象文件存储的存储桶名称
     * @param chunkNames        分片文件名称集合
     * @param objectName        合并后的对象文件名称
     * @return true/false
     */
    @SneakyThrows
    public boolean composeObject(String chunkBucKetName, String composeBucketName, List<String> chunkNames, String objectName, boolean isDeleteChunkObject) {
        if (null == chunkBucKetName) {
            chunkBucKetName = chunkBucKet;
        }
        List<ComposeSource> sourceObjectList = new ArrayList<>(chunkNames.size());
        for (String chunk : chunkNames) {
            sourceObjectList.add(
                    ComposeSource.builder()
                            .bucket(chunkBucKetName)
                            .object(chunk)
                            .build()
            );
        }
        instance.composeObject(
                ComposeObjectArgs.builder()
                        .bucket(composeBucketName)
                        .object(objectName)
                        .sources(sourceObjectList)
                        .build()
        );
        if (isDeleteChunkObject) {
            removeObjects(chunkBucKetName, chunkNames);
        }
        return true;
    }

    /**
     * 合并分片文件成对象文件
     *
     * @param bucketName 存储桶名称
     * @param chunkNames 分片文件名称集合
     * @param objectName 合并后的对象文件名称
     * @return true/false
     */
    public boolean composeObject(String bucketName, List<String> chunkNames, String objectName) {
        return composeObject(chunkBucKet, bucketName, chunkNames, objectName, NOT_DELETE_CHUNK_OBJECT);
    }

    /**
     * 合并分片文件成对象文件
     *
     * @param bucketName 存储桶名称
     * @param chunkNames 分片文件名称集合
     * @param objectName 合并后的对象文件名称
     * @return true/false
     */
    public boolean composeObject(String bucketName, List<String> chunkNames, String objectName, boolean isDeleteChunkObject) {
        return composeObject(chunkBucKet, bucketName, chunkNames, objectName, isDeleteChunkObject);
    }

    /**
     * 合并分片文件,合并成功后删除分片文件
     *
     * @param bucketName 存储桶名称
     * @param chunkNames 分片文件名称集合
     * @param objectName 合并后的对象文件名称
     * @return true/false
     */
    public boolean composeObjectAndRemoveChunk(String bucketName, List<String> chunkNames, String objectName) {
        return composeObject(chunkBucKet, bucketName, chunkNames, objectName, DELETE_CHUNK_OBJECT);
    }


    /**
     * @param originalFileName 原始名称
     * @return java.lang.String
     * @Description 生成上传文件名
     * @author exe.wangtaotao
     * @date 2020/10/21 15:07
     */
    private String minFileName(String originalFileName) {
        String suffix = originalFileName;
        if (originalFileName.contains(SEPARATOR_DOT)) {
            suffix = originalFileName.substring(originalFileName.lastIndexOf(SEPARATOR_DOT));
        }
        return UUID.randomUUID().toString().replace(SEPARATOR_ACROSS, SEPARATOR_STR).toUpperCase() + suffix;
    }


    /**
     * 将分钟数转换为秒数
     *
     * @param expiry 过期时间(分钟数)
     * @return expiry
     */
    private static int expiryHandle(Integer expiry) {
        expiry = expiry * 60;
        if (expiry > 604800) {
            return 604800;
        }
        return expiry;
    }

    static class Str2IntComparator implements Comparator<String> {
        private final boolean reverseOrder; // 是否倒序

        public Str2IntComparator(boolean reverseOrder) {
            this.reverseOrder = reverseOrder;
        }

        @Override
        public int compare(String arg0, String arg1) {
            Integer intArg0 = Integer.parseInt(arg0.substring(arg0.indexOf("/") + 1, arg0.lastIndexOf(".")));
            Integer intArg1 = Integer.parseInt(arg1.substring(arg1.indexOf("/") + 1, arg1.lastIndexOf(".")));
            if (reverseOrder) {
                return intArg1 - intArg0;
            } else {
                return intArg0 - intArg1;
            }
        }
    }

    /***
     * 根据url地址获取对象名称
     * @author hongawen
     * @date 2022/10/17 20:05
     * @param objectUrl 对象地址
     * @return String 对象名称
     */
    public static String getObjectNameByUrl(String objectUrl) {
        if(objectUrl.indexOf("?") < 0){
            return "unknownFile";
        }
        String objectName = objectUrl.substring(0, objectUrl.indexOf("?"));
        return objectName.substring(objectName.lastIndexOf("/") + 1);
    }
}
Result

结果封装工具类

package com.njcn.harmonic.util.minio;

import java.io.Serializable;

/**
 * @author qijian
 * @version 1.0.0
 * @date 2022年10月16日 18:41
 */
public class Result<T> implements Serializable {

    private static final long serialVersionUID = 6273326371984994386L;
    private Integer code;
    private String msg;
    private T data;

    private Result() {
        this.code = 200;
        this.msg = "OK";
    }

    private Result(T data) {
        this.code = 200;
        this.msg = "OK";
        this.setData(data);
    }

    private Result(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private Result(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public Result<T> setError(Integer code, String msg) {
        this.setCode(code);
        this.setMsg(msg);
        return this;
    }

    public boolean isSuccess() {
        return this.getCode().equals(200);
    }

    public static Result ok() {
        return new Result();
    }

    public static <T> Result ok(T data) {
        return new Result(data);
    }

    public static <T> Result ok(Integer code, String msg) {
        return new Result(code, msg);
    }

    public static <T> Result ok(Integer code, String msg, T data) {
        return new Result(code, msg, data);
    }

    public static <T> Result error() {
        return new Result(500, "failed");
    }

    public static <T> Result error(String msg) {
        return new Result(500, msg);
    }

    public static <T> Result error(Integer code, String msg) {
        return new Result(code, msg);
    }

    public static <T> Result error(Integer code, String msg, T data) {
        return new Result(code, msg, data);
    }

    public Integer getCode() {
        return this.code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return this.msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return this.data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
MinIoUploadResDTO

取File数据

@Data
public class MinIoUploadResDTO implements Serializable {

    private static final long serialVersionUID = 475040120689218785L;
    private String minFileName;
    private String minFileUrl;

    public MinIoUploadResDTO(String minFileName, String minFileUrl) {
        this.minFileName = minFileName;
        this.minFileUrl = minFileUrl;
    }

}

测试

public class MinioTest extends BaseJunitTest {

    @Resource
    private MinIoUtils minIoUtils;

    @Resource
    private MinIoProperties minIoProperties;

    /***
     * 上传
     */
    @Test
    public void upload() throws Exception {
        String strUrl = "C:\\Users\\DELL\\Desktop\\功能测试文件\\123.txt";
        File file = new File(strUrl);
        MultipartFile cMultiFile = getMultipartFile(file);
        System.out.println(minIoUtils.upload(cMultiFile, minIoProperties.getBucket(), "day/"));
    }
    
    /***
     * 删除
     */
    @Test
    public void removeObject(){
        String name = "day/8D113DD5CE4B4AB2ABB5E531373E3D88.txt";
        minIoUtils.removeObject(minIoProperties.getBucket(), name);
    }


    /***
     * 根据对象名获取查看的url
     */
    @Test
    public void getObjectUrl(){
        String name = "day/8D113DD5CE4B4AB2ABB5E531373E3D88.txt";
        System.out.println(minIoUtils.getObjectUrl(minIoProperties.getBucket(), name, 7 * 24 * 60 * 60));
    }

    public static MultipartFile getMultipartFile(File file) {
        FileItem item = new DiskFileItemFactory().createItem("file"
                , MediaType.MULTIPART_FORM_DATA_VALUE
                , true
                , file.getName());
        try (InputStream input = new FileInputStream(file);
             OutputStream os = item.getOutputStream()) {
            // 流转移
            IOUtils.copy(input, os);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid file: " + e, e);
        }

        return new CommonsMultipartFile(item);
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值