# 标签系统规范 > 版本: 1.1 > 最后更新: 2026-06-11 ## 1. 概述 标签系统提供三个核心能力: 1. **标签管理**:通过标签组 + 树形标签组织分类体系 2. **导入导出**:将标签组及其标签树导出为 JSON 文件,支持带冲突策略的导入 3. **打标关联**:对技能(Skill)等业务对象进行标签标注,支持多标签、跨标签组、非叶子节点打标 ## 2. JSON 格式规范 ### 2.1 顶层结构 ```json { "version": "1.0", "exportedAt": "2026-06-11T12:00:00", "groups": [ ... ] } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `version` | string | 是 | 格式版本号,当前为 `"1.0"` | | `exportedAt` | string | 是 | ISO 8601 格式的导出时间 | | `groups` | array | 是 | 标签组数组,见 2.2 | ### 2.2 标签组结构 (TagGroupExport) ```json { "name": "标签组名称", "description": "可选描述", "sortOrder": 0, "tags": [ ... ] } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `name` | string | 是 | 标签组名称,作为导入时的唯一匹配键 | | `description` | string | 否 | 标签组描述,可为 null 或省略 | | `sortOrder` | integer | 否 | 排序序号,默认 0 | | `tags` | array | 否 | 顶层标签节点数组,见 2.3 | **注意**:`tags` 数组中的是标签组根节点下的直接子标签,不包含标签组同名的根节点本身。 ### 2.3 标签节点结构 (TagNodeExport) 标签节点采用**递归嵌套**结构,自然表达树形层级: ```json { "name": "标签名称", "sortOrder": 0, "children": [ { "name": "子标签", "sortOrder": 0, "children": [] } ] } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `name` | string | 是 | 标签名称 | | `sortOrder` | integer | 否 | 同级排序序号,默认 0 | | `children` | array | 否 | 子标签数组,递归嵌套。无子节点时可省略或为空数组 | ### 2.4 完整示例 ```json { "version": "1.0", "exportedAt": "2026-06-11T14:30:00", "groups": [ { "name": "情感分类", "description": "文本情感极性标签", "sortOrder": 0, "tags": [ { "name": "正面", "sortOrder": 0, "children": [ { "name": "喜悦", "sortOrder": 0, "children": [ { "name": "狂喜", "sortOrder": 0 }, { "name": "微笑", "sortOrder": 1 } ] }, { "name": "满意", "sortOrder": 1 } ] }, { "name": "负面", "sortOrder": 1, "children": [ { "name": "愤怒", "sortOrder": 0 }, { "name": "失望", "sortOrder": 1 } ] }, { "name": "中性", "sortOrder": 2 } ] }, { "name": "主题分类", "description": null, "sortOrder": 1, "tags": [ { "name": "科技", "sortOrder": 0, "children": [ { "name": "人工智能", "sortOrder": 0, "children": [ { "name": "大语言模型", "sortOrder": 0 }, { "name": "计算机视觉", "sortOrder": 1 } ] }, { "name": "云计算", "sortOrder": 1 } ] }, { "name": "财经", "sortOrder": 1 }, { "name": "体育", "sortOrder": 2 } ] } ] } ``` ## 3. 导出行为 - **范围**:导出所有标签组及其全部标签树 - **触发**:一键导出,浏览器下载 JSON 文件 - **文件名**:`tags-export-{YYYY-MM-DD}.json` - **编码**:UTF-8 - **格式化**:2 空格缩进,便于人工阅读和编辑 - **数据完整性**:导出数据不包含数据库 ID,仅包含名称和层级关系 ## 4. 导入行为 ### 4.1 导入流程 1. 用户选择 JSON 文件 2. 前端解析文件,展示标签组列表及冲突状态 3. 用户对冲突标签组选择处理策略 4. 确认导入,后端执行并返回结果 ### 4.2 冲突检测 以标签组 **名称** (`name`) 作为匹配键。若导入文件中的标签组名称与系统中已有标签组名称相同,则判定为冲突。 ### 4.3 冲突处理策略 | 策略 | 值 | 说明 | |------|---|------| | 覆盖 | `overwrite` | 删除原有标签组及全部标签,用导入数据重新创建 | | 合并 | `merge` | 逐层按名称匹配:同名标签保留,新标签追加。详见 4.4 | | 跳过 | `skip` | 跳过该标签组,不做任何修改 | | 新建 | `create` | 无冲突时的默认行为,直接创建新标签组 | ### 4.4 合并策略详解 合并采用**递归名称匹配**算法: ``` 对于导入标签树的每一层节点: 1. 在目标标签组的同级子节点中查找同名标签 2. 如果找到同名标签 → 保留该标签,递归处理其子节点 3. 如果未找到 → 创建新标签节点 ``` **示例**(多级标签合并): 现有标签组 "情感分类": ``` 正面 喜悦 微笑 负面 ``` 导入合并数据: ``` 正面 喜悦 微笑 ← 同名,保留 感动 ← 新增 满意 ← 新增 负面 愤怒 ← 新增 ``` 合并结果: ``` 正面 喜悦 微笑 ← 保留(三级同名匹配) 感动 ← 新增 满意 ← 新增 负面 愤怒 ← 新增 ``` ### 4.5 导入请求格式 ```json { "data": { "version": "1.0", "exportedAt": "...", "groups": [ ... ] }, "strategies": { "冲突标签组名称": "overwrite | merge | skip", "另一个冲突标签组": "merge" } } ``` - `data`:完整的导出数据 - `strategies`:仅冲突标签组需要指定策略,无冲突的标签组自动新建 ### 4.6 导入结果 ```json { "created": 2, "overwritten": 1, "merged": 0, "skipped": 1, "details": [ "新建: 主题分类", "新建: 行业分类", "覆盖: 情感分类", "跳过: 优先级" ] } ``` ## 5. API 接口 ### 5.1 导出 ``` GET /api/tags/export ``` 响应:`Result` ### 5.2 导入 ``` POST /api/tags/import ``` 请求体:`TagImportDTO`(见 4.5) 响应:`Result`(见 4.6) ### 5.3 设置实体标签 ``` POST /api/tags/assignments ``` 请求体: ```json { "entityType": "skill", "entityId": "my-skill-folder", "tagIds": [1, 5, 12] } ``` 行为:全量替换该实体的所有标签关联。`tagIds` 为空数组时清除所有标签。 ### 5.4 获取实体标签 ``` GET /api/tags/assignments?entityType=skill&entityId=my-skill-folder ``` 响应:`Result>` 假设标签树结构如下: ``` 情感分类 正面 ← tagId: 1 喜悦 ← tagId: 2 狂喜 ← tagId: 3 负面 ← tagId: 4 主题分类 科技 ← tagId: 10 人工智能 ← tagId: 11 大语言模型 ← tagId: 12 ``` 为一个 Skill 同时打上"正面"(非叶子节点)、"狂喜"(三级叶子)、"大语言模型"(三级叶子)的响应示例: ```json { "data": [ { "tagId": 1, "tagName": "正面", "groupId": 1, "groupName": "情感分类" }, { "tagId": 3, "tagName": "狂喜", "groupId": 1, "groupName": "情感分类" }, { "tagId": 12, "tagName": "大语言模型", "groupId": 2, "groupName": "主题分类" } ] } ``` ### 5.5 获取所有标签组及其标签(用于标签选择器) ``` GET /api/tags/all-with-tags ``` 响应:`Result }>>` ## 6. 打标规范 ### 6.1 数据模型 打标通过 `tag_assignment` 表实现多对多关联: | 字段 | 类型 | 说明 | |------|------|------| | `id` | BIGINT | 主键 | | `entity_type` | VARCHAR(32) | 业务对象类型,如 `"skill"` | | `entity_id` | VARCHAR(128) | 业务对象标识,如 Skill 的 folderName | | `tag_id` | BIGINT | 关联的标签 ID | | `created_at` | TIMESTAMP | 创建时间 | **唯一约束**:`(entity_type, entity_id, tag_id)` — 同一实体的同一标签不可重复关联。 ### 6.2 打标原则 1. **多标签**:一个实体可以打 0 个、1 个或多个标签 2. **同组多标**:可以在同一个标签组内选择多个标签(如同时标记"正面"和"中性") 3. **跨组打标**:可以同时选择来自不同标签组的标签(如"正面"+"科技") 4. **非叶子可标**:树形标签中的任意层级节点都可以被选中,不限于叶子节点 5. **全量替换**:每次保存时提交全部标签 ID 列表,后端先清除旧关联再创建新关联 ### 6.3 支持的实体类型 | entityType | entityId 来源 | 说明 | |------------|--------------|------| | `skill` | folderName | 技能 | | (预留) | — | 未来可扩展智能体、工作流等 | ### 6.4 标签选择器交互 1. 弹窗展示所有标签组,每组可展开/折叠 2. 标签树中每个节点都可勾选(含非叶子节点) 3. 支持关键词搜索标签 4. 已选标签在顶部以 Tag 形式展示,可移除 5. 确认保存后刷新列表数据 ## 7. 设计原则 1. **名称即标识**:导出数据不包含数据库 ID,以名称作为匹配键,确保跨环境可移植 2. **树形嵌套**:标签树使用递归 children 嵌套,而非 parentId 扁平结构,直观且便于编辑 3. **安全优先**:冲突时默认跳过,避免误操作覆盖数据 4. **幂等安全**:合并策略是增量操作,重复导入不会产生重复数据(同名标签不会重复创建) 5. **通用打标**:打标机制通过 `entityType + entityId` 实现通用多对多关联,不与特定业务耦合 6. **全量替换**:打标保存采用全量替换策略,前端提交完整标签列表,语义简洁无歧义