SKC智能知识管理系统-设计分享 之 知识聚类图谱

SKC 知识聚类图谱 设计文档

状态 :Accepted · 2026-07-06(头部规范化 2026-07-10) 归属2-knowledge/ · 唯一权威(标签共现图谱建边/查询/双层展示) 读者 :改 TagRelationServiceImpl 建边/查询逻辑、改 WebRelationController 接口、排查"某个标签为什么没入图 / 总图看不到某主题 / 下钻拿不到知识"、评估把语义/向量增强层接入的开发者 不重复


1. 功能概述

知识聚类图谱基于标签共现(tag co-occurrence)构建,把知识库中"经常一起出现的标签"聚成一张网络。整体为双层图谱

  • 上层 · 知识聚类图谱-总图(主题导航图):全局 Top-N 核心标签及其共现边。用户从宏观视角看整个知识库有哪些主题、主题间如何关联。
  • 下层 · 知识关系子图(下钻):点击总图某个标签节点,下钻展示该标签下"当前用户有权访问"的知识,以及知识之间的关系(共享标签 / 同板块)。

核心设计原则:完全无模型、无向量也 100% 可用。全部信号来自结构化数据(标签共现、标签分型、知识板块),不依赖 AI 模型或向量库;模型/向量仅作为未来可插拔增强层预留(见 §11),不影响主体运行。

复制代码
┌───────────────────────────────────────────────────────┐
│  上层:知识聚类图谱-总图(宏观,Top-N 核心标签)           │
│                                                        │
│   ●云原生 ═══ ●K8s        ●客户成功 ── ●onboarding      │
│      ╲       ╱                 │                       │
│       ●微服务                  ●续约                    │
│                                                        │
│   节点 = Top-N 核心标签(按累计共现权重)                │
│   边   = 标签共现权重                                    │
│   上色 = 标签分型(领域/实体/场景/管理)                  │
└───────────────────────┬───────────────────────────────┘
                    点击某标签 ↓ 下钻
┌───────────────────────────────────────────────────────┐
│  下层:知识关系子图(微观,查询期实时算 + 权限过滤)        │
│                                                        │
│   [知识A]──共享标签──[知识B]                             │
│      │                                                 │
│   同板块              [知识C]                           │
│      │                                                 │
│   [知识D]─────────[知识E]                               │
│                                                        │
│   节点 = 知识(仅当前用户有权访问的)                     │
│   边   = 共享标签 / 同板块                               │
└───────────────────────────────────────────────────────┘
Copy

2. 设计决策取舍

本方案是在与业务方多轮讨论后收敛的落地版,对原始需求文档做了关键取舍:

议题 原始需求方案 本方案(最终落地) 理由
标签分级 四级逐标签打级 四级定义在标签类别 SkcCategory 上,标签经多对多继承 治理粒度从"上千标签"降到"十几类别",一线零负担
合法节点池 受控词表 只有归属到"已分级类别"的标签才入图 天然质量闸门,挡住噪声标签
领域联合主键 (标签名,领域) 防同名跨域 不做 同名跨域问题未经证实,避免过度设计
图谱形态 单层标签共现网络 双层:上层主题导航 + 下层知识关系子图(下钻) 用户要的是知识,标签只是入口
语义边 RAG 推断边(第二层) 降级为可插拔增强层(向量近邻/RAG),本阶段不实现 SKC 常有无模型部署,核心必须零模型可用
社区发现 自动聚类簇 不做 簇不稳定、可解释性差,体验负担
权限 展示层过滤 下沉到图谱边/节点(上层总图按标签可见性过滤,下层子图按可读知识过滤) 安全内生

3. 标签分型(四级分级)

图谱节点的着色、尺寸、以及"是否入图",都由标签的分型决定。

3.1 四种分型

FarmTagLevelEnumfarm2-skc-know/.../enums/FarmTagLevelEnum.java):

枚举 标题 优先级 图谱语义 总图角色 节点色 节点尺寸
DOMAIN 领域 1(最高) 图谱骨架 骨架(唯一) #5d74c5 深蓝 45
ENTITY 实体 2 具体对象 骨架(唯一) #67c23a 绿 35
SCENE 场景 3 应用场景 卫星(可重复) #e6a23c 30
MANAGE 管理 4(最低) 管理类标签 卫星(可重复) #909399 25
(无分型 null) --- --- 兜底 不入总图 #8e9ed7 浅蓝 30

