轻松聘AI开发
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.
 
 
 

20 KiB

Phase B:已认证企业业务门禁实施报告

日期:2026-09-23 范围:仅 Phase B;未暂存、未提交、未推送、未部署。

结论

Phase B 门禁已在服务层实施并通过全量验证。mvn test 与 mvn clean verify 均为 2880 / 0 failures / 0 errors / 0 skipped;真实 MySQL/Testcontainers 用例实际运行。OpenAPI 检查为 182 paths / 220 operations,All checks passed。API 路径和 operation 集合未改,docs/openapi-rc.json 无差异。

本轮没有启动应用部署或连接生产 RabbitMQ。拒绝路径的事件/通知调用通过测试中的 mock never() 断言验证;本机 RabbitMQ 容器当时为停止状态,因此不报告未经测量的 broker 队列深度。没有数据库迁移,也没有新增业务状态字段。

1. 开工核查与基线

  • 分支:master
  • HEAD:8c9429108cbd5d85e5245f6c404b2172bf6dd7f3(Phase A 已交付基线)
  • 暂存区:空;git diff --check:exit 0
  • 工作区:除本轮明确列出的改动外,原有历史未跟踪文件全部保留;未运行 reset、restore、checkout、clean 或任何目录级 git add。
  • git status 曾将 CandidateOverviewStatusPolicy.java 与 JobPreferenceScoreCalculator.java 标成修改;逐文件 git hash-object 与 HEAD blob 相同,且 git diff --quiet,确认是 Windows stat 缓存假阳性,内容未改。
  • 迁移库存最高为 V46;V1–V46 未改、无 V47。
  • 上线健康接口可见版本仅为 0.0.1-SNAPSHOT,不能据此映射出精确线上 commit。Phase B 未部署,线上版本未改变。
  • Docker 可用;最终全量验证中的 MySQL/Testcontainers 测试真实执行,Skipped=0。

2. 权威设计和门禁语义

以 docs/company-onboarding-admin-user-management-design.md §7 及独立评审为准:企业认证事实只读 companies.verified;users.is_verified 不用于企业认证判断。

检查顺序保持为:现有认证/活动账号校验 → user_roles 的 COMPANY 身份 → 当前企业资料存在及 owner 归属 → 仅对受限招聘业务检查 companies.verified=true。认证门禁不能取代任何角色、归属、资源可见性判断。资源查询前优先校验调用者所属企业;按资源授权的入口仍保留其原归属检查,避免跨企业数据探测。

未认证、审核中、已拒绝均按 companies.verified=false 统一拒绝受限操作,固定响应 COMPANY_NOT_VERIFIED / code 5004 / HTTP 403 / 文案 company not verified。该错误码与已有数字码无冲突,HTTP 状态集中登记在 ErrorHttpStatusRegistry,没有在 Controller 中复制异常映射。

3. 端点库存与允许/禁止矩阵

逐个检查了 Controller 映射及其 Service 路径。下表中“COMPANY”表示现有 Controller/服务中的 COMPANY 身份要求仍保留;“own/资源归属”表示原企业 owner、岗位、投递或参与者校验仍执行。受限入口在服务端、候选人/Offer/面试等敏感查询或副作用之前执行认证门禁。

