能否在React Native Expo项目中用AWS Amplify DataStore实现S3离线文件上传?
实现AWS Amplify DataStore + React Native/Expo的离线文件自动上传到S3
核心思路
AWS Amplify DataStore本身不直接处理大文件的离线同步,需要结合本地文件暂存、Amplify Storage上传队列和DataStore状态监听来实现:先将文件保存在本地,用DataStore记录文件状态,待网络恢复后自动触发S3上传并更新记录状态。
具体实现步骤
1. 定义DataStore模型
在GraphQL schema中添加字段,跟踪文件的本地路径、S3存储键和上传状态:
type Document @model { id: ID! name: String! fileType: String! # 例如 'pdf'/'jpg'/'png' fileLocalPath: String # 离线时的本地文件路径 fileS3Key: String # 上传到S3后的存储键 uploadStatus: String! # 枚举值:'PENDING'/'UPLOADED'/'FAILED' # 其他业务字段... }
2. 离线文件暂存
使用Expo的FileSystem和DocumentPickerAPI,将用户选择的文件复制到应用私有目录,同时创建DataStore记录标记为待上传:
import * as FileSystem from 'expo-file-system'; import * as DocumentPicker from 'expo-document-picker'; import { DataStore } from 'aws-amplify'; import { Document } from './models'; const pickAndSaveOfflineDocument = async () => { const pickerResult = await DocumentPicker.getDocumentAsync({ type: '*/*' }); if (pickerResult.type === 'success') { // 将文件复制到应用私有目录,避免原文件被系统删除 const localFilePath = `${FileSystem.documentDirectory}${Date.now()}-${pickerResult.name}`; await FileSystem.copyAsync({ from: pickerResult.uri, to: localFilePath, }); // 提取文件类型后缀 const fileType = pickerResult.name.split('.').pop().toLowerCase(); // 创建待上传的DataStore记录 await DataStore.save(new Document({ name: pickerResult.name, fileType, fileLocalPath: localFilePath, uploadStatus: 'PENDING', })); } };
3. 网络状态监听与自动上传
用Expo的NetworkAPI监听网络连接状态,恢复联网时自动处理所有待上传文件:
import { Network } from 'expo-network'; import { DataStore, Storage } from 'aws-amplify'; import { Document } from './models'; import * as FileSystem from 'expo-file-system'; // 监听网络状态变化 let networkListener; const setupNetworkUploadTrigger = async () => { const initialState = await Network.getNetworkStateAsync(); if (initialState.isConnected) processPendingUploads(); networkListener = Network.addNetworkStateListener(state => { if (state.isConnected) processPendingUploads(); }); }; // 处理所有待上传文件 const processPendingUploads = async () => { const pendingDocs = await DataStore.query(Document, d => d.uploadStatus('eq', 'PENDING')); for (const doc of pendingDocs) { try { // 生成S3存储键(用文档ID确保唯一性) const s3Key = `${doc.id}.${doc.fileType}`; // 上传本地文件到S3 await Storage.put(s3Key, doc.fileLocalPath, { contentType: getMimeType(doc.fileType), }); // 更新DataStore记录:标记为已上传,记录S3键 await DataStore.save(Document.copyOf(doc, updated => { updated.fileS3Key = s3Key; updated.uploadStatus = 'UPLOADED'; })); // 删除本地暂存文件 await FileSystem.deleteAsync(doc.fileLocalPath); } catch (error) { console.error(`文件 ${doc.name} 上传失败:`, error); // 标记上传失败,方便后续重试 await DataStore.save(Document.copyOf(doc, updated => { updated.uploadStatus = 'FAILED'; })); } } }; // 辅助函数:根据文件类型返回MIME类型 const getMimeType = (fileType) => { const mimeMap = { pdf: 'application/pdf', jpg: 'image/jpeg', png: 'image/png', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // 按需补充其他文件类型 }; return mimeMap[fileType] || 'application/octet-stream'; };
4. 错误重试机制
针对上传失败的文件,可在应用启动时触发重试,或提供手动重试按钮:
const retryFailedUploads = async () => { const failedDocs = await DataStore.query(Document, d => d.uploadStatus('eq', 'FAILED')); // 先将失败状态重置为待上传,再触发上传 for (const doc of failedDocs) { await DataStore.save(Document.copyOf(doc, updated => { updated.uploadStatus = 'PENDING'; })); } processPendingUploads(); };
注意事项
- 确保Amplify Storage已正确配置S3权限,允许用户上传文件
- 定期清理本地未上传的失效文件,避免占用存储空间
- 可添加上传进度监听,提升用户体验
内容的提问来源于stack exchange,提问作者QIAN TANG
相关产品推荐
相关产品推荐

