Commit 127a98a93478abbaa0e54a49052b0793c6334d8e

Authored by puhui999
1 parent 0be9c67a

MALL-KEFU: 新增客服相关操作接口

Showing 24 changed files with 993 additions and 4 deletions
sql/mysql/mall-promotion-kefu.sql 0 → 100644
  1 +DROP TABLE IF EXISTS `promotion_kefu_conversation`;
  2 +CREATE TABLE `promotion_kefu_conversation` (
  3 + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号',
  4 + `user_id` BIGINT NOT NULL COMMENT '会话所属用户',
  5 + `last_message_time` DATETIME NOT NULL COMMENT '最后聊天时间',
  6 + `last_message_content` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后聊天内容',
  7 + `last_message_content_type` INT NOT NULL COMMENT '最后发送的消息类型',
  8 + `admin_pinned` BIT(1) NOT NULL DEFAULT b'0' COMMENT '管理端置顶',
  9 + `user_deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '用户是否可见',
  10 + `admin_deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '管理员是否可见',
  11 + `admin_unread_message_count` INT NOT NULL COMMENT '管理员未读消息数',
  12 + `creator` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
  13 + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  14 + `updater` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
  15 + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  16 + `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  17 + PRIMARY KEY (`id`) USING BTREE
  18 +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客服会话' ROW_FORMAT = Dynamic;
  19 +
  20 +DROP TABLE IF EXISTS `promotion_kefu_message`;
  21 +CREATE TABLE `promotion_kefu_message` (
  22 + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号',
  23 + `conversation_id` BIGINT NOT NULL COMMENT '会话编号',
  24 + `sender_id` BIGINT NOT NULL COMMENT '发送人编号',
  25 + `sender_type` INT NOT NULL COMMENT '发送人类型',
  26 + `receiver_id` BIGINT NOT NULL COMMENT '接收人编号',
  27 + `receiver_type` INT NOT NULL COMMENT '接收人类型',
  28 + `content_type` INT NOT NULL COMMENT '消息类型',
  29 + `content` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息',
  30 + `read_status` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否已读',
  31 + `creator` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
  32 + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  33 + `updater` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
  34 + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  35 + `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  36 + PRIMARY KEY (`id`) USING BTREE
  37 +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客服消息' ROW_FORMAT = Dynamic;
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
... ... @@ -125,4 +125,10 @@ public interface ErrorCodeConstants {
125 125 ErrorCode DIY_PAGE_NOT_EXISTS = new ErrorCode(1_013_018_000, "装修页面不存在");
126 126 ErrorCode DIY_PAGE_NAME_USED = new ErrorCode(1_013_018_001, "装修页面名称({})已经被使用");
127 127  
  128 + // ========== 客服会话 1-013-019-000 ==========
  129 + ErrorCode KEFU_CONVERSATION_NOT_EXISTS = new ErrorCode(1_013_019_000, "客服会话不存在");
  130 +
  131 + // ========== 客服消息 1-013-020-000 ==========
  132 + ErrorCode KEFU_MESSAGE_NOT_EXISTS = new ErrorCode(1_013_020_000, "客服消息不存在");
  133 +
128 134 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.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.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationRespVO;
  6 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
  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.Parameter;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import jakarta.annotation.Resource;
  12 +import jakarta.validation.Valid;
  13 +import org.springframework.security.access.prepost.PreAuthorize;
  14 +import org.springframework.validation.annotation.Validated;
  15 +import org.springframework.web.bind.annotation.*;
  16 +
  17 +import java.util.List;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +
  21 +@Tag(name = "管理后台 - 客服会话")
  22 +@RestController
  23 +@RequestMapping("/promotion/kefu-conversation")
  24 +@Validated
  25 +public class KeFuConversationController {
  26 +
  27 + @Resource
  28 + private KeFuConversationService conversationService;
  29 +
  30 + @PostMapping("/update-pinned")
  31 + @Operation(summary = "置顶客服会话")
  32 + @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')")
  33 + public CommonResult<Boolean> updatePinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) {
  34 + conversationService.updatePinned(updateReqVO);
  35 + return success(true);
  36 + }
  37 +
  38 + @DeleteMapping("/delete")
  39 + @Operation(summary = "删除客服会话")
  40 + @Parameter(name = "id", description = "编号", required = true)
  41 + @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:delete')")
  42 + public CommonResult<Boolean> deleteKefuConversation(@RequestParam("id") Long id) {
  43 + conversationService.deleteKefuConversation(id);
  44 + return success(true);
  45 + }
  46 +
  47 + @GetMapping("/list")
  48 + @Operation(summary = "获得客服会话列表")
  49 + @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:query')")
  50 + public CommonResult<List<KeFuConversationRespVO>> getKefuConversationPage() {
  51 + return success(BeanUtils.toBean(conversationService.getKefuConversationList(), KeFuConversationRespVO.class));
  52 + }
  53 +
  54 +}
0 55 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.kefu;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
  4 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO;
  5 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
  6 +import org.springframework.web.bind.annotation.*;
  7 +import jakarta.annotation.Resource;
  8 +import org.springframework.validation.annotation.Validated;
  9 +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.*;
  15 +
  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 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  21 +
  22 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
  23 +import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService;
  24 +
  25 +@Tag(name = "管理后台 - 客服消息")
  26 +@RestController
  27 +@RequestMapping("/promotion/kefu-message")
  28 +@Validated
  29 +public class KeFuMessageController {
  30 +
  31 + @Resource
  32 + private KeFuMessageService messageService;
  33 +
  34 + @PostMapping("/send")
  35 + @Operation(summary = "发送客服消息")
  36 + @PreAuthorize("@ss.hasPermission('promotion:kefu-message:send')")
  37 + public CommonResult<Long> createKefuMessage(@Valid @RequestBody KeFuMessageSendReqVO sendReqVO) {
  38 + return success(messageService.sendKefuMessage(sendReqVO));
  39 + }
  40 +
  41 + @PutMapping("/update-read-status")
  42 + @Operation(summary = "更新客服消息已读状态")
  43 + @Parameter(name = "conversationId", description = "会话编号", required = true)
  44 + @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')")
  45 + public CommonResult<Boolean> updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) {
  46 + messageService.updateKefuMessageReadStatus(conversationId, getLoginUserId());
  47 + return success(true);
  48 + }
  49 +
  50 + @GetMapping("/page")
  51 + @Operation(summary = "获得客服消息分页")
  52 + @PreAuthorize("@ss.hasPermission('promotion:kefu-message:query')")
  53 + public CommonResult<PageResult<KeFuMessageRespVO>> getKefuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) {
  54 + PageResult<KeFuMessageDO> pageResult = messageService.getKefuMessagePage(pageReqVO);
  55 + return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class));
  56 + }
  57 +
  58 +}
