mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 10:37:41 +08:00 
			
		
		
		
	MALL-KEFU: 根据 review 完善客服相关实现
This commit is contained in:
		| @ -1,5 +1,6 @@ | |||||||
| package cn.iocoder.yudao.module.promotion.controller.admin.kefu; | package cn.iocoder.yudao.module.promotion.controller.admin.kefu; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||||
| @ -18,6 +19,7 @@ import org.springframework.validation.annotation.Validated; | |||||||
| import org.springframework.web.bind.annotation.*; | import org.springframework.web.bind.annotation.*; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||||||
|  | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||||||
|  |  | ||||||
| @Tag(name = "管理后台 - 客服消息") | @Tag(name = "管理后台 - 客服消息") | ||||||
| @RestController | @RestController | ||||||
| @ -32,6 +34,7 @@ public class KeFuMessageController { | |||||||
|     @Operation(summary = "发送客服消息") |     @Operation(summary = "发送客服消息") | ||||||
|     @PreAuthorize("@ss.hasPermission('promotion:kefu-message:send')") |     @PreAuthorize("@ss.hasPermission('promotion:kefu-message:send')") | ||||||
|     public CommonResult<Long> createKefuMessage(@Valid @RequestBody KeFuMessageSendReqVO sendReqVO) { |     public CommonResult<Long> createKefuMessage(@Valid @RequestBody KeFuMessageSendReqVO sendReqVO) { | ||||||
|  |         sendReqVO.setSenderId(getLoginUserId()).setSenderType(UserTypeEnum.ADMIN.getValue()); // 设置用户编号和类型 | ||||||
|         return success(messageService.sendKefuMessage(sendReqVO)); |         return success(messageService.sendKefuMessage(sendReqVO)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -40,7 +43,7 @@ public class KeFuMessageController { | |||||||
|     @Parameter(name = "conversationId", description = "会话编号", required = true) |     @Parameter(name = "conversationId", description = "会话编号", required = true) | ||||||
|     @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')") |     @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')") | ||||||
|     public CommonResult<Boolean> updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { |     public CommonResult<Boolean> updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { | ||||||
|         messageService.updateKefuMessageReadStatus(conversationId); |         messageService.updateKefuMessageReadStatus(conversationId, getLoginUserId(), UserTypeEnum.ADMIN.getValue()); | ||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -9,37 +9,19 @@ import lombok.Data; | |||||||
| @Data | @Data | ||||||
| public class KeFuMessageSendReqVO { | public class KeFuMessageSendReqVO { | ||||||
|  |  | ||||||
|     // TODO @puhui999:貌似字段多了;1)id 不用;2)senderId、senderType 不用;3)receiverId、receiverType 也不用;原因可以想下哈 |  | ||||||
|  |  | ||||||
|     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") |  | ||||||
|     private Long id; |  | ||||||
|  |  | ||||||
|     @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") |     @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") | ||||||
|     @NotNull(message = "会话编号不能为空") |     @NotNull(message = "会话编号不能为空") | ||||||
|     private Long conversationId; |     private Long conversationId; | ||||||
|  |  | ||||||
|     @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") |  | ||||||
|     @NotNull(message = "发送人编号不能为空") |  | ||||||
|     private Long senderId; |  | ||||||
|  |  | ||||||
|     @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |  | ||||||
|     @NotNull(message = "发送人类型不能为空") |  | ||||||
|     private Integer senderType; |  | ||||||
|  |  | ||||||
|     @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124") |  | ||||||
|     @NotNull(message = "接收人编号不能为空") |  | ||||||
|     private Long receiverId; |  | ||||||
|  |  | ||||||
|     @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |  | ||||||
|     @NotNull(message = "接收人类型不能为空") |  | ||||||
|     private Integer receiverType; |  | ||||||
|  |  | ||||||
|     @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |  | ||||||
|     @NotNull(message = "消息类型不能为空") |  | ||||||
|     private Integer contentType; |  | ||||||
|  |  | ||||||
|     @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) |     @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) | ||||||
|     @NotEmpty(message = "消息不能为空") |     @NotEmpty(message = "消息不能为空") | ||||||
|     private String content; |     private String content; | ||||||
|  |  | ||||||
|  |     // ========== 后端设置的参数,前端无需传递 ========== | ||||||
|  |  | ||||||
|  |     @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571", hidden = true) | ||||||
|  |     private Long senderId; | ||||||
|  |     @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1", hidden = true) | ||||||
|  |     private Integer senderType; | ||||||
|  |  | ||||||
| } | } | ||||||
| @ -43,8 +43,7 @@ public class AppKeFuMessageController { | |||||||
|     @Parameter(name = "conversationId", description = "会话编号", required = true) |     @Parameter(name = "conversationId", description = "会话编号", required = true) | ||||||
|     @PreAuthenticated |     @PreAuthenticated | ||||||
|     public CommonResult<Boolean> updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { |     public CommonResult<Boolean> updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { | ||||||
|         // TODO @puhui999:需要传递 userId;万一用户模拟一个 conversationId |         kefuMessageService.updateKefuMessageReadStatus(conversationId, getLoginUserId(), UserTypeEnum.MEMBER.getValue()); | ||||||
|         kefuMessageService.updateKefuMessageReadStatus(conversationId); |  | ||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -9,10 +9,6 @@ import lombok.Data; | |||||||
| @Data | @Data | ||||||
| public class AppKeFuMessageSendReqVO { | public class AppKeFuMessageSendReqVO { | ||||||
|  |  | ||||||
|     // TODO @puhui999:应该没有传递编号哈 |  | ||||||
|     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") |  | ||||||
|     private Long id; |  | ||||||
|  |  | ||||||
|     @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |     @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||||||
|     @NotNull(message = "消息类型不能为空") |     @NotNull(message = "消息类型不能为空") | ||||||
|     private Integer contentType; |     private Integer contentType; | ||||||
|  | |||||||
| @ -22,18 +22,14 @@ public interface KeFuConversationMapper extends BaseMapperX<KeFuConversationDO> | |||||||
|                 .orderByDesc(KeFuConversationDO::getCreateTime)); |                 .orderByDesc(KeFuConversationDO::getCreateTime)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // TODO @puhui999:这个不用单独搞个方法哈。Service 直接 new 一个对象,然后调用 update 方法。 |     default void updateAdminUnreadMessageCountIncrement(Long id) { | ||||||
|     default void updateAdminUnreadMessageCountWithZero(Long id) { |  | ||||||
|         update(new LambdaUpdateWrapper<KeFuConversationDO>() |  | ||||||
|                 .eq(KeFuConversationDO::getId, id) |  | ||||||
|                 .set(KeFuConversationDO::getAdminUnreadMessageCount, 0)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // TODO @puhui999:改成 updateAdminUnreadMessageCountIncrement 增加 |  | ||||||
|     default void updateAdminUnreadMessageCount(Long id) { |  | ||||||
|         update(new LambdaUpdateWrapper<KeFuConversationDO>() |         update(new LambdaUpdateWrapper<KeFuConversationDO>() | ||||||
|                 .eq(KeFuConversationDO::getId, id) |                 .eq(KeFuConversationDO::getId, id) | ||||||
|                 .setSql("admin_unread_message_count = admin_unread_message_count + 1")); |                 .setSql("admin_unread_message_count = admin_unread_message_count + 1")); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     default KeFuConversationDO selectByUserId(Long userId){ | ||||||
|  |         return selectOne(KeFuConversationDO::getUserId, userId); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
| @ -56,20 +56,21 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { | |||||||
|  |  | ||||||
|         // 2.1 更新管理员未读消息数 |         // 2.1 更新管理员未读消息数 | ||||||
|         if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType())) { |         if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType())) { | ||||||
|             conversationMapper.updateAdminUnreadMessageCount(kefuMessage.getConversationId()); |             conversationMapper.updateAdminUnreadMessageCountIncrement(kefuMessage.getConversationId()); | ||||||
|         } |         } | ||||||
|         // 2.2 会员用户发送消息时,如果管理员删除过会话则进行恢复 |         // 2.2 会员用户发送消息时,如果管理员删除过会话则进行恢复 | ||||||
|         // TODO @puhui999:其实不用判断用户类型;只要be已删除,就恢复! |         if (Boolean.TRUE.equals(conversation.getAdminDeleted())) { | ||||||
|         if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) |  | ||||||
|                 && Boolean.TRUE.equals(conversation.getAdminDeleted())) { |  | ||||||
|             updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); |             updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateAdminUnreadMessageCountWithZero(Long id) { |     public void updateAdminUnreadMessageCountWithZero(Long id) { | ||||||
|  |         // 校验存在 | ||||||
|         validateKefuConversationExists(id); |         validateKefuConversationExists(id); | ||||||
|         conversationMapper.updateAdminUnreadMessageCountWithZero(id); |          | ||||||
|  |         // 管理员未读消息数归零 | ||||||
|  |         conversationMapper.updateById(new KeFuConversationDO().setId(id).setAdminUnreadMessageCount(0)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @ -107,8 +108,7 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public KeFuConversationDO getConversationByUserId(Long userId) { |     public KeFuConversationDO getConversationByUserId(Long userId) { | ||||||
|         // TODO @puhui999:service 不写 dao 的逻辑哈 |         return conversationMapper.selectByUserId(userId); | ||||||
|         return conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
| @ -35,8 +35,10 @@ public interface KeFuMessageService { | |||||||
|      * 【管理员】更新消息已读状态 |      * 【管理员】更新消息已读状态 | ||||||
|      * |      * | ||||||
|      * @param conversationId 会话编号 |      * @param conversationId 会话编号 | ||||||
|  |      * @param userId         用户编号 | ||||||
|  |      * @param userType       用户类型 | ||||||
|      */ |      */ | ||||||
|     void updateKefuMessageReadStatus(Long conversationId); |     void updateKefuMessageReadStatus(Long conversationId, Long userId, Integer userType); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 获得客服消息分页 |      * 获得客服消息分页 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package cn.iocoder.yudao.module.promotion.service.kefu; | package cn.iocoder.yudao.module.promotion.service.kefu; | ||||||
|  |  | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
|  | import cn.hutool.core.util.ObjUtil; | ||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
| import cn.hutool.extra.spring.SpringUtil; | import cn.hutool.extra.spring.SpringUtil; | ||||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||||
| @ -22,10 +23,11 @@ import org.springframework.stereotype.Service; | |||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| import org.springframework.validation.annotation.Validated; | import org.springframework.validation.annotation.Validated; | ||||||
|  |  | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
|  | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | ||||||
|  | import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.KEFU_CONVERSATION_NOT_EXISTS; | ||||||
| import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ; | import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ; | ||||||
| import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE; | import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE; | ||||||
|  |  | ||||||
| @ -53,18 +55,19 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { | |||||||
|     @Transactional(rollbackFor = Exception.class) |     @Transactional(rollbackFor = Exception.class) | ||||||
|     public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) { |     public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) { | ||||||
|         // 1.1 校验会话是否存在 |         // 1.1 校验会话是否存在 | ||||||
|         conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); |         KeFuConversationDO conversation = conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); | ||||||
|         // 1.2 校验接收人是否存在 |         // 1.2 校验接收人是否存在 | ||||||
|         validateReceiverExist(sendReqVO.getReceiverId(), sendReqVO.getReceiverType()); |         validateReceiverExist(conversation.getUserId(), UserTypeEnum.MEMBER.getValue()); | ||||||
|  |  | ||||||
|         // 2.1 保存消息 |         // 2.1 保存消息 | ||||||
|         KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); |         KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); | ||||||
|  |         kefuMessage.setReceiverId(conversation.getUserId()).setReceiverType(UserTypeEnum.MEMBER.getValue()); // 设置接收人 | ||||||
|         keFuMessageMapper.insert(kefuMessage); |         keFuMessageMapper.insert(kefuMessage); | ||||||
|         // 2.2 更新会话消息冗余 |         // 2.2 更新会话消息冗余 | ||||||
|         conversationService.updateConversationLastMessage(kefuMessage); |         conversationService.updateConversationLastMessage(kefuMessage); | ||||||
|  |  | ||||||
|         // 3. 发送消息 |         // 3. 发送消息 | ||||||
|         getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage); |         getSelf().sendAsyncMessage(UserTypeEnum.MEMBER.getValue(), conversation.getUserId(), kefuMessage); | ||||||
|         return kefuMessage.getId(); |         return kefuMessage.getId(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -86,9 +89,13 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     @Transactional(rollbackFor = Exception.class) |     @Transactional(rollbackFor = Exception.class) | ||||||
|     public void updateKefuMessageReadStatus(Long conversationId) { |     public void updateKefuMessageReadStatus(Long conversationId, Long userId, Integer userType) { | ||||||
|         // 1.1 校验会话是否存在 |         // 1.1 校验会话是否存在 | ||||||
|         conversationService.validateKefuConversationExists(conversationId); |         KeFuConversationDO conversation = conversationService.validateKefuConversationExists(conversationId); | ||||||
|  |         // 1.2 如果是会员端处理已读,需要传递 userId;万一用户模拟一个 conversationId | ||||||
|  |         if (UserTypeEnum.MEMBER.getValue().equals(userType) && ObjUtil.notEqual(conversation.getUserId(), userId)) { | ||||||
|  |             throw exception(KEFU_CONVERSATION_NOT_EXISTS); | ||||||
|  |         } | ||||||
|         // 1.2 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了) |         // 1.2 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了) | ||||||
|         List<KeFuMessageDO> messageList = keFuMessageMapper.selectListByConversationIdAndReadStatus(conversationId, Boolean.FALSE); |         List<KeFuMessageDO> messageList = keFuMessageMapper.selectListByConversationIdAndReadStatus(conversationId, Boolean.FALSE); | ||||||
|         // 1.3 情况一:没有未读消息 |         // 1.3 情况一:没有未读消息 | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 puhui999
					puhui999