NodeJS 文件的上傳與下載

網(wǎng)上雖然有各種實例鸳吸,可在實際使用中,存在各種問題速勇,同時對錯誤并沒有完善的處理晌砾。所以經(jīng)過反復(fù)的調(diào)試,成功后烦磁,在此做個知識總結(jié)养匈。

需求

  • 開發(fā)語言: Typescript
  • 在 Electron Render 線程中調(diào)用
  • 用 Promise 方式實現(xiàn)
  • 下載用 pipe 方式,更好的支持大文件
  • 上傳支持附加請求參數(shù)
  • 上傳支持附加頭部信息
  • 完善的錯誤捕獲邏輯都伪,拋出統(tǒng)一的錯誤類型呕乎,方便捕獲處理

依賴庫

# 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();
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陨晶,隨后出現(xiàn)的幾起案子猬仁,更是在濱河造成了極大的恐慌,老刑警劉巖先誉,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湿刽,死亡現(xiàn)場離奇詭異,居然都是意外死亡褐耳,警方通過查閱死者的電腦和手機诈闺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铃芦,“玉大人雅镊,你說我怎么就攤上這事把曼。” “怎么了漓穿?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長注盈。 經(jīng)常有香客問我晃危,道長,這世上最難降的妖魔是什么老客? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任僚饭,我火速辦了婚禮,結(jié)果婚禮上胧砰,老公的妹妹穿的比我還像新娘鳍鸵。我一直安慰自己,他們只是感情好尉间,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布偿乖。 她就那樣靜靜地躺著,像睡著了一般哲嘲。 火紅的嫁衣襯著肌膚如雪贪薪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天眠副,我揣著相機與錄音画切,去河邊找鬼。 笑死囱怕,一個胖子當著我的面吹牛霍弹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娃弓,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼典格,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忘闻?” 一聲冷哼從身側(cè)響起钝计,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎齐佳,沒想到半個月后私恬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡炼吴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年本鸣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硅蹦。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡荣德,死狀恐怖闷煤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涮瞻,我是刑警寧澤鲤拿,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站署咽,受9級特大地震影響近顷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宁否,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一窒升、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慕匠,春花似錦饱须、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锅铅,卻和暖如春督怜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狠角。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工号杠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丰歌。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓姨蟋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親立帖。 傳聞我的和親對象是個殘疾皇子眼溶,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容