接口范围(HTTP method + path) Controller → Service Phase B 结果及既有校验
POST /jobs;PUT /jobs/{jobId};PATCH /jobs/{jobId}/status JobController → JobService 仍要求 COMPANY 和本企业岗位归属。创建 DRAFT、编辑自己的 DRAFT 允许;已发布内容编辑以及向 PUBLISHED 转换要求认证。将自己已发布岗位转为 CLOSED 仍允许,保留安全下架能力和旧状态语义。跨企业岗位仍按旧归属错误拒绝。公开 GET /jobs、GET /jobs/search、GET /jobs/public/{jobId}、GET /companies/{companyId}/jobs 不改变。
GET /company/applications;PATCH /applications/{id}/status;GET /company/candidates;GET /company/candidates/{applicationId};GET /company/candidates/{applicationId}/resume;GET /applications/{id}/timeline(企业分支) ApplicationController → ApplicationService / CompanyResumeAccessPolicy COMPANY、公司/岗位归属与既有可见性校验保留。企业认证先于敏感投递/候选人查询;下载门禁先于简历存储读取。拒绝时不读文件;学生本人的投递/timeline 分支不加企业门禁。
GET /company/candidates/{applicationId}/scoring;GET /company/scoring/dashboard/{overview,job-rankings,job-rankings/{jobId},candidate-rankings,candidate-rankings/{applicationId},trend} CandidateScoringController、CompanyScoringDashboardController → scoring services 企业评分/候选人评分属于候选人敏感数据,所有公司入口要求 verified;原公司归属与管理员分支保持不变。
GET/POST/PUT/DELETE /applications/{applicationId}/feedback CandidateFeedbackController → CandidateFeedbackService 企业侧 feedback 先校验公司归属及认证,再访问投递或写反馈;学生自己的反馈路径/权限保持原语义。
GET /companies/me/jobs/{jobId}/talent-recommendations;POST /companies/me/jobs/{jobId}/talent-invitations;GET /companies/me/talent-invitations;GET /companies/me/talent-invitations/{invitationId} TalentDiscoveryController、TalentInvitationController → respective services 企业身份、岗位/邀请归属后要求认证;门禁先于候选人匹配、资料加载及邀请写入/通知。/profile/me/talent-discovery 与求职者邀请列表/决定接口仍为求职者自身功能。
POST/GET /applications/{applicationId}/interviews;GET /interviews/me;GET/PUT /interviews/{interviewId};POST /interviews/{interviewId}/cancel、/complete;GET /interviews/{interviewId}/timeline InterviewController → InterviewService 企业安排、修改、取消、完成及查看面试时先检查企业认证及原面试/投递归属;求职者查看和回应自己的面试保留原路径与行为。
PUT/GET /interviews/{interviewId}/score;POST /interviews/{interviewId}/score/submit;GET /interviews/{interviewId}/score/history InterviewScoreController → InterviewScoreService 企业侧评分读写要求认证,并保留面试参与者/归属检查;求职者侧不被企业门禁误伤。
POST/GET /offers;GET/PUT /offers/{id};PATCH /offers/{id}/status;GET /offers/{id}/timeline;POST /offers/{id}/remind;GET/POST /offers/{id}/counter-offers;PATCH /offers/{id}/counter-offers/{coId}/accept、/reject;GET /offers/statistics OfferController → OfferService 对公司发起、读取、修改、发送/流转 Offer 及统计入口,在查询/写入/提醒副作用之前验证公司。原 application/offer/counter-offer 所有权检查保留。GET /offers/compare 为求职者接口,不改。
GET/PUT /companies/me/jobs/{jobId}/recommendation-profile;GET/PUT /companies/me/match-rule recommendation、MatchRuleController → services 公司侧候选人推荐/匹配配置要求 verified,并保留 owner 校验;/profile/me/match-rule 等求职者配置不改。
PUT /applications/{id}/hr-suitability-score;GET /applications/{id}/match;POST /applications/{id}/conversation;GET/POST /conversations;GET /conversations/{id}、/{id}/messages;POST /conversations/messages、/{id}/read、/{conversationId}/messages/{messageId}/recall match、conversation、MessagingController → services 企业候选人匹配、发起/读取招聘会话和消息均需 verified。会话成员、投递和消息归属校验保留;求职者自己的候选兴趣分/匹配路径不变。消息列表读取还使用明确加载 user_roles 的查询,避免 detached lazy roles 导致角色分支错误。
GET /notifications、/unread-count;POST /notifications/{id}/read、/read-all(企业主体) NotificationController → NotificationService 企业用户侧通知查看/读状态操作要求认证,拒绝先于通知列表读取或读状态写入;其他主体语义保留。系统 worker/事件生产路径不套用户发起的企业门禁。
GET/POST/PUT /companies/me;GET /companies/me/onboarding;POST/GET /companies/me/verification CompanyController、CompanyVerificationController → company/onboarding/verification services 未认证企业仍可查改自己的资料、查询聚合状态、提交和查询自己的认证申请。身份及 owner 关系照旧验证;认证审核状态机未改。

