第一次提交

This commit is contained in:
wyw
2024-08-08 00:31:26 +08:00
commit c202e2b63d
1819 changed files with 221890 additions and 0 deletions

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fastbee</groupId>
<artifactId>fastbee-notify</artifactId>
<version>3.8.5</version>
</parent>
<description>通知核心发送模块</description>
<artifactId>fastbee-notify-core</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fastbee</groupId>
<artifactId>fastbee-common</artifactId>
</dependency>
<!-- 通知配置 -->
<dependency>
<groupId>com.fastbee</groupId>
<artifactId>fastbee-notify-web</artifactId>
</dependency>
<!-- 微信 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>com.fastbee</groupId>
<artifactId>fastbee-iot-service</artifactId>
</dependency>
<!-- 阿里云语音 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dyvmsapi20170525</artifactId>
<version>2.1.4</version>
</dependency>
<!-- 腾讯云语音 -->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.952</version>
</dependency>
<!-- 邮箱快捷包 -->
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-Email-core</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 钉钉官方包 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>1.1.32</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,87 @@
package com.fastbee.notify.core.controller;
import com.fastbee.common.core.domain.AjaxResult;
import com.fastbee.notify.core.service.NotifySendService;
import com.fastbee.notify.core.vo.SendParams;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author fastb
* @version 1.0
* @description: 通知控制器
* @date 2023-12-15 11:44
*/
@Api(tags = "通知发送")
@RestController
@RequestMapping("/notify")
public class NotifyController {
@Resource
private NotifySendService notifySendService;
/**
* @description: 通知测试发送接口
* @param: sendParams 发送参数
* @return: com.fastbee.common.core.domain.AjaxResult
*/
@PreAuthorize("@ss.hasPermi('notify:template:send')")
@PostMapping("/send")
@ApiOperation("通知模版测试发送接口")
public AjaxResult send(@RequestBody SendParams sendParams){
return notifySendService.send(sendParams);
}
/**
* @description: 短信登录获取验证码
* @param: phoneNumber 手机号
* @return: com.fastbee.common.core.domain.AjaxResult
*/
@GetMapping("/smsLoginCaptcha")
@ApiOperation("短信登录获取验证码")
public AjaxResult smsLoginCaptcha(String phoneNumber){
return notifySendService.smsLoginCaptcha(phoneNumber);
}
/**
* 企业微信验证url有效性
* @param msgSignature
* @param: timestamp
* @param: nonce
* @param: echostr
* @param: response
* @return void
*/
@ApiOperation("企业微信验证url有效性")
@GetMapping("/weComVerifyUrl")
public void weComVerifyUrl(@RequestParam(value = "msg_signature") String msgSignature,
@RequestParam(value = "timestamp") String timestamp,
@RequestParam(value = "nonce") String nonce,
@RequestParam(value = "echostr") String echostr,
HttpServletResponse response) {
String msg = notifySendService.weComVerifyUrl(msgSignature, timestamp, nonce, echostr);
try {
response.getWriter().print(msg);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* @description: 短信注册获取验证码
* @param: phoneNumber 手机号
* @return: com.fastbee.common.core.domain.AjaxResult
*/
@GetMapping("/smsRegisterCaptcha")
@ApiOperation("短信登录获取验证码")
public AjaxResult smsRegisterCaptcha(String phoneNumber){
return notifySendService.smsRegisterCaptcha(phoneNumber);
}
}

View File

@ -0,0 +1,21 @@
package com.fastbee.notify.core.dingtalk.service;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.notify.vo.NotifyVO;
/**
* 钉钉通知服务类
* @author fastb
* @date 2024-01-12 17:57
* @version 1.0
*/
public interface DingTalkService {
/**
* 钉钉统一发送方法
* @param notifyVO 通知参数
* @return com.fastbee.common.core.notify.NotifySendResponse
*/
NotifySendResponse send(NotifyVO notifyVO);
}

View File

@ -0,0 +1,225 @@
package com.fastbee.notify.core.dingtalk.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.aliyun.dingtalkoauth2_1_0.Client;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.Common;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiMessageCorpconversationAsyncsendV2Request;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiMessageCorpconversationAsyncsendV2Response;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.common.core.notify.config.DingTalkConfigParams;
import com.fastbee.common.core.notify.msg.DingTalkMsgParams;
import com.fastbee.common.enums.NotifyChannelProviderEnum;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.notify.core.dingtalk.service.DingTalkService;
import com.fastbee.notify.domain.NotifyChannel;
import com.fastbee.notify.domain.NotifyTemplate;
import com.fastbee.notify.vo.NotifyVO;
import com.taobao.api.ApiException;
import org.springframework.stereotype.Service;
/**
* @author fastb
* @version 1.0
* @description: 钉钉通知服务类
* @date 2024-01-12 17:58
*/
@Service
public class DingTalkServiceImpl implements DingTalkService {
@Override
public NotifySendResponse send(NotifyVO notifyVO) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
NotifyChannel notifyChannel = notifyVO.getNotifyChannel();
NotifyTemplate notifyTemplate = notifyVO.getNotifyTemplate();
String content = JSONObject.parseObject(notifyTemplate.getMsgParams()).get("content").toString();
String sendContent = StringUtils.strReplaceVariable("${", "}", content, notifyVO.getMap());
DingTalkMsgParams dingTalkMsgParams = JSONObject.parseObject(notifyTemplate.getMsgParams(), DingTalkMsgParams.class);
// 获取AppKey和AppSecret
DingTalkConfigParams dingTalkConfigParams = JSONObject.parseObject(notifyChannel.getConfigContent(), DingTalkConfigParams.class);
if (NotifyChannelProviderEnum.DING_TALK_WORK.equals(notifyVO.getNotifyChannelProviderEnum())) {
notifySendResponse = this.workSend(dingTalkConfigParams, dingTalkMsgParams, notifyVO.getSendAccount(), sendContent);
} else if (NotifyChannelProviderEnum.DING_TALK_GROUP_ROBOT.equals(notifyVO.getNotifyChannelProviderEnum())) {
notifySendResponse = this.customizeRobotSend(dingTalkConfigParams, dingTalkMsgParams, sendContent);
}
return notifySendResponse;
}
/**
* 自定义机器人发送
* @param dingTalkConfigParams 渠道配置参数
* @param: dingTalkMsgParams 模版配置参数
* @param: sendContent 发送内容
* @return com.fastbee.common.core.notify.NotifySendResponse
*/
private NotifySendResponse customizeRobotSend(DingTalkConfigParams dingTalkConfigParams, DingTalkMsgParams dingTalkMsgParams, String sendContent) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
DefaultDingTalkClient client = new DefaultDingTalkClient(dingTalkConfigParams.getWebHook());
OapiRobotSendRequest request = this.createOapiRobotMsg(dingTalkMsgParams, sendContent);
try {
OapiRobotSendResponse execute = client.execute(request);
notifySendResponse.setStatus("0".equals(execute.getErrorCode()) ? 1 : 0);
notifySendResponse.setResultContent(JSON.toJSONString(execute));
} catch (ApiException e) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent(e.toString());
}
return notifySendResponse;
}
/**
* 工作通知发送
* @param dingTalkConfigParams 渠道配置参数
* @param: dingTalkMsgParams 模版配置参数
* @param: sendAccount 发送账号,多个以,隔开
* @param: sendContent 发送内容
* @return com.fastbee.common.core.notify.NotifySendResponse
*/
private NotifySendResponse workSend(DingTalkConfigParams dingTalkConfigParams, DingTalkMsgParams dingTalkMsgParams, String sendAccount, String sendContent) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
notifySendResponse.setSendContent(sendContent);
notifySendResponse.setStatus(1);
// 发送
try {
// 获取应用访问凭证accessToken
Config config = new Config();
config.protocol = "https";
config.regionId = "central";
Client client = new Client(config);
GetAccessTokenRequest accessTokenRequest = new GetAccessTokenRequest();
accessTokenRequest.setAppKey(dingTalkConfigParams.getAppKey());
accessTokenRequest.setAppSecret(dingTalkConfigParams.getAppSecret());
GetAccessTokenResponse accessTokenResponse = client.getAccessToken(accessTokenRequest);
String accessToken = accessTokenResponse.getBody().getAccessToken();
DingTalkClient dingTalkClient = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");
OapiMessageCorpconversationAsyncsendV2Request req = new OapiMessageCorpconversationAsyncsendV2Request();
req.setAgentId(Long.valueOf(dingTalkConfigParams.getAgentId()));
// 优先取发送账号、然后是部门、然后是所有人
if (StringUtils.isNotEmpty(sendAccount)) {
// 根据手机号获取钉钉userid
// DingTalkClient dingTalkClientUser = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getbymobile");
// OapiV2UserGetbymobileRequest phoneReq = new OapiV2UserGetbymobileRequest();
// phoneReq.setMobile(sendAccount);
// OapiV2UserGetbymobileResponse rsp = dingTalkClientUser.execute(phoneReq, accessToken);
// userId = rsp.getResult().getUserid();
req.setUseridList(sendAccount);
} else if (StringUtils.isNotEmpty(dingTalkMsgParams.getDeptId())) {
req.setDeptIdList(dingTalkMsgParams.getDeptId());
notifySendResponse.setOtherSendAccount(dingTalkMsgParams.getDeptId());
} else if (Boolean.TRUE.toString().equals(dingTalkMsgParams.getSendAllEnable())) {
req.setToAllUser(Boolean.TRUE);
notifySendResponse.setOtherSendAccount("allUser");
}
OapiMessageCorpconversationAsyncsendV2Request.Msg msg = this.createOapiMessageMsg(dingTalkMsgParams, sendContent);
req.setMsg(msg);
OapiMessageCorpconversationAsyncsendV2Response rsp = dingTalkClient.execute(req, accessToken);
notifySendResponse.setStatus(0 == rsp.getErrcode() ? 1 : 0);
notifySendResponse.setResultContent(JSON.toJSONString(rsp));
} catch (Exception e) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent(e.toString());
}
return notifySendResponse;
}
private OapiRobotSendRequest createOapiRobotMsg(DingTalkMsgParams dingTalkMsgParams, String sendContent) {
OapiRobotSendRequest request = new OapiRobotSendRequest();
request.setMsgtype(dingTalkMsgParams.getMsgType());
switch (request.getMsgtype()) {
case "text":
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(sendContent);
request.setText(text);
break;
case "link":
OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link();
link.setTitle(dingTalkMsgParams.getTitle());
link.setText(sendContent);
link.setMessageUrl(dingTalkMsgParams.getMessageUrl());
link.setPicUrl(dingTalkMsgParams.getPicUrl());
request.setLink(link);
break;
case "markdown":
OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
markdown.setText(sendContent);
markdown.setTitle(dingTalkMsgParams.getTitle());
request.setMarkdown(markdown);
break;
default:
break;
}
return request;
}
/**
* 构建钉钉发送消息
* @param msgParams 消息参数
* @return
*/
private OapiMessageCorpconversationAsyncsendV2Request.Msg createOapiMessageMsg(DingTalkMsgParams msgParams, String content) {
OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
msg.setMsgtype(msgParams.getMsgType());
switch (msg.getMsgtype()) {
case "text":
msg.setText(new OapiMessageCorpconversationAsyncsendV2Request.Text());
msg.getText().setContent(content);
break;
case "link":
msg.setLink(new OapiMessageCorpconversationAsyncsendV2Request.Link());
msg.getLink().setTitle(msgParams.getTitle());
msg.getLink().setText(content);
msg.getLink().setMessageUrl(msgParams.getMessageUrl());
msg.getLink().setPicUrl(msgParams.getPicUrl());
break;
case "markdown":
msg.setMarkdown(new OapiMessageCorpconversationAsyncsendV2Request.Markdown());
msg.getMarkdown().setText(content);
msg.getMarkdown().setTitle(msgParams.getTitle());
break;
default:
break;
}
return msg;
}
/**
* 获取AccessToken
* @param appKey
* @param: appSecret
* @return java.lang.String
*/
private String getAccessToken(String appKey, String appSecret) {
try {
Config config = new Config();
config.protocol = "https";
config.regionId = "central";
Client client = new Client(config);
GetAccessTokenRequest accessTokenRequest = new GetAccessTokenRequest();
accessTokenRequest.setAppKey(appKey);
accessTokenRequest.setAppSecret(appSecret);
GetAccessTokenResponse accessToken = client.getAccessToken(accessTokenRequest);
return accessToken.getBody().getAccessToken();
} catch (TeaException err) {
if (!Common.empty(err.code) && !Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
} catch (Exception _err) {
TeaException err = new TeaException(_err.getMessage(), _err);
if (!Common.empty(err.code) && !Common.empty(err.message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
}
return "";
}
}

View File

@ -0,0 +1,32 @@
package com.fastbee.notify.core.email.config;
import com.fastbee.common.core.notify.config.EmailConfigParams;
import org.dromara.email.api.MailClient;
import org.dromara.email.comm.config.MailSmtpConfig;
import org.dromara.email.core.factory.MailFactory;
/**
* @author fastb
* @version 1.0
* @description: 邮箱获取发送配置类
* @date 2023-12-20 10:23
*/
public class EmailNotifyConfig {
public static MailClient create(String mailClientKey, EmailConfigParams emailNotifyConfig) {
MailSmtpConfig config = MailSmtpConfig.builder()
.smtpServer(emailNotifyConfig.getSmtpServer())
.port(emailNotifyConfig.getPort())
.fromAddress(emailNotifyConfig.getUsername())
.username(emailNotifyConfig.getUsername())
.password(emailNotifyConfig.getPassword())
.isSSL(emailNotifyConfig.getSslEnable().toString())
.isAuth(emailNotifyConfig.getAuthEnable().toString())
.retryInterval(emailNotifyConfig.getRetryInterval())
.maxRetries(emailNotifyConfig.getMaxRetries())
.build();
MailFactory.put(mailClientKey, config);
return MailFactory.createMailClient(mailClientKey);
}
}

View File

@ -0,0 +1,21 @@
package com.fastbee.notify.core.email.service;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.notify.vo.NotifyVO;
/**
* @description: 邮箱发送业务类
* @author fastb
* @date 2023-12-29 16:20
* @version 1.0
*/
public interface EmailService {
/**
* @description: 邮件简要内容发送
* @param: notifyVO 发送VO类
* @return: void
*/
NotifySendResponse send(NotifyVO notifyVO);
}

View File

@ -0,0 +1,85 @@
package com.fastbee.notify.core.email.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.common.core.notify.config.EmailConfigParams;
import com.fastbee.common.core.notify.msg.EmailMsgParams;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.notify.core.email.config.EmailNotifyConfig;
import com.fastbee.notify.core.email.service.EmailService;
import com.fastbee.notify.domain.NotifyChannel;
import com.fastbee.notify.domain.NotifyTemplate;
import com.fastbee.notify.vo.NotifyVO;
import org.dromara.email.api.MailClient;
import org.dromara.email.comm.entity.MailMessage;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author fastb
* @version 1.0
* @description: 邮箱发送业务类
* @date 2023-12-20 10:21
*/
@Service
public class EmailServiceImpl implements EmailService {
public MailClient createMailClient(NotifyChannel notifyChannel, NotifyTemplate notifyTemplate) {
// 构建邮箱配置对象key为 provider_id
String mailClientKey = notifyChannel.getProvider() + "_" + notifyTemplate.getId();
EmailConfigParams emailNotifyConfig = JSONObject.parseObject(notifyChannel.getConfigContent(), EmailConfigParams.class);
return EmailNotifyConfig.create(mailClientKey, emailNotifyConfig);
}
@Override
public NotifySendResponse send(NotifyVO notifyVO) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
if (StringUtils.isEmpty(notifyVO.getSendAccount())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("发送邮箱号为空,请先配置发送邮箱号!");
return notifySendResponse;
}
MailClient mailClient;
try {
mailClient = this.createMailClient(notifyVO.getNotifyChannel(), notifyVO.getNotifyTemplate());
} catch (Exception e) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("获取邮箱发送类失败," + e);
return notifySendResponse;
}
NotifyTemplate notifyTemplate = notifyVO.getNotifyTemplate();
EmailMsgParams emailMsgParams = JSONObject.parseObject(notifyTemplate.getMsgParams(), EmailMsgParams.class);
// 目前附件就支持一个
Map<String, String> filesMap = new HashMap<>(2);
if (StringUtils.isNotEmpty(emailMsgParams.getAttachment())) {
String fileName = emailMsgParams.getAttachment().substring(emailMsgParams.getAttachment().lastIndexOf("/"));
filesMap.put(fileName, emailMsgParams.getAttachment());
}
// 多个邮箱以,分隔
List<String> mailList = StringUtils.str2List(notifyVO.getSendAccount(), ",", true, true);
MailMessage mailMessage = MailMessage.Builder()
.mailAddress(mailList)
.title(emailMsgParams.getTitle())
.html(new ByteArrayInputStream(emailMsgParams.getContent().getBytes()))
.htmlValues(notifyVO.getMap())
.files(filesMap)
.build();
try {
mailClient.send(mailMessage);
} catch (Exception e) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("邮箱发送失败," + e);
return notifySendResponse;
}
String sendContent = StringUtils.strReplaceVariable("#{", "}", emailMsgParams.getContent(), notifyVO.getMap());
notifySendResponse.setSendContent(sendContent);
notifySendResponse.setStatus(1);
return notifySendResponse;
}
}

