一、版本选择

直到笔者写这篇文章的时候Minio-API已经出到了8.x,我试用过,发现8.x版本的API主要的改动就是把零散的一些入参,整合成了一个个对象。经过对比,我发现还是7.x的简单易懂,如果小伙伴用的是8.x版本的Minio-API那么不建议你看第三步,因为我的方法已经不适用了。

二、Minio服务器搭建

2.1 下载

因为Docker下载安装启动各种中间件过于简单和方便,所以此处只介绍手动下载。
Minio官方下载地址:http://www.minio.org.cn/download.shtml#/,只需要下载服务器即可。

2.2 启动

Minio官方文档:http://docs.minio.org.cn/docs/,因为不同的系统启动指令不同,所以可以参考官方的启动文档,里面还算详细。

2.3 访问

成功启动后,可以看到如图的提示:

systemd服务将Spring Boot Ja springboot文件服务器_minio


这里的minioadmin是指的账号和密码,此时我们访问http://127.0.0.1:9000,即可成功访问到Minio管理端可视化界面。

界面如图:

systemd服务将Spring Boot Ja springboot文件服务器_List_02

  • Buckets:翻译过来就是“桶”,它主要是用来管理我们文件服务器有多少个“桶”的,“桶”可以理解为一个文件夹,里面是用来存放文件的。
  • Identity:用来管理用户和账号。
  • Access:用来控制一些权限。
  • Monitoring:用来监控一些文件服务器的指标,如桶的数量、文件大小、一些日志等。
  • Support:Minio服务器提供的一些额外支持功能。
  • License:Minio服务器的许可证和支持计划(付费买更高级的服务)。
  • Settings:Minio服务器设置相关。
  • Documentation:Minio的一些官方文档。

三、SpringBoot集成Minio-API

3.1 导入依赖

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

3.2 添加配置

配置文件:

minio:
  url: http://127.0.0.1:9000
  endpoint: http://127.0.0.1:9000
  access-key: minioadmin
  secret-key: minioadmin
  bucket-name: study
  test-folder: test

配置类 MinioProperties:

/**
 * @description: Minio文件配置
 * @author: Felix.Du
 * @date: 2022/4/11 3:38 下午
 */
@Data
@ConfigurationProperties(prefix = "minio")
@Component
public class MinioProperties {

    /**
     * minio 服务地址 http://ip:port
     */
    private String url;

    /**
     * minio 服务地址对外访问地址
     */
    private String endpoint;

    /**
     * accessKey
     */
    private String accessKey;

    /**
     * 密码
     */
    private String secretKey;

    /**
     * 桶名称
     */
    private String bucketName;

    /**
     * 下载文件夹名称
     */
    private String testFolder;
}

配置类 MinioConfiguration:

/**
 * @description: MinioClient配置Bean
 * @author: Felix.Du
 * @date: 2022/4/11 5:38 下午
 */
@Configuration
@Slf4j
public class MinioConfiguration {

    @Bean
    MinioClient minioClient(MinioProperties properties)  {
        try {
            return new MinioClient(
                    properties.getUrl(),
                    properties.getAccessKey(), properties.getSecretKey()
            );
        } catch (Exception e) {
            log.error("初始化MinioClient失败", e);
            return null;
        }
    }

}

3.3 工具类

OssUtils:

public class OssUtils {

    private static Map<String, String> map = new HashMap<>(){{
            put(".bmp", "image/bmp");
            put(".webp", "image/webp");
            put(".gif", "image/gif");
            put(".jpeg", "image/jpeg");
            put(".jpg", "image/jpg");
            put(".png", "image/png");
            put(".html", "text/html");
            put(".txt", "text/plain");
            put(".vsd", "application/vnd.visio");
            put(".ppt", "application/vnd.ms-powerpoint");
            put(".pptx", "application/vnd.ms-powerpoint");
            put(".doc", "application/msword");
            put(".docx", "application/msword");
            put(".xml", "text/xml");
            put(".mp4", "video/mp4");
            put(".mp3", "audio/mp3");
    }};

    private static FileNameMap fileNameMap = URLConnection.getFileNameMap();

    public static String getContentType(String fileName) {
        int index = fileName.lastIndexOf(".");
        if (index == -1) {
            return "application/octet-stream";
        }
        String fileExtension = fileName.substring(index);
        String type = fileNameMap.getContentTypeFor(fileExtension);
        if (Objects.isNull(type)) {
            type = map.get(fileExtension);
        }
        if (Objects.isNull(type)) {
            return "application/octet-stream";
        }
        return type;
    }
}

3.4 具体实现

MinioStorageService:

/**
 * @description: Minio文件存储服务
 * @author: Felix.Du
 * @date: 2022/4/11 3:38 下午
 */
public interface MinioStorageService {

    /**
     * 上传文件
     *
     * @param file 文件
     * @return
     */
    String upload(MultipartFile file);

    /**
     * 根据文件url删除普通文件
     *
     * @param fileUrl 文件url链接
     * @return 1->删除成功,0->删除失败
     */
    Integer deleteFile(String fileUrl);

    /**
     * 批量上传文件
     *
     * @param fileList 文件list
     * @return
     */
    List<String> uploadBatchFile(List<MultipartFile> fileList);

