Commit 285a431616e8cfcf55f6f473cf0ef918c9799bc3

Authored by puhui999
1 parent d89a19a9

MALL-KEFU: 根据代码评审完善相关方法

Showing 19 changed files with 144 additions and 211 deletions
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.enums;
  2 +
  3 +/**
  4 + * websocket 消息类型枚举类
  5 + *
  6 + * @author HUIHUI
  7 + */
  8 +public interface WebSocketMessageTypeConstants {
  9 +
  10 + //======================= mall 客服 =======================
  11 +
  12 + String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型
  13 + String KEFU_MESSAGE_ADMIN_READ = "kefu_message_read_status_change"; // 客服消息管理员已读
  14 +
  15 +}
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java
... ... @@ -19,6 +19,7 @@ public enum KeFuMessageContentTypeEnum implements IntArrayValuable {
19 19 IMAGE(2, "图片消息"),
20 20 VOICE(3, "语音消息"),
21 21 VIDEO(4, "视频消息"),
  22 + SYSTEM(5, "系统消息"),
22 23 // ========== 商城特殊消息 ==========
23 24 PRODUCT(10, "商品消息"),
24 25 ORDER(11, "订单消息");
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java
... ... @@ -27,12 +27,11 @@ public class KeFuConversationController {
27 27 @Resource
28 28 private KeFuConversationService conversationService;
29 29  
30   - // TODO @puhui999:updateConversationPinned
31   - @PostMapping("/update-pinned")
  30 + @PostMapping("/update-conversation-pinned")
32 31 @Operation(summary = "置顶客服会话")
33 32 @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')")
34   - public CommonResult<Boolean> updatePinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) {
35   - conversationService.updatePinned(updateReqVO);
  33 + public CommonResult<Boolean> updateConversationPinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) {
  34 + conversationService.updateAdminPinned(updateReqVO);
36 35 return success(true);
37 36 }
38 37  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.admin.kefu;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
... ... @@ -18,7 +19,6 @@ import org.springframework.validation.annotation.Validated;
18 19 import org.springframework.web.bind.annotation.*;
19 20  
20 21 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
21   -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
22 22  
23 23 @Tag(name = "管理后台 - 客服消息")
24 24 @RestController
... ... @@ -37,11 +37,11 @@ public class KeFuMessageController {
37 37 }
38 38  
39 39 @PutMapping("/update-read-status")
40   - @Operation(summary = "更新客服消息已读状态")
  40 + @Operation(summary = "更新会员客服消息已读状态")
41 41 @Parameter(name = "conversationId", description = "会话编号", required = true)
42 42 @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')")
43 43 public CommonResult<Boolean> updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) {
44   - messageService.updateKefuMessageReadStatus(conversationId, getLoginUserId());
  44 + messageService.updateKefuMessageReadStatus(conversationId);
45 45 return success(true);
46 46 }
47 47  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java
1 1 package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.*;
  4 +import lombok.Data;
5 5  
6 6 import java.time.LocalDateTime;
7 7  
... ... @@ -18,24 +18,22 @@ public class KeFuConversationRespVO {
18 18 @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED)
19 19 private LocalDateTime lastMessageTime;
20 20  
21   - // TODO @puhui999:, 缺了空格哈
22   -
23   - @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊")
  21 + @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "嗨,您好啊")
24 22 private String lastMessageContent;
25 23  
26   - @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED,example = "1")
  24 + @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
27 25 private Integer lastMessageContentType;
28 26  
29   - @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED,example = "false")
  27 + @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
30 28 private Boolean adminPinned;
31 29  
32   - @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true")
  30 + @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
33 31 private Boolean userDeleted;
34 32  
35   - @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true")
  33 + @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
36 34 private Boolean adminDeleted;
37 35  
38   - @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6")
  36 + @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "6")
