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.
 
 
 

5.5 KiB

R01 结构与性能收束权威设计

1. 目标与边界

R01 在 S01、S02、S03、Q01 已冻结行为之上收束重复代码和可证明的性能热点。所有改动必须保持 HTTP 契约、业务错误码、权限、事务、审计、隐私和数据副作用不变。

禁止在 R01 中新增业务功能、公开接口、Flyway、表或索引;禁止改变评分公式、推荐权重、百分位定义、消息未读定义、简历权限、Token 语义或岗位搜索语义。索引和全文搜索仅形成后续提案,不在本轮实施。

基线:分支 refactor/r01-structure-performance,起点 77dc624;OpenAPI 176 paths / 214 operations;全量测试 1890/1890。

2. 已确认问题

  • 41 个生产文件包含私有身份解析方法;不同实现对匿名主体、非数字主体和溢出主体的处理不一致。
  • 46 个生产文件包含私有 requestId 获取方法;部分直接取请求头,未复用 RequestIdFilter 的规范化结果。
  • 角色断言以 assertStudentRole、requireStudent、requireAdmin 等形式分散,存在重复查用户和语义漂移风险。
  • GlobalExceptionHandler.resolveHttpStatus 是大型 if 链,并含重复分支。
  • overviewStatus 在评分引擎和竞争力排名中存在两套等价规则。
  • JobRecommendationService.preferenceScore 被压缩为单行,权重含义难以评审。
  • CompanyScoringDashboardService.computePercentiles 对每名候选人扫描全部分数,复杂度 O(n²)。
  • MessagingService.listConversations 对每个会话分别加载最多 500 条消息和 read 状态,查询次数随会话数线性增长。
  • PersonalDataCollector 存在可批量化的数据读取。
  • 岗位关键字 %keyword% 与排序 filesort 是已记录风险,但未经产品和容量决策,不在 R01 直接改搜索技术栈。

3. 分批策略

R01-A:请求身份与 requestId 基础设施

扩展既有 CurrentUserAccessor,使所有迁移入口统一拒绝 null、匿名、失效、空白、非 ASCII 数字、溢出和非正 userId。不得在 Controller 接受 userId 参数。

新增单一 RequestIdAccessor:优先读取 RequestIdFilter 已规范化的 request attribute;缺失时使用现有安全归一化逻辑,而不是信任任意长度请求头。响应、审计和日志必须使用同一 requestId。

按小批次迁移 Controller,每批最多 8 个类。每个调用点逐项锁定原 401/403 和资源归属语义;禁止机械全仓替换。

角色授权暂不与身份解析混在同一提交。先盘点后新增 UserRoleGuard,保持 USER_NOT_FOUND 与 FORBIDDEN 的现有差异,不增加隐式缓存。

R01-B:异常映射和业务规则唯一来源

将业务错误码到 HTTP 状态的关系提取为不可变映射,启动或测试时检测重复和遗漏;默认仍为 400。不得更改任何现有映射。

提取唯一 CandidateOverviewStatusPolicy。评分引擎与竞争排名调用同一实现,并用参数化契约测试证明旧、新结果完全一致。

R01-C:纯计算与可读性

将百分位改为“排序一次 + 分数组到 lower-rank 的映射”,复杂度 O(n log n)。并列分数必须获得相同百分位;null、小于 2 个有效分数、精度和 HALF_UP 规则保持不变。

拆分 preferenceScore 为命名明确的五个维度贡献函数。城市 30、用工类型 20、最低薪资 20、行业 15、岗位族 15 及缺失维度不计分的规则不得变化。

R01-D:数据库查询收束

消息会话列表新增固定次数聚合查询,一次性按 conversationIds 计算未读数。定义保持:排除 RECALLED;发送者消息是否未读沿用当前 read-row 语义;不存在 read 行即未读。1/20/50 个会话查询次数必须固定。

markRead 是否突破 500 条属于独立语义问题,本轮先写失败证据和决策,不与列表未读优化混改。

PersonalDataCollector 按关联键批量读取;导出 JSON 字段、顺序、空值、脱敏与表范围不变。以 1/20/50 数据规模记录查询数和完整导出结果摘要。

R01-E:真实环境复验

在 MySQL 8.4 上执行查询预算、结果等价和 EXPLAIN。不得把 H2 结果当成 MySQL 证据;不得为了 EXPLAIN 修改迁移或索引。

4. 关键契约

模块 必须保持
身份 非法主体为 401,不回显 principal,不产生业务写入
角色 原有 USER_NOT_FOUND/FORBIDDEN、角色组合与归属判断不变
requestId 最大长度、生成规则、响应头、响应体、MDC 与审计同源
异常 每个 ErrorCode 的 HTTP 状态与响应 body 不变
百分位 lower / (n-1) * 100、并列同值、scale=2、HALF_UP
推荐 五项权重和缺失项分母规则逐字保持
未读 RECALLED 不计入;已有 read 行语义不变
导出 字段、隐私、顺序、内容与错误语义不变

5. 证据要求

每一批必须先建立特征测试或失败测试,再改生产代码。性能结论必须同时包含结果等价断言、SQL 次数和反证测试;仅写“无 N+1”不算证据。

自动化最低门槛:定向测试、关联回归、mvn test、mvn clean verify、静态 OpenAPI;Failures/Errors/Skipped 均为 0。涉及 SQL 的批次还必须有真实 MySQL 黑盒。

6. 提交边界

每个子阶段独立分支检查点或独立提交;不得将 R01-A 到 R01-E 压成一个大提交。所有历史未跟踪日志和验收产物保留但不提交。不得使用 git add -A、reset、restore、checkout 或 clean。