明确未加门禁的入口

  • 公开岗位、公司资料和公开评价查询没有候选人隐私或企业招聘副作用,保持公开行为。
  • CompanyReviewManagementController 的本企业公开岗位评价/回复用于企业声誉运营,不读取候选人敏感资料,也不是招聘动作;按 Phase B 设计范围不设认证门禁,既有公司归属照旧。
  • ApplicationMatchController 中求职者的 candidate-interest-score、求职者岗位匹配与个人设置仍按原 STUDENT 路径处理。
  • OfferExpiryScheduler/expiry service、匹配通知重试、候选评分 refresh worker、邀请过期清理和通知消费是系统维护/既有任务,不是未认证企业发起的招聘请求;逐一检查没有从这些后台入口调用受限用户写 API,因此不加企业认证门禁,避免误伤既有系统处理。

4. 实施内容和精确文件范围

新增服务层共用规则 mydreamjob-server/src/main/java/com/mydreamjob/company/VerifiedCompanyGuard.java。它只依据传入的 Company.verified 判定;角色、活动状态、公司归属和资源归属仍由现有 service/guard 流程负责。

修改的生产文件(20 个):

  • application/ApplicationService.java、application/CompanyResumeAccessPolicy.java
  • auth/UserRepository.java
  • bidirectionalmatch/BidirectionalMatchService.java、MatchConversationService.java、MatchRuleService.java
  • candidatescore/CandidateScoringEngineService.java、candidatescore/dashboard/CompanyScoringDashboardService.java
  • common/exception/ErrorCode.java、ErrorHttpStatusRegistry.java
  • feedback/CandidateFeedbackService.java
  • interview/InterviewService.java、interviewscore/InterviewScoreService.java
  • job/JobService.java
  • messaging/MessagingService.java、notification/NotificationService.java
  • offer/OfferService.java
  • recommendation/JobRecommendationService.java
  • talentdiscovery/TalentDiscoveryService.java、talentinvitation/TalentInvitationService.java

新增测试类(3 个):

  • phaseb/PhaseBVerifiedCompanyGateContractTest.java:静态端点—Service 覆盖映射,并锁定认证只看 companies.verified。
  • phaseb/VerifiedCompanyGuardTest.java:verified / false / null / company missing 与固定业务码和 403 映射。
  • phaseb/VerifiedCompanyGatePersistenceIntegrationTest.java:真实 HTTP PATCH /jobs/{id}/status 对 UNVERIFIED、PENDING、REJECTED 均返回 403/code 5004,并确认 DRAFT 仍未被发布;verified 企业发布成功。

另有既有 service/controller/persistence 测试被扩展:求职者、企业、管理员认证角色及旧行为回归;简历读取计数与租户边界;Job 发布;Offer、Interview、TalentInvitation 的拒绝副作用;feedback、matching、messaging、notification、scoring、resume、interview-score 的企业测试夹具现在明确使用已认证 Company,避免旧测试把默认 verified=false fixture 误当已认证企业。R01-B/R01-C/R01-E 错误映射和消息查询预算测试只调整了 Phase B 新码映射/现有 eager-role 读取所增加的固定查询基线。

被修改的既有测试文件(27 个):

  • application/ApplicationServiceTest.java、CompanyResumeAccessPolicyTest.java、CompanyResumeDownloadIntegrationTest.java
  • bidirectionalmatch/BidirectionalMatchPersistenceIntegrationTest.java、BidirectionalMatchServiceTest.java、MatchConversationServiceTest.java、MatchRuleServiceTest.java
  • candidatescore/CandidateScoringClockConsistencyTest.java、CandidateScoringEngineServiceTest.java
  • common/exception/GlobalExceptionHandlerStatusContractTest.java
  • common/r01b/ErrorHttpStatusRegistryTest.java、GlobalExceptionHandlerMappingParityTest.java、R01BIndependentHttpMappingParityTest.java
  • common/r01e/R01EIndependentCrossDatabaseVerificationTest.java、R01EQueryBudgetAcceptanceTest.java
  • feedback/CandidateFeedbackRefreshRegistrationTest.java、CandidateFeedbackServiceTest.java
  • interview/InterviewServiceTest.java
  • interviewscore/InterviewScoreControllerTest.java、InterviewScorePersistenceIntegrationTest.java、InterviewScoreServiceTest.java
  • job/JobServiceTest.java
  • messaging/MessageRecallHttpIntegrationTest.java、MessagingServiceTest.java
  • notification/NotificationServiceTest.java
  • offer/OfferServiceTest.java
  • talentinvitation/TalentInvitationServiceTest.java

