# Phase A 企业注册与入驻基础闭环实施报告 日期:2026-09-22 范围:企业专用注册、`company-register` 验证码、事务创建、入驻聚合查询 基线:`master` / `00153aa5d92d13e66d6cdc42ecd1fe903d6e666` ## 1. 结论与边界 本阶段实现了企业注册和企业入驻状态聚合,不进入 Phase B 已认证企业业务门禁,不进入 Phase C 管理员用户与角色管理,不修改 `users.version`、`user_management_actions`、岗位/简历/Offer/面试等业务门禁。 当前未暂存、未提交、未推送;V1–V46 未修改;未新增 V47;没有修改生产数据库的迁移脚本。 ## 2. 权威库存 编码前核对了 `AuthController`/`AuthService`、`SmsVerificationService`/`SmsVerificationCodeRepository`、`PasswordPolicy`/`PasswordAuthenticationService`、`AuthSessionService.createSession`、注销手机号墓碑、`User`/`UserRepository`/`user_roles`、`Company`/`CompanyRepository`/`CompanyService`、企业认证服务与状态、`ErrorCode`/`ErrorHttpStatusRegistry`、`SecurityConfig` 以及 OpenAPI 基线。 实际短信用途以源码为准,保留 `login`、`password-set`、`password-reset`,新增并白名单化 `company-register`;任意其他非空用途均拒绝。OpenAPI 基线为 180 paths / 218 operations,本阶段变更为 182 paths / 220 operations。 ## 3. 修改文件 生产代码新增: - `auth/company/CompanyRegistrationProperties.java` - `auth/company/CompanyRegistrationRequest.java` - `auth/company/CompanyRegistrationResponse.java` - `auth/company/CompanyRegistrationService.java` - `company/CompanyOnboardingResponse.java` - `company/CompanyOnboardingService.java` 生产代码修改:`AuthController`、`CompanyController`、`SecurityConfig`、`application.yml`、`SmsVerificationService`、`SmsVerificationCodeRepository`、`AuditService`、`ErrorCode`、`ErrorHttpStatusRegistry`。 测试新增/调整为 Phase A 行为锁定、既有短信/映射/OpenAPI 计数同步;未修改 V1–V46 或 `pom.xml`。 ## 4. 企业注册契约 新增 `POST /auth/company/register`,使用强类型请求 DTO 和 Jakarta Validation。客户端只能提交手机号、验证码、密码及企业资料,不能提交 `userId`、`role`、`verified` 或 `passwordHash`。成功响应包含 `userId`、`companyId`、`role=COMPANY`、`onboardingStatus=UNVERIFIED`、现有格式的 Access/Refresh Token 和 `expiresIn`,不回显完整手机号、密码、验证码或密码摘要。 新增 `GET /companies/me/onboarding`,仅认证用户可访问,响应只包含企业安全摘要、聚合状态、认证申请安全摘要、`missingFields` 与 `allowedActions`,不返回 `materialsJson`、`decisionReason`、`reviewerId`、存储路径或其他敏感企业数据。未新增重复的 verifications 接口。 ## 5. 事务边界 `CompanyRegistrationService.register` 在一个数据库事务中完成:功能开关、短信校验/消费、注销墓碑、手机号占用检查、BCrypt 密码、用户、`user_roles`、企业资料、登录会话和固定脱敏审计事件。用户保存并 flush 后再创建企业和会话;注册失败不会向客户端返回未提交 Token。数据库唯一约束是并发最终防线,`DataIntegrityViolationException` 转为统一冲突业务错误。 审计写入使用 `AuditService.logRequired` 的 `PROPAGATION_MANDATORY` + `saveAndFlush`,因此审计失败会使注册事务回滚。Redis 限流键不属于数据库事务,报告和测试没有虚假声称 Redis 自动回滚。 ## 6. 验证码原子消费 `SmsVerificationCodeRepository.consumeIfValid` 使用条件更新,同时约束 `id`、`used=false`、未过期和 `attempts < maxAttempts`。只有受影响行数恰为 1 才算消费成功,0 行统一按验证码无效处理。错误验证码仍递增尝试次数并保留锁定语义;用途在查询前经过固定白名单校验。企业注册事务回滚时,数据库中的 `used` 更新随事务回滚。 修复前行为是“查询 → 修改实体 `used` → `save`”,受控变异到该实现时,`SmsVerificationAtomicConsumptionTest` 为 4 run / 2 failures(条件更新未调用、0 行竞争未失败);恢复条件更新后为 4 / 0 / 0 / 0。该变异证明测试不是空转。 ## 7. 入驻状态推导 - `companies.verified=true` → `VERIFIED`; - 未认证且无申请 → `UNVERIFIED`; - 最新 `PENDING` 或 `RESUBMITTED` → `PENDING`; - 最新 `REJECTED` → `REJECTED`; - 未认证但最新 `APPROVED` 或未知状态 → 失败关闭并返回专用一致性错误。 `missingFields` 从资料实际内容派生,没有新增重复状态字段;`allowedActions` 随聚合状态返回。 ## 8. 错误码与安全配置 新增唯一数字码:`COMPANY_REGISTRATION_DISABLED=10301`、`COMPANY_REGISTRATION_CONFLICT=10302`、`COMPANY_ONBOARDING_INCONSISTENT_STATE=10303`、`COMPANY_REGISTRATION_DEPENDENCY_UNAVAILABLE=10304`。映射统一进入 `ErrorHttpStatusRegistry`:关闭/依赖不可用为 503,冲突/不一致为 409。 匿名放行仅新增 `POST /auth/company/register`;`GET /companies/me/onboarding` 保持认证,学生、管理员和匿名均不获得企业入驻数据。日志、审计和响应不记录密码、摘要、验证码、Token、SQL、手机号或请求 DTO。 ## 9. 测试矩阵 新增了企业注册 service/integration/concurrency、入驻 service/controller、验证码原子消费、用途白名单、事务回滚、敏感数据、功能开关等测试类;并同步了既有短信安全、公司 Controller 和异常映射测试。 - Phase A 定向测试:全部通过;两项阻断整改后的完整定向集 **28/28**; - 真实 MySQL/Testcontainers 并发:1/1,通过同验证码双线程仅一个成功; - `mvn test`:**2865 / Failures 0 / Errors 0 / Skipped 0**; - `mvn clean verify`:**2865 / Failures 0 / Errors 0 / Skipped 0**; - OpenAPI:`python ..\\docs\\verify_openapi.py` → **182 paths / 220 operations,All checks passed**。 全量验证期间出现的 Hikari 重连和 MySQL 8.4/Flyway 兼容性 warning 均未导致失败;一次与编译并发运行的错误结果未计入,随后单独顺序运行得到上述完整通过结果。 测试/脚本自身问题:早期黑盒驱动曾把短信发送参数误放在 JSON body(实际接口是 query 参数),且临时 JWT 密钥长度不足 32 字符;两项均在产品代码之外修正。重复运行时还触发了短信限流,随后销毁临时数据库并以全新隔离容器重跑,得到本节所列成功结果。未放宽产品断言。 ## 10. 真实 MySQL/Redis/RabbitMQ 黑盒 使用临时隔离容器(MySQL 8.4、Redis 7.4、RabbitMQ 3.13)和最新 JAR 完成真实 HTTP 验收。三中间件均真实健康,应用 `/api/v1/health` 为 200/UP。 验证结果:匿名 onboarding 为 401;无有效验证码注册被拒;发送 `company-register` 后注册 200;密码登录 200;`/auth/me` 200;带注册 Access Token 的 onboarding 200。数据库逐表核对为:目标手机号 `users=1`、`companies=1`、`user_roles=1` 且角色仅 `COMPANY`、`companies.verified=0`、验证码 `used=1`。密码登录产生第二个合法会话,属于预期行为。临时容器、临时 JAR 进程、临时端口和运行日志均已清理,未触碰既有 backend-infra 卷或历史数据。 ## 11. 敏感信息扫描 扫描新增生产代码、测试代码、OpenAPI、验证脚本相关输出和本报告:未发现真实手机号、明文密码、BCrypt 摘要、JWT/Bearer、Refresh Token、数据库/Redis/RabbitMQ 密码、私钥、AWS key 或连接串。黑盒使用的临时凭据只存在于进程环境和临时容器,测试结束后容器和日志已删除;报告不记录其值。 ## 12. 数据与兼容性 未新增数据库迁移;V1–V46 逐项保持不变。求职者短信登录、现有密码登录/设置/修改/重置、Token 刷新/登出、管理员初始化、既有企业资料和企业认证接口保持原路径与行为。企业注册后经现有 `/auth/password/login` 成功登录并得到 COMPANY 身份。 ## 13. 未进入的范围 本轮没有实现已认证企业业务门禁、管理员用户/角色管理、`users.version`、`user_management_actions`、岗位发布、简历、Offer 或面试门禁改造;没有提交、推送或进入 Phase B/C。 ## 14. 当前结束状态 验证完成后未暂存、未提交、未推送;无 Java/Maven 残留进程;8080/8081 空闲;临时测试账号与业务数据在隔离容器销毁时清理;未删除审计历史。工作区仍保留既有历史未跟踪文件和两个 Windows stat 缓存假阳性文件,未覆盖、删除或暂存。 ## 15. 结论 实施阶段完成后,独立复验发现功能开关的宽松布尔绑定问题;现已整改为字符串配置并严格使用小写字面量 `true` 判断。事务回滚测试也已补齐 users、user_roles、companies、session、audit 五类独立故障注入,新增回滚覆盖后的完整 Phase A 定向集为 **28/28**,全量回归为 **2865/2865**,均无失败、错误或跳过。