koa踩坑路之–传输blob或ArrayBuffer,koa-body意想不到的坑~~~~~

提示:本来打算尝试一下直接把input上传的blob二进制对象或者fileReader.readAsArrayBuffer得到的二进制缓冲区ArrayBuffer直接传给后台,但是后台接收到的并非blob和ArrayBuffer,而是一个稀奇古怪的对象(为什么说稀奇古怪呢,自己打印一下就知道了);


前言

学习多种方式上传图片—学习文档,大神绕行

一、Blob或ArrayBuffer直接传输

示例:koa-body配置的时候,multipart为true,是为了接收formData数据并解析,问题就出在了这里,formData是可以解析了,
blob和arrayBuffer无法直接接收解析了

	//如果想直接传输blob和arrayBuffer,去koa的app.js
	//注释掉koa-body,
	//注释掉koa-body,
	//注释掉koa-body,
	//重要的事情说3遍
app.use(koaBody({ 
    multipart: true,
    formidable: {
		//上传文件存储目录
		uploadDir:  path.join(__dirname, `/public/uploads/`),
		//允许保留后缀名
		keepExtensions: true,
		multipart: true,
	},
    jsonLimit:'10mb',
    formLimit:'10mb',
    textLimit:'10mb'
}));

koa-body源码中对接收到的数据进行了处理,所以配置过koa-body的项目,接收到的数据都会处理成一个新的对象。
下面附上koa-body源码
通过//--------xxxxxx---------标注问题冲突的点

/**
 * koa-body - index.js
 * Copyright(c) 2014
 * MIT Licensed
 *
 * @author  Daryl Lau (@dlau)
 * @author  Charlike Mike Reagent (@tunnckoCore)
 * @api private
 */

'use strict';

/**
 * Module dependencies.
 */

const buddy = require('co-body');
const forms = require('formidable');
const symbolUnparsed = require('./unparsed.js');

/**
 * Expose `requestbody()`.
 */

module.exports = requestbody;

const jsonTypes = [
  'application/json',
  'application/json-patch+json',
  'application/vnd.api+json',
  'application/csp-report'
];

/**
 *
 * @param {Object} options
 * @see https://github.com/dlau/koa-body
 * @api public
 */
function requestbody(opts) {
  opts = opts || {};
  opts.onError = 'onError' in opts ? opts.onError : false;
  opts.patchNode = 'patchNode' in opts ? opts.patchNode : false;
  opts.patchKoa = 'patchKoa' in opts ? opts.patchKoa : true;
  opts.multipart = 'multipart' in opts ? opts.multipart : false;
  opts.urlencoded = 'urlencoded' in opts ? opts.urlencoded : true;
  opts.json = 'json' in opts ? opts.json : true;
  opts.text = 'text' in opts ? opts.text : true;
  opts.encoding = 'encoding' in opts ? opts.encoding : 'utf-8';
  opts.jsonLimit = 'jsonLimit' in opts ? opts.jsonLimit : '1mb';
  opts.jsonStrict = 'jsonStrict' in opts ? opts.jsonStrict : true;
  opts.formLimit = 'formLimit' in opts ? opts.formLimit : '56kb';
  opts.queryString = 'queryString' in opts ? opts.queryString : null;
  opts.formidable = 'formidable' in opts ? opts.formidable : {};
  opts.includeUnparsed = 'includeUnparsed' in opts ? opts.includeUnparsed : false
  opts.textLimit = 'textLimit' in opts ? opts.textLimit : '56kb';

  // @todo: next major version, opts.strict support should be removed
  if (opts.strict && opts.parsedMethods) {
    throw new Error('Cannot use strict and parsedMethods options at the same time.')
  }

  if ('strict' in opts) {
    console.warn('DEPRECATED: opts.strict has been deprecated in favor of opts.parsedMethods.')
    if (opts.strict) {
      opts.parsedMethods = ['POST', 'PUT', 'PATCH']
    } else {
      opts.parsedMethods = ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE']
    }
  }

  opts.parsedMethods = 'parsedMethods' in opts ? opts.parsedMethods : ['POST', 'PUT', 'PATCH']
  opts.parsedMethods = opts.parsedMethods.map(function (method) { return method.toUpperCase() })

  return function (ctx, next) {
    var bodyPromise;
    // only parse the body on specifically chosen methods
    if (opts.parsedMethods.includes(ctx.method.toUpperCase())) {
      try {
        if (opts.json && ctx.is(jsonTypes)) {             //--------xxxxxx---------对类型判断,分别进行不同处理
          bodyPromise = buddy.json(ctx, {
            encoding: opts.encoding,
            limit: opts.jsonLimit,
            strict: opts.jsonStrict,
            returnRawBody: opts.includeUnparsed
          });
        } else if (opts.urlencoded && ctx.is('urlencoded')) {
          bodyPromise = buddy.form(ctx, {
            encoding: opts.encoding,
            limit: opts.formLimit,
            queryString: opts.queryString,
            returnRawBody: opts.includeUnparsed
          });
        } else if (opts.text && ctx.is('text/*')) {
          bodyPromise = buddy.text(ctx, {
            encoding: opts.encoding,
            limit: opts.textLimit,
            returnRawBody: opts.includeUnparsed
          });
        } else if (opts.multipart && ctx.is('multipart')) {    //--------xxxxxx---------当设置multipart后,执行了formy
          bodyPromise = formy(ctx, opts.formidable);
        }
      } catch (parsingError) {
        if (typeof opts.onError === 'function') {
          opts.onError(parsingError, ctx);
        } else {
          throw parsingError;
        }
      }
    }

    bodyPromise = bodyPromise || Promise.resolve({});
    return bodyPromise.catch(function(parsingError) {
      if (typeof opts.onError === 'function') {
        opts.onError(parsingError, ctx);
      } else {
        throw parsingError;
      }
      return next();
    })
    .then(function(body) {
      if (opts.patchNode) {
        if (isMultiPart(ctx, opts)) {
          ctx.req.body = body.fields;
          ctx.req.files = body.files;
        } else if (opts.includeUnparsed) {
          ctx.req.body = body.parsed || {};
          if (! ctx.is('text/*')) {
            ctx.req.body[symbolUnparsed] = body.raw;
          }
        } else {
          ctx.req.body = body;
        }
      }
      if (opts.patchKoa) {
        if (isMultiPart(ctx, opts)) {
          ctx.request.body = body.fields;
          ctx.request.files = body.files;
        } else if (opts.includeUnparsed) {
          ctx.request.body = body.parsed || {};
          if (! ctx.is('text/*')) {
            ctx.request.body[symbolUnparsed] = body.raw;
          }
        } else {
          ctx.request.body = body;
        }
      }
      return next();
    })
  };
}

