0


四万字长文详解——node.js使用移动云,EOS对象存储

前言

移动云对象存储EOS(Elastic Object Storage)是移动云提供的海量、高可靠、安全、低成本的云存储服务。其数据设计持久性不低于99.999999999%(11个9),服务可用性达到99.95%。使用移动云提供的API、SDK接口轻松地将海量数据移入和移出移动云EOS。数据存储到移动云EOS以后,可以选择标准存储作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低的低频访问存储、归档存储作为不经常访问数据的存储方式。

移动云是支持node.js的,本文主要讲述node.js如何来操作移动云的SDK,并将移动云与node.js结合,创造出更大的价值及未来的开发模式路线。

安装及安装前的操作

前置条件

  1. 已开通移动云对象存储 eos 服务。
  2. 已创建用户的 access key 和 secret access key。
如何创建认证信息

1、登录EOS 控制台。

2、点击【API 认证信息】,进入 API 认证信息页面。

3、用户点击【创建 AK/SK】,即可完成认证信息的新建,并获得相应的AccessID信息。

使用npm安装SDK开发包

注意:移动云EOS对象存储的node版本支持,最低为13版本,所以建议在使用移动云EOS对象存储之前将node版本调整为13版本及以上,并确保可以使用ES6语法实现代码的编写。

安装开发包命令

npm install aws-sdk

初始化操作

const AWS = require('aws-sdk')
var s3 = new AWS.S3({
apiVersion: '2006-03-01',
// 移动云 EOS accessKeyId 拥有所有API的访问权限,风险很高。建议您谨慎使用,以防泄露。
accessKeyId: 'your-access-key',
secretAccessKey: 'your-secret-access-key',
// endpoint 填写 Bucket 所在地域的域名,本章节以 wuxi1 为例
endpoint: 'eos-wuxi-1.cmecloud.cn',
signatureVersion: 'v2',
sslEnabled: true // 是否启用 HTTPS 连接
});

​

​代码中,access key 与 secret access key 是您在开通对象存储服务后,系统分配给您用于访问对象存储的凭证。

endpoint 是 EOS 对象存储的服务地址,例如:
Endpoint地域http://eos-wuxi-1.cmecloud.cnwuxi1http://eos-jinan-1.cmecloud.cnjinan1
地域介绍:地域是移动云托管机房的分布地区,对象存储 EOS 的数据存放在这些地域的存储桶中。

访问域名:访问域名表示 EOS 对外服务的访问域名,EOS 以 HTTP RESTful API 的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。

存储桶

存储桶是您用于存储文件 (Object) 的容器,在上传任何文件到 EOS 之前,您必须先创建存储桶。

在 init.js 文件中继续添加如下内容,可以在当前地域创建桶。

var params = {
    // 设置桶名称
    Bucket: 'your-bucket-name',
    // 设置读取方式
    ACL: 'private', 
    CreateBucketConfiguration: {
    //如果没有指定区域,LocationConstraint 会被设置为 None
        LocationConstraint: 'wuxi1'   
    }
}
s3.createBucket(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else console.log(data);
});

查看结果命令

 node init.js

查看桶列表

在 init.js 文件中继续添加如下内容,可以查看桶列表。

s3.listBuckets({}, function(err, data) {
    if (err) console.log(err, err.stack);
    else     console.log(data);
});

查看结果命令

node init.js

删除桶

在 init.js 文件中继续添加如下内容,可以删除当前所在地域的桶。

s3.deleteBucket({ Bucket: "您想删除的桶名称"}, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data);
});

查看结果命令

 node init.js

创建桶

以下代码向您展示如何向指定区域创建一个新的桶,并设置该桶的读写权限。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});

