美洽
首页 / 未分类 / 美洽怎么设置访客端聊天窗口离线缓存?

美洽怎么设置访客端聊天窗口离线缓存?

2026-05-10 · admin

访客端聊天窗口的离线缓存,核心是把“正在或曾经发生的会话状态”在访客设备上本地保存起来,并在网络恢复后把未送达或新消息与美洽后端进行可靠同步。实现方法包括:拦截 SDK 的消息收发回调、把消息(文本、附件引用、状态、时间戳、客户端 ID)持久化到 IndexedDB/ localStorage、为未发送消息建立队列并在重连时重试、处理去重与序号对齐、以及对隐私和容量做策略控制。整个过程还可以结合 Service Worker(或移动端的后台任务)、广播通道和后端校验来保证多端一致性和体验平滑。

美洽怎么设置访客端聊天窗口离线缓存?

先把问题拆开:为什么需要离线缓存,目标是什么

把事情讲清楚一点,不要一上来就代码。想象你在购物网站同时浏览商品,突然断网,回到页面后希望还能看到刚才的对话,而不是空白。这就是离线缓存要解决的真实场景。目标通常有三点:

  • 可用性:断网时仍能查看历史消息和未读内容;
  • 可靠性:访客输入的消息不会丢失,能在恢复网络后被送出并显示正确状态;
  • 用户体验:界面能立即渲染历史消息而不是等待网络,从而节省延迟。

技术选项总览(为什么选择 IndexedDB 而不是 localStorage 等)

不同存储方式各有利弊,按常见维度列个表,方便做决定:

存储方式 优点 缺点
localStorage 简单、同步 API、浏览器兼容好 容量小(约 5MB)、同步阻塞、不适合二进制/大附件
IndexedDB 容量大、支持事务、二进制存储(Blob)、适合消息存档 API 比较复杂(但有包装库)、异步
Service Worker + Cache / Background Sync 可在后台处理同步、支持资源缓存和离线恢复 实现复杂、兼容性需考虑、移动端差异
Session 或 Cookie 简单用于会话标识 不适合消息体缓存、安全与容量受限

结论(工程实践)

对于聊天消息,推荐把文本和元数据(状态、时间、client_msg_id)存到 IndexedDB;把较大附件保存为 Blob(或保存上传引用);把少量配置信息或短期标志放 localStorage;用 BroadcastChannel 或 localStorage 事件做多标签页同步;用 Service Worker 做离线报文的后台重试(如果需要)。

按步骤实现:从架构到代码(Web 浏览器场景)

下面按实际开发流程来讲,像在黑板上写步骤,边写边想。

1. 设计本地数据模型

最重要的是要有稳定的消息 ID 和状态机。常见字段:

  • message_id(服务器 id,可能在远端生成)
  • client_msg_id(客户端生成的唯一 ID,用于去重与重试)
  • conversation_id / session_id
  • sender(visitor/agent/system)
  • content(文本或引用)
  • attachments(数组,保存文件名、mime、本地 blob 引用或服务器 URL)
  • status(sending / sent / failed / received / read)
  • timestamp

2. 拦截 SDK 消息回调并持久化

大多数客服 SDK(包含美洽 SDK)会提供消息接收、发送状态回调。你需要把这些回调统一接入本地存储逻辑。

伪代码思路(不写具体 SDK 名称,按通用事件):

// 当接收到消息
sdk.on('message:receive', (msg) => {
  saveMessageToIndexedDB(msg);
  renderToUI(msg);
});

// 当发送消息(用户点击发送) function sendMessage(content) { const clientMsgId = genClientId(); const msg = { client_msg_id: clientMsgId, content, status: 'sending', ts: Date.now() }; saveMessageToIndexedDB(msg); renderToUI(msg); sdk.send(msg).then(serverInfo => { // 更新状态和 server id updateMessageStatus(clientMsgId, { status: 'sent', message_id: serverInfo.id }); }).catch(err => { updateMessageStatus(clientMsgId, { status: 'failed' }); }); }

