问题描述
我正在尝试使用 Firebase Cloud Functions 将 pdf 文件上传到 Firebase 存储,我有一个具有以下正文的 post 函数:
{
"email":"gianni@test.it","name":"Gianni","surname":"test","cellphone":"99999999","data":
{
"file":BASE_64,"fileName":"test.pdf"
}
}
我想在“file”字段中保存base64值并命名为“fileName”字段,这里是保存文件的函数:
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);
});
});
}
但我得到:
后课程
错误:必须指定文件名。在 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)
除了错误本身,是否有人有关于如何通过函数将文件上传到 firebase 存储的工作示例/教程链接?文档真的很缺乏。
谢谢
解决方法
除了@Renaud 的评论之外,Firebase Admin SDK 还依赖于 Cloud Storage client library 来访问存储桶。如果您查看 Nodejs API 文档,您会看到 getDownloadURL()
不存在,因此如果您想继续使用 Admin SDK 并获取下载 URL,您必须获取一次文件的元数据已上传。
这是我想出的示例代码:
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)
请注意,在此代码中,对象不是公开的,因此对 URL 的未授权访问将显示权限错误。如果您想让您的对象可供公众访问,请参阅 File.makePublic()
。
因此,根据 Donnald Cucharo 的回答,我将为像我这样仍在使用 javascript 引擎(不支持方法签名中的异步)而不是打字稿引擎的人发布我的工作解决方案:
步骤 1) 在您的项目中安装 @google-cloud/storage npm,据我所知,每个 Firebase 存储都构建在 Google 云存储实例之上。
第 2 步)按照 this guide 从您的 firebase 项目控制台生成一个管理密钥,以便将其上传到您的函数文件夹中,以授权您的函数项目对您的存储项目进行身份验证和通信。
第 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;
然后,是时候实际上传文件了:
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,"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;
现在让我们谈谈代码,因为有些部分我很难理解,但主要的推理是这样的:
-
我们需要在系统的临时目录下创建一个目录,里面有一个空文件。
-
通过使用 fs 库,我们需要打开一个流并将数据转储到我们创建的临时目录中:
fs.promises.writeFile(tmpDirectory,"base64").then(()=>{ ....})
-
我们现在可以使用我们在步骤 1-2 中创建的路径将文件上传到 firebase 存储中,但我们还无法访问下载 url。
-
因为在我们可以更改新创建的文件可见性并最终使用 getStorageFileFromPathAsync() 方法获取下载 url 之前,我们以管理员身份进行了身份验证。
希望对您有所帮助。