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 1/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E9=80=9A=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<>(); + } + } From 24544a18a8e2c2341702186cf569b5c8fcbcaacc 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:57 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E9=80=9A=E8=BF=87WebSocket=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/system/config/RocketMQConfig.java | 27 +++++ .../system/config/RocketMQConfiguration.java | 50 +++++++++ .../consumer/MessageRocketMQConsumer.java | 105 ++++++++++++++++++ .../system/service/IRocketMQService.java | 36 ++++++ .../service/impl/RocketMQServiceImpl.java | 88 +++++++++++++++ .../main/resources/META-INF/spring.factories | 2 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + 7 files changed, 309 insertions(+) create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfig.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfiguration.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/consumer/MessageRocketMQConsumer.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/IRocketMQService.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/RocketMQServiceImpl.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/resources/META-INF/spring.factories create mode 100644 ruoyi-modules/ruoyi-system/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfig.java new file mode 100644 index 000000000..c0b5fa45f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfig.java @@ -0,0 +1,27 @@ +package org.dromara.system.config; + +import org.springframework.context.annotation.Configuration; + +/** + * RocketMQ 配置类 + * + * @author wzj + */ +@Configuration +public class RocketMQConfig { + + /** + * 系统消息主题 + */ + public static final String TOPIC_SYS_MSG = "TOPIC_SYS_MSG"; + + /** + * 系统消息消费者组 + */ + public static final String CONSUMER_GROUP_SYS_MSG = "CONSUMER_GROUP_SYS_MSG"; + + /** + * 系统消息标签 + */ + public static final String TAG_SYS_MSG = "SYS_MSG"; +} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfiguration.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfiguration.java new file mode 100644 index 000000000..2d30b5af9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfiguration.java @@ -0,0 +1,50 @@ +package org.dromara.system.config; + +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration; +import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.apache.rocketmq.spring.support.RocketMQMessageConverter; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * RocketMQ配置类 + * 确保RocketMQTemplate被正确初始化 + * + * @author wzj + */ +@Configuration +@EnableConfigurationProperties(RocketMQProperties.class) +@ConditionalOnClass(DefaultMQProducer.class) +@Import(RocketMQAutoConfiguration.class) +public class RocketMQConfiguration { + + /** + * 确保RocketMQTemplate被正确初始化 + * 这个Bean会由RocketMQAutoConfiguration自动创建 + * 此处仅作为备用方案 + */ + @Bean + @ConditionalOnMissingBean(RocketMQTemplate.class) + @ConditionalOnBean(DefaultMQProducer.class) + public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer, + RocketMQMessageConverter messageConverter) { + RocketMQTemplate rocketMQTemplate = new RocketMQTemplate(); + rocketMQTemplate.setProducer(mqProducer); + rocketMQTemplate.setMessageConverter(messageConverter.getMessageConverter()); + return rocketMQTemplate; + } + + @Bean + @ConditionalOnMissingBean(RocketMQMessageConverter.class) + public RocketMQMessageConverter rocketMQMessageConverter() { + return new RocketMQMessageConverter(); + } +} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/consumer/MessageRocketMQConsumer.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/consumer/MessageRocketMQConsumer.java new file mode 100644 index 000000000..33062a200 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/consumer/MessageRocketMQConsumer.java @@ -0,0 +1,105 @@ +package org.dromara.system.consumer; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; + +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.system.config.RocketMQConfig; +import org.dromara.system.domain.SysMessageUser; +import org.dromara.system.domain.vo.SysMessageVo; +import org.dromara.system.mapper.SysMessageUserMapper; +import org.dromara.system.websocket.MessageWebSocketServer; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * RocketMQ消息消费者 + * + * @author wzj + */ +@Slf4j +@Component +@RequiredArgsConstructor +@RocketMQMessageListener( + topic = RocketMQConfig.TOPIC_SYS_MSG, + consumerGroup = RocketMQConfig.CONSUMER_GROUP_SYS_MSG, + selectorExpression = RocketMQConfig.TAG_SYS_MSG +) +public class MessageRocketMQConsumer implements RocketMQListener { + + private final MessageWebSocketServer messageWebSocketServer; + private final SysMessageUserMapper messageUserMapper; + + @Override + public void onMessage(String message) { + try { + log.info("接收到RocketMQ消息: {}", message); + // 解析消息格式,格式为: {"userId": "123", "message": {...}} + MessageWrapper wrapper = JsonUtils.parseObject(message, MessageWrapper.class); + if (wrapper != null && wrapper.getUserId() != null && wrapper.getMessage() != null) { + // 将String类型的userId转换为Long类型 + Long userIdLong = null; + try { + if (StringUtils.isNotBlank(wrapper.getUserId())) { + userIdLong = Long.parseLong(wrapper.getUserId()); + } + } catch (NumberFormatException e) { + log.error("用户ID转换失败: {}", wrapper.getUserId(), e); + return; + } + + if (userIdLong != null) { + // 保存消息用户关联记录 + try { + SysMessageUser messageUser = new SysMessageUser(); + messageUser.setMessageId(wrapper.getMessage().getId()); + messageUser.setUserId(userIdLong); + messageUser.setIsRead(false); + messageUserMapper.insert(messageUser); + log.info("消息用户关联记录保存成功,messageId: {}, userId: {}", wrapper.getMessage().getId(), userIdLong); + } catch (Exception e) { + log.error("保存消息用户关联记录失败", e); + } + + // 发送WebSocket消息 + messageWebSocketServer.sendMessage(userIdLong, JsonUtils.toJsonString(wrapper.getMessage())); + log.info("通过WebSocket发送消息成功,userId: {}", userIdLong); + } else { + log.warn("用户ID为空或无效: {}", wrapper.getUserId()); + } + } else { + log.warn("消息格式不正确: {}", message); + } + } catch (Exception e) { + log.error("处理RocketMQ消息失败", e); + } + } + + /** + * 消息包装类 + */ + public static class MessageWrapper { + private String userId; + private SysMessageVo message; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public SysMessageVo getMessage() { + return message; + } + + public void setMessage(SysMessageVo message) { + this.message = message; + } + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/IRocketMQService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/IRocketMQService.java new file mode 100644 index 000000000..8887a6658 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/IRocketMQService.java @@ -0,0 +1,36 @@ +package org.dromara.system.service; + +/** + * RocketMQ服务接口 + * + * @author wzj + */ +public interface IRocketMQService { + + /** + * 发送消息到指定主题 + * + * @param topic 主题 + * @param message 消息内容 + */ + void sendMessage(String topic, Object message); + + /** + * 发送消息到指定主题和标签 + * + * @param topic 主题 + * @param tag 标签 + * @param message 消息内容 + */ + void sendMessage(String topic, String tag, Object message); + + /** + * 发送延迟消息 + * + * @param topic 主题 + * @param tag 标签 + * @param message 消息内容 + * @param delayTimeLevel 延迟级别 (1s/5s/10s/30s/1m/2m/3m/4m/5m/6m/7m/8m/9m/10m/20m/30m/1h/2h) + */ + void sendDelayMessage(String topic, String tag, Object message, int delayTimeLevel); +} \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/RocketMQServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/RocketMQServiceImpl.java new file mode 100644 index 000000000..5e8049f7a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/RocketMQServiceImpl.java @@ -0,0 +1,88 @@ +package org.dromara.system.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.client.producer.SendCallback; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.system.service.IRocketMQService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Service; + +/** + * RocketMQ服务实现类 + * + * @author wzj + */ +@Slf4j +@Service +public class RocketMQServiceImpl implements IRocketMQService { + + @Autowired(required = false) + private RocketMQTemplate rocketMQTemplate; + + @Override + public void sendMessage(String topic, Object message) { + if (rocketMQTemplate == null) { + log.error("RocketMQTemplate未初始化,无法发送消息"); + return; + } + + try { + String jsonMessage = JsonUtils.toJsonString(message); + rocketMQTemplate.convertAndSend(topic, jsonMessage); + log.info("发送消息到RocketMQ成功,topic: {}, message: {}", topic, jsonMessage); + } catch (Exception e) { + log.error("发送消息到RocketMQ失败,topic: {}, message: {}", topic, message, e); + } + } + + @Override + public void sendMessage(String topic, String tag, Object message) { + if (rocketMQTemplate == null) { + log.error("RocketMQTemplate未初始化,无法发送消息"); + return; + } + + try { + String destination = topic + ":" + tag; + String jsonMessage = JsonUtils.toJsonString(message); + rocketMQTemplate.convertAndSend(destination, jsonMessage); + log.info("发送消息到RocketMQ成功,destination: {}, message: {}", destination, jsonMessage); + } catch (Exception e) { + log.error("发送消息到RocketMQ失败,topic: {}, tag: {}, message: {}", topic, tag, message, e); + } + } + + @Override + public void sendDelayMessage(String topic, String tag, Object message, int delayTimeLevel) { + if (rocketMQTemplate == null) { + log.error("RocketMQTemplate未初始化,无法发送延迟消息"); + return; + } + + try { + String destination = topic + ":" + tag; + String jsonMessage = JsonUtils.toJsonString(message); + rocketMQTemplate.asyncSend(destination, MessageBuilder.withPayload(jsonMessage).build(), + new SendCallback() { + @Override + public void onSuccess(SendResult sendResult) { + log.info("发送延迟消息到RocketMQ成功,destination: {}, message: {}, result: {}", + destination, jsonMessage, sendResult); + } + + @Override + public void onException(Throwable throwable) { + log.error("发送延迟消息到RocketMQ异常,destination: {}, message: {}", + destination, jsonMessage, throwable); + } + }, 3000, delayTimeLevel); + } catch (Exception e) { + log.error("发送延迟消息到RocketMQ失败,topic: {}, tag: {}, message: {}", topic, tag, message, e); + } + } +} + \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/META-INF/spring.factories b/ruoyi-modules/ruoyi-system/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..a13a85b2b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.dromara.system.config.RocketMQConfiguration \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-modules/ruoyi-system/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..ea40cbdfa --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.dromara.system.config.RocketMQConfig \ No newline at end of file From 5d5702e2025993e0a086b6b09c5de89480cbc254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BA=86=E7=A5=A5?= Date: Sat, 21 Jun 2025 14:40:22 +0800 Subject: [PATCH 3/6] =?UTF-8?q?[fix]=E5=85=85=E5=80=BC=E4=B8=8E=E6=8F=90?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 2 + .../member/controller/ChargeController.java | 77 ++++++++++++ .../member/controller/WithdrawController.java | 69 +++++++++++ .../soopin/member/convert/ChargeConvert.java | 19 +++ .../member/convert/WithdrawConvert.java | 16 +++ .../wzj/soopin/member/domain/bo/ChargeBO.java | 107 +++++++++++++++++ .../soopin/member/domain/bo/WithdrawBO.java | 109 +++++++++++++++++ .../wzj/soopin/member/domain/po/Charge.java | 79 +++++++++++++ .../wzj/soopin/member/domain/po/Withdraw.java | 45 +++++++ .../wzj/soopin/member/domain/vo/ChargeVO.java | 109 +++++++++++++++++ .../soopin/member/domain/vo/WithdrawVO.java | 111 ++++++++++++++++++ .../soopin/member/mapper/ChargeMapper.java | 14 +++ .../soopin/member/mapper/WithdrawMapper.java | 14 +++ .../soopin/member/service/IChargeService.java | 11 ++ .../member/service/IWithdrawService.java | 12 ++ .../service/impl/ChargeServiceImpl.java | 33 ++++++ .../service/impl/WithdrawServiceImpl.java | 24 ++++ 17 files changed, 851 insertions(+) create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ChargeController.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/convert/ChargeConvert.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/convert/WithdrawConvert.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/ChargeBO.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/WithdrawBO.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Charge.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/ChargeVO.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/WithdrawVO.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/mapper/ChargeMapper.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/mapper/WithdrawMapper.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IChargeService.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IWithdrawService.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/ChargeServiceImpl.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/WithdrawServiceImpl.java diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index b92c30ae3..a39a68e61 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -153,6 +153,8 @@ tenant: - ums_forbidden - ums_member_bank - ums_tenant_forbidden + - ums_charge + - ums_withdraw - oms_aftersale - oms_aftersale_item - oms_order diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ChargeController.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ChargeController.java new file mode 100644 index 000000000..c021271e4 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ChargeController.java @@ -0,0 +1,77 @@ +package com.wzj.soopin.member.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.wzj.soopin.member.convert.ChargeConvert; +import com.wzj.soopin.member.convert.MemberForbiddenConvert; +import com.wzj.soopin.member.domain.bo.ChargeBO; +import com.wzj.soopin.member.domain.bo.MemberForbiddenBO; +import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.po.MemberForbidden; +import com.wzj.soopin.member.domain.vo.ChargeVO; +import com.wzj.soopin.member.domain.vo.MemberForbiddenVO; +import com.wzj.soopin.member.service.IChargeService; +import com.wzj.soopin.member.service.IMemberForbiddenService; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.springframework.web.bind.annotation.*; + +/** + * 用户封禁 + */ +@Tag(name = "用户封禁") +@RestController +@RequestMapping("/ums/charge") +@RequiredArgsConstructor +public class ChargeController { + + private final IChargeService service; + private final ChargeConvert convert; + + @Tag(name = "查询列表") + @PostMapping("/list") + public R> list(@RequestBody ChargeBO bo, @RequestBody Page page) { + Page pages = service.page(page, bo.toWrapper()); + return R.ok(convert.toVO(pages)); + } + + @Tag(name = "新增") + @Log(title = "新增 ", businessType = BusinessType.INSERT) + @PostMapping("/add") + public R add(@RequestBody ChargeBO bo) { + return R.ok(service.save(convert.toPo(bo))); + } + + @Tag(name = "详情") + @GetMapping(value = "/{id}") + public R getInfo(@PathVariable("id") Long id) { + return R.ok(convert.toVO(service.getById(id))); + } + + @Tag(name = ("处理")) + @Log(title = "修改", businessType = BusinessType.UPDATE) + @PostMapping("/update") + public R update(@RequestBody ChargeBO bo) { + service.save(convert.toPo(bo)); + return R.ok(); + } + + @Tag(name = ("处理")) + @Log(title = "修改", businessType = BusinessType.UPDATE) + @PostMapping("/audit") + public R audit(@RequestBody ChargeBO bo) { + service.save(convert.toPo(bo)); + return R.ok(); + } + + + @Tag(name = "删除") + @Log(title = "删除", businessType = BusinessType.DELETE) + @DeleteMapping("/{id}") + public R remove(@PathVariable Long id) { + return R.ok(service.removeById(id)); + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java new file mode 100644 index 000000000..59d4ba295 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java @@ -0,0 +1,69 @@ +package com.wzj.soopin.member.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.wzj.soopin.member.convert.MemberForbiddenConvert; +import com.wzj.soopin.member.convert.WithdrawConvert; +import com.wzj.soopin.member.domain.bo.MemberForbiddenBO; +import com.wzj.soopin.member.domain.bo.WithdrawBO; +import com.wzj.soopin.member.domain.po.MemberForbidden; +import com.wzj.soopin.member.domain.po.Withdraw; +import com.wzj.soopin.member.domain.vo.MemberForbiddenVO; +import com.wzj.soopin.member.domain.vo.WithdrawVO; +import com.wzj.soopin.member.service.IMemberForbiddenService; +import com.wzj.soopin.member.service.IWithdrawService; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.springframework.web.bind.annotation.*; + +/** + * 用户封禁 + */ +@Tag(name = "提现") +@RestController +@RequestMapping("/ums/withdraw") +@RequiredArgsConstructor +public class WithdrawController { + + private final IWithdrawService service; + private final WithdrawConvert convert; + + @Tag(name = "查询列表") + @PostMapping("/list") + public R> list(@RequestBody WithdrawBO bo, @RequestBody Page page) { + Page pages = service.page(page, bo.toWrapper()); + return R.ok(convert.toVO(pages)); + } + + @Tag(name = "新增") + @Log(title = "新增 ", businessType = BusinessType.INSERT) + @PostMapping("/add") + public R add(@RequestBody WithdrawBO bo) { + return R.ok(service.save(convert.toPo(bo))); + } + + @Tag(name = "详情") + @GetMapping(value = "/{id}") + public R getInfo(@PathVariable("id") Long id) { + return R.ok(convert.toVO(service.getById(id))); + } + + @Tag(name = ("处理")) + @Log(title = "修改", businessType = BusinessType.UPDATE) + @PostMapping("/update") + public R update(@RequestBody WithdrawBO bo) { + service.save(convert.toPo(bo)); + return R.ok(); + } + + + @Tag(name = "删除") + @Log(title = "删除", businessType = BusinessType.DELETE) + @DeleteMapping("/{id}") + public R remove(@PathVariable Long id) { + return R.ok(service.removeById(id)); + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/convert/ChargeConvert.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/convert/ChargeConvert.java new file mode 100644 index 000000000..5616731ef --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/convert/ChargeConvert.java @@ -0,0 +1,19 @@ +package com.wzj.soopin.member.convert; + +import com.wzj.soopin.member.domain.bo.ChargeBO; +import com.wzj.soopin.member.domain.bo.FansBO; +import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.po.Fans; +import com.wzj.soopin.member.domain.vo.ChargeVO; +import com.wzj.soopin.member.domain.vo.FansVO; +import org.dromara.common.web.core.BaseConverter; +import org.mapstruct.Mapper; + +/** + * 充值 + * + * @author zcc + */ +@Mapper(componentModel = "spring",uses = BaseConverter.class) +public interface ChargeConvert extends BaseConverter { +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/convert/WithdrawConvert.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/convert/WithdrawConvert.java new file mode 100644 index 000000000..3abb98d77 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/convert/WithdrawConvert.java @@ -0,0 +1,16 @@ +package com.wzj.soopin.member.convert; + +import com.wzj.soopin.member.domain.bo.WithdrawBO; +import com.wzj.soopin.member.domain.po.Withdraw; +import com.wzj.soopin.member.domain.vo.WithdrawVO; +import org.dromara.common.web.core.BaseConverter; +import org.mapstruct.Mapper; + +/** + * 提现 + * + * @author zcc + */ +@Mapper(componentModel = "spring",uses = BaseConverter.class) +public interface WithdrawConvert extends BaseConverter { +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/ChargeBO.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/ChargeBO.java new file mode 100644 index 000000000..3182146d5 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/ChargeBO.java @@ -0,0 +1,107 @@ +package com.wzj.soopin.member.domain.bo; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.po.Withdraw; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.dromara.common.core.domain.BaseBO; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 充值 + * + * @author wzj + * @date 2023-03-07 + */ +@Schema(description="充值") +@Data +public class ChargeBO extends BaseBO { + + /** + * 主键 + */ + @Schema(description ="主键") + private Long id; + + /** + * 提现码 + */ + @Schema(description ="充值码") + private String code; + + /** + * 会员id + */ + @Schema(description ="会员id") + private Long memberId; + + + + /** + * 金额 + */ + @Schema(description ="金额") + private BigDecimal money; + + /** + * 手续费 + */ + @Schema(description ="手续费") + private BigDecimal fee; + + /** + * 实际金额 + */ + @Schema(description ="实际金额") + private BigDecimal actualMoney; + + + /** + * 状态 + */ + @Schema(description ="状态") + private Integer status; + /** + * 类型 + */ + @Schema(description ="类型") + private Integer type; + /** + * 审核人 + */ + @Schema(description ="审核人") + private Long auditBy; + + /** + * 审核时间 + */ + @Schema(description ="审核时间") + private LocalDateTime auditTime; + + /** + * 提现方式 + */ + @Schema(description ="提现方式") + private Integer method; + + /** + * 审核状态 + */ + @Schema(description ="审核状态") + private Integer auditStatus; + + @Override + public LambdaQueryWrapper toWrapper() { + return super.toWrapper().eq(id!=null, Charge::getId, id) + .eq(memberId!=null, Charge::getMemberId, memberId) + .eq(money!=null, Charge::getMoney, money) + .eq(fee!=null, Charge::getFee, fee) + .eq(actualMoney!=null, Charge::getActualMoney, actualMoney) + .eq(status!=null, Charge::getStatus, status) + .eq(type!=null, Charge::getType, type); + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/WithdrawBO.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/WithdrawBO.java new file mode 100644 index 000000000..c98fc4f89 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/WithdrawBO.java @@ -0,0 +1,109 @@ +package com.wzj.soopin.member.domain.bo; + + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.wzj.soopin.member.domain.po.Withdraw; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.dromara.common.core.domain.BaseBO; +import org.dromara.common.core.domain.model.BaseAudit; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 提现 + * + * @author wzj + * @date 2023-03-07 + */ +@Schema(description="提现") +@Data +public class WithdrawBO extends BaseBO { + + /** + * 主键 + */ + @Schema(description ="主键") + private Long id; + + /** + * 提现码 + */ + @Schema(description ="提现码") + private String code; + + /** + * 会员id + */ + @Schema(description ="会员id") + private Long memberId; + + + + /** + * 金额 + */ + @Schema(description ="金额") + private BigDecimal money; + + /** + * 手续费 + */ + @Schema(description ="手续费") + private BigDecimal fee; + + /** + * 实际金额 + */ + @Schema(description ="实际金额") + private BigDecimal actualMoney; + + + /** + * 状态 + */ + @Schema(description ="状态") + private Integer status; + /** + * 类型 + */ + @Schema(description ="类型") + private Integer type; + /** + * 审核人 + */ + @Schema(description ="审核人") + private Long auditBy; + + /** + * 审核时间 + */ + @Schema(description ="审核时间") + private LocalDateTime auditTime; + + /** + * 提现方式 + */ + @Schema(description ="提现方式") + private Integer method; + + /** + * 审核状态 + */ + @Schema(description ="审核状态") + private Integer auditStatus; + + @Override + public LambdaQueryWrapper toWrapper() { + return super.toWrapper().eq(id!=null, Withdraw::getId, id) + .eq(memberId!=null, Withdraw::getMemberId, memberId) + .eq(money!=null, Withdraw::getMoney, money) + .eq(fee!=null, Withdraw::getFee, fee) + .eq(actualMoney!=null, Withdraw::getActualMoney, actualMoney) + .eq(status!=null, Withdraw::getStatus, status) + .eq(type!=null, Withdraw::getType, type); + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Charge.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Charge.java new file mode 100644 index 000000000..537a09150 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Charge.java @@ -0,0 +1,79 @@ +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.Data; +import org.dromara.common.core.domain.model.BaseAudit; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 充值 + */ +@Schema(description="充值") +@Data +@TableName("ums_charge") +public class Charge extends BaseAudit { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 充值码 + */ + private String code; + + /** + * 会员id + */ + private Long memberId; + + /** + * 金额 + */ + private BigDecimal money; + + /** + * 手续费 + */ + private BigDecimal fee; + + /** + * 实际金额 + */ + private BigDecimal actualMoney; + + + /** + * 状态 + */ + private Integer status; + /** + * 类型 + */ + private Integer type; + /** + * 审核人 + */ + private Long auditBy; + + /** + * 审核时间 + */ + private LocalDateTime auditTime; + + /** + * 提现方式 + */ + private Integer method; + + /** + * 审核状态 + */ + private Integer auditStatus; +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Withdraw.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Withdraw.java index cf6ed0d9d..c5c6d8dd2 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Withdraw.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Withdraw.java @@ -1,6 +1,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.Data; import org.dromara.common.core.domain.model.BaseAudit; import java.math.BigDecimal; @@ -12,27 +16,68 @@ import java.time.LocalDateTime; * @author wzj * @date 2023-03-07 */ +@Schema(description="提现") +@Data +@TableName("ums_withdraw") public class Withdraw extends BaseAudit { + /** + * 主键 + */ + @TableId private Long id; + /** + * 提现码 + */ private String code; + /** + * 会员id + */ private Long memberId; + /** + * 金额 + */ private BigDecimal money; + /** + * 手续费 + */ private BigDecimal fee; + /** + * 实际金额 + */ private BigDecimal actualMoney; + + /** + * 状态 + */ private Integer status; + /** + * 类型 + */ private Integer type; + /** + * 审核人 + */ private Long auditBy; + /** + * 审核时间 + */ private LocalDateTime auditTime; + /** + * 提现方式 + */ private Integer method; + /** + * 审核状态 + */ private Integer auditStatus; } diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/ChargeVO.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/ChargeVO.java new file mode 100644 index 000000000..2a1787ae4 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/ChargeVO.java @@ -0,0 +1,109 @@ +package com.wzj.soopin.member.domain.vo; + + +import com.alibaba.excel.annotation.ExcelProperty; +import com.wzj.soopin.member.annotation.MemberFillField; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.dromara.common.core.domain.model.BaseAudit; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 提现 + * + * @author wzj + * @date 2023-03-07 + */ +@Schema(description="充值") +@Data +public class ChargeVO extends BaseAudit { + + /** + * 主键 + */ + @Schema(description ="主键") + @ExcelProperty(value = "主键", order = 1) + private Long id; + + /** + * 提现码 + */ + + @Schema(description ="充值码") + @ExcelProperty(value = "充值码", order = 2) + private String code; + + /** + * 会员id + */ + @Schema(description ="会员id") + @ExcelProperty(value ="会员id", order = 3) + private Long memberId; + + @Schema(description ="会员") + @MemberFillField(id = "memberId") + private MemberVO member; + /** + * 金额 + */ + @Schema(description ="金额") + @ExcelProperty(value ="金额", order = 4) + private BigDecimal money; + + /** + * 手续费 + */ + @Schema(description ="手续费") + @ExcelProperty(value ="手续费", order = 5) + private BigDecimal fee; + + /** + * 实际金额 + */ + @Schema(description ="实际金额") + @ExcelProperty(value ="实际金额", order = 6) + private BigDecimal actualMoney; + + + /** + * 状态 + */ + @Schema(description ="状态") + @ExcelProperty(value ="状态", order = 7) + private Integer status; + /** + * 类型 + */ + @Schema(description ="类型") + @ExcelProperty(value ="类型", order = 8) + private Integer type; + /** + * 审核人 + */ + @Schema(description ="审核人") + @ExcelProperty(value ="审核人", order = 9) + private Long auditBy; + + /** + * 审核时间 + */ + @Schema(description ="审核时间") + @ExcelProperty(value ="审核时间", order = 10) + private LocalDateTime auditTime; + + /** + * 提现方式 + */ + @Schema(description ="提现方式") + @ExcelProperty(value ="提现方式", order = 11) + private Integer method; + + /** + * 审核状态 + */ + @Schema(description ="审核状态") + @ExcelProperty(value ="审核状态", order = 12) + private Integer auditStatus; +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/WithdrawVO.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/WithdrawVO.java new file mode 100644 index 000000000..abb4b76c4 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/WithdrawVO.java @@ -0,0 +1,111 @@ +package com.wzj.soopin.member.domain.vo; + + +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.wzj.soopin.member.annotation.MemberFillField; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.dromara.common.core.domain.model.BaseAudit; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 提现 + * + * @author wzj + * @date 2023-03-07 + */ +@Schema(description="提现") +@Data +public class WithdrawVO extends BaseAudit { + + /** + * 主键 + */ + @Schema(description ="主键") + @ExcelProperty(value = "主键", order = 1) + private Long id; + + /** + * 提现码 + */ + + @Schema(description ="提现码") + @ExcelProperty(value = "提现码", order = 2) + private String code; + + /** + * 会员id + */ + @Schema(description ="会员id") + @ExcelProperty(value ="会员id", order = 3) + private Long memberId; + + @Schema(description ="会员") + @MemberFillField(id = "memberId") + private MemberVO member; + /** + * 金额 + */ + @Schema(description ="金额") + @ExcelProperty(value ="金额", order = 4) + private BigDecimal money; + + /** + * 手续费 + */ + @Schema(description ="手续费") + @ExcelProperty(value ="手续费", order = 5) + private BigDecimal fee; + + /** + * 实际金额 + */ + @Schema(description ="实际金额") + @ExcelProperty(value ="实际金额", order = 6) + private BigDecimal actualMoney; + + + /** + * 状态 + */ + @Schema(description ="状态") + @ExcelProperty(value ="状态", order = 7) + private Integer status; + /** + * 类型 + */ + @Schema(description ="类型") + @ExcelProperty(value ="类型", order = 8) + private Integer type; + /** + * 审核人 + */ + @Schema(description ="审核人") + @ExcelProperty(value ="审核人", order = 9) + private Long auditBy; + + /** + * 审核时间 + */ + @Schema(description ="审核时间") + @ExcelProperty(value ="审核时间", order = 10) + private LocalDateTime auditTime; + + /** + * 提现方式 + */ + @Schema(description ="提现方式") + @ExcelProperty(value ="提现方式", order = 11) + private Integer method; + + /** + * 审核状态 + */ + @Schema(description ="审核状态") + @ExcelProperty(value ="审核状态", order = 12) + private Integer auditStatus; +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/mapper/ChargeMapper.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/mapper/ChargeMapper.java new file mode 100644 index 000000000..442ca5ddc --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/mapper/ChargeMapper.java @@ -0,0 +1,14 @@ +package com.wzj.soopin.member.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.po.Withdraw; + +/** + * 意见反馈Mapper接口 + * + * @author zcc + */ +public interface ChargeMapper extends BaseMapper { + +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/mapper/WithdrawMapper.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/mapper/WithdrawMapper.java new file mode 100644 index 000000000..a1fa09e30 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/mapper/WithdrawMapper.java @@ -0,0 +1,14 @@ +package com.wzj.soopin.member.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.wzj.soopin.member.domain.po.Feedback; +import com.wzj.soopin.member.domain.po.Withdraw; + +/** + * 意见反馈Mapper接口 + * + * @author zcc + */ +public interface WithdrawMapper extends BaseMapper { + +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IChargeService.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IChargeService.java new file mode 100644 index 000000000..ca41a598c --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IChargeService.java @@ -0,0 +1,11 @@ +package com.wzj.soopin.member.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.po.Withdraw; + +public interface IChargeService extends IService { + + boolean audit(Long id, String status); + +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IWithdrawService.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IWithdrawService.java new file mode 100644 index 000000000..9dea55b44 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IWithdrawService.java @@ -0,0 +1,12 @@ +package com.wzj.soopin.member.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.wzj.soopin.member.domain.po.Feedback; +import com.wzj.soopin.member.domain.po.Withdraw; +import com.wzj.soopin.member.domain.vo.FeedbackVO; + +import java.io.Serializable; + +public interface IWithdrawService extends IService { + +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/ChargeServiceImpl.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/ChargeServiceImpl.java new file mode 100644 index 000000000..c40cb76d8 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/ChargeServiceImpl.java @@ -0,0 +1,33 @@ +package com.wzj.soopin.member.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.po.MemberForbidden; +import com.wzj.soopin.member.mapper.ChargeMapper; +import com.wzj.soopin.member.mapper.MemberForbiddenMapper; +import com.wzj.soopin.member.service.IChargeService; +import com.wzj.soopin.member.service.IMemberForbiddenService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 会员封禁 + * + * @author zcc + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class ChargeServiceImpl extends ServiceImpl implements IChargeService { + + @Override + public boolean audit(Long id, String status) { + Charge charge = getById(id); + //调用三方充值接口 + boolean chargeSuccess = true; + //充值成功后更新会员账户余额 + //生成充值记录 + return false; + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/WithdrawServiceImpl.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/WithdrawServiceImpl.java new file mode 100644 index 000000000..0f7384849 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/WithdrawServiceImpl.java @@ -0,0 +1,24 @@ +package com.wzj.soopin.member.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.po.Withdraw; +import com.wzj.soopin.member.mapper.ChargeMapper; +import com.wzj.soopin.member.mapper.WithdrawMapper; +import com.wzj.soopin.member.service.IChargeService; +import com.wzj.soopin.member.service.IWithdrawService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 会员封禁 + * + * @author zcc + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class WithdrawServiceImpl extends ServiceImpl implements IWithdrawService { + +} From ca99f31ebd2088920f8f69d8b3ea4fe539a60b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BA=86=E7=A5=A5?= Date: Sat, 21 Jun 2025 15:01:11 +0800 Subject: [PATCH 4/6] =?UTF-8?q?[fix]=E5=85=85=E5=80=BC=E4=B8=8E=E6=8F=90?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wzj/soopin/member/controller/ChargeController.java | 3 +++ .../wzj/soopin/member/controller/WithdrawController.java | 3 +++ .../org/dromara/system/config/RocketMQConfiguration.java | 7 +++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ChargeController.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ChargeController.java index c021271e4..15c4af9ca 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ChargeController.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/ChargeController.java @@ -2,6 +2,7 @@ package com.wzj.soopin.member.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.wzj.soopin.member.annotation.MemberFillMethod; import com.wzj.soopin.member.convert.ChargeConvert; import com.wzj.soopin.member.convert.MemberForbiddenConvert; import com.wzj.soopin.member.domain.bo.ChargeBO; @@ -33,6 +34,7 @@ public class ChargeController { @Tag(name = "查询列表") @PostMapping("/list") + @MemberFillMethod public R> list(@RequestBody ChargeBO bo, @RequestBody Page page) { Page pages = service.page(page, bo.toWrapper()); return R.ok(convert.toVO(pages)); @@ -47,6 +49,7 @@ public class ChargeController { @Tag(name = "详情") @GetMapping(value = "/{id}") + @MemberFillMethod public R getInfo(@PathVariable("id") Long id) { return R.ok(convert.toVO(service.getById(id))); } diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java index 59d4ba295..aacdde303 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java @@ -2,6 +2,7 @@ package com.wzj.soopin.member.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.wzj.soopin.member.annotation.MemberFillMethod; import com.wzj.soopin.member.convert.MemberForbiddenConvert; import com.wzj.soopin.member.convert.WithdrawConvert; import com.wzj.soopin.member.domain.bo.MemberForbiddenBO; @@ -33,6 +34,7 @@ public class WithdrawController { @Tag(name = "查询列表") @PostMapping("/list") + @MemberFillMethod public R> list(@RequestBody WithdrawBO bo, @RequestBody Page page) { Page pages = service.page(page, bo.toWrapper()); return R.ok(convert.toVO(pages)); @@ -47,6 +49,7 @@ public class WithdrawController { @Tag(name = "详情") @GetMapping(value = "/{id}") + @MemberFillMethod public R getInfo(@PathVariable("id") Long id) { return R.ok(convert.toVO(service.getById(id))); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfiguration.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfiguration.java index 2d30b5af9..c05032c64 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfiguration.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/RocketMQConfiguration.java @@ -5,7 +5,6 @@ import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration; import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.apache.rocketmq.spring.support.RocketMQMessageConverter; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -34,17 +33,17 @@ public class RocketMQConfiguration { @Bean @ConditionalOnMissingBean(RocketMQTemplate.class) @ConditionalOnBean(DefaultMQProducer.class) - public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer, + public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer, RocketMQMessageConverter messageConverter) { RocketMQTemplate rocketMQTemplate = new RocketMQTemplate(); rocketMQTemplate.setProducer(mqProducer); rocketMQTemplate.setMessageConverter(messageConverter.getMessageConverter()); return rocketMQTemplate; } - + @Bean @ConditionalOnMissingBean(RocketMQMessageConverter.class) public RocketMQMessageConverter rocketMQMessageConverter() { return new RocketMQMessageConverter(); } -} \ No newline at end of file +} From e2ef255d8fd551dd3f503daf695460154d2d0d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BA=86=E7=A5=A5?= Date: Mon, 23 Jun 2025 10:42:02 +0800 Subject: [PATCH 5/6] =?UTF-8?q?[fix]=E5=85=85=E5=80=BC=E4=B8=8E=E6=8F=90?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/core/domain/model/BaseAudit.java | 7 ++ .../handler/InjectionMetaObjectHandler.java | 9 +- .../member/controller/WithdrawController.java | 7 +- .../soopin/member/domain/bo/WithdrawBO.java | 6 + .../domain/po/MemberAccountChangeRecord.java | 2 + .../wzj/soopin/member/domain/po/Withdraw.java | 10 ++ .../soopin/member/domain/vo/WithdrawVO.java | 7 ++ .../member/domain/vo/YishengAccountVO.java | 48 ++++++++ ...mberAccountChangeRecordChangeTypeEnum.java | 26 +++++ .../MemberAccountChangeRecordSourceEnum.java | 23 ++++ .../member/enums/WithdrawAuditStatus.java | 20 ++++ .../soopin/member/enums/WithdrawStatus.java | 22 ++++ .../member/service/IWithdrawService.java | 3 + .../member/service/IYishengService.java | 19 ++++ .../service/impl/WithdrawServiceImpl.java | 105 +++++++++++++++++- .../service/impl/YishengServiceImpl.java | 40 +++++++ 16 files changed, 347 insertions(+), 7 deletions(-) create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/YishengAccountVO.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/MemberAccountChangeRecordChangeTypeEnum.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/MemberAccountChangeRecordSourceEnum.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/WithdrawAuditStatus.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/WithdrawStatus.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IYishengService.java create mode 100644 ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/YishengServiceImpl.java diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/BaseAudit.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/BaseAudit.java index ffffe0305..e6a8c0e69 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/BaseAudit.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/BaseAudit.java @@ -1,5 +1,7 @@ package org.dromara.common.core.domain.model; +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; import lombok.Data; import java.time.LocalDateTime; @@ -14,6 +16,10 @@ public class BaseAudit { /** * 创建时间 */ + /** + * 创建部门 + */ + @TableField(fill = FieldFill.INSERT) // @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; @@ -25,6 +31,7 @@ public class BaseAudit { /** * 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) // @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java index 713d4ce0c..97d7428b6 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java @@ -5,6 +5,7 @@ import cn.hutool.http.HttpStatus; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; +import org.dromara.common.core.domain.model.BaseAudit; import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.ObjectUtils; @@ -48,7 +49,11 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler { baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId())); } } - } else { + } else if(ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseAudit baseAudit) { + baseAudit.setCreateTime(LocalDateTime.now()); + baseAudit.setUpdateTime(LocalDateTime.now()); + + }else{ Date date = new Date(); this.strictInsertFill(metaObject, "createTime", Date.class, date); this.strictInsertFill(metaObject, "updateTime", Date.class, date); @@ -75,6 +80,8 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler { if (ObjectUtil.isNotNull(userId)) { baseEntity.setUpdateBy(userId); } + } else if(ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseAudit baseAudit) { + baseAudit.setUpdateTime(LocalDateTime.now()); } else { this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); } diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java index aacdde303..02a78dcf7 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/controller/WithdrawController.java @@ -54,12 +54,11 @@ public class WithdrawController { return R.ok(convert.toVO(service.getById(id))); } - @Tag(name = ("处理")) - @Log(title = "修改", businessType = BusinessType.UPDATE) + @Tag(name = ("审批")) + @Log(title = "审批", businessType = BusinessType.UPDATE) @PostMapping("/update") public R update(@RequestBody WithdrawBO bo) { - service.save(convert.toPo(bo)); - return R.ok(); + return R.ok(service.audit(bo)); } diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/WithdrawBO.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/WithdrawBO.java index c98fc4f89..6643b3c14 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/WithdrawBO.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/bo/WithdrawBO.java @@ -96,6 +96,12 @@ public class WithdrawBO extends BaseBO { @Schema(description ="审核状态") private Integer auditStatus; + /** + * 审核原因 + */ + @Schema(description ="审核原因") + private String auditReason; + @Override public LambdaQueryWrapper toWrapper() { return super.toWrapper().eq(id!=null, Withdraw::getId, id) diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/MemberAccountChangeRecord.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/MemberAccountChangeRecord.java index c6c9328e0..8efaf1657 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/MemberAccountChangeRecord.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/MemberAccountChangeRecord.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; import lombok.Data; import org.dromara.common.core.domain.model.BaseAudit; import org.dromara.common.excel.annotation.Excel; @@ -18,6 +19,7 @@ import java.math.BigDecimal; @Schema(description="会员账户变动记录") @Data @TableName("ums_account_change_record") +@Builder(toBuilder = true) public class MemberAccountChangeRecord extends BaseAudit { @Schema(description ="主键") diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Withdraw.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Withdraw.java index c5c6d8dd2..b56f92bae 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Withdraw.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Withdraw.java @@ -1,9 +1,12 @@ package com.wzj.soopin.member.domain.po; +import com.alibaba.excel.annotation.format.DateTimeFormat; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; import lombok.Data; import org.dromara.common.core.domain.model.BaseAudit; @@ -19,6 +22,7 @@ import java.time.LocalDateTime; @Schema(description="提现") @Data @TableName("ums_withdraw") +@Builder(toBuilder = true) public class Withdraw extends BaseAudit { /** @@ -69,6 +73,7 @@ public class Withdraw extends BaseAudit { /** * 审核时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime auditTime; /** @@ -80,4 +85,9 @@ public class Withdraw extends BaseAudit { * 审核状态 */ private Integer auditStatus; + + /** + * 审核原因 + */ + private String auditReason; } diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/WithdrawVO.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/WithdrawVO.java index abb4b76c4..eb24ade16 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/WithdrawVO.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/WithdrawVO.java @@ -108,4 +108,11 @@ public class WithdrawVO extends BaseAudit { @Schema(description ="审核状态") @ExcelProperty(value ="审核状态", order = 12) private Integer auditStatus; + + /** + * 审核原因 + */ + @Schema(description ="审核原因") + @ExcelProperty(value ="审核原因", order = 13) + private String auditReason; } diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/YishengAccountVO.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/YishengAccountVO.java new file mode 100644 index 000000000..570071dcd --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/YishengAccountVO.java @@ -0,0 +1,48 @@ +package com.wzj.soopin.member.domain.vo; + + +import com.alibaba.excel.annotation.ExcelProperty; +import com.wzj.soopin.member.annotation.MemberFillField; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; +import org.dromara.common.core.domain.model.BaseAudit; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 易生平台账户信息 + * + * @author wzj + * @date 2023-03-07 + */ +@Schema(description="易生") +@Data +@Builder(toBuilder = true) +public class YishengAccountVO extends BaseAudit { + + /** + * 主键 + */ + @Schema(description ="主键") + @ExcelProperty(value = "主键", order = 1) + private Long id; + + /** + * 会员id + */ + @Schema(description ="会员id") + @ExcelProperty(value ="会员id", order = 3) + private Long memberId; + + + /** + * 金额 + */ + @Schema(description ="金额") + @ExcelProperty(value ="金额", order = 4) + private BigDecimal balance; + + +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/MemberAccountChangeRecordChangeTypeEnum.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/MemberAccountChangeRecordChangeTypeEnum.java new file mode 100644 index 000000000..7b42f0c1f --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/MemberAccountChangeRecordChangeTypeEnum.java @@ -0,0 +1,26 @@ +package com.wzj.soopin.member.enums; + +public enum MemberAccountChangeRecordChangeTypeEnum { + WITHDRAW(1, "提现"), + CHARGE(2, "充值"), + RECHARGE(3, "充值"), + RECHARGE_REFUND(4, "充值退款"), + WITHDRAW_REFUND(5, "提现退款"), + RECHARGE_REFUND_REFUND(6, "充值退款退款"), + WITHDRAW_REFUND_REFUND(7, "提现退款退款"), + RECHARGE_REFUND_REFUND_REFUND(8, "充值退款退款退款"); + + private Integer code; + private String message; + MemberAccountChangeRecordChangeTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + public String getMessage() { + return message; + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/MemberAccountChangeRecordSourceEnum.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/MemberAccountChangeRecordSourceEnum.java new file mode 100644 index 000000000..f653678ba --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/MemberAccountChangeRecordSourceEnum.java @@ -0,0 +1,23 @@ +package com.wzj.soopin.member.enums; + +public enum MemberAccountChangeRecordSourceEnum { + YISHENG(1, "充值"), + CHARGE(2, "提现"), + WITHDRAW(3, "经营"), + + RECHARGE_REFUND_REFUND_REFUND(9, "充值退款退款退款"); + + private Integer code; + private String message; + MemberAccountChangeRecordSourceEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + public String getMessage() { + return message; + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/WithdrawAuditStatus.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/WithdrawAuditStatus.java new file mode 100644 index 000000000..8f9cb8fd2 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/WithdrawAuditStatus.java @@ -0,0 +1,20 @@ +package com.wzj.soopin.member.enums; + +public enum WithdrawAuditStatus { + PENDING(0, "待审核"), + APPROVED(1, "审核通过"), + REJECTED(2, "审核拒绝"); + private Integer code; + private String message; + WithdrawAuditStatus(Integer code, String message) { + this.code = code; + this.message = message; + } + public Integer getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/WithdrawStatus.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/WithdrawStatus.java new file mode 100644 index 000000000..b99979231 --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/enums/WithdrawStatus.java @@ -0,0 +1,22 @@ +package com.wzj.soopin.member.enums; + +public enum WithdrawStatus { + WAITING(0, "等待转账"), + PENDING(1, "转账中"), + SUCCESS(2, "转账成功"), + FAIL(3, "转账失败"); + + private Integer code; + private String message; + WithdrawStatus(Integer code, String message) { + this.code = code; + this.message = message; + } + public Integer getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IWithdrawService.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IWithdrawService.java index 9dea55b44..c666c6132 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IWithdrawService.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IWithdrawService.java @@ -1,6 +1,7 @@ package com.wzj.soopin.member.service; import com.baomidou.mybatisplus.extension.service.IService; +import com.wzj.soopin.member.domain.bo.WithdrawBO; import com.wzj.soopin.member.domain.po.Feedback; import com.wzj.soopin.member.domain.po.Withdraw; import com.wzj.soopin.member.domain.vo.FeedbackVO; @@ -8,5 +9,7 @@ import com.wzj.soopin.member.domain.vo.FeedbackVO; import java.io.Serializable; public interface IWithdrawService extends IService { + boolean audit(WithdrawBO bo); + boolean withdraw(Long id); } diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IYishengService.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IYishengService.java new file mode 100644 index 000000000..50dd109da --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IYishengService.java @@ -0,0 +1,19 @@ +package com.wzj.soopin.member.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.wzj.soopin.member.domain.po.Withdraw; +import com.wzj.soopin.member.domain.vo.YishengAccountVO; + +import java.math.BigDecimal; + +/** + * 易生支付的service + */ +public interface IYishengService { + YishengAccountVO getYishengAccount(Long memberId); + + boolean withdraw(Long memberId, BigDecimal money); + + + boolean syncMemberAccount(); +} diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/WithdrawServiceImpl.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/WithdrawServiceImpl.java index 0f7384849..45456c9c7 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/WithdrawServiceImpl.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/WithdrawServiceImpl.java @@ -1,16 +1,29 @@ package com.wzj.soopin.member.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.wzj.soopin.member.domain.bo.WithdrawBO; import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.po.MemberAccount; +import com.wzj.soopin.member.domain.po.MemberAccountChangeRecord; import com.wzj.soopin.member.domain.po.Withdraw; +import com.wzj.soopin.member.domain.vo.YishengAccountVO; +import com.wzj.soopin.member.enums.MemberAccountChangeRecordChangeTypeEnum; +import com.wzj.soopin.member.enums.MemberAccountChangeRecordSourceEnum; +import com.wzj.soopin.member.enums.WithdrawAuditStatus; +import com.wzj.soopin.member.enums.WithdrawStatus; import com.wzj.soopin.member.mapper.ChargeMapper; +import com.wzj.soopin.member.mapper.MemberAccountMapper; import com.wzj.soopin.member.mapper.WithdrawMapper; -import com.wzj.soopin.member.service.IChargeService; -import com.wzj.soopin.member.service.IWithdrawService; +import com.wzj.soopin.member.service.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.dromara.common.satoken.utils.LoginHelper; import org.springframework.stereotype.Service; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Objects; + /** * 会员封禁 * @@ -21,4 +34,92 @@ import org.springframework.stereotype.Service; @Slf4j public class WithdrawServiceImpl extends ServiceImpl implements IWithdrawService { + private final IMemberAccountService memberAccountService; + + /** + * 易生账户充值服务 + */ + private final IYishengService yishengService; + + private final IMemberAccountChangeRecordService memberAccountChangeRecordService; + + @Override + public boolean audit(WithdrawBO bo) { + Withdraw withdraw = getById(bo.getId()); + if (withdraw == null) { + throw new RuntimeException("提现申请不存在"); + } + if (!Objects.equals(WithdrawAuditStatus.PENDING.getCode(), withdraw.getAuditStatus())) { + throw new RuntimeException("提现申请已处理"); + } + withdraw = Withdraw.builder().id(bo.getId()) + .auditReason(bo.getAuditReason()) + .auditTime(LocalDateTime.now()) + .auditStatus(bo.getAuditStatus()) + .auditBy(LoginHelper.getUserId()) + .build(); + return this.updateById(withdraw); + } + + @Override + public boolean save(Withdraw entity) { + entity.setStatus(WithdrawStatus.WAITING.getCode()); + return super.save(entity); + } + + public boolean withdraw(Long id) { + Withdraw withdraw = getById(id); + //获取用户余额信息 + MemberAccount memberAccount = memberAccountService.getById(withdraw.getMemberId()); + if (memberAccount == null) { + throw new RuntimeException("用户不存在"); + } + //检查当前用于的账户余额是否充足 + BigDecimal balance = memberAccount.getMoneyBalance(); + + if (balance.compareTo(withdraw.getMoney()) < 0) { + throw new RuntimeException("用户余额不足"); + } + //调用三方支付平台获取用户余额 + YishengAccountVO yishengAccountVO = yishengService.getYishengAccount(withdraw.getMemberId()); + + if (yishengAccountVO == null) { + throw new RuntimeException("用户余额获取失败"); + } + + BigDecimal yishengBalance = yishengAccountVO.getBalance(); + if (yishengBalance.compareTo(withdraw.getMoney()) < 0) { + throw new RuntimeException("用户余额不足"); + } + + if (!yishengBalance.equals(balance)) { + throw new RuntimeException("用户余额不一致"); + } + + //发起提现 + boolean chargeSuccess = yishengService.withdraw(withdraw.getMemberId(), withdraw.getMoney()); + + if (chargeSuccess) { + //提现成功后,更新会员账户余额 + //从易生取,别用自己计算的 + //// TODO: 2025/6/21 测试的时候用计算的 测试完用易生的 + BigDecimal finalBalance = balance.subtract(withdraw.getMoney()); + yishengAccountVO = yishengService.getYishengAccount(withdraw.getMemberId()); + memberAccountService.updateById(memberAccount.toBuilder().moneyBalance(balance.subtract(finalBalance)).build()); + //生成账户变动记录bh + MemberAccountChangeRecord memberAccountChangeRecord = MemberAccountChangeRecord.builder() + .memberId(withdraw.getMemberId()) + .moneyBalance(finalBalance) + .beforeBalance(balance) + .afterBalance(yishengAccountVO.getBalance()) + .changeType(MemberAccountChangeRecordChangeTypeEnum.WITHDRAW.getCode()) + .changeDesc("提现") + .source(MemberAccountChangeRecordSourceEnum.WITHDRAW.getCode()).build(); + memberAccountChangeRecordService.save(memberAccountChangeRecord); + + } else { + return false; + } + return true; + } } diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/YishengServiceImpl.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/YishengServiceImpl.java new file mode 100644 index 000000000..d34b5005a --- /dev/null +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/impl/YishengServiceImpl.java @@ -0,0 +1,40 @@ +package com.wzj.soopin.member.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.wzj.soopin.member.domain.po.Charge; +import com.wzj.soopin.member.domain.vo.YishengAccountVO; +import com.wzj.soopin.member.mapper.ChargeMapper; +import com.wzj.soopin.member.service.IChargeService; +import com.wzj.soopin.member.service.IYishengService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +/** + * 会员封禁 + * + * @author zcc + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class YishengServiceImpl implements IYishengService { + + + @Override + public YishengAccountVO getYishengAccount(Long memberId) { + return YishengAccountVO.builder().balance(new BigDecimal(1000)).build(); + } + + @Override + public boolean withdraw(Long memberId, BigDecimal money) { + return true; + } + + @Override + public boolean syncMemberAccount() { + return true; + } +} From 3a66394bbe1c4c451ed66d01e8876c24d3902380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E4=BD=B3=E8=B1=AA?= Date: Mon, 23 Jun 2025 15:12:35 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E8=85=BE=E8=AE=AFim=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SysMessageController.java | 154 +++++- .../SysMessageTemplateController.java | 19 + .../org/dromara/system/domain/SysMessage.java | 7 + .../system/domain/SysMessageTemplate.java | 8 +- .../org/dromara/system/domain/SysUser.java | 9 + .../system/domain/bo/SysMessageBo.java | 10 +- .../domain/bo/SysMessageTemplateBo.java | 10 +- .../domain/vo/SysMessageTemplateVo.java | 34 +- .../system/domain/vo/SysMessageVo.java | 15 + .../system/event/MessageEventListener.java | 115 ++++- .../system/mapper/SysTenantMapper.java | 2 + .../service/ISysMessageTemplateService.java | 8 + .../system/service/ISysUserService.java | 8 + .../system/service/ITencentIMService.java | 86 ++++ .../service/impl/SysMessageServiceImpl.java | 74 ++- .../impl/SysMessageTemplateServiceImpl.java | 14 +- .../service/impl/SysUserServiceImpl.java | 24 +- .../service/impl/TencentIMServiceImpl.java | 447 ++++++++++++++++++ 18 files changed, 984 insertions(+), 60 deletions(-) create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ITencentIMService.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/TencentIMServiceImpl.java 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 43bc85443..a08c9dd3e 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 @@ -25,13 +25,16 @@ import org.dromara.system.domain.SysMessage; import org.dromara.system.domain.SysUser; import org.dromara.system.domain.bo.SysMessageBo; import org.dromara.system.domain.bo.SysUserBo; +import org.dromara.system.domain.vo.SysMessageTemplateVo; 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.ISysMessageTemplateService; import org.dromara.system.service.ISysUserService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; import java.util.List; /** @@ -47,6 +50,7 @@ public class SysMessageController extends BaseController { private final ISysMessageService messageService; private final ISysUserService userService; + private final ISysMessageTemplateService templateService; /** * 获取当前用户ID @@ -61,7 +65,8 @@ public class SysMessageController extends BaseController { // @SaCheckPermission("system:message:list") @Tag(name = "查询消息列表") @PostMapping("/list") - public R> list(@RequestBody SysMessageBo bo, @RequestBody Page page) { + public R> list(@RequestBody SysMessageBo bo, PageQuery pageQuery) { + Page page = pageQuery.build(); Page messagePage = messageService.page(page, bo.toWrapper()); Page voPage = new Page<>(messagePage.getCurrent(), messagePage.getSize(), messagePage.getTotal()); voPage.setRecords(MapstructUtils.convert(messagePage.getRecords(), SysMessageVo.class)); @@ -86,15 +91,137 @@ public class SysMessageController extends BaseController { @Log(title = "消息管理", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping("/send") - public R send(@Validated(AddGroup.class) @RequestBody SysMessageBo bo) { + public R send(@RequestBody SysMessageBo bo) { // 设置发送者ID为当前登录用户ID bo.setSenderId(getUserId()); - List userIdStrings; + // 如果提供了模板ID,则从模板中获取标题和内容 + Long templateId = bo.getTemplateId(); + if (templateId != null) { + SysMessageTemplateVo template = templateService.selectTemplateById(templateId); + if (template != null) { + // 使用模板的标题和内容 + bo.setTitle(template.getTitle()); + bo.setContent(template.getTemplateContent()); + } else { + return R.fail("未找到指定的消息模板"); + } + } - // 获取第一个发送范围作为主要处理对象 - String scope = bo.getSendScope() != null && !bo.getSendScope().isEmpty() ? - bo.getSendScope().get(0) : ""; + // 验证消息内容是否为空 + if (StringUtils.isBlank(bo.getContent())) { + return R.fail("消息内容不能为空"); + } + + // 验证发送范围是否为空 + List sendScope = bo.getSendScope(); + if (sendScope == null || sendScope.isEmpty()) { + return R.fail("发送范围不能为空"); + } + + // 处理接收者 + List userIdStrings = new ArrayList<>(); + + // 根据logmess值处理不同类型的消息接收者 + Integer logmess = bo.getLogmess(); + if (logmess != null) { + // 获取发送范围作为主要处理对象 + if (sendScope == null || sendScope.isEmpty()) { + return R.fail("发送范围不能为空"); + } + + String scope = sendScope.get(0); // 获取第一个范围值 + + switch (logmess) { + case 1: // 指定角色 + // 当logmess=1时,sendScope接收的是角色ID或特殊标识 + 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 if (StringUtils.isNotBlank(scope)) { + try { + // 尝试将scope解析为角色ID + Long roleId = Long.parseLong(scope); + List userIds = userService.selectUserIdsByRoleId(roleId); + if (userIds != null && !userIds.isEmpty()) { + userIdStrings = userIds.stream().map(String::valueOf).toList(); + } + } catch (NumberFormatException e) { + return R.fail("角色ID格式不正确"); + } + } + break; + case 2: // 指定用户 + // 当logmess=2时,sendScope接收的是用户ID数组 + if (sendScope != null && !sendScope.isEmpty()) { + userIdStrings = new ArrayList<>(); + for (String userId : sendScope) { + if (StringUtils.isNotBlank(userId)) { + userIdStrings.add(userId); + } + } + } else if (bo.getUserIds() != null && !bo.getUserIds().isEmpty()) { + // 如果没有指定scope但有userIds,使用userIds + userIdStrings = bo.getUserIds().stream().map(String::valueOf).toList(); + } + break; + case 3: // 指定群组/部门 + // 当logmess=3时,sendScope接收的是群组/部门ID + if (StringUtils.isNotBlank(scope)) { + try { + Long deptId = Long.parseLong(scope); + List users = userService.selectUserListByDept(deptId); + if (users != null && !users.isEmpty()) { + userIdStrings = users.stream() + .map(user -> String.valueOf(user.getUserId())) + .toList(); + } + } catch (NumberFormatException e) { + return R.fail("群组/部门ID格式不正确"); + } + } + break; + default: + return R.fail("不支持的消息类型"); + } + } else { + // 如果没有指定logmess,则使用原有的处理逻辑 + if (sendScope == null || sendScope.isEmpty()) { + return R.fail("发送范围不能为空"); + } + + String scope = sendScope.get(0); // 获取第一个范围值 // 如果是群发消息类型,则根据类型筛选用户 if ("all".equals(scope) || "expert".equals(scope) || "merchant".equals(scope) || "user".equals(scope)) { @@ -136,15 +263,18 @@ public class SysMessageController extends BaseController { 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(); + } else if (sendScope != null && !sendScope.isEmpty()) { + // 如果userIds为空但sendScope不为空,将sendScope作为用户ID使用 + userIdStrings = sendScope; + } } } + // 如果没有找到接收者,返回失败 + if (userIdStrings.isEmpty()) { + return R.fail("未找到消息接收者"); + } + return toAjax(messageService.sendMessageToUsers(bo, userIdStrings)); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/SysMessageTemplateController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/SysMessageTemplateController.java index 05dbea391..89c99602c 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/SysMessageTemplateController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/SysMessageTemplateController.java @@ -1,13 +1,16 @@ package org.dromara.system.controller; import cn.dev33.satoken.annotation.SaCheckPermission; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; 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.idempotent.annotation.RepeatSubmit; @@ -60,6 +63,22 @@ public class SysMessageTemplateController extends BaseController { SysMessageTemplate template = templateService.getById(id); return R.ok(MapstructUtils.convert(template, SysMessageTemplateVo.class)); } + + /** + * 根据名称查询消息模板列表(不分页) + * + * @param name 模板名称(可选,支持模糊查询) + */ + @SaCheckPermission("system:message:template:query") + @Operation(summary = "根据名称查询消息模板列表(不分页)") + @GetMapping("/listByName") + public R> listByName(@RequestParam(value = "name", required = false) String[] name) { + String searchName = null; + if (name != null && name.length > 0) { + searchName = name[0]; + } + return R.ok(templateService.selectTemplateListByName(searchName)); + } /** * 新增消息模板 diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMessage.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMessage.java index 8defcfae9..d924b9cd5 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMessage.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMessage.java @@ -1,5 +1,6 @@ package org.dromara.system.domain; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -10,6 +11,8 @@ import org.dromara.common.tenant.core.TenantEntity; import java.io.Serializable; import java.util.Date; +import java.util.HashMap; +import java.util.Map; /** * 消息对象 sys_message @@ -47,4 +50,8 @@ public class SysMessage extends BaseAudit { // /** 状态(0正常 1停用) */ // private String status; + + /** 扩展参数 */ + @TableField(exist = false) + private Map params = new HashMap<>(); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMessageTemplate.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMessageTemplate.java index f23165a14..aa26bea8f 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMessageTemplate.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysMessageTemplate.java @@ -32,10 +32,10 @@ public class SysMessageTemplate extends BaseAudit { private String templateType; private String title; - /** - * 模板编码 - */ - private String templateCode; +// /** +// * 模板编码 +// */ +// private String templateCode; /** * 模板名称 diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java index 3712f805f..356f82271 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java @@ -112,4 +112,13 @@ public class SysUser extends TenantEntity { return SystemConstants.SUPER_ADMIN_ID.equals(this.userId); } + /** + * 手动设置更新者,父类使用LocalDateTime而这里使用Long类型 + * + * @param updateBy 更新者ID + */ +// @Override +// public void setUpdateBy(Long updateBy) { +// super.setUpdateBy(updateBy); +// } } 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 cbdd0d3eb..a1a6b8f15 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 @@ -14,7 +14,6 @@ import org.dromara.common.mybatis.core.domain.BaseEntity; import org.dromara.system.domain.SysMessage; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; -import io.github.linpeilie.annotations.AutoMapper; import java.util.Date; import java.util.List; @@ -27,7 +26,6 @@ import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @ExcelIgnoreUnannotated -@AutoMapper(target = SysMessage.class, reverseConvertGenerate = false) public class SysMessageBo extends BaseAudit { /** 主键ID */ @@ -35,7 +33,7 @@ public class SysMessageBo extends BaseAudit { private Long id; /** 消息标题 */ - @NotBlank(message = "消息标题不能为空", groups = { AddGroup.class, EditGroup.class }) +// @NotBlank(message = "消息标题不能为空", groups = { AddGroup.class, EditGroup.class }) @Size(min = 0, max = 100, message = "消息标题长度不能超过100个字符") @ExcelProperty(value = "消息标题") private String title; @@ -75,6 +73,12 @@ public class SysMessageBo extends BaseAudit { /** 扩展数据(JSON格式) */ private String extraData; + /** 消息类型标识 1=指定角色 2=指定用户 3=指定群 */ + private Integer logmess; + + /** 模板ID */ + private Long templateId; + /** 状态(0正常 1停用) */ private String status; diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMessageTemplateBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMessageTemplateBo.java index 398ff67f6..d8d99011c 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMessageTemplateBo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysMessageTemplateBo.java @@ -43,9 +43,9 @@ public class SysMessageTemplateBo extends BaseAudit { /** * 模板编码 */ - @NotBlank(message = "模板编码不能为空", groups = { AddGroup.class, EditGroup.class }) - @Size(max = 50, message = "模板编码长度不能超过{max}个字符") - private String templateCode; +// @NotBlank(message = "模板编码不能为空", groups = { AddGroup.class, EditGroup.class }) +// @Size(max = 50, message = "模板编码长度不能超过{max}个字符") +// private String templateCode; /** * 模板类型(SMS=短信 MAIL=邮件 WECHAT=微信 SYSTEM=系统消息) @@ -79,7 +79,7 @@ public class SysMessageTemplateBo extends BaseAudit { public LambdaQueryWrapper toWrapper() { LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); lqw.like(StringUtils.isNotBlank(templateName), SysMessageTemplate::getTemplateName, templateName) - .like(StringUtils.isNotBlank(templateCode), SysMessageTemplate::getTemplateCode, templateCode) +// .like(StringUtils.isNotBlank(templateCode), SysMessageTemplate::getTemplateCode, templateCode) .eq(StringUtils.isNotBlank(templateType), SysMessageTemplate::getTemplateType, templateType) .eq(StringUtils.isNotBlank(status), SysMessageTemplate::getStatus, status) .orderByDesc(SysMessageTemplate::getCreateTime); @@ -93,7 +93,7 @@ public class SysMessageTemplateBo extends BaseAudit { SysMessageTemplate entity = new SysMessageTemplate(); entity.setId(id); entity.setTemplateName(templateName); - entity.setTemplateCode(templateCode); +// entity.setTemplateCode(templateCode); entity.setTemplateType(templateType); entity.setTemplateContent(templateContent); entity.setStatus(status); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMessageTemplateVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMessageTemplateVo.java index 10bb840a5..e395443ec 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMessageTemplateVo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMessageTemplateVo.java @@ -8,6 +8,7 @@ import org.dromara.system.domain.SysMessage; import org.dromara.system.domain.SysMessageTemplate; import java.io.Serializable; +import java.util.Date; /** * 消息模板视图对象 sys_message_template @@ -30,16 +31,21 @@ public class SysMessageTemplateVo implements Serializable { */ private String templateType; - /** - * 模板编码 - */ - private String templateCode; +// /** +// * 模板编码 +// */ +// private String templateCode; /** * 模板名称 */ private String templateName; + /** + * 模板标题 + */ + private String title; + /** * 模板内容 */ @@ -55,6 +61,26 @@ public class SysMessageTemplateVo implements Serializable { */ private String status; + /** + * 创建者 + */ + private String createBy; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新者 + */ + private String updateBy; + + /** + * 更新时间 + */ + private Date updateTime; + // /** // * 备注 // */ diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMessageVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMessageVo.java index 455a29e47..4c1c7feba 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMessageVo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysMessageVo.java @@ -10,6 +10,8 @@ import org.dromara.system.domain.SysNotice; import java.io.Serial; import java.io.Serializable; import java.util.Date; +import java.util.HashMap; +import java.util.Map; /** * 消息视图对象 @@ -17,6 +19,7 @@ import java.util.Date; * @author ruoyi */ @Data +@ExcelIgnoreUnannotated @AutoMapper(target = SysMessage.class) public class SysMessageVo implements Serializable { @@ -62,4 +65,16 @@ public class SysMessageVo implements Serializable { /** 创建时间 */ @ExcelProperty(value = "创建时间") private Date createTime; + + /** 更新时间 */ + private Date updateTime; + + /** 创建者 */ + private String createBy; + + /** 更新者 */ + private String updateBy; + + /** 扩展参数 */ + private Map params = new HashMap<>(); } 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 d5e5a49df..760d0eed2 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 @@ -8,12 +8,19 @@ 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.service.ITencentIMService; import org.dromara.system.websocket.MessageWebSocketServer; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + /** * 消息事件监听器 * @@ -28,6 +35,10 @@ public class MessageEventListener { @Autowired private MessageWebSocketServer messageWebSocketServer; + + @Autowired + @Qualifier("systemTencentIMService") + private ITencentIMService tencentIMService; /** * 处理消息事件 @@ -36,27 +47,115 @@ public class MessageEventListener { @EventListener public void handleMessageEvent(MessageEvent event) { try { + // 提取消息和用户ID + String userId = event.getUserIdStr(); + // 创建消息包装对象 MessageRocketMQConsumer.MessageWrapper wrapper = new MessageRocketMQConsumer.MessageWrapper(); - wrapper.setUserId(event.getUserIdStr()); + wrapper.setUserId(userId); wrapper.setMessage(event.getMessage()); - - // 尝试通过RocketMQ发送消息 - boolean sendSuccess = false; + + // 首先尝试发送消息到腾讯IM + boolean tencentIMSendSuccess = false; + try { + // 消息发送者可能是系统或管理员,这里使用固定的管理员账号作为发送者 + String fromUserId = "administrator"; + String toUserId = userId; // 接收者是事件中的用户ID + String content = JsonUtils.toJsonString(event.getMessage()); // 消息内容序列化为JSON + + // 处理消息变量替换(如果有) + Map variables = new HashMap<>(); + // 这里可以添加变量映射,例如:variables.put("userName", "张三"); + + // 从消息中提取标题和描述 + String title = event.getMessage().getTitle(); + String desc = event.getMessage().getContent(); + + // 处理变量替换 + if (variables.size() > 0) { + title = tencentIMService.processMessageVariables(title, variables); + desc = tencentIMService.processMessageVariables(desc, variables); + } + + // 扩展字段,可以在APP端获取 + String ext = null; + if (event.getMessage().getParams() != null && !event.getMessage().getParams().isEmpty()) { + ext = JsonUtils.toJsonString(event.getMessage().getParams()); + } + + // 根据消息类型决定推送方式 + Map params = event.getMessage().getParams(); + String pushType = null; + if (params != null && params.containsKey("pushType")) { + pushType = String.valueOf(params.get("pushType")); + } + + if (pushType != null) { + switch (pushType) { + case "ALL": + // 全员推送 + tencentIMSendSuccess = tencentIMService.pushToAll(title, desc, true, ext); + break; + case "ATTRIBUTE": + // 属性推送 + Map attributes = new HashMap<>(); + // 从消息参数中提取属性条件 + if (params != null && params.containsKey("attributes")) { + Object attrObj = params.get("attributes"); + if (attrObj instanceof Map) { + attributes = (Map) attrObj; + } + } + tencentIMSendSuccess = tencentIMService.pushByAttributes(title, desc, attributes, true, ext); + break; + case "TAG": + // 标签推送 + if (params != null && params.containsKey("tags")) { + Object tagsObj = params.get("tags"); + if (tagsObj instanceof String[]) { + tencentIMSendSuccess = tencentIMService.pushByTags(title, desc, + Arrays.asList((String[]) tagsObj), true, ext); + } + } + break; + case "USER": + default: + // 单用户推送(默认) + tencentIMSendSuccess = tencentIMService.pushToUsers(title, desc, + Collections.singletonList(toUserId), true, ext); + break; + } + } else { + // 默认为单用户推送 + tencentIMSendSuccess = tencentIMService.sendMessageToTencentIM(fromUserId, toUserId, content); + } + + if (tencentIMSendSuccess) { + log.info("消息已成功发送到腾讯IM,接收者: {}", toUserId); + } else { + log.warn("发送消息到腾讯IM失败,将尝试通过其他方式发送"); + } + } catch (Exception e) { + log.error("发送消息到腾讯IM异常,将尝试通过其他方式发送", e); + } + + // 无论腾讯IM是否发送成功,都继续尝试通过RocketMQ发送消息 + // 这样可以确保消息至少通过一种方式发送出去 + boolean rocketMQSendSuccess = false; try { rocketMQService.sendMessage( RocketMQConfig.TOPIC_SYS_MSG, RocketMQConfig.TAG_SYS_MSG, wrapper ); - sendSuccess = true; - log.info("消息事件已通过RocketMQ发送,userId: {}", event.getUserIdStr()); + rocketMQSendSuccess = true; + log.info("消息事件已通过RocketMQ发送,userId: {}", userId); } catch (Exception e) { log.error("通过RocketMQ发送消息失败,将尝试直接通过WebSocket发送", e); } - // 如果RocketMQ发送失败,则尝试直接通过WebSocket发送 - if (!sendSuccess && event.getUserId() != null) { + // 如果前两种方式都失败,则尝试直接通过WebSocket发送 + if (!tencentIMSendSuccess && !rocketMQSendSuccess && event.getUserId() != null) { try { messageWebSocketServer.sendMessage( event.getUserId(), diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantMapper.java index 7e1167aa3..c50ef497d 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantMapper.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysTenantMapper.java @@ -1,5 +1,6 @@ package org.dromara.system.mapper; +import org.apache.ibatis.annotations.Mapper; import org.dromara.system.domain.SysTenant; import org.dromara.system.domain.vo.SysTenantVo; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; @@ -9,6 +10,7 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; * * @author Michelle.Chung */ +@Mapper public interface SysTenantMapper extends BaseMapperPlus { } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMessageTemplateService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMessageTemplateService.java index c53b0ec44..2e642cfbd 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMessageTemplateService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysMessageTemplateService.java @@ -72,4 +72,12 @@ public interface ISysMessageTemplateService extends IService * @return 结果 */ int deleteTemplateById(Long id); + + /** + * 根据名称查询消息模板列表(不分页) + * + * @param name 模板名称(可选,支持模糊查询) + * @return 消息模板列表 + */ + List selectTemplateListByName(String name); } 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 475359f44..937da0c60 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 @@ -251,4 +251,12 @@ public interface ISysUserService { * @return 会员用户列表 */ List selectMemberUsers(String keyword); + + /** + * 根据角色ID查询用户ID列表 + * + * @param roleId 角色ID + * @return 用户ID列表 + */ + List selectUserIdsByRoleId(Long roleId); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ITencentIMService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ITencentIMService.java new file mode 100644 index 000000000..bfb647b34 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ITencentIMService.java @@ -0,0 +1,86 @@ +package org.dromara.system.service; + +import java.util.List; +import java.util.Map; + +/** + * 腾讯IM服务接口 + * + * @author wzj + */ +public interface ITencentIMService { + + /** + * 发送消息到腾讯IM + * + * @param fromUserId 发送方用户ID + * @param toUserId 接收方用户ID + * @param content 消息内容 + * @return 是否发送成功 + */ + boolean sendMessageToTencentIM(String fromUserId, String toUserId, String content); + + /** + * 创建腾讯IM账号 + * + * @param userId 用户ID + * @return 用户签名 + */ + String createTencentIMAccount(String userId); + + /** + * 推送消息给全体用户 + * + * @param title 消息标题 + * @param desc 消息描述 + * @param offlinePush 是否离线推送 + * @param ext 扩展字段(JSON字符串) + * @return 是否发送成功 + */ + boolean pushToAll(String title, String desc, boolean offlinePush, String ext); + + /** + * 根据用户属性推送消息 + * + * @param title 消息标题 + * @param desc 消息描述 + * @param attributes 用户属性条件 + * @param offlinePush 是否离线推送 + * @param ext 扩展字段(JSON字符串) + * @return 是否发送成功 + */ + boolean pushByAttributes(String title, String desc, Map attributes, boolean offlinePush, String ext); + + /** + * 根据标签推送消息 + * + * @param title 消息标题 + * @param desc 消息描述 + * @param tags 标签列表 + * @param offlinePush 是否离线推送 + * @param ext 扩展字段(JSON字符串) + * @return 是否发送成功 + */ + boolean pushByTags(String title, String desc, List tags, boolean offlinePush, String ext); + + /** + * 推送消息给指定用户 + * + * @param title 消息标题 + * @param desc 消息描述 + * @param userIds 用户ID列表 + * @param offlinePush 是否离线推送 + * @param ext 扩展字段(JSON字符串) + * @return 是否发送成功 + */ + boolean pushToUsers(String title, String desc, List userIds, boolean offlinePush, String ext); + + /** + * 处理消息变量替换 + * + * @param content 原始内容 + * @param variables 变量映射 + * @return 替换后的内容 + */ + String processMessageVariables(String content, Map variables); +} \ No newline at end of file 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 fda8bed01..2ddee744d 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 @@ -85,12 +85,28 @@ public class SysMessageServiceImpl extends ServiceImpl queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysMessageUser::getMessageId, entity.getId()) + .eq(SysMessageUser::getUserId, userId); + int count = Math.toIntExact(messageUserMapper.selectCount(queryWrapper)); + + // 只有当关联不存在时才创建 + int rows = 0; + if (count == 0) { + // 创建消息用户关联 + SysMessageUser messageUser = new SysMessageUser(); + messageUser.setMessageId(entity.getId()); + messageUser.setUserId(userId); + messageUser.setIsRead(false); + rows = messageUserMapper.insert(messageUser); + } else { + log.info("消息与用户关联已存在,跳过创建: messageId={}, userId={}", entity.getId(), userId); + // 已存在也认为操作成功 + rows = 1; + } + // 发送WebSocket消息 if (rows > 0) { SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class); @@ -119,17 +135,33 @@ public class SysMessageServiceImpl extends ServiceImpl 0) { + + // 检查消息与用户关联是否已存在 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysMessageUser::getMessageId, entity.getId()) + .eq(SysMessageUser::getUserId, userId); + int existCount = Math.toIntExact(messageUserMapper.selectCount(queryWrapper)); + + // 只有当关联不存在时才创建 + if (existCount == 0) { + SysMessageUser messageUser = new SysMessageUser(); + messageUser.setMessageId(entity.getId()); + messageUser.setUserId(userId); + messageUser.setIsRead(false); + int rows = messageUserMapper.insert(messageUser); + if (rows > 0) { + count++; + } + } else { + log.info("消息与用户关联已存在,跳过创建: messageId={}, userId={}", entity.getId(), userId); + // 已存在也计入成功数量 count++; - // 发送WebSocket消息 - SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class); - eventPublisher.publishEvent(MessageEvent.createWithStringUserId(this, messageVo, userIdStr)); } + + // 无论关联是否新建,都发送事件通知 + SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class); + eventPublisher.publishEvent(MessageEvent.createWithStringUserId(this, messageVo, userIdStr)); + } catch (NumberFormatException e) { log.error("无法将String类型的用户ID转换为Long: {}", userIdStr); } @@ -235,7 +267,17 @@ public class SysMessageServiceImpl extends ServiceImpl page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); @@ -51,7 +51,7 @@ public class SysMessageTemplateServiceImpl extends ServiceImpl selectTemplateListByName(String name) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.isNotBlank(name)) { + queryWrapper.like(SysMessageTemplate::getTemplateName, name); + } + queryWrapper.orderByDesc(SysMessageTemplate::getCreateTime); + return MapstructUtils.convert(templateMapper.selectList(queryWrapper), SysMessageTemplateVo.class); + } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java index df961a514..ec1f92fa0 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java @@ -796,6 +796,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService { @Override public List selectMemberUsers(String keyword) { + // 尝试获取会员服务 try { // 获取 Spring 上下文中的 MemberService bean IMemberService memberService = SpringUtils.getBean(IMemberService.class); @@ -807,12 +808,12 @@ public class SysUserServiceImpl implements ISysUserService, UserService { // 如果有关键词,添加模糊查询条件(昵称、用户名、手机号) if (StringUtils.isNotBlank(keyword)) { queryWrapper.like(Member::getNickname, keyword) - .or() - .like(Member::getUserName, keyword) - .or() - .like(Member::getPhoneEncrypted, keyword) - .or() - .like(Member::getPhoneHidden, keyword); + .or() + .like(Member::getUserName, keyword) + .or() + .like(Member::getPhoneEncrypted, keyword) + .or() + .like(Member::getPhoneHidden, keyword); } // 查询会员信息 @@ -856,4 +857,15 @@ public class SysUserServiceImpl implements ISysUserService, UserService { return new ArrayList<>(); } + /** + * 根据角色ID查询用户ID列表 + * + * @param roleId 角色ID + * @return 用户ID列表 + */ + @Override + public List selectUserIdsByRoleId(Long roleId) { + return userRoleMapper.selectUserIdsByRoleId(roleId); + } + } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/TencentIMServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/TencentIMServiceImpl.java new file mode 100644 index 000000000..d3ba5773d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/TencentIMServiceImpl.java @@ -0,0 +1,447 @@ +package org.dromara.system.service.impl; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.system.service.ITencentIMService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 腾讯IM服务实现类 + * + * @author wzj + */ +@Slf4j +@Service("systemTencentIMService") +public class TencentIMServiceImpl implements ITencentIMService { + + @Value("${tencent.im.sdkappid}") + private long sdkAppId; + + @Value("${tencent.im.secretkey}") + private String secretKey; + + @Value("${tencent.im.admin}") + private String adminAccount; + + private final RestTemplate restTemplate = new RestTemplate(); + + // 变量替换的正则表达式模式 + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$(\\w+)"); + + /** + * 生成用户签名 + * + * @param userId 用户ID + * @return 用户签名 + */ + private String generateUserSig(String userId) { + // 注意: 这里需要实现腾讯云IM SDK的TLSSigAPIv2签名生成 + // 参考腾讯云文档: https://cloud.tencent.com/document/product/269/32688 + // 实现方法: + // 1. 导入腾讯云IM SDK依赖 + // 2. 使用sdkAppId和secretKey生成签名 + // 示例代码: + // TLSSigAPIv2 tlsSigApi = new TLSSigAPIv2(sdkAppId, secretKey); + // return tlsSigApi.genUserSig(userId, 86400); // 有效期设为1天(86400秒) + + log.info("生成用户签名: {}", userId); + + // 当前为模拟实现,实际项目中请替换为真实签名生成逻辑 + try { + // 临时返回一个固定值用于测试 + // 注意:这是临时方案,正式环境必须替换为正确实现 + return "eJyrVgrxDXFSsrS0MDA2MzY1NTawNDAzsTQ1sTCyMDI2NdRRKs9ILUpVsjKoLEgsKk7NswlPzYnIsagoyIgrsYl3Lg5IALITi5BkS1JzSlOLbMJKi32DKtNCglONDSNNXfMqk3wN0myrvVUYGBgYGZiaGBoamCgZ6hlYGOUb6Rmaq1QHAAD--wJe"; + } catch (Exception e) { + log.error("生成用户签名异常", e); + return null; + } + } + + /** + * 生成管理员签名 + * + * @return 管理员签名 + */ + private String generateAdminUserSig() { + return generateUserSig(adminAccount); + } + + @Override + public String createTencentIMAccount(String userId) { + try { + String userSig = generateAdminUserSig(); + String random = String.valueOf(System.currentTimeMillis()); + + // 构建API请求URL + String url = String.format( + "https://console.tim.qq.com/v4/im_open_login_svc/account_import" + + "?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json", + sdkAppId, + adminAccount, + userSig, + random); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("UserID", userId); + requestBody.put("Nick", userId); + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + log.info("创建腾讯IM账号结果: {}", response.getBody()); + + // 生成并返回用户签名 + return generateUserSig(userId); + } catch (Exception e) { + log.error("创建腾讯IM账号异常: {}", userId, e); + return null; + } + } + + @Override + public boolean sendMessageToTencentIM(String fromUserId, String toUserId, String content) { + try { + String userSig = generateAdminUserSig(); + String random = String.valueOf(new Random().nextInt(999999999)); + + // 构建API请求URL + String url = String.format( + "https://console.tim.qq.com/v4/openim/sendmsg" + + "?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json", + sdkAppId, + adminAccount, + userSig, + random); + + // 构建消息内容 + Map msgContent = new HashMap<>(); + msgContent.put("Text", content); + + // 构建消息体 + Map msgBody = new HashMap<>(); + msgBody.put("MsgType", "TIMTextElem"); + msgBody.put("MsgContent", msgContent); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("SyncOtherMachine", 2); // 消息不同步至发送方 + requestBody.put("From_Account", fromUserId); + requestBody.put("To_Account", toUserId); + requestBody.put("MsgRandom", Integer.parseInt(random)); + requestBody.put("MsgBody", new Object[]{msgBody}); + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + log.info("发送消息到腾讯IM结果: {}", response.getBody()); + + // 解析响应判断是否成功 + JSONObject responseJson = JSONObject.parseObject(response.getBody()); + return "OK".equals(responseJson.getString("ActionStatus")); + } catch (Exception e) { + log.error("发送消息到腾讯IM异常", e); + return false; + } + } + + @Override + public boolean pushToAll(String title, String desc, boolean offlinePush, String ext) { + try { + String userSig = generateAdminUserSig(); + String random = String.valueOf(new Random().nextInt(999999999)); + + // 构建API请求URL - 使用全员推送接口 + String url = String.format( + "https://console.tim.qq.com/v4/all_member_push/im_push" + + "?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json", + sdkAppId, + adminAccount, + userSig, + random); + + // 构建消息内容 + Map msgContent = new HashMap<>(); + msgContent.put("Title", title); + msgContent.put("Desc", desc); + + // 构建消息体 + Map msgBody = new HashMap<>(); + msgBody.put("MsgType", "TIMCustomElem"); + msgBody.put("MsgContent", msgContent); + + // 如果有扩展字段,添加到消息内容中 + if (ext != null && !ext.isEmpty()) { + msgContent.put("Ext", ext); + } + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("From_Account", adminAccount); + requestBody.put("MsgRandom", Integer.parseInt(random)); + requestBody.put("MsgBody", new Object[]{msgBody}); + + // 设置离线推送信息 + if (offlinePush) { + Map offlinePushInfo = new HashMap<>(); + offlinePushInfo.put("PushFlag", 1); // 1表示推送,0表示不离线推送 + offlinePushInfo.put("Title", title); + offlinePushInfo.put("Desc", desc); + offlinePushInfo.put("Ext", ext); + requestBody.put("OfflinePushInfo", offlinePushInfo); + } + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + log.info("全员推送消息结果: {}", response.getBody()); + + // 解析响应判断是否成功 + JSONObject responseJson = JSONObject.parseObject(response.getBody()); + return "OK".equals(responseJson.getString("ActionStatus")); + } catch (Exception e) { + log.error("全员推送消息异常", e); + return false; + } + } + + @Override + public boolean pushByAttributes(String title, String desc, Map attributes, boolean offlinePush, String ext) { + try { + String userSig = generateAdminUserSig(); + String random = String.valueOf(new Random().nextInt(999999999)); + + // 构建API请求URL - 使用属性推送接口 + String url = String.format( + "https://console.tim.qq.com/v4/all_member_push/im_push_attr" + + "?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json", + sdkAppId, + adminAccount, + userSig, + random); + + // 构建消息内容 + Map msgContent = new HashMap<>(); + msgContent.put("Title", title); + msgContent.put("Desc", desc); + + // 构建消息体 + Map msgBody = new HashMap<>(); + msgBody.put("MsgType", "TIMCustomElem"); + msgBody.put("MsgContent", msgContent); + + // 如果有扩展字段,添加到消息内容中 + if (ext != null && !ext.isEmpty()) { + msgContent.put("Ext", ext); + } + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("From_Account", adminAccount); + requestBody.put("MsgRandom", Integer.parseInt(random)); + requestBody.put("MsgBody", new Object[]{msgBody}); + requestBody.put("AttrNames", attributes.keySet().toArray(new String[0])); + requestBody.put("AttrValues", attributes.values().toArray()); + + // 设置离线推送信息 + if (offlinePush) { + Map offlinePushInfo = new HashMap<>(); + offlinePushInfo.put("PushFlag", 1); // 1表示推送,0表示不离线推送 + offlinePushInfo.put("Title", title); + offlinePushInfo.put("Desc", desc); + offlinePushInfo.put("Ext", ext); + requestBody.put("OfflinePushInfo", offlinePushInfo); + } + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + log.info("属性推送消息结果: {}", response.getBody()); + + // 解析响应判断是否成功 + JSONObject responseJson = JSONObject.parseObject(response.getBody()); + return "OK".equals(responseJson.getString("ActionStatus")); + } catch (Exception e) { + log.error("属性推送消息异常", e); + return false; + } + } + + @Override + public boolean pushByTags(String title, String desc, List tags, boolean offlinePush, String ext) { + try { + String userSig = generateAdminUserSig(); + String random = String.valueOf(new Random().nextInt(999999999)); + + // 构建API请求URL - 使用标签推送接口 + String url = String.format( + "https://console.tim.qq.com/v4/all_member_push/im_push_tags" + + "?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json", + sdkAppId, + adminAccount, + userSig, + random); + + // 构建消息内容 + Map msgContent = new HashMap<>(); + msgContent.put("Title", title); + msgContent.put("Desc", desc); + + // 构建消息体 + Map msgBody = new HashMap<>(); + msgBody.put("MsgType", "TIMCustomElem"); + msgBody.put("MsgContent", msgContent); + + // 如果有扩展字段,添加到消息内容中 + if (ext != null && !ext.isEmpty()) { + msgContent.put("Ext", ext); + } + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("From_Account", adminAccount); + requestBody.put("MsgRandom", Integer.parseInt(random)); + requestBody.put("MsgBody", new Object[]{msgBody}); + requestBody.put("TagList", tags); + + // 设置离线推送信息 + if (offlinePush) { + Map offlinePushInfo = new HashMap<>(); + offlinePushInfo.put("PushFlag", 1); // 1表示推送,0表示不离线推送 + offlinePushInfo.put("Title", title); + offlinePushInfo.put("Desc", desc); + offlinePushInfo.put("Ext", ext); + requestBody.put("OfflinePushInfo", offlinePushInfo); + } + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + log.info("标签推送消息结果: {}", response.getBody()); + + // 解析响应判断是否成功 + JSONObject responseJson = JSONObject.parseObject(response.getBody()); + return "OK".equals(responseJson.getString("ActionStatus")); + } catch (Exception e) { + log.error("标签推送消息异常", e); + return false; + } + } + + @Override + public boolean pushToUsers(String title, String desc, List userIds, boolean offlinePush, String ext) { + try { + String userSig = generateAdminUserSig(); + String random = String.valueOf(new Random().nextInt(999999999)); + + // 构建API请求URL - 使用批量单发接口 + String url = String.format( + "https://console.tim.qq.com/v4/openim/batchsendmsg" + + "?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json", + sdkAppId, + adminAccount, + userSig, + random); + + // 构建消息内容 + Map msgContent = new HashMap<>(); + msgContent.put("Title", title); + msgContent.put("Desc", desc); + + // 构建消息体 + Map msgBody = new HashMap<>(); + msgBody.put("MsgType", "TIMCustomElem"); + msgBody.put("MsgContent", msgContent); + + // 如果有扩展字段,添加到消息内容中 + if (ext != null && !ext.isEmpty()) { + msgContent.put("Ext", ext); + } + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("From_Account", adminAccount); + requestBody.put("To_Account", userIds); + requestBody.put("MsgRandom", Integer.parseInt(random)); + requestBody.put("MsgBody", new Object[]{msgBody}); + + // 设置离线推送信息 + if (offlinePush) { + Map offlinePushInfo = new HashMap<>(); + offlinePushInfo.put("PushFlag", 1); // 1表示推送,0表示不离线推送 + offlinePushInfo.put("Title", title); + offlinePushInfo.put("Desc", desc); + offlinePushInfo.put("Ext", ext); + requestBody.put("OfflinePushInfo", offlinePushInfo); + } + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + + log.info("指定用户推送消息结果: {}", response.getBody()); + + // 解析响应判断是否成功 + JSONObject responseJson = JSONObject.parseObject(response.getBody()); + return "OK".equals(responseJson.getString("ActionStatus")); + } catch (Exception e) { + log.error("指定用户推送消息异常", e); + return false; + } + } + + @Override + public String processMessageVariables(String content, Map variables) { + if (content == null || variables == null || variables.isEmpty()) { + return content; + } + + Matcher matcher = VARIABLE_PATTERN.matcher(content); + StringBuffer sb = new StringBuffer(); + + while (matcher.find()) { + String variableName = matcher.group(1); + String replacement = variables.getOrDefault(variableName, "$" + variableName); + matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + } + + matcher.appendTail(sb); + return sb.toString(); + } +} \ No newline at end of file