Commit 189672157529669cc2e4c81bba24568180727803

Authored by YunaiV
1 parent 44981bbe

✨ feat(im): 群/好友更新消息改为事件通知,客户端自行拉取

- ImPrivateMessageDTO#ofFriendUpdate 移除 content 字段,删除 FriendUpdateMessage
- 新增 ImMessageTypeEnum.GROUP_MEMBER_UPDATE(203) 和 ofGroupMemberUpdate 工厂方法
- ImGroupMemberServiceImpl#updateGroupMember 推送成员变更事件(多端同步,仅推自己)
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java
@@ -30,11 +30,12 @@ public enum ImMessageTypeEnum implements ArrayValuable<Integer> { @@ -30,11 +30,12 @@ public enum ImMessageTypeEnum implements ArrayValuable<Integer> {
30 30
31 FRIEND_ADD(100, "好友添加"), // 暂无 31 FRIEND_ADD(100, "好友添加"), // 暂无
32 FRIEND_DELETE(101, "好友删除"), // 暂无 32 FRIEND_DELETE(101, "好友删除"), // 暂无
33 - FRIEND_UPDATE(102, "好友更新"), // 对应 FriendUpdateMessage 类 33 + FRIEND_UPDATE(102, "好友更新"), // 暂无(客户端收到后自行拉取)
34 34
35 GROUP_CREATE(200, "群创建"), // 暂无 35 GROUP_CREATE(200, "群创建"), // 暂无
36 GROUP_UPDATE(201, "群信息变更"), // 暂无 36 GROUP_UPDATE(201, "群信息变更"), // 暂无
37 - GROUP_DELETE(202, "群删除"); // 暂无(解散/退群/踢出均用此类型) 37 + GROUP_DELETE(202, "群删除"), // 暂无(解散/退群/踢出均用此类型)
  38 + GROUP_MEMBER_UPDATE(203, "群成员信息变更"); // 暂无(客户端收到后自行拉取)
38 39
39 public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageTypeEnum::getType).toArray(Integer[]::new); 40 public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageTypeEnum::getType).toArray(Integer[]::new);
40 41
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/user/ImUserController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.user;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
  7 +import cn.iocoder.yudao.module.im.controller.admin.user.vo.ImUserRespVO;
  8 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  9 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  10 +import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.Parameter;
  12 +import io.swagger.v3.oas.annotations.tags.Tag;
  13 +import jakarta.annotation.Resource;
  14 +import org.springframework.validation.annotation.Validated;
  15 +import org.springframework.web.bind.annotation.GetMapping;
  16 +import org.springframework.web.bind.annotation.RequestMapping;
  17 +import org.springframework.web.bind.annotation.RequestParam;
  18 +import org.springframework.web.bind.annotation.RestController;
  19 +
  20 +import java.util.Collections;
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +
  25 +@Tag(name = "管理后台 - IM 用户查询")
  26 +@RestController
  27 +@RequestMapping("/im/user")
  28 +@Validated
  29 +public class ImUserController {
  30 +
  31 + @Resource
  32 + private AdminUserApi adminUserApi;
  33 +
  34 + @GetMapping("/get-self")
  35 + @Operation(summary = "获取当前登录用户信息")
  36 + public CommonResult<ImUserRespVO> getSelfInfo() {
  37 + Long userId = SecurityFrameworkUtils.getLoginUserId();
  38 + AdminUserRespDTO user = adminUserApi.getUser(userId);
  39 + return success(BeanUtils.toBean(user, ImUserRespVO.class));
  40 + }
  41 +
  42 + @GetMapping("/get")
  43 + @Operation(summary = "按用户编号查询用户(用于点头像弹名片)")
  44 + @Parameter(name = "id", description = "用户编号", required = true, example = "2048")
  45 + public CommonResult<ImUserRespVO> getUser(@RequestParam("id") Long id) {
  46 + AdminUserRespDTO user = adminUserApi.getUser(id);
  47 + if (user == null) {
  48 + return success(null);
  49 + }
  50 + return success(BeanUtils.toBean(user, ImUserRespVO.class));
  51 + }
  52 +
  53 + @GetMapping("/list-by-name")
  54 + @Operation(summary = "按昵称模糊搜索用户(用于加好友场景)")
  55 + @Parameter(name = "name", description = "昵称关键词", required = true, example = "芋道")
  56 + public CommonResult<List<ImUserRespVO>> getUserListByName(@RequestParam("name") String name) {
  57 + if (name == null || name.trim().isEmpty()) {
  58 + return success(Collections.emptyList());
  59 + }
  60 + List<AdminUserRespDTO> users = adminUserApi.getUserListByNickname(name.trim());
  61 + // TODO @芋艿:【对齐】这里需要支持按「用户名 or 昵称」搜索;
  62 + // AdminUser 按用户名模糊的接口暂未暴露,先仅按昵称。后续可扩 AdminUserApi 加一个
  63 + // `getUserListByUsernameOrNickname(keyword)` 或让前端输入时选搜索模式。
  64 + return success(CollectionUtils.convertList(users, u -> BeanUtils.toBean(u, ImUserRespVO.class)));
  65 + }
  66 +
  67 +}
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/user/vo/ImUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.user.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * IM 用户 Response VO(对 AdminUser 的字段窄投影)
  8 + * <p>
  9 + * 面向 IM 场景,主要关注 id / userName / nickName / headImage / sex / signature 这些字段;
  10 + * 但 yudao 侧 AdminUser 没有 signature 字段,暂不返回(TODO @芋艿:【对齐】后续可加用户扩展表承载)。
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Schema(description = "管理后台 - IM 用户 Response VO")
  15 +@Data
  16 +public class ImUserRespVO {
  17 +
  18 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + private Long id;
  20 +
  21 + @Schema(description = "用户名", example = "admin")
  22 + private String username;
  23 +
  24 + @Schema(description = "昵称", example = "芋道")
  25 + private String nickname;
  26 +
  27 + @Schema(description = "头像")
  28 + private String avatar;
  29 +
  30 + @Schema(description = "性别 0=未知 1=男 2=女", example = "1")
  31 + private Integer sex;
  32 +
  33 + @Schema(description = "手机号")
  34 + private String mobile;
  35 +
  36 + /**
  37 + * TODO @芋艿:【对齐】个性签名(signature)字段当前缺失,yudao AdminUser 无此字段;
  38 + * 如果要对齐,可走 system 模块的用户扩展表,或 IM 模块独立存。
  39 + */
  40 +
  41 +}
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
@@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
7 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; 7 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
8 import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; 8 import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
9 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 9 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
10 -import cn.iocoder.yudao.module.im.service.websocket.dto.friend.FriendUpdateMessage;  
11 import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; 10 import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
12 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 11 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
13 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
@@ -89,10 +88,8 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -89,10 +88,8 @@ public class ImFriendServiceImpl implements ImFriendService {
89 imFriendMapper.updateById(new ImFriendDO().setId(friend.getId()).setMuted(reqVO.getMuted())); 88 imFriendMapper.updateById(new ImFriendDO().setId(friend.getId()).setMuted(reqVO.getMuted()));
90 89
91 // 3. 推送好友更新通知(多端同步) 90 // 3. 推送好友更新通知(多端同步)
92 - FriendUpdateMessage websocketContent = new FriendUpdateMessage()  
93 - .setFriendUserId(reqVO.getFriendUserId()).setMuted(reqVO.getMuted());  
94 imWebSocketService.sendPrivateMessageAsync(userId, 91 imWebSocketService.sendPrivateMessageAsync(userId,
95 - ImPrivateMessageDTO.ofFriendUpdate(userId, reqVO.getFriendUserId(), websocketContent)); 92 + ImPrivateMessageDTO.ofFriendUpdate(userId, reqVO.getFriendUserId()));
96 } 93 }
97 94
98 @Override 95 @Override
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
@@ -6,6 +6,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -6,6 +6,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; 6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
8 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; 8 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
  9 +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
  10 +import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;
