当前位置:   article > 正文

AWS S3 协议对接 minio/oss 等_minio s3协议

minio s3协议
使用亚马逊 S3 协议访问对象存储
[s3-API](https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_Operations_Amazon_Simple_Storage_Service.html)

- 兼容S3协议的对象存储有
  - minio
    - 似乎是完全兼容 [兼容文档](https://www.minio.org.cn/product/s3-compatibility.html)
  - 阿里云oss
    - [兼容主要的 API ](https://help.aliyun.com/zh/oss/developer-reference/compatibility-with-amazon-s3?spm=a2c4g.11186623.0.0.590b32bcHb4D6a)
  - 七牛云oss
  - 等等

依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.projectlombok</groupId>
  8. <artifactId>lombok</artifactId>
  9. <optional>true</optional>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-test</artifactId>
  14. <scope>test</scope>
  15. </dependency>
  16. <!--使用的依赖-->
  17. <dependency>
  18. <groupId>com.amazonaws</groupId>
  19. <artifactId>aws-java-sdk-s3</artifactId>
  20. <version>1.12.522</version>
  21. </dependency>
  22. <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
  23. <dependency>
  24. <groupId>org.apache.commons</groupId>
  25. <artifactId>commons-lang3</artifactId>
  26. <version>3.12.0</version>
  27. </dependency>
  28. </dependencies>

读取配置

  1. package com.xx.awss3demo.config;
  2. import lombok.Data;
  3. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  4. import org.springframework.boot.context.properties.ConfigurationProperties;
  5. import org.springframework.stereotype.Component;
  6. @Data
  7. @ConfigurationProperties(prefix = "s3")
  8. @Component
  9. public class S3Properties {
  10. /**
  11. * 对象存储服务的URL
  12. */
  13. private String endpoint;
  14. /**
  15. * path-style nginx 反向代理和S3默认支持
  16. * 模式 {http://bucketname.endpoint} -- true
  17. * 模式 {http://endpoint/bucketname} -- false
  18. */
  19. private Boolean pathStyleAccess = false;
  20. /**
  21. * 区域
  22. */
  23. private String region;
  24. /**
  25. * Access key就像用户ID,可以唯一标识你的账户
  26. */
  27. private String accessKey;
  28. /**
  29. * Secret key是你账户的密码
  30. */
  31. private String secretKey;
  32. /**
  33. * 最大线程数,默认: 100
  34. */
  35. private Integer maxConnections = 50;
  36. }

配置文件

  1. server:
  2. port: 8888
  3. s3:
  4. # aliyun oss
  5. #endpoint: http://oss-cn-shanghai.aliyuncs.com
  6. #accessKey:
  7. #secretKey:
  8. # minio
  9. endpoint: http://192.168.1.1:9000
  10. accessKey: admin
  11. secretKey: admin1234
  12. bucketName: lqs3bucket
  13. region:
  14. maxConnections: 100

文件操作

  1. package com.xx.awss3demo.service;
  2. import com.amazonaws.ClientConfiguration;
  3. import com.amazonaws.ClientConfigurationFactory;
  4. import com.amazonaws.auth.AWSStaticCredentialsProvider;
  5. import com.amazonaws.auth.BasicAWSCredentials;
  6. import com.amazonaws.client.builder.AwsClientBuilder;
  7. import com.amazonaws.services.s3.AmazonS3;
  8. import com.amazonaws.services.s3.AmazonS3ClientBuilder;
  9. import com.amazonaws.services.s3.model.*;
  10. import com.amazonaws.util.IOUtils;
  11. import com.liuqi.awss3demo.config.S3Properties;
  12. import lombok.SneakyThrows;
  13. import lombok.extern.log4j.Log4j2;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  16. import org.springframework.stereotype.Service;
  17. import org.springframework.web.multipart.MultipartFile;
  18. import javax.annotation.PostConstruct;
  19. import java.io.*;
  20. import java.net.URL;
  21. import java.util.*;
  22. import java.util.concurrent.ExecutorService;
  23. import java.util.concurrent.Executors;
  24. @ConditionalOnClass(S3Properties.class)
  25. @Service
  26. @Log4j2
  27. public class S3FileService {
  28. @Autowired
  29. private S3Properties s3Properties;
  30. private AmazonS3 amazonS3;
  31. @PostConstruct
  32. public void init() {
  33. log.info(s3Properties);
  34. amazonS3 = AmazonS3ClientBuilder.standard()
  35. .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(s3Properties.getAccessKey(), s3Properties.getSecretKey())))
  36. .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(
  37. s3Properties.getEndpoint(),
  38. s3Properties.getRegion()))
  39. .withPathStyleAccessEnabled(s3Properties.getPathStyleAccess())
  40. .withChunkedEncodingDisabled(true)
  41. .withClientConfiguration(new ClientConfiguration()
  42. .withMaxConnections(s3Properties.getMaxConnections())
  43. .withMaxErrorRetry(1))
  44. .build();
  45. }
  46. /**
  47. * 创建bucket
  48. * 注意:bucket name 不允许有特殊字符及大写字母
  49. *
  50. * @param bucketName bucket名称
  51. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/CreateBucket">AWS API
  52. * Documentation</a>
  53. */
  54. @SneakyThrows
  55. public void createBucket(String bucketName) {
  56. if (!bucketName.toLowerCase().equals(bucketName)) {
  57. throw new RuntimeException("bucket name not allow upper case");
  58. }
  59. if (checkBucketExist(bucketName)) {
  60. log.info("bucket: {} 已经存在", bucketName);
  61. return;
  62. }
  63. amazonS3.createBucket((bucketName));
  64. }
  65. @SneakyThrows
  66. public boolean checkBucketExist(String bucketName) {
  67. return amazonS3.doesBucketExistV2(bucketName);
  68. }
  69. /**
  70. * 获取全部bucket
  71. * <p>
  72. *
  73. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
  74. * API Documentation</a>
  75. */
  76. @SneakyThrows
  77. public List<Bucket> getAllBuckets() {
  78. return amazonS3.listBuckets();
  79. }
  80. /**
  81. * 根据bucket获取bucket详情
  82. *
  83. * @param bucketName bucket名称
  84. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
  85. * API Documentation</a>
  86. */
  87. @SneakyThrows
  88. public Optional<Bucket> getBucket(String bucketName) {
  89. return amazonS3.listBuckets().stream().filter(b -> b.getName().equals(bucketName)).findFirst();
  90. }
  91. /**
  92. * @param bucketName bucket名称
  93. * @see <a href=
  94. * "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteBucket">AWS API
  95. * Documentation</a>
  96. */
  97. @SneakyThrows
  98. public void removeBucket(String bucketName) {
  99. amazonS3.deleteBucket(bucketName);
  100. }
  101. /**
  102. * 复制文件
  103. * @param bucketName
  104. * @param srcObjectName
  105. * @param tarObjectName
  106. */
  107. public void copyObject(String bucketName, String srcObjectName,String tarObjectName){
  108. amazonS3.copyObject(bucketName,srcObjectName,bucketName,tarObjectName);
  109. }
  110. /**
  111. * 上传文件,指定文件类型
  112. *
  113. * @param bucketName bucket名称
  114. * @param objectName 文件名称
  115. * @param stream 文件流
  116. * @param contextType 文件类型
  117. * @throws Exception
  118. */
  119. @SneakyThrows
  120. public void putObject(String bucketName, String objectName, InputStream stream,
  121. String contextType) {
  122. ObjectMetadata objectMetadata = new ObjectMetadata();
  123. objectMetadata.setContentLength(stream.available());
  124. objectMetadata.setContentType(contextType);
  125. putObject(bucketName, objectName, stream, objectMetadata);
  126. }
  127. /**
  128. * 上传文件
  129. *
  130. * @param bucketName bucket名称
  131. * @param objectName 文件名称
  132. * @param stream 文件流
  133. * @throws Exception
  134. */
  135. @SneakyThrows
  136. public void putObject(String bucketName, String objectName, InputStream stream) {
  137. ObjectMetadata objectMetadata = new ObjectMetadata();
  138. objectMetadata.setContentLength(stream.available());
  139. objectMetadata.setContentType("application/octet-stream");
  140. putObject(bucketName, objectName, stream, objectMetadata);
  141. }
  142. /**
  143. * 上传文件
  144. *
  145. * @param bucketName bucket名称
  146. * @param objectName 文件名称
  147. * @param stream 文件流
  148. * @param objectMetadata 对象元数据
  149. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/PutObject">AWS
  150. * API Documentation</a>
  151. */
  152. @SneakyThrows
  153. private PutObjectResult putObject(String bucketName, String objectName, InputStream stream,
  154. ObjectMetadata objectMetadata) {
  155. byte[] bytes = IOUtils.toByteArray(stream);
  156. ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
  157. // 上传
  158. return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
  159. }
  160. /**
  161. * 判断object是否存在
  162. *
  163. * @param bucketName bucket名称
  164. * @param objectName 文件名称
  165. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
  166. * API Documentation</a>
  167. */
  168. @SneakyThrows
  169. public boolean checkObjectExist(String bucketName, String objectName) {
  170. return amazonS3.doesObjectExist(bucketName, objectName);
  171. }
  172. /**
  173. * 获取文件
  174. *
  175. * @param bucketName bucket名称
  176. * @param objectName 文件名称
  177. * @return 二进制流
  178. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
  179. * API Documentation</a>
  180. */
  181. @SneakyThrows
  182. public S3Object getObject(String bucketName, String objectName) {
  183. return amazonS3.getObject(bucketName, objectName);
  184. }
  185. /**
  186. * 删除文件
  187. *
  188. * @param bucketName bucket名称
  189. * @param objectName 文件名称
  190. * @throws Exception
  191. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteObject">AWS
  192. * API Documentation</a>
  193. */
  194. @SneakyThrows
  195. public void deleteObject(String bucketName, String objectName) {
  196. amazonS3.deleteObject(bucketName, objectName);
  197. }
  198. /**
  199. * 大文件分段上传
  200. *
  201. * @param file MultipartFile
  202. * @param bucketName bucketName
  203. * @param objectName objectName
  204. * @param minPartSize 每片大小,单位:字节(eg:5242880 <- 5m)
  205. */
  206. public void uploadMultipartFileByPart(MultipartFile file, String bucketName, String objectName,
  207. int minPartSize) {
  208. if (file.isEmpty()) {
  209. log.error("file is empty");
  210. }
  211. // 计算分片大小
  212. long size = file.getSize();
  213. // 得到总共的段数,和 分段后,每个段的开始上传的字节位置
  214. List<Long> positions = Collections.synchronizedList(new ArrayList<>());
  215. long filePosition = 0;
  216. while (filePosition < size) {
  217. positions.add(filePosition);
  218. filePosition += Math.min(minPartSize, (size - filePosition));
  219. }
  220. if (log.isDebugEnabled()) {
  221. log.debug("总大小:{},分为{}段", size, positions.size());
  222. }
  223. // 创建一个列表保存所有分传的 PartETag, 在分段完成后会用到
  224. List<PartETag> partETags = Collections.synchronizedList(new ArrayList<>());
  225. // 第一步,初始化,声明下面将有一个 Multipart Upload
  226. // 设置文件类型
  227. ObjectMetadata metadata = new ObjectMetadata();
  228. metadata.setContentType(file.getContentType());
  229. InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName,
  230. objectName, metadata);
  231. InitiateMultipartUploadResult initResponse = this.initiateMultipartUpload(initRequest);
  232. if (log.isDebugEnabled()) {
  233. log.debug("开始上传");
  234. }
  235. //声明线程池
  236. ExecutorService exec = Executors.newFixedThreadPool(3);
  237. long begin = System.currentTimeMillis();
  238. try {
  239. // MultipartFile 转 File
  240. File toFile = multipartFileToFile(file);
  241. for (int i = 0; i < positions.size(); i++) {
  242. int finalI = i;
  243. exec.execute(() -> {
  244. long time1 = System.currentTimeMillis();
  245. UploadPartRequest uploadRequest = new UploadPartRequest()
  246. .withBucketName(bucketName)
  247. .withKey(objectName)
  248. .withUploadId(initResponse.getUploadId())
  249. .withPartNumber(finalI + 1)
  250. .withFileOffset(positions.get(finalI))
  251. .withFile(toFile)
  252. .withPartSize(Math.min(minPartSize, (size - positions.get(finalI))));
  253. // 第二步,上传分段,并把当前段的 PartETag 放到列表中
  254. partETags.add(this.uploadPart(uploadRequest).getPartETag());
  255. if (log.isDebugEnabled()) {
  256. log.debug("第{}段上传耗时:{}", finalI + 1, (System.currentTimeMillis() - time1));
  257. }
  258. });
  259. }
  260. //任务结束关闭线程池
  261. exec.shutdown();
  262. //判断线程池是否结束,不加会直接结束方法
  263. while (true) {
  264. if (exec.isTerminated()) {
  265. break;
  266. }
  267. }
  268. // 第三步,完成上传,合并分段
  269. CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(
  270. bucketName,
  271. objectName,
  272. initResponse.getUploadId(), partETags);
  273. this.completeMultipartUpload(compRequest);
  274. //删除本地缓存文件
  275. if (toFile != null && !toFile.delete()) {
  276. log.error("Failed to delete cache file");
  277. }
  278. } catch (Exception e) {
  279. this.abortMultipartUpload(
  280. new AbortMultipartUploadRequest(bucketName, objectName,
  281. initResponse.getUploadId()));
  282. log.error("Failed to upload, " + e.getMessage());
  283. }
  284. if (log.isDebugEnabled()) {
  285. log.debug("总上传耗时:{}", (System.currentTimeMillis() - begin));
  286. }
  287. }
  288. /**
  289. * 根据文件前置查询文件集合
  290. *
  291. * @param bucketName bucket名称
  292. * @param prefix 前缀
  293. * @param recursive 是否递归查询
  294. * @return S3ObjectSummary 列表
  295. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
  296. * API Documentation</a>
  297. */
  298. @SneakyThrows
  299. public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix,
  300. boolean recursive) {
  301. ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
  302. return new ArrayList<>(objectListing.getObjectSummaries());
  303. }
  304. /**
  305. * 查询文件版本
  306. *
  307. * @param bucketName bucket名称
  308. * @return S3ObjectSummary 列表
  309. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
  310. * API Documentation</a>
  311. */
  312. @SneakyThrows
  313. public List<S3VersionSummary> getAllObjectsVersionsByPrefixV2(String bucketName,
  314. String objectName) {
  315. VersionListing versionListing = amazonS3.listVersions(bucketName, objectName);
  316. return new ArrayList<>(versionListing.getVersionSummaries());
  317. }
  318. /**
  319. * 获取文件外链
  320. *
  321. * @param bucketName bucket名称
  322. * @param objectName 文件名称
  323. * @param expires 过期时间 <=7 单位天
  324. * @return url
  325. */
  326. @SneakyThrows
  327. public String generatePresignedUrl(String bucketName, String objectName, Integer expires) {
  328. Date date = new Date();
  329. Calendar calendar = new GregorianCalendar();
  330. calendar.setTime(date);
  331. calendar.add(Calendar.DAY_OF_MONTH, expires);
  332. URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
  333. return url.toString();
  334. }
  335. /**
  336. * 开放链接,默认public没有设置访问权限
  337. * url 规则:${endPoint}/${bucketName}/${objectName}
  338. *
  339. * @param bucketName
  340. * @param objectName
  341. * @return
  342. */
  343. public String generatePublicUrl(String bucketName, String objectName) {
  344. return s3Properties.getEndpoint() + "/" + bucketName + "/" + objectName;
  345. }
  346. /**
  347. * 初始化,声明有一个Multipart Upload
  348. *
  349. * @param initRequest 初始化请求
  350. * @return 初始化返回
  351. */
  352. private InitiateMultipartUploadResult initiateMultipartUpload(
  353. InitiateMultipartUploadRequest initRequest) {
  354. return amazonS3.initiateMultipartUpload(initRequest);
  355. }
  356. /**
  357. * 上传分段
  358. *
  359. * @param uploadRequest 上传请求
  360. * @return 上传分段返回
  361. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/UploadPart">AWS
  362. * API Documentation</a>
  363. */
  364. private UploadPartResult uploadPart(UploadPartRequest uploadRequest) {
  365. return amazonS3.uploadPart(uploadRequest);
  366. }
  367. /**
  368. * 分段合并
  369. *
  370. * @param compRequest 合并请求
  371. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/CompleteMultipartUpload">AWS
  372. * API Documentation</a>
  373. */
  374. private CompleteMultipartUploadResult completeMultipartUpload(
  375. CompleteMultipartUploadRequest compRequest) {
  376. return amazonS3.completeMultipartUpload(compRequest);
  377. }
  378. /**
  379. * 中止分片上传
  380. *
  381. * @param uploadRequest 中止文件上传请求
  382. * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/AbortMultipartUpload">AWS
  383. * API Documentation</a>
  384. */
  385. private void abortMultipartUpload(AbortMultipartUploadRequest uploadRequest) {
  386. amazonS3.abortMultipartUpload(uploadRequest);
  387. }
  388. /**
  389. * MultipartFile 转 File
  390. */
  391. private File multipartFileToFile(MultipartFile file) throws Exception {
  392. File toFile = null;
  393. if (file.equals("") || file.getSize() <= 0) {
  394. file = null;
  395. } else {
  396. InputStream ins = null;
  397. ins = file.getInputStream();
  398. toFile = new File(file.getOriginalFilename());
  399. //获取流文件
  400. OutputStream os = new FileOutputStream(toFile);
  401. int bytesRead = 0;
  402. byte[] buffer = new byte[8192];
  403. while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
  404. os.write(buffer, 0, bytesRead);
  405. }
  406. os.close();
  407. ins.close();
  408. }
  409. return toFile;
  410. }
  411. }

