From bcc01a252240024bc8ded1feef4a106ea80bfe7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E4=BD=B3=E8=B1=AA?= Date: Fri, 20 Jun 2025 20:13:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B6=88=E6=81=AF=E9=80=9A?= =?UTF-8?q?=E8=BF=87WebSocket=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 45 ++++++ .../src/main/resources/application.yml | 1 + .../content/controller/RabbitMQConsumer.java | 6 +- .../content/domain/base/RabbitMQConfig.java | 12 +- .../demo/service/IExportExcelService.java | 2 + .../wzj/soopin/member/domain/po/Member.java | 4 + ruoyi-modules/ruoyi-system/pom.xml | 13 ++ .../controller/SysMessageController.java | 119 ++++++++------ .../system/domain/bo/SysMessageBo.java | 7 +- .../system/domain/event/MessageEvent.java | 37 +++++ .../system/event/MessageEventListener.java | 45 +++++- .../system/service/ISysMessageService.java | 32 +++- .../system/service/ISysUserService.java | 23 +++ .../service/impl/SysMessageServiceImpl.java | 64 ++++++-- .../service/impl/SysUserServiceImpl.java | 148 +++++++++++++++++- 15 files changed, 470 insertions(+), 88 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index f2e3a6360..7484e7f3d 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -11,6 +11,13 @@ spring.boot.admin.client: username: @monitor.username@ password: @monitor.password@ +--- # 禁用RabbitMQ自动配置 +spring: + rabbitmq: + enabled: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration + --- # snail-job 配置 snail-job: enabled: false @@ -133,6 +140,30 @@ redisson: # 发布和订阅连接池大小 subscriptionConnectionPoolSize: 50 +--- # RocketMQ 配置 +rocketmq: + # RocketMQ 服务器地址 + name-server: 82.156.121.2:9876 + # 生产者配置 + producer: + # 生产者组名 + group: wzj_group + # 发送消息超时时间 + send-message-timeout: 30000 + # 消息最大长度 + max-message-size: 4194304 + # 消息发送失败重试次数 + retry-times-when-send-failed: 3 + # 异步消息发送失败重试次数 + retry-times-when-send-async-failed: 3 + # 消费者配置 + consumer: + # 拉取消息最大数量 + pull-batch-size: 10 + # 消费者组 (系统模块) + group: consumer_group_system + # 是否启动消费者 + enabled: true --- # mail 邮件发送 mail: enabled: false @@ -263,3 +294,17 @@ justauth: client-id: 10**********6 client-secret: 1f7d08**********5b7**********29e redirect-uri: ${justauth.address}/social-callback?source=gitlab + +# 腾讯云IM配置 +tencent: + im: + # 腾讯云 SDKAppId + sdkappid: 1600080789 + # 密钥 + secretkey: 311b5309d714a20f7f5b54360ee21b1e24ec208ebcd25ce8f47d24753bccc091 + # 签名过期时间(秒) + expire: 604800 + # 管理员账号 + admin: administrator + # API调用密钥 + api-secret: 311b5309d714a20f7f5b54360ee21b1e24ec208ebcd25ce8f47d24753bccc091 diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 888c73f77..f654d9b17 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -147,6 +147,7 @@ tenant: - ums_account - ums_account_change_record - sys_message_template + - sys_message_user - ums_fans - ums_block - oms_aftersale diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/RabbitMQConsumer.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/RabbitMQConsumer.java index 9c4d7d9a5..9ed9d08db 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/RabbitMQConsumer.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/RabbitMQConsumer.java @@ -14,6 +14,10 @@ import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +/** + * RabbitMQ消费者 + * 已禁用,改用RocketMQ + */ @Slf4j @Component public class RabbitMQConsumer { @@ -21,7 +25,7 @@ public class RabbitMQConsumer { @Autowired private MsgService msgService; - @RabbitListener(queues = {RabbitMQConfig.QUEUE_SYS_MSG}) + // @RabbitListener(queues = {RabbitMQConfig.QUEUE_SYS_MSG}) public void watchQueue(String payload, Message message) { log.info(payload); diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/base/RabbitMQConfig.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/base/RabbitMQConfig.java index 61d58ec07..89dad8ab8 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/base/RabbitMQConfig.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/base/RabbitMQConfig.java @@ -9,7 +9,11 @@ import org.springframework.context.annotation.Configuration; -@Configuration +/** + * RabbitMQ配置类 + * 已禁用,改用RocketMQ + */ +// @Configuration public class RabbitMQConfig { /** @@ -25,7 +29,7 @@ public class RabbitMQConfig { public static final String QUEUE_SYS_MSG = "queue_sys_msg"; - @Bean(EXCHANGE_MSG) + // @Bean(EXCHANGE_MSG) public Exchange exchange() { return ExchangeBuilder // 构建交换机 .topicExchange(EXCHANGE_MSG) // 使用topic类型,参考:https://www.rabbitmq.com/getstarted.html @@ -33,12 +37,12 @@ public class RabbitMQConfig { .build(); } - @Bean(QUEUE_SYS_MSG) + // @Bean(QUEUE_SYS_MSG) public Queue queue() { return new Queue(QUEUE_SYS_MSG); } - @Bean + // @Bean public Binding binding(@Qualifier(EXCHANGE_MSG) Exchange exchange, @Qualifier(QUEUE_SYS_MSG) Queue queue) { diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java index 4dfa5effa..a15f49563 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java @@ -1,12 +1,14 @@ package org.dromara.demo.service; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Service; /** * 导出下拉框Excel示例 * * @author Emil.Zhang */ +@Service public interface IExportExcelService { /** diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Member.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Member.java index 0c7868b54..e18791057 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Member.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Member.java @@ -3,8 +3,10 @@ package com.wzj.soopin.member.domain.po; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import org.dromara.common.core.domain.model.BaseAudit; import org.dromara.common.excel.annotation.Excel; @@ -19,6 +21,8 @@ import java.time.LocalDateTime; @Schema(description="会员信息对象") @Data @Builder +@NoArgsConstructor +@AllArgsConstructor @TableName("ums_member") public class Member extends BaseAudit { @Schema(description = "会员id") diff --git a/ruoyi-modules/ruoyi-system/pom.xml b/ruoyi-modules/ruoyi-system/pom.xml index dd960febd..23d05a29c 100644 --- a/ruoyi-modules/ruoyi-system/pom.xml +++ b/ruoyi-modules/ruoyi-system/pom.xml @@ -131,6 +131,19 @@ tomcat-embed-websocket + + + org.apache.rocketmq + rocketmq-spring-boot-starter + 2.2.3 + + + + + org.springframework + spring-messaging + + diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/SysMessageController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/SysMessageController.java index 196a8f4a7..43bc85443 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/SysMessageController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/SysMessageController.java @@ -2,6 +2,7 @@ package org.dromara.system.controller; import cn.dev33.satoken.annotation.SaCheckPermission; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -9,6 +10,7 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.core.service.UserService; import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; import org.dromara.common.core.validate.QueryGroup; @@ -27,8 +29,6 @@ import org.dromara.system.domain.vo.SysMessageVo; import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.service.ISysMessageService; import org.dromara.system.service.ISysUserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -58,7 +58,7 @@ public class SysMessageController extends BaseController { /** * 查询消息列表 */ - @SaCheckPermission("system:message:list") +// @SaCheckPermission("system:message:list") @Tag(name = "查询消息列表") @PostMapping("/list") public R> list(@RequestBody SysMessageBo bo, @RequestBody Page page) { @@ -73,7 +73,7 @@ public class SysMessageController extends BaseController { * * @param id 消息ID */ - @SaCheckPermission("system:message:query") +// @SaCheckPermission("system:message:query") @GetMapping("/{id}") public R getInfo(@NotNull(message = "消息ID不能为空") @PathVariable Long id) { return R.ok(messageService.selectMessageById(id)); @@ -82,52 +82,76 @@ public class SysMessageController extends BaseController { /** * 发送消息 */ - @SaCheckPermission("system:message:send") +// @SaCheckPermission("system:message:send") @Log(title = "消息管理", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping("/send") public R send(@Validated(AddGroup.class) @RequestBody SysMessageBo bo) { - List users = userService.selectUserListByDept(null); - List userIds; + // 设置发送者ID为当前登录用户ID + bo.setSenderId(getUserId()); - switch (bo.getSendScope()) { - case "all": - // 全部用户 - userIds = users.stream().map(SysUserVo::getUserId).toList(); - break; - case "expert": - // 达人 - userIds = users.stream() - .filter(user -> "expert".equals(user.getUserType())) - .map(SysUserVo::getUserId) - .toList(); - break; - case "merchant": - // 商户 - userIds = users.stream() - .filter(user -> "merchant".equals(user.getUserType())) - .map(SysUserVo::getUserId) - .toList(); - break; - case "user": - // 普通用户 - userIds = users.stream() - .filter(user -> "user".equals(user.getUserType())) - .map(SysUserVo::getUserId) - .toList(); - break; - default: - // 其他情况(如指定userIds) - userIds = bo.getUserIds(); + List userIdStrings; + + // 获取第一个发送范围作为主要处理对象 + String scope = bo.getSendScope() != null && !bo.getSendScope().isEmpty() ? + bo.getSendScope().get(0) : ""; + + // 如果是群发消息类型,则根据类型筛选用户 + if ("all".equals(scope) || "expert".equals(scope) || "merchant".equals(scope) || "user".equals(scope)) { + List users = userService.selectUserListByDept(null); + List userIds; + + switch (scope) { + case "all": + // 全部用户 + userIds = users.stream().map(SysUserVo::getUserId).toList(); + break; + case "expert": + // 达人 + userIds = users.stream() + .filter(user -> "expert".equals(user.getUserType())) + .map(SysUserVo::getUserId) + .toList(); + break; + case "merchant": + // 商户 + userIds = users.stream() + .filter(user -> "merchant".equals(user.getUserType())) + .map(SysUserVo::getUserId) + .toList(); + break; + case "user": + // 普通用户 + userIds = users.stream() + .filter(user -> "user".equals(user.getUserType())) + .map(SysUserVo::getUserId) + .toList(); + break; + default: + userIds = List.of(); + } + userIdStrings = userIds.stream().map(String::valueOf).toList(); + } else { + // 直接使用指定的接收者ID + if (bo.getUserIds() != null && !bo.getUserIds().isEmpty()) { + // 如果userIds不为空,使用它 + userIdStrings = bo.getUserIds().stream().map(String::valueOf).toList(); + } else if (bo.getSendScope() != null && !bo.getSendScope().isEmpty()) { + // 如果userIds为空但sendScope不为空,将sendScope中的值作为用户ID使用 + userIdStrings = bo.getSendScope(); + } else { + // 都为空,返回空列表 + userIdStrings = List.of(); + } } - return toAjax(messageService.sendMessageToUsers(bo, userIds)); + return toAjax(messageService.sendMessageToUsers(bo, userIdStrings)); } /** * 标记消息为已读 */ - @SaCheckPermission("system:message:mark") +// @SaCheckPermission("system:message:mark") @Log(title = "消息管理", businessType = BusinessType.UPDATE) @PutMapping("/mark/{id}") public R markAsRead(@NotNull(message = "消息ID不能为空") @PathVariable Long id) { @@ -139,7 +163,7 @@ public class SysMessageController extends BaseController { * * @param ids 消息ID串 */ - @SaCheckPermission("system:message:remove") +// @SaCheckPermission("system:message:remove") @Log(title = "消息管理", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public R remove(@NotEmpty(message = "消息ID不能为空") @PathVariable Long[] ids) { @@ -149,7 +173,7 @@ public class SysMessageController extends BaseController { /** * 获取未读消息列表 */ - @SaCheckPermission("system:message:list") +// @SaCheckPermission("system:message:list") @Tag(name = "查询未读消息列表") @PostMapping("/unread") public R> unreadList(@RequestBody Page page) { @@ -160,7 +184,7 @@ public class SysMessageController extends BaseController { /** * 获取已读消息列表 */ - @SaCheckPermission("system:message:list") +// @SaCheckPermission("system:message:list") @Tag(name = "查询已读消息列表") @PostMapping("/read") public R> readList(@RequestBody Page page) { @@ -172,12 +196,9 @@ public class SysMessageController extends BaseController { * 获取用户列表 */ // @SaCheckPermission("system:message:list") -// @GetMapping("/user/list") -// public R> getUserList() { -// PageQuery pageQuery = new PageQuery(); -// pageQuery.setPageNum(1); -// pageQuery.setPageSize(Integer.MAX_VALUE); -// TableDataInfo users = userService.selectPageUserList(new SysUserBo(), pageQuery); -// return R.ok(users); -// } + @GetMapping("/user/list") + public R> getUserList(@RequestParam(required = false) String keyword) { + // 无论是否有关键词,都查询 ums_member 表中的用户信息 + return R.ok(userService.selectMemberUsers(keyword)); + } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMessageBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMessageBo.java index e85ae3705..cbdd0d3eb 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMessageBo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMessageBo.java @@ -3,6 +3,7 @@ package org.dromara.system.domain.bo; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; @@ -45,7 +46,7 @@ public class SysMessageBo extends BaseAudit { private String content; /** 消息类型(AUTO自动/MANUAL手动) */ - @NotBlank(message = "消息类型不能为空", groups = { AddGroup.class, EditGroup.class }) +// @NotBlank(message = "消息类型不能为空", groups = { AddGroup.class, EditGroup.class }) @ExcelProperty(value = "消息类型") private String msgType; @@ -62,8 +63,8 @@ public class SysMessageBo extends BaseAudit { private Date scheduledTime; /** 发送范围(all:全部用户, userType:按用户类型, userIds:指定用户) */ - @NotBlank(message = "发送范围不能为空", groups = { AddGroup.class }) - private String sendScope; + @NotEmpty(message = "发送范围不能为空", groups = { AddGroup.class }) + private List sendScope; /** 接收用户ID列表 */ private List userIds; diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/event/MessageEvent.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/event/MessageEvent.java index 9cea99281..97cf2e934 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/event/MessageEvent.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/event/MessageEvent.java @@ -14,10 +14,47 @@ public class MessageEvent extends ApplicationEvent { private final SysMessageVo message; private final Long userId; + private final String userIdStr; + /** + * 使用 Long 类型用户 ID 创建消息事件 + */ public MessageEvent(Object source, SysMessageVo message, Long userId) { super(source); this.message = message; this.userId = userId; + this.userIdStr = userId != null ? String.valueOf(userId) : null; + } + + /** + * 使用 String 类型用户 ID 创建消息事件 + */ + public MessageEvent(Object source, SysMessageVo message, String userIdStr) { + super(source); + this.message = message; + this.userIdStr = userIdStr; + + // 尝试转换为 Long 类型 + Long parsedUserId = null; + try { + if (userIdStr != null) { + parsedUserId = Long.parseLong(userIdStr); + } + } catch (NumberFormatException e) { + // 无法解析为 Long 类型,保持 userId 为 null + } + this.userId = parsedUserId; + } + + /** + * 创建 MessageEvent 实例(支持 String 类型用户ID) + * + * @param source 事件源 + * @param message 消息对象 + * @param userIdStr 用户ID (String类型) + * @return MessageEvent 实例 + */ + public static MessageEvent createWithStringUserId(Object source, SysMessageVo message, String userIdStr) { + return new MessageEvent(source, message, userIdStr); } } \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/event/MessageEventListener.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/event/MessageEventListener.java index 0c38d1c0e..d5e5a49df 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/event/MessageEventListener.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/event/MessageEventListener.java @@ -2,8 +2,14 @@ package org.dromara.system.event; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; + +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.system.config.RocketMQConfig; +import org.dromara.system.consumer.MessageRocketMQConsumer; import org.dromara.system.domain.event.MessageEvent; +import org.dromara.system.service.IRocketMQService; import org.dromara.system.websocket.MessageWebSocketServer; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -15,10 +21,13 @@ import org.springframework.stereotype.Component; */ @Slf4j @Component -@RequiredArgsConstructor public class MessageEventListener { - private final MessageWebSocketServer messageWebSocketServer; + @Autowired + private IRocketMQService rocketMQService; + + @Autowired + private MessageWebSocketServer messageWebSocketServer; /** * 处理消息事件 @@ -27,7 +36,37 @@ public class MessageEventListener { @EventListener public void handleMessageEvent(MessageEvent event) { try { - messageWebSocketServer.sendMessage(event.getUserId(), String.valueOf(event.getMessage())); + // 创建消息包装对象 + MessageRocketMQConsumer.MessageWrapper wrapper = new MessageRocketMQConsumer.MessageWrapper(); + wrapper.setUserId(event.getUserIdStr()); + wrapper.setMessage(event.getMessage()); + + // 尝试通过RocketMQ发送消息 + boolean sendSuccess = false; + try { + rocketMQService.sendMessage( + RocketMQConfig.TOPIC_SYS_MSG, + RocketMQConfig.TAG_SYS_MSG, + wrapper + ); + sendSuccess = true; + log.info("消息事件已通过RocketMQ发送,userId: {}", event.getUserIdStr()); + } catch (Exception e) { + log.error("通过RocketMQ发送消息失败,将尝试直接通过WebSocket发送", e); + } + + // 如果RocketMQ发送失败,则尝试直接通过WebSocket发送 + if (!sendSuccess && event.getUserId() != null) { + try { + messageWebSocketServer.sendMessage( + event.getUserId(), + JsonUtils.toJsonString(event.getMessage()) + ); + log.info("消息事件已直接通过WebSocket发送,userId: {}", event.getUserId()); + } catch (Exception e) { + log.error("通过WebSocket发送消息失败", e); + } + } } catch (Exception e) { log.error("处理消息事件失败", e); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMessageService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMessageService.java index 0fb33c2e5..c8956ca11 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMessageService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMessageService.java @@ -34,22 +34,40 @@ public interface ISysMessageService extends IService { Page selectReadMessagesPage(Long userId, Page page); /** - * 发送消息给指定用户 + * 向用户发送消息 * - * @param message 消息内容 - * @param userId 用户ID + * @param message 消息信息 + * @param userId 用户ID (Long类型) * @return 结果 */ int sendMessageToUser(SysMessageBo message, Long userId); /** - * 发送消息给多个用户 + * 向用户发送消息 (String类型用户ID) * - * @param message 消息内容 - * @param userIds 用户ID列表 + * @param message 消息信息 + * @param userId 用户ID (String类型) * @return 结果 */ - int sendMessageToUsers(SysMessageBo message, List userIds); + int sendMessageToUserByStringId(SysMessageBo message, String userId); + + /** + * 向多个用户发送消息 + * + * @param message 消息信息 + * @param userIds 用户ID列表 (Long类型) + * @return 结果 + */ +// int sendMessageToUsers(SysMessageBo message, List userIds); + + /** + * 向多个用户发送消息 + * + * @param message 消息信息 + * @param userIds 用户ID列表 (String类型) + * @return 结果 + */ + int sendMessageToUsers(SysMessageBo message, List userIds); /** * 发送自动消息 diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java index 1fe554547..475359f44 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java @@ -228,4 +228,27 @@ public interface ISysUserService { * @return 结果 */ List selectUserListByDept(Long deptId); + + /** + * 根据条件查询用户列表 + * + * @param user 用户信息 + * @return 用户列表 + */ + List selectUserList(SysUserBo user); + + /** + * 查询所有会员用户信息(ums_member表) + * + * @return 会员用户列表 + */ + List selectAllMemberUsers(); + + /** + * 根据关键字查询会员用户信息(ums_member表) + * + * @param keyword 关键字(用户名、昵称或手机号) + * @return 会员用户列表 + */ + List selectMemberUsers(String keyword); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMessageServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMessageServiceImpl.java index 1578590cb..fda8bed01 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMessageServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysMessageServiceImpl.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.PageQuery; @@ -20,7 +21,9 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * 消息Service业务层处理 @@ -29,6 +32,7 @@ import java.util.List; */ @Service @RequiredArgsConstructor +@Slf4j public class SysMessageServiceImpl extends ServiceImpl implements ISysMessageService { private final SysMessageMapper messageMapper; @@ -55,7 +59,7 @@ public class SysMessageServiceImpl extends ServiceImpl messagePage = messageMapper.selectPage(page, lqw); Page voPage = new Page<>(messagePage.getCurrent(), messagePage.getSize(), messagePage.getTotal()); voPage.setRecords(MapstructUtils.convert(messagePage.getRecords(), SysMessageVo.class)); @@ -68,7 +72,7 @@ public class SysMessageServiceImpl extends ServiceImpl messagePage = messageMapper.selectPage(page, lqw); Page voPage = new Page<>(messagePage.getCurrent(), messagePage.getSize(), messagePage.getTotal()); voPage.setRecords(MapstructUtils.convert(messagePage.getRecords(), SysMessageVo.class)); @@ -97,25 +101,40 @@ public class SysMessageServiceImpl extends ServiceImpl userIds) { + public int sendMessageToUsers(SysMessageBo message, List userIds) { + if (userIds == null || userIds.isEmpty()) { + return 0; + } + // 保存消息 SysMessage entity = message.toEntity(); messageMapper.insert(entity); + // 批量创建消息用户关联 int count = 0; - for (Long userId : userIds) { - SysMessageUser messageUser = new SysMessageUser(); - messageUser.setMessageId(entity.getId()); - messageUser.setUserId(userId); - messageUser.setIsRead(false); - int rows = messageUserMapper.insert(messageUser); - if (rows > 0) { - count++; - // 发送WebSocket消息 - SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class); - eventPublisher.publishEvent(new MessageEvent(this, messageVo, userId)); + for (String userIdStr : userIds) { + if (StringUtils.isBlank(userIdStr)) { + continue; + } + + try { + Long userId = Long.parseLong(userIdStr); + SysMessageUser messageUser = new SysMessageUser(); + messageUser.setMessageId(entity.getId()); + messageUser.setUserId(userId); + messageUser.setIsRead(false); + int rows = messageUserMapper.insert(messageUser); + if (rows > 0) { + count++; + // 发送WebSocket消息 + SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class); + eventPublisher.publishEvent(MessageEvent.createWithStringUserId(this, messageVo, userIdStr)); + } + } catch (NumberFormatException e) { + log.error("无法将String类型的用户ID转换为Long: {}", userIdStr); } } + return count; } @@ -123,7 +142,7 @@ public class SysMessageServiceImpl extends ServiceImpl userIds) { message.setMsgType("AUTO"); - return sendMessageToUsers(message, userIds); + return sendMessageToUsers(message, userIds.stream().map(String::valueOf).collect(Collectors.toList())); } @Override @@ -204,5 +223,20 @@ public class SysMessageServiceImpl extends ServiceImpl selectUserListByDept(Long deptId) { - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.eq(SysUser::getDeptId, deptId); - lqw.orderByAsc(SysUser::getUserId); - return baseMapper.selectVoList(lqw); + return baseMapper.selectUserList(new LambdaQueryWrapper() + .eq(ObjectUtil.isNotNull(deptId), SysUser::getDeptId, deptId) + .eq(SysUser::getStatus, SystemConstants.NORMAL)); } /** @@ -720,4 +724,136 @@ public class SysUserServiceImpl implements ISysUserService, UserService { return selectListByIds(new ArrayList<>(userIds)); } + /** + * 根据条件查询用户列表 + * + * @param user 用户信息 + * @return 用户列表 + */ + @Override + public List selectUserList(SysUserBo user) { + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(SysUser::getStatus, SystemConstants.NORMAL); + + // 模糊搜索昵称或手机号 + if (StringUtils.isNotBlank(user.getNickName())) { + lqw.and(wrapper -> wrapper + .like(SysUser::getNickName, user.getNickName()) + .or() + .like(StringUtils.isNotBlank(user.getPhonenumber()), SysUser::getPhonenumber, user.getPhonenumber())); + } + + return baseMapper.selectUserList(lqw); + } + + @Override + public List selectAllMemberUsers() { + try { + // 获取 Spring 上下文中的 MemberService bean + IMemberService memberService = SpringUtils.getBean(IMemberService.class); + + if (memberService != null) { + // 查询所有会员信息 + List members = memberService.list(); + + // 将 Member 转换为 SysUserVo + return members.stream().map(member -> { + SysUserVo userVo = new SysUserVo(); + // 设置用户ID + userVo.setUserId(member.getId()); + // 设置用户名 + userVo.setUserName(member.getUserName()); + // 设置昵称 + userVo.setNickName(member.getNickname()); + // 设置手机号,如果加密了就用隐藏版本 + userVo.setPhonenumber(StringUtils.isNotBlank(member.getPhoneHidden()) ? + member.getPhoneHidden() : member.getPhoneEncrypted()); + // 设置头像 + if (StringUtils.isNotBlank(member.getAvatar())) { + try { + userVo.setAvatar(Long.parseLong(member.getAvatar())); + } catch (NumberFormatException e) { + log.warn("会员头像ID格式不正确: {}", member.getAvatar()); + } + } + // 设置性别 + if (member.getGender() != null) { + userVo.setSex(member.getGender().toString()); + } + // 设置状态 + userVo.setStatus(member.getStatus() == 1 ? "0" : "1"); // 转换状态值 + + return userVo; + }).collect(Collectors.toList()); + } + } catch (Exception e) { + log.error("查询会员信息失败", e); + } + + // 如果查询失败或会员服务不可用,返回空列表 + return new ArrayList<>(); + } + + @Override + public List selectMemberUsers(String keyword) { + try { + // 获取 Spring 上下文中的 MemberService bean + IMemberService memberService = SpringUtils.getBean(IMemberService.class); + + if (memberService != null) { + // 创建查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 如果有关键词,添加模糊查询条件(昵称、用户名、手机号) + if (StringUtils.isNotBlank(keyword)) { + queryWrapper.like(Member::getNickname, keyword) + .or() + .like(Member::getUserName, keyword) + .or() + .like(Member::getPhoneEncrypted, keyword) + .or() + .like(Member::getPhoneHidden, keyword); + } + + // 查询会员信息 + List members = memberService.list(queryWrapper); + + // 将 Member 转换为 SysUserVo + return members.stream().map(member -> { + SysUserVo userVo = new SysUserVo(); + // 设置用户ID + userVo.setUserId(member.getId()); + // 设置用户名 + userVo.setUserName(member.getUserName()); + // 设置昵称 + userVo.setNickName(member.getNickname()); + // 设置手机号,如果加密了就用隐藏版本 + userVo.setPhonenumber(StringUtils.isNotBlank(member.getPhoneHidden()) ? + member.getPhoneHidden() : member.getPhoneEncrypted()); + // 设置头像 + if (StringUtils.isNotBlank(member.getAvatar())) { + try { + userVo.setAvatar(Long.parseLong(member.getAvatar())); + } catch (NumberFormatException e) { + log.warn("会员头像ID格式不正确: {}", member.getAvatar()); + } + } + // 设置性别 + if (member.getGender() != null) { + userVo.setSex(member.getGender().toString()); + } + // 设置状态 + userVo.setStatus(member.getStatus() == 1 ? "0" : "1"); // 转换状态值 + + return userVo; + }).collect(Collectors.toList()); + } + } catch (Exception e) { + log.error("查询会员信息失败", e); + } + + // 如果查询失败或会员服务不可用,返回空列表 + return new ArrayList<>(); + } + }