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 a1e12ec..4b3e6e7 100644 --- a/im-client/src/main/java/com/bx/imclient/IMClient.java +++ b/im-client/src/main/java/com/bx/imclient/IMClient.java @@ -1,14 +1,14 @@ package com.bx.imclient; import com.bx.imclient.sender.IMSender; +import com.bx.imcommon.enums.IMTerminalType; import com.bx.imcommon.model.IMGroupMessage; import com.bx.imcommon.model.IMPrivateMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; -import java.util.Collection; -import java.util.Collections; import java.util.List; +import java.util.Map; @Configuration public class IMClient { @@ -31,8 +31,19 @@ public class IMClient { * @param userIds 用户id列表 * @return 在线的用户列表 */ - public List isOnline(List userIds){ - return imSender.isOnline(userIds); + public List getOnlineUser(List userIds){ + return imSender.getOnlineUser(userIds); + } + + + /** + * 判断多个用户是否在线 + * + * @param userIds 用户id列表 + * @return 在线的用户终端 + */ + public Map> getOnlineTerminal(List userIds){ + return imSender.getOnlineTerminal(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 c92a990..1d3dc87 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 @@ -14,6 +14,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @Service @@ -90,12 +91,12 @@ public class IMSender { List serverIds = redisTemplate.opsForValue().multiGet(sendMap.keySet()); // 格式:map<服务器id,list<接收方>> Map> serverMap = new HashMap<>(); - List offLineUsers = Collections.synchronizedList(new LinkedList<>()); + List offLineUsers = new LinkedList<>(); int idx = 0; for (Map.Entry entry : sendMap.entrySet()) { Integer serverId = (Integer)serverIds.get(idx++); if (serverId != null) { - List list = serverMap.computeIfAbsent(serverId, o -> Collections.synchronizedList(new LinkedList<>())); + List list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>()); list.add(entry.getValue()); } else { // 加入离线列表 @@ -150,34 +151,40 @@ public class IMSender { } } - public Boolean isOnline(Long userId) { - String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*"); - return !redisTemplate.keys(key).isEmpty(); - } - - public List isOnline(List userIds){ + public Map> getOnlineTerminal(List userIds){ if(CollectionUtil.isEmpty(userIds)){ - return Collections.emptyList(); + return Collections.EMPTY_MAP; } // 把所有用户的key都存起来 - Map keyMap = new HashMap<>(); + Map userMap = new HashMap<>(); for(Long id:userIds){ for (Integer terminal : IMTerminalType.codes()) { String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString()); - keyMap.put(key,id); + userMap.put(key,new IMUserInfo(id,terminal)); } } // 批量拉取 - List serverIds = redisTemplate.opsForValue().multiGet(keyMap.keySet()); + List serverIds = redisTemplate.opsForValue().multiGet(userMap.keySet()); int idx = 0; - List onlineIds = new LinkedList<>(); - for (Map.Entry entry : keyMap.entrySet()) { + Map> onlineMap = new HashMap<>(); + for (Map.Entry entry : userMap.entrySet()) { // serverid有值表示用户在线 if(serverIds.get(idx++) != null){ - onlineIds.add(entry.getValue()); + IMUserInfo userInfo = entry.getValue(); + List terminals = onlineMap.computeIfAbsent(userInfo.getId(), o -> new LinkedList<>()); + terminals.add(IMTerminalType.fromCode(userInfo.getTerminal())); } } // 去重并返回 - return onlineIds.stream().distinct().collect(Collectors.toList()); + return onlineMap; + } + + public Boolean isOnline(Long userId) { + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*"); + return !redisTemplate.keys(key).isEmpty(); + } + + public List getOnlineUser(List userIds){ + return new LinkedList<>(getOnlineTerminal(userIds).keySet()); } } diff --git a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java index 63efcd8..22222c0 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java @@ -7,6 +7,7 @@ import com.bx.implatform.service.IUserService; import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.UserSession; import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.vo.OnlineTerminalVO; import com.bx.implatform.vo.UserVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -34,6 +35,13 @@ public class UserController { return ResultUtils.success(onlineIds); } + @GetMapping("/terminal/online") + @ApiOperation(value = "判断用户哪个终端在线",notes="返回在线的用户id的终端集合") + public Result> getOnlineTerminal(@NotEmpty @RequestParam("userIds") String userIds){ + return ResultUtils.success(userService.getOnlineTerminals(userIds)); + } + + @GetMapping("/self") @ApiOperation(value = "获取当前用户信息",notes="获取当前用户信息") public Result findSelfInfo(){ @@ -46,10 +54,8 @@ public class UserController { @GetMapping("/find/{id}") @ApiOperation(value = "查找用户",notes="根据id查找用户") - public Result findByIde(@NotEmpty @PathVariable("id") long id){ - User user = userService.getById(id); - UserVO userVO = BeanUtils.copyProperties(user,UserVO.class); - return ResultUtils.success(userVO); + public Result findById(@NotEmpty @PathVariable("id") Long id){ + return ResultUtils.success(userService.findUserById(id)); } @PutMapping("/update") diff --git a/im-platform/src/main/java/com/bx/implatform/service/IUserService.java b/im-platform/src/main/java/com/bx/implatform/service/IUserService.java index 107d51e..c675567 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IUserService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IUserService.java @@ -6,6 +6,7 @@ import com.bx.implatform.entity.User; import com.bx.implatform.dto.LoginDTO; import com.bx.implatform.dto.RegisterDTO; import com.bx.implatform.vo.LoginVO; +import com.bx.implatform.vo.OnlineTerminalVO; import com.bx.implatform.vo.UserVO; import java.util.List; @@ -25,8 +26,13 @@ public interface IUserService extends IService { void update(UserVO vo); + UserVO findUserById(Long id); + List findUserByName(String name); List checkOnline(String userIds); + List getOnlineTerminals(String userIds); + + } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java index e2104fc..623d64f 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java @@ -3,6 +3,7 @@ package com.bx.implatform.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.imclient.IMClient; import com.bx.implatform.contant.Constant; import com.bx.implatform.contant.RedisKey; import com.bx.implatform.entity.Friend; @@ -52,6 +53,9 @@ public class GroupServiceImpl extends ServiceImpl implements @Autowired private IFriendService friendsService; + @Autowired + private IMClient imClient; + /** * 创建新群聊 * @@ -292,7 +296,15 @@ public class GroupServiceImpl extends ServiceImpl implements @Override public List findGroupMembers(Long groupId) { List members = groupMemberService.findByGroupId(groupId); - return members.stream().map(m->BeanUtils.copyProperties(m,GroupMemberVO.class)).collect(Collectors.toList()); + List userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList()); + List onlineUserIds = imClient.getOnlineUser(userIds); + return members.stream().map(m->{ + GroupMemberVO vo = BeanUtils.copyProperties(m,GroupMemberVO.class); + vo.setOnline(onlineUserIds.contains(m.getUserId())); + return vo; + }).sorted((m1,m2)->{ + return m2.getOnline().compareTo(m1.getOnline()); + }).collect(Collectors.toList()); } } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java index 269a937..09fadef 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java @@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bx.imclient.IMClient; import com.bx.imcommon.contant.IMConstant; -import com.bx.imcommon.enums.IMTerminalType; import com.bx.imcommon.model.IMPrivateMessage; import com.bx.imcommon.model.IMUserInfo; import com.bx.implatform.entity.Friend; diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java index 0d43247..0d9a8bf 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bx.imclient.IMClient; +import com.bx.imcommon.enums.IMTerminalType; import com.bx.imcommon.util.JwtUtil; import com.bx.implatform.config.JwtProperties; import com.bx.implatform.dto.LoginDTO; @@ -24,6 +25,7 @@ import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.UserSession; import com.bx.implatform.util.BeanUtils; import com.bx.implatform.vo.LoginVO; +import com.bx.implatform.vo.OnlineTerminalVO; import com.bx.implatform.vo.UserVO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -33,7 +35,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @@ -95,7 +99,7 @@ public class UserServiceImpl extends ServiceImpl implements IU /** * 用refreshToken换取新 token * - * @param refreshToken + * @param refreshToken 刷新token * @return 登录token */ @Override @@ -150,7 +154,7 @@ public class UserServiceImpl extends ServiceImpl implements IU * 根据用户名查询用户 * * @param username 用户名 - * @return + * @return 用户信息 */ @Override public User findUserByUserName(String username) { @@ -205,12 +209,25 @@ public class UserServiceImpl extends ServiceImpl implements IU log.info("用户信息更新,用户:{}}", user); } + /** + * 根据用户昵id查询用户以及在线状态 + * + * @param id 用户id + * @return 用户信息 + */ + @Override + public UserVO findUserById(Long id) { + User user = this.getById(id); + UserVO vo = BeanUtils.copyProperties(user,UserVO.class); + vo.setOnline(imClient.isOnline(id)); + return vo; + } /** * 根据用户昵称查询用户,最多返回20条数据 * * @param name 用户名或昵称 - * @return + * @return 用户列表 */ @Override public List findUserByName(String name) { @@ -221,7 +238,7 @@ public class UserServiceImpl extends ServiceImpl implements IU .last("limit 20"); List users = this.list(queryWrapper); List userIds = users.stream().map(User::getId).collect(Collectors.toList()); - List onlineUserIds = imClient.isOnline(userIds); + List onlineUserIds = imClient.getOnlineUser(userIds); return users.stream().map(u-> { UserVO vo = BeanUtils.copyProperties(u,UserVO.class); vo.setOnline(onlineUserIds.contains(u.getId())); @@ -239,7 +256,28 @@ public class UserServiceImpl extends ServiceImpl implements IU public List checkOnline(String userIds) { List userIdList = Arrays.stream(userIds.split(",")) .map(Long::parseLong).collect(Collectors.toList()); - return imClient.isOnline(userIdList); + return imClient.getOnlineUser(userIdList); } + + /** + * 获取用户在线的终端类型 + * + * @param userIds 用户id,多个用‘,’分割 + * @return 在线用户终端 + */ + @Override + public List getOnlineTerminals(String userIds) { + List userIdList = Arrays.stream(userIds.split(",")) + .map(Long::parseLong).collect(Collectors.toList()); + // 查询在线的终端 + Map> terminalMap = imClient.getOnlineTerminal(userIdList); + // 组装vo + List vos = new LinkedList<>(); + terminalMap.forEach((userId,types)->{ + List terminals = types.stream().map(IMTerminalType::code).collect(Collectors.toList()); + vos.add(new OnlineTerminalVO(userId,terminals)); + }); + return vos; + } } diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java index 7428985..2e605c9 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java +++ b/im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java @@ -21,6 +21,9 @@ public class GroupMemberVO { @ApiModelProperty("是否已退出") private Boolean quit; + @ApiModelProperty(value = "是否在线") + private Boolean online; + @ApiModelProperty("备注") private String remark; diff --git a/im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java b/im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java new file mode 100644 index 0000000..4aed6c1 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java @@ -0,0 +1,24 @@ +package com.bx.implatform.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +/** + * @author: 谢绍许 + * @date: 2023-10-28 21:17:59 + * @version: 1.0 + */ +@Data +@AllArgsConstructor +public class OnlineTerminalVO { + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "在线终端类型") + private List terminals; + +} 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 babd2a0..d748d6e 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 @@ -91,8 +91,8 @@ public class IMChannelHandler extends SimpleChannelInboundHandler { AttributeKey attr = AttributeKey.valueOf("USER_ID"); Long userId = ctx.channel().attr(attr).get(); AttributeKey terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE); - Integer ternimal = ctx.channel().attr(terminalAttr).get(); - log.info("心跳超时,即将断开连接,用户id:{},终端类型:{} ",userId,ternimal); + Integer terminal = ctx.channel().attr(terminalAttr).get(); + log.info("心跳超时,即将断开连接,用户id:{},终端类型:{} ",userId,terminal); ctx.channel().close(); } } else { 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 b72bccd..f24f848 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 @@ -45,8 +45,8 @@ public class HeartbeatProcessor extends AbstractMessageProcessor userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID); Long userId = ctx.channel().attr(userIdAttr).get(); AttributeKey terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE); - Integer ternimal = ctx.channel().attr(terminalAttr).get(); - String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(),ternimal.toString()); + Integer terminal = ctx.channel().attr(terminalAttr).get(); + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(),terminal.toString()); redisTemplate.expire(key, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS); } } diff --git a/im-ui/src/api/enums.js b/im-ui/src/api/enums.js index 0d1e0fb..27bfb78 100644 --- a/im-ui/src/api/enums.js +++ b/im-ui/src/api/enums.js @@ -22,7 +22,13 @@ const USER_STATE = { BUSY: 2 } +const TERMINAL_TYPE = { + WEB: 0, + APP: 1 +} + export { MESSAGE_TYPE, - USER_STATE + USER_STATE, + TERMINAL_TYPE } diff --git a/im-ui/src/assets/default_head.png b/im-ui/src/assets/default_head.png deleted file mode 100644 index c7a9e0e..0000000 Binary files a/im-ui/src/assets/default_head.png and /dev/null differ diff --git a/im-ui/src/components/chat/ChatItem.vue b/im-ui/src/components/chat/ChatItem.vue index 3e72124..4cdcc38 100644 --- a/im-ui/src/components/chat/ChatItem.vue +++ b/im-ui/src/components/chat/ChatItem.vue @@ -1,7 +1,7 @@