Skip to main content

BLOGE 0.9.8-RC1 在线版 · 事实校验 2026-09-15 · English

附录 A —— 内置函数

bloge DSL 中所有的 guard、constraint 与表达式共享同一组内置函数。本附录是这套工 具箱的唯一参考:每个函数名、每个签名、每个陷阱都在这里。当你开始写真实的 DSL, 就把它收藏起来。

什么时候需要这份参考

问题章节
coalesce / ifNull / isNull 各自做什么?第 5 节:Null 与默认值
when: 守卫里能用正则吗?第 1 节:字符串函数
怎么对列表字段求和或求平均?第 2 节:数学与聚合
怎么读嵌套 map 里的值?第 4 节:Map 与对象
怎么格式化时间戳或计算时长?第 7 节:日期/时间
怎么签名或哈希一段载荷?可选模块:bloge-functions-crypto
怎么判断某个函数能否在编译期使用?纯函数与编译期求值

按任务查函数

我要做什么先查哪里
清洗、拆分、匹配或格式化文本字符串函数
汇总、比较、取整或聚合数值数学与聚合
过滤、映射、展平或检查列表集合函数
安全读取嵌套对象或 mapMap/对象与 null 处理
在 schema 校验前转换类型类型转换
计算 deadline 或 duration日期/时间函数
创建摘要或签名可选 crypto 模块

第 5 章 (会分支的图) 在 when: guard 里引入了这些函数。第 7 章 (设计良好的算子) 在 算子的 constraintsusageExample 中使用它们。第 15 章 (状态机) 在转换 guard 中使用它们。在 bloge DSL 中只要见到表达式,能调用的就是本附录列出的这套函数。

函数调用语法

所有函数都使用标准调用语法:

when: concat("Hello, ", ctx.name) == "Hello, Alice"
when: contains(split(ctx.tags, ","), "premium")
when: padLeft(toString(ctx.id), 10, "0")

bloge 没有 Unix 风格的管道操作符 (|)。| 在词法分析中保留给逻辑或 (||)。 要组合多个函数,就把调用嵌套起来。

Null 处理约定

核心内置函数默认 null 安全。传 null 时要么:

  • 返回 null (safe-nav 语义);要么
  • 返回一个合理的默认值 —— size(null) → 0isEmpty(null) → trueconcat(null, "x") → "x" 等等。

你可以依赖这个约定,少写很多防御性判断。可选的加密模块是唯一的例外,参见下文。

纯函数与编译期求值

绝大多数函数是纯函数 —— 相同输入总是产生相同输出,没有副作用。当参数是常量 时,DSL 编译器允许在编译期折叠这些纯函数调用。

少数函数是非纯函数,不可折叠:

  • now() —— 当前时间戳,来自引擎的 time source
  • today() —— 当前日期,来自引擎的 time source
  • uuid() —— 随机 UUID
  • secret(name) —— 密钥查询 (加密模块)

在严格的"编译期专属"位置 (常量、schema constraint),编译器会拒绝非纯函数调用。把 它们留到运行期的 guard 里使用。


1. 字符串函数 (17)

函数签名返回说明
concatconcat(arg1, arg2, …)String把所有参数按字符串拼接。null 视为空串。
substringsubstring(str, start [, end])Stringstart (含) 到 end (不含);自动钳制越界。
uppercase / upperuppercase(str)String转大写。
lowercase / lowerlowercase(str)String转小写。
trimtrim(str)String去掉首尾空白。
replacereplace(str, oldStr, newStr)String把所有字面量 oldStr 替换为 newStr
startsWithstartsWith(str, prefix)Boolean前缀判断;遇 null 返回 false
endsWithendsWith(str, suffix)Boolean后缀判断;遇 null 返回 false
indexOfindexOf(str, target)Number第一次出现的下标,找不到返回 -1
length / lenlength(str)Number字符串长度;列表、map 同样适用;null 返回 0
matchesmatches(str, regex)Boolean正则匹配;null 或正则错误返回 false (不抛异常)。
replaceAllreplaceAll(str, regex, replacement)String正则全量替换;null 或正则错误返回原值。
splitsplit(str, delimiter)List按字面分隔符 (非正则) 切分;null 返回空列表。
joinjoin(list, delimiter)String用分隔符拼接列表元素;null 元素变空串。
padLeftpadLeft(str, length, padChar)String用单字符左填充到指定长度。
padRightpadRight(str, length, padChar)String用单字符右填充到指定长度。
when: startsWith(ctx.email, "admin@") and contains(ctx.email, "@")
when: matches(ctx.phone, "^\\+?[0-9 ]{6,}$")
when: padLeft(toString(ctx.orderId), 8, "0") == "00012345"

