febs
Version:
febs is a useful utilities set
279 lines (239 loc) • 7.11 kB
JavaScript
;
/**
* Copyright (c) 2017 Copyright brainpoint All Rights Reserved.
* Author: lipengxiang
* Desc:
* upload控件使用一个接口来上传文件, 使用multpart/form-data方式传输:
* 1. uploadUrl: 上传文件.
* Example:
* 前台引入:
* 上传方式只能使用post.
* 1. 在需要upload的页面上引入 control_upload.hbs页面; 或者使用如下语句:
* <form id="fileForm" method="post" role="form" enctype="multipart/form-data">
* <input type="file" class="form-control" name="file" onchange="control_upload(cfg)" multiple>
* </form>
* 2. 调用脚本进行初始化设置: control_upload_init(uploadUrl, finishCB, progressCB);
* 其中 uploadUrl为上传地址, 不能带参数.
* 后台:
* 1. 在uploadUrl中调用 await require('febs').controls.upload.accept(app, conditionCB); 当满足条件时将存储, 并返回true表示成功.
*/
var URL = require('url');
var fs = require('fs');
var parse = require('co-busboy');
var assert = require('assert');
var febs = require('..');
var PromiseLib = Promise;
function save_to(stream, writeStream, writeStreamPath, size, crc32, done) {
// streams2+: assert the stream encoding is buffer.
var state = stream._readableState;
if (state && state.encoding != null) {
if (typeof stream.pause === 'function')
stream.pause();
process.nextTick(function () {
console.debug('febs upload.accpet stream encoding should not be set');
done(null, false)
})
return defer;
}
stream.pipe(writeStream);
var received = 0;
var crc32_check = 0;
stream.on('data', onData)
stream.once('end', onEnd)
stream.once('close', onClose)
stream.once('error', onFinish)
writeStream.once('error', onFinish)
// shouldn't ever emit 'close' without `finish`.
writeStream.once('close', onFinish)
return defer
function defer(fn) {
done = fn
}
function onData(chunk) {
if (crc32) {
crc32_check = febs.crypt.crc32(chunk, crc32_check);
}
received += chunk.length
if (size !== null && received > size) {
var err;
err = 'febs upload.accpet request entity too large';
onFinish(err)
}
}
// If a 'close' event is emitted before the
// readable stream has ended,
// then we assume that it was prematurely closed
// and we cleanup the file appropriately.
function onClose() {
if (state && !state.ended)
cleanup(true)
}
function onEnd() {
if (received != size) {
var err;
err = 'febs upload.accpet request size did not match content length: ' + received + ',' + size;
onFinish(err)
}
else
{
if (crc32_check != crc32)
{
onFinish('febs upload.accpet request crc32 is wrong');
}
else
{
finish(null);
}
}
}
function onFinish(err) {
if (err)
finish(err)
}
function finish(err) {
cleanup(err)
if (err)
{
console.debug(err);
done(null, false);
}
// else
// done(null, true);
}
function cleanup(err, cb) {
if (err) {
writeStream.destroy()
fs.unlink(writeStreamPath, function(){})
}
stream.removeListener('data', onData)
stream.removeListener('end', onEnd)
stream.removeListener('close', onClose)
stream.removeListener('error', onFinish)
writeStream.removeListener('error', onFinish)
writeStream.removeListener('close', onFinish)
if (!err) {
var tempStream = writeStream;
function _onError() {
tempStream.removeListener('finish', _onFlush)
tempStream = null;
done(null, false);
}
function _onFlush() {
tempStream.removeListener('error', _onError)
tempStream = null;
done(null, true);
}
writeStream.once('finish', _onFlush)
writeStream.once('error', _onError)
writeStream.end()
}
stream = writeStream = null
}
};
/**
* 接收上传文件内容.
* @param conditionCB: async function(data, filesize, filename, filemimeType):string.
* - data: 用户上传的数据.
* - filesize: 将要存储的文件大小.
* - filename: 上传的文件名.
* - filemimeType: 文件类型, 例如: 'image/jpeg'.
* - return: 存储的文件路径, 返回null表示不存储.
* @return Promise.
* @resolve
* - bool. 指明是否存储成功.
*/
exports.accept = function(app, conditionCB, checkCrc32=true, append=true)
{
assert(conditionCB);
return new PromiseLib((resolve, reject)=>{
try {
if ('POST' != app.method) {
resolve(false);
return;
}
var query = URL.parse(app.request.url, true).query;
if (checkCrc32 && !query.crc32) {
resolve(false);
return;
}
if (!query.size) {
resolve(false);
return;
}
if (!app.request.is('multipart/*')) {
resolve(false);
return;
}
// parse the multipart body
var parts = parse(app, {
autoFields: true // saves the fields to parts.field(s)
});
if (!parts) {
resolve(false);
return;
}
} catch(err) {
reject(err);
return;
}
parts(function(err, part){
if (err) {
reject(err);
return;
}
if (!part) {
reject('no stream');
return;
}
var srcStream = part;
var destStream;
var fn1;
conditionCB(query.data, Number(query.size), part.filename, part.mimeType)
.then(fn=>{
if (!fn)
{
console.debug('febs upload.accpet un return a filename');
if (typeof srcStream.pause === 'function')
srcStream.pause();
let req = app.request.req || app.request;
req.destroy();
return false;
}
fn1 = fn;
// create stream.
if (append) {
destStream = fs.createWriteStream(fn, {flags: 'a'});
}
else {
destStream = fs.createWriteStream(fn);
}
if (!destStream)
{
console.debug('febs upload.accpet createWriteStream err:' + fn);
if (typeof srcStream.pause === 'function')
srcStream.pause();
let req = app.request.req || app.request;
req.destroy();
return false;
}
return true;
})
.then(ret=>{
if (!ret) {
resolve(false);
}
else {
save_to(srcStream, destStream, fn1, Number(query.size), (checkCrc32?Number(query.crc32):0), (err, ret)=>{
if (err)
reject(err);
else
resolve(ret);
});
}
})
.catch(err=>{
reject(err);
});
});
});
};