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,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 |