MinIO对象存储服务与SpringBoot集成

MinIO

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

     相当于利用MinIO自建一个OSS对象存储服务

     MinIO是一个支持分布式部署的对象存储服务,官网地址:https://docs.min.io/cn/,可以使用源码、二进制文件、docker等方式安装MinIO,具体参考官网

使用docker 安装 MinIO 单机版(Linux)

  • 1.创建目录:mkdir -p /root/minio/config , mkdir -p /root/minio/data
  • 2.MinIO-docker启动命令:
docker run -p 9000:9000 --name minio \
  -it -d --restart=always \
  -e "MINIO_ACCESS_KEY=admin" \
  -e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG" \
  -v /root/minio/data:/data \
  -v /root/minio/config:/root/.minio \
  minio/minio server /data
  • 3.浏览器访问MinIO服务:http://192.168.1.101:9000/minio ,登录页面的用户名和密码就是上面的 admin 和 wJalrXUtnFEMI/K7MDENG
    在这里插入图片描述
  • 4.在该页面右下角 + 号可以创建桶、设置桶的访问、读写权限,并在桶中上传文件,还可以下载、删除、分享文件

MinIO与SpringBoot集成测试

可以参考:https://docs.min.io/cn/java-client-quickstart-guide.html

  • 1.新建SpringBoot项目,添加pom.xml依赖(下面只是部分依赖,具体请看源码
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-thymeleaf</artifactId>
   </dependency>

   <!-- https://mvnrepository.com/artifact/io.minio/minio -->
   <dependency>
       <groupId>io.minio</groupId>
       <artifactId>minio</artifactId>
       <version>7.1.4</version>
   </dependency>
  • 2.配置MinIO服务地址,application.properties
minio.endpoint=106.52.16.101
minio.port=9000
minio.accessKey=admin
minio.secretKey=wJalrXUtnFEMI/K7MDENG
minio.secure=false
minio.bucketDefaultName=test
  • 3.MinioConfig构建MinioClient Bean

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    /**
     * endPoint是一个URL,域名,IPv4或者IPv6地址
     */
    private String endpoint;

    /**
     * TCP/IP端口号
     */
    private int port;

    /**
     * accessKey类似于用户ID,用于唯一标识你的账户
     */
    private String accessKey;

    /**
     * secretKey是你账户的密码
     */
    private String secretKey;

    /**
     * 如果是true,则用的是https而不是http,默认值是true
     */
    private Boolean secure;

    /**
     * 默认存储桶名称
     */
    private String bucketDefaultName = "test";


    @Bean
    public MinioClient getMinioClient() {
        MinioClient minioClient = MinioClient.builder()
                .endpoint(endpoint , port , secure)
                .credentials(accessKey, secretKey)
                .build();
        return minioClient;
    }
}
  • 4.文件服务控制器FileController,提供上传、下载、预览、删除文件接口
package com.zypcy.file.minioservice.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.IoUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zypcy.file.minioservice.config.FileModel;
import com.zypcy.file.minioservice.config.MinioConfig;
import com.zypcy.file.minioservice.service.MinioService;
import io.minio.messages.Tags;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.Map;

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("files")
public class FileController {

    @Autowired
    private MinioService minioService;

    @Resource
    private MinioConfig minioConfig;

