[原创] 通过第三方工具/JAVA程序访问S3存储

S3是亚马逊2006年推出的简单存储服务(Simple Storage Service),理论上是一个全球存储区域网络,你可以把它想像成一个超大的硬盘,可以在其中存储和检索数字资产,通过 S3 存储和检索的资产被称为对象,对象存储在存储段(bucket)中。
很多公司都推出了自己的对象存储服务,例如阿里云对象存储服务OSS,可以使用S3 API进行访问。

话不多说,我们看看如何读写S3。
通过第三方工具
这种工具很多,s3-command-line 就是一个。这是一个GoLang开发的命令行工具,编译出来只有13MB。它支持文件的上传、下载、删除、列表,已经满足了大多数使用场景需求。
checkout它的源码,在安装有Go语言环境的服务器上执行 go build 即可编译出可执行程序 s3-command-line,这个只需要做一次,最终运行 s3-command-line 软件的服务器上并不需要安装GoLang环境。
在使用 s3-command-line 之前,我们需要创建一个配置文件 config.json,填入我们访问S3所需的密钥等信息:

{
  "id""...",
  "secret""...",
  "endPoint""http://...",
  "region""..."
}

文章来源:https://www.codelast.com/
这些信息你可以在你使用的S3平台上找到。
假设我们要读写的bucket名为 my-bucket,那么下面的命令会告诉你怎么使用 s3-command-line:

列出指定 bucket 里的文件:
./s3-command-line ls -c config.json my-bucket /

从指定的 bucket 删除文件 1.txt:
./s3-command-line rm -c config.json my-bucket 1.txt

上传本地文件 1.txt 到指定的 bucket:
./s3-command-line put -c config.json my-bucket 1.txt

从指定的 bucket 下载文件 1.txt 到本地:
./s3-command-line get -c config.json my-bucket 1.txt

文章来源:https://www.codelast.com/
总结:简单好用,编译成可执行程序后没有对其他library的依赖,令人舒适。

 自己写JAVA程序
JDK版本:1.8.0_221,Maven:3.6.3
首先你需要在 pom.xml 中添加相关依赖(参考这里):

<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>aws-sdk-java</artifactId>
  <version>2.18.16</version>
</dependency>

文章来源:https://www.codelast.com/
然后就可以开始写代码了(测试可用):

package com.codelast;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;

/**
 * S3存储操作类。
 * 参考:https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/PutObject.java
 *
 * @author DarranZhang@codelast.com
 * @version 2022-11-14
 */

@Slf4j
public class S3Operator {
  public static final String ACCESS_KEY = "your_key";
  public static final String SECRET_KEY = "your_secret";
  public static final String END_POINT = "http://your_end_point_url";
  public static final String REGION = "your_region";
  public static final String BUCKET_NAME = "your_bucket_name";

  private static class Options {
    @Option(name = "--localFile", usage = "the path of the local file to upload to S3", required = true)
    String localFile;
  }

  public static void main(String[] args) throws Exception {
    Options opts = new Options();
    CmdLineParser parser = new CmdLineParser(opts);
    try {
      parser.parseArgument(args);
    } catch (CmdLineException e) {
      log.error("command parse fail", e);
      System.exit(1);
    }

    S3Operator operator = new S3Operator();
    operator.upload(opts.localFile);
  }

  private void upload(String localFile) throws Exception {
    AwsBasicCredentials credentials = AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY);
    Region region = Region.of(REGION);
    S3Client s3Client = S3Client.builder()
      .endpointOverride(new URI(END_POINT))
      .region(region)
      .credentialsProvider(StaticCredentialsProvider.create(credentials))
      .build();

    String s3FileName = StringUtils.substringAfterLast(localFile, "/");  // 上传到S3之后的文件名,不一定要和本地文件名一致,但为了直观,这里保持一致
    if (StringUtils.isEmpty(s3FileName)) {
      String msg = "local file name is empty";
      log.error(msg);
      throw new RuntimeException(msg);
    }

    // 每次上传前,不用先删除再上传,因为最新上传的同名文件会覆盖之前的文件
    putObject(s3Client, BUCKET_NAME, s3FileName, localFile);
    s3Client.close();
  }

  public static void putObject(S3Client s3Client, String bucketName, String objectKey, String objectPath) {
    try {
      PutObjectRequest request = PutObjectRequest.builder().bucket(bucketName).key(objectKey).build();
      PutObjectResponse response = s3Client.putObject(request, RequestBody.fromBytes(getObjectFile(objectPath)));
      log.info("successfully upload file [{}] to S3, entity tag: [{}]", objectPath, response.eTag());
    } catch (S3Exception e) {
      log.error(String.format("failed to upload file [%s] to S3", objectPath), e);
    }
  }

  private static byte[] getObjectFile(String filePath) {
    FileInputStream fileInputStream = null;
    byte[] bytesArray = null;

    try {
      File file = new File(filePath);
      bytesArray = new byte[(int) file.length()];
      fileInputStream = new FileInputStream(file);
      fileInputStream.read(bytesArray);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fileInputStream != null) {
        try {
          fileInputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    return bytesArray;
  }

  private static void deleteObject(S3Client s3, String bucket, String s3FileName) {
    DeleteObjectRequest request = DeleteObjectRequest.builder().bucket(bucket).key(s3FileName).build();
    s3.deleteObject(request);
  }
}

这样我们就实现了用JAVA访问S3,不过,如果你仔细看会发现,当引入S3的依赖后,你的项目编译出的 fat jar 体积剧增了400MB左右!这也太恶心了...
当然,上面引入的 whole-SDK 依赖,是我为了省事引入了所有S3相关的模块,或许你有些用不到的模块,可以不用引入 whole-SDK,而是分别引入单独的模块,但我有空去想这些玩意的话,还不如使用 s3-command-line 简单呢,不是吗?毕竟时间就是生命,别在这些无聊的事上浪费生命。
文章来源:https://www.codelast.com/
 总结
如果你操作S3时只使用上传、下载、删除、列表功能,那么建议不要自己写代码,使用 s3-command-line 更香。

文章来源:https://www.codelast.com/
➤➤ 版权声明 ➤➤ 
转载需注明出处:codelast.com 
感谢关注我的微信公众号(微信扫一扫):
wechat qrcode of codelast
以及我的微信视频号:

发表评论