NodeJS 文件的上传与下载

网上虽然有各种实例,可在实际使用中,存在各种问题,同时对错误并没有完善的处理。所以经过反复的调试,成功后,在此做个知识总结。

需求

  • 开发语言: Typescript
  • 在 Electron Render 线程中调用
  • 用 Promise 方式实现
  • 下载用 pipe 方式,更好的支持大文件
  • 上传支持附加请求参数
  • 上传支持附加头部信息
  • 完善的错误捕获逻辑,抛出统一的错误类型,方便捕获处理

依赖库

# For download
yarn add request

# For upload
yarn add form-data

逻辑代码

  • transfer.ts
import FormData from 'form-data/lib/form_data';
import request from 'request';
import * as fs from 'fs';
import { IncomingMessage } from 'http';

export function TransferError(message: string = '', code: number = -1) {
  this.name = 'TransferError';
  this.message = message;
  this.code = code;
}
TransferError.prototype = Error.prototype;

// Transfer file between local and remote.
class Transfer {
  debug: Function | null = null; // Example: transfer.debug = console.log

  // Upload file to remote.
  upload(
    url: string,
    filePath: string,
    params: any = {},
    addHeaders: any = {}
  ) {
    return new Promise((resolve, reject) => {
      const stream = fs.createReadStream(filePath);
      stream.on('error', (e: any) => reject(new TransferError(e)));

      const form = new FormData();
      form.append('file', stream);
      for (const k in params) {
        form.append(k, params[k]);
      }

      const u = new URL(url);
      const data = {
        protocol: u.protocol,
        host: u.hostname,
        port: u.port,
        path: u.pathname,
        headers: {},
      };
      for (const k in addHeaders) {
        data.headers[k] = addHeaders[k];
      }

      if (this.debug) {
        this.debug(`[transfer] upload - data: ${JSON.stringify(data)}`);
      }

      form
        .submit(data, (e: any, resp: IncomingMessage) => {
          if (e || resp.statusCode !== 200) {
            if (resp) {
              reject(new TransferError(resp.statusMessage, resp.statusCode));
            } else {
              reject(new TransferError(e));
            }
          } else {
            resolve(resp.read());
          }
        })
        .on('error', (e: any) => {
          reject(new TransferError(e));
        });
    });
  }

  // Download file from remote.
  download(url: string, destPath: string) {
    return new Promise((resolve, reject) => {
      const r = request(url);
      r.on('response', (resp: IncomingMessage) => {
        if (resp.statusCode === 200) {
          const writer = fs.createWriteStream(destPath);
          r.pipe(writer)
            .on('error', (e: any) => reject(new TransferError(e)))
            .on('finish', resolve);
        } else {
          reject(new TransferError(resp.statusMessage, resp.statusCode));
        }
      }).on('error', (e: any) => reject(new TransferError(e)));
    });
  }
}

export const transfer = new Transfer();

测试代码

  • transfer_test.ts
import { transfer, TransferError } from './transfer';

async function upload() {
  const url = 'http://192.168.10.11:8081/upload';
  const filePath = '/Users/mac/Desktop/upload.jpg';
  const openid = 'cd2b61ce043f6eed3f9d86aa6e670dba91a436ac';
  const token =
    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyIjp7ImlkIjoxLCJvcGVuaWQiOiJlZjE2NDU3YS0wNDgxLTRlYzUtOTQzMS0yZDhlYTA3MDdkODkifSwiZXhwIjoxNTc2NTAwNTA4LCJpc3MiOiJub3RlZG93bi5hdXRoIn0.86IwjCon8F3s5Pngn6gK5uuPrxEUmgFvzpkm_Jfhrzc';
  const addHeaders = {
    Authorization: `token ${token}`,
  };

  transfer.debug = console.log;
  try {
    const resp = await transfer.upload(url, filePath, { openid }, addHeaders);
    console.log('upload success', resp);
  } catch (e) {
    if (e instanceof TransferError) {
      console.log(e);
    } else {
      console.error(e);
    }
  }
}

async function download() {
  const url =
    'http://192.168.10.11:8081/static/86500d81f4a0921e27b4fbd89ce1eb1cc164109f.jpg';
  const filePath = '/Users/mac/Desktop/download.jpg';

  try {
    const resp = await transfer.download(url, filePath);
    console.log('download success', resp);
  } catch (e) {
    if (e instanceof TransferError) {
      console.log(e);
    } else {
      console.error(e);
    }
  }
}

export default function test() {
  upload();
  download();
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容