Java SpringBoot

一、minio简介

引用官网:MinIO是根据GNU Affero通用公共许可证v3.0发布的高性能对象存储。它与Amazon S3云存储服务兼容。使用MinIO构建用于机器学习,分析和应用程序数据工作负载的高性能基础架构。

官网地址:https://min.io/
文档地址:https://docs.min.io/

二、使用docker 搭建minio 服务

GNU / Linux和macOS

  1. docker run -p 9000:9000 \
  2. --name minio1 \
  3. -v /mnt/data:/data \
  4. -e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \
  5. -e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
  6. minio/minio server /data

windows

  1. docker run -p 9000:9000 \
  2. --name minio1 \
  3. -v D:\data:/data \
  4. -e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \
  5. -e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
  6. minio/minio server /data
  • MINIO_ROOT_USER:为用户key
  • MINIO_ROOT_PASSWORD:为用户密钥

以上搭建的都是单机版的。想要了解分布式 的方式请查看官网文档。
当启动后在浏览器访问http://localhost:9000就可以访问minio的图形化界面了。

三、下面开始搭建SpringBoot环境

初始化一个SpringBoot项目大家都会,这里不多做介绍。

需要引入的依赖

  1. <!-- thymeleaf模板渲染引擎-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  5. </dependency>
  6. <!-- 操作minio的java客户端-->
  7. <dependency>
  8. <groupId>io.minio</groupId>
  9. <artifactId>minio</artifactId>
  10. <version>8.2.1</version>
  11. </dependency>
  12. <!-- lombok插件-->
  13. <dependency>
  14. <groupId>org.projectlombok</groupId>
  15. <artifactId>lombok</artifactId>
  16. <optional>true</optional>
  17. </dependency>

依赖可以官方文档里找:https://docs.min.io/docs/java-client-quickstart-guide.html

配置文件

  1. spring:
  2. servlet:
  3. multipart:
  4. max-file-size: 10MB
  5. max-request-size: 10MB
  6. #minio配置
  7. minio:
  8. access-key: AKIAIOSFODNN7EXAMPLE #key就是docker初始化是设置的,密钥相同
  9. secret-key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
  10. url: http://localhost:9000
  11. bucket-name: wdhcr
  12. thymeleaf:
  13. cache: false

创建minio的配置类:

  1. @Configuration
  2. @ConfigurationProperties(prefix = "spring.minio")
  3. @Data
  4. public class MinioConfiguration {
  5. private String accessKey;
  6. private String secretKey;
  7. private String url;
  8. private String bucketName;
  9. @Bean
  10. public MinioClient minioClient() {
  11. return MinioClient.builder()
  12. .endpoint(url)
  13. .credentials(accessKey, secretKey)
  14. .build();
  15. }
  16. }

使用配置属性绑定进行参数绑定,并初始化一个minio client对象放入容器中。