0 59 \ 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/KeFuConversationRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.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 = "管理后台 - 客服会话 Response VO")
  9 +@Data
  10 +public class KeFuConversationRespVO {
  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 +}
0 43 \ 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 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 客服会话置顶 Request VO")
  8 +@Data
  9 +public class KeFuConversationUpdatePinnedReqVO {
  10 +
  11 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
  12 + @NotNull(message = "会话编号,不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  16 + @NotNull(message = "管理端置顶,不能为空")
  17 + private Boolean adminPinned;
  18 +
  19 +}
0 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 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message;
  2 +
  3 +import lombok.*;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  6 +
  7 +@Schema(description = "管理后台 - 客服消息分页 Request VO")
  8 +@Data
  9 +@EqualsAndHashCode(callSuper = true)
  10 +@ToString(callSuper = true)
  11 +public class KeFuMessagePageReqVO extends PageParam {
  12 +
  13 + @Schema(description = "会话编号", example = "12580")
  14 + private Long conversationId;
  15 +
  16 +}
0 17 \ 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/KeFuMessageRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import com.alibaba.excel.annotation.*;
  8 +
  9 +@Schema(description = "管理后台 - 客服消息 Response VO")
  10 +@Data
  11 +public class KeFuMessageRespVO {
  12 +
  13 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
  14 + private Long id;
  15 +
  16 + @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580")
  17 + private Long conversationId;
  18 +
  19 + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571")
  20 + private Long senderId;
  21 +
  22 + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  23 + private Integer senderType;
  24 +
  25 + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124")
  26 + private Long receiverId;
  27 +
  28 + @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  29 + private Integer receiverType;
  30 +
  31 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  32 + private Integer contentType;
  33 +
  34 + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED)
  35 + private String content;
  36 +
  37 + @Schema(description = "是否已读", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  38 + private Boolean readStatus;
  39 +
  40 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  41 + private LocalDateTime createTime;
  42 +
  43 +}
0 44 \ 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/KeFuMessageSendReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - 发送客服消息 Request VO")
  9 +@Data
  10 +public class KeFuMessageSendReqVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
  13 + private Long id;
  14 +
  15 + @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580")
  16 + @NotNull(message = "会话编号不能为空")
  17 + private Long conversationId;
  18 +
  19 + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571")
  20 + @NotNull(message = "发送人编号不能为空")
  21 + private Long senderId;
  22 +
  23 + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  24 + @NotNull(message = "发送人类型不能为空")
  25 + private Integer senderType;
  26 +
  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 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  36 + @NotNull(message = "消息类型不能为空")
  37 + private Integer contentType;
  38 +
  39 + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED)
  40 + @NotEmpty(message = "消息不能为空")
  41 + private String content;
  42 +
  43 +}