2. 数学与聚合 (10)

函数签名返回说明
absabs(n)Number绝对值。
minmin(a, b)Number两数最小值。
maxmax(a, b)Number两数最大值。
roundround(n)Number四舍五入到整数 (Math.round)。
ceilceil(n)Number向上取整。
floorfloor(n)Number向下取整。
clampclamp(n, min, max)Numbern 钳到 [min, max]
powpow(base, exponent)Numberbase ^ exponent
sumsum(list)Number数值元素求和;null 跳过;空列表返回 0
avgavg(list)Number数值元素求平均;空或 null 返回 null
when: sum(ctx.items.*.price) > 1000
when: clamp(ctx.retries, 0, 5) == ctx.retries

3. 集合函数 (13)

函数签名返回说明
sizesize(collection)Number列表 / map / 字符串的长度;null 返回 0
containscontains(collection, item)Boolean成员判断。列表→精确匹配;字符串→子串;map→是否含该键。
firstfirst(list)any第一个元素;空或 null 返回 null
lastlast(list)any最后一个元素;空或 null 返回 null
isEmptyisEmpty(collection)Boolean空或 null 返回 true
distinctdistinct(list)List去重并保持原顺序。
flattenflatten(listOfLists)List展平一层;非列表元素透传。
sortsort(list)List自然序排序,落到 toString 作为后备;null 排在前面。
taketake(list, n)Listn 个元素。
dropdrop(list, n)List跳过前 n 个元素。
reversereverse(list)List反转顺序。
anyany(list, value)Boolean是否存在等于 value 的元素。
allall(list, value)Boolean是否所有元素都等于 value;空列表返回 true
when: contains(ctx.tags, "premium") and size(ctx.items) > 0
when: any(ctx.errors, "TIMEOUT")
when: all(distinct(ctx.statuses), "OK")

4. Map 与对象 (5)

函数签名返回说明
keyskeys(map)List所有键作为列表返回。
valuesvalues(map)List所有值作为列表返回。
entriesentries(map)List返回 {key, value} map 列表。
mergemerge(map1, map2)Map浅合并;map2 覆盖 map1
hashas(map, key)Booleanmap 是否含该键。
when: has(ctx.headers, "X-Trace-Id")
when: size(keys(ctx.attributes)) > 3

5. Null 与默认值 (3)

函数签名返回说明
coalescecoalesce(v1, v2, …)any第一个非 null 的参数;全都是 null 则返回 null
isNullisNull(value)Boolean是否为 null。
isNotNullisNotNull(value)Boolean是否非 null。

coalesce 是默认值的主力。如果只需要一个回退值,第 9 节“高级工具”中的 ifNull 读起来更自然。

when: isNotNull(ctx.userId)
let region = coalesce(ctx.region, ctx.country, "GLOBAL")

6. 类型转换 (5)

函数签名返回说明
toStringtoString(value)String任意值转字符串。
toNumbertoNumber(value)Number从字符串解析数值,或直接返回数值;无法解析的字符串归约为 0
toBooleantoBoolean(value)Boolean解析 "true" / "false" 字符串或透传布尔值。
toInttoInt(value)Number转整数;截断小数。
typeOftypeOf(value)String返回 "String""Number""Boolean""List""Map""Object""Null"
when: toNumber(ctx.amountStr) > 100
when: typeOf(ctx.payload) == "Map"

