外观
容器模型与层级模式篇
目标:让集成方组织容器树、按层级取祖先 / 子代、跨层移动 / 重命名容器、用
aclInheritEnabled控制 ACL 继承入口、统计容器下的子内容总量,并理解容器生命周期(归档 / 回收站 / 彻底清除)与文档生命周期的关系。不在本篇范围:文档与版本(见 文档与版本模式篇);ACL 继承解析规则与 AclPolicy(见 ACL 与继承模式篇);容器上的字段值读写(见 元数据与 schema 模式篇);分享;错误码完整码表(见错误码参考)。
本篇是 领域模型 §Container 的纵深展开。概念页讲「容器是内容的归属单元、ACL 继承的起点」的模型,本篇讲层级查询、移动、断继承、生命周期投影的具体接口与坑。
一、容器层级模型(用前必读)
- 容器组织成租户内的层级树。一个租户有一棵或多棵根容器树。
ContainerDTO.parentId为空或缺省表示该容器是顶层容器(对集成方而言它就是该租户视角下的「根」)。- 一个 Document 必属于某一个具体 Container(
Document.containerId不可为空)。Document 不能挂在根级,必须在某个业务容器内。 - 容器之间是父子关系;同一父容器下子容器名同租户内是否唯一不强制,但集成方业务建模通常自行保证唯一性(按业务规则)。
容器树按集成方的业务结构组织(如「客户 / 项目 / 合同」三层、或「部门 / 项目库 / 文件夹」),怎么建是业务建模决策——平台不规定层级形态。
二、容器 CRUD
创建容器(POST /v1/containers/create)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/containers/create' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"parentId": "${parentContainerId}",
"name": "2026Q2 合同",
"containerTypeId": "${projectContainerTypeId}",
"description": "Q2 合同归集",
"aclInheritEnabled": true,
"sortOrder": 1
}'parentId缺省 / 空白时回退为顶层容器——此时新建出来的是「顶层容器」。containerTypeId可选;不传时回退租户根容器类型。容器类型同样来自 ContentType 体系,按targetKind=CONTAINER取——见 元数据与 schema 模式篇 §五。aclInheritEnabled默认true(从父接收 ACL 继承);设为false即断继承(见 §五)。
响应是 ContainerDTO:
json
{
"code": 0,
"data": {
"containerId": "C1000001",
"parentId": "${parentContainerId}",
"parentName": "客户 A",
"containerTypeId": "${projectContainerTypeId}",
"containerTypeName": "项目",
"name": "2026Q2 合同",
"status": "ACTIVE",
"aclInheritEnabled": true,
"createBy": "U1000001",
"createByName": "alice(Alice)",
"createTime": "2026-05-23T10:00:00.000+08:00"
}
}查容器详情(GET /v1/containers/get?containerId=...)
返回单个 ContainerDTO,含 effectivePermissions(当前主体对该容器的有效权限投影,便于前端按位渲染 UI)。
更新容器(POST /v1/containers/update)
按 containerId 定位更新:
bash
curl -X POST 'https://api.atkonbase.example.com/v1/containers/update' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"containerId": "${containerId}",
"name": "2026Q2 合同(修订)",
"description": "Q2 合同归集 · 含变更",
"sortOrder": 2,
"aclInheritEnabled": false
}'containerId未传时返回业务码CONTAINER_ID_REQUIRED(code=1段,msg明确指出缺失)。- 状态切换不通过 update——归档 / 删除 / 恢复有专用端点(见 §六)。
- ⚠️
aclInheritEnabled的修改是即时生效的继承入口开关——切到false后,本容器及其下后续新建子资源不再从父接收继承(已存在的 ACL 不动)。详见 §五。
删除容器(GET /v1/containers/delete?containerId=...)
把 status 置 TRASHED,进入回收站(见 §六)。
三、按层级查询
子容器(GET /v1/containers/getChildren)
bash
curl -X GET 'https://api.atkonbase.example.com/v1/containers/getChildren?containerId=${parentId}' \
-H 'Authorization: Bearer ${accessToken}'- 返回直接子容器列表(不递归)。
- ⚠️
containerId缺省时端点返回当前租户可见的顶层容器列表——用此路径作「拉根」入口,不要尝试用占位 ID 模拟。 - 响应中的
hasChildren是布尔标志:表示该容器下是否存在ACTIVE/ARCHIVED状态的子容器,仅 list 类端点(/getChildren)填充,detail端点(/get)保持 null——以避免详情端点承担额外的子节点统计开销。前端做「可展开节点」徽标时用列表端点的此字段,不要逐节点点详情判定。
祖先链(GET /v1/containers/getAncestors?containerId=...)
返回从根方向到自身的祖先链(不含根级以上的层级)——例如 客户A → 项目X → 合同2026Q2 → 当前容器。 集成方做面包屑导航时直接拿数组渲染即可。
bash
curl -X GET 'https://api.atkonbase.example.com/v1/containers/getAncestors?containerId=${containerId}' \
-H 'Authorization: Bearer ${accessToken}'- 顶层容器(
parentId=null)的祖先链是空数组[]。 - ⚠️ 不含根级以上的层级:响应里不会出现顶层容器之上的任何节点——面包屑首项即顶层容器自身。
分页查容器(POST /v1/containers/getPage)
按 parentId / containerTypeId / metadataFilters 分页查(同级筛选,不递归):
bash
curl -X POST 'https://api.atkonbase.example.com/v1/containers/getPage' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"parentId": "${parentId}",
"containerTypeId": "${projectContainerTypeId}",
"metadataFilters": [
{ "attributeKey": "region", "operator": "EQ", "value": "cn" }
],
"pageSize": 20,
"pageNumber": 1
}'metadataFilters 同文档侧——见 元数据与 schema 模式篇 §三。
容器统计(GET /v1/containers/stats?containerId=...)
json
{
"code": 0,
"data": {
"documentCount": 12,
"totalDocumentCount": 47,
"totalSizeBytes": 4823551,
"childContainerCount": 3,
"totalContainerCount": 8
}
}| 字段 | 含义 |
|---|---|
documentCount | 直接子文档数(不递归) |
totalDocumentCount | 递归文档总数(自身 + 所有后代容器内) |
totalSizeBytes | 逻辑内容总量——所有版本字节累加(含历史版本,不做去重);与对象存储物理占用不等同 |
childContainerCount | 直接子容器数 |
totalContainerCount | 递归子容器总数 |
⚠️
totalSizeBytes是逻辑量(所有版本累加),不反映去重后的物理占用。租户级实际存储计费应读/v1/tenants/me/storage,不要拿单容器totalSizeBytes累加去做租户总量估算。
四、容器移动 / 跨层重组
单条移动(POST /v1/containers/move)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/containers/move?containerId=${containerId}&targetParentId=${newParentId}' \
-H 'Authorization: Bearer ${accessToken}'targetParentId缺省 / 空白时移回顶层(成为顶层容器)。- ⚠️ 禁止把容器移到自身的后代下:会形成循环树,平台拒绝(业务错)。规避:移动前先取
getAncestors(target),确认target不在current的祖先链。
批量移动(POST /v1/containers/batchMove)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/containers/batchMove' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'Content-Type: application/json' \
-d '{
"containerIds": ["${c1}", "${c2}"],
"targetParentId": "${newParentId}"
}'- 单批 ≤ 100 条;可传空数组(原样返回
success=[], failed=[],不报错)。 - 响应
BatchResultDTO<String>:success/failed列表,逐项给原因。
重命名
直接 containers/update 改 name 即可,没有独立端点。
⚠️ 重命名 / 移动不重写文档归属 ID:Document 仍以
containerId寻址、文档自身的containerId不变——下游业务无需重新建索引。containerName/parentName等冗余字段在下次读取时刷新。
五、ACL 继承入口(断继承)
容器是 ACL 继承的起点;继承的「断 vs 通」由容器的 aclInheritEnabled 控制。
| 取值 | 含义 |
|---|---|
true(默认) | 通——本容器从父接收继承;上层授权沿树向下流到本容器 + 所有后代 |
false | 断——切断本容器整棵子树的继承入口;上层授权不再流入;新建子资源不从父继承(改为给创建者授全部位) |
切到 false 的典型场景:「这是一个敏感子树,上面任何 ACL Policy 都不许波及」。完整解析规则、新建 vs 重继承的差异、传播深度上限 100 等见 ACL 与继承模式篇 §三。
⚠️
aclInheritEnabled不回填到子容器:切了根容器的false,子容器自身的aclInheritEnabled字段值不变——它表达的是「该容器是否从父接收」,断在哪一层就在哪一层切断。false在子树范围内生效靠的是「继承传播经过该层被截断」,不是字段沿树同步。
六、容器生命周期
容器有 status 状态:ACTIVE / ARCHIVED / TRASHED。
归档 / 取消归档(/v1/containers/archive · /unarchive)
archive:status→ARCHIVED。对默认查询(未带status的列表)不可见,但仍可读、文档可读 / 可改、ACL 仍生效。归档不删字节。unarchive:还原回ACTIVE。
删除(GET /v1/containers/delete?containerId=...)
- 容器及其全部后代容器与文档整树进入回收站(
TRASHED)。 - 也可批量:
POST /v1/containers/batchDelete,containerIds[]单批 ≤ 100。
回收站
| 端点 | 用途 |
|---|---|
POST /v1/containers/trash/getPage | 分页查已删除容器(含 purgeAt) |
GET /v1/containers/restore?containerId=... | 单条恢复 |
POST /v1/containers/batchRestore | 批量恢复 |
GET /v1/containers/purge?containerId=... | 单条彻底清除(不可恢复,连同子树文档一并物理清除) |
POST /v1/containers/batchPurge | 批量彻底清除 |
⚠️ 容器侧
restore/purge/batchRestore/batchPurge不在/trash/子路径下——只有getPage是/v1/containers/trash/getPage,其它是/v1/containers/restore//purge等同级路径。这与文档侧(/v1/documents/trash/restore//trash/purge)形态不对称,按上表实际路径调用,不要凭文档侧直觉拼 URL。
purgeAt由租户级回收站保留天数(可配)+ 进入回收站时间算得;策略调整下次响应即生效。- ⚠️
purge是不可逆的物理清除:连同该容器整棵子树(后代容器 + 所有文档及全部版本)一并清除。UI 务必二次确认;批量 purge 尤其谨慎。 - 恢复时若原父容器已被 purge 或 TRASHED,恢复目标会回退到一个合法父容器(具体策略与失败原因在响应
failed[].errorMessage中给出);如确需精确恢复到指定父容器,恢复后再containers/move。
七、容器上挂字段值与表单
容器也是字段值的挂载目标:
- 读:
GET /v1/metadata/values/get?resourceType=CONTAINER&resourceId=${containerId} - 写:
POST /v1/metadata/values/setbodyresourceType=CONTAINER - 表单 Schema:
GET /v1/containers/getFormSchema?containerId=${containerId}
具体读写与字段定义查询参见 元数据与 schema 模式篇。容器类型字段(applicableTargets 包含 CONTAINER)通常用于业务建模属性——如「项目编号」「客户区域」「合同金额上限」等。
常见坑
- ⚠️ 试图把容器移到自身后代下:循环树,平台拒绝。规避:移动前用
getAncestors(target)排除。 - ⚠️ 找不到「拉根」入口:以为要传特定占位 ID 才能拉顶层。规避:
getChildren不带containerId即返回顶层;不要构造占位的根级 ID。 - ⚠️
getAncestors找根级以上节点:以为响应里会出现顶层之上的节点用于面包屑首项。规避:响应不含顶层之上的层级;面包屑首项就是「顶层容器自身」,UI 上「全部容器 / 根」这类入口由集成方自行拼。 - ⚠️
aclInheritEnabled=false改后期望子容器字段同步:以为子容器的字段也会被改成false。规避:断继承靠传播截断,不靠字段沿树回填——子容器字段值不变。 - ⚠️ 误以为删除只是软删除、永不真正清理:以为
delete只把状态置 TRASHED 永不真删。规避:回收站到期会按回收站保留天数(可配)自动purge;要永久保留改用archive。 - ⚠️
purge当delete双确认对话框:UI 把两者混用。规避:delete可恢复、purge不可恢复——UI 用不同动作名 + 不同确认文案严格区分。 - ⚠️
totalSizeBytes当物理占用做计费:以为它是该容器在对象存储里实际占用。规避:它是逻辑量(含历史版本,不去重);计费走租户级/v1/tenants/me/storage。 - ⚠️ 批量端点单批 > 100:传 200 条期望分两批跑。规避:
batchDelete/batchPurge/batchRestore/batchMove都是单批 ≤ 100,集成方自行切片。 - ⚠️ 容器重命名后下游索引重建:以为 ID 也变了。规避:
containerId不变,下游业务无需重建关联。 - ⚠️ 顶层容器
parentId期望是某个稳定值:用某常量做「这是顶层容器」判断。规避:顶层容器的parentId为空 / 缺省(顶层之上没有可引用的 ID),按「parentId为空」判定即可。
下一步
- 容器→文档继承解析、断继承在 ACL 解析中的语义、AclPolicy → ACL 与继承模式篇
- 文档版本链与生命周期 → 文档与版本模式篇
- 容器上挂字段值、按字段筛选容器 → 元数据与 schema 模式篇
- V1 端点契约 + Try-it → 接口参考