39 37 private Integer adminUnreadMessageCount;
40 38  
41 39 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java
... ... @@ -6,8 +6,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 6  
7 7 @Schema(description = "管理后台 - 客服消息分页 Request VO")
8 8 @Data
9   -// TODO @puhui999:不用 @EqualsAndHashCode 哈
10   -@EqualsAndHashCode(callSuper = true)
11 9 @ToString(callSuper = true)
12 10 public class KeFuMessagePageReqVO extends PageParam {
13 11  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java deleted
1   -package cn.iocoder.yudao.module.promotion.controller.app.kefu;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5   -import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
6   -import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.conversation.AppKeFuConversationRespVO;
7   -import cn.iocoder.yudao.module.promotion.service.kefu.KeFuConversationService;
8   -import io.swagger.v3.oas.annotations.Operation;
9   -import io.swagger.v3.oas.annotations.tags.Tag;
10   -import jakarta.annotation.Resource;
11   -import org.springframework.validation.annotation.Validated;
12   -import org.springframework.web.bind.annotation.GetMapping;
13   -import org.springframework.web.bind.annotation.RequestMapping;
14   -import org.springframework.web.bind.annotation.RestController;
15   -
16   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
17   -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
18   -
19   -@Tag(name = "用户 APP - 客户会话")
20   -@RestController
21   -@RequestMapping("/promotion/kefu-conversation")
22   -@Validated
23   -public class AppKeFuConversationController {
24   -
25   - @Resource
26   - private KeFuConversationService conversationService;
27   -
28   - // TODO @puhui999:接口名不对噢;
29   - @GetMapping("/get")
30   - @Operation(summary = "获得客服会话")
31   - @PreAuthenticated
32   - public CommonResult<AppKeFuConversationRespVO> getDiyPage() {
33   - // TODO @puhui999:建议获取;和转换,分成 2 个哈;干净一些;
34   - return success(BeanUtils.toBean(conversationService.getOrCreateConversation(getLoginUserId()), AppKeFuConversationRespVO.class));
35   - }
36   -
37   -}
38 0 \ No newline at end of file
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.app.kefu;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
... ... @@ -35,7 +36,8 @@ public class AppKeFuMessageController {
35 36 @Operation(summary = "发送客服消息")
36 37 @PreAuthenticated
37 38 public CommonResult<Long> createKefuMessage(@Valid @RequestBody AppKeFuMessageSendReqVO sendReqVO) {
38   - return success(kefuMessageService.sendKefuMessage(BeanUtils.toBean(sendReqVO, KeFuMessageSendReqVO.class)));
  39 + sendReqVO.setSenderId(getLoginUserId()).setSenderType(UserTypeEnum.MEMBER.getValue()); // 设置用户编号和类型
  40 + return success(kefuMessageService.sendKefuMessage(sendReqVO));
39 41 }
40 42  
41 43 @PutMapping("/update-read-status")
... ... @@ -43,7 +45,7 @@ public class AppKeFuMessageController {
43 45 @Parameter(name = "conversationId", description = "会话编号", required = true)
44 46 @PreAuthenticated
45 47 public CommonResult<Boolean> updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) {
46   - kefuMessageService.updateKefuMessageReadStatus(conversationId, getLoginUserId());
  48 + kefuMessageService.updateKefuMessageReadStatus(conversationId);
47 49 return success(true);
48 50 }
49 51  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java deleted
1   -package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.conversation;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.*;
5   -
6   -import java.time.LocalDateTime;
7   -
8   -@Schema(description = "用户 App - 客服会话 Response VO")
9   -@Data
10   -public class AppKeFuConversationRespVO {
11   -
12   - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24988")
13   - private Long id;
14   -
15   - @Schema(description = "会话所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "8300")
16   - private Long userId;
17   -
18   - @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00")
19   - private LocalDateTime lastMessageTime;
20   -
21   - @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊")
22   - private String lastMessageContent;
23   -
24   - @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED,example = "1")
25   - private Integer lastMessageContentType;
26   -
27   - @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED,example = "false")
28   - private Boolean adminPinned;
29   -
30   - @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true")
31   - private Boolean userDeleted;
32   -
33   - @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true")
34   - private Boolean adminDeleted;
35   -
36   - @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6")
37   - private Integer adminUnreadMessageCount;
38   -
39   - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00")
40   - private LocalDateTime createTime;
41   -
42   -}
43 0 \ No newline at end of file
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java
... ... @@ -8,8 +8,6 @@ import lombok.ToString;
8 8  
9 9 @Schema(description = "用户 App - 客服消息分页 Request VO")
10 10 @Data
11   -// TODO @puhui999:不用 @EqualsAndHashCode 哈
12   -@EqualsAndHashCode(callSuper = true)
13 11 @ToString(callSuper = true)
14 12 public class AppKeFuMessagePageReqVO extends PageParam {
15 13  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java
... ... @@ -12,30 +12,14 @@ public class AppKeFuMessageSendReqVO {
12 12 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
13 13 private Long id;
14 14  
15   - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580")
16   - @NotNull(message = "会话编号不能为空")
17   - private Long conversationId;
18   -
19 15 @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571")
20   - @NotNull(message = "发送人编号不能为空")
21 16 private Long senderId;
22   -
23 17 @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
24   - @NotNull(message = "发送人类型不能为空")
25 18 private Integer senderType;
26 19  
27   - @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124")
28   - @NotNull(message = "接收人编号不能为空")
29   - private Long receiverId;
30   -
31   - @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
32   - @NotNull(message = "接收人类型不能为空")
33   - private Integer receiverType;
34   -
35 20 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
36 21 @NotNull(message = "消息类型不能为空")
37 22 private Integer contentType;
38   -
39 23 @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED)
40 24 @NotEmpty(message = "消息不能为空")
41 25 private String content;
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo;
0 2 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java
... ... @@ -16,24 +16,22 @@ import java.util.List;
16 16 @Mapper
17 17 public interface KeFuConversationMapper extends BaseMapperX<KeFuConversationDO> {
18 18  
19   - // TODO @puhui999:排序可以交给前端,或者 controller;数据库的计算尽量少哈;
20   - default List<KeFuConversationDO> selectListWithSort() {
  19 + default List<KeFuConversationDO> selectConversationList() {
21 20 return selectList(new LambdaQueryWrapperX<KeFuConversationDO>()
22 21 .eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
23   - .orderByDesc(KeFuConversationDO::getAdminPinned) // 置顶优先
24 22 .orderByDesc(KeFuConversationDO::getCreateTime));
25 23 }
26 24  
27   - // TODO @puhui999:是不是置零,用 update 就 ok 拉;然后单独搞个 +1 的方法;
28   - default void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) {
29   - LambdaUpdateWrapper<KeFuConversationDO> updateWrapper = new LambdaUpdateWrapper<>();
30   - updateWrapper.eq(KeFuConversationDO::getId, id);
31   - if (count != null && count > 0) { // 情况一:会员发送消息时增加管理员的未读消息数
32   - updateWrapper.setSql("admin_unread_message_count = admin_unread_message_count + 1");
33   - } else { // 情况二:管理员已读后重置
34   - updateWrapper.set(KeFuConversationDO::getAdminUnreadMessageCount, 0);
35   - }
36   - update(updateWrapper);
  25 + default void updateAdminUnreadMessageCountWithZero(Long id) {
  26 + update(new LambdaUpdateWrapper<KeFuConversationDO>()
  27 + .eq(KeFuConversationDO::getId, id)
  28 + .set(KeFuConversationDO::getAdminUnreadMessageCount, 0));
  29 + }
  30 +
  31 + default void updateAdminUnreadMessageCount(Long id) {
  32 + update(new LambdaUpdateWrapper<KeFuConversationDO>()
  33 + .eq(KeFuConversationDO::getId, id)
  34 + .setSql("admin_unread_message_count = admin_unread_message_count + 1"));
37 35 }
38 36  
39 37 }
40 38 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java
... ... @@ -26,20 +26,15 @@ public interface KeFuMessageMapper extends BaseMapperX&lt;KeFuMessageDO&gt; {
26 26 .orderByDesc(KeFuMessageDO::getId));
27 27 }
28 28  
29   - default List<KeFuMessageDO> selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId,
30   - Long receiverId,
31   - Boolean readStatus) {
  29 + default List<KeFuMessageDO> selectListByConversationIdAndReadStatus(Long conversationId, Boolean readStatus) {
32 30 return selectList(new LambdaQueryWrapper<KeFuMessageDO>()
33 31 .eq(KeFuMessageDO::getConversationId, conversationId)
34   - .eq(KeFuMessageDO::getReceiverId, receiverId)
35 32 .eq(KeFuMessageDO::getReadStatus, readStatus));
36 33 }
37 34  
38   - // TODO @puhui999:status 拼写不对哈;ps:是不是搞个 ids + entity 的更新,更通用点
39   - default void updateReadStstusBatchByIds(Collection<Long> ids, Boolean readStatus) {
40   - update(new LambdaUpdateWrapper<KeFuMessageDO>()
41   - .in(KeFuMessageDO::getId, ids)
42   - .set(KeFuMessageDO::getReadStatus, readStatus));
  35 + default void updateReadStatusBatchByIds(Collection<Long> ids, KeFuMessageDO keFuMessageDO) {
  36 + update(keFuMessageDO, new LambdaUpdateWrapper<KeFuMessageDO>()
  37 + .in(KeFuMessageDO::getId, ids));
43 38 }
44 39  
45 40 }
46 41 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java
... ... @@ -2,11 +2,10 @@ package cn.iocoder.yudao.module.promotion.service.kefu;
2 2  
3 3 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
4 4 import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
  5 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
5 6  
6   -import java.time.LocalDateTime;
7 7 import java.util.List;
8 8  
9   -// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下
10 9 /**
11 10 * 客服会话 Service 接口
12 11 *
... ... @@ -15,48 +14,43 @@ import java.util.List;
15 14 public interface KeFuConversationService {
16 15  
17 16 /**
18   - * 删除客服会话
  17 + * 【管理员】删除客服会话
19 18 *
20 19 * @param id 编号
21 20 */
22 21 void deleteKefuConversation(Long id);
23 22  
24   - // TODO @puhui999:是不是方法名,体现出更新的是管理员的置顶哈
25 23 /**
26   - * 客服会话置顶
  24 + * 【管理员】客服会话置顶
27 25 *
28 26 * @param updateReqVO 请求
29 27 */
30   - void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO);
  28 + void updateAdminPinned(KeFuConversationUpdatePinnedReqVO updateReqVO);
31 29  
32   - // TODO @puhui999:updateConversationLastMessage 会好点哈
33 30 /**
34 31 * 更新会话客服消息冗余信息
35 32 *
36   - * @param id 编号
37   - * @param lastMessageTime 最后聊天时间
38   - * @param lastMessageContent 最后聊天内容
39   - * @param lastMessageContentType 最后聊天内容类型
  33 + * @param kefuMessage 消息
40 34 */
41   - void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType);
  35 + void updateConversationLastMessage(KeFuMessageDO kefuMessage);