View File

@ -0,0 +1,80 @@
package com.fastbee.notify.core.service;
import com.fastbee.common.core.domain.AjaxResult;
import com.fastbee.common.core.notify.AlertPushParams;
import com.fastbee.notify.core.vo.SendParams;
import com.fastbee.notify.vo.NotifyVO;
/**
* @description: 所有通知发送入口
* 以后有通知业务可写在这里
* @author fastb
* @date 2023-12-26 15:47
* @version 1.0
*/
public interface NotifySendService {
/**
* @description: 通知测试发送接口
* @param: sendParams
* @return: void
*/
AjaxResult send(SendParams sendParams);
/**
* @description: 通知统一发送方法
* @param: notifyVO 通知发送参数VO
* @return: void
*/
AjaxResult notifySend(NotifyVO notifyVO);
/**
* 告警通知统一推送
* @param alertPushParams 告警推送参数
* @return void
*/
void alertSend(AlertPushParams alertPushParams);
/**
* @description: 发送短信验证码
* @author fastb
* @date 2023-12-26 15:49
* @version 1.0
*/
void sendCaptchaSms(String phone, String captcha);
/**
* @description: 根据业务编码、渠道、服务商获取唯一启用通知配置信息
* @param serviceCode 业务编码,一定传
* @param channelType 渠道类型 一定传
* @param provider 钉钉和微信渠道 一定传
* @author fastb
* @date 2024-01-02 11:13
*/
NotifyVO selectOnlyEnable(String serviceCode, String channelType, String provider, Long tenantId);
/**
* @description: 短信登录获取验证码
* @param: phoneNumber
* @return: com.fastbee.common.core.domain.AjaxResult
*/
AjaxResult smsLoginCaptcha(String phoneNumber);
/**
* 企业微信验证url有效性
* @param msgSignature
* @param: timestamp
* @param: nonce
* @param: echostr
* @param: response
* @return void
*/
String weComVerifyUrl(String msgSignature, String timestamp, String nonce, String echostr);
/**
* @description: 短信注册获取验证码
* @param: phoneNumber 手机号
* @return: com.fastbee.common.core.domain.AjaxResult
*/
AjaxResult smsRegisterCaptcha(String phoneNumber);
}