var params = {
    // 您要设置的桶名
    // 桶的命名需要遵循对象存储中容器的命名规范
    // 桶名称是全局唯一的,所以您需要保证桶名称不与已有桶名重复
    Bucket: 'myBucket',
    // 桶的读写权限
    // 默认权限是私有读写
    // 有效值:private | public-read | public-read-write
    // 除了在创建桶的时候设置权限之外,您也可以使用`setBucketAcl`接口进行权限的修改,详情见《管理桶的访问权限》章节。
    ACL: 'private', 
    CreateBucketConfiguration: {
        //如果没有指定区域,LocationConstraint 被设置为 None
        LocationConstraint: 'wuxi1' 
    }
}
s3.createBucket(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

注意:

  1. 创建桶的时候您需要指定桶所属的区域,例如要在 wuxi1 数据中心创建桶
  2. 请求的 endpoint 要写为 http://eos-wuxi-1.cmecloud.cn
  3. 创建参数 LocationConstraint 为 wuxi1

获取桶列表

以下代码用于请求用户在指定区域内所有存储桶的列表,桶按照字母顺序排列。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
// 列举当前地域下的所有桶

s3.listBuckets({}, function(err, data) {
    if (err) console.log(err, err.stack);
    else     console.log(data);
});

​

判断桶是否存在

当用户不确定一个桶是否存在时,可以通过以下代码来验证该桶是否存在。使用该方法请先确保您有权限来访问该桶。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
// 指定需要查询的桶名称
var params = {
    Bucket: 'your-bucket-name'
}
s3.headBucket(params, function(err, data) {
    if (err.statusCode === 404) console.log('存储桶不存在');
    if (data) console.log('存储桶存在');
});

​

查询桶所属地域

返回桶所属的地域。您可以在 CreateBucket 请求中使用 LocationConstraint 请求参数设置 bucket 的 Region。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
s3.getBucketLocation({Bucket: 'your-bucket-name'}, function(err, data) {
    if (err) console.log(err, err.stack);
    else     console.log(data);
});

​

查询桶的访问权限

该操作用于查询并返回桶的访问权限列表 (ACL)。要使用 GET 返回桶的 ACL,必须具有对桶的

read_acp

访问权。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
s3.getBucketAcl({Bucket: 'your-bucket-name'}, function(err, data) {
    if (err) console.log(err, err.stack);
    else     console.log(data);
});

​

返回值示例:

{
  Owner: {
    DisplayName: 'user_name',
    // 获取桶的拥有者 ID
    ID: 'user_id'
  },
  Grants: [
    //permission 的返回有以下几种:
    //FULL_CONTROL:完全权限
    //WRITE:写权限
    //WRITE_ACP:写 ACL 权限
    //READ:读权限
    //READ_ACP:读 ACL 权限
    { Grantee: [Object], Permission: 'FULL_CONTROL' },
    { Grantee: [Object], Permission: 'WRITE' },
    { Grantee: [Object], Permission: 'READ_ACP' }
  ]
}

​

管理桶的访问权限

桶的访问权限可以在创建桶时设置,也可以在创建桶后根据自己的业务需求修改桶的访问权限。
桶的访问权限有以下三种:

  1. private:私有,桶的拥有者和授权用户有该桶内的文件 (Object) 的读写权限,其他用户没有权限操作该存储空间内的文件。
  2. public-read:公共可读,桶的拥有者和授权用户有该桶内的文件的读写权限,其他用户只有该桶内的文件的读权限。请谨慎使用该权限。
  3. public-read-write:公共可读写,所有用户都拥有该桶内文件的可读写权限。请谨慎使用该权限。
const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
let params = {
  Bucket:'your-bucket-name',
  // 有效值:
  //private:私有读写
  //public-read:公共读私有写
  //public-read-write:公共读写
  ACL: 'private'
}
s3.putBucketAcl(params, function(err, data) {
    if (err) console.log(err, err.stack);
    else     console.log(data);
});

​

删除桶

删除桶之前,必须先删除桶内的所有文件 (Object)、版本、标记和分块上传产生的碎片。
如果该桶下还有未完成的上传请求,则需要通过 listUploads 和 abortMultipartUpload 取消请求后才能删除桶。

如果桶不为空,则无法被删除,会返回错误。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
s3.deleteBucket({ Bucket: 'your-bucket-name'}, function(err, data) {
    if (err) console.log(err, err.stack);
    else     console.log(data);
});

​

生命周期

对象存储支持设置生命周期 (Lifecycle) 规则,自动删除过期的文件 (Object) 和碎片,或将到期的文件转储为低频或归档存储类型,从而节省存储费用。本文介绍如何管理生命周期规则。

生命周期包括:
规则 ID:用于标识一条规则,不能重复。
筛选条件:用于标识规则应用的对象,通过使用一个对象前缀,和/或一个或多个标签来指定筛选条件。
是否生效
过期时间:有两种指定方式:
1.指定距对象最后修改时间N天过期。
2.指定在具体的某一天过期,即在那天之后符合前缀或标签的对象将会过期,而不论对象的最后修改时间。不推荐使用。

查询桶的生命周期

以下代码用于查询指定桶的生命周期配置,您只需传入桶名即可。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
s3.getBucketLifecycleConfiguration({Bucket: 'your-bucket-name'}, function(err, data) {
    if (err) console.log(err, err.stack);
    else     console.log(data);
})

​

返回值示例:

data = {
    // Rules 数组包含了当前桶所拥有的所有规则
    Rules: [
       {
      ID: 'xxxxx',   //规则的唯一标识
      Prefix: 'xxxxx', 
      Status: 'Enabled',  //如果是 enabled 则表明当前规则正在使用,Disabled 则相反
      Tags:'xxxx', //查看生命周期的标签
      Transitions: [
         {
        Days: 365, // 查看生命周期的过期天数
        StorageClass: 'STANDARD_IA'  //查询转换存储类型
       }
      ]
     }
    ]
   }

​

设置桶的生命周期

以下代码用于为桶创建新的生命周期配置或替换现有的生命周期配置。

  1. 在存储桶生命周期配置中,可以指定基于文件名前缀、文件标签或两者指定筛选生命周期规则适用的文件。如果指定基于标签,需要同时匹配标签的键和值。如果同时配置了前缀和标签,则需要满足前缀,同时匹配标签,才满足筛选规则。
  2. 注意:您可以为单个桶设置多个生命周期规,但不同规则间不能冲突,否则服务端会返回错误。
  3. 规则冲突是指:有两条规则的 prefix 有重叠,并且都定义了相同的过期操作。
const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name', // required
  LifecycleConfiguration: {
    Rules: [ // required
      {
        Status: 'Enabled', // 'Enabled' or 'Disabled'
        AbortIncompleteMultipartUpload: {
          DaysAfterInitiation: 7 // 根据需要调整为实际的数字
        },
        Expiration: {
          Days: 21  // 根据需要调整
        },
        Filter: {
          Tag: { // 正确设置 Tag 在 Filter 内部
            Key: 'mytags', // required
            Value: 'mytagsValue' // required
          }
        },
        ID: 'STRING_VALUE'  // 唯一标识
      }
    ]
  }
};
s3.putBucketLifecycleConfiguration(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

清除桶的生命周期

从指定桶中删除生命周期配置。对象存储将删除与该桶关联的生命周期子资源中的所有生命周期配置规则,您的文件永远不会过期。在将生命周期配置删除完全传播到所有系统之前,通常会有一些时间滞后。

您只能对桶的生命周期全部删除,而不能只删除某一条规则。
如果只是为了去除或修改某一条规则,您可以使用设置生命周期接口。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
s3.deleteBucketLifecycle({Bucket: 'your-bucket-name'}, function(err, data) {
    if (err) console.log(err, err.stack);
    else     console.log(data);
})

​

防盗链

EOS 支持对桶设置防盗链,即通过对访问来源设置白名单的机制,避免 EOS 资源被其他人盗用。

  1. 同一个桶上不建议防盗链和静态网站同时开启,否则访问静态网站会失败。
  2. 最佳实践:桶 1 开启静态网站,桶 2 存放静态网站需要的图片和视频资源,然后开启这个桶 2 的防盗链,设置允许桶 1 的域名访问桶 2 的资源。

设置防盗链

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Policy: `{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": ["arn:aws:s3:::桶名称/*"],
      "Condition": {
        "StringLike": {
          "aws:Referer": "http://域名/*"
        }
      }
    }]
  }`
}
s3.putBucketPolicy(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

查询防盗链

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
s3.getBucketPolicy({Bucket: 'your-bucket-name'}, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data); 
  // 如果返回值为 404,则表明该桶没有授权策略
});

