【问题标题】:react-native (Expo) upload file on backgroundreact-native (Expo) 在后台上传文件
【发布时间】:2022-06-21 11:03:27
【问题描述】:

在我的 Expo (react-native) 应用程序中,即使应用程序在后台或被终止,我也想做上传任务。

  • 应该上传到 Firebase 存储,所以我们没有 REST API。
  • 查看了 Expo 任务管理器库,但我不知道该怎么做。甚至有可能通过世博会实现这个目标吗? TaskManager 是这个任务的正确包吗?
  • 只有一些 Expo 包可以注册为任务(例如 backgroundFetch),并且无法注册自定义函数(在本例中为 uploadFile 方法)。
  • 我什至更加困惑,因为我们应该为 iOS 启用添加 UIBackgroundModes 键,但它只有 audio,location,voip,external-accessory,bluetooth-central,bluetooth-peripheral,fetch,remote-notification,processing 作为可能的值。

如果您至少可以指导我从哪里开始或搜索什么,即使应用程序在后台被终止/终止,也能够上传文件,我将不胜感激。

   import { getStorage, ref, uploadBytes } from "firebase/storage";    
    const storage = getStorage();
    const storageRef = ref(storage, 'videos');    
    const uploadFile = async (file)=>{ 
      // the file is Blob object
      await uploadBytes(storageRef, file);
    }
  • 我已经查看了react-native-background-fetchreact-native-background-uploadreact-native-background-jobupload 应该弹出 Expo,job 不支持 iOS,fetch 是一个为间隔执行任务而设计的获取任务。 如果有办法为我的目的使用提到的库,请指导我:)
  • 据我了解,Firebase Cloud JSON API 不接受文件,是吗?如果是这样,请给我一个例子。如果我可以使存储 json API 与文件上传一起使用,那么我可以使用 Expo asyncUpload 可能不会弹出。

【问题讨论】:

标签: react-native file-upload expo firebase-storage taskmanager


【解决方案1】:

希望这对您有所帮助还为时不晚。 我最近一直在处理各种 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 上传,一切都很好。

【讨论】:

    【解决方案2】:

    我已经完成了您想要的类似操作,您可以使用expo-task-managerexpo-background-fetch。这是我使用的代码。我希望这对你有用。

    import * as BackgroundFetch from 'expo-background-fetch';
    import * as TaskManager from 'expo-task-manager';
    
    const BACKGROUND_FETCH_TASK = 'background-fetch';
    const [isRegistered, setIsRegistered] = useState(false);
    const [status, setStatus] = useState(null);
    
    //Valor para que se ejecute en IOS
    BackgroundFetch.setMinimumIntervalAsync(60 * 15);
    
    // Define the task to execute 
    TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
      const now = Date.now();
      console.log(`Got background fetch call at date: ${new Date(now).toISOString()}`);
    
    //   Your function or instructions you want
      return BackgroundFetch.Result.NewData;
    });
    
    // Register the task in BACKGROUND_FETCH_TASK
    async function registerBackgroundFetchAsync() {
      return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
        minimumInterval: 60 * 15, // 1 minutes
        stopOnTerminate: false, // android only,
        startOnBoot: true, // android only
      });
    }
    
    // Task Status 
    const checkStatusAsync = async () => {
      const status = await BackgroundFetch.getStatusAsync();
      const isRegistered = await TaskManager.isTaskRegisteredAsync(
        BACKGROUND_FETCH_TASK
      );
      setStatus(status);
      setIsRegistered(isRegistered);
    };
    
    // Check if the task is already register
    const toggleFetchTask = async () => {
      if (isRegistered) {
        console.log('Task ready');
      } else {
        await registerBackgroundFetchAsync();
        console.log('Task registered');
      }
    
      checkStatusAsync();
    };
    
    useEffect(() => {
        toggleFetchTask();
      }, []);
    

    【讨论】:

    • 这和我问的有什么关系?设置获取间隔甚至不接近按需上传。这个代码示例在 Internet 上随处可见,如果你搜索 expo 后台搜索,这个 sn-p 就会出现在任何地方。
    • 呃,如果我没有理解这个问题,对不起,祝你好运。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-31
    • 2021-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多