别再花大价钱买客服系统了!这款开源方案让中小企业也能拥有 Zendesk 级别的体验
为什么这个项目值得关注
在当今数字时代,客户服务的质量直接影响着企业的生存与发展。想象一下这样的场景:客户在你的网站上遇到问题,却找不到快速响应的渠道;客服人员在多个平台之间疲于切换,导致响应延迟和客户流失;管理层想要分析客服数据,却只能依靠繁琐的手工统计。
这些痛点,正是 Chatwoot 致力于解决的核心问题。
Chatwoot 是一款开源的客户沟通平台,它将网站在线聊天、电子邮件、社交媒体消息等所有客户接触点整合到一个统一的收件箱中。不同于市场上价格昂贵的商业解决方案,Chatwoot 允许企业完全掌控自己的数据,无需按席位付费,而且源代码完全开放,可以根据业务需求进行深度定制。
这个项目的优势不仅在于功能全面,更在于其架构设计的优雅性。它采用了现代化的前后端分离架构,后端使用 Ruby on Rails 提供了稳定可靠的业务逻辑处理,前端则基于 Vue.js 打造了流畅的用户体验。借助 Docker 和 Kubernetes 的支持,部署变得前所未有的简单——即使你不是专业的运维工程师,也能在几分钟内搭建起一套完整的客服系统。
根据 GitHub 上的统计数据,Chatwoot 已经获得了超过 20,000 颗星标,被数千家企业用于生产环境,从初创公司到中型企业都能看到它的身影。更重要的是,它拥有一个活跃的开源社区,持续为项目贡献新功能和修复问题。
如果你正在寻找一个性价比高、可定制性强且社区活跃的开源客服解决方案,Chatwoot 绝对值得深入了解。在接下来的教程中,我将带你从零开始,掌握这个强大工具的每一个细节。
环境搭建:快速启动你的客服平台
基础环境要求
在开始安装 Chatwoot 之前,我们需要确保系统满足以下基本要求。这个配置对于开发和测试环境来说已经足够,生产环境可能需要根据实际流量进行相应的扩展。
首先,操作系统方面,Chatwoot 支持主流的 Linux 发行版,包括 Ubuntu、Debian 和 CentOS。对于 macOS 用户,也可以进行本地开发。Windows 用户建议使用 WSL2(Windows Subsystem for Linux 2)来获得最佳体验。
其次,需要准备以下软件组件:Ruby 3.2.x 版本作为后端运行时环境,Node.js 18.x 或更高版本用于前端资源编译,PostgreSQL 13 或更高版本作为主数据库存储,Redis 6.x 或更高版本用于缓存和会话管理。如果你计划使用 Sidekiq 进行后台任务处理,Redis 是不可或缺的依赖。
此外,还需要安装 Yarn 作为 JavaScript 包管理器,Git 用于版本控制,以及 ImageMagick 处理图片资源。
使用 Docker 部署(推荐方式)
对于大多数用户来说,使用 Docker 部署 Chatwoot 是最简单快捷的方式。Docker 不仅简化了依赖管理,还确保了开发、测试和生产环境的一致性。
第一步,克隆 Chatwoot 的官方仓库到本地:
git clone https://github.com/chatwoot/chatwoot.git
cd chatwoot
第二步,复制环境变量配置文件。Chatwoot 使用 Rails 环境变量来管理各种配置选项,我们需要从示例文件创建一个实际可用的配置文件:
cp .env.example .env
第三步,生成本地密钥。这是 Rails 应用安全机制的重要组成部分:
rake secret
将生成的密钥复制并设置为环境变量中的 SECRET_KEY_BASE 值。
第四步,启动 Docker 容器。在 Chatwoot 仓库根目录下执行:
docker-compose up -d
这个命令会自动下载所有必要的镜像,创建网络,初始化数据库,并启动所有服务组件。首次启动可能需要几分钟时间来完成依赖安装和数据库迁移。
等待容器启动完成后,打开浏览器访问 http://localhost:3000,你应该能看到 Chatwoot 的安装向导界面。
手动安装详解
虽然 Docker 是推荐方式,但了解手动安装过程能帮助你更好地理解系统架构,在排查问题时也会更加得心应手。
数据库配置
首先安装 PostgreSQL。在 Ubuntu 系统上执行:
sudo apt update
sudo apt install postgresql postgresql-contrib
安装完成后,创建一个专用的数据库用户和数据库:
sudo -u postgres psql
在 PostgreSQL 控制台中执行以下命令创建一个具有适当权限的用户:
CREATE USER chatwoot WITH PASSWORD 'your_secure_password';
CREATE DATABASE chatwoot_production OWNER chatwoot;
GRANT ALL PRIVILEGES ON DATABASE chatwoot_production TO chatwoot;
\q
Ruby 环境配置
建议使用 rbenv 或 rvm 来管理 Ruby 版本,这样可以避免系统级别的 Ruby 版本冲突。以下是使用 rbenv 的配置过程:
# 安装 rbenv 依赖
sudo apt install -y build-essential libpq-dev libssl-dev libreadline-dev zlib1g-dev
# 克隆 rbenv 仓库
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
# 安装 ruby-build 插件
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
# 安装 Ruby 3.2.0
rbenv install 3.2.0
rbenv global 3.2.0
ruby -v
Node.js 和 Yarn
使用 NodeSource 仓库安装指定版本的 Node.js:
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs
npm install -g yarn
配置环境变量
编辑之前创建的 .env 文件,填入实际的配置值:
RAILS_ENV=development
FRONTEND_URL=http://localhost:3000
POSTGRES_HOST=localhost
POSTGRES_DATABASE=chatwoot_development
POSTGRES_USERNAME=chatwoot
POSTGRES_PASSWORD=your_secure_password
REDIS_URL=redis://localhost:6379
SECRET_KEY_BASE=your_generated_secret_key
安装依赖并初始化数据库
bundle install
yarn install
rake db:create db:migrate
rails server
如果一切顺利,现在你应该可以通过 http://localhost:3000 访问 Chatwoot 了。
首次配置向导
无论是使用 Docker 还是手动安装,首次访问 Chatwoot 都会进入配置向导。这个向导会引导你完成以下设置:
创建管理员账户是第一步,需要提供用户名、电子邮件地址和强密码。建议使用一个可以长期访问的邮箱,因为管理员账户拥有系统的全部权限。
接下来是组织设置,包括组织名称(会显示在聊天窗口的标题栏)、Logo 上传以及时区选择。时区设置会影响消息时间戳的显示和报表数据的统计。
最后是可选的预配置渠道设置,包括网站在线聊天渠道、电子邮件渠道和社交媒体渠道。你可以选择现在配置,也可以之后在管理后台添加。
完成向导后,你将进入 Chatwoot 的主控制台,开始真正体验这个强大的客服平台。
核心功能详解
统一收件箱:所有客户消息的汇聚点
Chatwoot 最核心的设计理念是”统一收件箱”(Unified Inbox)。在传统的客服模式中,客服人员需要同时登录多个平台——邮箱、社交媒体、网站聊天插件等——来响应来自不同渠道的客户消息。这种分散的模式不仅效率低下,还容易导致消息遗漏和响应延迟。
Chatwoot 的统一收件箱将所有渠道的消息汇聚到一个界面中。无论客户是通过网站聊天窗口发起咨询,还是发送电子邮件,甚至是在 Facebook 粉丝页留言,都会在同一个列表中显示,客服人员无需切换界面就能处理所有客户对话。
在技术实现上,每个渠道都有独立的配置和凭证管理,但消息最终都会被标准化处理后存入同一个数据库。数据库架构设计考虑到了扩展性,每个会话记录都包含渠道来源标识、消息类型、客户信息和时间戳等关键字段。
统一收件箱还支持多团队协作。不同的团队可以负责不同的渠道,也可以根据话题标签或客户类型进行会话分配。这种灵活性使得 Chatwoot 能够适应各种规模的组织结构。
网站在线聊天
网站在线聊天是 Chatwoot 最受欢迎的渠道之一。它允许你在网站上嵌入一个可定制的聊天小部件,让访问者能够即时与你沟通。
聊天小部件的设计非常注重用户体验。它支持多种主题配色方案,可以无缝集成到任何网站的视觉风格中。小部件提供了预设的问候语功能,可以根据访问者的来源页面或访问时间显示不同的欢迎信息。常见问题快捷回复按钮能够引导客户自助解决简单问题,减少客服工作量。
消息预读功能让客服人员在客户打字时就能看到即将发送的内容,从而提前准备回复,这种”边聊边想”的体验大大提升了响应效率。
离线消息处理是另一个关键功能。当所有客服人员都处于离线状态时,客户仍然可以提交消息,这些消息会被标记为待处理状态,并在客服上线时第一时间通知。
从技术角度看,网站聊天功能基于 ActionCable 实现,这是 Rails 内置的 WebSocket 解决方案。它提供了可靠的实时双向通信能力,同时还支持消息持久化,确保即使在网络波动的情况下也不会丢失消息。
邮件渠道集成
电子邮件作为最传统的客户沟通渠道,在 Chatwoot 中得到了现代化的处理。系统会为每个收件箱分配一个唯一的电子邮件地址,当客户发送邮件到这个地址时,系统会自动创建一个新的会话或将其关联到已有的会话中。
邮件渠道的核心优势在于双向同步。当你通过 Chatwoot 回复客户邮件时,客户会在自己的邮箱中收到你的回复;同样,客户对邮件的回复也会自动同步到 Chatwoot 中。这种同步机制基于标准的 IMAP 和 SMTP 协议,确保了广泛的兼容性。
智能邮件处理是 Chatwoot 的一个亮点功能。系统会自动识别邮件的线程关系,将同一主题的多封邮件聚合到同一个会话中。当客户回复一封来自不同渠道的邮件时,系统也能智能匹配并关联到对应的会话。
邮件模板功能让客服人员能够快速发送格式化的回复。你可以根据不同的场景预设多个模板,并在回复时一键插入,大大提高了工作效率。
社交媒体渠道
Chatwoot 支持与主流社交媒体平台的集成,包括 Twitter、Facebook 和 Instagram。这些渠道的消息同样会出现在统一收件箱中,享受与其他渠道一致的协作功能。
Twitter 集成允许你监听特定账户的提及和直接消息,或者追踪特定的标签。当有人在 Twitter 上 @ 你的账户或发送包含特定关键词的推文时,系统会自动创建会话并通知相关客服人员。
Facebook 集成则需要你拥有 Facebook Page 的管理权限。Chatwoot 可以接收来自 Facebook Page 的消息和评论,并支持通过同一个界面进行回复。Instagram 集成的工作方式与 Facebook 类似,主要用于处理 DM 和评论。
社交媒体渠道的特殊性在于其公开性。与私人邮件不同,社交媒体上的对话往往是对公众可见的。Chatwoot 提供了专门的工具来处理这种场景,包括评论审核(需要额外配置 Facebook 应用审核)和公开回复与私信的切换。
团队与角色管理
Chatwoot 提供了细粒度的权限控制系统,这是支持大型团队协作的基础。系统预置了三种内置角色:管理员拥有系统的全部权限,可以进行系统配置、用户管理和报表查看; Agent 是普通的客服人员,只能处理分配给自己的会话;自定义角色则允许你根据实际需求组合不同的权限。
团队是 Chatwoot 组织工作的核心概念。每个团队可以包含多名客服人员,并分配特定的渠道和收件箱。你可以将某个渠道的所有会话分配给一个团队,或者根据客户类型创建专门的团队来处理不同类型的咨询。
会话分配规则是管理效率的关键。Chatwoot 支持多种分配策略:平均分配确保每个客服人员获得大致相同数量的会话;负载均衡会考虑当前在线人员的工作量,将新会话分配给最空闲的客服;优先分配则允许特定渠道或客户享有更高的优先级。
报表与分析
数据驱动的决策是现代客服运营的核心。Chatwoot 内置了功能丰富的报表系统,涵盖了响应时间、会话量、客户满意度等关键指标。
响应时间报表展示了团队在不同时间段的表现,包括首次响应时间(客户发起会话到客服首次回复的时间)和平均响应时间。这些数据可以帮助你识别高峰期,优化人员排班。
会话报表提供了会话量趋势图,按渠道、按团队或按客服人员的维度进行分解。你可以清楚地看到哪些渠道带来了最多的咨询,哪些客服处理了最多的会话。
客户满意度调查是 Chatwoot 的一大特色功能。在会话结束后,系统会自动向客户发送满意度评分邀请。收集到的评分会被汇总成满意度报表,并支持按客服人员进行排名。
这些报表数据支持导出为 CSV 格式,方便在 Excel 或其他 BI 工具中进行进一步分析。对于需要更深度分析的场景,Chatwoot 还提供了与 Google Analytics 和 Mixpanel 等工具的集成。
自动化与工作流
自动化是提升客服效率的关键武器。Chatwoot 提供了两类自动化工具:规则引擎和工作流程。
规则引擎允许你根据预设条件自动执行特定操作。例如,当新会话来自特定地区时自动分配给熟悉该地区的客服;当客户等待时间超过阈值时自动发送提醒;当会话长时间未得到响应时自动升级给主管。
规则的配置界面非常直观。你只需要定义触发条件(WHEN)、过滤条件(IF)和执行动作(THEN),系统就会自动评估每条规则并执行匹配的操作。以下是一个典型的规则配置示例:
触发条件:新会话创建
过滤条件:客户语言包含“日语”
执行动作:将会话分配给日语客服团队
工作流程则提供了更复杂的自动化能力。它支持多步骤的条件分支、定时延迟和循环等高级特性。你可以用它来实现复杂的客服流程自动化,比如新客户引导序列、问题升级路径或自动满意度跟进。
实战教程:从零构建你的第一个客服渠道
教程目标概述
在这个实战教程中,我们将一步步完成以下任务:创建一个网站聊天渠道、配置聊天小部件样式、在网站上嵌入聊天代码、实现自动客服响应,以及设置基础的报表看板。通过这个完整的示例,你将掌握 Chatwoot 的核心使用流程,为实际应用打下坚实基础。
第一步:创建网站聊天渠道
登录 Chatwoot 后台管理界面,你会看到左侧的导航菜单。点击”渠道”选项,然后选择”网站”渠道进入创建流程。
在创建页面中,首先需要填写渠道的基本信息。名称可以填写为”官方网站客服”,这将显示在统一收件箱的渠道标签中。接下来需要设置网站域名白名单,这是安全机制的一部分,只有白名单中的域名才能加载这个聊天小部件。
配置完成后,系统会生成一段 JavaScript 嵌入代码。这段代码看起来像这样:
<script>
(function(d,t) {
var BASE_URL="https://your-chatwoot-server.com";
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=BASE_URL+"/packs/sdk.js";
s.parentNode.insertBefore(g,s);
g.onload=function(){
window.chatwootSDK.run({
websiteToken: 'your_website_token_here',
baseUrl: BASE_URL
})
}
})(document,"script");
</script>
请将这段代码保存下来,下一步我们将把它嵌入到网站中。
第二步:在 Vue.js 项目中集成聊天小部件
假设你有一个基于 Vue.js 构建的现有网站,我们将演示如何正确集成 Chatwoot 聊天小部件。
创建一个名为 ChatwootWidget.vue 的组件文件,这个组件将负责聊天小部件的加载和管理:
<template>
<div class="chat-widget-container">
<!-- 聊天小部件将通过SDK自动渲染到页面 -->
</div>
</template>
<script>
export default {
name: 'ChatwootWidget',
props: {
websiteToken: {
type: String,
required: true
},
baseUrl: {
type: String,
default: 'https://app.chatwoot.com'
},
enableRecaptcha: {
type: Boolean,
default: false
}
},
data() {
return {
isLoaded: false
}
},
mounted() {
this.initChatwoot()
},
beforeDestroy() {
// 清理工作:移除脚本标签
const existingScript = document.getElementById('chatwoot-sdk')
if (existingScript) {
existingScript.remove()
}
},
methods: {
initChatwoot() {
// 检查是否已加载SDK
if (window.chatwootSDK) {
this.runWidget()
return
}
// 创建脚本元素
const script = document.createElement('script')
script.id = 'chatwoot-sdk'
script.async = true
script.src = `${this.baseUrl}/packs/sdk.js`
script.onload = () => {
this.isLoaded = true
this.runWidget()
}
script.onerror = () => {
console.error('Chatwoot SDK加载失败,请检查网络连接')
}
document.head.appendChild(script)
},
runWidget() {
// 初始化聊天小部件
window.chatwootSDK.run({
websiteToken: this.websiteToken,
baseUrl: this.baseUrl,
// 自定义配置选项
chatwootSettings: {
hideMessageBubble: false,
position: 'right', // 气泡位置:left 或 right
language: 'zh-CN', // 界面语言
useBrowserLanguage: false,
widgetColor: '#1f93ff', // 小部件主题色
onlineOccupancy: null,
startsWith: '你好,有什么可以帮助你的吗?', // 欢迎语
calloutTimer: 5 // 呼叫气泡显示延迟(秒)
}
})
console.log('Chatwoot聊天小部件已初始化')
},
// 显示聊天窗口
showWidget() {
if (window.chatwootSDK) {
window.chatwootSDK.toggle()
}
},
// 隐藏聊天窗口
hideWidget() {
if (window.chatwootSDK) {
window.chatwootSDK.toggle('hide')
}
},
// 识别当前用户(登录用户)
setUser(identifier, userInfo) {
if (window.chatwootSDK) {
window.chatwootSDK.setUser(identifier, {
name: userInfo.name,
email: userInfo.email,
avatar_url: userInfo.avatarUrl,
phone_number: userInfo.phone
})
}
},
// 清除当前用户会话
logout() {
if (window.chatwootSDK) {
window.chatwootSDK.reset()
}
}
}
}
</script>
<style scoped>
.chat-widget-container {
position: relative;
width: 100%;
height: 100%;
}
</style>
在你的应用中使用这个组件:
<template>
<div id="app">
<header>
<h1>我的企业网站</h1>
<button @click="toggleChat" class="chat-toggle-btn">
💬 在线咨询
</button>
</header>
<main>
<p>欢迎访问我们的网站,有什么可以帮到您的吗?</p>
</main>
<!-- 聊天小部件组件 -->
<ChatwootWidget
website-token="your_website_token"
base-url="https://your-chatwoot-server.com"
/>
</div>
</template>
<script>
import ChatwootWidget from '@/components/ChatwootWidget.vue'
export default {
name: 'App',
components: {
ChatwootWidget
},
data() {
return {
isLoggedIn: false,
currentUser: null
}
},
mounted() {
// 模拟用户登录后的场景
this.checkUserSession()
},
methods: {
checkUserSession() {
// 假设这里有获取当前用户信息的逻辑
const userData = localStorage.getItem('user')
if (userData) {
this.currentUser = JSON.parse(userData)
this.isLoggedIn = true
// 识别已登录用户
this.$nextTick(() => {
if (window.chatwootSDK) {
window.chatwootSDK.setUser(this.currentUser.id.toString(), {
name: this.currentUser.name,
email: this.currentUser.email,
avatar_url: this.currentUser.avatar
})
}
})
}
},
toggleChat() {
// 触发聊天窗口显示
const event = new CustomEvent('toggle-chatwoot')
window.dispatchEvent(event)
}
}
}
</script>
<style>
.chat-toggle-btn {
padding: 10px 20px;
background-color: #1f93ff;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.chat-toggle-btn:hover {
background-color: #0d7cd9;
}
</style>
第三步:配置聊天小部件外观
Chatwoot 允许你通过多种方式自定义聊天小部件的外观,包括颜色、文字和功能选项。
在你的 .env 文件中添加或修改以下配置:
# 聊天小部件配置
CHATWOOT_WIDGET_BG_COLOR="#1f93ff" # 小部件背景色
CHATWOOT_WIDGET_TEXT_COLOR="#ffffff" # 文字颜色
或者在聊天小部件的设置页面中,通过可视化界面进行配置。支持的定制选项包括:
气泡颜色和文字颜色决定了小部件的整体视觉风格;欢迎语是访客首次打开聊天窗口时看到的第一句话,建议使用友好的问候语并介绍客服团队;呼叫气泡是在页面右下角显示的迷你气泡,点击可以快速打开聊天窗口;预设快捷回复是一些常见问题的快捷按钮,客户点击后系统会自动发送相应的问题,节省打字时间。
以下是定制配置的完整示例:
window.chatwootSDK.run({
websiteToken: 'your_website_token',
baseUrl: 'https://your-chatwoot-server.com',
chatwootSettings: {
// 外观定制
widgetColor: '#6366f1', // 紫色主题
buttonTextColor: '#ffffff',
// 功能设置
hideMessageBubble: false, // 显示气泡
bubblePosition: 'right',
bubbleAvatarStyle: 'circle', // 头像样式
welcomeMessage: '欢迎回来!有什么可以帮助您的吗?',
// 位置和动画
position: 'right',
effect: 'scale', // 打开动画效果
// 社交分享
shareMenuInConversationList: true, // 允许分享对话
// 联系人信息
useExplicitContactName: true,
requireEmail: true,
// 时间设置
calloutDuration: 5, // 呼叫气泡显示时间(秒)
// 桌面通知
enableDesktopNotification: true,
notificationDelay: 0
}
})
第四步:实现自动客服响应
自动回复是提升客户体验的重要功能。当客户发送消息时,Chatwoot 可以根据预设的规则自动响应,减少客户等待时间。
在 Chatwoot 后台,进入”自动化”菜单,点击”新建规则”开始创建自动回复规则。
一个典型的自动回复规则配置如下:
规则名称:工作时间外自动回复
触发条件:收到新消息
条件判断:当前时间不在工作时间范围内
执行动作:发送预设回复
// 工作时间定义(示例)
const WORK_HOURS = {
timezone: 'Asia/Shanghai',
days: [1, 2, 3, 4, 5], // 周一到周五
startHour: 9,
endHour: 18
}
// 检查是否在工作时间内
function isWithinWorkHours() {
const now = new Date()
const options = { timeZone: WORK_HOURS.timezone }
const localTime = new Date(now.toLocaleString('en-US', options))
const day = localTime.getDay()
const hour = localTime.getHours()
const isWorkDay = WORK_HOURS.days.includes(day)
const isWorkHour = hour >= WORK_HOURS.startHour && hour < WORK_HOURS.endHour
return isWorkDay && isWorkHour
}
// 生成下班时间回复
function getAfterHoursMessage() {
const messages = [
"感谢您的留言!我们的工作时间是周一至周五 9:00-18:00。我们会在工作时间尽快回复您。",
"您好,现在是下班时间。您的消息已收到,我们将在下一个工作日第一时间处理。祝您生活愉快!",
"感谢您的咨询!我们将于工作时间内处理您的消息,通常会在1-2小时内回复。"
]
return messages[Math.floor(Math.random() * messages.length)]
}
对于更复杂的场景,可以使用 Chatwoot 的 Inbox 规则功能。以下是一个通过 API 触发自动回复的示例:
import requests
import json
from datetime import datetime
class ChatwootAutomation:
"""
Chatwoot 自动化工具类
用于实现智能自动回复和会话管理
"""
def __init__(self, api_url, api_token):
"""
初始化 Chatwoot 自动化客户端
参数:
api_url: Chatwoot 服务器地址,例如 'https://your-server.com'
api_token: 你的 API 访问令牌
"""
self.api_url = api_url.rstrip('/')
self.api_token = api_token
self.headers = {
'Content-Type': 'application/json',
'api_access_token': api_token
}
def get_conversation(self, inbox_id, conversation_id):
"""
获取指定会话的详细信息
"""
endpoint = f"{self.api_url}/api/v1/accounts/{account_id}/conversations/{conversation_id}"
response = requests.get(endpoint, headers=self.headers)
return response.json()
def send_message(self, conversation_id, content):
"""
向指定会话发送消息
"""
endpoint = f"{self.api_url}/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages"
payload = {
'content': content,
'message_type': 'outgoing'
}
response = requests.post(endpoint, headers=self.headers, json=payload)
return response.json()
def check_business_hours(self):
"""
检查当前是否在工作时间内
返回:
bool: True 表示在工作时间内,False 表示非工作时间
"""
now = datetime.now()
weekday = now.weekday()
hour = now.hour
# 周一到周五(0-4),9点到18点
is_workday = weekday < 5
is_workhour = 9 <= hour < 18
return is_workday and is_workhour
def handle_incoming_message(self, message_data):
"""
处理接收到的消息,根据规则决定是否自动回复
参数:
message_data: 包含消息信息的字典
"""
message_type = message_data.get('message_type')
content = message_data.get('content', '').lower()
conversation_id = message_data.get('conversation_id')
# 仅处理客户发送的消息
if message_type != 'incoming':
return
# 检查是否在工作时间
if not self.check_business_hours():
after_hours_response = self.get_after_hours_response()
self.send_message(conversation_id, after_hours_response)
return
# 关键词自动回复
auto_reply = self.match_keyword_response(content)
if auto_reply:
self.send_message(conversation_id, auto_reply)
# 更新会话标签
self.add_label(conversation_id, 'auto-replied')
def get_after_hours_response(self):
"""
获取非工作时间的自动回复内容
"""
responses = [
"您好,现在是下班时间(18:00-次日9:00)。"
"您的消息已收到,我们将在工作时间尽快处理。",
"感谢您的留言!我们团队将在周一至周五9:00-18:00回复您。",
"非工作时间留言?没问题!我们的客服会在下一个工作日开始处理您的问题。"
]
return responses[0] # 可根据需要随机选择
def match_keyword_response(self, content):
"""
根据关键词匹配自动回复
参数:
content: 消息内容(小写)
返回:
str or None: 匹配的回复内容,如果没有匹配则返回 None
"""
# 关键词-回复映射表
keyword_map = {
'价格': '感谢您的价格咨询!我们的产品定价根据功能套餐有所不同。'
'基础版免费,高级版每月99元起。企业版请联系我们获取定制报价。'
'请问您想了解哪个版本的详情呢?',
'试用': '我们提供14天的免费试用期!您可以体验全部高级功能。'
'点击以下链接注册试用:https://example.com/trial',
'退款': '关于退款政策:未使用的服务可在购买后7天内申请全额退款。'
'请联系我们的客服团队,提供订单号以便处理。',
'密码': '重置密码请访问:https://example.com/reset-password'
'系统会发送重置链接到您的注册邮箱。',
'功能': '我们的主要功能包括:智能客服、多渠道接入、数据分析、'
'自动化工作流等。详情请查看功能介绍页面。'
}
# 检查消息中是否包含任何关键词
for keyword, response in keyword_map.items():
if keyword in content:
return response
return None
def add_label(self, conversation_id, label_name):
"""
为会话添加标签,用于分类和后续分析
"""
endpoint = f"{self.api_url}/api/v1/accounts/{account_id}/conversations/{conversation_id}/labels"
payload = {
'labels': [label_name]
}
response = requests.post(endpoint, headers=self.headers, json=payload)
return response.status_code == 200
# 使用示例
if __name__ == '__main__':
# 初始化客户端
client = ChatwootAutomation(
api_url='https://your-chatwoot-server.com',
api_token='your_api_token_here'
)
# 模拟处理一条客户消息
test_message = {
'message_type': 'incoming',
'content': '你好,我想了解一下价格',
'conversation_id': 123
}
# 处理消息
client.handle_incoming_message(test_message)
第五步:构建基础报表看板
现在让我们创建一个自定义的报表看板页面,用于展示关键的客服指标。这个看板可以嵌入到你的内部管理系统中:
<template>
<div class="dashboard-container">
<header class="dashboard-header">
<h1>客服运营看板</h1>
<div class="date-range-picker">
<select v-model="selectedPeriod" @change="fetchReportData">
<option value="today">今日</option>
<option value="week">本周</option>
<option value="month">本月</option>
<option value="custom">自定义</option>
</select>
</div>
</header>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">💬</div>
<div class="stat-content">
<div class="stat-label">总会话数</div>
<div class="stat-value">{{ stats.totalConversations }}</div>
<div class="stat-change" :class="stats.conversationsTrend > 0 ? 'up' : 'down'">
{{ stats.conversationsTrend > 0 ? '↑' : '↓' }}
{{ Math.abs(stats.conversationsTrend) }}% vs 上期
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">⏱️</div>
<div class="stat-content">
<div class="stat-label">平均响应时间</div>
<div class="stat-value">{{ formatDuration(stats.avgResponseTime) }}</div>
<div class="stat-change" :class="stats.responseTimeTrend < 0 ? 'up' : 'down'">
{{ stats.responseTimeTrend < 0 ? '↓' : '↑' }}
{{ Math.abs(stats.responseTimeTrend) }}% vs 上期
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">😊</div>
<div class="stat-content">
<div class="stat-label">客户满意度</div>
<div class="stat-value">{{ stats.satisfactionRate }}%</div>
<div class="stat-change" :class="stats.satisfactionTrend > 0 ? 'up' : 'down'">
{{ stats.satisfactionTrend > 0 ? '↑' : '↓' }}
{{ Math.abs(stats.satisfactionTrend) }}% vs 上期
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">✅</div>
<div class="stat-content">
<div class="stat-label">解决率</div>
<div class="stat-value">{{ stats.resolutionRate }}%</div>
<div class="stat-change" :class="stats.resolutionTrend > 0 ? 'up' : 'down'">
{{ stats.resolutionTrend > 0 ? '↑' : '↑' }}
{{ Math.abs(stats.resolutionTrend) }}% vs 上期
</div>
</div>
</div>
</div>
<div class="charts-section">
<div class="chart-card">
<h3>会话趋势</h3>
<canvas ref="conversationChart"></canvas>
</div>
<div class="chart-card">
<h3>渠道分布</h3>
<canvas ref="channelChart"></canvas>
</div>
</div>
<div class="table-section">
<h3>客服绩效排行</h3>
<table class="performance-table">
<thead>
<tr>
<th>排名</th>
<th>客服姓名</th>
<th>处理会话</th>
<th>平均响应</th>
<th>满意度</th>
<th>解决率</th>
</tr>
</thead>
<tbody>
<tr v-for="(agent, index) in agentPerformance" :key="agent.id">
<td>{{ index + 1 }}</td>
<td>{{ agent.name }}</td>
<td>{{ agent.conversations }}</td>
<td>{{ formatDuration(agent.avgResponseTime) }}</td>
<td>{{ agent.satisfaction }}%</td>
<td>{{ agent.resolutionRate }}%</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios'
import Chart from 'chart.js/auto'
export default {
name: 'DashboardPage',
data() {
return {
selectedPeriod: 'week',
stats: {
totalConversations: 0,
conversationsTrend: 0,
avgResponseTime: 0,
responseTimeTrend: 0,
satisfactionRate: 0,
satisfactionTrend: 0,
resolutionRate: 0,
resolutionTrend: 0
},
agentPerformance: [],
conversationChart: null,
channelChart: null
}
},
mounted() {
this.fetchReportData()
},
methods: {
async fetchReportData() {
try {
// 从 Chatwoot API 获取报表数据
const response = await axios.get(
`${process.env.CHATWOOT_API_URL}/api/v1/accounts/${process.env.ACCOUNT_ID}/reports`,
{
params: {
period: this.selectedPeriod,
type: 'agent'
},
headers: {
'api_access_token': process.env.CHATWOOT_API_TOKEN
}
}
)
this.processReportData(response.data)
this.renderCharts()
} catch (error) {
console.error('获取报表数据失败:', error)
// 使用模拟数据展示
this.useMockData()
this.renderCharts()
}
},
processReportData(data) {
// 处理从 API 获取的数据
// 实际实现中根据 API 返回的数据格式进行调整
},
useMockData() {
// 模拟数据,用于演示
this.stats = {
totalConversations: 1247,
conversationsTrend: 15,
avgResponseTime: 180, // 秒
responseTimeTrend: -8,
satisfactionRate: 92,
satisfactionTrend: 3,
resolutionRate: 87,
resolutionTrend: 5
}
this.agentPerformance = [
{ id: 1, name: '张三', conversations: 156, avgResponseTime: 120, satisfaction: 96, resolutionRate: 92 },
{ id: 2, name: '李四', conversations: 143, avgResponseTime: 150, satisfaction: 94, resolutionRate: 89 },
{ id: 3, name: '王五', conversations: 138, avgResponseTime: 180, satisfaction: 91, resolutionRate: 85 },
{ id: 4, name: '赵六', conversations: 125, avgResponseTime: 200, satisfaction: 88, resolutionRate: 82 },
{ id: 5, name: '钱七', conversations: 112, avgResponseTime: 220, satisfaction: 85, resolutionRate: 78 }
]
},
formatDuration(seconds) {
if (seconds < 60) {
return `${seconds}秒`
}
const minutes = Math.floor(seconds / 60)
const remainingSeconds = seconds % 60
return `${minutes}分${remainingSeconds}秒`
},
renderCharts() {
this.renderConversationChart()
this.renderChannelChart()
},
renderConversationChart() {
const ctx = this.$refs.conversationChart
if (!ctx) return
if (this.conversationChart) {
this.conversationChart.destroy()
}
// 模拟趋势数据
const labels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
const data = [165, 189, 201, 178, 199, 145, 170]
this.conversationChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: '会话数',
data: data,
borderColor: '#6366f1',
backgroundColor: 'rgba(99, 102, 241, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: false
}
}
}
})
},
renderChannelChart() {
const ctx = this.$refs.channelChart
if (!ctx) return
if (this.channelChart) {
this.channelChart.destroy()
}
this.channelChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['网站聊天', '电子邮件', '微信', '其他'],
datasets: [{
data: [55, 25, 15, 5],
backgroundColor: [
'#6366f1',
'#10b981',
'#f59e0b',
'#6b7280'
]
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
}
}
}
})
}
}
}
</script>
<style scoped>
.dashboard-container {
padding: 24px;
background-color: #f9fafb;
min-height: 100vh;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.dashboard-header h1 {
font-size: 24px;
font-weight: 600;
color: #111827;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: white;
border-radius: 12px;
padding: 20px;
display: flex;
align-items: flex-start;
gap: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.stat-icon {
font-size: 32px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f3f4f6;
border-radius: 10px;
}
.stat-value {
font-size: 28px;
font-weight: 700;
color: #111827;
}
.stat-change {
font-size: 14px;
margin-top: 4px;
}
.stat-change.up {
color: #10b981;
}
.stat-change.down {
color: #ef4444;
}
.charts-section {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
.chart-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.chart-card h3 {
margin-bottom: 16px;
font-size: 16px;
font-weight: 600;
color: #374151;
}
.table-section {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.table-section h3 {
margin-bottom: 16px;
font-size: 16px;
font-weight: 600;
color: #374151;
}
.performance-table {
width: 100%;
border-collapse: collapse;
}
.performance-table th,
.performance-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.performance-table th {
font-weight: 600;
color: #6b7280;
font-size: 14px;
}
.performance-table td {
color: #111827;
}
.performance-table tbody tr:hover {
background-color: #f9fafb;
}
</style>
常见应用场景与案例分析
场景一:电商平台的售前咨询
电商场景下的客服需求有其独特性。客户在浏览商品时会产生各种疑问——尺寸是否合适、发货时间、支付方式、退换货政策等。这些咨询往往发生在购前的关键决策节点,及时且专业的响应直接影响转化率。
使用 Chatwoot 的解决方案是将网站聊天与产品页面联动。当客户在特定产品页面发起咨询时,系统会自动识别当前页面并将产品信息传递给客服人员。客服可以快速调用预设的产品知识库回复常见问题,也可以直接分享产品链接给客户。
自动回复在这里发挥了重要作用。例如,当客户询问尺码时,系统可以自动回复尺码表和测量指南;当客户犹豫不决时,可以发送限时优惠券来促进转化。这种智能的自动化流程在降低人工成本的同时,也提升了响应速度和客户体验。
推荐配置:
- 开启主动邀请功能,在客户页面停留超过30秒时自动弹出咨询邀请
- 设置关键词自动回复,覆盖80%的常见问题
- 配置满意度评价,及时收集客户反馈
- 与 CRM 系统集成,记录客户咨询历史
场景二:SaaS 产品的技术支持
软件即服务(SaaS)产品的技术支持通常涉及更复杂的问题。客户可能在使用过程中遇到技术障碍,需要详细的排查指导。这种场景对客服团队的专业能力要求较高,同时也需要有效的知识积累和共享机制。
Chatwoot 的对话管理功能非常适合技术支持场景。你可以为不同的问题类型创建专门的团队——功能咨询、账单问题、技术故障等——确保每类问题都能分配给最合适的人员。
会话转接和协作功能让复杂问题可以得到多位专家的共同处理。当一线客服无法解决问题时,可以一键将会话转接给二线技术支持,同时保留完整的对话历史,避免客户重复描述问题。
推荐配置:
- 创建问题分类标签体系,便于统计和分析
- 启用内部备注功能,团队成员可以分享排查思路
- 设置 SLA 规则,确保响应时间承诺
- 导出技术支持数据用于产品改进
场景三:在线教育平台的家校沟通
教育机构的家校沟通有其特定的时间规律和沟通内容。家长通常在接送孩子或检查作业的时间段发起咨询,问题可能涉及课程安排、作业反馈、请假流程等。这类沟通需要既专业又富有人情味。
Chatwoot 的多渠道接入能力让家长可以通过最喜欢的方式联系学校。网站聊天适合工作时间的即时沟通,离线消息则方便家长在非工作时间留言。自动回复可以处理请假申请、课程时间查询等标准化请求,释放班主任的工作量。
会话总结功能对教育场景特别有价值。当一个沟通周期结束时,系统可以自动生成会话摘要,记录关键问题和解决方案,便于后续跟进和统计。
推荐配置:
- 设置明确的上下班时间,自动切换值班模式
- 配置节假日自动回复模板
- 创建常见问题 FAQ,嵌入聊天窗口
- 与学校管理系统对接,同步学生信息
场景四:金融行业的合规咨询
金融行业的客服有着特殊的合规要求。所有沟通记录都需要完整保存以满足监管检查,风险提示和免责声明必须在特定场景下自动展示。这类场景对系统的可定制性和数据安全性提出了更高要求。
Chatwoot 的数据导出和 API 集成能力可以与企业的合规系统对接。所有会话记录都可以导出为标准格式,供合规部门审查。敏感信息处理功能可以在数据库层面实现脱敏存储。
对于风险提示,系统支持在特定话题被触发时自动发送合规声明。例如,当客户询问投资收益时,系统会自动附带”投资有风险,入市需谨慎”的提示,并记录该提示已被发送。
推荐配置:
- 启用会话存档功能,确保所有对话可追溯
- 配置敏感词过滤,自动拦截违规内容
- 设置会话审查工作流,重要对话需要主管审核
- 与工单系统集成,形成完整的合规记录
进阶技巧与最佳实践
性能优化建议
当 Chatwoot 处理的会话量逐渐增长时,性能优化成为维护良好用户体验的关键。以下是经过实际生产环境验证的优化策略。
数据库层面的优化是最基础也是最重要的。确保 PostgreSQL 配置了合适的连接池大小,通常建议将 max_connections 设置为 Rails 服务器进程数的两倍。定期执行 ANALYZE 命令更新查询计划器的统计信息,对于经常查询的字段创建适当的索引。
-- 为高频查询字段创建索引
CREATE INDEX CONCURRENTLY idx_conversations_account_id
ON conversations(account_id);
CREATE INDEX CONCURRENTLY idx_messages_conversation_id
ON messages(conversation_id) WHERE deleted_at IS NULL;
-- 复合索引用于常见查询组合
CREATE INDEX CONCURRENTLY idx_conversations_status_assignee
ON conversations(account_id, status, assignee_id);
Redis 配置对系统响应速度影响显著。建议将 Redis 的 maxmemory-policy 设置为 allkeys-lru,以在内存不足时自动清理最少使用的键。同时,为不同的用途使用独立的 Redis 数据库可以避免键冲突。
# config/initializers/redis.rb
$redis = Redis.new(
host: ENV.fetch('REDIS_HOST', 'localhost'),
port: ENV.fetch('REDIS_PORT', 6379),
db: ENV.fetch('REDIS_DB', 0),
password: ENV.fetch('REDIS_PASSWORD', nil),
timeout: 5,
reconnect_attempts: 3
)
# 为不同用途配置独立的连接
$redis_pubsub = Redis.new(
host: ENV.fetch('REDIS_HOST', 'localhost'),
port: ENV.fetch('REDIS_PORT', 6379),
db: ENV.fetch('REDIS_PUBSUB_DB', 1)
)
安全加固指南
将 Chatwoot 暴露在公网环境时,安全加固是不可忽视的环节。以下是生产环境部署的推荐安全配置。
首先,强制使用 HTTPS 是最基本的保障。如果你使用 Let’s Encrypt 获取免费证书,配置 Nginx 反向代理的示例如下:
server {
listen 80;
server_name your-chatwoot-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-chatwoot-domain.com;
ssl_certificate /etc/letsencrypt/live/your-chatwoot-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-chatwoot-domain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# 安全响应头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 90;
}
location /cable {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
}
其次,配置强密码策略和双因素认证。Chatwoot 支持通过 omniauth 集成多种第三方认证服务,包括 Google、GitHub 和 SAML。对于企业部署,建议启用 SSO(单点登录)以统一身份认证管理。
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2,
ENV['GOOGLE_CLIENT_ID'],
ENV['GOOGLE_CLIENT_SECRET'],
{
scope: 'email,profile',
prompt: 'select',
image_aspect_ratio: 'square',
image_size: 50
}
# SAML 配置示例
provider :saml,
assertion_consumer_service_url: "https://your-domain.com/auth/saml/callback",
idp_sso_target_url: "https://your-idp.com/sso",
idp_cert: ENV['SAML_IDP_CERT']
end
最后,定期更新 Chatwoot 到最新版本以获取安全补丁。在更新前务必备份数据库和上传的文件。上线前应在预发布环境进行充分测试。
多语言配置详解
对于面向全球用户的部署,多语言支持是提升用户体验的重要因素。Chatwoot 本身支持多语言界面,但你可能还需要配置聊天小部件的翻译内容。
聊天小部件支持超过 35 种语言,你可以通过配置 API 设置小部件的语言:
window.chatwootSDK.run({
websiteToken: 'your_website_token',
baseUrl: 'https://your-chatwoot-server.com',
chatwootSettings: {
language: 'zh-CN', // 使用简体中文
// 或者根据用户浏览器语言自动选择
useBrowserLanguage: true,
// 自定义翻译覆盖
customTranslations: {
'zh-CN': {
'Welcome': '欢迎咨询',
'We are not available right now...': '当前非工作时间,请在工作时间联系我们',
'Chat with us': '在线咨询'
}
}
}
})
对于更复杂的多语言需求,你可以创建自定义的翻译文件:
# config/locales/zh-CN.yml
zh-CN:
chatwoot:
website_widget:
greeting_message: "您好,有什么可以帮助您的吗?"
reply_chat: "回复聊天"
conversation_started: "对话已开始"
自定义集成开发
Chatwoot 提供了丰富的 API 和 Webhook,让你可以将它与现有的业务系统深度集成。以下是一个典型的集成开发示例,演示如何将 Chatwoot 与内部工单系统同步。
import hmac
import hashlib
import json
from datetime import datetime
import requests
class ChatwootWebhookHandler:
"""
处理 Chatwoot Webhook 的工具类
用于将客服会话与内部工单系统同步
"""
def __init__(self, webhook_secret, internal_api_url, api_key):
"""
初始化 Webhook 处理器
参数:
webhook_secret: Chatwoot Webhook 签名密钥
internal_api_url: 内部工单系统 API 地址
api_key: 内部工单系统 API 密钥
"""
self.webhook_secret = webhook_secret
self.internal_api_url = internal_api_url.rstrip('/')
self.api_key = api_key
self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
def verify_signature(self, payload_body, signature_header):
"""
验证 Webhook 请求签名,防止伪造请求
参数:
payload_body: 请求体原始内容(字节串)
signature_header: X_Chatwoot_Signature 头部的值
返回:
bool: 签名是否有效
"""
if not signature_header:
return False
# 计算期望的签名
secret = self.webhook_secret.encode('utf-8')
expected_signature = hmac.new(
secret,
payload_body,
hashlib.sha256
).hexdigest()
# 安全地比较签名
return hmac.compare_digest(f'sha256={expected_signature}', signature_header)
def handle_conversation_created(self, event_data):
"""
处理新会话创建事件
当客户发起新的咨询会话时,在内部工单系统创建对应的工单
"""
conversation = event_data.get('conversation', {})
meta = event_data.get('meta', {})
# 提取客户信息
customer = meta.get('customer', {})
external_id = customer.get('id')
customer_name = customer.get('name', '匿名用户')
customer_email = customer.get('email', '')
# 在内部系统创建工单
ticket_data = {
'title': f"客服咨询 - {customer_name}",
'description': f"来源:{conversation.get('channel', '网站')}\n"
f"会话 ID:{conversation.get('id')}\n"
f"客户邮箱:{customer_email}",
'priority': 'normal',
'category': 'customer_service',
'external_ref': f"chatwoot_{conversation.get('id')}",
'customer': {
'name': customer_name,
'email': customer_email,
'external_id': str(external_id) if external_id else None
},
'custom_fields': {
'chatwoot_conversation_id': conversation.get('id'),
'chatwoot_inbox_id': conversation.get('inbox_id'),
'channel': conversation.get('channel')
}
}
# 发送创建请求
response = requests.post(
f"{self.internal_api_url}/api/tickets",
headers=self.headers,
json=ticket_data
)
return response.json() if response.status_code == 201 else None
def handle_message_created(self, event_data):
"""
处理新消息创建事件
将聊天记录同步到内部工单系统的对话历史中
"""
message = event_data.get('message', {})
conversation = event_data.get('conversation', {})
ticket_ref = f"chatwoot_{conversation.get('id')}"
# 根据消息类型构建不同的记录格式
message_type = message.get('message_type')
if message_type == 'incoming':
# 客户发送的消息
content = {
'type': 'customer_message',
'author': message.get('sender', {}).get('name', '客户'),
'content': message.get('content', ''),
'timestamp': message.get('created_at')
}
elif message_type == 'outgoing':
# 客服回复的消息
sender = message.get('sender', {})
content = {
'type': 'agent_message',
'author': sender.get('name', '客服'),
'author_role': sender.get('role', 'agent'),
'content': message.get('content', ''),
'timestamp': message.get('created_at')
}
elif message_type == 'activity':
# 系统活动消息
content = {
'type': 'activity',
'content': message.get('content', ''),
'timestamp': message.get('created_at')
}
else:
return None
# 添加对话记录到工单
response = requests.post(
f"{self.internal_api_url}/api/tickets/{ticket_ref}/messages",
headers=self.headers,
json=content
)
return response.status_code == 200
def handle_conversation_resolved(self, event_data):
"""
处理会话解决事件
更新内部工单状态为已解决,并计算响应时间等指标
"""
conversation = event_data.get('conversation', {})
meta = event_data.get('meta', {})
ticket_ref = f"chatwoot_{conversation.get('id')}"
# 计算关键指标
metrics = meta.get('metrics', {})
update_data = {
'status': 'resolved',
'resolved_at': datetime.now().isoformat(),
'metrics': {
'first_response_time': metrics.get('first_response_time'),
'avg_response_time': metrics.get('avg_response_time'),
'resolution_time': metrics.get('resolution_time')
}
}
response = requests.patch(
f"{self.internal_api_url}/api/tickets/{ticket_ref}",
headers=self.headers,
json=update_data
)
return response.status_code == 200
def process_webhook(self, payload_body, signature):
"""
Webhook 统一处理入口
根据事件类型分发到相应的处理方法
"""
# 验证签名
if not self.verify_signature(payload_body, signature):
raise ValueError("无效的 Webhook 签名")
# 解析事件数据
try:
event_data = json.loads(payload_body)
except json.JSONDecodeError:
raise ValueError("无效的 JSON 数据")
# 获取事件类型
event_type = event_data.get('event')
# 分发处理
handlers = {
'conversation_created': self.handle_conversation_created,
'message_created': self.handle_message_created,
'conversation_resolved': self.handle_conversation_resolved
}
handler = handlers.get(event_type)
if handler:
return handler(event_data)
return None
# Webhook 接收端点示例(Flask)
"""
from flask import Flask, request, abort
import json
app = Flask(__name__)
handler = ChatwootWebhookHandler(
webhook_secret='your_webhook_secret',
internal_api_url='https://internal-api.example.com',
api_key='your_internal_api_key'
)
@app.route('/webhooks/chatwoot', methods=['POST'])
def chatwoot_webhook():
# 获取原始请求体
payload = request.get_data()
# 获取签名头
signature = request.headers.get('X_Chatwoot_Signature')
try:
result = handler.process_webhook(payload, signature)
return json.dumps({'success': True, 'result': result}), 200
except ValueError as e:
return json.dumps({'error': str(e)}), 400
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
"""
总结与资源链接
通过这篇详尽的教程,你应该已经对 Chatwoot 有了全面的认识,从基础的环境搭建到高级的 API 集成,从核心功能的使用到生产环境的优化。Chatwoot 不仅仅是一个客服工具,更是一套完整的客户沟通解决方案,它的开源特性让企业能够完全掌控自己的客户数据,而丰富的功能集则确保了它能够满足各种复杂的业务场景需求。
值得强调的是,Chatwoot 的价值不仅在于它本身的功能,更在于它所代表的开源精神和社区力量。作为一个活跃的开源项目,Chatwoot 持续吸收来自全球开发者的贡献,不断完善和增强其功能。你在使用过程中发现的问题或提出的改进建议,都可能成为推动项目发展的力量。
如果你觉得这个项目对你有帮助,不妨给它一个 Star,或者在社交媒体上分享你的使用经验。对于企业用户,也可以考虑赞助这个项目,支持其持续发展。开源生态的繁荣需要每个人的参与和贡献。
相关资源链接
以下是帮助你进一步学习和使用 Chatwoot 的精选资源:
Chatwoot 官方文档:https://www.chatwoot.com/docs
GitHub 仓库:https://github.com/chatwoot/chatwoot
官方演示站点:https://app.chatwoot.com(可以体验完整功能)
社区论坛:https://github.com/chatwoot/chatwoot/discussions
如果你对开源客服领域感兴趣,以下是一些值得关注的同类项目:
Botpress 是一个开源的对话式 AI 平台,专注于构建智能聊天机器人,适合需要强大 AI 能力的场景。它支持自然语言理解、多语言支持和复杂对话流程设计。
Rasa 是另一个开源的机器学习框架,专门用于构建对话式 AI。它提供了完整的技术栈,从 NLU(自然语言理解)到对话管理都可以在本地完成,适合对数据隐私有严格要求的场景。
Canny 则是一个专注于产品反馈管理的开源工具,虽然不是传统的客服系统,但它提供的客户声音收集和分析功能可以与 Chatwoot 形成互补。
每个工具都有其独特的定位和优势,根据你的具体需求选择合适的组合,才能构建出最适合自己的客户沟通体系。祝你在这个领域探索愉快!
评论区