簡體   English   中英

將字節數組從 axios 上傳到節點服務器

[英]Upload byte array from axios to Node server

背景

Microsoft Office 加載項的 Javascript 庫允許您通過getFileAsync() api 獲取 DOCX 文件的原始內容,它在一個 go 中返回最多 4MB 的切片。您繼續使用滑動 window 方法調用 function,直到您讀完為止全部內容。 我需要將這些切片上傳到服務器並將它們重新加入以重新創建原始 DOCX 文件。

我的嘗試

我在客戶端使用 axios,在我的節點服務器上使用基於 busboy 的express-chunked-file-upload中間件。 當我遞歸調用getFileAsync時,我得到一個原始字節數組,然后將其轉換為Blob並將 append 轉換為FormData ,然后再將其post到節點服務器。 一切正常,我在服務器上得到了切片。 但是,寫入服務器磁盤的塊比我上傳的 blob 大得多,通常是 3 倍左右,所以它顯然沒有收到我發送的內容。

我懷疑這可能與 stream 編碼有關,但節點中間件沒有公開任何設置編碼的選項。

這是當前的 state 代碼:

客戶端

public sendActiveDocument(uploadAs: string, sliceSize: number): Promise<boolean> {
  return new Promise<boolean>((resolve) => {
    Office.context.document.getFileAsync(Office.FileType.Compressed,
      { sliceSize: sliceSize },

      async (result) => {
        if (result.status == Office.AsyncResultStatus.Succeeded) {

          // Get the File object from the result.
          const myFile = result.value;
          const state = {
            file: myFile,
            filename: uploadAs,
            counter: 0,
            sliceCount: myFile.sliceCount,
            chunkSize: sliceSize
          } as getFileState;

          console.log("Getting file of " + myFile.size + " bytes");
          const hash = makeId(12)
          this.getSlice(state, hash).then(resolve(true))
        } else {
          resolve(false)
        }
      })
  })
}

private async getSlice(state: getFileState, fileHash: string): Promise<boolean> {
  const result = await this.getSliceAsyncPromise(state.file, state.counter)

  if (result.status == Office.AsyncResultStatus.Succeeded) {

    const data = result.value.data;

    if (data) { 
      const formData = new FormData();
      formData.append("file", new Blob([data]), state.filename);

      const boundary = makeId(12);

      const start = state.counter * state.chunkSize
      const end = (state.counter + 1) * state.chunkSize
      const total = state.file.size

      return await Axios.post('/upload', formData, {
        headers: {
          "Content-Type": `multipart/form-data; boundary=${boundary}`,
          "file-chunk-id": fileHash,
          "file-chunk-size": state.chunkSize,
          "Content-Range": 'bytes ' + start + '-' + end + '/' + total,
        },
      }).then(async res => {
        if (res.status === 200) {
          state.counter++;

          if (state.counter < state.sliceCount) {
            return await this.getSlice(state, fileHash);
          }
          else {
            this.closeFile(state);
            return true
          }
        }
        else {
          return false
        }
      }).catch(err => {
        console.log(err)
        this.closeFile(state)
        return false
      })
    } else {
      return false
    }
  }
  else {
    console.log(result.status);
    return false
  }
}

private getSliceAsyncPromise(file: Office.File, sliceNumber: number): Promise<Office.AsyncResult<Office.Slice>> {
  return new Promise(function (resolve) {
    file.getSliceAsync(sliceNumber, result => resolve(result))
  })
}

服務器端

此代碼完全來自 npm package(上面的鏈接),所以我不應該在這里更改任何內容,但仍供參考:

makeMiddleware = () => {
    return (req, res, next) => {
        const busboy = new Busboy({ headers: req.headers });
        busboy.on('file', (fieldName, file, filename, _0, _1) => {

            if (this.fileField !== fieldName) {  // Current field is not handled.
                return next();
            }

            const chunkSize = req.headers[this.chunkSizeHeader] || 500000;  // Default: 500Kb.
            const chunkId = req.headers[this.chunkIdHeader] || 'unique-file-id';  // If not specified, will reuse same chunk id.
            // NOTE: Using the same chunk id for multiple file uploads in parallel will corrupt the result.

            const contentRangeHeader = req.headers['content-range'];
            let contentRange;

            const errorMessage = util.format(
                'Invalid Content-Range header: %s', contentRangeHeader
            );

            try {
                contentRange = parse(contentRangeHeader);
            } catch (err) {
                return next(new Error(errorMessage));
            }

            if (!contentRange) {
                return next(new Error(errorMessage));
            }

            const part = contentRange.start / chunkSize;
            const partFilename = util.format('%i.part', part);

            const tmpDir = util.format('/tmp/%s', chunkId);
            this._makeSureDirExists(tmpDir);

            const partPath = path.join(tmpDir, partFilename);

            const writableStream = fs.createWriteStream(partPath);
            file.pipe(writableStream);

            file.on('end', () => {
                req.filePart = part;
                if (this._isLastPart(contentRange)) {
                    req.isLastPart = true;
                    this._buildOriginalFile(chunkId, chunkSize, contentRange, filename).then(() => {
                        next();
                    }).catch(_ => {
                        const errorMessage = 'Failed merging parts.';
                        next(new Error(errorMessage));
                    });
                } else {
                    req.isLastPart = false;
                    next();
                }
            });
        });

        req.pipe(busboy);
    };
}

更新

所以看起來我至少找到了問題。 busboy似乎將我的字節數組作為文本寫入 output 文件中。 當我上傳字節數組[80,75,3,4,20,0,6,0,8,0,0,0,33,0,44,25] 80,75,3,4,20,0,6,0,8,0,0,0,33,0,44,25 [80,75,3,4,20,0,6,0,8,0,0,0,33,0,44,25] 現在需要弄清楚如何強制將其寫入二進制 stream。

想通了。 以防萬一它可以幫助任何人, busboyoffice.jsaxios沒有問題。 在從中創建 blob 之前,我只需要將傳入的數據塊轉換為Uint8Array 所以不是:

formData.append("file", new Blob([data]), state.filename);

像這樣:

const blob = new Blob([ new Uint8Array(data) ])
formData.append("file", blob, state.filename);

它就像一個魅力。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM