轻量级 S3 存储 Java 客户端
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 |
以下内容由 AI 翻译,如有问题请 点此提交 issue 反馈
A Lightweight S3 Client / S3 存储 Java 客户端
如今,直接将二进制文件存储在应用服务器上的做法已不多见。人们更倾向于使用“对象存储”解决方案,例如亚马逊 AWS S3、阿里云 OSS、Cloudflare R2 等。 AWS 是该领域的先驱,其 S3 协议几乎已成为事实上的行业标准。 大多数主流厂商的对象存储产品都已实现了这一协议。 几乎所有厂商都提供了便捷的 SDK,以便快速集成 S3 服务。 就复杂度而言,构建一个 S3 客户端并非难事。 出于一时的“造轮子”冲动,我决定着手开发自己的 S3 客户端。 该组件追求轻量级设计,包含不超过十个类,同时仍能覆盖日常对象存储操作所需的基本功能。
直接支持以下 S3 存储服务:
- Cloudflare R2
- Scaleway
- Backblaze
- 阿里云 OSS
- 网易 OSS
教程(中文):https://blog.csdn.net/zhangxin09/article/details/137671230。
源代码
安装
需要 Java 8 或更高版本。
<dependency>
<groupId>com.ajaxjs</groupId>
<artifactId>aj-s3client</artifactId>
<version>1.2</version>
</dependency>
单元测试的配置文件application.yml未随版本控制系统(VCS)提交。其内容如下:
S3Storage:
Nso:
accessKey: xx
accessSecret: xx
api: nos-eastchina1.126.net
bucket: xx
Oss:
accessKeyId: xx
secretAccessKey: xx
endpoint: oss-cn-beijing.aliyuncs.com
bucket: xx
LocalStorage: # 本地保存
absoluteSavePath: c:\temp\ # 若有此值,保存这个绝对路径上
使用方法
步骤 1:设置 OSS 参数
第一步是配置所有必要的 OSS 参数。这些参数在配置对象 Config 中有详细说明。以下是设置这些参数的示例:
/**
* 配置
*/
@Data
public class Config {
/**
* 访问 API
*/
private String endPoint;
/**
* 访问 Key
*/
private String accessKey;
/**
* 访问密钥
*/
private String secretKey;
/**
* 存储桶名称
*/
private String bucketName;
/**
* 签名中的标识,每个厂商不同
*/
private String remark;
}
此 POJO 可通过 Spring 注入到各厂商的实现中。 如果您未使用 Spring,最简单的方式是像这样手动实例化并配置它:
关于各种 OSS API 操作,请参考 S3Client 接口。 由于 Java 开发者通常对此较为熟悉,这里不再进行详细说明。
package com.ajaxjs.s3client;
import java.util.Map;
/**
* S3 客户端
*/
public interface S3Client {
/**
* 列出存储桶中的所有对象
*
* @return XML List
*/
String listBucket();
/**
* 列出存储桶中的所有对象
*
* @return XML List Map
*/
Map<String, String> listBucketXml();
/**
* 创建一个存储桶(Bucket)
*
* @param bucketName 存储桶的名称,必须全局唯一
* @return true=操作成功
*/
boolean createBucket(String bucketName);
/**
* 删除一个存储桶(Bucket)
*
* @param bucketName 存储桶的名称
* @return true=操作成功
*/
boolean deleteBucket(String bucketName);
/**
* 将字节数据上传到指定的存储桶中
*
* @param bucketName 存储桶的名称
* @param objectName 对象(文件)在存储桶中的名称
* @param fileBytes 要上传的文件的字节数据
* @return true=操作成功
*/
boolean putObject(String bucketName, String objectName, byte[] fileBytes);
/**
* 将字节数据上传到指定的存储桶中
*
* @param objectName 对象(文件)在存储桶中的名称
* @param fileBytes 要上传的文件的字节数据
* @return true=操作成功
*/
boolean putObject(String objectName, byte[] fileBytes);
/**
* 将字节数据上传到指定的存储桶中
*
* @param bucketName 存储桶的名称
* @param objectName 对象(文件)在存储桶中的名称
* @return true=操作成功
*/
boolean getObject(String bucketName, String objectName);
/**
* 将字节数据上传到指定的存储桶中
*
* @param objectName 对象(文件)在存储桶中的名称
* @return true=操作成功
*/
boolean getObject(String objectName);
/**
* 删除指定的文件
*
* @param bucketName 存储桶的名称
* @param objectName 要删除的文件名称
* @return true=操作成功
*/
boolean deleteObject(String bucketName, String objectName);
/**
* 删除指定的文件
*
* @param objectName 要删除的文件名称
* @return true=操作成功
*/
boolean deleteObject(String objectName);
}
开发心得
在实现这款客户端的过程中,我收获了以下几点心得:
- 依赖 HTTP 通信 尽管是对象存储服务,但 OSS 操作本质上围绕文件管理展开——上传、下载、删除文件,以及处理“Bucket”。所有这些交互都通过 HTTP API 完成。因此,客户端必须封装一个 HttpClient 组件来妥善处理 API 调用。Cloudflare R2 为其 API 提供了 Postman 示例,这对我重构 Java HTTP 请求帮助很大。
- 身份验证与请求签名 与任何基于协议的通信一样,身份验证是一大挑战。S3 协议要求每个 HTTP 请求都包含
Authorization头。生成有效的签名是一个相对复杂的过程,涉及多个参数和加密步骤,这部分代码占了相当大的比重。主要有两个签名版本:SigV2 和 SigV4。本组件同时支持这两种协议。 - 厂商间的差异 尽管大多数厂商都实现了 S3 协议,但他们各自的 API 往往存在细微差异。为了适应这些差异,代码设计采用了继承结构:
BaseS3Client–>BaseS3ClientSigV2/BaseS3ClientSigV4–>厂商特定实现。这种结构允许在基础层共享逻辑,同时支持按厂商进行定制。 - 签名生成是最棘手的部分 开发过程中最具挑战性的部分是生成正确的签名。即使是一个小错误也可能导致签名无效,使服务器因验证失败而拒绝请求。在两个版本中,SigV2 相对简单,而 SigV4 涉及更多步骤和更严格的规则。因此,我找到了一个很棒的开源实现,名为 aws-v4-signer-java——它零依赖,非常符合我的需求。我对其代码进行了大量简化和重构,以更好地适应本项目。此外,阿里云关于 S3 兼容签名的文档也非常清晰且有帮助。更多详情,请参考他们的文档和源代码。
- 单元测试仍然极其重要——尤其是在开发或重构组件时。很多情况下,没有单元测试,重现问题会非常困难。扎实的测试覆盖率也能显著提高开发效率。
本组件并非完整的 S3 客户端实现。它仅涵盖了我们软件所需的一小部分 S3 功能。设计目标是简洁、最少的外部依赖以及低内存占用。