42 36  
43 37 /**
44   - * 更新管理员未读消息数
  38 + * 【管理员】将管理员未读消息计数更新为零
45 39 *
46   - * @param id 编号
47   - * @param count 数量:0 则重置 1 则消息数加一
  40 + * @param id 编号
48 41 */
49   - void updateAdminUnreadMessageCountByConversationId(Long id, Integer count);
  42 + void updateAdminUnreadMessageCountWithZero(Long id);
50 43  
51 44 /**
52   - * 更新会话对于管理员是否可见
  45 + * 【管理员】更新会话对于管理员是否可见
53 46 *
  47 + * @param id 编号
54 48 * @param adminDeleted 管理员是否可见
55 49 */
56 50 void updateConversationAdminDeleted(Long id, Boolean adminDeleted);
57 51  
58 52 /**
59   - * 获得客服会话列表
  53 + * 【管理员】获得客服会话列表
60 54 *
61 55 * @return 会话列表
62 56 */
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java
1 1 package cn.iocoder.yudao.module.promotion.service.kefu;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
3 5 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
4 6 import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
  7 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
5 8 import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuConversationMapper;
6 9 import cn.iocoder.yudao.module.promotion.enums.kehu.KeFuMessageContentTypeEnum;
7 10 import jakarta.annotation.Resource;
8 11 import org.springframework.stereotype.Service;
  12 +import org.springframework.transaction.annotation.Transactional;
