You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

12 KiB

N03 SDD W2 整批独立评审报告

评审对象

  • 范围:b960036..f296df4
  • Wave:W2
  • 批次:B2 / T4–T7
  • 提交:
    • 0c77824 feat: add skill taxonomy repositories
    • 52751e5 fix: constrain skill projections by occupation category
    • e3eecfe feat: add skill cloud query service
    • 1666162 fix: expose skill source metadata in cloud items
    • 6eb30e7 feat: add personal skill match service
    • fca1160 feat: add skill learning path service
    • f296df4 test: harden learning path ordering regression
  • 评审依据:修订后的 execution-contract.md、design.md、tasks.md、三份 spec、V35/V36,以及 W2-T4*、W2-T5*、W2-T6*、W2-T7* 报告。
  • 环境:JAVA_HOME=D:\java\jdk,Maven D:\maven\apache-maven-3.9.7\bin\mvn.cmd,工作目录 D:\1\zhi\zhilai\mydreamjob-server。

Verdict

PASS

W2/T4–T7 的领域与查询实现完整覆盖 B2 范围;词云、个人匹配、学习路径确实共享同一排名管道;未发现 Critical 或 Important 阻断项。W3/T8 可以开始,但必须先处理报告中的 Minor 项,并在 B3 补齐 HTTP、权限、响应序列化、隐私和真实 MySQL 验证。

测试结果

独立执行:

$env:JAVA_HOME='D:\java\jdk'
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest,SkillMatchServiceTest,SkillLearningPathServiceTest' test

结果:

Tests run: 50, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

Surefire 分类:

测试类 通过数
SkillTaxonomyMigrationTest 2
SkillCloudSeedDataIntegrationTest 8
SkillTaxonomyRepositoryTest 6
SkillCloudQueryServiceTest 8
SkillMatchServiceTest 16
SkillLearningPathServiceTest 10

