UNPKG

fc-body

Version:
374 lines (342 loc) 9.9 kB
/* * @Author : lovefc * @Date : 2021-02-22 08:49:39 * @LastEditTime : 2025-04-13 23:00:08 */ const fs = require('fs'); const path = require('path'); const querystring = require('querystring'); const os = require('os'); const mime = require('mime-types'); /* 切割buffer */ Buffer.prototype.split = Buffer.prototype.split || function (spl) { let arr = []; let cur = 0; let n = 0; while ((n = this.indexOf(spl, cur)) != -1) { arr.push(this.slice(cur, n)); cur = n + spl.length; } arr.push(this.slice(cur)); return arr; } class body { // 构造函数,开始了 constructor(options) { let that = this; this.type = ''; // 限制上传类型 this.mimes = []; // 存储下类型 this.isAutoSaveFile = false; // 是否自动存储文件,设为不存储,那么file_data返回的是该文件的二进制数据,需要自己手动保存 this.savePath = os.tmpdir(); // 上传目录,默认是临时目录 this.minSize = 0; // 最小限制 this.maxSize = 5; // 最大限制 this.errorMsg = { 'TIMEOUT': 'POST超时', 'UNDERSIZE': '数据过小', 'OVERSIZE': '数据过大', 'NOTALLOWEDTYPE': '不允许的类型' }; // 接受数据结束事件 this.closeEvent = function () { }; // 这里用了个暴力的方式来覆盖初始配置 for (let key in options) { if (key in that) { that[key] = options[key]; } } } // 错误显示 error(err, path) { let error = { code: '', message: '', path: '' }; // 很奇怪,err返回的变量竟然是个错误对象 if (Object.prototype.toString.call(err) === "[object Error]") { error.code = err.code; error.message = err.toString(); error.path = err.path; } else if (typeof err === 'string') { error.code = err; error.message = this.errorMsg[err]; error.path = path; } return error; } // 写入文件 writefile(file, body) { return new Promise((resolve, reject) => { fs.writeFile(file, body, "binary", function (err) { if (err) { reject(err); } resolve(true); }); }); } // 是否上传文件 isUpfile(request) { let type = ('content-type' in request.headers) ? request.headers['content-type'] : ''; if (type.match(/boundary=(?:"([^"]+)"|([^;]+))/i)) { return true; } return false; } // 是否为get isGet(request) { let method = request.method.toLowerCase(); if (method === 'get') { return true; } return false; } // 是否为post isPost(request) { let method = request.method.toLowerCase(); if (method === 'post') { return true; } return false; } // 是否为表单提交 isForm(request) { let type = ('content-type' in request.headers) ? request.headers['content-type'] : ''; if (type.match(/application\/x-www-form-urlencoded/i)) { return true; } return false; } // 是否为raw isRaw(request) { let type = ('content-type' in request.headers) ? request.headers['content-type'] : ''; if (type.match(/text\/plain/i)) { return true; } return false; } // 是否为json isJson(request) { let type = ('content-type' in request.headers) ? request.headers['content-type'] : ''; if (type.match(/application\/json/i)) { return true; } return false; } // 获取post数据 getBody(req) { let that = this; let request = req; // 异步操作 return new Promise((resolve, reject) => { let body = '', chunks = [], size = 0; // 超时 request.on('aborted', function () { reject(that.error('PTIMEOUT')); }); // 接受数据 request.on("data", function (postDataChunk) { chunks.push(postDataChunk); // 为什么要这样?一般使用postDataChunk += postDataChunk,这会把buf转换成string,这就是字符串相加了 size += Buffer.from(postDataChunk).length; // 获取长度 // 判断最小post if (that.minSize) { let minLimitNum = that.limitFileSize(that.minSize + 'MB'); if (size < minLimitNum) { reject(that.error('UNDERSIZE')); return; } } // 判断最大post if (that.maxSize) { let maxLimitNum = that.limitFileSize(that.maxSize + 'MB'); if (size > maxLimitNum) { reject(that.error('OVERSIZE')); return; } } }); // 数据接收完毕,执行回调函数 request.on("end", function () { let buf = Buffer.concat(chunks, size); // 这里为什么要指定size,因为指定size后,计算的会更快一点 if (that.isUpfile(request)) { try { body = that.upload(request, buf); } catch (e) { reject(e); } } else if (that.isPost(request)) { if (that.isJson(request)) { body = JSON.parse(buf.toString()); } else if (that.isForm(request)) { body = querystring.parse(buf.toString()); } else { body = buf.toString(); } } else if (that.isRaw(request)) { // 这里其实是txt形式的,所以它返回的也是txt body = buf.toString(); } else { body = buf; } // 判断,如果为空 if (Buffer.isBuffer(buf) && buf.length == 0) { body = null; } chunks = []; resolve(body); }); // 链接关闭 request.on('close', that.closeEvent); // 出现错误 request.on('error', function (err) { reject(that.error(err)); }); }); } // 计算大小格式 limitFileSize(limitSize) { let arr = ["KB", "MB", "GB"], limit = limitSize.toUpperCase(), limitNum = 0; for (let i = 0; i < arr.length; i++) { let leval = limit.indexOf(arr[i]); if (leval > -1) { limitNum = parseInt(limit.substr(0, leval)) * Math.pow(1024, (i + 1)); break; } } return limitNum; } // 生成uuid UUID(len, radix) { let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); let uuid = [], i; radix = radix || chars.length; if (len) { for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]; } else { let r; uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; uuid[14] = '4'; for (i = 0; i < 36; i++) { if (!uuid[i]) { r = 0 | Math.random() * 16; uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; } } } return uuid.join(''); } async upload(request, postData) { let file_array = {}; // 这里按照换行符分割 let buffer = Buffer.from(postData); let _arr = buffer.split('\r\n'); // 这一步很简单,根据\r\n来切割buffer,我并不知道其它的切片是怎么做的,我只是用我的方式,测试过,还不错 let files = []; let file_data = []; let start_line = []; let k = 0; // 这一步先取出位置 for (let i = 0; i < _arr.length; i++) { let file = []; let str = _arr[i].toString(); // 转成字符串来匹配,因为现在是buffer,toString的默认编码是utf8 let match = /Content-Disposition: form-data; (name=""|name="(.+?)")(; filename="(.+?)"|)/i; let m2 = str.match(match); if (m2 != null) { start_line[k] = i; k++; file['name'] = m2[2]; if (m2[3]) { file['name'] = m2[2] ? m2[2] : m2[4]; file['file_name'] = m2[4]; } } if (file['name'] || file['file_name']) { files[i] = file; } } start_line.push(_arr.length - 1); files = files.filter(d => d); // 这一步,开始分割body内容 for (let i2 = 0; i2 < files.length; i2++) { let start = 0, end = 0, name2 = files[i2]['name'], arr = {}; if (typeof (files[i2]['file_name']) != 'undefined') { start = start_line[i2] + 3; let str = _arr[start_line[i2] + 1].toString(); let match2 = /Content-Type: ([\s\S]+)/i; let m3 = str.match(match2); arr['name'] = files[i2]['file_name']; if (m3 != null) { arr['type'] = m3[1]; // 等于-1,说明不存在 let contentType = mime.extension(arr['type']); if (!contentType) { throw this.error('NOTALLOWEDTYPE', files[i2]['file_name']); break; } } } else { start = start_line[i2] + 2; } end = start_line[i2 + 1] - 1; let buf_list = _arr.slice(start, end); if (buf_list) { // 合并buffer数组,很重要的一步哦 let buf_array = []; let body = Buffer.concat(buf_list); let size = Buffer.from(body).length; // 获取大小,可避免中文字符串的转换问题 if (typeof (files[i2]['file_name']) != 'undefined') { buf_list.forEach(function (v, k) { let buffer1 = Buffer.from(v); if (k == (buf_list.length - 1)) { buf_array[k] = buffer1; } else { let buffer2 = Buffer.from('\r\n'); buf_array[k] = Buffer.concat([buffer1, buffer2]); } }); body = Buffer.concat(buf_array); arr['size'] = size; // 判断是否自动保存到临时文件夹 if (this.isAutoSaveFile === true) { let uuid = this.UUID(10, 16); let file_name = uuid + '_' + files[i2]['file_name']; let tmp_file = this.savePath + path.sep + file_name; arr['tmp_name'] = tmp_file; try { await this.writefile(tmp_file, body); } catch (e) { throw this.error(e); } } else { arr['data'] = body; } } else { arr = body.toString(); } // 清掉内存 body = null; buf_array = null; } // 如果有[]符号,这个用来处理多文件 let _name2 = name2.match(/\[\]$/i); if (_name2) { if (file_array.hasOwnProperty(name2) === false) { file_array[name2] = []; } file_array[name2].push(arr); } else { file_array[name2] = arr; } } files = null; return file_array; } } module.exports = body;