Cookbook(实战示例集)
最后更新:2026年7月17日
以完整的 ScriptDefinition 展示各种场景。语法依据请参见 Statement 目录与值表达式,执行与限制请参见执行语义、限制、安全。所有示例中写入的 fields 值都是 Locale 映射({ "<locale>": 值 }),示例 Locale 统一使用 en-US。涉及外部调用(Http)或 Media 文件摄取的示例,其 executionMode 为 "Async"。
目录
- 基本 CRUD:1. 创建并发布 Content · 2. 用计算值 update · 3. 只读 GET · 4. 单条查询后 guard 再审批
- 查询与 upsert:5. slug upsert · 6. 动态字段键与 Locale patch
- 外部 API:7. 额度预扣(CAS)与 LLM 调用·退款 · 8. 将图片 URL 转为 Media · 9. 将 base64 图片转为 Media · 10. 审核后有条件处理 · 11. try/catch fallback
- 并行:12. 并行调用后合并 · 13. 注册审核
- 循环与聚合:14. 用数组输入创建 N 个 · 15. counted loop 种子数据 · 16. cascade 删除 · 17. 循环累加求和 · 18. 遍历全部分页 · 19. id 批量收集
- saga 与并发:20. 支付 saga · 21. 乐观锁 CAS
基本 CRUD
1. 创建并发布 Content
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"fields": { "title": { "en-US": "{ /payload/fields/title }" }, "body": { "en-US": "{ /payload/fields/body }" } },
"publish": true, "name": "post" },
{ "type": "Return", "value": { "id": "{ /post/sys/id }" }, "statusCode": 201 } ] }2. 用计算值 update(浏览量 +1)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "+": [ "{ /payload/fields/viewCount }", 1 ] } } } } ] }3. 只读 GET:我的订单列表
{ "method": "Get", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"where": { "createdBy": ":self" }, "order": "-sys.createdAt", "limit": 20, "name": "orders" },
{ "type": "Return", "value": { "orders": "{ /orders/items }", "next": "{ /orders/next }" } } ] }Script 不仅用于写入,也可用作只读 BFF 端点。用 createdBy: ":self" 只查询“属于我的”数据,并原样返回结果。
4. 单条查询后 guard 再审批
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceRead", "resource": "Content", "target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" },
{ "type": "If", "condition": { "!=": [ "{ /order/fields/status/en-US }", "pending" ] },
"then": [ { "type": "Return", "value": { "reason": "not pending" }, "isError": true, "statusCode": 409 } ] },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "approved" } }, "publish": true },
{ "type": "Return", "value": { "ok": true } } ] }用 ResourceRead 将单条绑定到名称后,可通过 { /order/fields/... } 直接引用(无需 items/0)。若不存在则在查询时报错(可用 Try 包裹)。
查询与 upsert
5. slug upsert(find-then-upsert)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" },
{ "type": "If", "condition": { "!!": "{ /found/sys/id }" },
"then": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /found/sys/id }" } },
"fields": { "body": { "en-US": "{ /payload/fields/body }" } } },
{ "type": "Return", "value": { "id": "{ /found/sys/id }", "op": "updated" } } ],
"else": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"fields": { "slug": { "en-US": "{ /payload/fields/slug }" }, "body": { "en-US": "{ /payload/fields/body }" } }, "name": "created" },
{ "type": "Return", "value": { "id": "{ /created/sys/id }", "op": "created" }, "statusCode": 201 } ] } ] }ResourceFind 会直接绑定第一个匹配项(若无则为 null),并通过 { "!!": "{ /found/sys/id }" } 按是否存在进行分支。
6. 动态字段键与动态 Locale patch
{ "method": "Patch", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "{ /payload/fields/fieldKey }": { "{ /payload/fields/locale }": "{ /payload/fields/value }" } } } ] }字段键和 Locale 桶键两者都是 { /ptr } 引用。用于把译文插入到特定的 Locale 桶中。
外部 API
7. 额度 guard、预扣(CAS)、LLM 调用、失败时退款(代表性示例)
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_wallet" } },
"where": { "createdBy": ":self" }, "name": "wallet" },
{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "isError": true, "statusCode": 402 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"version": "{ /wallet/sys/version }",
"fields": { "balance": { "en-US": { "-": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } },
"name": "charged" } ],
"catch": [ { "type": "Return", "value": { "ok": false, "reason": "version conflict, 重试" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 15000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/choices/0/message/content }" } }, "name": "out" },
{ "type": "Return", "value": { "ok": true, "id": "{ /out/sys/id }", "remaining": "{ /charged/fields/balance/en-US }" } } ],
"catch": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"fields": { "balance": { "en-US": { "+": [ "{ /charged/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } } },
{ "type": "Return", "value": { "ok": false, "reason": "generation failed, refunded" }, "isError": true, "statusCode": 502 } ] } ] }先用 guard 检查余额是否充足,然后在外部调用之前先扣减。扣减通过 wallet 的 sys.version 施加乐观锁(CAS)。若在读取余额与扣减之间有其他执行改动了 wallet,则因版本不一致而 abort,catch 会返回 409。由于此时尚未发起外部调用,并发请求不会被重复扣减。只有在扣减确定之后才调用 LLM;若该调用失败,则在 catch 中把扣减的金额(cost)重新加回以完成退款(补偿),随后返回 502。这样的顺序是:在不可逆的外部调用之前先确定计费,仅在失败时才进行补偿。密钥放在 secret:true 的请求头中。关于补偿的局限,请参见执行语义中的“无事务与补偿”。
8. 将图片(URL)创建为 Media 并附加到 Content
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } } }, "name": "img" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_artwork" } },
"fields": { "prompt": { "en-US": "{ /payload/fields/prompt }" }, "image": { "en-US": "{ /img/sys/id }" } } } ] }先以 name 创建 Media,再由 ResourceCreate(Content)把 { /img/sys/id } 插入到引用字段中。Media 与 Content 使用相同的 fields 模型。file 是摄取指令 { source, encoding }。因为涉及文件摄取,所以为 Async。
9. 将 base64 图片转为 Media
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "file": { "en-US": { "source": "{ /gen/body/data/0/b64_json }", "encoding": "base64" } } } } ] }10. 审核后有条件 publish 或删除
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.mod.com/check",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "text": "{ /payload/fields/body }" }, "name": "mod" },
{ "type": "If", "condition": { "==": [ "{ /mod/body/flagged }", true ] },
"then": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ],
"else": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] } ] }11. try/catch:外部失败时 fallback
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://primary.api/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 8000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/text }" }, "source": { "en-US": "primary" } } } ],
"catch": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "生成失败" }, "error": { "en-US": "{ /error/message }" }, "source": { "en-US": "fallback" } } } ] } ] }并行
12. 合并 2 个并行外部调用生成 Content
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "GET", "url": "https://api.a.com/x", "name": "a" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.b.com/y", "name": "b" } ] ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_merged" } },
"fields": { "left": { "en-US": "{ /a/body/value }" }, "right": { "en-US": "{ /b/body/value }" } } } ] }13. 注册审核:并行评分后用 and 判定
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "POST", "url": "https://api.fraud.com/score",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "email": "{ /payload/fields/email }" }, "name": "fraud" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.credit.com/v1/{ /payload/fields/userId }/score",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ], "name": "credit" } ] ] },
{ "type": "If",
"condition": { "and": [ { "<": [ "{ /fraud/body/risk }", 0.5 ] }, { ">=": [ "{ /credit/body/score }", 700 ] } ] },
"then": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"fields": { "email": { "en-US": "{ /payload/fields/email }" }, "status": { "en-US": "approved" } } },
{ "type": "Return", "value": { "decision": "approved" }, "statusCode": 201 } ],
"else": [ { "type": "Return", "value": { "decision": "manual-review" }, "statusCode": 202 } ] } ] }URL 路径中也可以插入 { /ptr }。分支结果在 join 之后引用。外部调用为 2 个(不超过 3 个)。
循环与聚合
14. 用数组输入创建 N 个 Content(Loop over)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "Loop", "over": "{ /payload/fields/items }", "as": "item", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_item" } },
"fields": { "name": { "en-US": "{ /item/name }" }, "qty": { "en-US": "{ /item/qty }" } } } ] } ] }15. counted loop(for):槽位种子数据
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "Loop", "for": { "from": 1, "to": 5 }, "as": "i", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_slot" } },
"fields": { "index": { "en-US": "{ /i }" }, "status": { "en-US": "open" } } } ] } ] }for 是从 from 到 to 包含两端(整数字面量,step 默认为 1)。as 会把当前计数器绑定到 { /i }。
16. cascade 删除(PageRead、Loop、Delete)
{ "method": "Delete", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
"where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "limit": 100, "name": "comments" },
{ "type": "Loop", "over": "{ /comments/items }", "as": "c", "maxIterations": 100,
"body": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /c/sys/id }" } } } ] },
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] }超过 100 个时,用18. 遍历全部分页处理。
17. 循环累加:SetVar 求和
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "total", "value": 0 },
{ "type": "Loop", "over": "{ /payload/fields/items }", "as": "row", "maxIterations": 100,
"body": [ { "type": "SetVar", "var": "total", "value": { "+": [ "{ /vars/total }", "{ /row/qty }" ] } } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_summary" } },
"fields": { "totalQty": { "en-US": "{ /vars/total }" } } } ] }18. 遍历全部分页
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "cursor", "value": null },
{ "type": "SetVar", "var": "hasMore", "value": true },
{ "type": "Loop", "while": "{ /vars/hasMore }", "maxIterations": 1000,
"body": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"where": { "fields.status": { "eq": "draft" } }, "limit": 100, "cursor": "{ /vars/cursor }", "name": "page" },
{ "type": "Loop", "over": "{ /page/items }", "as": "p", "maxIterations": 100,
"body": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /p/sys/id }" } } } ] },
{ "type": "SetVar", "var": "cursor", "value": "{ /page/next }" },
{ "type": "SetVar", "var": "hasMore", "value": { "!!": "{ /page/next }" } } ] } ] }用 cursor、while、SetVar 遍历所有页面。由于在 Loop body 内禁止外部调用,这里只使用资源操作。
19. 用邮箱列表批量收集 id(merge)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "ids", "value": [] },
{ "type": "SetVar", "var": "missing", "value": [] },
{ "type": "Loop", "over": "{ /payload/fields/emails }", "as": "email", "maxIterations": 100,
"body": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"where": { "fields.email": { "eq": "{ /email }" } }, "name": "acc" },
{ "type": "If", "condition": { "!!": "{ /acc/sys/id }" },
"then": [ { "type": "SetVar", "var": "ids", "value": { "merge": [ "{ /vars/ids }", [ "{ /acc/sys/id }" ] ] } } ],
"else": [ { "type": "SetVar", "var": "missing", "value": { "merge": [ "{ /vars/missing }", [ "{ /email }" ] ] } } ] } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_campaign" } },
"fields": { "recipients": { "en-US": "{ /vars/ids }" }, "unresolved": { "en-US": "{ /vars/missing }" } } } ] }读取(ResourceFind)不是外部调用,因此在 Loop body 中是允许的。分别用 merge 累积存在与不存在两种情况。
saga 与并发
20. 支付 saga(Try/catch/finally)
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"fields": { "sku": { "en-US": "{ /payload/fields/sku }" }, "status": { "en-US": "reserved" } },
"publish": false, "name": "order" },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.pay.com/charge",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "amount": "{ /payload/fields/amount }", "ref": "{ /order/sys/id }" }, "timeoutMs": 10000, "name": "pay" },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "paid" }, "txId": { "en-US": "{ /pay/body/transactionId }" } }, "publish": true },
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 } ],
"catch": [
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } } },
{ "type": "Return", "value": { "reason": "payment failed", "detail": "{ /error/message }" }, "isError": true, "statusCode": 402 } ],
"finally": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_paylog" } },
"fields": { "orderRef": { "en-US": "{ /order/sys/id }" }, "amount": { "en-US": "{ /payload/fields/amount }" } } } ] } ] }预留(draft)之后,若支付成功则确定并 publish 且返回 201;失败时 catch 会删除预留(补偿)并返回 402;finally 始终记录日志。删除式补偿会产生新的 sys.id,因此存在引用断裂的局限(参见执行语义中的“无事务与补偿”)。
21. 乐观锁 CAS
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_stock" } },
"where": { "fields.sku": { "eq": "{ /payload/fields/sku }" } }, "limit": 1, "name": "stock" },
{ "type": "If", "condition": { "<": [ "{ /stock/items/0/fields/qty/en-US }", "{ /payload/fields/amount }" ] },
"then": [ { "type": "Return", "value": { "reason": "out of stock" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /stock/items/0/sys/id }" } },
"version": "{ /stock/items/0/sys/version }",
"fields": { "qty": { "en-US": { "-": [ "{ /stock/items/0/fields/qty/en-US }", "{ /payload/fields/amount }" ] } } } },
{ "type": "Return", "value": { "ok": true } } ],
"catch": [
{ "type": "Return", "value": { "reason": "version conflict, 重试" }, "isError": true, "statusCode": 409 } ] } ] }先读取库存以获取最新的 sys.version,再用该版本进行扣减(version)。若在读取与写入之间有其他执行改动了值,则因版本不一致而 abort,catch 返回 409。库存不足的 guard 位于 Try 之外(正常的提前返回)。
相关文档
- Statement 目录:示例中所用语句的字段与结果。
- 值表达式:引用、JsonLogic、Locale 映射规则。
- 执行语义、限制、安全:执行顺序、补偿、乐观锁、限制。
- Script 概述:顶层结构与执行模式。
