提示:本来打算尝试一下直接把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");
总结
踩坑路漫漫长@~@