先说结论
Discuss 里的 guest,不是“完全匿名的人”。
Odoo 把 guest 建成了一个正式但受限的协作身份:它有名字、语言、时区、访问令牌、Cookie 识别、presence 状态,还能成为频道成员。
所以 guest 更像“外部访客身份”,而不是“未登录用户随便看看”。
一、为什么 guest 要单独成模
mail.guest 不是挂在 res.users 或 public user 上的一个小补丁,而是独立模型。
它有:
nameaccess_tokenlangtimezonechannel_idspresence_idsim_status
这说明 Odoo 想解决的问题不是“给匿名用户一点只读权限”,而是:
- 让外部参与者进入有限协作空间
- 又不要求对方注册正式内部账号
二、为什么要用 Cookie + token 识别 guest
源码里 guest 会通过 dgid Cookie 保存 guest_id|access_token。
这套设计有两个好处:
- 浏览器下次回来还能被识别成同一个 guest
- 不是只靠裸 guest_id,降低伪造风险
这说明 Odoo 不把 guest 当成一次性临时噪音,而是允许他在一段时间内持续参与同一协作上下文。
三、为什么 guest 还能有 presence 和 IM status
很多系统会让外部访客只能“进来看消息”。
Odoo 更进一步:guest 也可以有 presence_ids 和 im_status,还能带 scoped token 去订阅相关状态。
这背后的意义是:
对协作而言,guest 不是静态收件人,而是有在线/离线状态的真实参与者。
所以如果你在公开频道、外部协作场景里看到 guest 的在线状态变化,那不是额外插件效果,而是核心模型支持。
四、为什么 public user 不能直接当频道成员
discuss.channel.member 源码里明确拦截了 public user 进入成员列表。
这点非常关键。
Odoo 的思路不是:
- 让公共用户直接混进频道体系
而是:
- 内部人走 partner / user 身份
- 外部轻量参与者走 guest 身份
这样权限边界才清楚:
- public user 保持公共网站意义
- guest 才承担受限协作意义
五、为什么 guest 与频道成员是正式关系
discuss.channel.member 要么绑定 partner_id,要么绑定 guest_id,不能两个都空,也不能两个都占。
这说明 Odoo 把“谁在频道里”当作严肃关系,而不是前端临时列表。
只要 guest 成了成员,就有:
- 成员唯一性
- 已读未读状态
- 钉住/取消钉住
- RTC 邀请等后续行为
六、最容易误解的地方
误区 1:guest = public user
不是。两者职责不同。
误区 2:guest 没账号,所以不算正式参与者
不是。guest 有自己一套身份与 presence 机制。
误区 3:只要是公开频道,谁都天然是成员
不是。公开加入和正式成员关系仍然是两回事。
七、排错顺序
遇到“外部访客为什么进不来 / 状态不对”时,建议按这个顺序查:
- 当前进来的是 public user 还是 guest
- 浏览器 Cookie 里的 guest 身份是否还有效
- guest 是否真的被加入
discuss.channel.member - 相关 presence / token 是否能正常订阅
- 最后再看频道公开性和组授权
最后一句
Odoo 的 guest 设计,不是为了放松权限,而是为了在不发正式账号的前提下,仍然保留‘这是一个被识别、可追踪、可协作的人’。
这就是它和匿名访问最大的区别。
DISCUSSION
评论区