node代理文件上传,解析文件流

2,937 阅读3分钟

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自定义的边界对参数和文件进行截取分类。