修改消息通过WebSocket发送

This commit is contained in:
曹佳豪 2025-06-20 20:13:15 +08:00
parent e3ae60b5f8
commit bcc01a2522
15 changed files with 470 additions and 88 deletions

View File

@ -11,6 +11,13 @@ spring.boot.admin.client:
username: @monitor.username@ username: @monitor.username@
password: @monitor.password@ password: @monitor.password@
--- # 禁用RabbitMQ自动配置
spring:
rabbitmq:
enabled: false
autoconfigure:
exclude: org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
--- # snail-job 配置 --- # snail-job 配置
snail-job: snail-job:
enabled: false enabled: false
@ -133,6 +140,30 @@ redisson:
# 发布和订阅连接池大小 # 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50 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 邮件发送
mail: mail:
enabled: false enabled: false
@ -263,3 +294,17 @@ justauth:
client-id: 10**********6 client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitlab redirect-uri: ${justauth.address}/social-callback?source=gitlab
# 腾讯云IM配置
tencent:
im:
# 腾讯云 SDKAppId
sdkappid: 1600080789
# 密钥
secretkey: 311b5309d714a20f7f5b54360ee21b1e24ec208ebcd25ce8f47d24753bccc091
# 签名过期时间(秒)
expire: 604800
# 管理员账号
admin: administrator
# API调用密钥
api-secret: 311b5309d714a20f7f5b54360ee21b1e24ec208ebcd25ce8f47d24753bccc091

View File

@ -147,6 +147,7 @@ tenant:
- ums_account - ums_account
- ums_account_change_record - ums_account_change_record
- sys_message_template - sys_message_template
- sys_message_user
- ums_fans - ums_fans
- ums_block - ums_block
- oms_aftersale - oms_aftersale

View File

