Backend Function 编写指南
本文档旨在指导开发者编写适用于 lovrabet-runtime 平台的动态脚本。这些脚本用于实现数据权限过滤、动态脱敏、数据增强以及复杂的独立业务逻辑。
1. 核心约定 (Core Conventions)
为了支持高性能的异步操作(如数据库访问),所有业务脚本必须严格遵循以下函数签名规范。
1.1 入口函数签名
所有脚本必须导出一个 异步函数 (Async Function),并接收两个固定参数:params 和 context。
JavaScript
/**
* 标准入口函数
* @param {Object} params - 业务数据对象(如请求参数、查询结果),可原地修改。
* @param {Object} context - 执行上下文,包含用户信息、应用信息和数据库操作能力。
* @returns {Promise<Object>} - 返回修改后的 params 对象(对于 HOOK 脚本)或业务结果(对于 ENDPOINT 脚本)。
*/
export default async function functionName(params, context) {
// 业务逻辑...
return params;
}1.2 参数详解
params (业务数据)
- Before 阶段 (前置脚本): 对应 API 的请求参数(
requestBody)。例如查询条件、表单提交数据。 - After 阶段 (后置脚本): 对应 API 的返回结果(
responseBody.data)。例如查询出的列表数据。 - Endpoint 脚本: 对应 HTTP 请求体中的 JSON 数据。
context (执行上下文)
提供脚本运行所需的辅助信息和能力:
| 属性名 | 类型 | 说明 |
|---|---|---|
context.userInfo | Object | 当前登录用户信息(如 id, username, tenantCode)。 |
context.appCode | String | 当前应用编码。 |
context.tenantCode | String | 当前租户编码。 |
context.client | Object | 数据库操作入口。提供 models 方法以访问数据集。 |
2. 脚本类型与示例 (Script Types & Examples)
2.1 HOOK 脚本 (Before/After)
HOOK 脚本依附于标准数据接口(如 getList, create),用于拦截和修改数据。作用域在 http 接口层。
示例 1: 列表查询前置 - 权限过滤 (Before)
场景: 强制只允许查询当前用户创建的数据。
JavaScript
export default async function before(params, context) {
// params 是 API 请求参数(如查询条件)
// 强制注入过滤条件:create_by 必须等于当前用户 ID
params.create_by = context.userInfo.id;
// 必须返回修改后的 params
return params;
}示例 2: 根据用户角色过滤-权限过滤(before)
场景: 非管理员用户只能看到自己创建的数据。
JavaScript
export default async function(params, context) {
// 非管理员只能看自己的数据
if (context.userInfo.role !== "admin") {
params.created_by = context.userInfo.id;
}
return params;
}示例 3: 数据创建前置 - 业务校验 (Before)
场景: 校验订单金额不能超过限制。
JavaScript
export default async function before(params, context) {
// params 是提交的表单数据
if (params.amount > 500) {
throw new Error("无法创建超过 500 元的订单");;
}
// 补充默认值
params.source = "WEB_APP";
return params;
}示例 4: 自动填充字段 (Before)
JavaScript
export default async function(params, context) {
params.created_by = context.userInfo.id;
params.created_at = new Date().toISOString();
params.tenant_code = context.tenantCode;
return params;
}示例 5: 数据脱敏 (After)
场景: 对返回列表中的手机号进行脱敏。
JavaScript
export default async function after(params, context) {
// params 对应 response.data (通常包含 tableData 列表)
const list = params.tableData;
if (list && list.length > 0) {
list.forEach(record => {
if (record.phone) {
// 正则替换
record.phone = record.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
});
}
return params;
}示例 6: 动态追加计算字段 (After)
JavaScript
export default async function(params, context) {
// params 为接口返回的数据
params.tableData?.forEach(record => {
record.total_amount = record.price * record.quantity;
record.is_vip = record.order_count > 10;
});
// 追加列定义(前端自动展示)
const extraColumns = [
{ dataIndex: "total_amount", title: "总金额", type: "NUMBER" },
{ dataIndex: "is_vip", title: "VIP客户", type: "BOOLEAN" }
];
extraColumns.forEach(col => params.tableColumns?.push(col));
return params;
}示例 7:数据权限过滤 (After)
JavaScript
export default async function(params, context) {
// params 为接口返回的数据
if (context.userInfo.role !== "admin") {
params.tableColumns = params.tableColumns?.filter(col =>
!["salary", "commission", "bank_account"].includes(col.dataIndex)
);
params.tableData?.forEach(record => {
delete record.salary;
delete record.commission;
delete record.bank_account;
});
}
return params;
}示例8 :执行自定义SQL
执行自定义SQL的案例:BF支持自定义SQL示例
2.2 ENDPOINT 脚本 (独立端点)
INFO
需要独立部署的场景下:。。。。
独立端点脚本是一种特殊的 JS 脚本,其类型为 ENDPOINT。
- API 路径:
/api/``endpoint/``{appCode}/{scriptName} - 请求方式:
POST - 请求体: JSON 对象,将直接作为
params传递给脚本。
示例 1: 复杂业务逻辑 (库存校验与下单)
这是一个独立端点脚本,模拟“下单”事务。
JavaScript
export default async function createOrder(params, context) {
// 1. 校验库存
const product = await context.client.models.dataset_XXXXXXXXXX.getOne({
id: params.productId
});
if (!product || product.stock < params.quantity) {
throw new Error(`库存不足,当前库存: ${product ? product.stock : 0}`);
}
// 2. 计算价格 (根据用户等级打折)
const user = await context.client.models.dataset_XXXXXXXXXX.getOne({
id: context.userInfo.id
});
const discount = (user.vip_level >= 3) ? 0.8 : 1.0;
const finalPrice = product.price * params.quantity * discount;
// 3. 创建订单
const orderId = await context.client.models.dataset_XXXXXXXXXX.create({
user_id: context.userInfo.id,
product_id: params.productId,
quantity: params.quantity,
amount: finalPrice,
status: "pending",
create_time: new Date()
});
// 4. 扣减库存 (更新操作)
await context.client.models.dataset_XXXXXXXXXX.update({
id: product.id,
stock: product.stock - params.quantity
});
return {
success: true,
orderId: orderId,
payAmount: finalPrice,
discountRate: discount
};
}3. 数据集模型操作能力 (Dataset Model Access)
通过 context.client.models,脚本可以安全、异步地访问应用内的数据集。支持 await 语法。
3.1 基础 API
所有操作均基于数据集编码 (datasetCode)。api 依赖于java 应用内部的接口实现。
JavaScript
// 获取数据集访问器
const ds = context.client.models.dataset_XXXXXXXXXX // "XXXXXXXXXX" 是数据集编码| 方法 | 说明 | 参数示例 | 返回值 |
getOne(params) | 查询单条数据 | { id: 1 } | 对象 Object |
getList(params) | 查询列表(分页) | { page: 1, size: 20 } | 列表 List |
filter(params) | 高级过滤查询 | {
} | 列表 List |
create(data) | 创建数据 | { name: "John", age: 20 } | 无 |
update(data) | 更新数据 | { id: 1, name: "New Name" } | 成功(true)/失败(false) |
delete(params) | 删除数据 | { id: 1 } | 成功(true)/失败(false) |
3.2 关联查询示例 (Join-like)
在查询订单列表后,根据 user_id 查询用户详情并拼接到订单数据中。
JavaScript
export default async function before(params, context) {
const list = params.tableData;
// 遍历列表(注意:为了性能,建议使用 Promise.all 并行查询)
for (let i = 0; i < list.length; i++) {
const order = list[i];
if (order.user_id) {
// 异步查询用户信息
const user = await context.client.models.dataset_XXXXXXXXXX.getOne({
id: order.user_id
});
// 数据增强
if (user) {
order.user_name = user.username;
order.user_level = user.vip_level;
}
}
}
return params;
}4. 注意事项 (Best Practices)
- 返回值: 务必在函数末尾
return params(对于 HOOK)或业务结果对象(对于 ENDPOINT)。如果忘记返回,后续流程可能会收到null数据。 - 异步等待: 所有数据库操作 (
context.client.models) 都是异步的,必须使用await等待结果,否则脚本会继续执行而无法拿到数据。 - 异常处理: 脚本中抛出的
Error会被系统捕获并返回给前端,状态码通常为 500 (Failure)。请确保错误信息对用户友好。 - 限流: 为了防止死循环,单个脚本执行过程中允许的数据库调用次数有限制(默认 50 次)。超出限制会抛出异常。
5. 脚本生效与缓存机制 (Caching)
为了保证高性能,系统对动态脚本实施了多级缓存机制。在修改数据库中的脚本内容后,请注意以下生效逻辑:
5.1 缓存级别
- 元数据缓存 (Metadata Cache):系统切面会缓存脚本的查询结果(包括脚本代码),缓存有效期为 5 分钟。这意味着在数据库修改脚本后,最长可能需要 5 分钟才会触发更新。
- 编译缓存 (Execution Cache):脚本引擎会缓存已编译的 JavaScript 对象以提高执行效率,该缓存遵循“最近最少使用”原则(LRU),默认有效期为 10 分钟。
5.2 调试建议
- 开发测试:在开发环境下,如果您希望修改立即生效,建议临时重启应用或通过管理接口清理缓存。
- 发布生产:脚本发布到生产环境后,请预留至少 5-10 分钟的观察窗口,确保所有集群节点的缓存都已刷新。