修改消息通过腾讯im发送bug加模糊查询
This commit is contained in:
parent
3a66394bbe
commit
297b0332da
@ -298,13 +298,8 @@ justauth:
|
||||
# 腾讯云IM配置
|
||||
tencent:
|
||||
im:
|
||||
# 腾讯云 SDKAppId
|
||||
sdkappid: 1600080789
|
||||
# 密钥
|
||||
secretkey: 311b5309d714a20f7f5b54360ee21b1e24ec208ebcd25ce8f47d24753bccc091
|
||||
# 签名过期时间(秒)
|
||||
expire: 604800
|
||||
# 管理员账号
|
||||
admin: administrator
|
||||
# API调用密钥
|
||||
api-secret: 311b5309d714a20f7f5b54360ee21b1e24ec208ebcd25ce8f47d24753bccc091
|
||||
enabled: true # 启用腾讯云IM
|
||||
sdk-app-id: 1600080789 # 你的腾讯云 SDKAppID
|
||||
secret-key: "311b5309d714a20f7f5b54360ee21b1e24ec208ebcd25ce8f47d24753bccc091" # 你的密钥
|
||||
administrator: "administrator" # 管理员账号
|
||||
expire-time: 604800 # UserSig 过期时间(7天,单位:秒)
|
||||
|
@ -113,7 +113,7 @@ public class LogAspect {
|
||||
// 设置消耗时间
|
||||
StopWatch stopWatch = KEY_CACHE.get();
|
||||
stopWatch.stop();
|
||||
operLog.setCostTime(stopWatch.getDuration().toMillis());
|
||||
operLog.setCostTime(stopWatch.getTime());
|
||||
// 发布事件保存数据库
|
||||
SpringUtils.context().publishEvent(operLog);
|
||||
} catch (Exception exp) {
|
||||
|
@ -67,7 +67,7 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
|
||||
StopWatch stopWatch = KEY_CACHE.get();
|
||||
if (ObjectUtil.isNotNull(stopWatch)) {
|
||||
stopWatch.stop();
|
||||
log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getDuration().toMillis());
|
||||
log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
|
||||
KEY_CACHE.remove();
|
||||
}
|
||||
}
|
||||
|
@ -8,14 +8,10 @@ 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消息消费者
|
||||
*
|
||||
@ -32,7 +28,6 @@ import java.util.Date;
|
||||
public class MessageRocketMQConsumer implements RocketMQListener<String> {
|
||||
|
||||
private final MessageWebSocketServer messageWebSocketServer;
|
||||
private final SysMessageUserMapper messageUserMapper;
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
@ -53,18 +48,6 @@ public class MessageRocketMQConsumer implements RocketMQListener<String> {
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -2,6 +2,8 @@ package org.dromara.system.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.wzj.soopin.member.domain.po.Member;
|
||||
import com.wzj.soopin.member.mapper.MemberMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
@ -31,6 +33,9 @@ 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.dromara.common.satoken.utils.LoginHelper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -51,6 +56,7 @@ public class SysMessageController extends BaseController {
|
||||
private final ISysMessageService messageService;
|
||||
private final ISysUserService userService;
|
||||
private final ISysMessageTemplateService templateService;
|
||||
private final MemberMapper umsMemberMapper;
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
@ -107,12 +113,12 @@ public class SysMessageController extends BaseController {
|
||||
return R.fail("未找到指定的消息模板");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 验证消息内容是否为空
|
||||
if (StringUtils.isBlank(bo.getContent())) {
|
||||
return R.fail("消息内容不能为空");
|
||||
}
|
||||
|
||||
|
||||
// 验证发送范围是否为空
|
||||
List<String> sendScope = bo.getSendScope();
|
||||
if (sendScope == null || sendScope.isEmpty()) {
|
||||
@ -129,37 +135,35 @@ public class SysMessageController extends BaseController {
|
||||
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)) {
|
||||
if ("all".equals(scope)) {
|
||||
// 全部会员用户(查ums_member表)
|
||||
List<Member> members = umsMemberMapper.selectList(
|
||||
new QueryWrapper<Member>().eq("status", 1)
|
||||
);
|
||||
userIdStrings = members.stream().map(m -> String.valueOf(m.getId())).toList();
|
||||
} else if ("expert".equals(scope) || "merchant".equals(scope) || "user".equals(scope)) {
|
||||
List<SysUserVo> users = userService.selectUserListByDept(null);
|
||||
List<Long> 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)
|
||||
@ -220,14 +224,14 @@ public class SysMessageController extends BaseController {
|
||||
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)) {
|
||||
List<SysUserVo> users = userService.selectUserListByDept(null);
|
||||
List<Long> userIds;
|
||||
|
||||
|
||||
switch (scope) {
|
||||
case "all":
|
||||
// 全部用户
|
||||
|
@ -14,6 +14,8 @@ 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 com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -85,16 +87,25 @@ public class SysMessageBo extends BaseAudit {
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间-起始 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")
|
||||
private LocalDateTime startTime;
|
||||
/** 创建时间-结束 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")
|
||||
private LocalDateTime sendTime;
|
||||
|
||||
/**
|
||||
* 转换为查询条件
|
||||
*/
|
||||
public LambdaQueryWrapper<SysMessage> toWrapper() {
|
||||
LambdaQueryWrapper<SysMessage> lqw = new LambdaQueryWrapper<>();
|
||||
lqw.like(StringUtils.isNotBlank(this.getTitle()), SysMessage::getTitle, this.getTitle())
|
||||
.like(StringUtils.isNotBlank(this.getContent()), SysMessage::getContent, this.getContent())
|
||||
.eq(StringUtils.isNotBlank(this.getMsgType()), SysMessage::getMsgType, this.getMsgType())
|
||||
.eq(StringUtils.isNotBlank(this.getSubType()), SysMessage::getSubType, this.getSubType())
|
||||
.eq(this.getSenderId() != null, SysMessage::getSenderId, this.getSenderId())
|
||||
// .eq(StringUtils.isNotBlank(this.getStatus()), SysMessage::getStatus, this.getStatus())
|
||||
.ge(this.getStartTime() != null, SysMessage::getCreateTime, this.getStartTime())
|
||||
.le(this.getSendTime() != null, SysMessage::getCreateTime, this.getSendTime())
|
||||
.orderByDesc(SysMessage::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.dromara.system.event;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.system.config.RocketMQConfig;
|
||||
import org.dromara.system.consumer.MessageRocketMQConsumer;
|
||||
@ -61,7 +62,7 @@ public class MessageEventListener {
|
||||
// 消息发送者可能是系统或管理员,这里使用固定的管理员账号作为发送者
|
||||
String fromUserId = "administrator";
|
||||
String toUserId = userId; // 接收者是事件中的用户ID
|
||||
String content = JsonUtils.toJsonString(event.getMessage()); // 消息内容序列化为JSON
|
||||
String content = event.getMessage().getContent(); // 只取content字段
|
||||
|
||||
// 处理消息变量替换(如果有)
|
||||
Map<String, String> variables = new HashMap<>();
|
||||
@ -127,7 +128,7 @@ public class MessageEventListener {
|
||||
}
|
||||
} else {
|
||||
// 默认为单用户推送
|
||||
tencentIMSendSuccess = tencentIMService.sendMessageToTencentIM(fromUserId, toUserId, content);
|
||||
tencentIMSendSuccess = tencentIMService.sendMessageToTencentIM(fromUserId, toUserId, content);
|
||||
}
|
||||
|
||||
if (tencentIMSendSuccess) {
|
||||
@ -141,6 +142,7 @@ public class MessageEventListener {
|
||||
|
||||
// 无论腾讯IM是否发送成功,都继续尝试通过RocketMQ发送消息
|
||||
// 这样可以确保消息至少通过一种方式发送出去
|
||||
/*
|
||||
boolean rocketMQSendSuccess = false;
|
||||
try {
|
||||
rocketMQService.sendMessage(
|
||||
@ -153,6 +155,8 @@ public class MessageEventListener {
|
||||
} catch (Exception e) {
|
||||
log.error("通过RocketMQ发送消息失败,将尝试直接通过WebSocket发送", e);
|
||||
}
|
||||
*/
|
||||
boolean rocketMQSendSuccess = false; // 直接设为false,表示不走MQ
|
||||
|
||||
// 如果前两种方式都失败,则尝试直接通过WebSocket发送
|
||||
if (!tencentIMSendSuccess && !rocketMQSendSuccess && event.getUserId() != null) {
|
||||
|
@ -22,7 +22,10 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -128,6 +131,11 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
|
||||
|
||||
// 批量创建消息用户关联
|
||||
int count = 0;
|
||||
|
||||
// 先过滤有效的用户ID
|
||||
List<Long> validUserIds = new ArrayList<>();
|
||||
Map<Long, String> userIdStrMap = new HashMap<>();
|
||||
|
||||
for (String userIdStr : userIds) {
|
||||
if (StringUtils.isBlank(userIdStr)) {
|
||||
continue;
|
||||
@ -135,37 +143,61 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
|
||||
|
||||
try {
|
||||
Long userId = Long.parseLong(userIdStr);
|
||||
|
||||
// 检查消息与用户关联是否已存在
|
||||
LambdaQueryWrapper<SysMessageUser> 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++;
|
||||
validUserIds.add(userId);
|
||||
userIdStrMap.put(userId, userIdStr);
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("无法将String类型的用户ID转换为Long: {}", userIdStr);
|
||||
}
|
||||
}
|
||||
|
||||
// 一次性查询所有已存在的关联记录
|
||||
if (!validUserIds.isEmpty()) {
|
||||
LambdaQueryWrapper<SysMessageUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SysMessageUser::getMessageId, entity.getId())
|
||||
.in(SysMessageUser::getUserId, validUserIds);
|
||||
List<SysMessageUser> existingRecords = messageUserMapper.selectList(queryWrapper);
|
||||
|
||||
// 创建已存在用户ID的集合
|
||||
Set<Long> existingUserIds = existingRecords.stream()
|
||||
.map(SysMessageUser::getUserId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
log.info("已存在的消息关联记录数: {}", existingUserIds.size());
|
||||
|
||||
// 对不存在关联的用户批量插入
|
||||
for (Long userId : validUserIds) {
|
||||
if (!existingUserIds.contains(userId)) {
|
||||
try {
|
||||
SysMessageUser messageUser = new SysMessageUser();
|
||||
messageUser.setMessageId(entity.getId());
|
||||
messageUser.setUserId(userId);
|
||||
messageUser.setIsRead(false);
|
||||
int rows = messageUserMapper.insert(messageUser);
|
||||
if (rows > 0) {
|
||||
count++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("创建消息用户关联失败: messageId={}, userId={}, error={}",
|
||||
entity.getId(), userId, e.getMessage());
|
||||
}
|
||||
} else {
|
||||
log.info("消息与用户关联已存在,跳过创建: messageId={}, userId={}", entity.getId(), userId);
|
||||
// 已存在也计入成功数量
|
||||
count++;
|
||||
}
|
||||
|
||||
// 无论关联是否新建,都发送事件通知
|
||||
SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class);
|
||||
eventPublisher.publishEvent(MessageEvent.createWithStringUserId(this, messageVo, userIdStr));
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("无法将String类型的用户ID转换为Long: {}", userIdStr);
|
||||
}
|
||||
}
|
||||
|
||||
// 最后只发送一次批量事件通知
|
||||
if (count > 0) {
|
||||
SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class);
|
||||
// 为所有有效用户ID组装一个逗号分隔的字符串
|
||||
String batchUserIds = validUserIds.stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(","));
|
||||
// 发布一个批量事件,在MessageEvent和MessageEventListener中进行处理
|
||||
eventPublisher.publishEvent(MessageEvent.createWithStringUserId(this, messageVo, batchUserIds));
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.dromara.system.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.system.service.ITencentIMService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -8,9 +9,17 @@ import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.Base64;
|
||||
import com.wzj.soopin.member.util.TLSSigAPIv2;
|
||||
|
||||
/**
|
||||
* 腾讯IM服务实现类
|
||||
@ -21,20 +30,30 @@ import java.util.regex.Pattern;
|
||||
@Service("systemTencentIMService")
|
||||
public class TencentIMServiceImpl implements ITencentIMService {
|
||||
|
||||
@Value("${tencent.im.sdkappid}")
|
||||
@Value("${tencent.im.sdk-app-id}")
|
||||
private long sdkAppId;
|
||||
|
||||
@Value("${tencent.im.secretkey}")
|
||||
@Value("${tencent.im.secret-key}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${tencent.im.admin}")
|
||||
@Value("${tencent.im.administrator}")
|
||||
private String adminAccount;
|
||||
|
||||
@Value("${tencent.im.expire-time:180}")
|
||||
private int expireTime;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
|
||||
// 变量替换的正则表达式模式
|
||||
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$(\\w+)");
|
||||
|
||||
private TLSSigAPIv2 tlsSigApi;
|
||||
|
||||
@PostConstruct
|
||||
private void initTlsSigApi() {
|
||||
this.tlsSigApi = new TLSSigAPIv2(sdkAppId, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用户签名
|
||||
*
|
||||
@ -42,28 +61,84 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
* @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);
|
||||
|
||||
// 当前为模拟实现,实际项目中请替换为真实签名生成逻辑
|
||||
// 直接调用官方TLSSigAPIv2生成
|
||||
return tlsSigApi.genUserSig("administrator", expireTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 tls 票据
|
||||
*
|
||||
* @param sdkappid 应用的 appid
|
||||
* @param userId 用户id
|
||||
* @param expire 有效期 (时间戳 单位秒)
|
||||
* @param priKeyContent 私钥文件内容
|
||||
* @return 通过 base64 编码的 tls 票据
|
||||
*/
|
||||
private String genTLSSignature(long sdkappid, String userId, long expire, String priKeyContent) {
|
||||
try {
|
||||
// 临时返回一个固定值用于测试
|
||||
// 注意:这是临时方案,正式环境必须替换为正确实现
|
||||
return "eJyrVgrxDXFSsrS0MDA2MzY1NTawNDAzsTQ1sTCyMDI2NdRRKs9ILUpVsjKoLEgsKk7NswlPzYnIsagoyIgrsYl3Lg5IALITi5BkS1JzSlOLbMJKi32DKtNCglONDSNNXfMqk3wN0myrvVUYGBgYGZiaGBoamCgZ6hlYGOUb6Rmaq1QHAAD--wJe";
|
||||
JSONObject sigDoc = new JSONObject();
|
||||
sigDoc.put("TLS.ver", "2.0");
|
||||
sigDoc.put("TLS.identifier", userId);
|
||||
sigDoc.put("TLS.sdkappid", sdkappid);
|
||||
sigDoc.put("TLS.expire", expire);
|
||||
sigDoc.put("TLS.time", System.currentTimeMillis() / 1000);
|
||||
|
||||
String base64UserBuf = null;
|
||||
if (null != userId) {
|
||||
base64UserBuf = base64EncodeUrl(userId.getBytes(StandardCharsets.UTF_8));
|
||||
sigDoc.put("TLS.userbuf", base64UserBuf);
|
||||
}
|
||||
|
||||
String sig = hmacSHA256(sdkappid, userId, expire, priKeyContent, base64UserBuf);
|
||||
if (sig.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
sigDoc.put("TLS.sig", sig);
|
||||
Deflater compressor = new Deflater();
|
||||
compressor.setInput(sigDoc.toString().getBytes(StandardCharsets.UTF_8));
|
||||
compressor.finish();
|
||||
byte[] compressedBytes = new byte[2048];
|
||||
int compressedBytesLength = compressor.deflate(compressedBytes);
|
||||
compressor.end();
|
||||
return base64EncodeUrl(Arrays.copyOfRange(compressedBytes, 0, compressedBytesLength));
|
||||
} catch (Exception e) {
|
||||
log.error("生成用户签名异常", e);
|
||||
return null;
|
||||
log.error("生成tls票据异常", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 HMAC-SHA256 生成签名
|
||||
*/
|
||||
private String hmacSHA256(long sdkappid, String userId, long expire, String priKeyContent, String base64UserBuf) {
|
||||
try {
|
||||
String contentToBeSigned = "TLS.identifier:" + "administrator" + "\n"
|
||||
+ "TLS.sdkappid:" + sdkappid + "\n"
|
||||
+ "TLS.time:" + System.currentTimeMillis() / 1000 + "\n"
|
||||
+ "TLS.expire:" + expire + "\n";
|
||||
if (null != base64UserBuf) {
|
||||
contentToBeSigned += "TLS.userbuf:" + base64UserBuf + "\n";
|
||||
}
|
||||
byte[] byteKey = priKeyContent.getBytes(StandardCharsets.UTF_8);
|
||||
Mac hmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");
|
||||
hmac.init(keySpec);
|
||||
byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes(StandardCharsets.UTF_8));
|
||||
return base64EncodeUrl(byteSig);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组使用 base64 编码(URL 安全)
|
||||
*/
|
||||
private String base64EncodeUrl(byte[] data) {
|
||||
byte[] encodedBytes = Base64.getEncoder().encode(data);
|
||||
String encodedStr = new String(encodedBytes);
|
||||
return encodedStr.replace('+', '*').replace('/', '-').replace('=', '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成管理员签名
|
||||
*
|
||||
@ -92,17 +167,17 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("UserID", userId);
|
||||
requestBody.put("Nick", userId);
|
||||
|
||||
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
|
||||
// 发送请求
|
||||
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||||
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
|
||||
|
||||
|
||||
log.info("创建腾讯IM账号结果: {}", response.getBody());
|
||||
|
||||
|
||||
// 生成并返回用户签名
|
||||
return generateUserSig(userId);
|
||||
} catch (Exception e) {
|
||||
@ -146,13 +221,13 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
|
||||
// 发送请求
|
||||
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||||
ResponseEntity<String> 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"));
|
||||
@ -161,7 +236,7 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean pushToAll(String title, String desc, boolean offlinePush, String ext) {
|
||||
try {
|
||||
@ -181,12 +256,12 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
Map<String, Object> msgContent = new HashMap<>();
|
||||
msgContent.put("Title", title);
|
||||
msgContent.put("Desc", desc);
|
||||
|
||||
|
||||
// 构建消息体
|
||||
Map<String, Object> msgBody = new HashMap<>();
|
||||
msgBody.put("MsgType", "TIMCustomElem");
|
||||
msgBody.put("MsgContent", msgContent);
|
||||
|
||||
|
||||
// 如果有扩展字段,添加到消息内容中
|
||||
if (ext != null && !ext.isEmpty()) {
|
||||
msgContent.put("Ext", ext);
|
||||
@ -197,7 +272,7 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
requestBody.put("From_Account", adminAccount);
|
||||
requestBody.put("MsgRandom", Integer.parseInt(random));
|
||||
requestBody.put("MsgBody", new Object[]{msgBody});
|
||||
|
||||
|
||||
// 设置离线推送信息
|
||||
if (offlinePush) {
|
||||
Map<String, Object> offlinePushInfo = new HashMap<>();
|
||||
@ -211,13 +286,13 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
|
||||
// 发送请求
|
||||
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||||
ResponseEntity<String> 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"));
|
||||
@ -226,7 +301,7 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean pushByAttributes(String title, String desc, Map<String, Object> attributes, boolean offlinePush, String ext) {
|
||||
try {
|
||||
@ -246,12 +321,12 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
Map<String, Object> msgContent = new HashMap<>();
|
||||
msgContent.put("Title", title);
|
||||
msgContent.put("Desc", desc);
|
||||
|
||||
|
||||
// 构建消息体
|
||||
Map<String, Object> msgBody = new HashMap<>();
|
||||
msgBody.put("MsgType", "TIMCustomElem");
|
||||
msgBody.put("MsgContent", msgContent);
|
||||
|
||||
|
||||
// 如果有扩展字段,添加到消息内容中
|
||||
if (ext != null && !ext.isEmpty()) {
|
||||
msgContent.put("Ext", ext);
|
||||
@ -264,7 +339,7 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
requestBody.put("MsgBody", new Object[]{msgBody});
|
||||
requestBody.put("AttrNames", attributes.keySet().toArray(new String[0]));
|
||||
requestBody.put("AttrValues", attributes.values().toArray());
|
||||
|
||||
|
||||
// 设置离线推送信息
|
||||
if (offlinePush) {
|
||||
Map<String, Object> offlinePushInfo = new HashMap<>();
|
||||
@ -278,13 +353,13 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
|
||||
// 发送请求
|
||||
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||||
ResponseEntity<String> 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"));
|
||||
@ -293,7 +368,7 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean pushByTags(String title, String desc, List<String> tags, boolean offlinePush, String ext) {
|
||||
try {
|
||||
@ -313,12 +388,12 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
Map<String, Object> msgContent = new HashMap<>();
|
||||
msgContent.put("Title", title);
|
||||
msgContent.put("Desc", desc);
|
||||
|
||||
|
||||
// 构建消息体
|
||||
Map<String, Object> msgBody = new HashMap<>();
|
||||
msgBody.put("MsgType", "TIMCustomElem");
|
||||
msgBody.put("MsgContent", msgContent);
|
||||
|
||||
|
||||
// 如果有扩展字段,添加到消息内容中
|
||||
if (ext != null && !ext.isEmpty()) {
|
||||
msgContent.put("Ext", ext);
|
||||
@ -330,7 +405,7 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
requestBody.put("MsgRandom", Integer.parseInt(random));
|
||||
requestBody.put("MsgBody", new Object[]{msgBody});
|
||||
requestBody.put("TagList", tags);
|
||||
|
||||
|
||||
// 设置离线推送信息
|
||||
if (offlinePush) {
|
||||
Map<String, Object> offlinePushInfo = new HashMap<>();
|
||||
@ -344,13 +419,13 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
|
||||
// 发送请求
|
||||
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||||
ResponseEntity<String> 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"));
|
||||
@ -359,7 +434,7 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean pushToUsers(String title, String desc, List<String> userIds, boolean offlinePush, String ext) {
|
||||
try {
|
||||
@ -379,12 +454,12 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
Map<String, Object> msgContent = new HashMap<>();
|
||||
msgContent.put("Title", title);
|
||||
msgContent.put("Desc", desc);
|
||||
|
||||
|
||||
// 构建消息体
|
||||
Map<String, Object> msgBody = new HashMap<>();
|
||||
msgBody.put("MsgType", "TIMCustomElem");
|
||||
msgBody.put("MsgContent", msgContent);
|
||||
|
||||
|
||||
// 如果有扩展字段,添加到消息内容中
|
||||
if (ext != null && !ext.isEmpty()) {
|
||||
msgContent.put("Ext", ext);
|
||||
@ -396,7 +471,7 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
requestBody.put("To_Account", userIds);
|
||||
requestBody.put("MsgRandom", Integer.parseInt(random));
|
||||
requestBody.put("MsgBody", new Object[]{msgBody});
|
||||
|
||||
|
||||
// 设置离线推送信息
|
||||
if (offlinePush) {
|
||||
Map<String, Object> offlinePushInfo = new HashMap<>();
|
||||
@ -410,13 +485,13 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
|
||||
// 发送请求
|
||||
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
||||
ResponseEntity<String> 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"));
|
||||
@ -425,23 +500,23 @@ public class TencentIMServiceImpl implements ITencentIMService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String processMessageVariables(String content, Map<String, String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user