​

清除桶的防盗链

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
s3.deleteBucketPolicy({Bucket: 'your-bucket-name'}, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

桶跨域规则

桶的 CORS 配置。如果配置存在,则会替换它。

设置桶的跨域规则

  • 为了在桶上启用 CORS,可以将 CORS 子资源添加到桶中。
  • CORS 子资源是一个 XML 文档,您可以在其中配置标识起源的规则以及可以在桶上执行的 HTTP 方法。文档的大小限制为 64 KB。
const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name', 
  CORSConfiguration: {
   CORSRules: [
      {
      // 指定允许跨域请求的响应头。建议无特殊情况下将此项设置为通配符星号 (*)
     AllowedHeaders: ['*'], 
     // 指定允许的跨域请求方法,数组,支持 GET、PUT、DELETE、POST 和 HEAD 方法
     AllowedMethods: [
        'PUT', 
        'POST', 
        'DELETE'
     ], 
     // 指定允许跨域请求的来源,支持通配符星号 (*),表示允许所有的来源域
     AllowedOrigins: ['http://www.ecloud.10086.cn'], 
     // 指定允许用户从应用程序中访问的响应头,例如一个 JavaScript 的 XMLHttpRequest 对象。不允许使用通配符星号 (*)
     ExposeHeaders: ['x-amz-server-side-encryption'], 
     // 指定浏览器对特定资源的预取 (OPTIONS) 请求返回结果的缓存时间,单位为秒
     MaxAgeSeconds: 3000
    }
   ]
  }
 };
 s3.putBucketCors(params, function(err, data) {
   if (err) console.log(err, err.stack); // an error occurred
   else     console.log(data);           // successful response
 });

​

查询桶的跨域规则

返回桶的 CORS 配置信息集。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 s3.getBucketCors({Bucket: 'your-bucket-name'}, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data); 
 });
 //返回值解析见设置跨域规则时的注释信息

​

删除桶的跨域规则

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 s3.deleteBucketCors({Bucket: 'your-bucket-name'}, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data); 
 });

​

访问日志

访问 EOS 的过程中会产生大量的访问日志。您可以通过日志转存功能将这些日志按照固定命名规则,以小时为单位生成日志文件写入您指定的桶。

开启访问日志

设置桶的日志记录参数,并指定谁可以查看和修改日志记录参数的权限。所有日志都保存到与源桶相同的区域中的存储桶中。日志桶需要添加acl授权。若要设置桶的日志记录状态,您必须是桶的所有者。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: '原桶名', 
  BucketLoggingStatus: {
   LoggingEnabled: {
    TargetBucket: '目标桶名',
    TargetPrefix: 'xxxx', // 目标前缀
    
   }
  }
 };
 s3.putBucketLogging(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data);
 });

​

获取访问日志

返回桶的日志记录状态以及用户查看和修改该状态所拥有的权限。要使用该方法,您必须是桶的所有者。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 s3.getBucketLogging({Bucket: 'your-bucket-name'}, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data);
 });

​

静态网站

静态网站是指所有的网页都由静态内容构成,包括客户端执行的脚本 (例如 JavaScript)。您可以通过静态网站托管功能将您的静态网站托管到桶,并使用桶的访问域名访问这个网站。

设置静态网站

您可以将桶配置成静态网站托管模式。配置生效后(请确认桶内的索引页面和错误页面公开可读),访问网站相当于访问桶,并且能够自动跳转至指定的索引页面和错误页面。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 var params = {
  Bucket: 'your-bucket-name',
  WebsiteConfiguration: {
   ErrorDocument: {
   // error.html
    Key: '错误页面文件名称'
   },
   IndexDocument: {
   // index.html
    Suffix: '默认前缀文件名称'
   }
  }
 };
 s3.putBucketWebsite(params, function(err, data) {
   if (err) console.log(err, err.stack); // an error occurred
   else     console.log(data);           // successful response
 });

​

查看静态网站

通过传入桶名,您可以查看指定桶的静态网站配置信息。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 s3.getBucketWebsite({Bucket: 'your-bucket-name'}, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data);
 });

​

删除静态网站

  • 此操作用于删除一个桶的网站配置。在成功删除指定桶上的网站配置后,返回 200 OK 响应。
  • 如果您想删除的网站配置不存在,您会得到一个 200 OK 的回复。如果请求中指定的桶不存在,将返回一个 404 响应。
