diff --git a/sql/mysql/mall-promotion-kefu.sql b/sql/mysql/mall-promotion-kefu.sql index e0b478f57..67054cbb1 100644 --- a/sql/mysql/mall-promotion-kefu.sql +++ b/sql/mysql/mall-promotion-kefu.sql @@ -1,37 +1,39 @@ DROP TABLE IF EXISTS `promotion_kefu_conversation`; CREATE TABLE `promotion_kefu_conversation` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', - `user_id` BIGINT NOT NULL COMMENT '会话所属用户', - `last_message_time` DATETIME NOT NULL COMMENT '最后聊天时间', - `last_message_content` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后聊天内容', - `last_message_content_type` INT NOT NULL COMMENT '最后发送的消息类型', - `admin_pinned` BIT(1) NOT NULL DEFAULT b'0' COMMENT '管理端置顶', - `user_deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '用户是否可见', - `admin_deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '管理员是否可见', - `admin_unread_message_count` INT NOT NULL COMMENT '管理员未读消息数', - `creator` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NOT NULL COMMENT '会话所属用户', + `last_message_time` datetime NOT NULL COMMENT '最后聊天时间', + `last_message_content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后聊天内容', + `last_message_content_type` int NOT NULL COMMENT '最后发送的消息类型', + `admin_pinned` bit(1) NOT NULL DEFAULT b'0' COMMENT '管理端置顶', + `user_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '用户是否可见', + `admin_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '管理员是否可见', + `admin_unread_message_count` int NOT NULL COMMENT '管理员未读消息数', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客服会话' ROW_FORMAT = Dynamic; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='客服会话'; DROP TABLE IF EXISTS `promotion_kefu_message`; CREATE TABLE `promotion_kefu_message` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', - `conversation_id` BIGINT NOT NULL COMMENT '会话编号', - `sender_id` BIGINT NOT NULL COMMENT '发送人编号', - `sender_type` INT NOT NULL COMMENT '发送人类型', - `receiver_id` BIGINT NOT NULL COMMENT '接收人编号', - `receiver_type` INT NOT NULL COMMENT '接收人类型', - `content_type` INT NOT NULL COMMENT '消息类型', - `content` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息', - `read_status` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否已读', - `creator` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `conversation_id` bigint NOT NULL COMMENT '会话编号', + `sender_id` bigint NOT NULL COMMENT '发送人编号', + `sender_type` int NOT NULL COMMENT '发送人类型', + `receiver_id` bigint DEFAULT NULL COMMENT '接收人编号', + `receiver_type` int DEFAULT NULL COMMENT '接收人类型', + `content_type` int NOT NULL COMMENT '消息类型', + `content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息', + `read_status` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已读', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客服消息' ROW_FORMAT = Dynamic; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='客服消息'; \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java new file mode 100644 index 000000000..d060998ad --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.promotion.enums; + +/** + * websocket 消息类型枚举类 + * + * @author HUIHUI + */ +public interface WebSocketMessageTypeConstants { + + //======================= mall 客服 ======================= + + String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型 + String KEFU_MESSAGE_ADMIN_READ = "kefu_message_read_status_change"; // 客服消息管理员已读 + +} diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java index d12e3ed44..616630c2e 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java @@ -19,6 +19,7 @@ public enum KeFuMessageContentTypeEnum implements IntArrayValuable { IMAGE(2, "图片消息"), VOICE(3, "语音消息"), VIDEO(4, "视频消息"), + SYSTEM(5, "系统消息"), // ========== 商城特殊消息 ========== PRODUCT(10, "商品消息"), ORDER(11, "订单消息"); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java index 09be04c0e..5a1d9e77f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java @@ -27,12 +27,11 @@ public class KeFuConversationController { @Resource private KeFuConversationService conversationService; - // TODO @puhui999:updateConversationPinned - @PostMapping("/update-pinned") + @PostMapping("/update-conversation-pinned") @Operation(summary = "置顶客服会话") @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')") - public CommonResult updatePinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) { - conversationService.updatePinned(updateReqVO); + public CommonResult updateConversationPinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) { + conversationService.updateAdminPinned(updateReqVO); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java index 4409905c1..04a32e156 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java @@ -1,5 +1,6 @@ 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.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -18,7 +19,6 @@ 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 @@ -37,11 +37,11 @@ public class KeFuMessageController { } @PutMapping("/update-read-status") - @Operation(summary = "更新客服消息已读状态") + @Operation(summary = "更新会员客服消息已读状态") @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')") public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { - messageService.updateKefuMessageReadStatus(conversationId, getLoginUserId()); + messageService.updateKefuMessageReadStatus(conversationId); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java index 2fdad1f80..9f499e09e 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; import java.time.LocalDateTime; @@ -18,24 +18,22 @@ public class KeFuConversationRespVO { @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime lastMessageTime; - // TODO @puhui999:, 缺了空格哈 - - @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊") + @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "嗨,您好啊") private String lastMessageContent; - @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED,example = "1") + @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer lastMessageContentType; - @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED,example = "false") + @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") private Boolean adminPinned; - @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean userDeleted; - @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean adminDeleted; - @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6") + @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "6") private Integer adminUnreadMessageCount; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java index f959aeb8b..f60c997be 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java @@ -6,8 +6,6 @@ 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 { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java deleted file mode 100644 index caf754edd..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.promotion.controller.app.kefu; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; -import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.conversation.AppKeFuConversationRespVO; -import cn.iocoder.yudao.module.promotion.service.kefu.KeFuConversationService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "用户 APP - 客户会话") -@RestController -@RequestMapping("/promotion/kefu-conversation") -@Validated -public class AppKeFuConversationController { - - @Resource - private KeFuConversationService conversationService; - - // TODO @puhui999:接口名不对噢; - @GetMapping("/get") - @Operation(summary = "获得客服会话") - @PreAuthenticated - public CommonResult getDiyPage() { - // TODO @puhui999:建议获取;和转换,分成 2 个哈;干净一些; - return success(BeanUtils.toBean(conversationService.getOrCreateConversation(getLoginUserId()), AppKeFuConversationRespVO.class)); - } - -} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java index 1af72dba7..8b54c8db4 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.promotion.controller.app.kefu; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 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.framework.security.core.annotations.PreAuthenticated; -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 cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService; @@ -15,14 +15,13 @@ 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 = "管理后台 - 客服消息") +@Tag(name = "用户 APP - 客服消息") @RestController @RequestMapping("/promotion/kefu-message") @Validated @@ -35,7 +34,8 @@ public class AppKeFuMessageController { @Operation(summary = "发送客服消息") @PreAuthenticated public CommonResult createKefuMessage(@Valid @RequestBody AppKeFuMessageSendReqVO sendReqVO) { - return success(kefuMessageService.sendKefuMessage(BeanUtils.toBean(sendReqVO, KeFuMessageSendReqVO.class))); + sendReqVO.setSenderId(getLoginUserId()).setSenderType(UserTypeEnum.MEMBER.getValue()); // 设置用户编号和类型 + return success(kefuMessageService.sendKefuMessage(sendReqVO)); } @PutMapping("/update-read-status") @@ -43,15 +43,15 @@ public class AppKeFuMessageController { @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthenticated public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { - kefuMessageService.updateKefuMessageReadStatus(conversationId, getLoginUserId()); + kefuMessageService.updateKefuMessageReadStatus(conversationId); return success(true); } @GetMapping("/page") @Operation(summary = "获得客服消息分页") @PreAuthenticated - public CommonResult> getKefuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) { - PageResult pageResult = kefuMessageService.getKefuMessagePage(pageReqVO); + public CommonResult> getKefuMessagePage(@Valid AppKeFuMessagePageReqVO pageReqVO) { + PageResult pageResult = kefuMessageService.getKefuMessagePage(pageReqVO, getLoginUserId()); return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class)); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java deleted file mode 100644 index 39a295dc1..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.conversation; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import java.time.LocalDateTime; - -@Schema(description = "用户 App - 客服会话 Response VO") -@Data -public class AppKeFuConversationRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24988") - private Long id; - - @Schema(description = "会话所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "8300") - private Long userId; - - @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") - private LocalDateTime lastMessageTime; - - @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊") - private String lastMessageContent; - - @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED,example = "1") - private Integer lastMessageContentType; - - @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED,example = "false") - private Boolean adminPinned; - - @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") - private Boolean userDeleted; - - @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") - private Boolean adminDeleted; - - @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6") - private Integer adminUnreadMessageCount; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java index 12afd6001..a354a5858 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java @@ -8,8 +8,6 @@ import lombok.ToString; @Schema(description = "用户 App - 客服消息分页 Request VO") @Data -// TODO @puhui999:不用 @EqualsAndHashCode 哈 -@EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class AppKeFuMessagePageReqVO extends PageParam { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java index f2de632da..78df4112d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java @@ -12,30 +12,14 @@ public class AppKeFuMessageSendReqVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") private Long id; - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") - @NotNull(message = "会话编号不能为空") - 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) @NotEmpty(message = "消息不能为空") private String content; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/package-info.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/package-info.java new file mode 100644 index 000000000..9116686b0 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo; \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java index 909ce32ab..577ce1b50 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java @@ -16,24 +16,22 @@ import java.util.List; @Mapper public interface KeFuConversationMapper extends BaseMapperX { - // TODO @puhui999:排序可以交给前端,或者 controller;数据库的计算尽量少哈; - default List selectListWithSort() { + default List selectConversationList() { return selectList(new LambdaQueryWrapperX() .eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE) - .orderByDesc(KeFuConversationDO::getAdminPinned) // 置顶优先 .orderByDesc(KeFuConversationDO::getCreateTime)); } - // TODO @puhui999:是不是置零,用 update 就 ok 拉;然后单独搞个 +1 的方法; - default void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) { - LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); - updateWrapper.eq(KeFuConversationDO::getId, id); - if (count != null && count > 0) { // 情况一:会员发送消息时增加管理员的未读消息数 - updateWrapper.setSql("admin_unread_message_count = admin_unread_message_count + 1"); - } else { // 情况二:管理员已读后重置 - updateWrapper.set(KeFuConversationDO::getAdminUnreadMessageCount, 0); - } - update(updateWrapper); + default void updateAdminUnreadMessageCountWithZero(Long id) { + update(new LambdaUpdateWrapper() + .eq(KeFuConversationDO::getId, id) + .set(KeFuConversationDO::getAdminUnreadMessageCount, 0)); + } + + default void updateAdminUnreadMessageCount(Long id) { + update(new LambdaUpdateWrapper() + .eq(KeFuConversationDO::getId, id) + .setSql("admin_unread_message_count = admin_unread_message_count + 1")); } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java index 87dfb5340..3de68fe0f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -23,23 +24,24 @@ public interface KeFuMessageMapper extends BaseMapperX { default PageResult selectPage(KeFuMessagePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(KeFuMessageDO::getConversationId, reqVO.getConversationId()) - .orderByDesc(KeFuMessageDO::getId)); + .orderByDesc(KeFuMessageDO::getCreateTime)); } - default List selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId, - Long receiverId, - Boolean readStatus) { + default List selectListByConversationIdAndReadStatus(Long conversationId, Boolean readStatus) { return selectList(new LambdaQueryWrapper() .eq(KeFuMessageDO::getConversationId, conversationId) - .eq(KeFuMessageDO::getReceiverId, receiverId) .eq(KeFuMessageDO::getReadStatus, readStatus)); } - // TODO @puhui999:status 拼写不对哈;ps:是不是搞个 ids + entity 的更新,更通用点 - default void updateReadStstusBatchByIds(Collection ids, Boolean readStatus) { - update(new LambdaUpdateWrapper() - .in(KeFuMessageDO::getId, ids) - .set(KeFuMessageDO::getReadStatus, readStatus)); + default void updateReadStatusBatchByIds(Collection ids, KeFuMessageDO keFuMessageDO) { + update(keFuMessageDO, new LambdaUpdateWrapper() + .in(KeFuMessageDO::getId, ids)); + } + + default PageResult selectPage(AppKeFuMessagePageReqVO pageReqVO){ + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eqIfPresent(KeFuMessageDO::getConversationId, pageReqVO.getConversationId()) + .orderByDesc(KeFuMessageDO::getCreateTime)); } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java index d6179383d..c99e74b5c 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java @@ -2,11 +2,10 @@ package cn.iocoder.yudao.module.promotion.service.kefu; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; -import java.time.LocalDateTime; import java.util.List; -// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下 /** * 客服会话 Service 接口 * @@ -15,48 +14,43 @@ import java.util.List; public interface KeFuConversationService { /** - * 删除客服会话 + * 【管理员】删除客服会话 * * @param id 编号 */ void deleteKefuConversation(Long id); - // TODO @puhui999:是不是方法名,体现出更新的是管理员的置顶哈 /** - * 客服会话置顶 + * 【管理员】客服会话置顶 * * @param updateReqVO 请求 */ - void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO); + void updateAdminPinned(KeFuConversationUpdatePinnedReqVO updateReqVO); - // TODO @puhui999:updateConversationLastMessage 会好点哈 /** * 更新会话客服消息冗余信息 * - * @param id 编号 - * @param lastMessageTime 最后聊天时间 - * @param lastMessageContent 最后聊天内容 - * @param lastMessageContentType 最后聊天内容类型 + * @param kefuMessage 消息 */ - void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType); + void updateConversationLastMessage(KeFuMessageDO kefuMessage); /** - * 更新管理员未读消息数 + * 【管理员】将管理员未读消息计数更新为零 * - * @param id 编号 - * @param count 数量:0 则重置 1 则消息数加一 + * @param id 编号 */ - void updateAdminUnreadMessageCountByConversationId(Long id, Integer count); + void updateAdminUnreadMessageCountWithZero(Long id); /** - * 更新会话对于管理员是否可见 + * 【管理员】更新会话对于管理员是否可见 * + * @param id 编号 * @param adminDeleted 管理员是否可见 */ void updateConversationAdminDeleted(Long id, Boolean adminDeleted); /** - * 获得客服会话列表 + * 【管理员】获得客服会话列表 * * @return 会话列表 */ @@ -80,4 +74,12 @@ public interface KeFuConversationService { */ KeFuConversationDO validateKefuConversationExists(Long id); + /** + * 【会员】获得客服会话 + * + * @param userId 用户编号 + * @return 客服会话 + */ + KeFuConversationDO getConversationByUserId(Long userId); + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java index c78989c6c..0f6c185f3 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -1,11 +1,15 @@ package cn.iocoder.yudao.module.promotion.service.kefu; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuConversationMapper; import cn.iocoder.yudao.module.promotion.enums.kehu.KeFuMessageContentTypeEnum; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; @@ -36,19 +40,35 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { } @Override - public void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO) { + public void updateAdminPinned(KeFuConversationUpdatePinnedReqVO updateReqVO) { conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned())); } @Override - public void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType) { - conversationMapper.updateById(new KeFuConversationDO().setId(id).setLastMessageTime(lastMessageTime) - .setLastMessageContent(lastMessageContent).setLastMessageContentType(lastMessageContentType)); + @Transactional(rollbackFor = Exception.class) + public void updateConversationLastMessage(KeFuMessageDO kefuMessage) { + // 1.1 校验会话是否存在 + KeFuConversationDO conversation = validateKefuConversationExists(kefuMessage.getConversationId()); + // 1.2 更新会话消息冗余 + conversationMapper.updateById(new KeFuConversationDO().setId(kefuMessage.getConversationId()) + .setLastMessageTime(kefuMessage.getCreateTime()).setLastMessageContent(kefuMessage.getContent()) + .setLastMessageContentType(kefuMessage.getContentType())); + + // 2.2 更新管理员未读消息数 + if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType())) { + conversationMapper.updateAdminUnreadMessageCount(kefuMessage.getConversationId()); + } + // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复 + if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) + && Boolean.TRUE.equals(conversation.getAdminDeleted())) { + updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); + } } @Override - public void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) { - conversationMapper.updateAdminUnreadMessageCountByConversationId(id, count); + public void updateAdminUnreadMessageCountWithZero(Long id) { + validateKefuConversationExists(id); + conversationMapper.updateAdminUnreadMessageCountWithZero(id); } @Override @@ -58,17 +78,16 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { @Override public List getKefuConversationList() { - return conversationMapper.selectListWithSort(); + return conversationMapper.selectConversationList(); } - // TODO @puhui999:貌似这个对话,得用户主动创建。不然管理员会看到一个空的对话? @Override public KeFuConversationDO getOrCreateConversation(Long userId) { KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); // 没有历史会话,则初始化一个新会话 if (conversation == null) { conversation = new KeFuConversationDO().setUserId(userId).setLastMessageTime(LocalDateTime.now()) - .setLastMessageContent("").setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType()) + .setLastMessageContent(StrUtil.EMPTY).setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType()) .setAdminPinned(Boolean.FALSE).setUserDeleted(Boolean.FALSE).setAdminDeleted(Boolean.FALSE) .setAdminUnreadMessageCount(0); conversationMapper.insert(conversation); @@ -85,4 +104,9 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { return conversation; } + @Override + public KeFuConversationDO getConversationByUserId(Long userId) { + return conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); + } + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java index 7f7a77375..2563ada34 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java @@ -3,10 +3,11 @@ package cn.iocoder.yudao.module.promotion.service.kefu; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import jakarta.validation.Valid; -// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下 /** * 客服消息 Service 接口 * @@ -15,7 +16,7 @@ import jakarta.validation.Valid; public interface KeFuMessageService { /** - * 发送客服消息 + * 【管理员】发送客服消息 * * @param sendReqVO 信息 * @return 编号 @@ -23,12 +24,19 @@ public interface KeFuMessageService { Long sendKefuMessage(@Valid KeFuMessageSendReqVO sendReqVO); /** - * 更新消息已读状态 + * 【会员】发送客服消息 + * + * @param sendReqVO 信息 + * @return 编号 + */ + Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO); + + /** + * 【管理员】更新消息已读状态 * * @param conversationId 会话编号 - * @param receiverId 用户编号 */ - void updateKefuMessageReadStatus(Long conversationId, Long receiverId); + void updateKefuMessageReadStatus(Long conversationId); /** * 获得客服消息分页 @@ -38,4 +46,13 @@ public interface KeFuMessageService { */ PageResult getKefuMessagePage(KeFuMessagePageReqVO pageReqVO); + /** + * 【会员】获得客服消息分页 + * + * @param pageReqVO 请求 + * @param userId 用户编号 + * @return 客服消息分页 + */ + PageResult getKefuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId); + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index 861f7cb92..22339e608 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.promotion.service.kefu; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -9,6 +10,8 @@ import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuMessageMapper; @@ -19,11 +22,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getFirst; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +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; /** * 客服消息 Service 实现类 @@ -34,16 +38,10 @@ 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; - + private KeFuMessageMapper keFuMessageMapper; @Resource private KeFuConversationService conversationService; - @Resource private AdminUserApi adminUserApi; @Resource @@ -55,58 +53,60 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { @Transactional(rollbackFor = Exception.class) public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) { // 1.1 校验会话是否存在 - KeFuConversationDO conversation = conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); + conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); // 1.2 校验接收人是否存在 validateReceiverExist(sendReqVO.getReceiverId(), sendReqVO.getReceiverType()); // 2.1 保存消息 KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); - messageMapper.insert(kefuMessage); - // TODO @puhui999:是不是 updateConversationMessage,里面统一处理未读、恢复;直接设置 KeFuMessageDO 作为参数好了。。。 + keFuMessageMapper.insert(kefuMessage); // 2.2 更新会话消息冗余 - conversationService.updateConversationMessage(kefuMessage.getConversationId(), LocalDateTime.now(), - kefuMessage.getContent(), kefuMessage.getContentType()); - // 2.3 更新管理员未读消息数 - if (UserTypeEnum.ADMIN.getValue().equals(kefuMessage.getReceiverType())) { - 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); - } + conversationService.updateConversationLastMessage(kefuMessage); // 3. 发送消息 getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage); return kefuMessage.getId(); } + @Override + public Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO) { + // 1.1 设置会话编号 + KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); + KeFuConversationDO conversation = conversationService.getOrCreateConversation(sendReqVO.getSenderId()); + kefuMessage.setConversationId(conversation.getId()); + // 1.2 保存消息 + keFuMessageMapper.insert(kefuMessage); + + // 2. 更新会话消息冗余 + conversationService.updateConversationLastMessage(kefuMessage); + // 3. 发送消息 + getSelf().sendAsyncMessageToAdmin(kefuMessage); + return kefuMessage.getId(); + } + @Override @Transactional(rollbackFor = Exception.class) - public void updateKefuMessageReadStatus(Long conversationId, Long receiverId) { + public void updateKefuMessageReadStatus(Long conversationId) { // 1.1 校验会话是否存在 conversationService.validateKefuConversationExists(conversationId); - // 1.2 查询接收人所有的未读消息 - // TODO @puhui999:应该不能 receiverId 过滤哈。因为多个客服,一个人点了,就都点了。 - List messageList = messageMapper.selectListByConversationIdAndReceiverIdAndReadStatus( - conversationId, receiverId, Boolean.FALSE); + // 1.2 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了) + List messageList = keFuMessageMapper.selectListByConversationIdAndReadStatus(conversationId, Boolean.FALSE); // 1.3 情况一:没有未读消息 if (CollUtil.isEmpty(messageList)) { return; } // 2.1 情况二:更新未读消息状态为已读 - messageMapper.updateReadStstusBatchByIds(convertSet(messageList, KeFuMessageDO::getId), Boolean.TRUE); - // 2.2 更新管理员未读消息数 - KeFuMessageDO message = getFirst(messageList); - assert message != null; - if (UserTypeEnum.ADMIN.getValue().equals(message.getReceiverType())) { - conversationService.updateAdminUnreadMessageCountByConversationId(conversationId, 0); - } + keFuMessageMapper.updateReadStatusBatchByIds(convertSet(messageList, KeFuMessageDO::getId), + new KeFuMessageDO().setReadStatus(Boolean.TRUE)); + // 2.2 将管理员未读消息计数更新为零 + conversationService.updateAdminUnreadMessageCountWithZero(conversationId); - // 2.3 发送消息通知发送者,接收者已读 -> 发送者更新发送的消息状态 + // 2.3 发送消息通知会员,管理员已读 -> 会员更新发送的消息状态 // TODO @puhui999:待定~ - getSelf().sendAsyncMessage(message.getSenderType(), message.getSenderId(), "keFuMessageReadStatusChange"); + KeFuMessageDO keFuMessage = getFirst(filterList(messageList, message -> UserTypeEnum.MEMBER.getValue().equals(message.getSenderType()))); + assert keFuMessage != null; // 断言避免警告 + webSocketSenderApi.sendObject(UserTypeEnum.MEMBER.getValue(), keFuMessage.getSenderId(), KEFU_MESSAGE_ADMIN_READ, StrUtil.EMPTY); } private void validateReceiverExist(Long receiverId, Integer receiverType) { @@ -123,9 +123,26 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { webSocketSenderApi.sendObject(userType, userId, KEFU_MESSAGE_TYPE, content); } + @Async + public void sendAsyncMessageToAdmin(Object content) { + webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), KEFU_MESSAGE_TYPE, content); + } + @Override public PageResult getKefuMessagePage(KeFuMessagePageReqVO pageReqVO) { - return messageMapper.selectPage(pageReqVO); + return keFuMessageMapper.selectPage(pageReqVO); + } + + @Override + public PageResult getKefuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId) { + // 1. 获得客服会话 + KeFuConversationDO conversation = conversationService.getConversationByUserId(userId); + if (conversation == null) { + return PageResult.empty(); + } + // 2. 设置会话编号 + pageReqVO.setConversationId(conversation.getId()); + return keFuMessageMapper.selectPage(pageReqVO); } private KeFuMessageServiceImpl getSelf() { diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 561db7a93..32ebdd045 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -156,7 +156,7 @@ yudao: - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 websocket: enable: true # websocket的开关 - path: /infra/ws # 路径 + path: /infra/ws/ # 路径 // TODO @puhui999: 小程序 socket.io 连接会多一个 / 🤣🤣🤣 sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq sender-rocketmq: topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic