Skip to content

BF common functions

INFO

Notes on common BFs

  • Give common BFs a consistent prefix (for example, "public") to distinguish them from regular BFs and make them easy for both humans and AI to recognize;
  • Changing the input or output contract of a common function breaks every BF that references it. If you truly need to change it, create a V2 version of the BF and migrate callers gradually;

Quick start

1. Create a common function

In script management, create a script with the type COMMON:

JavaScript
// 脚本类型:COMMON
// 函数名称:getUserInfo

export default async function getUserInfo(params, context) {
    const { userId } = params;

    const user = await context.client.models.user.findOne({ id: userId });

    if (!user) {
        throw new Error('用户不存在');
    }

    return {
        id: user.id,
        name: user.name,
        email: user.email
    };

2. Call a common function

Call it from other scripts via bff.execute:

JavaScript
export default async function test(params, context) {
    // 调用公共函数
    const userInfo = await context.client.bff.execute({
        scriptName: 'getUserInfo',  // 公共函数的名称
        params: { userId: params.userId }
    });

    return userInfo;
}

Calling conventions

1. Basic syntax

JavaScript
const result = await context.client.bff.execute({
    scriptName: 'functionName',  // 必填:公共函数名称
    params: { ... }              // 可选:传递的参数
});

2. Parameters

JavaScript

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `scriptName` | string || 公共函数的名称(即 COMMON 脚本的 functionName) |
| `params` | object || 传递给公共函数的参数,默认为 `{}` |

3. Return value

JavaScript
返回公共函数的执行结果。

Examples

Example 1: Generate an order number (simple utility function)

JavaScript
// 脚本类型:COMMON
// 函数名称:generateOrderNo

export default async function generateOrderNo(params, context) {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0');
  const day = String(now.getDate()).padStart(2, '0');
  const hour = String(now.getHours()).padStart(2, '0');
  const minute = String(now.getMinutes()).padStart(2, '0');
  const second = String(now.getSeconds()).padStart(2, '0');
  const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');

  return `ORD${year}${month}${day}${hour}${minute}${second}${random}`;
}

//调用方
export default async function(params, context) {
    // 生成订单编号
    const orderNo = await context.client.bff.execute({
        scriptName: 'generateOrderNo',
        params: {}
    });

    console.log('生成的订单编号:', orderNo);  // 例如:ORD20260204153045123

    return { orderNo };
}

Example 2: Order creation with a transaction (complex business logic)

JavaScript
/ 脚本类型:COMMON
// 函数名称:createOrderWithTransaction
export default async function createOrderWithTransaction(params, context) {
  console.log('[COMMON] createOrderWithTransaction 收到参数: ' + JSON.stringify(params));

  const { userId, items, remark, orderNo } = params;

  // 参数验证
  if (!userId) {
    throw new Error('userId 参数必填');
  }

  if (!items || !Array.isArray(items) || items.length === 0) {
    throw new Error('items 参数必填且不能为空数组');
  }

  if (!orderNo) {
    throw new Error('orderNo 参数必填');
  }

  console.log('[DEBUG] orderNo: ' + orderNo + ', typeof: ' + (typeof orderNo));
  // 开启事务
  return await context.client.db.transaction(async (tx) => {
    console.log('=== 开始创建订单 ===');
    console.log(`订单编号: ${orderNo}`);
    console.log(`用户ID: ${userId}`);
    console.log(`订单项: ${JSON.stringify(items)}`);

    // 1. 查询并验证商品信息
    let totalAmount = 0;
    const itemDetails = [];
    const productStockMap = new Map(); // 保存商品库存信息,用于后续扣减

    for (const item of items) {
      // 查询商品信息
      const product = await tx.models.dataset_2175853f58f34c36a1007ab3506727d5.findOne({
        id: item.productId,
        status: 1
      });

      if (!product) {
        throw new Error(`商品不存在或已下架: ${item.productId}`);
      }

      // 验证库存
      if (product.stock < item.quantity) {
        throw new Error(`商品库存不足: ${product.name},当前库存: ${product.stock},需要: ${item.quantity}`);
      }

      // 保存商品当前库存,用于后续扣减
      productStockMap.set(item.productId, {
        currentStock: product.stock,
        quantity: item.quantity,
        productName: product.name
      });

      // 计算小计
      const subtotal = product.price * item.quantity;
      totalAmount += subtotal;

      itemDetails.push({
        productId: item.productId,
        productName: product.name,
        price: product.price,
        quantity: item.quantity,
        subtotal: subtotal
      });
    }

    console.log('订单总金额:', totalAmount);

    // 2. 创建订单记录
    const orderId = await tx.models.dataset_4332c83cabdd4d9dab66c39f6d2bc730.create({
      order_no: orderNo,
      user_id: userId,
      total_amount: totalAmount,
      status: 0,
      remark: remark || ''
    });

    console.log('订单创建成功, ID:', orderId);

    // 3. 创建订单明细
    const orderItems = [];
    for (const itemDetail of itemDetails) {
      const itemId = await tx.models.dataset_54cb75b37b834f62ac2c032b903d8ccd.create({
        order_id: orderId,
        product_id: itemDetail.productId,
        product_name: itemDetail.productName,
        price: itemDetail.price,
        quantity: itemDetail.quantity,
        subtotal: itemDetail.subtotal
      });

      orderItems.push({
        id: itemId,
        ...itemDetail
      });
    }

    console.log('订单明细创建成功, 数量:', orderItems.length);

    // 4. 扣减商品库存
    console.log('开始扣减库存...');

    for (const item of items) {
      const stockInfo = productStockMap.get(item.productId);

      if (!stockInfo) {
        throw new Error(`未找到商品库存信息: ${item.productId}`);
      }

      // 计算新库存
      const newStock = stockInfo.currentStock - stockInfo.quantity;

      console.log(
        `扣减库存: 商品ID=${item.productId}, ` +
        `商品名=${stockInfo.productName}, ` +
        `当前库存=${stockInfo.currentStock}, ` +
        `扣减数量=${stockInfo.quantity}, ` +
        `新库存=${newStock}`
      );

      // 更新库存(注意:update 方法只接收一个参数,id 和更新字段在同一个对象中)
      await tx.models.dataset_2175853f58f34c36a1007ab3506727d5.update({
        id: item.productId,
        stock: newStock
      });

      console.log(`✓ 商品 ${item.productId} 库存扣减成功`);
    }

    console.log('库存扣减成功');
    console.log('=== 订单创建完成 ===');

    return {
      success: true,
      order: {
        id: orderId,
        orderNo: orderNo,
        userId: userId,
        totalAmount: totalAmount,
        status: 0,
        remark: remark || ''
      },
      orderItems: orderItems,
      message: '订单创建成功'
    };
  });
}

Example 3: Compose multiple common functions

INFO

ENDPOINT script: testCreateOrderSuccess

JavaScript
// 脚本类型:ENDPOINT
// 函数名称:testCreateOrderSuccess

export default async function testCreateOrderSuccess(params, context) {
  console.log('=== 测试:成功创建订单 ===');

  // 1. 调用公共函数生成订单编号
  const orderNo = await context.client.bff.execute({
    scriptName: 'generateOrderNo',
    params: {}
  });
  console.log('[ENDPOINT] 生成订单编号: ' + orderNo);

  // 2. 调用公共函数创建订单(传入 orderNo)
  const result = await context.client.bff.execute({
    scriptName: 'createOrderWithTransaction',
    params: {
      ...params,
      orderNo: orderNo
    }
  });

  return {
    test: 'create_order_success',
    success: result.success,
    orderId: result.order.id,
    orderNo: result.order.orderNo,
    totalAmount: result.order.totalAmount,
    itemCount: result.orderItems.length,
    items: result.orderItems
  };
}

Pitfalls

scriptName must be exact

JavaScript
// ❌ 错误:scriptName 拼写错误
await context.client.bff.execute({
    scriptName: 'getUserinfo',  // 应该是 'getUserInfo'
    params: { userId: 1 }
});
// 报错:[BFF] Common function not found: 'getUserinfo'

// ✅ 正确:scriptName 与公共函数名称完全一致
await context.client.bff.execute({
    scriptName: 'getUserInfo',
    params: { userId: 1 }
});

2. Always use await

JavaScript
const result = context.client.bff.execute({
    scriptName: 'getUserInfo',
    params: { userId: 1 }
});
console.log(result);  // 输出 Promise 对象,不是结果

// ✅ 正确:使用 await
const result = await context.client.bff.execute({
    scriptName: 'getUserInfo',
    params: { userId: 1 }
});
console.log(result);  // 输出实际结果

3. params is deep-copied

INFO

The common function can never modify your params

JavaScript
const myParams = { userId: 1, data: { name: 'test' } };

await context.client.bff.execute({
    scriptName: 'updateUser',
    params: myParams
});

// myParams 不会被公共函数修改
console.log(myParams.data.name);  // 仍然是 'test'

4. Avoid circular calls

INFO

Common functions must not call other common functions

JavaScript
// ❌ 危险:A 调用 B,B 又调用 A
// functionA.js
export default async function(params, context) {
    return await context.client.bff.execute({
        scriptName: 'functionB',
        params
    });
}

// functionB.js
export default async function(params, context) {
    return await context.client.bff.execute({
        scriptName: 'functionA',  // 循环调用!
        params
    });
}

5. Exceptions propagate up

JavaScript
// 公共函数抛出的异常会传播到调用方
try {
    await context.client.bff.execute({
        scriptName: 'validateUser',
        params: { userId: 999 }
    });
} catch (error) {
    console.log(error.message);  // '用户不存在'
}

基于飞书知识库同步生成,内容以飞书源文档为准