前文提到我们的单聊只缓存了一条数据,这只是测试整个流程用的中间状态
对于实际的聊天室,这显然是不合理的
我们只需要把redis缓存的数据类型改为List,如此一来,不论是什么类型的消息,只需添加到缓存List里就可以,当用户登录上线或消息队列处理数据时,抢夺分布式锁成功后,将缓存的消息返回给用户端即可
LoginRequestMessageHandler改造
checkMsgCache方法更正:
private void checkMsgCache(Channel channel,Long userId) {
//TODO 分布式锁 -》 消息拉取
List<Object> object = redisCache.getCacheList(RedisKey.userMsgAutoKey(userId));
if(object == null){
return;
}
channel.writeAndFlush(new TextWebSocketFrame(new ResponseResult<>(Code.SUCCESS.code,object).toString()));
redisCache.deleteObject(RedisKey.userMsgAutoKey(userId));
}
直接拉取用户对应的缓存消息列表并返回,前端针对集合中的每个对象进行单独解析操作即可
RabbitMqPublisher改造
废弃私聊和群聊消息推送方法,使用通用消息推送方法(sendMsg):
/**
* @author 14501
*/
@Slf4j
@Component
public class RabbitMqPublisher {
@Resource
private RabbitTemplate rabbitTemplate;
@Resource
private RedisCache redisCache;
@Resource
private RedisTemplate redisTemplate;
public void sendMsg(List<Long> userIds, Object o){
if(userIds == null || userIds.size() == 0 || o == null){
return;
}
boolean res = (boolean) redisTemplate.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(@NotNull RedisOperations operations) throws DataAccessException {
try {
operations.multi();
for (Long userId : userIds) {
ArrayList<Object> list = new ArrayList<>();
list.add(o);
operations.opsForList().rightPushAll(RedisKey.userMsgAutoKey(userId), list);
}
operations.exec();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
});
if(!res){
log.error("消息提交到Redis失败");
}else{
log.info("开始消息分发");
for(Long userId: userIds){
JSONObject json = new JSONObject();
json.put("userId", userId);
rabbitTemplate.convertAndSend("msg.topic","single.1", JSON.toJSONString(json));
}
log.info("消息分发完成");
}
}
@Deprecated
public void sendGroupMsg(List<Long> userIds){
JSONObject json = new JSONObject();
json.put("userIds",userIds);
rabbitTemplate.convertAndSend("msg.topic","group.1",JSON.toJSONString(json));
}
@Deprecated
public void sendSingleMsg(Long userId, ChatMsg msg){
JSONObject json = new JSONObject();
json.put("userId", userId);
rabbitTemplate.convertAndSend("msg.topic","single.1", JSON.toJSONString(json));
json = new JSONObject();
json.put("msg",msg);
redisCache.setCacheObject(RedisKey.userMsgKey(userId), json);
}
}
消息会添加到redis的List中,如不存在list则会添加,使用Redis事务的方式批量进行添加,减少网络IO
RabbitMqConsumer改造
废弃群聊queue,只使用针对单个用户的消息拉取(listenterSingleMsg):
/**
* @author 14501
*/
@Slf4j
@Configuration
public class RabbitMqConsumer {
@Resource
private Session session;
@Resource
private RedisCache redisCache;
@Deprecated
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "msg.group.queue"),
exchange = @Exchange(name = "msg.topic",type = ExchangeTypes.TOPIC),
key = "group.#")
)
public void listenerGroupMsg(String msg) {
log.info("接收到群聊消息");
//TODO 分布式锁 -》 消息拉取
JSONObject json = JSON.parseObject(msg);
ArrayList<Long> userIds = (ArrayList<Long>) json.get("userIds");
GroupChatRequestMessage message = (GroupChatRequestMessage) MessageUtil.decode(JSON.toJSONString(json.get("msg")));
for (Long userId: userIds) {
Channel channel = session.getChannel(userId);
if(channel != null){
channel.writeAndFlush(new TextWebSocketFrame(new ResponseResult<>(Code.SUCCESS.code,message).toString()));
}
}
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "msg.single.queue"),
exchange = @Exchange(name = "msg.topic",type = ExchangeTypes.TOPIC),
key = "single.#")
)
public void listenerSingleMsg(String msg) {
log.info("接收到分发消息");
//TODO 分布式锁 -》 消息拉取
JSONObject json = JSON.parseObject(msg);
long userId = Long.parseLong(json.get("userId") + "");
List<Object> list = redisCache.getCacheList(RedisKey.userMsgAutoKey(userId));
if(list == null){
return;
}
Channel channel = session.getChannel(userId);
if(channel != null){
channel.writeAndFlush(new TextWebSocketFrame(new ResponseResult<>(Code.SUCCESS.code,list).toString()));
redisCache.deleteObject(RedisKey.userMsgAutoKey(userId));
}
}
}
ChatRequestMessageHandler改造
改为使用sendMsg发送消息推送:
/**
* @author 14501
*/
@Slf4j
@Component
@ChannelHandler.Sharable
public class ChatRequestMessageHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {
@Resource
private RabbitMqPublisher publisher;
@Resource
private ChatMsgService chatMsgService;
@Resource
private Session session;
@Override
protected void channelRead0(ChannelHandlerContext ctx, ChatRequestMessage msg) throws Exception {
Long to = msg.getTo();
msg.setFrom(session.getUserId(ctx.channel()));
ChatMsg chatMsg = new ChatMsg(msg);
chatMsgService.save(chatMsg);
ArrayList<Long> list = new ArrayList<>();
list.add(to);
publisher.sendMsg(list,chatMsg);
ctx.writeAndFlush(new TextWebSocketFrame(new ResponseResult<>(Code.SUCCESS,chatMsg).toString()));
log.info("msg:{},已提交发送",chatMsg);
}
}
改造完成,下面进行调试
调试
在线发送
发送方视角:
接收方视角:
离线发送
发送方,发送三条数据:
接收方不在线,缓存仍然存在:
接收方登录后缓存清空,且用户接收到数据:
下一章:群组实现,目前已经把聊天室的基石打好了,群组和普通的消息传输已经对系统没有太大提升了,准备接触一下语音聊天的功能,但是语音前端方案没啥思路