u
This commit is contained in:
1 parent
00b21b6b44
commit
6fd3bf5138
41 files changed
+1183
-98
No files matched your search
@@ -65,13 +65,13 @@ public class NettyServer {
|
||||
pipeline.addLast(new CorsHandler(CorsConfig.withAnyOrigin().build()));
|
||||
pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath(), null, true, 65536));
|
||||
// 4. 空闲检测(60 秒没收到客户端任何消息就断)
|
||||
pipeline.addLast("idle-handler",
|
||||
new IdleStateHandler(
|
||||
60,
|
||||
0,
|
||||
0,
|
||||
TimeUnit.SECONDS
|
||||
));
|
||||
// pipeline.addLast("idle-handler",
|
||||
// new IdleStateHandler(
|
||||
// 60,
|
||||
// 0,
|
||||
// 0,
|
||||
// TimeUnit.SECONDS
|
||||
// ));
|
||||
pipeline.addLast(serverHandler);
|
||||
pipeline.addLast(authHandler);
|
||||
pipeline.addLast(dispatchHandler);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.link56.im.controller;
|
||||
|
||||
import cn.link56.common.BasePageVO;
|
||||
import cn.link56.common.Result;
|
||||
import cn.link56.common.context.UserContext;
|
||||
import cn.link56.im.model.dto.CreateFriendDTO;
|
||||
import cn.link56.im.model.dto.QueryFriendDTO;
|
||||
import cn.link56.im.model.dto.QueryGroupDTO;
|
||||
import cn.link56.im.model.vo.QueryFriendVO;
|
||||
import cn.link56.im.model.vo.QueryGroupVO;
|
||||
import cn.link56.im.service.ImFriendService;
|
||||
import cn.link56.im.service.ImGroupService;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/im")
|
||||
public class ImController {
|
||||
@Resource
|
||||
private ImFriendService imFriendService;
|
||||
@Resource
|
||||
private ImGroupService imGroupService;
|
||||
|
||||
@PostMapping("/friend/create")
|
||||
public Result<Void> createFriend(@RequestBody @Validated CreateFriendDTO dto){
|
||||
Long friendId = dto.getFriendId();
|
||||
Long userId = UserContext.getUserId();
|
||||
|
||||
if (!Objects.equals(friendId, userId)){
|
||||
imFriendService.createFriend(userId,friendId);
|
||||
}
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@PostMapping("/friend/query")
|
||||
public Result<BasePageVO<QueryFriendVO>> queryFriends(@RequestBody QueryFriendDTO dto){
|
||||
BasePageVO<QueryFriendVO> basePageVO = imFriendService.queryFriend(dto);
|
||||
return Result.success(basePageVO);
|
||||
}
|
||||
|
||||
@PostMapping("/group/query")
|
||||
public Result<BasePageVO<QueryGroupVO>> queryFriends(@RequestBody QueryGroupDTO dto){
|
||||
BasePageVO<QueryGroupVO> basePageVO = imGroupService.queryGroup(dto);
|
||||
return Result.success(basePageVO);
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,11 @@ public enum MessageTypeEnums {
|
||||
LOGIN(2),
|
||||
LOGOUT(3),
|
||||
ONLINE(4),
|
||||
OFFLINE(5),
|
||||
PRIVATE(6),
|
||||
GROUP(7);
|
||||
GROUP_ONLINE(5),
|
||||
OFFLINE(6),
|
||||
GROUP_OFFLINE(7),
|
||||
PRIVATE(8),
|
||||
GROUP(9);
|
||||
|
||||
private final int code;
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package cn.link56.im.handler;
|
||||
|
||||
import cn.link56.auth.token.model.TokenPayload;
|
||||
import cn.link56.auth.token.service.AuthTokenService;
|
||||
import cn.link56.im.enums.MessageTypeEnums;
|
||||
import cn.link56.im.message.MessageRequest;
|
||||
import cn.link56.im.model.dto.OnlineDTO;
|
||||
import cn.link56.im.storage.SessionManager;
|
||||
import com.alibaba.druid.util.StringUtils;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
@@ -33,13 +30,14 @@ public class AuthHandler extends ChannelInboundHandlerAdapter {
|
||||
MessageRequest req = (MessageRequest) msg;
|
||||
Integer type = req.getType();
|
||||
|
||||
if (type == MessageTypeEnums.PING.getCode() || type == MessageTypeEnums.ONLINE.getCode()){
|
||||
if (type == MessageTypeEnums.PING.getCode() || type == MessageTypeEnums.LOGIN.getCode()){
|
||||
ctx.fireChannelRead(req);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果该通道未验证
|
||||
if (!SessionManager.isAuthenticated(ctx.channel())) {
|
||||
// 不断开连接
|
||||
ctx.close();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,33 +5,39 @@ import cn.link56.auth.token.service.AuthTokenService;
|
||||
import cn.link56.im.enums.MessageTypeEnums;
|
||||
import cn.link56.im.message.MessageRequest;
|
||||
import cn.link56.im.message.MessageResponse;
|
||||
import cn.link56.im.model.dto.OnlineDTO;
|
||||
import cn.link56.im.model.dto.LoginDTO;
|
||||
import cn.link56.im.service.ImFriendService;
|
||||
import cn.link56.im.service.ImGroupUserService;
|
||||
import cn.link56.im.storage.SessionManager;
|
||||
import cn.link56.im.utils.MessageDataConverter;
|
||||
import cn.link56.im.utils.MessageWriter;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class MessageHandler {
|
||||
@Resource
|
||||
private AuthTokenService authTokenService;
|
||||
@Resource
|
||||
private ImFriendService imFriendService;
|
||||
@Resource
|
||||
private ImGroupUserService imGroupUserService;
|
||||
|
||||
// 处理消息
|
||||
public void handleMessage(Channel channel, MessageRequest request) {
|
||||
System.out.println(request);
|
||||
switch (MessageTypeEnums.valueOf(request.getType())) {
|
||||
case PING:
|
||||
handlePing(channel, request);
|
||||
break;
|
||||
case ONLINE:
|
||||
handleOnline(channel, request);
|
||||
case LOGIN:
|
||||
handleLogin(channel,request);
|
||||
break;
|
||||
case OFFLINE:
|
||||
handleOffline(channel, request);
|
||||
case LOGOUT:
|
||||
handleLogout(channel,request);
|
||||
break;
|
||||
case PRIVATE:
|
||||
handlePrivate(channel, request);
|
||||
@@ -49,9 +55,9 @@ public class MessageHandler {
|
||||
MessageWriter.writeToChannel(channel, MessageResponse.success(MessageTypeEnums.PONG));
|
||||
}
|
||||
|
||||
// 上线
|
||||
private void handleOnline(Channel channel, MessageRequest request) {
|
||||
OnlineDTO dto = MessageDataConverter.convert(request.getData(), OnlineDTO.class);
|
||||
private void handleLogin(Channel channel, MessageRequest request) {
|
||||
System.out.println("===LOGIN===");
|
||||
LoginDTO dto = MessageDataConverter.convert(request.getData(), LoginDTO.class);
|
||||
TokenPayload tokenPayload = authTokenService.parsePayload(dto.getToken());
|
||||
|
||||
if (tokenPayload == null) {
|
||||
@@ -65,11 +71,41 @@ public class MessageHandler {
|
||||
// 添加 Session 并自动标记已认证
|
||||
SessionManager.addSession(companyId, userId, channel);
|
||||
|
||||
MessageWriter.writeToChannel(channel, MessageResponse.success(MessageTypeEnums.ONLINE));
|
||||
MessageWriter.writeToChannel(channel, MessageResponse.success(MessageTypeEnums.LOGIN));
|
||||
|
||||
handleOnline(channel,userId);
|
||||
}
|
||||
|
||||
private void handleLogout(Channel channel, MessageRequest request) {
|
||||
System.out.println("===LOGOUT===");
|
||||
|
||||
Long userId = SessionManager.getUserId(channel);
|
||||
if (ObjectUtils.isNotEmpty(userId)){
|
||||
handleOffline(channel , userId);
|
||||
SessionManager.removeChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
// 上线:通知好友
|
||||
private void handleOnline(Channel channel, Long from) {
|
||||
// 通知给好友
|
||||
List<Long> onlineFriends = imFriendService.getOnlineFriends(from);
|
||||
MessageWriter.broadcastToUsers(onlineFriends,MessageResponse.success(MessageTypeEnums.ONLINE).setFrom(from));
|
||||
|
||||
// 通知群组
|
||||
List<Long> onlineGroupUsers = imGroupUserService.getOnlineGroupUsers(from);
|
||||
MessageWriter.broadcastToUsers(onlineGroupUsers,MessageResponse.success(MessageTypeEnums.GROUP_ONLINE).setFrom(from));
|
||||
}
|
||||
|
||||
// 下线逻辑
|
||||
private void handleOffline(Channel channel, MessageRequest request) {
|
||||
private void handleOffline(Channel channel, Long from) {
|
||||
// 通知给好友
|
||||
List<Long> onlineFriends = imFriendService.getOnlineFriends(from);
|
||||
MessageWriter.broadcastToUsers(onlineFriends,MessageResponse.success(MessageTypeEnums.OFFLINE).setFrom(from));
|
||||
|
||||
// 通知群组
|
||||
List<Long> onlineGroupUsers = imGroupUserService.getOnlineGroupUsers(from);
|
||||
MessageWriter.broadcastToUsers(onlineGroupUsers,MessageResponse.success(MessageTypeEnums.GROUP_OFFLINE).setFrom(from));
|
||||
}
|
||||
|
||||
// 私聊逻辑
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
package cn.link56.im.handler;
|
||||
|
||||
import cn.link56.im.enums.MessageTypeEnums;
|
||||
import cn.link56.im.message.MessageRequest;
|
||||
import cn.link56.im.storage.Session;
|
||||
import cn.link56.im.storage.SessionManager;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Component
|
||||
@ChannelHandler.Sharable
|
||||
public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||
@Resource
|
||||
private MessageHandler messageHandler;
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
@@ -22,14 +31,32 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
System.out.println("客户端断开: " + ctx.channel().id().asShortText());
|
||||
Channel channel = ctx.channel();
|
||||
System.out.println("客户端断开: " + channel.id().asShortText());
|
||||
|
||||
SessionManager.removeChannel(ctx.channel());
|
||||
boolean authenticated = SessionManager.isAuthenticated(channel);
|
||||
if (authenticated){
|
||||
Long userId = SessionManager.getUserId(channel);
|
||||
|
||||
if (ObjectUtils.isEmpty(userId)){
|
||||
SessionManager.removeChannel(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
// 通知
|
||||
MessageRequest request = new MessageRequest();
|
||||
request.setType(MessageTypeEnums.LOGOUT.getCode());
|
||||
request.setFrom(userId);
|
||||
|
||||
messageHandler.handleMessage(channel,request);
|
||||
} else {
|
||||
SessionManager.removeChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception {
|
||||
MessageRequest request = MAPPER.readValue(frame.text(), MessageRequest.class);
|
||||
MessageRequest request = MAPPER.readValue(frame.text(), MessageRequest.class);
|
||||
|
||||
ctx.fireChannelRead(request);
|
||||
}
|
||||
@@ -37,8 +64,17 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
System.out.println("连接超时,关闭: " + ctx.channel().id().asShortText());
|
||||
ctx.close();
|
||||
IdleStateEvent idleEvent = (IdleStateEvent) evt; // 强制类型转换
|
||||
switch (idleEvent.state()) {
|
||||
case READER_IDLE:
|
||||
System.out.println("读空闲超时,关闭: " + ctx.channel().id().asShortText());
|
||||
ctx.close();
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
case ALL_IDLE:
|
||||
// 可选处理
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
@@ -50,4 +86,5 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.link56.im.mapper;
|
||||
|
||||
import cn.link56.im.model.po.ImFriend;
|
||||
import cn.link56.im.model.vo.QueryFriendVO;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ImFriendMapper extends BaseMapper<ImFriend> {
|
||||
|
||||
List<QueryFriendVO> selectFriend(
|
||||
@Param("offset") int offset,
|
||||
@Param("limit") int limit,
|
||||
@Param("userId") Long userId,
|
||||
@Param("name") String name,
|
||||
@Param("id") Long id
|
||||
);
|
||||
|
||||
Long selectFriendTotal(@Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.link56.im.mapper;
|
||||
|
||||
import cn.link56.im.model.po.ImGroup;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ImGroupMapper extends BaseMapper<ImGroup> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.link56.im.mapper;
|
||||
|
||||
import cn.link56.im.model.po.ImGroupMessage;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ImGroupMessageMapper extends BaseMapper<ImGroupMessage> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.link56.im.mapper;
|
||||
|
||||
import cn.link56.im.model.po.ImGroupUser;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ImGroupUserMapper extends BaseMapper<ImGroupUser> {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package cn.link56.im.mapper;
|
||||
|
||||
import cn.link56.im.model.po.ImPrivateMessage;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ImPrivateMessageMapper extends BaseMapper<ImPrivateMessage> {
|
||||
}
|
||||
+55
-18
@@ -9,42 +9,79 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class MessageResponse {
|
||||
private Integer type;
|
||||
private Long from;
|
||||
private Object data;
|
||||
private Long timestamp;
|
||||
|
||||
private Integer code;
|
||||
private String message;
|
||||
|
||||
|
||||
public static MessageResponse success() {
|
||||
return buildResponse(null, MessageResponseStatusConstants.SUCCESS, null, null);
|
||||
// ---------------- 链式 Setter ----------------
|
||||
public MessageResponse setType(MessageTypeEnums type) {
|
||||
this.type = type.getCode();
|
||||
return this;
|
||||
}
|
||||
|
||||
public static MessageResponse success(MessageTypeEnums messageTypeEnums) {
|
||||
return buildResponse(messageTypeEnums.getCode(), MessageResponseStatusConstants.SUCCESS, null, null);
|
||||
public MessageResponse setFrom(Long from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static MessageResponse error() {
|
||||
return buildResponse(null, MessageResponseStatusConstants.ERROR, null, "系统异常");
|
||||
public MessageResponse setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageResponse setTimestamp(Long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageResponse setCode(Integer code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageResponse setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
// ---------------- 构建方法 ----------------
|
||||
public static MessageResponse success(MessageTypeEnums type) {
|
||||
return new MessageResponse()
|
||||
.setCode(MessageResponseStatusConstants.SUCCESS)
|
||||
.setType(type)
|
||||
.setTimestamp(TimeUtils.getCurrentTimestampMillis());
|
||||
}
|
||||
|
||||
public static MessageResponse success(MessageTypeEnums type, Long from) {
|
||||
return new MessageResponse()
|
||||
.setCode(MessageResponseStatusConstants.SUCCESS)
|
||||
.setType(type)
|
||||
.setFrom(from)
|
||||
.setTimestamp(TimeUtils.getCurrentTimestampMillis());
|
||||
}
|
||||
|
||||
public static MessageResponse error(String msg) {
|
||||
return buildResponse(null, MessageResponseStatusConstants.ERROR, null, msg);
|
||||
return new MessageResponse()
|
||||
.setCode(MessageResponseStatusConstants.ERROR)
|
||||
.setMessage(msg)
|
||||
.setTimestamp(TimeUtils.getCurrentTimestampMillis());
|
||||
}
|
||||
|
||||
private static MessageResponse buildResponse(Integer type, Integer code, Object data, String message) {
|
||||
MessageResponse response = new MessageResponse();
|
||||
response.setType(type);
|
||||
response.setCode(code);
|
||||
response.setData(data);
|
||||
response.setMessage(message);
|
||||
response.setTimestamp(TimeUtils.getCurrentTimestampMillis());
|
||||
return response;
|
||||
public static MessageResponse error(String msg, MessageTypeEnums type, Long from) {
|
||||
return new MessageResponse()
|
||||
.setCode(MessageResponseStatusConstants.ERROR)
|
||||
.setMessage(msg)
|
||||
.setType(type)
|
||||
.setFrom(from)
|
||||
.setTimestamp(TimeUtils.getCurrentTimestampMillis());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.link56.im.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CreateFriendDTO {
|
||||
@NotNull(message = "好友ID不能为空")
|
||||
private Long friendId;
|
||||
}
|
||||
+1
-1
@@ -7,6 +7,6 @@ import lombok.NoArgsConstructor;
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class OnlineDTO {
|
||||
public class LoginDTO {
|
||||
private String token;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.link56.im.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class QueryFriendDTO {
|
||||
private Integer currentPage;
|
||||
private Integer pageSize;
|
||||
|
||||
private String name;
|
||||
|
||||
private Long id;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.link56.im.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class QueryGroupDTO {
|
||||
private Integer currentPage;
|
||||
private Integer pageSize;
|
||||
|
||||
private String name;
|
||||
|
||||
private Long id;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.link56.im.model.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName("b_im_friend")
|
||||
@Accessors(chain = true)
|
||||
public class ImFriend {
|
||||
@TableId(value = "b_id")
|
||||
private Long id;
|
||||
|
||||
@TableField("b_user_id")
|
||||
private Long userId;
|
||||
|
||||
@TableField("b_friend_id")
|
||||
private Long friendId;
|
||||
|
||||
@TableField("b_unread_count")
|
||||
private Integer unreadCount;
|
||||
|
||||
@TableField("b_last_message_id")
|
||||
private Long lastMessageId;
|
||||
|
||||
@TableField(
|
||||
value = "b_create_time",
|
||||
fill = FieldFill.INSERT
|
||||
)
|
||||
private Date createTime;
|
||||
|
||||
@TableField(
|
||||
value = "b_update_time",
|
||||
fill = FieldFill.INSERT_UPDATE
|
||||
)
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package cn.link56.im.model.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName("b_im_group")
|
||||
public class ImGroup {
|
||||
@TableId(value = "b_id")
|
||||
private Long id;
|
||||
|
||||
@TableField("b_name")
|
||||
private String name;
|
||||
|
||||
@TableField("b_description")
|
||||
private String description;
|
||||
|
||||
@TableField("b_last_message_id")
|
||||
private Long lastMessageId;
|
||||
|
||||
@TableField("b_avatar")
|
||||
private String avatar;
|
||||
|
||||
@TableField(
|
||||
value = "b_create_by",
|
||||
fill = FieldFill.INSERT
|
||||
)
|
||||
private Long createBy;
|
||||
|
||||
@TableField(
|
||||
value = "b_create_time",
|
||||
fill = FieldFill.INSERT
|
||||
)
|
||||
private Date createTime;
|
||||
|
||||
@TableField(
|
||||
value = "b_update_by",
|
||||
fill = FieldFill.INSERT_UPDATE
|
||||
)
|
||||
private Long updateBy;
|
||||
|
||||
@TableField(
|
||||
value = "b_update_time",
|
||||
fill = FieldFill.INSERT_UPDATE
|
||||
)
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.link56.im.model.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName("b_im_group_message")
|
||||
public class ImGroupMessage {
|
||||
@TableId(value = "b_id")
|
||||
private Long id;
|
||||
|
||||
@TableField("b_sender_id")
|
||||
private Long senderId;
|
||||
|
||||
@TableField("b_group_id")
|
||||
private Long groupId;
|
||||
|
||||
@TableField("b_content")
|
||||
private String content;
|
||||
|
||||
@TableField("b_type")
|
||||
private Integer type;
|
||||
|
||||
@TableField("b_send_time")
|
||||
private Long sendTime;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.link56.im.model.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName("b_im_group_user")
|
||||
public class ImGroupUser {
|
||||
@TableField("b_group_id")
|
||||
private Long groupId;
|
||||
|
||||
@TableField("b_user_id")
|
||||
private Long userId;
|
||||
|
||||
@TableField("b_join_time")
|
||||
private Date joinTime;
|
||||
|
||||
@TableField("b_last_active_time")
|
||||
private Date lastActiveTime;
|
||||
|
||||
@TableField("b_unread_count")
|
||||
private Integer unreadCount;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.link56.im.model.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName("b_im_private_message")
|
||||
public class ImPrivateMessage {
|
||||
@TableId(value = "b_id")
|
||||
private Long id;
|
||||
|
||||
@TableField("b_sender_id")
|
||||
private Long senderId;
|
||||
|
||||
@TableField("b_receiver_id")
|
||||
private Long receiverId;
|
||||
|
||||
@TableField("b_content")
|
||||
private String content;
|
||||
|
||||
@TableField("b_type")
|
||||
private Integer type;
|
||||
|
||||
@TableField("b_send_time")
|
||||
private Long sendTime;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.link56.im.model.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class QueryFriendVO {
|
||||
private Long id;
|
||||
|
||||
private Long friendId;
|
||||
private String friendName;
|
||||
private String friendAvatar;
|
||||
|
||||
private Integer unreadCount;
|
||||
|
||||
private Long lastMessageId;
|
||||
private Long laseMessageTime;
|
||||
private Integer lastMessageType;
|
||||
private String lastMessageContent;
|
||||
|
||||
private Boolean isOnline;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.link56.im.model.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class QueryGroupVO {
|
||||
private Long id;
|
||||
private String name;
|
||||
private String avatar;
|
||||
|
||||
private Integer unreadCount;
|
||||
|
||||
private Long lastMessageId;
|
||||
private Long laseMessageTime;
|
||||
private Integer lastMessageType;
|
||||
private String lastMessageContent;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package cn.link56.im.service;
|
||||
|
||||
import cn.link56.common.BasePageVO;
|
||||
import cn.link56.common.context.UserContext;
|
||||
import cn.link56.common.utils.snowflake.idgen.IdGenerator;
|
||||
import cn.link56.im.mapper.ImFriendMapper;
|
||||
import cn.link56.im.model.dto.QueryFriendDTO;
|
||||
import cn.link56.im.model.po.ImFriend;
|
||||
import cn.link56.im.model.vo.QueryFriendVO;
|
||||
import cn.link56.im.storage.SessionManager;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class ImFriendService extends ServiceImpl<ImFriendMapper, ImFriend> {
|
||||
@Resource
|
||||
private ImFriendMapper imFriendMapper;
|
||||
|
||||
public void createFriend(Long userId,Long friendId){
|
||||
// 先检查是否存在
|
||||
LambdaQueryWrapper<ImFriend> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImFriend::getUserId,userId)
|
||||
.eq(ImFriend::getFriendId,friendId);
|
||||
Long selectCount = imFriendMapper.selectCount(queryWrapper);
|
||||
|
||||
if (selectCount > 0){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 不存在则插入
|
||||
ImFriend imFriend = new ImFriend();
|
||||
|
||||
imFriend.setId(IdGenerator.nextId())
|
||||
.setUserId(userId)
|
||||
.setFriendId(friendId)
|
||||
.setUnreadCount(0);
|
||||
|
||||
imFriendMapper.insert(imFriend);
|
||||
}
|
||||
|
||||
public BasePageVO<QueryFriendVO> queryFriend(QueryFriendDTO dto){
|
||||
Long userId = UserContext.getUserId();
|
||||
|
||||
String name = dto.getName();
|
||||
Long id = dto.getId();
|
||||
|
||||
int offset = (dto.getCurrentPage() - 1) * dto.getPageSize() + 1;
|
||||
int limit = dto.getCurrentPage() * dto.getPageSize();
|
||||
|
||||
// 分页查询
|
||||
List<QueryFriendVO> queryFriendVOS = imFriendMapper.selectFriend(offset, limit,userId, name, id);
|
||||
|
||||
queryFriendVOS.forEach(item -> {
|
||||
Long friendId = item.getFriendId();
|
||||
|
||||
item.setIsOnline(SessionManager.isOnline(friendId));
|
||||
});
|
||||
// 查询总数
|
||||
Long total = imFriendMapper.selectFriendTotal(userId);
|
||||
|
||||
BasePageVO<QueryFriendVO> basePageVO = new BasePageVO<>();
|
||||
basePageVO.setRecords(queryFriendVOS);
|
||||
basePageVO.setTotal(total);
|
||||
|
||||
return basePageVO;
|
||||
}
|
||||
|
||||
// 获取在线好友ID列表
|
||||
public List<Long> getOnlineFriends(Long userId) {
|
||||
LambdaQueryWrapper<ImFriend> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImFriend::getUserId, userId)
|
||||
.select(ImFriend::getFriendId);
|
||||
|
||||
List<ImFriend> friends = imFriendMapper.selectList(queryWrapper);
|
||||
|
||||
return friends.stream()
|
||||
.map(ImFriend::getFriendId)
|
||||
.filter(SessionManager::isOnline)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package cn.link56.im.service;
|
||||
|
||||
import cn.link56.im.mapper.ImGroupMessageMapper;
|
||||
import cn.link56.im.model.po.ImGroupMessage;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ImGroupMessageService extends ServiceImpl<ImGroupMessageMapper, ImGroupMessage> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.link56.im.service;
|
||||
|
||||
import cn.link56.common.BasePageVO;
|
||||
import cn.link56.im.mapper.ImGroupMapper;
|
||||
import cn.link56.im.model.dto.QueryGroupDTO;
|
||||
import cn.link56.im.model.po.ImGroup;
|
||||
import cn.link56.im.model.vo.QueryGroupVO;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class ImGroupService extends ServiceImpl<ImGroupMapper, ImGroup>{
|
||||
@Resource
|
||||
private ImGroupMapper imGroupMapper;
|
||||
|
||||
public BasePageVO<QueryGroupVO> queryGroup(QueryGroupDTO dto) {
|
||||
String name = dto.getName();
|
||||
Long id = dto.getId();
|
||||
|
||||
int offset = (dto.getCurrentPage() - 1) * dto.getPageSize() + 1;
|
||||
int limit = dto.getCurrentPage() * dto.getPageSize();
|
||||
|
||||
|
||||
return new BasePageVO<>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.link56.im.service;
|
||||
|
||||
import cn.link56.im.mapper.ImGroupUserMapper;
|
||||
import cn.link56.im.model.po.ImGroupUser;
|
||||
import cn.link56.im.storage.SessionManager;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class ImGroupUserService extends ServiceImpl<ImGroupUserMapper, ImGroupUser> {
|
||||
@Resource
|
||||
private ImGroupUserMapper imGroupUserMapper;
|
||||
|
||||
// 获取群组在线用户列表
|
||||
public List<Long> getOnlineGroupUsers(Long userId) {
|
||||
// 1️⃣ 查询用户所在的群组ID列表
|
||||
LambdaQueryWrapper<ImGroupUser> groupWrapper = new LambdaQueryWrapper<>();
|
||||
groupWrapper.eq(ImGroupUser::getUserId, userId)
|
||||
.select(ImGroupUser::getGroupId);
|
||||
List<ImGroupUser> userGroups = imGroupUserMapper.selectList(groupWrapper);
|
||||
List<Long> groupIds = userGroups.stream()
|
||||
.map(ImGroupUser::getGroupId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (groupIds.isEmpty()) return Collections.emptyList();
|
||||
|
||||
// 2️⃣ 查询这些群组的成员ID
|
||||
LambdaQueryWrapper<ImGroupUser> memberWrapper = new LambdaQueryWrapper<>();
|
||||
memberWrapper.in(ImGroupUser::getGroupId, groupIds)
|
||||
.select(ImGroupUser::getUserId);
|
||||
List<ImGroupUser> groupMembers = imGroupUserMapper.selectList(memberWrapper);
|
||||
|
||||
// 3️⃣ 过滤在线用户
|
||||
|
||||
return groupMembers.stream()
|
||||
.map(ImGroupUser::getUserId)
|
||||
.filter(SessionManager::isOnline) // 内存判断在线
|
||||
.distinct() // 去重
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package cn.link56.im.service;
|
||||
|
||||
import cn.link56.im.mapper.ImPrivateMessageMapper;
|
||||
import cn.link56.im.model.po.ImPrivateMessage;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ImPrivateMessageService extends ServiceImpl<ImPrivateMessageMapper, ImPrivateMessage> {
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import io.netty.util.AttributeKey;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class SessionManager {
|
||||
|
||||
/** userId -> Session */
|
||||
private static final ConcurrentHashMap<Long, Session> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@@ -42,8 +42,6 @@ public class MessageWriter {
|
||||
String payload = MAPPER.writeValueAsString(message);
|
||||
// 发送给客户端
|
||||
channel.writeAndFlush(new TextWebSocketFrame(payload));
|
||||
System.out.println("===发送消息===");
|
||||
System.out.println(payload);
|
||||
} catch (Exception e) {
|
||||
System.out.println("===发送消息失败===");
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="cn.link56.im.mapper.ImFriendMapper">
|
||||
|
||||
<select id="selectFriend" resultType="cn.link56.im.model.vo.QueryFriendVO">
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT
|
||||
f.b_id AS id,
|
||||
f.b_friend_id AS friendId,
|
||||
u.b_name AS friendName,
|
||||
u.b_avatar AS friendAvatar,
|
||||
f.b_unread_count AS unreadCount,
|
||||
m.b_id AS lastMessageId,
|
||||
m.b_send_time AS lastMessageTime,
|
||||
m.b_type AS lastMessageType,
|
||||
m.b_content AS lastMessageContent,
|
||||
ROW_NUMBER() OVER (
|
||||
ORDER BY
|
||||
<if test="id != null and id != 0">
|
||||
CASE WHEN f.b_id = #{id} THEN 0 ELSE 1 END,
|
||||
</if>
|
||||
m.b_send_time DESC
|
||||
) AS rn
|
||||
FROM b_im_friend f
|
||||
LEFT JOIN b_company_user u ON f.b_friend_id = u.b_id
|
||||
LEFT JOIN b_im_private_message m ON f.b_last_message_id = m.b_id
|
||||
WHERE f.b_user_id = #{userId}
|
||||
) AS t
|
||||
WHERE t.rn BETWEEN #{offset} AND #{limit}
|
||||
</select>
|
||||
|
||||
<select id="selectFriendTotal" resultType="java.lang.Long">
|
||||
SELECT COUNT(*)
|
||||
FROM b_im_friend f
|
||||
WHERE f.b_user_id = #{userId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="cn.link56.im.mapper.ImGroupMapper">
|
||||
|
||||
<select id="selectGroup" resultType="cn.link56.im.model.vo.QueryGroupVO">
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT
|
||||
g.b_id AS id,
|
||||
g.b_name AS name,
|
||||
g.b_avatar AS avatar,
|
||||
gu.b_unread_count AS unreadCount,
|
||||
m.b_id AS lastMessageId,
|
||||
m.b_send_time AS lastMessageTime,
|
||||
m.b_type AS lastMessageType,
|
||||
m.b_content AS lastMessageContent,
|
||||
ROW_NUMBER() OVER (
|
||||
ORDER BY
|
||||
<if test="id != null and id != 0">
|
||||
CASE WHEN g.b_id = #{id} THEN 0 ELSE 1 END,
|
||||
</if>
|
||||
m.b_send_time DESC
|
||||
) AS rn
|
||||
FROM b_im_group g
|
||||
INNER JOIN b_im_group_user gu ON g.b_id = gu.b_group_id
|
||||
LEFT JOIN b_im_group_message m ON gu.b_last_message_id = m.b_id
|
||||
WHERE gu.b_user_id = #{userId}
|
||||
) AS t
|
||||
WHERE t.rn BETWEEN #{offset} AND #{limit}
|
||||
</select>
|
||||
|
||||
<select id="selectGroupTotal" resultType="java.lang.Long">
|
||||
SELECT COUNT(DISTINCT g.b_id)
|
||||
FROM b_im_group g
|
||||
INNER JOIN b_im_group_user gu ON g.b_id = gu.b_group_id
|
||||
WHERE gu.b_user_id = #{userId}
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
||||
Reference in new issue
Block a user