背景
在项目的实施过程中,遇到越来越多的第三方私有或公有化存储云服务,有阿里OSS、华为云OBS、七牛云Kodo等,各存储厂商对接标准备和SDK尽不相同,这对项目的实施和运维带来了很大的挑战,所以急需为各项目开发一套统一的存储接入标准,屏蔽各厂商接入差异,增强业务快速实施及产品化的能力。
项目痛点分析
- 项目中附件上传下载实现五花八门,代码难以维护
- 存储参数配置凌乱,无法统一维护和配置,运维头大
- 项目无法快速接入不同第三方云存储,业务要大改一通且维护不同的代码分支
- 上传下载功能(前后端)常被业务重复开发,浪费资源
- 通过网关、nginx等简单包装代理方式处理第三方存储,丢失CDN及高可用性,浪费流量、计算时间等资源
目的
- 简单统一存储接入标准,为业务屏蔽存储技术细节
- 统一的存储配置管理中心,达到快速运维能力
- 可复用,节省项目重复开发成本
- 可扩展性,可接入更多类型第三方或自有存储服务
现市场上主要对象存储服务类型
- Amazon S3
https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/Introduction.html
- 阿里云OSS
- 腾讯云COS
- 七牛云KODO
- 京东云存储
-
华为云OBS
https://www.huaweicloud.com/product/obs.html
https://support.huaweicloud.com/basics-terraform/terraform_0021.html
-
MinIO(基于go开源分布式存储方案)
他们有一个共同的特性,都兼容亚马逊S3存储协议
华为云存储
权限机制
- 取永久访问密钥(AK/SK)
- 临时访问密钥场景(AK/SK/securitytoken)
Java SDK上传
TempTokenDO tempTokenDO = getTempToken();
ObsClient obsClient = new ObsClient(tempTokenDO.getAk(), tempTokenDO.getSk(), tempTokenDO.getSt(), thirdpartyEndpoint);
PutObjectResult putObjectResult = obsClient.putObject(bucketName, key, fileInputStream);
//获取token
private TempTokenDO getTempToken(String ak, String sk){
StopWatch sw = new StopWatch();
TempTokenDO tempTokenDO = null;
try{
sw.start("getTempToken");
ICredential auth = new GlobalCredentials().withAk(ak).withSk(sk);
IamClient client = IamClient.newBuilder().withCredential(auth).withRegion(IamRegion.valueOf(this.region)).build();
CreateTemporaryAccessKeyByTokenRequest request = new CreateTemporaryAccessKeyByTokenRequest();
CreateTemporaryAccessKeyByTokenRequestBody body = new CreateTemporaryAccessKeyByTokenRequestBody();
List<TokenAuthIdentity.MethodsEnum> listIdentityMethods = new ArrayList<>();
listIdentityMethods.add(TokenAuthIdentity.MethodsEnum.fromValue("token"));
TokenAuthIdentity identityAuth = new TokenAuthIdentity();
identityAuth.withMethods(listIdentityMethods);
TokenAuth authBody = new TokenAuth();
authBody.withIdentity(identityAuth);
body.withAuth(authBody);
request.withBody(body);
CreateTemporaryAccessKeyByTokenResponse response = client.createTemporaryAccessKeyByToken(request);
Credential credential = response.getCredential();
if(null == credential){
throw new BaseException(StorageMessage.B4101);
}
tempTokenDO = new TempTokenDO().setAk(credential.getAccess()).setSk(credential.getSecret())
.setSt(credential.getSecuritytoken()).setExpiresAt(credential.getExpiresAt());
}catch (ServiceResponseException e) {
log.error("获取临时凭证异常:"+e.getMessage(), e);
throw new BaseException(StorageMessage.B4101, e);
}finally {
if(sw.isRunning()){
sw.stop();
}
log.debug("sw_getTempToken:"+sw.toString());
}
return tempTokenDO;
}
JS SDK上传
-
永久访问秘钥(AK/SK)创建OBS客户端代码如下:
//未引入AMD,直接通过构造函数创建ObsClient实例 var obsClient = new ObsClient({access_key_id: '*** Provide your Access Key ***',secret_access_key: '*** Provide your Secret Key ***',server : 'https://your-endpoint'});
-
临时访问秘钥(AK/SK/SecurityToken)创建OBS客户端代码如下
//未引入AMD,直接通过构造函数创建ObsClient实例 var obsClient = new ObsClient({access_key_id: '*** Provide your Access Key ***',secret_access_key: '*** Provide your Secret Key ***',security_token: '*** Provide you Security Token ***',server : 'https://your-endpoint'});
阿里云存储
权限机制
Java SDK上传
JS SDK上传
七牛云存储
Kodo 是七牛云提供的高可靠、强安全、低成本、可扩展的存储服务。可通过控制台、API、SDK 等方式简单快速地接入七牛存储服务,实现海量数据的存储和管理。对文件下载进行加速,智能多媒体 人脸技术、场景物体识别、OCR 文字识别和内容审核等
请参阅对象存储文档:https://developer.qiniu.com/kodo
接入流程
注册七牛云帐号-->创建空间-->绑定域名-->上传下载等资源管理
参考文档:https://developer.qiniu.com/kodo/1233/console-quickstart
空间(bucket)
空间是资源的组织管理单位,一个资源必然位于某个空间中。可以为每个空间设置一系列的属性,以对资源提供合理的管理动作
公开空间:可通过文件对象的 URL 直接访问。
私有空间:文件对象的访问则必须获得拥有者的授权才能访问。
空间设置功能
- 访问控制
- 默认首页设置
- 404 页面设置
- 文件客户端缓存 maxAge
- 空间日志
- 内容审核
- 标签管理
- 空间授权
- Referer 防盗链
- 跨域设置
- 生命周期设置
- 事件通知
- 镜像回源
- 删除空间
- 原图保护
编程模型
安全机制
七牛云存储服务的过程中,需要考虑安全机制的三种场景对应三种凭证:
https://github.com/qiniu/java-sdk/blob/master/src/main/java/com/qiniu/util/Auth.java
推荐的安全模型如下所示:
上传资源
https://developer.qiniu.com/kodo/1234/upload-types
上传文件的名称中,不支持\0字符,若文件名中存在\0字符,则会返回 400 Bad Request 和 error message “key must not contain null byte”。
上传文件名 utf-8 编码字符,长度不超过 750 字节 。
- 表单上传
表单上传是指在一个单一的 HTTP POST 请求中完成一个文件的上传,比较适合简单的应用场景和尺寸较小的文件 - 分片上传
分片上传是将一个文件分为多个小数据块,每个小数据块以一个独立的 HTTP 请求分别上传。所有小数据块都上传完成后,再发送一个请求给服务端将这些小数据块组织成一个逻辑资源,以完成上传过程 - 上传后续动作
在上传时开发者可以指定上传完成后服务端的后续动作,例如回调通知(callback)、自定义响应内容、303重定向等
下载资源
- 公开资源下载
公开资源下载通过 HTTP GET 的方式访问资源 URL 即可。资源 URL 的构成如下:http://<domain>/<key>
其中
有两种形态:七牛子域名和自定义域名。默认分配测试域名为七牛子域名,形式类似于 78re52.com1.z0.glb.clouddn.com,用户可以通过以下 URL 下载名为 resource/flower.jpg的资源 http://78re52.com1.z0.glb.clouddn.com/resource/flower.jpg
您也可以为某特定空间,绑定自定义域名,例如i.example.com,您就可以通过以下 URL 访问同样的资源:
http://i.example.com/resource/flower.jpg
- 私有资源下载
当您将空间设置成私有时,必须获得授权,才能对空间内的资源进行访问。
私有资源下载是通过HTTP GET的方式访问特定的 URL。私有资源URL与公开资源URL相比只是增加了两个参数e和token,分别表示过期时间和下载凭证。一个完整的私有资源 URL 如下所示:
http://<domain>/<key>?e=<deadline>&token=<downloadToken>
SDK
AWS S3 兼容
为了使众多基于AWS S3接口协议开发的各类应用及服务能够轻便的接入七牛对象存储,七牛对象存储兼容了AWS S3常用接口。接口的具体兼容情况,在下文中做了详细叙述。
七牛云存储兼容S3协议接口,是为了尽可能的方便基于 AWS S3 而开发的应用接入到七牛对象存储。如果您刚刚开始着手开发新的应用,为了更好的使用七牛对象存储丰富的产品功能,更推荐使用原生接口进行开发。
实用工具
实例演示
后端上传
https://github.com/qiniu/java-sdk
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @SpringBootTest @Slf4j class UploadTest {
/**
* 空间
*/
private static String bucketName;
/**
* 下载自定义域名
*/
private String endpoint;
/**
* 上传管理器
*/
UploadManager uploadManager;
/**
* 空间管理器
*/
private BucketManager bucketManager;
/**
* 验证管理器
*/
Auth auth = null;
@BeforeAll
void init(){
this.bucketName = "hushow";
this.endpoint = "http://qnvs9yac3.hn-bkt.clouddn.com";
String accessKey = "PheIieVFKcYRmdn6mFDNZ9uIO6NJN36Q4fM1HudC";
String secretKey = "OZWR8o20c0OrLryl2TJoH8JAu8LifyTo3rto-OjC";
this.auth = Auth.create(accessKey, secretKey);
this.uploadManager = new UploadManager(new Configuration());
bucketManager = new BucketManager(auth, new Configuration());
}
@Test
void uploadTest() throws Exception{
String token = auth.uploadToken(bucketName);
String fileUrl = "classpath:images/1.jpg";
File file = ResourceUtils.getFile(fileUrl);
String key = file.getName();
Response response = uploadManager.put(file, key, token);
log.info("response:{}", JSONObject.toJSONString(response));
Assertions.assertTrue(response.isOK(), "上传失败");
}
@Test
void deleteTest() throws Exception{
String key = "1.jpg";
Response response = bucketManager.delete(bucketName, key);
log.info("response:{}", JSONObject.toJSONString(response));
Assertions.assertTrue(response.isOK(), "删除失败");
}
}
上传结果响应
```json
{
"address": "upload-z2.qiniup.com/14.29.110.7:443",
"duration": 2.352,
"info": "POST https://upload-z2.qiniup.com/
{ResponseInfo:com.qiniu.http.Response@1f9c6ea,status:200, reqId:Y40AAADoLc02HWAW, xlog:X-Log, xvia:, adress:upload-z2.qiniup.com/14.29.110.7:443, duration:2.352000 s, error:null}
{\"hash\":\"Fm1LTYXuHg2xpqJ0-lTcPE11zUcY\",\"key\":\"1.jpg\"}",
"json": true,
"method": "POST",
"networkBroken": false,
"oK": true,
"reqId": "Y40AAADoLc02HWAW",
"serverError": false,
"statusCode": 200,
"xlog": "X-Log",
"xvia": ""
}
查看上传附件
前端上传
需求梳理
- 统一前端附件上传下载接口标准及SDK封半
- 统一后端附件管理接口标准及SDK封装
- 支持快速切换存储方案,对业务无感知,调整相关参数和endPoint即可
- 支持自有的分存式存储和普通单机存储方案接入
- 保证第三方存储的优势,避免浪费资源
- 前端代码devOps接入OSS存储中
初步方案
具体场景设计
上传资源
-
华为云JS SDK接入场景
-
本地普通存储JS SDK接入场景
下载资源
-
公开资源
-
私有资源