@ -14,6 +14,10 @@ import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/**
* RabbitMQ消费者
* 已禁用改用RocketMQ
*/
@Slf4j @Slf4j
@Component @Component
public class RabbitMQConsumer { public class RabbitMQConsumer {
@ -21,7 +25,7 @@ public class RabbitMQConsumer {
@Autowired @Autowired
private MsgService msgService; private MsgService msgService;
@RabbitListener(queues = {RabbitMQConfig.QUEUE_SYS_MSG}) // @RabbitListener(queues = {RabbitMQConfig.QUEUE_SYS_MSG})
public void watchQueue(String payload, Message message) { public void watchQueue(String payload, Message message) {
log.info(payload); log.info(payload);

View File

@ -9,7 +9,11 @@ import org.springframework.context.annotation.Configuration;
@Configuration /**
* RabbitMQ配置类
* 已禁用改用RocketMQ
*/
// @Configuration
public class RabbitMQConfig { public class RabbitMQConfig {
/** /**
@ -25,7 +29,7 @@ public class RabbitMQConfig {
public static final String QUEUE_SYS_MSG = "queue_sys_msg"; public static final String QUEUE_SYS_MSG = "queue_sys_msg";
@Bean(EXCHANGE_MSG) // @Bean(EXCHANGE_MSG)
public Exchange exchange() { public Exchange exchange() {
return ExchangeBuilder // 构建交换机 return ExchangeBuilder // 构建交换机
.topicExchange(EXCHANGE_MSG) // 使用topic类型参考https://www.rabbitmq.com/getstarted.html .topicExchange(EXCHANGE_MSG) // 使用topic类型参考https://www.rabbitmq.com/getstarted.html
@ -33,12 +37,12 @@ public class RabbitMQConfig {
.build(); .build();
} }
@Bean(QUEUE_SYS_MSG) // @Bean(QUEUE_SYS_MSG)
public Queue queue() { public Queue queue() {
return new Queue(QUEUE_SYS_MSG); return new Queue(QUEUE_SYS_MSG);
} }
@Bean // @Bean
public Binding binding(@Qualifier(EXCHANGE_MSG) Exchange exchange, public Binding binding(@Qualifier(EXCHANGE_MSG) Exchange exchange,
@Qualifier(QUEUE_SYS_MSG) Queue queue) { @Qualifier(QUEUE_SYS_MSG) Queue queue) {

View File

@ -1,12 +1,14 @@
package org.dromara.demo.service; package org.dromara.demo.service;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
/** /**
* 导出下拉框Excel示例 * 导出下拉框Excel示例
* *
* @author Emil.Zhang * @author Emil.Zhang
*/ */
@Service
public interface IExportExcelService { public interface IExportExcelService {
/** /**

View File

@ -3,8 +3,10 @@ package com.wzj.soopin.member.domain.po;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.common.core.domain.model.BaseAudit; import org.dromara.common.core.domain.model.BaseAudit;
import org.dromara.common.excel.annotation.Excel; import org.dromara.common.excel.annotation.Excel;
@ -19,6 +21,8 @@ import java.time.LocalDateTime;
@Schema(description="会员信息对象") @Schema(description="会员信息对象")
@Data @Data
@Builder @Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("ums_member") @TableName("ums_member")
public class Member extends BaseAudit { public class Member extends BaseAudit {
@Schema(description = "会员id") @Schema(description = "会员id")

View File

@ -131,6 +131,19 @@
<artifactId>tomcat-embed-websocket</artifactId> <artifactId>tomcat-embed-websocket</artifactId>
</dependency> </dependency>
<!-- RocketMQ -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
<!-- Spring Messaging for RocketMQ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -2,6 +2,7 @@ package org.dromara.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -9,6 +10,7 @@ import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.core.service.UserService; import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.MapstructUtils; 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.AddGroup;
import org.dromara.common.core.validate.EditGroup; import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.core.validate.QueryGroup; 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.domain.vo.SysUserVo;
import org.dromara.system.service.ISysMessageService; import org.dromara.system.service.ISysMessageService;
import org.dromara.system.service.ISysUserService; 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.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; 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 = "查询消息列表") @Tag(name = "查询消息列表")
@PostMapping("/list") @PostMapping("/list")
public R<Page<SysMessageVo>> list(@RequestBody SysMessageBo bo, @RequestBody Page<SysMessage> page) { public R<Page<SysMessageVo>> list(@RequestBody SysMessageBo bo, @RequestBody Page<SysMessage> page) {
@ -73,7 +73,7 @@ public class SysMessageController extends BaseController {
* *
* @param id 消息ID * @param id 消息ID
*/ */
@SaCheckPermission("system:message:query") // @SaCheckPermission("system:message:query")
@GetMapping("/{id}") @GetMapping("/{id}")
public R<SysMessageVo> getInfo(@NotNull(message = "消息ID不能为空") @PathVariable Long id) { public R<SysMessageVo> getInfo(@NotNull(message = "消息ID不能为空") @PathVariable Long id) {
return R.ok(messageService.selectMessageById(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) @Log(title = "消息管理", businessType = BusinessType.INSERT)
@RepeatSubmit() @RepeatSubmit()
@PostMapping("/send") @PostMapping("/send")
public R<Void> send(@Validated(AddGroup.class) @RequestBody SysMessageBo bo) { public R<Void> send(@Validated(AddGroup.class) @RequestBody SysMessageBo bo) {
List<SysUserVo> users = userService.selectUserListByDept(null); // 设置发送者ID为当前登录用户ID
List<Long> userIds; bo.setSenderId(getUserId());
switch (bo.getSendScope()) { List<String> userIdStrings;
case "all":
// 全部用户 // 获取第一个发送范围作为主要处理对象
userIds = users.stream().map(SysUserVo::getUserId).toList(); String scope = bo.getSendScope() != null && !bo.getSendScope().isEmpty() ?
break; bo.getSendScope().get(0) : "";
case "expert":
// 达人 // 如果是群发消息类型则根据类型筛选用户
userIds = users.stream() if ("all".equals(scope) || "expert".equals(scope) || "merchant".equals(scope) || "user".equals(scope)) {
.filter(user -> "expert".equals(user.getUserType())) List<SysUserVo> users = userService.selectUserListByDept(null);
.map(SysUserVo::getUserId) List<Long> userIds;
.toList();
break; switch (scope) {
case "merchant": case "all":
// 商户 // 全部用户
userIds = users.stream() userIds = users.stream().map(SysUserVo::getUserId).toList();
.filter(user -> "merchant".equals(user.getUserType())) break;
.map(SysUserVo::getUserId) case "expert":
.toList(); // 达人
break; userIds = users.stream()
case "user": .filter(user -> "expert".equals(user.getUserType()))
// 普通用户 .map(SysUserVo::getUserId)
userIds = users.stream() .toList();
.filter(user -> "user".equals(user.getUserType())) break;
.map(SysUserVo::getUserId) case "merchant":
.toList(); // 商户
break; userIds = users.stream()
default: .filter(user -> "merchant".equals(user.getUserType()))
// 其他情况如指定userIds .map(SysUserVo::getUserId)
userIds = bo.getUserIds(); .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) @Log(title = "消息管理", businessType = BusinessType.UPDATE)
@PutMapping("/mark/{id}") @PutMapping("/mark/{id}")
public R<Void> markAsRead(@NotNull(message = "消息ID不能为空") @PathVariable Long id) { public R<Void> markAsRead(@NotNull(message = "消息ID不能为空") @PathVariable Long id) {
@ -139,7 +163,7 @@ public class SysMessageController extends BaseController {
* *
* @param ids 消息ID串 * @param ids 消息ID串
*/ */
@SaCheckPermission("system:message:remove") // @SaCheckPermission("system:message:remove")
@Log(title = "消息管理", businessType = BusinessType.DELETE) @Log(title = "消息管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}") @DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "消息ID不能为空") @PathVariable Long[] ids) { public R<Void> 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 = "查询未读消息列表") @Tag(name = "查询未读消息列表")
@PostMapping("/unread") @PostMapping("/unread")
public R<Page<SysMessageVo>> unreadList(@RequestBody Page<SysMessage> page) { public R<Page<SysMessageVo>> unreadList(@RequestBody Page<SysMessage> page) {
@ -160,7 +184,7 @@ public class SysMessageController extends BaseController {
/** /**
* 获取已读消息列表 * 获取已读消息列表
*/ */
@SaCheckPermission("system:message:list") // @SaCheckPermission("system:message:list")
@Tag(name = "查询已读消息列表") @Tag(name = "查询已读消息列表")
@PostMapping("/read") @PostMapping("/read")
public R<Page<SysMessageVo>> readList(@RequestBody Page<SysMessage> page) { public R<Page<SysMessageVo>> readList(@RequestBody Page<SysMessage> page) {
@ -172,12 +196,9 @@ public class SysMessageController extends BaseController {
* 获取用户列表 * 获取用户列表
*/ */
// @SaCheckPermission("system:message:list") // @SaCheckPermission("system:message:list")
// @GetMapping("/user/list") @GetMapping("/user/list")
// public R<TableDataInfo<SysUserVo>> getUserList() { public R<List<SysUserVo>> getUserList(@RequestParam(required = false) String keyword) {
// PageQuery pageQuery = new PageQuery(); // 无论是否有关键词都查询 ums_member 表中的用户信息
// pageQuery.setPageNum(1); return R.ok(userService.selectMemberUsers(keyword));
// pageQuery.setPageSize(Integer.MAX_VALUE); }
// TableDataInfo<SysUserVo> users = userService.selectPageUserList(new SysUserBo(), pageQuery);
// return R.ok(users);
// }
} }

View File

@ -3,6 +3,7 @@ package org.dromara.system.domain.bo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -45,7 +46,7 @@ public class SysMessageBo extends BaseAudit {
private String content; private String content;
/** 消息类型AUTO自动/MANUAL手动 */ /** 消息类型AUTO自动/MANUAL手动 */
@NotBlank(message = "消息类型不能为空", groups = { AddGroup.class, EditGroup.class }) // @NotBlank(message = "消息类型不能为空", groups = { AddGroup.class, EditGroup.class })
@ExcelProperty(value = "消息类型") @ExcelProperty(value = "消息类型")
private String msgType; private String msgType;
@ -62,8 +63,8 @@ public class SysMessageBo extends BaseAudit {
private Date scheduledTime; private Date scheduledTime;
/** 发送范围all:全部用户, userType:按用户类型, userIds:指定用户) */ /** 发送范围all:全部用户, userType:按用户类型, userIds:指定用户) */
@NotBlank(message = "发送范围不能为空", groups = { AddGroup.class }) @NotEmpty(message = "发送范围不能为空", groups = { AddGroup.class })
private String sendScope; private List<String> sendScope;
/** 接收用户ID列表 */ /** 接收用户ID列表 */
private List<Long> userIds; private List<Long> userIds;

View File

@ -14,10 +14,47 @@ public class MessageEvent extends ApplicationEvent {
private final SysMessageVo message; private final SysMessageVo message;
private final Long userId; private final Long userId;
private final String userIdStr;
/**
* 使用 Long 类型用户 ID 创建消息事件
*/
public MessageEvent(Object source, SysMessageVo message, Long userId) { public MessageEvent(Object source, SysMessageVo message, Long userId) {
super(source); super(source);
this.message = message; this.message = message;
this.userId = userId; 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);
} }
} }

View File

@ -2,8 +2,14 @@ package org.dromara.system.event;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; 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.domain.event.MessageEvent;
import org.dromara.system.service.IRocketMQService;
import org.dromara.system.websocket.MessageWebSocketServer; import org.dromara.system.websocket.MessageWebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -15,10 +21,13 @@ import org.springframework.stereotype.Component;
*/ */
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor
public class MessageEventListener { public class MessageEventListener {
private final MessageWebSocketServer messageWebSocketServer; @Autowired
private IRocketMQService rocketMQService;
@Autowired
private MessageWebSocketServer messageWebSocketServer;
/** /**
* 处理消息事件 * 处理消息事件
@ -27,7 +36,37 @@ public class MessageEventListener {
@EventListener @EventListener
public void handleMessageEvent(MessageEvent event) { public void handleMessageEvent(MessageEvent event) {
try { 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) { } catch (Exception e) {
log.error("处理消息事件失败", e); log.error("处理消息事件失败", e);
} }

View File

@ -34,22 +34,40 @@ public interface ISysMessageService extends IService<SysMessage> {
Page<SysMessageVo> selectReadMessagesPage(Long userId, Page<SysMessage> page); Page<SysMessageVo> selectReadMessagesPage(Long userId, Page<SysMessage> page);
/** /**
* 发送消息给指定用户 * 向用户发送消息
* *
* @param message 消息内容 * @param message 消息信息
* @param userId 用户ID * @param userId 用户ID (Long类型)
* @return 结果 * @return 结果
*/ */
int sendMessageToUser(SysMessageBo message, Long userId); int sendMessageToUser(SysMessageBo message, Long userId);
/** /**
* 发送消息给多个用户 * 向用户发送消息 (String类型用户ID)
* *
* @param message 消息内容 * @param message 消息信息
* @param userIds 用户ID列表 * @param userId 用户ID (String类型)
* @return 结果 * @return 结果
*/ */
int sendMessageToUsers(SysMessageBo message, List<Long> userIds); int sendMessageToUserByStringId(SysMessageBo message, String userId);
/**
* 向多个用户发送消息
*
* @param message 消息信息
* @param userIds 用户ID列表 (Long类型)
* @return 结果
*/
// int sendMessageToUsers(SysMessageBo message, List<Long> userIds);
/**
* 向多个用户发送消息
*
* @param message 消息信息
* @param userIds 用户ID列表 (String类型)
* @return 结果
*/
int sendMessageToUsers(SysMessageBo message, List<String> userIds);
/** /**
* 发送自动消息 * 发送自动消息

View File

@ -228,4 +228,27 @@ public interface ISysUserService {
* @return 结果 * @return 结果
*/ */
List<SysUserVo> selectUserListByDept(Long deptId); List<SysUserVo> selectUserListByDept(Long deptId);
/**
* 根据条件查询用户列表
*
* @param user 用户信息
* @return 用户列表
*/
List<SysUserVo> selectUserList(SysUserBo user);
/**
* 查询所有会员用户信息ums_member表
*
* @return 会员用户列表
*/
List<SysUserVo> selectAllMemberUsers();
/**
* 根据关键字查询会员用户信息ums_member表
*
* @param keyword 关键字用户名昵称或手机号
* @return 会员用户列表
*/
List<SysUserVo> selectMemberUsers(String keyword);
} }

View File

@ -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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 消息Service业务层处理 * 消息Service业务层处理
@ -29,6 +32,7 @@ import java.util.List;
*/ */
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMessage> implements ISysMessageService { public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMessage> implements ISysMessageService {
private final SysMessageMapper messageMapper; private final SysMessageMapper messageMapper;
@ -55,7 +59,7 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
lqw.inSql(SysMessage::getId, lqw.inSql(SysMessage::getId,
"SELECT message_id FROM sys_message_user WHERE user_id = " + userId + " AND is_read = 0") "SELECT message_id FROM sys_message_user WHERE user_id = " + userId + " AND is_read = 0")
.orderByDesc(SysMessage::getCreateTime); .orderByDesc(SysMessage::getCreateTime);
Page<SysMessage> messagePage = messageMapper.selectPage(page, lqw); Page<SysMessage> messagePage = messageMapper.selectPage(page, lqw);
Page<SysMessageVo> voPage = new Page<>(messagePage.getCurrent(), messagePage.getSize(), messagePage.getTotal()); Page<SysMessageVo> voPage = new Page<>(messagePage.getCurrent(), messagePage.getSize(), messagePage.getTotal());
voPage.setRecords(MapstructUtils.convert(messagePage.getRecords(), SysMessageVo.class)); voPage.setRecords(MapstructUtils.convert(messagePage.getRecords(), SysMessageVo.class));
@ -68,7 +72,7 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
lqw.inSql(SysMessage::getId, lqw.inSql(SysMessage::getId,
"SELECT message_id FROM sys_message_user WHERE user_id = " + userId + " AND is_read = 1") "SELECT message_id FROM sys_message_user WHERE user_id = " + userId + " AND is_read = 1")
.orderByDesc(SysMessage::getCreateTime); .orderByDesc(SysMessage::getCreateTime);
Page<SysMessage> messagePage = messageMapper.selectPage(page, lqw); Page<SysMessage> messagePage = messageMapper.selectPage(page, lqw);
Page<SysMessageVo> voPage = new Page<>(messagePage.getCurrent(), messagePage.getSize(), messagePage.getTotal()); Page<SysMessageVo> voPage = new Page<>(messagePage.getCurrent(), messagePage.getSize(), messagePage.getTotal());
voPage.setRecords(MapstructUtils.convert(messagePage.getRecords(), SysMessageVo.class)); voPage.setRecords(MapstructUtils.convert(messagePage.getRecords(), SysMessageVo.class));
@ -97,25 +101,40 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public int sendMessageToUsers(SysMessageBo message, List<Long> userIds) { public int sendMessageToUsers(SysMessageBo message, List<String> userIds) {
if (userIds == null || userIds.isEmpty()) {
return 0;
}
// 保存消息 // 保存消息
SysMessage entity = message.toEntity(); SysMessage entity = message.toEntity();
messageMapper.insert(entity); messageMapper.insert(entity);
// 批量创建消息用户关联 // 批量创建消息用户关联
int count = 0; int count = 0;
for (Long userId : userIds) { for (String userIdStr : userIds) {
SysMessageUser messageUser = new SysMessageUser(); if (StringUtils.isBlank(userIdStr)) {
messageUser.setMessageId(entity.getId()); continue;
messageUser.setUserId(userId); }
messageUser.setIsRead(false);
int rows = messageUserMapper.insert(messageUser); try {
if (rows > 0) { Long userId = Long.parseLong(userIdStr);
count++; SysMessageUser messageUser = new SysMessageUser();
// 发送WebSocket消息 messageUser.setMessageId(entity.getId());
SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class); messageUser.setUserId(userId);
eventPublisher.publishEvent(new MessageEvent(this, messageVo, 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; return count;
} }
@ -123,7 +142,7 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public int sendAutoMessage(SysMessageBo message, List<Long> userIds) { public int sendAutoMessage(SysMessageBo message, List<Long> userIds) {
message.setMsgType("AUTO"); message.setMsgType("AUTO");
return sendMessageToUsers(message, userIds); return sendMessageToUsers(message, userIds.stream().map(String::valueOf).collect(Collectors.toList()));
} }
@Override @Override
@ -204,5 +223,20 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
return lqw; return lqw;
} }
@Override
@Transactional(rollbackFor = Exception.class)
public int sendMessageToUserByStringId(SysMessageBo message, String userId) {
if (StringUtils.isBlank(userId)) {
return 0;
}
try {
Long userIdLong = Long.parseLong(userId);
return sendMessageToUser(message, userIdLong);
} catch (NumberFormatException e) {
log.error("无法将String类型的用户ID转换为Long: {}", userId);
return 0;
}
}
} }

View File

@ -39,6 +39,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import com.wzj.soopin.member.domain.po.Member;
import com.wzj.soopin.member.service.IMemberService;
import org.dromara.common.core.utils.SpringUtils;
/** /**
* 用户 业务层处理 * 用户 业务层处理
@ -541,15 +546,14 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
/** /**
* 通过部门id查询当前部门所有用户 * 通过部门id查询当前部门所有用户
* *
* @param deptId 部门ID * @param deptId 部门id
* @return 用户信息集合信息 * @return 结果
*/ */
@Override @Override
public List<SysUserVo> selectUserListByDept(Long deptId) { public List<SysUserVo> selectUserListByDept(Long deptId) {
LambdaQueryWrapper<SysUser> lqw = Wrappers.lambdaQuery(); return baseMapper.selectUserList(new LambdaQueryWrapper<SysUser>()
lqw.eq(SysUser::getDeptId, deptId); .eq(ObjectUtil.isNotNull(deptId), SysUser::getDeptId, deptId)
lqw.orderByAsc(SysUser::getUserId); .eq(SysUser::getStatus, SystemConstants.NORMAL));
return baseMapper.selectVoList(lqw);
} }
/** /**
@ -720,4 +724,136 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
return selectListByIds(new ArrayList<>(userIds)); return selectListByIds(new ArrayList<>(userIds));
} }
/**
* 根据条件查询用户列表
*
* @param user 用户信息
* @return 用户列表
*/
@Override
public List<SysUserVo> selectUserList(SysUserBo user) {
LambdaQueryWrapper<SysUser> 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<SysUserVo> selectAllMemberUsers() {
try {
// 获取 Spring 上下文中的 MemberService bean
IMemberService memberService = SpringUtils.getBean(IMemberService.class);
if (memberService != null) {
// 查询所有会员信息
List<Member> 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<SysUserVo> selectMemberUsers(String keyword) {
try {
// 获取 Spring 上下文中的 MemberService bean
IMemberService memberService = SpringUtils.getBean(IMemberService.class);
if (memberService != null) {
// 创建查询条件
LambdaQueryWrapper<Member> 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<Member> 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<>();
}
} }