Commit 0e79d8ec53ae4f234adc1ebe28ac5f86c52abb54
1 parent
9c1764e3
im:code review 消息发送的逻辑
Showing
21 changed files
with
74 additions
and
35 deletions
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/conversation/ImConversationTypeEnum.java
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java
| ... | ... | @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; |
| 6 | 6 | |
| 7 | 7 | import java.util.Arrays; |
| 8 | 8 | |
| 9 | +// TODO @hao:注释哈; | |
| 9 | 10 | @RequiredArgsConstructor |
| 10 | 11 | @Getter |
| 11 | 12 | public enum ImMessageStatusEnum implements IntArrayValuable { |
| ... | ... | @@ -16,7 +17,9 @@ public enum ImMessageStatusEnum implements IntArrayValuable { |
| 16 | 17 | DELETED(4, "已删除"), |
| 17 | 18 | RECALL(5, "已撤回"); |
| 18 | 19 | |
| 20 | + // TODO @hao:静态变量,和普通变量,最好空一行; | |
| 19 | 21 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ImMessageStatusEnum::getStatus).toArray(); |
| 22 | + // TODO @hao:注释哈; | |
| 20 | 23 | private final Integer status; |
| 21 | 24 | private final String name; |
| 22 | 25 | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/ImInboxPageReqVO.java
| ... | ... | @@ -11,6 +11,7 @@ import java.time.LocalDateTime; |
| 11 | 11 | |
| 12 | 12 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| 13 | 13 | |
| 14 | +// TODO @hao:这个是不是删除掉,应该不会有这个 VO 哈 | |
| 14 | 15 | @Schema(description = "管理后台 - 收件箱分页 Request VO") |
| 15 | 16 | @Data |
| 16 | 17 | @EqualsAndHashCode(callSuper = true) | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/ImInboxRespVO.java
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/inbox/vo/ImInboxSaveReqVO.java
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImMessageController.java
| ... | ... | @@ -24,7 +24,7 @@ import java.util.List; |
| 24 | 24 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 25 | 25 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 26 | 26 | |
| 27 | -@Tag(name = "管理后台 - 消息") | |
| 27 | +@Tag(name = "管理后台 - IM 消息") | |
| 28 | 28 | @RestController |
| 29 | 29 | @RequestMapping("/im/message") |
| 30 | 30 | @Validated |
| ... | ... | @@ -33,19 +33,20 @@ public class ImMessageController { |
| 33 | 33 | @Resource |
| 34 | 34 | private ImMessageService imMessageService; |
| 35 | 35 | |
| 36 | - @GetMapping("/get-message-by-sequence") | |
| 37 | - @Operation(summary = "拉取消息-增量拉取大于 seq 的消息") | |
| 36 | + @GetMapping("/get-message-by-sequence") // TODO @hao:list-by-sequence | |
| 37 | + @Operation(summary = "拉取消息-增量拉取大于 seq 的消息") // TODO @hao:可以改成 拉取大于 sequence 的消息列表 | |
| 38 | 38 | @Parameter(name = "sequence", description = "序号", required = true, example = "1") |
| 39 | 39 | @Parameter(name = "size", description = "条数", required = true, example = "10") |
| 40 | - @PreAuthorize("@ss.hasPermission('im:message:query')") | |
| 41 | - public CommonResult<List<ImMessageRespVO>> loadMessage( | |
| 42 | - @RequestParam("sequence") Long sequence, | |
| 43 | - @RequestParam("size") Integer size) { | |
| 40 | + @PreAuthorize("@ss.hasPermission('im:message:query')") // TODO @hao:权限可以删除哈; | |
| 41 | + public CommonResult<List<ImMessageRespVO>> loadMessage(@RequestParam("sequence") Long sequence, | |
| 42 | + @RequestParam("size") Integer size) { | |
| 43 | + // TODO @hao:方法名,可以改成 getMessageListBySequence | |
| 44 | + // TODO @hao:如果是返回列表,变量要用 messages, 或者 messageList,体现出是复数 | |
| 44 | 45 | List<ImMessageDO> message = imMessageService.loadMessage(getLoginUserId(), sequence, size); |
| 45 | 46 | return success(BeanUtils.toBean(message, ImMessageRespVO.class)); |
| 46 | 47 | } |
| 47 | 48 | |
| 48 | - | |
| 49 | + // TODO @hao:这个接口的使用场景是哪个哈? | |
| 49 | 50 | @GetMapping("/get-all-message") |
| 50 | 51 | @Operation(summary = "拉取全部消息") |
| 51 | 52 | @Parameter(name = "size", description = "条数", required = true, example = "10") |
| ... | ... | @@ -55,14 +56,13 @@ public class ImMessageController { |
| 55 | 56 | return success(BeanUtils.toBean(message, ImMessageRespVO.class)); |
| 56 | 57 | } |
| 57 | 58 | |
| 58 | - | |
| 59 | + // TODO @hao:是不是分页参数不太对哈?应该只有 conversationNo,sendTime 字段;然后,做链式分页的查询,不太适合传统的 pageSize + number 查询; | |
| 59 | 60 | @GetMapping("/page") |
| 60 | 61 | @Operation(summary = "查询聊天记录-分页") |
| 61 | - @PreAuthorize("@ss.hasPermission('im:message:query')") | |
| 62 | + @PreAuthorize("@ss.hasPermission('im:message:query')") // TODO @hao:权限可以删除哈; | |
| 62 | 63 | public CommonResult<PageResult<ImMessageRespVO>> getMessagePage(@Valid ImMessagePageReqVO pageReqVO) { |
| 63 | 64 | PageResult<ImMessageDO> messagePage = imMessageService.getMessagePage(pageReqVO); |
| 64 | 65 | return success(BeanUtils.toBean(messagePage, ImMessageRespVO.class)); |
| 65 | 66 | } |
| 66 | 67 | |
| 67 | - | |
| 68 | 68 | } |
| 69 | 69 | \ No newline at end of file | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationMapper.java
| ... | ... | @@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper; |
| 10 | 10 | import java.util.Optional; |
| 11 | 11 | |
| 12 | 12 | /** |
| 13 | - * 会话 Mapper | |
| 13 | + * IM 会话 Mapper | |
| 14 | 14 | * |
| 15 | 15 | * @author 芋道源码 |
| 16 | 16 | */ |
| ... | ... | @@ -29,6 +29,7 @@ public interface ImConversationMapper extends BaseMapperX<ImConversationDO> { |
| 29 | 29 | .orderByDesc(ImConversationDO::getId)); |
| 30 | 30 | } |
| 31 | 31 | |
| 32 | + // TODO @hao:1)no) {,要有空格哈;2)可以直接 selectOne(ImConversationDO::getNo, no) 父类做了封装 | |
| 32 | 33 | default ImConversationDO selectByNo(String no){ |
| 33 | 34 | return selectOne(new LambdaQueryWrapperX<ImConversationDO>().eq(ImConversationDO::getNo, no)); |
| 34 | 35 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/inbox/ImInboxMapper.java
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImMessageMapper.java
| ... | ... | @@ -36,6 +36,7 @@ public interface ImMessageMapper extends BaseMapperX<ImMessageDO> { |
| 36 | 36 | .orderByDesc(ImMessageDO::getId)); |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | + // TODO @hao:不链表哈;先从 ImInboxDO 查询出 messageId,然后再到 ImMessageDO 里 IN | |
| 39 | 40 | default List<ImMessageDO> getGreaterThanSequenceMessage(Long userId, Long sequence, Integer size) { |
| 40 | 41 | //查询 inbox 表中,大于 sequence 的消息,关联 message 表,按照 inbox 表 sequence 升序 |
| 41 | 42 | return selectJoinList(ImMessageDO.class, new MPJLambdaWrapper<ImMessageDO>() |
| ... | ... | @@ -47,6 +48,7 @@ public interface ImMessageMapper extends BaseMapperX<ImMessageDO> { |
| 47 | 48 | .last("limit 0," + size)); |
| 48 | 49 | } |
| 49 | 50 | |
| 51 | + // TODO @hao:在 dao 里,使用 selectListByUserId,查询用 select,条件用 by,这个算是 spring data 的 method dsl | |
| 50 | 52 | default List<ImMessageDO> getAllMessage(Long userId, Integer size) { |
| 51 | 53 | //查询 inbox 表中,100条消息,关联 message 表,按照 inbox 表 sequence 降序 |
| 52 | 54 | return selectJoinList(ImMessageDO.class, new MPJLambdaWrapper<ImMessageDO>() |
| ... | ... | @@ -56,4 +58,5 @@ public interface ImMessageMapper extends BaseMapperX<ImMessageDO> { |
| 56 | 58 | .orderByDesc(ImInboxDO::getSequence) |
| 57 | 59 | .last("limit 0," + size)); |
| 58 | 60 | } |
| 61 | + | |
| 59 | 62 | } |
| 60 | 63 | \ No newline at end of file | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/inbox/SequenceGeneratorRedisDao.java
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationService.java
| ... | ... | @@ -9,7 +9,7 @@ import jakarta.validation.Valid; |
| 9 | 9 | import java.util.List; |
| 10 | 10 | |
| 11 | 11 | /** |
| 12 | - * 会话 Service 接口 | |
| 12 | + * IM 会话 Service 接口 | |
| 13 | 13 | * |
| 14 | 14 | * @author 芋道源码 |
| 15 | 15 | */ |
| ... | ... | @@ -74,4 +74,5 @@ public interface ImConversationService { |
| 74 | 74 | * @param updateReqVO 更新信息 |
| 75 | 75 | */ |
| 76 | 76 | void updateLastReadTime(ImConversationSaveReqVO updateReqVO); |
| 77 | + | |
| 77 | 78 | } |
| 78 | 79 | \ No newline at end of file | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationServiceImpl.java
| ... | ... | @@ -18,7 +18,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU |
| 18 | 18 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.CONVERSATION_NOT_EXISTS; |
| 19 | 19 | |
| 20 | 20 | /** |
| 21 | - * 会话 Service 实现类 | |
| 21 | + * IM 会话 Service 实现类 | |
| 22 | 22 | * |
| 23 | 23 | * @author 芋道源码 |
| 24 | 24 | */ |
| ... | ... | @@ -26,18 +26,19 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.CONVERSATION_N |
| 26 | 26 | @Validated |
| 27 | 27 | public class ImConversationServiceImpl implements ImConversationService { |
| 28 | 28 | |
| 29 | + // TODO @hao: 自己模块的注入,不用带 im 前缀哈; | |
| 29 | 30 | @Resource |
| 30 | 31 | private ImConversationMapper imConversationMapper; |
| 31 | 32 | |
| 33 | + // TODO @hao: 这个方法,是不是不需要哈 | |
| 32 | 34 | @Override |
| 33 | 35 | public Long createConversation(ImConversationSaveReqVO createReqVO) { |
| 34 | - // 插入 | |
| 35 | 36 | ImConversationDO conversation = BeanUtils.toBean(createReqVO, ImConversationDO.class); |
| 36 | 37 | imConversationMapper.insert(conversation); |
| 37 | - // 返回 | |
| 38 | 38 | return conversation.getId(); |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | + // TODO @hao: 这个方法,是不是不需要哈 | |
| 41 | 42 | @Override |
| 42 | 43 | public void updateConversation(ImConversationSaveReqVO updateReqVO) { |
| 43 | 44 | // 校验存在 |
| ... | ... | @@ -47,6 +48,7 @@ public class ImConversationServiceImpl implements ImConversationService { |
| 47 | 48 | imConversationMapper.updateById(updateObj); |
| 48 | 49 | } |
| 49 | 50 | |
| 51 | + // TODO @hao: 考虑到可能和端上不同步,可以不校验是不是存储。另外,不基于 id 删除。要基于 no + userId 删除哈。说白了,对端上要屏蔽 id 字段 | |
| 50 | 52 | @Override |
| 51 | 53 | public void deleteConversation(Long id) { |
| 52 | 54 | // 校验存在 |
| ... | ... | @@ -86,6 +88,11 @@ public class ImConversationServiceImpl implements ImConversationService { |
| 86 | 88 | createOrUpdateConversation(updateReqVO); |
| 87 | 89 | } |
| 88 | 90 | |
| 91 | + // TODO @hao:updateTop 和 updateLastReadTime 使用独立的逻辑实现,不使用统一的 ImConversationSaveReqVO; | |
| 92 | + // TODO 大体步骤建议: | |
| 93 | + // 1. 先 getOrderCreateConversation,查询会话,不存在则插入; | |
| 94 | + // 2. 更新对应的字段 | |
| 95 | + // 3. 做对应更新的 notify 推送 | |
| 89 | 96 | private void createOrUpdateConversation(ImConversationSaveReqVO updateReqVO) { |
| 90 | 97 | // 操作会话(已读、置顶)时,才会延迟创建,要先判断是否存在,根据 no 查询是否存在,不存在则新增 |
| 91 | 98 | ImConversationDO conversation = imConversationMapper.selectByNo(updateReqVO.getNo()); | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/inbox/ImInboxService.java
| ... | ... | @@ -6,8 +6,9 @@ import cn.iocoder.yudao.module.im.controller.admin.inbox.vo.ImInboxSaveReqVO; |
| 6 | 6 | import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO; |
| 7 | 7 | import jakarta.validation.Valid; |
| 8 | 8 | |
| 9 | +// TODO @hao:不用的方法,删除下哈; | |
| 9 | 10 | /** |
| 10 | - * 收件箱 Service 接口 | |
| 11 | + * IM 收件箱 Service 接口 | |
| 11 | 12 | * |
| 12 | 13 | * @author 芋道源码 |
| 13 | 14 | */ | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/inbox/ImInboxServiceImpl.java
| ... | ... | @@ -27,10 +27,8 @@ public class ImInboxServiceImpl implements ImInboxService { |
| 27 | 27 | |
| 28 | 28 | @Override |
| 29 | 29 | public Long createInbox(ImInboxSaveReqVO createReqVO) { |
| 30 | - // 插入 | |
| 31 | 30 | ImInboxDO inbox = BeanUtils.toBean(createReqVO, ImInboxDO.class); |
| 32 | 31 | imInboxMapper.insert(inbox); |
| 33 | - // 返回 | |
| 34 | 32 | return inbox.getId(); |
| 35 | 33 | } |
| 36 | 34 | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImMessageService.java
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/message/ImMessageServiceImpl.java
| ... | ... | @@ -84,26 +84,31 @@ public class ImMessageServiceImpl implements ImMessageService { |
| 84 | 84 | } |
| 85 | 85 | |
| 86 | 86 | private ImMessageDO saveImMessageDO(ImSendMessage message, Long fromUserId) { |
| 87 | + // TODO @hao:可以搞成 messageDO | |
| 88 | + // TODO @hao:链式调用 | |
| 87 | 89 | ImMessageDO imMessageDO = new ImMessageDO(); |
| 88 | 90 | imMessageDO.setClientMessageId(message.getClientMessageId()); |
| 89 | 91 | imMessageDO.setSenderId(fromUserId); |
| 90 | 92 | imMessageDO.setReceiverId(message.getReceiverId()); |
| 91 | - //查询发送人昵称和发送人头像 | |
| 93 | + // 查询发送人昵称和发送人头像 | |
| 92 | 94 | AdminUserRespDTO user = adminUserApi.getUser(fromUserId); |
| 93 | 95 | imMessageDO.setSenderNickname(user.getNickname()); |
| 94 | 96 | imMessageDO.setSenderAvatar(user.getAvatar()); |
| 95 | 97 | imMessageDO.setConversationType(message.getConversationType()); |
| 96 | 98 | imMessageDO.setContentType(message.getContentType()); |
| 97 | - //单聊:s_{userId}_{targetId} 群聊:群聊:g_{groupId} | |
| 99 | + // 单聊:s_{userId}_{targetId} 群聊:群聊:g_{groupId} | |
| 100 | + // TODO @hao:这里的 conversationNo 的生成,可以写到工具类里,或者干脆放到 ImConversationTypeEnum 里; | |
| 98 | 101 | if (message.getConversationType().equals(ImConversationTypeEnum.PRIVATE.getType())) { |
| 99 | 102 | imMessageDO.setConversationNo("s_" + fromUserId + "_" + message.getReceiverId()); |
| 100 | 103 | } else if (message.getConversationType().equals(ImConversationTypeEnum.GROUP.getType())) { |
| 101 | 104 | imMessageDO.setConversationNo("g_" + message.getReceiverId()); |
| 102 | 105 | } |
| 103 | 106 | imMessageDO.setContent(message.getContent()); |
| 104 | - //消息来源 100-用户发送;200-系统发送(一般是通知);不能为空 | |
| 107 | + // TODO @hao:枚举哈; | |
| 108 | + // 消息来源 100-用户发送;200-系统发送(一般是通知);不能为空 | |
| 105 | 109 | imMessageDO.setSendFrom(100); |
| 106 | 110 | imMessageDO.setSendTime(TimeUtil.now()); |
| 111 | + // TODO @hao:服务端的消息,就是默认生成哈; | |
| 107 | 112 | imMessageDO.setMessageStatus(ImMessageStatusEnum.SENDING.getStatus()); |
| 108 | 113 | imMessageMapper.insert(imMessageDO); |
| 109 | 114 | return imMessageDO; |
| ... | ... | @@ -111,7 +116,7 @@ public class ImMessageServiceImpl implements ImMessageService { |
| 111 | 116 | |
| 112 | 117 | @Override |
| 113 | 118 | public void updateMessageStatus(Long messageId, Integer messageStatus) { |
| 114 | - //校验 id 是否存在 | |
| 119 | + // 校验 id 是否存在 | |
| 115 | 120 | validateMessageExists(messageId); |
| 116 | 121 | //更新消息状态 |
| 117 | 122 | ImMessageDO imMessageDO = new ImMessageDO(); | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/ImWebSocketMessageListener.java
| ... | ... | @@ -23,6 +23,7 @@ import org.springframework.web.socket.WebSocketSession; |
| 23 | 23 | |
| 24 | 24 | import java.util.List; |
| 25 | 25 | |
| 26 | +// TODO @hao:消息发送,使用 http 上行。因为在 cloud 框架下,我们比较难去 Listener。因为 im-server 不会自己启动 websocket 路径 | |
| 26 | 27 | /** |
| 27 | 28 | * WebSocket im |
| 28 | 29 | * |
| ... | ... | @@ -60,9 +61,10 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSe |
| 60 | 61 | if (message.getConversationType().equals(ImConversationTypeEnum.PRIVATE.getType())) { |
| 61 | 62 | handlePrivateMessage(fromUserId, message); |
| 62 | 63 | } else if (message.getConversationType().equals(ImConversationTypeEnum.GROUP.getType())) { |
| 63 | - //处理群聊消息 | |
| 64 | + // 处理群聊消息 | |
| 64 | 65 | handleGroupMessage(fromUserId, message); |
| 65 | 66 | } else { |
| 67 | + // TODO @hao:是不是应该会话类型哈?然后打印这种错误日志,最好把 message 整个打进去,这样更好定位完整的消息; | |
| 66 | 68 | log.error("[onMessage][消息类型({}) 未支持]", message.getConversationType()); |
| 67 | 69 | } |
| 68 | 70 | } |
| ... | ... | @@ -74,13 +76,17 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSe |
| 74 | 76 | * @param message 发送的IM消息 |
| 75 | 77 | */ |
| 76 | 78 | private void handleGroupMessage(Long fromUserId, ImSendMessage message) { |
| 79 | + // TODO @hao:群存在; | |
| 80 | + // TODO @hao:发送人在群里; | |
| 77 | 81 | ImMessageDO imMessageDO = imMessageService.saveGroupMessage(message, fromUserId); // 保存群聊消息 |
| 78 | 82 | Long groupId = message.getReceiverId(); |
| 79 | 83 | |
| 80 | 84 | // 发送消息给群聊成员 |
| 85 | + // TODO @hao:变量名可以叫 groupMembers;一般不用 DO 后缀,除非为了区分; | |
| 81 | 86 | List<GroupMemberDO> groupMemberDOList = imGroupMemberService.selectByGroupId(groupId); |
| 82 | 87 | groupMemberDOList.forEach(groupMemberDO -> { |
| 83 | - //过滤掉自己 | |
| 88 | + // 过滤掉自己 | |
| 89 | + // TODO @hao:98 到 99 的代码,是不是可以融合到这里来; | |
| 84 | 90 | if (groupMemberDO.getUserId().equals(fromUserId)) { |
| 85 | 91 | return; |
| 86 | 92 | } |
| ... | ... | @@ -106,11 +112,14 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSe |
| 106 | 112 | * @param message 发送的IM消息 |
| 107 | 113 | */ |
| 108 | 114 | private void handlePrivateMessage(Long fromUserId, ImSendMessage message) { |
| 115 | + // TODO @hao:需要校验 receiverId 存在; | |
| 109 | 116 | ImMessageDO imMessageDO = imMessageService.savePrivateMessage(message, fromUserId); // 保存私人消息 |
| 110 | 117 | Long receiverId = message.getReceiverId(); |
| 111 | 118 | |
| 119 | + // TODO @hao:sequenceGeneratorRedisDao.generateSequence 和 createAndSaveInbox 步骤,要加锁;不然会有并发问题;如果有疑问,可以微信招我沟通哈;另外,创建 seq、保存收件箱、发送,可以统一封装到 imInboxService 里哈;这样,后续要发某个消息,就是丢个 message 对象进去; | |
| 112 | 120 | Long fromUserSequence = sequenceGeneratorRedisDao.generateSequence(fromUserId); // 生成发送者序列 |
| 113 | 121 | Long fromUserInboxId = createAndSaveInbox(fromUserId, imMessageDO.getId(), fromUserSequence); // 创建并保存发送者收件箱 |
| 122 | + | |
| 114 | 123 | Long receiverSequence = sequenceGeneratorRedisDao.generateSequence(message.getReceiverId()); // 生成接收者序列 |
| 115 | 124 | Long receiverInboxId = createAndSaveInbox(message.getReceiverId(), imMessageDO.getId(), receiverSequence); // 创建并保存接收者收件箱 |
| 116 | 125 | |
| ... | ... | @@ -119,6 +128,7 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSe |
| 119 | 128 | sendMessage(receiverId, receiverInboxId, imMessageDO, message, receiverSequence); |
| 120 | 129 | |
| 121 | 130 | // 更新消息状态为成功 |
| 131 | + // TODO @hao:后端消息的发送,默认就是 success 哈,不要区分 status;status 更多是前端使用的; | |
| 122 | 132 | imMessageService.updateMessageStatus(imMessageDO.getId(), ImMessageStatusEnum.SUCCESS.getStatus()); |
| 123 | 133 | // 保存私人会话,只有在 client 操作会话(已读、置顶)时,才会延迟创建 |
| 124 | 134 | //imConversationService.savePrivateConversation(fromUserId, receiverId); |
| ... | ... | @@ -137,6 +147,7 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSe |
| 137 | 147 | return imInboxService.createInbox(inboxSaveReqVO); // 创建收件箱 |
| 138 | 148 | } |
| 139 | 149 | |
| 150 | + // TODO @hao:这个使用 WebSocketSenderApi 发送哈; | |
| 140 | 151 | /** |
| 141 | 152 | * 发送消息 |
| 142 | 153 | * |
| ... | ... | @@ -147,6 +158,7 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<ImSe |
| 147 | 158 | * @param sequence 序列 |
| 148 | 159 | */ |
| 149 | 160 | private void sendMessage(Long fromUserId, Long inboxId, ImMessageDO imMessageDO, ImSendMessage message, Long sequence) { |
| 161 | + // TODO @hao:不要每个字段 set 占一行;想同类型的字段用一行,然后链式 set,会更简洁干净。 | |
| 150 | 162 | ImReceiveMessage receiveMessage = new ImReceiveMessage(); // 创建接收消息 |
| 151 | 163 | receiveMessage.setFromId(fromUserId); // 设置发送者ID |
| 152 | 164 | receiveMessage.setConversationType(message.getConversationType()); // 设置会话类型 | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/message/ImReceiveMessage.java
| 1 | 1 | package cn.iocoder.yudao.module.im.websocket.message; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.im.dal.dataobject.message.body.ImMessageBody; | |
| 4 | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | -import jakarta.validation.constraints.NotNull; | |
| 6 | 4 | import lombok.Data; |
| 7 | 5 | |
| 8 | 6 | import java.time.LocalDateTime; |
| ... | ... | @@ -17,22 +15,25 @@ public class ImReceiveMessage { |
| 17 | 15 | @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| 18 | 16 | private Long fromId; // 根据 conversationType 区分 |
| 19 | 17 | |
| 18 | + // TODO @hao:昵称和头像,也直接发给接收人好了; | |
| 19 | + | |
| 20 | + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12454") | |
| 21 | + private Long messageId; | |
| 22 | + | |
| 20 | 23 | @Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 21 | 24 | private Integer contentType; // 参见 ImMessageTypeEnum 枚举 |
| 22 | 25 | |
| 23 | 26 | @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED) |
| 24 | 27 | private String content; |
| 25 | 28 | |
| 26 | - @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12454") | |
| 27 | - private Long messageId; | |
| 28 | - | |
| 29 | 29 | @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 30 | 30 | private LocalDateTime sendTime; |
| 31 | 31 | |
| 32 | + // TODO @hao:inboxId 不需要哈; | |
| 32 | 33 | @Schema(description = "收件箱编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18389") |
| 33 | 34 | private Long inboxId; |
| 34 | 35 | |
| 35 | - @Schema(description = "序号,按照 user 递增", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 36 | + @Schema(description = "序号", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 36 | 37 | private Long sequence; |
| 37 | 38 | |
| 38 | 39 | } |
| 39 | 40 | \ No newline at end of file | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/websocket/message/ImSendMessage.java
| 1 | 1 | package cn.iocoder.yudao.module.im.websocket.message; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.im.dal.dataobject.message.body.ImMessageBody; | |
| 4 | 3 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | -import jakarta.validation.constraints.NotEmpty; | |
| 6 | 4 | import lombok.Data; |
| 7 | 5 | |
| 8 | -@Schema(description = "管理后台 - 消息发送 send") | |
| 6 | +@Schema(description = "管理后台 - IM 消息发送 send") | |
| 9 | 7 | @Data |
| 10 | 8 | public class ImSendMessage { |
| 11 | 9 | |
| ... | ... | @@ -13,6 +11,7 @@ public class ImSendMessage { |
| 13 | 11 | private String clientMessageId; |
| 14 | 12 | |
| 15 | 13 | @Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 14 | + // TODO @hao:用下 InEnum 校验。这样可以少写 15 的注释哈;类似 contentType 也是; | |
| 16 | 15 | private Integer conversationType; // 对应 ImConversationTypeEnum 枚举 |
| 17 | 16 | |
| 18 | 17 | @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/resources/mapper/conversation/ImConversationMapper.xml
| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| 3 | 3 | <mapper namespace="cn.iocoder.yudao.module.im.dal.mysql.conversation.ImConversationMapper"> |
| 4 | 4 | |
| 5 | + <!-- TODO @hao:xml 如果用不到,可以先删除哈 --> | |
| 5 | 6 | <!-- |
| 6 | 7 | 一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。 |
| 7 | 8 | 无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。 | ... | ... |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java