# W2-T5 独立评审报告 - **评审对象**:`e3eecfe feat: add skill cloud query service` - **评审基线**:`52751e5`(T4 复审通过提交) - **结论**:**FAIL** - **评审范围**:仅检查提交 `e3eecfe` 的 5 个文件;未修改实现文件。 ## 独立测试结果 环境:`JAVA_HOME=D:\java\jdk`,Maven `D:\maven\apache-maven-3.9.7\bin\mvn.cmd`。 执行: ```powershell mvn -Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest test ``` 结果: ```text Tests run: 24, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS ``` 测试数量拆分为:迁移 2、种子数据 8、Repository 6、词云 Service 8。 ## Important ### [I-1] 响应项遗漏技能自身的来源标识,违反 T5 的“来源字段”和可追溯要求 `SkillCloudSkillProjection` 已查询并携带技能来源字段: - `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\dto\SkillCloudSkillProjection.java:10-11` - `sourceType` - `sourceCode` - `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:25-26` - 查询投影包含 `s.sourceType` 和 `s.sourceCode` - `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:52-53` - 分类查询同样包含这两个来源字段 但 Service 转换成响应 DTO 时丢弃了这两个字段: - `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\SkillCloudQueryService.java:102-115` - `toItem(...)` 没有传入 `projection.sourceType()` 或 `projection.sourceCode()` - `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\dto\SkillCloudItem.java:5-17` - Item 只有 `heatSourceCode`、`heatElementId`、`heatProxy`,没有技能来源类型或技能来源编码 这会使返回结果无法在接口层区分某个技能来自 ESCO 还是 O*NET `software_skills`,只剩通过数据库内部 ID 才能反查来源。T5 明确要求返回“来源字段”: - `D:\1\zhi\zhilai\changes\n03-skill-cloud\tasks.md:21` - `D:\1\zhi\zhilai\changes\n03-skill-cloud\execution-contract.md:41` - `D:\1\zhi\zhilai\changes\n03-skill-cloud\specs\skill-cloud\spec.md:86-93` 当前测试也没有覆盖该缺口:`SkillCloudQueryServiceTest` 只断言了 React 的 `heatSourceCode`、`heatElementId` 和 `heatProxy`(`SkillCloudQueryServiceTest.java:198-223`),没有断言 Item 中应携带 `sourceType`/`sourceCode`。因此该遗漏是可复现但不可被现有测试发现的。 **修复要求**:在 `SkillCloudItem` 中补齐技能来源字段,在 `toItem` 中映射,并增加至少一个 ESCO 技能和一个 O*NET `software_skills` 技能的精确断言;同时确认无 heat 的技能仍保留其技能来源。 ## Minor ### [M-1] `heatSource` 的响应形态与设计文档不一致,需在 T8 前统一 设计文档将词云项描述为包含 `heatSource`: - `D:\1\zhi\zhilai\changes\n03-skill-cloud\design.md:131` 当前实现使用三个扁平字段表达同一语义: - `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\dto\SkillCloudItem.java:13-16` - `heatSourceCode` - `heatElementId` - `heatProxy` 这不是当前 T5 的运行时错误,但 T8 必须明确 OpenAPI 和接口文档采用扁平字段还是嵌套 `heatSource` 对象,避免契约漂移。 ### [M-2] T5 服务测试对 null heat 的来源字段覆盖不足 `SkillCloudQueryServiceTest` 对 React 的非空 heat 来源做了精确断言,但没有对无 heat 技能(如当前种子数据中的 Figma)断言 `heatSourceCode`、`heatElementId` 和 `heatProxy` 的最终 DTO 值。T4 投影测试覆盖了该场景,因此暂不阻断;修复 I-1 时应顺带补齐服务层断言。 ## 已通过检查 - 行业存在性、职位存在性、行业—职位归属校验均使用 `BusinessException(ErrorCode.BAD_REQUEST)`。 - 非法 `industryId`、`occupationId`、`categoryId`(null、0、负数)均返回 400 语义;未知节点和跨行业/跨职位节点同样返回 400。 - `categoryId` 缺省时调用整职位查询;传入时调用职位+分类查询,并保留 Repository 的职位—分类归属约束。 - 排序实现为 `heat DESC`、null heat 最后、`nameZh ASC`、`skillId ASC`;当前种子数据测试能捕获名称比较器和 skillId 比较器写错,也覆盖了 null heat 末位和连续 rank。 - 空分类返回成功、`total=0`、空 items。 - `SkillCloudResponse` 对列表执行 `List.copyOf`,响应列表不可变。 - 提交范围仅包含 T5 的 Service、3 个 DTO 和测试文件,没有实现 T6/T7/T8,也没有修改 V35/V36、安全配置或既有接口。 - 未发现事务、空指针、可变集合、随机性或明显性能问题;当前静态词库规模下三次校验查询加一次投影查询是合理的。 ## 未决问题 1. 技能来源字段的最终 JSON 名称应为 `sourceType`/`sourceCode`,还是采用带 `skillSource` 前缀的名称;需在修复 I-1 和 T8 OpenAPI 时统一。 2. `heatSource` 是否需要改成嵌套对象;若保持扁平字段,应同步修订设计文档和后续接口文档。 3. I-1 修复后必须重新执行本报告使用的 24 项联合回归,并重新评审;T5 评审通过前不得进入 T6。