由于您未能指定目标语言,我将假设您使用的是 JavaScript Web SDK。在每个 SDK 中,add(data) 是 doc().set(data) 的语法糖。因此,要生成我们可以在文件上传中使用的表单 ID,我们可以使用:
const formRef = firebase.firestore().collection("forms").doc();
最终,在您的数据库中存储图像时,您有两种选择。
不推荐:将资源嵌入 Cloud Firestore 文档中
在文档中存储二进制文件的最简单方法之一是使用Data URL。这些 URL 获取二进制数据,将其编码为 Base64,添加一些关于存储数据的元数据,然后生成如下所示的字符串:
// Data URL for a PNG image of a single #FFFFFF pixel
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAMSURBVBhXY/j//z8ABf4C/qc1gYQAAAAASUVORK5CYII="
然后可以将这些数据 URL 存储在文档中(例如 /forms/{formId})或其子集合之一中(例如 /forms/{formId}/attachments/{attachmentId})。通过以这种方式存储图像,由于 Base64 编码,文件大小会膨胀大约 25% 或更多。
使用此方法,您可以使用Cloud Firestore Security Rules 控制对图像的访问,但它还需要您实现从数据库中获取图像并将其插入网页的逻辑。
要将添加到表单的图像转换为数据 URL,请参阅FileReader#readAsDataURL() 的文档。您还可以将 Web 画布转换为数据 URL,如 question thread 所示。
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener("load", function () { resolve(this.result) }, false);
reader.addEventListener("error", function () { resolve(this.error) }, false);
reader.readAsDataURL(file);
});
}
async function onSubmitForm(e) {
const user = firebase.auth().currentUser;
if (!user) {
setErrorMessage("You must be signed in!");
return false;
}
const uid = user.uid;
const files = document.querySelector('input[type=file]').files;
const dataURLs = files
? await Promise.all([].map.call(files, readFileAsDataURL))
: [];
// prepare database stuff
const db = firebase.firestore();
const batch = db.batch();
// create form reference
const formRef = db.collection("forms").doc();
// assemble & queue upload of each attachment
const attachmentsColRef = formRef.collection("attachments");
const attachmentIds = dataURLs.map((url, i) => {
const file = files[i];
const attachmentRef = attachmentsColRef.doc();
const attachmentData = {
id: attachmentRef.id, // optional
lastModified: firebase.firestore.Timestamp.fromMillis(file.lastModified),
name: file.name,
src: url,
type: file.type
}
batch.set(attachmentRef, attachmentData);
return attachmentRef.id;
}
// assemble form data
const formData = {
attachments: attachmentIds,
content: "this is my first post",
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
id: formRef.id, // optional
name: "john",
title: "Hello world! I'm John",
uid,
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
views: 0
}
// queue upload of form data
batch.set(formRef, formData);
// do the upload
return batch.commit()
.then(() => {
console.log(`Successfully created Form #${formRef.id}!`);
return true;
})
.catch((error) => {
setErrorMessage("Failed to submit form data!");
console.error(`Failed to create Form #${formRef.id}!`, error);
return false;
});
}
推荐:将文件存储在云存储(或其他合适的二进制数据存储)中
这部分将专注于谷歌云存储,但也可以应用于其他基于应用程序的文件存储服务(如 Amazon Elastic File System、Amazon S3 Buckets、DigitalOcean Spaces 等)和以消费者为中心的文件存储服务(例如 Google Drive、OneDrive、Dropbox、WeTransfer 等)。
与 Cloud Firestore 类似,您可以将图像存储在这个“二进制对象数据库”中,并使用Cloud Storage Security Rules 保护它。但是,与 Cloud Firestore 相比,此存储方法针对以多种不同形式存储二进制数据进行了优化,并提供了许多特定于文件处理的重点功能,例如限制上传的文件、恢复上传、浏览器缓存信息、保留策略(例如删除所有超过 1 年的文件)和文件版本控制(如果您想使用它)。
可以通过 Cloud Storage 和 Firebase SDK 或 URL 访问存储在 Cloud Storage 中的文件(如果文件是私有文件,则在访问 URL 时需要提供访问令牌)。
这些 URL 通常采用以下形式:
// public file, in Google Cloud Storage
"https://storage.googleapis.com/BUCKET_NAME/OBJECT_NAME"
// private file, with an access token, in Firebase Cloud Storage
"https://firebasestorage.googleapis.com/v0/b/PROJECT_ID.appspot.com/o/OBJECT_NAME.png?alt=media&token=ACCESS_TOKEN"
async function onSubmitForm(e) {
const user = firebase.auth().currentUser;
if (!user) {
setErrorMessage("You must be signed in!");
return false;
}
const uid = user.uid;
const files = document.querySelector('input[type=file]').files;
// prepare database stuff
const db = firebase.firestore();
const batch = db.batch();
// create form reference
const formRef = db.collection("forms").doc();
// upload and assemble the document for each attachment
const attachmentsColRef = formRef.collection("attachments");
const attachmentIds = !files ? [] : await Promise.all(
[].map.call(files, async (file) => {
const attachmentRef = attachmentsColRef.doc();
const storageRef = firebase.storage()
.ref(`formUploads/${formRef.id}`)
.child(`attachments/${attachmentRef.id}`)
.child(uid)
.child(file.name);
const uploadResult = await storageRef.put(file);
const url = await storageRef.getDownloadURL();
const attachmentData = {
id: attachmentRef.id, // optional
lastModified: firebase.firestore.Timestamp.fromMillis(file.lastModified),
name: file.name,
src: url,
type: file.type
}
batch.set(attachmentRef, attachmentData);
return attachmentRef.id;
})
);
// assemble form data
const formData = {
attachments: attachmentIds,
content: "this is my first post",
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
id: formRef.id, // optional
name: "john",
title: "Hello world! I'm John",
uid,
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
views: 0
}
// queue upload of form data
batch.set(formRef, formData);
// do the upload
return batch.commit()
.then(() => {
console.log(`Successfully created Form #${formRef.id}!`);
return true;
})
.catch((error) => {
setErrorMessage("Failed to submit form data!");
console.error(`Failed to create Form #${formRef.id}!`, error);
return false;
});
}