parse(String) 对非法/空值安全返回 null

3.2 分型定义在"标签类别"上,而非逐标签

分型字段落在 SkcCategory.taglevel (标签类别)上,标签通过多对多关系(SkcTagCategory)继承其所属类别的分型。

  • 治理粒度:从"上千标签"降到"十几个类别",一线几乎零负担。管理员给类别打级,下面的标签自动继承入图资格。
  • 入图闸门只有归属到"已设置分型的类别"的标签才会进入图谱 。未归属任何已分级类别的标签(resolveTagLevel 返回 null)天然被挡在图谱之外,作为质量过滤。
  • 多分型取优先级最高:一个标签可能归属多个已分级类别,取优先级数值最小者(领域 > 实体 > 场景 > 管理)作为其显示分型。入图宽松(命中任一已分级类别即入图),显示身份唯一。

3.3 分型解析与缓存

SkcTagServiceImpl.resolveTagLevel(tagId, isByCache)

  1. 查标签关联的全部类别(SkcTagCategorySkcCategory)。
  2. 取其中已分级类别的最高优先级分型(computeTagLevel)。
  3. 结果写入缓存 FarmCacheKeys.TAG_LEVEL(30 分钟)。"已算过但不入图"缓存为空串,避免缓存穿透。

缓存在类别分型编辑/删除(SkcCategoryServiceImpl)、标签与类别关系增删改(SkcTagCategoryServiceImpl)时清除,保证分型变更即时生效。


4. 数据模型

4.1 SKC_TAG_RELATION --- 聚合共现边表

每对标签一条无向聚合边 (规范化存储,SOURCEID <= TARGETID)。图谱渲染直接读它。

字段 含义
SOURCEID / SOURCENAME 边一端标签 id / 名称(规范化后字典序较小者)
TARGETID / TARGETNAME 边另一端标签 id / 名称
LEVELNUM 加权后的共现权重(TF-IDF 累加值 × 100 取整存储)
COOCCURCOUNT 原始共现知识数(无 TF-IDF 衰减),用于增量维护与运营调试
RELATIONMODEL 边来源,共现图谱固定为 KNOW_TAG
NODEMODEL 节点模型,固定 TAG

注:NAME/RELATIONKEY/CUSERKEY 等为旧图谱遗留字段,共现边统一写 NONE,不参与新图谱逻辑。表结构从旧的"每知识每标签对一条明细式"重构为"每标签对全局一条聚合式",LEVELNUM 从旧的恒为 1 改为持久化加权共现强度。

4.2 SKC_TAG_COOCCUR_DETAIL --- 共现溯源明细表

记录每篇知识对每一对标签共现边的贡献,是聚合边的可追溯来源与增量维护基础。