封装的minio client 操作minio的简单方法的组件

  1. @Component
  2. public class MinioComp {
  3. @Autowired
  4. private MinioClient minioClient;
  5. @Autowired
  6. private MinioConfiguration configuration;
  7. /**
  8. * @description: 获取上传临时签名
  9. * @dateTime: 2021/5/13 14:12
  10. */
  11. public Map getPolicy(String fileName, ZonedDateTime time) {
  12. PostPolicy postPolicy = new PostPolicy(configuration.getBucketName(), time);
  13. postPolicy.addEqualsCondition("key", fileName);
  14. try {
  15. Map<String, String> map = minioClient.getPresignedPostFormData(postPolicy);
  16. HashMap<String, String> map1 = new HashMap<>();
  17. map.forEach((k,v)->{
  18. map1.put(k.replaceAll("-",""),v);
  19. });
  20. map1.put("host",configuration.getUrl()+"/"+configuration.getBucketName());
  21. return map1;
  22. } catch (ErrorResponseException e) {
  23. e.printStackTrace();
  24. } catch (InsufficientDataException e) {
  25. e.printStackTrace();
  26. } catch (InternalException e) {
  27. e.printStackTrace();
  28. } catch (InvalidKeyException e) {
  29. e.printStackTrace();
  30. } catch (InvalidResponseException e) {
  31. e.printStackTrace();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. } catch (NoSuchAlgorithmException e) {
  35. e.printStackTrace();
  36. } catch (ServerException e) {
  37. e.printStackTrace();
  38. } catch (XmlParserException e) {
  39. e.printStackTrace();
  40. }
  41. return null;
  42. }
  43. /**
  44. * @description: 获取上传文件的url
  45. * @dateTime: 2021/5/13 14:15
  46. */
  47. public String getPolicyUrl(String objectName, Method method, int time, TimeUnit timeUnit) {
  48. try {
  49. return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
  50. .method(method)
  51. .bucket(configuration.getBucketName())
  52. .object(objectName)
  53. .expiry(time, timeUnit).build());
  54. } catch (ErrorResponseException e) {
  55. e.printStackTrace();
  56. } catch (InsufficientDataException e) {
  57. e.printStackTrace();
  58. } catch (InternalException e) {
  59. e.printStackTrace();
  60. } catch (InvalidKeyException e) {
  61. e.printStackTrace();
  62. } catch (InvalidResponseException e) {
  63. e.printStackTrace();
  64. } catch (IOException e) {
  65. e.printStackTrace();
  66. } catch (NoSuchAlgorithmException e) {
  67. e.printStackTrace();
  68. } catch (XmlParserException e) {
  69. e.printStackTrace();
  70. } catch (ServerException e) {
  71. e.printStackTrace();
  72. }
  73. return null;
  74. }
  75. /**
  76. * @description: 上传文件
  77. * @dateTime: 2021/5/13 14:17
  78. */
  79. public void upload(MultipartFile file, String fileName) {
  80. // 使用putObject上传一个文件到存储桶中。
  81. try {
  82. InputStream inputStream = file.getInputStream();
  83. minioClient.putObject(PutObjectArgs.builder()
  84. .bucket(configuration.getBucketName())
  85. .object(fileName)
  86. .stream(inputStream, file.getSize(), -1)
  87. .contentType(file.getContentType())
  88. .build());
  89. } catch (ErrorResponseException e) {
  90. e.printStackTrace();
  91. } catch (InsufficientDataException e) {
  92. e.printStackTrace();
  93. } catch (InternalException e) {
  94. e.printStackTrace();
  95. } catch (InvalidKeyException e) {
  96. e.printStackTrace();
  97. } catch (InvalidResponseException e) {
  98. e.printStackTrace();
  99. } catch (IOException e) {
  100. e.printStackTrace();
  101. } catch (NoSuchAlgorithmException e) {
  102. e.printStackTrace();
  103. } catch (ServerException e) {
  104. e.printStackTrace();
  105. } catch (XmlParserException e) {
  106. e.printStackTrace();
  107. }
  108. }
  109. /**
  110. * @description: 根据filename获取文件访问地址
  111. * @dateTime: 2021/5/17 11:28
  112. */
  113. public String getUrl(String objectName, int time, TimeUnit timeUnit) {
  114. String url = null;
  115. try {
  116. url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
  117. .method(Method.GET)
  118. .bucket(configuration.getBucketName())
  119. .object(objectName)
  120. .expiry(time, timeUnit).build());
  121. } catch (ErrorResponseException e) {
  122. e.printStackTrace();
  123. } catch (InsufficientDataException e) {
  124. e.printStackTrace();
  125. } catch (InternalException e) {
  126. e.printStackTrace();
  127. } catch (InvalidKeyException e) {
  128. e.printStackTrace();
  129. } catch (InvalidResponseException e) {
  130. e.printStackTrace();
  131. } catch (IOException e) {
  132. e.printStackTrace();
  133. } catch (NoSuchAlgorithmException e) {
  134. e.printStackTrace();
  135. } catch (XmlParserException e) {
  136. e.printStackTrace();
  137. } catch (ServerException e) {
  138. e.printStackTrace();
  139. }
  140. return url;
  141. }
  142. }

