09-09-netty消息搭建

This commit is contained in:
2025-09-09 09:12:19 +08:00
parent 8a18223d06
commit 261dd0b643
17 changed files with 971 additions and 0 deletions

View File

@ -252,6 +252,11 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -127,6 +127,7 @@ public class BusLandTransferLedgerServiceImpl extends ServiceImpl<BusLandTransfe
String transferStatus = son.getTransferStatus(); String transferStatus = son.getTransferStatus();
//0是未流转1已流转2是不流转 //0是未流转1已流转2是不流转
switch (transferStatus) { switch (transferStatus) {
case "1": case "1":
if (son.getAreaValue() != null) { if (son.getAreaValue() != null) {
transferArea = transferArea.add(son.getAreaValue()); transferArea = transferArea.add(son.getAreaValue());
@ -138,17 +139,21 @@ public class BusLandTransferLedgerServiceImpl extends ServiceImpl<BusLandTransfe
noTrans = noTrans.add(son.getAreaValue()); noTrans = noTrans.add(son.getAreaValue());
} }
break; break;
default: default:
break; break;
} }
//土地租金(元) //土地租金(元)
if (son.getLandRent() != null) { if (son.getLandRent() != null) {
landRentAll = landRentAll.add(son.getLandRent()); landRentAll = landRentAll.add(son.getLandRent());
} }
//青苗赔偿(元) //青苗赔偿(元)
if (son.getSeedlingCompensation()!=null) { if (son.getSeedlingCompensation()!=null) {
seedlingCompensationAll = seedlingCompensationAll.add(son.getSeedlingCompensation()); seedlingCompensationAll = seedlingCompensationAll.add(son.getSeedlingCompensation());
} }
//总金额(元) //总金额(元)
if (son.getTotalAmount()!=null) { if (son.getTotalAmount()!=null) {
totalAmountAll = totalAmountAll.add(son.getTotalAmount()); totalAmountAll = totalAmountAll.add(son.getTotalAmount());

View File

@ -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();
}
}

View File

@ -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();
// }
}

View File

@ -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){
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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