希望这对您有所帮助还为时不晚。
我最近一直在处理各种 expo firebase 存储集成,这里有一些可能有用的信息。
首先,我建议不要使用 Firebase 中的 uploadBytes / uploadBytesResumable 方法 This Thread 对此进行了长时间的讨论,但基本上它在 v9 中已被破坏,并且有点糟透了。也许将来 Firebase 团队会解决这些问题,但现在与 Expo 完全不同。
相反,我建议您编写一个小型 Firebase 函数,该函数提供 signed-upload-url 或 自行处理上传。
基本上,如果您可以通过 http 端点让存储上传工作,那么您可以让任何类型的上传机制工作。(例如,您可能在这里寻找的 FileSystem.uploadAsync() 方法,就像@brentvatne 指出的那样,或者 fetch,或者 axios。我将在最后展示一个基本的接线)。
服务器端
选项 1:签名 URL 上传。
基本上,有一个返回签名 url 的小型 firebase 函数。您的应用程序会调用类似 /get-signed-upload-url 的云函数,它会返回您使用的 url。查看:https://cloud.google.com/storage/docs/access-control/signed-urls 了解您将如何处理。
这可能适用于您的用例。它可以像任何 httpsCallable 函数一样进行配置,因此与选项 2 相比,它的设置工作并不多。
然而,这不适用于 firebase 存储/功能模拟器!出于这个原因,我不使用这种方法,因为我喜欢集中使用模拟器,它们只提供所有功能的一个子集。
选项 2:完全通过函数上传文件
这有点麻烦,但可以让您上传的保真度更高,并且可以在模拟器上运行!我也喜欢这个,因为它允许在端点执行中进行上传过程,而不是作为副作用。
例如,您可以让照片上传端点生成缩略图,如果端点是 201,那您就很好了!而不是传统的 Firebase 方法让云存储监听器会生成缩略图作为副作用,然后会产生各种不良竞争条件(通过指数退避检查处理完成?恶心!)
我建议使用以下三个资源来了解这种方法:
基本上,如果您可以创建一个 Firebase 云端点来接受 formdata 中的文件,您可以让 busboy 解析它,然后您可以用它做任何您想做的事情……比如将其上传到 Cloud Storage!
在伪代码中:
export const uploadPhoto = functions.https.onRequest(async (req, res) => {
verifyRequest(req); // Verify parameters, auth, etc. Better yet, use a middleware system for this like express.
const fileWrites: Promise<unknown>[] = [];
const errors: string[] = []; // Aggregate errors through the bb parsing and processing steps
const bb = busboy({
headers: req.headers,
limits: {
files: 1,
fields: 1,
fileSize: MAX_FILE_SIZE
}
);
bb.on("file", (name, file, info) => {
verifyFile(...); // Verify your mimeType / filename, etc.
file.on("limit", () => {
console.log("too big of file!");
});
const { filename, mimeType } = info;
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
uploads[filename] = {
filepath,
mimeType,
};
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written.
// Note: GCF may not persist saved files across invocations.
// Persistent files must be kept in other locations
// (such as Cloud Storage buckets).
const promise = new Promise((resolve, reject) => {
file.on("end", () => {
writeStream.end();
});
writeStream.on("finish", resolve);
writeStream.on("error", reject);
});
fileWrites.push(promise);
});
bb.on("close", async () => {
await Promise.all(fileWrites);
// Fail if errors:
if (errors.length > 0) {
functions.logger.error("Upload failed", errors);
res.status(400).send(errors.join());
} else {
try {
const upload = Object.values(uploads)[0];
if (!upload) {
functions.logger.debug("No upload found");
res.status(400).send("No file uploaded");
return;
}
const { uploadId } = await processUpload(upload, userId);
cleanup();
res.status(201).send({
photoId,
});
} catch (error) {
cleanup();
functions.logger.error("Error processing file", error);
res.status(500).send("Error processing file");
}
}
});
bb.end(req.rawBody);
然后,processUpload 函数可以对文件做任何你想做的事情,比如上传到云存储:
async function processUpload({ filepath, mimeType }: Upload, userId: string) {
const fileId = uuidv4();
const bucket = admin.storage().bucket();
await bucket.upload(filepath, {
destination: `users/${userId}/${fileId}`,
{
contentType: mimeType,
},
});
return { fileId };
}
移动端
然后,在移动端,你可以像这样与之交互:
async function uploadFile(uri: string) {
function getFunctionsUrl(): string {
if (USE_EMULATOR) {
const origin =
Constants?.manifest?.debuggerHost?.split(":").shift() || "localhost";
const functionsPort = 5001;
const functionsHost = `http://${origin}:${functionsPort}/{PROJECT_NAME}/us-central1`;
return functionsHost;
} else {
return `https://{PROJECT_LOCATION}-{PROJECT_NAME}.cloudfunctions.net`;
}
}
// The url of your endpoint. Make this as smart as you want.
const url = `${getFunctionsUrl()}/uploadPhoto`;
await FileSystem.uploadAsync(uploadUrl, uri, {
httpMethod: "POST",
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
fieldName: "file", // Important! make sure this matches however you want bussboy to validate the "name" field on file.
mimeType,
headers: {
"content-type": "multipart/form-data",
Authorization: `${idToken}`,
},
});
});
TLDR
将云存储包装在您自己的端点中,将其视为普通的 http 上传,一切都很好。