const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 s3.deleteBucketWebsite({Bucket: 'your-bucket-name'}, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data);
 });

​

上传本地文件

将文件 (Object) 添加到存储桶。您必须对存储桶拥有 WRITE 权限才能向其中添加文件。对象存储是一个分布式系统,如果它同时接收到对同一文件的多个写入请求,它将覆盖除最后一个写入的文件之外的所有文件。

const AWS = require('aws-sdk');
const fs = require('fs');

// 指定要上传的本地文件路径
const localFilePath = '本地文件路径';

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});

// 读取本地文件内容
fs.readFile(localFilePath, (readErr, fileData) => {
  if (readErr) {
    console.error('读取本地文件时发生错误:', readErr);
    return;
  }

  // 设置 S3 参数
  const params = {
    Body: fileData,             // 本地文件内容
    Bucket: 'your-bucket-name', // 你的 S3 桶名称
    Key: '对象完整路径', 
    StorageClass: 'STANDARD_IA'  // 存储类别
  };

  // 将文件上传到 S3
  s3.putObject(params, (err, data) => {
    if (err) console.log(err, err.stack);
    else     console.log(data);
  });
});

​

完整的上传本地文件代码参考

var fileChooser = document.getElementById('file‐chooser');    
var button = document.getElementById('upload‐button');    
var results = document.getElementById('results');    
button.addEventListener('click', function() {    
  var file = fileChooser.files[0];    
  if (file) {      
    results.innerHTML = '';      
    var params = {
      Key: '对象完整路径 (不包括桶名)',
      Bucket: 'target-bucket',  // 您要上传到的目标桶名
      ContentType: file.type, 
      Body: file
    }
    s3.putObject(params, function (err, data) {        
      results.innerHTML = err ? 'ERROR!' : 'UPLOADED.';      
    });    
  } else {      
    results.innerHTML = 'Nothing to upload.';    
  }    
}, false);

​

上传本地内存

将本地内存上传至存储桶。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var msg = '123456'
 var params = {
  Body: msg, 
  Bucket: 'your-bucket-name', 
  Key: '对象完整路径 (不包括桶名)', 
 };
 s3.putObject(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data);
 });

​

分块上传

EOS 提供的分块上传 (Multipart Upload) 功能,将要上传的较大文件 (Object) 分成多个分块 (Part) 来分别上传。
上传完成后再调用 CompleteMultipartUpload 接口将这些 Part 组合成一个 Object 来达到断点续传的效果。

当您所要上传的文件过大时,可以使用分块上传的方式进行上传。该方法会将您的大文件分割成规定尺寸的分块进行上传,上传完成过后再整合成完整的文件。
分块上传共分为以下几个步骤:
1:制作分块
2:请求上传 id
3:将带有 id 的分块上传至指定桶
4:整合分块

分块上传函数,并逐步解析每一步的含义

// 在根目录下新建 upload.js 文件,写入以下代码
// 引入依赖
const fs = require('fs')
const AWS = require('aws-sdk')
 
// 指定目标桶名
const BUCKET_NAME = 'testBucket';
 
// 写入目标必传参数
const s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'https://eos-wuxi-1.cmecloud.cn',
    // 建议开启 ssl
    sslEnabled:  true // 是否启用 HTTPS 连接
});
 
// 分块上传文件函数
const uploadFile = async (fileName) => {
  // 设置每个分块大小为 5MB
  const fiveM = 5 * 1024 * 1024;
  const buffers = [];
  let len = 0;

  const res = fs.createReadStream(fileName, {
    highWaterMark: fiveM,
  });

  res.on('data', async (chunk) => {
    buffers.push(chunk);
    len += chunk.length;
  });

  res.on('error', (err) => {
    if (err) {
      console.error(err);
    }
  });

  res.on('end', async () => {
    const params0 = {
      Bucket: BUCKET_NAME,
      Key: fileName,
      ACL: 'private',
    };

    try {
      var data = await createMultipartUpload(params0);
      console.log('分块上传初始化成功:', data);

      // 分块上传任务 ID
      const UploadId = data.UploadId;
      await uploadParts(UploadId, buffers, len, fileName, fiveM)
    } catch (err) {
      console.error('分块上传初始化失败:', err);
    }
  });
};

