tag-system-spec.md 9.7 KB

标签系统规范

版本: 1.1 最后更新: 2026-06-11

1. 概述

标签系统提供三个核心能力:

  1. 标签管理:通过标签组 + 树形标签组织分类体系
  2. 导入导出:将标签组及其标签树导出为 JSON 文件,支持带冲突策略的导入
  3. 打标关联:对技能(Skill)等业务对象进行标签标注,支持多标签、跨标签组、非叶子节点打标

2. JSON 格式规范

2.1 顶层结构

{
  "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)

{
  "name": "标签组名称",
  "description": "可选描述",
  "sortOrder": 0,
  "tags": [ ... ]
}
字段 类型 必填 说明
name string 是 标签组名称,作为导入时的唯一匹配键
description string 否 标签组描述,可为 null 或省略
sortOrder integer 否 排序序号,默认 0
tags array 否 顶层标签节点数组,见 2.3

注意:tags 数组中的是标签组根节点下的直接子标签,不包含标签组同名的根节点本身。

2.3 标签节点结构 (TagNodeExport)

标签节点采用递归嵌套结构,自然表达树形层级:

{
  "name": "标签名称",
  "sortOrder": 0,
  "children": [
    {
      "name": "子标签",
      "sortOrder": 0,
      "children": []
    }
  ]
}
字段 类型 必填 说明
name string 是 标签名称
sortOrder integer 否 同级排序序号,默认 0
children array 否 子标签数组,递归嵌套。无子节点时可省略或为空数组

2.4 完整示例

{
  "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 导入请求格式

{
  "data": {
    "version": "1.0",
    "exportedAt": "...",
    "groups": [ ... ]
  },
  "strategies": {
    "冲突标签组名称": "overwrite | merge | skip",
    "另一个冲突标签组": "merge"
  }
}
  • data:完整的导出数据
  • strategies:仅冲突标签组需要指定策略,无冲突的标签组自动新建

4.6 导入结果

{
  "created": 2,
  "overwritten": 1,
  "merged": 0,
  "skipped": 1,
  "details": [
    "新建: 主题分类",
    "新建: 行业分类",
    "覆盖: 情感分类",
    "跳过: 优先级"
  ]
}

5. API 接口

5.1 导出

GET /api/tags/export

响应:Result<TagExportDTO>

5.2 导入

POST /api/tags/import

请求体:TagImportDTO(见 4.5) 响应:Result<TagImportResultDTO>(见 4.6)

5.3 设置实体标签

POST /api/tags/assignments

请求体:

{
  "entityType": "skill",
  "entityId": "my-skill-folder",
  "tagIds": [1, 5, 12]
}

行为:全量替换该实体的所有标签关联。tagIds 为空数组时清除所有标签。

5.4 获取实体标签

GET /api/tags/assignments?entityType=skill&entityId=my-skill-folder

响应:Result<List<TagBriefVO>>

假设标签树结构如下:

情感分类
  正面          ← tagId: 1
    喜悦        ← tagId: 2
      狂喜      ← tagId: 3
  负面          ← tagId: 4
主题分类
  科技          ← tagId: 10
    人工智能    ← tagId: 11
      大语言模型 ← tagId: 12

为一个 Skill 同时打上"正面"(非叶子节点)、"狂喜"(三级叶子)、"大语言模型"(三级叶子)的响应示例:

{
  "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<List<{ id, name, tags: List<Tag> }>>

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. 全量替换:打标保存采用全量替换策略,前端提交完整标签列表,语义简洁无歧义