9 13 import org.springframework.validation.annotation.Validated;
10 14  
11 15 import java.time.LocalDateTime;
... ... @@ -36,19 +40,35 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
36 40 }
37 41  
38 42 @Override
39   - public void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO) {
  43 + public void updateAdminPinned(KeFuConversationUpdatePinnedReqVO updateReqVO) {
40 44 conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned()));
41 45 }
42 46  
43 47 @Override
44   - public void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType) {
45   - conversationMapper.updateById(new KeFuConversationDO().setId(id).setLastMessageTime(lastMessageTime)
46   - .setLastMessageContent(lastMessageContent).setLastMessageContentType(lastMessageContentType));
  48 + @Transactional(rollbackFor = Exception.class)
  49 + public void updateConversationLastMessage(KeFuMessageDO kefuMessage) {
  50 + // 1.1 校验会话是否存在
  51 + KeFuConversationDO conversation = validateKefuConversationExists(kefuMessage.getConversationId());
  52 + // 1.2 更新会话消息冗余
  53 + conversationMapper.updateById(new KeFuConversationDO().setId(kefuMessage.getConversationId())
  54 + .setLastMessageTime(kefuMessage.getCreateTime()).setLastMessageContent(kefuMessage.getContent())
  55 + .setLastMessageContentType(kefuMessage.getContentType()));
  56 +
  57 + // 2.2 更新管理员未读消息数
  58 + if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType())) {
  59 + conversationMapper.updateAdminUnreadMessageCount(kefuMessage.getConversationId());
  60 + }
  61 + // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复
  62 + if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType())
  63 + && Boolean.TRUE.equals(conversation.getAdminDeleted())) {
  64 + updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE);
  65 + }
