# 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