|
|
37b478a052
|
fix(kg): 修复 Codex 审查发现的 P1/P2 问题并补全测试
修复内容:
P1 级别(关键):
1. 数据隔离漏洞:邻居查询添加 graph_id 路径约束,防止跨图谱数据泄漏
2. 空快照误删风险:添加 allowPurgeOnEmptySnapshot 保护开关(默认 false)
3. 弱默认凭据:启动自检,生产环境检测到默认密码直接拒绝启动
P2 级别(重要):
4. 配置校验:importBatchSize 添加 @Min(1) 验证,启动时 fail-fast
5. N+1 性能:重写 upsertEntity 为单条 Cypher 查询(从 3 条优化到 1 条)
6. 服务认证:添加 mTLS/JWT 文档说明
7. 错误处理:改进 Schema 初始化和序列化错误处理
测试覆盖:
- 新增 69 个单元测试,全部通过
- GraphEntityServiceTest: 13 个测试(CRUD、验证、分页)
- GraphRelationServiceTest: 13 个测试(CRUD、方向验证)
- GraphSyncServiceTest: 5 个测试(验证、全量同步)
- GraphSyncStepServiceTest: 14 个测试(空快照保护、N+1 验证)
- GraphQueryServiceTest: 13 个测试(邻居/路径/子图/搜索)
- GraphInitializerTest: 11 个测试(凭据验证、Schema 初始化)
技术细节:
- 数据隔离:使用 ALL() 函数约束路径中所有节点和关系的 graph_id
- 空快照保护:新增配置项 allow-purge-on-empty-snapshot 和错误码 EMPTY_SNAPSHOT_PURGE_BLOCKED
- 凭据检查:Java 和 Python 双端实现,根据环境(dev/test/prod)采取不同策略
- 性能优化:使用 SDN 复合属性格式(properties.key)在 MERGE 中直接设置属性
- 属性安全:使用白名单 [a-zA-Z0-9_] 防止 Cypher 注入
代码变更:+210 行,-29 行
|
2026-02-18 09:25:00 +08:00 |
|
|
|
a260134d7c
|
fix(knowledge-graph): 修复 Codex 审查发现的 5 个问题并新增查询功能
本次提交包含两部分内容:
1. 新增知识图谱查询功能(邻居查询、最短路径、子图提取、全文搜索)
2. 修复 Codex 代码审查发现的 5 个问题(3 个 P1 严重问题 + 2 个 P2 次要问题)
## 新增功能
### GraphQueryService 和 GraphQueryController
- 邻居查询:GET /query/neighbors/{entityId}?depth=2&limit=50
- 最短路径:GET /query/shortest-path?sourceId=...&targetId=...&maxDepth=3
- 子图提取:POST /query/subgraph + body {"entityIds": [...]}
- 全文搜索:GET /query/search?q=keyword&page=0&size=20
### 新增 DTO
- EntitySummaryVO, EdgeSummaryVO:实体和边的摘要信息
- SubgraphVO:子图结果(nodes + edges + counts)
- PathVO:路径结果
- SearchHitVO:搜索结果(含相关度分数)
- SubgraphRequest:子图请求 DTO(含校验)
## 问题修复
### P1-1: 邻居查询图边界风险
**文件**: GraphQueryService.java
**问题**: getNeighborGraph 使用 -[*1..N]-,未约束中间路径节点/关系的 graph_id
**修复**:
- 使用路径变量 p:MATCH p = ...
- 添加 ALL(n IN nodes(p) WHERE n.graph_id = $graphId)
- 添加 ALL(r IN relationships(p) WHERE r.graph_id = $graphId)
- 限定关系类型为 :RELATED_TO
- 排除自环:WHERE e <> neighbor
### P1-2: 全图扫描性能风险
**文件**: GraphRelationRepository.java
**问题**: findByEntityId/countByEntityId 先匹配全图关系,再用 s.id = X OR t.id = X 过滤
**修复**:
- findByEntityId:改为 CALL { 出边锚定查询 UNION ALL 入边锚定查询 }
- countByEntityId:
- "in"/"out" 方向:将 id: $entityId 直接写入 MATCH 模式
- "all" 方向:改为 CALL { 出边 UNION 入边 } RETURN count(r)
- 利用 (graph_id, id) 索引直接定位,避免全图扫描
### P1-3: 接口破坏性变更
**文件**: GraphEntityController.java
**问题**: GET /knowledge-graph/{graphId}/entities 从 List<GraphEntity> 变为 PagedResponse<GraphEntity>
**修复**: 使用 Spring MVC params 属性实现零破坏性升级
- @GetMapping(params = "!page"):无 page 参数时返回 List(向后兼容)
- @GetMapping(params = "page"):有 page 参数时返回 PagedResponse(新功能)
- 现有调用方无需改动,新调用方可选择分页
### P2-4: direction 参数未严格校验
**文件**: GraphEntityController.java, GraphRelationService.java
**问题**: 非法 direction 值被静默当作 "all" 处理
**修复**: 双层校验
- Controller 层:@Pattern(regexp = "^(all|in|out)$")
- Service 层:VALID_DIRECTIONS.contains() 校验
- 非法值返回 INVALID_PARAMETER 异常
### P2-5: 子图接口请求体缺少元素级校验
**文件**: GraphQueryController.java, SubgraphRequest.java
**问题**: /query/subgraph 直接接收 List<String>,无 UUID 校验
**修复**: 创建 SubgraphRequest DTO
- @NotEmpty:列表不能为空
- @Size(max = 500):元素数量上限
- List<@Pattern(UUID) String>:每个元素必须是合法 UUID
- Controller 使用 @Valid @RequestBody SubgraphRequest
- ⚠️ API 变更:请求体格式从 ["uuid1"] 变为 {"entityIds": ["uuid1"]}
## 技术亮点
1. **图边界安全**: 路径变量 + ALL 约束确保跨图查询安全
2. **查询性能**: 实体锚定查询替代全图扫描,利用索引优化
3. **向后兼容**: params 属性实现同路径双端点,零破坏性升级
4. **多层防御**: Controller + Service 双层校验,框架级 + 业务级
5. **类型安全**: DTO + Bean Validation 确保请求体格式和内容合法
## 测试建议
1. 编译验证:mvn -pl services/knowledge-graph-service -am compile
2. 测试邻居查询的图边界约束
3. 测试实体关系查询的性能(大数据集)
4. 验证实体列表接口的向后兼容性(无 page 参数)
5. 测试 direction 参数的非法值拒绝
6. 测试子图接口的请求体校验(非法 UUID、空列表、超限)
Co-authored-by: Claude (Anthropic)
Reviewed-by: Codex (OpenAI)
|
2026-02-18 07:49:16 +08:00 |
|
|
|
8b1ab8ff36
|
feat(kg-sync): 实现图谱构建流程(MySQL → Neo4j 同步)
实现功能:
- 实现 GraphSyncService(同步编排器)
- 实现 GraphSyncStepService(同步步骤执行器)
- 实现 GraphSyncController(同步 API)
- 实现 GraphInitializer(图谱初始化)
- 实现 DataManagementClient(数据源客户端)
同步功能:
- syncDatasets:同步数据集实体
- syncFields:同步字段实体
- syncUsers:同步用户实体
- syncOrgs:同步组织实体
- buildHasFieldRelations:构建 HAS_FIELD 关系
- buildDerivedFromRelations:构建 DERIVED_FROM 关系
- buildBelongsToRelations:构建 BELONGS_TO 关系
- syncAll:全量同步(实体 + 关系 + 对账删除)
API 端点:
- POST /{graphId}/sync/full:全量同步
- POST /{graphId}/sync/datasets:同步数据集
- POST /{graphId}/sync/fields:同步字段
- POST /{graphId}/sync/users:同步用户
- POST /{graphId}/sync/orgs:同步组织
- POST /{graphId}/sync/relations/has-field:构建 HAS_FIELD
- POST /{graphId}/sync/relations/derived-from:构建 DERIVED_FROM
- POST /{graphId}/sync/relations/belongs-to:构建 BELONGS_TO
技术实现:
- Upsert 策略:
- 实体:两阶段(Cypher MERGE 原子创建 + SDN save 更新扩展属性)
- 关系:Cypher MERGE 幂等创建
- 全量对账删除:purgeStaleEntities() 删除 MySQL 中已删除的实体
- 并发安全:
- 图级互斥锁(ConcurrentHashMap<String, ReentrantLock>)
- 复合唯一约束(graph_id, source_id, type)
- 锁自动回收(releaseLock() 原子检查并移除空闲锁)
- 重试机制:HTTP 调用失败时按指数退避重试(默认 3 次)
- 错误处理:
- 逐条错误处理(单条失败不影响其他记录)
- 统一异常包装(BusinessException.of(SYNC_FAILED))
- 错误信息脱敏(仅返回 errorCount + syncId)
- 事务管理:
- GraphSyncService(编排器,无事务)
- GraphSyncStepService(步骤执行器,@Transactional)
- 性能优化:
- 全量同步共享数据快照
- 批量日志跟踪
- 图谱初始化:
- 1 个唯一性约束(entity ID)
- 1 个复合唯一约束(graph_id, source_id, type)
- 9 个索引(5 个单字段 + 3 个复合 + 1 个全文)
- 幂等性保证(IF NOT EXISTS)
代码审查:
- 经过 3 轮 Codex 审查和 2 轮 Claude 修复
- 所有问题已解决(3个P0 + 5个P1 + 3个P2 + 1个P3)
- 编译验证通过(mvn compile SUCCESS)
设计决策:
- 最终一致性:允许短暂的数据不一致
- 对账机制:定期对比并修复差异
- 信任边界:网关负责鉴权,服务层只做格式校验
- 多实例部署:依赖复合唯一约束兜底
|
2026-02-17 23:46:03 +08:00 |
|
|
|
910251e898
|
feat(kg-relation): 实现 Java 关系(Relation)功能
实现功能:
- 实现 GraphRelationRepository(Neo4jClient + Cypher)
- 实现 GraphRelationService(业务逻辑层)
- 实现 GraphRelationController(REST API)
- 新增 RelationDetail 领域对象
- 新增 RelationVO、UpdateRelationRequest DTO
API 端点:
- POST /{graphId}/relations:创建关系(201)
- GET /{graphId}/relations:分页列表查询(支持 type/page/size)
- GET /{graphId}/relations/{relationId}:单个查询
- PUT /{graphId}/relations/{relationId}:更新关系
- DELETE /{graphId}/relations/{relationId}:删除关系(204)
技术实现:
- Repository:
- 使用 Neo4jClient + Cypher 实现 CRUD
- 使用 bindAll(Map) 一次性绑定参数
- properties 字段使用 JSON 序列化存储
- 支持分页查询(SKIP/LIMIT)
- 支持类型过滤
- Service:
- graphId UUID 格式校验
- 实体存在性校验
- @Transactional 事务管理
- 信任边界说明(网关负责鉴权)
- 分页 skip 使用 long 计算,上限保护 100,000
- Controller:
- 所有 pathVariable 添加 UUID pattern 校验
- 使用 @Validated 启用参数校验
- 使用平台统一的 PagedResponse 分页响应
- DTO:
- weight/confidence 添加 @DecimalMin/@DecimalMax(0.0-1.0)
- relationType 添加 @Size(1-50)
- sourceEntityId/targetEntityId 添加 UUID pattern 校验
架构设计:
- 分层清晰:interfaces → application → domain
- Repository 返回领域对象 RelationDetail
- DTO 转换在 Service 层
- 关系类型:Neo4j 使用统一 RELATED_TO 标签,语义类型存储在 relation_type 属性
代码审查:
- 经过 2 轮 Codex 审查和 1 轮 Claude 修复
- 所有问题已解决(2个P0 + 2个P1 + 4个P2)
- 编译验证通过(mvn compile SUCCESS)
设计决策:
- 使用 Neo4jClient 而非 Neo4jRepository(@RelationshipProperties 限制)
- 分页 size 上限 200,防止大查询
- properties 使用 JSON 序列化,支持灵活扩展
- 复用现有错误码(ENTITY_NOT_FOUND、RELATION_NOT_FOUND、INVALID_RELATION)
|
2026-02-17 22:40:27 +08:00 |
|
|
|
5a553ddde3
|
feat(knowledge-graph): 实现知识图谱基础设施搭建
实现功能:
- Neo4j Docker Compose 配置(社区版,端口 7474/7687,数据持久化)
- Makefile 新增 Neo4j 命令(neo4j-up/down/logs/shell)
- knowledge-graph-service Spring Boot 服务(完整的 DDD 分层架构)
- kg_extraction Python 模块(基于 LangChain LLMGraphTransformer)
技术实现:
- Neo4j 配置:环境变量化密码,统一默认值 datamate123
- Java 服务:
- Domain: GraphEntity, GraphRelation 实体模型
- Repository: Spring Data Neo4j,支持 graphId 范围查询
- Service: 业务逻辑,graphId 双重校验,查询限流
- Controller: REST API,UUID 格式校验
- Exception: 实现 ErrorCode 接口,统一异常体系
- Python 模块:
- KnowledgeGraphExtractor 类
- 支持异步/同步/批量抽取
- 支持 schema-guided 模式
- 兼容 OpenAI 及自部署模型
关键设计:
- graphId 权限边界:所有实体操作都在正确的 graphId 范围内
- 查询限流:depth 和 limit 参数受配置约束
- 异常处理:统一使用 BusinessException + ErrorCode
- 凭据管理:环境变量化,避免硬编码
- 双重防御:Controller 格式校验 + Service 业务校验
代码审查:
- 经过 3 轮 Codex 审查和 2 轮 Claude 修复
- 所有 P0 和 P1 问题已解决
- 编译通过,无阻塞性问题
文件变更:
- 新增:Neo4j 配置、knowledge-graph-service(11 个 Java 文件)、kg_extraction(3 个 Python 文件)
- 修改:Makefile、pom.xml、application.yml、pyproject.toml
|
2026-02-17 20:42:55 +08:00 |
|