测试方法

  1. package com.xx.awss3demo;
  2. import com.amazonaws.services.s3.model.S3Object;
  3. import com.amazonaws.services.s3.model.S3ObjectSummary;
  4. import com.liuqi.awss3demo.service.S3FileService;
  5. import lombok.extern.log4j.Log4j2;
  6. import org.junit.jupiter.api.Test;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import java.io.ByteArrayInputStream;
  10. import java.io.IOException;
  11. import java.nio.charset.StandardCharsets;
  12. import java.util.List;
  13. @SpringBootTest
  14. @Log4j2
  15. class AwsS3DemoApplicationTests {
  16. @Autowired
  17. private S3FileService s3FileService;
  18. public String bk="lqs3bucket";
  19. @Test
  20. void contextLoads() {
  21. }
  22. @Test
  23. public void bucketTest() {
  24. s3FileService.createBucket(bk);
  25. s3FileService.getAllBuckets().forEach(b -> System.out.println(b.getName()));
  26. s3FileService.removeBucket(bk);
  27. }
  28. @Test
  29. public void objectTest() throws IOException {
  30. s3FileService.createBucket(bk);
  31. if (s3FileService.checkObjectExist(bk, "d1/ss/1.txt")) {
  32. log.info("文件已经存在");
  33. }
  34. s3FileService.putObject(bk,"d1/ss/1.txt",new ByteArrayInputStream("hello world xxx".getBytes(StandardCharsets.UTF_8)));
  35. s3FileService.copyObject(bk,"d1/ss/1.txt","d1/ss/1_copy.txt");
  36. S3Object object = s3FileService.getObject(bk, "d1/ss/1_copy.txt");
  37. byte[] bytes = object.getObjectContent().readAllBytes();
  38. log.info("内容是:{}",new String(bytes,StandardCharsets.UTF_8));
  39. //s3FileService.deleteObject(bk,"1.txt");
  40. }
  41. @Test
  42. public void listTest(){
  43. List<S3ObjectSummary> objectList = s3FileService.getAllObjectsByPrefix(bk, "/d1", true);
  44. objectList.forEach(object->{
  45. log.info(object.getKey());
  46. });
  47. }
  48. @Test
  49. public void genUrlTest(){
  50. String s = s3FileService.generatePresignedUrl(bk, "1.txt", 7);
  51. System.out.println(s);
  52. }
  53. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/554365
推荐阅读
相关标签
  

闽ICP备14008679号