跳到主要内容

文件上传

busboy 模块

npm install --save busboy

busboy 模块是用来解析 POST 请求,node 原生 req 中的文件流。

const inspect = require('util').inspect;
const path = require('path');
const fs = require('fs');
const Busboy = require('busboy');

// req 为node原生请求
const busboy = new Busboy({ headers: req.headers });

// ...

// 监听文件解析事件
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
console.log(`File [${fieldname}]: filename: ${filename}`);

// 文件保存到特定路径
file.pipe(fs.createWriteStream('./upload'));

// 开始解析文件流
file.on('data', function (data) {
console.log(`File [${fieldname}] got ${data.length} bytes`);
});

// 解析文件结束
file.on('end', function () {
console.log(`File [${fieldname}] Finished`);
});
});

// 监听请求中的字段
busboy.on('field', function (fieldname, val, fieldnameTruncated, valTruncated) {
console.log(`Field [${fieldname}]: value: ${inspect(val)}`);
});

// 监听结束事件
busboy.on('finish', function () {
console.log('Done parsing form!');
res.writeHead(303, { Connection: 'close', Location: '/' });
res.end();
});
req.pipe(busboy);

上传文件简单实现

demo 源码:

https://github.com/ChenShenhai/koa2-note/blob/master/demo/upload/

const Koa = require('koa');
const path = require('path');
const app = new Koa();
// const bodyParser = require('koa-bodyparser')

const { uploadFile } = require('./util/upload');

// app.use(bodyParser())

app.use(async ctx => {
if (ctx.url === '/' && ctx.method === 'GET') {
// 当GET请求时候返回表单页面
let html = `
<h1>koa2 upload demo</h1>
<form method="POST" action="/upload.json" enctype="multipart/form-data">
<p>file upload</p>
<span>picName:</span><input name="picName" type="text" /><br/>
<input name="file" type="file" /><br/><br/>
<button type="submit">submit</button>
</form>
`;
ctx.body = html;
} else if (ctx.url === '/upload.json' && ctx.method === 'POST') {
// 上传文件请求处理
let result = { success: false };
let serverFilePath = path.join(__dirname, 'upload-files');

// 上传文件事件
result = await uploadFile(ctx, {
fileType: 'album', // common or album
path: serverFilePath
});

ctx.body = result;
} else {
// 其他请求显示404
ctx.body = '<h1>404!!! o(╯□╰)o</h1>';
}
});

app.listen(5000, () => {
console.log('[demo] upload-simple is starting at port 3000');
});
const inspect = require('util').inspect;
const path = require('path');
const os = require('os');
const fs = require('fs');
const Busboy = require('busboy');

/**
* 同步创建文件目录
* @param {string} dirname 目录绝对地址
* @return {boolean} 创建目录结果
*/
function mkdirsSync(dirname) {
if (fs.existsSync(dirname)) {
return true;
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname);
return true;
}
}
}

/**
* 获取上传文件的后缀名
* @param {string} fileName 获取上传文件的后缀名
* @return {string} 文件后缀名
*/
function getSuffixName(fileName) {
let nameList = fileName.filename.split('.');
return nameList[nameList.length - 1];
}

/**
* 上传文件
* @param {object} ctx koa上下文
* @param {object} options 文件上传参数 fileType文件类型, path文件存放路径
* @return {promise}
*/
function uploadFile(ctx, options) {
let req = ctx.req;
let res = ctx.res;
let busboy = Busboy({ headers: req.headers });

// 获取类型
let fileType = options.fileType || 'common';
let filePath = path.join(options.path, fileType);
let mkdirResult = mkdirsSync(filePath);

return new Promise((resolve, reject) => {
console.log('文件上传中...');
let result = {
success: false,
formData: {}
};

// 解析请求文件事件
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
let fileName = Math.random().toString(16).substr(2) + '.' + getSuffixName(filename);
let _uploadFilePath = path.join(filePath, fileName);
let saveTo = path.join(_uploadFilePath);

// 文件保存到制定路径
file.pipe(fs.createWriteStream(saveTo));

// 文件写入事件结束
file.on('end', function () {
result.success = true;
result.message = '文件上传成功';

console.log('文件上传成功!');
resolve(result);
});
});

// 解析表单中其他字段信息
busboy.on('field', function (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
console.log('表单字段数据 [' + fieldname + ']: value: ' + inspect(val));
result.formData[fieldname] = inspect(val);
});

// 解析结束事件
busboy.on('finish', function () {
console.log('文件上结束');
resolve(result);
});

// 解析错误事件
busboy.on('error', function (err) {
console.log('文件上出错');
reject(result);
});

req.pipe(busboy);
});
}

