外观
元数据与 schema 模式篇
目标:让集成方用 ContentType / Field / Template 三件套描述「这一类内容长什么样」,按
attributeKey读写文档与容器的字段值,按字段筛选与批量取值,并理解内置字段与业务字段的边界、「访问受控」字段对读写与可见性的影响。不在本篇范围:字段值按类型如何编码、null 三态语义(见字段值编码契约);ACL 权限位(见 ACL 与继承模式篇);版本保留策略由 ContentType 声明、但版本链能力本身见文档与版本模式篇;字段定义的 CRUD(增删字段、改约束)当前不在 V1 对外,仅查询;错误码完整码表(见错误码参考)。
本篇是领域模型 §ContentType / Field / Template的纵深展开。概念页讲「声明式 schema 三件套」的模型,本篇讲读写字段值、按字段筛选、取表单 Schema 与按类型查询的具体接口与坑。
schema 三件套关系(用前必读)
集成方先读懂三件套各自承担什么,再读端点:
| 对象 | 解决什么 | 关键稳定标识 |
|---|---|---|
| ContentType | 「这是哪类内容」——给文档或容器贴的类型标签,声明版本保留策略、绑定字段与模板 | typeId(环境内)、code + targetKind(跨环境稳定,租户内唯一) |
| Field(字段定义) | 「这类内容有哪些可挂的业务字段」——值类型、基数、是否必填、能力声明(可检索 / 可筛选 / 访问受控) | attributeKey(租户内唯一、跨环境不变,对外稳定标识,不用 fieldId) |
| Template | 「相关字段的成套复用单元」——字段的分组载体,绑定到 ContentType;同一字段可被多个模板复用 | templateId |
⚠️ 字段对外稳定标识是
attributeKey,不是fieldId。fieldId在每个环境独立生成、跨环境不通用,仅用于环境内寻址;对外稳定引用请始终使用attributeKey,不要跨环境复用fieldId。读写值的入参也都是attributeKey。
字段「挂在哪个资源类型上」由字段自身的 applicableTargets 集合决定——非空、可同时包含 DOCUMENT 与 CONTAINER。一个字段可同时给文档与容器用;从字段定义看 applicableTargets,不从 ContentType 反推。
一、读字段值
字段值按 (resourceType, resourceId) 寻址,单资源一次取回全部已设置值。
单资源(GET /v1/metadata/values/get)
bash
curl -X GET 'https://api.atkonbase.example.com/v1/metadata/values/get?resourceType=DOCUMENT&resourceId=${docId}' \
-H 'Authorization: Bearer ${accessToken}'resourceType取值DOCUMENT/CONTAINER(其它值返通用错code=1)。- 响应是字段值列表(已设置过的字段才出现):
json
{
"code": 0,
"data": [
{
"valueId": "MV1000001",
"fieldId": "F1000001",
"attributeKey": "department",
"displayName": "归属部门",
"dataType": "STRING",
"value": "engineering",
"updateTime": "2026-05-23T10:00:00.000+08:00"
},
{
"valueId": "MV1000002",
"fieldId": "F1000002",
"attributeKey": "tags",
"displayName": "标签",
"dataType": "STRING",
"value": "[\"vip\",\"premium\"]",
"updateTime": "2026-05-23T10:00:00.000+08:00"
}
]
}- ⚠️
dataType与字段定义里的valueType是同一套枚举,只是出现位置不同——读值响应叫dataType,schema 定义叫valueType,请勿混用。完整说明见字段值编码契约 §二。 - ⚠️
value是字符串原文,MULTI 字段是 JSON 数组字符串。解析必须按(valueType, cardinality)选策略,若一律按字符串解析,会在 MULTI / INTEGER / DECIMAL 等场景出错——见字段值编码契约 §一。 - ⚠️ 「从未写入」与「写入后又清空」是两种状态:从未写入的字段不出现在列表里(不是
value=null)。集成方处理「未设置」时按「列表里没有该attributeKey」判定,不要假设一定有项。 - 字段定义
optionSourceType=TERM_SET(词表字段)时,响应项额外携带termLabel——词表条目对应的可读标签;按词表展示的 UI 直接渲染该字段,不要拿value(词表条目 code)当展示名。
批量(POST /v1/metadata/values/getBatch)
单一 resourceType + 一组 resourceId(单批最多 200 个),一次返回各资源的值:
bash
curl -X POST 'https://api.atkonbase.example.com/v1/metadata/values/getBatch' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"resourceType": "DOCUMENT",
"resourceIds": ["${docId1}", "${docId2}", "${docId3}"]
}'响应:
json
{
"code": 0,
"data": {
"items": [
{ "resourceId": "${docId1}", "values": [ /* 同单资源响应中的元素 */ ] },
{ "resourceId": "${docId2}", "values": [] }
]
}
}- 逐资源做 READ 鉴权——无权或不存在的
resourceId不出现在结果中(二者不区分,避免主体存在性枚举);可读但无值的资源以空values出现。集成方按resourceId自己匹配回去,不要假设响应顺序与请求顺序一致。
二、写字段值
按 attributeKey 写,传字符串值(按字段 valueType 编码)。
单资源(POST /v1/metadata/values/set)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/metadata/values/set' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"resourceType": "DOCUMENT",
"resourceId": "${docId}",
"values": {
"department": "engineering",
"tags": "[\"vip\",\"premium\"]",
"publishedAt": "2026-05-23T10:00:00+08:00"
},
"partialUpdate": true
}'values是attributeKey → 编码后字符串的对象;编码规则见字段值编码契约。partialUpdate:true(默认)——仅校验 / 写入values中出现的字段,未出现的字段保持原值;适合「补充几个字段」「修改某个字段」。false——全量校验语义;该 ContentType 上所有必填字段必须出现在values中,否则拒收;适合「重新整段填一遍」「保证必填齐全」。
响应原样回返写入后该资源的字段值列表(与读响应同形态),便于客户端校对。
批量同值(POST /v1/metadata/values/batchSet)
把同一份 values 写到一组资源上(单批最多 100 个)——典型场景是给一批文档统一打标:
bash
curl -X POST 'https://api.atkonbase.example.com/v1/metadata/values/batchSet' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"resourceType": "DOCUMENT",
"resourceIds": ["${docId1}", "${docId2}"],
"values": {
"lifecycleStage": "released"
},
"partialUpdate": true
}'响应:
json
{
"code": 0,
"data": {
"totalCount": 2,
"successCount": 1,
"failureCount": 1,
"results": [
{ "resourceId": "${docId1}", "success": true },
{ "resourceId": "${docId2}", "success": false, "errorMessage": "..." }
]
}
}- 单批部分失败不滚回——
results逐项告诉成败,集成方据此重试或上报。
能力声明与访问受控字段(capabilities)
字段定义的「是否进入全文检索 / 是否可筛选 / 是否访问受控」统一由 capabilities 对象声明——键 → { effect, value } 映射:
| 能力键 | 含义 |
|---|---|
SEARCHABLE | 字段值是否进入全文检索 |
FILTERABLE | 字段是否可作筛选条件 |
ACCESS_CONTROLLED | 字段值是否「访问受控」(受字段级权限管控) |
effect 取值 ALLOW / DENY / INHERIT;INHERIT 或缺省表示沿内容类型链继承(类型上也可声明同名能力,对该类型及其子类型下的字段统一生效),最终落系统默认。
访问受控字段的读写语义(在资源 READ / WRITE 权限基础上,叠加字段级权限):
- 读:调用方所代表的用户无该字段的字段级「读」授权时,字段值在详情、列表、结构化检索与全文搜索结果中直接不可见(列表里不会出现该项,不是值为 null)。
- 写:无字段级「写」授权时,写入 / 清空该字段值被拒,返回错误码 182012。
- 授权:由管理方按用户 / 角色 / 部门在对应资源或其上级容器上授予字段级读 / 写权限;在上级容器授予一次即对其下所有内容生效。资源创建者携带受控字段值创建资源不受限(创建后的编辑仍按授权判定)。
⚠️ 集成方对受控字段要单独按权限分支处理,不能假设「读得到就能写」,也不能假设「字段定义可见就一定读得到值」——字段定义对所有调用方可见,被遮蔽的只是值。
三、按字段筛选
「找出某容器下、类型为 X、字段 status=signed 的全部文档」——靠 documents/getPage / containers/getPage 的 metadataFilters。
查文档(POST /v1/documents/getPage)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/documents/getPage' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"containerId": "${containerId}",
"documentTypeId": "${contractDocTypeId}",
"state": "ACTIVE",
"metadataFilters": [
{ "attributeKey": "status", "operator": "EQ", "value": "signed" },
{ "attributeKey": "signedDate", "operator": "RANGE", "rangeFrom": "2026-01-01", "rangeTo": "2026-06-30" }
],
"pageSize": 20,
"pageNumber": 1
}'metadataFilters中每项三段式:attributeKey+operator+ 值字段(按 operator 选)。- 多个 filter 之间是 AND 关系。
- operator 与值字段对应:
| operator | 用哪个值字段 | 含义 |
|---|---|---|
EQ | value | 等于(精确匹配) |
IN | values(数组) | 在集合内 |
RANGE | rangeFrom + rangeTo | 闭区间 |
PREFIX | value | 前缀匹配(仅 STRING) |
CONTAINS | value | 包含子串(仅 STRING) |
- 顶层非元数据筛选条件直接展平:
containerId/documentTypeId/state/containerNameLike(与containerId同传时是 AND 交集)。 - ⚠️ 「不可筛选」字段:字段定义
filterable=false的字段不参与筛选——传入也会被忽略或拒绝。/v1/documents/getFilterFields?documentTypeId=...返回的列表才是该类型可筛选字段的真值清单,集成方搭筛选 UI 应以它为准、不要拿全字段定义反推。
查容器(POST /v1/containers/getPage)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/containers/getPage' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"parentId": "${parentContainerId}",
"containerTypeId": "${projectContainerTypeId}",
"metadataFilters": [
{ "attributeKey": "region", "operator": "IN", "values": ["cn", "us"] }
]
}'containerTypeId 顶层过滤 + metadataFilters 数组结构与文档侧一致,不再赘述。
取「可筛选字段」清单(GET /v1/documents/getFilterFields)
筛选 UI 的源头:
bash
curl -X GET 'https://api.atkonbase.example.com/v1/documents/getFilterFields?documentTypeId=${typeId}' \
-H 'Authorization: Bearer ${accessToken}'响应是 FilterFieldDTO[],对每个可筛选字段给出 attributeKey / displayName / valueType / cardinality / optionSourceType / options(INLINE 时)/ termSetCode 等关键事实。容器侧目前无对应端点,需要时按 fieldDefinitions/getPage + applicableTargets=CONTAINER + filterable=true 自行筛取。
四、取「表单 Schema」(按类型 / 按资源)
集成方搭表单 UI 时,要知道「这一类内容应渲染哪些字段、字段在哪个模板分组、字段当前值是多少」——靠 getFormSchema 三个变体。
按 ContentType(GET /v1/contentTypes/getFormSchema)
「这一类内容应该有哪些字段」(不含资源当前值,适合新建表单):
bash
curl -X GET 'https://api.atkonbase.example.com/v1/contentTypes/getFormSchema?typeId=${typeId}&targetKind=DOCUMENT' \
-H 'Authorization: Bearer ${accessToken}'响应:
json
{
"code": 0,
"data": {
"typeId": "${typeId}",
"typeName": "合同",
"templateGroups": [
{
"templateId": "T1000001",
"templateName": "基础字段",
"source": "TYPE",
"fields": [
{
"attributeKey": "title",
"displayName": "标题",
"valueType": "STRING",
"cardinality": "SINGLE",
"required": true,
"capabilities": {},
"sortOrder": 1,
"sourceType": "TYPE"
}
]
}
],
"legacyFields": []
}
}templateGroups[].source取值:TYPE(类型自身绑定的模板)/TYPE_DIRECT(类型直接挂的字段)/INHERITED(从父类型继承)/RESOURCE(资源级模板,仅按资源查时出现)/LEGACY(遗留字段分组,仅按资源查时出现)。field.sourceType取值:TYPE/TEMPLATE/LEGACY,标识该字段是哪一层来的。TEMPLATE时sourceTemplateId/sourceTemplateName有值。
按 Document(GET /v1/documents/getFormSchema)
「这个文档当前的表单」(含当前值、含资源级模板字段、含遗留字段):
bash
curl -X GET 'https://api.atkonbase.example.com/v1/documents/getFormSchema?docId=${docId}' \
-H 'Authorization: Bearer ${accessToken}'字段项除上面的 schema 信息外还会带 value / values(当前值,SINGLE / MULTI 取一)。
按 Container(GET /v1/containers/getFormSchema)
同理,按容器返回:
bash
curl -X GET 'https://api.atkonbase.example.com/v1/containers/getFormSchema?containerId=${containerId}' \
-H 'Authorization: Bearer ${accessToken}'⚠️ 「遗留字段」(
legacyFields/sourceType=LEGACY)是历史上挂在资源上、但当前 ContentType / Template 已不再声明的字段;前端表单不应主动暴露它们供修改,只读展示即可。集成方据此判断「这个字段是历史包袱、不要继续往里写新值」。
五、查字段定义与内容类型(只读)
V1 仅暴露字段定义与内容类型的查询端点;创建 / 修改 / 删除属 Console 管理动作,不对外。
字段定义分页查询(POST /v1/fieldDefinitions/getPage)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/fieldDefinitions/getPage' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"targetKind": "DOCUMENT",
"status": "ACTIVE",
"pageSize": 50,
"pageNumber": 1
}'- 过滤位:
targetKind(按applicableTargets包含该 kind 过滤)/status(ACTIVE/DEPRECATED/DISABLED)/valueType/displayName(模糊)。 - 字段字段含义(
FieldDefinitionDTO):
| 字段 | 含义 |
|---|---|
attributeKey | 业务稳定标识,跨环境不变 |
valueType | STRING / INTEGER / DECIMAL / BOOLEAN / DATE / DATETIME |
cardinality | SINGLE / MULTI |
applicableTargets | 适用资源种类集合,非空 |
required | 是否必填(partialUpdate=false 全量校验时强制) |
defaultValue | 默认值(按 valueType 编码的字符串;null = 无默认值) |
validationRule | 校验规则字符串(按 valueType 编码——见字段值编码契约 §校验规则) |
capabilities | 能力声明映射(SEARCHABLE / FILTERABLE / ACCESS_CONTROLLED → { effect, value },空 = 继承 / 系统默认,见上文 §能力声明);筛选 UI 真值参考 getFilterFields 端点而非此声明 |
listVisible | 列表默认是否展示 |
system | 是否系统字段(只读、不可写;如标题、文件名、文档类型等) |
status | ACTIVE 正常 / DEPRECATED 已弃用(仍可读不应继续用)/ DISABLED 禁用 |
optionSourceType | NONE 无选项约束 / INLINE 内联选项 / TERM_SET 词表 |
optionsList | INLINE 时的选项数组 |
⚠️ 内置字段 vs 业务字段:
system=true的字段(如title/originalFilename/mimeType/documentTypeId)是平台内置、由上传 / 创建端点等通道写入,metadata/values/set不接受这些字段的写入——它们是文档自身属性,不是 metadata 槽。在集成方表单里也应作为只读字段呈现。
字段定义详情(GET /v1/fieldDefinitions/get?fieldId=...)
按 fieldId 取详情;对外稳定标识仍是 attributeKey,fieldId 用于在当前环境内精确定位某条字段定义(如从分页列表项跳转详情)。
内容类型分页 / 树 / 按 code 查(/v1/contentTypes/*)
| 端点 | 用途 |
|---|---|
POST /v1/contentTypes/getPage | 分页查询;过滤位 targetKind / parentTypeId / status / name(模糊) |
GET /v1/contentTypes/getTree?targetKind=DOCUMENT | 按 targetKind 取类型树,含父子层级 |
GET /v1/contentTypes/get?typeId=... | 按 typeId 取详情 |
GET /v1/contentTypes/getByCode?code=...&targetKind=DOCUMENT | 按 (code, targetKind) 取详情;code 是跨环境稳定标识(租户内唯一),跨环境引用请用此端点,不要写死 typeId |
ContentTypeDTO 上的 maxVersionCount / retentionDays 是版本保留策略——含义与 null 语义见文档与版本模式篇。
常见坑
- ⚠️ 跨环境复用
fieldId/typeId:二者每环境独立生成,跨环境直接引用会失效。规避:fieldDef用attributeKey、contentType用getByCode取。 - ⚠️ 响应字段叫
dataType、定义叫valueType:同一枚举不同字段名,写解析时混用导致取不到。规避:按位置看名字,统一映射到同一内部枚举再消费。 - ⚠️
MULTI字段当字符串解析:MULTI 值是 JSON 数组字符串,直接.value.split(",")会切错(带引号 / 转义)。规避:按cardinality=MULTI走JSON.parse。 - ⚠️ 「未设置」误判为 null:从未写入的字段不出现在列表中,不是
value=null。规避:按attributeKey在列表中存在性判,而非取出后判 null。 - ⚠️ 访问受控字段的值在普通读响应里看不见:以为字段定义里有就一定能读到值。规避:受控字段值的可见性由该用户的字段级读授权决定,UI 上对受控字段单独走「未授权时隐藏」分支,缺授权时引导联系管理方授予。
- ⚠️
partialUpdate=false漏必填:切换到全量语义却没给必填字段,整批拒收。规避:默认true改字段;切false前用getFormSchema列出该 type 全部 required 字段。 - ⚠️
batchSet部分失败被忽略:successCount < totalCount但调用方按总返回成功处理。规避:始终读results[].success,按 resourceId 收集失败项重试或上报。 - ⚠️ 拿全字段定义搭筛选 UI:把
filterable=false的字段也放进筛选条件,传上去被忽略 / 拒绝。规避:搭文档筛选 UI 用getFilterFields;搭容器筛选 UI 按fieldDefinitions/getPage+filterable=true自行筛取。 - ⚠️ 给 system 字段写 metadata:把
title/originalFilename这些塞进values/set的values。规避:识别system=true字段,从 metadata 写入清单里剔除,改走对应业务端点(如documents/update改 title)。 - ⚠️
DEPRECATED字段还在新数据上继续用:字段已弃用但表单仍允许写入。规避:表单按status=ACTIVE过滤可写字段;DEPRECATED字段只读旧数据。
下一步
- 看字段值具体怎么按类型编码 / null 三态语义 → 字段值编码契约
- 版本保留策略(
maxVersionCount/retentionDays)与版本链 → 文档与版本模式篇 - 给文档 / 容器授权 → ACL 与继承模式篇
- V1 端点契约 + Try-it → 接口参考