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); // '用户不存在'
}