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