Kotlin专题

Kotlin与Minio存储整合详解

2019-06-11  本文已影响0人  爱学习的蹭蹭

1、Minio安装

请看我之前撰写的 Docker安装Minio存储服务器详解

2、工程架构模式

请看我之前撰写的 Kotlin +SpringBoot + MyBatis完美搭建最简洁最酷的前后端分离框架

3 、依赖jar

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

4、自动装配

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flong.kotlin.core.minio.MinioAutoConfiguration

5、properties配置minio信息

# minio服务器url
minio.url=http://192.168.1.133:9000
# minio安装指定的访问key
minio.accessKey=admin
# minio安装指定的访问秘钥
minio.secretKey=admin123456
# minio的启用配置
minio.endpoint.enable=true

6、工程核心代码如下

package com.flong.kotlin.core.minio
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean

@EnableConfigurationProperties(MinioProperties::class)
open class MinioAutoConfiguration {
    @Autowired lateinit var minioProperties: MinioProperties;
    
    @Bean
    @ConditionalOnMissingBean(MinioTemplate::class)
    @ConditionalOnProperty(name = arrayOf("minio.url"))
    open fun minioTemplate():MinioTemplate {
        return MinioTemplate(minioProperties.url,
                minioProperties.accessKey,
                minioProperties.secretKey)
    }
}
package com.flong.kotlin.core.minio
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value
/*
 * Value注解的使用
 * https://segmentfault.com/a/1190000010978025
 * https://blog.jetbrains.com/idea/2018/10/spring-kotlin-references-in-value-annotation/
 */
@Configuration
@ConfigurationProperties(prefix = "minio")
open class MinioProperties {
    /**
     * minio 服务地址 http://ip:port
     */
    var url:String=""
    /**
     * 用户名
     */
    var accessKey:String=""
    /**
     * 密码
     */
    var secretKey:String =""
}
package com.flong.kotlin.core.minio

import io.minio.MinioClient
import io.minio.ObjectStat
import io.minio.messages.Bucket
import io.minio.messages.Item
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.annotation.Bean
import java.io.InputStream
import java.util.ArrayList
import java.util.Optional
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.beans.factory.annotation.Autowired
/**
 * MinioTemplate連接工具
 * Linux的时区与minio容器的时区不一致导致的异常
 * ErrorResponse(code=RequestTimeTooSkewed, message=The difference between the request time and the server's time is too large.,
 * bucketName=null, objectName=null, resource=/kotlin, requestId=null, hostId=null)
 */
@Component
@Configuration
@EnableConfigurationProperties(MinioProperties::class)
open class MinioTemplate  {
    var url:String       = ""
    var accessKey:String = ""
    var secretKey:String = ""
    //constructor无参数构造方法是为了解决以下错误:
    //Unsatisfied dependency expressed through constructor parameter 0; nested exception is
    //org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String'
    //available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    constructor()
    
    //有参数的构造方法
    constructor(url:String , accessKey:String, secretKey:String){
        this.url = url
        this.accessKey = accessKey
        this.secretKey = secretKey
    }
    
    @Autowired lateinit var minioProperties: MinioProperties
    
    open fun getMinioClient(): MinioClient {
        return MinioClient(minioProperties.url,minioProperties.accessKey,minioProperties.secretKey)
    }
    
    /**
     * 创建bucket
     * @param bucketName bucket名称
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#makeBucket
     */
    fun createBucket(bucketName: String) {
        var client = getMinioClient()
        if (!client.bucketExists(bucketName)) {
            client.makeBucket(bucketName)
        }
    }

    /**
     * 获取全部bucket
     * <p>
     * https://docs.minio.io/cn/java-client-api-reference.html#listBuckets
     */
    fun getAllBuckets(): List<Bucket> {
        return getMinioClient().listBuckets()
    }

    /**
     * @param bucketName bucket名称
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#listBuckets
     */
    fun getBucket(bucketName: String): Optional<Bucket> {
        return getMinioClient().listBuckets().stream().filter({ b -> b.name().equals(bucketName) }).findFirst()
    }
    /**
     * @param bucketName bucket名称
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#removeBucket
     */
    fun removeBucket(bucketName: String) {
        getMinioClient().removeBucket(bucketName)
    }
    /**
     * 根据文件前置查询文件
     * @param bucketName bucket名称
     * @param prefix     前缀
     * @param recursive  是否递归查询
     * @return
     * @throws Exception
     */
    fun getAllObjectsByPrefix(bucketName: String, prefix: String, recursive: Boolean): List<Item> {
        var objectList = ArrayList<Item>()
        var objectsIterator = getMinioClient().listObjects(bucketName, prefix, recursive)
        while (objectsIterator.iterator().hasNext()) {
            objectList.add(objectsIterator.iterator().next().get())
        }
        return objectList
    }
    