/**
 * Check if multipart handling is enabled and that this is a multipart request
 *
 * @param  {Object} ctx
 * @param  {Object} opts
 * @return {Boolean} true if request is multipart and being treated as so
 * @api private
 */
function isMultiPart(ctx, opts) {
  return opts.multipart && ctx.is('multipart');
}

/**
 * Donable formidable
 *
 * @param  {Stream} ctx
 * @param  {Object} opts
 * @return {Promise}
 * @api private
 */
function formy(ctx, opts) {                                       //--------xxxxxx---------这里就是formy了
  return new Promise(function (resolve, reject) {
    var fields = {};
    var files = {};
    var form = new forms.IncomingForm(opts);
    form.on('end', function () {
      return resolve({
        fields: fields,
        files: files
      });
    }).on('error', function (err) {
      return reject(err);
    }).on('field', function (field, value) {
      if (fields[field]) {                                        //--------xxxxxx---------对field进行了处理
        if (Array.isArray(fields[field])) {
          fields[field].push(value);
        } else {
          fields[field] = [fields[field], value];
        }
      } else {
        fields[field] = value;
      }
    }).on('file', function (field, file) {                       //--------xxxxxx---------对file进行了处理
      if (files[field]) {
        if (Array.isArray(files[field])) {
          files[field].push(file);
        } else {
          files[field] = [files[field], file];
        }
      } else {
        files[field] = file;
      }
    });
    if (opts.onFileBegin) {
      form.on('fileBegin', opts.onFileBegin);
    }
    form.parse(ctx.req);
  });
}

当我们直接传blob或者arrayBuffer时,会被重新生成一个对象返给ctx.request.body,然后这个对象经过不懈的努力,解出来的字符串或buffer都和原始数据有冲突(有能力的小伙伴可以自行试一下),于是有了下面的解决方法,仅限测试踩坑,实际应用中用处不大

二、使用步骤

1.vue中HelloWorld.vue

代码如下(示例):

<template>
  <div class="hello">
    <h1>{{msg}}</h1>
    <el-upload class="avatar-uploader" action="''" :http-request="uploadBuffer" :show-file-list="false" :before-upload="beforeAvatarUpload">
      <img v-if="imgUrl" :src="imgUrl" class="avatar">
      <i v-else class="el-icon-plus avatar-uploader-icon"></i>
    </el-upload>
  </div>