47 66 }
48 67  
49 68 @Override
50   - public void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) {
51   - conversationMapper.updateAdminUnreadMessageCountByConversationId(id, count);
  69 + public void updateAdminUnreadMessageCountWithZero(Long id) {
  70 + validateKefuConversationExists(id);
  71 + conversationMapper.updateAdminUnreadMessageCountWithZero(id);
52 72 }
53 73  
54 74 @Override
... ... @@ -58,17 +78,16 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
58 78  
59 79 @Override
60 80 public List<KeFuConversationDO> getKefuConversationList() {
61   - return conversationMapper.selectListWithSort();
  81 + return conversationMapper.selectConversationList();
62 82 }
63 83  
64   - // TODO @puhui999:貌似这个对话,得用户主动创建。不然管理员会看到一个空的对话?
65 84 @Override
66 85 public KeFuConversationDO getOrCreateConversation(Long userId) {
67 86 KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId);
68 87 // 没有历史会话,则初始化一个新会话
69 88 if (conversation == null) {
70 89 conversation = new KeFuConversationDO().setUserId(userId).setLastMessageTime(LocalDateTime.now())
71   - .setLastMessageContent("").setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType())
  90 + .setLastMessageContent(StrUtil.EMPTY).setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType())
72 91 .setAdminPinned(Boolean.FALSE).setUserDeleted(Boolean.FALSE).setAdminDeleted(Boolean.FALSE)
73 92 .setAdminUnreadMessageCount(0);
74 93 conversationMapper.insert(conversation);
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java
... ... @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.promotion.service.kefu;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
5 5 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
  6 +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
6 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
7 8 import jakarta.validation.Valid;
8 9  
9   -// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下
10 10 /**
11 11 * 客服消息 Service 接口
12 12 *
... ... @@ -15,7 +15,7 @@ import jakarta.validation.Valid;
15 15 public interface KeFuMessageService {
16 16  
17 17 /**
18   - * 发送客服消息
  18 + * 【管理员】发送客服消息
19 19 *
20 20 * @param sendReqVO 信息
21 21 * @return 编号
... ... @@ -23,12 +23,19 @@ public interface KeFuMessageService {
23 23 Long sendKefuMessage(@Valid KeFuMessageSendReqVO sendReqVO);
24 24  
25 25 /**
26   - * 更新消息已读状态
  26 + * 【会员】发送客服消息
  27 + *
  28 + * @param sendReqVO 信息
  29 + * @return 编号
  30 + */
  31 + Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO);
  32 +
  33 + /**
  34 + * 【管理员】更新消息已读状态
27 35 *
28 36 * @param conversationId 会话编号
29   - * @param receiverId 用户编号
30 37 */
31   - void updateKefuMessageReadStatus(Long conversationId, Long receiverId);
  38 + void updateKefuMessageReadStatus(Long conversationId);
