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

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 写必失败(400 code=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!),全部返回 401 code=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. §1.12 PUT /profile/me/review-weights 的 body 契约:接口文档示例是错的,同时传 templateCode + weights 必 400 code=1001。请确认「二选一」为准,并修正文档;同时请把 bad request 细分为可辨识的错误码(总和≠100 / 缺维度 / 未知 token / 两字段同时传)。
  2. §1.13 权重维度补 displayName:后端只下发英文 token(8 个维度),前端不得不自建映射表——后端每新增维度都要前端发版。建议在 review-weights 与 review-weight-templates 响应里带上中文名。
  3. §2 GET /applications/me 缺展示字段:items 只有 jobId,没有岗位名/公司名/城市/薪资,投递列表无法直接渲染,请 join 出岗位摘要。
  4. §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)。