先说结论
在 Odoo 里,网站重定向不是一个“Excel 里两列地址一映射”的小功能。
从 /home/ubuntu/odoo-temp/addons/website/models/website_rewrite.py 与 tests/test_redirect.py 看,website.rewrite 实际承担的是:
- 页面迁移;
- 临时引流;
- 路由别名;
- 特定入口下线;
- 以及这些变化对 routing map 的刷新。
所以它真正解决的问题是:
当网站入口发生变化时,如何既不把旧流量摔死,又不让路由系统进入混乱状态。
如果你把它只理解成“跳转表”,后面会很容易踩到 SEO、缓存和路由一致性的坑。
一、为什么 Odoo 要把“重定向”和“重写”分成 301/302/308/404 四种动作
redirect_type 不是简单一个 yes/no,而是四选一:
301 Moved permanently302 Moved temporarily308 Redirect / Rewrite404 Not Found
这四种在 Odoo 里的语义并不一样。
1. 301:旧地址正式退役
适合:
- 页面永久迁移;
- 改版后旧 URL 回收;
- 希望浏览器和搜索引擎长期记住新地址。
2. 302:临时导流
适合:
- 短期活动页;
- AB 测试阶段;
- 临时把某个入口导去另一页,但不想让缓存长期固化。
3. 308:更像“路由别名”而不是普通跳转
源码 help 直接说明了这个用途:
- 比如把
/shop改成/garden; - 两个 URL 都能存在,但旧入口会自动重定向到新入口语义。
更关键的是,308 会参与 routing map 层面的治理,而不是仅仅作为一个请求后置 fallback。
4. 404:显式下线某个入口
这个非常容易被忽略。
它不是“找不到页面时自然 404”,而是:
- 即便系统本来有这个 controller / 路由,当前网站也可以有意把它屏蔽掉。
最典型的例子就是:
- 你装了电商模块,
- 但某个站点根本不想开放
/shop。
这时 404 rewrite 就比你到处硬删模板稳得多。
二、为什么 308 和 404 会触发 routing 缓存失效,而 301/302 不一定
create()、write()、unlink() 里有一个非常关键的判断:
- 只要涉及
308或404,就会_invalidate_routing(),最终clear_cache('routing')。
源码注释讲得很清楚:
404会改变 routing map,可把某入口从映射里拿掉;308会给 routing map 增加 alias;301/302更多是路由找不到后再走的跳转处理,不一定改 routing map 本身。
这说明 Odoo 对几种 rewrite 的定位不一样:
有些是“请求进来后告诉你去哪儿”,有些是“入口地图本身已经变了”。
这也是为什么 308/404 更重,更需要刷新所有 worker 的路由缓存。
三、为什么 url_to 校验会这么严格
_check_url_to() 里有一整组看起来很“挑剔”的校验,实际上都很有必要。
1. 不能空
301/302/308 都必须同时有 url_from 和 url_to。
2. 不能以 # 开头
因为 fragment 不是可路由入口,拿它当目标没有意义。
3. base URL 不能和来源一样
否则你等于造了一条自循环规则。
4. 308 的目标必须以 / 开头
因为它是站内路由别名,不是随手跳到任意片段。
5. 308 的动态参数要一一对应
如果 url_from 里有 /<id> 这样的参数,url_to 必须也带上;反之亦然。
这非常关键,因为一旦参数丢了,重写就不再是“别名”,而是在改请求语义。
6. 308 不能把目标设成首页 /
首页切换应该走网站首页机制,不该混进 rewrite 规则里。
7. 308 不能指向已存在页面
如果新目标本来就是一个现成路由,再去把旧入口 alias 到它,路由优先级和匹配行为会变得很容易含混。
这也是 tests/test_redirect.py 里专门断言的点。
四、为什么 website.route 要单独维护一张“全路由目录”
website.route 这个模型很多人平时根本不会注意。
它会通过 ir.http._generate_routing_rules() 把当前系统里所有 GET 路由刷新成一张目录表。
这样做有两个好处:
- rewrite 配置时可以有更明确的来源候选;
- 当
name_search()搜不到时,系统还能先 refresh 再查,尽量保证配置界面拿到最新路由。
这说明 Odoo 的 rewrite 不是完全脱离路由系统独立存在的,它会主动和当前路由图同步。
换句话说:
你不是在配一个随意的映射表,而是在给现有网站入口图做手术。
五、为什么 sitemap 行为也会受 rewrite 影响
tests/test_redirect.py 里有个非常值得注意的断言:
- 创建 308 规则后,
_enumerate_pages()的 sitemap 结果里仍包含旧地址, - 但不包含 rewrite 目标页本身作为新增条目。
这说明 Odoo 不想让 sitemap 因为 route alias 突然长出一堆重复入口。
从 SEO 角度看,这个选择很合理:
- rewrite 的目标是入口治理;
- 不是让搜索引擎把同一内容当成两套独立页面重新抓。
六、最容易误解的,是把 308 当成更高级的 301
这其实不准确。
在 Odoo 语境里:
301/302更像结果层的跳转规则;308更接近路由层的别名治理。
所以如果你只是做:
- 旧文章链接迁新地址;
- 临时活动导流;
通常 301/302 就够了。
而当你想做的是:
- 改一个 controller 入口名;
- 对现有路由语义做“别名替换”;
- 按网站维度下线或改写某入口;
308/404 才更像正确工具。
七、实战里什么时候该选哪一种
选 301
- 页面永久迁移;
- SEO 资产需要继承;
- 旧地址以后不再保留语义。
选 302
- 临时活动导流;
- 还没决定新地址是否长期生效;
- 不想让浏览器和搜索引擎过早固化。
选 308
- 想把某个路由入口改名;
- 需要参数位保持一致;
- 需要动 routing map 语义。
选 404
- 当前网站明确不想暴露某个已安装模块入口;
- 希望从入口层直接下线,而不是在模板层遮遮掩掩。
八、实施中最值得提前做的,不是配规则,而是盘点入口资产
在真实项目里,我会先把入口分成三类:
- 内容资产入口:文章、专题页、落地页;
- 产品功能入口:如
/shop、/event; - 技术路由入口:带参数的 controller 路由。
三类入口对应的 rewrite 策略其实不一样。
如果一开始不分,后面很容易出现:
- 把 controller alias 当 SEO 重定向做;
- 把临时活动页做成永久迁移;
- 把站点下线需求硬塞到模板里。
结语
Odoo 的 website.rewrite 值得重视,不是因为它能做跳转,而是因为它把网站入口变化这件事认真建模了。
它区分:
- 是长期迁移还是临时导流;
- 是路由别名还是普通重定向;
- 是保留入口还是直接屏蔽入口;
- 变化是否已经影响 routing map 本身。
所以理解它之后,你会更容易看清楚一个官网改版的本质:
不是把页面换个地址,而是在重写“用户和搜索引擎该如何抵达这套网站”。
DISCUSSION
评论区