如何在Firebase第二代可调用函数中调用其他可调用函数并完善代码
在Firebase第二代可调用函数中调用另一个可调用函数的两种方案
方案一:直接复用核心逻辑(推荐)
这种方式无需跨函数网络请求,性能更高且代码易维护。我们可以把getMetadata的核心功能抽离为独立函数,让两个可调用函数共享该逻辑。
步骤1:重构getMetadata.ts,抽离核心函数
import * as functions from "firebase-functions"; import metascraper from "metascraper"; import metascraperTitle from "metascraper-title"; import metascraperDescription from "metascraper-description"; // 抽离独立的元数据获取函数,供其他模块复用 export async function getUrlMetadata(url: string) { try { const res = await fetch(url); const html = await res.text(); const metadata = await metascraper([ metascraperTitle(), metascraperDescription(), ])({ html, url }); return { title: metadata.title, description: metadata.description, }; } catch (error) { functions.logger.error("获取网页元数据失败:", error); throw new functions.https.HttpsError("internal", "无法获取网页元数据"); } } // 原可调用函数,调用抽离的核心函数 export const getMetadata = functions.https.onCall(async (data, context) => { // 验证输入有效性 if (!data.url || typeof data.url !== "string") { throw new functions.https.HttpsError("invalid-argument", "必须提供有效的URL"); } return getUrlMetadata(data.url); });
步骤2:完善saveBookmark.ts,调用核心函数
import * as functions from "firebase-functions"; import * as admin from "firebase-admin"; // 导入抽离的核心函数 import { getUrlMetadata } from "./getMetadata"; // 初始化Firebase Admin(全局仅需初始化一次) if (!admin.apps.length) { admin.initializeApp(); } export const saveBookmark = functions.https.onCall(async (data, context) => { // 1. 验证用户身份(如需登录才能保存书签) if (!context.auth) { throw new functions.https.HttpsError("unauthenticated", "用户未登录,无法保存书签"); } // 2. 验证输入参数 if (!data.url || typeof data.url !== "string") { throw new functions.https.HttpsError("invalid-argument", "必须提供有效的URL"); } try { // 3. 获取网页元数据 const metadata = await getUrlMetadata(data.url); if (!metadata.title) { throw new functions.https.HttpsError("internal", "无法提取网页标题"); } // 4. 将书签保存到Firestore const bookmarkRef = await admin.firestore().collection("bookmarks").add({ url: data.url, title: metadata.title, description: metadata.description || "", // 兼容描述为空的情况 createdAt: admin.firestore.FieldValue.serverTimestamp(), // 使用Firestore服务器时间更可靠 userId: context.auth.uid, // 关联用户ID,方便后续查询管理 }); return { success: true, bookmarkId: bookmarkRef.id, metadata, }; } catch (error) { functions.logger.error("保存书签失败:", error); // 传递原始HTTPS错误,否则统一抛出内部错误 if (error instanceof functions.https.HttpsError) { throw error; } throw new functions.https.HttpsError("internal", "保存书签失败,请稍后重试"); } });
方案二:通过HTTP调用可调用函数端点
如果两个函数不在同一项目,或必须通过可调用函数的公开接口调用,可使用此方式,需处理认证和请求格式。
完善后的saveBookmark.ts代码
import * as functions from "firebase-functions"; import * as admin from "firebase-admin"; import { GoogleAuth } from "google-auth-library"; // 初始化Firebase Admin if (!admin.apps.length) { admin.initializeApp(); } const auth = new GoogleAuth(); export const saveBookmark = functions.https.onCall(async (data, context) => { // 验证用户身份 if (!context.auth) { throw new functions.https.HttpsError("unauthenticated", "用户未登录,无法保存书签"); } // 验证输入参数 if (!data.url || typeof data.url !== "string") { throw new functions.https.HttpsError("invalid-argument", "必须提供有效的URL"); } try { // 构造目标可调用函数的URL(推荐通过环境变量配置,避免硬编码) const functionRegion = process.env.FUNCTION_REGION || "us-central1"; const projectId = process.env.GCP_PROJECT; const getMetadataUrl = `https://${functionRegion}-${projectId}.cloudfunctions.net/getMetadata`; // 获取服务账号ID Token,用于认证调用可调用函数 const client = await auth.getIdTokenClient(getMetadataUrl); const idToken = await client.idTokenProvider.fetchIdToken(getMetadataUrl); // 发送请求到可调用函数 const response = await fetch(getMetadataUrl, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${idToken}`, }, body: JSON.stringify({ data: { url: data.url }, }), }); // 处理响应错误 if (!response.ok) { const errorData = await response.json(); throw new functions.https.HttpsError(errorData.error.status, errorData.error.message); } const result = await response.json(); const metadata = result.data; if (!metadata.title) { throw new functions.https.HttpsError("internal", "无法提取网页标题"); } // 保存书签到Firestore const bookmarkRef = await admin.firestore().collection("bookmarks").add({ url: data.url, title: metadata.title, description: metadata.description || "", createdAt: admin.firestore.FieldValue.serverTimestamp(), userId: context.auth.uid, }); return { success: true, bookmarkId: bookmarkRef.id, metadata, }; } catch (error) { functions.logger.error("保存书签失败:", error); if (error instanceof functions.https.HttpsError) { throw error; } throw new functions.https.HttpsError("internal", "保存书签失败,请稍后重试"); } });
注意事项
- 需要安装依赖:
npm install google-auth-library - 确保函数的服务账号拥有调用目标可调用函数的权限
- 推荐通过环境变量配置函数区域和项目ID,避免硬编码
内容的提问来源于stack exchange,提问作者Julien Reszka
相关产品推荐
相关产品推荐

