09-09-netty消息搭建
This commit is contained in:
@ -252,6 +252,11 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -127,6 +127,7 @@ public class BusLandTransferLedgerServiceImpl extends ServiceImpl<BusLandTransfe
|
||||
String transferStatus = son.getTransferStatus();
|
||||
//0是未流转,1已流转,2是不流转
|
||||
switch (transferStatus) {
|
||||
|
||||
case "1":
|
||||
if (son.getAreaValue() != null) {
|
||||
transferArea = transferArea.add(son.getAreaValue());
|
||||
@ -138,17 +139,21 @@ public class BusLandTransferLedgerServiceImpl extends ServiceImpl<BusLandTransfe
|
||||
noTrans = noTrans.add(son.getAreaValue());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//土地租金(元)
|
||||
if (son.getLandRent() != null) {
|
||||
landRentAll = landRentAll.add(son.getLandRent());
|
||||
}
|
||||
|
||||
//青苗赔偿(元)
|
||||
if (son.getSeedlingCompensation()!=null) {
|
||||
seedlingCompensationAll = seedlingCompensationAll.add(son.getSeedlingCompensation());
|
||||
}
|
||||
|
||||
//总金额(元)
|
||||
if (son.getTotalAmount()!=null) {
|
||||
totalAmountAll = totalAmountAll.add(son.getTotalAmount());
|
||||
|
@ -0,0 +1,22 @@
|
||||
package org.dromara.websocket;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
@ -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<SocketChannel>() {
|
||||
@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();
|
||||
// }
|
||||
|
||||
}
|
@ -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<TextWebSocketFrame> {
|
||||
|
||||
// 移除 @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<String, List<ChannelHandlerContext>> userChannelMap = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<ChannelHandlerContext, String> channelUserMap = new ConcurrentHashMap<>();
|
||||
//维护用户房间未读数量的映射表 用户->RoomId RoomId->Count 用户+房间->Count
|
||||
private static final ConcurrentHashMap<String, Integer> userRoomCountMap = new ConcurrentHashMap<>();
|
||||
//维护一个在线用户列表
|
||||
private static final List<String> onlineUserList = new ArrayList<>();
|
||||
public static List<String> 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<ChannelHandlerContext> 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<Long> userIds = new ArrayList<>();
|
||||
//类型转换
|
||||
for (String s : onlineUserList) {
|
||||
userIds.add(Long.parseLong(s));
|
||||
}
|
||||
// List<UserDTO> userDTOS = sysUserService.selectListByIds(userIds);
|
||||
|
||||
//构建各个聊天房间未读 数量
|
||||
LambdaQueryWrapper<ChatGroup> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(ChatGroup::getMembers, loginUser.getUserId()+",").or().like(ChatGroup::getMembers,loginUser.getUserId()+"]");
|
||||
//拿到该用户的房间列表
|
||||
List<ChatGroup> chatGroups = chatGroupService.list(queryWrapper);
|
||||
if (chatGroups != null && !chatGroups.isEmpty()) {
|
||||
// List<HashMap<String, Object>> roomCounts = new ArrayList<>();
|
||||
HashMap<String, Object> roomCounts = new HashMap<>();
|
||||
for (ChatGroup chatGroup : chatGroups) {
|
||||
LambdaQueryWrapper<ChatHistory> queryWrapper1 = new LambdaQueryWrapper<>();
|
||||
queryWrapper1.eq(ChatHistory::getGeterId, chatGroup.getId());
|
||||
queryWrapper1.ne(ChatHistory::getSenderId, loginUser.getUserId());
|
||||
queryWrapper1.eq(ChatHistory::getIsRead, "1");
|
||||
List<ChatHistory> list = chatHistoryService.list(queryWrapper1);
|
||||
if (list != null && !list.isEmpty()) {
|
||||
// HashMap<String, Object> 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<ChannelHandlerContext> channelHandlerContexts = userChannelMap.get(userId);
|
||||
channelHandlerContexts.remove(ctx);
|
||||
//如果该账号下没有通道实例了
|
||||
if (channelHandlerContexts.isEmpty()) {
|
||||
//在缓存的列表里去掉在线ID 然后更新缓存
|
||||
onlineUserList.remove(userId);
|
||||
//删除该账号的通道实例列表
|
||||
userChannelMap.remove(userId);
|
||||
|
||||
//构建推送消息
|
||||
List<Long> userIds = new ArrayList<>();
|
||||
//类型转换
|
||||
for (String s : onlineUserList) {
|
||||
userIds.add(Long.parseLong(s));
|
||||
}
|
||||
List<UserDTO> 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<Long> 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<ChannelHandlerContext> 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<ChatHistory> 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<ChatHistory> 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){
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<Void> addFromDept(){
|
||||
Long userId = LoginHelper.getLoginUser().getUserId();
|
||||
Long deptId = LoginHelper.getLoginUser().getDeptId();
|
||||
LambdaQueryWrapper<ChatFriendship> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
//首先处理以前部门好友关系
|
||||
lambdaQueryWrapper.eq(ChatFriendship::getOurSide,userId);
|
||||
lambdaQueryWrapper.eq(ChatFriendship::getType,"0");
|
||||
lambdaQueryWrapper.ne(ChatFriendship::getFromDept,deptId);
|
||||
chatFriendshipService.remove(lambdaQueryWrapper);
|
||||
|
||||
//需要新增的关系列表
|
||||
List<ChatFriendship> chatFriendshipList = new ArrayList<>();
|
||||
//查询出同部门下的用户
|
||||
List<SysUserVo> 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<ChatFriendship> 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<Void> add(@RequestParam Long otherSide){
|
||||
Long userId = LoginHelper.getLoginUser().getUserId();
|
||||
|
||||
if (userId.equals(otherSide)){
|
||||
return R.fail("不能添加自己为好友");
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<ChatFriendship> 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<List<SysUserVo>> getList(){
|
||||
Long userId = LoginHelper.getLoginUser().getUserId();
|
||||
LambdaQueryWrapper<ChatFriendship> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaQueryWrapper.eq(ChatFriendship::getOurSide,userId);
|
||||
List<ChatFriendship> list = chatFriendshipService.list(lambdaQueryWrapper);
|
||||
|
||||
List<SysUserVo> sysUserVos = new ArrayList<>();
|
||||
for (ChatFriendship chatFriendship : list) {
|
||||
sysUserVos.add(sysUserService.selectUserById(chatFriendship.getOtherSide()));
|
||||
}
|
||||
return R.ok(sysUserVos);
|
||||
}
|
||||
|
||||
}
|
@ -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<ChatGroup> createChatGroup(@RequestParam String type, @RequestParam Long[] ids, @RequestParam(required = false) String name) {
|
||||
Long userId = LoginHelper.getLoginUser().getUserId();
|
||||
List<Long> idList = new ArrayList<>(Arrays.asList(ids));
|
||||
idList.add(userId);
|
||||
|
||||
//在点击聊天 新建房间之前 判断是否已经有该房间 没有则创建 有再判断是否是隐藏 修改状态更新
|
||||
boolean isHave = false;
|
||||
LambdaQueryWrapper<ChatGroup> 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<List<ChatGroup>> listChatGroup() {
|
||||
Long userId = LoginHelper.getLoginUser().getUserId();
|
||||
LambdaQueryWrapper<ChatGroup> queryWrapper = new LambdaQueryWrapper<>();
|
||||
//无论单群聊 群聊 成员都会有自己
|
||||
queryWrapper.like(ChatGroup::getMembers,userId+",").or().like(ChatGroup::getMembers,userId+"]");//.eq(ChatGroup::getOwerId,userId).or()
|
||||
//按最后聊天时间排序
|
||||
queryWrapper.orderByDesc(ChatGroup::getLastMessageTime);
|
||||
List<ChatGroup> list = chatGroupService.list(queryWrapper);
|
||||
for (ChatGroup chatGroup : list) {
|
||||
setValue(chatGroup,userId);
|
||||
}
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看某个房间的详情信息 传递房间ID
|
||||
*/
|
||||
// @SaCheckPermission("chatGroup:list:getInfo")
|
||||
@GetMapping("/getInfo")
|
||||
public R<ChatGroup> 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<List<ChatHistory>> groupChatRecord(@RequestParam Long id) {
|
||||
LambdaQueryWrapper<ChatHistory> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
// lambdaQueryWrapper.eq(ChatHistory::getGroupId,id).eq(ChatHistory::getType,"1");
|
||||
lambdaQueryWrapper.eq(ChatHistory::getGeterId, id);
|
||||
List<ChatHistory> 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<Long> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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<ChatFriendship> {
|
||||
|
||||
}
|
@ -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<ChatGroup> {
|
||||
}
|
@ -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<ChatHistory> {
|
||||
}
|
@ -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<ChatHistory> {
|
||||
}
|
@ -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<ChatFriendshipMapper, ChatFriendship> {
|
||||
|
||||
}
|
@ -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<ChatGroupMapper, ChatGroup> {
|
||||
}
|
@ -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<ChatHistoryMapper, ChatHistory> implements IChatHistoryService {
|
||||
|
||||
}
|
Reference in New Issue
Block a user