企业协同

Odoo 企业版 Knowledge 搜索为什么像“先找到目录,再找到正文”:根祖先回溯与可见性过滤讲透

Knowledge 的搜索体验看起来像全文检索,但用户又总能先落到自己有权限的目录树。原因不只是权限判断,而是 search_fetch、favorite 优先、root ancestor 回溯、visible/hidden 分流和排序策略共同作用的结果。

企业 协同办公
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 4 阅读

先说结论

Odoo 企业版 Knowledge 的搜索体验之所以看起来很“懂权限”,不是因为它只在最后多做了一次 ACL 过滤,而是因为整条链路从一开始就把以下几件事揉在一起了:

  • 用户是否有访问权
  • 文章是 visible 还是 hidden
  • 收藏是否应该优先
  • 搜索结果返回后,左侧树应该展开到哪个根祖先
  • 在结果太多时,全文检索并不追求绝对最优,而追求足够快且足够相关。

所以一句话概括:

Knowledge 搜索返回的不是“纯文本匹配结果”,而是“带权限、带导航上下文、带用户偏好的可落地结果”。


这篇主要看哪里

核心源码在:

  • enterprise/knowledge/models/knowledge_article.py

重点方法:

  • search_fetch()
  • get_user_sorted_articles()
  • get_sorted_articles()
  • _get_first_accessible_article()
  • _get_accessible_root_ancestors()

这几段放在一起看,才能理解为什么 Knowledge 搜索出来后,体验像“先把你带到正确目录,再展开正文”。


第一层:不是所有搜索都从全文检索开始

Knowledge 里至少有两条入口很容易混淆:

入口 A:普通列表 / web client 排序

这里会走 search_fetch()

它专门处理一个特殊问题:

  • 如果排序里带了 is_user_favorite,就不能粗暴地让数据库按普通顺序查。

源码的做法是分两步:

  1. 先把当前用户收藏的文章查出来;
  2. 再用剩余窗口补其他文章。

这意味着:

“收藏优先”不是前端自己调顺序,而是后端专门重写了查询窗口。

因此同样一组 domain,不同排序参数,Knowledge 给你的第一屏结果可能完全不同。

入口 B:Command Palette / 搜索框

这里更常走 get_user_sorted_articles()

它先决定当前是在:

  • visible 模式;
  • 还是 hidden 模式;
  • 没有搜索词时是不是优先返回 favorites。

只有在真正有搜索词时,才继续下沉到 get_sorted_articles()

所以“搜索”其实不是一条单一查询,而是“用户上下文分流 + 文本匹配”的组合。


第二层:get_user_sorted_articles() 决定的是“搜哪一类文章”

这段方法最重要的不是全文检索本身,而是它先设了一个 domain:

  • is_article_visible != hidden_mode
  • user_has_access = True

也就是说,在真正排序之前,它已经先把两个边界钉死了:

边界 1:当前是在 visible 还是 hidden 空间

Knowledge 的 hidden 文章不是简单“搜索时也顺带搜一下”。

它有独立模式。

边界 2:当前用户必须真有访问权

这里很关键:

  • 不是管理员就天然能看到别人的私有知识;
  • 不是全文命中就能越权展示。

所以很多人说“Knowledge 搜索像带权限”,其实不够准确。

更准确的说法是:

Knowledge 的搜索范围本身就是权限裁过的,不是最后渲染时才补一刀。


第三层:get_sorted_articles() 不是追求绝对最准,而是追求可控性能

这段源码的注释写得很坦白:

  • 会用 knowledge.fts_search_cut_off 控制候选上限;
  • 会按 title+body、title-only、body-only 分层预选候选;
  • 候选过多时,不保证穷尽所有匹配,只保证返回“足够相关且响应够快”的结果。

这很重要,因为它揭示了一个产品取舍:

Odoo 在这里优先要“企业知识库可用”

而不是学术意义上的全局最优搜索排序。

