<返回更多

前端如何进行单文件上传云服务存储

2023-06-01  政采云技术  
加入收藏

在日常的开发过程中,我相信大家肯定会碰到很多的文件上传需求,例如流程中的附件,设置头像图片等等内容,并且上传的文件,为了前端页面的加载性能,一般也都会选择将文件上传至云服务存储当中去,之后直接使用文件的 cdn 路径来访问。那么问题来了,对于文件如何上传到云服务存储当中去大家是否了解呢?上传流程有遇到什么困难吗,所以这篇文章也借着我们团队遇到的一些问题,跟大家交流一下云服务文件存储当中的一些问题与解决方式。

目前常用的上传方式

后端上传

不知道大家日常使用的上传方式是否和我们团队一致,之前上传文件方案中,我司后端团队会提供一个后端上传服务接口,前端直接使用这个接口进行文件上传,后端接受到完整文件后,会再通过调用云文件服务提供的后端 JAVA SDK 进行文件上传

图片

这个方案的优缺点

优点:前端所有使用的上传接口统一,前端统一对接公司内部的上传服务,后端上传服务再去对接各个不同的云存储服务厂家,保证文件上传

缺点:后端服务需要接受所有的文件上传的流量,然后再次进行上传,服务器压力比较大。

基于上面提到的缺点,在经历过服务器压力过大,导致几次大文件上传失败、各种外地网络延迟导致超时故障之后,痛定思痛,决定要重新调整上传的方式。

前端上传

既然后端服务上传需要走流程传输导致资源压力过大,那是否可以可以将压力转移到用户侧,使用用户的浏览器直连云存储服务进行上传呢?答案是当然可以,不然也就没有本文了。

在翻阅了几个不同的云服务的上传文档后发现,目前主流常用的前端上传方案会分为两种方式:

  1. 前端调用各大云服务的 JavaScript SDK 进行上传
  1. 优点:无需后端服务介入,直接调用各个云服务 SDK 方法使用即可
  2. 缺点:前端需要获取各个云服务的 AK (AccessKey ID),SK (AccessKey Secret) 等账号信息,并且会暴漏在代码中,并且各个云服务场景会有对应的 SDK 以及调用方式,全部做了集成的话,包的体积可能不可控,并且有些云服务商,没有提供前端使用的SDK。
  1. 云服务会提供临时授权的 URL,前端可以直接通过这个授权 URL 访问云服务,进行文件上传
  2. 优点:前端不需要获取云服务的 AK (AccessKey ID),SK (AccessKey Secret) 信息,统一由后端接口提供对应上传所需的请求地址,数据格式即可,前端通过一个接口获取这些信息后,调用上传即可

  3. 缺点:各家云服务上传所需的数据格式都不相同,前端需要调研,解析这个数据格式

上传示例

下面以大家常用的阿里云举例

SDK上传

webpack打包类型项目,可以先通过 npm install ali-oss 安装 SDK,以下为上传数据到 examplebucket 中 exampledir 目录下的exampleobject.txt 文件的代码示例

const OSS = require('ali-oss');

const client = new OSS({
  // 以下为初始化参数
  region: 'yourRegion',
  // 从 STS 服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
  accessKeyId: 'yourAccessKeyId',
  accessKeySecret: 'yourAccessKeySecret',
  // 从STS服务获取的安全令牌(SecurityToken)。
  stsToken: 'yourSecurityToken',
  // 填写 Bucket 名称(可以简单理解为,你上传不同文件到不同的文件夹命名)。
  bucket: 'examplebucket'
});
// 从输入框获取 file 对象,例如 <input type="file" id="file" />。
let data;
// 创建并填写 Blob 数据。
//const data = new Blob(['Hello OSS']);
// 创建并填写 OSS Buffer内容。
//const data = new OSS.Buffer(['Hello OSS']);
const upload = document.getElementById("upload");
const headers = {
  // 以下为上传时可以设置的一些 header 数据,不同云服务需要的不同,具体参考各个版本文档
  // 'Content-Type': 'text/html', // 指定上传文件的类型。
  // 'Cache-Control': 'no-cache',  // 指定该 Object 被下载时网页的缓存行为。
  // 'Content-Disposition': 'oss_download.txt',  // 指定该 Object 被下载时的名称。
  // 'Content-Encoding': 'UTF-8',  // 指定该 Object 被下载时的内容编码格式。
  // 'Expires': 'Wed, 08 Jul 2022 16:57:01 GMT',  // 指定过期时间。
  // 'x-oss-storage-class': 'Standard',  // 指定 Object 的存储类型。
  // 'x-oss-object-acl': 'private',  // 指定 Object 的访问权限。
};
async function putObject(data) {
  try {
    // 填写Object完整路径。Object 完整路径中不能包含 Bucket 名称。
    // 您可以通过自定义文件名(例如 exampleobject.txt )或文件完整路径(例如 exampledir/exampleobject.txt )的形式实现将数据上传到当前 Bucket 或 Bucket 中的指定目录。
    // data 对象可以自定义为 file 对象、Blob 数据或者 OSS Buffer。
    const result = awAIt client.put(
      "exampledir/exampleobject.txt",
      data
      //{headers}
    );
    console.log(result);
  } catch (e) {
    console.log(e);
  }
}
upload.addEventListener("click", () => {
  data = document.getElementById("file").files[0];
  putObject(data);
});

直接调用 SDK 中提供的 put 等方法即可完成文件上传

临时 URL 上传(STS 临时授权)

