上传文件到七牛空间

3,343 阅读5分钟

服务端进行上传

服务端将图片源传递到七牛,这种方式的流程是

1 前端将图片传到自己的服务器 2 在server将文件传递到七牛

前端

需要注意,要使用 formData 格式进行文件上传, 关于 formData

document.querySelector('input').onchange = () => {
  const file = document.querySelector('input').files[0]

  let formData = new FormData()
  formData.append('file', file)

  // fetch调用接口
  fetch(path, {
    body: formData,
    credentials: 'same-origin',
    method: 'POST'
  })
}

server 接口部分

  • 基本参数
const bucket = 'cancan'
const accessKey = '2LC7KPjwnYdxxxc'
const secretKey = 'SXrxxx'
const domain = 'http://pq3xxxdn.com'
  • 获取token
function get_token () {
  const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
  const policyParams = { scope: bucket }
  const putPolicy = new qiniu.rs.PutPolicy(policyParams);
  const uploadToken = putPolicy.uploadToken(mac);
  return uploadToken
}
  • 拿到文件path之后进行七牛上传
async function uploadFile(localFile, key) {
  const uploadToken = get_token()

  const config = new qiniu.conf.Config();
  // 空间对应的机房
  config.zone = qiniu.zone.Zone_z0;

  const formUploader = new qiniu.form_up.FormUploader(config);
  const putExtra = new qiniu.form_up.PutExtra();

  return new Promise((resolve, reject) => {
    // 文件上传
    formUploader.putFile(uploadToken, key, localFile, putExtra, function (respErr, respBody, respInfo) {
      if (respErr) {
        reject('error')
      }
      if (respInfo.statusCode == 200) {
        const { key } = respBody
        resolve(`${domain}/${key}`)
      } 
    });
  })
}
  • 接口
router.post('/upload', async (ctx) => {
  // 获取到上传到服务器的文件信息
  const { img } = ctx.request.files
  // path 拿到临时文件地址
  const { path } = img

  // key 是给图片的命名
  const key = Math.random() * 100 + '.png'

  const result = await uploadFile(path, key)

  ctx.body = result
})

遇到的问题

  • post 接口 始终无法解析到file参数

后来发现是因为 中间件只使用了 koa-bodyparser, 一般图片上传使用的是 koa-multer,可以使用 koa-body来代替这两个

参考文章

  • 临时文件占用大量磁盘空间

可以看到,在将文件上传到服务器的时候,会创建一个临时文件- 接口中解析的那个 path

path

对于服务器,可以写一个定时的脚本去清除这些文件。或者我们在脚本增加一段逻辑,在成功上传到七牛之后,手动将临时文件进行删除

fs.unlink(path, (err) => {
  if (err) throw err;
  console.log('文件已删除', path);
})

前端直接上传

项目中的例子涉及到的图片上传,都是将文件上传到服务器然后再去传到七牛空间,这种操作方式,相当于在自己的服务器做了一层中转站。开发者可以在中转站对图片做一些处理然后在传到七牛空间。但是有一些缺点

  • 内存占用量增大

  • 临时文件占用磁盘空间,需要每次上传之后需要进行文件删除

如果开发中不需要中转站,可以考虑直接从前端将图片上传到七牛空间

参考七牛的API文档,基本可以涉及到的是

获取上传凭证 token

router.get('/qiniu', async (ctx, next) => {
  const bucket = 'activity'
  const accessKey = '2LCxxxrxxx'
  const secretKey = 'SXrdqdvxxx'

  const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
  const policyParams = { scope: bucket }
  const putPolicy = new qiniu.rs.PutPolicy(policyParams);
  const uploadToken = putPolicy.uploadToken(mac);
  ctx.body = uploadToken
})

前端上传代码

  • base64转 blob
    function base64ToBlob(dataurl) { 
      var arr = dataurl.split(','), 
      mime = arr[0].match(/:(.*?);/)[1], 
      bstr = atob(arr[1]), 
      n = bstr.length, 
      u8arr = new Uint8Array(n); 
      while (n--) { 
        u8arr[n] = bstr.charCodeAt(n); 
      } 
      return new Blob([u8arr], { type: mime }); 
    }
  • 基本参数
// 这个token 就是 server接口返回的token
const token = '2LC7KTxxx'

const config = {
  useCdnDomain: true,
  region: qiniu.region.z1
};

const putExtra = {
  fname: "",
  params: {},
  mimeType: [] || null
};

  • 核心上传
const inputTarget = document.querySelector('input')
inputTarget.onchange = (data) => {
    const imgfile = inputTarget.files[0]
   
    const reader = new FileReader();
    reader.readAsDataURL(imgfile);
    reader.onload = function (e) {
      const urlData = this.result;
      const blobData = base64ToBlob(urlData)
      
      // 这里第一个参数的形式是blob
      const observable = qiniu.upload(blobData, 'filename.png', token, putExtra, config)
      
      const observer = {
        next(res) {console.log(res)},
        error(err) {console.log(err)},
        complete(res) {console.log(res)}
       }
       // 注册observer 对象
      observable.subscribe(observer)
    }
    
}

result

文件就可以被传到七牛了

可以看到这里返回的是一个hash和key,图片最后完整的访问路径,是你的七牛上配置的域名+key

在七牛创建一个新空间的话,会提供一个30天的免费域名可以使用

遇到的问题

  • cdnUphost 解析失败

原来是把

 const config = {
    useCdnDomain: true,
    region: 'qiniu.region.z0'
 }

这里的region 写错了,不应是字符串,就是一个七牛的变量

 const config = {
    useCdnDomain: true,
    region: qiniu.region.z0
 }

这个参数是取决于你的空间存储区域选择的是哪里

对照文档 区域 进行z0 z1的选择

  • 图片上传之后却无法读取,显示已损坏

是因为第一次的时候,直接将 input的files[0] 值上传, 这里与文档要求的参数格式不一致 文档里对这个参数的要求是blob,所以记得这里需要转化一下文件格式

以前的demo写的一直失败就搁置了,今天重新跑了一次,再次吐槽七牛的文档,写的真是让人头大

编写 webpack 上传七牛的插件

在开发中,有使用一个webpack的配置, qiniu-webpack-plugin

参照这个的使用方式,另外写一个上传plugin

涉及到的一些文档

plugin 使用

    // 这个是编译文件编译到的文件夹
    const filePath = path.resolve(__dirname, 'public')
    
    new QiuniuUploadPlugin({
      bucket,
      accessKey,
      secretKey,
      domain,
      path: filePath
    })

类的实现

这个的逻辑大部分和从后端直接上传到七牛有重合,这里只把另一部分写出来

  • 大致实现
class QiuniuUploadPlugin {
  constructor () {...}
  
  uploadQn (filename) {...}
  
  apply (compiler) {
    const pluginName = 'QiuniuUploadPlugin'
    // 事件钩子
    compiler.hooks.run.tap(pluginName, compilation => {
      console.log("webpack 构建过程开始!")
    });

    // afterEmit - 生成资源到 output 目录之后将触发  异步钩子 - tapPromise
    compiler.hooks.afterEmit.tapPromise(pluginName, (compilation) => {
      let { assets } = compilation
      // assets 这里拿到的assets是一个对象 key是文件名 value是相关关参数 我们只需要key
      // assets -->>>  { 'index.js' : xxxx }

      // 遍历所有的文件名 进行七牛的上传
      const allUplaod = Object.keys(assets).map((item) => {
        return this.uplaodQn(item)
      })

      return Promise.all(allUplaod)

    })
  }
}