Appearance
后端脚本
后端脚本在 ProCode 服务的 Jint JavaScript 引擎中执行。它用于实现接口、表单提交钩子、工作流节点逻辑、数据库访问、缓存和出站 HTTP 调用。
运行语法
平台把当前请求上下文注入为全局变量 App,并直接执行脚本正文:
javascript
var name = App.Request.Body && App.Request.Body.name;
App.Response.StatusCode = 200;
App.Response.Body = {
success: true,
name: name
};后端脚本遵循 Jint 支持的 JavaScript 语法,并具有以下约定:
- 直接编写脚本正文,不要添加
<script>标签。 - 不存在
window、document、localStorage或 Vue 组件。 - 平台服务通常以同步方法暴露,示例中无需
await。 - .NET 方法和属性区分大小写,使用文档中的 PascalCase 名称。
- 推荐使用
var、普通函数和明确的空值判断,以保持脚本兼容性。 - 结束前设置
App.Response.Body;需要提前终止时设置App.Stop = true。
Jint 额外为数组提供以下方法:
javascript
var first = rows.FirstOrDefault();
var required = rows.First();
var last = rows.LastOrDefault();First() 和 Last() 在空数组上会抛出异常;不确定是否有数据时使用 FirstOrDefault() 或先判断 length。
脚本事件
| 配置字段 | 触发时机 | 可用数据 |
|---|---|---|
接口 context | HTTP 或 MCP 调用接口时 | Request、Response、Services |
back_before_submit_command | 表单写入数据库之前 | App.Form.Data、Operation |
back_after_submit_command | 表单写入数据库之后 | App.Form.Data、RowId |
back_before_delete_command | 删除数据之前 | App.Form.Data、RowId |
back_after_delete_command | 删除完成之后 | App.Form |
back_after_fetch_command | 获取单条表单数据之后 | App.Form.Data |
back_start_command | 工作流节点开始时 | App.Workflow、App.Form |
back_end_command | 工作流节点结束时 | App.Workflow、App.Form |
App.Request 与 App.Response
读取请求
javascript
var routeId = App.Request.Routes.id;
var pageNumber = App.Request.Querys.page_number;
var authorization = App.Request.Headers.Authorization;
var body = App.Request.Body;| 成员 | 说明 |
|---|---|
Request.Routes | 路由占位符,例如 /order/{id} 中的 id |
Request.Querys | 查询字符串;属性名保留为 Querys |
Request.Headers | HTTP 请求头 |
Request.Body | 已反序列化的请求体 |
Response.StatusCode | HTTP 状态码,默认 200 |
Response.Body | 响应正文,平台会在执行后序列化 |
参数校验与提前返回
javascript
var body = App.Request.Body;
if (!body || !body.name) {
App.Response.StatusCode = 400;
App.Response.Body = {
success: false,
message: 'name 不能为空'
};
App.Stop = true;
return;
}设置 App.Stop = true 后,引擎停止后续处理;如果数据库服务处于事务中,当前实现会回滚事务。
App.Form 表单上下文
表单后端事件通过 App.Form 访问数据,字段键使用表字段的 mapname:
javascript
var data = App.Form.Data;
var operation = App.Form.Operation; // get / create / update / delete
var rowId = App.Form.RowId;
if (operation === 'create') {
data.f_status = 'draft';
data.f_creator_id = App.CurrentUserId;
}| 成员 | 说明 |
|---|---|
Form.Form | 当前表单元数据 |
Form.FormComponent | 当前组件元数据,部分事件中存在 |
Form.Data | 当前数据,可在写入前修改 |
Form.AfterData | 变更前数据,更新或删除时用于对比 |
Form.Operation | get、create、update 或 delete |
Form.RowId | 当前行 ID |
表单提交前校验示例:
javascript
var data = App.Form.Data;
if (!data.f_customer_name) {
App.Response.StatusCode = 400;
App.Response.Body = {
success: false,
message: '客户名称不能为空'
};
App.Stop = true;
return;
}
if (App.Form.Operation === 'update') {
var before = App.Form.AfterData || {};
App.Helpers.Logger.Info(
'状态变更: ' + String(before.f_status) + ' -> ' + String(data.f_status)
);
}DatabaseService 数据库语法
数据库对象位于 App.Services.DatabaseService。table 使用平台表的逻辑名称或 mapname,不要把用户输入拼接到 SQL 中。
Query 查询
javascript
var rows = App.Services.DatabaseService.Query({
table: 'crm_customer',
type: 0,
wheres: [
{
type: 0,
values: [
{
column: 'f_status',
type: 0,
value: 1,
value_source_type: 1,
value_type: 2
},
{
column: 'f_is_delete',
type: 0,
value: false,
value_source_type: 1,
value_type: 3
}
]
}
],
columns: ['f_id', 'f_name', 'f_status'],
descs: [
{ column: 'f_create_time', type: 1 }
],
page: {
page_size: 20,
page_number: 1
}
});QueryModel 结构:
| 字段 | 是否必填 | 说明 |
|---|---|---|
table | 是 | 表逻辑名称或 mapname |
type | 是 | 条件组逻辑:0 为 And,1 为 Or |
wheres | 否 | 条件组数组 |
joins | 否 | 关联表配置 |
columns | 否 | 要返回的列 |
descs | 否 | 排序;0 升序,1 降序 |
page | 否 | page_size 与 page_number,页码从 1 开始 |
条件 type:
| 值 | 含义 | 是否需要 value |
|---|---|---|
0 | 等于 | 是 |
1 | 不等于 | 是 |
2 | 包含 | 是 |
3 | 不包含 | 是 |
4 | 为空 | 否 |
5 | 非空 | 否 |
6 | 大于 | 是 |
7 | 大于等于 | 是 |
8 | 小于 | 是 |
9 | 小于等于 | 是 |
value_type:0 字符串、1 长整数、2 整数、3 布尔、4 数值、5 日期时间、6 枚举;数组类型为基础值加 10。
列与列比较时使用 value_source_type: 0 和 right_column:
javascript
var rows = App.Services.DatabaseService.Query({
table: 'crm_order',
type: 0,
wheres: [
{
type: 0,
values: [
{
column: 'f_paid_amount',
type: 8,
right_column: 'f_total_amount',
value_source_type: 0
}
]
}
]
});Create 新增
javascript
var created = App.Services.DatabaseService.Create({
table: 'crm_customer',
value: {
f_name: '示例客户',
f_status: 1,
f_creator_id: App.CurrentUserId,
f_create_time: new Date()
}
});Update 更新
优先按 ID 更新:
javascript
var updated = App.Services.DatabaseService.Update({
table: 'crm_customer',
id: App.Request.Routes.id,
value: {
f_status: 2,
f_update_time: new Date()
}
});也可以使用 wheres 批量更新,但必须明确限制范围:
javascript
App.Services.DatabaseService.Update({
table: 'crm_customer',
type: 0,
wheres: [
{
type: 0,
values: [
{ column: 'f_status', type: 0, value: 0, value_source_type: 1, value_type: 2 }
]
}
],
value: { f_status: 1 }
});Delete 删除
javascript
var deleted = App.Services.DatabaseService.Delete({
table: 'crm_customer',
type: 0,
wheres: [
{
type: 0,
values: [
{ column: 'f_id', type: 0, value: App.Request.Routes.id, value_source_type: 1, value_type: 1 }
]
}
]
});业务表优先使用软删除字段;直接删除前必须校验权限和关联数据。
参数化 SQL
表达式查询无法满足需求时才使用 SQL,并始终参数化:
javascript
var rows = App.Services.DatabaseService.Query(
'SELECT f_id, f_name FROM t_customer WHERE f_status = @status',
[
{ name: 'status', value: 1 }
],
false
);禁止把请求参数拼接到 SQL:
javascript
// 错误:存在 SQL 注入风险
// var sql = "SELECT * FROM t_customer WHERE f_name='" + name + "'";其他服务
出站 HTTP
javascript
var text = App.Services.HttpService.Post(
'https://api.example.com/v1/items',
{ name: '示例' },
{ Authorization: 'Bearer ' + App.Helpers.Env.Get('EXTERNAL_API_TOKEN') }
);
var result = App.Helpers.Json.Deserialize(text);缓存
javascript
var key = 'customer:' + String(App.Request.Routes.id);
var cached = App.Services.CacheService.Get(key);
if (cached == null) {
cached = App.Services.DatabaseService.Query({
table: 'crm_customer',
type: 0,
wheres: []
});
App.Services.CacheService.Set(key, cached, null);
}日志与工具
javascript
App.Helpers.Logger.Info('开始处理接口');
var payloadText = App.Helpers.Json.Serialize(App.Request.Body);
var requestId = App.Helpers.Id.GetGuid();
var userId = App.Helpers.Convert.ToInt64(App.CurrentUserId, 0);
var enabled = App.Helpers.Convert.ToBoolean(App.Request.Querys.enabled, false);常用服务还包括 StorageService、FormService、WorkflowService、UserService、DepartmentService、RoleService、MongoDbService 和 TimeSeriesDatabaseService。某项服务是否可用取决于当前站点配置,以代码编辑器类型提示和部署版本为准。
完整接口示例
下面的脚本校验参数、查询数据并构造统一响应:
javascript
try {
var id = App.Request.Routes.id;
if (!id) {
App.Response.StatusCode = 400;
App.Response.Body = { success: false, message: '缺少 id' };
App.Stop = true;
return;
}
var rows = App.Services.DatabaseService.Query({
table: 'crm_customer',
type: 0,
wheres: [
{
type: 0,
values: [
{ column: 'f_id', type: 0, value: id, value_source_type: 1, value_type: 1 },
{ column: 'f_is_delete', type: 0, value: false, value_source_type: 1, value_type: 3 }
]
}
],
page: { page_size: 1, page_number: 1 }
});
if (rows.length === 0) {
App.Response.StatusCode = 404;
App.Response.Body = { success: false, message: '数据不存在' };
App.Stop = true;
return;
}
App.Response.StatusCode = 200;
App.Response.Body = {
success: true,
data: rows[0]
};
} catch (e) {
App.IsException = true;
App.Helpers.Logger.Error('查询客户失败: ' + String(e));
App.Response.StatusCode = 500;
App.Response.Body = {
success: false,
message: '服务器处理失败'
};
}事务、错误与安全
- 脚本异常时引擎会标记
App.IsException,回滚活动事务并返回500。 - 使用
DatabaseService的事务能力时,确保成功路径提交、失败路径回滚;不要让连接跨脚本保存。 - 对所有路由、查询和请求体参数执行类型、长度与业务范围校验。
- 只返回业务需要的数据,不要把异常堆栈、连接字符串或密钥写入响应。
- 凭据应通过环境或安全配置读取,不要直接写在脚本中。
- 更新和删除必须带有限制条件;禁止无条件批量修改生产数据。
- 后端脚本没有
App.Services.InterfaceService,接口之间调用应使用HttpService或抽取公共服务。
API 全量参考
DatabaseService
查询与写入
| 方法签名 | 返回值 | 说明 |
|---|---|---|
Query(query) | Array<Record> | 使用当前数据库和 QueryModel 查询。 |
Query(databaseName, query) | Array<Record> | 在指定逻辑数据库执行表达式查询。 |
Query(sql) | Array<Record> | 在当前数据库执行无参数 SQL。 |
Query(sql, parameters?, enableTransaction?) | Array<Record> | 在当前数据库执行参数化 SQL。 |
Query(databaseName, sql, parameters, enableTransaction) | Array<Record> | 在指定数据库执行参数化 SQL。 |
Create(create) | Record | 在当前数据库新增一条数据;参数为 { table, value }。 |
Create(databaseName, create) | Record | 在指定数据库新增数据。 |
Update(update) | Record | Record[] | 按 id 或 wheres 更新当前数据库数据。 |
Update(databaseName, update) | Record | Record[] | 在指定数据库更新数据。 |
Delete(query) | boolean | 按 QueryModel 删除当前数据库数据。 |
Delete(databaseName, query) | boolean | 在指定数据库删除数据。 |
Execute(sql) | number | 执行无参数 SQL,返回影响行数。 |
Execute(sql, parameters?, enableTransaction?) | number | 执行参数化 SQL。 |
Execute(databaseName, sql, parameters, enableTransaction) | number | 在指定数据库执行参数化 SQL。 |
事务与辅助方法
| 方法签名 | 返回值 | 说明 |
|---|---|---|
Commit() | void | 提交当前活动事务。 |
Rollback() | void | 回滚当前活动事务。 |
IsTransaction() | boolean | 判断当前是否存在活动事务。 |
ToTree(items, key, parent, value, children?) | Record[] | 将平铺记录按主键和父键转换为树。 |
Close() | void | 关闭并释放数据库连接。 |
最小调用:
javascript
var rows = App.Services.DatabaseService.Query({
table: 'crm_customer',
type: 0,
wheres: [],
page: { page_size: 20, page_number: 1 }
});DatabaseSchemaService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
GetDatabases() | string[] | 获取可用逻辑数据库名称。 |
GetTablesByDatabase(database) | string[] | 获取指定物理库的表名。 |
GetTableByName(name) | string[] | 获取当前绑定数据库中指定表的列名。 |
GetColumnsByTableName(name) | BackendEngineColumn[] | 获取引擎元数据中的列定义。 |
UpsertTable(databaseId, table, columns) | Promise<boolean> | 写入或更新表与列元数据,并同步物理结构。 |
SchemaSyncEngineAsync(databaseId, tableName) | Promise<TableSchemaSyncOutcome> | 将物理库表结构同步到引擎元数据。 |
EngineSyncSchemaAsync(databaseId, tableName) | Promise<TableSchemaSyncOutcome> | 将引擎元数据同步到物理库。 |
同步结果:0 数据库不存在、1 表不存在、2 无列定义、3 成功。
HttpService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
Get(url, header?, cookie?) | string | 发送 GET 请求并返回响应文本。 |
Post(url, body, header?, cookie?) | string | 发送 POST 请求。 |
Put(url, body, header?, cookie?) | string | 发送 PUT 请求。 |
Delete(url, header?, cookie?) | string | 发送 DELETE 请求。 |
javascript
var text = App.Services.HttpService.Get(
'https://api.example.com/v1/status',
{ Accept: 'application/json' }
);
var data = App.Helpers.Json.Deserialize(text);CacheService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
Get(key) | T | 读取缓存;不存在时返回空值。 |
Set(key, value, duration?) | boolean | 写入缓存和可选有效期。 |
Remove(...keys) | void | 删除一个或多个缓存键。 |
AuthService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
Get(id) | { access_token, expires, token_type } | 获取指定认证配置对应的访问令牌。 |
StorageService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
DownloadFile(fileId, fileName?, expireHours?, queryParams?) | string | null | 生成文件下载地址。 |
DownloadThumbFile(fileId) | string | null | 生成缩略图下载地址。 |
FormService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
GetList(name?) | Array<Record> | 获取表单列表,可按名称过滤。 |
GetById(id) | Record | null | 获取表单定义。 |
Create(model) | Record | 创建表单。 |
Update(id, model) | Record | 更新表单。 |
Delete(id) | boolean | 删除表单。 |
WorkflowService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
Start(model) | Record | null | 启动工作流实例。 |
Approve(model) | Record | null | 审批当前流程节点。 |
TimeSeriesDatabaseService
该服务只有在 t_time_series_database 已配置连接时可用。
| 方法签名 | 返回值 | 说明 |
|---|---|---|
GetMapNames() | string[] | 获取已配置的时序库 map_name。 |
Query(mapName, sql, parameters?) | Array<Record> | 在指定时序库执行查询。 |
Insert(mapName, pointOrBatch) | void | 写入单条或一组测点。 |
InsertBatch(mapName, points) | number | 批量写入并返回条数。 |
Ping(mapName) | boolean | 探测连接是否可用。 |
IsConfigured 是只读属性,不是方法。
javascript
if (App.Services.TimeSeriesDatabaseService != null &&
App.Services.TimeSeriesDatabaseService.Ping('iot')) {
App.Services.TimeSeriesDatabaseService.Insert('iot', {
path: 'device_01',
timestamp: new Date().getTime(),
fields: { temperature: 23.6 }
});
}UserService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
GetCurrent() | User | null | 获取当前用户摘要。 |
GetById(userId) | User | null | 按 ID 获取用户。 |
GetByIds(userIds) | User[] | 批量获取用户。 |
List(keyword?, pageNumber?, pageSize?) | { items, total, page_number, page_size } | 分页查询用户。 |
GetByDepartment(departmentId?) | User[] | 获取部门成员;空值表示无部门,0 表示全部。 |
GetByRole(roleId) | User[] | 获取角色成员。 |
DepartmentService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
GetContext() | { departments, users } | 获取部门与用户组织上下文。 |
GetTree() | Department[] | 获取部门树。 |
GetById(departmentId) | Department | null | 获取指定部门。 |
GetByUser(userId?) | Department[] | 获取用户所属部门;不传表示当前用户。 |
RoleService
| 方法签名 | 返回值 | 说明 |
|---|---|---|
GetList() | Role[] | 获取角色列表。 |
GetById(roleId) | Role | null | 获取指定角色。 |
GetByUser(userId?) | Role[] | 获取用户角色;0 或不传表示当前用户。 |
Logger
| 方法签名 | 返回值 | 说明 |
|---|---|---|
App.Helpers.Logger.Debug(message) | void | 写入调试日志。 |
App.Helpers.Logger.Info(message) | void | 写入信息日志。 |
App.Helpers.Logger.Warn(message) | void | 写入警告日志。 |
App.Helpers.Logger.Error(message) | void | 写入错误日志。 |
Env 与 Json
| 方法签名 | 返回值 | 说明 |
|---|---|---|
App.Helpers.Env.Get(key) | string | null | 读取环境或平台配置。 |
App.Helpers.Env.GetApiBase() | string | 获取 API 基础地址。 |
App.Helpers.Json.Serialize(value) | string | 序列化为 JSON。 |
App.Helpers.Json.Deserialize(json) | T | undefined | 将 JSON 反序列化为对象。 |
Crypto
| 方法签名 | 返回值 | 说明 |
|---|---|---|
Md5(value) | string | 计算 MD5。 |
Md5Guid(value) | string | null | 生成 GUID 形态 MD5。 |
GenerateBearerToken(length?, key?) | string | 生成随机令牌。 |
Base64Encode(value) | string | null | Base64 编码。 |
Base64Decode(value) | string | null | Base64 解码。 |
AesEncrypt(input, iv, key) | string | AES 加密。 |
AesDecrypt(input, iv, key) | string | AES 解密。 |
GenerateAesKey(keySize?) | string | 生成 AES 密钥。 |
GenerateAesIv() | string | 生成 AES IV。 |
RsaEncrypt(input, publicKey) | string | RSA 公钥加密。 |
RsaDecrypt(input, privateKey) | string | RSA 私钥解密。 |
RsaSign(input, privateKey) | string | RSA 私钥签名。 |
RsaVerify(input, signature, publicKey) | boolean | RSA 公钥验签。 |
这些方法通过 App.Helpers.Crypto 调用。
Id 与 Validation
| 方法签名 | 返回值 | 说明 |
|---|---|---|
App.Helpers.Id.IsGuid(value) | boolean | 判断字符串是否为 GUID。 |
App.Helpers.Id.GetGuid() | string | 生成 GUID。 |
App.Helpers.Id.GetSnowflake() | number | 生成雪花 ID。 |
App.Helpers.Validation.IsEmail(value) | boolean | 校验邮箱格式。 |
Dictionary 与 List
| 方法签名 | 返回值 | 说明 |
|---|---|---|
Dictionary.AddOrSet(dict, key, value) | Record | 新增或覆盖字典项。 |
Dictionary.Remove(dict, key) | Record | 删除字典项。 |
List.AddOrSet(array, value) | Array | 向数组增加值,已有时按实现更新。 |
List.AddOrSet(array, object, name) | Array | 按字段名增加或更新对象。 |
List.GetList(objects, name) | Array | 从对象数组提取指定字段。 |
List.Remove(array, value) | Array | 从数组移除值。 |
List.ContainsKey(array, value) | boolean | 判断数组是否包含值。 |
以上方法分别通过 App.Helpers.Dictionary 与 App.Helpers.List 调用。
Convert
| 方法签名 | 返回值 | 说明 |
|---|---|---|
ToString(value) | string | null | 转为可空字符串。 |
ToStringOrEmpty(value) | string | 转为字符串,空值返回空字符串。 |
ToInt32(value, defaultValue?) | number | 转为 32 位整数。 |
ToInt64(value, defaultValue?) | number | 转为 64 位整数。 |
ToDouble(value, defaultValue?) | number | 转为双精度数值。 |
ToDecimal(value, defaultValue?) | number | 转为十进制数值。 |
ToBoolean(value, defaultValue?) | boolean | 转为布尔值。 |
ToDateTime(value, defaultValue?) | Date | 转为日期时间。 |
ToNullableDateTime(value) | Date | null | 转为可空日期时间。 |
ToGuidString(value) | string | null | 转为 GUID 字符串。 |
To(value, defaultValue?) | T | null | 转为指定泛型类型。 |
JsonTo(value) | T | null | 通过 JSON 转换对象类型。 |
ChangeType(value, typeName, defaultValue?) | any | 按 .NET 类型名转换。 |
以上方法通过 App.Helpers.Convert 调用。
IO
| 方法签名 | 返回值 | 说明 |
|---|---|---|
App.Helpers.IO.SelectFile(options?) | Promise<File[]> | 请求宿主选择文件;是否可用取决于运行环境。 |
App.Helpers.IO.SelectFolder() | Promise<File[]> | 请求宿主选择文件夹;是否可用取决于运行环境。 |
App 控制属性
下列是属性而非方法,但会直接影响脚本执行:
| 属性 | 类型 | 说明 |
|---|---|---|
App.Debug | boolean | 是否启用调试模式。 |
App.CurrentUserId | string | 当前用户 ID。 |
App.Trigger | object | null | 当前触发器。 |
App.Param | any | 扩展参数。 |
App.IsException | boolean | 是否已发生异常。 |
App.IsAsync | boolean | 是否异步执行。 |
App.Stop | boolean | 停止后续执行并按当前 Response 返回。 |
未形成稳定契约的服务
SMSService、EmailService、InboundEmailService、QrCodeService、JPushService、MongoDbService 与 InboxMessageService 已存在于 ProCode 服务容器,但当前脚本编辑器未公开稳定的方法签名,因此不把其内部方法作为可兼容的脚本 API。后续一旦进入公开类型定义,应同步补充到本页。
常见问题
DatabaseService 查不到表
table / 列使用 map_name;多库须传数据源 map_name。见 关系数据库。
时序库怎么写
用 TimeSeriesDatabaseService,先判 IsConfigured,再传路由名。见 时序数据库。
异常后接口仍返回 200
设置 App.IsException = true 并指定 App.Response.StatusCode;必要时 App.Stop = true。
