Ошибка при загрузке Excel в Google Cloud Storage - PullRequest
0 голосов
/ 19 февраля 2020

Я использую библиотеку 'excel js'. Это отлично работает на моем сервере локального узла. Сейчас я пытаюсь использовать Firebase Functions для загрузки файла Excel в хранилище Google Cloud.

Весь код, который я использую:

'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const ExcelJS = require('exceljs');

admin.initializeApp();

var workbook = new ExcelJS.Workbook();
var worksheet = workbook.addWorksheet('Relatório Consolidado');



function startExcel(){

  worksheet.columns = [
    { header: 'Empresa', key: 'empresa', width: 25 },
    { header: 'Data criação', key: 'data_criacao', width: 25 },
    { header: 'Responsável agendamento', key: 'agendador', width: 25 },
    { header: 'Colaborador', key: 'colaborador', width: 25 },
    { header: 'Endereço', key: 'endereco', width: 25 },
    { header: 'CPF', key: 'cpf', width: 25 },
    { header: 'CTPS', key: 'ctps', width: 25 },
    { header: 'Função', key: 'funcao', width: 25 },

    { header: 'Data agendado', key: 'nome_subtipo_produto', width: 25 },
    { header: 'Data atendimento médico', key: 'nome_subtipo_produto', width: 25 },
    { header: 'Data inicio atendimento', key: 'nome_subtipo_produto', width: 25 },
    { header: 'Data inicio exames', key: 'nome_subtipo_produto', width: 25 },
    { header: 'Tipo de exame', key: 'valor_produto', width: 25 },
    { header: 'Exames realizados', key: 'valor_produto', width: 25 },
    { header: 'Status atendimento', key: 'tipoPagamento', width: 25 },
    { header: 'Status exames', key: 'centroCustoStr', width: 25 }
  ];        
}

function salvaExcel(){

  return new Promise(function(resolve, reject){

      let filename = `/tmp/Relatorio.xlsx`
      let bucketName = 'gs://xxx.appspot.com/Relatorios'
      const bucket = admin.storage().bucket(bucketName);      

      workbook.xlsx.writeFile(filename)
      .then(() => {

      console.log('Excel criado com sucesso! Enviando upload do arquivo: ' + filename)          

        const metadata = {
          contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        };

        bucket.upload(filename, metadata)

        .then(() => {
          const theFile = bucket.file(filename);
          theFile.getSignedURL(signedUrlOptions)

          .then((signedUrl) => {
            resolve(signedUrl)
          });

        })
        .catch((error) => {
          reject('Erro ao realizar upload: ' + error)
        })                      
      })  

      .catch((error) => {
        reject('Erro ao realizar upload: ' + error)
      })         

  })    
}

startExcel()


/**********************************
 * Relatórios
 ********************************/

function relatorios(change, context){

  return new Promise((resolve, reject) => {

    const snapshot = change.after
    const data = snapshot.val()  

    verificaRelatorioAgendamentos(change)

    .then(() => {
      resolve()

    })

    .catch((error => {                
      reject(error)

    }))

  })           
}


function verificaRelatorioAgendamentos(change, context){

  return new Promise((resolve, reject) => {

    const snapshot = change.after
    const data = snapshot.val()      
    const dataInicial = data.dataInicial    
    const year = moment(dataInicial).format('YYYY')
    const month = moment(dataInicial).format('MM')
    const state = 'DF'    
    let path = "/agendamentos/" + state + "/" +  year + "/" + month

    const relatorios = admin.database().ref(path).once('value');

    return Promise.all([relatorios])

      .then(results => {                

        let valores = results[0]
        criaRelatorioAgendamentos(valores)

        .then(() => {
          resolve()

        })

        .catch((error => {          
          reject(error)

        }))

    })

  })       

}