View File

@ -0,0 +1,333 @@
package com.fastbee.notify.core.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.constant.CacheConstants;
import com.fastbee.common.core.domain.AjaxResult;
import com.fastbee.common.core.notify.AlertPushParams;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.enums.NotifyChannelEnum;
import com.fastbee.common.enums.NotifyChannelProviderEnum;
import com.fastbee.common.enums.NotifyServiceCodeEnum;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.ValidationUtils;
import com.fastbee.common.utils.VerifyCodeUtils;
import com.fastbee.common.utils.wechat.AesException;
import com.fastbee.common.utils.wechat.WXBizMsgCrypt;
import com.fastbee.notify.core.dingtalk.service.DingTalkService;
import com.fastbee.notify.core.email.service.EmailService;
import com.fastbee.notify.core.service.NotifySendService;
import com.fastbee.notify.core.sms.service.ISmsService;
import com.fastbee.notify.core.vo.SendParams;
import com.fastbee.notify.core.voice.service.VoiceService;
import com.fastbee.notify.core.wechat.service.WeChatPushService;
import com.fastbee.notify.domain.NotifyChannel;
import com.fastbee.notify.domain.NotifyLog;
import com.fastbee.notify.domain.NotifyTemplate;
import com.fastbee.notify.service.INotifyChannelService;
import com.fastbee.notify.service.INotifyLogService;
import com.fastbee.notify.service.INotifyTemplateService;
import com.fastbee.notify.vo.NotifyVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.checkerframework.checker.units.qual.C;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author fastb
* @version 1.0
* @description: 通知业务类
* @date 2023-12-26 10:38
*/
@Slf4j
@Service
public class NotifySendServiceImpl implements NotifySendService {
@Resource
private INotifyChannelService notifyChannelService;
@Resource
private INotifyTemplateService notifyTemplateService;
@Resource
private INotifyLogService notifyLogService;
@Resource
private ISmsService smsService;
@Resource
private VoiceService voiceService;
@Resource
private EmailService emailService;
@Resource
private WeChatPushService weChatPushService;
@Resource
private RedisCache redisCache;
@Resource
private DingTalkService dingTalkService;
@Override
public AjaxResult send(SendParams sendParams) {
// 获取配置参数
NotifyTemplate notifyTemplate = notifyTemplateService.selectNotifyTemplateById(sendParams.getId());
NotifyChannel notifyChannel = notifyChannelService.selectNotifyChannelById(notifyTemplate.getChannelId());
LinkedHashMap<String,String> map = new LinkedHashMap<>();
if (StringUtils.isNotEmpty(sendParams.getVariables())) {
map = JSONObject.parseObject(sendParams.getVariables(), LinkedHashMap.class);
}
NotifyChannelProviderEnum notifyChannelProviderEnum = NotifyChannelProviderEnum.getByChannelTypeAndProvider(notifyChannel.getChannelType(), notifyChannel.getProvider());
NotifyVO notifyVO = new NotifyVO();
notifyVO.setNotifyChannel(notifyChannel).setNotifyTemplate(notifyTemplate)
.setSendAccount(sendParams.getSendAccount()).setMap(map).setNotifyChannelProviderEnum(notifyChannelProviderEnum);
return this.notifySend(notifyVO);
}
@Override
public AjaxResult notifySend(NotifyVO notifyVO) {
// 获取发送参数
NotifyChannel notifyChannel = notifyVO.getNotifyChannel();
NotifyTemplate notifyTemplate = notifyVO.getNotifyTemplate();
String sendAccount = notifyVO.getSendAccount();
if (StringUtils.isNotEmpty(notifyVO.getSendAccount())) {
String s = notifyVO.getSendAccount().replaceAll("", ",");
sendAccount = s;
notifyVO.setSendAccount(s);
}
NotifyChannelEnum notifyChannelEnum = NotifyChannelEnum.getNotifyChannelEnum(notifyChannel.getChannelType());
// 组装模板内容参数,发送通知
NotifySendResponse notifySendResponse = new NotifySendResponse();
switch (Objects.requireNonNull(notifyChannelEnum)) {
case SMS:
notifySendResponse = smsService.send(notifyVO);
break;
case EMAIL:
notifySendResponse = emailService.send(notifyVO);
break;
case VOICE:
notifySendResponse = voiceService.send(notifyVO);
break;
case WECHAT:
notifySendResponse = weChatPushService.send(notifyVO);
break;
case DING_TALK:
notifySendResponse = dingTalkService.send(notifyVO);
break;
default:
break;
}
// 保存日志
NotifyLog notifyLog = new NotifyLog();
notifyLog.setChannelId(notifyChannel.getId()).setNotifyTemplateId(notifyTemplate.getId())
.setSendAccount(StringUtils.isNotEmpty(notifySendResponse.getOtherSendAccount()) ? notifySendResponse.getOtherSendAccount() : sendAccount)
.setServiceCode(notifyTemplate.getServiceCode())
.setMsgContent(notifySendResponse.getSendContent())
.setSendStatus(notifySendResponse.getStatus()).setResultContent(notifySendResponse.getResultContent())
.setTenantId(notifyTemplate.getTenantId()).setTenantName(notifyTemplate.getTenantName());
notifyLogService.insertNotifyLog(notifyLog);
return AjaxResult.success();
}
@Override
public void alertSend(AlertPushParams alertPushParams) {
// 获取发送模版
NotifyTemplate notifyTemplate = notifyTemplateService.selectNotifyTemplateById(alertPushParams.getNotifyTemplateId());
if (Objects.isNull(notifyTemplate) || 0 == notifyTemplate.getStatus()) {
log.info("告警关联通知模版未启用,模版编号:{}", alertPushParams.getNotifyTemplateId());
return;
}
NotifyChannel notifyChannel = notifyChannelService.selectNotifyChannelById(notifyTemplate.getChannelId());
NotifyChannelProviderEnum notifyChannelProviderEnum = NotifyChannelProviderEnum.getByChannelTypeAndProvider(notifyChannel.getChannelType(), notifyChannel.getProvider());
NotifyVO notifyVO = new NotifyVO();
notifyVO.setNotifyChannel(notifyChannel).setNotifyTemplate(notifyTemplate).setNotifyChannelProviderEnum(notifyChannelProviderEnum);
// 获取模版参数
JSONObject jsonMsgParams = JSONObject.parseObject(notifyVO.getNotifyTemplate().getMsgParams());
String content = jsonMsgParams.get("content").toString();
List<String> variables = notifyTemplateService.listVariables(content, notifyChannelProviderEnum);
// 获取模版变量
assert notifyChannelProviderEnum != null;
NotifyChannelEnum notifyChannelEnum = NotifyChannelEnum.getNotifyChannelEnum(notifyChannelProviderEnum.getChannelType());
LinkedHashMap<String, String> map = new LinkedHashMap<>();
switch (Objects.requireNonNull(notifyChannelEnum)) {
// 示例内容变量顺序:您的设备:${name},设备编号:${serialnumber},在${address}发生${alert}告警; 可自行修改
case SMS:
case EMAIL:
case WECHAT:
case DING_TALK:
// 按顺序依次替换变量信息
for (int i = 0; i < variables.size(); i++) {
if (i == 0) {
map.put(variables.get(i), alertPushParams.getDeviceName());
} else if (i == 1) {
map.put(variables.get(i), alertPushParams.getSerialNumber());
} else if (i == 2) {
map.put(variables.get(i), alertPushParams.getAddress());
} else {
map.put(variables.get(i), alertPushParams.getAlertName());
}
}
break;
// 示例内容变量顺序:您的设备:${name},在${address}发生告警,请尽快处理;
// 阿里云语音模版只支持两个变量,所有语音统一使用两个变量,可自行修改
case VOICE:
// 按顺序依次替换变量信息
for (int i = 0; i < variables.size(); i++) {
if (i == 0) {
map.put(variables.get(i), alertPushParams.getDeviceName());
} else {
map.put(variables.get(i), alertPushParams.getAddress());
}
}
break;
default:
break;
}
// 获取发送账号
Object sendAccountObject = jsonMsgParams.get("sendAccount");
Set<String> sendAccountSet = new HashSet<>();
if (ObjectUtil.isNotEmpty(sendAccountObject)) {
Collections.addAll(sendAccountSet, sendAccountObject.toString());
}
// 短信、语音、微信小程序需要取设备所属及分享用户信息+模版配置账号,其余使用模版配置的账号
if (NotifyChannelEnum.SMS.equals(notifyChannelEnum) || NotifyChannelEnum.VOICE.equals(notifyChannelEnum)) {
if (CollectionUtils.isNotEmpty(alertPushParams.getUserPhoneSet())) {
sendAccountSet.addAll(alertPushParams.getUserPhoneSet());
}
}
if (NotifyChannelProviderEnum.WECHAT_MINI_PROGRAM.equals(notifyChannelProviderEnum)
|| NotifyChannelProviderEnum.WECHAT_PUBLIC_ACCOUNT.equals(notifyChannelProviderEnum)) {
if (CollectionUtils.isNotEmpty(alertPushParams.getUserIdSet())) {
for (Long userId : alertPushParams.getUserIdSet()) {
sendAccountSet.add(userId.toString());
}
}
}
notifyVO.setSendAccount(StringUtils.join(sendAccountSet, ","));
// 发送
notifyVO.setMap(map);
this.notifySend(notifyVO);
}
@Override
public void sendCaptchaSms(String phone, String captcha) {
NotifyVO notifyVO = this.selectOnlyEnable(NotifyServiceCodeEnum.CAPTCHA.getServiceCode(), NotifyChannelEnum.SMS.getType(), null, 1L);
NotifyChannelProviderEnum notifyChannelProviderEnum = notifyVO.getNotifyChannelProviderEnum();
// 获取模板参数
JSONObject jsonMsgParams = JSONObject.parseObject(notifyVO.getNotifyTemplate().getMsgParams());
String content = jsonMsgParams.get("content").toString();
// 从模板内容中获取 占位符 关键字
LinkedHashMap<String, String> map = new LinkedHashMap<>();
List<String> variables = new ArrayList<>();
if (NotifyChannelProviderEnum.SMS_ALIBABA.equals(notifyChannelProviderEnum)) {
variables = StringUtils.getVariables("${}", content);
} else if (NotifyChannelProviderEnum.SMS_TENCENT.equals(notifyChannelProviderEnum)) {
variables = StringUtils.getVariables("{}", content);
}
map.put(variables.get(0), captcha);
notifyVO.setSendAccount(phone);
notifyVO.setMap(map);
this.notifySend(notifyVO);
}
@Override
public NotifyVO selectOnlyEnable(String serviceCode, String channelType, String provider, Long tenantId) {
// 获取查询条件
NotifyTemplate enableQueryCondition = notifyTemplateService.getEnableQueryCondition(serviceCode, channelType, provider, tenantId);
NotifyTemplate notifyTemplate = notifyTemplateService.selectOnlyEnable(enableQueryCondition);
if (Objects.isNull(notifyTemplate)) {
throw new ServiceException("查询不到启用的通知模板");
}
NotifyChannel notifyChannel = notifyChannelService.selectNotifyChannelById(notifyTemplate.getChannelId());
if (Objects.isNull(notifyChannel)) {
throw new ServiceException("查询不到通知渠道");
}
NotifyVO notifyVO = new NotifyVO();
notifyVO.setNotifyChannel(notifyChannel);
notifyVO.setNotifyTemplate(notifyTemplate);
NotifyChannelProviderEnum notifyChannelProviderEnum = NotifyChannelProviderEnum.getByChannelTypeAndProvider(notifyVO.getNotifyChannel().getChannelType(), notifyVO.getNotifyChannel().getProvider());
notifyVO.setNotifyChannelProviderEnum(notifyChannelProviderEnum);
return notifyVO;
}
@Override
public AjaxResult smsLoginCaptcha(String phoneNumber) {
String userIdKey = CacheConstants.LOGIN_SMS_CAPTCHA_PHONE + phoneNumber;
String captcha = VerifyCodeUtils.generateVerifyCode(6, "0123456789");
this.sendCaptchaSms(phoneNumber, captcha);
redisCache.setCacheObject(userIdKey, captcha, 5, TimeUnit.MINUTES);
return AjaxResult.success();
}
@Override
public String weComVerifyUrl(String msgSignature, String timestamp, String nonce, String echostr) {
// 因为只用验证一次,下面三个参数就不写在配置文件里了,需要验证的可以把下面的验证参数改为自己公司的,然后部署到服务器验证就行
//token
String token = "pr77kdcA5mzJwNeAwV86UcIS";
// encodingAESKey
String encodingAesKey = "efNILsQxM6wOCsrNPiBeuLOBDgDSnNtOVFBbtf6jwTe";
//企业ID
String corpId = "ww4761023a5d81550f";
// 通过检验msg_signature对请求进行校验若校验成功则原样返回echostr表示接入成功否则接入失败
String result = null;
try {
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAesKey, corpId);
result = wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr);
} catch (AesException e) {
log.error("企业微信验证url错误,error:{}", e.getMessage());
}
return result;
}
@Override
public AjaxResult smsRegisterCaptcha(String phoneNumber) {
String userIdKey = CacheConstants.REGISTER_SMS_CAPTCHA_PHONE + phoneNumber;
Object cacheObject = redisCache.getCacheObject(userIdKey);
if (ObjectUtil.isNotNull(cacheObject)) {
return AjaxResult.error("验证码已发送,请稍后重试!");
}
// String captcha = VerifyCodeUtils.generateVerifyCode(6, "0123456789");
// this.sendCaptchaSms(phoneNumber, captcha);
String captcha = "123456";
redisCache.setCacheObject(userIdKey, captcha, 1, TimeUnit.MINUTES);
return AjaxResult.success();
}
/**
* 校验发送账号格式
* @param sendAccount 发送账号
* @param: notifyChannelEnum 通知枚举
* @return java.lang.String
*/
private String checkSendAccountMsg(String sendAccount, NotifyChannelProviderEnum notifyChannelProviderEnum) {
boolean matches;
switch (Objects.requireNonNull(notifyChannelProviderEnum)) {
case SMS_ALIBABA:
case SMS_TENCENT:
case VOICE_ALIBABA:
case DING_TALK_WORK:
case WECHAT_WECOM_APPLY:
matches = ValidationUtils.isMobile(sendAccount);
if (!matches) {
return "请输入正确的电话号码!";
}
break;
case EMAIL_QQ:
case EMAIL_163:
matches = ValidationUtils.isEmail(sendAccount);
if (!matches) {
return "请输入正确的邮箱地址!";
}
break;
case WECHAT_MINI_PROGRAM:
if (!StringUtils.isNumeric(sendAccount)) {
return "请输入正确的用户id";
}
break;
default:
return "";
}
return "";
}
}