// 创建 S3 分块上传任务
async function createMultipartUpload(params) {
  return new Promise((resolve, reject) => {
    s3.createMultipartUpload(params, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
}
// 上传任务
async function uploadParts (UploadId, buffers, len, fileName, fiveM) {
  const result = Buffer.concat(buffers, len);

  const chunkCount = Math.ceil(result.length / fiveM);

  // 存储已成功上传的分片信息
  const uploadedParts = [];

  // 设置并发上传数量
  const concurrentUploads = 3;
  // 存储上传分片的 Promise
  const uploadPromises = [];

  // 遍历分片并发上传
  for (let i = 0; i < chunkCount; i++) {
    const start = i * fiveM;
    const end = Math.min(start + fiveM, result.length);
    const partNumber = i + 1;
    const part = result.slice(start, end);

    // 并发上传多个分片,支持自动重试
    uploadPromises.push(uploadPart(UploadId, partNumber, part, fileName, uploadedParts));

    // 判断是否达到并发数量或已经是最后一个分片
    if (uploadPromises.length === concurrentUploads || i === chunkCount - 1) {
      // 等待所有上传 Promise 完成
      await Promise.all(uploadPromises);
      // 清空数组,准备下一批并发上传
      uploadPromises.length = 0;
    }
  }
  // 在所有分块上传完成后,调用该函数完成分段上传操作
  await completeMultipartUpload(UploadId, uploadedParts, fileName);
}

// 上传单个分片的函数
const uploadPart = async (uploadId, partNumber, part, fileName, uploadedParts) => {
  // 设置最大重试次数
  const maxRetryAttempts = 3;
  let retryCount = 0;

  // 重试
  while (retryCount < maxRetryAttempts) {
    try {
      // 调用上传分片到 S3 的函数
      const data = await uploadPartToS3(uploadId, partNumber, part, fileName);
      // 记录成功上传的分片信息
      uploadedParts.push({ PartNumber: partNumber, ETag: data.ETag });
      console.log('分块上传成功:', { PartNumber: partNumber, ETag: data.ETag });
      // 上传成功,退出循环
      return;
    } catch (err) {
      console.error('分块上传失败,尝试重试:', err);
      // 增加重试次数
      retryCount++;
    }
  }

  // 达到最大重试次数后仍然失败,抛出错误
  throw new Error('分块上传失败,已达到最大重试次数。');
};

// 将单个分块上传到 S3 的函数
const uploadPartToS3 = async (uploadId, partNumber, part, fileName) => {
  const params = {
    Key: fileName, 
    Bucket: BUCKET_NAME,
    PartNumber: partNumber,
    UploadId: uploadId,
    Body: part,
  };

  return new Promise((resolve, reject) => {
    s3.uploadPart(params, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
};

// 在所有分块上传完成后,调用该函数完成分段上传操作
function completeMultipartUpload(uploadId, uploadedParts, fileName) {
  var params = {
    Key: fileName,
    Bucket: BUCKET_NAME,
    UploadId: uploadId,
    MultipartUpload: {
      Parts: uploadedParts
    }
  };

  s3.completeMultipartUpload(params, function(err, data) {
    if (err) {
      console.log('完成分段上传失败:', err);
    } else {
      console.log('完成分段上传成功:', data);
    }
  });
}

// 调用分块上传函数,传入本地文件路径
uploadFile('your-file');

​

断点续传

断点续传是将要上传的文件 (Object) 分成若干个 part 分别上传,您可以在上传过程中获得断点信息 (checkpoint)。
一旦上传发生错误,可以通过该 checkpoint 找到之前的上传进度,从失败的地方继续上传。

创建断点续传

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
 var params = {
  Bucket: 'your-bucket-name', 
  Key: '对象完整路径 (不包括桶名)', 
 };
 s3.createMultipartUpload(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data);
 });

​

返回值解析

data = {
    Bucket: 'your-bucket-name', 
    Key: '对象完整路径 (不包括桶名)', 
    UploadId: 'LdxMVpAlj6ZQjEs.OwyF3953-'
   }

​

完成并组装分块

通过组装之前上传的部分来完成分块上传。
您首先启动分块上传,然后使用 UploadPart 操作上传所有部分。
成功上传所有相关部分后,调用此操作完成上传。收到此请求后,对象存储服务将按部件编号升序连接所有部件以创建新文件。
在 CompleteMultipartUpload 请求中,您必须提供部件列表。您必须确保零件清单是完整的。
此操作连接您在列表中提供的部分。对于列表中的每个部分,您必须提供该部分 ETag 上传后返回的部分编号和值。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
 var params = {
  Bucket: 'your-bucket-name', 
  Key: '对象完整路径 (不包括桶名)', 
  MultipartUpload: {
   Parts: [
      {
     ETag: 'd8c2eafd90c266e19ab9dcacc479f8af', 
     PartNumber: 1
    }, 
      {
     ETag: 'd8c2eafd90c266e19ab9dcacc479f8af', 
     PartNumber: 2
    }
   ]
  }, 
  UploadId: '7YPBOJuoFiQ9cz4P3Pe6FI'
 };
 s3.completeMultipartUpload(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data)
 })

​

列出分块

此操作列出正在进行的分块上传。正在进行的分块上传是已使用 InitiateMultipart Upload 请求启动但尚未完成或中止的分块上传。
此操作在响应中最多返回 1000 个分块上传。1000 分块上传是响应可以包含的最大上传次数,这也是默认值。
max-uploads 可以通过指定参数来进一步限制响应中的上传次数。如果其他分块上传满足列表条件,则响应将包含 IsTruncated 值为 true 的元素。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
var params = {
  Bucket: 'your-bucket-name'
};
 s3.listMultipartUploads(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data)
 })

​

取消断点续传

此操作可以终止分块上传。终止分块上传后,无法再使用该上传 ID 上传其他部分。任何先前上传的部分所占用的存储空间将被释放,当前正在进行的上传也不会成功。
为完全释放所有已上传 part 所消耗的存储空间,您可能需要多次终止给定的分块上传。
要验证是否已移除所有部件,您可以调用 ListParts 操作来查看并确保部件列表为空。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
var params = {
  Bucket: 'your-bucket-name', 
  Key: '对象完整路径 (不包括桶名)',
  UploadId: "xadcOB_7YPBOJuoFiQ9cz"
 };
 s3.abortMultipartUpload(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data)
 })

​

下载到本地文件

对象存储没有像在典型的计算机文件系统中那样的目录层次结构。但是,您可以通过使用表示文件夹结构的文件键名来创建逻辑层次结构。
例如,您可以将文件命名为 file/myfile/aaa.txt,来下载您的 aaa.txt 文件。

