# object-storage
**Repository Path**: glodon/object-storageo
## Basic Information
- **Project Name**: object-storage
- **Description**: 云中立-对象存储
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: open_1.5.x
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 1
- **Created**: 2024-03-06
- **Last Updated**: 2024-12-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
Glodon Foundation Object Storage Client
======
代码依赖下载:
代码依赖中有两个包进行了处理,需要手动下载并放至本地maven仓库,
下载地址为:https:/static.goujianwu.com/201_project/package/multicloud/云中立二开依赖.zip
单元测试部分:
* 使用了maven-surefire-plugin作为单元测试执行插件
* 测试运行需要提供对象存储的endpoint,key和secret,以及对象存储的协议,目前有OSS和S3两种
* 单元测试会根据输入的信息,真实得连接对象存储服务。连接的仓库名为:yzl-storage-test,需要确保此仓库可访问
* 测试命令如下:
```sh
测试阿里云OSS
mvn test -Dclient.endpoint=http://oss-cn-beijing.aliyuncs.com -Dclient.key=xxxx -Dclient.secret=xxxx -Dclient.type=OSS
测试华为云S3
mvn test -Dclient.endpoint=http://obs.cn-north-1.myhuaweicloud.com -Dclient.key=xxxx -Dclient.secret=xxxx -Dclient.type=S3
```
Download
========
Download the latest JAR or grab via Maven:
```xml
com.glodon.paas.foundation
object-storage-core
1.3.13.SB1_5-SNAPSHOT
```
or Gradle:
```groovy
compile 'com.glodon.paas.foundation:objectstorage:0.0.1-SNAPSHOT'
```
# 签名直传
```java
PostPolicyRequest postPolicyRequest = new PostPolicyRequest(600, 1000L, "$bucket");
PostPolicyInfo info = client.generatePostPolicy(postPolicyRequest);
```
拿到 info 后,浏览器发起的请求应该如下:
```bash
curl -X POST ${info.host} \
-H 'content-type: multipart/form-data' \
-F 'key=ABCD.file' \
-F policy=${info.encodedPolicy} \
-F ${info.accessIdName}=${info.accessId} \
-F callback=${info.callback} \
-F signature=${info.postSignature} \
-F success_action_status=200 \
-F 'file=@/your/path/ABCD.file'
```
## 示例
服务端:
```java
@RestController
@CrossOrigin(allowCredentials = "true")
public class ObjectStorageController {
@Autowired
private ObjectClient objectClient;
@GetMapping("signature")
public String getSignature(){
PostPolicyRequest postPolicyRequest = new PostPolicyRequest(600, 10 * 1024 * 1024L, bucketName);
PostPolicyInfo postPolicyInfo = objectClient.generatePostPolicy(postPolicyRequest);
return JSONObject.toJSONString(postPolicyInfo);
}
}
```
前端html:
```html
OBS web直传
web直传
```
objectstorage.js文件:
```javascript
/**
* @Author: baijd-a
* @Date: 2020-10-26 15:58:29
* @LastEditTime: 2020-10-26 15:58:29
* @LastEditors: baijd-a
* @Description: 采用云中立平台的对象存储适配,下面代码演示了前端如何采用签名直传的方式上传文件到 OSS、S3
* 针对请求方式可采用 ajax axios
*/
/**
* 根据url从服务端获取签名直传的签名相关信息
* @param url
* @returns 签名相关信息
*/
function getSignature(url) {
let result = null;
$.ajax({
url: url,
type: "get",
async: false,
success: function (data) {
result = JSON.parse(data);
}
});
return result;
}
/**
* 通过 Ajax 上传文件到云服务
*
* 注意:
* 请求时 FormData 参数中的 Content-Type, Content-Disposition, keyPrefix 需要和后端返回的策略中保持一致
*
* @param file 文件
* @param accessId AK
* @param policy 上传策略 base64值
* @param signature 签名
* @param host 上传的host
* @param contentType 上传的Content-Type ===> 从服务端获取到的 Policy 中的值
* @param contentDisposition 展示形式:内联(inline),附件下载(attachment; filename="xxx") ===> 从服务端获取到的 Policy 中的值
* @param objectKeyPrefix 上传的key前缀
* @param isS3 是否是S3服务[华为云、腾讯云、MINIO、CEPH、AWS]
*/
function postObjectByAjax(file, accessId, policy, signature, host, contentType, contentDisposition, objectKeyPrefix, isS3) {
let form = new FormData();
if (isS3) {
form.append('AWSAccessKeyId', accessId); // S3 的 accessID; 若是上传到 S3 采用这个参数
} else {
form.append('OSSAccessKeyId', accessId); // OSS 的 accessID; 若是上传到 OSS 采用这个参数
}
form.append('policy', policy);
form.append('signature', signature);
if (contentType !== null && contentType !== '') {
form.append("Content-Type", contentType);
}
if (contentDisposition !== null && contentDisposition !== '') {
form.append("Content-Disposition", contentDisposition)
}
if (objectKeyPrefix == null) {
objectKeyPrefix = '';
} else if (objectKeyPrefix !== '' && !objectKeyPrefix.endsWith("/")) {
objectKeyPrefix += "/";
}
form.append('key', objectKeyPrefix + file.name); // key: 文件上传的完整路径 若有目录含有目录
form.append('success_action_status', 200); // 文件上传成功后返回的状态
form.append('file', file);
return _ajaxPostObject(host, form);
}
function _ajaxPostObject(url, formData) {
var result = false;
$.ajax({
type: "POST",
url: url,
data: formData,
processData: false,
cache: false, // 设置为false将不会从浏览器缓存中加载请求信息
async: false, // 发送同步请求
contentType: false, // 避免服务器不能正常解析文件
// dataType: 'JSONP', // jsonp只能提供get请求; 不涉及跨域, 写json即可
success: function (data) {
result = true;
}
});
return result;
}
/**
* 通过 Axios 上传文件到云服务
*
* 注意:
* 请求时 FormData 参数中的 Content-Type, Content-Disposition, keyPrefix需要和后端返回的策略中保持一致
*
* @param file 文件
* @param accessId AK
* @param policy 上传策略 base64值
* @param signature 签名
* @param host 上传的host
* @param contentType 上传的Content-Type ===> 从服务端获取到的 Policy 中的值
* @param contentDisposition 展示形式:内联(inline),附件下载(attachment; filename="xxx") ===> 从服务端获取到的 Policy 中的值
* @param objectKeyPrefix 上传的key前缀
* @param isS3 是否是S3服务[华为云、腾讯云、MINIO、CEPH、AWS]
*/
function postObjectByAxios(file, accessId, policy, signature, host, contentType, contentDisposition, objectKeyPrefix, isS3) {
let form = new FormData();
if (isS3) {
form.append('AWSAccessKeyId', accessId); // S3 的 accessID; 若是上传到 S3 采用这个参数
} else {
form.append('OSSAccessKeyId', accessId); // OSS 的 accessID; 若是上传到OSS 采用这个参数
}
form.append('policy', policy);
form.append('signature', signature);
form.append('signature', signature);
if (contentType !== null && contentType !== '') {
form.append("Content-Type", contentType);
}
if (contentDisposition !== null && contentDisposition !== '') {
form.append("Content-Disposition", contentDisposition)
}
if (objectKeyPrefix == null) {
objectKeyPrefix = '';
} else if (objectKeyPrefix !== '' && !objectKeyPrefix.endsWith("/")) {
objectKeyPrefix += "/";
}
form.append('key', objectKeyPrefix + file.name); // key: 文件上传的完整路径 若有目录含有目录
form.append('success_action_status', 200); // 文件上传成功后返回的状态,默认204
form.append('file', file); // 文件流
let config = {
header: {
'Content-Type': 'multipart/form-data;',
}
};
axios.post(host, form, config)
.then(res => {
console.log('上传成功');
})
.catch(error => {
console.log('上传失败');
console.log(error)
});
}
/**
* 判断对象是否为数组
* @param o 要判断的对象
* @returns {boolean} 判断结果
*/
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}
/**
* 将后端返回的结构中的策略 Policy json 进行解析
* @param postPolicyJson Policy json
* @returns {{contentDisposition: string, contentType: string, keyPrefix: string}}
*/
function resolvePostPolicy(postPolicyJson) {
// 解析从服务端获取到的签名和策略相关的信息
let contentDisposition = '';
let contentType = '';
let keyPrefix = '';
const postPolicy = JSON.parse(postPolicyJson);
const conditions = postPolicy.conditions;
for (let i = 0; i < conditions.length; i++) {
const condition = conditions[i];
if (isArray(condition) && condition[0] === 'starts-with') {
keyPrefix = condition[2];
} else {
if (condition['Content-Type'] !== undefined) {
contentType = condition['Content-Type'];
}
if (condition['Content-Disposition'] !== undefined) {
contentDisposition = condition['Content-Disposition'];
}
}
}
return {
contentType: contentType,
contentDisposition: contentDisposition,
keyPrefix: keyPrefix
}
}
```
服务端http://localhost:9000/signature返回结果:
```json
{
accessId: "xxxxx"
accessIdName: "OSSAccessKeyId"
encodedPolicy: "xxxxxxx"
expireTimeSeconds: 1603790181
host: "https://xxxxxx"
postPolicy: "{"expiration":"2020-10-27T17:16:21.203Z","conditions":[["content-length-range",0,10485760],{"bucket":"xxxxxx"},{"success_action_status":"200"},["starts-with","$key","test1/"]]}"
postSignature: "xxxxxx"
}
```
阿里云上传结果:
```txt
General
Request URL: https://xxxxxx.com/
Request Method: POST
Status Code: 200 OK
Remote Address: xxx.xxx.xxx.xxx
Referrer Policy: strict-origin-when-cross-origin
Form Data
OSSAccessKeyId: xxxxxx
policy: xxxxxx
key: test1/xxxxxx.txt
success_action_status: 200
file: (binary)
```
# 图片参数处理
- [OBS图片处理](https://support.huaweicloud.com/fg-obs/obs_01_0001.html)
- [OSS 图片处理](https://help.aliyun.com/document_detail/44686.html?spm=a2c4g.11186623.6.1281.5f01c1f6FmBBnn)
```java
// 先定义 DataProcessor
DataProcessor processor = new DataProcessor(DataProcessor.Type.IMAGE, Collections.singletonList("resize,w_100,h_100"));
// 最后调用下面两个方法之一
ObjectClient.getSignedURL(ObjectBasicInfo objectBasicInfo, int expiredTime, String fileName,SignedUrlBehavior behavior, DataProcessor processor)
RawClient.getSignedURL(SignedURLRequest signedURLRequest);
```
针对华为云和阿里OSS,图片处理是全功能支持的。
针对ceph、minIO、QingYunS3,图片处理功能是在object-storage中自己实现的。
实现方式是基于github上的开源项目thumbnailator:[前往GitHub查看](https://github.com/coobird/thumbnailator)。
仅支持了resize 操作,format操作。resize 功能与阿里云保持一致,但是调用resize,允许放大和缩小(没有实现limit_0和limit_1功能)。
在一个action中,参数优先级[w,h]>l>s>p,高优先级的参数将覆盖低优先级的参数操作,与出现的顺序无关,同一个参数出现多次,后面的会覆盖前面的,参数m没有优先级,
原理:先将图片下载下来,然后使用工具处理之后,将处理后的图片上传至同一个bucket下的ImageProcessTempFolder文件夹内。文件名字为:etag+actions+format
实现方式参见类:
```java
com.glodon.paas.foundation.objectstorage.interfaces.impl.ImageProcessFunction
```
# 视频截图
**第二步:定义 processor**
| 参数 | 描述 | 取值范围 |
| ---- | ------------------------------------------------------------ | -------------------- |
| t | 截图时间 | 单位ms,[0,视频时长] |
| w | 截图宽度,如果指定为0则按照比例自动计算 | 像素值:[0,视频宽度] |
| h | 截图高度,如果指定为0则按照比例自动计算,如果w和h都为0则输出为原视频宽高 | 像素值:[0,视频高度] |
| m | 截图模式,不指定则为默认模式,根据时间精确截图,如果指定为fast则截取该时间点之前的最近的一个关键帧 | 枚举值:fast |
| f | 输出图片格式 | 枚举值:jpg、png |
```java
String action = "snapshot,t_7000,f_jpg,w_800,h_600,m_fast";
DataProcessor processor = new DataProcessor(DataProcessor.Type.VIDEO, Collections.singletonList(action));
```
**第三步:获取截图**
最后调用下面两个方法之一。
```java
ObjectClient.getSignedURL(ObjectBasicInfo objectBasicInfo, int expiredTime, String fileName,SignedUrlBehavior behavior, DataProcessor processor));
RawClient.getSignedURL(SignedURLRequest signedURLRequest);
```
视频截图功能支持阿里云、华为云、ceph、minIO、QingYun
- [OSS 视频截图](https://help.aliyun.com/document_detail/64555.html?spm=a2c4g.11186623.6.1365.4125218cDRs2bR)
# 临时密钥访问(STS)
**第一步:初始化stsClient**
```java
StsClient stsClient;
final StsClientConfig.Builder configBuilder = StsClientConfig.builder();
configBuilder.accessKey(stsKey).accessSecret(stsSecret);
if (StringUtils.isNotBlank(stsEndpoint)) {
configBuilder.stsEndpoint(stsEndpoint);
}
switch (ClientType.valueOf(type)) {
case OSS: {
configBuilder.roleArn(roleArn);
break;
}
case S3_HW: {
configBuilder.imaDomainName(imaDomainName)
.imaUserName(imaUserName)
.imaPassword(imaPassword);
break;
}
case S3_COS: {
configBuilder.bucket(bucket)
.region(region);
break;
}
case S3_AWS:
case S3_AWS_V4:
case S3_MINIO: {
configBuilder.roleArn(roleArn)
.externalId(externalId);
break;
}
default:
break;
}
try {
stsClient = new StsClient(ClientType.valueOf(type), configBuilder.build());
} catch (Throwable e) {
throw new RuntimeException("client type not supported!, type: " + type);
}
```
**第二步:获取临时秘钥**
```java
// action,resources请参考各个云厂商的需要值
// 例如:
// 阿里云:String[] aliActionParams = {"oss:*"}; String[] aliResourceParams = {"acs:oss:*:*:" + bucket, "acs:oss:*:*:" + bucket + "/*"};
// 华为云:String[] hwActionParams = new String[]{"obs:object:*"}; String[] hwResourceParams = new String[]{"obs:*:*:object:*"};
// 腾讯云: String[] tencentActionParams = new String[]{"name/cos:*"}; String[] tencentResourceParams = new String[]{"*"};
// AWS/MINIO: String[] awsActionParams = new String[]{"s3:*"}; String[] awsResourceParams = new String[]{"arn:aws:s3:::*"};
StsRequestPolicyStatement statement = new StsRequestPolicyStatement.Builder()
.action(actions)
.resource(resources)
.build();
StsRequestPolicy stsRequestPolicy = new StsRequestPolicy.Builder()
.statement(new StsRequestPolicyStatement[]{statement})
.build();
StsResponse stsResponse = stsClient.getStsResponse(stsRequestPolicy, 15 * 60);
```
**第三步:使用临时秘钥创建对象存储客户端**
```java
RawClientConfig.Builder configBuilder = RawClientConfig.builder()
.endpoint(endpoint)
.accessKeyId(stsResponse.getAccessKeyId())
.secretAccessKey(stsResponse.getAccessKeySecret())
.securityToken(stsResponse.getSecurityToken());
if (StringUtils.isNotBlank(region)) {
configBuilder.region(region);
}
objectClient = new ObjectClient(configBuilder.build(), ClientType.valueOf(type.toUpperCase()), null, null);
```
**定时更新临时秘钥**
注意临时秘钥的有效时长,需要在过期前更新临时秘钥
```java
// 获取临时秘钥
stsResponse = stsClient.getStsResponse(stsRequestPolicy, 15 * 60);
// 更新临时秘钥
objectClient.updateAccessKeyAndAccessSecret(stsResponse.getAccessKeyId(),
stsResponse.getAccessKeySecret(), stsResponse.getSecurityToken());
```
# 对象存储starter说明
## 说明
* object-storage-starter是对[objectstorage](http://geek.glodon.com/projects/MULTI-CLOUD/repos/object-storage/browse)封装的starter包
* 使用时,只需添加pom依赖,添加配置文件。即可开箱试用
## 快速上手
### 1. 添加pom依赖
```xml
com.glodon.paas.foundation
object-storage-starter-spring-boot-starter
1.3.13.SB1_5-SNAPSHOT
```
### 2. 配置pom依赖的私仓地址
```xml
...
multicloud-release
multicloud-release
https://packages.glodon.com/artifactory/maven-multicloud-release
multicloud-snapshot
multicloud-snapshot
https://packages.glodon.com/artifactory/maven-multicloud-snapshot
```
### 3. 配置yaml文件
- oss
```
object-storage:
end-point: obs.cn-north-1.myhuaweicloud.com
access-key-id: xxxxxxxxxxxxxxxxx
access-key-secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
protocol: oss
path-style-access: true # 该字段选填
```
- 标准s3(基于S3协议的还有:S3_HW、S3_QY、S3_CEPH、S3_MINIO)
```yaml
object-storage:
end-point: obs.cn-north-1.myhuaweicloud.com
access-key-id: xxxxxxxxxxx
access-key-secret: xxxxxxxxxxxxxxxxxxxxxxx
protocol: S3
path-style-access: false # 该字段选填
```
- 基于S3协议的OBS
```yaml
object-storage:
end-point: obs.cn-north-1.myhuaweicloud.com
access-key-id: xxxxxxxxxx
access-key-secret: xxxxxxxxxxxxxxx
protocol: S3_HW
path-style-access: true # 该字段选填
```
- 基于S3协议的S3_QY
```yaml
object-storage:
end-point: obs.cn-north-1.myhuaweicloud.com
access-key-id: xxxxxxxx
access-key-secret: xxxxxxxxxxxxxxxxxx
protocol: S3_HW
path-style-access: true # 该字段选填
```
- path-style-access
```
是否强制开启路径形式的资源访问
(指定为Boolean.TRUE,那么使用endpoint/bucket的方式来访问)
(指定为Boolean.FALSE,那么使用bucket.endpoint的方式来访问)
(不指定则为null,使用OSS或者S3的默认方式:
OSS默认采用bucket.endpoint;
S3默认对于ip格式的endpoint和非ipv4格式合法的bucket.endpoint采用endpoint/bucket,而其他情况使用bucket.endpoint;
S3_HW:与S3默认情况相同;
S3_QY:与S3默认情况相同;
S3_CEPH:默认使用endpoint/bucket方式访问
S3_MINIO: 默认使用endpoint/bucket方式访问;
)
默认值大多数情况下足够使用,绝大多数情况不需要指定这个参数
```
### 4. 使用文件操作对象
```java
@Autowired
private ObjectClient objectClient;
...
objectClient.put("bucket-name", "key", file);
```
### 5.备注
eos-java-s3-sdk 移动云提供的sdk的包目录与 com.amazonaws 亚马逊的包目录冲突,因为大部分接口移动云兼容s3协议,只是拓展了文件追加上传功能,
所以我们使用移动云的时候大部分接口使用的s3公共部分,只有append使用了移动云提供的sdk,为了避免jar目录冲突,通过jarjar工具将移动云sdk的包结构进行调整,
步骤如下:
第一步 获取jar包
com.googlecode.jarjar
jarjar
1.3
第二步 将jarjar.jar移动至要替换的jar包同一目录下
第三步 创建rule.txt文档,内容如下 (.含义:com.amazonaws 替换为 com.eos.amazonaws)
rule com.amazonaws.** com.eos.amazonaws.@1
第四步 执行jarjar.jar工具 ( java -jar jarjar-1.3.jar process rule.txt 待处理的jar 最后生成的jar名)
java -jar jarjar-1.3.jar process rule.txt "EOS S3 Java SDK.jar" eos-sdk.jar