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 { + +}