const fs = require('fs')
const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
var params = {
  Bucket: 'your-bucket-name', 
  Key: "您需要下载的文件路径 + 文件名", 
 };
 s3.getObject(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else {
        console.log(data)
        // 此时的 data 是 buffer 数组
        // 将 buffer 解析为文件:
        // 第一个参数为文件名
        // 第二个参数为 buffer 数组
        // 下载的文件会与当前文件平级
        fs.writeFile('my.mp4', data.Body, (err) => {
            if (err) throw err;
            console.log('The file has been saved!');
        });
    }
 })
 // 如果您需要的数据格式为 stream 流,可以将下载代码改写为:
 const file = s3.getObject(params).createReadStream()
 // 此时的 file 为 stream 流
 // 您还可以将 stream 流写入指定文件:
 const writeStream = fs.createWriteStream('./myStream')
 file.pipe(writeStream)

​

下载到本地内存

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
var params = {
  Bucket: 'your-bucket-name', 
  Key: '您需要下载的字符串路径 + 变量名', 
 };
 s3.getObject(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data)
 })

​

范围下载

如果仅需要文件 (Object) 中的部分数据,您可以使用范围下载,下载指定范围内的数据。

当需要下载的文件很大,或网络状况不够理想,往往下载到中途就失败了。如果下次重试,还需要重新下载,就会浪费时间和带宽。范围下载的过程大致如下:
1:在本地创建一个临时文件,文件名由原始文件名加上一个随机的后缀组成;
2:通过指定 HTTP 请求的 Range 头,按照范围读取对象存储服务器上的文件,并写入到临时文件里相应的位置;
3:下载完成之后,把临时文件重名为目标文件。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
var params = {
  Bucket: 'your-bucket-name', 
  Key: '对象完整路径 (不包括桶名)', 
  // 文件的第一个字节的位置为 0。比如您需要下载文件的前十个字节,则可以指定范围为 0-9
  // 下载文件的部分为前 3 个字节
  Range: 'bytes=0-2',  
 };
 s3.getObject(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else     console.log(data)
 })

​

注意:

  1. 如果 range 超过了文件的大小,则会下载整个文件。
  2. 利用范围下载,可以实现断点下载功能。即当下载中断时,只需要从断点处,采用范围下载,就可以继续下载文件,而不需要下载完整文件。

使用私有链接下载文件

通过私有链接下载文件 (Object),通过这种方法,您可以获得一个 url。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
var params = {
  Bucket: 'your-bucket-name', 
  Key: 'your-object-name'
}
var url = s3.getSignedUrl('getObject', params);
console.log('The URL is', url);
// url 示例:
// http://eos-beijing-1.cmecloud.cn/examplebucket/copyedjpg2?AWSAccessKeyId=您的 access-key&Expires=1504535428&Signature=t%2FHDsHAbbBTuRRIsx4M%2Bi9e%2F5ho%3D

​

判断文件是否存在

对桶中的任意文件 (Object) 进行相关操作前,您需要先判断该文件是否存在。
以下代码用于通过指定文件名来判断该文件是否存在:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  // 文件名填写不包含桶名称在内的文件的完整路径,例如 example/test.txt
  Key: '对象完整路径 (不包括桶名)', 
 };
s3.headObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

返回值解析

// 如果您得到的返回值如下,则表明该文件不存在
  {
    code: 'NotFound',
    region: null,
    time: 2022-01-26T07:36:04.142Z,
    requestId: '',
    extendedRequestId: undefined,
    cfId: undefined,
    statusCode: 404,
    retryable: false,
    retryDelay: 22.996084169055344
  }
    // 如果您得到的返回值如下,则表明该文件存在,并为您返回该文件的基础信息
  {
    AcceptRanges: 'bytes',
    LastModified: '2021-05-13T19:45:39.000Z',
    ContentLength: 2690910,
    ETag: '"0b973b664237ab1e28a03bd18a517175"',
    VersionId: 'Z9qjOPrV3zxi5fc2lkKf0KKlIKWeYx5',
    ContentType: 'video/mp4',
    Metadata: {},
    StorageClass: 'STANDARD_IA'
  }

​

管理文件元信息

文件 (Object) 元数据,是对用户上传到文件存储的文件的属性描述,可以在各种方式上传或者拷贝文件时进行设置。这些元信息会附在 HTTP 请求头部,上传时与文件一起存储,在下载时与文件一起返回。

以下代码用于在上传文件时设置文件的元信息:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
  // 注意:元信息只能是简单的 ASCII 可见字符且不能包含换行
  Metadata: {
    'name': 'sampleCode', 
    'year': '2022',
    'age': '18'
  }
};
s3.putObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

以下代码用于查询指定文件的元信息:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
}
s3.headObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

返回值:

{
  AcceptRanges: 'bytes',
  LastModified: '2022-02-16T06:40:25.000Z',
  ContentLength: 6,
  ETag: 'e10adc3949ba59abbe56e057f20f883e',
  ContentType: 'application/octet-stream',
  Metadata: { age: '18', name: 'sampleCode', year: '2022' }
}

​

管理文件访问权限

除了桶级别 ACL 以外,EOS 还提供了文件级别的 ACL。
也可以在上传 Object 时设置相应的 ACL,也可以在 Object 上传后的任意时间内根据自己的业务需求随时修改 ACL。
需要注意的是,文件 (Object) 的权限优先级要高于桶的权限。
比如桶的权限是私有读写的,但是文件 (Object) 的权限是公共可读的,则所有用户都可以访问该文件 (Object)。
默认情况下,只有文件 (Object) 的拥有者才能访问该文件 (Object),即文件 (Object) 的权限默认是私有读写的。

文件的访问权限分为三种:
1:私有 (private):文件的拥有者和授权用户有该文件的读写权限,其他用户没有权限操作该文件。
2:公共可读 (public-read):文件的拥有者和授权用户有该文件的读写权限,其他用户只有文件的读权限,安全风险高。
3:公共可读写 (public-read-write):所有用户都有该文件的读写权限,安全风险高。

