13 KiB
岗位与企业展示字段 · 前端交接说明
批次:job-company-display-fields 后端基线:
1cbadc3e18a786ef642b0d329adde6b173c7b2ac(分支fix/mysql57-module-migrations-20261009) 数据库迁移:V57__job_company_display_fields.sql本文档面向前端工程师,描述本批岗位展示字段的接入参考,不改变既有字段语义。 更新:2026-10-19。本地后端验收已通过,但尚未提交、推送或发布;目标服务器是否具备这些字段必须按实际部署版本确认。本文不是前端已经完成接入的证明。
1. 新字段在哪些端点返回
| 端点 | 新增顶层字段 | company 摘要 |
|---|---|---|
GET /jobs(公开列表) |
✅ 5 个 | ✅ 新增 |
GET /jobs/search(公开搜索) |
✅ 5 个 | ✅ 新增(兼容扩展) |
GET /jobs/public/{jobId}(公开详情) |
✅ 5 个 | ✅ 新增 |
GET /companies/{companyId}/jobs |
✅ 5 个 | ✅ 新增 |
GET /jobs/{jobId}(企业端自己看) |
✅ 5 个 | ❌ 不返回 |
POST /jobs、PUT /jobs/{jobId}、PATCH /jobs/{jobId}/status |
✅ 5 个(响应同企业端视图) | ❌ 不返回 |
GET /companies/me/jobs(企业端岗位管理) |
✅ 5 个 | ❌ 不返回 |
GET /companies/{companyId}(公开企业详情) |
companySize |
— |
GET/POST/PUT /companies/me(企业端自己看) |
companySize |
— |
GET /jobs/favorites(我的收藏) |
✅ 5 个 | ❌ 不返回 |
「5 个」= experience、education、benefits、salaryMonths、workLocation。
说明:收藏列表复用公开视图组装,因此自动带上 5 个新增顶层字段;本批未为收藏域增加
company嵌套。若前端需要企业名,请先用岗位的companyId调GET /companies/{companyId}。
2. 字段类型、单位、缺省值与更新语义
2.1 岗位新增字段
| 字段 | 类型 | 缺省值 | 写入语义 |
|---|---|---|---|
experience |
string | null | null |
null/缺省 = 保持原值;空白串 = 清空为 null;非空 = 去两端空白后保存;最长 40 |
education |
string | null | null |
同 experience,最长 40 |
benefits |
string[] | [](永不返回 null) |
null/缺省 = 保持原值;[] = 清空;非空 = 逐项 trim → 拒绝空白项 → 单项 ≤40 → 去重且保留首次出现顺序 → 最多 20 项;违反任一条返回 400 |
salaryMonths |
int | null | null |
null/缺省 = 保持原值(本批不提供显式清空);非 null 必须 1—24,0 视为非法返回 400;只接受 JSON 整数——小数(1.9/1.0)、数字字符串("12"/"")、布尔、数组、对象、超出 int 范围一律 400,绝不截断或四舍五入 |
workLocation |
string | null | null |
同 experience,最长 255 |
2.2 企业新增字段
| 字段 | 类型 | 缺省值 | 写入语义 |
|---|---|---|---|
companySize |
string | null | null |
null/缺省 = 保持原值;空白串 = 清空为 null;非空 = trim 后保存;超过 40 字符返回 400,不截断 |
上述「缺省/null 保持原值」针对更新;创建时没有旧值,对应可选字段存 NULL。响应中的福利始终为数组,请求中的 benefits 可以为 null。
2.3 福利归一化与 OpenAPI 3.1
- 去重后的数量上限为 20,不是原始请求数组上限:21 个相同标签会成为 1 项并可接受,21 个不同标签返回 400。
- 单项长度在 trim 后判断;空白项拒绝,不是自动删去。
- 更新
benefits: null保留原值,benefits: []清空。不要把这两种请求合并。 - 静态及运行时契约用
x-normalization描述trimItems、deduplicate、rejectBlankItems、maxItems=20、itemMaxLength=40,不对原始请求误加maxItems/元素maxLength。 - 这是项目的业务扩展,通用 OpenAPI 客户端不会自动执行归一化。若前端加校验,应按相同顺序处理;后端仍是最终校验方。
- 运行时为 OpenAPI 3.1,可空字段使用包含 null 的联合类型,不以缺少 OpenAPI 3.0 的
nullable判为不可空。
2.4 单位与语义(保持不变)
salaryMin/salaryMax:单位仍是「元/月」,本批未改成 K。前端展示 K 时自行/1000(既有约定)。- 行业仍来自
companies.industry,不在岗位表重复存储。
2.5 只读约定
benefits读取时:库中为NULL→ 返回[](不是null)。前端可安全地对它做.length/.map,无需判空。company:企业不存在时返回null,不会编造数据、也不会 500。前端需容忍company === null。
3. company 嵌套结构 → 前端类型映射
后端 company 对象(白名单,仅这 7 个字段):
interface BackendJobCompany {
id: number;
name: string;
logoUrl: string | null;
description: string | null;
industry: string | null;
verified: boolean;
companySize: string | null;
}
建议按此前前端类型映射到 src/types/job.ts;本轮未修改或重新验收前端,具体类型请以接手工程师当前代码为准:
| 后端字段 | 前端字段 | 备注 |
|---|---|---|
company.id |
—(可用于跳企业页) | 前端 Job 无对应字段 |
company.name |
Job.companyName |
已有映射 company.name → companyName |
company.logoUrl |
— | 前端暂无对应字段 |
company.description |
— | 前端暂无对应字段 |
company.industry |
Job.industry |
本批接通,取代前端写死的空值 |
company.verified |
— | 前端暂无对应字段 |
company.companySize |
JobDetail.companySize |
本批接通 |
job.workLocation |
(见 §4) | |
job.city |
Job.city |
既有 |
job.title |
Job.positionName |
既有 |
job.salaryMin/salaryMax |
Job.salaryMin/salaryMax |
单位元→K 需 /1000 |
job.experience |
Job.experience |
本批接通 |
job.education |
Job.education |
本批接通 |
job.benefits |
Job.benefits |
本批接通 |
job.salaryMonths |
JobDetail.salaryMonths |
本批接通(展示为「N 薪」) |
job.description / requirements |
JobDetail.description / requirements |
既有,原样保留换行、不截断 |
逐条要点:
company.industry→ 前端industry:直接取company.industry,缺失时为null。company.companySize→ 前端companySize:直接取company.companySize,缺失时为null。- 顶层
experience、education、benefits、salaryMonths直接映射到同名前端字段。
4. workLocation 的处理
workLocation是岗位明确录入的工作地点,独立于city。- 若
workLocation缺失(null),前端可退化为展示city。 - 但不得据此回写:不要因为展示用了
city,就把city当作详细办公地址保存或提交回后端。后端的「企业登记地址」(companies.address)也不会被当作公开招聘地址下发,前端同样不要拿它当工作地点展示。
5. 评分与评价:继续调用既有真实接口
本批没有新增/修改任何评分、评价、排名相关字段或端点。前端请继续使用既有接口:
- 岗位个性化评分:
GET /jobs/{jobId}/personalized-review-score - 岗位评价列表 / 汇总:
GET /jobs/{jobId}/reviews、GET /jobs/{jobId}/reviews/summary
本批也不补造综合评分、浏览量、排名或员工评价——这些字段若前端仍写死,属前端侧待接,不是后端缺字段。
6. 前端工程师需核对的字段映射
以下字段在本批后端验收制品中可返回。此前交接发现部分前端映射为空;接手工程师需核对当前实现,不能由本文件推断前端仍未接入或服务器已发布:
| 前端字段 | 后端来源 | 备注 |
|---|---|---|
Job.experience |
job.experience |
本批新增下发 |
Job.education |
job.education |
本批新增下发 |
Job.benefits |
job.benefits |
本批新增下发,恒为数组 |
Job.industry |
company.industry |
本批新增下发 |
JobDetail.companySize |
company.companySize |
本批新增下发 |
JobDetail.salaryMonths |
job.salaryMonths |
本批新增下发 |
需接入的展示字段应按上述来源映射;JobDetail.views(浏览数)本批未新增提供,不要把本地模拟值当服务器真实数据。
7. 投递 / 沟通按钮
投递、沟通、邀请面试等按钮属于前端入口。本批只是补齐后端岗位与企业资料字段,不会自动生成任何按钮,也不会新增/修改投递与沟通的接口契约。
8. 响应示例
8.1 新增字段完整(GET /jobs/public/{jobId})
{
"code": 200,
"message": "ok",
"data": {
"id": 101,
"companyId": 5,
"title": "前端开发工程师",
"description": "负责招聘平台 App 的页面开发。",
"requirements": "1. 熟悉 React Native\n2. 熟悉 TypeScript",
"city": "北京",
"employmentType": "FULL_TIME",
"salaryMin": 18000,
"salaryMax": 26000,
"experience": "3-5年",
"education": "本科",
"benefits": ["五险一金", "年终奖", "弹性工作"],
"salaryMonths": 14,
"workLocation": "北京市海淀区中关村大街 1 号 A 座 12 层",
"status": "PUBLISHED",
"publishedAt": "2026-10-01T10:00:00",
"createdAt": "2026-09-30T18:20:00",
"company": {
"id": 5,
"name": "演示示例科技有限公司",
"logoUrl": "https://example.invalid/logo.png",
"description": "非真实招聘,仅用于本地演示。",
"industry": "互联网/IT",
"verified": true,
"companySize": "100-499人"
}
},
"requestId": "app-xxxxxx",
"timestamp": "2026-10-01T10:00:01Z"
}
8.2 历史数据字段缺失(老岗位,未补录)
{
"code": 200,
"message": "ok",
"data": {
"id": 7,
"companyId": 2,
"title": "后端开发实习生",
"description": "参与接口开发。",
"requirements": null,
"city": "武汉",
"employmentType": "INTERN",
"salaryMin": 3000,
"salaryMax": 5000,
"experience": null,
"education": null,
"benefits": [],
"salaryMonths": null,
"workLocation": null,
"status": "PUBLISHED",
"publishedAt": "2026-09-01T09:00:00",
"createdAt": "2026-08-31T20:00:00",
"company": {
"id": 2,
"name": "演示联合信息科技有限公司",
"logoUrl": null,
"description": null,
"industry": null,
"verified": false,
"companySize": null
}
},
"requestId": "app-yyyyyy",
"timestamp": "2026-10-01T10:00:01Z"
}
缺失即为
null/[],后端不会填充「本科」「3—5年」「13薪」等默认值。前端请按缺失处理(隐藏区块或显示「未填写」)。
9. 兼容性提示
- 旧请求不受影响:不传新字段的创建/修改请求行为完全不变。
- 旧响应字段不变:
companyId等既有字段保留,未删除、未改名、未改类型;GET /jobs/search原company结构为兼容扩展(只在其中新增字段)。 - 草稿与已关闭岗位的可见性规则不变:DRAFT 岗位公开详情仍不可见(404)。
error_code约定不变:本批所有新增字段的校验失败统一返回既有参数错误响应(code=1001)。
10. 发布后联调检查(尚未执行)
以下为待办,不是已完成的验收结果。仅在对应后端版本发布后,使用获得授权的测试账号执行。
- 确认响应来自目标服务而非本地示例;企业名、规模、行业和岗位五字段按实际值显示,缺失时隐藏或提示未填写。
- 月薪元到 K 的转换只做一次;
salaryMonths: null不默认为 13 或 0,不向更新接口提交 0。 - 检查字段缺省/null/空白/空数组语义,福利重复和 trim 边界与后端一致。
- 验证公开摘要仅七字段、
company: null可正常渲染;不要用企业登记地址代替工作地点。 - 评分和员工评价继续使用真实接口,不能用演示值补齐;投递和沟通入口按已有业务权限处理。
- 对参数错误展示可理解提示,失败后重新读取确认没有部分更新;不伪造成功。
11. 验收与相关说明
- 最新本地全量
clean verify:3464 项通过,失败/错误/跳过均为 0。真实 HTTP 黑盒合计 106 项,其中岗位企业展示 48 项;重启合计 6 项。 - 静态/运行时契约对照 129 项通过,契约反向测试 13 项通过。不是前端端到端验收结果。
- 交付报告:
docs/batch-job-company-display-fields-report.md;简历权限与文件名规则:docs/resume-ownership-hardening-20261016.md。 - 最新证据:
D:/Acceptance/qsp-final-20261019/evidence/acceptance-summary.json。未发布、未连接生产库、未向线上导入演示企业岗位。