Skip to content

service.json 配置详解

这篇文档说明 Service Tree 的 service.json 怎么写。读完后,可以把底层数据集、SQL 或 Backend Function 封装成业务命令,例如:

text
lovrabet crm customer list
lovrabet crm customer detail 10001
lovrabet crm customer contact list --customer-id 10001

新的推荐写法是树状结构:用 resources 表示业务对象,用 actions 表示这个对象上能做什么。旧的 commands 数组仍可用,但只建议在兼容老配置时保留。

先看最小结构

一个服务配置通常包含四部分:

  1. service:服务入口,例如 crm
  2. app / apps:服务默认使用哪个应用。
  3. resources:服务里有哪些业务对象,例如客户、联系人、跟进记录。
  4. actions:每个业务对象能做什么,例如列表、详情、创建。

最小示例:

json
{
  "service": "crm",
  "name": "CRM",
  "description": "客户、联系人和跟进记录的业务服务",
  "app": "app-xxxxxxxx",
  "resources": {
    "customer": {
      "name": "客户",
      "datasetCode": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "actions": {
        "list": {
          "description": "查看客户列表",
          "defaults": {
            "currentPage": 1,
            "pageSize": 20,
            "orderBy": [{ "updated_at": "desc" }]
          },
          "flags": {
            "status": "where.status.$eq",
            "keyword": {
              "to": "where.customer_name",
              "op": "$contain",
              "description": "客户名称关键字"
            }
          }
        },
        "detail": {
          "description": "查看客户详情",
          "action": "getOne",
          "args": ["id"],
          "map": {
            "id": {
              "target": "id",
              "transform": "number"
            }
          }
        }
      }
    }
  }
}

上面会生成两个业务入口:

text
lovrabet crm customer list --status active --keyword 科技
lovrabet crm customer detail 10001

它们分别会映射到:

text
lovrabet data filter --code aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --params '{"where":{"status":{"$eq":"active"},"customer_name":{"$contain":"科技"}},"currentPage":1,"pageSize":20,"orderBy":[{"updated_at":"desc"}]}'
lovrabet data getOne --code aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --params '{"id":10001}'

顶层字段

字段是否必填说明
protocol协议名。默认按 lovrabet.service-tree/v1 处理。
version配置文件版本。不写时默认 1.0.0
service服务入口。推荐写字符串,例如 "crm";也支持 { "code": "crm", "name": "CRM" }
name / description展示名称和说明,会出现在 help、schema、doctor 中。
app单应用快捷绑定。可写 appcode,也可写应用名称。
apps多应用绑定表。推荐新配置使用这个字段。
appBindings旧命名,语义同 apps,兼容已有配置。
defaults服务级默认底层参数,会被资源和动作继承。
resources推荐业务对象树。新配置优先使用。
commands兼容旧的扁平命令数组。仍可用,但不再作为推荐写法。

service 不是应用编码。不要写成 app-xxxx。一个服务可能串联多个应用,service 应表达业务域,例如 crmorderstore-ops

app 和 apps:绑定应用

单应用服务可以直接写 app

json
{
  "service": "crm",
  "app": "app-xxxxxxxx"
}

如果一个服务会串联多个应用,使用 apps

json
{
  "service": "customer-success",
  "apps": {
    "crm": {
      "appcode": "app-crmxxxx",
      "env": "daily"
    },
    "order": {
      "appcode": "app-orderxxxx",
      "env": "daily"
    }
  }
}

资源或动作里通过 appRef 引用应用别名:

json
{
  "resources": {
    "customer": {
      "appRef": "crm",
      "datasetCode": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "actions": { "list": {} }
    },
    "order": {
      "appRef": "order",
      "datasetCode": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "actions": { "list": {} }
    }
  }
}

appappsappBindings 只是默认值。用户执行命令时显式指定的应用或环境优先。

resources:定义业务对象树

resources 的每个 key 都会成为命令路径的一段。

json
{
  "resources": {
    "customer": {
      "name": "客户",
      "resources": {
        "contact": {
          "name": "联系人",
          "actions": {
            "list": {}
          }
        }
      }
    }
  }
}

这会生成:

text
lovrabet crm customer contact list

Resource 常用字段:

字段说明
name / description业务对象展示信息。
appRef / app引用 apps / appBindings 里的应用别名。多应用服务里常用。
datasetCode数据集 code,推荐优先使用。
datatable / table物理表名。未写 datasetCode 时,可按表名解析数据集。
defaults / params资源级默认底层参数,例如分页、排序、固定条件。
actions这个业务对象上的动作,例如 listminedetailcreate
resources子业务对象,用于更深层命令。

