5.9 KiB
W3-T8 独立评审报告
评审结论
- Verdict:FAIL
- 评审对象:
mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillCloudController.javamydreamjob-server/src/main/java/com/mydreamjob/config/SecurityConfig.javamydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudControllerTest.java
- 依据:修订后的
execution-contract.md、design.md、tasks.md,三份 spec,以及现有 JWT/Security/Controller 约定。 - 评审方式:静态核对实现与契约,独立运行指定回归,检查 Git 工作区范围;未修改实现文件。
独立测试结果
执行命令:
$env:JAVA_HOME='D:\java\jdk'
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest,SkillMatchServiceTest,SkillLearningPathServiceTest,SkillCloudControllerTest' test
结果:
Tests run: 69, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
其中 SkillCloudControllerTest 为 19 项通过。git diff --check HEAD 对 T8 三个文件无空白错误。docs/openapi-rc.json、docs/frontend-integration.md 无改动,docs/skill-cloud-api.md 不存在;未发现 T9/T10 实现。
阻断项
T8-I1(Important):OpenAPI 成功示例与 DTO/运行时契约矛盾
文件与行号:
SkillCloudController.java:56:词云示例中data.total=15,但items只有 1 项。SkillCloudController.java:103:匹配示例中matchedCount=8、totalCount=10,但同时返回matchedSkills=[]、missingSkills=[]。SkillCloudController.java:150:学习路径示例中data.total=2,但items只有 1 项。SkillCloudControllerTest.java:247-269:OpenAPI 测试只检查路径存在、bearerAuth、状态码和参数必填性,没有校验示例内容或 200 响应 schema,因此上述矛盾不会使测试失败。changes/n03-skill-cloud/execution-contract.md:120:明确要求“OpenAPI 注解、示例和实际响应必须一致”。
影响:客户端联调、接口文档和运行时契约可能按错误示例理解 TOP10、命中/缺失列表与总数关系。应修正三个示例,使计数、数组长度和 DTO 字段一致,并在 T8 的 OpenAPI 测试中锁定关键示例字段;同时建议为 200 响应声明对应 SkillCloudResponse、SkillMatchResponse、SkillLearningPathResponse schema,而非只有示例。
T8-I2(Important):角色测试通过 Mock 按 userId 放行,未证明真实 STUDENT 校验和多角色集合
文件与行号:
SkillCloudControllerTest.java:93-97:ProfileService被@MockBean替换,并按100L/200L/300L直接设置成功或抛FORBIDDEN。SkillCloudControllerTest.java:161-171:测试传入COMPANY/ADMIN角色声明,但实际 403 取决于 mock 对200L的硬编码行为,而非数据库角色集合。SkillCloudControllerTest.java:296-301:JWT 的rolesclaim 只作为测试辅助值传入。SkillCloudController.java:185-198:Controller 确实从SecurityContext取身份并调用profileService.assertStudentRole(userId)。ProfileService.java:50-57:生产实现从数据库读取用户,并使用anyMatch(STUDENT),因此单角色和包含 STUDENT 的多角色在代码层面是正确的。- 现有
ProfileControllerTest.java:167-185覆盖了数据库单角色 COMPANY/ADMIN 的 403,但没有覆盖 STUDENT+COMPANY 或 STUDENT+ADMIN 多角色集合,也没有覆盖本组/skills/**端点的真实角色链路。
影响:T8 的 Controller 测试无法证明 COMPANY/ADMIN 是被真实服务端角色检查拒绝,也无法证明多角色用户中只要包含 STUDENT 就能通过;它可能在 ProfileService.assertStudentRole 被替换或角色逻辑回归时仍通过。应增加使用真实 ProfileService、真实数据库用户的集成场景,至少覆盖:
- COMPANY、ADMIN:预期 403。
- STUDENT+COMPANY:预期允许。
- 无 STUDENT 的多角色集合:预期 403。
非阻断项
T8-M1:身份注入测试只覆盖 /skills/match
文件与行号:SkillCloudControllerTest.java:214-225。
当前实现三个端点都调用同一个 resolveAuthenticatedStudent(),代码层面正确;但测试只验证 match 端点会忽略 userId/studentId,没有对词云和学习路径端点做同样断言。建议参数化覆盖三个路径,并 verify 服务收到的是 JWT 中的 100L。
T8-M2:隐私断言只匹配 skills_json
文件与行号:SkillCloudControllerTest.java:227-244。
测试没有覆盖驼峰形式 skillsJson 或其他可能的新增个人技能字段。当前返回 DTO 为受控记录类型,因此非阻断;建议后续端到端测试使用真实服务响应,并对 DTO schema/序列化字段做更严格断言。
检查结论
- 三个路径和参数语义:通过。
industryId、occupationId必填,categoryId可选;实测缺参/0/负数返回 400。 - 身份来源:通过。Controller 只从
SecurityContext取主体,不绑定userId/studentId。 - 权限行为:实现通过,测试证明不完整。匿名 401、COMPANY/ADMIN 403 的 MockMvc 场景通过;但角色拒绝依赖 mock,多角色未覆盖。
ApiResponse与成功 DTO:通过。成功响应统一包装,字段与 Service DTO 一致。- OpenAPI:不通过。注解具备 Tag、Operation、SecurityRequirement、参数说明和 200/400/401/403,但成功示例与 DTO/运行时数据矛盾,且 200 响应缺少明确 schema。
- 改动范围:通过。未修改
docs/openapi-rc.json,未实现 T9/T10。
未决问题
- 当前安全层的 401/403 JSON 由既有
SecurityConfig直接写出,不是ApiResponse实体序列化结果;是否在本变更中统一该历史约定,需由项目维护者决定。本次未将其列为 T8 阻断项,因为它与仓库既有安全配置约定一致。