网站机制

Odoo 多网站为什么不是“绑个域名就完事”:current website、domain 命中与上下文隔离讲透

很多人以为 Odoo 多网站只是给网站记录填一个域名,但源码真正做的是“会话强制、上下文指定、请求域名命中、规则缓存隔离”四层联动。本文把 current website 的判定链路讲清楚。

网站
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

在 Odoo 里,“当前网站是谁”并不是一个随手取出来的配置值。

website.models.website 里的 get_current_website() 实际按一条很明确的优先级链路来判定:

  1. 先看 session 里有没有 force_website_id
  2. 再看 context 里有没有 website_id
  3. 前台请求时,再按当前请求 host 去匹配网站 domain;
  4. 如果还找不到,且允许 fallback,才退回数据库里第一条网站记录。

所以多网站的本质不是“一个域名对应一个页面主题”,而是:

Odoo 先决定当前请求属于哪个 website,再让模板、规则、语言、资源、搜索域和缓存都沿着这个 website 上下文继续走。

这就是为什么多网站问题一旦出错,表面看像域名问题,深层常常其实是上下文问题。


一、为什么 current website 是整条网站链路的起点

源码里最关键的方法就是:

  • get_current_website()
  • _get_current_website_id(domain_name, fallback=True)

前者决定“现在是谁”,后者决定“用什么 host 去命中谁”。

一旦 current website 确认下来,后面的很多能力都会跟着变:

  • website_domain() 会把查询限制到 website_id in [False, current_website]
  • QWeb 模板缓存会把 website_id 纳入 key;
  • ir.rule 的 domain cache 也会把 website_id 纳入 key;
  • 当前网站的语言、公司、logo、社交图、CDN 策略都会接管后续渲染。

所以它不是“前台首页该显示哪个主题色”的小变量,而是网站请求的根上下文。


二、优先级为什么先看 session,再看 context,再看域名

很多人直觉上会以为:

  • 访问哪个域名,就一定是哪个网站。

但 Odoo 源码不是这么简单。

1. session 强制网站

如果 session 里有 force_website_idget_current_website() 会优先直接返回它。

这说明 Odoo 允许“临时强制切站”。这很适合:

  • 网站编辑器预览;
  • 后台切换目标站点;
  • 某些带上下文的管理动作。

也就是说:

域名不是永远最高优先级,用户会话可以临时盖过域名判断。

2. context 指定网站

如果没有 session 强制,再看 context['website_id']

这个设计很重要,因为很多后台逻辑、模板渲染、RPC、批量查询并不一定处在完整前台 HTTP 请求里,但仍然需要明确“以哪个网站视角”来读数据。

3. 前台请求才按 host 匹配 domain

只有 session 和 context 都没给出答案时,Odoo 才去看 request host。

这意味着域名命中更像:

  • 默认推断机制,
  • 而不是唯一真理。

三、域名命中为什么不是简单字符串相等

_get_current_website_id() 里做得比表面细很多。

它会处理:

  • host 中可能带端口;
  • 网站 domain 可能带 scheme;
  • 国际化域名可能出现 punycode / unicode 两种表示;
  • ilike 搜到的结果还要再做一次精确过滤,避免子域名误命中。

源码里先搜索候选网站,再通过 _filter_domain() 做精确匹配;如果带端口匹配不到,还会再尝试忽略端口。

这背后的设计很务实:

  • 先尽量把候选集找出来;
  • 再做“真正属于这个 host 吗”的二次确认;
  • 最后才考虑 fallback。

所以如果你在多网站环境里遇到:

  • 同一个网站在 :80 正常、:8069 不正常;
  • Unicode 域名和 punycode 表现不一致;

问题往往就出在这一层,而不只是 Nginx 配置。


四、为什么 website_domain() 这么关键

website_domain() 返回的是:

  • website_id in [False, 当前网站]

这句很短,但它决定了 Odoo 多网站不是“绝对隔离”,而是“共享 + 定向覆盖”的模型。

什么意思?

  • website_id=False 的内容,视为所有网站都能看见;
  • website_id=当前网站 的内容,只属于当前网站;
  • 当前网站查询时,会把这两类一起纳入范围。

这比“每条数据必须绑定一个网站”灵活得多。

因为真实场景里常常有两类内容:

  1. 全站共享内容;
  2. 某个站点定制内容。

Odoo 用一个 domain 就把这两类统一起来了。


五、为什么很多多网站 bug 看起来像缓存串站

源码里 ir.rule_compute_domain_keys() 会追加 website_idir.qweb 的模板缓存 key 也会把 website_id 算进去。

这说明 Odoo 非常清楚:

如果缓存不按 website 维度分开,多网站就会出现最典型的串站问题。

比如:

  • A 站模板被 B 站复用;
  • A 站菜单跑到 B 站;
  • A 站文章列表出现在 B 站;
  • A 站语言/资源缓存污染 B 站。

所以当你排查“为什么同样模板在一个站正常、另一个站不正常”时,不要只看 XML 继承,也要想到:

  • 当前请求到底命中了哪个 website;
  • 当前渲染是否真的带上了 website_id 上下文;
  • 某个自定义查询是否忘了补 website_domain()

六、最容易误解的 4 件事

1. 以为多网站只靠域名字段驱动

不对。session 与 context 都可以抢在域名前面决定当前网站。

2. 以为 current website 只影响页面外观

不对。它会影响规则、模板缓存、语言、公司、资源、搜索域。

3. 以为数据隔离就是“按 website_id 严格相等”

不对。Odoo 默认允许 website_id=False 作为全站共享数据。

4. 以为 backend 下也一定能自动推断网站

不对。源码明确区分前台请求和非前台请求;backend 场景经常要靠 context 明确指定。


七、实战排错顺序

多网站串站、取错站、模板异常时,我更推荐按这个顺序查:

  1. 当前请求的 host 到底是什么;
  2. session 里有没有 force_website_id
  3. context 里有没有残留 website_id
  4. 目标数据查询是否用了 website_domain()
  5. 自定义缓存、模板或 snippet 是否把 website_id 纳入隔离维度。

这个顺序比一上来怀疑 DNS、Nginx 或模板继承更高效。


一句话记忆

Odoo 多网站不是“域名 -> 页面”的薄映射,而是“session / context / host 先判定 current website,再让规则、缓存、模板和数据范围一起沿着 website 上下文继续走”。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。