深入解析 Odoo 在线客服模块 (im_livechat)

在这里插入图片描述

深入解析 Odoo 在线客服模块 (im_livechat)

Odoo Livechat 是一款集成于 Odoo 平台的实时在线客服系统,它赋予用户在网页界面上直接与客服人员进行即时沟通的能力。本文将逐步剖析 Livechat 的实现细节,从入口模板文件的加载机制,到后端初始化逻辑,再到前端客服服务的构建与交互,全方位揭示其内部运作机制。

相关功能介绍见:Odoo讨论+聊天模块

1. 入口模板文件:集成与数据传递

  • 网站与在线客服频道关联配置

每个网站可设置一个 im_livechat.channel 实例作为在线客服频道,通过字段 channel_id = fields.Many2one(‘im_livechat.channel’) 进行关联。此配置决定了特定网站的在线客服参数及客服机器人运行规则。

  • 模板集成与数据获取

website_livechat/views/website_livechat.xml文件中, Livechat 被无缝集成到 Odoo 网站模板中。具体操作如下:

  • 条件加载:通过 <t t-if="not no_livechat and website and website.channel_id"> 判断语句,确保 Livechat 只在未被禁用且已配置频道的页面上加载。
  • 数据传递:调用 website._get_livechat_channel_info() 函数获取频道信息,并将其传递给 im_livechat.loader 模板, 初始化 odoo.__session_info__.livechatData
    <!--Integrate Livechat in Common Frontend for Website Template registering all the assets required to execute the Livechat from a page containing odoo--><template id="loader" inherit_id="website.layout" name="Livechat : include loader on Website"><xpath expr="//head"><!-- Odoo机器人测试页面 chatbot_test_script_page 通过 <t t-set="no_livechat" t-value="True"/> 设置 no_livechat=True。调查问卷相关页面也设置 no_livechat=True。 --><t t-if="not no_livechat and website and website.channel_id"><script><t t-call="im_livechat.loader"><t t-set="info" t-value="website._get_livechat_channel_info()"/></t></script></t></xpath></template>
  • 后端数据初始化

在 Python 后端 im_livechat/models/im_livechat_channel.py,通过 livechatData = get_livechat_info() 函数,初始化并返回包含在线客服状态、服务器URL等关键信息的数据对象。其中,判断在线客服是否可用的依据是频道中设置的聊天机器人脚本数量或可用客服人数:

info['available'] = self.chatbot_script_count or len(self.available_operator_ids) > 0
  • 前端数据注入

模板文件 im_livechat/views/im_livechat_channel_templates.xml 负责将后端获取的 Livechat 数据注入到前端 JavaScript 环境:

    <!-- initialize the LiveSupport object --><template id="loader" name="Livechat : Javascript appending the livechat button"><t t-translation="off">odoo.__session_info__ = Object.assign(odoo.__session_info__ || {}, {livechatData: {isAvailable: <t t-out="'true' if info['available'] else 'false'"/>,serverUrl: "<t t-out="info['server_url']"/>",options: <t t-out="json.dumps(info.get('options', {}))"/>,},});</t></template>

2. 前端客服相关核心服务详解

在前端主要依赖以下三个核心服务:

  1. mail.thread: import(“@mail/core/common/thread_service”).ThreadService
  2. im_livechat.livechat: import(“@im_livechat/embed/common/livechat_service”).LivechatService
  3. im_livechat.chatbot: import(“@im_livechat/embed/common/chatbot/chatbot_service”).ChatBotService
  • im_livechat/livechat_service.js文件在前端注册 im_livechat.livechat 服务:

首先将后端传递的 options 和 isAvailable 状态赋值给 LivechatService 类,并实例化。接着,根据服务的可用性决定是否执行初始化操作。若服务可用,调用 LivechatService.initialize() 方法,发起 RPC 调用 /im_livechat/init,传入当前频道 ID,并在响应后标记服务为已初始化。最后,将 im_livechat.livechat 服务注册到前端服务注册表中。

