Skip to content

容器模型与层级模式篇

目标:让集成方组织容器树、按层级取祖先 / 子代、跨层移动 / 重命名容器、用 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_REQUIREDcode=1 段,msg 明确指出缺失)。
  • 状态切换不通过 update——归档 / 删除 / 恢复有专用端点(见 §六)。
  • ⚠️ aclInheritEnabled 的修改是即时生效的继承入口开关——切到 false 后,本容器及其下后续新建子资源不再从父接收继承(已存在的 ACL 不动)。详见 §五。

删除容器(GET /v1/containers/delete?containerId=...

statusTRASHED,进入回收站(见 §六)。

三、按层级查询

子容器(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/updatename 即可,没有独立端点。

⚠️ 重命名 / 移动不重写文档归属 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

  • archivestatusARCHIVED。对默认查询(未带 status 的列表)不可见,但仍可读、文档可读 / 可改、ACL 仍生效。归档不删字节
  • unarchive:还原回 ACTIVE

删除(GET /v1/containers/delete?containerId=...

  • 容器及其全部后代容器与文档整树进入回收站(TRASHED)。
  • 也可批量:POST /v1/containers/batchDeletecontainerIds[] 单批 ≤ 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/set body resourceType=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
  • ⚠️ purgedelete 双确认对话框:UI 把两者混用。规避:delete 可恢复、purge 不可恢复——UI 用不同动作名 + 不同确认文案严格区分。
  • ⚠️ totalSizeBytes 当物理占用做计费:以为它是该容器在对象存储里实际占用。规避:它是逻辑量(含历史版本,不去重);计费走租户级 /v1/tenants/me/storage
  • ⚠️ 批量端点单批 > 100:传 200 条期望分两批跑。规避:batchDelete / batchPurge / batchRestore / batchMove 都是单批 ≤ 100,集成方自行切片。
  • ⚠️ 容器重命名后下游索引重建:以为 ID 也变了。规避:containerId 不变,下游业务无需重建关联。
  • ⚠️ 顶层容器 parentId 期望是某个稳定值:用某常量做「这是顶层容器」判断。规避:顶层容器的 parentId 为空 / 缺省(顶层之上没有可引用的 ID),按「parentId 为空」判定即可。

下一步