我很难找到关于如何在基于 Expo 的移动应用程序中上传和下载文件的清晰示例。为了帮助面临同样挑战的其他人或任何只是好奇的人,我写了这篇文章。
在此过程中,我们将探索有助于理解的关键概念:
我们将涵盖的内容:
所有代码和Postman集合都可以在我的GitHub上找到。
服务器在Fastify(Express.js 的现代化版本)上运行。要启动应用程序,请执行以下操作:
在 app.js 中,我们有三个关键端点:
fastify.get("/download", async function handler(_, reply) { const fd = await open(FILE_TO_DOWNLOAD); const stream = fd.createReadStream(); const mimeType = mime.lookup(FILE_TO_DOWNLOAD); console.log(`Downloading -> ${FILE_TO_DOWNLOAD}`); return reply .type(mimeType) .header( "Content-Disposition", `attachment; filename=${path.basename(FILE_TO_DOWNLOAD)}` ) .send(stream); });
此端点使用 createReadStream() 将 example.webp 作为流发送。包含 MIME 类型,以便客户端知道如何处理该文件。例如.webp,这将是image/webp。
?注意:MIME 类型定义了正在发送的文件的格式。这有助于客户端正确显示它。
查看更多 MIME 类型。
Content-Disposition 标头定义了如何将内容呈现给客户端。包括附件;文件名=;提示浏览器下载文件而不是内联显示它。要直接显示它,请使用内联而不是附件。
了解有关内容处置的更多信息
fastify.post("/upload-multiples", async function handler(request) { const parts = request.files(); const uploadResults = []; for await (const file of parts) { const fileBuffer = await file.toBuffer(); const filename = file.filename; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, fileBuffer); uploadResults.push({ filename, uploaded: true }); console.log(`Uploaded -> ${filePath}`); } return { uploadedFiles: uploadResults }; });
此端点接受多部分/表单数据请求。它:
例如,请求可能如下所示:
fastify.get("/download", async function handler(_, reply) { const fd = await open(FILE_TO_DOWNLOAD); const stream = fd.createReadStream(); const mimeType = mime.lookup(FILE_TO_DOWNLOAD); console.log(`Downloading -> ${FILE_TO_DOWNLOAD}`); return reply .type(mimeType) .header( "Content-Disposition", `attachment; filename=${path.basename(FILE_TO_DOWNLOAD)}` ) .send(stream); });
此端点期望请求正文中有一个二进制文件(应用程序/八位字节流)。与multipart/form-data不同的是,该文件已经是二进制数据了,所以我们可以直接将其写入磁盘。
请求在 Postman 中看起来像这样:
运行应用程序:
Web 应用程序的所有功能都包含在 App.tsx 中:
这个 React 应用程序提供三个关键功能:
fastify.post("/upload-multiples", async function handler(request) { const parts = request.files(); const uploadResults = []; for await (const file of parts) { const fileBuffer = await file.toBuffer(); const filename = file.filename; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, fileBuffer); uploadResults.push({ filename, uploaded: true }); console.log(`Uploaded -> ${filePath}`); } return { uploadedFiles: uploadResults }; });
当用户单击“下载”按钮时,应用程序:
行为取决于服务器返回的 Content-Disposition 标头:
为了触发下载,应用程序会创建一个临时的 ; href 设置为 objectURL 的元素并以编程方式单击它,模拟用户下载操作。
fastify.post("/upload-octet-stream", async function handler(request) { const filename = request.headers["x-file-name"] ?? "unknown.text"; const data = request.body; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, data); return { uploaded: true }; });
单击“上传文件”按钮时:
这使得服务器能够正确处理和保存上传的文件。
const downloadFile = async () => { const response = await fetch(DOWNLOAD_API); if (!response.ok) throw new Error("Failed to download file"); const blob = await response.blob(); const contentDisposition = response.headers.get("Content-Disposition"); const isInline = contentDisposition?.split(";")[0] === "inline"; const filename = contentDisposition?.split("filename=")[1]; const url = window.URL.createObjectURL(blob); if (isInline) { window.open(url, "_blank"); } else { const a = document.createElement("a"); a.href = url; a.download = filename || "file.txt"; a.click(); } window.URL.revokeObjectURL(url); };
这种方法比使用 multipart/form-data 更简单 - 只需将文件作为二进制数据直接在请求正文中发送,并将文件名包含在请求标头中。
您可以使用以下命令启动应用程序:
主要逻辑位于 App.tsx 中,它呈现以下内容:
fastify.get("/download", async function handler(_, reply) { const fd = await open(FILE_TO_DOWNLOAD); const stream = fd.createReadStream(); const mimeType = mime.lookup(FILE_TO_DOWNLOAD); console.log(`Downloading -> ${FILE_TO_DOWNLOAD}`); return reply .type(mimeType) .header( "Content-Disposition", `attachment; filename=${path.basename(FILE_TO_DOWNLOAD)}` ) .send(stream); });
要在新视图中显示文件(就像浏览器在新选项卡中打开文件一样),我们必须将响应作为 blob 读取,然后使用 FileReader 将其转换为 base64。
我们将文件写入缓存目录(只有应用程序可以访问的私有目录),然后使用 IntentLauncher 或共享(如果用户使用 iOS)显示它。
fastify.post("/upload-multiples", async function handler(request) { const parts = request.files(); const uploadResults = []; for await (const file of parts) { const fileBuffer = await file.toBuffer(); const filename = file.filename; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, fileBuffer); uploadResults.push({ filename, uploaded: true }); console.log(`Uploaded -> ${filePath}`); } return { uploadedFiles: uploadResults }; });
这与 Web 进程类似,但我们必须使用 FileReader 将 blob 读取为 base64,然后请求权限将文件下载到用户想要保存文件的位置。
fastify.post("/upload-octet-stream", async function handler(request) { const filename = request.headers["x-file-name"] ?? "unknown.text"; const data = request.body; const filePath = path.join(DIR_TO_UPLOAD, filename); await writeFile(filePath, data); return { uploaded: true }; });
使用 DocumentPicker 使用户能够选择文件,然后使用 FormData 将所选文件附加到请求中。过程非常简单。
const downloadFile = async () => { const response = await fetch(DOWNLOAD_API); if (!response.ok) throw new Error("Failed to download file"); const blob = await response.blob(); const contentDisposition = response.headers.get("Content-Disposition"); const isInline = contentDisposition?.split(";")[0] === "inline"; const filename = contentDisposition?.split("filename=")[1]; const url = window.URL.createObjectURL(blob); if (isInline) { window.open(url, "_blank"); } else { const a = document.createElement("a"); a.href = url; a.download = filename || "file.txt"; a.click(); } window.URL.revokeObjectURL(url); };
作为 Application/octet-stream 上传比使用 FormData 更简单:使用文件详细信息和内容类型设置标头,然后将文件添加到请求正文,就是这样!
如何在平台之间查看、下载和上传文件可能有点令人困惑,在这篇文章中我们看到了最常见的。
希望对您有帮助?
让我在@twitter上
以上是React & Expo - 如何上传和下载文件的详细内容。更多信息请关注PHP中文网其他相关文章!