Skip to content

Backend Function:把业务逻辑放在它该在的地方——后端

企业级系统开发中,有些事情必须由后端来做:敏感数据的校验、权限控制、复杂业务逻辑、多表事务处理。前端可以做基础校验,但不能保证安全;SQL 可以查询数据,但很难实现复杂业务规则。

Backend Function 的价值,就是让开发者能够在后端执行自定义业务逻辑,同时保持和前端 SDK 的无缝协作。前端只需要调用接口,不需要关心后端的实现细节;后端通过 Backend Function 统一处理业务规则,确保数据安全和一致性。

💡 Backend Function 是 Lovrabet 平台的业务逻辑扩展层。它让标准的数据操作(增删改查)可以无限扩展,满足企业特有的业务需求,同时保持代码的清晰和可维护性。

图片展示了Backend Function的工作流程,前端/SDK提交表单、列表查询、业务操作调用,经Backend Function处理…

为什么需要 Backend Function?

企业在构建业务系统时,经常遇到三类问题:

问题类型痛点Backend Function 如何解决
数据安全手机号唯一性校验、身份证号脱敏、权限控制——前端可以绕过,数据库约束不够灵活提供先验函数和后验函数,在数据操作前后自动执行,业务规则无法被绕过
复杂业务逻辑价格计算、库存扣减、订单状态流转——涉及多表操作、事务控制、复杂规则判断支持完整的 JavaScript/TypeScript 编程能力,灵活实现任何业务逻辑
性能与可维护性关联多张表的查询、批量处理、异步执行提供独立端点(ENDPOINT),让复杂逻辑在后端高效执行,前端只需调用一个接口

Backend Function 是什么?

Backend Function 是 Lovrabet 平台的后端业务逻辑扩展能力。它允许开发者编写 JavaScript/TypeScript 代码,在 Lovrabet 服务端环境中执行,通过 context.client 访问数据集 API 和 SQL 执行能力。

从技术上看,Backend Function 是运行在 Lovrabet 服务端的 JavaScript 函数,具有以下特点:

  • 服务端执行:代码在 Lovrabet 服务器上运行,安全可靠
  • 完整编程能力:支持 JavaScript/TypeScript,可以编写复杂业务逻辑
  • 数据库访问:通过 context.client.models 访问数据集,通过 context.client.sql 执行 SQL
  • 用户上下文:通过 context.userInfo 获取当前登录用户信息
  • 三种类型:先验函数(Before)、后验函数(After)、独立端点(ENDPOINT)

它的意义不只是"能写后端代码",而是让业务逻辑可以在合适的位置统一管理。前端专注交互和展示,后端专注业务规则和数据处理,两者之间通过清晰的 API 接口协作——这正是企业系统能够长期演进的关键。

Backend Function 由什么组成?

如果把 Backend Function 拆开来看,它由三种类型的函数组成,分别解决不同场景的问题。

三种核心类型

1. 先验函数(Before 脚本 / RequestInception)

在数据操作执行前自动触发,用于校验和拦截。

适用场景:

  • 数据唯一性校验:手机号、邮箱、身份证号等
  • 业务规则验证:年龄限制、金额限制、库存检查等
  • 权限控制:多租户隔离、角色权限、数据归属检查
  • 自动补充数据:设置所属部门、租户ID、创建人等

触发时机createupdatedeletefilter 等操作前

🚫 阻止能力:抛出异常即阻止操作执行,返回错误信息给前端

JavaScript
/**
 * 客户创建先验函数 - 手机号唯一性校验
 */
export default async function beforeCreate(params, context) {
  const TABLES = {
    customers: "dataset_XXXXXXXXXX", // 客户数据集
  };
  const models = context.client.models;

  // 校验手机号唯一性
  if (params.phone) {
    const existing = await models[TABLES.customers].filter({
      where: { phone: { $eq: params.phone } },
    });
    if (existing.tableData && existing.tableData.length > 0) {
      throw new Error("手机号已被使用,请使用其他手机号");
    }
  }
  return params;
}

