代码之家  ›  专栏  ›  技术社区  ›  Amir-Mousavi

react本机(Expo)后台上传文件

  •  0
  • Amir-Mousavi  · 技术社区  · 2 年前

    在我的Expo(react native)应用程序中,即使应用程序在后台或已终止,我也希望执行上载任务。

    • 上传应该完成到firebase存储,所以我们没有REST API。
    • 查看了世博会任务经理库,但我不知道该怎么做。世博会有可能实现这个目标吗?TaskManager是否适用于此任务?
    • 只有一些Expo包可以注册为任务(例如backgroundFetch),并且不可能注册自定义函数(在这种情况下 uploadFile 方法)。
    • 我甚至更困惑了,因为我们应该启用add 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-fetch , react-native-background-upload , react-native-background-job upload 应该退出世博会, job 不支持iOS,并且 fetch 是一个提取任务,设计用于每隔一段时间执行任务。 如果有一种方法可以将上述库用于我的目的,请指导我:)
    • 据我所知,Firebase Cloud JSON API不接受文件,是吗?如果是,请给我举个例子。如果我可以让存储json API与文件上传一起工作,那么我可以使用Expo asyncUpload 可能不会弹出。
    0 回复  |  直到 2 年前
        1
  •  1
  •   Jacob    2 年前

    希望现在还为时不晚。 我一直在处理各种各样的博览会<->firebase存储集成最近,这里有一些信息可能会有所帮助。

    首先,我推荐 使用 uploadBytes / uploadBytesResumable 方法。 This Thread 关于它有一个长期的讨论,但基本上它在v9中被打破了。也许在未来,Firebase团队会解决这些问题,但现在它与Expo的关系相当破裂。

    相反,我建议要么编写一个小的Firebase函数 签名上传url 处理上传本身

    基本上,如果您可以通过http端点使存储上传工作,那么您可以使任何类型的上传机制工作。 (例如 FileSystem.uploadAsync() 你可能在这里寻找的方法,比如@brentvatne指出的,或者fetch,或者axios。我将在最后展示一个基本的接线)。

    服务器端

    选项1:已签名的URL上载。

    基本上,有一个小的firebase函数,它返回一个签名的url。您的应用程序调用云功能,如 /get-signed-upload-url ,返回url,然后使用该url。退房时间: https://cloud.google.com/storage/docs/access-control/signed-urls 你会怎么做。

    这可能适用于您的用例。它可以像任何配置一样进行配置 httpsCallable 功能,因此与选项2相比,设置起来不需要太多工作。

    然而 ,这不适用于firebase存储/函数模拟器!出于这个原因,我不使用这种方法,因为我喜欢集中使用模拟器,而且它们只提供所有功能的一个子集。

    选项2:完全通过函数上传文件

    这是一个有点毛,但给你的上传更多的保真度,并将在模拟器上工作!我也喜欢这样,因为它允许在端点执行中进行上传过程,而不是作为副作用。

    例如,你可以让照片上传端点生成缩略图,如果端点是201,那么你就很好了!而不是传统的Firebase方法,即让云存储的侦听器生成缩略图作为副作用,然后会有各种糟糕的竞争条件(通过指数后退检查处理完成情况?太恶心了!)

    以下是我建议采用这种方法的三种资源:

    基本上,如果你能在formdata中创建一个接受File的Firebase云端点,你可以让busboy解析它,然后你可以用它做任何你想做的事情……比如上传到云存储!

    概述:

    import * as functions from "firebase-functions";
    import * as busboy from "busboy";
    import * as os from "os";
    import * as path from "path";
    import * as fs from "fs";
    
    type FieldMap = {
      [fieldKey: string]: string;
    };
    
    type Upload = {
      filepath: string;
      mimeType: string;
    };
    
    type UploadMap = {
      [fileName: string]: Upload;
    };
    
    const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
    
    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.
    
      // This object will accumulate all the fields, keyed by their name
      const fields: FieldMap = {};
    
      // This object will accumulate all the uploaded files, keyed by their name.
      const uploads: UploadMap = {};
    
      // This will accumulator errors during the busboy process, allowing us to end early.
      const errors: string[] = [];
    
      const tmpdir = os.tmpdir();
    
      const fileWrites: Promise<unknown>[] = [];
    
      function cleanup() {
        Object.entries(uploads).forEach(([filename, { filepath }]) => {
          console.log(`unlinking: ${filename} from ${path}`);
          fs.unlinkSync(filepath);
        });
      }
    
      const bb = busboy({
        headers: req.headers,
        limits: {
          files: 1,
          fields: 1,
          fileSize: MAX_FILE_SIZE,
        },
      });
    
      bb.on("file", (name, file, info) => {
        verifyFile(name, file, info); // 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({
              uploadId,
            });
          } 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}/${PROJECT_LOCATION}`;
        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}`,
        },
      });
    });
    

    太长,读不下去了

    将云存储封装在您自己的端点中,将其视为正常的http上传,一切都很好。

        2
  •  0
  •   Brandonjgs    2 年前

    我做了一些类似的事情,你可以使用 expo-task-manager expo-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();
      }, []);