招聘平台的核心体验,浓缩在一条链路上:求职者搜到好职位 → 查看详情 → 一键投递 → 企业在简历中心筛选处理。这条链路一旦有任何断点,产品就"转不起来"。本文复盘我在一天内把这条链路从后端到前端完整打通、并顺手修复 9 个真实 Bug 的全过程,所有代码均来自 FastAPI + Tortoise ORM + Vue3 实战项目。
技术栈延续一贯风格:FastAPI + Tortoise ORM(异步)做业务,Elasticsearch做搜索,Vue3 + Element Plus做前端,API 统一三层分离(Router → Service → Model)。
二、后端四大功能模块
2.1 ES 职位搜索服务(job_search_service.py)
基于boss_job_index_v2索引,支持多字段加权搜索与高亮:
「只搜招聘中职位」是关键业务约束——草稿/关闭的职位不应出现在求职者搜索结果里。
2.2 职位详情 + 相似推荐(job_detail_service.py)
get_job_detail()用 4 表联合查询(Job + Enterprise + EnterpriseInfo + IndustryPosition),把部门枚举、企业规模、融资阶段映射成中文名;get_similar_jobs()按「同城市 + 同部门」推荐相似职位,并排除同企业自身。
2.3 简历投递(resume_submission)
数据模型用unique_together=(job_id, job_seeker_id)防重复投递,状态机设计:
Service 层submit_resume()会校验职位存在且招聘中、检查重复(已撤回可重投);get_enterprise_resumes()给企业提供「求职者基本信息 + 简历摘要」,并支持各状态计数统计。
2.4 简历中心(企业端)
GET /resume/center收到的简历列表
GET /resume/center/stats统计概览(总数 / 今日新增 / 未读 / 各状态分布)
PUT /resume/center/status企业更新状态(标"不合适"时强制填原因)
三、前端四大模块对接
求职者端(boss-candidate-ui)与企业端(boss-company-ui)同步对接:
求职者端
JobList.vue:搜索接口替换 mock,处理字符串薪资格式化、loading / 空态
JobDetail.vue:拉取详情 + 相似职位,投递按钮调submitResume({job_id})
DeliveryRecord.vue:我的投递记录,canWithdraw(status)仅 1/2 可撤回
企业端
ResumeList.vue:简历中心列表 + 统计卡片(总数 / 今日新增 / 待处理)
ResumeDetail.vue:解析路由query.data渲染真实字段;改状态时 status=6 弹窗强制填原因
四、9 个真实 Bug 修复复盘(重点)
Bug 1:下拉框选择后不显示值
原因:Element Plus 中el-form-item嵌套带prop的内层 form-item,select 值渲染有 bug。修复:去掉外层 form-item,改用.form-group-label分组标签 +label-width="0"并排布局。
Bug 2:发布职位鉴权死循环
原因:后端/job/save用get_job_info解析teamToken,但企业端用户只有companyToken→ 跳转团队登录 → 点企业登录 → 回发布页 → 死循环。修复:新增POST /job/enterprise-save(用get_enterprise_info解析 companyToken),前端jobSaveEnterprise()改用 companyToken 提交。
Bug 3:团队登录验证码 500
原因:/enterprise/team/send_code业务异常raise Exception("该手机号未注册招聘团队成员")未被捕获 → 500。修复:登录类 4 个接口统一加 try/except,业务异常返回{code:0, message}而非 500。
Bug 4:recruit_team_id NOT NULL
原因:Job 模型该字段原非空,企业端发布传 None 触发ValueError。修复:模型加null=True+ 执行ALTER TABLE t_job MODIFY COLUMN recruit_team_id INT NULL。
Bug 5:团队成员管理是 mock
问题:邀请成员只弹假成功,后端根本没有 CRUD 接口。修复:新增 4 个接口(invite / members / member status / delete)+ Service 层(含手机号脱敏、中文状态映射、软删除),前端 TeamMembers.vue 全量对接真实 API。
Bug 6:发布后列表不回显
双重根因:① 前端if (!teamLoggedIn) return无 teamToken 直接不请求;② 后端/job/list按recruit_team_id过滤,企业发布的职位该字段为 NULL 也查不到。修复:新增GET /job/enterprise-list(按 enterprise_id 查全部职位),前端改用jobListEnterprise()。
Bug 7:删除职位待补全(本次新增)
现象:PositionList.vue 的暂停/关闭/删除三个操作是 mock 成功提示。修复:后端已有PUT /job/enterprise/{id}/status与DELETE /job/enterprise/{id},前端补jobUpdateStatus/jobDelete并接真实 API + 操作后刷新列表。
Bug 8:搜索一直 loading(本次新增重点)
现象:求职者端输入关键词点搜索,页面转圈不出结果。根因:搜索走 ES,ES 未运行时请求挂起。修复:新增search_db_fallback(),ES 异常时自动降级为数据库查询,返回格式与 ES 完全一致,前端零改动:
降级搜索支持关键词模糊匹配、城市/经验/学历筛选、分页排序,保证 ES 宕机时搜索依然可用——这是生产环境必须具备的兜底能力。
Bug 9:搜索页无分页 / 错误"热"标签
修复:JobList.vue 重写,补充分页组件、经验筛选、重置按钮、清空筛选空态、薪资橙色高亮、卡片 hover、路由参数监听、回车搜索;移除"所有招聘中职位都标热"的错误逻辑。
五、架构总结与技术沉淀
六、写在最后
一天内把「搜索 → 投递 → 简历中心」这条主线打通,最值得记住的不是写了多少接口,而是每一次"页面转圈/白屏/500"背后都是一个具体的边界没处理好:嵌套组件、Token 类型、字段可空、服务降级、mock 占位。把这些都补上,产品才真正"能用"。
下一步可以继续做:搜索建议(Suggestion)、简历智能解析、投递状态实时通知(WebSocket)。