2. 后验函数(After 脚本 / HOOK 后置脚本)

在数据操作执行后、返回给前端之前自动触发,用于数据处理。

适用场景:

  • 敏感数据脱敏:手机号、身份证号、银行卡号等
  • 动态计算字段:从生日计算年龄、从消费金额计算等级
  • 数据格式转换:日期格式化、状态码转显示名称
  • 关联数据补充:自动查询并填充关联表信息

触发时机filtergetOnecreateupdate 等操作后

✏️ 修改能力:可以修改返回的数据结构,前端拿到的是处理后的数据

JavaScript
/**
 * 客户查询后验函数 - 数据脱敏
 */
export default async function afterFilter(params, context) {
  if (params.tableData) {
    params.tableData = params.tableData.map((customer) => {
      // 手机号脱敏:138****8000
      if (customer.phone) {
        customer.phone = customer.phone
          .replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
      }
      // 计算年龄
      if (customer.birthday) {
        const today = new Date();
        const birthDate = new Date(customer.birthday);
        customer.age = today.getFullYear() - birthDate.getFullYear();
      }
      return customer;
    });
  }
  return params;
}

3. 独立端点(ENDPOINT)

独立的 API 端点,前端通过 BFF API 主动调用,用于复杂业务逻辑。

适用场景:

  • 复杂业务流程:订单处理(库存扣减 + 价格计算 + 优惠券应用)
  • 批量操作:批量导入、批量更新、批量删除
  • 聚合统计:复杂报表、多维度分析
  • 第三方集成:调用外部 API、处理回调

📞 调用方式client.bff.execute({ scriptName: "xxx", params: {...} })

💻 编程能力:完整的 JavaScript/TypeScript 编程,支持循环、条件、异常处理等

JavaScript
/**
 * 订单处理 - Backend Function 独立端点
 */
export default async function processOrder(params, context) {
  const TABLES = {
    orders: "dataset_XXXXXXXXXX",
    products: "dataset_YYYYYYYYYY",
  };
  const models = context.client.models;
  const { items, couponCode } = params;

  // 1. 检查库存
  for (const item of items) {
    const product = await models[TABLES.products].getOne(item.productId);
    if (product.stock < item.quantity) {
      throw new Error(`产品 ${product.name} 库存不足`);
    }
  }

  // 2. 计算价格
  let totalPrice = 0;
  for (const item of items) {
    const product = await models[TABLES.products].getOne(item.productId);
    totalPrice += product.price * item.quantity;
  }

  // 3. 应用优惠券
  if (couponCode) {
    const discount = await calculateDiscount(couponCode, totalPrice);
    totalPrice -= discount;
  }

  // 4. 创建订单
  const orderId = await models[TABLES.orders].create({
    items: JSON.stringify(items),
    totalPrice,
    status: "pending",
    createTime: new Date().toISOString(),
  });

  // 5. 扣减库存
  for (const item of items) {
    const product = await models[TABLES.products].getOne(item.productId);
    await models[TABLES.products].update(item.productId, {
      stock: product.stock - item.quantity,
    });
  }

  return { success: true, orderId, totalPrice };
}

核心 API

数据集操作

JavaScript
const models = context.client.models;

// 查询单条
const record = await models[TABLES.customers].getOne(123);

// 查询列表
const result = await models[TABLES.customers].filter({
  where: { status: { $eq: "active" } },
  select: ["id", "name", "phone"],
  orderBy: [{ createTime: "desc" }],
  pageSize: 20,
});

// 创建
const id = await models[TABLES.customers].create({
  name: "张三", phone: "13800138000",
});

// 更新
await models[TABLES.customers].update(123, { status: "inactive" });

// 删除
await models[TABLES.customers].delete(123);

SQL 执行

