You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript编写Firebase可调用函数入参类型校验最佳实践

问题根因

TypeScript的interface、type定义仅在编译阶段生效,代码编译为JavaScript后所有类型标注会被完全擦除,运行时根本不存在questionnaireDoc这个值,自然无法通过instanceof做类型判断。
客户端侧的HttpsCallable<parameterType, returnType>仅能约束本地TypeScript代码的编译检查,对实际运行时传入的参数没有任何校验能力:非TS客户端、恶意构造的请求、客户端逻辑bug传错参数,都会直接透传到云函数逻辑中,因此服务端侧的运行时校验是强制要求,不能省略。
你目前用switch判断字段是否为undefined的方案有两个明显缺陷:一是只能判断字段存在性,无法校验字段类型、格式是否符合要求(比如本该传数字的字段传了字符串,会直接绕过校验);二是字段多了之后维护成本极高,新增字段很容易漏加校验。

最佳实践:使用Schema-first的运行时校验库

目前TypeScript生态下最适配这类场景的方案是使用「Schema定义同时生成TS类型」的运行时校验库,一次定义就能同时拿到编译时类型提示和运行时校验能力,不需要重复写类型和校验逻辑,社区最常用的是Zod,和Firebase云函数适配成本极低。

具体实现

  1. 安装依赖
    npm install zod
    
  2. 定义校验Schema,自动推导TS类型
    替换原来手动写的questionnaireDoc接口,用Zod定义字段规则,通过内置的infer方法直接导出对应TS类型,后续改字段规则只需要改Schema一处:
    import { z } from "zod";
    import { https } from "firebase-functions"; // 按你实际使用的v1/v2版本调整导入路径
    
    // 在这里定义每个字段的校验规则:类型、是否必填、格式要求、取值范围等
    const QuestionnaireDocSchema = z.object({
      property1: z.string().min(1, "property1不能为空字符串"),
      property2: z.number().int().positive("property2必须为正整数"),
      property3: z.boolean(),
      property4: z.string().optional(), // 可选字段
      // property5 ~ property9按实际业务规则补充即可
    });
    
    // 自动推导TS类型,和你之前手动写的interface效果完全一致
    type QuestionnaireDoc = z.infer<typeof QuestionnaireDocSchema>;
    
    // 返回值类型也可以用同样的方式定义
    const ApiOutSchema = z.object({
      code: z.number(),
      msg: z.string()
    });
    type ApiOut = z.infer<typeof ApiOutSchema>;
    
  3. 在云函数入口统一做入参校验
    用Schema的safeParse方法校验入参,校验失败直接抛出Firebase规范的invalid-argument错误,校验通过后拿到的参数TS会自动识别为正确类型,不需要额外写类型断言:
    export const questionnaireSubmit = https.onCall(async (data): Promise<ApiOut> => {
      const parseResult = QuestionnaireDocSchema.safeParse(data);
      if (!parseResult.success) {
        // 可以把具体的校验错误信息返回给客户端,方便定位问题
        throw new https.HttpsError(
          "invalid-argument",
          "请求参数不合法",
          parseResult.error.issues
        );
      }
    
      // 后续业务逻辑直接用parseResult.data即可,类型完全匹配
      const { property1, property2 } = parseResult.data;
    
      // 正常写业务逻辑
      return {
        code: 0,
        msg: "提交成功"
      };
    });
    

方案优势

  • 没有重复代码:Schema是唯一数据源,修改字段规则后TS类型自动同步,不会出现类型定义和校验逻辑不一致的问题
  • 校验能力完整:不止能判断字段是否存在,还能校验字段类型、格式(比如邮箱、手机号正则)、取值范围、枚举值、嵌套对象/数组结构等
  • 可扩展性强:不管有多少字段,只需要在Schema里加对应规则即可,不会出现冗余的重复判断代码
  • 错误信息清晰:校验失败时会返回具体哪个字段、因为什么规则不通过,比笼统的“缺少必填字段”更容易排查问题

轻量替代方案(无额外依赖)

如果场景非常简单,不想引入第三方依赖,可以自己写TS类型守卫实现校验,适合字段数量少、规则简单的场景:

function isQuestionnaireDoc(data: unknown): data is QuestionnaireDoc {
  if (typeof data !== "object" || data === null) return false;
  const record = data as Record<string, unknown>;
  return (
    typeof record.property1 === "string" &&
    typeof record.property2 === "number" &&
    typeof record.property3 === "boolean"
    // 补充其余字段的校验规则
  );
}

// 函数内使用
if (!isQuestionnaireDoc(data)) {
  throw new https.HttpsError("invalid-argument", "请求参数不合法");
}

注意不要用as类型断言跳过校验,也不要只判断字段存在性忽略类型校验,否则类型不匹配的问题会留到业务逻辑运行时才触发,排查成本极高。

内容的提问来源于stack exchange,提问作者Matthew Keller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 23:48:22