# 标签系统规范

> 版本: 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<TagExportDTO>`

### 5.2 导入

```
POST /api/tags/import
```

请求体：`TagImportDTO`（见 4.5）
响应：`Result<TagImportResultDTO>`（见 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<List<TagBriefVO>>`

假设标签树结构如下：
```
情感分类
  正面          ← 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<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. **全量替换**：打标保存采用全量替换策略，前端提交完整标签列表，语义简洁无歧义