function criaRelatorioAgendamentos(results){

  return new Promise((resolve, reject) => {

    let promises = []

    results.forEach(element => {

      let promise = new Promise(function(resolveExcel){ 

        let data = element.val()        

        worksheet.addRow({
          id: 1, 
          empresa: data.agendador.company, 
          data_criacao: data.dataCriacao, 
          agendador: data.agendador.nome, 
          colaborador: data.colaborador.nome,
          cpf: data.colaborador.cpf, 
          ctps: data.colaborador.ctps, 
          funcao: data.colaborador.funcao, 
          data_agendado: data.data, 
          data_atendimento_medico: data.dataAtendimento, 
          data_inicio_atendimento: data.dataInicio, 
          data_inicio_exames: data.dataInicioExames, 
          tipo_exame: data.tipoExame, 
          exames: data.exames[0].nome, 
          status_atendimento: data.status, 
          status_exames: data.statusExames

        })

        resolveExcel()

      })      

      promises.push(promise)

    })

    Promise.all(promises)

      .then(() => {          
        salvaExcel()

        .then((url) => {

          console.log('Salvar URL' + url) 

          resolve(url)

        })

        .catch((error => {
          reject(error)

        }))


    })


  })       

}


exports.relatorios = functions.database.ref('/relatorios/{state}/{year}/{month}/{relatoriosId}')
    .onWrite((change, context) => {      
      return relatorios(change, context)
});

В консоли функций журнал Покажите мне, что файл Excel был создан успешно. Но при загрузке выскакивает очень странная ошибка:

enter image description here

Что я делаю не так? Я ценю любую помощь.

Спасибо!

1 Ответ

1 голос
/ 19 февраля 2020

Сообщение об ошибке появляется при попытке получить подписанный URL-адрес несуществующего файла.

Когда вы звоните bucket.upload(filename, metadata), вы загружаете файл /tmp/Relatorio.xlsx, который создает файл в вашем ведре под названием Relatorio.xlsx. На следующей строке вы вызываете bucket.file(filename);, который неправильно ассоциируется с /tmp/Relatorio.xlsx вместо Relatorio.xlsx.

Чтобы исправить это, вы должны использовать объект File, который разрешен из bucket.upload() вместо создавая его самостоятельно:

bucket.upload(filename, metadata)
    .then((file) => file.getSignedURL())
    .then((url) => {
        console.log('Salvar URL' + url)
    })

Другие заметки и исправления

Ваш код также содержит множество ненужных new Promise((resolve, reject) => { ... }) вызовов. Это называется анти-шаблон конструктора Promise , и большинство из них можно удалить, правильно связав Обещания. Этот пост в блоге - хороший курс sh об обещаниях и их правильном использовании.

Относительно исходного кода вашей функции, поскольку файл index.js для функций будет содержать несколько функций определения, вы не должны определять переменные в верхней части вашего index.js файла, если они не являются общими для всех ваших функций, и они не сохраняют состояния в случае, если функция вызывается несколько раз. Это особенно важно, когда речь идет о ресурсах ввода-вывода или ресурсоемких ресурсах, таких как файлы.

С вашим текущим кодом, если функция relator ios вызывается дважды за короткий период, сохраненный файл будет содержать и старые данные из первого вызова, и новые данные из текущего вызова, приводящие как к неверному файлу, так и к потенциальной утечке памяти.

Удаление вызовов с чрезмерным обещанием и создание так, чтобы ваш код exceljs мог перезапустить без повреждения каких-либо данных в следующем файле index.js:

'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// 'exceljs' is required on-demand in MyExcelSheetHelper

admin.initializeApp();

/* HELPER CLASS */

/**
 * A helper class used to create reuseable functions that won't
 * conflict with each other
 */
class MyExcelSheetHelper {

