从您的应用中调用函数


借助 Cloud Functions for Firebase 客户端 SDK,您可以直接从 Firebase 应用调用函数。如需以这种方式从应用调用函数,请在 Cloud Functions 中编写和部署 HTTP Callable 函数,然后添加从应用调用该函数的客户端逻辑。

请务必注意,HTTP Callable 函数与 HTTP 函数类似,但并不完全相同。如需使用 HTTP Callable 函数,您必须将适用于您平台的客户端 SDK 与后端 API 搭配使用(或实现相关协议)。Callable 函数与 HTTP 函数存在以下重要区别:

  • 使用 Callable 函数时,Firebase Authentication 令牌、FCM 令牌和 App Check 令牌(如果可用)会自动包含在请求中。
  • 触发器会自动对请求正文进行反序列化,并验证身份验证令牌。

Firebase SDK for Cloud Functions 第 2 代及更高版本可与下列 Firebase 客户端 SDK 版本及更高版本交互,以支持 HTTPS Callable 函数:

  • Firebase SDK 11.5.0(Apple 平台)
  • Firebase SDK 21.1.0(Android 平台)
  • Firebase Modular Web SDK v. 9.7.0

如果您要在不受支持的平台上构建的应用中添加类似功能,请参阅 https.onCall 协议规范。本指南的其余部分提供了有关如何编写、部署和调用面向 Apple、Android、Web、C++ 和 Unity 平台的 HTTP Callable 函数的说明。

编写和部署 Callable 函数

使用 functions.https.onCall 方法创建 HTTPS Callable 函数。此方法可接受两个参数:data 和可选的 context

  // Saves a message to the Firebase Realtime Database but sanitizes the
  // text by removing swearwords.
  exports.addMessage = functions.https.onCall((data, context) => {
    // ...
  });
  

例如,对于将文本消息保存到 Realtime Database 的 Callable 函数,可在 data 中包含消息文本,而用 context 参数表示用户身份验证信息:

// Message text passed from the client.
const text = request.data.text;
// Authentication / user information is automatically added to the request.
const uid = request.auth.uid;
const name = request.auth.token.name || null;
const picture = request.auth.token.picture || null;
const email = request.auth.token.email || null;

Callable 函数的位置与调用客户端位置之间的距离可能会造成网络延迟。为了优化性能,请考虑在适用时指定函数位置,并确保将 Callable 函数的位置与您在客户端初始化 SDK 时设置的位置保持一致。

(可选)您可以附加 App Check 证明,帮助保护您的后端资源免遭滥用(例如账单欺诈或钓鱼式攻击)。请参阅Cloud Functions 启用 App Check 强制执行

返回结果

如需向客户端发回数据,请返回可以进行 JSON 编码的数据。例如,如需返回加法运算的结果:

// returning result.
return {
  firstNumber: firstNumber,
  secondNumber: secondNumber,
  operator: "+",
  operationResult: firstNumber + secondNumber,
};

如需在异步操作之后返回数据,请返回一个 Promise。Promise 返回的数据将发回给客户端。例如,您可以返回经过净化 (sanitize) 的文本(由 Callable 函数写入 Realtime Database):

// Saving the new message to the Realtime Database.
const sanitizedMessage = sanitizer.sanitizeText(text); // Sanitize message.

return getDatabase().ref("/messages").push({
  text: sanitizedMessage,
  author: {uid, name, picture, email},
}).then(() => {
  logger.info("New Message written");
  // Returning the sanitized message to the client.
  return {text: sanitizedMessage};
})

处理错误

为了确保客户端获得有用的错误详情,可以抛出一个 functions.https.HttpsError 实例(或返回一个被拒的 Promise 并包括此实例),通过 Callable 函数返回错误。此类错误有一个 code 属性,该属性可以是 functions.https.HttpsError 中列出的某个值。这些错误还有一个字符串 message,默认为空字符串。它们可能还有一个含任意值的可选 details 字段。如果您的函数抛出了一个 HttpsError 以外的错误,您的客户端会收到包含消息 INTERNAL 和代码 internal 的错误。

例如,函数可以抛出数据验证和身份验证错误,并将错误消息返回给调用客户端:

// Checking attribute.
if (!(typeof text === "string") || text.length === 0) {
  // Throwing an HttpsError so that the client gets the error details.
  throw new HttpsError("invalid-argument", "The function must be called " +
          "with one arguments \"text\" containing the message text to add.");
}
// Checking that the user is authenticated.
if (!request.auth) {
  // Throwing an HttpsError so that the client gets the error details.
  throw new HttpsError("failed-precondition", "The function must be " +
          "called while authenticated.");
}

部署 Callable 函数

