Introduction to Minio series [12] Spring Boot integrated Minio

Posted by sri.sjc on Sat, 23 Oct 2021 07:56:59 +0200

preface

Previously, we introduced how to use the JAVA SDK provided by Minio to upload and download files. On this basis, we can use spring boot to integrate the Minio JAVA SDK and add functions such as automatic configuration, assembly and client management to simplify development.

Spring Boot integrated Minio

1. Environmental construction

First, we build a spring boot basic project and introduce the following dependencies

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.minio/minio -->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.3.1</version>
        </dependency>
        <dependency>
            <groupId>me.tongfei</groupId>
            <artifactId>progressbar</artifactId>
            <version>0.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.2</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.13</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>

2. Operation template

spring provides many operation template classes that integrate with third parties, such as RedisTemplate and RestTemplate. We can refer to these and provide an integration template class of minio SDK, which is more convenient when using the API.

A Minio template is created here, which provides a series of integration of Minio API s.

@Slf4j
@AllArgsConstructor
public class MinioTemplate {

    /**
     * MinIO client
     */
    MinioClient minioClient;

    /**
     * MinIO Configuration class
     */
    OssProperties ossProperties;

    /**
     * Query all buckets
     *
     * @return Bucket aggregate
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * Does the bucket exist
     *
     * @param bucketName Bucket name
     * @return Does it exist
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * Create bucket
     *
     * @param bucketName Bucket name
     */
    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());

        }
    }

    /**
     * Delete an empty bucket. If the bucket object is not empty, the deletion will report an error.
     *
     * @param bucketName Bucket name
     */
    @SneakyThrows
    public void removeBucket(String bucketName) {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * Upload file
     *
     * @param inputStream      flow
     * @param originalFileName original filename
     * @param bucketName       Bucket name
     * @return OssFile
     */
    @SneakyThrows
    public OssFile putObject(InputStream inputStream, String bucketName, String originalFileName) {
        String uuidFileName = generateOssUuidFileName(originalFileName);
        try {
            if (StrUtil.isEmpty(bucketName)) {
                bucketName = ossProperties.getDefaultBucketName();
            }
            minioClient.putObject(
                    PutObjectArgs.builder().bucket(bucketName).object(uuidFileName).stream(
                            inputStream, inputStream.available(), -1)
                            .build());
            return new OssFile(uuidFileName, originalFileName);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * Return the temporary access URL with signature, expiration time of one day and Get request method
     *
     * @param bucketName  Bucket name
     * @param ossFilePath Oss File path
     * @return
     */
    @SneakyThrows
    public String getPresignedObjectUrl(String bucketName, String ossFilePath) {
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(ossFilePath)
                        .expiry(60 * 60 * 24)
                        .build());
    }

    /**
     * GetObject Interface is used to get a file (Object). This operation requires read permission on this Object.
     *
     * @param bucketName  Bucket name
     * @param ossFilePath Oss File path
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String ossFilePath) {
        return minioClient.getObject(
                GetObjectArgs.builder().bucket(bucketName).object(ossFilePath).build());
    }

    /**
     * Query object information of bucket
     *
     * @param bucketName Bucket name
     * @param recursive  Recursive query
     * @return
     */
    @SneakyThrows
    public Iterable<Result<Item>> listObjects(String bucketName, boolean recursive) {
        return minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).recursive(recursive).build());
    }

	/**
     * Generate random file names to prevent duplication
     *
     * @param originalFilename original filename
     * @return 
     */
    public String generateOssUuidFileName(String originalFilename) {
        return "files" + StrUtil.SLASH + DateUtil.format(new Date(), "yyyy-MM-dd") + StrUtil.SLASH + UUID.randomUUID() + StrUtil.SLASH + originalFilename;
    }

    /**
     * Obtain the temporary upload metadata object with signature, which can be obtained by the front end and uploaded directly to Minio
     *
     * @param bucketName
     * @param fileName
     * @return
     */
    @SneakyThrows
    public Map<String, String> getPresignedPostFormData(String bucketName, String fileName) {
        // Create an upload policy for the bucket with an expiration time of 7 days
        PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusDays(7));
        // Set a parameter key. The value is the name of the upload object
        policy.addEqualsCondition("key", fileName);
        // Adding content type starting with "image /" means that only photos can be uploaded
        policy.addStartsWithCondition("Content-Type", "image/");
        // Set the size of the uploaded file to 64kiB to 10MiB
        policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
        return minioClient.getPresignedPostFormData(policy);
    }

    /**
     * Initialize default bucket
     */
    @PostConstruct
    public void initDefaultBucket() {
        String defaultBucketName = ossProperties.getDefaultBucketName();
        if (bucketExists(defaultBucketName)) {
            log.info("Default bucket already exists");
        } else {
            log.info("Create default bucket");
            makeBucket(ossProperties.getDefaultBucketName());
        }
        ;
    }
}