</template>
import { uploadImg } from "@/axios/index";
export default {
  name: "HelloWorld",
  data() {
    return {
      imgUrl: null,
      msg: "", //"活在当下!",
    };
  },
  methods: {
    beforeAvatarUpload(file) {
      const isImage = file.type === "image/jpeg" || "image/png";
      const limitSize = file.size / 1024 / 1024 < 2;
      if (!isImage) {
        this.$message.error("上传头像图片只能是 JPG或png 格式!");
      }
      if (!limitSize) {
        this.$message.error("上传头像图片大小不能超过 2MB!");
      }
      return isImage && limitSize;
    },
    async uploadBuffer(item) {

      //传blob
      uploadBuffer(item.file);

      //传arrayBuffer
      // const fileReader = new FileReader();
      // let sourceId = new Date().getTime() + item.file.name.split(".")[0];
      // fileReader.onload = (e) => {
      //   let content = e.target.result;
      //   uploadBuffer(content);
      // };
      // await fileReader.readAsArrayBuffer(item.file);
      // await fileReader.readAsText(item.file, "utf8");
    },
  },
};
</script>

2.vue中axios.js

代码如下(示例):

import axios from 'axios';
import { Loading, Message } from 'element-ui';

let urlData = { basicUrl: "http://127.0.0.1:3002" }

let loading;

const instance = axios.create({
  baseURL: urlData.basicUrl,
  timeout: 1000,
  headers: { "X-Requested-With": "XMLHttpRequest" },
  withCredentials: false,
});

// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  loading = Loading.service({
    lock: true, // 是否锁屏
    text: '正在加载...', // 加载动画的文字
    spinner: 'el-icon-loading', // 引入的loading图标
    background: 'rgba(0, 0, 0, 0.3)', // 背景颜色
  })
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
  loading.close();
  // 对响应数据做点什么
  return response.data;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});

//GET
export function getUserInfoById(id) {
    let params={ id }
  return instance.get('/getUserInfo', { params });
}

//POST
export async function uploadBuffer(data) {
  return instance.post('/uploadBuffer', data);
}

export default instance;

3.koa中routes.js

代码如下(示例):

import Router from 'koa-router';
import fs, { readFileSync } from 'fs';

const router = new Router();

export const uploadBuffer = async ctx=>{
    let params = [];
    ctx.req.on('data', (chunk) => {
        params.push(chunk);
    })
    ctx.req.on('end', (chunk) => {
        let buffer = Buffer.concat(params);
        fs.writeFileSync(`./public/uploads/111.png`,buffer);
    })
    ctx.body = {
        code: 200,
        data: {},
        msg: "",
    }
}
router.post('/uploadBuffer',uploadBuffer);

export default router;

4.koa中app.js

代码如下(示例):
注释掉koaBody的引用

import koa from 'koa';
import cors  from 'koa-cors';
import router from './routes/routes.js';
import staticFiles from 'koa-static';
import koaBody from 'koa-body';

import path from 'path';

const __dirname = path.resolve();
const app = new koa();

app.use(cors({ // 指定一个或多个可以跨域的域名
    origin: function (ctx) { // 设置允许来自指定域名请求
        if (ctx.url === '/') {
            return "*"; // 允许来自所有域名请求, 这个不管用
        }
        // return 'http://localhost:8000'; // 这样就能只允许 http://localhost:8000 这个域名的请求了
        return '*'; // 这样就能只允许 http://localhost:8000 这个域名的请求了
    },
    maxAge: 5, // 指定本次预检请求的有效期,单位为秒。
    credentials: true,  // 是否允许发送Cookie
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],  // 设置所允许的HTTP请求方法
    allowHeaders: ['Content-Type', 'Authorization', 'Accept'],  // 设置服务器支持的所有头信息字段
    exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] // 设置获取其他自定义字段
}))

// app.use(koaBody({                                
//     multipart: false,
//     formidable: {
// 		//上传文件存储目录
// 		uploadDir:  path.join(__dirname, `/public/uploads/`),
// 		//允许保留后缀名
// 		keepExtensions: true,
// 		multipart: true,
// 	},
//     jsonLimit:'10mb',
//     formLimit:'10mb',
//     textLimit:'10mb'
// }));  
//解析formdata过来的数据
app.use(router.routes());
app.use(router.allowedMethods());
app.use(staticFiles(__dirname + '/public'));

app.listen('3002');
console.log("项目启动,访问:","localhost:3002");

总结

踩坑路漫漫长@~@

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值