将 Excel 上传到 Google 云存储时出错
Erro uploading Excel to Google Cloud Storage
我正在使用 'exceljs' 库。它在我的本地节点服务器上运行良好。现在我正在尝试使用 Firebase Functions 将 excel 文件上传到 Google 云存储。
这是我使用的全部代码:
'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)
});
在 Functions 控制台上,日志显示 excel 文件已成功创建。但是上传的时候,弹出一个很奇怪的错误:
我做错了什么?我很感激任何帮助。
谢谢!
您收到的错误消息来自于尝试获取一个不存在的文件的已签名 URL。
当您调用 bucket.upload(filename, metadata)
时,您正在上传文件 /tmp/Relatorio.xlsx
,这会在您的存储桶中创建一个名为 Relatorio.xlsx
的文件。在下一行你调用 bucket.file(filename);
错误地将自己与 /tmp/Relatorio.xlsx
而不是 Relatorio.xlsx
.
相关联
要解决此问题,您应该使用从 bucket.upload()
解析的 File
对象,而不是自己创建它:
bucket.upload(filename, metadata)
.then((file) => file.getSignedURL())
.then((url) => {
console.log('Salvar URL' + url)
})
其他注释和修复
您的代码还包含很多不必要的 new Promise((resolve, reject) => { ... })
调用。这称为 Promise 构造函数反模式,其中大部分可以通过正确链接 Promise 来移除。 blog post 是关于 Promises 以及如何正确使用它们的很好的速成课程。
关于您的函数的源代码,由于函数的 index.js
文件将包含多个函数定义,因此您不应在 index.js
文件的顶部定义变量,除非它们由所有函数共享你的函数,它们是无状态的,以防一个函数被多次调用。这在处理 I/O 或文件等内存密集型资源时尤为重要。
使用您当前的代码,如果 relatorios 函数在短时间内被调用两次,保存的文件将包含第一次调用的旧数据和当前调用的新数据,从而导致文件无效和潜在的内存泄漏。
删除过多的 promise 调用并进行调用,以便您的 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);
});
});
这是我用来保存由 exceljs 生成的 excel 文件的版本
及其各自的库版本
// @google-cloud/storage --> 5.3.0
// exceljs --> 4.3.0
// moment --> 2.29.1
const moment = require('moment');
const ExcelJS = require('exceljs');
class Excel {
constructor(nameSheet) {
this.workbook = new ExcelJS.Workbook();
this.workbook.creator = 'User...';
this.workSheet = this.workbook.addWorksheet(nameSheet);
}
// ..... other code....
saveFile = async (path, filename) =>
{
const storage = new Storage();
const bucket = storage.bucket('name_bucket...');
const storageFile = bucket.file(`${path}/${filename}`);
const blobStream = storageFile.createWriteStream({
contentType: 'application/ms-excel',
});
await this.workbook.xlsx.write(blobStream);
blobStream.end();
const config = {
action: 'read',
expires: moment().add(30, 'minutes').format(),
};
return await storageFile.getSignedUrl(config)
}
}
我正在使用 'exceljs' 库。它在我的本地节点服务器上运行良好。现在我正在尝试使用 Firebase Functions 将 excel 文件上传到 Google 云存储。
这是我使用的全部代码:
'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)
});
在 Functions 控制台上,日志显示 excel 文件已成功创建。但是上传的时候,弹出一个很奇怪的错误:
我做错了什么?我很感激任何帮助。
谢谢!
您收到的错误消息来自于尝试获取一个不存在的文件的已签名 URL。
当您调用 bucket.upload(filename, metadata)
时,您正在上传文件 /tmp/Relatorio.xlsx
,这会在您的存储桶中创建一个名为 Relatorio.xlsx
的文件。在下一行你调用 bucket.file(filename);
错误地将自己与 /tmp/Relatorio.xlsx
而不是 Relatorio.xlsx
.
要解决此问题,您应该使用从 bucket.upload()
解析的 File
对象,而不是自己创建它:
bucket.upload(filename, metadata)
.then((file) => file.getSignedURL())
.then((url) => {
console.log('Salvar URL' + url)
})
其他注释和修复
您的代码还包含很多不必要的 new Promise((resolve, reject) => { ... })
调用。这称为 Promise 构造函数反模式,其中大部分可以通过正确链接 Promise 来移除。 blog post 是关于 Promises 以及如何正确使用它们的很好的速成课程。
关于您的函数的源代码,由于函数的 index.js
文件将包含多个函数定义,因此您不应在 index.js
文件的顶部定义变量,除非它们由所有函数共享你的函数,它们是无状态的,以防一个函数被多次调用。这在处理 I/O 或文件等内存密集型资源时尤为重要。
使用您当前的代码,如果 relatorios 函数在短时间内被调用两次,保存的文件将包含第一次调用的旧数据和当前调用的新数据,从而导致文件无效和潜在的内存泄漏。
删除过多的 promise 调用并进行调用,以便您的 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);
});
});
这是我用来保存由 exceljs 生成的 excel 文件的版本 及其各自的库版本
// @google-cloud/storage --> 5.3.0
// exceljs --> 4.3.0
// moment --> 2.29.1
const moment = require('moment');
const ExcelJS = require('exceljs');
class Excel {
constructor(nameSheet) {
this.workbook = new ExcelJS.Workbook();
this.workbook.creator = 'User...';
this.workSheet = this.workbook.addWorksheet(nameSheet);
}
// ..... other code....
saveFile = async (path, filename) =>
{
const storage = new Storage();
const bucket = storage.bucket('name_bucket...');
const storageFile = bucket.file(`${path}/${filename}`);
const blobStream = storageFile.createWriteStream({
contentType: 'application/ms-excel',
});
await this.workbook.xlsx.write(blobStream);
blobStream.end();
const config = {
action: 'read',
expires: moment().add(30, 'minutes').format(),
};
return await storageFile.getSignedUrl(config)
}
}