View File

@ -0,0 +1,47 @@
package com.fastbee.notify.core.sms.config;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.enums.NotifyChannelEnum;
import com.fastbee.common.enums.NotifyChannelProviderEnum;
import com.fastbee.notify.domain.NotifyChannel;
import com.fastbee.notify.domain.NotifyTemplate;
import com.fastbee.notify.service.INotifyChannelService;
import com.fastbee.notify.service.INotifyTemplateService;
import org.dromara.sms4j.core.datainterface.SmsReadConfig;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* @author gsb
* @date 2023/12/14 17:10
*/
@Component
public class ReadConfig implements SmsReadConfig {
@Resource
private INotifyChannelService notifyChannelService;
@Resource
private INotifyTemplateService notifyTemplateService;
@Override
public BaseConfig getSupplierConfig(String notifyTemplateId) {
NotifyTemplate notifyTemplate = notifyTemplateService.selectNotifyTemplateById(Long.valueOf(notifyTemplateId));
NotifyChannel notifyChannel = notifyChannelService.selectNotifyChannelById(notifyTemplate.getChannelId());
NotifyChannelProviderEnum notifyChannelProviderEnum = NotifyChannelProviderEnum.getByChannelTypeAndProvider(notifyChannel.getChannelType(), notifyChannel.getProvider());
// 注意因为配置参数是分开渠道和模版配的所以这里需要先转换再copy一下
BaseConfig baseConfig = (BaseConfig) JSONObject.parseObject(notifyChannel.getConfigContent(), notifyChannelProviderEnum.getConfigContentClass());
CopyOptions copyOptions = CopyOptions.create(null, true);
BeanUtil.copyProperties(JSONObject.parseObject(notifyTemplate.getMsgParams(), notifyChannelProviderEnum.getMsgParamsClass()), baseConfig, copyOptions);
return baseConfig;
}
@Override
public List<BaseConfig> getSupplierConfigList() {
return null;
}
}

View File

@ -0,0 +1,68 @@
package com.fastbee.notify.core.sms.service;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.notify.vo.NotifyVO;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 短信发送接口
* @author gsb
* @date 2023/12/15 11:01
*/
public interface ISmsService {
/**
* 根据模版统一发送短信
* @param notifyVO 发送类
* @return com.fastbee.common.core.notify.NotifySendResponse
*/
NotifySendResponse send(NotifyVO notifyVO);
/**
* 单个电话发送短信
* @param phone 电话
* @param message 模板内容
* @return 结果
*/
SmsResponse sendMessage(SmsBlend smsBlend, String phone, String message);
/**
* 根据模板id发送 --多参数
* @param phone 电话
* @param templateId 模板id
* @param messages 内容集合
* @return 结果
*/
SmsResponse sendMessage(SmsBlend smsBlend, String phone, String templateId, LinkedHashMap<String,String> messages);
/**
* 群发短信
* @param phones 电话集合
* @param templateId 模板id
* @param messages 内容集合
* @return 结果
*/
SmsResponse massTexting(SmsBlend smsBlend, List<String> phones, String templateId, LinkedHashMap<String,String> messages);
/**
* 延迟发送
* @param phone 电话
* @param message 模板内容
* @param delayedTime 延迟时间
*/
void delayedMessage(SmsBlend smsBlend,String phone ,String message,Long delayedTime);
/**
* 根据模板延迟发送
* @param phone 电话
* @param messages 模板内容集合
* @param delayedTime 延迟时间
*/
void delayedMessage(SmsBlend smsBlend, String phone ,String templateId, LinkedHashMap<String,String> messages,Long delayedTime);
}

View File

