站内已经有一篇 web push 文章,但它更偏浏览器设备生命周期。这一篇故意换主链路:讲移动端。enterprise/mail_mobile/models/res_partner.py 与 enterprise/mail_mobile/models/mail_thread.py 说明,Odoo 企业版移动消息不是“消息来了就推一下”,而是围绕设备注册、OCN 推送、线程通知和未读状态同步做了一层专门设计。
一、移动推送首先是设备身份问题,不是消息问题
res.partner 上的 ocn_token 注释写得很直白:当任一设备注册后,会从 OCN 服务拿到 token。这个 token 不是附件字段,而是移动设备被纳入推送网络的标志。
也就是说,系统在决定“能不能推”之前,先要回答“这个 partner 现在有没有可用移动设备”。没有 token,后面一切移动通知策略都无从谈起。
二、线程通知不是直接发推送,而是先走统一通知编排
在 mail_thread.py 里,_notify_thread() 仍然是入口;移动逻辑是以 _notify_thread_by_ocn()、_notify_by_ocn_send()、_notify_by_ocn_prepare_payload() 的形式插进去的。这种设计很重要,因为它说明:
- 移动推送不是绕开 mail thread 单干;
- 它跟桌面通知、频道消息、@mention 一样,属于统一通知流水线的一部分;
- 只有先经过线程级 recipients 编排,系统才知道哪些人该收到 OCN 消息,哪些人该被排除。
这就是“bus fallback”能成立的基础:移动推送不是独立世界,而是统一消息架构上的一个投递分支。
三、所谓 fallback,本质是不要让移动端因为一种投递方式失效就失联
虽然 mail_mobile 源码不会写一句“这里正式进入 fallback 状态机”,但从它的插入位置你能看出设计意图:当消息线程在正常 bus / discuss 通知链路上运转时,移动端再额外拿到一份适合 OCN 的 payload。这样做的结果是,即使实时前台通信、活跃会话和移动推送三者不总在同一设备、同一时刻成立,用户仍有机会在某个终端上看见提醒。
对企业协同来说,这比“推送一定成功”更现实。移动通知永远会受系统权限、厂商限制、离线状态影响,真正能做的是多给一层可靠的补偿链路。
四、未读同步的关键,不在 badge,而在 payload 携带的线程语义
_notify_by_ocn_prepare_payload() 负责把消息信息打成适合移动端消费的数据包,_notify_get_action_link() 和 _at_mention_analyser() 进一步影响通知内容和落地动作。这里的重点不是 UI 花样,而是:移动端收到提醒后,能否正确打开对应线程、知道这条消息属于哪个对象、并把未读状态与服务端线程状态对齐。
这也是为什么“未读同步”通常不能靠客户端自己猜。真正可信的未读,必须基于服务端消息线程与接收者关系来回写。
五、容易误解的地方
1. 有 token 不等于一定能推送成功
token 只是设备注册成功,不代表用户系统权限没关、第三方通道没抖、厂商后台不拦截。
2. 移动推送不是替代 discuss/bus
它是线程通知的补充分支,而不是另一套独立消息中心。
3. 未读同步不是视觉问题
真正难的是线程状态对齐,不是角标显示成 3 还是 5。
六、实战建议
- 先排查 partner 上有没有有效
ocn_token; - 再看消息是否通过
_notify_thread()进入正确 recipients; - 最后才去看移动端是否正确消费 payload、是否能回到对应线程。
七、结论
Odoo 企业版移动消息做对的地方,是把移动推送放进统一线程通知架构里,再用设备 token 和 OCN payload 把移动端接进来。这样即便实时在线、系统推送、用户阅读发生在不同终端,消息仍能维持一条相对稳定的协同链路。
主要源码锚点:
enterprise/mail_mobile/models/res_partner.pyenterprise/mail_mobile/models/mail_thread.py
DISCUSSION
评论区