3. 未发送队列与重试策略

核心点:不要丢数据。常见做法:

  • 把 status 为 sending 或 failed 的消息放入“待发送队列”(保存在 IndexedDB)
  • 当网络恢复或 WebSocket 重连时,按时间/序号顺序逐条重发(使用 client_msg_id 防止重复)
  • 实现指数退避(exponential backoff)与最大重试次数,超过后标记为 failed 并给用户重发按钮

4. 重连同步与去重策略

重连后要做三件事:拉取服务器漏掉的消息、发送本地未送达的消息、合并两端历史。具体策略:

  • 按时间或 sequence 拉取服务器端最新的消息段(如果美洽有消息序号 API,就更好)
  • 用 client_msg_id 去重:如果服务器返回的消息包里包含和本地 client_msg_id 相同的消息,则把本地发送记录替换为服务器记录(并更新 status)
  • 如果没有 client_msg_id,需比对时间戳 + 内容 + sender 做近似去重,但要小心边界情况

附件(图片、文件)如何缓存与同步

附件比文本复杂:占空间、上传中断、需要断点续传时要做更多工作。实战建议:

  • 展示:先把本地文件生成缩略图或占位图并存入 IndexedDB(Blob);UI 显示“正在上传”或“等待网络”状态。
  • 上传:如果网络可用立即上传文件到美洽/自家 CDN,成功后把服务器 URL 写回消息记录;若网络不可用,把文件暂存 IndexedDB 并放入附件上传队列,重连后自动上传。
  • 断点续传:如果文件很大,优先使用 SDK/后端提供的分片上传能力;如果没有,至少保证重试不会重复上传多个副本(用 client_msg_id + 上传标识)。

多标签页和移动端的同步问题

用户可能在多个浏览器标签或不同设备上打开同一会话。要考虑并发更新与一致性。

  • 在浏览器内:用 BroadcastChannel 或 localStorage 的 storage 事件把缓存变更广播给其它标签,确保 UI 同步。
  • 跨设备:依赖后端(美洽)去做最终一致性,客户端只做本地持久化与按需拉取。
  • 移动端(iOS/Android):如果使用美洽移动 SDK,优先使用 SDK 的本地持久化机制(若提供);否则用 SQLite/Realm 做本地存储,后台任务或推送机制做同步。

安全、隐私与合规

离线缓存里会有用户私人信息,要认真保护:

  • 敏感信息加密:在客户端使用 Web Crypto(浏览器)或平台密钥库(移动端)对本地存储进行加密,或对关键信息字段加密。
  • 最小化存储时间:按业务需要设定缓存 TTL,用户登出或切换账号时立即清理缓存。
  • 权限控制:不要把敏感 token(如登录凭证)直接存入聊天缓存,使用短期授权并通过安全通道刷新。
  • 合规要求:如果涉及个人数据保护法规(例如 GDPR / 中国个人信息保护法),要提供数据查看/删除入口并记录操作日志。

性能与空间管理策略

不加控制的离线缓存会把用户设备塞满,所以要有清理规则:

  • 按会话或时间窗口保留最近 N 条消息或最近 T 天的记录(可配置);
  • 附件用 LRU(最近最少使用)删除策略,或只保留缩略图;
  • 定期压缩(例如把多条小消息合并索引),但在实现时要平衡复杂度;
  • 为大型文件或媒体提供“仅在 Wi-Fi 下保留”选项。

测试矩阵:怎么验证离线缓存功能有效

测试要覆盖断网、弱网、切换网络、并发多端、附件上传中断、重连后顺序错乱等场景。常见测试步骤:

  • 无网络:加载页面,确认历史消息能从本地读取并正确渲染;
  • 发消息后断网:消息在 UI 显示为 sending 或 queued,并存入本地;
  • 恢复网络:消息自动重试并被服务器接受,客户端记录更新为 sent;
  • 多标签页并发:一个标签发送或接收消息,另一个标签同步显示;
  • 大附件:中断上传,再次恢复后能继续或成功重新上传且不重复计费/不重复展示。

