springboot+minio文件存储初尝试

文件服务算是一个基础服务了,比较专业的组件有FastDFS,也可以采用基本的保存在本地的文件上传方式,但是其安全性不高。对于中小公司来说,可以直接购买云存储服务,当然也可以自己搭建,下面就聊聊springboot集成文件服务组件MinIO,看看这个组件带来了那些惊喜。
MinIO是一个基于Apache License v2.0开源协议的对象存储服务,非常合适存储大容量非结构化的数据,如图片、视频、日志文件等,一个对象文件的大小可以是几KB到5T不等。MinIO是一个轻量级的服务,类似redis、mysql,可以很方便的和其他应用结合,并且自带非常方便的可视化界面,让人心生喜欢。
MinIO的特点请查看官网,https://min.io/。
1、MinIO安装
进入官网下载对应的版本,可以选择linux、windows、docker等,本次选择windows环境
在这里插入图片描述
在这里插入图片描述
windows环境minio安装启动
查看minio版本

minio.exe -version

在这里插入图片描述
首先进入minio.exe所在目录,在cmd命令行中输入如下命令即完成安装启动,其中server后面的地址是文件上传的存储目录。

minio.exe server D:\minio

在这里插入图片描述
api端口是9000,所以下面的yml文件中端口需要配置为9000,accessKey和secretKey需要配置为启动窗口上对应的值。
启动成功后,访问默认地址http://127.0.0.1:9000/minio进入到minio控制台,输入账号密码登录即可。
在这里插入图片描述
在这里插入图片描述
可以在控制台创建bucketName,也可以在代码中创建。注意在启动服务后使用minio。

linux环境minio安装启动
创建data目录
mkdir/usr/local/minio/data
添加可执行权限
chmod +x minio
设置登录的access_key
export MINIO_ACCESS_KEY=minioadmin
设置登录的secret key
export MINIO_SECRET_KEY=minioadmin
启动 minio
./minio server /usr/local/minio/data
或指定端口号
./minio server /usr/local/minio/data --address=0.0.0.0:9001
其他同windows环境。
2、springboot集成MinIO
创建springboot工程,引入web依赖、minio依赖

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.1</version>
</dependency>

配置application.yml文件

spring:
  servlet:
    multipart:
      max-file-size: 200MB
      max-request-size: 200MB

minio:
  endpoint: http://localhost:9000
  accessKey: minioadmin
  secretKey: minioadmin
  nginxUrl: http://localhost:9000


创建Properties类接收上面四个属性

@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
    /**
     * 连接地址
     */
    private String endpoint;
    /**
     * 用户名
     */
    private String accessKey;
    /**
     * 密码
     */
    private String secretKey;
    /**
     * 域名
     */
    private String nginxUrl;

    public String getEndpoint() {
        return endpoint;
    }

    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public String getSecretKey() {
        return secretKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public String getNginxUrl() {
        return nginxUrl;
    }

    public void setNginxUrl(String nginxUrl) {
        this.nginxUrl = nginxUrl;
    }
}

创建配置类,注入MinIOClient

@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioConfig {

    @Autowired
    private MinioProperties minioProperties;

    /**
     * 获取MinioClient
     */
    @Bean
    public MinioClient minioClient() {
        return MinioClient
                .builder()
                .endpoint(minioProperties.getEndpoint())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();
    }

}

创建Client工具类,方便操作文件

@Component
public class MinioUtil {

    @Autowired
    private MinioProperties minioProperties;

    @Autowired
    private MinioClient minioClient;

    /**
     * 创建bucket
     */
    public void createBucket(String bucketName) throws Exception {
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 删除bucket
     */
    public void removeBucket(String bucketName) throws Exception {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 获取全部bucket
     */
    public List<Bucket> getBuckets() throws Exception {
        return minioClient.listBuckets();
    }

    /**
     * 根据bucketName获取bucket
     * @param bucketName bucket名称
     */
    public Optional<Bucket> getBucket(String bucketName) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InvalidResponseException, InternalException, ErrorResponseException, ServerException, XmlParserException, ServerException {
        return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    /**
     * 上传文件
     */
    public MinioObject uploadFile(MultipartFile file, String bucketName) throws Exception {
        //判断文件是否为空,为空则返回
        if (null == file || 0 == file.getSize()) {
            return null;
        }
        //判断存储桶是否存在,不存在则创建
        createBucket(bucketName);
        //文件名
        String originalFilename = file.getOriginalFilename();
        //新的文件名 = 存储桶文件名_时间戳.后缀名
        assert originalFilename != null;
        String fileName = bucketName + "_" +
                new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "_" +
                new Random().nextInt(1000) +
                originalFilename.substring(originalFilename.lastIndexOf("."));
        //开始上传
        minioClient.putObject(
                PutObjectArgs
                        .builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .stream(file.getInputStream(), file.getSize(), -1)
                        .contentType(file.getContentType())
                        .build());
        String url = minioProperties.getEndpoint() + "/" + bucketName + "/" + fileName;
        String urlHost = minioProperties.getNginxUrl() + "/" + bucketName + "/" + fileName;
        return new MinioObject(url, urlHost);
    }

    /**
     * 下载文件
     */
    public void downloadFile(String filename, String bucketName, HttpServletResponse httpServletResponse){
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).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();
                httpServletResponse.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                httpServletResponse.setContentType("application/force-download");
                httpServletResponse.addHeader("Content-Disposition", "attachment;fileName=" + filename);
                try ( ServletOutputStream stream = httpServletResponse.getOutputStream()){
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 下载文件
     */
    public void downloadObject(String fileName, String bucketName, HttpServletResponse httpServletResponse) {
        try {
            GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(fileName).build();
            InputStream file = minioClient.getObject(objectArgs);
            String filename = new String(fileName.getBytes("ISO8859-1"), "utf-8");
            httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + filename);
            ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = file.read(buffer)) > 0) {
                servletOutputStream.write(buffer, 0, len);
            }
            servletOutputStream.flush();
            file.close();
            servletOutputStream.close();
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 上传⽂件
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @param stream     ⽂件流
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
     */
    public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, stream.available(), -1).contentType(objectName.substring(objectName.lastIndexOf("."))).build());
    }

