Commit b0ca5b75505461908c57e6ab75944731f1dd91d4
1 parent
2804e5ed
✨ feat(im):实现 1209 FRIEND_INFO_UPDATED 推送(system 发 AdminUserProfileUpdateMessage…
…,IM Consumer 监听后批量推好友多端)
Showing
6 changed files
with
149 additions
and
6 deletions
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 | 80 | * 对应 OpenIM:FriendInfoUpdatedNotification 1209 |
| 81 | 81 | * 对应自己的类:FriendInfoUpdatedNotification |
| 82 | 82 | * 场景:B 改了昵称 / 头像后,推给 B 的所有好友 |
| 83 | - * TODO @AI:未实现;待 system 模块改昵称 / 头像时回调 IM 模块批量推此事件,前端 dispatcher 已就绪 | |
| 83 | + * 触发:system 模块发 AdminUserProfileUpdateMessage,IM 消费者 AdminUserProfileUpdateConsumer 批量推此通知 | |
| 84 | 84 | */ |
| 85 | 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 | 2 | |
| 3 | 3 | import lombok.Data; |
| 4 | 4 | |
| 5 | -// TODO @AI:1209 暂未实现;好友信息在 system 模块,需要 system 改昵称 / 头像后发 ApplicationEvent,IM 模块监听后回调发送此通知 | |
| 6 | 5 | /** |
| 7 | 6 | * 好友资料变更通知(对端改了昵称 / 头像) |
| 8 | 7 | * <p> |
| 9 | - * 推送给资料被改的人的所有好友;前端清缓存 + 重拉资料 | |
| 8 | + * 由 AdminUserProfileUpdateConsumer 监听 system 模块的 AdminUserProfileUpdateMessage 后,批量推送给资料被改的人的所有好友; | |
| 9 | + * 前端 dispatcher 收到后调 loadFriendInfo 重拉资料 | |
| 10 | 10 | * <p> |
| 11 | 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 | 25 | import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; |
| 26 | 26 | import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; |
| 27 | 27 | import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; |
| 28 | +import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; | |
| 28 | 29 | import cn.iocoder.yudao.module.system.service.dept.DeptService; |
| 29 | 30 | import cn.iocoder.yudao.module.system.service.dept.PostService; |
| 30 | 31 | import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; |
| ... | ... | @@ -88,6 +89,9 @@ public class AdminUserServiceImpl implements AdminUserService { |
| 88 | 89 | @Resource |
| 89 | 90 | private ConfigApi configApi; |
| 90 | 91 | |
| 92 | + @Resource | |
| 93 | + private AdminUserProducer adminUserProducer; | |
| 94 | + | |
| 91 | 95 | @Override |
| 92 | 96 | @Transactional(rollbackFor = Exception.class) |
| 93 | 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 | 162 | userMapper.updateById(updateObj); |
| 159 | 163 | // 2.2 更新岗位 |
| 160 | 164 | updateUserPost(updateReqVO, updateObj); |
| 165 | + // 2.3 昵称 / 头像变化时,发送消息供下游订阅(如 IM 模块推 FRIEND_INFO_UPDATED) | |
| 166 | + publishUserProfileUpdatedIfChanged(oldUser, updateReqVO.getNickname(), updateReqVO.getAvatar()); | |
| 161 | 167 | |
| 162 | 168 | // 3. 记录操作日志上下文 |
| 163 | 169 | LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class)); |
| ... | ... | @@ -188,12 +194,30 @@ public class AdminUserServiceImpl implements AdminUserService { |
| 188 | 194 | |
| 189 | 195 | @Override |
| 190 | 196 | public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) { |
| 191 | - // 校验正确性 | |
| 192 | - validateUserExists(id); | |
| 197 | + // 1. 校验正确性 | |
| 198 | + AdminUserDO oldUser = validateUserExists(id); | |
| 193 | 199 | validateEmailUnique(id, reqVO.getEmail()); |
| 194 | 200 | validateMobileUnique(id, reqVO.getMobile()); |
| 195 | - // 执行更新 | |
| 201 | + | |
| 202 | + // 2. 执行更新 | |
| 196 | 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 | 223 | @Override | ... | ... |