实用代码片段(简化示例,用于理解设计,不依赖具体 SDK)

下面是一个极简的 IndexedDB 保存/读取消息的示意,供思路参考:

async function openDB() {
  return new Promise((res, rej) => {
    const req = indexedDB.open('chat-db', 1);
    req.onupgradeneeded = e => {
      const db = e.target.result;
      if (!db.objectStoreNames.contains('messages')) {
        db.createObjectStore('messages', { keyPath: 'client_msg_id' });
      }
    };
    req.onsuccess = e => res(e.target.result);
    req.onerror = e => rej(e);
  });
}

async function saveMessage(msg) { const db = await openDB(); return new Promise((res, rej) => { const tx = db.transaction('messages', 'readwrite'); tx.objectStore('messages').put(msg); tx.oncomplete = () => res(); tx.onerror = e => rej(e); }); }

async function getMessages(sessionId) { const db = await openDB(); return new Promise((res, rej) => { const tx = db.transaction('messages', 'readonly'); const store = tx.objectStore('messages'); const req = store.getAll(); req.onsuccess = e => res(e.target.result.filter(m => m.session_id === sessionId)); req.onerror = e => rej(e); }); }

和美洽 SDK 配合时的注意点(工程层面的建议)

虽然不同版本的美洽 SDK 在细节上会有差异,但普适的工程建议如下:

  • 查 SDK 文档找出“消息接收/发送回调”、“连接状态回调”、“消息 ID/序列号”这些接口点,把它们作为缓存逻辑的钩子;
  • 优先使用 SDK 提供的本地持久化能力(如果存在),因为它可能更健壮并兼容美洽服务;
  • 如果 SDK 不提供或你需要自定义策略,就在 SDK 之上做一层“消息持久化中间件”,不要改动 SDK 的低层通信;
  • 把 client_msg_id 作为消息可靠性设计的核心,能简化去重与幂等处理;
  • 与后端(或美洽客服后端)约定冲突解决策略:例如后端以时间戳或序列号为准,或以 server_message_id 为准。

常见坑与如何规避

  • 坑:重复消息——解决:一定使用 client_msg_id 去重,且在重连时不要盲目把本地记录全部覆盖;
  • 坑:序列错位导致错乱展示——解决:按时间戳 + 序号排序并展示“正在发送”状态,直到服务器确认;
  • 坑:本地空间耗尽——解决:限制保留条数/天数、清理策略与用户可控选项;
  • 坑:隐私泄露——解决:加密、合理 TTL、登出清理;
  • 坑:跨浏览器兼容——解决:在兼容性较差的环境(旧浏览器、隐私模式)回退到 localStorage 或只在有网时加载。

最终的工程实施 checklist(可复制粘贴去验收)

  • 是否为每条消息生成并存储 client_msg_id?
  • 是否把消息与附件持久化到 IndexedDB(或合理后备)?
  • 是否实现了未发送队列与重试机制?
  • 是否在网络状态变化时正确触发重连与同步?
  • 是否实现了去重策略与服务器冲突处理流程?
  • 是否做了删除/过期策略并遵守隐私规则?
  • 是否覆盖了多标签页/多设备的测试场景?

好了,写到这里我还在想有没有遗漏的边界条件:比如客服转接、机器人接入、群聊(如果支持)会带来更多同步复杂度,实际上一般做法是把这些额外的变更点也记录为“系统消息”,同样走本地持久化与同步流程。你如果需要,我可以把这套实现按你当前使用的美洽 SDK 版本再做一次具体代码改写,或者给出移动端(iOS/Android)的实现示例。就先到这儿,边写边想,难免有点散,但把关键点都列出来了。

最新文章

即刻美洽,拥抱 AI

90% 以上企业使用美洽后客户满意度提升30%以上的 AI Agent