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] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B6=88=E6=81=AF=E9=80=9A?= =?UTF-8?q?=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