aws-sdk for JavaScript 对接私有云对象存储

1,819 阅读1分钟

背景

原本通过 NFS 的方式实现了和私有云(腾讯)的对接,但是 NFS 的方式上传文件会有延时,所以不得不通过其他方式上传文件。通过集成 aws-sdk,用类的方式封装了 s3 实例的创建过程和上传逻辑,提供更加简便的上传入口。

实现

封装上传逻辑

这里只是简单使用了上传的功能,而且是对接的私有云,更多关于 sdk 的使用方法可以参考官方 API 文档

引入 sdk 依赖

npm install aws-sdk --save

封装上传逻辑

这里简单实现了一个类,在构造函数里创建 s3 实例,options 是配置选项,可以在 upload() 中传,我这里是设置默认值。upload() 中返回 Promise 实例,调用方可以获取上传成功后的数据做其他操作。

import AWS from 'aws-sdk'

class CloudStorage {
  constructor () {
    this.s3 = new AWS.S3({
      apiVersion: '2006-03-01',
      endpoint: /** 上传地址 */ 'xxx',
      accessKeyId: /** 密钥 */ 'xxx',
      secretAccessKey: /** 私钥 */ 'xxx',
      s3ForcePathStyle: true
    })
    this.options = { partSize: 10 * 1024 * 1024, queueSize: 1 }
  }

  upload (bucket = 'default', dir = 'default', file) {
    const self = this
    return new Promise((resolve, reject) => {
      self.s3.upload({
        Bucket: bucket,
        Key: `${dir}/${new Date().getTime()}/${file.name}`,
        Body: file.raw
      }, self.options, (err, data) => {
        if (err) {
          console.log('文件上传云存储失败', err)
          reject(err)
        } else {
          console.log('文件上传云存储成功', data)
          resolve(data)
        }
      })
    })
  }
}

使用

new CloudStorage().upload(bucket, dir, file)

扩展/优化

单例模式

上面的实现有一个问题,就是每次创建 CloudStorage 对象时,都会去调用一次构造函数,也就造成了重复地创建 s3 实例,但其实我们只要创建一次就行了。因此这里可以通过单例模式进行优化,只对外提供唯一的实例。 单例模式代码如下:

function getInstance () {
  if (!CloudStorage.instance) {
    CloudStorage.instance = new CloudStorage()
  }
  return CloudStorage.instance
}

// 调用
getInstance().upload(bucket, dir, file)

柯里化

通过上面的单例模式优化后,已经可以很简单地调用 upload 上传文件了。但是别急,还可以继续优化。

如果我们像上面一样调用,上传单个文件是没问题,但是如果是这样的场景:多个文件上传或者多个文件上传到不同的 bucket 或文件夹下,又好像还是有些多余的操作存在,我们来看一下调用:

getInstance().upload('bucket1', 'dir1', file1)
getInstance().upload('bucket1', 'dir1', file2)
getInstance().upload('bucket2', 'dir2', file3)
...

可以这里的 bucket 和文件夹参数重复地去传了,有没有什么办法可以减少这样的参数传递?有!通过柯里化,就是一种将一个多参数函数转换成一系列使用一个参数的函数的技术。

可以通过判断参数的数量来判断,当参数满足函数需要之后才真正调用真实的函数。代码实现:

/**
 * 这里的实现不止可接收一个参数
 * @param {function} 真实调用的函数
 * @return {function} 转换后的函数
 */
function curry (fn) {
  const judge = (...args) =>
    args.length < fn.length
      ? (...nextArgs) => judge(...args, ...nextArgs)
      : fn(...args)
  return judge
}

// 现在的调用方式
const curryUpload = curry(getInstance().upload)
let bucket1Upload = curryUpload('bucket1')
let bucket2Upload = curryUpload('bucket2')
let dir1Upload = bucket1Upload('dir1')
let dir2Upload = bucket2Upload('dir2')
dir1Upload(file1)
dir2Upload(file2)
dir1Upload(file3)

是不是感觉这样调用舒服多了:)。 但是因为使用了柯里化,这里要注意有两个坑的地方:

  • 函数参数有默认值的情况下,函数的 length 值为 0,所以不能在参数列表的地方设置默认值,可以在函数体里判断。
  • 用了柯里化之后(因为我的柯里化实现是通过箭头函数的),返回的函数 this 执行不是指向 CloudStorage 实例了,但我们还是可以通过 getInstance() 来获取实例。

完整版代码如下:

import AWS from 'aws-sdk'
import { curry } from '../utils/utils' // 柯里化的函数,大家也可用自己的实现

class CloudStorage {
  constructor () {
    this.s3 = new AWS.S3({
      apiVersion: '2006-03-01',
      endpoint: /** 上传地址 */ 'xxx',
      accessKeyId: /** 密钥 */ 'xxx',
      secretAccessKey: /** 私钥 */ 'xxx',
      s3ForcePathStyle: true
    })
    this.options = { partSize: 10 * 1024 * 1024, queueSize: 1 }
  }

  upload (bucket, dir, file) {
    if (!bucket) bucket = 'default'
    if (!dir) dir = 'default'
    const instance = getInstance()
    return new Promise((resolve, reject) => {
      instance.s3.upload({
        Bucket: bucket,
        Key: `${dir}/${new Date().getTime()}/${file.name}`,
        Body: file.raw
      }, instance.options, (err, data) => {
        if (err) {
          console.log('文件上传云存储失败', err)
          reject(err)
        } else {
          console.log('文件上传云存储成功', data)
          resolve(data)
        }
      })
    })
  }
}

function getInstance () {
  if (!CloudStorage.instance) {
    CloudStorage.instance = new CloudStorage()
  }
  return CloudStorage.instance
}

export const upload = curry(getInstance().upload)

OK,到这里就已经完成了。应用方面,我还封装了 element ui,在 element ui 的组件里调用上面的函数来去把文件上传到云存储,有时间再把这部分也补上。:)