@ -0,0 +1,139 @@
package com.fastbee.notify.core.sms.service.Impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.notify.core.sms.config.ReadConfig;
import com.fastbee.notify.core.sms.service.ISmsService;
import com.fastbee.notify.vo.NotifyVO;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
/**
* @author gsb
* @date 2023/12/15 11:03
*/
@Service
public class SmsServiceImpl implements ISmsService {
@Resource
private ReadConfig config;
@Override
public NotifySendResponse send(NotifyVO notifyVO) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
// 注意因为配置参数是分开渠道和模版配的所以这里需要先转换再copy一下
BaseConfig baseConfig = (BaseConfig) JSONObject.parseObject(notifyVO.getNotifyChannel().getConfigContent(), notifyVO.getNotifyChannelProviderEnum().getConfigContentClass());
CopyOptions copyOptions = CopyOptions.create(null, true);
BeanUtil.copyProperties(JSONObject.parseObject(notifyVO.getNotifyTemplate().getMsgParams(), notifyVO.getNotifyChannelProviderEnum().getMsgParamsClass()), baseConfig, copyOptions);
JSONObject jsonMsgParams = JSONObject.parseObject(notifyVO.getNotifyTemplate().getMsgParams());
String content = jsonMsgParams.get("content").toString();
LinkedHashMap<String, String> map = notifyVO.getMap();
String sendContent = "";
switch (notifyVO.getNotifyChannelProviderEnum()) {
case SMS_ALIBABA:
sendContent = StringUtils.strReplaceVariable("${", "}", content, map);
break;
case SMS_TENCENT:
sendContent = StringUtils.strReplaceVariable("{", "}", content, map);
break;
default:
break;
}
notifySendResponse.setSendContent(sendContent);
SmsBlend smsBlend = this.getSmsInstance(notifyVO.getNotifyTemplate().getId().toString());
List<String> phoneList = StringUtils.str2List(notifyVO.getSendAccount(), ",", true, true);
SmsResponse smsResponse = this.massTexting(smsBlend, phoneList, baseConfig.getTemplateId(), map);
notifySendResponse.setStatus(smsResponse.isSuccess() ? 1 : 0);
notifySendResponse.setResultContent(JSON.toJSONString(smsResponse.getData()));
return notifySendResponse;
}
/**
* 获取短信实例
* @return
*/
private SmsBlend getSmsInstance(String configId){
SmsBlend smsBlend = SmsFactory.getSmsBlend(configId);
if (Objects.isNull(smsBlend)){
//如果没有初始化,则先进行初始化
SmsFactory.createSmsBlend(config, configId);
return SmsFactory.getSmsBlend(configId);
}
return smsBlend;
}
/**
* 单个电话发送短信
*
* @param phone 电话
* @param message 模板内容
* @return 结果
*/
@Override
public SmsResponse sendMessage(SmsBlend smsBlend, String phone, String message) {
return smsBlend.sendMessage(phone, message);
}
/**
* 根据模板id发送 --多参数
*
* @param phone 电话
* @param templateId 模板id
* @param messages 内容集合
* @return 结果
*/
@Override
public SmsResponse sendMessage(SmsBlend smsBlend, String phone, String templateId, LinkedHashMap<String, String> messages) {
return smsBlend.sendMessage(phone, templateId, messages);
}
/**
* 群发短信
*
* @param phones 电话集合
* @param templateId 模板id
* @param messages 内容集合
* @return 结果
*/
@Override
public SmsResponse massTexting(SmsBlend smsBlend, List<String> phones, String templateId, LinkedHashMap<String,String> messages) {
return smsBlend.massTexting(phones, templateId, messages);
}
/**
* 延迟发送
*
* @param phone 电话
* @param message 模板内容
* @param delayedTime 延迟时间
*/
@Override
public void delayedMessage(SmsBlend smsBlend, String phone, String message, Long delayedTime) {
smsBlend.delayedMessage(phone, message, delayedTime);
}
/**
* 根据模板延迟发送
*
* @param phone 电话
* @param messages 模板内容集合
* @param delayedTime 延迟时间
*/
@Override
public void delayedMessage(SmsBlend smsBlend, String phone, String templateId, LinkedHashMap<String, String> messages, Long delayedTime) {
smsBlend.delayedMessage(phone, templateId, messages, delayedTime);
}
}

View File

@ -0,0 +1,26 @@
package com.fastbee.notify.core.vo;
import lombok.Data;
/**
* @author fastb
* @version 1.0
* @description: 通知测试传参
* @date 2023-12-28 15:15
*/
@Data
public class SendParams {
/**
* 模板编号
*/
private Long id;
/**
* 发送账号手机号、邮箱、用户id
*/
private String sendAccount;
/**
* 模板内容变量json字符串
*/
private String variables;
}

View File

@ -0,0 +1,31 @@
package com.fastbee.notify.core.voice.config;
import com.aliyun.dyvmsapi20170525.Client;
import com.aliyun.teaopenapi.models.Config;
/**
* @author fastb
* @version 1.0
* @description: 语音配置类
* @date 2024-01-11 16:06
*/
public class VoiceConfig {
/**
* 使用AK&SK初始化账号Client
* @param accessKeyId
* @param accessKeySecret
* @return Client
* @throws Exception
*/
public static Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
Config config = new Config()
// 必填,您的 AccessKey ID
.setAccessKeyId(accessKeyId)
// 必填,您的 AccessKey Secret
.setAccessKeySecret(accessKeySecret);
// Endpoint 请参考 https://api.aliyun.com/product/Dyvmsapi
config.endpoint = "dyvmsapi.aliyuncs.com";
return new Client(config);
}
}

View File

@ -0,0 +1,20 @@
package com.fastbee.notify.core.voice.service;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.notify.vo.NotifyVO;
/**
* @description: 语音通知服务类
* @author fastb
* @date 2023-12-15 11:05
* @version 1.0
*/
public interface VoiceService {
/**
* 语音发送
* @param notifyVO 发送参数
* @return
*/
NotifySendResponse send(NotifyVO notifyVO);
}

View File