资源上的 appRefdatasetCodedatatabledefaults 会被子资源和动作继承。动作里可以覆盖这些值。

actions:定义业务动作

actions 的每个 key 是命令路径的最后一段。

json
{
  "actions": {
    "list": {
      "description": "查看客户列表"
    },
    "detail": {
      "description": "查看客户详情",
      "action": "getOne",
      "args": ["id"],
      "map": {
        "id": {
          "target": "id",
          "transform": "number"
        }
      }
    }
  }
}

Action 常用字段:

字段说明
description命令说明。
action动作简写。默认是 filter。可写 getOnecreateupdatedelete,也可写 sql.execbff.exec
target完整目标写法,可写对象,也可写 data.filtersql.execbff.exec
kind / commandtarget 的展开写法,例如 { "kind": "data", "command": "filter" }
datasetCode / datatable / table覆盖资源继承的数据集定位。
sqlCodeSQL 编码。配合 action: "sql.exec" 使用。
bffCode / bffId / scriptNameBackend Function 定位。配合 action: "bff.exec" 使用。
args位置参数。字符串简写会自动变成必填参数。
flags可选参数。推荐对象写法。
defaults / params动作级默认底层参数。
map / mapTo参数映射规则。推荐新配置使用 map,旧配置里的 mapTo 仍可用。
risk风险等级。只读可省略;写入用 write;删除或不可逆操作用 high-risk-write

action 写法选择

1. 查询数据集列表

不写 action 时,默认就是 data.filter

json
{
  "resources": {
    "customer": {
      "datasetCode": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "actions": {
        "list": {
          "description": "查看客户列表"
        }
      }
    }
  }
}

生成:

text
lovrabet crm customer list

映射到:

text
lovrabet data filter --code aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --params '{}'

2. 查询单条详情

json
{
  "actions": {
    "detail": {
      "description": "查看客户详情",
      "action": "getOne",
      "args": ["id"],
      "map": {
        "id": {
          "target": "id",
          "transform": "number"
        }
      }
    }
  }
}

生成:

text
lovrabet crm customer detail 10001

映射到:

text
lovrabet data getOne --code aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --params '{"id":10001}'

3. 执行 SQL

json
{
  "actions": {
    "stats": {
      "description": "查看客户统计",
      "action": "sql.exec",
      "sqlCode": "customer_stats",
      "flags": {
        "startDate": {
          "to": "startDate",
          "description": "开始日期"
        },
        "endDate": {
          "to": "endDate",
          "description": "结束日期"
        }
      }
    }
  }
}

生成:

text
lovrabet crm customer stats --start-date 2026-01-01 --end-date 2026-01-31

映射到:

text
lovrabet sql exec --sqlcode customer_stats --params '{"startDate":"2026-01-01","endDate":"2026-01-31"}'

4. 执行 Backend Function

复杂写入建议优先封装到 Backend Function,让服务端处理校验、幂等、权限、事务和失败恢复。

json
{
  "actions": {
    "create": {
      "description": "创建客户",
      "action": "bff.exec",
      "scriptName": "createCustomer",
      "risk": "write",
      "flags": {
        "customerName": {
          "to": "customerName",
          "required": true,
          "description": "客户名称"
        },
        "industry": {
          "to": "industry",
          "description": "所属行业"
        }
      }
    }
  }
}

生成:

text
lovrabet crm customer create --customer-name 云兔科技 --industry software

映射到:

text
lovrabet bff exec --name createCustomer --params '{"customerName":"云兔科技","industry":"software"}'

args:必须给出的值

args 适合“详情 ID”“编号”这类调用时必须提供的值。

简写:

json
{
  "args": ["id"]
}

完整写法:

json
{
  "args": [
    {
      "name": "id",
      "description": "客户编号",
      "required": true
    }
  ]
}

args 本身只声明“要收什么值”。要把这个值放到底层请求里,还需要写 map

json
{
  "args": ["id"],
  "map": {
    "id": {
      "target": "id",
      "transform": "number"
    }
  }
}

flags:可选查询条件

flags 适合状态、负责人、关键字、时间范围、分页等条件。

推荐对象写法:

json
{
  "flags": {
    "status": "where.status.$eq",
    "ownerId": {
      "to": "where.owner_id",
      "op": "$eq",
      "type": "number",
      "transform": "number",
      "description": "负责人 ID"
    },
    "startDate": {
      "to": "where.created_at",
      "op": "$gte",
      "description": "开始日期"
    },
    "pageSize": {
      "to": "pageSize",
      "type": "number",
      "transform": "number",
      "description": "每页条数"
    }
  }
}

对外使用时,camelCase 会自动转成短横线:

text
lovrabet crm customer list --owner-id 12 --start-date 2026-01-01 --page-size 50

上面会映射到:

json
{
  "where": {
    "owner_id": { "$eq": 12 },
    "created_at": { "$gte": "2026-01-01" }
  },
  "pageSize": 50
}

Flag 对象字段:

字段说明
to / target映射目标路径。
op / operator操作符,例如 $eq$contain$gte
type参数类型:stringnumberbooleanjson。不写默认 string
transform类型转换:stringnumberbooleanjson
description参数说明。
required是否必填。
default默认值。
enum可选值列表。
cliName对外展示的参数名。不写时由 name 自动转短横线。
omitEmpty空值时是否跳过映射。

map 和 mapTo:把业务参数翻译成底层参数

map / mapTo 回答一个问题:使用者给出的业务参数,要放到底层请求的哪个位置。

推荐新配置使用 map,旧配置中的 mapTo 仍支持。

常见来源

来源说明示例
flags.xxx来自可选参数。flags.status
args.xxx来自位置参数。args.id
context.xxx / ctx.xxx来自当前登录用户或运行上下文。context.userId
const.xxx固定值。const.deleted
const固定值来源。const

map 里,未带前缀的 key 会自动匹配 argsflags。比如有 args: ["id"] 时,"id" 会按 args.id 处理。

简写:直接写目标路径

json
{
  "map": {
    "status": "where.status.$eq"
  }
}

等价于把 status 参数放到:

json
{
  "where": {
    "status": { "$eq": "<status>" }
  }
}

完整写法:目标、操作符、转换分开写

json
{
  "map": {
    "ownerId": {
      "target": "where.owner_id",
      "operator": "$eq",
      "transform": "number"
    }
  }
}

业务命令:

text
lovrabet crm customer list --owner-id 12

底层参数:

json
{
  "where": {
    "owner_id": { "$eq": 12 }
  }
}

固定条件

固定条件适合表达“默认只看未删除数据”“默认只看某类客户”。

json
{
  "map": {
    "const.deleted": {
      "target": "where.deleted",
      "operator": "$eq",
      "value": 0,
      "transform": "number"
    }
  }
}

底层参数:

json
{
  "where": {
    "deleted": { "$eq": 0 }
  }
}

当前用户

“我的客户”不要写死某个人的 ID,使用运行上下文:

json
{
  "actions": {
    "mine": {
      "description": "查看我负责的客户",
      "map": {
        "context.userId": {
          "target": "where.owner_id",
          "operator": "$eq",
          "transform": "number"
        }
      }
    }
  }
}

生成:

text
lovrabet crm customer mine

底层参数:

json
{
  "where": {
    "owner_id": { "$eq": "<当前登录用户 ID>" }
  }
}

defaults 和 params:默认底层参数

defaultsparams 都表示默认底层参数。推荐新配置统一使用 defaultsparams 主要用于兼容旧表达。

可以写在三层:

  1. 顶层 defaults:整个服务都继承。
  2. resource defaults:这个业务对象和子对象继承。
  3. action defaults:只影响当前动作。

合并顺序是从外到内,越靠近 action 越优先:

text
service.defaults -> resource.defaults -> action.defaults

示例:

json
{
  "defaults": {
    "where": {
      "deleted": { "$eq": 0 }
    }
  },
  "resources": {
    "customer": {
      "defaults": {
        "pageSize": 20
      },
      "actions": {
        "list": {
          "defaults": {
            "currentPage": 1,
            "orderBy": [{ "updated_at": "desc" }]
          }
        }
      }
    }
  }
}

datasetCode 和 datatable

data 动作需要能定位到数据集。

推荐优先写 datasetCode