以下代码用于设置指定文件的访问权限:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
  ACL: 'private'
};
s3.putObjectAcl(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

以下代码用于查询指定文件的访问权限:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
}
s3.getObjectAcl(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

列举文件

列举 (listObjects) 指定存储桶下的所有文件 (Object),您可以通过参数来指定前缀、个数等。

返回桶中的部分或全部文件 (最多可达 1000 个)。您可以使用请求参数作为选择条件来返回桶中的文件子集。

以下代码用于请求查看指定桶内的文件列表:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  // 对文件名称进行分组的字符,例如'/'
  Delimiter: '/',
  // 对返回的内容进行编码并指定编码类型为 URL,可选值为:'url' | ''
  EncodingType: 'url',
  // 指定桶拥有者
  ExpectedBucketOwner: 'STRING_VALUE',
  // 列举文件名在 marker 之后的文件
  Marker: 'STRING_VALUE',
  // 设置按字母排序最多返回前 10 个文件
  MaxKeys: 'NUMBER_VALUE',
  // 列举符合特定前缀的文件
  Prefix: 'STRING_VALUE'
};
s3.listObjects(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

查询指定目录下的文件和子目录,可以编写如下代码:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  // 您将查看 aaa 文件夹下的 bbb 文件夹下的所有文件和目录
  Prefix: 'aaa/bbb/',
  // 设置正斜线 (/) 为文件夹的分隔符。
  Delimiter: '/'
};
s3.listObjects(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

根据前缀搜索文件,可以编写如下代码:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  // search-text 为您输入的搜索条件
  prefix: 'search-text'
};
s3.listObjects(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

实现分页功能,可以编写如下代码:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  // 每页展示 100 个文件
  MaxKeys: 100,
  // 上一页的最后一个文件名,第一页可传空字符串
  Marker: 'lastfilename',
  
};
s3.listObjects(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

拷贝文件

在同一个区域中,您可以将文件 (Object) 从一个目录中拷贝到同一桶的另一个目录中,也可以从一个桶拷贝到另一个桶中。
使用这个 API,您可以在指定目录中创建一个最大为

5GB

的文件 (Object) 副本。

注意事项:
1:您必须拥有源文件的读权限及目标桶的读写权限。
2:源桶和目标桶均未设置保留策略。
3:不支持跨地域拷贝文件。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: '桶名称',
  CopySource: '/被拷贝文件所在桶/被拷贝文件名称',
  Key: '目标对象完整路径 (不包括桶名)'
};
s3.copyObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

删除文件

以下代码用于删除单个文件:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)'
};
s3.deleteObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

以下代码用于批量删除:

可以使用 deleteObjects 接口批量删除文件。每次最多删除 1000 个 Object,并提供两种返回模式:
详细模式:包括了成功的与失败的结果,默认模式;
简单模式:只返回删除失败的结果。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Delete: {
   Objects: [
      {
     Key: '删除的文件 1',  // required
     VersionId: '2LWg7lQLnY41.maGB5Z6SWW.dcq0vx7b'
    }, 
      {
     Key: '删除的文件 2',  // required
     VersionId: 'yoz3HB.ZhCS_tKVEmIOr7qYyyAaZSKVd'
    }
   ], 
   // 返回模式:是否返回消息体
   Quiet: true || false
  };
}
s3.deleteObjects(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

解冻文件

归档类型的文件 (Object) 无法直接下载。如果您需要下载归档类型的文件,需要先进行解冻,等待解冻完成后才可以下载文件。

发送解冻请求后,文件需要几十分钟的解冻过程,解冻完成后才可以下载。
解冻的过期时间最大为 7 天。
只有归档类型的文件才可以被解冻,否则服务端会返回 403 InvalidObjectState 错误。
如果一个文件处于解冻过程中,客户端再次发送解冻请求,服务端会返回 409 RestoreAlreadyInProgress 错误。
如果一个文件还未解冻完成,此时去下载该文件,服务端会返回 403 InvalidObjectState 错误。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
  RestoreRequest: {
   // 设置文件的解冻过期时间,超过该时间后,文件不能下载,需要重新解冻
   Days: 3
  }
};
s3.restoreObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

生成共享链接

利用 getSignedUrl 接口,可以为一个文件 (Object) 生成一个预签名的 URL 链接。

带有预签名的 URL 链接,都带有过期时间。
链接不过期,可以将过期时间设置的很大,或将文件设置成公共可访问 (访问时不需要签名)。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
const signedUrlExpireSeconds = 60 * 5; // 设置 PreSignedUrl 的有效时长
const url = s3.getSignedUrl('getObject', {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
  Expires: signedUrlExpireSeconds
});
console.log(url);

​

管理版本控制

桶的版本状态包括非版本化 (默认)、开启版本控制及暂停版本控制三种。

设置桶的版本控制状态:

  • Enabled:启用桶中文件的版本控制。添加到桶中的所有文件都会收到一个唯一的版本 ID。
  • Suspended:禁用 bucket 中文件的版本控制。添加到 bucket 中的所有文件都会收到版本 ID null。

注意:

  • 在开启版本控制功能后,上传同名文件将不再删除旧文件,而是添加一个新的文件。普通的删除操作也不会将文件彻底删除,而是添加一个 Delete Marker 作为标识。
  • 桶开启版本控制功能之后,无法再关闭该功能,而只能暂停。
