让它守规矩:提交条件与一个事务
提交、批准、拒绝三个 Action:提交条件逐条把关,批准时在一个事务里改两个对象,角色决定谁能做。

本课目标
读完这一课,你将能够
- 写提交条件,并给每一条配好失败时给人看的那句话
- 让一个 Action 在一个事务里改两个对象
- 分清参数限制、提交条件和 roles 各挡什么
提交:两条提交条件
参数限制只看参数自己,比如座位数在 1 到 10000 之间。「申请的座位数要比现在的多」要拿参数和公司当前的座位数比,这是提交条件的事。把下面的文件存成 ontology/04-submit-seat-request.json:
{
"kind": "actionType",
"apiName": "submit-seat-request",
"title": "提交座位申请",
"description": "客户公司申请把座位数提到新的数目。要比现在多;暂停中的公司不能申请。",
"schema": {
"parameters": [
{ "name": "customer", "type": "object", "objectType": "customer", "title": "公司", "required": true },
{ "name": "seats", "type": "integer", "title": "申请的座位数", "required": true, "min": 1, "max": 10000 },
{ "name": "reason", "type": "string", "title": "理由", "maxLength": 200 }
],
"submissionCriteria": [
{
"condition": { "type": "comparison", "left": { "param": "seats" }, "operator": "gt", "right": { "param": "customer", "property": "seats" } },
"failureMessage": "申请的座位数要比现在的多"
},
{
"condition": { "type": "comparison", "left": { "param": "customer", "property": "stage" }, "operator": "isNot", "right": { "literal": "暂停" } },
"failureMessage": "暂停中的公司不能申请座位"
}
],
"rules": [
{
"type": "createObject",
"objectType": "seatRequest",
"values": {
"requestId": "$uuid",
"customerId": "$customer",
"customerName": "$customer.name",
"currentSeats": "$customer.seats",
"seats": "$seats",
"reason": "$reason",
"status": "submitted",
"requestedBy": "$user",
"requestedAt": "$now"
}
}
],
"summary": "{customer} 申请座位数 {seats}",
"actionLog": true,
"toolDescription": "Submit a seat request for a customer company: the requested seat count must be higher than the current one, and paused companies cannot request."
}
}
每条提交条件是一个比较:左边和右边可以是参数(param)、对象参数的属性(加 property)、字面量(literal),运算符有 is、isNot、gt、gte、lt、lte 等;不满足时,返回你写在 failureMessage 里的那句话。规则是一条 createObject:$customer 取公司的主键,$customer.name 取公司的名字,$uuid 是新申请的编号。requestedBy 存的是 $user:执行人的账号 ID,不是名字——名字会改、会重名,也不该散落在数据里;界面再把 ID 换成「你」「自动化」这样的说法。$user 的样子随入口不同:开发者 Key 从命令行、SDK、REST 调用时是账号 ID,自动化是 workflow:<应用名>,经 aidc app call 时是 account:<账号 ID>。
aidc semantic define ontology && aidc semantic publish --notes "提交座位申请"
aidc semantic apply submit-seat-request --param customer=acme-east --param seats=20
# 422 申请的座位数要比现在的多
aidc semantic apply submit-seat-request --param customer=acme-pause --param seats=30
# 422 暂停中的公司不能申请座位
aidc semantic apply submit-seat-request --param customer=acme-east --param seats=30 \
--param reason="新开两个仓库,调度员增加"
aidc semantic objects seatRequest --select customerName,seats,status # 第一列 __primaryKey 就是申请编号
两条条件都不满足时(暂停的公司、座位数还更少),执行只报第一条;--validate-only 会把每一条的结果都列出来,界面上用它给人一次说全。
批准:一个事务里改两个对象
批准要做两件事:把申请标成已批准,把公司的座位数改成申请的数目。写成两条规则,放进同一个 Action。存成 ontology/05-approve-seat-request.json:
{
"kind": "actionType",
"apiName": "approve-seat-request",
"title": "批准座位申请",
"description": "批准一张待审批的申请:申请标为已批准,同一个事务里把这家公司的座位数改成申请的数目。只有开发者能批。",
"schema": {
"parameters": [
{ "name": "request", "type": "object", "objectType": "seatRequest", "title": "申请", "required": true },
{ "name": "customer", "type": "object", "objectType": "customer", "title": "公司", "required": true },
{ "name": "note", "type": "string", "title": "批复意见", "maxLength": 200 }
],
"submissionCriteria": [
{
"condition": { "type": "comparison", "left": { "param": "request", "property": "status" }, "operator": "is", "right": { "literal": "submitted" } },
"failureMessage": "只能批准待审批的申请"
},
{
"condition": { "type": "comparison", "left": { "param": "request", "property": "customerId" }, "operator": "is", "right": { "param": "customer", "property": "customerId" } },
"failureMessage": "这张申请不是这家公司的"
},
{
"condition": { "type": "comparison", "left": { "param": "request", "property": "seats" }, "operator": "gt", "right": { "param": "customer", "property": "seats" } },
"failureMessage": "这张申请已经过时:公司现有的座位数不比它少"
},
{
"condition": { "type": "comparison", "left": { "param": "request", "property": "seats" }, "operator": "gte", "right": { "param": "customer", "property": "members" } },
"failureMessage": "座位数不能少于成员数"
}
],
"rules": [
{
"type": "modifyObject",
"objectType": "seatRequest",
"object": "$request",
"values": { "status": "approved", "decidedBy": "$user", "decidedAt": "$now", "decisionNote": "$note" }
},
{
"type": "modifyObject",
"objectType": "customer",
"object": "$customer",
"values": { "seats": "$request.seats" }
}
],
"roles": ["developer"],
"summary": "批准 {customer} 的座位申请:{seats} 座",
"actionLog": true,
"toolDescription": "Approve a pending seat request and set the customer's seat count to the requested number, in one transaction. Developers only."
}
}
存好后再 define、publish 一次(还是 ontology 目录;没变的定义不受影响):
aidc semantic define ontology && aidc semantic publish --notes "批准座位申请"
只能批准待审批的
状态不是 submitted 的申请,批第二遍会被拒。
参数之间要对得上
申请和公司是两个参数,条件核对申请的 customerId 就是这家公司。填错了公司,拒绝。
别把座位数改小
批准那一刻按公司当前的座位数再核一遍:另一张更大的先批了,这张就过时了。
不少于成员数
提交时够用,批准前成员多了,就不够了;条件在批准的那一刻按当时的值再核一遍。
把下面的 2e291b2914258863 换成你自己的申请编号:
aidc semantic apply approve-seat-request \
--param request=2e291b2914258863 --param customer=acme-east \
--param note="同意,试点结束前观察一个月" --return-edits --json
"edits": {
"edits": [
{ "type": "modifyObject", "primaryKey": "2e291b2914258863", "objectType": "seatRequest" },
{ "type": "modifyObject", "primaryKey": "acme-east", "objectType": "customer" }
],
"addedObjectCount": 0,
"modifiedObjectsCount": 2,
…
}
两条规则按顺序执行,在同一个事务里落库:任何一条写不进去,两个对象都保持原样。modifiedObjectsCount 是 2,公司的座位数从 20 变成了 30,aidc semantic objects customer 里能看到。再批一次同一张申请,返回「只能批准待审批的申请」。
拒绝,和谁能做
拒绝只改申请,不动公司,而且必须写批复意见。存成 ontology/06-reject-seat-request.json:
{
"kind": "actionType",
"apiName": "reject-seat-request",
"title": "拒绝座位申请",
"description": "拒绝一张待审批的申请,必须写批复意见。不改公司的座位数。",
"schema": {
"parameters": [
{ "name": "request", "type": "object", "objectType": "seatRequest", "title": "申请", "required": true },
{ "name": "note", "type": "string", "title": "批复意见", "required": true, "maxLength": 200 }
],
"submissionCriteria": [
{
"condition": { "type": "comparison", "left": { "param": "request", "property": "status" }, "operator": "is", "right": { "literal": "submitted" } },
"failureMessage": "只能拒绝待审批的申请"
}
],
"rules": [
{
"type": "modifyObject",
"objectType": "seatRequest",
"object": "$request",
"values": { "status": "rejected", "decidedBy": "$user", "decidedAt": "$now", "decisionNote": "$note" }
}
],
"roles": ["developer"],
"summary": "拒绝 {customerName} 的座位申请",
"actionLog": true,
"toolDescription": "Reject a pending seat request with a note. Does not change the customer's seat count. Developers only."
}
}
aidc semantic define ontology && aidc semantic publish --notes "拒绝座位申请"
note 是必填参数,不填,校验会指到它(「批复意见 必填」)。批准和拒绝都写了 "roles": ["developer"]:只有开发者能执行。提交没写 roles,缺省是 developer、member、editor 都行。
| 提交条件 | roles | |
|---|---|---|
| 看什么 | 参数,以及对象参数当前的属性值 | 谁在执行:developer、member、editor… |
| 写在哪 | submissionCriteria,每条配一句失败信息 | roles 数组 |
| 挡下时你看到 | 422,你事先写好的那句话 | 403,「只有 developer 能执行「批准座位申请」。」 |
| 例子 | 申请的座位数要比现在的多 | 批准只给开发者 |
只读访问(分享链接、只读分享)永远不能执行 Action,连「只校验」也不行,会得到 403。这一条不用你写。
三个坑
- 遇到空值,比较不成立。
gte、gt、lt、lte碰到没有值的属性一律判不通过。所以登记客户时members是必填的;关键属性要在新建时就写上。 - 提交条件不做算术。比不了「差值不超过 5」这种。把要比的数先算好存进属性(比如存一份快照),或者直接写成字面量(第 7 课的 50 座)。
- 执行只报第一条。想让人一次看到所有问题,界面就先
--validate-only,逐条列出来。
要点
- 参数限制只看参数自己;要拿参数和对象当前的值比,写成提交条件,并配一句人看得懂的失败信息。
- 一个 Action 可以有多条规则,在同一个事务里落库:要么两个对象都改,要么都不改。
- 条件在执行的那一刻按当前的值再核一遍,所以过时的申请、后来多出的成员,批准时都拦得住。
roles管谁能做,返回 403;提交条件管做得对不对,返回 422。只读访问永远不能执行。
练一练
把每一种失败跑一遍
在你自己的公司里做;每种失败都先 --validate-only,再执行。
座位数不比现在多、暂停的公司、公司不存在、缺 seats、合格:各得到什么信息?哪一种是参数不通过,哪一种是提交条件不通过?
给 ACME West 提交 55 和 60 两张申请,先批 60,再批 55:得到什么?再用拒绝把 55 收掉。
批准 ACME East 的申请,但公司参数填 ACME West:用 --validate-only 看每一条提交条件的结果。
小测
选一个答案,马上看解析。
Q1「申请的座位数要比公司现在的多」应该写成什么?
min、max 是固定的数,只看参数自己;要和对象的当前值比,就是提交条件。
Q2批准时,公司的那条规则写入失败了(比如公司刚被删除),申请会变成已批准吗?
一个 Action 的全部规则在同一个事务里落库;任何一条写不进去,整个 Action 不生效。
Q3一个 member 账号通过应用执行「批准座位申请」(roles 只有 developer),会得到什么?
roles 在校验之前就判定谁能做;不在名单里直接 403,不会走到参数和提交条件。