标签

Electron 构建多店铺智能客服系统:会话隔离、消息抓取与自动回复实现

发布时间:2026-08-28 16:18阅读:1

项目目前主要围绕以下几个问题展开:

这篇文章记录一下整个项目的架构和几个比较关键的实现思路。

注意:本文主要讨论 Electron、网页观察、本地数据处理和 AI API 集成等技术实现。涉及第三方平台的功能应优先使用其官方开放接口,并遵守对应平台的最新规则。

这个项目本质上需要同时具备两种能力:

第一种是浏览器能力。

需要加载现有的商家后台网页,并维护 Cookie、登录状态以及网页运行环境。

第二种是桌面程序能力。

需要:

Electron 正好把 Chromium 和 Node.js 组合在一起,因此比较适合这种“网页 + 本地应用”的场景。

项目核心依赖:

整体结构大致如下:

其中:

负责 Electron 主进程、窗口管理、账号管理、数据存储以及 AI 调用。

负责观察被加载的商家页面,并在网页环境和 Electron 主进程之间建立桥梁。

负责本地快捷回答匹配。

这种拆分方式有一个好处:

页面观察、业务逻辑和 UI 不会全部堆在一个文件里。

如果直接创建多个普通网页窗口,很容易遇到一个问题:

登录 A 店铺之后,另一个窗口也变成 A 店铺。

原因是多个页面可能共用 Chromium Session。

多店铺客服工具真正需要的是:

每个店铺必须拥有自己的浏览器会话。

Electron 的 Session/Partition 机制非常适合解决这个问题。

设计上可以为每个账号生成稳定的 partition:

然后让对应页面始终使用这个 partition。

这样:

互不影响。

而 persist: 前缀还有一个非常重要的作用:

关闭程序以后 Session 仍然可以持久化。

下一次打开程序时,只要平台本身的登录状态仍然有效,就不需要重新登录全部店铺。

对于多账号桌面应用而言,这是整个架构的基础。

项目中另外一个比较重要的设计思想是:

店铺账号是业务对象,浏览器页面只是它的一个运行载体。

本地保存类似:

用于记录:

这样以后即使重新创建窗口,也可以根据账号 ID 找回对应 Session。

整个关系变成:

而不是:

这样后期做窗口重建、页面异常恢复以及批量店铺操作会方便很多。

客服工作台的第二个核心问题是:

怎么知道客户发来了新消息?

如果没有官方消息接口,一种研究思路是在 preload 环境中观察当前页面。

项目将这一部分单独放在:

中。

页面观察层负责识别:

然后通过 IPC 将结构化数据交给主进程。

架构类似:

这里有一个非常重要的原则:

不要让 Renderer 直接负责网页采集。

Renderer 应该只关心:

而不是:

否则平台网页稍微发生变化,整个应用 UI 层也会受到影响。

网页观察有一个非常常见的问题:

同一条消息可能被观察到很多次。

例如:

如果直接保存,就可能出现:

因此消息进入本地存储前必须进行去重。

一种常见方案是给消息构造稳定标识:

然后生成 message key。

例如:

保存之前判断:

实际项目中还需要考虑:

因此不能简单地只使用:

作为唯一标识。

为了降低部署复杂度,这个项目没有一开始就引入数据库,而是使用本地 JSON 文件。

类似:

每个店铺单独保存。

这样的优点是:

但普通:

并不是非常可靠。

如果程序恰好在写文件过程中异常退出,JSON 可能只写了一半。

所以项目采用了类似:

的方式降低损坏概率。

对于 Electron 本地工具来说,这是一个很值得做的小优化。

最开始设计 AI 客服时,很容易想到:

但实际运行后会发现大量客服问题都是重复的:

这些问题调用一次大模型,本质上是在浪费:

所以项目增加了一层:

完整流程变成:

这样高频问题基本不需要调用远程模型。

最简单的快捷回答可能这样写:

但真实客户可能会说:

所以项目实现了一个简单的中文概念归一化系统。

例如:

这样:

都会被归一化为:

客服消息中还有大量没有实际语义的信息:

例如:

真正有价值的信息可能只有:

因此在计算相似度之前,会先进行文本标准化:

这里还使用:

处理 Unicode 字符形式差异。

为了保持整个本地匹配器足够轻量,项目没有引入大型 NLP 模型,而是使用字符 Bigram。

例如:

可以拆成:

然后计算两个集合之间的 Dice Similarity。

核心思路:

这样就得到一个:

之间的相似度分数。

相比简单关键词匹配,这种方法对于:

会更加友好。

这里踩过一个比较有意思的坑。

例如:

和:

文本非常相似。

但是业务含义完全不同。