9 import jakarta.annotation.Resource; 11 import jakarta.annotation.Resource;
10 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
11 import org.springframework.validation.annotation.Validated; 13 import org.springframework.validation.annotation.Validated;
@@ -32,6 +34,9 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -32,6 +34,9 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
32 @Resource 34 @Resource
33 private ImGroupMemberMapper groupMemberMapper; 35 private ImGroupMemberMapper groupMemberMapper;
34 36
  37 + @Resource
  38 + private ImWebSocketService webSocketService;
  39 +
35 @Override 40 @Override
36 public ImGroupMemberDO getGroupMember(Long id) { 41 public ImGroupMemberDO getGroupMember(Long id) {
37 return groupMemberMapper.selectById(id); 42 return groupMemberMapper.selectById(id);
@@ -108,10 +113,15 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -108,10 +113,15 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
108 public void updateGroupMember(Long userId, ImGroupMemberUpdateReqVO updateReqVO) { 113 public void updateGroupMember(Long userId, ImGroupMemberUpdateReqVO updateReqVO) {
109 // 1. 校验是群的有效成员 114 // 1. 校验是群的有效成员
110 ImGroupMemberDO member = validateMemberInGroup(updateReqVO.getGroupId(), userId); 115 ImGroupMemberDO member = validateMemberInGroup(updateReqVO.getGroupId(), userId);
  116 +
111 // 2. 更新群成员信息 117 // 2. 更新群成员信息
112 ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class) 118 ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class)
113 .setId(member.getId()); 119 .setId(member.getId());
114 groupMemberMapper.updateById(updateObj); 120 groupMemberMapper.updateById(updateObj);
  121 +
  122 + // 3. 推送群成员变更通知(多端同步,仅推给自己)
  123 + webSocketService.sendGroupMessageAsync(userId,
  124 + ImGroupMessageDTO.ofGroupMemberUpdate(userId, updateReqVO.getGroupId()));
115 } 125 }
116 126
117 @Override 127 @Override
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java
@@ -162,4 +162,18 @@ public class ImGroupMessageDTO { @@ -162,4 +162,18 @@ public class ImGroupMessageDTO {
162 .setSendTime(LocalDateTime.now()); 162 .setSendTime(LocalDateTime.now());
163 } 163 }
164 164
  165 + /**
  166 + * 构建群成员信息变更推送 DTO(多端同步:通知自己"在某群的个人设置变了")
  167 + *
  168 + * @param senderId 当前用户编号
  169 + * @param groupId 群编号
  170 + * @return 群聊 DTO
  171 + */
  172 + public static ImGroupMessageDTO ofGroupMemberUpdate(Long senderId, Long groupId) {
  173 + return new ImGroupMessageDTO()
  174 + .setType(ImMessageTypeEnum.GROUP_MEMBER_UPDATE.getType())
  175 + .setSenderId(senderId).setGroupId(groupId)
  176 + .setSendTime(LocalDateTime.now());
  177 + }
  178 +
