Commit 0e79d8ec53ae4f234adc1ebe28ac5f86c52abb54

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