3. Automatic configuration

After understanding the object storage OSS provided by BAT company, we found that its API interface standards are similar. From the perspective of scalability, our current services should support various types of OSS, such as Alibaba, Huawei, Tencent, etc. Therefore, an enumeration class is defined here to provide support for adapting to other manufacturers in addition to Minio.

@Getter
@AllArgsConstructor
public enum OssType {
    /**
     * Minio Object storage
     */
    MINIO("minio", 1),

    /**
     * Huawei OBS
     */
    OBS("obs", 2),

    /**
     * Tencent COS
     */
    COS("tencent", 3),

    /**
     * Alibaba SSO
     */
    ALIBABA("alibaba", 4),
    ;

    /**
     * name
     */
    final String name;
    /**
     * type
     */
    final int type;

}

To create an OSS configuration class, you can select different types of OSS Integration and configure the access address, user password and other information required for integration.

@Data
@ConfigurationProperties(prefix = "oss")
public class OssProperties {

    /**
     * Open
     */
    Boolean enabled;

    /**
     * Storage object server type
     */
    OssType type;

    /**
     * OSS When accessing the endpoint, a unified entry is required for the cluster
     */
    String endpoint;

    /**
     * user name
     */
    String accessKey;

    /**
     * password
     */
    String secretKey;

    /**
     * Default bucket name. If it is not specified, it will be placed in the default bucket
     */
    String defaultBucketName;
}

Then compile the project and convert the configuration class into the spring-configuration-metadata.json file, so that these configurations have the prompt function in yml.

Finally, we create different automatic configuration classes according to the OSS types we configure. The MinioConfiguration created here is mainly to inject MinioClient and MinioTemplate template classes according to the configuration and hand them over to the Spring container for management.

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({MinioClient.class})
@EnableConfigurationProperties(OssProperties.class)
@ConditionalOnExpression("${oss.enabled}")
@ConditionalOnProperty(value = "oss.type", havingValue = "minio")
public class MinioConfiguration {


    @Bean
    @SneakyThrows
    @ConditionalOnMissingBean(MinioClient.class)
    public MinioClient minioClient(OssProperties ossProperties) {
        return MinioClient.builder()
                .endpoint(ossProperties.getEndpoint())
                .credentials(ossProperties.getAccessKey(), ossProperties.getSecretKey())
                .build();
    }

    @Bean
    @ConditionalOnBean({MinioClient.class})
    @ConditionalOnMissingBean(MinioTemplate.class)
    public MinioTemplate minioTemplate(MinioClient minioClient, OssProperties ossProperties) {
        return new MinioTemplate(minioClient, ossProperties);
    }
}

4. Test

First, add the Minio configuration in yml:

oss:
  enabled: true
  type: MINIO
  endpoint: http://127.0.0.1:9000
  access-key: admin
  secret-key: admin123
  default-bucket-name: pearl-buckent

Then create an access interface and directly call minioTemplate for file operation, which is very convenient and achieves the purpose of simplifying development.

	@Autowired
    MinioTemplate minioTemplate;
    @PostMapping("/upload")
    @ResponseBody
    public Object upload(MultipartFile file, String bucketName) throws IOException {
        return minioTemplate.putObject(file.getInputStream(), bucketName, file.getOriginalFilename());
    }

Topics: Java Spring Boot Back-end