鉴于 SDK 上传方案中,会在代码中暴漏 AK (AccessKey ID),SK (AccessKey Secret)  等云服务数据,所以云服务厂家一般也会提供生成临时令牌的方式,可以由后端服务生成一个自定义时效以及权限的访问凭证提供给前端进行上传,有效期到期后,这个访问令牌就会失效,保证了前端上传的安全性。

图片

1. 客户端向自己的后端应用发起请求,将文件类型,名称信息等传给后端,获取对应的上传信息以及授权签名信息 signature 等,

const UploadParams = {
  "accessid":"LTAI5tBDFVar1hoq****",
  "host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
  "policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDoyMzoyM1oiLCJjxb25kaXRpb25zIjpbWyJjcb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8i****",
  "signature":"VsxOcOudx******z93CLaXPz+4s=",
  "expire":1446727949,
  "dir":"user-dirs/"
}

2. 在获取到服务器返回的签名信息等内容后,客户端则可以通过 POST 或者 PUT 请求直接向云服务发送上传文件的请求(上传形式多种多样,并且有些云服务有要求上传数据类型为 form-data 格式)

// form-data 类型
let params = {
   // key表示上传到 Bucket 内的 Object 的完整路径,例如 exampledir/exampleobject.txtObject,完整路径中不能包含 Bucket 名称。
   // filename 表示待上传的本地文件名称。
   'key' : key + '${filename}',
   'policy': UploadParams.policy,
   'OSSAccessKeyId': UploadParams.accessid,
   // 设置服务端返回状态码为200,不设置则默认返回状态码204。
   'success_action_status' : '200',    
   'signature': UploadParams.signature,
}
let requestData = new FormData();

Object.keys(params).map(key => {
  requestData.Append(key, params[key]);
});
// 获取的上传 file 文件,file 必须为最后一个表单域,除 file 以外的其他表单域无顺序要求
requestData.append('file', fileObj);

// 非 form-data 类型(非阿里云云服务会遇到,以下代码仅举例,不代表真实使用场景)
let requestData = fileObj;
let headers = {
   'key' : key + '${filename}',
   'policy': UploadParams.policy,
   'OSSAccessKeyId': UploadParams.accessid,
   'success_action_status' : '200',    
   'signature': UploadParams.signature,
}

// 进行接口请求,上传文件
axIOS({
  method: 'post',
  url: params.host,
  data: requestData,
  headers: headers || {},
});

这里代码只是简单的示例,实际使用时需要对各个文件服务需要进行不同的适配。

加密算法和解析

对于获取 Signature 鉴权信息等内容时,后端服务在有文档或者 SDK 时,可以对接不同的云服务 JAVA SDK 直接进行生成临时授权的信息,在没有文档的情况下,则需要前端或者后端,针对各个不同的云服务,进行解析加密 Signature 的步骤(我司这里是前端进行了加密过程解析后,后续日常生成由后端服务完成)。

加密算法

此处我以紫光云的 Signature 生成步骤给大家简单介绍下加密算法的流程,不同的云服务,加密过程都比较类似。

图片

图片来源:紫光云上传流程(https://www.unicloud.com/document/show-19262078.html)

以下是根据上述的加密流程写的测试生成 Signature 的代码部分,大家也可以自行测试试用。

按流程主要分成3步即可

  1. 生成 CanonicalRequest 字段
  2. 生成前面的 StringToSign
  3. 根据 AK (AccessKey ID),SK (AccessKey Secret)  生成 Signature,最后组装 Authorization。
const crypto = require('crypto');
const CryptoJS = require('crypto-js')

function zip() {
  const filename = 'uploadTest.png'
  // const date = new Date()
  // const timeStampISO8601Format = `${date.toISOString().replace(/-/g, '').replace(/:/g, '').split('.')[0]}Z` // ISO 8601 格式
  const timeStampISO8601Format = '20230101T000000Z' // ISO 8601 格式
  const dateString = timeStampISO8601Format.substr(0, 8) // YYYYMMDD 格式时间

  const uriFileName = uriEscapePath(filename)
  const content = 'UNSIGNED-PAYLOAD'
  
  // 生成 CanonicalRequest 字段
  let CanonicalRequest = `PUTn${uriFileName}nncontent-disposition:attachment;filename=uploadTest.pngncontent-type:image/pngnhost:oos-cn.ctyunapi.cnnx-amz-content-sha256:${content}nx-amz-date:${timeStampISO8601Format}nncontent-disposition;content-type;host;x-amz-content-sha256;x-amz-daten${content}`
  let hashedCanonicalRequest = crypto.createHash('sha256').update(CanonicalRequest).digest('hex');

  // 生成前面的 StringToSign
  const signStr = `AWS4-Hmac-SHA256n${timeStampISO8601Format}n${dateString}/cn/s3/aws4_requestn${hashedCanonicalRequest}`
  //根据 AK (AccessKey ID),SK (AccessKey Secret) 生成 Signature
  const AWSAccessKeyId = 'AWSAccessKeyId';
  const AWSSecretAccessKey = 'AWSSecretAccessKey';

  var DateKey = CryptoJS.HmacSHA256(dateString, `AWS4${AWSSecretAccessKey}`);
  var DateRegionKey = CryptoJS.HmacSHA256('cn', DateKey);
  var DateRegionServiceKey = CryptoJS.HmacSHA256('s3', DateRegionKey);
  var SigningKey = CryptoJS.HmacSHA256('aws4_request', DateRegionServiceKey);
  var Signature = CryptoJS.HmacSHA256(signStr, SigningKey);
  console.log('
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>