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
| @@ -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<ImConversationDO> { | @@ -29,6 +29,7 @@ public interface ImConversationMapper extends BaseMapperX<ImConversationDO> { | ||
| 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<ImMessageDO> { | @@ -36,6 +36,7 @@ public interface ImMessageMapper extends BaseMapperX<ImMessageDO> { | ||
| 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<ImMessageDO> { | @@ -47,6 +48,7 @@ public interface ImMessageMapper extends BaseMapperX<ImMessageDO> { | ||
| 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<ImMessageDO> { | @@ -56,4 +58,5 @@ public interface ImMessageMapper extends BaseMapperX<ImMessageDO> { | ||
| 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<ImSe | @@ -60,9 +61,10 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<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<ImSe | @@ -74,13 +76,17 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<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<ImSe | @@ -106,11 +112,14 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<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<ImSe | @@ -119,6 +128,7 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<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<ImSe | @@ -137,6 +147,7 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<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<ImSe | @@ -147,6 +158,7 @@ public class ImWebSocketMessageListener implements WebSocketMessageListener<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