    /**
     * 检查文件存储桶是否存在
     *
     * @param bucketName bucket桶名称
     * @return true->存在,false->不存在
     */
    boolean bucketExists(String bucketName);

    /**
     * 创建bucket
     *
     * @param bucketName bucket桶名称
     */
    void createBucket(String bucketName);

    /**
     * 获取所有bucket
     *
     * @return bucket 列表
     */
    List<BucketVO> getAllBuckets();

}

MinioStorageServiceImpl:

/**
 * @description: Minio文件存储服务
 * @author: Felix.Du
 * @date: 2022/4/11 3:38 下午
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MinioStorageServiceImpl implements MinioStorageService {

    private final MinioClient minioClient;

    private final MinioProperties minioProperties;

    @Override
    public String upload(MultipartFile file) {
        return uploadFile(file);
    }

    @Override
    public boolean bucketExists(String bucketName) {
        try {
            return minioClient.bucketExists(bucketName);
        } catch (Exception e) {
            log.error("检查文件存储桶是否存在出错", e);
        }
        return false;
    }

    @Override
    public void createBucket(String bucketName) {
        try {
            minioClient.makeBucket(bucketName);
        } catch (Exception e) {
            log.error("创建bucket出错", e);
        }
    }

    @Override
    public Integer deleteFile(String fileUrl) {
        try {
            if (checkFileName(fileUrl)) {
                minioClient.removeObject(
                        minioProperties.getBucketName(),
                        minioProperties.getTestFolder() + fileUrl.substring(fileUrl.lastIndexOf("/"))
                );
                return 1;
            } else {
                log.error("删除文件失败,无效的文件url");
            }
        } catch (Exception e) {
            log.error("删除文件出错", e);
        }
        return 0;
    }

    @Override
    public List<String> uploadBatchFile(List<MultipartFile> fileList) {
        List<String> urlList = new ArrayList<>();
        fileList.forEach(file -> urlList.add(uploadFile(file)));
        return urlList;
    }

    @Override
    public List<BucketVO> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            List<BucketVO> bucketVos = new ArrayList<>();
            if (buckets != null && !buckets.isEmpty()) {
                buckets.forEach(bucket -> {
                    BucketVO bucketVO = new BucketVO();
                    BeanUtils.copyProperties(bucket, bucketVO);
                    bucketVO.setCreateTime(bucket.creationDate().toLocalDateTime());
                    bucketVos.add(bucketVO);
                });
            }
            return bucketVos;
        } catch (Exception e) {
            log.error("获取所有bucket出错", e);
            return Collections.emptyList();
        }
    }

    private String replaceEndpoint(String url) {
        return url.replace(minioProperties.getUrl(), minioProperties.getEndpoint());
    }

    /**
     * 校验文件名是否合法
     *
     * @return true->合法,false->不合法
     */
    private boolean checkFileName(String fileUrl) {
        return !StringUtils.isEmpty(fileUrl) && fileUrl.contains("/");
    }

    private String uploadFile(MultipartFile file) {
        try (InputStream is = file.getInputStream()) {
            String originName = file.getOriginalFilename(),
                    contentType = file.getContentType();
            assert originName != null;
            if (Objects.isNull(contentType)) {
                contentType = OssUtils.getContentType(originName);
            }
            int index = originName.lastIndexOf(".");

            var fileName = UUID.randomUUID().toString();
            if (index != -1) {
                String fileExtension = originName.substring(index);
                fileName = fileName + fileExtension;
            }
            var options = new PutObjectOptions(file.getSize(), -1);
            options.setContentType(contentType);
            minioClient.putObject(minioProperties.getBucketName(), minioProperties.getTestFolder() + File.separator + fileName, is, options);

            return replaceEndpoint(minioClient.getObjectUrl(minioProperties.getBucketName(), minioProperties.getTestFolder() + File.separator + fileName));
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("上传文件出现错误", e);
            }
        }
        return null;
    }

}

控制层 MinioStorageController:

/**
 * @description: Minio文件存储服务
 * @author: Felix.Du
 * @date: 2022/4/11 5:38 下午
 */
@Slf4j
@RestController
@RequiredArgsConstructor
public class MinioStorageController {

    private final MinioStorageService minioStorageService;

    @PostMapping("/upload")
    public String upload(MultipartFile file) {
        return minioStorageService.upload(file);
    }

    @GetMapping("/bucketExists")
    public boolean bucketExists(@RequestParam String bucketName) {
        return minioStorageService.bucketExists(bucketName);
    }

    @PostMapping("/createBucket")
    public void createBucket(String bucketName) {
        minioStorageService.createBucket(bucketName);
    }

    @DeleteMapping("/deleteFile")
    public Integer deleteFile(String fileUrl) {
        return minioStorageService.deleteFile(fileUrl);
    }

    @PostMapping("/uploadBatchFile")
    public List<String> uploadBatchFile(@RequestPart("file") MultipartFile[] files) {
        return minioStorageService.uploadBatchFile(Arrays.stream(files).collect(Collectors.toList()));
    }

    @GetMapping("/getAllBuckets")
    public List<BucketVO> getAllBuckets() {
        return minioStorageService.getAllBuckets();
    }
}

注意:上传文件成功后返回的url,如果输入浏览器无法直接打开,是因为没有将该“桶”的权限放开。点击左侧的“Buckets” -> “Manage” -> “Access Policy”,将“private”修改为“public”即可。