字段 类型 含义
ID varchar(32) PK 主键 UUID32
KNOWID varchar(32) 贡献该共现的知识 id;删除/编辑知识时按此列精确扣减
TAGA varchar(32) 标签对 A(SkcTag.id),规范化 TAGA < TAGB
TAGB varchar(32) 标签对 B(SkcTag.id
WEIGHT double 该篇对此边的贡献权重(含 TF-IDF 抑制)
CTIME varchar(14) 生成时间 yyyyMMddHHmmss

索引:idx_cooccur_know(KNOWID)idx_cooccur_pair(TAGA, TAGB)

4.3 两表关系

一对多:一条聚合边 = 多条明细的聚合 。明细支撑"编辑一篇知识时精确撤销其历史贡献",无需全量重算。这同时满足需求文档「每条连线可回溯至具体知识文档」的信任底座要求。DDL 见 versions/v1.3.0-v1.3.1.sql

4.4 关系枚举

  • FarmRelationEnumKNOW_TAG(知识标签共现,当前唯一落地来源)。为增强层预留 VECTOR_SIM/RAG_INFER/MANUAL(见 §11)。
  • FarmRelationNodeEnumTAG(标签节点)。

5. 建边逻辑

5.1 入图标签筛选 getGraphTags(knowId)

取知识的标签,依次过滤:

  1. 停用词过滤 :命中 farm2.config.graph.stopwords 配置(逗号分隔,NONE 表示无)的标签名剔除。
  2. 分级闸门resolveTagLevel == null(未归属任何已分级类别)的标签剔除。

四种分型(含管理类)均正常参与建边;管理类的"卫星化、不当枢纽"由总图渲染层(§6.1)处理,而非在建边阶段跳过。
说明:getKnowTags 只返回能在 SkcTag 表命中的标签,纯自定义未注册标签天然不参与建边------共现锚定在受控词表上。

5.2 单篇建边 initKnowIdRelations(knowId)

幂等重建,步骤:

  1. 先撤销 :调 delKnowIdRelations(knowId),按明细扣减该知识历史贡献并清明细(保证编辑知识时先撤旧边、再建新边)。
  2. 取入图标签 getGraphTags,不足 2 个则不建边。
  3. TF-IDF 抑制权重weight = 1 / log₂(2 + n)n 为入图标签数。标签越多,单篇贡献越低,抑制"标签堆砌"式知识的过度影响。
  4. 无向两两建边 :对入图标签两两配对,规范化 taga < tagb 后:
    • 插一条 SKC_TAG_COOCCUR_DETAIL 明细(溯源);
    • increaseEdgeweight 累加到聚合边 LEVELNUM(× 100 取整),COOCCURCOUNT + 1;不存在则新建边。

5.3 扣减 reduceEdge / 删除 delKnowIdRelations

  • delKnowIdRelations:按 KNOWID 查明细,逐条 reduceEdge,再删明细。
  • reduceEdgeCOOCCURCOUNT - 1;归零则删除该边,否则回退 LEVELNUM(不低于 0)。

5.4 全量重建 clearAllRelations

ID != '' 匹配全部,清空两张表。用于建边规则/配置变更后的数据迁移。


6. 图谱查询

6.1 上层总图 getTopicGraph(limit) --- 骨架+卫星结构

总图采用骨架+卫星布局,消除高连接度枢纽造成的"毛线球",语义分层清晰:

  • 骨架 (领域 DOMAIN / 实体 ENTITY):图的主干,每个标签唯一一个节点
  • 卫星 (场景 SCENE / 管理 MANAGE):依附在骨架上,允许在不同骨架上重复展示(如"故障排查"可同时挂在"云原生""数据库"下),一眼看出该场景/管理项横跨哪些主题。

步骤:

  1. 取全部 KNOW_TAG 共现边,按 LEVELNUM 降序。
  2. 权限过滤 :只保留"当前用户至少有一篇可读知识支撑"的标签,并据此裁剪边(两端标签都可见才保留)。避免总图泄露无权主题的标签名。实现见 getUserVisibleTagIds:按图谱边涉及的标签(数百个)反查其知识绑定行,在内存中与 getUserReadKnowids 求交,查询量由标签数约束而非知识总量。
  3. 骨架节点 :按累计共现权重(仅统计骨架端)取 Top-N(默认 50),生成唯一节点;两端都是入选骨架的边作为主干边
  4. 卫星节点 :对每个卫星标签,统计它与各入选骨架的共现权重,取最强 Top-3 骨架 ,各生成一个复制节点(id = 卫星id@骨架id 唯一,显示名不变,tagid 指向真实 SkcTag.id),连一条依附边到该骨架。卫星-卫星边、非入选骨架的边一律丢弃
  5. 节点按分型着色/定尺寸(levelColor / levelSize),category 设为分型名。

前端下钻:卫星复制节点 id 是拼接的唯一值,真实标签 id 由 FarmEchartNode.tagid 承载。前端 tagClickHandletagid ?? id 下钻,保证卫星节点也能正确解析到真实标签。

6.2 下层知识 getKnowGraph(tagkey, parentTagId, limit)

给定种子标签,取其下知识(前端就地展开为星形知识节点,见 §9.1):

  1. 种子解析 :入参可能是标签 id(总图节点 id)或 tagkey,先按 id 查(getSkcTagById)再按 key 兜底(getTagByKey),统一得到 resolvedTagKeyseedTagId
  2. 权限下沉 :取该标签下知识,用 getUserReadKnowids 过滤为当前用户可读 范围,limit(默认 30)截断。
  3. 父骨架上下文过滤 :展开卫星复制节点时前端传入 parentTagId(父骨架标签 id),仅保留同时含父骨架标签 的知识(交集)。这样 故障排查@云原生故障排查@数据库 各自展示所属领域内的知识,复制节点才具语义。骨架节点、tags 模式不传,行为不变。
  4. 预取每篇知识的标签集、板块集、标题。
  5. 返回知识节点(data)与知识间共享标签/同板块边(links)。

前端就地展开只使用返回的知识节点 data ,自行构造"知识 → 被点标签"的星形边(见 §9.1),忽略后端返回的知识间 links ------ 星形结构比知识两两互连更清晰。后端保留知识间边计算,供未来"知识子图"视图或其他调用方复用。

卫星复制节点 id 形如 卫星id@骨架id,前端解析 @ 后段作为 parentTagId 传入,并在标题展示 父骨架 › 卫星 上下文。

6.3 兼容旧接口 getKnowGraphs(tagKeys, level)

BFS 逐层扩展的标签关系图,被 /api/wrelation/relations(level=2)、/relations1(level=1)沿用。种子 tagkey → id,逐层调 getTagRelationByNodeIds 取邻居边,签名去重 + 交叉边裁剪。节点同样按分型着色。


7. 触发时机(何时刷新图谱)

7.1 自动 --- 知识变更事件驱动

EventViewHandlefarm2-service)监听知识对象事件:

动作 事件 处理
新增知识 ADD initKnowIdRelations
编辑知识 UPDATE initKnowIdRelations(幂等,先撤旧边再建新边)
删除知识 DELETE delKnowIdRelations

⚠️ 仅走 F2Event 事件机制的知识增删改才会自动刷新。绕过事件的批量改库不会触发。

7.2 手动 --- 全量重建

  • POST /api/tagrelation/rebuildAll:先 clearAllRelations 清空,再分页遍历全部知识重建,带进度反馈。前端「标签管理」页的「重建图谱」按钮触发。
  • POST /api/tagrelation/loadKnows:按指定知识 id 列表逐个重建(旧入口)。

需手动重建的场景:调整标签分型 / 停用词 / 管理类开关等规则后;首次上线初始化;清理旧图谱残留;怀疑数据不一致。


8. 接口清单

8.1 前台(/api/wrelationWebRelationController

接口 说明 入参 出参
POST /topic 上层总图 TopicGraphDto{limit?} GraphRelationDto{data(nodes), links}
POST /knowgraph 下层知识(就地展开) KnowGraphDto{tagkey, parenttagid?, limit?} GraphRelationDto
POST /relations 兼容:标签关系图 2 层 IdsAndIdsDto{ids1: tagkeys} GraphRelationDto
POST /relations1 兼容:标签关系图 1 层 同上 GraphRelationDto

8.2 后台(/api/tagrelationTagRelationController,需权限 tagRelation.*

标准 CRUD(query/{id} 增删改查)+ POST /rebuildAll(全量重建)+ POST /loadKnows(指定知识重建)。

契约文件:docs/api-contract/farm2-service/know.openapi.yaml

8.3 下钻知识列表 + 权限过滤

WebSearchController#tagKnowPOST /api/search/relationTagKnow):按标签 Lucene 检索知识,WebSearchService.initPopFilter 按当前用户可读范围过滤(KNOW_DOCgetUserReadKnowids 求交等),返回有权知识。


9. 前端实现(vue3 relation_graph

组件位于 src/views/web_page/relation_graph/

组件 职责
topic_graph_view.vue 统一图谱组件。mode='topic'(总图)/ mode='tags'(种子标签图),支持就地展开知识、图例、标题点击放大。prop inDialog 标记自身是否已在放大弹窗内(弹窗内不再显示放大入口,避免弹窗套弹窗)
graph_full_win.vue 图谱放大弹窗包装。openWin(isOpen, tags, winMode, opts?)opts={level?, limit?} 透传当前图谱的展开度/数量,保证放大版与原图一致;内部 topic_graph_view:in-dialog="true" 渲染

底层图形组件:views/chart/relation/echarts_relation.vue(ECharts 力导向图)。支持 watch 数据变化增量 setOption(原地增删节点);nodeClickHandle 回传完整节点数据供按类型分流(clickHandle 仍回传 id,向后兼容)。

9.1 交互 --- 原地展开(不切换视图)

标签图与知识共用一张力导向图,点击标签节点在其周围就地长出知识节点,而非跳转到独立子图:

  • 点标签节点 → 先清空图上已有的知识节点,再调 /knowgraph 取该标签知识 Top-10 ,作为灰色小方块 知识节点挂上,每个知识仅连一条边到被点标签(星形展开,知识之间不互连,避免密集交叉)。
  • 再点同一个已展开的标签 → 收起它的知识(nodeType 区分标签/知识节点)。
  • 点知识节点 → 侧边预览(Know_view_side)。知识节点 id 带 know: 前缀避免与标签 id 冲突,预览时还原。
  • 卫星复制节点 展开时,解析其 id 的 @ 后段作为 parentTagId 传入 /knowgraph,按父骨架交集过滤知识;标题展示 父骨架 › 卫星 上下文。
  • 点标题 :有展开知识时收起;否则(非弹窗内)点击标题将当前这张图谱放大 到弹窗展示------透传当前 mode/tags/level/limit,弹窗内是同一张图的放大版而非全局总图。已在放大弹窗内(inDialog)的实例不再显示放大入口,避免弹窗套弹窗。
  • 节点按分型着色,图例置于右下角避让标题。放大弹窗去除了 el-dialog 默认 header/body 内边距,使图谱标题紧贴窗口上沿。

9.2 接入点

my_page_home.vue(个人首页,topic 模式)、know_view.vue(知识详情,tags 模式)、my_page_tag.vue(收藏标签)、tag_info_popover.vue(标签浮层,全屏窗口)。

旧组件 min_graph_view.vue / max_graph_win.vue / relation_know_win.vue 已删除,全部由新组件替代。手绘嵌入图 GraphPart/api/wgraph)为独立功能,与本图谱无关。后台管理页:views/back_console/view/tagrelation/


10. 配置项

farm2config.xmlgraph 分组(version=1.3.1):

key 类型 默认 说明
farm2.config.graph.stopwords text NONE 图谱建边停用词,逗号分隔;NONE 表示无

变更配置后需手动重建图谱才对存量边生效。


11. 后续路线(增强层,本阶段不实现)

架构预留,仅确保接口/枚举/前端渲染为其留位,无模型环境静默跳过:

  • 向量语义近邻边VECTOR_SIM):知识-知识用 embedding 余弦近邻补边,解决"没共享标签但内容相关"。复用现有 searchByEmbedding / 向量检索能力。
  • RAG 推断边RAG_INFER):LLM 基于原文推断标签间语义关系,附证据原文 + 置信度阈值,虚线区分;人工采纳后升级为 MANUAL(人工确认关系)。
  • 激活开关:按"是否配置了 embedding / LLM client"自动判定,无模型环境不产生也不渲染增强层边。前端约定:增强层边一律虚线,核心层边实线。