json
{
  "datasetCode": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

如果希望按物理表名解析,可以写 datatabletable

json
{
  "datatable": "crm_customer"
}

两者至少写一个。datasetCode 最明确,要求是 32 位编码。datatable 适合多环境数据表一致、但数据集编码可能不同的场景。

risk:标记风险等级

适合场景
read查询、详情、统计。默认值。
write新建、更新、同步、触发业务动作。
high-risk-write删除、批量替换、不可逆操作。

data 动作会自动推断部分风险:

  • filtergetOneaggregate 默认是 read
  • createbatchCreateupdate 默认是 write
  • delete 默认是 high-risk-write

Backend Function 和 SQL 默认按 read 处理。只要实际会写数据,就应显式设置 risk: "write"risk: "high-risk-write"

旧 commands 写法如何升级

旧写法:

json
{
  "service": {
    "code": "crm",
    "name": "CRM"
  },
  "appBindings": {
    "main": { "appcode": "app-xxxxxxxx" }
  },
  "commands": [
    {
      "path": "customer list",
      "description": "查看客户列表",
      "target": {
        "kind": "data",
        "command": "filter",
        "appRef": "main",
        "datasetCode": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
      },
      "flags": [
        {
          "name": "status",
          "type": "string",
          "mapTo": "where.status.$eq"
        }
      ]
    }
  ]
}

新写法:

json
{
  "service": "crm",
  "name": "CRM",
  "apps": {
    "main": { "appcode": "app-xxxxxxxx" }
  },
  "resources": {
    "customer": {
      "appRef": "main",
      "datasetCode": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "actions": {
        "list": {
          "description": "查看客户列表",
          "flags": {
            "status": "where.status.$eq"
          }
        }
      }
    }
  }
}

对应关系:

旧字段新字段
service.codeservice
appBindingsapps
commands[].pathresources 路径 + actions key
commands[].target.datasetCoderesource 或 action 的 datasetCode
commands[].target.commandaction 的 action
commands[].flags[]action 的 flags 对象
commands[].mapToaction 的 map

commands 数组仍能导入和执行。新配置优先使用 resources/actions,因为它能自然表达业务对象层级,也避免在 JSON key 或 path 里用空格拼命令。

完整 CRM 示例

json
{
  "service": "crm",
  "name": "CRM",
  "description": "客户、联系人和跟进记录的业务服务",
  "app": "app-xxxxxxxx",
  "resources": {
    "customer": {
      "name": "客户",
      "datasetCode": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "defaults": {
        "currentPage": 1,
        "pageSize": 20
      },
      "actions": {
        "list": {
          "description": "查看客户列表",
          "defaults": {
            "orderBy": [{ "updated_at": "desc" }]
          },
          "flags": {
            "status": "where.status.$eq",
            "keyword": {
              "to": "where.customer_name",
              "op": "$contain",
              "description": "客户名称关键字"
            },
            "page": {
              "to": "currentPage",
              "type": "number",
              "transform": "number"
            },
            "pageSize": {
              "to": "pageSize",
              "type": "number",
              "transform": "number"
            }
          }
        },
        "mine": {
          "description": "查看我负责的客户",
          "map": {
            "context.userId": {
              "target": "where.owner_id",
              "operator": "$eq",
              "transform": "number"
            }
          }
        },
        "detail": {
          "description": "查看客户详情",
          "action": "getOne",
          "args": ["id"],
          "map": {
            "id": {
              "target": "id",
              "transform": "number"
            }
          }
        },
        "create": {
          "description": "创建客户",
          "action": "bff.exec",
          "scriptName": "createCustomer",
          "risk": "write",
          "flags": {
            "customerName": {
              "to": "customerName",
              "required": true,
              "description": "客户名称"
            },
            "industry": {
              "to": "industry",
              "description": "所属行业"
            }
          }
        }
      },
      "resources": {
        "contact": {
          "name": "联系人",
          "datasetCode": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
          "actions": {
            "list": {
              "description": "查看客户联系人",
              "flags": {
                "customerId": {
                  "to": "where.customer_id",
                  "op": "$eq",
                  "type": "number",
                  "transform": "number",
                  "required": true
                }
              }
            }
          }
        }
      }
    }
  }
}

生成的业务入口:

text
lovrabet crm customer list
lovrabet crm customer mine
lovrabet crm customer detail 10001
lovrabet crm customer create --customer-name 云兔科技 --industry software
lovrabet crm customer contact list --customer-id 10001

校验清单

导入前逐项检查:

  • service 是小写短横线格式,例如 crmorderstore-ops
  • 至少有一个 resourcescommands
  • 新配置优先使用 resources/actions
  • 每个 resource key 和 action key 都是小写短横线格式。
  • 每个生成的完整命令路径不重复。
  • data 动作有 datasetCodedatatable / table
  • datasetCode 是 32 位编码。
  • SQL 动作有 sqlCode
  • Backend Function 动作有 bffCodebffIdscriptName
  • flags.type 只写 stringnumberbooleanjson
  • map / mapTo 的来源只使用 flags.args.context.ctx.const.,或能自动匹配到已有 args / flags 的短名。
  • 写入类动作设置了正确的 risk
  • 多应用服务里,resource 或 action 的 appRef 能在 apps 中找到。

什么时候用旧 commands

只有两种情况继续用 commands

  1. 已有配置很多,短期只做小修,不想立刻调整结构。
  2. 某个服务非常简单,团队确认不需要资源层级。

新建配置优先使用 resources/actions

基于飞书知识库同步生成,内容以飞书源文档为准