12 KiB
N03 SDD W2 整批独立评审报告
评审对象
- 范围:
b960036..f296df4 - Wave:W2
- 批次:B2 / T4–T7
- 提交:
0c77824 feat: add skill taxonomy repositories52751e5 fix: constrain skill projections by occupation categorye3eecfe feat: add skill cloud query service1666162 fix: expose skill source metadata in cloud items6eb30e7 feat: add personal skill match servicefca1160 feat: add skill learning path servicef296df4 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,MavenD:\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 单测。
- 位置:
未决问题
SkillCloudItem热度来源字段的最终 JSON 形态尚未在设计中收口,需在 T8 同时对齐实现、OpenAPI 和前端联调文档。- B2 只验证 H2
MODE=MySQL与 Service 层异常类型;真实 MySQL 迁移、HTTP 401/403/400、JWT 到studentUserId的绑定和ApiResponse包装仍属于 B3。 - B2 没有执行
mvn clean verify或静态 OpenAPI 校验;这些属于 T9/T10 的退出条件,当前不作为 W2 阻断。
是否放行 W3
放行。
W2 的领域行为、排名管道、个人匹配、学习路径和可证伪测试已达到 B2 完成定义。允许进入 W3/T8;W3 开始前必须在实现 Controller/SecurityConfig/OpenAPI 时处理 M1,并在 T8–T10 验证上述 HTTP、权限、隐私和真实数据库边界。