@ -0,0 +1,212 @@
package com.fastbee.notify.core.voice.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.aliyun.dyvmsapi20170525.Client;
import com.aliyun.dyvmsapi20170525.models.SingleCallByTtsRequest;
import com.aliyun.dyvmsapi20170525.models.SingleCallByTtsResponse;
import com.aliyun.teautil.models.RuntimeOptions;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.common.core.notify.config.VoiceConfigParams;
import com.fastbee.common.core.notify.msg.VoiceMsgParams;
import com.fastbee.common.enums.NotifyChannelProviderEnum;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.notify.core.voice.config.VoiceConfig;
import com.fastbee.notify.core.voice.service.VoiceService;
import com.fastbee.notify.vo.NotifyVO;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.vms.v20200902.VmsClient;
import com.tencentcloudapi.vms.v20200902.models.SendTtsVoiceRequest;
import com.tencentcloudapi.vms.v20200902.models.SendTtsVoiceResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
/**
* @author fastb
* @version 1.0
* @description: 语音通知发送业务类
* @date 2023-12-26 9:54
*/
@Slf4j
@Service
public class VoiceServiceImpl implements VoiceService {
@Override
public NotifySendResponse send(NotifyVO notifyVO) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
VoiceConfigParams configParams = JSONObject.parseObject(notifyVO.getNotifyChannel().getConfigContent(), VoiceConfigParams.class);
VoiceMsgParams msgParams = JSONObject.parseObject(notifyVO.getNotifyTemplate().getMsgParams(), VoiceMsgParams.class);
LinkedHashMap<String, String> map = notifyVO.getMap();
NotifyChannelProviderEnum notifyChannelProviderEnum = notifyVO.getNotifyChannelProviderEnum();
if (StringUtils.isEmpty(notifyVO.getSendAccount())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("发送电话不能为空,请先配置!");
return notifySendResponse;
}
String phoneStr = notifyVO.getSendAccount();
List<String> phoneList = StringUtils.str2List(phoneStr, ",", true, true);
String sendContent = "";
List<String> resultList = new ArrayList<>();
for (String phone : phoneList) {
switch (notifyChannelProviderEnum) {
case VOICE_ALIBABA:
sendContent = StringUtils.strReplaceVariable("${", "}", msgParams.getContent(), map);
try {
notifySendResponse = singleCallByTts(configParams, msgParams, map, phone);
} catch (Exception e) {
log.error("阿里云语音通知异常phone:{}, exception:{}", phone, e.toString());
}
break;
case VOICE_TENCENT:
sendContent = StringUtils.strReplaceVariable("{", "}", msgParams.getContent(), map);
notifySendResponse = this.sendTtsVoice(configParams, msgParams, map, phone);
break;
default:
break;
}
resultList.add("phone:[" + phone + "]status:[" + notifySendResponse.getStatus() + "]resultContent:[" + notifySendResponse.getResultContent() + "]");
}
notifySendResponse.setSendContent(sendContent);
notifySendResponse.setResultContent(StringUtils.join(resultList, "; "));
return notifySendResponse;
}
/**
* @description: 阿里云文本转语音通知
* @param: configParams 渠道服务商配置参数
* @param: msgParams 模版通知内容
* @param: map 变量参数
* @param: phone 通知电话
* @return: com.aliyun.dyvmsapi20170525.models.SingleCallByTtsResponse
*/
public NotifySendResponse singleCallByTts(VoiceConfigParams configParams, VoiceMsgParams msgParams, LinkedHashMap<String, String> map, String phone) throws Exception {
NotifySendResponse notifySendResponse = new NotifySendResponse();
try {
Client client = VoiceConfig.createClient(configParams.getAccessKeyId(), configParams.getAccessKeySecret());
SingleCallByTtsRequest singleCallByTtsRequest = new SingleCallByTtsRequest()
.setTtsCode(msgParams.getTemplateId())
.setCalledNumber(phone)
.setTtsParam(JSON.toJSONString(map))
.setPlayTimes(StringUtils.isNotEmpty(msgParams.getPlayTimes()) ? Integer.parseInt(msgParams.getPlayTimes()) : 1)
.setVolume(StringUtils.isNotEmpty(msgParams.getVolume()) ? Integer.parseInt(msgParams.getVolume()) : 50)
.setSpeed(StringUtils.isNotEmpty(msgParams.getSpeed()) ? Integer.parseInt(msgParams.getSpeed()) : 0);
RuntimeOptions runtimeOptions = new RuntimeOptions();
SingleCallByTtsResponse singleCallByTtsResponse = client.singleCallByTtsWithOptions(singleCallByTtsRequest, runtimeOptions);
notifySendResponse.setStatus("OK".equals(singleCallByTtsResponse.getBody().getCode()) ? 1 : 0);
notifySendResponse.setResultContent(JSON.toJSONString(singleCallByTtsResponse.getBody()));
} catch (Exception e) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent(e.toString());
}
return notifySendResponse;
}
public NotifySendResponse sendTtsVoice(VoiceConfigParams configParams, VoiceMsgParams msgParams, LinkedHashMap<String, String> map, String phone) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
try {
/* 必要步骤:
* 实例化一个认证对象入参需要传入腾讯云账户密钥对secretIdsecretKey。
* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
* 您也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
* 以免泄露密钥对危及您的财产安全。
* CAM密匙查询: https://console.cloud.tencent.com/cam/capi*/
Credential cred = new Credential(configParams.getAccessKeyId(), configParams.getAccessKeySecret());
// 实例化一个http选项可选没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
// 设置代理
// httpProfile.setProxyHost("host");
// httpProfile.setProxyPort(port);
// SDK默认使用POST方法。
// 如果您一定要使用GET方法可以在这里设置。GET方法无法处理一些较大的请求
httpProfile.setReqMethod("POST");
/* SDK有默认的超时时间非必要请不要进行调整
* 如有需要请在代码中查阅以获取最新的默认值 */
httpProfile.setConnTimeout(60);
/* SDK会自动指定域名。通常是不需要特地指定域名的但是如果您访问的是金融区的服务
* 则必须手动指定域名例如vms的上海金融区域名 vms.ap-shanghai-fsi.tencentcloudapi.com */
httpProfile.setEndpoint("vms.tencentcloudapi.com");
/* 非必要步骤:
* 实例化一个客户端配置对象,可以指定超时时间等配置 */
ClientProfile clientProfile = new ClientProfile();
/* SDK默认用TC3-HMAC-SHA256进行签名
* 非必要请不要修改这个字段 */
clientProfile.setSignMethod("TC3-HMAC-SHA256");
clientProfile.setHttpProfile(httpProfile);
/* 实例化要请求产品(以vms为例)的client对象
* 第二个参数是地域信息可以直接填写字符串ap-guangzhou或者引用预设的常量 */
VmsClient client = new VmsClient(cred, "ap-guangzhou", clientProfile);
/* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
* 您可以直接查询SDK源码确定接口有哪些属性可以设置
* 属性可能是基本类型,也可能引用了另一个数据结构
* 推荐使用IDE进行开发可以方便的跳转查阅各个接口和数据结构的文档说明 */
SendTtsVoiceRequest req = new SendTtsVoiceRequest();
/* 填充请求参数,这里request对象的成员变量即对应接口的入参
* 您可以通过官网接口文档或跳转到request对象的定义处查看请求参数的定义
* 基本类型的设置:
* 帮助链接:
* 语音消息控制台https://console.cloud.tencent.com/vms
* vms helperhttps://cloud.tencent.com/document/product/1128/37720 */
// 模板 ID必须填写在控制台审核通过的模板 ID可登录 [语音消息控制台] 查看模板 ID
String templateId = msgParams.getTemplateId();
req.setTemplateId(templateId);
// 模板参数,若模板没有参数,请提供为空数组
String[] templateParamSet = map.values().toArray(new String[0]);;
req.setTemplateParamSet(templateParamSet);
/* 被叫手机号码,采用 e.164 标准,格式为+[国家或地区码][用户号码]
* 例如:+8613711112222其中前面有一个+号86为国家码13711112222为手机号 */
String calledNumber = "+86" + phone;
req.setCalledNumber(calledNumber);
// 在 [语音控制台] 添加应用后生成的实际SdkAppid示例如1400006666
String voiceSdkAppid = msgParams.getSdkAppId();
req.setVoiceSdkAppid(voiceSdkAppid);
// 播放次数可选最多3次默认2次
Long playTimes = 2L;
req.setPlayTimes(playTimes);
// 用户的 session 内容,腾讯 server 回包中会原样返回
String sessionContext = phone;
req.setSessionContext(sessionContext);
/* 通过 client 对象调用 SendTtsVoice 方法发起请求。注意请求方法名与请求对象是对应的
* 返回的 res 是一个 SendTtsVoiceResponse 类的实例,与请求对象对应 */
SendTtsVoiceResponse response = client.SendTtsVoice(req);
notifySendResponse.setStatus(1);
notifySendResponse.setResultContent(JSON.toJSONString(response));
} catch (TencentCloudSDKException e) {
// log.error("腾讯云语音通知异常phone:{}, exception:{}", phone, e.toString());
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent(e.toString());
}
return notifySendResponse;
}
}

View File

@ -0,0 +1,41 @@
package com.fastbee.notify.core.wechat.service;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.notify.core.wechat.vo.WeChatMiniPushVO;
import com.fastbee.notify.core.wechat.vo.WxMssVo;
import com.fastbee.notify.domain.NotifyChannel;
import com.fastbee.notify.domain.NotifyTemplate;
import com.fastbee.notify.vo.NotifyVO;
/**
* @description: 微信通知推送业务类
* @author fastb
* @date 2023-12-29 16:39
* @version 1.0
*/
public interface WeChatPushService {
/**
* 统一发送接口
* @param notifyVO 发送参数
* @return
*/
NotifySendResponse send(NotifyVO notifyVO);
/**
* @description: 推送消息给指定的用户 --微信小程序服务号推送
* @param: wxMssVo
* @param: url
* @return: java.lang.String
*/
NotifySendResponse weChatPostPush(String json, String url);
/**
* @description: 生成微信小程序基本推送参数
* @param: notifyTemplateId
* @return: com.fastbee.notify.core.wechat.vo.WeChatMiniPushVO
*/
WeChatMiniPushVO createWeChatMiniPushVO(NotifyChannel notifyChannel, NotifyTemplate notifyTemplate, Long userId);
}

View File