没有修改 Controller mappings、DTO、pom.xml、Flyway、V1–V46、OpenAPI 文件、短信服务、Phase A 注册/验证码/Token/企业认证审核流程,也没有任何 Phase C 用户/角色管理代码。

5. TDD 失败证据与测试覆盖

  • 先增加 JobServiceTest.transitionStatus_unverifiedCompanyCannotPublish,在尚无门禁时失败:期望 BusinessException,旧实现没有抛出。修复前失败日志保存在系统临时目录,不加入仓库。
  • 对简历策略做单点变异(移除认证判断)后,CompanyResumeAccessPolicyTest 新增负向用例失败:未认证时仍访问 applicationRepository.findById,与“门禁先于候选人/简历数据读取”相违。源码恢复后,变异目标测试转绿;变异失败日志保存在系统临时目录。
  • HTTP 集成覆盖 UNVERIFIED/PENDING/REJECTED 统一 403 及 verified 可发布。跨企业资源仍由原 service ownership tests 和既有集成回归覆盖。
  • 简历测试包含 storage read probe,拒绝路径 READ_CALLS=0。Job 拒绝后状态仍 DRAFT。Offer、Interview、TalentInvitation 的未认证路径对投递/岗位/候选人/offer/interview/invitation/history/audit/notification/刷新等依赖均断言 never(),无业务写入或通知触发。
  • 无效 Token、匿名、STUDENT、ADMIN 的 401/403 使用现有 SecurityFilterChain/Controller 回归;Phase B 没有放开匿名路由,也没有改 SecurityConfig。

本轮测试驱动过程中的非产品问题:部分旧单测严格 Mockito stubbing 仍 stub 旧的 findById;用户/角色集合在 service 离开事务后为 lazy,导致企业身份分支不能可靠判定。通过更新这些测试的 stub,并新增 UserRepository.findByIdWithRoles 明确加载 user_roles 修复;没有降低断言,也没有删角色检查。OpenAPI 初次调用时使用了不存在的 docs/verify/_openapi.py 路径;依据仓库文件清单改用 docs/verify_openapi.py 后全部通过,未改脚本。

6. 验证结果

检查 结果
Phase B 定向(Contract + Guard + persistence + Job/Application/Resume/Offer/Interview/Invitation 及关联服务) 189/189,F=0 E=0 S=0;另外最新版 persistence HTTP 类 2/2 单独通过
全量 mvn test 2880/2880,F=0 E=0 S=0,BUILD SUCCESS
最终 mvn clean verify 2880/2880,F=0 E=0 S=0,BUILD SUCCESS;MySQL/Testcontainers 实测执行
python ..\docs\verify_openapi.py(从 server 模块目录) 182 paths / 220 operations,All checks passed
数据库 schema/migration V1–V46 无差异;无迁移需求;无 V47
OpenAPI/API surface Controller 路径和 operation 集合未改变;docs/openapi-rc.json 无差异

Phase B 新增用例共 12,全套从 2868 增至 2880。Docker 中 MySQL 8.4/Testcontainers 用例真实执行,Skipped=0。Hikari 在 Testcontainers 销毁/重建连接时输出连接已关闭的校验 WARN;两轮全量均以 0 errors 成功完成。

