Java客服系统案例

wen java案例 3

本文目录导读:

Java客服系统案例

  1. 系统架构图(分层设计)
  2. 核心技术栈
  3. 核心业务模块实现(含代码逻辑)
  4. 关键技术难点与解决方案
  5. 数据库表设计(核心表)
  6. 项目启动与演示建议
  7. 特别注意事项(避坑指南)

搭建一个客服系统是一个经典的企业级应用场景,涉及多渠道接入实时通信(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,建议按以下顺序进行:

  1. MVP 版本:使用 Spring Boot + WebSocket + JPA,实现“一对一单聊”功能(即用户和客服点对点聊天)。

    • Demo 界面:用 Vue 或 Thymeleaf 写简单的聊天框。
    • 功能:登录、发送消息、接收消息、查看历史记录。
  2. 进阶功能(加分项):

    • 接入 AI 智能回复(调用大模型接口,如通义千问或 ChatGPT),实现机器人先应答,转人工时需要按键。
    • 引入 RabbitMQ,将消息发送与消息推送进行解耦。
  3. 简历描述话术

    • “基于 Netty/WebSocket 构建客服聊天系统,实现单机 5K 长连接,消息吞吐量提升 50%。”
    • “设计了基于 Redis 的会话绑定与路由策略,解决了客服多开导致的重复接入问题。”

特别注意事项(避坑指南)

  • 不要WebSocketHandler 中直接进行数据库查询,每次握手/消息都会阻塞EventLoop,应该做好异步处理(CompletableFuture 结合线程池)。
  • 注意消息积压:客服忙的时候,用户消息需要进入缓冲队列,并提示“排队中”,这里可以使用 RabbitMQ 的延迟队列或优先级队列。
  • 安全方面:WebSocket 连接建立时,必须校验 JWT Token,防止未授权访问。

如果需要我提供一个具体模块的完整可运行源码(WebSocket 的 Netty 配置类),可以随时告诉我。

抱歉,评论功能暂时关闭!