【问题标题】:Connect a Cloud Function to an iOS app to send mass push notifications at once将云功能连接到 iOS 应用程序以立即发送大量推送通知
【发布时间】:2020-09-16 23:28:16
【问题描述】:

我是 Swift 开发人员,但我不是后端开发人员。这实际上是下面以粗体显示的 3 个不同的问题,但它们都相互依赖。任何类似问题的堆栈溢出答案都足以让我开始

@followers
      |
    kim_userId // kimKardashian
          -userId_0: 1
           -... // every user in between
           -userId_188_million: 1

现在我正在使用一种非常低效的方式来发送大量推送通知:

@IBAction func postButtonTapped(button: UIButton) {

    let postsRef = Database.database().reference().child("posts").child(kim_userId).child(postId)
    postsRef.updateChildValues(postDictionary, withCompletionBlock: { (err, _)

        if let error = error { return }

        // post was successful now send a push notification to all of these followers
        self.fetchFollowers(for: kim_userId, send: postId)
    })
}

func fetchFollowers(for userId: String, send newPostId: String) {

    let followersRef = Database.database().reference().child("followers").child(userId)
    followersRef.observe(.childAdded) { (snapshot) in

        let userId = snapshot.key

        self.fetchDeviceToken(forFollower: userId, send: newPostId)          
    }
}

func fetchDeviceToken(forFollower userId: String, send newPostId: String) {

    let usersRef = Database.database().reference().child("users").child(userId)
    usersRef.observeSingleEvent(of .value) { (snapshot) in

        guard let dict = snapshot.value as? [String: Any] else { return }

        guard let deviceToken = dict["deviceToken"] as? String else { return }

        self.sendPushNotification(toFollower: userId, with: deviceToken, send: newPostId)
    }
}

func sendPushNotification(toFollower: userId, with deviceToken: String, send newPostId: String) {
      
    var apsDict = [String: Any]()
    // newPostId and whatever other values added to the dictionary

    guard let url = URL(string: "https://fcm.googleapis.com/fcm/send") else { return }

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = try? JSONSerialization.data(withJSONObject: apsDict, options: [])
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("key=\(my_serverKey...)", forHTTPHeaderField: "Authorization")
    let task = URLSession.shared.dataTask(with: request)  { (data, response, error) in
        do {
            if let jsonData = data {
                if let jsonDataDict = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject] {
                    print("Received data:\n\(jsonDataDict))")
                }
            }
        } catch let err as NSError {
            print(err.debugDescription)
        }
    }
    task.resume()
}

例如,金·卡戴珊 (Kim Kardashian) 在 Instagram 上拥有 1.88 亿关注者,当她发布一些内容时,它会立即发送给她的所有关注者。我目前正在做的方式不是要走的路。我很确定这是 Cloud Functions 的一种情况,但我对 Cloud Functions 的了解不够,所以我正在寻找从哪里开始。

-如何在 iOS 应用中连接 Cloud Functions?

-无论我必须从“追随者”参考中获取每个追随者,然后我必须从他们的“用户”参考中获取每个追随者的 deviceToken,我不知道从哪里开始强>

-如何在 Cloud Functions 中实际发送推送通知代码?我找到了这个answer but it's in javascript。我不懂 javascript,但我知道一点 Node.js

后VC:

@IBAction func postButtonTapped(button: UIButton) {

    let postsRef = Database.database().reference().child("posts").child(kim_userId).child(postId)
    postsRef.updateChildValues(postDictionary, withCompletionBlock: { (err, _)

        if let error = error { return }

        // post was successful now connect to Cloud Functions so that a mass push notification can be sent

        self.codeToConnectWithCloudFunctions(for: kim_userId, send: postId)
    })
}

func codeToConnectWithCloudFunctions(for userId: String, send newPostId: String) {

    // 1. how do I get each of her followers
    // 2. how do I get each of their deviceTokens
    // 3. how do I send the push notification
}

任何具有相似答案的链接都足以让我开始。我可以从那里进行更多挖掘,并根据我发现的任何内容提出更具体的问题

