You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 15:45:32