  constructor() {
    const ExcelJS = require('exceljs');

    this.workbook = new ExcelJS.Workbook();
    this.worksheet = this.workbook.addWorksheet('Relatório Consolidado');

    this.worksheet.columns = [
      { header: 'Empresa', key: 'empresa', width: 25 },
      { header: 'Data criação', key: 'data_criacao', width: 25 },
      { header: 'Responsável agendamento', key: 'agendador', width: 25 },
      { header: 'Colaborador', key: 'colaborador', width: 25 },
      { header: 'Endereço', key: 'endereco', width: 25 },
      { header: 'CPF', key: 'cpf', width: 25 },
      { header: 'CTPS', key: 'ctps', width: 25 },
      { header: 'Função', key: 'funcao', width: 25 },

      { header: 'Data agendado', key: 'nome_subtipo_produto', width: 25 },
      { header: 'Data atendimento médico', key: 'nome_subtipo_produto', width: 25 },
      { header: 'Data inicio atendimento', key: 'nome_subtipo_produto', width: 25 },
      { header: 'Data inicio exames', key: 'nome_subtipo_produto', width: 25 },
      { header: 'Tipo de exame', key: 'valor_produto', width: 25 },
      { header: 'Exames realizados', key: 'valor_produto', width: 25 },
      { header: 'Status atendimento', key: 'tipoPagamento', width: 25 },
      { header: 'Status exames', key: 'centroCustoStr', width: 25 }
    ];
  }

  /**
   * Streams this workbook to Cloud Storage
   * @param storageFilepath - the relative path where the file is uploaded to Cloud Storage
   * @returns the signed URL for the file
   */
  salva(storageFilepath) {
    if (!storageFilepath) {
      return Promise.reject(new Error('storageFilepath is required'));
    }

    const bucket = admin.storage().bucket();

    const storageFile = bucket.file(storageFilepath);

    const uploadFilePromise = new Promise((resolve, reject) => {
      try {
        const stream = storageFile.createWriteStream({
          contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        });

        stream.on('finish', () => {
          resolve();
        });

        stream.on('error', error => {
          reject(error);
        });

        this.workbook.xlsx.write(stream)
          .then(() => {
            stream.end();
          });

      } catch (e) { // catches errors from createWriteStream
        reject(e);
      }
    })

    return uploadFilePromise
      .then(() => {
        var CONFIG = {                                                                      
          action: 'read',                                                               
          expires: '03-01-2500',                                                        
        };    

        bucket.file(storageFilepath).getSignedUrl(CONFIG)
        .then((signedUrl) => {

          return signedUrl
        })
      })

  }
}

/* FUNCTIONS CODE */

function criaRelatorioAgendamentos(path, querySnapshot) {
  const excelFileHelper = new MyExcelSheetHelper();
  const worksheet = excelFile.worksheet;

  // this forEach loop is synchronous, so no Promises are needed here
  querySnapshot.forEach(entrySnapshot => {
    const data = entrySnapshot.val();

    worksheet.addRow({
      id: 1,
      empresa: data.agendador.company,
      data_criacao: data.dataCriacao,
      agendador: data.agendador.nome,
      colaborador: data.colaborador.nome,
      cpf: data.colaborador.cpf,
      ctps: data.colaborador.ctps,
      funcao: data.colaborador.funcao,
      data_agendado: data.data,
      data_atendimento_medico: data.dataAtendimento,
      data_inicio_atendimento: data.dataInicio,
      data_inicio_exames: data.dataInicioExames,
      tipo_exame: data.tipoExame,
      exames: data.exames[0].nome,
      status_atendimento: data.status,
      status_exames: data.statusExames
    });
  });

  return excelFileHelper.salva(path + '/Relatorio.xlsx');
}

exports.relatorios = functions.database.ref('/relatorios/{state}/{year}/{month}/{relatoriosId}')
    .onWrite((change, context) => {

    // Verificar relatorio agendamentos

    const snapshot = change.after;
    const data = snapshot.val();
    const dataInicial = data.dataInicial;
    const year = moment(dataInicial).format('YYYY');
    const month = moment(dataInicial).format('MM');
    const state = 'DF';
    const path = "/agendamentos/" + state + "/" +  year + "/" + month;

    return admin.database().ref(path).once('value')
      .then(valores => {
        return criaRelatorioAgendamentos(path, valores);
      });
});
...