明确暂不做:社区发现聚类、领域联合主键 / 同名跨域隔离(待问题被证实后再评估)。


12. 隐含假设与前提(AI 阅读须知)

以下前提不写在代码里、但影响图谱正确性判断。修改建边/查询/权限过滤逻辑前必读:

假设 具体含义 违反后果 / 排查线索
共现边是无向的,靠 SOURCEID <= TARGETID 规范化实现 TagRelationServiceImpl.initKnowIdRelations 建边时 a.getId().compareTo(b.getId()) <= 0 决定 src/tgt,findEdge / reduceEdge 也按同一规范化查找;SKC_TAG_RELATION没有唯一约束,规范化是唯一的重复防御 直接手写 SQL 或旁路 Service 插入不规范化的边 → 同一对标签出现两条边、reduceEdge 扣不到、getTopicGraph 权重翻倍。排查:"某对标签边异常" 先看 SOURCEID/TARGETID 字典序是否成立
SkcCategory.taglevel 是入图闸门;不打级的标签一律不参与建边 getGraphTagsresolveTagLevel(tagId, true) == null 直接 continue;即使标签在 SkcTag 表命中、也在知识上挂载了,只要它归属的所有 SkcCategory.taglevel 都为空/非法枚举,就永远不进 SKC_TAG_RELATION 上线新分类忘打级 → 该类下所有标签"看得见但连不上图"。排查:"知识里挂了 A/B 两标签为什么没生成 A-B 边" → 查 SkcTagCategory 关联的 SkcCategory.taglevel
TAG_LEVEL 缓存 30 分钟 TTL,空串代表"已算且不入图" FarmCacheKeys.TAG_LEVEL("图谱标签分型缓存", 5000, NONE(), minutes(30))resolveTagLevel 命中缓存后走 FarmTagLevelEnum.parse(cached),空串安全返回 null 手动改 DB 的 SkcCategory.taglevel不通过 ServiceSkcCategoryServiceImpl / SkcTagCategoryServiceImpl)→ 缓存不清,30 分钟内新建边仍走旧分型。排查:"改了类别分型但新知识建边没生效" → 手动清缓存或走 Service 入口
建边只走 F2Event;批量 DB 改动不会自动更新图谱 EventViewHandle.handle 只在 F2EObjectT.KNOW + ADD/UPDATE/DELETE 三个动作时调 initKnowIdRelations / delKnowIdRelations;未发事件的批量 SQL 变动、脚本导入、直接 UPDATE SKC_KNOW_TAG 全都不触发 SQL 导入知识后不重建图谱 → SKC_TAG_RELATION 与实际标签绑定不一致。排查线索:任何数据一致性告警先问"这个知识变更是通过 Service 层的吗"
图谱建边不做知识可见性判断;权限只在查询期下沉 initKnowIdRelations 全库统一建边,边权重 = 全体贡献之和;权限过滤在 getTopicGraph.getUserVisibleTagIds / getKnowGraph.readable 里做 想要"每个用户看到自己的私有图谱"?不能靠现有边表,得改建边模型(每用户一份图,成本极高,方案未采纳)
LEVELNUM 是"权重×100 取整"的累积整数,不是共现次数 increaseEdge / reduceEdgeMath.round(weight * 100) 累加/回退;共现次数是独立字段 COOCCURCOUNT 直接把 LEVELNUM 当计数用 → 计算 avg/median 会得到偏离 100 倍的结果。总图排序按 LEVELNUM 降序正确;但"共现了多少次"必须读 COOCCURCOUNT
总图骨架仅取 DOMAIN / ENTITYSCENE / MANAGE 一律降级为卫星 `isSkeleton(level) = level == DOMAIN
卫星复制节点 id 用 @ 分隔,与真实标签 id 不同 cloneId = satId + "@" + skId;前端 tagClickHandleFarmEchartNode.tagid ?? id 下钻,并解析 @ 后段作为 parentTagId 标签 id 里带 @ 字符 → 前端解析错乱。当前 SkcTag.idFarmUUID.getUUID32()(纯十六进制),暂无风险,但若换 id 生成器需重新评估
下钻权限依赖 getUserReadKnowids,返回集合可能上万 getKnowGraph / getUserVisibleTagIds 都做内存后置过滤readable.contains(...)),不把集合塞 SQL IN 若图谱扩展到"知识板块图"等需要按可读集合联表的场景,直接改成 IN 会挂 ------ 参照 permission-model.md §7.2 走 Lucene Filter 回调
getUserVisibleTagIds 假设 SkcKnowTag.TAGKEY 对应 SkcTag.tagkey(不是 id) 代码走 tag.getTagkey()SkcKnowTag.TAGKEY → 反查 readable 交集;tagkey 空的标签天然不进入可见集StringUtils.isNotBlank(tag.getTagkey()) 过滤) 历史脏数据 SkcTag.tagkey 为空 → 该标签边在总图消失。排查:"某标签明明有可读知识却不显示" → 先查其 tagkey 是否为空
getKnowGraph 前端仅使用 data,忽略后端 links 前端 §9.1 明确"就地展开只使用返回的知识节点,自行构造星形边";后端 links 保留仅为未来扩展 改后端边算法(如换成 Jaccard)时不用担心影响现有交互;但若前端要复用 links,须先测试是否与星形结构冲突
rebuildAll / loadKnowsnew Thread 里跑,无事务包裹 TagRelationController.rebuildAll 直接 new Thread(...).start();单篇失败 catch 后继续,不回滚 中断/宕机 → 图谱处于"部分重建"状态。生产上跑重建后应检查 FarmProcessUtils 进度是否走到 100,未完成的手动再点一次

13. 反例与陷阱

13.1 不要用等值查询查"某个标签的所有共现边"

错误做法SELECT * FROM SKC_TAG_RELATION WHERE SOURCEID = ? 问题 :边是规范化存储的(SOURCEID <= TARGETID),单向查询只能查到该标签作为字典序较小端的那一半边,字典序较大端的边全丢。 正确做法WHERE SOURCEID = ? OR TARGETID = ?getTagRelationByNodeIds 就是分两次查再用无向签名 min|max 去重)。

13.2 不要在 SkcCategory.taglevel 上写非枚举字符串

问题FarmTagLevelEnum.parsevalueOf(name.trim().toUpperCase()),非法值走 IllegalArgumentException 分支返回 nullnull 分型的标签不入图 。写 "1" / "级别1" / "domain "(带空格没问题,带全角字符不行)都会静默失效。 正确做法 :DB 里必须是 DOMAIN / ENTITY / SCENE / MANAGE 四个字面量之一;后台管理页 SkcCategoryServiceImpl 保存路径已保证这点,但直接 SQL 补数据必须遵守。

13.3 不要相信"改了停用词/分型/管理开关立刻在图上生效"

问题farm2.config.graph.stopwords 只影响建边时 筛选;已经建的边不会因为停用词新增而消失。同理,SkcCategory.taglevel 改动只影响:(a) TAG_LEVEL 缓存清空后新算的分型(用于总图渲染上色/骨架选择),(b) 新增/更新知识时的新建边入选。存量边不会自动重算正确做法 :调整规则后必须点后台的"重建图谱"按钮(POST /api/tagrelation/rebuildAll),或至少对涉及的知识 loadKnows

13.4 不要在 EventViewHandle 里发同源 KNOW 事件

问题EventViewHandle.handle 监听 KNOW.ADD/UPDATE/DELETE 后调 initKnowIdRelations;如果这里再触发一次 F2Event 且对象是 KNOW,会自我雪崩 。参考 permission-model.md 中"Handler 里无用户上下文"的类似警告。 正确做法initKnowIdRelations 内部只操作 SKC_TAG_RELATIONSKC_TAG_COOCCUR_DETAIL,不要发事件;若确需通知下游,用不同 F2EObjectT(如 TAG_RELATION)避免回环。

13.5 不要期望超级用户能看到"全部标签"的总图

问题getUserVisibleTagIds 直接调 getUserReadKnowids ------ 参照 permission-model.md §7.4:getUserReadKnowids 不为超管特殊短路 。超管的可读知识集仍受其岗位授权约束,若超管没归属任何有 KNOW_READ 权限的岗位,readable 为空,getTopicGraph 返回空图。 排查线索:"管理员打开总图空白" → 先查超管账号的岗位授权,不是图谱 bug。

13.6 不要用 SkcTag.id 直接查 SKC_KNOW_TAG

问题SKC_KNOW_TAG.TAGKEY 存的是 SkcTag.tagkey(业务 key),不是 SkcTag.id(UUID32)。getUserVisibleTagIds 中间要做一次 id ↔ tagkey 的映射,就是这个原因。 正确做法 :任何"通过标签反查知识"的路径必须先解析到 tagkey 再查 SkcKnowTagSkcKnowServiceImpl.getTagKnows(tagkey) / getKnowTags(knowId) 已封装这一层。

13.7 不要把 getKnowGraph 的入参 tagkey 当纯 tagkey 用

问题getKnowGraph(tagkey, parentTagId, limit) 的第一个参数同时接受 标签 id 和 tagkey ------ getSkcTagById(tagkey) 命中就当 id 用,否则回退到 getTagByKey。这是为了兼容总图节点 id(是标签 id)和 /relations 接口(是 tagkey)。 陷阱 :若某标签的 tagkey 与另一标签的 id 哈希冲突(极小概率但存在),会解析到错的标签。当前 UUID32(十六进制 32 位)与 tagkey(业务生成)不同名空间,冲突未观测到,但重构 id 生成器时须重新评估。

13.8 不要用 SKC_TAG_COOCCUR_DETAIL 做业务展示

问题 :明细表只是"扣减/溯源"的支撑数据,插入频率极高(N*(N-1)/2 条/知识)。走 UI 展示会拖慢查询、并且暴露的信息可能超出用户权限(明细不做权限过滤)。 正确做法 :如需"这条边来自哪些知识"的可视化,先在 Service 层按 getUserReadKnowids 过滤 KNOWID 再返回。

13.9 不要指望孤立标签或"只自共现"的知识产生边

问题initKnowIdRelationsvalidTags.size() < 2 时直接 return(§5.2 step 2)。只挂 1 个入图标签的知识既不写明细、也不建边。 陷阱:"发布了 10 篇知识总图还是空" → 极可能每篇知识只挂了 1 个已分级标签;至少要有 2 个都通过分级闸门的标签才会产生一条边。


14. 关键文件索引

关注点 文件
分型枚举 farm2-skc-know/.../enums/FarmTagLevelEnum.java
分型解析 farm2-skc-know/.../service/impl/SkcTagServiceImpl.java#resolveTagLevel
类别分型字段 farm2-skc-know/.../domain/SkcCategory.java#taglevel
建边/查询核心 farm2-view/.../service/impl/TagRelationServiceImpl.java
聚合边实体 / 明细表 DAO farm2-view/.../domain/TagRelation.java.../dao/TagCooccurDetailDao.java
关系/节点枚举 farm2-view/.../service/utils/FarmRelationEnum.javaFarmRelationNodeEnum.java
事件驱动 farm2-service/.../event/EventViewHandle.java
前台控制器 farm2-service/.../controller/WebRelationController.java
下钻权限过滤 farm2-service/.../controller/WebSearchController.javaWebSearchService.initPopFilter
后台控制器 farm2-view/.../controller/TagRelationController.java
前端组件 vue3/.../web_page/relation_graph/*.vueviews/chart/relation/echarts_relation.vue
DDL farm2-main/.../versions/v1.3.0-v1.3.1.sql(聚合边重构 + 明细表)、v0_3/v0.3.8-v0.3.9.sql(旧边表初建)

附录 A. 旧实现回顾(改造前基线)

改造前 SKC 已有一个共现网络雏形,与当前实现的关键差异如下,作为历史参照:

维度 旧实现 当前实现
边存储 每知识每标签对一条有向 边(N*(N-1) 条),relationkey=knowId 每标签对全局一条无向聚合边,明细表溯源
权重 LEVELNUM 恒为 1,共现频次靠查询期数记录条数累加 LEVELNUM 持久化 TF-IDF 加权共现强度
标签筛选 无分级、无停用词、无 TF-IDF 类别分型闸门 + 停用词 + 管理类跳过 + TF-IDF 抑制
图谱形态 单层,种子标签 BFS 扩展邻居 双层,总图 + 下钻知识子图
权限 仅下钻知识列表 initPopFilter 过滤,图谱拓扑不过滤 下层子图节点/边下沉到用户可读范围
前端 min_graph_view / max_graph_win / relation_know_win topic_graph_view / graph_full_win(旧组件已删除)

旧边表 DDL 见 v0_3/v0.3.8-v0.3.9.sql;旧建边逻辑为 initKnowIdRelations 内两两全连接写有向边(LEVELNUM=1),已被 §5 的聚合增量逻辑取代。


变更日志

日期 变更 备注
2026-07-06 初版 合并现状基线 / 改造目标 / 现行实现三份文档
2026-07-10 头部规范化 + 补面向 AI 章节 头部改为四要素格式;新增 §12 隐含假设与前提(11 条)、§13 反例与陷阱(9 条);原 §12 "关键文件索引" 顺延为 §14
相关推荐
Ivanqhz1 小时前
DRN(深度强化学习推荐网络)
人工智能·线性代数·机器学习·矩阵·dnn
一楼的猫3 小时前
网文AI辅助怎么过审?番茄200+维度检测与去AI味实战
人工智能·学习·机器学习·自然语言处理·ai写作
醒过来摸鱼4 小时前
机器学习知识框架:从数据处理到模型部署
人工智能·机器学习
lisw055 小时前
智能CAD:理论、算法、应用与展望!
人工智能·机器学习·软件工程
ShallWeL6 小时前
AUC、F1、召回率怎么选
人工智能·算法·机器学习
STLearner16 小时前
ICML 2026 | LLM×Graph论文总结[1]【图基础模型,文本属性图,多模态属性图,图对齐,图提示学习,关系深度学习
论文阅读·人工智能·python·深度学习·学习·机器学习·数据挖掘
Young Doro18 小时前
SAC 算法
线性代数·算法·机器学习
_Meilinger_1 天前
碎片笔记|分类问题与回归问题的关系
深度学习·机器学习·监督学习·损失函数·回归问题·分类问题·条件概率
学究天人1 天前
数学公理体系大全:第七章 连续统假设与力迫法简介
人工智能·算法·机器学习·数学建模·动态规划·图论·抽象代数
我不是QI1 天前
周志华《机器学习—西瓜书》九
人工智能·机器学习·支持向量机