XLSX File corrupted when sent from Node.js via Res

2019-07-11 18:59发布

问题:

I am working on a project where I am creating an excel file using XLSX node.js library, sending it to a client via Restify where I then use the FileSaver.js library to save it on the local computer. When I write the xlsx workbook to file on the backend, it opens fine, however, when I open it on the client, it is corrupted. I get the error: "Excel cannot open this file. The file format or file extension is not valid. Verify that the file has not been corrupted and that the file extension matches the format of the file".

Here is my code for writing and sending the file on the backend:

 var wopts = { bookType:'xlsx', bookSST:false, type:'binary' };
 var workbook = xlsx.write(wb, wopts);
 res.send(200, workbook);

On the front end, I am using code from the XLSX documentation:

function s2ab(s) {
    var buf = new ArrayBuffer(s.length);
    var view = new Uint8Array(buf);
    for (var i=0; i!=s.length; ++i) 
        view[i] = s.charCodeAt(i) & 0xFF;
    return buf;
}

saveAs(new Blob([s2ab(response.data)],{type:""}), "test.xlsx");

Any thoughts on why this would not work? Any help would be much appreciated. Thanks.

回答1:

As Luke mentioned in the comments, you have to do a base64 encoding before sending the buffer. Here's a snippet that used the NPM module node-xlsx.

var xlsx = require('node-xlsx');

router.get('/history', function (req, res) {
  var user = new User();
  user.getHistory(req.user.userId, req.query.offset, req.query.limit)
    .then(function (history) {
      if (req.headers.contenttype && req.headers.contenttype.indexOf('excel') > -1) {
        var data = [['Data', 'amount'], ['19/12/2016', '10']];
        var xlsxBuffer = xlsx.build([{ name: 'History', data: data }]);
        res.end(xlsxBuffer.toString('base64'));
      } else {
        res.send(history);
      }
    })
    .catch(function (err) {
      res.status(500).send(err);
    });
});

And this is the frontend code using Angular:

  $scope.getXlsFile = function() {
    var config = {
      params: {
        offset: $scope.offset,
        limit: $scope.limit
      },
      headers: {
        'contentType': 'application/vnd.ms-excel',
        'responseType': 'arraybuffer'
      }
    };
    $http.get('/api/history', config)
      .then(function(res) {
        var blob = new Blob([convert.base64ToArrayBuffer(res.data)]);
        FileSaver.saveAs(blob, 'historial.xlsx');
      })
  }

where convert is the following factory:

.factory('convert', function () {
  return {
    base64ToArrayBuffer: function (base64) {
      var binary_string = window.atob(base64);
      var len = binary_string.length;
      var bytes = new Uint8Array(len);
      for (var i = 0; i < len; i++) {
        bytes[i] = binary_string.charCodeAt(i);
      }
      return bytes.buffer;
    }
  }
})