6.7 KiB
title, description, pageClass
| title | description | pageClass |
|---|---|---|
| 数据写入 | saveobjt / delete,含 $original、$expr 与 guards | g3-api |
数据写入
POST /data/saveobjt
请求体是一个数组:一次可提交多个表的增、改、删,全部在同一个事务里 —— 任何一项失败整批回滚,不会留下半套数据。
请求字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
table |
string | 是 | 保存目标表 |
key_field |
string | 是 | 主键列名,用于定位要改/删的行 |
updates |
object[] | 要更新的行,主键值必须在行里 | |
inserts |
object[] | 要新增的行 | |
deletes |
object[] | 要删除的行(用 key_field 定位) |
|
delete_target |
string | 声明 deletes 走哪条路径:module(按模块配置联动删子表,需 moduleCode)或 direct(直接删,需 table / key_field) |
|
guards |
object | 前置条件,见下 |
[
{
"table": "b_othercompany",
"key_field": "b_id",
"updates": [{ "b_id": "C001", "b_name": "新名字" }],
"inserts": [{ "b_id": "C002", "b_name": "新公司" }],
"deletes": [{ "b_id": "C003" }]
}
]
成功时 data 为 null,message 为 保存成功:
{
"code": 0,
"message": "保存成功",
"data": null
}
没传
delete_target时,deletes是逐行按主键直删,不会触发模块配置里的联动删除。需要联动就把delete_target声明成module并带上moduleCode。
行内的两个保留键
键名以 $ 开头的不会被当成业务列:
| 键 | 用途 | 说明 |
|---|---|---|
$original |
乐观锁:声明「我改动之前看到的值」 | 这些字段只进 WHERE 不进 SET;对不上就是 2003。值为 null 时生成 IS NULL |
$expr |
字段自增:{"$expr": "b_amount + 5"} |
只支持「本表列 ± 数值常量」,列必须是数值类型 |
{
"b_id": "C001",
"b_amount": { "$expr": "b_amount + 5" },
"$original": { "b_name": "旧名字", "b_row_version": 3 }
}
$original里带b_row_version时走整行版本锁:服务端校验通过后自动+1,请求里同名的普通值不生效(避免SET里出现两次该列)。inserts的行不允许带$original/$expr—— 新增的行没有原值可比,带了会直接报1000。$expr的字段不存在、不可写、不是数值类型、格式不对,都会在任何一行写入之前报1000(这类参数错误不会等到写到第 N 行才暴露)。
guards:写入前置条件
让「判断」和「写入」发生在同一条 SQL 里,避免先查后写的竞态。
{
"table": "b_othercompany",
"key_field": "b_id",
"updates": [{ "b_id": "C001", "b_name": "新名字" }],
"guards": {
"updates": [
{
"condition": "{b_state} = 'draft'",
"refs": ["b_othercompany"],
"message": "只有草稿状态才能改名",
"retryable": false
}
]
}
}
guards 下的三个分节键按动作区分 —— inserts / updates / deletes(注意是 deletes):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
condition |
string | 是 | SQL 布尔表达式;{列名} 是受控占位符,替换为 t.[列名];不允许注释 |
refs |
string[] | 声明引用的表,只进日志 | |
message |
string | 条件不成立时给用户看的文案 | |
retryable |
boolean | 默认 true;false 时前端文案不追加「请刷新后重试」 |
条件不成立时返回 2002,message 就是上面写的 message,data 带 table / action / index。
两条互斥规则(同时用会报 1000):
guards.deletes与delete_target互斥;guards.deletes与dependent_deletes互斥。
原因是带条件的删除只能逐行唯一定位,而 delete_target / 依赖删除那两条旁路拿不到 guards —— 与其让条件被静默忽略,不如直接拒绝。
三个错误码怎么分
| code | 含义 | 典型原因 | data 里的定位信息 |
|---|---|---|---|
2001 |
保存失败 | 字段不存在、类型不匹配、数据库约束冲突等 | table / action / index / cause |
2002 |
前置条件不成立 | guards 里的条件没满足 |
table / action / index |
2003 |
并发编辑冲突 | $original 的原值已被别人改过、b_row_version 不匹配 |
table / action / index / fields |
2003 的正确处理不是重试,而是:重新拉取该行,用 data.fields 标出哪些字段已被他人修改,让用户决定覆盖还是放弃。
action 的取值是 update / insert / delete,index 是请求数组里的下标(从 0 开始)—— 批量保存失败时靠它定位到具体哪一行。
POST /data/delete
独立删除接口。和 saveobjt 的区别是它不进保存事务、也不涉及增改。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
target |
string | 是 | module 或 direct,其它值报 1000 |
moduleCode |
string | 是(target=module) |
模块编码(s_module.b_id) |
table |
string | 是(target=direct) |
表名 |
key_field |
string | 是(target=direct) |
主键列名 |
ids |
any[] | 要删除的主键值;direct 下为空数组会直接返回 {} |
按模块删除(会按模块配置联动删除子表):
{
"target": "module",
"moduleCode": "othercompany",
"ids": ["C001", "C002"]
}
指定表直接删除:
{
"target": "direct",
"table": "b_othercompany",
"key_field": "b_id",
"ids": ["C001"]
}
{
"code": 0,
"message": "操作成功",
"data": { "b_othercompany": 2 }
}
data 是 {表名: 实际删除行数} —— direct 只有一项;module 会带出所有被联动删除的表,所以可能有多项。整个删除同样在单个事务里完成。
target必须显式声明。后端刻意不做「没传moduleCode就当直删」这种隐式分支,避免漏传参数时静默绕过模块规则。