From b0ca5b75505461908c57e6ab75944731f1dd91d4 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 4 May 2026 18:23:58 +0800 Subject: [PATCH] ✨ 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 | 2 +- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendInfoUpdatedNotification.java | 4 ++-- yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/message/user/AdminUserProfileUpdateMessage.java | 31 +++++++++++++++++++++++++++++++ yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/user/AdminUserProducer.java | 33 +++++++++++++++++++++++++++++++++ yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java | 30 +++++++++++++++++++++++++++--- 6 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java create mode 100644 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/message/user/AdminUserProfileUpdateMessage.java create mode 100644 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/user/AdminUserProducer.java diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java index ceeb844..3a8b7e3 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java @@ -80,7 +80,7 @@ public enum ImMessageTypeEnum implements ArrayValuable { * 对应 OpenIM:FriendInfoUpdatedNotification 1209 * 对应自己的类:FriendInfoUpdatedNotification * 场景:B 改了昵称 / 头像后,推给 B 的所有好友 - * TODO @AI:未实现;待 system 模块改昵称 / 头像时回调 IM 模块批量推此事件,前端 dispatcher 已就绪 + * 触发:system 模块发 AdminUserProfileUpdateMessage,IM 消费者 AdminUserProfileUpdateConsumer 批量推此通知 */ FRIEND_INFO_UPDATED(1209, "好友资料变更", false, false), /** diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java new file mode 100644 index 0000000..40ddcab --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.im.mq.consumer.friend; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; +import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendInfoUpdatedNotification; +import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 监听 system 模块的 {@link AdminUserProfileUpdateMessage} 消息,向「资料被改的人」的所有好友推送 FRIEND_INFO_UPDATED 通知 + * + * @author 芋道源码 + */ +@Slf4j +@Component +public class AdminUserProfileUpdateConsumer { + + @Resource + private ImFriendService friendService; + @Resource + private ImWebSocketService websocketService; + + @EventListener + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步 + public void onMessage(AdminUserProfileUpdateMessage message) { + Long userId = message.getUserId(); + // 1. 找当前用户的所有有效好友(DISABLE 已删的不推) + List friends = friendService.getFriendList(userId); + if (friends.isEmpty()) { + return; + } + + // 2. 给每个好友的多端推 FRIEND_INFO_UPDATED;payload 里 operatorUserId / friendUserId 都是「资料被改的人」 + for (ImFriendDO friend : friends) { + if (CommonStatusEnum.isDisable(friend.getStatus())) { + continue; + } + FriendInfoUpdatedNotification payload = (FriendInfoUpdatedNotification) new FriendInfoUpdatedNotification() + .setOperatorUserId(userId).setFriendUserId(userId); + websocketService.sendPrivateMessageAsync(friend.getFriendUserId(), ImPrivateMessageDTO.ofFriendNotification( + ImMessageTypeEnum.FRIEND_INFO_UPDATED.getType(), userId, friend.getFriendUserId(), payload)); + } + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendInfoUpdatedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendInfoUpdatedNotification.java index a7be29d..8f81489 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendInfoUpdatedNotification.java +++ b/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; import lombok.Data; -// TODO @AI:1209 暂未实现;好友信息在 system 模块,需要 system 改昵称 / 头像后发 ApplicationEvent,IM 模块监听后回调发送此通知 /** * 好友资料变更通知(对端改了昵称 / 头像) *

- * 推送给资料被改的人的所有好友;前端清缓存 + 重拉资料 + * 由 AdminUserProfileUpdateConsumer 监听 system 模块的 AdminUserProfileUpdateMessage 后,批量推送给资料被改的人的所有好友; + * 前端 dispatcher 收到后调 loadFriendInfo 重拉资料 *

* 此处 friendUserId 表示「资料被更新的那个人」(即对端),与 operatorUserId 一致 */ diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/message/user/AdminUserProfileUpdateMessage.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/message/user/AdminUserProfileUpdateMessage.java new file mode 100644 index 0000000..13f3cc6 --- /dev/null +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/message/user/AdminUserProfileUpdateMessage.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.system.api.message.user; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 管理员用户资料(昵称 / 头像)变更消息 + *

+ * 仅当 nickname 或 avatar 真的发生变化时才发送;订阅方据此做下游分发, + * 例如 IM 模块向该用户的所有好友推送 FRIEND_INFO_UPDATED 通知 + * + * @author 芋道源码 + */ +@Data +public class AdminUserProfileUpdateMessage { + + /** + * 用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 变更后的昵称(无变更时为 null) + */ + private String nickname; + /** + * 变更后的头像(无变更时为 null) + */ + private String avatar; + +} diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/user/AdminUserProducer.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/user/AdminUserProducer.java new file mode 100644 index 0000000..4af3931 --- /dev/null +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/user/AdminUserProducer.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.system.mq.producer.user; + +import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +/** + * 管理员用户 Producer + * + * @author 芋道源码 + */ +@Slf4j +@Component +public class AdminUserProducer { + + @Resource + private ApplicationContext applicationContext; + + /** + * 发送 {@link AdminUserProfileUpdateMessage} 消息 + * + * @param userId 用户编号 + * @param nickname 变更后的昵称(无变更传 null) + * @param avatar 变更后的头像(无变更传 null) + */ + public void sendUserProfileUpdateMessage(Long userId, String nickname, String avatar) { + applicationContext.publishEvent(new AdminUserProfileUpdateMessage() + .setUserId(userId).setNickname(nickname).setAvatar(avatar)); + } + +} diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index 317d629..c2840b8 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; +import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.dept.PostService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; @@ -88,6 +89,9 @@ public class AdminUserServiceImpl implements AdminUserService { @Resource private ConfigApi configApi; + @Resource + private AdminUserProducer adminUserProducer; + @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}", @@ -158,6 +162,8 @@ public class AdminUserServiceImpl implements AdminUserService { userMapper.updateById(updateObj); // 2.2 更新岗位 updateUserPost(updateReqVO, updateObj); + // 2.3 昵称 / 头像变化时,发送消息供下游订阅(如 IM 模块推 FRIEND_INFO_UPDATED) + publishUserProfileUpdatedIfChanged(oldUser, updateReqVO.getNickname(), updateReqVO.getAvatar()); // 3. 记录操作日志上下文 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class)); @@ -188,12 +194,30 @@ public class AdminUserServiceImpl implements AdminUserService { @Override public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) { - // 校验正确性 - validateUserExists(id); + // 1. 校验正确性 + AdminUserDO oldUser = validateUserExists(id); validateEmailUnique(id, reqVO.getEmail()); validateMobileUnique(id, reqVO.getMobile()); - // 执行更新 + + // 2. 执行更新 userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id)); + + // 3. 昵称 / 头像变化时,发送消息供下游订阅(如 IM 模块推 FRIEND_INFO_UPDATED) + publishUserProfileUpdatedIfChanged(oldUser, reqVO.getNickname(), reqVO.getAvatar()); + } + + /** + * 仅当 nickname 或 avatar 跟旧值不一致时,发送 AdminUserProfileUpdateMessage + */ + private void publishUserProfileUpdatedIfChanged(AdminUserDO oldUser, String newNickname, String newAvatar) { + boolean nicknameChanged = newNickname != null && !ObjUtil.equal(oldUser.getNickname(), newNickname); + boolean avatarChanged = newAvatar != null && !ObjUtil.equal(oldUser.getAvatar(), newAvatar); + if (!nicknameChanged && !avatarChanged) { + return; + } + adminUserProducer.sendUserProfileUpdateMessage(oldUser.getId(), + nicknameChanged ? newNickname : null, + avatarChanged ? newAvatar : null); } @Override -- libgit2 0.21.4