伪代码如下:

    LivechatService.options = session.livechatData.optionsLivechatService.available = session.livechatData.isAvailable;livechat = new LivechatService()if (livechat.available) {LivechatService.initialize()-> this.rpc("/im_livechat/init", {channel_id})-> livechat.initialized = true}registry.category("services").add("im_livechat.livechat");
  • mail/thread_service.js 在前端注册 mail.thread 服务:
export const threadService = {dependencies: ["mail.store", "orm", "rpc", "notification", "router", "mail.message","mail.persona", "mail.out_of_focus", "ui", "user", "im_livechat.livechat", "im_livechat.chatbot"],/***@param {import("@web/env").OdooEnv} env* @param {Partial<import("services").Services>} services*/start(env, services) {return new ThreadService(env, services);},
};
registry.category("services").add("mail.thread", threadService);

3. 在线聊天按钮组件

LivechatButton 组件负责呈现在线聊天按钮并响应用户点击事件。点击按钮时,执行以下操作:

  • 调用 this.threadService.openChat() 打开聊天窗口。
  • 使用 getOrCreateThread({ persist = false }) 通过 RPC 调用 /im_livechat/get_session 获取或创建临时会话信息。此时,由于 persist = false,仅返回 type='livechat' 的临时会话,不立即创建 discuss.channel 记录。用户发送消息时会创建 discuss.channel 记录。
  • 如果聊天机器人可用则启动 chatbotService 服务。

伪代码如下:

export class LivechatButton extends Component {static template = "im_livechat.LivechatButton"onClick -> this.threadService.openChat()-> thread = getOrCreateThread({ persist = false }) -> rpc("/im_livechat/get_session")-> if (this.chatbotService.active) chatbotService.start()
}

4. 后端创建会话

当 Livechat 频道支持机器人且存在 chatbot_script 时,RPC 调用 /im_livechat/get_session 会触发 _get_livechat_discuss_channel_vals() 方法,用于在后端创建相应的会话记录。

5. 聊天窗口

  • 聊天机器人处理,伪代码如下:
// 无需用户回复时,直接触发下一步
chatBotService.start() -> _triggerNextStep() -> _getNextStep() -> this.rpc("/chatbot/step/trigger")
// 点击推荐回复选项时, 发送对应的回复
onclick -> this.answerChatbot(answer) -> this.threadService.post(answer)
  • 后端处理用户回复答案并返回下一步:
# 调用处理用户回复路由:/chatbot/step/trigger, 处理存储用户回复并返回下一步对话
chatbot_trigger_step() -> next_step = _process_answer(channel, answer)-> if step_type in ['question_email', 'question_phone'] chatbot_message.write({'user_raw_answer': message_body}) # 存储用户回复-> _fetch_next_step() -> 'chatbot.script.step' # 下一步数据
# 处理下一步,如果下一步为切换到真人客服,则自动添加相关人员到频道中
posted_message = next_step._process_step(discuss_channel)-> if self.step_type == 'forward_operator' -> _process_step_forward_operator(discuss_channel)-> discuss_channel.add_members()-> channel._chatbot_post_message() -> message_post() # 发送消息
  • 用户输入检测
    使用 useEffect 监听用户输入变化,调用 self.detect() 和 getSupportedDelimiters() 检测是否包含特定指令字符(如 @、# 或 :)。
    // 用户输入时检测是否包含指令字符useEffect(() => self.detect() -> getSupportedDelimiters(),() => [selection.start, selection.end, textInputContent]);// 输入 @ 或 # 时触发搜索联系人或频道suggestionService.fetchSuggestions(self.search)// 点击推荐列表项设置 search 条件,例如选择 '/help'onSelect: this.suggestion.insert(option) -> this.search = {delimiter: "/", position: 0, term: "help "}// im_livechat 模块扩展 suggestionService 模块,支持冒号 ':' 命令getSupportedDelimiters(thread) {// 仅 livechat 频道支持通过 ':' 搜索内置的快速回复return thread?.model !== "discuss.channel" || thread.type === "livechat"? [...super.getSupportedDelimiters(...arguments), [":"]]: super.getSupportedDelimiters(...arguments);},

6. 前端发送消息

前端发送消息时执行以下操作:

  • 调用 post(thread, body) 方法,传入会话 thread 和消息内容 body。
  • 如果 thread.type 为 “livechat”,调用 livechatService.persistThread() 生成 ‘discuss.channel’ 记录。
  • 如果消息内容以 / 开头,识别为命令。从命令注册表 commandRegistry 中获取命令 command。如果找到命令,执行 executeCommand(command) 并返回结果;否则继续消息发送流程。
  • 否则,通过 RPC 调用 /mail/message/post 将消息发送到服务端。
  • 触发 chatbotService 上的 MESSAGE_POST 事件监听器。

伪代码如下:

post(thread, body) -> if (thread.type === "livechat") livechatService.persistThread()-> if (body.startsWith("/")) -> command = commandRegistry.get()-> if (command) executeCommand(command) return; // 执行命令返回结果-> else this.rpc('/mail/message/post') // 发送消息到服务端-> this.chatbotService.bus.trigger("MESSAGE_POST") // 触发 chatbotService 监听器

7. 后台消息处理

  • 路由 /mail/message/post 处理函数 mail_message_post, 根据 thread_id 查找对应的频道(discuss.channel)实例。调用 message_post() 发布消息到频道, 存储消息并根据需要通过多种方式发送通知(如短信、Web 推送、Inbox、电子邮件等)。其中 _message_post_after_hook(new_message, msg_values)是一个钩子函数,可能包含如 mail_bot 模块中用于用户与机器人私下交互的逻辑。
class Channel(models.Model):_name = 'discuss.channel'_inherit = ['mail.thread']# 1. 路由:/mail/message/post
mail_message_post() -> if "partner_emails" in post_data 创建联系人-> thread = env['discuss.channel'].search([("id", "=", thread_id)])-> thread.message_post() # 发布消息
# 2. 发布消息流程
message_post()-> new_message = self._message_create([msg_values]) # 存储消息-> self._message_post_after_hook(new_message, msg_values) # 钩子,如在 mail_bot 模块中添加mail_bot 模块添加用户与 odoo 初始化机器人私下交互的逻辑 `self.env['mail.bot']._apply_logic(self, msg_vals)`-> self._notify_thread(new_message) # 给相关收件人通过多种方式发送通知,如: _notify_thread_by_sms 或 by_web_push, by_inbox, by_email
  • _notify_thread 消息发送流程

构建通知数据,并通过 bus._sendmany(bus_notifications) 使用 PostgreSQL 的 pg_notify 发送异步通知。分发线程 (ImDispatch) 通过 监听 ‘imbus’ 通道,接收到此通知。如果是聊天频道或群组频道,调用_notify_thread_by_web_push() 发送 Web 推送通知。

# discuss.channel 扩展 mail.thread, 通过发送消息到前端
def _notify_thread(self, message, msg_vals=False, **kwargs):# 调用父类方法rdata = super()._notify_thread(message, msg_vals=msg_vals, **kwargs)message_format = message.message_format()[0]# 更新消息格式以包含临时IDif "temporary_id" in self.env.context:message_format["temporary_id"] = self.env.context["temporary_id"]# 生成通知数据payload = {"id": self.id, "is_pinned": True,"last_interest_dt": fields.Datetime.now()}bus_notifications = [(self, "discuss.channel/last_interest_dt_changed", payload),(self, "discuss.channel/new_message",{"id": self.id, "message": message_format}),]# 使用 PostgreSQL 的内置函数 pg_notify 发送异步通知, SELECT "pg_notify"('imbus', json_dump(list(channels)))# 其他客户端在该数据库连接中使用 LISTEN 命令监听 'imbus' 通道时,它们会接收到这个通知self.env["bus.bus"].sudo()._sendmany(bus_notifications)# 如果是聊天频道或群组频道,给相关人员发送 Web 推送通知if self.is_chat or self.channel_type == "group":self._notify_thread_by_web_push(message, rdata, msg_vals, **kwargs)return rdata
  • ImDispatch 分发线程

作为线程运行,监听 ‘imbus’ 通道上的数据库通知, 当接收到通知时,将其转发给订阅了相应通道的 WebSockets。

class ImDispatch(threading.Thread):'分发线程'def loop(self):_logger.info("Bus.loop listen imbus on db postgres")with odoo.sql_db.db_connect('postgres').cursor() as cr, selectors.DefaultSelector() as sel:cr.execute("listen imbus") # 监听while not stop_event.is_set():if sel.select(TIMEOUT):conn.poll()channels = []while conn.notifies:channels.extend(json.loads(conn.notifies.pop().payload))# 将 postgres 通知转发给订阅了相应通道的 websockets"websockets = set()for channel in channels:websockets.update(self._channels_to_ws.get(hashable(channel), []))for websocket in websockets:websocket.trigger_notification_dispatching()
  • 推送 Web 通知: _notify_thread_by_web_push
def _notify_thread_by_web_push(self, message, recipients_data, msg_vals=False, **kwargs):"""为每个用户的提及和直接消息发送 web 通知。:param message: 需要通知的`mail.message`记录:param recipients_data: 收件人信息列表(基于res.partner记录):param msg_vals: 使用它来访问与`message`相关联的值, 通过避免访问数据库中的消息内容来减少数据库查询次数"""# 提取通知所需的伙伴IDpartner_ids = self._extract_partner_ids_for_notifications(message, msg_vals, recipients_data)if not partner_ids:return# 前端 navigator.serviceWorker.register("/web/service-worker.js")# 以超级用户权限获取伙伴设备, service_worker.js 执行 jsonrpc 注册设备 call_kw/mail.partner.device/register_devicespartner_devices_sudo = self.env['mail.partner.device'].sudo()devices = partner_devices_sudo.search([('partner_id', 'in', partner_ids)])if not devices:return# 准备推送负载payload = self._notify_by_web_push_prepare_payload(message, msg_vals=msg_vals)payload = self._truncate_payload(payload)# 如果设备数量小于最大直接推送数量,则直接推送通知if len(devices) < MAX_DIRECT_PUSH:push_to_end_point()else:# 如果设备数量超过最大直接推送数量,则创建一个通知队列项并触发异步推送self.env['mail.notification.web.push'].sudo().create([{...} for device in devices])self.env.ref('mail.ir_cron_web_push_notification')._trigger()

8. chatbotService 处理用户与机器人对话时选择的答案

通过 MESSAGE_POST 事件监听器。如果当前步骤类型为 free_input_multi,调用 debouncedProcessUserAnswer(message) 处理用户输入。否则,调用 _processUserAnswer(message), 保存答案到后端(通过 /chatbot/answer/save RPC 调用)。调用 _triggerNextStep() 继续对话流程(参见标题5)。

this.bus.addEventListener("MESSAGE_POST", ({ detail: message }) => {if (this.currentStep?.type === "free_input_multi") {this.debouncedProcessUserAnswer(message)} else {this._processUserAnswer(message) -> this.rpc("/chatbot/answer/save") // 保存答案->_triggerNextStep() // 发送用户答案到后端继续下一步,上面标题5}
})

9. 前端接收及处理消息

  • simpleNotificationService 初始化时启动 busService,从指定 URL 加载 websocket_worker 源码。
  • websocket_worker 监听处理 message 事件,当接收到消息时触发 notificationBus 上的 notification 事件。
  • 根据不同的消息类型分别处理,如当接收到新的消息时:添加到相应频道的消息列表中,触发 discuss.channel/new_message 事件。
  • threadService 监听此事件,并调用 notifyMessageToUser(channel, message) 方法显示新消息。
simpleNotificationService -> busService.start()-> startWorker()// workerURL = http://localhost:8069/bus/websocket_worker_bundle?v=1.0.7"-> worker = new workerClass(workerURL, {"websocket_worker"});-> worker.addEventListener("message", handleMessage)-> handleMessage = () => notificationBus.trigger(type, payload)-> this.busService.addEventListener("notification", () => this._handleNotificationNewMessage(notify))-> channel.messages.push(message);-> this.env.bus.trigger("discuss.channel/new_message")-> this.env.bus.addEventListener("discuss.channel/new_message", () => this.threadService.notifyMessageToUser(channel, message))-> this.store.ChatWindow.insert({ thread });

消息数据 messageEv.data 内容示例:

   [{"type": "discuss.channel.member/seen","payload": {"channel_id": 35,"last_message_id": 658,"guest_id": 9},},{"type": "discuss.channel/last_interest_dt_changed","payload": {"id": 35,"is_pinned": true,"last_interest_dt": "2024-04-23 16:24:32"},},{"type": "discuss.channel/new_message","payload": {"message": {"body": "<p>hello!</p>","date": "2024-04-23 16:24:32","email_from": false,"message_type": "comment",}}}]

总结

本文深入剖析了 Odoo 在线客服功能背后的前后端消息通信的主要过程及细节。前端通过智能识别用户输入,区分命令与常规消息,发送消息到后端。MESSAGE_POST 事件触发 chatbotService 实时响应用户对机器人问题的选择。后端接收到消息后,存储、处理、转发并以多种方式通知相关收件人。前端通过 WebSocket 监听后端消息并展示。

相关功能介绍见:Odoo讨论+聊天模块


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2980451.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】文件描述符——有这篇就够了

目录 前言 预备知识 复习C语言的文件接口 写方式打开文件 追加方式打开文件 读方式打开文件 系统的文件接口 open close write read 文件描述符 0 & 1 & 2 理解文件描述符 文件描述符的分配规则 重定向的本质 dup2 理解Linux下一切皆文件 缓冲区…

09_FreeRTOS任务通知

任务通知 任务通知常用任务通知API函数 任务通知 FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能&#xff0c;每个任务都有一个 32 位的通知值&#xff0c;在大多数情况下&#xff0c;任务通知可以替代二值信号量、计数信号量、事件组&#xff0c;也可以替代长度为 1 的队列…

自制Apache-Doris 2.0.4镜像Docker部署一Fe和一Be集群及遇到的问题解决

自制Apache-Doris 2.0.4镜像Docker部署一Fe和一Be集群及遇到的问题解决 文章目录 1.前言2.doris是什么&#xff1f;2.1简介2.2介绍2.3使用场景2.4架构 3.官网4.构建部署4.1 构建环境4.2 doris2.0.4的fe和be镜像构建4.2.1 fe2.0.4镜像构建脚本4.2.2 be2.0.4镜像构建4.2.3 启动脚…

Java -- (part16)

一.多线程基础知识 1.进程:在内存中执行的应用程序 2.线程:进程中的一个最小的执行单元 3.并行:在同一时刻,有多个指令在多个CPU上同时执行 4.并发:在同一时刻,有多个指令在单个CPU上交替执行 5.CPU调度 a.分时调度 b.抢占式调度:Java程序 6.主线程:CPU和内存之间开辟的…

C++ CRUD programming for DB

1、ODBC 开放数据库互连&#xff0c;微软主导的关系型数据库接口标准&#xff0c;允许同一代码访问不同DBMS中的数据。小案例&#xff1a;C连接Access数据库----增删改查_c access数据库-CSDN博客 ODBC(Open Database Connectivity&#xff0c;开放数据库连接) ODBC是Microsof…

PCB的通孔、盲孔、埋孔

通孔&#xff1a;是从顶层到底层 盲孔&#xff1a;看不到头&#xff0c;跟井一样&#xff0c;起点永远是第一层 埋孔&#xff1a;是正反都看不到的 总结&#xff1a; 这些孔都是用来切换层的

超详细的Maven安装与使用还有内容讲解

文章目录 作用简介模型仓库 安装配置IDEA配置Maven坐标概念主要组成 IDEA创建Maven项目基本使用常用命令生命周期使用坐标导入jar包 注意事项清理maven仓库更新索引依赖 作用 Maven是专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化…

力扣HOT100 - 101. 对称二叉树

解题思路&#xff1a; class Solution {public boolean isSymmetric(TreeNode root) {if(root null) return true;return recur(root.left, root.right);}boolean recur(TreeNode L, TreeNode R) {if (L null && R null) return true;if (L null || R null || L.…

基于深度学习网络的十二生肖图像分类matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................................................... for i 1:16subplot(4,4,…

第七章 信息系统维护与管理

文章目录 一&#xff0c;概述二&#xff0c;信息系统的使用&#xff08;一&#xff09;用户培训&#xff08;二&#xff09;系统转换&#xff08;三&#xff09;系统运行 三&#xff0c;信息系统的维护&#xff08;一&#xff09;信息系统维护过程1&#xff0c;维护组织2&#…

Meta Llama 3本地部署

感谢阅读 环境安装收尾 环境安装 项目文件 下载完后在根目录进入命令终端&#xff08;windows下cmd、linux下终端、conda的话activate&#xff09; 运行 pip install -e .不要控制台&#xff0c;因为还要下载模型。这里挂着是节省时间 模型申请链接 复制如图所示的链接 然后…

mongodb 安装问题

1. mongodb启动时显示 Illegal instruction (core dumped) mongodb 5.0之后(包括5.0) 开始使用需要使用 AVX 指令集 2.启动时报错 ERROR: child process failed, exited with 1 通过指令 bin/mongod --repair 查看报错信息 根据报错信息进行修改 3. 配置服务器添加节点时…

【北京迅为】《iTOP-3588开发板系统编程手册》-第19章 V4L2摄像头应用编程

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

企商在线亮相2024中国生成式AI大会,展出多元异构算力服务

4月18—19日&#xff0c;由知名媒体机构智东西与智猩猩共同主办的2024中国生成式AI大会在北京举行&#xff0c;55位重量级产学研投界代表同台分享。企商在线作为算力行业代表企业&#xff0c;参展生成式AI展区&#xff0c;现场展出企商在线AI算力平台及异构算力服务。 大会以“…

三分钟快速理解Flink 作业提交流程(包工头的工程之路)

核心组件 我们先来简单了解一下 flink 作业提交涉及到的组件 同时&#xff0c;如果不了解 Yarn 的同学欢迎跳转到这篇文章&#xff0c;了解一下健鑫集团的工程承包流程(doge): 三分钟快速理解Yarn的工作流程 JobManager JobManager 是整个flink作业的管理者 包含 Dispatch…

“PowerInfer:消费级GPU上的高效大型语言模型推理引擎“

PowerInfer是由上海交通大学IPADS实验室开发的一个高效大型语言模型&#xff08;LLM&#xff09;推理引擎&#xff0c;专为个人电脑&#xff08;PC&#xff09;上的消费者级GPU设计。它通过利用LLM推理中的高局部性&#xff0c;实现了快速且资源消耗低的模型推理&#xff0c;这…

深入探究图像增强(C语言实现)

我们将从基础出发使用C语言进行图像处理与分析&#xff0c;重点讨论图像增强和平滑技术。图像增强技术旨在通过增加对比度、亮度和整体清晰度来改善图像的视觉质量。另一方面&#xff0c;图像平滑方法则用于减少噪声并减少图像中的突变&#xff0c;使图像更加均匀和视觉上吸引人…

Github Copilot正版的激活成功,终于可以chat了

Github Copilot 代码补全等功能&#xff0c;提高写代码的效率 https://web.52shizhan.cn/activity/copilot 登录授权后&#xff0c;已经可以使用&#xff0c;完美。如图

OpenFE:开启数据特征工程新时代

OpenFE&#xff1a;开启数据特征工程新时代 数据特征工程是机器学习和数据分析领域中至关重要的一环&#xff0c;它涉及对原始数据进行处理和转换&#xff0c;以提取出有用的特征&#xff0c;为模型构建和预测提供更好的输入。在这个领域中&#xff0c;Python库OpenFE为数据科学…

查找两个字符串的最长公共子串

暴力解法 #include <iostream> #include <vector> #include <cstring> using namespace std; string a, b, minn ""; // a和b是我们输入的 // minn存储的是我们最小的那个字符串string cut(int l, int r) {string tmp "";for (int i …