0 44 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java 0 → 100644
  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 + @GetMapping("/get")
  29 + @Operation(summary = "获得客服会话")
  30 + @PreAuthenticated
  31 + public CommonResult<AppKeFuConversationRespVO> getDiyPage() {
  32 + return success(BeanUtils.toBean(conversationService.getOrCreateConversation(getLoginUserId()), AppKeFuConversationRespVO.class));
  33 + }
  34 +
  35 +}
0 36 \ 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 0 → 100644
  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.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  7 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
  8 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO;
  9 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
  10 +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
  11 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
  12 +import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService;
  13 +import io.swagger.v3.oas.annotations.Operation;
  14 +import io.swagger.v3.oas.annotations.Parameter;
  15 +import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import jakarta.annotation.Resource;
  17 +import jakarta.validation.Valid;
  18 +import org.springframework.security.access.prepost.PreAuthorize;
  19 +import org.springframework.validation.annotation.Validated;
  20 +import org.springframework.web.bind.annotation.*;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  23 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  24 +
  25 +@Tag(name = "管理后台 - 客服消息")
  26 +@RestController
  27 +@RequestMapping("/promotion/kefu-message")
  28 +@Validated
  29 +public class AppKeFuMessageController {
  30 +
  31 + @Resource
  32 + private KeFuMessageService kefuMessageService;
  33 +
  34 + @PostMapping("/send")
  35 + @Operation(summary = "发送客服消息")
  36 + @PreAuthenticated
  37 + public CommonResult<Long> createKefuMessage(@Valid @RequestBody AppKeFuMessageSendReqVO sendReqVO) {
  38 + return success(kefuMessageService.sendKefuMessage(BeanUtils.toBean(sendReqVO, KeFuMessageSendReqVO.class)));
  39 + }
  40 +
  41 + @PutMapping("/update-read-status")
  42 + @Operation(summary = "更新客服消息已读状态")
  43 + @Parameter(name = "conversationId", description = "会话编号", required = true)
  44 + @PreAuthenticated
  45 + public CommonResult<Boolean> updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) {
  46 + kefuMessageService.updateKefuMessageReadStatus(conversationId, getLoginUserId());
  47 + return success(true);
  48 + }
  49 +
  50 + @GetMapping("/page")
  51 + @Operation(summary = "获得客服消息分页")
  52 + @PreAuthenticated
  53 + public CommonResult<PageResult<KeFuMessageRespVO>> getKefuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) {
  54 + PageResult<KeFuMessageDO> pageResult = kefuMessageService.getKefuMessagePage(pageReqVO);
  55 + return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class));
  56 + }
  57 +
  58 +}
