forked from baskbuddy/zhangshun
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.
8.1 KiB
8.1 KiB
统一求职进度聚合 API(V34)
版本:V34 设计锁定版 状态:已编码实现 适用范围:学生端只读接口,为「求职进度」页聚合投递、面试、结构化面试评分、Offer、同岗位竞争力排名与通知计数。
1. 定位与边界
为 STUDENT 提供「求职进度」页所需的单次聚合数据,避免前端串行调用多个接口。与既有接口边界:
| 接口 | 是否改动 | 关系 |
|---|---|---|
GET /jobs/{jobId}/competition-ranking |
不改 | 复用同套候选人池/排序/百分位口径(共享 CompetitionRankingCalculator),但本接口只返回摘要,不返回 dimensions/gaps/topCandidates |
GET /notifications |
不改 | 本接口只返回通知计数,通知标题/正文/详情仍由 /notifications 提供 |
GET /interviews/{id}/score 等 |
不改 | 仅读取已提交(SUBMITTED)评分摘要,不返回 DRAFT、不返回 comment |
GET /offers/... |
不改 | 仅返回 Offer 摘要,不返回薪资/条款细节 |
GET /profile/me/...、投递、面试 |
不改 | 本接口为其只读聚合,不重写既有语义 |
2. 接口
GET /api/v1/profile/me/career-progress
Query 参数:
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
page |
int | 否 | 0 |
页码,从 0 开始,>= 0 |
size |
int | 否 | 20 |
每页条数,1–50 |
status |
string | 否 | — | 投递状态过滤,仅 SUBMITTED/VIEWED/INTERVIEW/HIRED/REJECTED/WITHDRAWN |
- 角色:仅 STUDENT。
- 身份从 JWT 获取,不接收
userId/studentUserId/candidateId等身份注入参数。 - 新增 1 条 GET 路径,OpenAPI 125 → 126。
3. 权限矩阵
| 身份/场景 | HTTP | code | 说明 |
|---|---|---|---|
| 未登录 | 401 | 2001 | 既有安全机制 |
| COMPANY / ADMIN | 403 | 2002 | Service 层 assertStudentRole |
| STUDENT 正常 | 200 | — | 正常返回 |
page < 0 / size < 1 / size > 50 / 非法 status |
400 | 1001 | BAD_REQUEST |
不新增错误码,复用既有 ErrorCode。
4. 响应结构
ApiResponse<CareerProgressResponse>
CareerProgressResponse {
ProgressTodoSummary todo;
PaginatedApplications applications;
Instant generatedAt;
}
ProgressTodoSummary {
long pendingInterviewResponseCount; // 状态 SCHEDULED 的面试数量
long upcomingConfirmedInterviewCount; // 状态 CONFIRMED 且 startAt > 当前时间
long pendingOfferResponseCount; // 状态 SENT/NEGOTIATING 且 expireAt > 当前时间
long unreadNotificationCount; // 未读通知总数
long unreadReportDecisionCount; // 未读 REPORT_DECISION 通知数
}
PaginatedApplications {
List<CareerProgressItem> items;
long total;
int page;
int size;
int totalPages;
}
CareerProgressItem {
Long applicationId;
String applicationStatus;
LocalDateTime appliedAt;
LocalDateTime updatedAt;
JobSummary job;
InterviewSummary nextInterview; // 可空
ScoreSummary latestSubmittedInterviewScore; // 可空
OfferSummary offer; // 可空
CompetitionSummary competition;
}
JobSummary { Long jobId; String title; String companyName; String city; String employmentType; String jobStatus; }
InterviewSummary { Long interviewId; Integer roundNo; String title; String interviewType; LocalDateTime startAt; LocalDateTime endAt; String status; }
ScoreSummary { Long interviewId; BigDecimal overallScore; String recommendation; LocalDateTime submittedAt; }
OfferSummary { Long offerId; String status; String title; LocalDateTime expireAt; LocalDate onboardingDate; }
CompetitionSummary {
boolean eligible;
Integer rank; // 有分才返回;WITHDRAWN/REJECTED 或本人无分 → null
Integer cohortSize; // 仅 eligible=true 时返回
BigDecimal overallScore; // 本人无分 → null
BigDecimal percentile; // 有分者 < 2 → null
String status; // OK / PARTIAL / INSUFFICIENT_DATA / PENDING
String mySnapshotPeriod; // 本人实际使用快照月;无快照 → null
String rulesVersion;
}
明确不返回字段
- 不返回
studentUserId、candidateId、companyUserId、evaluatorUserId、phone、email、resumeUrl、passwordHash、JWT。 - 不返回通知标题/正文(
title/content)、举报详情、面试评分comment、Offer 薪资/条款细节(salaryMin/salaryMax/baseSalary/housingAllowance/annualBonusMonths/benefits/description/otherAllowances)。 - 不返回其他候选人的任何身份信息,也不返回 V32 的
dimensions、gaps、topCandidates。
5. 聚合口径
5.1 投递
- 读取当前学生全部投递,按
createdAt DESC分页。 status存在时按该状态筛选。- 不过滤
WITHDRAWN/REJECTED/HIRED,均属于进度历史。
5.2 Todo
| 字段 | 口径 |
|---|---|
pendingInterviewResponseCount |
当前学生 SCHEDULED 面试数量 |
upcomingConfirmedInterviewCount |
当前学生 CONFIRMED 且 startAt > now 的面试数量 |
pendingOfferResponseCount |
当前学生 SENT/NEGOTIATING 且 expireAt > now 的 Offer 数量 |
unreadNotificationCount |
复用 NotificationRepository 未读总数 |
unreadReportDecisionCount |
未读且 type='REPORT_DECISION' 的通知数量 |
5.3 nextInterview
- 只看该投递的
SCHEDULED / CONFIRMED面试,取startAt最早一场。 RESCHEDULE_REQUESTED不算已确定;已完成/取消/拒绝不作为 nextInterview。
5.4 latestSubmittedInterviewScore
- 只读
SUBMITTED状态的InterviewScore,每个applicationId取submittedAt最新一条。 - 不返回 DRAFT、不返回
comment。
5.5 Offer
- 仅学生本人
candidateId的 Offer;DRAFT永不返回。 - 优先展示
SENT/NEGOTIATING且expireAt > now的有效 Offer,按expireAt ASC取最早到期一条。 - 无有效 Offer 时,展示该投递
createdAt最新的一条非 DRAFT Offer。 - 不返回薪资/条款细节。
5.6 competition
- 投递状态
SUBMITTED/VIEWED/INTERVIEW/HIRED→eligible=true;WITHDRAWN/REJECTED→eligible=false(不返回其它排名字段)。 - 复用 V32 候选池口径、最新快照口径、
CandidateScoringCalculator#overallScoreFromSnapshots、稳定排序与百分位公式。 percentile = 严格低于本人分数的人数 / (有分候选人数 - 1) × 100,scale=2、HALF_UP;有分者< 2时null。- 不返回 V32 的
dimensions/gaps/topCandidates。
6. 综合分 / 排序 / 百分位
- 全部复用
CompetitionRankingCalculator(V32/V34 共享),禁止复制第二套公式、禁止 SQL 重算。 status复用引擎语义:OK / PARTIAL / INSUFFICIENT_DATA / PENDING,不把PENDING/INSUFFICIENT_DATA折算为 0。
7. 只读与性能
CareerProgressService标注@Transactional(readOnly=true),不写业务表/审计表/通知表/缓存。- 所有附属数据批量读取(jobs/companies/interviews/scores/offers/cohort/快照/通知计数),查询次数与分页
items数量无关,目标约 8–10 次,无 N+1。 size上限固定 50。
8. 数据库与 Flyway
- 不需要新表。
- 不新增 Flyway V34。
- 无 DDL、无数据变更。
9. 测试
CareerProgressServiceTest:权限/参数、空数据、完整聚合、DRAFT 不泄露、WITHDRAWN/REJECTED、无快照、批量查询无 N+1。CareerProgressControllerTest:STUDENT 正常、COMPANY/ADMIN 403、未登录 401、参数非法 400、脱敏断言。CareerProgressPersistenceIntegrationTest:真实仓库批量读取 + 只读性(关键表计数前后不变)。
10. 禁止事项(实现阶段)
- 不修改 Candidate Scoring Engine、Competition Ranking 对外接口/权限/状态口径。
- 不修改 Offer、Interview、Notification、Report/Block 的既有接口语义。
- 不新增数据库表、不新增 Flyway、不新增 Redis 缓存、不新增异步任务。
- 不新增错误码。
- 不做企业回复率、推荐算法、AI 分析、薪资建议、通知内容聚合。
- 不启停 Docker(黑盒阶段除外)。