[英]Error while uploading file to Firebase Storage using Firebase Cloud Functions
I'm trying to upload a pdf to Firebase Storage using Firebase Cloud Functions, I have this post function with the following body: I'm trying to upload a pdf to Firebase Storage using Firebase Cloud Functions, I have this post function with the following body:
{
"email":"gianni@test.it",
"name":"Gianni",
"surname":"test",
"cellphone":"99999999",
"data":
{
"file":BASE_64,
"fileName":"test.pdf"
}
}
I want to save the base64 value in the "file" field and name as "fileName" field, here is the function that saves the file:我想在“文件”字段中保存 base64 值并命名为“文件名”字段,这里是保存文件的 function:
const admin = require("firebase-admin");
/**
* Create a new file in the storage.
* @param {Object} wrapper [File to upload in the storage.]
* @param {String} path [Path to upload file to.]
* @return {object} [Containing the response]
*/
postStorageFileAsync(wrapper, path) {
return new Promise((res, rej)=>{
System.prototype.writeLog({
wrapper: wrapper,
path: path,
});
const newFile = admin.storage().bucket().file(wrapper.path);
return newFile.save(wrapper.file).then((snapshot) => {
System.prototype.writeLog({snap: snapshot});
return snapshot.ref.getDownloadURL().then((downloadURL) => {
System.prototype.writeLog({fileUrl: downloadURL});
return res({code: 200, data: {url: downloadURL}});
});
}).catch((err)=>{
System.writeLog(err);
return rej(err);
});
});
}
But I'm getting:但我得到:
postCurriculum
课后
Error: A file name must be specified.
错误:必须指定文件名。 at Bucket.file (/workspace/node_modules/@google-cloud/storage/build/src/bucket.js:1612:19) at /workspace/Firebase/Firebase.js:43:48 at new Promise () at Object.postStorageFileAsync (/workspace/Firebase/Firebase.js:38:12) at /workspace/PersistanceStorage/PersistanceStorage.js:407:35 at processTicksAndRejections (internal/process/task_queues.js:97:5)
在 Bucket.file (/workspace/node_modules/@google-cloud/storage/build/src/bucket.js:1612:19) 在 /workspace/Firebase/Firebase.js:43:48 在新 Promise () 在 Z497031794414A552435F90151AC3B54. postStorageFileAsync (/workspace/Firebase/Firebase.js:38:12) at /workspace/PersistanceStorage/PersistanceStorage.js:407:35 at processTicksAndRejections (internal/process/task_queues.js:97:5)
Aside for the error itself, does anybody has a working example/tutorial link on how to upload files to firebase storage through functions?除了错误本身,是否有人有关于如何通过函数将文件上传到 firebase 存储的工作示例/教程链接? The documentation is really lacking.
文档真的很缺乏。
Thanks谢谢
In addition to @Renaud's comment, Firebase Admin SDK relies on Cloud Storage client library to access storage buckets.除了@Renaud 的评论,Firebase Admin SDK 依赖Cloud Storage 客户端库来访问存储桶。 If you check the Nodejs API doc, you'll see that
getDownloadURL()
does not exist so if you want to continue using the Admin SDK and get the download URL, you have to get the metadata of the file once it's uploaded. If you check the Nodejs API doc, you'll see that
getDownloadURL()
does not exist so if you want to continue using the Admin SDK and get the download URL, you have to get the metadata of the file once it's uploaded.
Here's a sample code I came up with:这是我想出的示例代码:
const admin = require("firebase-admin");
const os = require('os');
const fs = require('fs');
const path = require('path');
...
const wrapper = {
"file" : "BASE_64",
"fileName" : "filename.pdf",
}
const pathName = "test/dir"
const bucket = admin.storage().bucket();
async function postStorageFileAsync(wrapper, pathName) {
// Convert Base64 to PDF. You're only allowed to write in /tmp on Cloud Functions
const tmp = `${os.tmpdir()}/converted.pdf`
fs.writeFileSync(tmp, wrapper.file, 'base64', (error) => {
if (error) throw error;
});
// Upload to GCS
const target = path.join(pathName,wrapper.fileName);
await bucket.upload(tmp, {
destination: target
}).then(res =>{
fs.unlinkSync(tmp)
console.log(`Uploaded to GCS`)
})
// Get Download URL
const newFile = await bucket.file(target);
const [metadata] = await newFile.getMetadata();
const url = metadata.mediaLink;
console.log(url);
}
postStorageFileAsync(wrapper,pathName)
Note that in this code the object is not public, so unauthorized access to the URL will display a permission error.请注意,在此代码中 object 不是公开的,因此未经授权访问 URL 将显示权限错误。 If you want to make your objects accessible to public, see
File.makePublic()
.如果您想让您的对象可供公众访问,请参阅
File.makePublic()
。
So based upon Donnald Cucharo answer I will post my working solution for the people like me that are still using the javascript engine (that does not support the async in method's signature) instead of the typescript engine:因此,根据 Donnald Cucharo 的回答,我将为像我这样仍在使用 javascript 引擎(不支持方法签名中的异步)而不是 typescript 引擎的人发布我的工作解决方案:
Step 1) Install the @google-cloud/storage npm in your project, from what I understood every firebase storage is built on top of a google cloud storage instance.步骤 1)在您的项目中安装@google-cloud/storage npm,据我了解,每个 firebase 存储都构建在谷歌云存储实例之上。
Step 2) Follow this guide to generate an admin key from your firebase project console in order to upload it inside your functions folder to authorize your functions project to authenticate and comunicate with your storage project.步骤 2) 按照本指南从您的 firebase 项目控制台生成一个管理员密钥,以便将其上传到您的函数文件夹中,以授权您的函数项目与您的存储项目进行身份验证和通信。
Step 3) Use the following code (it is very similar to Donnald Cucharo's solution but with heavy use of promises):第 3 步)使用以下代码(它与 Donnald Cucharo 的解决方案非常相似,但大量使用了 Promise):
const functions = require("firebase-functions");
const path = require("path");
const os = require("os");
class System {
...
/**
* Extract th extension of a file from it's path, and returns it.
* @param {String} directory [Dir from where to extract the extension from.]
* @return {String} [Containing the extension of the file from the directory or undefined]
*/
getFileExtensionFromDirectory(directory) {
const ext = path.extname(directory);
if (ext === "") {
return undefined;
}
return ext;
}
/**
* Extract the name of the file from a directory.
* @param {String} directory [Dir from where to extract the extension from.]
* @return {String} [Containing the filename]
*/
getFileNameFromDirectory(directory) {
const extension = this.getFileExtensionFromDirectory(directory);
if (extension === undefined) return extension;
return path.basename(directory, extension);
}
/**
* Returns the system temp directory.
* @return {String} [Containing the system temporary directory.]
*/
getSystemTemporarydirectory() {
return os.tmpdir();
}
}
module.exports = System;
then, it's time for the actual uploading of the file:然后,是时候实际上传文件了:
const admin = require("firebase-admin");
const functions = require("firebase-functions");
const bucket = admin.storage().bucket(functions.config().bucket.url);
const System = require("../System/System");
const fs = require("fs");
/**
* Pure fabrication class to access the Firebase services
*/
class Firebase {
/**
* Create a new file in the storage.
* @param {Object} wrapper [File to upload in the storage.]
* @param {String} path [Path to upload file to.]
* @return {object} [Containing the response]
*/
postStorageFileAsync(wrapper) {
return new Promise((res, rej)=>{
if (wrapper.file === undefined) {
return rej({code: 400, error:
{it: "Devi forninre il valore in base64 del file che vuoi caricare.",
en: "You must provide the base64 value of the file to upload."}});
}
if (wrapper.path === undefined) {
return rej({code: 400, error:
{it: "Devi fornire il percorso dove vuoi caricare il tuo file.",
en: "Missing path filed in wrapper."}});
}
const fileName = System.prototype.getFileNameFromDirectory(wrapper.path);
const fileExtension = System.prototype.getFileExtensionFromDirectory(wrapper.path);
if (fileName === undefined || fileExtension === undefined) {
return rej({code: 400, error:
{it: "Formato del file non riconosciuto.",
en: "Unrecognized file type or file name, the file should be in fileName.extension format"}});
}
const file = fileName + fileExtension;
const tmpDirectory = System.prototype.getSystemTemporarydirectory() + "/" + file;
return fs.promises.writeFile(tmpDirectory, wrapper.file, "base64").then(()=>{
const options = {
destination: wrapper.path,
};
return bucket.upload(tmpDirectory, options).then(() =>{
fs.unlinkSync(tmpDirectory);
return this.getStorageFileFromPathAsync(wrapper.path).then((response)=>{
return res(response);
});
});
}).catch((err)=>{
fs.unlinkSync(tmpDirectory);
System.prototype.writeLog(err);
return rej({code: 500, error: err});
});
});
}
/**
* Retrieve the url of a file in the storage from the path.
* @param {String} path [Path of the file to get the url]
* @return {Object} [Containing te response]
*/
getStorageFileFromPathAsync(path) {
return new Promise((res, rej)=>{
bucket.file(path).makePublic().then(()=>{
bucket.file(path).getMetadata() .then((response)=>{
System.prototype.writeLog({response: response});
const metadata = response[0];
System.prototype.writeLog({metadata: metadata});
return res({code: 200, url: metadata.mediaLink});
});
}).catch((err)=>{
System.prototype.writeLog(err);
return rej({code: 500, error: err});
});
});
}
}
module.exports = Firebase;
Now let's talk about the code, because there were some parts that I've struggle to understand, but the main reasoning is something like this:现在让我们谈谈代码,因为有些部分我很难理解,但主要原因是这样的:
We need to create a directory with an empty file in the temp directory of the system.我们需要在系统的 temp 目录中创建一个包含空文件的目录。
By using the fs library we need to open a stream and dump the data in the temp directory we created:通过使用 fs 库,我们需要打开 stream 并将数据转储到我们创建的临时目录中:
fs.promises.writeFile(tmpDirectory, wrapper.file, "base64").then(()=>{....}) fs.promises.writeFile(tmpDirectory, wrapper.file, "base64").then(()=>{....})
We can now upload the file in the firebase storage by using the path we created in step 1-2 but we can't access the download url yet.我们现在可以使用我们在步骤 1-2 中创建的路径将文件上传到 firebase 存储中,但我们还无法访问下载 url。
As we authenticated as admins before we can just change the newly created file visibility and finally get the download url by using the getStorageFileFromPathAsync() method.由于我们在通过使用 getStorageFileFromPathAsync() 方法更改新创建的文件可见性并最终获得下载 url 之前以管理员身份进行了身份验证。
I hope it was helpful.我希望它是有帮助的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.