    /**
     * 获取文件外链
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param expires    过期时间 <=7
     * @return url
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#getObject
     */
    fun getObjectURL(bucketName:String , objectName:String , expires:Int ) :String {
        return getMinioClient().presignedGetObject(bucketName, objectName, expires)
    }
    
    /**
     * 获取文件
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @return 二进制流
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#getObject
     */
    fun getObject(bucketName:String , objectName:String ) :InputStream{
        return getMinioClient().getObject(bucketName, objectName)
    }
    
    /**
     * 上传文件
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param stream     文件流
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
     */
    fun putObject(bucketName:String , objectName:String , stream:InputStream)  {
        //假设不存在 bucket名称,程序创建 bucket名称,不需要手动创建
        createBucket(bucketName)
        getMinioClient().putObject(bucketName, objectName, stream, stream.available().toLong(), "application/octet-stream");
    }
    /**
     * 上传文件
     *
     * @param bucketName  bucket名称
     * @param objectName  文件名称
     * @param stream      文件流
     * @param size        大小
     * @param contextType 类型
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
     */
    fun putObject(bucketName:String, objectName:String, stream:InputStream, size:Long,contextType:String ):Unit {
        //假设不存在 bucket名称,程序创建 bucket名称,不需要手动创建
        createBucket(bucketName)
        getMinioClient().putObject(bucketName, objectName, stream, size, contextType)
    }
    
    /**
     * 获取文件信息
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject
     */
    fun getObjectInfo(bucketName:String , objectName:String ) : ObjectStat {
        return getMinioClient().statObject(bucketName, objectName)
    }
    
    /**
     * 删除文件
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#removeObject
     */
    fun removeObject(bucketName:String ,objectName : String ) :Unit {
        getMinioClient().removeObject(bucketName, objectName)
    }

}

