先说结论
Odoo 的官网招聘不是“把职位发到网站,再给个申请按钮”这么简单。
在 website_hr_recruitment 里,真正搭起来的是一条职位发现链路:
/jobs作为统一入口;- GeoIP 辅助默认国家;
- 搜索词进入网站搜索体系;
- 各筛选项按当前上下文重新计算剩余数量;
- 分页继续保留当前筛选语境;
- 职位详情统一收口到 slug 路由。
所以它的本质不是职位展示,而是:
把招聘内容组织成一个可搜索、可筛选、可持续浏览的网站内容系统。
一、为什么 /jobs 不是普通查询页
jobs() 的参数很多:
- 国家;
- 部门;
- 办公室;
- 合同类型;
- 行业;
- 是否远程;
- 搜索词;
- 页码。
这说明 Odoo 从一开始就把职位页当成“内容发现页”,而不是后台搜索表单搬到前台。
用户在这里的行为通常是:
- 先按地理位置看;
- 再按部门缩小;
- 再通过搜索词快速定位。
二、为什么 GeoIP 默认国家很克制
如果用户没有手动指定过滤条件,系统才会尝试读取 request.geoip.country_code。
而且不是识别到国家就强行预选,而是只有在该国家确实存在已发布职位时,才把它设为默认值。
这点非常重要,因为很多招聘站最大的坑就是:
- 自动猜了一个位置;
- 结果那个位置没有岗位;
- 用户一进来就是空页面。
Odoo 这里的逻辑更稳:
- 只有在猜测真的有帮助时,才使用猜测。
三、为什么筛选计数要按“污染式”方式计算
源码里最有意思的不是筛选本身,而是筛选项数量计算。
它的思路是:
- 计算某一维筛选项剩余数量时,保留其他已选条件;
- 暂时去掉当前这一维,再重新统计。
这意味着前端显示的数字不是全局总量,而是:
- 在你当前浏览语境下,如果切到这个选项,还会剩多少职位。
这个设计很像成熟招聘网站,而不是简单的后台 search panel。
四、为什么职位搜索接的是网站统一搜索体系
职位搜索不是另写一套独立全文检索。
控制器会接入:
website._search_with_fuzzy("jobs", ...)
同时 hr.job 通过 _search_get_detail() 声明:
- 搜索字段;
- 返回字段;
- 结果映射;
- 图标与访问方式。
这说明 Odoo 不是把职位页当孤岛,而是让它接进站点统一搜索协议。
这样做的好处是:
- 搜索体验和站内其他内容更一致;
- 模糊搜索、结果映射、URL 规则都能统一。
五、为什么分页必须保留当前路径与参数
职位列表用的是网站 pager,而不是后台列表分页。
因为招聘站分页不是纯技术问题,它还关系到:
- 当前筛选语境是否能持续;
- 链接是否可分享;
- 刷新后是否还能保持同样结果;
- 搜索引擎看到的是不是稳定页面结构。
所以这里的分页,本质上是在保护:
- 职位发现上下文。
六、为什么旧详情路由要 301 到 slug 路由
源码同时保留旧的详情入口和新的 slug 路由,但旧路由会 301 跳转。
这意味着 Odoo 在做的不是“兼容一下”,而是:
- 主动收口 canonical 风格 URL。
对招聘官网来说,这很有价值,因为职位详情页本身就是:
- 可搜索;
- 可分享;
- 可被索引的内容资产。
七、最容易误解的几个点
误解 1:职位页只是已发布职位列表
不对。核心其实是发现效率。
误解 2:GeoIP 就是自动筛选本地岗位
不对。前提是这个猜测不会把页面筛空。
误解 3:筛选数字显示全局总量就行
不对。那样和用户当前语境脱节。
误解 4:职位 URL 只是前端问题
不对。它直接影响分享、SEO 和长期可达性。
最后一句
理解 Odoo 官网招聘,重点不是“职位能不能显示出来”,而是看懂这条主链:
GeoIP 与请求参数确定语境 → 网站搜索体系找职位 → 污染式筛选计数保持上下文 → 网站 pager 持续浏览 → slug 路由承接详情页。
看懂以后你就会知道,Odoo 在做的不是一张招聘列表,而是一套职位发现系统。
DISCUSSION
评论区