165 } 179 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java
1 package cn.iocoder.yudao.module.im.service.websocket.dto; 1 package cn.iocoder.yudao.module.im.service.websocket.dto;
2 2
3 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
6 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 5 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
7 -import cn.iocoder.yudao.module.im.service.websocket.dto.friend.FriendUpdateMessage;  
8 import lombok.Data; 6 import lombok.Data;
9 import lombok.experimental.Accessors; 7 import lombok.experimental.Accessors;
10 8
@@ -137,14 +135,13 @@ public class ImPrivateMessageDTO { @@ -137,14 +135,13 @@ public class ImPrivateMessageDTO {
137 * 135 *
138 * @param userId 当前用户编号 136 * @param userId 当前用户编号
139 * @param friendUserId 好友的用户编号 137 * @param friendUserId 好友的用户编号
140 - * @param content 更新后的好友属性  
141 * @return 私聊 DTO 138 * @return 私聊 DTO
142 */ 139 */
143 - public static ImPrivateMessageDTO ofFriendUpdate(Long userId, Long friendUserId, FriendUpdateMessage content) { 140 + public static ImPrivateMessageDTO ofFriendUpdate(Long userId, Long friendUserId) {
144 return new ImPrivateMessageDTO() 141 return new ImPrivateMessageDTO()
145 .setType(ImMessageTypeEnum.FRIEND_UPDATE.getType()) 142 .setType(ImMessageTypeEnum.FRIEND_UPDATE.getType())
146 .setSenderId(userId).setReceiverId(friendUserId) 143 .setSenderId(userId).setReceiverId(friendUserId)
147 - .setContent(JsonUtils.toJsonString(content)).setSendTime(LocalDateTime.now()); 144 + .setSendTime(LocalDateTime.now());
148 } 145 }
149 146
150 } 147 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/friend/FriendUpdateMessage.java deleted
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.friend;  
2 -  
3 -import lombok.Data;  
4 -import lombok.experimental.Accessors;  
5 -  
6 -/**  
7 - * 好友更新消息内容(目前仅免打扰)  
8 - */  
9 -@Data  
10 -@Accessors(chain = true)  
11 -public class FriendUpdateMessage {  
12 -  
13 - /**  
14 - * 好友的用户编号  
15 - */  
16 - private Long friendUserId;  
17 - /**  
18 - * 是否免打扰  
19 - */  
20 - private Boolean muted;  
21 -  
22 -}