const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  VersioningConfiguration: {
   // 可选状态:Enabled | Suspended
   Status: "Enabled"
  }
};
s3.putBucketVersioning(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

获取桶的版本控制状态:

在默认情况下,桶的版本控制状态是 OFF,即关闭状态。


const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name'
};
s3.getBucketVersioning(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
  /* 
   // 返回值解析:
   已设置过:
   data = {
    Status: "Enabled"
   }
   // 未设置过:
   data = { }
   */
});

​

列举文件

使用 listVersions 接口,可以获取 bucket 中的文件 (Object) 及其各个版本。

该接口中的参数与 listObjects 作用相同,具体可以参见 listObjects 的介绍。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name'
};
s3.listObjectVersions(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

返回值示例:

// 返回值:
  data: {
    Versions: [{
      ETag: '6805f2cfc46c0f04559748bb039d69ae', 
      IsLatest: true, 
      Key: 'HappyFace.jpg', 
      LastModified: <Date Representation>, 
      Owner: {
        DisplayName: 'owner-display-name', 
        ID: 'example7a2f25102679df27bb0ae12b3fc'
      }, 
      Size: 3191, 
      StorageClass: 'STANDARD', 
      VersionId: 'yoz3HBZhCS_tKVEmIOr7qYyyAaZSKVd'
    },
      ...
    ]
  }

​

获取指定版本对象

获取早期版本的文件 (Object),可以在请求中携带其版本号。默认情况下获取的是当前版本的文件 (Object)。

const fs = require('fs')
const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
 
var params = {
  Bucket: 'your-bucket-name', 
  Key: "您需要下载的文件路径 + 文件名", 
  VersionId: 'xxxxxx 指定版本号'
 };
 s3.getObject(params, function(err, data) {
   if (err) console.log(err, err.stack);
   else {
        console.log(data)
    }
 })

​

拷贝指定版本文件

在受版本控制的桶中拷贝指定版本号的文件 (Object)。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: '桶名称',
  // 在 CopySource 后传入 ?versionId=指定版本号 即可
  CopySource: '/被拷贝文件所在桶/被拷贝文件名称 ?versionId=QUpfdndhfd8438MNFDN93jdnJFkdmqnh893',
  Key: '目标文件完整路径'
};
s3.copyObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

彻底删除文件

默认情况下,如果开启了版本控制,删除文件 (Object) 不会将该文件彻底删除,而是添加一个 Delete Marker,来作为标识。

确定不再需要某个文件,则在删除中携带该文件的版本号,就可以将其彻底删除了。
文件的版本号可以通过 listVersions 接口获取。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: "对象完整路径 (不包括桶名)",
  VersionId: '对象版本号'
};
s3.deleteObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

解冻文件

在受版本控制的桶中,文件 (Object) 的各个版本可以对应不同的存储类型。
RestoreObject 接口默认解冻文件的当前版本,您可以通过指定 versionId 的方式来解冻指定版本的 Object。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
  RestoreRequest: {
   // 设置文件的解冻过期时间,超过该时间后,文件不能下载,需要重新解冻
   Days: 3
  },
  VersionId: 'xxxxxx 指定版本号'
};
s3.restoreObject(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

管理文件访问权限

以下代码用于设置指定文件的访问权限:

PutObjectACL 默认设置 Object 当前版本的 ACL 权限,请求参数中指定 versionId 可以设置指定 Object 版本的 ACL 权限。

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
  ACL: 'private',
  VersionId: 'xxxxxx 指定版本号'
};
s3.putObjectAcl(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

以下代码用于查询指定文件的访问权限:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
  Key: '对象完整路径 (不包括桶名)',
  VersionId: 'xxxxxx 指定版本号'
}
s3.getObjectAcl(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

设置桶加密

支持服务端加密的地域包括

华东-无锡、 华东-上海2、华东-苏州2、华东-苏州3

可以通过以下代码设置桶的默认加密方式,设置成功之后,所有上传至该桶但未设置加密方式的 Object 都会使用桶默认加密方式进行加密:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name',
   ServerSideEncryptionConfiguration: {
    Rules: [
      {
        ApplyServerSideEncryptionByDefault: {
          // 指定桶的加密方式,可选值:AES256 或 aws:kms
          SSEAlgorithm: AES256 | aws:kms,
          // 若使用 KMS 加密,需添加 KMSMasterKeyID 属性
          KMSMasterKeyID: 'STRING_VALUE'
        }
      }
    ]
  }
}
s3.putBucketEncryption(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

获取桶加密配置

支持服务端加密的地域包括

华东-无锡、 华东-上海2、华东-苏州2、华东-苏州3

以下代码用于查询指定桶的加密配置:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name'
}
s3.getBucketEncryption(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

删除桶加密配置

支持服务端加密的地域包括

华东-无锡、 华东-上海2、华东-苏州2、华东-苏州3

以下代码用于删除指定桶的加密配置:

const AWS = require('aws-sdk')

var s3 = new AWS.S3({
    // 移动云 EOS access key ID 拥有所有 API 的访问权限,风险很高。建议您谨慎使用,以防泄露。
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-access-key',
    // endpoint 填写 bucket 所在地域的域名,本章节以 wuxi1 为例
    endpoint: 'eos-wuxi-1.cmecloud.cn'
});
var params = {
  Bucket: 'your-bucket-name'
}
s3.deleteBucketEncryption(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else     console.log(data);
});

​

标签: node.js

本文转载自: https://blog.csdn.net/lbcyllqj/article/details/139102819
版权归原作者 淼学派对 所有, 如有侵权,请联系我们删除。

“四万字长文详解——node.js使用移动云,EOS对象存储”的评论:

还没有评论