注意点1: Linux的时区与minio容器的时区不一致导致的异常
ErrorResponse(code=RequestTimeTooSkewed, message=The difference between the request time and the server's time is too large., bucketName=null, objectName=null, resource=/kotlin, requestId=null, hostId=null)

注意点2 constructor无参数构造方法是为了解决以下错误:
Unsatisfied dependency expressed through constructor parameter 0; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

constructor()
package com.flong.kotlin.modules.controller

import io.minio.messages.*;
import com.flong.kotlin.core.PageVO
import com.flong.kotlin.core.exception.BaseException
import com.flong.kotlin.core.web.BaseController
import com.flong.kotlin.modules.entity.User
import com.flong.kotlin.modules.enums.UserMsgCode
import com.flong.kotlin.modules.query.UserQuery
import com.flong.kotlin.modules.service.UserService
import com.flong.kotlin.modules.vo.resp.UserRespVo
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile
import org.springframework.beans.factory.annotation.Autowired
import com.flong.kotlin.core.minio.MinioTemplate
import com.flong.kotlin.core.minio.vo.*
import java.lang.*
import javax.servlet.http.HttpServletResponse
import com.flong.kotlin.utils.IoUtils
import java.util.UUID
import com.flong.kotlin.core.vo.LiveResp

@ConditionalOnProperty(name = arrayOf("minio.endpoint.enable"), havingValue = "true")
@RestController
@RequestMapping("/rest")
class MinioController : BaseController() {
    @Autowired private lateinit var template: MinioTemplate

    //常量
    companion object MinioConstant {
        const val fileName = "kotlin.xls"
        const val bucketName = "kotlin"
    }

    /**
     * Bucket Endpoints
     */
    @PostMapping("/bucket/{bucketName}")
    fun createBucker(@PathVariable bucketName: String): Bucket {
        print("bucketName=" + bucketName)
        template.createBucket(bucketName)
        return template.getBucket(bucketName).get()
    }

    @GetMapping("/bucket")
    fun getBuckets(): List<Bucket> {
        return template.getAllBuckets()
    }
    @GetMapping("/bucket/{bucketName}")
    fun getBucket(@PathVariable bucketName: String): Bucket {
        return template.getBucket(bucketName).orElseThrow({ IllegalStateException("Bucket Name not found!") })
    }

    @DeleteMapping("/bucket/{bucketName}")
    @ResponseStatus(HttpStatus.ACCEPTED)
    fun deleteBucket(@PathVariable bucketName: String) {
        template.removeBucket(bucketName)
    }

    /**
     * Object Endpoints
     */

    @PostMapping("/object/{bucketName}")
    fun createObject(@RequestBody file: MultipartFile, @PathVariable bucketName: String): MinioObject {
        var name = file.getOriginalFilename()
        template.putObject(bucketName, name, file.getInputStream(), file.getSize(), file.getContentType())
        return MinioObject(template.getObjectInfo(bucketName, name))

    }

    @PostMapping("/object/{bucketName}/{objectName}")
    fun createObject(@RequestBody file: MultipartFile, @PathVariable bucketName: String, @PathVariable objectName: String): MinioObject {
        template.putObject(bucketName, objectName, file.getInputStream(), file.getSize(), file.getContentType())
        return MinioObject(template.getObjectInfo(bucketName, objectName))
    }

    @GetMapping("/object/{bucketName}/{objectName}")
    fun filterObject(@PathVariable bucketName: String, @PathVariable objectName: String): List<Item> {
        return template.getAllObjectsByPrefix(bucketName, objectName, true)
    }

    @GetMapping("/object/{bucketName}/{objectName}/{expires}")
    fun getObject(@PathVariable bucketName: String, @PathVariable objectName: String, @PathVariable expires: Int): Map<String, Any> {
        var responseBody = HashMap<String, Any>()
        // Put Object info
        responseBody.put("bucket", bucketName)
        responseBody.put("object", objectName)
        responseBody.put("url", template.getObjectURL(bucketName, objectName, expires))
        responseBody.put("expires", expires)
        return responseBody
    }

    @DeleteMapping("/object/{bucketName}/{objectName}/")
    @ResponseStatus(HttpStatus.ACCEPTED)
    fun deleteObject(@PathVariable bucketName: String, @PathVariable objectName: String) {
        template.removeObject(bucketName, objectName)
    }

    /*
     *下载文件
     */
    @GetMapping("/downloadFile")
    fun downloadFile(response: HttpServletResponse) {
        var fileName = MinioConstant.fileName
        var bucketName = MinioConstant.bucketName
        var inputStream = template.getObject(bucketName, fileName)
        IoUtils.downloadFile(response, fileName, "UTF-8", inputStream) //从minio下载
    }

    /*
     *上传文件
     */
    @PostMapping("/upload")
    fun upload(@RequestParam("file") file: MultipartFile) : LiveResp<Any>{
        var originalFilename = file.getOriginalFilename();
        //随机数和源文件名称
        var newFilename = UUID.randomUUID().toString().replace("-", "") + originalFilename

        var resultMap = HashMap<String, String>(4);
        resultMap.put("bucketName", MinioConstant.bucketName);
        resultMap.put("fileName", newFilename);
        template.putObject(MinioConstant.bucketName, newFilename, file.getInputStream())
        
        return LiveResp(resultMap);
        
    }

}

常量值可以根据自己实际情况进行修改测试,这里以一个图片进行测试。

companion object MinioConstant {
    const val fileName = "kotlin.xls"
    const val bucketName = "kotlin"
}
访问路径:http://localhost:8080/rest/downloadFile

7、工程源代码

工程源代码在dev-minio分支上

8 、总结与建议

1 、以上问题根据搭建 kotlin与Minio 实际情况进行总结整理,除了技术问题查很多网上资料通过进行学习之后梳理与分享。

2、 在学习过程中也遇到很多困难和疑点,如有问题或误点,望各位老司机多多指出或者提出建议。本人会采纳各种好建议和正确方式不断完善现况,人在成长过程中的需要优质的养料。

3、 希望此文章能帮助各位老铁们更好去了解如何在 kotlin上搭建Minio,也希望您看了此文档或者通过找资料进行手动安装效果会更好。

备注:此文章属于本人原创,欢迎转载和收藏.

上一篇下一篇

猜你喜欢

热点阅读