当您在 index.js 内保存已完成的 Callable 函数后,系统会在您运行 firebase deploy 时部署该函数以及其他所有函数。如需只部署 Callable 函数,请按如下所示的方式使用 --only 参数执行部分部署

firebase deploy --only functions:addMessage

如果在部署函数时遇到权限错误,请确保已将适当的 IAM 角色分配给运行部署命令的用户。

设置您的客户端开发环境

请确保您满足所有前提条件,并将必要的依赖项和客户端库添加到您的应用。

iOS+

按照相关说明将 Firebase 添加到您的 Apple 应用

使用 Swift Package Manager 安装和管理 Firebase 依赖项。

  1. 在 Xcode 中打开您的应用项目,依次点击 File(文件)> Add Packages(添加软件包)
  2. 出现提示时,添加 Firebase Apple 平台 SDK 代码库:
  3.   https://github.com/firebase/firebase-ios-sdk.git
  4. 选择 Cloud Functions 库。
  5. -ObjC 标志添加到目标 build 设置的“其他链接器标志”部分。
  6. 完成之后,Xcode 将会自动开始在后台解析和下载您的依赖项。

Web

  1. 按照相关说明将 Firebase 添加到您的 Web 应用。请务必在终端运行以下命令:
    npm install firebase@11.0.2 --save
  2. 需要手动导入 Firebase 核心和 Cloud Functions

     import { initializeApp } from 'firebase/app';
     import { getFunctions } from 'firebase/functions';
    
     const app = initializeApp({
         projectId: '### CLOUD FUNCTIONS PROJECT ID ###',
         apiKey: '### FIREBASE API KEY ###',
         authDomain: '### FIREBASE AUTH DOMAIN ###',
       });
     const functions = getFunctions(app);

Web

  1. 按照相关说明将 Firebase 添加到您的 Web 应用
  2. 将 Firebase 核心和 Cloud Functions 客户端库添加到您的应用:
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-functions.js"></script>

Cloud Functions SDK 也可以作为 npm 软件包提供。

  1. 从终端运行以下命令:
    npm install firebase@8.10.1 --save
  2. 需要手动导入 Firebase 核心和 Cloud Functions
    const firebase = require("firebase");
    // Required for side-effects
    require("firebase/functions");

Kotlin+KTX

  1. 按照相关说明将 Firebase 添加到您的 Android 应用

  2. 模块(应用级)Gradle 文件(通常是 <project>/<app-module>/build.gradle.kts<project>/<app-module>/build.gradle)中,添加 Cloud Functions 库的依赖项。我们建议使用 Firebase Android BoM 来实现库版本控制。

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.6.0"))
    
        // Add the dependency for the Cloud Functions library
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-functions")
    }

    借助 Firebase Android BoM,可确保您的应用使用的始终是 Firebase Android 库的兼容版本。

    (替代方法) 在不使用 BoM 的情况下添加 Firebase 库依赖项

    如果您选择不使用 Firebase BoM,则必须在每个 Firebase 库的依赖项行中指定相应的库版本。

    请注意,如果您在应用中使用多个 Firebase 库,我们强烈建议您使用 BoM 来管理库版本,从而确保所有版本都兼容。

    dependencies {
        // Add the dependency for the Cloud Functions library
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-functions:21.1.0")
    }
    是否想要查找 Kotlin 专用的库模块?2023 年 10 月 (Firebase BoM 32.5.0) 开始,Kotlin 和 Java 开发者可以依赖于主库模块(如需了解详情,请参阅关于此计划的常见问题解答)。

Java

  1. 按照相关说明将 Firebase 添加到您的 Android 应用

  2. 模块(应用级)Gradle 文件(通常是 <project>/<app-module>/build.gradle.kts<project>/<app-module>/build.gradle)中,添加 Cloud Functions 库的依赖项。我们建议使用 Firebase Android BoM 来实现库版本控制。

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.6.0"))
    
        // Add the dependency for the Cloud Functions library
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-functions")
    }

    借助 Firebase Android BoM,可确保您的应用使用的始终是 Firebase Android 库的兼容版本。

    (替代方法) 在不使用 BoM 的情况下添加 Firebase 库依赖项

    如果您选择不使用 Firebase BoM,则必须在每个 Firebase 库的依赖项行中指定相应的库版本。

    请注意,如果您在应用中使用多个 Firebase 库,我们强烈建议您使用 BoM 来管理库版本,从而确保所有版本都兼容。

    dependencies {
        // Add the dependency for the Cloud Functions library
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-functions:21.1.0")
    }
    是否想要查找 Kotlin 专用的库模块?2023 年 10 月 (Firebase BoM 32.5.0) 开始,Kotlin 和 Java 开发者可以依赖于主库模块(如需了解详情,请参阅关于此计划的常见问题解答)。