32 39  
33 40 /**
34 41 * 获得客服消息分页
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java
1 1 package cn.iocoder.yudao.module.promotion.service.kefu;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
4 5 import cn.hutool.extra.spring.SpringUtil;
5 6 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
... ... @@ -9,6 +10,7 @@ import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
9 10 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
10 11 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
11 12 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
  13 +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
12 14 import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
13 15 import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
14 16 import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuMessageMapper;
... ... @@ -19,11 +21,11 @@ import org.springframework.stereotype.Service;
19 21 import org.springframework.transaction.annotation.Transactional;
20 22 import org.springframework.validation.annotation.Validated;
21 23  
22   -import java.time.LocalDateTime;
23 24 import java.util.List;
24 25  
25   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
26   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getFirst;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  27 +import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ;
  28 +import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE;
27 29  
28 30 /**
29 31 * 客服消息 Service 实现类
... ... @@ -34,16 +36,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
34 36 @Validated
35 37 public class KeFuMessageServiceImpl implements KeFuMessageService {
36 38  
37   - // TODO @puhui999:@芋艿:捉摸要不要拿到一个地方枚举;
38   - private static final String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型
39   -
40   - // TODO @puhui999:kefuMessageMapper;因为 messageMapper 可能会重叠
41 39 @Resource
42   - private KeFuMessageMapper messageMapper;
43   -
  40 + private KeFuMessageMapper keFuMessageMapper;
44 41 @Resource
45 42 private KeFuConversationService conversationService;
46   -
47 43 @Resource
48 44 private AdminUserApi adminUserApi;
49 45 @Resource
... ... @@ -55,26 +51,15 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
55 51 @Transactional(rollbackFor = Exception.class)
56 52 public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) {
57 53 // 1.1 校验会话是否存在
58   - KeFuConversationDO conversation = conversationService.validateKefuConversationExists(sendReqVO.getConversationId());
  54 + conversationService.validateKefuConversationExists(sendReqVO.getConversationId());
59 55 // 1.2 校验接收人是否存在
60 56 validateReceiverExist(sendReqVO.getReceiverId(), sendReqVO.getReceiverType());
61 57  
62 58 // 2.1 保存消息
63 59 KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class);
64   - messageMapper.insert(kefuMessage);
65   - // TODO @puhui999:是不是 updateConversationMessage,里面统一处理未读、恢复;直接设置 KeFuMessageDO 作为参数好了。。。
  60 + keFuMessageMapper.insert(kefuMessage);
66 61 // 2.2 更新会话消息冗余
67   - conversationService.updateConversationMessage(kefuMessage.getConversationId(), LocalDateTime.now(),
68   - kefuMessage.getContent(), kefuMessage.getContentType());
69   - // 2.3 更新管理员未读消息数
70   - if (UserTypeEnum.ADMIN.getValue().equals(kefuMessage.getReceiverType())) {
71   - conversationService.updateAdminUnreadMessageCountByConversationId(kefuMessage.getConversationId(), 1);
72   - }
73   - // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复
74   - // TODO @puhui999:建议 && 换一行
75   - if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) && Boolean.TRUE.equals(conversation.getAdminDeleted())) {
76   - conversationService.updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE);
77   - }
  62 + conversationService.updateConversationLastMessage(kefuMessage);
78 63  
79 64 // 3. 发送消息
80 65 getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage);
... ... @@ -82,31 +67,44 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
82 67 }
83 68  
84 69 @Override
  70 + public Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO) {
  71 + // 1.1 设置会话编号
  72 + KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class);
  73 + KeFuConversationDO conversation = conversationService.getOrCreateConversation(sendReqVO.getSenderId());
  74 + kefuMessage.setConversationId(conversation.getId());
  75 + // 1.2 保存消息
  76 + keFuMessageMapper.insert(kefuMessage);
  77 +
  78 + // 2. 更新会话消息冗余
  79 + conversationService.updateConversationLastMessage(kefuMessage);
  80 + // 3. 发送消息
  81 + getSelf().sendAsyncMessageToAdmin(kefuMessage);
  82 + return kefuMessage.getId();
  83 + }
  84 +
  85 + @Override
85 86 @Transactional(rollbackFor = Exception.class)
86   - public void updateKefuMessageReadStatus(Long conversationId, Long receiverId) {
  87 + public void updateKefuMessageReadStatus(Long conversationId) {
87 88 // 1.1 校验会话是否存在
88 89 conversationService.validateKefuConversationExists(conversationId);
89   - // 1.2 查询接收人所有的未读消息
90   - // TODO @puhui999:应该不能 receiverId 过滤哈。因为多个客服,一个人点了,就都点了。
91   - List<KeFuMessageDO> messageList = messageMapper.selectListByConversationIdAndReceiverIdAndReadStatus(
92   - conversationId, receiverId, Boolean.FALSE);
  90 + // 1.2 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了)
  91 + List<KeFuMessageDO> messageList = keFuMessageMapper.selectListByConversationIdAndReadStatus(conversationId, Boolean.FALSE);
93 92 // 1.3 情况一:没有未读消息
94 93 if (CollUtil.isEmpty(messageList)) {
95 94 return;
96 95 }
97 96  
98 97 // 2.1 情况二:更新未读消息状态为已读
99   - messageMapper.updateReadStstusBatchByIds(convertSet(messageList, KeFuMessageDO::getId), Boolean.TRUE);
100   - // 2.2 更新管理员未读消息数
101   - KeFuMessageDO message = getFirst(messageList);
102   - assert message != null;
103   - if (UserTypeEnum.ADMIN.getValue().equals(message.getReceiverType())) {
104   - conversationService.updateAdminUnreadMessageCountByConversationId(conversationId, 0);
105   - }
  98 + keFuMessageMapper.updateReadStatusBatchByIds(convertSet(messageList, KeFuMessageDO::getId),
  99 + new KeFuMessageDO().setReadStatus(Boolean.TRUE));
  100 + // 2.2 将管理员未读消息计数更新为零
  101 + conversationService.updateAdminUnreadMessageCountWithZero(conversationId);
106 102  
107   - // 2.3 发送消息通知发送者,接收者已读 -> 发送者更新发送的消息状态
  103 + // 2.3 发送消息通知会员,管理员已读 -> 会员更新发送的消息状态
108 104 // TODO @puhui999:待定~
109   - getSelf().sendAsyncMessage(message.getSenderType(), message.getSenderId(), "keFuMessageReadStatusChange");
  105 + KeFuMessageDO keFuMessage = getFirst(filterList(messageList, message -> UserTypeEnum.MEMBER.getValue().equals(message.getSenderType())));
  106 + assert keFuMessage != null; // 断言避免警告
  107 + webSocketSenderApi.sendObject(UserTypeEnum.MEMBER.getValue(), keFuMessage.getSenderId(), KEFU_MESSAGE_ADMIN_READ, StrUtil.EMPTY);
110 108 }
111 109  
112 110 private void validateReceiverExist(Long receiverId, Integer receiverType) {
... ... @@ -123,9 +121,14 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
123 121 webSocketSenderApi.sendObject(userType, userId, KEFU_MESSAGE_TYPE, content);
124 122 }
125 123  
  124 + @Async
  125 + public void sendAsyncMessageToAdmin(Object content) {
  126 + webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), KEFU_MESSAGE_TYPE, content);
  127 + }
  128 +
126 129 @Override
127 130 public PageResult<KeFuMessageDO> getKefuMessagePage(KeFuMessagePageReqVO pageReqVO) {
128   - return messageMapper.selectPage(pageReqVO);
  131 + return keFuMessageMapper.selectPage(pageReqVO);
129 132 }
130 133  
131 134 private KeFuMessageServiceImpl getSelf() {
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -156,7 +156,7 @@ yudao:
156 156 - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
157 157 websocket:
158 158 enable: true # websocket的开关
159   - path: /infra/ws # 路径
  159 + path: /infra/ws/ # 路径 // TODO @puhui999: 小程序 socket.io 连接会多一个 / 🤣🤣🤣
160 160 sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq
161 161 sender-rocketmq:
162 162 topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic
... ...