换句话说:

  • 小库里,它尽量给你 top-k 真正最相关结果;
  • 大库里,它更在乎延迟可控,再用预选 + 打分给你一批足够好的结果。

这也是为什么有时你会感觉:

  • 搜索结果“很像懂你要什么”;
  • 但在超大库、超泛关键词下,又不一定是全库最完美排序。

这是有意设计,不是 bug。


第四层:为什么用户总像是“先回到目录树,再进正文”

这就要看 _get_accessible_root_ancestors() 了。

这段逻辑会从当前文章往上回溯:

  • 只要父文章仍然可读,就继续往上走;
  • 把沿途可访问的祖先都收集起来;
  • 最终形成一条“可访问的根祖先链”。

这意味着:

Knowledge 并不满足于“这篇正文你能看”。它还想知道“应该把你安放到哪一段你看得见的目录树里”。

所以当当前文章本身不是 root,或者上层有隐藏/权限边界时,系统仍会尽量找一条你可见的祖先路径,把侧边树正确展开。

这就是为什么 Knowledge 的体验不像裸搜索引擎,而像“搜索 + 导航定位器”。


第五层:_get_first_accessible_article() 解决的是默认落点问题

用户进 Knowledge 时,系统需要决定“先打开哪篇”。

源码顺序大致是:

  1. 如果用户有 active favorites,优先取 favorite;
  2. 否则在可读、非模板、可见的 root 文章里,按 sequenceinternal_permission 找第一篇。

这说明 Knowledge 的首页默认落点也不是随便选的。

它同时考虑:

  • 用户偏好;
  • 根目录结构;
  • 可访问性;
  • 可见状态。

所以“为什么我进来总先看到某篇文章”通常不是前端记忆,而是后端默认入口策略。


第六层:为什么说它不只是权限系统,而是“带导航语义的权限系统”

如果只有权限过滤,Knowledge 完全可以做成:

  • 查出所有命中文章;
  • 把没权限的去掉;
  • 把剩下的直接列出来。

但它没有停在这里。

它还做了:

  • favorites 前置;
  • visible/hidden 分流;
  • accessible root ancestors 回溯;
  • 默认入口文章选择。

所以真实体验是:

  • 你看到的不只是“哪几篇能读”;
  • 还是“这些文章在你的知识树里应该怎么被安放”。

这正是企业知识库和普通全文检索的区别。


实战里最容易误解的几个点

误解 1:搜索排序全靠 PostgreSQL FTS

不对。

FTS 只是底层的一部分,外围还有:

  • candidate cut-off;
  • favorites 优先;
  • visible/hidden domain;
  • user access domain。

误解 2:能搜到说明一定在左侧树里可见

不一定。

系统还要通过 _get_accessible_root_ancestors() 找一条你可见的祖先路径,才能把导航树摆正。

误解 3:管理员就天然看到所有 private article

源码里 user_has_access = True 这类限制并不是摆设。Knowledge 明确希望“别人的私有知识”不要被普通搜索体验轻易穿透。


二开时最值得注意的边界

1. 不要只改前端排序,忽略 search_fetch()

你会发现收藏优先、分页窗口和首屏结果都不对。

2. 不要把 hidden/visible 混成一个 domain

用户会得到看似命中、实际无法落地的结果,体验很差。

3. 不要只返回正文结果,不处理祖先展开链

这会让左侧目录树和正文上下文脱节,用户会觉得“搜到了,但不知道它在哪”。


结论

Knowledge 搜索看起来像全文检索,但真实实现更像一套多层协同机制:

  • 权限先裁范围
  • 收藏和可见性决定结果入口
  • FTS 决定候选与相关性
  • 根祖先回溯决定目录树怎么展开。

一句话总结:

Odoo Knowledge 搜索返回的不是一串匹配文本,而是一组“当前用户能安全落地、能看懂上下文、还能在目录树里站得住”的文章结果。

DISCUSSION

评论区

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