node代理文件上传
前端代码:
<!-- accept:过滤选择的文件,选择的时候会将不是以配置的文件后缀结尾的文件隐藏掉 -->
<input type="file" name="file" id="file" accept=".jpg,.jpge,.JPG" />
<!-- 展示上传回传的图片 -->
<img class="imgAdd" src="" />
// 文件上传
$(document).on('change', 'input[type="file"]', function () {
file = this.files[0];
let formData = new FormData(); // 创建表单数据
formData.append('file', file); // 传输数据,file就是key值来去文件信息
$.ajax({
type: 'post',
data: formData,
contentType: false, // 不要去添加 contentType 头,让FormData自动添加
processData: false, // 不要去检查传输的数据
// url: 'http://127.0.0.1:3000/uploadTemp',
url: 'http://127.0.0.1:3099/getServerFileUrl', // 访问中间服务器
success: function (res) {
if (res.code === 200) {
$('.imgAdd').show();
$('.imgAdd')[0].src = res.data.url;
}
}
});
});
代理服务器代码 Koa2
// 保存文件至服务器并拿到响应结果
router.post('/getServerFileUrl', async (ctx, next) => {
let headers = ctx.request.header;
let params = await getFileBody(ctx); // {data:文件内容Buffer, total:内容的长度大小}
let result = await axios({
method: 'POST',
url: 'http://127.0.0.1:3000/uploadTemp',
data: params.data, // 直接发送buffer数据即可。
headers, // 请求头原样发送过去,主要是请求头 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryu4b3YLukdh8kqpWN
});
ctx.body = result.data;
});
/**
* 获取文件内容
* @param {} ctx
*/
function getFileBody(ctx) {
return new Promise((resolve, reject) => {
let arr = [];
let headers = ctx.request.header;
ctx.req.on('data', (chunk) => {
arr.push(chunk); // 存放请求过来的表单数据
});
ctx.req.on('end', () => {
let total = 0;
arr.forEach((item) => {
total += item.length; // 计算文件内容(Buffer)的总长度,里面有文件名等具体的描述信息。
});
let list = Buffer.concat(arr, total);// 把读取的每一片buffer数据整合在一起。
resolve({
data: list,
total,
headers
});
// 解析失败
ctx.req.on('error', (err) => {
reject(err);
});
});
});
}
// ----------------------------------------------->
// 自定义流 切割方法
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;
}
// 将文件流请求信息具体拿出来
function readFileReqStram(content) {
let fileData = {}; // 用于存储普通数据
let fileArrs = []; // 用于存储所有的文件数据信息
if (content.data) {
// 解析数据流中的文件内容
if (content.headers['content-type']) { // 获取文件分隔符
let str = content.headers['content-type'].split('; ')[1];
if (str) {
// 获取boundary分割信息
let boundary = '--' + str.split('=')[1];
//1. 用分隔符切分整个数据
let arr = content.data.split(boundary);
//2. 丢弃头尾两个数据
arr.shift()
arr.pop()
//3. 丢弃每个数据头尾的\r\n
arr = arr.map(buffer => buffer.slice(2, buffer.length - 2));
//4. 每个数据的第一个‘\r\n\r\n’处切开
arr.forEach(buffer => {
debugger
let n = buffer.indexOf('\r\n\r\n'); // 截取出每一项数据信息
let disposition = buffer.slice(0, n); // 描述信息
let content = buffer.slice(n + 4); // 具体的文件流信息
disposition = disposition.toString()
if (disposition.indexOf('\r\n') == -1) {
// 普通数据;
content = content.toString();
let name = disposition.split('; ')[1].split('=')[1];
name = name.slice(1, name.length - 1)
fileData[name] = content;
} else {
// 文件数据
let [line1, line2] = disposition.split('\r\n');
//line1: Content-Disposition: form-data; name="f1"; filename="1.txt"
//line2: Content-Type: text/plain
console.log(line1.split('; '));
let [, name, filename] = line1.split('; ');
name = name.split('=')[1];
name = name.slice(1, name.length - 1); // 获取到上传的name
filename = filename.split('=')[1];
filename = filename.slice(1, filename.length - 1); // 获取到上传时真实的文件名
let type = line2.split(': ')[1]; // 获取到文件的类型
fileArrs.push({
name,
filename,
type,
content
});
}
});
// 最后将所有的文件信息存储至对象中去
fileData.files = fileArrs;
console.log(fileData);
}
}
}
return fileData;
}
// 注意如果想要将文件写入到本地可以采用此方式
fileData.files.forEach(item => {
// 将文件原封不动的写入到本地种去
fs.writeFile(path.join(savePath, item.filename), item.content, (err) => {
if(err){ // 处理失败
return ;
}
// 上传成功
});
});
注意:请求头必须有 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryu4b3YLukdh8kqpWN ,表示文件的分割风格。在HTTP协议中会通过boundary=----WebKitFormBoundaryu4b3YLukdh8kqpWN自定义的边界对参数和文件进行截取分类。