    /**
     * 上传⽂件
     *
     * @param bucketName  bucket名称
     * @param objectName  ⽂件名称
     * @param stream      ⽂件流
     * @param size        ⼤⼩
     * @param contextType 类型
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
     */
    public void putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) throws Exception {
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, size, -1).contentType(contextType).build());
    }

    /**
     * 删除⽂件
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @throws Exception https://docs.minio.io/cn/java-client-apireference.html#removeObject
     */
    public void removeObject(String bucketName, String objectName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 获取⽂件url
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @param expires    过期时间 <=7
     * @return url
     */
    public String getObjectURL(String bucketName, String objectName, Integer expires) throws Exception {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectName).expiry(expires).build());
    }

    /**
     * 获取⽂件输入流
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @return ⼆进制流
     */
    public InputStream getObject(String bucketName, String objectName) throws Exception {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 获取⽂件信息
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject
     */
    public StatObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {
        return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 查看文件对象列表
     *
     * @param bucketName 存储bucket名称
     * @return 存储bucket内文件对象信息
     */
    public List<MinioObject> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
        List<MinioObject> objectItems = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                Item item = result.get();
                MinioObject minioObject = new MinioObject();
                minioObject.setMinIoUrl(item.objectName());;
                minioObject.setSize(item.size());
                objectItems.add(minioObject);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectItems;
    }

}

访问接口

	@PostMapping("/upload")
    public String fileUpload(MultipartFile file) throws Exception {
        MinioObject minioObject = minioUtil.uploadFile(file, "public");
        System.out.println("bucket01.getMinIoUrl() = " + minioObject.getMinIoUrl());
        System.out.println("bucket01.getNginxUrl() = " + minioObject.getNginxUrl());
        return minioObject.getMinIoUrl();
    }
	@GetMapping("download")
    public Object download(HttpServletResponse response) throws Exception {
        minioUtil.downloadObject("","image",response);
        return "success";
    }

文件上传完成之后如图
在这里插入图片描述
注意:可以通过可视化界面设置存储桶的权限级别,是否开发,访问的url有效期等,具体看组件提供的方法,如

getObjectURL()获取桶内文件的url地址,如果设置了只写权限,用户直接访问地址是查看不了的;
getObgect()可以获取桶内对应文件的流对象,进行文件流传输;
presignedGetObject()返回的是进行加密算法的地址,通过它可以直接访问文件;
getPresignedObjectUrl(Method.GET,"asiatrip","minio.png",10,null)有效期10秒,可以通过get方式访问;
makeBucket 创建桶
listBuckets 列出当前所有桶
bucketExists 判断桶是否存在
removeBucket 移除桶
listObjects 获取桶内的内容
listIncompleteUploads 获取上传中断的内容,支持断点续传
getObject 下载文件
putObject 上传文件

文件存储可以使用之后也可以通过配置nginx的方式对静态资源进行代理,前端直接通过url进行访问。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现Spring Boot与Minio的大文件上传并解压的功能,可以按照以下步骤进行操作: 1. 首先,在Spring Boot项目的pom.xml文件中引入Minio的依赖。可以使用以下代码将Minio的依赖添加到项目中: ``` <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.2.1</version> </dependency> ``` 2. 然后,将大文件分片并上传到Minio对象存储服务。可以按照以下步骤进行操作: - 将要上传的大文件进行分片处理,可以使用文件分片算法将大文件分成多个小块。 - 将分片后的文件依次上传到Minio对象存储服务,并指定一个临时文件名。 3. 分片上传完成后,将所有分片合并为一个完整的文件。可以按照以下步骤进行操作: - 从Minio中读取所有分片文件的流。 - 将分片流写入到一个临时文件中,以便后续解压。 4. 解压临时文件。可以按照以下步骤进行操作: - 使用合适的解压库或工具,对临时文件进行解压。 - 解压后的文件可以按需求进行进一步处理,例如再次上传到Minio或其他存储系统。 综上所述,要实现Spring Boot与Minio的大文件上传并解压功能,需要将文件分片并上传到Minio,然后将分片合并为完整文件,最后解压文件。这样可以实现大文件的上传和解压操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Spring boot + minio 分片上传](https://blog.csdn.net/XIAOTONGZHU/article/details/130346735)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [SpringBoot 使用 Minio 进行文件存储](https://blog.csdn.net/qq_43692950/article/details/125961685)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Windows常用软件压缩包,后端Java适用于springboot 2.6.x等高版本](https://download.csdn.net/download/m0_55710969/85062866)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值