0 59 \ 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/conversation/AppKeFuConversationRespVO.java 0 → 100644
  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 +}
0 43 \ 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 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "用户 App - 客服消息分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class AppKeFuMessagePageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "会话编号", example = "12580")
  16 + private Long conversationId;
  17 +
  18 +}
0 19 \ 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/AppKeFuMessageRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "用户 App - 客服消息 Response VO")
  9 +@Data
  10 +public class AppKeFuMessageRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
  13 + private Long id;
  14 +
  15 + @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580")
  16 + private Long conversationId;
  17 +
  18 + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571")
  19 + private Long senderId;
  20 +
  21 + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  22 + private Integer senderType;
  23 +
  24 + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124")
  25 + private Long receiverId;
  26 +
  27 + @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  28 + private Integer receiverType;
  29 +
  30 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  31 + private Integer contentType;
  32 +
  33 + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED)
  34 + private String content;
  35 +
  36 + @Schema(description = "是否已读", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + private Boolean readStatus;
  38 +
  39 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  40 + private LocalDateTime createTime;
  41 +
  42 +}
0 43 \ 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/AppKeFuMessageSendReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "用户 App - 发送客服消息 Request VO")
  9 +@Data
  10 +public class AppKeFuMessageSendReqVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202")
  13 + private Long id;
  14 +
  15 + @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580")
  16 + @NotNull(message = "会话编号不能为空")
  17 + private Long conversationId;
  18 +
  19 + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571")
  20 + @NotNull(message = "发送人编号不能为空")
  21 + private Long senderId;
  22 +
  23 + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  24 + @NotNull(message = "发送人类型不能为空")
  25 + private Integer senderType;
  26 +
  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 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  36 + @NotNull(message = "消息类型不能为空")
  37 + private Integer contentType;
  38 +
  39 + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED)
  40 + @NotEmpty(message = "消息不能为空")
  41 + private String content;
  42 +
  43 +}
