Commit d89a19a92ba588724ae7858fc8488b1a6fdfac31

Authored by YunaiV
1 parent 2347bffe

【代码评审】商城:客服功能

Showing 16 changed files with 57 additions and 31 deletions
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java
... ... @@ -7,7 +7,7 @@ import lombok.Getter;
7 7 import java.util.Arrays;
8 8  
9 9 /**
10   - * 消息类型枚举
  10 + * 客服消息的类型枚举
11 11 *
12 12 * @author HUIHUI
13 13 */
... ... @@ -19,7 +19,7 @@ public enum KeFuMessageContentTypeEnum implements IntArrayValuable {
19 19 IMAGE(2, "图片消息"),
20 20 VOICE(3, "语音消息"),
21 21 VIDEO(4, "视频消息"),
22   - // 和正常消息隔离下
  22 + // ========== 商城特殊消息 ==========
23 23 PRODUCT(10, "商品消息"),
24 24 ORDER(11, "订单消息");
25 25  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java
... ... @@ -27,6 +27,7 @@ public class KeFuConversationController {
27 27 @Resource
28 28 private KeFuConversationService conversationService;
29 29  
  30 + // TODO @puhui999:updateConversationPinned
30 31 @PostMapping("/update-pinned")
31 32 @Operation(summary = "置顶客服会话")
32 33 @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')")
... ...
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.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
3 6 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
4 7 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO;
5 8 import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
6   -import org.springframework.web.bind.annotation.*;
  9 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
  10 +import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
7 14 import jakarta.annotation.Resource;
8   -import org.springframework.validation.annotation.Validated;
  15 +import jakarta.validation.Valid;
9 16 import org.springframework.security.access.prepost.PreAuthorize;
10   -import io.swagger.v3.oas.annotations.tags.Tag;
11   -import io.swagger.v3.oas.annotations.Parameter;
12   -import io.swagger.v3.oas.annotations.Operation;
13   -
14   -import jakarta.validation.*;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
15 19  
16   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
17   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
18   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
19 20 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 21 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
21 22  
22   -import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
23   -import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService;
24   -
25 23 @Tag(name = "管理后台 - 客服消息")
26 24 @RestController
27 25 @RequestMapping("/promotion/kefu-message")
... ... @@ -47,6 +45,7 @@ public class KeFuMessageController {
47 45 return success(true);
48 46 }
49 47  
  48 + // TODO @puhui999:这个应该是某个会话,上翻、下翻;不是传统的分页哈;
50 49 @GetMapping("/page")
51 50 @Operation(summary = "获得客服消息分页")
52 51 @PreAuthorize("@ss.hasPermission('promotion:kefu-message:query')")
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java
... ... @@ -15,9 +15,11 @@ public class KeFuConversationRespVO {
15 15 @Schema(description = "会话所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "8300")
16 16 private Long userId;
17 17  
18   - @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00")
  18 + @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED)
19 19 private LocalDateTime lastMessageTime;
20 20  
  21 + // TODO @puhui999:, 缺了空格哈
  22 +
21 23 @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊")
22 24 private String lastMessageContent;
23 25  
... ... @@ -36,7 +38,7 @@ public class KeFuConversationRespVO {
36 38 @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6")
37 39 private Integer adminUnreadMessageCount;
38 40  
39   - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00")
  41 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
40 42 private LocalDateTime createTime;
41 43  
42 44 }
43 45 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java
... ... @@ -9,11 +9,11 @@ import lombok.Data;
9 9 public class KeFuConversationUpdatePinnedReqVO {
10 10  
11 11 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
12   - @NotNull(message = "会话编号,不能为空")
  12 + @NotNull(message = "会话编号不能为空")
13 13 private Long id;
14 14  
15 15 @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
16   - @NotNull(message = "管理端置顶,不能为空")
  16 + @NotNull(message = "管理端置顶不能为空")
17 17 private Boolean adminPinned;
18 18  
19 19 }
20 20 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 6  
7 7 @Schema(description = "管理后台 - 客服消息分页 Request VO")
8 8 @Data
  9 +// TODO @puhui999:不用 @EqualsAndHashCode 哈
9 10 @EqualsAndHashCode(callSuper = true)
10 11 @ToString(callSuper = true)
11 12 public class KeFuMessagePageReqVO extends PageParam {
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java
... ... @@ -9,6 +9,8 @@ import lombok.Data;
9 9 @Data
10 10 public class KeFuMessageSendReqVO {
11 11  
  12 + // TODO @puhui999:貌似字段多了;1)id 不用;2)senderId、senderType 不用;3)receiverId、receiverType 也不用;原因可以想下哈
  13 +
12 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
13 15 private Long id;
14 16  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java
... ... @@ -25,10 +25,12 @@ public class AppKeFuConversationController {
25 25 @Resource
26 26 private KeFuConversationService conversationService;
27 27  
  28 + // TODO @puhui999:接口名不对噢;
28 29 @GetMapping("/get")
29 30 @Operation(summary = "获得客服会话")
30 31 @PreAuthenticated
31 32 public CommonResult<AppKeFuConversationRespVO> getDiyPage() {
  33 + // TODO @puhui999:建议获取;和转换,分成 2 个哈;干净一些;
32 34 return success(BeanUtils.toBean(conversationService.getOrCreateConversation(getLoginUserId()), AppKeFuConversationRespVO.class));
33 35 }
34 36  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java
... ... @@ -8,6 +8,7 @@ import lombok.ToString;
8 8  
9 9 @Schema(description = "用户 App - 客服消息分页 Request VO")
10 10 @Data
  11 +// TODO @puhui999:不用 @EqualsAndHashCode 哈
11 12 @EqualsAndHashCode(callSuper = true)
12 13 @ToString(callSuper = true)
13 14 public class AppKeFuMessagePageReqVO extends PageParam {
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java
... ... @@ -16,6 +16,7 @@ import java.util.List;
16 16 @Mapper
17 17 public interface KeFuConversationMapper extends BaseMapperX<KeFuConversationDO> {
18 18  
  19 + // TODO @puhui999:排序可以交给前端,或者 controller;数据库的计算尽量少哈;
19 20 default List<KeFuConversationDO> selectListWithSort() {
20 21 return selectList(new LambdaQueryWrapperX<KeFuConversationDO>()
21 22 .eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
... ... @@ -23,6 +24,7 @@ public interface KeFuConversationMapper extends BaseMapperX&lt;KeFuConversationDO&gt;
23 24 .orderByDesc(KeFuConversationDO::getCreateTime));
24 25 }
25 26  
  27 + // TODO @puhui999:是不是置零,用 update 就 ok 拉;然后单独搞个 +1 的方法;
26 28 default void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) {
27 29 LambdaUpdateWrapper<KeFuConversationDO> updateWrapper = new LambdaUpdateWrapper<>();
28 30 updateWrapper.eq(KeFuConversationDO::getId, id);
... ... @@ -31,7 +33,6 @@ public interface KeFuConversationMapper extends BaseMapperX&lt;KeFuConversationDO&gt;
31 33 } else { // 情况二:管理员已读后重置
32 34 updateWrapper.set(KeFuConversationDO::getAdminUnreadMessageCount, 0);
33 35 }
34   -
35 36 update(updateWrapper);
36 37 }
37 38  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java
... ... @@ -26,13 +26,16 @@ public interface KeFuMessageMapper extends BaseMapperX&lt;KeFuMessageDO&gt; {
26 26 .orderByDesc(KeFuMessageDO::getId));
27 27 }
28 28  
29   - default List<KeFuMessageDO> selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId, Long receiverId, Boolean readStatus) {
  29 + default List<KeFuMessageDO> selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId,
  30 + Long receiverId,
  31 + Boolean readStatus) {
30 32 return selectList(new LambdaQueryWrapper<KeFuMessageDO>()
31 33 .eq(KeFuMessageDO::getConversationId, conversationId)
32 34 .eq(KeFuMessageDO::getReceiverId, receiverId)
33 35 .eq(KeFuMessageDO::getReadStatus, readStatus));
34 36 }
35 37  
  38 + // TODO @puhui999:status 拼写不对哈;ps:是不是搞个 ids + entity 的更新,更通用点
36 39 default void updateReadStstusBatchByIds(Collection<Long> ids, Boolean readStatus) {
37 40 update(new LambdaUpdateWrapper<KeFuMessageDO>()
38 41 .in(KeFuMessageDO::getId, ids)
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
6 6 import java.time.LocalDateTime;
7 7 import java.util.List;
8 8  
  9 +// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下
9 10 /**
10 11 * 客服会话 Service 接口
11 12 *
... ... @@ -20,6 +21,7 @@ public interface KeFuConversationService {
20 21 */
21 22 void deleteKefuConversation(Long id);
22 23  
  24 + // TODO @puhui999:是不是方法名,体现出更新的是管理员的置顶哈
23 25 /**
24 26 * 客服会话置顶
25 27 *
... ... @@ -27,6 +29,7 @@ public interface KeFuConversationService {
27 29 */
28 30 void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO);
29 31  
  32 + // TODO @puhui999:updateConversationLastMessage 会好点哈
30 33 /**
31 34 * 更新会话客服消息冗余信息
32 35 *
... ... @@ -60,7 +63,9 @@ public interface KeFuConversationService {
60 63 List<KeFuConversationDO> getKefuConversationList();
61 64  
62 65 /**
63   - * 获得或创建会话
  66 + * 【会员】获得或创建会话
  67 + *
  68 + * 对于【会员】来说,有且仅有一个对话
64 69 *
65 70 * @param userId 用户编号
66 71 * @return 客服会话
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java
... ... @@ -37,7 +37,6 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
37 37  
38 38 @Override
39 39 public void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO) {
40   - // 只有管理员端可以置顶会话
41 40 conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned()));
42 41 }
43 42  
... ... @@ -62,10 +61,12 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
62 61 return conversationMapper.selectListWithSort();
63 62 }
64 63  
  64 + // TODO @puhui999:貌似这个对话,得用户主动创建。不然管理员会看到一个空的对话?
65 65 @Override
66 66 public KeFuConversationDO getOrCreateConversation(Long userId) {
67 67 KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId);
68   - if (conversation == null) { // 没有历史会话则初始化一个新会话
  68 + // 没有历史会话,则初始化一个新会话
  69 + if (conversation == null) {
69 70 conversation = new KeFuConversationDO().setUserId(userId).setLastMessageTime(LocalDateTime.now())
70 71 .setLastMessageContent("").setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType())
71 72 .setAdminPinned(Boolean.FALSE).setUserDeleted(Boolean.FALSE).setAdminDeleted(Boolean.FALSE)
... ... @@ -77,12 +78,11 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
77 78  
78 79 @Override
79 80 public KeFuConversationDO validateKefuConversationExists(Long id) {
80   - KeFuConversationDO conversationDO = conversationMapper.selectById(id);
81   - if (conversationDO == null) {
  81 + KeFuConversationDO conversation = conversationMapper.selectById(id);
  82 + if (conversation == null) {
82 83 throw exception(KEFU_CONVERSATION_NOT_EXISTS);
83 84 }
84   -
85   - return conversationDO;
  85 + return conversation;
86 86 }
87 87  
88 88 }
89 89 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMe
6 6 import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
7 7 import jakarta.validation.Valid;
8 8  
  9 +// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下
9 10 /**
10 11 * 客服消息 Service 接口
11 12 *
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java
... ... @@ -34,12 +34,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
34 34 @Validated
35 35 public class KeFuMessageServiceImpl implements KeFuMessageService {
36 36  
  37 + // TODO @puhui999:@芋艿:捉摸要不要拿到一个地方枚举;
37 38 private static final String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型
38 39  
  40 + // TODO @puhui999:kefuMessageMapper;因为 messageMapper 可能会重叠
39 41 @Resource
40 42 private KeFuMessageMapper messageMapper;
  43 +
41 44 @Resource
42 45 private KeFuConversationService conversationService;
  46 +
43 47 @Resource
44 48 private AdminUserApi adminUserApi;
45 49 @Resource
... ... @@ -58,6 +62,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
58 62 // 2.1 保存消息
59 63 KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class);
60 64 messageMapper.insert(kefuMessage);
  65 + // TODO @puhui999:是不是 updateConversationMessage,里面统一处理未读、恢复;直接设置 KeFuMessageDO 作为参数好了。。。
61 66 // 2.2 更新会话消息冗余
62 67 conversationService.updateConversationMessage(kefuMessage.getConversationId(), LocalDateTime.now(),
63 68 kefuMessage.getContent(), kefuMessage.getContentType());
... ... @@ -66,14 +71,13 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
66 71 conversationService.updateAdminUnreadMessageCountByConversationId(kefuMessage.getConversationId(), 1);
67 72 }
68 73 // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复
  74 + // TODO @puhui999:建议 && 换一行
69 75 if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) && Boolean.TRUE.equals(conversation.getAdminDeleted())) {
70 76 conversationService.updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE);
71 77 }
72 78  
73 79 // 3. 发送消息
74 80 getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage);
75   -
76   - // 返回
77 81 return kefuMessage.getId();
78 82 }
79 83  
... ... @@ -83,6 +87,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
83 87 // 1.1 校验会话是否存在
84 88 conversationService.validateKefuConversationExists(conversationId);
85 89 // 1.2 查询接收人所有的未读消息
  90 + // TODO @puhui999:应该不能 receiverId 过滤哈。因为多个客服,一个人点了,就都点了。
86 91 List<KeFuMessageDO> messageList = messageMapper.selectListByConversationIdAndReceiverIdAndReadStatus(
87 92 conversationId, receiverId, Boolean.FALSE);
88 93 // 1.3 情况一:没有未读消息
... ... @@ -98,7 +103,9 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
98 103 if (UserTypeEnum.ADMIN.getValue().equals(message.getReceiverType())) {
99 104 conversationService.updateAdminUnreadMessageCountByConversationId(conversationId, 0);
100 105 }
  106 +
101 107 // 2.3 发送消息通知发送者,接收者已读 -> 发送者更新发送的消息状态
  108 + // TODO @puhui999:待定~
102 109 getSelf().sendAsyncMessage(message.getSenderType(), message.getSenderId(), "keFuMessageReadStatusChange");
103 110 }
104 111  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
... ... @@ -131,8 +131,9 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
131 131 @Override
132 132 public BrokerageUserDO getOrCreateBrokerageUser(Long id) {
133 133 BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(id);
  134 + // 特殊:人人分销的情况下,如果分销人为空则创建分销人
134 135 if (brokerageUser == null && ObjUtil.equal(BrokerageEnabledConditionEnum.ALL.getCondition(),
135   - tradeConfigService.getTradeConfig().getBrokerageEnabledCondition())) { // 人人分销的情况下,如果分销人为空则创建分销人
  136 + tradeConfigService.getTradeConfig().getBrokerageEnabledCondition())) {
136 137 brokerageUser = new BrokerageUserDO().setId(id).setBrokerageEnabled(true).setBrokeragePrice(0)
137 138 .setBrokerageTime(LocalDateTime.now()).setFrozenPrice(0);
138 139 brokerageUserMapper.insert(brokerageUser);
... ...