简单说明:

  • 使用MultipartFile接收前端文件流,再上传到minio。
  • 构建一个formData的签名数据,给前端,让前端之前上传到minio。
  • 构建一个可以上传的临时URL给前端,前端通过携带文件请求该URL进行上传。
  • 使用filename请求服务端获取临时访问文件的URL。(最长时间为7 天,想要永久性访问,需要其他设置,这里不做说明。)

    展示页面html,使用的是VUE+element-ui进行渲染

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="UTF-8">
    5. <!-- import CSS -->
    6. <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    7. <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    8. <title>上传图片</title>
    9. </head>
    10. <body>
    11. <div id="app">
    12. <el-row :gutter="2">
    13. <el-col :span="8">
    14. <div class="div-center-class">
    15. <div class="">
    16. <center><h3>传统上传</h3></center>
    17. <el-upload
    18. class="upload-demo"
    19. action="#"
    20. drag
    21. :http-request="uploadHandle">
    22. <i class="el-icon-upload"></i>
    23. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    24. <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
    25. </el-upload>
    26. <div v-if="imgUrl">
    27. <img :src="imgUrl" style="width: 40px;height: 40px"></img>
    28. </div>
    29. </div>
    30. </div>
    31. </el-col>
    32. <el-col :span="8">
    33. <div class="div-center-class">
    34. <div class="">
    35. <center><h3>前端formData直传</h3></center>
    36. <el-upload
    37. class="upload-demo"
    38. action="#"
    39. drag
    40. :http-request="httpRequestHandle">
    41. <i class="el-icon-upload"></i>
    42. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    43. <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
    44. </el-upload>
    45. <div v-if="directUrl">
    46. <img :src="directUrl" style="width: 40px;height: 40px"></img>
    47. </div>
    48. </div>
    49. </div>
    50. </el-col>
    51. <el-col :span="8">
    52. <div class="div-center-class">
    53. <div class="">
    54. <center><h3>前端Url直传</h3></center>
    55. <el-upload
    56. class="upload-demo"
    57. action="#"
    58. drag
    59. :http-request="UrlUploadHandle">
    60. <i class="el-icon-upload"></i>
    61. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    62. <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
    63. </el-upload>
    64. <div v-if="uploadUrl">
    65. <img :src="uploadUrl" style="width: 40px;height: 40px"></img>
    66. </div>
    67. </div>
    68. </div>
    69. </el-col>
    70. </el-row>
    71. </div>
    72. </body>
    73. <!-- import Vue before Element -->
    74. <script src="https://unpkg.com/vue/dist/vue.js"></script>
    75. <!-- import JavaScript -->
    76. <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    77. <!--import axios -->
    78. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    79. <script>
    80. new Vue({
    81. el: '#app',
    82. data: function () {
    83. return {
    84. imgUrl: '',
    85. directUrl: '',
    86. uploadUrl: ''
    87. }
    88. },
    89. methods: {
    90. uploadHandle(options) {
    91. let {file} = options;
    92. this.traditionPost(file);
    93. },
    94. traditionPost(file) {
    95. _that = this
    96. const form = new FormData();
    97. form.append("fileName", file.name);
    98. form.append("file", file);
    99. this.axiosPost("post", "/upload", form).then(function (res) {
    100. if (res.status === 200) {
    101. _that.imgUrl = res.data.data
    102. } else {
    103. alert("上传失败!")
    104. }
    105. })
    106. },
    107. getpolicy(file) {
    108. _that = this
    109. axios.get('policy?fileName=' + file.name)
    110. .then(function (response) {
    111. let {xamzalgorithm, xamzcredential, policy, xamzsignature, xamzdate, host} = response.data.data;
    112. let formData = new FormData();
    113. formData.append("key", file.name);
    114. formData.append("x-amz-algorithm", xamzalgorithm); // 让服务端返回200,不设置则默认返回204。
    115. formData.append("x-amz-credential", xamzcredential);
    116. formData.append("policy", policy);
    117. formData.append("x-amz-signature", xamzsignature);
    118. formData.append("x-amz-date", xamzdate);
    119. formData.append("file", file);
    120. // 发送 POST 请求
    121. _that.axiosPost("post", host, formData).then(function (res) {
    122. if (res.status === 204) {
    123. axios.get('url?fileName=' + file.name).then(function (res) {
    124. _that.directUrl = res.data.data;
    125. })
    126. } else {
    127. alert("上传失败!")
    128. }
    129. })
    130. })
    131. },
    132. httpRequestHandle(options) {
    133. let {file} = options;
    134. this.getpolicy(file);
    135. },
    136. UrlUploadHandle(options) {
    137. let {file} = options;
    138. this.getUploadUrl(file);
    139. },
    140. getUploadUrl(file) {
    141. _that = this
    142. console.log(file)
    143. axios.get('uploadUrl?fileName=' + file.name)
    144. .then(function (response) {
    145. let url = response.data.data;
    146. // 发送 put 请求
    147. let config = {'Content-Type': file.type}
    148. _that.axiosPost("put", url, file, config).then(function (res) {
    149. if (res.status === 200) {
    150. axios.get('url?fileName=' + file.name).then(function (res) {
    151. _that.uploadUrl = res.data.data;
    152. })
    153. } else {
    154. alert("上传失败!")
    155. }
    156. })
    157. })
    158. },
    159. //封装
    160. //axios封装post请求
    161. axiosPost(method, url, data, config) {
    162. let result = axios({
    163. method: method,
    164. url: url,
    165. data: data,
    166. headers: config
    167. }).then(resp => {
    168. return resp
    169. }).catch(error => {
    170. return "exception=" + error;
    171. });
    172. return result;
    173. }
    174. }
    175. })
    176. </script>
    177. <style>
    178. .div-center-class {
    179. padding: 28% 0%;
    180. text-align: center;
    181. background: beige;
    182. }
    183. </style>
    184. </html>

    image.png
    可以分别体验不同的实现效果。
    以上就是使用SpringBoot搭建基于minio的高性能存储服务的全部步骤了。