0 44 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java
... ... @@ -61,15 +61,15 @@ public class KeFuConversationDO extends BaseDO {
61 61 /**
62 62 * 用户是否可见
63 63 *
64   - * true - 可见,默认值
65   - * false - 不可见,用户删除时设置为 false
  64 + * false - 可见,默认值
  65 + * true - 不可见,用户删除时设置为 true
66 66 */
67 67 private Boolean userDeleted;
68 68 /**
69 69 * 管理员是否可见
70 70 *
71   - * true - 可见,默认值
72   - * false - 不可见,管理员删除时设置为 false
  71 + * false - 可见,默认值
  72 + * true - 不可见,管理员删除时设置为 true
73 73 */
74 74 private Boolean adminDeleted;
75 75  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.dal.mysql.kefu;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
  6 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  7 +import org.apache.ibatis.annotations.Mapper;
  8 +
  9 +import java.util.List;
  10 +
  11 +/**
  12 + * 客服会话 Mapper
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Mapper
  17 +public interface KeFuConversationMapper extends BaseMapperX<KeFuConversationDO> {
  18 +
  19 + default List<KeFuConversationDO> selectListWithSort() {
  20 + return selectList(new LambdaQueryWrapperX<KeFuConversationDO>()
  21 + .eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
  22 + .orderByDesc(KeFuConversationDO::getAdminPinned) // 置顶优先
  23 + .orderByDesc(KeFuConversationDO::getCreateTime));
  24 + }
  25 +
  26 + default void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) {
  27 + LambdaUpdateWrapper<KeFuConversationDO> updateWrapper = new LambdaUpdateWrapper<>();
  28 + updateWrapper.eq(KeFuConversationDO::getId, id);
  29 + if (count != null && count > 0) { // 情况一:会员发送消息时增加管理员的未读消息数
  30 + updateWrapper.setSql("admin_unread_message_count = admin_unread_message_count + 1");
  31 + } else { // 情况二:管理员已读后重置
  32 + updateWrapper.set(KeFuConversationDO::getAdminUnreadMessageCount, 0);
  33 + }
  34 +
  35 + update(updateWrapper);
  36 + }
  37 +
  38 +}
0 39 \ 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 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.dal.mysql.kefu;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
  7 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
  8 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  9 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  10 +import org.apache.ibatis.annotations.Mapper;
  11 +
  12 +import java.util.Collection;
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * 客服消息 Mapper
  17 + *
  18 + * @author HUIHUI
  19 + */
  20 +@Mapper
  21 +public interface KeFuMessageMapper extends BaseMapperX<KeFuMessageDO> {
  22 +
  23 + default PageResult<KeFuMessageDO> selectPage(KeFuMessagePageReqVO reqVO) {
  24 + return selectPage(reqVO, new LambdaQueryWrapperX<KeFuMessageDO>()
  25 + .eqIfPresent(KeFuMessageDO::getConversationId, reqVO.getConversationId())
  26 + .orderByDesc(KeFuMessageDO::getId));
  27 + }
  28 +
  29 + default List<KeFuMessageDO> selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId, Long receiverId, Boolean readStatus) {
  30 + return selectList(new LambdaQueryWrapper<KeFuMessageDO>()
  31 + .eq(KeFuMessageDO::getConversationId, conversationId)
  32 + .eq(KeFuMessageDO::getReceiverId, receiverId)
  33 + .eq(KeFuMessageDO::getReadStatus, readStatus));
  34 + }
  35 +
  36 + default void updateReadStstusBatchByIds(Collection<Long> ids, Boolean readStatus) {
  37 + update(new LambdaUpdateWrapper<KeFuMessageDO>()
  38 + .in(KeFuMessageDO::getId, ids)
  39 + .set(KeFuMessageDO::getReadStatus, readStatus));
  40 + }
  41 +
  42 +}
0 43 \ 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 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.service.kefu;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
  4 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 客服会话 Service 接口
  11 + *
  12 + * @author HUIHUI
  13 + */
  14 +public interface KeFuConversationService {
  15 +
  16 + /**
  17 + * 删除客服会话
  18 + *
  19 + * @param id 编号
  20 + */
  21 + void deleteKefuConversation(Long id);
  22 +
  23 + /**
  24 + * 客服会话置顶
  25 + *
  26 + * @param updateReqVO 请求
  27 + */
  28 + void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO);
  29 +
  30 + /**
  31 + * 更新会话客服消息冗余信息
  32 + *
  33 + * @param id 编号
  34 + * @param lastMessageTime 最后聊天时间
  35 + * @param lastMessageContent 最后聊天内容
  36 + * @param lastMessageContentType 最后聊天内容类型
  37 + */
  38 + void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType);
  39 +
  40 + /**
  41 + * 更新管理员未读消息数
  42 + *
  43 + * @param id 编号
  44 + * @param count 数量:0 则重置 1 则消息数加一
  45 + */
  46 + void updateAdminUnreadMessageCountByConversationId(Long id, Integer count);
  47 +
  48 + /**
  49 + * 更新会话对于管理员是否可见
  50 + *
  51 + * @param adminDeleted 管理员是否可见
  52 + */
  53 + void updateConversationAdminDeleted(Long id, Boolean adminDeleted);
  54 +
  55 + /**
  56 + * 获得客服会话列表
  57 + *
  58 + * @return 会话列表
  59 + */
  60 + List<KeFuConversationDO> getKefuConversationList();
  61 +
  62 + /**
  63 + * 获得或创建会话
  64 + *
  65 + * @param userId 用户编号
  66 + * @return 客服会话
  67 + */
  68 + KeFuConversationDO getOrCreateConversation(Long userId);
  69 +
  70 + /**
  71 + * 校验客服会话是否存在
  72 + *
  73 + * @param id 编号
  74 + * @return 客服会话
  75 + */
  76 + KeFuConversationDO validateKefuConversationExists(Long id);
  77 +
  78 +}
