Commit b0ca5b75505461908c57e6ab75944731f1dd91d4

Authored by YunaiV
1 parent 2804e5ed

✨ feat(im):实现 1209 FRIEND_INFO_UPDATED 推送(system 发 AdminUserProfileUpdateMessage…

…,IM Consumer 监听后批量推好友多端)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java
@@ -80,7 +80,7 @@ public enum ImMessageTypeEnum implements ArrayValuable<Integer> { @@ -80,7 +80,7 @@ public enum ImMessageTypeEnum implements ArrayValuable<Integer> {
80 * 对应 OpenIM:FriendInfoUpdatedNotification 1209 80 * 对应 OpenIM:FriendInfoUpdatedNotification 1209
81 * 对应自己的类:FriendInfoUpdatedNotification 81 * 对应自己的类:FriendInfoUpdatedNotification
82 * 场景:B 改了昵称 / 头像后,推给 B 的所有好友 82 * 场景:B 改了昵称 / 头像后,推给 B 的所有好友
83 - * TODO @AI:未实现;待 system 模块改昵称 / 头像时回调 IM 模块批量推此事件,前端 dispatcher 已就绪 83 + * 触发:system 模块发 AdminUserProfileUpdateMessage,IM 消费者 AdminUserProfileUpdateConsumer 批量推此通知
84 */ 84 */
85 FRIEND_INFO_UPDATED(1209, "好友资料变更", false, false), 85 FRIEND_INFO_UPDATED(1209, "好友资料变更", false, false),
86 /** 86 /**
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.mq.consumer.friend;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
  5 +import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;
  6 +import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
  7 +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
  8 +import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
  9 +import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendInfoUpdatedNotification;
  10 +import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage;
  11 +import jakarta.annotation.Resource;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.springframework.context.event.EventListener;
  14 +import org.springframework.scheduling.annotation.Async;
  15 +import org.springframework.stereotype.Component;
  16 +
  17 +import java.util.List;
  18 +
  19 +/**
  20 + * 监听 system 模块的 {@link AdminUserProfileUpdateMessage} 消息,向「资料被改的人」的所有好友推送 FRIEND_INFO_UPDATED 通知
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +@Slf4j
  25 +@Component
  26 +public class AdminUserProfileUpdateConsumer {
  27 +
  28 + @Resource
  29 + private ImFriendService friendService;
  30 + @Resource
  31 + private ImWebSocketService websocketService;
  32 +
  33 + @EventListener
  34 + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步
  35 + public void onMessage(AdminUserProfileUpdateMessage message) {
  36 + Long userId = message.getUserId();
  37 + // 1. 找当前用户的所有有效好友(DISABLE 已删的不推)
  38 + List<ImFriendDO> friends = friendService.getFriendList(userId);
  39 + if (friends.isEmpty()) {
  40 + return;
  41 + }
  42 +
  43 + // 2. 给每个好友的多端推 FRIEND_INFO_UPDATED;payload 里 operatorUserId / friendUserId 都是「资料被改的人」
  44 + for (ImFriendDO friend : friends) {
  45 + if (CommonStatusEnum.isDisable(friend.getStatus())) {
  46 + continue;
  47 + }
  48 + FriendInfoUpdatedNotification payload = (FriendInfoUpdatedNotification) new FriendInfoUpdatedNotification()
  49 + .setOperatorUserId(userId).setFriendUserId(userId);
  50 + websocketService.sendPrivateMessageAsync(friend.getFriendUserId(), ImPrivateMessageDTO.ofFriendNotification(
  51 + ImMessageTypeEnum.FRIEND_INFO_UPDATED.getType(), userId, friend.getFriendUserId(), payload));
  52 + }
  53 + }
  54 +
  55 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendInfoUpdatedNotification.java
@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
5 -// TODO @AI:1209 暂未实现;好友信息在 system 模块,需要 system 改昵称 / 头像后发 ApplicationEvent,IM 模块监听后回调发送此通知  
6 /** 5 /**
7 * 好友资料变更通知(对端改了昵称 / 头像) 6 * 好友资料变更通知(对端改了昵称 / 头像)
8 * <p> 7 * <p>
9 - * 推送给资料被改的人的所有好友;前端清缓存 + 重拉资料 8 + * 由 AdminUserProfileUpdateConsumer 监听 system 模块的 AdminUserProfileUpdateMessage 后,批量推送给资料被改的人的所有好友;
  9 + * 前端 dispatcher 收到后调 loadFriendInfo 重拉资料
10 * <p> 10 * <p>
11 * 此处 friendUserId 表示「资料被更新的那个人」(即对端),与 operatorUserId 一致 11 * 此处 friendUserId 表示「资料被更新的那个人」(即对端),与 operatorUserId 一致
12 */ 12 */
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/message/user/AdminUserProfileUpdateMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.message.user;
  2 +
  3 +import jakarta.validation.constraints.NotNull;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 管理员用户资料(昵称 / 头像)变更消息
  8 + * <p>
  9 + * 仅当 nickname 或 avatar 真的发生变化时才发送;订阅方据此做下游分发,
  10 + * 例如 IM 模块向该用户的所有好友推送 FRIEND_INFO_UPDATED 通知
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +public class AdminUserProfileUpdateMessage {
  16 +
  17 + /**
  18 + * 用户编号
  19 + */
  20 + @NotNull(message = "用户编号不能为空")
  21 + private Long userId;
  22 + /**
  23 + * 变更后的昵称(无变更时为 null)
  24 + */
  25 + private String nickname;
  26 + /**
  27 + * 变更后的头像(无变更时为 null)
  28 + */
  29 + private String avatar;
  30 +
  31 +}
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/user/AdminUserProducer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.mq.producer.user;
  2 +
  3 +import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage;
  4 +import jakarta.annotation.Resource;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.context.ApplicationContext;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * 管理员用户 Producer
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Slf4j
  15 +@Component
  16 +public class AdminUserProducer {
  17 +
  18 + @Resource
  19 + private ApplicationContext applicationContext;
  20 +
  21 + /**
  22 + * 发送 {@link AdminUserProfileUpdateMessage} 消息
  23 + *
  24 + * @param userId 用户编号
  25 + * @param nickname 变更后的昵称(无变更传 null)
  26 + * @param avatar 变更后的头像(无变更传 null)
  27 + */
  28 + public void sendUserProfileUpdateMessage(Long userId, String nickname, String avatar) {
  29 + applicationContext.publishEvent(new AdminUserProfileUpdateMessage()
  30 + .setUserId(userId).setNickname(nickname).setAvatar(avatar));
  31 + }
  32 +
  33 +}
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
@@ -25,6 +25,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; @@ -25,6 +25,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
25 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 25 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
26 import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; 26 import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
27 import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; 27 import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
  28 +import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer;
28 import cn.iocoder.yudao.module.system.service.dept.DeptService; 29 import cn.iocoder.yudao.module.system.service.dept.DeptService;
29 import cn.iocoder.yudao.module.system.service.dept.PostService; 30 import cn.iocoder.yudao.module.system.service.dept.PostService;
30 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; 31 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
@@ -88,6 +89,9 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -88,6 +89,9 @@ public class AdminUserServiceImpl implements AdminUserService {
88 @Resource 89 @Resource
89 private ConfigApi configApi; 90 private ConfigApi configApi;
90 91
  92 + @Resource
  93 + private AdminUserProducer adminUserProducer;
  94 +
91 @Override 95 @Override
92 @Transactional(rollbackFor = Exception.class) 96 @Transactional(rollbackFor = Exception.class)
93 @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}", 97 @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
@@ -158,6 +162,8 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -158,6 +162,8 @@ public class AdminUserServiceImpl implements AdminUserService {
158 userMapper.updateById(updateObj); 162 userMapper.updateById(updateObj);
159 // 2.2 更新岗位 163 // 2.2 更新岗位
160 updateUserPost(updateReqVO, updateObj); 164 updateUserPost(updateReqVO, updateObj);
  165 + // 2.3 昵称 / 头像变化时,发送消息供下游订阅(如 IM 模块推 FRIEND_INFO_UPDATED)
  166 + publishUserProfileUpdatedIfChanged(oldUser, updateReqVO.getNickname(), updateReqVO.getAvatar());
161 167
162 // 3. 记录操作日志上下文 168 // 3. 记录操作日志上下文
163 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class)); 169 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));
@@ -188,12 +194,30 @@ public class AdminUserServiceImpl implements AdminUserService { @@ -188,12 +194,30 @@ public class AdminUserServiceImpl implements AdminUserService {
188 194
189 @Override 195 @Override
190 public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) { 196 public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) {
191 - // 校验正确性  
192 - validateUserExists(id); 197 + // 1. 校验正确性
  198 + AdminUserDO oldUser = validateUserExists(id);
193 validateEmailUnique(id, reqVO.getEmail()); 199 validateEmailUnique(id, reqVO.getEmail());
194 validateMobileUnique(id, reqVO.getMobile()); 200 validateMobileUnique(id, reqVO.getMobile());
195 - // 执行更新 201 +
  202 + // 2. 执行更新
196 userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id)); 203 userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id));
  204 +
  205 + // 3. 昵称 / 头像变化时,发送消息供下游订阅(如 IM 模块推 FRIEND_INFO_UPDATED)
  206 + publishUserProfileUpdatedIfChanged(oldUser, reqVO.getNickname(), reqVO.getAvatar());
  207 + }
  208 +
  209 + /**
  210 + * 仅当 nickname 或 avatar 跟旧值不一致时,发送 AdminUserProfileUpdateMessage
  211 + */
  212 + private void publishUserProfileUpdatedIfChanged(AdminUserDO oldUser, String newNickname, String newAvatar) {
  213 + boolean nicknameChanged = newNickname != null && !ObjUtil.equal(oldUser.getNickname(), newNickname);
  214 + boolean avatarChanged = newAvatar != null && !ObjUtil.equal(oldUser.getAvatar(), newAvatar);
  215 + if (!nicknameChanged && !avatarChanged) {
  216 + return;
  217 + }
  218 + adminUserProducer.sendUserProfileUpdateMessage(oldUser.getId(),
  219 + nicknameChanged ? newNickname : null,
  220 + avatarChanged ? newAvatar : null);
197 } 221 }
198 222
199 @Override 223 @Override