22 KiB
职来(有职)前端 × 后端 联调问题清单(致后端同事)
生成时间:2026-09-30 最近复测:2026-09-30(第二轮:权重设置域接通 + 覆盖率审计脚本修正) 前端仓库:
mydreamjob(React Native + Expo,包管理器 yarn) 后端基址:https://job.youthome.com.cn/api/v1文档口径:以《职来后端接口.md》(248 个端点)为准。scripts/openapi.json已过期(仅 220 个,缺/auth/identity/select等 28 个),后续请以 md 为准。 产生方式:① 覆盖审计(scripts/audit_coverage.py:md 端点 ↔ 前端request()双向比对);② 实网探测(scripts/live_probe.py:真实学生账号登录后批量 GET);③ 写入类端点实网试跑(本轮新增:PUT 变体逐个试,定位出接口文档的 body 示例错误)。
0. 结论速览
| 指标 | 数值 |
|---|---|
| 后端端点总数 | 248 |
| 前端已接入 | 85(上轮 83) |
| 前端未接入 | 163(上轮 165) |
| 前端「幽灵调用」(调了后端不存在的端点) | 0 |
| 实网可探测 GET 端点 | 142(另有 42 个因缺路径 id 未探测) |
| 实网探测 正常 / 异常 | 50 / 50(本轮复测与上轮一致,无新增异常) |
| 后端已实现且有真实数据、但前端未接 | 25 个(见第 6.2 节,可排期接通) |
需后端配合的事项分四类:① 契约确认(第 1 节)② 端点缺失需补齐(第 2 节)③ 异常端点需关注(第 3 节)④ 接口文档与实际不符(第 1.12 节);测试账号问题见第 4 节;前端已自行修掉的问题见第 5 节。
本轮最重要的一条:
PUT /profile/me/review-weights的实际行为与接口文档不一致,照文档 body 写必失败(400code=1001)。详见 §1.12,附可复现命令。
1. 需要后端确认的接口契约
| # | 项目 | 前端现状(已按此实现) | 需后端确认 |
|---|---|---|---|
| 1 | 分页基准 | 按 0-based 传 page(Spring Data 默认),列表首页 page=0 |
全站是否统一 0-based?若个别端点从 1 开始,请指出 |
| 2 | 统一响应包 | 按 {code,message,data,requestId,timestamp},code=0 成功(HTTP 200),失败读 message |
成功码是否恒为 0?requestId 是否用于排障回溯 |
| 3 | 鉴权 | Authorization: Bearer <accessToken>;401 时用 refreshToken 静默换新(双 Token) |
刷新端点与失效口径;刷新失败是否也返回 401 |
| 4 | 401 / 403 语义 | 401=未登录或 Token 失效;403=角色无权限 | 确认区分稳定(实网 403 返回 code=2002) |
| 5 | 错误码清单 | 已观测:2002=forbidden、6001=profile not found |
请提供完整业务错误码表(前端要按码做差异化提示,不能只靠文案) |
| 6 | 角色模型 | STUDENT / COMPANY / ADMIN;/companies/me/*、/company/*、/admin/* 对 STUDENT 一律 403 |
企业端角色是否单一 COMPANY?有无子角色 |
| 7 | 岗位状态枚举 | 前端把后端 status 映射为中文(如「招聘中」) |
请给 status 全量取值集合 |
| 8 | 求职偏好字段缺口 | 后端下发多值数组 + 英文 token(preferredJobTypes[]、employmentTypes:['FULL_TIME']),前端取首项并译中文 |
后端未下发「期望薪资」「求职状态」,前端只能显示「未设置」。是否补字段? |
| 9 | 测评(assessment) | 按「答卷(attempt)」模型桥接:一次抽 30 题 → 批量保存 → 提交出结果;保存/提交带乐观锁 version,且 typeCode+version 锁定同一套题 |
并发冲突返回什么码/结构?前端需据此提示「题目已更新,请重试」 |
| 10 | 能力测评(ability) | 用 attemptQuestionId + selectedOptionId 提交,无 version |
与测评(9)不一致是否有意?能力测评是否也需要乐观锁 |
| 11 | 岗位详情口径 | 学生端公开详情走 GET /jobs/public/{jobId};GET /jobs/{jobId} 是公司端看自己发布的岗位 |
确认:/jobs/{jobId} 对 STUDENT 返回 403 是预期(学生不该走这条) |
| 12 | 权重保存写入模式 | 实测为两种互斥模式:套模板只传 templateCode、自定义只传 weights;不能同时传 |
⚠️ 接口文档的 body 示例 {templateCode, weights} 是错的,照抄必 400。详见 §1.12 |
| 13 | 权重维度集合与命名 | 后端 8 个维度(BENEFITS/WORK_LIFE_BALANCE/GROWTH/TEAM_ATMOSPHERE/PROMOTION/AUTHENTICITY/JD_CLARITY/SALARY_TRANSPARENCY),只下发英文 token,不下发中文名 |
前端已自建 token→中文名映射表兜底。建议后端在权重/模板响应里补 displayName,否则后端每加一个维度,前端都要发版才能显示中文 |
| 14 | 权重来源枚举 | 实测三种:SYSTEM_DEFAULT(从未保存)/ TEMPLATE(套过模板)/ CUSTOM(自定义过) |
确认这三态是否稳定;updatedAt 是否只在保存过之后下发 |
| 15 | 错误码补齐 | 本轮新观测:2005 = 手机号或密码错误 |
并入 §1.5 的「完整错误码表」诉求 |
1.12 接口文档与实际不符:PUT /profile/me/review-weights(需后端修正文档或实现)
问题:《职来后端接口.md》给该端点的 body 示例是
{ "templateCode": "string", "weights": { "property1": 0, "property2": 0 } }
但实网(学生账号 19900009021,2026-09-30 实测)照此提交必失败。逐变体实测结果:
| 提交的 body | 实网结果 |
|---|---|
{ "templateCode": "BALANCED" } |
✅ 200,source 变 TEMPLATE |
{ "weights": { …八个维度全给,总和 100… } } |
✅ 200,source 变 CUSTOM,templateCode 消失 |
{ "templateCode": "BALANCED", "weights": {…} } ← 文档示例 |
❌ 400 code=1001 bad request |
{ "configuredWeights": {…} } |
❌ 400 code=1001 |
{ "weights": {…}, "source": "USER_CUSTOM", "templateCode": "CUSTOM" } |
❌ 400 code=1001 |
{ "templateCode": "CUSTOM" }(自定义模板码) |
❌ 400 code=1001 |
{ "templateCode": "NONEXIST" } |
❌ 400 code=1001 |
{ "weights": {…总和 90…} } |
❌ 400 code=1001 |
{ "weights": {…少一个维度…} } |
❌ 400 code=1001 |
{ "weights": {…含未知 token UNKNOWN…} } |
❌ 400 code=1001 |
{} / { "weights": {} } |
❌ 400 code=1001 |
可复现命令(替换 <TOKEN>):
# 1) 登录取 token
curl -s -X POST https://job.youthome.com.cn/api/v1/auth/password/login \
-H 'Content-Type: application/json' \
-d '{"phone":"19900009021","password":"ZhilaiTest!2026#A"}'
# 2) 文档示例(同时传两者)→ 400 code=1001
curl -s -X PUT https://job.youthome.com.cn/api/v1/profile/me/review-weights \
-H "Authorization: Bearer <TOKEN>" -H 'Content-Type: application/json' \
-d '{"templateCode":"BALANCED","weights":{"BENEFITS":12.5,"WORK_LIFE_BALANCE":12.5,"GROWTH":12.5,"TEAM_ATMOSPHERE":12.5,"PROMOTION":12.5,"AUTHENTICITY":12.5,"JD_CLARITY":12.5,"SALARY_TRANSPARENCY":12.5}}'
# 3) 只传 weights → 200
curl -s -X PUT https://job.youthome.com.cn/api/v1/profile/me/review-weights \
-H "Authorization: Bearer <TOKEN>" -H 'Content-Type: application/json' \
-d '{"weights":{"BENEFITS":12.5,"WORK_LIFE_BALANCE":12.5,"GROWTH":12.5,"TEAM_ATMOSPHERE":12.5,"PROMOTION":12.5,"AUTHENTICITY":12.5,"JD_CLARITY":12.5,"SALARY_TRANSPARENCY":12.5}}'
需要后端(二选一):
- (推荐)修正文档:把 body 示例改为「二选一」的说明,并注明互斥规则与 400
code=1001; - 或修正实现:允许同时传(此时以
weights为准、templateCode仅作来源标注)。
另请补两点:① 400 的 message 只有 bad request,无法定位是「总和不为 100」「缺维度」还是「两个字段同时传」——建议按下述细分错误码,前端要据此给不同的提示文案;② 权重总和是否必须严格等于 100(实测不等即 400),是否允许 99.9/100.1 这类浮点误差。
2. 后端未实现 / 缺失的端点(实网 404,需补齐)
以下端点前端已按接口文档实现调用,但实网返回 404 not found,对应页面因此无真实数据源:
| 端点 | 实网结果 | 受影响功能 | 建议 |
|---|---|---|---|
GET /salary-calculator/calculations、/cities、/cities/{cityCode}、/calculations/{id}、DELETE /calculations/{id} |
404 | 薪资计算器工具页 | 优先补齐,属求职者高频工具 |
GET /skills/word-cloud、/skills/match、/skills/learning-path |
404 | 成长域「技能词云 / 岗位匹配 / 学习路径」 | 补齐或明确砍掉(前端好同步摘页面) |
GET /onboarding/company/checklist-items、/onboarding/company/knowledge |
404 | 企业端入职准备 | 补齐 |
GET /account/deletion-request |
404 | 账号注销申请查询(POST 同路径待验) | 确认注销链路:查询端点是否存在 |
GET /admin/reviews/replies/pending、GET /company/review-reply/stats |
404 | 评价回复审核 / 统计 | 确认是否已实现 |
GET /profile/me/resume |
404(code=6001 profile not found) |
简历页 | 学生尚未创建简历时建议返回 200 + 空结构,否则前端把「未填写」当「请求失败」 |
GET /applications/me |
200,但 items 只有 {id, jobId, studentUserId, status, createdAt, updatedAt} |
投递列表 / 求职进度页 | 缺岗位名、公司名、城市、薪资等展示字段,前端只拿到 jobId。请 join 出岗位摘要;否则前端只能逐条反查岗位(N+1,列表 20 条即 20 个请求) |
本轮复测(2026-09-30 第二轮):上表 404 组的结论无变化,均仍为 404。另
GET /jobs/{jobId}/salary在接口文档中不存在(salary-calculator 域也 404)。早期前端误调该端点,已在本次修正中移除。若产品需要「按经验段拆分的真实薪资」,请后端设计并给出端点。
附:
/jobs/filters可正常返回(城市 10 个、薪资区间 2207–74568、雇佣类型 4 种),但前端岗位筛选仍是本地硬编码;若后端确认该端点稳定,前端下一轮改为下发制(避免两边枚举分叉)。
3. 实网探测异常汇总(142 个 GET,50 个异常)
3.1 403 forbidden(角色权限,预期,但请复核清单)
/admin/** 全部(scoring / reviews / reports / companies / candidate-scoring)、
/companies/me/** 全部(jobs / talent-invitations / match-rule / onboarding / verification / job-reviews)、
/company/** 全部(applications / candidates / scoring)、
/jobs/{jobId}、/jobs/{jobId}/competition-ranking、/offers/statistics。
学生账号访问自然 403,属预期。唯一需要确认的是
/jobs/{jobId}:学生端应走/jobs/public/{jobId},请确认这条 403 是设计如此。
3.2 400 missing parameter(前端需带参,请确认参数名与必填性)
| 端点 | 缺失参数 |
|---|---|
GET /jobs/compare |
ids |
GET /offers/compare |
ids |
GET /company/applications |
jobId |
GET /admin/scoring/fairness/overview |
period |
GET /admin/scoring/fairness/dimensions |
period |
GET /admin/scoring/risk/trend |
bucket |
GET /admin/companies/verifications/history |
verificationId |
3.3 404 not found(未实现)
见第 2 节。
4. 测试账号
| 账号 | 密码 | 实网结果 |
|---|---|---|
19900009021(学生) |
ZhilaiTest!2026#A |
✅ 可用,登录返回 role=STUDENT, userId=4 |
19900009801(企业) |
文档给的密码 | ❌ 401 code=2005 手机号或密码错误 |
19900009802(企业) |
文档给的密码 | ❌ 401 code=2005 手机号或密码错误 |
本轮(2026-09-30 第二轮)对两个企业号复测了 4 组常见口令变体(
ZhilaiTest!2026#A/…#B/Test!2026#A/Zhilai2026!),全部返回 401code=2005。两点请求:① 确认这两个手机号是否真的已注册为企业账号(也可能只是没注册,那 2005 属于正确返回,但请给一组真实可用的);②code=2005同时用于「手机号不存在」与「密码错误」,前端无法区分,建议拆分或至少让文案可辨别。
需后端提供一组可用的企业端测试账号(含正确口令),否则企业端 /companies/me/**、/company/** 无法联调(学生 token 一律 403)。同时请提供一组 ADMIN 测试账号用于后台域联调。
5. 前端已自行修复(无需后端改动)
| 问题 | 处理 |
|---|---|
分页基准错误:此前传 page=1,会静默丢掉第 1 页数据 |
改为 0-based page=0,并封装 toBackendPage/toFrontendPage |
岗位 DTO 字段名不一致(title/company.name/薪资单位「元」) |
新增映射:title→positionName、company.name→companyName、薪资「元→K」、status→中文 |
| 求职偏好结构不一致(后端多值数组+英文 token ↔ 前端单值中文) | API 层双向映射(取首项 / token↔label),并新增 EMPLOYMENT_TYPE_LABEL/EMPLOYMENT_TYPE_TOKEN |
岗位评分调了不存在的 GET /jobs/{id}/score |
改打真实端点 GET /jobs/{id}/personalized-review-score,新增 mapPersonalizedScore(后端 dimensionScores 对象 → 雷达图维度数组) |
岗位详情调了不存在的 GET /jobs/{id}/salary,且它在 Promise.all 中 → 404 会让整个详情页加载失败 |
移除该调用与「真实薪资区间」表;薪资仍由岗位对象的 salaryMin/salaryMax 展示 |
| 测评 / 能力测评的单题推进 UI 与后端「答卷模型」不同构 | Hook/API 层桥接(抽题→答题→提交→取果),沉淀 mapAttemptToAssessment 等 |
后端错误响应体无 data 字段,导致真实 message(如 sms rate limited、unsupported sms purpose)被覆盖成笼统的「请求参数有误」 |
请求层新增 extractError():错误分支不再要求 data 存在。影响所有非 2xx,不止验证码 |
覆盖率审计脚本用正则 <[^>]*> 解析 request<T>() 泛型,遇到嵌套泛型(如 request<{items: Omit<X,'id'>[]}> 中第一个 > 就收尾)会静默漏检,覆盖率被系统性低估 |
改为平衡尖括号解析(忽略 =>)。修正后已接入 83 → 85,未接入 165 → 163 |
评分权重设置页(app/jobs/weight-settings.tsx + useWeightSettings)此前全走 mock,维度是前端自造的「九大中文维度」 |
本轮接通真实后端:并行拉 GET /profile/me/review-weights + GET /review-weight-templates,维度集合改为以后端下发为准(8 个英文 token → 自建中文名映射,未登记 token 原样兜底显示);保存按 §1.12 的互斥双模式择一提交 |
⚠️ 关于 mock 开关的事实更正(前一版文档此处描述有误):
app.config.js的extra.apiUseMock = true时,isMockMode()为真,api.ts 中 71 处if (isMockMode())的业务函数一律返回 mock——包括已与后端对齐的投递、岗位、测评等域。也就是说「已接通的域走真实后端」并不自动成立,必须把apiUseMock置为false才会真实触网。 唯一的例外是src/api/auth.ts(鉴权域已于 2026-09-30 整体硬切真实后端,不再读该开关)。 因此 App 内联调的正确姿势是:apiUseMock: false。当前为true(保 App 全程可跑、不回归),此时 App 里看到的岗位/投递/权重数据都是 mock,不代表后端返回了假数据;真实链路的验证目前走scripts/live_probe.py与 curl 直连。 未接通的「幽灵域」函数已按约定写成「真实模式下也不触网、直接返回 mock」,故翻为false不会引发大面积报错;但仍建议翻开关后跑一轮全页面冒烟。
6. 覆盖率明细与后续排期
未接入 163 个端点,按域分布(2026-09-30 第二轮,审计脚本修正后):
| 域 | 未接入数 | 性质 |
|---|---|---|
| admin | 34 | 前端未建后台页(后端应已实现) |
| profile | 28 | 候选人评分/数据导出/推荐/隐私/匹配阈值等 |
| companies | 20 | 企业端未接 |
| offers | 12 | 前端未接(Offer 域) |
| company | 10 | 企业端候选人/评分 |
| onboarding | 9 | 含 2 个后端 404 |
| interviews | 9 | 面试评分域,前端未接 |
| jobs | 7 | 含比较/竞争排名等 |
| salary-calculator | 6 | 后端 404 |
| reviews | 4 | 评价回复域 |
| blocks / reports / applications / skills | 3 × 4 | 屏蔽 / 举报 / 投递剩余 / 后端 404 |
| account / health / info / 等 | 6 | 工具类端点 |
6.1 前端后续接入优先级
- P0:求职者核心闭环 ——
applications(投递剩余)、interviews(面试)、offers(Offer) - P1:企业端
companies/**、company/**、reviews回复、blocks、reports - P2:
admin/**(后台)、profile剩余(候选人评分、数据导出、推荐、匹配阈值)
6.2 后端已实现且有真实数据、前端尚未接通的端点(本轮盘点,可直接排期)
这 25 个端点实网返回 200 且带真实数据,是「接通即见效」的部分,不需要等后端做任何事。 数据来源:
scripts/live_probe.py实网结果 × 覆盖率审计未接入清单 求交集。
| 端点 | 实网返回要点 | 建议 |
|---|---|---|
GET /profile/me/job-recommendations |
推荐岗位列表,含 title/city/employmentType/salaryMin/Max/mode/reasons |
P1 求职者侧可直接出「为你推荐」 |
GET /profile/me/review-weights ✅本轮已接 |
8 维度权重 + source/templateCode |
已完成 |
GET /review-weight-templates ✅本轮已接 |
4 个模板(BALANCED/SALARY_FIRST/GROWTH_FIRST/STABILITY_FIRST) | 已完成 |
PUT /profile/me/review-weights ✅本轮已接 |
见 §1.12 互斥双模式 | 已完成 |
GET /jobs/{jobId}/match |
matchScore / eligible / matchedConditions / explanation / gaps(含「未设置城市偏好」这类可执行诊断) |
P1 「双向透明」核心:岗位详情页展示「为什么匹配 / 差在哪」 |
GET /jobs/filters |
城市 10 个、薪资区间 2207–74568、雇佣类型 4 种 | P1 替换前端硬编码筛选枚举 |
GET /profile/me/privacy + PUT |
resumeVisibility / contactVisible / version |
P1 简历可见性与联系方式开关(合规相关) |
GET /profile/me/match-rule + PUT |
requiredHrScore(默认 60) |
P1 学生侧双向匹配阈值 |
GET /profile/me/talent-discovery + PUT |
enabled / version(默认 false) |
P2 「让企业发现我」开关 |
GET /profile/me/candidate-scoring |
overallScore + 8 维 weights |
P2 候选人评分卡 |
GET /profile/me/candidate-scoring/trend |
月度趋势(含 rulesVersion) |
P2 |
GET /profile/me/candidate-percentile |
百分位(当前 score/percentile 为 null,cohortSize 有值) |
需后端确认「样本不足」时的口径 |
GET /profile/me/candidate-score-card |
评分卡(overallScore 为 null) |
同上 |
GET /job-recommendation-taxonomies |
行业 / 职能分类树(code/type/displayName/parentCode) |
P2 推荐偏好选择器 |
GET /profile/me/deletion + POST |
{userId, status, version} |
P2 注销状态查询(注意:/account/deletion-request 是 404 的另一套路径,见 §2) |
实网 200 但返回空、需先造数据才能联调的端点(不阻塞接口本身):
GET /offers、GET /blocks/me、GET /reports/me、GET /profile/me/talent-invitations、GET /profile/me/data-export-tasks、GET /profile/me/candidate-score-trend、GET /profile/me/candidate-scoring/appeals。
→ 其中 /interviews/me、/offers、/conversations 返回空数组,加上企业账号 401,是面试/Offer 域目前无法实网验证字段的根因(见 §4)。请后端为学生测试号造 1–2 条面试与 Offer 数据,或提供有数据的学生账号。
7. 待后端回复的清单(速览)
本轮新增(P0,优先)
- §1.12
PUT /profile/me/review-weights的 body 契约:接口文档示例是错的,同时传templateCode+weights必 400code=1001。请确认「二选一」为准,并修正文档;同时请把bad request细分为可辨识的错误码(总和≠100 / 缺维度 / 未知 token / 两字段同时传)。 - §1.13 权重维度补
displayName:后端只下发英文 token(8 个维度),前端不得不自建映射表——后端每新增维度都要前端发版。建议在review-weights与review-weight-templates响应里带上中文名。 - §2
GET /applications/me缺展示字段:items 只有 jobId,没有岗位名/公司名/城市/薪资,投递列表无法直接渲染,请 join 出岗位摘要。 - §4 企业账号仍全部 401(
code=2005):试了 4 组口令变体均失败。请确认这两个号是否真已注册,并提供一组可用的企业 / ADMIN 账号;另请为学生测试号造 1–2 条面试与 Offer 数据(/interviews/me、/offers现为空数组,字段无法验证)。
沿用上轮
5. 第 1 节 11 项契约逐条确认(尤其:分页基准、完整错误码表、测评 version 冲突码、岗位 status 枚举、偏好缺「期望薪资/求职状态」)。
6. 第 2 节 404 端点:是「未实现」还是「路径变更」?给出排期或确认砍掉(尤其 salary-calculator/**、skills/**、onboarding/company/**)。
7. 第 3.2 节 400 端点:确认必填参数名(ids / jobId / period / bucket / verificationId)。
8. GET /jobs/{jobId} 对学生的 403 是否为预期(学生端是否只应走 /jobs/public/{jobId})。
9. /profile/me/privacy 的 version 字段语义(乐观锁?前端 PUT 是否需要回传?当前前端未回传也能成功,请确认)。
10. candidate-percentile / candidate-score-card 的 score/percentile/overallScore 当前为 null:是「样本不足」的正常态,还是数据未产出?(前端需据此决定展示「暂无数据」还是继续 loading)
11. 是否需要后端补「岗位分经验段薪资」端点(前端已移除误调的 /jobs/{jobId}/salary)。