0 79 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.service.kefu;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
  4 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
  5 +import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuConversationMapper;
  6 +import cn.iocoder.yudao.module.promotion.enums.kehu.KeFuMessageContentTypeEnum;
  7 +import jakarta.annotation.Resource;
  8 +import org.springframework.stereotype.Service;
  9 +import org.springframework.validation.annotation.Validated;
  10 +
  11 +import java.time.LocalDateTime;
  12 +import java.util.List;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  15 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.KEFU_CONVERSATION_NOT_EXISTS;
  16 +
  17 +/**
  18 + * 客服会话 Service 实现类
  19 + *
  20 + * @author HUIHUI
  21 + */
  22 +@Service
  23 +@Validated
  24 +public class KeFuConversationServiceImpl implements KeFuConversationService {
  25 +
  26 + @Resource
  27 + private KeFuConversationMapper conversationMapper;
  28 +
  29 + @Override
  30 + public void deleteKefuConversation(Long id) {
  31 + // 校验存在
  32 + validateKefuConversationExists(id);
  33 +
  34 + // 只有管理员端可以删除会话,也不真的删,只是管理员端看不到啦
  35 + conversationMapper.updateById(new KeFuConversationDO().setId(id).setAdminDeleted(Boolean.TRUE));
  36 + }
  37 +
  38 + @Override
  39 + public void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO) {
  40 + // 只有管理员端可以置顶会话
  41 + conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned()));
  42 + }
  43 +
  44 + @Override
  45 + public void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType) {
  46 + conversationMapper.updateById(new KeFuConversationDO().setId(id).setLastMessageTime(lastMessageTime)
  47 + .setLastMessageContent(lastMessageContent).setLastMessageContentType(lastMessageContentType));
  48 + }
  49 +
  50 + @Override
  51 + public void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) {
  52 + conversationMapper.updateAdminUnreadMessageCountByConversationId(id, count);
  53 + }
  54 +
  55 + @Override
  56 + public void updateConversationAdminDeleted(Long id, Boolean adminDeleted) {
  57 + conversationMapper.updateById(new KeFuConversationDO().setId(id).setAdminDeleted(adminDeleted));
  58 + }
  59 +
  60 + @Override
  61 + public List<KeFuConversationDO> getKefuConversationList() {
  62 + return conversationMapper.selectListWithSort();
  63 + }
  64 +
  65 + @Override
  66 + public KeFuConversationDO getOrCreateConversation(Long userId) {
  67 + KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId);
  68 + if (conversation == null) { // 没有历史会话则初始化一个新会话
  69 + conversation = new KeFuConversationDO().setUserId(userId).setLastMessageTime(LocalDateTime.now())
  70 + .setLastMessageContent("").setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType())
  71 + .setAdminPinned(Boolean.FALSE).setUserDeleted(Boolean.FALSE).setAdminDeleted(Boolean.FALSE)
  72 + .setAdminUnreadMessageCount(0);
  73 + conversationMapper.insert(conversation);
  74 + }
  75 + return conversation;
  76 + }
  77 +
  78 + @Override
  79 + public KeFuConversationDO validateKefuConversationExists(Long id) {
  80 + KeFuConversationDO conversationDO = conversationMapper.selectById(id);
  81 + if (conversationDO == null) {
  82 + throw exception(KEFU_CONVERSATION_NOT_EXISTS);
  83 + }
  84 +
  85 + return conversationDO;
  86 + }
  87 +
  88 +}