【问题讨论】:

    标签: ios node.js swift google-cloud-functions


    【解决方案1】:

    如何在 iOS 应用中连接 Cloud Functions?

    您可以通过执行与调用任何 API 相同的操作来调用 Cloud Functions,您必须将 Cloud Functions 设计为可调用函数,您可以找到有关它们的更多详细信息以及如何设置它们,在Swift 和其他语言,在此Documentation

    无论我必须从“追随者”参考中获取每个追随者,然后我必须从他们的“用户”参考中获取每个追随者的 deviceToken,我不知道从哪里开始如何在 Cloud Functions 中实际发送推送通知代码?我找到了这个答案,但它在 javascript 中。我不懂 javascript,但我知道一点 Node.js

    community answer 可以一起回答这两个问题,该community answer 声明您应该将 Firebase Cloud Messaging 集成到您的 iOS 应用程序中,并提供指向该主题的完整文档的链接。此外,您可以在此Documentation 中找到如何将通知发送到多个设备,在那里您还可以找到需要使用 Admin SDK 在 Cloud Function 本身中使用的代码示例。

    注意:Cloud Functions 可以用 Node.js、Go、Java 和 Python 编写,Cloud Functions 的所有示例代码都使用这些语言。

    【讨论】:

      【解决方案2】:

      以下是使用 Cloud Functions 将数据从 iOS 应用程序发送到 RealTimeDatabase 的方法:

      在开始之前,您需要安装node.js/npm,这很容易做到,请按照此youtube

      1- 假设您已安装 node,请转到 Firebase 控制台 > 在右侧选择 Functions > Upgrade > Pay as you go Blaze Plan > Get Started > Continue > Finish

      2- 打开终端并输入 $npm install -g firebase-tools(如果已安装,则可以跳过此步骤)。如果您收到 Error: EACCES: permission denied 响应,请输入 $sudo npm install -g firebase-tools 并输入您的 cpu 密码以继续

      3-完成后输入$npm --version查看你安装的是哪个版本

      4- 创建一个空文件夹并将其命名为 Cloud_Functions 之类的名称。将该文件夹拖到下面的步骤 5 中。您将在此 Cloud_Functions 文件夹中执行以下所有步骤。

      5- 转到 Xcode 项目所在的主文件夹,地雷在我的桌面上并命名为 fooProject

      6- cd 进入该文件夹 $ cd fooProject 然后 $ cd Cloud_Functions

      7- 在终端中输入 $ pwd 因为如果您必须在 Cloud_Functions 文件夹中才能进行此工作

      8- 在终端输入 $firebase login(输入您的登录凭据并按 Enter)

      9-假设您在正确的文件夹中,在终端输入 $ firebase init functions

      10.A- 您可能会首先看到此选项(或根本看不到):

      您要为此文件夹设置哪些 Firebase CLI 功能?按 Space 选择功能,然后按 Enter 确认您的选择

      使用向上/向下键选择Functions: Configure and deploy Cloud Functions,先按SPACE BAR,再按ENTER。如果先按 Enter 则不起作用,您必须先按空格键,然后再按 Enter。如果这个选项没有出现,因为它没有出现在一个项目中,出现在另一个项目中,然后又没有出现在另一个项目中,那么下面的 10.B 肯定会首先出现。

      10.B- 您可能会在第二个(或第一个)Use an existing project 看到此选项,按 Enter 键

      11- 下一个选项是Select a default Firebase project for this directory,使用向上/向下箭头选择您的项目,按回车键

      12-下一个选项是What language would you like to use to write Cloud Functions?,两个选项是Javascript和typescript,我用上/下箭头选择Javascript然后回车

      13- 下一个选项是Do you want to use ESLint to catch probable bugs and enforce style? 我输入了n 并按下回车键。这些规则非常严格,如果事先不了解规则,在第 22 步部署时会出现一堆错误。如果您不知道它们,我建议您为 no

      选择 n

      14- 下一个选项是Do you want to install dependencies with npm now? 我输入了y 并按下回车键

      15- 完成安装后不能选择下一个,建议更新到最新版本并建议输入 $npm install -g firebase-tools。我一直收到错误,所以我跳过了这一步,因为它与第 2 步相同

      16- 仍在 Cloud_Functions 文件夹中时,运行 $ ls,您将看到一个 functions 文件夹。运行 $ cd functions,因为下一步必须在 functions 文件夹内进行。

      17- $ pwd 使您位于functions 文件夹中

      18- 下次运行 $npm i --save firebase-functions@latest

      19- 接下来运行 $open index.js 打开 index.js 文件。在 Sublime 中自动打开地雷

      20- 这是一个简单的youtube video,说明如何处理index.js 文件中的现有代码。或者您可以简单地取消注释所有代码,确保按保存(command+S),然后运行 ​​$firebase deploy --only functions。几分钟后,您应该会收到 Deploy complete! 声明

      21- 您可以注释掉示例代码,这里是从我的 iOS 应用程序中的视图控制器接收一些数据的代码(步骤 25)。通过控制台确保您的 Firebase 规则允许写入您正在写入的任何 ref,并确保设置了计费(步骤 1),因为它会在您首次部署时验证计费(步骤 22)。在index.js 文件中输入:

      const functions = require('firebase-functions');
      const admin = require('firebase-admin');
      admin.initializeApp();
      
      exports.updateSneakerTypeToPostsRef = functions.https.onCall((data, context) => {
      
          const postId = data.postId; // this will be abc123
          const userId = data.uid; // this will be whatever the user's id is
          const sneakerName = data.sneakerName; // this will be Adidas
          const receivedTimeStamp = Date.now(); // Data.now() is how you receive the timestamp in Javascript/Node 
      
          // this is just a print statement
          console.log("received values =" + " | postId: " + postId + " | userId: " + userId + " | sneakerName: " + sneakerName + " | timeStamp: " + receivedTimeStamp);
      
          // this is the database path: posts/postId/userId in step 25 and I'm going to add the *sneakerName:Adidas* and *receivedTimeStamp* to it
          var postsRef = admin.database().ref('/posts/' + postId + '/' + userId);
      
          return postsRef.set({ "sneakerName": sneakerName, "timeStamp": receivedTimeStamp })
          .catch((error) => {
              console.log('ERROR - updateSneakerTypeToPostsRef() Failed: ', error);
          });
      });
      

      22- 保存上面的文件,现在在终端输入下面,冒号后面的部分是第 21 步中exports. 函数的名称,它必须是完全相同的名称:

      $firebase deploy --only functions:updateSneakerTypeToPostsRef。这只会部署这个 1 函数。如果您有多个功能,请使用 $ firebase deploy --only functions 一次性全部部署它们

      如果您删除了初始示例代码,您将收到响应:在您的项目中找到以下函数,但在您的本地源代码中不存在:helloWorld(us-central...) 如果您正在重命名函数或更改其区域,建议您先创建新函数,然后再删除旧函数,以防止事件丢失。欲了解更多信息,请访问https://firebase.google.com/docs/functions/manage-functions#modify ?您要继续删除吗?选择 no 将继续其余部署。* 您可以输入 no 删除它或输入 yes 保留它,没关系。无论哪种方式,然后按 Enter。

      这需要大约 3 分钟才能完成,但当我收到 functions[updateSneakerTypeToPostsRef(us-central4)]: Successful create operation. Deploy complete! 时,我在下面输入此内容只是为了获取日志信息:

      $ firebase functions:log // 这是日志数据

      现在开始从 iOS 应用程序内部连接到云功能。

      23- cd 到实际的 Xcode 项目,打开你的 Podfile 并输入 pod 'Firebase/Functions' 然后安装它 $ pod install

      24- 安装后去任何视图控制器并将import Firebase 添加到文件顶部,然后将此行添加为类属性lazy var functions = Functions.functions()

      25- 下面是如何将数据发送到 index.js 文件中的函数(步骤 21)

      import Firebase
      lazy var functions = Functions.functions()
      
      @IBAction func buttonTapped(_sender : AnyObject){
      
          sendDataToCloudFunction()
      }
      
      func sendDataToCloudFunction() {
      
          let data: [String: Any] = ["postId": "abc123",
                                     "uid": Auth.auth().currentUser!.uid,
                                     "sneakerName": "Adidas"]
      
          let exportsName = "updateSneakerTypeToPostsRef" // *** this HAS TO BE the SAME exact function name from steps 21 and 22 ***
      
          functions.httpsCallable(exportsName).call(data) { (result, error) in
              print("Function returned")
              if let error = error as NSError? {
                  if error.domain == FunctionsErrorDomain {
                      let code = FunctionsErrorCode(rawValue: error.code)
                      let message = error.localizedDescription
                      let details = error.userInfo[FunctionsErrorDetailsKey]
                      print(code.debugDescription)
                      print(message.debugDescription)
                      print(details.debugDescription)
                  }
                  print(error.localizedDescription)
                  return
              }
                  
              if let res = result {
                  print("------->", res)
              }
                  
              if let operationResult = (result?.data as? [String: Any])?["operationResult"] as? Int {
                  print("\(operationResult)")
              }
          }
      }
      

      26- firebase 内的结果如下所示

      @posts
         @abc123
            @whatever_the_userId_is...
               -sneakerName: "Adidas"
               -timeStamp: 1595874879.9619331
      

      【讨论】:

        猜你喜欢
        • 2021-12-04
        • 2020-10-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-10
        • 2012-10-24
        • 1970-01-01
        • 1970-01-01
        • 2018-06-01
        相关资源
        最近更新 更多