module.exports = {
uploadFile
};

异步上传图片实现

demo 地址

https://github.com/ChenShenhai/koa2-note/tree/master/demo/upload-async

const Koa = require('koa');
const views = require('koa-views');
const path = require('path');
const static = require('koa-static');
const { uploadFile } = require('./util/upload');

const app = new Koa();

/**
* 使用第三方中间件 start
*/
app.use(
views(path.join(__dirname, './view'), {
extension: 'ejs'
})
);

// 静态资源目录对于相对入口文件index.js的路径
const staticPath = './static';

app.use(static(path.join(__dirname, staticPath)));
/**
* 使用第三方中间件 end
*/

app.use(async ctx => {
if (ctx.method === 'GET') {
let title = 'upload pic async';
await ctx.render('index', {
title
});
} else if (ctx.url === '/api/picture/upload.json' && ctx.method === 'POST') {
// 上传文件请求处理
let result = { success: false };
let serverFilePath = path.join(__dirname, 'static/image');

// 上传文件事件
result = await uploadFile(ctx, {
fileType: 'album',
path: serverFilePath
});
ctx.body = result;
} else {
// 其他请求显示404
ctx.body = '<h1>404!!! o(╯□╰)o</h1>';
}
});

app.listen(5000, () => {
console.log('[demo] upload-pic-async is starting at port 3000');
});
<!-- view/index.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<style type="text/css">
.btn {
width: 100px;
height: 40px;
}
.preview-picture {
width: 300px;
min-height: 300px;
border: 1px #f0f0f0 solid;
}
</style>
</head>
<body>
<button class="btn" id="J_UploadPictureBtn">上传图片</button>
<hr />
<p>
上传进度
<span id="J_UploadProgress">0</span>
%
</p>
<p>上传结果图片</p>
<div id="J_PicturePreview" class="preview-picture"></div>
<script src="/js/index.js"></script>
</body>
</html>
// util/upload.js
const inspect = require('util').inspect;
const path = require('path');
const os = require('os');
const fs = require('fs');
const Busboy = require('busboy');

/**
* 同步创建文件目录
* @param {string} dirname 目录绝对地址
* @return {boolean} 创建目录结果
*/
function mkdirsSync(dirname) {
if (fs.existsSync(dirname)) {
return true;
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname);
return true;
}
}
}

/**
* 获取上传文件的后缀名
* @param {string} fileName 获取上传文件的后缀名
* @return {string} 文件后缀名
*/
function getSuffixName(fileName) {
let nameList = fileName.filename.split('.');
return nameList[nameList.length - 1];
}

/**
* 上传文件
* @param {object} ctx koa上下文
* @param {object} options 文件上传参数 fileType文件类型, path文件存放路径
* @return {promise}
*/
function uploadFile(ctx, options) {
let req = ctx.req;
let res = ctx.res;
let busboy = Busboy({ headers: req.headers });

// 获取类型
let fileType = options.fileType || 'common';
let filePath = path.join(options.path, fileType);
let mkdirResult = mkdirsSync(filePath);

return new Promise((resolve, reject) => {
console.log('文件上传中...');
let result = {
success: false,
message: '',
data: null
};

// 解析请求文件事件
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
let fileName = Math.random().toString(16).substr(2) + '.' + getSuffixName(filename);
let _uploadFilePath = path.join(filePath, fileName);
let saveTo = path.join(_uploadFilePath);

// 文件保存到制定路径
file.pipe(fs.createWriteStream(saveTo));

// 文件写入事件结束
file.on('end', function () {
result.success = true;
result.message = '文件上传成功';
result.data = {
pictureUrl: `//${ctx.host}/image/${fileType}/${fileName}`
};
console.log('文件上传成功!');
resolve(result);
});
});

// 解析结束事件
busboy.on('finish', function () {
console.log('文件上结束');
resolve(result);
});

// 解析错误事件
busboy.on('error', function (err) {
console.log('文件上出错');
reject(result);
});

req.pipe(busboy);
});
}

module.exports = {
uploadFile
};