7. 副作用、安全与已知观测

  • 认证事实静态断言确认 Guard 只读取 Company.verified,不读取 User.isVerified。
  • 租户 A 即使已认证仍须通过原 owner/资源关系校验;新门禁不构成跨企业授权。服务先验证企业/权限,再查候选人敏感资源;简历 policy 在 read(storagePath) 之前失败关闭。
  • 拒绝请求不会改变 DRAFT/Offer/Interview/Invitation 等业务记录;未调用 audit、notification、message/event publisher 或 scoring refresh 依赖。没有真实 RabbitMQ 运行,因此不声称 broker queue depth 被独立读取;拒绝路径的 publisher 无交互由测试逐项锁定。
  • 新增代码和报告未包含凭据、JWT、密码、真实手机号、数据库连接秘密、容器动态口令或本机路径;测试数据使用非真实 fixture。修复前日志留在临时目录,未加入 Git。
  • 没有通过设置 verified=true 绕过生产流程;“verified”测试场景仅在测试 fixture 中按显式场景构造,以验证认证企业原行为。

8. Git 结束状态

  • HEAD 仍为 8c9429108cbd5d85e5245f6c404b2172bf6dd7f3,分支仍为 master。
  • 暂存区为空,git diff --check exit 0;没有提交、推送、部署或合并。
  • 新增生产组件和 Phase B 测试及本报告仍未跟踪;所有此前历史未跟踪文件原样保留。
  • 工作区中 20 个已跟踪生产文件有 Phase B 改动;额外两个显示为修改的既有文件已通过 blob hash 证实内容与 HEAD 完全一致。
  • 最终核查无 Java/Maven 残留进程,8080/8081 无监听;未停止或删除 Docker 容器/镜像。

Phase C 管理员用户与角色管理未进入;Phase B 未部署。下一步是独立代码复验,不是提交或部署。

9. 两项独立复验阻断修复(2026-09-23)

独立复验发现岗位发布门禁发生在岗位 ID/归属查询之后,可能通过业务码与 HTTP 状态差异探测跨企业岗位;另发现 VerifiedCompanyGatePersistenceIntegrationTest 使用全局 TestAuthTokens 数据源注册表,在组合运行时偶发无法签发会话。

  • JobService.transitionStatus 对目标 PUBLISHED 先调用 requireVerifiedCompany,再查询岗位;未认证公司的有效 ID、其他公司 ID 和不存在 ID 均先得到固定认证拒绝,不触发岗位查询。
  • 新增 JobRepository.findByIdAndCompanyId,并用于企业岗位读取、修改和状态操作。跨企业岗位和不存在岗位都按 JOB_NOT_FOUND/404 处理;JobController.getJob 改为单次归属限定查询,避免泄露资源存在性。
  • 保留设计允许的未认证企业自有 DRAFT 岗位查看与编辑;已认证企业仍必须通过原企业归属条件,不获得跨企业访问权限。
  • VerifiedCompanyGatePersistenceIntegrationTest 不再调用静态全局 TestAuthTokens,改由当前 Spring 测试上下文的真实 AuthSessionService 创建会话,Token 和会话记录落在当前事务数据源中,事务结束回滚,不依赖其他测试上下文的注册/销毁顺序。
  • 先加 HTTP 回归再改生产代码时,新测试复现了原差异:未认证发布外企业岗位得到 400、读取/修改外企业岗位得到 400,而目标契约为认证门禁 403 或与不存在资源一致的 404。更新后的 HTTP 集成类 4/4 通过;岗位与认证边界组合 33/33 通过;原 10 类定向组合现为 191/191,F=0/E=0/S=0。
  • 两条既有跨企业岗位 HTTP 断言改为校验 404/JOB_NOT_FOUND;夹具明确设成 VERIFIED,确保发布测试实际走到资源归属门禁而不是被未认证门禁短路。

修复后验证:

检查 结果
mvn test 2882/2882,F=0/E=0/S=0,BUILD SUCCESS
mvn clean verify 2882/2882,F=0/E=0/S=0,BUILD SUCCESS;Testcontainers 真实执行
python ..\docs\verify_openapi.py 182 paths / 220 operations,All checks passed

两项阻断已修复并验证。本轮仍未暂存、提交、推送或部署;Phase B 后续仍需独立复验结论确认后才能进入相应检查点。