JavaScript
// Backend Function 中 SQL 直接返回数组
const rows = await context.client.sql.execute({
  sqlCode: "getUserStats",
  params: { userId: "123" },
});
console.log(rows[0].orderCount);

用户信息

JavaScript
const user = context.userInfo;
console.log(user.id);           // 用户ID
console.log(user.username);     // 用户名
console.log(user.tenantCode);   // 租户编码
console.log(user.department);   // 部门

为什么它能让开发更简单?

传统模式下,实现一个"创建订单"功能需要:

步骤传统方式使用 Backend Function
前端基础校验(数量 > 0)基础校验 + 调用 BFF API
后端 API接收请求不需要单独写 API
后端逻辑检查库存、计算价格、应用优惠券、创建订单、扣减库存Backend Function 统一处理
数据库执行多条 SQL,保证事务一致性通过 SDK 操作,自动保证事务

核心优势:

  • 🎯 代码集中:业务逻辑统一在后端,前端不需要关心实现细节
  • 🔒 安全可控:敏感操作必须经过 Backend Function,无法绕过
  • 🔧 易于维护:业务规则变更只需修改 Backend Function,前端无需改动
  • 性能优化:批量查询、缓存、异步处理等优化手段可以灵活使用

实际效果:

  • 开发效率提升 2-3 倍:后端逻辑统一管理,避免前后端反复沟通
  • 代码复用率提升 60%:相同的业务逻辑可以在多个接口中复用
  • 维护成本降低 50%:业务规则集中在后端,修改不影响前端

典型应用场景

场景一:多租户数据隔离

企业内部有多个租户(子公司、事业部),每个租户只能看到自己的数据。

JavaScript
export default async function beforeFilter(params, context) {
  const userTenantId = context.userInfo?.tenantCode;
  if (!userTenantId) {
    throw new Error("用户未分配租户,无法操作");
  }
  if (!params.where) { params.where = {}; }
  params.where.tenant_id = { $eq: userTenantId };
  return params;
}

效果:前端查询时自动过滤,无需手动添加租户条件,确保数据安全。

场景二:敏感数据脱敏

客户列表需要显示手机号,但不能暴露完整号码。

JavaScript
export default async function afterFilter(params, context) {
  if (params.tableData) {
    params.tableData = params.tableData.map((customer) => {
      if (customer.phone) {
        customer.phone = customer.phone
          .replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
      }
      return customer;
    });
  }
  return params;
}

效果:前端拿到的数据已经是脱敏后的,无需额外处理,确保数据安全。

场景三:复杂订单处理

创建订单需要检查库存、计算价格、应用优惠券、扣减库存。

JavaScript
export default async function processOrder(params, context) {
  const TABLES = {
    orders: "dataset_XXXXXXXXXX",
    products: "dataset_YYYYYYYYYY",
  };
  const models = context.client.models;
  // 1. 检查库存 → 2. 计算价格 → 3. 应用优惠券 → 4. 创建订单 → 5. 扣减库存
  return { success: true, orderId, totalPrice };
}

前端调用:

TypeScript
const result = await client.bff.execute({
  scriptName: "processOrder",
  params: {
    items: [{ productId: "p1", quantity: 2 }],
    couponCode: "SUMMER2024",
  },
});
console.log(result.orderId); // 订单ID

效果:前端只需调用一个接口,后端统一处理所有业务逻辑,确保数据一致性。

写在最后

今天的企业数字化,业务逻辑越来越复杂。单纯的 CRUD 操作已经不能满足需求,企业需要的是一套能够在后端灵活实现业务规则的能力体系。

Backend Function 所做的,就是让业务逻辑可以放在它该在的地方——后端。它帮助企业把通用能力标准化(先验函数、后验函数),把复杂业务逻辑模块化(独立端点),让前端和后端可以各司其职,通过清晰的 API 接口协作——这正是企业系统能够长期演进的关键。

如何开始

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