0 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 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.service.kefu;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
  5 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
  6 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
  7 +import jakarta.validation.Valid;
  8 +
  9 +/**
  10 + * 客服消息 Service 接口
  11 + *
  12 + * @author HUIHUI
  13 + */
  14 +public interface KeFuMessageService {
  15 +
  16 + /**
  17 + * 发送客服消息
  18 + *
  19 + * @param sendReqVO 信息
  20 + * @return 编号
  21 + */
  22 + Long sendKefuMessage(@Valid KeFuMessageSendReqVO sendReqVO);
  23 +
  24 + /**
  25 + * 更新消息已读状态
  26 + *
  27 + * @param conversationId 会话编号
  28 + * @param receiverId 用户编号
  29 + */
  30 + void updateKefuMessageReadStatus(Long conversationId, Long receiverId);
  31 +
  32 + /**
  33 + * 获得客服消息分页
  34 + *
  35 + * @param pageReqVO 分页查询
  36 + * @return 客服消息分页
  37 + */
  38 + PageResult<KeFuMessageDO> getKefuMessagePage(KeFuMessagePageReqVO pageReqVO);
  39 +
  40 +}
0 41 \ No newline at end of file
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.service.kefu;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
  9 +import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
  10 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
  11 +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
  12 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
  13 +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
  14 +import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuMessageMapper;
  15 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  16 +import jakarta.annotation.Resource;
  17 +import org.springframework.scheduling.annotation.Async;
  18 +import org.springframework.stereotype.Service;
  19 +import org.springframework.transaction.annotation.Transactional;
  20 +import org.springframework.validation.annotation.Validated;
  21 +
  22 +import java.time.LocalDateTime;
  23 +import java.util.List;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getFirst;
  27 +
  28 +/**
  29 + * 客服消息 Service 实现类
  30 + *
  31 + * @author HUIHUI
  32 + */
  33 +@Service
  34 +@Validated
  35 +public class KeFuMessageServiceImpl implements KeFuMessageService {
  36 +
  37 + private static final String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型
  38 +
  39 + @Resource
  40 + private KeFuMessageMapper messageMapper;
  41 + @Resource
  42 + private KeFuConversationService conversationService;
  43 + @Resource
  44 + private AdminUserApi adminUserApi;
  45 + @Resource
  46 + private MemberUserApi memberUserApi;
  47 + @Resource
  48 + private WebSocketSenderApi webSocketSenderApi;
  49 +
  50 + @Override
  51 + @Transactional(rollbackFor = Exception.class)
  52 + public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) {
  53 + // 1.1 校验会话是否存在
  54 + KeFuConversationDO conversation = conversationService.validateKefuConversationExists(sendReqVO.getConversationId());
  55 + // 1.2 校验接收人是否存在
  56 + validateReceiverExist(sendReqVO.getReceiverId(), sendReqVO.getReceiverType());
  57 +
  58 + // 2.1 保存消息
  59 + KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class);
  60 + messageMapper.insert(kefuMessage);
  61 + // 2.2 更新会话消息冗余
  62 + conversationService.updateConversationMessage(kefuMessage.getConversationId(), LocalDateTime.now(),
  63 + kefuMessage.getContent(), kefuMessage.getContentType());
  64 + // 2.3 更新管理员未读消息数
  65 + if (UserTypeEnum.ADMIN.getValue().equals(kefuMessage.getReceiverType())) {
  66 + conversationService.updateAdminUnreadMessageCountByConversationId(kefuMessage.getConversationId(), 1);
  67 + }
  68 + // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复
  69 + if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) && Boolean.TRUE.equals(conversation.getAdminDeleted())) {
  70 + conversationService.updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE);
  71 + }
  72 +
  73 + // 3. 发送消息
  74 + getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage);
  75 +
  76 + // 返回
  77 + return kefuMessage.getId();
  78 + }
  79 +
  80 + @Override
  81 + @Transactional(rollbackFor = Exception.class)
  82 + public void updateKefuMessageReadStatus(Long conversationId, Long receiverId) {
  83 + // 1.1 校验会话是否存在
  84 + conversationService.validateKefuConversationExists(conversationId);
  85 + // 1.2 查询接收人所有的未读消息
  86 + List<KeFuMessageDO> messageList = messageMapper.selectListByConversationIdAndReceiverIdAndReadStatus(
  87 + conversationId, receiverId, Boolean.FALSE);
  88 + // 1.3 情况一:没有未读消息
  89 + if (CollUtil.isEmpty(messageList)) {
  90 + return;
  91 + }
  92 +
  93 + // 2.1 情况二:更新未读消息状态为已读
  94 + messageMapper.updateReadStstusBatchByIds(convertSet(messageList, KeFuMessageDO::getId), Boolean.TRUE);
  95 + // 2.2 更新管理员未读消息数
  96 + KeFuMessageDO message = getFirst(messageList);
  97 + assert message != null;
  98 + if (UserTypeEnum.ADMIN.getValue().equals(message.getReceiverType())) {
  99 + conversationService.updateAdminUnreadMessageCountByConversationId(conversationId, 0);
  100 + }
  101 + // 2.3 发送消息通知发送者,接收者已读 -> 发送者更新发送的消息状态
  102 + getSelf().sendAsyncMessage(message.getSenderType(), message.getSenderId(), "keFuMessageReadStatusChange");
  103 + }
  104 +
  105 + private void validateReceiverExist(Long receiverId, Integer receiverType) {
  106 + if (UserTypeEnum.ADMIN.getValue().equals(receiverType)) {
  107 + adminUserApi.validateUser(receiverId);
  108 + }
  109 + if (UserTypeEnum.MEMBER.getValue().equals(receiverType)) {
  110 + memberUserApi.validateUser(receiverId);
  111 + }
  112 + }
  113 +
  114 + @Async
  115 + public void sendAsyncMessage(Integer userType, Long userId, Object content) {
  116 + webSocketSenderApi.sendObject(userType, userId, KEFU_MESSAGE_TYPE, content);
  117 + }
  118 +
  119 + @Override
  120 + public PageResult<KeFuMessageDO> getKefuMessagePage(KeFuMessagePageReqVO pageReqVO) {
  121 + return messageMapper.selectPage(pageReqVO);
  122 + }
  123 +
  124 + private KeFuMessageServiceImpl getSelf() {
  125 + return SpringUtil.getBean(getClass());
  126 + }
  127 +
  128 +}
