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

This commit is contained in:
YunaiV 2024-06-04 20:25:24 +08:00
parent 2347bffeb1
commit d89a19a92b
16 changed files with 60 additions and 34 deletions

View File

@ -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, "订单消息");

View File

@ -27,6 +27,7 @@ public class KeFuConversationController {
@Resource
private KeFuConversationService conversationService;
// TODO @puhui999updateConversationPinned
@PostMapping("/update-pinned")
@Operation(summary = "置顶客服会话")
@PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')")

View File

@ -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')")

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -9,6 +9,8 @@ import lombok.Data;
@Data
public class KeFuMessageSendReqVO {
// TODO @puhui999貌似字段多了1id 不用2senderIdsenderType 不用3receiverIdreceiverType 也不用原因可以想下哈
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
private Long id;

View File

@ -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));
}

View File

@ -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 {

View File

@ -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);
}

View File

@ -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 @puhui999status 拼写不对哈ps是不是搞个 ids + entity 的更新更通用点
default void updateReadStstusBatchByIds(Collection<Long> ids, Boolean readStatus) {
update(new LambdaUpdateWrapper<KeFuMessageDO>()
.in(KeFuMessageDO::getId, ids)

View File

@ -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 @puhui999updateConversationLastMessage 会好点哈
/**
* 更新会话客服消息冗余信息
*
@ -60,7 +63,9 @@ public interface KeFuConversationService {
List<KeFuConversationDO> getKefuConversationList();
/**
* 获得或创建会话
* 会员获得或创建会话
*
* 对于会员来说有且仅有一个对话
*
* @param userId 用户编号
* @return 客服会话

View File

@ -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;
}
}

View File

@ -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 接口
*

View File

@ -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 @puhui999kefuMessageMapper因为 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");
}

View File

@ -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);