7. 日期/时间 (6)

所有日期都以 ISO-8601 字符串 (yyyy-MM-ddTHH:mm:ssZ) 流转。用以下函数格式化、 解析、加减和计算时长。

函数签名返回纯?说明
nownow()String来自 GraphEngine.currentTimeSource() 的当前时间戳。
todaytoday()String当前日期 yyyy-MM-dd (UTC)。
formatDateformatDate(isoStr, pattern)StringDateTimeFormatter 模式格式化 ISO 时间 (如 "yyyy-MM-dd HH:mm")。
parseDateparseDate(str, pattern)String按模式解析;返回 ISO-8601 时间。
addDurationaddDuration(isoStr, durationStr)String加一段时长。支持 "2h""30m""1d""500ms" 或 ISO-8601 形式 "PT2H"
diffDurationdiffDuration(iso1, iso2, unit)Number两个时间点之间的距离。unit: "s"/"seconds""m"/"minutes""h"/"hours""d"/"days"
when: diffDuration(ctx.createdAt, now(), "h") > 24
let expiresAt = addDuration(now(), "30m")

确定性小贴士。 在对重放敏感的场景 (durable session、确定性测试) 中,优先用 基于存储时间戳的 parseDate/addDuration,而不要直接调用 now() / today()


8. 工具 / ID (2)

函数签名返回纯?说明
uuiduuid()String随机 UUID v4。
formatformat(template, arg1, arg2, …)String使用 Java String.format 风格的占位符 (%s%d 等);格式错误时返回模板原文。
let traceId = uuid()
let message = format("Order %s failed after %d retries", ctx.orderId, ctx.retries)

9. 高级工具 (8)

函数签名返回说明
regexExtractregexExtract(str, pattern [, group])String取正则匹配的分组 (默认 1);未匹配返回 null
regexExtractAllregexExtractAll(str, pattern)List所有不重叠的匹配。
getFieldgetField(obj, fieldName)any通过反射读 map 或 bean 的字段;null 安全。
setFieldsetField(obj, fieldName, value)Map返回更新字段后的浅拷贝 (不可变语义)。
templatetemplate(str, bindings)String用 map 替换 ${key} 占位符。
rangerange(start, end)List整数序列 [start, end)
mapOfmapOf(k1, v1, k2, v2, …)Map用交替的键值参数构造 map。
listOflistOf(e1, e2, …)List用参数构造列表。
ifNullifNull(value, default)any非 null 返回 value,否则返回 default
let host = regexExtract(ctx.url, "https?://([^/]+)/")
let updated = setField(ctx.user, "lastSeenAt", now())
let greeting = template("Hello, ${name}!", mapOf("name", ctx.name))

10. JSON 函数 (2)

当 DSL 编译器配置了 JsonCodec,JSON 工具就会注册到函数表里 —— BuiltInFunctions.registerAll(registry, jsonCodec)

函数签名返回说明
toJsontoJson(obj)String通过配置的 codec 序列化为 JSON。
fromJsonfromJson(str)any反序列化为 Map / List / 基础类型。
let payload = toJson(mapOf("orderId", ctx.orderId, "total", ctx.total))
let parsed = fromJson(ctx.responseBody)

若未配置 JsonCodec,DSL 编译时将以"未知函数"报错。


可选模块 (bloge-functions-crypto)

bloge-functions-crypto JAR 出现在类路径上,Java ServiceLoader 会自动加载该模 块。它提供三组函数。涉及密钥解析的加密函数还需要在引擎上配置 SecretProvider

加密 (5)

