本文目录导读:

搭建一个客服系统是一个经典的企业级应用场景,涉及多渠道接入、实时通信(WebSocket)、智能路由、会话管理和工单系统。
下面我将为您提供一个 企业级 Java 客服系统 的完整架构设计方案、核心代码实现思路以及关键技术难点解析。
系统架构图(分层设计)
[ 客户端渠道 ]
(PC网页/APP/H5/小程序/微信公众号)
|
v
[ 接入层 (Gateway/API) ]
(鉴权、限流、协议转换: HTTP/HTTPS/WebSocket)
|
v
[ 核心业务层 (微服务/模块) ]
------------------------------------------------------------
| 会话服务 | 消息服务 | 路由分配 | 客服工作台 | 工单系统 |
| (Session)| (IM) | (ACD) | (Agent) | (Ticket) |
------------------------------------------------------------
| |
v v
[ 支撑中间件 ]
(Redis: 在线状态/Distributed Lock)
(RabbitMQ/Kafka: 消息异步解耦)
(Elasticsearch: 聊天记录搜索)
(MySQL: 持久化数据)
(MinIO/OSS: 文件/图片存储)
核心技术栈
| 技术维度 | 推荐框架/工具 | 说明 |
|---|---|---|
| 基础框架 | Spring Boot 3.x, Spring Cloud Alibaba | 微服务基础 |
| 实时通信 | Netty 或 Spring WebFlux | 处理高并发长连接,代替传统Tomcat处理WS |
| 消息队列 | RabbitMQ / Apache Kafka | 削峰填谷,消息异步处理,保证顺序性 |
| 缓存技术 | Redis (5.0+) | 存储会话、在线状态、未读数 |
| 搜索引擎 | Elasticsearch | 海量聊天记录检索、全文搜索 |
| 数据库 | MySQL (8.0) + MyBatis-Plus | 业务数据持久化 |
| 对象存储 | MinIO / 阿里云 OSS | 图片、文件消息存储 |
核心业务模块实现(含代码逻辑)
会话管理(Session)与路由分发
这是客服系统的核心,当访客接入时,系统需要根据客户等级(VIP)、客服空闲度等因素分配客服。
核心实体设计:
// 会话实体
public class ChatSession {
private String sessionId; // 会话ID (UUID)
private String userId; // 访客ID
private String agentId; // 客服ID (可为空,等待分配)
private Integer status; // 0:排队中 1:进行中 2:已结束
private Long createTime;
private Long acceptTime;
private Integer channelType; // 渠道 (1:WEB, 2:APP)
}
路由策略代码(简单版):
利用 Redis ZSet 存储客服的负载量,实现“最空闲优先分配”。
@Service
public class RouterService {
@Autowired
private StringRedisTemplate redisTemplate;
// 客服技能集合,"skill:售前"
public String allocateAgent(String skillGroup) {
// 1. 获取该技能组下所有在线的客服ID (从Redis Set获取)
String onlineAgentsKey = "online_agents:" + skillGroup;
Set<String> agents = redisTemplate.opsForSet().members(onlineAgentsKey);
if (agents == null || agents.isEmpty()) {
return null; // 无人接待,进入排队
}
// 2. 计算每个客服当前负载量 (使用ZSet存储会话数)
String loadKey = "agent_load";
// 找出在线的客服中,会话数最少的那个
// 这里简化为遍历或使用 ZRangeWithScores
String bestAgent = null;
double minScore = Double.MAX_VALUE;
for (String agent : agents) {
Double score = redisTemplate.opsForZSet().score(loadKey, agent);
double currentLoad = (score == null) ? 0 : score;
if (currentLoad < minScore) {
minScore = currentLoad;
bestAgent = agent;
}
}
return bestAgent;
}
}
实时消息推送(WebSocket)
关键点:如何做到定向推送?访客和客服建立WebSocket连接后,需要用 UserID 作为标识,注册到 连接池 中。
核心实现(基于 Spring WebSocket + Handler):
@Component
public class SocketHandler extends TextWebSocketHandler {
// 模拟连接池: userId -> WebSocketSession
private static final ConcurrentHashMap<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 1. 解析消息体
JSONObject msg = JSON.parseObject(message.getPayload());
String from = msg.getString("from");
String to = msg.getString("to");
String content = msg.getString("content");
// 2. 调用消息服务,保存消息并推送
messageService.handleAndPush(from, to, content);
}
// 推送方法(主动推给某个用户)
public static void sendToUser(String userId, String message) {
WebSocketSession session = SESSIONS.get(userId);
if (session != null && session.isOpen()) {
session.sendMessage(new TextMessage(message));
}
}
}
需要注意:为了支撑高并发,通常不直接使用内存的 ConcurrentHashMap,而是通过 Redis Pub/Sub 或 消息队列,这样即使客服分布在不同服务器节点,也能通过中间件将消息推送到正确的目标节点。
消息一致性与离线消息
为了保证消息不丢失,采用持久化 + 消息队列的补偿机制:
- 流程:客户端发送消息 -> 消息写入 MySQL -> 投递到 RabbitMQ -> 消费订阅推送至 WebSocket -> 客户端确认(ACK)。
- 离线消息:用户离线时,将消息存入 Redis,上线后拉取。
关键技术难点与解决方案
问题 1:高并发下的“惊群效应”
- 场景:客服端开启多个标签页,消息推送导致所有连接都尝试抢消息。
- 方案:强制用户ID维度单连接,或引入 Session 版本号,旧连接自动丢弃。
问题 2:消息顺序的一致性
- 场景:如果消息串行发送,采用 RabbitMQ 的单队列保证顺序;如果有多线程消费,容易乱序。
- 方案:采用 一致性哈希 对
sessionId进行分片,确保同一个会话的消息总是进入同一个队列(如Direct Exchange + RoutingKey = sessionId)。
问题 3:客服“小休”状态心跳监测
- 方案:客服端每 30 秒发送心跳包,后台利用 Netty 的 IdleStateHandler 检测空闲,如果客服长时间无响应,自动置为离线,并将当前会话转给其他客服。
数据库表设计(核心表)
-- 会话表 CREATE TABLE `session` ( `session_id` varchar(64) NOT NULL, `visitor_id` varchar(32) DEFAULT NULL, `agent_id` varchar(32) DEFAULT NULL, `status` tinyint(1) DEFAULT '0' COMMENT '0排队,1进行中,2关闭', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`session_id`), KEY `idx_agent` (`agent_id`) ); -- 消息表 CREATE TABLE `chat_message` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `session_id` varchar(64) NOT NULL, `from_user` varchar(32) NOT NULL COMMENT '发送人', `to_user` varchar(32) DEFAULT NULL COMMENT '接收人', `msg_type` tinyint(1) DEFAULT '1' COMMENT '1文本,2图片,3文件', `content` text, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_session_time` (`session_id`, `create_time`) ); -- 客服账号表 CREATE TABLE `agent` ( `agent_id` varchar(32) NOT NULL, `name` varchar(50) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `status` tinyint(1) DEFAULT '1' COMMENT '1在线,2离线,3忙碌', `max_sessions` int(11) DEFAULT '5' COMMENT '最大接待数', PRIMARY KEY (`agent_id`) );
项目启动与演示建议
如果是写简历项目或Demo,建议按以下顺序进行:
-
MVP 版本:使用
Spring Boot + WebSocket + JPA,实现“一对一单聊”功能(即用户和客服点对点聊天)。- Demo 界面:用 Vue 或 Thymeleaf 写简单的聊天框。
- 功能:登录、发送消息、接收消息、查看历史记录。
-
进阶功能(加分项):
- 接入 AI 智能回复(调用大模型接口,如通义千问或 ChatGPT),实现机器人先应答,转人工时需要按键。
- 引入 RabbitMQ,将消息发送与消息推送进行解耦。
-
简历描述话术:
- “基于 Netty/WebSocket 构建客服聊天系统,实现单机 5K 长连接,消息吞吐量提升 50%。”
- “设计了基于 Redis 的会话绑定与路由策略,解决了客服多开导致的重复接入问题。”
特别注意事项(避坑指南)
- 不要在
WebSocketHandler中直接进行数据库查询,每次握手/消息都会阻塞EventLoop,应该做好异步处理(CompletableFuture结合线程池)。 - 注意消息积压:客服忙的时候,用户消息需要进入缓冲队列,并提示“排队中”,这里可以使用
RabbitMQ的延迟队列或优先级队列。 - 安全方面:WebSocket 连接建立时,必须校验 JWT Token,防止未授权访问。
如果需要我提供一个具体模块的完整可运行源码(WebSocket 的 Netty 配置类),可以随时告诉我。