    /**
     * 表单/JS 上传文件
     *
     * @param file 文件
     * @return
     */
    @PostMapping("/upload")
    public JSONObject upload(@RequestParam("file") MultipartFile file) {
        JSONObject result = JSONUtil.createObj();
        result.set("code", "fail");
        try {
            if (file != null && !file.isEmpty()) {
                Map<String, String> map = minioService.putObject(minioConfig.getBucketDefaultName(), file);
                System.out.println(map);
                if (map != null) {
                    result.set("code", "success");
                    result.set("message", "上传成功");
                    result.set(FileModel.url, map.get(FileModel.url));
                    result.set(FileModel.name, map.get(FileModel.name));
                    result.set(FileModel.oldName, Base64.decodeStr(map.get(FileModel.oldName), "UTF-8"));
                    result.set(FileModel.size, map.get(FileModel.size));
                    result.set(FileModel.uploadDate, map.get(FileModel.uploadDate));
                    result.set(FileModel.suffix, map.get(FileModel.suffix));
                    map = null;
                } else {
                    result.set("message", "上传失败,请重新上传");
                }
            } else {
                result.set("message", "请传入文件");
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            result.set("message", ex.getMessage());
        }
        return result;
    }


    /**
     * 预览文件(只能预览图片、txt等部分文件)
     * objectName = dateDir +"/"+fileName;
     * @param bucketName 桶名称
     * @param dateDir    时间目录
     * @param fileName   文件名
     * @return
     */
    @GetMapping("/view/{bucketName}/{dateDir}/{fileName}")
    public ResponseEntity<Object> view(@PathVariable String bucketName, @PathVariable String dateDir, @PathVariable String fileName) {
        if (bucketName == null || fileName == null || dateDir == null) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Please pass in the correct parameters");
        }
        String objectName = dateDir +"/"+fileName;
        Tags tags = minioService.getObjectTags(bucketName , objectName);
        if(tags != null && tags.get() != null){
            try {
                Map<String, String> maps = tags.get();
                return ResponseEntity.ok()
                        .header(HttpHeaders.CONTENT_DISPOSITION, "fileName=" + maps.get(FileModel.name))
                        .header(HttpHeaders.CONTENT_TYPE, maps.get(FileModel.contentType))
                        .header(HttpHeaders.CONTENT_LENGTH, maps.get(FileModel.size))
                        .header("Connection", "close")
                        .body(IoUtil.readBytes(minioService.getObject(bucketName , objectName)));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File Not Found");
    }

    /**
     * 下载附件
     * objectName = dateDir +"/"+fileName;
     * @param bucketName 桶名称
     * @param dateDir    时间目录
     * @param fileName   文件名
     * @return
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/download/{bucketName}/{dateDir}/{fileName}")
    public void downloadFile(@PathVariable String bucketName, @PathVariable String dateDir, @PathVariable String fileName , HttpServletResponse response){
        if (bucketName == null || fileName == null || dateDir == null) {
            return ;
        }
        String objectName = dateDir +"/"+fileName;
        minioService.downloadObject(bucketName , objectName , response);
    }

    /**
     * 删除附件
     * objectName = dateDir +"/"+fileName;
     * @param bucketName 桶名称
     * @param dateDir    时间目录
     * @param fileName   文件名
     * @return
     */
    @GetMapping("/delete/{bucketName}/{dateDir}/{fileName}")
    public JSONObject deleteFile(@PathVariable String bucketName, @PathVariable String dateDir, @PathVariable String fileName) {
        JSONObject result = JSONUtil.createObj();
        result.set("code", "fail");
        if (bucketName == null || fileName == null || dateDir == null) {
            result.set("message", "请传入正确参数");
            return result;
        }
        String objectName = dateDir +"/"+fileName;
        boolean flag = minioService.removeObject(bucketName, objectName);
        result.set("code", flag ? "success" : "fail");
        result.set("message", flag ? "删除成功" : "删除失败");
        return result;
    }
}
  • 5.上传服务MinioService,与MinIO集成的关键代码,供参考
package com.zypcy.file.minioservice.service;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.zypcy.file.minioservice.config.FileModel;
import io.minio.*;
import io.minio.errors.InvalidExpiresRangeException;
import io.minio.http.Method;
import io.minio.messages.*;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Component
public class MinioService {

    @Autowired
    private MinioClient minioClient;
    //默认过期时间7天
    private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600;

    /**
     * 检查存储桶是否存在
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        boolean flag = false;
        flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        if (flag) {
            return true;
        }
        return false;
    }

    /**
     * 创建存储桶
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    public boolean makeBucket(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            return false;
        } else {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            return true;
        }
    }


    /**
     * 删除存储桶
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public boolean removeBucket(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                // 有对象文件,则删除失败
                if (item.size() > 0) {
                    return false;
                }
            }
            // 删除存储桶,注意,只有存储桶为空时才能删除成功。
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            flag = bucketExists(bucketName);
            if (!flag) {
                return true;
            }
        }
        return false;
    }

    /**
     * 列出所有存储桶名称
     * @return
     */
    @SneakyThrows
    public List<String> listBucketNames() {
        List<Bucket> bucketList = listBuckets();
        List<String> bucketListName = new ArrayList<>(bucketList.size());
        for (Bucket bucket : bucketList) {
            bucketListName.add(bucket.name());
        }
        return bucketListName;
    }

    /**
     * 列出所有存储桶
     * @return
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 列出存储桶中的所有对象名称
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public List<String> listObjectNames(String bucketName) {
        List<String> listObjectNames = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                listObjectNames.add(item.objectName());
            }
        }
        return listObjectNames;
    }

    /**
     * 列出存储桶中的所有对象
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public Iterable<Result<Item>> listObjects(String bucketName) {
        return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 列出存储桶中的所有对象
     * @param bucketName 存储桶名称
     * @param prefix     前缀
     * @param after      后缀
     * @param maxKeys    最大数量
     * @return
     */
    @SneakyThrows
    public Iterable<Result<Item>> listObjects(String bucketName, String prefix, String after, int maxKeys) {
        ListObjectsArgs.Builder builder = ListObjectsArgs.builder().bucket(bucketName);
        if (prefix != null && prefix.length() > 0) {
            builder.prefix(prefix);
        }
        if (after != null && after.length() > 0) {
            builder.startAfter(after);
        }
        if (maxKeys > 0) {
            builder.maxKeys(maxKeys);
        }
        return minioClient.listObjects(builder.build());
    }

    /**
     * 删除对象tag信息
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     */
    @SneakyThrows
    public void deleteObjectTags(String bucketName, String objectName) {
        minioClient.deleteObjectTags(DeleteObjectTagsArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 文件上传(已知文件大小)
     * @param bucketName  存储桶名称
     * @param objectName  存储桶里的对象名称
     * @param stream      文件流
     * @param size        大小
     * @param contentType 文件类型
     * @return
     */
    @SneakyThrows
    public boolean putObject(String bucketName, String objectName, InputStream stream, long size, String contentType) {
        ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName).object(objectName)
                .stream(stream, size, -1)
                .contentType(contentType).build());
        ObjectStat statObject = statObject(bucketName, objectName);
        if (statObject != null && statObject.length() > 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 文件上传(已知文件大小)
     * @param bucketName  存储桶名称
     * @param objectName  存储桶里的对象名称
     * @param stream      文件流
     * @param size        大小
     * @param contentType 文件类型
     * @param headers     文件headers
     * @return
     */
    @SneakyThrows
    public boolean putObject(String bucketName, String objectName, InputStream stream, long size, String contentType, Map<String, String> headers) {
        ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName).object(objectName)
                .stream(stream, size, -1)
                .headers(headers)
                .tags(headers)
                .contentType(contentType).build());
        ObjectStat statObject = statObject(bucketName, objectName);
        if (statObject != null && statObject.length() > 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 文件上传 ,最大5G
     * @param bucketName 桶名称
     * @param multipartFile 上传的文件
     * @param objectName 自定义文件名
     */
    @SneakyThrows
    public Map<String,String> putObject(String bucketName, MultipartFile multipartFile, String objectName) {
        return multipartFileUpload(bucketName , multipartFile , objectName);
    }

    /**
     * 文件上传 ,最大5G
     * @param bucketName 桶名称
     * @param multipartFile 上传的文件
     */
    @SneakyThrows
    public Map<String,String> putObject(String bucketName, MultipartFile multipartFile) {
        return multipartFileUpload(bucketName , multipartFile , IdUtil.simpleUUID());
    }

    //文件上传公用方法,以bucketName为根目录,年月为次级目录,name为文件名
    // url = bucketName/timePrefix/filename
    @SneakyThrows
    private Map<String,String> multipartFileUpload(String bucketName, MultipartFile multipartFile, String objectName){
        long size = multipartFile.getSize();
        String oldName = multipartFile.getOriginalFilename();
        String suffix = oldName.substring(oldName.lastIndexOf("."));
        oldName = Base64.encode(oldName , "UTF-8"); //需要对原文件名进行编码处理,否则中文名称会报错
        String name = (objectName != null && objectName.length() > 0) ? objectName : oldName;
        String timePrefix = DateUtil.format(LocalDateTime.now() , "yyyyMM");
        name = timePrefix + "/" + name + suffix;
        String contentType = multipartFile.getContentType();
        Map<String,String> headers = new ConcurrentHashMap<>();
        headers.put(FileModel.size,  size + "");
        headers.put(FileModel.name , name);         //文件新名称
        headers.put(FileModel.oldName , oldName);   //文件原名称
        headers.put(FileModel.contentType , contentType);
        headers.put(FileModel.suffix , suffix);
        headers.put(FileModel.uploadDate , DateUtil.now());

        //上传文件 ,最大5G
        minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(name)
                .contentType(contentType)
                .headers(headers)
                .tags(headers)
                .stream(multipartFile.getInputStream(), size , PutObjectOptions.MAX_PART_SIZE)
                .build());

        headers.put(FileModel.url , bucketName + "/" + name);
        return headers;
    }

    /**
     * 通过InputStream上传对象
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param stream     要上传的流
     * @return
     */
    @SneakyThrows
    public boolean putObject(String bucketName, String objectName, InputStream stream) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(stream, stream.available(), -1).build());
            ObjectStat statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.length() > 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * 以流的形式获取一个文件对象
     * 需要释放stream资源
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName) {
        /*boolean flag = bucketExists(bucketName);
        if (flag) {
            ObjectStat statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.length() > 0) {
                InputStream stream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
                return stream;
            }
        }*/
        InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
        return stream;
    }

    /**
     * 以流的形式获取一个文件对象(断点下载)
     * 需要释放stream资源
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度 (可选,如果无值则代表读到文件结尾)
     * @return
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName, long offset, Long length) {
        InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .offset(offset)
                        .length(length)
                        .build());
        return stream;
    }

    /**
     * 获取对象的tags
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    public Tags getObjectTags(String bucketName, String objectName) {
        Tags tags = null;
        try {
            tags = minioClient.getObjectTags(GetObjectTagsArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            //e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
        return tags;
    }

    /**
     * 删除一个对象
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     */
    @SneakyThrows
    public boolean removeObject(String bucketName, String objectName) {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
        return true;
    }

    /**
     * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
     * @param bucketName  存储桶名称
     * @param objectNames 含有要删除的多个object名称的迭代器对象
     * @return
     */
    @SneakyThrows
    public List<String> removeObjects(String bucketName, List<String> objectNames) {
        List<String> deleteErrorNames = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        if (flag) {
            List<DeleteObject> list = new LinkedList<>();
            objectNames.forEach(item -> list.add(new DeleteObject(item)));

            Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build());
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                deleteErrorNames.add(error.objectName());
            }
        }
        return deleteErrorNames;
    }

    /**
     * 给文件添加tags
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param tags       标签
     */
    @SneakyThrows
    public void setObjectTags(String bucketName, String objectName, Map<String, String> tags) {
        minioClient.setObjectTags(SetObjectTagsArgs.builder().bucket(bucketName).object(objectName).tags(tags).build());
    }

    /**
     * 生成一个给HTTP GET请求用的presigned URL。
     * 浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param expires    失效时间(以秒为单位),默认是7天,不得大于七天
     * @return
     */
    @SneakyThrows
    public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires, Method method) {
        String url = "";
        if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
            throw new InvalidExpiresRangeException(expires,
                    "expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME);
        }
        if (method == null) {
            method = Method.GET;
        }
        url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(method)
                .bucket(bucketName).object(objectName)
                .expiry(expires, TimeUnit.SECONDS).build());
        return url;
    }

    /**
     * 获取对象的元数据
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    @SneakyThrows
    public ObjectStat statObject(String bucketName, String objectName) {
        return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 文件访问路径
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName) {
        return minioClient.getObjectUrl(bucketName, objectName);
    }

    /**
     * 下载文件,在项目根目录
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param fileName   下载后文件名称
     */
    @SneakyThrows
    public void downloadObject(String bucketName, String objectName, String fileName) {
        minioClient.downloadObject(
                DownloadObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .filename(fileName)
                        .build());
    }

    /**
     * 下载文件
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param response
     */
    public void downloadObject(String bucketName, String objectName, HttpServletResponse response) {
        try {
            InputStream is = getObject(bucketName, objectName);
            if(is == null){
                return;
            }
            String fileName = objectName.substring(objectName.indexOf("/")+1 , objectName.length());
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            ServletOutputStream servletOutputStream = response.getOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) > 0) {
                servletOutputStream.write(buffer, 0, len);
            }
            servletOutputStream.flush();
            is.close();
            servletOutputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 6.以上是关键代码,其他代码是集成Thymeleaf,让项目有页面能上传文件,具体看源码:https://gitee.com/zhuyu1991/spring-cloud/tree/master/minio-service
  • 7.启动项目,访问 localhost:8080,有2个按钮,表单上传与JS上传,上传接口是相同的,上传成功后返回如下json信息
    在这里插入图片描述
  • 8.通过返回的url信息,加上 minio 服务端前缀访问,或者根据项目的预览接口访问
    在这里插入图片描述
  • 9.还可以通过url信息,访问项目预览接口:http://localhost:8080/files/view/test/202011/84b5bc96280c4c7d9af58a6a3eac82e9.txt

在这里插入图片描述

下篇文章分享MinIO分布式部署

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页