0 129 \ No newline at end of file
... ...
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java
... ... @@ -57,4 +57,12 @@ public interface MemberUserApi {
57 57 * @return 用户信息
58 58 */
59 59 MemberUserRespDTO getUserByMobile(String mobile);
  60 +
  61 + /**
  62 + * 校验用户是否存在
  63 + *
  64 + * @param id 用户编号
  65 + */
  66 + void validateUser(Long id);
  67 +
60 68 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java
... ... @@ -11,6 +11,9 @@ import jakarta.annotation.Resource;
11 11 import java.util.Collection;
12 12 import java.util.List;
13 13  
  14 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  15 +import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.USER_MOBILE_NOT_EXISTS;
  16 +
14 17 /**
15 18 * 会员用户的 API 实现类
16 19 *
... ... @@ -44,4 +47,12 @@ public class MemberUserApiImpl implements MemberUserApi {
44 47 return MemberUserConvert.INSTANCE.convert2(userService.getUserByMobile(mobile));
45 48 }
46 49  
  50 + @Override
  51 + public void validateUser(Long id) {
  52 + MemberUserDO user = userService.getUser(id);
  53 + if (user == null) {
  54 + throw exception(USER_MOBILE_NOT_EXISTS);
  55 + }
  56 + }
  57 +
47 58 }
... ...