From 00a0ab327ccba95f911f577dfe24aa4221527da2 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Thu, 30 Jul 2026 20:20:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:bug=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/bx/imclient/IMClient.java | 4 ++++ .../java/com/bx/imclient/sender/IMSender.java | 16 +++++++++++++ .../com/bx/imcommon/contant/IMRedisKey.java | 4 ++++ .../third/impl/ThirdUserServiceImpl.java | 23 ++++++++++++++++++- .../third/user/ThirdUserOnLineStatusVo.java | 3 +++ .../bx/imserver/netty/IMChannelHandler.java | 7 +++++- .../netty/processor/HeartbeatProcessor.java | 5 ++++ .../netty/processor/LoginProcessor.java | 5 ++++ 8 files changed, 65 insertions(+), 2 deletions(-) diff --git a/im-client/src/main/java/com/bx/imclient/IMClient.java b/im-client/src/main/java/com/bx/imclient/IMClient.java index 1630d64..e05f626 100644 --- a/im-client/src/main/java/com/bx/imclient/IMClient.java +++ b/im-client/src/main/java/com/bx/imclient/IMClient.java @@ -36,6 +36,10 @@ public class IMClient { return imSender.isOnline(userId,terminal); } + public Map getLastActiveTime(List userIds) { + return imSender.getLastActiveTime(userIds); + } + /** * 判断多个用户是否在线 diff --git a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java index e036e5e..45b0287 100644 --- a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java +++ b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java @@ -259,4 +259,20 @@ public class IMSender { public List getOnlineUser(List userIds) { return new LinkedList<>(getOnlineTerminal(userIds).keySet()); } + + public Map getLastActiveTime(List userIds) { + if (CollUtil.isEmpty(userIds)) { + return Collections.emptyMap(); + } + List fields = userIds.stream().map(id -> (Object) String.valueOf(id)).toList(); + List values = redisMQTemplate.opsForHash().multiGet(IMRedisKey.IM_USER_LAST_ACTIVE, fields); + Map result = new HashMap<>(); + for (int i = 0; i < userIds.size(); i++) { + Object value = values.get(i); + if (value instanceof Number) { + result.put(userIds.get(i), ((Number) value).longValue()); + } + } + return result; + } } diff --git a/im-common/src/main/java/com/bx/imcommon/contant/IMRedisKey.java b/im-common/src/main/java/com/bx/imcommon/contant/IMRedisKey.java index a7a8bf3..2b3387f 100644 --- a/im-common/src/main/java/com/bx/imcommon/contant/IMRedisKey.java +++ b/im-common/src/main/java/com/bx/imcommon/contant/IMRedisKey.java @@ -11,6 +11,10 @@ public final class IMRedisKey { * 用户ID所连接的IM-server的ID */ public static final String IM_USER_SERVER_ID = "im:user:server_id"; + /** + * 用户最后活跃时间,hash field 为用户ID,value 为毫秒时间戳 + */ + public static final String IM_USER_LAST_ACTIVE = "im:user:last_active"; /** * 系统消息队列 */ diff --git a/im-platform/src/main/java/com/bx/implatform/service/third/impl/ThirdUserServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/third/impl/ThirdUserServiceImpl.java index 877cab8..c029ab9 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/third/impl/ThirdUserServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/third/impl/ThirdUserServiceImpl.java @@ -26,7 +26,11 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** * 第三方用户 @@ -111,12 +115,29 @@ public class ThirdUserServiceImpl extends ServiceImpl implemen .select(User::getId) .in(User::getId, dto.getUserIdList()) ); + List userIds = userIdList.stream().map(User::getId).toList(); + Set onlineUserIds = new HashSet<>(imClient.getOnlineUser(userIds)); + Map lastActiveTimes = getLastActiveTimeSafely(userIds); + long now = System.currentTimeMillis(); return userIdList.stream().map(item -> { ThirdUserOnLineStatusVo entity = new ThirdUserOnLineStatusVo(); entity.setId(item.getId()); - entity.setOnline(imClient.isOnline(item.getId())); + entity.setOnline(onlineUserIds.contains(item.getId())); + entity.setLastActiveTime(lastActiveTimes.get(item.getId())); + if (Boolean.TRUE.equals(entity.getOnline()) && entity.getLastActiveTime() == null) { + entity.setLastActiveTime(now); + } return entity; }).toList(); } + private Map getLastActiveTimeSafely(List userIds) { + try { + return imClient.getLastActiveTime(userIds); + } catch (Exception e) { + log.warn("查询用户最后活跃时间失败,userIds:{}", userIds, e); + return Collections.emptyMap(); + } + } + } diff --git a/im-platform/src/main/java/com/bx/implatform/vo/third/user/ThirdUserOnLineStatusVo.java b/im-platform/src/main/java/com/bx/implatform/vo/third/user/ThirdUserOnLineStatusVo.java index 6e5eaa3..a879d70 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/third/user/ThirdUserOnLineStatusVo.java +++ b/im-platform/src/main/java/com/bx/implatform/vo/third/user/ThirdUserOnLineStatusVo.java @@ -15,4 +15,7 @@ public class ThirdUserOnLineStatusVo { @Schema(description = "是否在线") private Boolean online; + @Schema(description = "最后活跃时间,毫秒时间戳") + private Long lastActiveTime; + } diff --git a/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java b/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java index 298f1c3..819380b 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java +++ b/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java @@ -74,6 +74,11 @@ public class IMChannelHandler extends SimpleChannelInboundHandler { RedisMQTemplate redisTemplate = SpringContextHolder.getBean(RedisMQTemplate.class); String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.toString()); redisTemplate.delete(key); + try { + redisTemplate.opsForHash().put(IMRedisKey.IM_USER_LAST_ACTIVE, userId.toString(), System.currentTimeMillis()); + } catch (Exception e) { + log.warn("记录用户最后活跃时间失败,userId:{}", userId, e); + } log.info("断开连接,userId:{},终端类型:{},{}", userId, terminal, ctx.channel().id().asLongText()); } } @@ -96,4 +101,4 @@ public class IMChannelHandler extends SimpleChannelInboundHandler { } } -} \ No newline at end of file +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java index 01f032d..239c6f3 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java @@ -42,6 +42,11 @@ public class HeartbeatProcessor extends AbstractMessageProcessor userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID); Long userId = ctx.channel().attr(userIdAttr).get(); diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java index b5480f5..fc2438d 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java @@ -68,6 +68,11 @@ public class LoginProcessor extends AbstractMessageProcessor { // 在redis上记录每个user的channelId,15秒没有心跳,则自动过期 String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.toString()); redisMQTemplate.opsForValue().set(key, IMServerGroup.serverId, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS); + try { + redisMQTemplate.opsForHash().put(IMRedisKey.IM_USER_LAST_ACTIVE, userId.toString(), System.currentTimeMillis()); + } catch (Exception e) { + log.warn("记录用户最后活跃时间失败,userId:{}", userId, e); + } // 响应ws IMSendInfo sendInfo = new IMSendInfo<>(); sendInfo.setCmd(IMCmdType.LOGIN.code());