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 30  
31 31 FRIEND_ADD(100, "好友添加"), // 暂无
32 32 FRIEND_DELETE(101, "好友删除"), // 暂无
33   - FRIEND_UPDATE(102, "好友更新"), // 对应 FriendUpdateMessage 类
  33 + FRIEND_UPDATE(102, "好友更新"), // 暂无(客户端收到后自行拉取)
34 34  
35 35 GROUP_CREATE(200, "群创建"), // 暂无
36 36 GROUP_UPDATE(201, "群信息变更"), // 暂无
37   - GROUP_DELETE(202, "群删除"); // 暂无(解散/退群/踢出均用此类型)
  37 + GROUP_DELETE(202, "群删除"), // 暂无(解散/退群/踢出均用此类型)
  38 + GROUP_MEMBER_UPDATE(203, "群成员信息变更"); // 暂无(客户端收到后自行拉取)
38 39  
39 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 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
8 8 import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
9 9 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
10   -import cn.iocoder.yudao.module.im.service.websocket.dto.friend.FriendUpdateMessage;
11 10 import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
12 11 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
13 12 import jakarta.annotation.Resource;
... ... @@ -89,10 +88,8 @@ public class ImFriendServiceImpl implements ImFriendService {
89 88 imFriendMapper.updateById(new ImFriendDO().setId(friend.getId()).setMuted(reqVO.getMuted()));
90 89  
91 90 // 3. 推送好友更新通知(多端同步)
92   - FriendUpdateMessage websocketContent = new FriendUpdateMessage()
93   - .setFriendUserId(reqVO.getFriendUserId()).setMuted(reqVO.getMuted());
94 91 imWebSocketService.sendPrivateMessageAsync(userId,
95   - ImPrivateMessageDTO.ofFriendUpdate(userId, reqVO.getFriendUserId(), websocketContent));
  92 + ImPrivateMessageDTO.ofFriendUpdate(userId, reqVO.getFriendUserId()));
96 93 }
97 94  
98 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 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
7 7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
8 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 11 import jakarta.annotation.Resource;
10 12 import org.springframework.stereotype.Service;
11 13 import org.springframework.validation.annotation.Validated;
... ... @@ -32,6 +34,9 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
32 34 @Resource
33 35 private ImGroupMemberMapper groupMemberMapper;
34 36  
  37 + @Resource
  38 + private ImWebSocketService webSocketService;
  39 +
35 40 @Override
36 41 public ImGroupMemberDO getGroupMember(Long id) {
37 42 return groupMemberMapper.selectById(id);
... ... @@ -108,10 +113,15 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
108 113 public void updateGroupMember(Long userId, ImGroupMemberUpdateReqVO updateReqVO) {
109 114 // 1. 校验是群的有效成员
110 115 ImGroupMemberDO member = validateMemberInGroup(updateReqVO.getGroupId(), userId);
  116 +
111 117 // 2. 更新群成员信息
112 118 ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class)
113 119 .setId(member.getId());
114 120 groupMemberMapper.updateById(updateObj);
  121 +
  122 + // 3. 推送群成员变更通知(多端同步,仅推给自己)
  123 + webSocketService.sendGroupMessageAsync(userId,
  124 + ImGroupMessageDTO.ofGroupMemberUpdate(userId, updateReqVO.getGroupId()));
115 125 }
116 126  
117 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 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 1 package cn.iocoder.yudao.module.im.service.websocket.dto;
2 2  
3   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
6 5 import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
7   -import cn.iocoder.yudao.module.im.service.websocket.dto.friend.FriendUpdateMessage;
8 6 import lombok.Data;
9 7 import lombok.experimental.Accessors;
10 8  
... ... @@ -137,14 +135,13 @@ public class ImPrivateMessageDTO {
137 135 *
138 136 * @param userId 当前用户编号
139 137 * @param friendUserId 好友的用户编号
140   - * @param content 更新后的好友属性
141 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 141 return new ImPrivateMessageDTO()
145 142 .setType(ImMessageTypeEnum.FRIEND_UPDATE.getType())
146 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   -}