一个问:

另一个问:

如果只计算字符串相似度,很容易误匹配。

因此代码中增加了概念冲突:

也就是说:

当输入和规则存在明显概念冲突时,直接降低甚至取消匹配。

最终评分不是简单的字符串相似度,而是类似:

这种方法虽然没有 embedding 那么强,但优势非常明显:

非常适合几十到几百条客服 FAQ。

有时候一条消息虽然命中了关键词,但实际上并不应该触发对应回答。

例如规则:

可能需要排除:

因此每条快捷回答规则包含:

匹配过程先检查:

只有达到阈值并且没有明显歧义,才返回快捷回答。

这个设计可以显著降低自动客服的“答非所问”。

最终形成的回复架构实际上是三级:

相比所有消息直接发送给大模型,这种架构更适合客服系统。

原因很简单:

确定性问题应该由确定性系统解决。

AI 更适合处理:

而不是用来回答每天重复几百次的:

为了避免系统和单一 AI 服务绑定,项目把 AI Provider 抽象成三种:

用户可以自行选择。

整体结构:

上层客服逻辑并不需要知道底层究竟调用了谁。

FastGPT 提供 OpenAI 风格的 Chat API,因此接入成本比较低。

请求大致可以抽象为:

其中一个很有用的字段是:

可以为:

生成稳定 ID。

例如:

这样不同客户的上下文不会混在一起。

FastGPT 官方当前文档也说明,chat/completions 推荐在请求体中传入 appId,并可通过 chatId 管理应用下的独立会话。

Dify Chatflow 同样存在会话上下文问题。

假设:

如果没有前面的聊天记录:

实际上无法判断指什么。

因此系统为:

保存独立:

结构类似:

这样每个客户都拥有自己的 AI 上下文。

Electron 桌面应用还有一个非常重要的问题:

API Key 放在哪里?

最简单的方法当然是:

但这样任何能够读取文件的人都能直接看到 Key。

项目使用 Electron:

进行本地加密:

保存时转换成 Base64:

使用时再:

这样至少不会直接把 API Key 以明文形式放在配置文件中。

当然需要说明:

safeStorage 并不能代替完整的操作系统安全措施。

如果系统账号本身已经被攻破,本地应用也无法保证绝对安全。

项目给每个店铺设计了三种模式:

其中实际更推荐:

工作流程:

原因是大模型可能出现:

客服系统一旦自动发送错误信息,影响的是实际客户。

因此“AI 生成 + 人工确认”通常是更稳妥的过渡方案。

自动客服还有一个容易被忽略的问题:

AI 回答正确,不代表消息发送就是安全的。

假设程序当前处理:

但页面因为刷新或者人工操作切换到了:

这时候如果代码直接:

就可能把客户 A 的回复发送给客户 B。

所以自动发送之前必须重新确认目标身份。

设计上应该:

这里宁可:

也不要:

这是自动客服系统非常重要的一条安全原则。

除了监听实时消息,项目还实现了历史会话批量采集。

流程大致是:

这里不能一切换会话就马上读取 DOM。

因为现代网页通常存在异步渲染。

例如:

因此需要:

而不是单纯:

把所有模块组合起来以后,大概是:

做完这个项目以后,我觉得有几个设计思路比较值得保留。

例如:

很多都是确定性问题。

使用:

通常比大模型更:

比较合理的客服链路是:

而不是:

无论做的是:

只要存在多账号,第一件事都应该考虑:

否则后期会出现大量登录状态污染问题。

比如自动发送。

真正可靠的逻辑不是:

而是:

任何一步无法确认:

这比单纯追求自动化成功率重要得多。

目前这个架构还有很多可以继续扩展的地方。

例如:

当聊天记录越来越多以后,可以把:

迁移到:

实现更好的查询、索引和数据管理。

当前快捷回答使用:

未来可以增加 embedding:

提高自然语言问题的召回率。

本地知识库目前可以进一步升级成:

从简单文本知识库变成真正的 RAG。

整个架构实际上并不一定只能处理一个电商平台。

如果把:

进一步抽象成:

理论上可以形成:

上层:

都可以继续复用。

这个项目表面上看是一个 Electron 客服工具,但真正值得研究的是背后的几个通用问题:

其中我认为最有价值的并不是“接入了 AI”,而是:

把确定性的本地规则、业务知识和不确定性的大模型组合起来。

高频问题由本地系统解决,复杂问题交给 AI,风险操作再增加人工确认。

这种架构不仅适用于电商客服。

同样可以用在:

等场景。

后续如果继续迭代,我准备重点尝试:

把现在的“AI 辅助客服”进一步变成一个更加通用的本地智能客服工作台。