From 261dd0b643c269e6c279e518b805256f53ea7ad8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B1=88=E5=B1=95=E8=88=AA?= <2426745133@qq.com>
Date: Tue, 9 Sep 2025 09:12:19 +0800
Subject: [PATCH] =?UTF-8?q?09-09-netty=E6=B6=88=E6=81=AF=E6=90=AD=E5=BB=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ruoyi-modules/ruoyi-system/pom.xml | 5 +
.../BusLandTransferLedgerServiceImpl.java | 5 +
.../dromara/websocket/ChatClientHandler.java | 22 ++
.../org/dromara/websocket/ChatServer.java | 98 +++++
.../dromara/websocket/ChatServerHandler.java | 338 ++++++++++++++++++
.../controller/ChatFriendshipController.java | 145 ++++++++
.../controller/ChatGroupController.java | 162 +++++++++
.../websocket/domain/ChatFriendship.java | 22 ++
.../dromara/websocket/domain/ChatGroup.java | 46 +++
.../dromara/websocket/domain/ChatHistory.java | 59 +++
.../mapper/ChatFriendshipMapper.java | 10 +
.../websocket/mapper/ChatGroupMapper.java | 9 +
.../websocket/mapper/ChatHistoryMapper.java | 9 +
.../service/IChatHistoryService.java | 7 +
.../Impl/ChatFriendshipServiceImpl.java | 11 +
.../service/Impl/ChatGroupServiceImpl.java | 10 +
.../service/Impl/ChatHistoryServiceImpl.java | 13 +
17 files changed, 971 insertions(+)
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatClientHandler.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServer.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServerHandler.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatFriendshipController.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatGroupController.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatFriendship.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatGroup.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatHistory.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatFriendshipMapper.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatGroupMapper.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatHistoryMapper.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/IChatHistoryService.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatFriendshipServiceImpl.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatGroupServiceImpl.java
create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatHistoryServiceImpl.java
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml b/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml
index 8b4e1935..28edad17 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml
@@ -252,6 +252,11 @@
compile
+
+ io.netty
+ netty-all
+
+
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/land/service/impl/BusLandTransferLedgerServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/land/service/impl/BusLandTransferLedgerServiceImpl.java
index a320c25b..af57e603 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/land/service/impl/BusLandTransferLedgerServiceImpl.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/land/service/impl/BusLandTransferLedgerServiceImpl.java
@@ -127,6 +127,7 @@ public class BusLandTransferLedgerServiceImpl extends ServiceImpl {
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
+ // 打印服务器发送的消息
+ log.info(msg);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ log.error("客户端发生异常:" + cause.getMessage());
+ ctx.close();
+ }
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServer.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServer.java
new file mode 100644
index 00000000..462d7a04
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServer.java
@@ -0,0 +1,98 @@
+package org.dromara.websocket;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Configuration
+public class ChatServer {
+ private final int port = 9099; // 聊天服务器端口
+
+ private EventLoopGroup bossGroup;
+ private EventLoopGroup workerGroup;
+
+
+// @Override
+// public void run(String... args) throws Exception {
+// // 使用新线程启动Netty服务器,避免阻塞Spring启动
+// new Thread(() -> {
+// try {
+// start();
+// } catch (Exception e) {
+// log.error("Netty服务器启动失败", e);
+// }
+// }, "NettyServer").start();
+// }
+
+ // 启动Netty服务器
+ @PostConstruct
+ public void start() throws Exception {
+ bossGroup = new NioEventLoopGroup();
+ workerGroup = new NioEventLoopGroup();
+
+ try {
+ ServerBootstrap bootstrap = new ServerBootstrap();
+ bootstrap.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .option(ChannelOption.SO_BACKLOG, 128)
+ .childOption(ChannelOption.SO_KEEPALIVE, true)
+ .childHandler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(SocketChannel ch) throws Exception {
+ // websocket协议本身是基于http协议的,所以这边也要使用http解编码器
+ ch.pipeline().addLast(new HttpServerCodec());
+ // 以块的方式来写的处理器
+ ch.pipeline().addLast(new ChunkedWriteHandler());
+ ch.pipeline().addLast(new HttpObjectAggregator(8192));
+ // 注意:WebSocketServerProtocolHandler 必须在 ChatServerHandler 之前添加
+ ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10,false,true));
+ ch.pipeline().addLast(new ChatServerHandler()); // 添加聊天消息处理类
+ }
+ });
+
+ log.info("Netty聊天服务器启动,端口:" + port);
+ ChannelFuture future = bootstrap.bind(port).sync();
+// future.channel().closeFuture().sync();
+ } finally {
+// workerGroup.shutdownGracefully().sync();
+// bossGroup.shutdownGracefully().sync();
+ }
+ }
+
+ // 关闭服务器时释放资源
+ @PreDestroy
+ public void destroy() {
+ if (bossGroup != null) {
+ bossGroup.shutdownGracefully();
+ }
+ if (workerGroup != null) {
+ workerGroup.shutdownGracefully();
+ }
+ }
+
+// @Bean
+// public ServerBootstrap serverBootstrap() {
+// // 确保在 pipeline 中添加了 WebSocketServerProtocolHandler
+// // 例如:
+// // pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null, true));
+// return new ServerBootstrap();
+// }
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServerHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServerHandler.java
new file mode 100644
index 00000000..a6c282af
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/ChatServerHandler.java
@@ -0,0 +1,338 @@
+package org.dromara.websocket;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.util.concurrent.GlobalEventExecutor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.dto.UserDTO;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.bo.SysUserBo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.impl.SysUserServiceImpl;
+import org.dromara.websocket.domain.ChatGroup;
+import org.dromara.websocket.domain.ChatHistory;
+import org.dromara.websocket.service.Impl.ChatGroupServiceImpl;
+import org.dromara.websocket.service.Impl.ChatHistoryServiceImpl;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@Component
+@ChannelHandler.Sharable
+public class ChatServerHandler extends SimpleChannelInboundHandler {
+
+ // 移除 @Autowired 注解
+ private static ChatHistoryServiceImpl chatHistoryService;
+ private static ChatGroupServiceImpl chatGroupService;
+ private static SysUserServiceImpl sysUserService;
+ @Autowired
+ public void setChatHistoryService(ChatHistoryServiceImpl service) {
+ chatHistoryService = service;
+ }
+ @Autowired
+ public void setChatGroupService(ChatGroupServiceImpl service){
+ chatGroupService = service;
+ }
+ @Autowired
+ public void setSysUserService(SysUserServiceImpl service){
+ sysUserService = service;
+ }
+
+ // 存储所有连接的客户端Channel
+ private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
+ //日期格式
+ private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ // 使用内存映射替代Redis存储
+ private static final ConcurrentHashMap> userChannelMap = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap channelUserMap = new ConcurrentHashMap<>();
+ //维护用户房间未读数量的映射表 用户->RoomId RoomId->Count 用户+房间->Count
+ private static final ConcurrentHashMap userRoomCountMap = new ConcurrentHashMap<>();
+ //维护一个在线用户列表
+ private static final List onlineUserList = new ArrayList<>();
+ public static List getOnlineUserList(){
+ return onlineUserList;
+ }
+
+
+ // 当有客户端连接时调用
+ @Override
+ public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+
+ }
+
+ // 处理用户认证事件
+ @Override
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+ if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
+ WebSocketServerProtocolHandler.HandshakeComplete handshake = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
+
+ // 从HTTP请求URI中提取token
+ String uri = handshake.requestUri();
+ String token = null;
+ if (uri.contains("Authorization=")) {
+ token = uri.split("Authorization=")[1].split("&")[0];
+ }
+
+ //建立双向映射关系
+ LoginUser loginUser = LoginHelper.getLoginUser(token.replace("Bearer%20", ""));
+ if (loginUser == null){
+ throw new RuntimeException("token获取信息失败");
+ }
+ //判断是否存在该账号的通道实例列表
+ userChannelMap.computeIfAbsent(loginUser.getUserId().toString(), k -> new ArrayList<>());
+ List channelHandlerContexts = userChannelMap.get(loginUser.getUserId().toString());
+ if (!channelHandlerContexts.contains( ctx)){
+ channelHandlerContexts.add(ctx);
+ }
+ //把该账号的通道实例列表跟账号id关联 一个账号有多个通道实例
+ userChannelMap.put(loginUser.getUserId().toString(), channelHandlerContexts);
+ //把通道实例跟账号id关联
+ channelUserMap.put(ctx, loginUser.getUserId().toString());
+
+ if (!onlineUserList.contains(loginUser.getUserId().toString())) {
+ onlineUserList.add(loginUser.getUserId().toString());
+ }
+
+ Channel channel = ctx.channel();
+ channelGroup.writeAndFlush(new TextWebSocketFrame("[系统消息] " + sdf.format(new Date()) + ":" + channel.remoteAddress() + " 上线\n"));
+ channelGroup.add(channel);
+
+ //构建推送消息
+ List userIds = new ArrayList<>();
+ //类型转换
+ for (String s : onlineUserList) {
+ userIds.add(Long.parseLong(s));
+ }
+// List userDTOS = sysUserService.selectListByIds(userIds);
+
+ //构建各个聊天房间未读 数量
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.like(ChatGroup::getMembers, loginUser.getUserId()+",").or().like(ChatGroup::getMembers,loginUser.getUserId()+"]");
+ //拿到该用户的房间列表
+ List chatGroups = chatGroupService.list(queryWrapper);
+ if (chatGroups != null && !chatGroups.isEmpty()) {
+// List> roomCounts = new ArrayList<>();
+ HashMap roomCounts = new HashMap<>();
+ for (ChatGroup chatGroup : chatGroups) {
+ LambdaQueryWrapper queryWrapper1 = new LambdaQueryWrapper<>();
+ queryWrapper1.eq(ChatHistory::getGeterId, chatGroup.getId());
+ queryWrapper1.ne(ChatHistory::getSenderId, loginUser.getUserId());
+ queryWrapper1.eq(ChatHistory::getIsRead, "1");
+ List list = chatHistoryService.list(queryWrapper1);
+ if (list != null && !list.isEmpty()) {
+// HashMap map = new HashMap<>();
+ roomCounts.put(chatGroup.getId().toString(), list.size());
+// roomCounts.add(map);
+ }
+ }
+
+
+ JSONObject message = new JSONObject();
+// message.put("type", "2");
+// message.put("onLineUser", userDTOS);
+ message.put("type", "3");
+ message.put("unReadCount", roomCounts);
+ log.info("发送所有未读消息:{}",message);
+// channelGroup.writeAndFlush(new TextWebSocketFrame(message.toJSONString()));
+
+ sendMessage(ctx, message.toJSONString());
+ }
+
+ }
+ super.userEventTriggered(ctx, evt);
+ }
+
+ // 当有客户端断开连接时调用
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ Channel channel = ctx.channel();
+ // 实际上ChannelGroup会自动移除断开连接的channel,这里只是演示
+ // 向所有已连接的客户端广播客户端离开的消息
+ channelGroup.writeAndFlush(new TextWebSocketFrame("[系统消息] " + sdf.format(new Date()) + ":" + channel.remoteAddress() + " 离开聊天\n"));
+ channelGroup.remove(channel);
+ log.info("{} 离开聊天,剩余在线人数:{}", channel.remoteAddress(), channelGroup.size());
+
+ //先找到该通道绑定的哪个账号
+ String userId = channelUserMap.get(ctx);
+ if (userId != null) {
+ //获取该账号下的通道列表 剔除元素
+ List channelHandlerContexts = userChannelMap.get(userId);
+ channelHandlerContexts.remove(ctx);
+ //如果该账号下没有通道实例了
+ if (channelHandlerContexts.isEmpty()) {
+ //在缓存的列表里去掉在线ID 然后更新缓存
+ onlineUserList.remove(userId);
+ //删除该账号的通道实例列表
+ userChannelMap.remove(userId);
+
+ //构建推送消息
+ List userIds = new ArrayList<>();
+ //类型转换
+ for (String s : onlineUserList) {
+ userIds.add(Long.parseLong(s));
+ }
+ List userDTOS = sysUserService.selectListByIds(userIds);
+
+ JSONObject message = new JSONObject();
+ message.put("type", "2");
+ message.put("users", userDTOS);
+ channelGroup.writeAndFlush(new TextWebSocketFrame(message.toJSONString()));
+ } else {
+ userChannelMap.put(userId, channelHandlerContexts);
+ }
+ //删除 通道-ID
+ channelUserMap.remove(ctx);
+
+ }
+
+ }
+
+ // 当通道就绪时调用
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ log.info("{} 上线了", ctx.channel().remoteAddress());
+ }
+
+ // 当通道不可用时调用
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ log.info(ctx.channel().remoteAddress() + " 下线了");
+ }
+
+ // 读取客户端发送的消息
+ @Transactional
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
+
+// {
+// "type":"0",
+// "from":"66666666",
+// "roomId": "1",
+// "message": "testing"
+// }
+ //0聊天推送的正常消息 1前端主动发送消息以确认消息收到 3发送离线后未读消息列表
+ //前端判断当前聊天框跟消息接收ID一致后,收到消息后,主动发送消息给服务端,确认该消息收到
+ //当发送消息后,给接收方推送未读消息
+
+ JSONObject jsonObject = JSONObject.parseObject(msg.text());
+ String type = jsonObject.get("type").toString();
+
+ if ("0".equals(type)) {
+ //来自哪个用户
+ jsonObject.put("from", channelUserMap.get(ctx));
+ log.info("收到客户端消息:{}", jsonObject);
+ String RoomId = jsonObject.get("roomId").toString();
+ //根据ID拿到房间实例
+ ChatGroup byId = chatGroupService.getById(RoomId);
+ //通过RoomId拿到该房间的所有成员
+ List ids = JSONObject.parseArray(byId.getMembers(), Long.class);
+ if (ids != null && !ids.isEmpty()) {
+ //要从IDS中去掉自己ID防止发送自己消息
+ ids.remove(Long.valueOf(channelUserMap.get(ctx)));
+ for (Long id : ids) {
+ //通过每个用户ID拿到该用户所有通道实例
+ List channelHandlerContexts = userChannelMap.get(id.toString());
+ //如果满足则说明用户在线
+ if (channelHandlerContexts != null && !channelHandlerContexts.isEmpty()) {
+ //只要发送一条数据,就要给接收方推送所有未读消息
+ if (!userRoomCountMap.containsKey(id + "+" + RoomId)) {
+ userRoomCountMap.put(id + "+" + RoomId, 1);
+ }else {
+ userRoomCountMap.put(id + "+" + RoomId, userRoomCountMap.get(id + "+" + RoomId) + 1);
+ }
+ //所有房间的未读消息数
+ jsonObject.put("countValue", JsonUtils.toJsonString(userRoomCountMap));
+ //给每个通道发送对应消息
+ for (ChannelHandlerContext handlerContext : channelHandlerContexts) {
+ sendMessage(handlerContext, jsonObject.toString());
+ log.info("发送消息{}", jsonObject);
+ }
+ }
+ //发送消息完成后添加聊天记录
+ ChatHistory chatHistory = new ChatHistory();
+ chatHistory.setMessageDate(new Date());
+ chatHistory.setGeterId(Long.valueOf(RoomId));
+ chatHistory.setSenderId(Long.valueOf(channelUserMap.get(ctx)));
+ chatHistory.setIsRead("1");
+ chatHistory.setMessage(String.valueOf(jsonObject));
+ chatHistoryService.save(chatHistory);
+
+ //将房间最后消息及时间存储
+ byId.setLastMessage(jsonObject.get("message").toString());
+ byId.setLastMessageTime(new Date());
+ chatGroupService.updateById(byId);
+ }
+
+ }
+ }else if ("1".equals(type)){
+ log.info("收到客户端确认消息:{}", jsonObject);
+ //将数据库中该消息的已读状态改为已读
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ lambdaQueryWrapper
+ .eq(ChatHistory::getSenderId,jsonObject.get("from"))
+ .eq(ChatHistory::getGeterId,jsonObject.get("roomId"))
+ .eq(ChatHistory::getMessage,jsonObject.get("message"))
+ .eq(ChatHistory::getIsRead,"1");
+ List list = chatHistoryService.list(lambdaQueryWrapper);
+ if (list != null && !list.isEmpty()){
+ for (ChatHistory chatHistory : list) {
+ chatHistory.setIsRead("0");
+ }
+ }
+ chatHistoryService.updateBatchById( list);
+
+ //将未读消息数减一
+ if(userRoomCountMap.get(channelUserMap.get(ctx) + "+" + jsonObject.get("roomId")) > 0) {
+ userRoomCountMap.put(channelUserMap.get(ctx) + "+" + jsonObject.get("roomId"), userRoomCountMap.get(channelUserMap.get(ctx) + "+" + jsonObject.get("roomId")) - 1);
+ }else {
+ userRoomCountMap.put(channelUserMap.get(ctx) + "+" + jsonObject.get("roomId"), 0);
+ }
+ }
+ }
+
+ // 处理异常
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ log.error("发生异常:{}", cause.getMessage());
+// this.handlerRemoved( ctx);
+ ctx.close(); // 关闭通道
+ }
+
+ //给固定的人发消息
+ private void sendMessage(ChannelHandlerContext ctx,String message) {
+
+ ctx.channel().writeAndFlush(new TextWebSocketFrame(message));
+ }
+ //发送群消息,此时其他客户端也能收到群消息
+ private void sendAllMessage(){
+ String message = "我是服务器,这里发送的是群消息";
+ channelGroup.writeAndFlush( new TextWebSocketFrame(message));
+ }
+
+
+ //通过userId进行发送消息
+ private void sendMessageByUserId(Long userId,String message){
+
+ }
+
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatFriendshipController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatFriendshipController.java
new file mode 100644
index 00000000..b4c5b0de
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatFriendshipController.java
@@ -0,0 +1,145 @@
+package org.dromara.websocket.controller;
+
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.impl.SysUserServiceImpl;
+import org.dromara.websocket.domain.ChatFriendship;
+import org.dromara.websocket.service.Impl.ChatFriendshipServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController
+@RequestMapping("/chatFriendship")
+public class ChatFriendshipController {
+
+ @Autowired
+ private ChatFriendshipServiceImpl chatFriendshipService;
+ @Autowired
+ private SysUserServiceImpl sysUserService;
+
+ /**
+ * 同步部门下的所有用户 每次进入都可调用 判断出没有的好友关系进行同步
+ */
+// @SaCheckPermission("chatGroup:chatFriendship:addFromDept")
+ @GetMapping("/addFromDept")
+ @Transactional
+ public R addFromDept(){
+ Long userId = LoginHelper.getLoginUser().getUserId();
+ Long deptId = LoginHelper.getLoginUser().getDeptId();
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+
+ //首先处理以前部门好友关系
+ lambdaQueryWrapper.eq(ChatFriendship::getOurSide,userId);
+ lambdaQueryWrapper.eq(ChatFriendship::getType,"0");
+ lambdaQueryWrapper.ne(ChatFriendship::getFromDept,deptId);
+ chatFriendshipService.remove(lambdaQueryWrapper);
+
+ //需要新增的关系列表
+ List chatFriendshipList = new ArrayList<>();
+ //查询出同部门下的用户
+ List sysUserVos = sysUserService.selectUserListByDept(deptId);
+ for (SysUserVo sysUserVo : sysUserVos) {
+ if (sysUserVo.getUserId().equals(userId)){
+ sysUserVos.remove(sysUserVo);
+ break;
+ }
+ }
+ //查询出自己的所有好友关系
+ lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ lambdaQueryWrapper.eq(ChatFriendship::getOurSide,userId);
+ lambdaQueryWrapper.eq(ChatFriendship::getType,"0");
+ List chatFriendships = chatFriendshipService.list(lambdaQueryWrapper);
+ for (SysUserVo sysUserVo : sysUserVos) {
+ boolean isFind = false;
+ for (ChatFriendship chatFriendship : chatFriendships) {
+ if (chatFriendship.getOtherSide().equals(sysUserVo.getUserId()) && chatFriendship.getOurSide().equals(userId)){
+ chatFriendships.remove(chatFriendship);
+ isFind = true;
+ break;
+ }
+ }
+ if (!isFind){
+ ChatFriendship chatFriendship = new ChatFriendship();
+ chatFriendship.setOtherSide(sysUserVo.getUserId());
+ chatFriendship.setOurSide(userId);
+ chatFriendship.setType("0");
+ chatFriendship.setFromDept(deptId);
+ chatFriendshipList.add(chatFriendship);
+ }
+ }
+ if (!chatFriendshipList.isEmpty()){
+ boolean b = chatFriendshipService.saveBatch(chatFriendshipList);
+ if (b){
+ return R.ok();
+ }else {
+ return R.fail("同步失败");
+ }
+ }else {
+ return R.ok("无新增关系");
+ }
+ }
+
+ /**
+ * 添加好友关系
+ */
+// @SaCheckPermission("chatGroup:chatFriendship:add")
+ @Transactional
+ @GetMapping("/add")
+ public R add(@RequestParam Long otherSide){
+ Long userId = LoginHelper.getLoginUser().getUserId();
+
+ if (userId.equals(otherSide)){
+ return R.fail("不能添加自己为好友");
+ }
+
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ lambdaQueryWrapper.eq(ChatFriendship::getOtherSide,otherSide);
+ lambdaQueryWrapper.eq(ChatFriendship::getOurSide,userId);
+ ChatFriendship one = chatFriendshipService.getOne(lambdaQueryWrapper);
+ if (one != null){
+ return R.fail("已添加");
+ }
+
+ ChatFriendship chatFriendship = new ChatFriendship();
+ chatFriendship.setOtherSide(otherSide);
+ chatFriendship.setOurSide(userId);
+ chatFriendship.setType("1");
+ boolean b = chatFriendshipService.save(chatFriendship);
+ if (b){
+ return R.ok("添加成功");
+ }else {
+ return R.fail();
+ }
+ }
+
+
+ /**
+ * 获取好友列表
+ */
+// @SaCheckPermission("chatGroup:chatFriendship:getList")
+ @GetMapping("/getList")
+ public R> getList(){
+ Long userId = LoginHelper.getLoginUser().getUserId();
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ lambdaQueryWrapper.eq(ChatFriendship::getOurSide,userId);
+ List list = chatFriendshipService.list(lambdaQueryWrapper);
+
+ List sysUserVos = new ArrayList<>();
+ for (ChatFriendship chatFriendship : list) {
+ sysUserVos.add(sysUserService.selectUserById(chatFriendship.getOtherSide()));
+ }
+ return R.ok(sysUserVos);
+ }
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatGroupController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatGroupController.java
new file mode 100644
index 00000000..39e17fb0
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/controller/ChatGroupController.java
@@ -0,0 +1,162 @@
+package org.dromara.websocket.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.impl.SysUserServiceImpl;
+import org.dromara.websocket.domain.ChatGroup;
+import org.dromara.websocket.domain.ChatHistory;
+import org.dromara.websocket.service.Impl.ChatGroupServiceImpl;
+import org.dromara.websocket.service.Impl.ChatHistoryServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping("/chatGroup")
+public class ChatGroupController {
+
+ @Autowired
+ private ChatGroupServiceImpl chatGroupService;
+ @Autowired
+ private ChatHistoryServiceImpl chatHistoryService;
+ @Autowired
+ private SysUserServiceImpl sysUserService;
+
+ /***
+ * 新建聊天房间 不论单群
+ */
+// @SaCheckPermission("chatGroup:create:add")
+ @GetMapping("/create")
+ public R createChatGroup(@RequestParam String type, @RequestParam Long[] ids, @RequestParam(required = false) String name) {
+ Long userId = LoginHelper.getLoginUser().getUserId();
+ List idList = new ArrayList<>(Arrays.asList(ids));
+ idList.add(userId);
+
+ //在点击聊天 新建房间之前 判断是否已经有该房间 没有则创建 有再判断是否是隐藏 修改状态更新
+ boolean isHave = false;
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ if (type.equals("0")){
+ //单聊
+ queryWrapper.eq(ChatGroup::getType,"0").eq(ChatGroup::getMembers,idList.toString());
+ ChatGroup one = chatGroupService.getOne(queryWrapper);
+ if (one!=null){
+ return R.ok(one);
+ }
+ }else if (type.equals("1")){
+ //群聊
+ queryWrapper.clear();
+ queryWrapper.eq(ChatGroup::getType,"1").eq(ChatGroup::getMembers,idList.toString());
+ queryWrapper.eq(ChatGroup::getOwerId,userId);
+ ChatGroup one = chatGroupService.getOne(queryWrapper);
+ if (one!=null){
+ return R.ok(one);
+ }
+ }
+
+
+ ChatGroup chatGroup = new ChatGroup();
+ chatGroup.setMembers(idList.toString());
+ if (type.equals("1")){
+ if (name == null){
+ return R.fail("群聊名称不能为空");
+ }
+ chatGroup.setOwerId(userId);
+ chatGroup.setName(name);
+ }else {
+ chatGroup.setName(null);
+ chatGroup.setOwerId(null);
+ }
+
+ boolean save = chatGroupService.save(chatGroup);
+ if (save) {
+ return R.ok(chatGroup);
+ }else {
+ return R.fail("创建群聊关系失败");
+ }
+ }
+
+
+ /***
+ * 查看与自己有关聊天房间 不论单群
+ */
+// @SaCheckPermission("chatGroup:list:getList")
+ @GetMapping("/list")
+ public R> listChatGroup() {
+ Long userId = LoginHelper.getLoginUser().getUserId();
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ //无论单群聊 群聊 成员都会有自己
+ queryWrapper.like(ChatGroup::getMembers,userId+",").or().like(ChatGroup::getMembers,userId+"]");//.eq(ChatGroup::getOwerId,userId).or()
+ //按最后聊天时间排序
+ queryWrapper.orderByDesc(ChatGroup::getLastMessageTime);
+ List list = chatGroupService.list(queryWrapper);
+ for (ChatGroup chatGroup : list) {
+ setValue(chatGroup,userId);
+ }
+ return R.ok(list);
+ }
+
+ /**
+ * 查看某个房间的详情信息 传递房间ID
+ */
+// @SaCheckPermission("chatGroup:list:getInfo")
+ @GetMapping("/getInfo")
+ public R getInfo(@RequestParam Long id){
+ Long userId = LoginHelper.getLoginUser().getUserId();
+
+ ChatGroup byId = chatGroupService.getById(id);
+ setValue(byId,userId);
+
+ return R.ok(byId);
+ }
+
+ /***
+ * 获取房间聊天记录 传递房间ID 获取发送给该房间的所有聊天记录
+ */
+// @SaCheckPermission("chatGroup:list:getHistory")
+ @GetMapping("/groupChatRecord")
+ public R> groupChatRecord(@RequestParam Long id) {
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+// lambdaQueryWrapper.eq(ChatHistory::getGroupId,id).eq(ChatHistory::getType,"1");
+ lambdaQueryWrapper.eq(ChatHistory::getGeterId, id);
+ List list = chatHistoryService.list(lambdaQueryWrapper);
+ for (ChatHistory chatHistory : list) {
+ SysUserVo sysUserVo = sysUserService.selectUserById(chatHistory.getSenderId());
+ if (sysUserVo != null){
+ chatHistory.setAvatar(sysUserVo.getAvatar());
+ chatHistory.setNickName(sysUserVo.getNickName());
+ }
+ }
+ return R.ok(chatHistoryService.list(lambdaQueryWrapper));
+ }
+
+ /**
+ * 将房间进行返回前的处理
+ */
+ private void setValue(ChatGroup byId, Long userId){
+ if (byId.getType().equals("0")){
+ //单聊 要获取对方头像 对方的名称作为聊天昵称
+ String members = byId.getMembers();
+ List list = JSONObject.parseArray(members, Long.class);
+ list.remove(userId);
+ //如果单聊 则集合只剩有一人
+ SysUserVo sysUserVo = sysUserService.selectUserById(list.get(0));
+ byId.setName(sysUserVo.getNickName());
+ byId.setAvatar(sysUserVo.getAvatar());
+ }else {
+ //群聊 则只需要将群主头像赋值给群聊头像
+ SysUserVo sysUserVo = sysUserService.selectUserById(byId.getOwerId());
+ byId.setAvatar(sysUserVo.getAvatar());
+ }
+ }
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatFriendship.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatFriendship.java
new file mode 100644
index 00000000..e8d67fed
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatFriendship.java
@@ -0,0 +1,22 @@
+package org.dromara.websocket.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("chat_friendship")
+public class ChatFriendship {
+
+ @TableId("id")
+ private Long id;
+
+ private Long ourSide;
+
+ private Long otherSide;
+
+ private Long fromDept;
+
+ private String type;
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatGroup.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatGroup.java
new file mode 100644
index 00000000..7908d4b6
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatGroup.java
@@ -0,0 +1,46 @@
+package org.dromara.websocket.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+@TableName("chat_group")
+@Data
+public class ChatGroup implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id")
+ private Long id;
+
+ private String type;
+
+ private String name;
+
+ private Long owerId;
+
+ private String members;
+
+ private String lastMessage;
+
+ private Date lastMessageTime;
+
+ @TableField(exist = false)
+ @Translation(type = TransConstant.OSS_ID_TO_URL)
+ private Long avatar;
+
+ /**
+ * 是否显示0显示1隐藏
+ */
+ private String isShowOut;
+
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatHistory.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatHistory.java
new file mode 100644
index 00000000..c302f8b8
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/domain/ChatHistory.java
@@ -0,0 +1,59 @@
+package org.dromara.websocket.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+
+import java.io.Serial;
+import java.util.Date;
+
+@Data
+@TableName("chat_history")
+public class ChatHistory {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id")
+ private Long id;
+
+ /***
+ * 0私聊1群聊
+ */
+// private String type;
+
+// private Long groupId;
+
+ private Long senderId;
+
+ /***
+ * 接收房间id
+ */
+ private Long geterId;
+
+ private String message;
+
+ private Date messageDate;
+
+ /**
+ * 发送人头像
+ */
+ @TableField(exist = false)
+ @Translation(type = TransConstant.OSS_ID_TO_URL)
+ private Long avatar;
+
+ /**
+ * 发送人昵称
+ */
+ @TableField(exist = false)
+ private String nickName;
+
+ /***
+ * 0读1未
+ */
+ private String isRead;
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatFriendshipMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatFriendshipMapper.java
new file mode 100644
index 00000000..74f0349a
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatFriendshipMapper.java
@@ -0,0 +1,10 @@
+package org.dromara.websocket.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.dromara.websocket.domain.ChatFriendship;
+
+@Mapper
+public interface ChatFriendshipMapper extends BaseMapper {
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatGroupMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatGroupMapper.java
new file mode 100644
index 00000000..535d58de
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatGroupMapper.java
@@ -0,0 +1,9 @@
+package org.dromara.websocket.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.dromara.websocket.domain.ChatGroup;
+
+@Mapper
+public interface ChatGroupMapper extends BaseMapper {
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatHistoryMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatHistoryMapper.java
new file mode 100644
index 00000000..43225e2a
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/mapper/ChatHistoryMapper.java
@@ -0,0 +1,9 @@
+package org.dromara.websocket.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.dromara.websocket.domain.ChatHistory;
+
+@Mapper
+public interface ChatHistoryMapper extends BaseMapper {
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/IChatHistoryService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/IChatHistoryService.java
new file mode 100644
index 00000000..84303e37
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/IChatHistoryService.java
@@ -0,0 +1,7 @@
+package org.dromara.websocket.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.websocket.domain.ChatHistory;
+
+public interface IChatHistoryService extends IService {
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatFriendshipServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatFriendshipServiceImpl.java
new file mode 100644
index 00000000..4b97c7ad
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatFriendshipServiceImpl.java
@@ -0,0 +1,11 @@
+package org.dromara.websocket.service.Impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.websocket.domain.ChatFriendship;
+import org.dromara.websocket.mapper.ChatFriendshipMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ChatFriendshipServiceImpl extends ServiceImpl {
+
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatGroupServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatGroupServiceImpl.java
new file mode 100644
index 00000000..4ad9b201
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatGroupServiceImpl.java
@@ -0,0 +1,10 @@
+package org.dromara.websocket.service.Impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.websocket.domain.ChatGroup;
+import org.dromara.websocket.mapper.ChatGroupMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ChatGroupServiceImpl extends ServiceImpl {
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatHistoryServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatHistoryServiceImpl.java
new file mode 100644
index 00000000..27085fe7
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/service/Impl/ChatHistoryServiceImpl.java
@@ -0,0 +1,13 @@
+package org.dromara.websocket.service.Impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.tender.service.ITenderSupplierInputService;
+import org.dromara.websocket.domain.ChatHistory;
+import org.dromara.websocket.mapper.ChatHistoryMapper;
+import org.dromara.websocket.service.IChatHistoryService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ChatHistoryServiceImpl extends ServiceImpl implements IChatHistoryService {
+
+}