Dart

  1. 按照相关说明将 Firebase 添加到您的 Flutter 应用

  2. 从 Flutter 项目的根目录运行以下命令,安装该插件:

    flutter pub add cloud_functions
    
  3. 完成后,重新构建您的 Flutter 应用:

    flutter run
    
  4. 安装后,您可以通过在 Dart 代码中导入 cloud_functions 插件加以使用:

    import 'package:cloud_functions/cloud_functions.dart';
    

C++

对于 Android 平台的 C++ 项目

  1. 按照相关说明将 Firebase 添加到您的 C++ 项目
  2. firebase_functions 库添加到您的 CMakeLists.txt 文件中。

对于 Apple 平台的 C++ 项目

  1. 按照相关说明将 Firebase 添加到您的 C++ 项目
  2. Cloud Functions Pod 添加到您的 Podfile
    pod 'Firebase/Functions'
  3. 保存文件,然后运行:
    pod install
  4. Firebase C++ SDK 中的 Firebase 核心和 Cloud Functions 框架添加到您的 Xcode 项目。
    • firebase.framework
    • firebase_functions.framework

Unity

  1. 按照相关说明将 Firebase 添加到您的 Unity 项目
  2. Firebase Unity SDK 中的 FirebaseFunctions.unitypackage 添加到您的 Unity 项目中。

初始化客户端 SDK

初始化 Cloud Functions 的实例:

Swift

lazy var functions = Functions.functions()

Objective-C

@property(strong, nonatomic) FIRFunctions *functions;
// ...
self.functions = [FIRFunctions functions];

Web

firebase.initializeApp({
  apiKey: '### FIREBASE API KEY ###',
  authDomain: '### FIREBASE AUTH DOMAIN ###',
  projectId: '### CLOUD FUNCTIONS PROJECT ID ###'
  databaseURL: 'https://### YOUR DATABASE NAME ###.firebaseio.com',
});

// Initialize Cloud Functions through Firebase
var functions = firebase.functions();

Web

const app = initializeApp({
  projectId: '### CLOUD FUNCTIONS PROJECT ID ###',
  apiKey: '### FIREBASE API KEY ###',
  authDomain: '### FIREBASE AUTH DOMAIN ###',
});
const functions = getFunctions(app);

Kotlin+KTX

private lateinit var functions: FirebaseFunctions
// ...
functions = Firebase.functions

Java

private FirebaseFunctions mFunctions;
// ...
mFunctions = FirebaseFunctions.getInstance();

Dart

final functions = FirebaseFunctions.instance;

C++

firebase::functions::Functions* functions;
// ...
functions = firebase::functions::Functions::GetInstance(app);

Unity

functions = Firebase.Functions.DefaultInstance;

调用函数

Swift

functions.httpsCallable("addMessage").call(["text": inputField.text]) { result, error in
  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]
    }
    // ...
  }
  if let data = result?.data as? [String: Any], let text = data["text"] as? String {
    self.resultField.text = text
  }
}

Objective-C

[[_functions HTTPSCallableWithName:@"addMessage"] callWithObject:@{@"text": _inputField.text}
                                                      completion:^(FIRHTTPSCallableResult * _Nullable result, NSError * _Nullable error) {
  if (error) {
    if ([error.domain isEqual:@"com.firebase.functions"]) {
      FIRFunctionsErrorCode code = error.code;
      NSString *message = error.localizedDescription;
      NSObject *details = error.userInfo[@"details"];
    }
    // ...
  }
  self->_resultField.text = result.data[@"text"];
}];

Web

var addMessage = firebase.functions().httpsCallable('addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    var sanitizedMessage = result.data.text;
  });

Web

import { getFunctions, httpsCallable } from "firebase/functions";

const functions = getFunctions();
const addMessage = httpsCallable(functions, 'addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const sanitizedMessage = data.text;
  });

Kotlin+KTX

private fun addMessage(text: String): Task<String> {
    // Create the arguments to the callable function.
    val data = hashMapOf(
        "text" to text,
        "push" to true,
    )

    return functions
        .getHttpsCallable("addMessage")
        .call(data)
        .continueWith { task ->
            // This continuation runs on either success or failure, but if the task
            // has failed then result will throw an Exception which will be
            // propagated down.
            val result = task.result?.data as String
            result
        }
}

Java

private Task<String> addMessage(String text) {
    // Create the arguments to the callable function.
    Map<String, Object> data = new HashMap<>();
    data.put("text", text);
    data.put("push", true);

    return mFunctions
            .getHttpsCallable("addMessage")
            .call(data)
            .continueWith(new Continuation<HttpsCallableResult, String>() {
                @Override
                public String then(@NonNull Task<HttpsCallableResult> task) throws Exception {
                    // This continuation runs on either success or failure, but if the task
                    // has failed then getResult() will throw an Exception which will be
                    // propagated down.
                    String result = (String) task.getResult().getData();
                    return result;
                }
            });
}