@ -0,0 +1,399 @@
package com.fastbee.notify.core.wechat.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.notify.NotifySendResponse;
import com.fastbee.common.core.notify.config.WeChatConfigParams;
import com.fastbee.common.core.notify.msg.WeComMsgParams;
import com.fastbee.common.core.notify.msg.WechatMsgParams;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.enums.NotifyChannelProviderEnum;
import com.fastbee.common.enums.SocialPlatformType;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.http.HttpUtils;
import com.fastbee.common.utils.wechat.WechatUtils;
import com.fastbee.common.wechat.WeChatAppResult;
import com.fastbee.iot.domain.SocialUser;
import com.fastbee.iot.service.ISocialUserService;
import com.fastbee.notify.core.wechat.service.WeChatPushService;
import com.fastbee.notify.core.wechat.vo.TemplateDataVo;
import com.fastbee.notify.core.wechat.vo.WeChatMiniPushVO;
import com.fastbee.notify.core.wechat.vo.WeChatPublicAccountPushVO;
import com.fastbee.notify.core.wechat.vo.WxMssVo;
import com.fastbee.notify.domain.NotifyChannel;
import com.fastbee.notify.domain.NotifyTemplate;
import com.fastbee.notify.vo.NotifyVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author fastb
* @version 1.0
* @description: 微信相关发送服务类
* @date 2023-12-26 17:15
*/
@Slf4j
@Service
public class WeChatPushServiceImpl implements WeChatPushService {
@Resource
private ISocialUserService socialUserService;
private static RestTemplate restTemplate;
@Resource
private RedisCache redisCache;
@Override
public NotifySendResponse send(NotifyVO notifyVO) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
NotifyChannelProviderEnum notifyChannelProviderEnum = notifyVO.getNotifyChannelProviderEnum();
switch (notifyChannelProviderEnum) {
case WECHAT_MINI_PROGRAM:
notifySendResponse = this.weChatMiniSend(notifyVO);
break;
case WECHAT_WECOM_ROBOT:
notifySendResponse = this.weComRobotSend(notifyVO);
break;
case WECHAT_WECOM_APPLY:
notifySendResponse = this.weComApplySend(notifyVO);
break;
case WECHAT_PUBLIC_ACCOUNT:
notifySendResponse = this.weChatPublicAccountSend(notifyVO);
default:
break;
}
return notifySendResponse;
}
/**
* 微信公众号发送
* @param notifyVO 通知参数
* @return com.fastbee.common.core.notify.NotifySendResponse
*/
private NotifySendResponse weChatPublicAccountSend(NotifyVO notifyVO) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
if (StringUtils.isEmpty(notifyVO.getSendAccount())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("发送用户id为空请先配置发送用户id");
return notifySendResponse;
}
LinkedHashMap<String, String> map = notifyVO.getMap();
LinkedHashMap<String,String> mapVariable = new LinkedHashMap<>();
Map<String, Object> sendMap = new HashMap<>(5);
for (Map.Entry<String, String> m : map.entrySet()) {
sendMap.put(m.getKey(), new TemplateDataVo(m.getValue()));
mapVariable.put(m.getKey() + ".DATA", m.getValue());
}
NotifyChannel notifyChannel = notifyVO.getNotifyChannel();
NotifyTemplate notifyTemplate = notifyVO.getNotifyTemplate();
WeChatConfigParams weChatConfigParams = JSONObject.parseObject(notifyChannel.getConfigContent(), WeChatConfigParams.class);
WechatMsgParams wechatMsgParams = JSONObject.parseObject(notifyTemplate.getMsgParams(), WechatMsgParams.class);
// 获取accessToken
if (StringUtils.isEmpty(weChatConfigParams.getAppId()) || StringUtils.isEmpty(weChatConfigParams.getAppSecret())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("通知渠道配置参数为空,请先配置!");
return notifySendResponse;
}
WeChatAppResult weChatAppResult = WechatUtils.getAccessToken(weChatConfigParams.getAppId(), weChatConfigParams.getAppSecret());
if (ObjectUtil.isNull(weChatAppResult) || StringUtils.isEmpty(weChatAppResult.getAccessToken())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("获取AccessToken失败原因" + JSON.toJSONString(weChatAppResult));
return notifySendResponse;
}
String pushUrl = FastBeeConstant.URL.WX_PUBLIC_ACCOUNT_TEMPLATE_SEND_URL_PREFIX + weChatAppResult.getAccessToken();
// 组装发送参数
WeChatPublicAccountPushVO weChatPublicAccountPushVO = new WeChatPublicAccountPushVO();
weChatPublicAccountPushVO.setData(sendMap);
weChatPublicAccountPushVO.setTemplateId(wechatMsgParams.getTemplateId());
if (StringUtils.isNotEmpty(wechatMsgParams.getRedirectUrl())) {
weChatPublicAccountPushVO.setUrl(wechatMsgParams.getRedirectUrl());
}
if (StringUtils.isNotEmpty(wechatMsgParams.getPagePath())) {
WeChatPublicAccountPushVO.MiniProgram miniProgram = new WeChatPublicAccountPushVO.MiniProgram();
miniProgram.setAppId(wechatMsgParams.getAppid());
miniProgram.setPagePath(wechatMsgParams.getPagePath());
weChatPublicAccountPushVO.setMiniProgram(miniProgram);
}
// 获取用户id
List<String> userIdList = StringUtils.str2List(notifyVO.getSendAccount(), ",", true, true);
List<SocialUser> socialUserList = socialUserService.listWechatPublicAccountOpenId(userIdList);
Map<Long, String> userMap = socialUserList.stream().collect(Collectors.toMap(SocialUser::getSysUserId, SocialUser::getOpenId, (o, n) -> n));
List<String> resultContentList = new ArrayList<>();
for (String userId : userIdList) {
String openId = userMap.get(Long.valueOf(userId));
if (StringUtils.isEmpty(openId)) {
resultContentList.add("userId:[" + userId + "]status:[0]resultContent:[该用户未绑定微信,请先绑定后重试!]");
notifySendResponse.setStatus(0);
continue;
}
weChatPublicAccountPushVO.setTouser(openId);
notifySendResponse = this.weChatPostPush(JSON.toJSONString(weChatPublicAccountPushVO), pushUrl);
resultContentList.add("userId:[" + userId + "]status:[" + notifySendResponse.getStatus() +"]resultContent:[" + notifySendResponse.getResultContent() + "]");
}
String content = JSONObject.parseObject(notifyVO.getNotifyTemplate().getMsgParams()).get("content").toString();
String sendContent = StringUtils.strReplaceVariable("{{", "}}", content, mapVariable);
notifySendResponse.setSendContent(sendContent);
notifySendResponse.setResultContent(StringUtils.join(resultContentList, "; "));
return notifySendResponse;
}
/**
* 企业微信应用消息发送
* @param notifyVO 发送vo类
* @return com.fastbee.common.core.notify.NotifySendResponse
*/
private NotifySendResponse weComApplySend(NotifyVO notifyVO) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
if (StringUtils.isEmpty(notifyVO.getSendAccount())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("发送成员账号为空,请先配置!");
return notifySendResponse;
}
WeChatConfigParams weChatConfigParams = JSONObject.parseObject(notifyVO.getNotifyChannel().getConfigContent(), WeChatConfigParams.class);
if (StringUtils.isEmpty(weChatConfigParams.getCorpId()) || StringUtils.isEmpty(weChatConfigParams.getCorpSecret())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("企业微信应用消息渠道配置信息为空,请先配置!");
return notifySendResponse;
}
WeComMsgParams weComMsgParams = JSONObject.parseObject(notifyVO.getNotifyTemplate().getMsgParams(), WeComMsgParams.class);
// 获取accessToken优先从缓存获取不能频繁的获取
String accessToken;
Object accessTokenRedis = redisCache.getCacheObject(FastBeeConstant.REDIS.NOTIFY_WECOM_APPLY_ACCESSTOKEN + weChatConfigParams.getAgentId());
if (Objects.nonNull(accessTokenRedis)) {
accessToken = accessTokenRedis.toString();
} else {
String s = HttpUtils.sendGet(FastBeeConstant.URL.WECOM_GET_ACCESSTOKEN + "?corpid=" + weChatConfigParams.getCorpId() + "&corpsecret=" + weChatConfigParams.getCorpSecret());
JSONObject accessTokenJson = JSONObject.parseObject(s);
if (!"0".equals(accessTokenJson.get("errcode").toString())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent(s);
return notifySendResponse;
}
accessToken = accessTokenJson.get("access_token").toString();
redisCache.setCacheObject(FastBeeConstant.REDIS.NOTIFY_WECOM_APPLY_ACCESSTOKEN + weChatConfigParams.getAgentId(), accessToken, 2, TimeUnit.HOURS);
}
// 通过手机号获取企业微信用户名
// JSONObject phoneReq = new JSONObject();
// phoneReq.put("mobile", notifyVO.getSendAccount());
// String phoneRes = HttpUtils.sendPost("https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?access_token=" + accessToken, phoneReq.toString());
// JSONObject phoneResJson = JSONObject.parseObject(phoneRes);
// if (!"0".equals(phoneResJson.get("errcode").toString())) {
// notifySendResponse.setStatus(0);
// notifySendResponse.setResultContent(phoneRes);
// return notifySendResponse;
// }
// String userid = phoneResJson.get("userid").toString();
// 构建消息内容
String sendContent = StringUtils.strReplaceVariable("${", "}", weComMsgParams.getContent(), notifyVO.getMap());
notifySendResponse.setSendContent(sendContent);
JSONObject msg = this.createWeComMsg(weComMsgParams, sendContent);
// 多个用户用|分隔
List<String> userIdList = StringUtils.str2List(notifyVO.getSendAccount(), ",", true, true);
String userIdStr = String.join("|", userIdList);
msg.put("touser", userIdStr);
msg.put("agentid", weChatConfigParams.getAgentId());
// 发送
String sendUrl = FastBeeConstant.URL.WECOM_APPLY_SEND + accessToken;
String result = HttpUtils.sendPost(sendUrl, msg.toString());
notifySendResponse.setStatus("0".equals(JSONObject.parseObject(result).get("errcode").toString()) ? 1 : 0);
notifySendResponse.setResultContent(result);
return notifySendResponse;
}
/**
* 企业微信群机器人发送
* @param notifyVO 发送配置类
* @return com.fastbee.common.core.notify.NotifySendResponse
*/
private NotifySendResponse weComRobotSend(NotifyVO notifyVO) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
NotifyChannel notifyChannel = notifyVO.getNotifyChannel();
NotifyTemplate notifyTemplate = notifyVO.getNotifyTemplate();
WeChatConfigParams weChatConfigParams = JSONObject.parseObject(notifyChannel.getConfigContent(), WeChatConfigParams.class);
if (StringUtils.isEmpty(weChatConfigParams.getWebHook())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("企业微信群机器人webHook为空请先去通知渠道下配置");
return notifySendResponse;
}
WeComMsgParams weComMsgParams = JSONObject.parseObject(notifyTemplate.getMsgParams(), WeComMsgParams.class);
String sendContent = StringUtils.strReplaceVariable("${", "}", weComMsgParams.getContent(), notifyVO.getMap());
JSONObject sendJson = this.createWeComMsg(weComMsgParams, sendContent);
String s = HttpUtils.sendPost(weChatConfigParams.getWebHook(), sendJson.toString());
notifySendResponse.setSendContent(sendJson.toJSONString());
notifySendResponse.setStatus("0".equals(JSONObject.parseObject(s).get("errcode").toString()) ? 1 : 0);
notifySendResponse.setResultContent(s);
return notifySendResponse;
}
/**
* 构建企业微信发送参数
* @param weComMsgParams 消息配置参数
* @param: sendContent
* @return java.lang.String
*/
private JSONObject createWeComMsg(WeComMsgParams weComMsgParams, String sendContent) {
JSONObject req = new JSONObject();
String msgType = weComMsgParams.getMsgType();
req.put("msgtype", msgType);
switch (msgType) {
case "text":
JSONObject text = new JSONObject();
text.put("content", sendContent);
req.put("text", text);
break;
case "markdown":
JSONObject markdown = new JSONObject();
markdown.put("content", sendContent);
req.put("markdown", markdown);
break;
case "news":
JSONObject articles = new JSONObject();
articles.put("title", weComMsgParams.getTitle());
articles.put("description", sendContent);
articles.put("url", weComMsgParams.getUrl());
articles.put("picurl", weComMsgParams.getPicUrl());
JSONObject news = new JSONObject();
news.put("articles", articles);
req.put("news", news);
break;
default:
break;
}
return req;
}
/**
* 小程序发送
* @param notifyVO 发送vo类
* @return com.fastbee.common.core.notify.NotifySendResponse
*/
private NotifySendResponse weChatMiniSend(NotifyVO notifyVO) {
// 微信小程序
NotifySendResponse notifySendResponse = new NotifySendResponse();
if (StringUtils.isEmpty(notifyVO.getSendAccount())) {
notifySendResponse.setStatus(0);
notifySendResponse.setResultContent("发送用户id为空请先配置发送用户id");
return notifySendResponse;
}
LinkedHashMap<String, String> map = notifyVO.getMap();
LinkedHashMap<String,String> mapVariable = new LinkedHashMap<>();
Map<String, Object> sendMap = new HashMap<>(5);
for (Map.Entry<String, String> m : map.entrySet()) {
sendMap.put(m.getKey(), new TemplateDataVo(m.getValue()));
mapVariable.put(m.getKey() + ".DATA", m.getValue());
}
List<String> userIdList = StringUtils.str2List(notifyVO.getSendAccount(), ",", true, true);
List<String> resultContentList = new ArrayList<>();
for (String userId : userIdList) {
WeChatMiniPushVO weChatMiniPushVO = this.createWeChatMiniPushVO(notifyVO.getNotifyChannel(), notifyVO.getNotifyTemplate(), Long.valueOf(userId));
if (Objects.isNull(weChatMiniPushVO)) {
resultContentList.add("userId:[" + userId + "]status:[0]resultContent:[获取微信小程序推送配置信息失败!]");
notifySendResponse.setStatus(0);
continue;
}
if (StringUtils.isNotEmpty(weChatMiniPushVO.getErrorMsg())) {
resultContentList.add("userId:[" + userId + "]status:[0]resultContent:[" + weChatMiniPushVO.getErrorMsg() + "]");
notifySendResponse.setStatus(0);
continue;
}
WxMssVo wxMssVo = weChatMiniPushVO.getWxMssVo();
wxMssVo.setData(sendMap);
notifySendResponse = this.weChatPostPush(JSON.toJSONString(wxMssVo), weChatMiniPushVO.getUrl());
resultContentList.add("userId:[" + userId + "]status:[" + notifySendResponse.getStatus() +"]resultContent:[" + notifySendResponse.getResultContent() + "]");
}
String content = JSONObject.parseObject(notifyVO.getNotifyTemplate().getMsgParams()).get("content").toString();
String sendContent = StringUtils.strReplaceVariable("{{", "}}", content, mapVariable);
notifySendResponse.setSendContent(sendContent);
notifySendResponse.setResultContent(StringUtils.join(resultContentList, "; "));
return notifySendResponse;
}
/**
* 推送消息给指定的用户 --微信小程序服务号推送
* @param json 推送参数
* @return 推送结果
*/
@Override
public NotifySendResponse weChatPostPush(String json, String url) {
NotifySendResponse notifySendResponse = new NotifySendResponse();
if(restTemplate==null){
restTemplate = new RestTemplate();
}
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);
ResponseEntity<String> responseEntity =
restTemplate.postForEntity(url, httpEntity, String.class);
log.warn("小程序推送结果={}", responseEntity.getBody());
String response = responseEntity.getBody();
notifySendResponse.setStatus("0".equals(JSONObject.parseObject(response).get("errcode").toString()) ? 1 : 0);
notifySendResponse.setResultContent(response);
return notifySendResponse;
}
@Override
public WeChatMiniPushVO createWeChatMiniPushVO(NotifyChannel notifyChannel, NotifyTemplate notifyTemplate, Long userId) {
WeChatMiniPushVO weChatMiniPushVO = new WeChatMiniPushVO();
//获取微信与用户关联信息
SocialUser socialUser = socialUserService.selectByUserIdAndSourceClient(userId, SocialPlatformType.WECHAT_OPEN_MINI_PROGRAM.sourceClient);
if (Objects.isNull(socialUser) || StringUtils.isEmpty(socialUser.getOpenId())) {
weChatMiniPushVO.setErrorMsg("该用户未绑定微信小程序,请先绑定后重试");
return weChatMiniPushVO;
}
//获取openId
String openId = socialUser.getOpenId();
//拼接推送的模版
WxMssVo wxMssVo = new WxMssVo();
//用户openid
wxMssVo.setTouser(openId);
if (notifyTemplate == null) {
weChatMiniPushVO.setErrorMsg("推送模板为空,请先配置微信小程序推送模板");
return weChatMiniPushVO;
}
//获取微信服务号推送的配置参数
if (notifyChannel == null) {
weChatMiniPushVO.setErrorMsg("推送渠道为空,请检查微信小程序推送渠道");
return weChatMiniPushVO;
}
WeChatConfigParams weChatConfigParams = JSONObject.parseObject(notifyChannel.getConfigContent(), WeChatConfigParams.class);
if (StringUtils.isEmpty(weChatConfigParams.getAppId()) || StringUtils.isEmpty(weChatConfigParams.getAppSecret())) {
weChatMiniPushVO.setErrorMsg("微信小程序渠道配置信息为空,请先配置!");
return weChatMiniPushVO;
}
//获取access_token
WeChatAppResult weChatAppResult = WechatUtils.getAccessToken(weChatConfigParams.getAppId(), weChatConfigParams.getAppSecret());
if (weChatAppResult == null || StringUtils.isEmpty(weChatAppResult.getAccessToken())) {
weChatMiniPushVO.setErrorMsg("获取用户调用凭据失败,请重新登录!");
return weChatMiniPushVO;
}
//微信推送URL
String url = FastBeeConstant.URL.WX_MINI_PROGRAM_PUSH_URL_PREFIX + "?access_token=" + weChatAppResult.getAccessToken();
WechatMsgParams msgParams = JSONObject.parseObject(notifyTemplate.getMsgParams(), WechatMsgParams.class);
//模版id
wxMssVo.setTemplateId(msgParams.getTemplateId());
//推送路径
if (StringUtils.isNotEmpty(msgParams.getRedirectUrl())){
wxMssVo.setPage(msgParams.getRedirectUrl());
}
weChatMiniPushVO.setWxMssVo(wxMssVo);
weChatMiniPushVO.setUrl(url);
return weChatMiniPushVO;
}
}