补充静态检查:

  • git diff --check b960036..f296df4 通过。
  • git log --oneline b960036..f296df4 只包含上述 7 个 W2/T4–T7 提交。
  • 范围内实现文件集中在 mydreamjob-server/src/main/java/com/mydreamjob/skill/** 与对应测试;未发现 SkillCloudController、SecurityConfig 或 OpenAPI 变更。

实现与需求覆盖矩阵

需求 W2 结论 主要实现与证据
REQ-SC-1 不属于 W2,待 B3 未实现 Controller/SecurityConfig;由 T8 负责。W2 只提供可被 Controller 调用的服务。
REQ-SC-2 通过(服务层) SkillCloudQueryService.query 校验必填、正整数、行业—职位关系、分类—职位关系,非法组合抛 BusinessException(BAD_REQUEST),见 SkillCloudQueryService.java:57-79、150-158;测试见 SkillCloudQueryServiceTest.java:238-264。
REQ-SC-3 通过 heat DESC, nameZh ASC, skillId ASC,null heat 末位,见 SkillCloudQueryService.java:28-40、86-99、120-144;测试覆盖空 heat、同 heat、同名称 skillId,见 SkillCloudQueryServiceTest.java:84-145。
REQ-SC-4 通过(B2) 词云返回完整列表;匹配直接取 min(10, actualSize),未另建排序,见 SkillMatchService.java:53-55;测试见 SkillMatchServiceTest.java:85-118。
REQ-SC-5 B2 通过,B3 待补 B2 已返回 rank、技能身份、分类、heat、技能来源及热度来源字段,见 SkillCloudItem.java:5-19、SkillCloudQueryService.java:102-117;ApiResponse 包装属于 T8。
REQ-SM-1 通过 匹配服务复用 SkillCloudQueryService.query 的结果前 min(10, actualSize) 项,见 SkillMatchService.java:53-55;测试见 SkillMatchServiceTest.java:85-137。
REQ-SM-2 B2 通过,B3 待补 服务只通过 studentUserId 查询 student_profiles.skills_json,缺失、空、损坏 JSON 均按空技能处理,见 SkillMatchService.java:81-116;JWT 身份提取必须由 T8 Controller 完成。
REQ-SM-3 通过 NFKC、trim、Locale.ROOT、Unicode 连字符、连续空白、句点、+/# 规则见 SkillNameNormalizer.java:12-27;nameZh/nameEn/skillCode/alias 和别名归属有隔离测试,见 SkillMatchServiceTest.java:237-292。
REQ-SM-4 通过 round(matched * 100.0 / actualTopSize),空集合返回 0,重复学生技能通过 Set 去重,见 SkillMatchService.java:54-77、100-110;测试覆盖 1/3=33、全命中、空集合。
REQ-SM-5 通过 命中/缺失列表按 TOP 顺序分别构造,并集等于实际 TOP10,见 SkillMatchService.java:59-78;测试见 SkillMatchServiceTest.java:294-370。
REQ-LP-1 通过 学习路径完全由 SkillMatchResponse.missingSkills() 驱动,不重新加入已命中或 TOP10 外技能,见 SkillLearningPathService.java:29-40;测试见 SkillLearningPathServiceTest.java:131-174。
REQ-LP-2 通过 直接沿用 missingSkills 顺序,优先级从 1 连续递增,见 SkillLearningPathService.java:35-38;真实 V36 多热度分组及名称/ID 乱序测试见 SkillLearningPathServiceTest.java:88-121。
REQ-LP-3 通过 四类确定性模板及未知模板回退见 SkillLearningPathService.java:56-93;模板精确断言见 SkillLearningPathServiceTest.java:176-265。
REQ-LP-4 通过(B2) 路径项包含技能 ID、名称、分类、优先级和建议,建议文本不含链接、课程、价格、时长、完成度或 AI 内容,见 SkillLearningPathItem.java:3-10、SkillLearningPathServiceTest.java:358-362。

关键检查结论

1. 三服务共享同一排名管道:通过

SkillCloudQueryService 是唯一排序实现:

  • 过滤:SkillCloudQueryService.java:57-84
  • 排序:SkillCloudQueryService.java:28-40、86-88
  • 排名:SkillCloudQueryService.java:89-91
  • 来源映射:SkillCloudQueryService.java:102-117

SkillMatchService 直接调用词云服务并截取前 min(10, actualSize) 项,没有复制排序逻辑,见 SkillMatchService.java:53-55。SkillLearningPathService 直接消费匹配服务的 missingSkills,没有重新排序,见 SkillLearningPathService.java:33-38。因此 TOP10、匹配缺口和学习路径的顺序语义一致。

2. 跨任务接口一致性:通过,存在命名待收口项

DTO 字段和顺序语义互相衔接:

  • SkillCloudItem 提供词云和匹配共用的完整技能项,见 SkillCloudItem.java:5-19。
  • SkillMatchResponse 返回 matchScore、matchedCount、totalCount、matchedSkills、missingSkills,见 SkillMatchResponse.java:5-15。
  • SkillLearningPathResponse 返回 total、items;SkillLearningPathItem 返回 priority、技能、分类和建议,见 SkillLearningPathResponse.java:5-11、SkillLearningPathItem.java:3-10。

Minor M1:设计文档 design.md:131 使用概念字段名 heatSource,而实现采用扁平字段 heatSourceCode、heatElementId、heatProxy,见 SkillCloudItem.java:15-18。这不是 B2 行为错误,但 T8 必须在 OpenAPI、接口文档和前端契约中明确最终 JSON 命名,避免文档与运行时漂移。

3. 领域边界、事务、空值和异常:通过

  • 三个业务服务均为 @Transactional(readOnly = true),没有写操作,见三个服务类的类级注解。
  • 分类—职位约束同时存在于服务校验和 Repository 投影查询中;跨职位分类错配测试见 SkillTaxonomyRepositoryTest.java:123-152。
  • 空 heat 保留并末位排序:SkillCloudQueryService.java:120-131。
  • 空结果:词云返回 total=0, items=[];匹配返回 matchScore=0、空命中/缺失;学习路径返回空列表,见对应测试。
  • 损坏 JSON 被捕获并仅记录异常类型和 profile ID,不输出原始 JSON,见 SkillMatchService.java:91-114。
  • 学习路径未知/空/缺失模板回退通用模板,见 SkillLearningPathService.java:56-65、84-93。

4. 确定性与性能:通过,Minor N+1

  • 排序使用 BigDecimal.compareTo、nameZh、skillId 全序比较,重复请求结果确定。
  • 每个缺失技能最多一次分类模板查询,构建学习路径时最多 10 次查询,见 SkillLearningPathService.java:61-64。

Minor M2:这是有界的 N+1。当前任务是代表性小词库、最多 10 个路径项,不构成 W2 阻断;如果后续词库或 TOP 上限扩大,应在 T8/T10 或后续优化中改为一次批量读取模板。

5. 测试可证伪性:通过,有少量 Minor 缺口

已覆盖用户要求的关键反例:

  • 跨职位分类错配:SkillTaxonomyRepositoryTest.java:123-152、SkillMatchServiceTest.java:347-370。
  • 排序错误:SkillCloudQueryServiceTest.java:84-145 覆盖热度、同名/同热度的 skillId 稳定排序。
  • 名称、别名和别名归属遗漏:SkillMatchServiceTest.java:237-292。
  • 分母错误:SkillMatchServiceTest.java:120-137。
  • 优先级重排:SkillLearningPathServiceTest.java:88-174。
  • 模板遗漏:SkillLearningPathServiceTest.java:176-265。
  • 空路径:SkillLearningPathServiceTest.java:268-280。

Minor M3:B2 测试未直接覆盖 SkillLearningPathService.resolveTemplate 在 skill.categoryId()==null 时的通用模板分支;也未通过完整序列化验证所有响应对象不含原始 skills_json。生产 DTO 当前不含隐私字段,且 SkillLearningPathServiceTest.java:358-362 已约束建议文本;T8/T9 的 MockMvc/端到端测试必须补齐响应级隐私断言。

6. W2 未越过 T8 边界:通过

git diff --name-only b960036..f296df4 未出现:

  • SkillCloudController 或任何 /skills/** Controller;
  • SecurityConfig 变更;
  • OpenAPI 文件;
  • /api/v1/skills/* 运行时端点。

因此 W2 没有实现 T8 的接口、权限或 OpenAPI 工作,边界符合 tasks.md:24-25 的后续安排。

严重级别与发现

Critical

无。

Important

无。

Minor

  • M1:设计中的 heatSource 与实际扁平来源字段未最终对齐。

    • 位置:changes/n03-skill-cloud/design.md:131;mydreamjob-server/src/main/java/com/mydreamjob/skill/dto/SkillCloudItem.java:15-18。
    • 处理方向:T8 明确选择扁平 heatSourceCode/heatElementId/heatProxy 或嵌套 heatSource,并同步 OpenAPI、接口文档和前端示例。
  • M2:学习路径模板查询是有界 N+1。

    • 位置:mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillLearningPathService.java:61-64。
    • 处理方向:当前最多 10 次查询,不阻断;若词库或 TOP 上限扩大,应批量读取 (categoryId, occupationId) 模板映射。
  • M3:B2 测试未直接覆盖空 categoryId 模板回退和完整响应序列化隐私断言。

    • 位置:mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillLearningPathServiceTest.java:240-265、358-362。
    • 处理方向:T8 Controller 测试必须验证三个接口的响应不含 skills_json、手机号、邮箱和简历内容;可同时补 categoryId=null 的 Service 单测。

未决问题

  1. SkillCloudItem 热度来源字段的最终 JSON 形态尚未在设计中收口,需在 T8 同时对齐实现、OpenAPI 和前端联调文档。
  2. B2 只验证 H2 MODE=MySQL 与 Service 层异常类型;真实 MySQL 迁移、HTTP 401/403/400、JWT 到 studentUserId 的绑定和 ApiResponse 包装仍属于 B3。
  3. B2 没有执行 mvn clean verify 或静态 OpenAPI 校验;这些属于 T9/T10 的退出条件,当前不作为 W2 阻断。

是否放行 W3

放行。

W2 的领域行为、排名管道、个人匹配、学习路径和可证伪测试已达到 B2 完成定义。允许进入 W3/T8;W3 开始前必须在实现 Controller/SecurityConfig/OpenAPI 时处理 M1,并在 T8–T10 验证上述 HTTP、权限、隐私和真实数据库边界。