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