Dart

    final result = await FirebaseFunctions.instance.httpsCallable('addMessage').call(
      {
        "text": text,
        "push": true,
      },
    );
    _response = result.data as String;

C++

firebase::Future<firebase::functions::HttpsCallableResult> AddMessage(
    const std::string& text) {
  // Create the arguments to the callable function.
  firebase::Variant data = firebase::Variant::EmptyMap();
  data.map()["text"] = firebase::Variant(text);
  data.map()["push"] = true;

  // Call the function and add a callback for the result.
  firebase::functions::HttpsCallableReference doSomething =
      functions->GetHttpsCallable("addMessage");
  return doSomething.Call(data);
}

Unity

private Task<string> addMessage(string text) {
  // Create the arguments to the callable function.
  var data = new Dictionary<string, object>();
  data["text"] = text;
  data["push"] = true;

  // Call the function and extract the operation from the result.
  var function = functions.GetHttpsCallable("addMessage");
  return function.CallAsync(data).ContinueWith((task) => {
    return (string) task.Result.Data;
  });
}

在客户端上处理错误

如果服务器抛出错误或者生成的 Promise 被拒绝,客户端就会收到错误。

如果函数返回的错误类型为 function.https.HttpsError,客户端会从该服务器错误中收到错误的 codemessagedetails。如果返回该类型以外的错误,错误会包含消息 INTERNAL 和代码 INTERNAL。请参阅有关如何处理 Callable 函数中的错误的指南。

Swift

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]
  }
  // ...
}

Objective-C

if (error) {
  if ([error.domain isEqual:@"com.firebase.functions"]) {
    FIRFunctionsErrorCode code = error.code;
    NSString *message = error.localizedDescription;
    NSObject *details = error.userInfo[@"details"];
  }
  // ...
}

Web

var addMessage = firebase.functions().httpsCallable('addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    var sanitizedMessage = result.data.text;
  })
  .catch((error) => {
    // Getting the Error details.
    var code = error.code;
    var message = error.message;
    var details = error.details;
    // ...
  });

Web

import { getFunctions, httpsCallable } from "firebase/functions";

const functions = getFunctions();
const addMessage = httpsCallable(functions, 'addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const sanitizedMessage = data.text;
  })
  .catch((error) => {
    // Getting the Error details.
    const code = error.code;
    const message = error.message;
    const details = error.details;
    // ...
  });

Kotlin+KTX

addMessage(inputMessage)
    .addOnCompleteListener { task ->
        if (!task.isSuccessful) {
            val e = task.exception
            if (e is FirebaseFunctionsException) {
                val code = e.code
                val details = e.details
            }
        }
    }

Java

addMessage(inputMessage)
        .addOnCompleteListener(new OnCompleteListener<String>() {
            @Override
            public void onComplete(@NonNull Task<String> task) {
                if (!task.isSuccessful()) {
                    Exception e = task.getException();
                    if (e instanceof FirebaseFunctionsException) {
                        FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;
                        FirebaseFunctionsException.Code code = ffe.getCode();
                        Object details = ffe.getDetails();
                    }
                }
            }
        });

Dart

try {
  final result =
      await FirebaseFunctions.instance.httpsCallable('addMessage').call();
} on FirebaseFunctionsException catch (error) {
  print(error.code);
  print(error.details);
  print(error.message);
}

C++

void OnAddMessageCallback(
    const firebase::Future<firebase::functions::HttpsCallableResult>& future) {
  if (future.error() != firebase::functions::kErrorNone) {
    // Function error code, will be kErrorInternal if the failure was not
    // handled properly in the function call.
    auto code = static_cast<firebase::functions::Error>(future.error());

    // Display the error in the UI.
    DisplayError(code, future.error_message());
    return;
  }

  const firebase::functions::HttpsCallableResult* result = future.result();
  firebase::Variant data = result->data();
  // This will assert if the result returned from the function wasn't a string.
  std::string message = data.string_value();
  // Display the result in the UI.
  DisplayResult(message);
}

// ...

// ...
  auto future = AddMessage(message);
  future.OnCompletion(OnAddMessageCallback);
  // ...

Unity

 addMessage(text).ContinueWith((task) => {
  if (task.IsFaulted) {
    foreach (var inner in task.Exception.InnerExceptions) {
      if (inner is FunctionsException) {
        var e = (FunctionsException) inner;
        // Function error code, will be INTERNAL if the failure
        // was not handled properly in the function call.
        var code = e.ErrorCode;
        var message = e.ErrorMessage;
      }
    }
  } else {
    string result = task.Result;
  }
});

在发布应用之前,您应启用 App Check,以便确保只有您的应用可以访问您的 Callable 函数端点。