函数签名返回纯?说明
hmacSha256hmacSha256(data, keyRef)StringHMAC-SHA256 hex 摘要;keyRef 通过 SecretProvider 解析。
hmacSha512hmacSha512(data, keyRef)StringHMAC-SHA512 hex 摘要。
aesEncryptaesEncrypt(plaintext, keyRef [, ivRef])StringAES-256-CBC;base64 输出;不传 IV 时随机生成。
aesDecryptaesDecrypt(ciphertext, keyRef [, ivRef])StringAES-256-CBC;输入 base64。
secretsecret(name)StringSecretProvider 按名字解析密钥。

哈希 (4)

函数签名返回说明
md5md5(str)StringMD5 hex。
sha1sha1(str)StringSHA-1 hex。
sha256sha256(str)StringSHA-256 hex。
sha512sha512(str)StringSHA-512 hex。

编码 (6)

函数签名返回说明
base64Encodebase64Encode(str)StringUTF-8 字符串 base64 编码。
base64Decodebase64Decode(str)Stringbase64 解码;输入非法返回 null
hexEncodehexEncode(str)StringUTF-8 字符串 hex 编码。
hexDecodehexDecode(str)Stringhex 解码;输入非法返回 null
urlEncodeurlEncode(str)StringURL 编码 (UTF-8)。
urlDecodeurlDecode(str)StringURL 解码 (UTF-8);输入非法返回 null
let signature = hmacSha256(ctx.requestBody, "webhook-secret")
let fingerprint = sha256(ctx.userId)
let safeQuery = urlEncode(ctx.search)

加密模块的例外。 与核心函数不同,加密 / 哈希函数遇到非法输入 (错误密钥、损坏 的密文) 可能抛异常。请在边界做校验,或把调用放进能处理对应失败模式的算子内部。


常见错误

  • 管道操作符。 value | uppercase 在 bloge DSL 不合法。要写 uppercase(value)。词法器把 | 当作 || 的开头来读。
  • 正则切分。 split(s, ",") 是按字面字符串 "," 切分,不是正则。如果需要按 模式切分,先用 replaceAll 预处理。
  • 不传 ISO 字符串做日期运算。 addDuration("2024-01-01", "1d") 会失败 —— 第一 个参数必须是 ISO-8601 时间。如果你拿到的是裸日期,先用 addDuration(parseDate("2024-01-01", "yyyy-MM-dd"), "1d")
  • 就地修改 map。 setField 不会原地修改,它返回一个副本。要绑定结果: let updated = setField(ctx.user, "name", "Alice")
  • 常量位置调用非纯函数。 now()uuid() 在编译期专属位置 (常量折叠、schema 常量) 会被拒绝。把它们移到运行期 guard 或算子输出里。
  • 没配 SecretProvider 就用加密。 hmacSha256 / aesEncrypt 在缺少 SecretProvider 时会失败。在 Spring 或手工 bootstrap 里注入它 (见第 20 章)。
  • toBoolean 不接受数字。 toBoolean(1) 返回 null不是 true;只接受 "true" / "false" 字符串或原生布尔值。

与正文的关联

  • 第 5 章 —— 会分支的图。 when: guard 的引入章节,也是组合这些函数最主要的 场所。
  • 第 7 章 —— 设计良好的算子。 在算子的 constraintsusageExample 表达式 中使用同一套函数。
  • 第 14 章 —— 多轮 session第 15 章 —— 状态机。 session 阶段转换与状 态机转换的 guard 都使用 guard 表达式,两者通过同一个 ExpressionEvaluator 编 译。
  • 第 9 章 —— 工具链工作流。 operator-metadata.json 导出器会把这些函数名暴 露给 IDE,让补全和校验与编译器的能力保持一致。
  • 第 20 章 —— Spring 与生产接线。 JsonCodecSecretProvider 在此被装配, 从而启用上文的 JSON / 加密扩展。

校验。 72 个核心函数对应 bloge-core 中的 BuiltInFunctionsTest.registerAll_populatesExpectedNumberOfEntries()。再加上 2 个 JSON 工具与 11 个 crypto/hash/encoding 工具,总数即本附录列出的 85 个。