View File

@ -0,0 +1,19 @@
package com.fastbee.notify.core.wechat.vo;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TemplateDataVo {
/*微信文档中要求的格式 "data": { "name01": {"value": "某某"},"thing01": {"value": "广州至北京"
} ,"date01": {"value": "2018-01-01"}
}*/
@JSONField(name = "value")
private String value;
}

View File

@ -0,0 +1,19 @@
package com.fastbee.notify.core.wechat.vo;
import lombok.Data;
/**
* @author fastb
* @version 1.0
* @description: 获取微信小程序服务通知推送类,推送内容变量参数需自己组装
* @date 2023-12-28 16:38
*/
@Data
public class WeChatMiniPushVO {
private WxMssVo wxMssVo;
private String url;
private String errorMsg;
}

View File

@ -0,0 +1,62 @@
package com.fastbee.notify.core.wechat.vo;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.Map;
/**
* @author fastb
* @version 1.0
* @description: 微信公众号推送参数
* @date 2024-03-09 14:10
*/
@Data
public class WeChatPublicAccountPushVO {
/**
* 接收者(用户)的 openid
*/
@JSONField(name = "touser")
private String touser;
/**
* 所需下发的订阅模板id
*/
@JSONField(name = "template_id")
private String templateId;
/**
* 模板跳转链接(海外账号没有跳转能力)
*/
@JSONField(name = "url")
private String url;
/**
* 跳小程序所需数据,不需跳小程序可不用传该数据
*/
@JSONField(name = "miniprogram")
private MiniProgram miniProgram;
/**
* 防重入id。对于同一个openid + client_msg_id, 只发送一条消息,10分钟有效,超过10分钟不保证效果。若无防重入需求可不填
*/
@JSONField(name = "client_msg_id")
private String clientMsgId;
/**
* 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
*/
@JSONField(name = "data")
private Map<String, Object> data;
@Data
public static class MiniProgram {
/**
* 所需跳转到的小程序appid
*/
@JSONField(name = "appid")
private String appId;
/**
* 所需跳转到小程序的具体页面路径,支持带参数,示例index?foo=bar要求该小程序已发布暂不支持小游戏
*/
@JSONField(name = "pagepath")
private String pagePath;
}
}

View File

@ -0,0 +1,50 @@
package com.fastbee.notify.core.wechat.vo;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.Map;
/*
* 小程序推送所需数据
* */
@Data
public class WxMssVo {
/**
* 接收者(用户)的 openid
*/
@JSONField(name = "touser")
private String touser;
/**
* 所需下发的订阅模板id
*/
@JSONField(name = "template_id")
private String templateId;
/**
* 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,示例index?foo=bar。该字段不填则模板无跳转。
*/
@JSONField(name = "page")
private String page = "pages/index/index";
/**
* 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
*/
@JSONField(name = "data")
private Map<String, Object> data;
/**
* 跳转小程序类型developer为开发版trial为体验版formal为正式版默认为正式版
*/
@JSONField(name = "miniprogram_state")
private String miniprogramState;
/**
* 进入小程序查看”的语言类型支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文)默认为zh_CN
*/
@JSONField(name = "lang")
private String lang;
/**
* 默认正式版 和 简体中文
*/
public WxMssVo() {
this.miniprogramState = "formal";
this.lang = "zh_CN";
}
}