Merge remote-tracking branch 'origin/dev-3.0.x-xiaoyan' into dev-3.0.x

# Conflicts:
#	sms4j-provider/src/main/java/org/dromara/sms4j/netease/service/NeteaseSmsImpl.java
This commit is contained in:
wind 2023-09-05 19:15:34 +08:00
commit 8235c34a7d
101 changed files with 2367 additions and 4768 deletions

View File

@ -131,6 +131,7 @@
<groupId>com.jdcloud.sdk</groupId>
<artifactId>sms</artifactId>
<version>${jdcloud.version}</version>
<scope>provided</scope>
</dependency>
<dependency>

View File

@ -2,453 +2,13 @@ package org.dromara.email.api;
import org.dromara.email.comm.entity.MailMessage;
import java.io.InputStream;
import java.lang.String;
import java.util.List;
import java.util.Map;
public interface MailClient {
/**
* sendMail
* <p> 发送纯文本邮件
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param body 邮件正文
* @author :Wind
*/
void sendMail(String mailAddress, String title ,String body);
/**
* sendMail
* <p> 群体发送纯文本邮件
* @param mailAddress 收件人地址,添加多个
* @param title 邮件标题
* @param body 邮件正文
* @author :Wind
*/
void sendMail(List<String> mailAddress ,String title ,String body);
/**
* sendEmail
* <p>发送带有附件的文本邮件
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param body 邮件正文
* @param files 附件可添加多个
* @author :Wind
*/
void sendEmail(String mailAddress, String title, String body,Map<String,String> files);
/**
* sendEmail
* <p>发送带有附件的文本邮件
* @param mailAddress 收件人地址 多个收件人地址请按英文','字符隔开
* @param title 邮件标题
* @param body 邮件正文
* @param zipName 压缩包名称 比如 附件.zip
* @param files 附件可添加多个
* @author :Wind
*/
void sendEmail(String mailAddress, String title, String body, String zipName, Map<String,String> files);
/**
* sendEmail
* <p>群体发送带有附件的文本邮件
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件正文
* @param files 附件可添加多个
* @author :Wind
*/
void sendEmail(List<String> mailAddress, String title, String body, Map<String,String> files);
/**
* sendEmail
* <p>单人发送带有附件的文本邮件,同时可以携带密送人和抄送人
* <p>需要注意的是密送人和抄送人也不能存在于黑名单内否则也会被过滤
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件正文
* @param files 附件可添加多个
* @author :Wind
*/
void sendEmail(String mailAddress, String title, String body,List<String> cc,List<String> bcc,Map<String,String> files);
/**
* sendEmail
* <p>群体发送带有附件的文本邮件,同时可以携带密送人和抄送人
* <p>需要注意的是密送人和抄送人也不能存在于黑名单内否则也会被过滤
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件正文
* @param files 附件可添加多个
* @param bcc 密送人
* @param cc 抄送人
* @author :Wind
*/
void sendEmail(List<String> mailAddress, String title, String body,List<String> cc,List<String> bcc,Map<String,String> files);
/**
* sendEmail
* <p> 发送邮件可以通过对象构造群体发送或者单体发送取决于添加进去的收件人同时可以添加
* 密送人抄送人附件等参数
* @param mailMessage 发送邮件参数对象
* @author :Wind
*/
void sendEmail(MailMessage mailMessage);
/**
* sendHtml
* <p> 读取模板发送html邮件无正文
* <p> 将默认读取resources/template下的html文件第三个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param htmlName 邮件正文
* @param parameter key为模板的变量名称 无需携带大括号 value为模板变量所对应的值
* @author :Wind
*/
void sendHtml(String mailAddress, String title , String htmlName, Map<String,String> parameter);
/**
* sendHtml
* <p> 读取模板发送html邮件无正文
* <p> 将默认读取resources/template下的html文件第三个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param htmlName 邮件正文
* @param parameter key为模板的变量名称 无需携带大括号 value为模板变量所对应的值
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title , String htmlName, Map<String,String> parameter);
/**
* sendHtml
* <p> 读取模板发送html邮件,无正文
* <p> 将默认读取resources/template下的html文件第三个参数为html的名称需携带尾缀
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param htmlName 邮件模板名称
* @param parameter 实体
* @author :Wind
*/
void sendHtml(String mailAddress, String title , String htmlName, Parameter parameter);
/**
* sendHtml
* <p> 读取模板发送html邮件,无正文
* <p> 将默认读取resources/template下的html文件第三个参数为html的名称需携带尾缀
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param htmlName 邮件模板名称
* @param parameter 实体
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title , String htmlName, Parameter parameter);
/**
* sendHtml
* <p> 读取模板发送html邮件,无正文带附件
* <p> 将默认读取resources/template下的html文件第三个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param htmlName 邮件模板名称
* @param parameter 实体
* @param files 附件可添加多个
* @author :Wind
*/
void sendHtml(String mailAddress, String title , String htmlName,Map<String,String> parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,无正文带附件
* <p> 将默认读取resources/template下的html文件第三个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param htmlName 邮件模板名称
* @param parameter 实体
* @param files 附件可添加多个
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title , String htmlName,Map<String,String> parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,无正文带附件
* <p> 将默认读取resources/template下的html文件第三个参数为html的名称需携带尾缀
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param htmlName 邮件模板名称
* @param parameter 实体
* @param files 附件可添加多个
* @author :Wind
*/
void sendHtml(String mailAddress, String title , String htmlName,Parameter parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,无正文带附件
* <p> 将默认读取resources/template下的html文件第三个参数为html的名称需携带尾缀
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param htmlName 邮件模板名称
* @param parameter 实体
* @param files 附件可添加多个
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title , String htmlName,Parameter parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param body 邮件文本正文
* @param htmlName 邮件正文
* @param parameter key为模板的变量名称 无需携带大括号 value为模板变量所对应的值
* @author :Wind
*/
void sendHtml(String mailAddress, String title ,String body, String htmlName, Map<String,String> parameter);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param htmlName 邮件正文
* @param parameter key为模板的变量名称 无需携带大括号 value为模板变量所对应的值
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title ,String body, String htmlName, Map<String,String> parameter);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param body 邮件文本正文
* @param htmlName 邮件正文
* @param parameter 实体
* @author :Wind
*/
void sendHtml(String mailAddress, String title ,String body, String htmlName, Parameter parameter);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param htmlName 邮件正文
* @param parameter 实体
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title ,String body, String htmlName, Parameter parameter);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param body 邮件文本正文
* @param htmlName 邮件正文
* @param parameter key为模板的变量名称 无需携带大括号 value为模板变量所对应的值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @author :Wind
*/
void sendHtml(String mailAddress, String title ,String body, String htmlName, Map<String,String> parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址 多个收件人地址请按英文','字符隔开
* @param title 邮件标题
* @param body 邮件文本正文 可为空
* @param htmlName 邮件正文
* @param parameter key为模板的变量名称 无需携带大括号 value为模板变量所对应的值
* @param zipName 压缩包名称 比如 附件.zip
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @author :bleachtred
*/
void sendHtml(String mailAddress, String title, String body, String htmlName, Map<String,String> parameter, String zipName, Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址 多个收件人地址请按英文','字符隔开
* @param title 邮件标题
* @param body 邮件文本正文 可为空
* @param htmlName 邮件正文
* @param parameter 字段名称为变量名称字段值为变量值
* @param zipName 压缩包名称 比如 附件.zip
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @author :Wind
*/
void sendHtml(String mailAddress, String title, String body, String htmlName, Parameter parameter, String zipName, Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param htmlName 邮件正文
* @param parameter key为模板的变量名称 无需携带大括号 value为模板变量所对应的值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title ,String body, String htmlName, Map<String,String> parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址
* @param title 邮件标题
* @param body 邮件文本正文
* @param htmlName 邮件正文
* @param parameter 字段名称为变量名称字段值为变量值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @author :Wind
*/
void sendHtml(String mailAddress, String title ,String body, String htmlName, Parameter parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 将默认读取resources/template下的html文件第四个参数为html的名称需携带尾缀
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param htmlName 邮件正文
* @param parameter 字段名称为变量名称字段值为变量值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title ,String body, String htmlName, Parameter parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 从用户给定的输入流获取html模板文件
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param html html模板的输入流这个流可以来自任何来源例如网络请求或是本地文件或者对象存储等
* @param parameter key为模板的变量名称 无需携带大括号 value为模板变量所对应的值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title , String body, InputStream html, Map<String, String> parameter, Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 从用户给定的输入流获取html模板文件
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param html html模板的输入流,这个流可以来自任何来源例如网络请求或是本地文件或者对象存储等
* @param parameter 字段名称为变量名称字段值为变量值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title ,String body, InputStream html, Parameter parameter,Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 从用户给定的输入流获取html模板文件
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param html html模板的输入流,这个流可以来自任何来源例如网络请求或是本地文件或者对象存储等
* @param parameter key为变量名称value为变量值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @param cc 抄送人可添加多个
* @param bcc 密送人可添加多个
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title,String body,InputStream html,List<String> cc,List<String> bcc,Map<String, String> parameter, Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 从用户给定的输入流获取html模板文件
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param html html模板的输入流,这个流可以来自任何来源例如网络请求或是本地文件或者对象存储等
* @param parameter 字段名称为变量名称字段值为变量值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @param cc 抄送人可添加多个
* @param bcc 密送人可添加多个
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title,String body,InputStream html,List<String> cc,List<String> bcc,Parameter parameter, Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 从用户给定的输入流获取html模板文件
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param html html模板的名称
* @param parameter 字段名称为变量名称字段值为变量值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @param cc 抄送人可添加多个
* @param bcc 密送人可添加多个
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title,String body,String html,List<String> cc,List<String> bcc,Map<String, String> parameter, Map<String,String> files);
/**
* sendHtml
* <p> 读取模板发送html邮件,并携带正文和附件
* <p> 从用户给定的输入流获取html模板文件
* <p> 用户可以自己编写一个实体类并实现Parameter接口编写get和set方法这样一来字段的名称则为模板变量名称对象的值则为模板变量的值
* @param mailAddress 收件人地址添加多个
* @param title 邮件标题
* @param body 邮件文本正文
* @param html html模板的名称
* @param parameter 字段名称为变量名称字段值为变量值
* @param files 附件可添加多个 key 为文件名value为文件的路径
* @param cc 抄送人可添加多个
* @param bcc 密送人可添加多个
* @author :Wind
*/
void sendHtml(List<String> mailAddress, String title,String body,String html,List<String> cc,List<String> bcc,Parameter parameter, Map<String,String> files);
/**
* sendHtml
* <p> 发送邮件可以通过对象构造群体发送或者单体发送取决于添加进去的收件人同时可以添加
* 发送邮件可以通过对象构造群体发送或者单体发送取决于添加进去的收件人同时可以添加
* 密送人抄送人附件等参数
* @param mailMessage 发送邮件参数对象
* @author :Wind
*/
void sendHtml(MailMessage mailMessage);
void send(MailMessage mailMessage);
}

View File

@ -1,5 +1,8 @@
package org.dromara.email.comm.entity;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import lombok.Builder;
import lombok.Getter;
import java.io.InputStream;
@ -9,136 +12,62 @@ import java.util.List;
import java.util.Map;
@Getter
@Builder
public class MailMessage {
/** 收件人地址*/
private List<String> mailAddress;
/**
* 收件人地址
*/
private String mailAddress;
/** 邮件主题*/
/**
* 收件人地址多人(优先使用)
*/
private List<String> mailAddressList;
/**
* 邮件主题
*/
private String title;
/** 文字正文*/
/**
* 文字正文
*/
private String body;
/** html模板文件路径resources目录下的路径*/
/**
* html模板文件路径resources目录下的路径
*/
private String htmlPath;
/** html模板文件的输入流可来自任意可读取位置*/
/**
* html模板文件的输入流可来自任意可读取位置
*/
private InputStream htmlInputStream;
/** html 模板参数*/
private Map<String,String> htmlValues;
/**
* html 模板参数
*/
private Map<String, String> htmlValues;
/** 抄送人*/
/**
* zip文件名称
*/
private String zipName;
/**
* 抄送人
*/
private List<String> cc;
/** 密送人*/
/**
* 密送人
*/
private List<String> bcc;
/** 附件*/
private Map<String,String> files;
/**
* 附件
*/
private Map<String, String> files;
public static MailsBuilder Builder(){
return new MailsBuilder();
}
static class MailsBuilder{
private final MailMessage mailMessage = new MailMessage();
public MailsBuilder() {
}
public MailMessage build(){
return mailMessage;
}
public MailsBuilder setMailAddress(List<String> mailAddress) {
mailMessage.mailAddress = mailAddress;
return this;
}
public MailsBuilder setMailAddress(String mailAddress){
if ( mailMessage.mailAddress == null){
mailMessage.mailAddress = new ArrayList<>();
}
mailMessage.mailAddress.add(mailAddress);
return this;
}
public MailsBuilder setTitle(String title){
mailMessage.title = title;
return this;
}
public MailsBuilder setBody(String body){
mailMessage.body = body;
return this;
}
public MailsBuilder setCc(List<String> cc){
mailMessage.cc = cc;
return this;
}
public MailsBuilder setCc(String cc){
if (mailMessage.cc == null){
mailMessage.cc = new ArrayList<>();
}
mailMessage.cc.add(cc);
return this;
}
public MailsBuilder setBcc(List<String> bcc){
mailMessage.bcc = bcc;
return this;
}
public MailsBuilder setBcc(String bcc){
if (mailMessage.bcc == null){
mailMessage.bcc = new ArrayList<>();
}
mailMessage.bcc.add(bcc);
return this;
}
public MailsBuilder setFiles(Map<String, String> files){
if (mailMessage.files == null){
mailMessage.files = new HashMap<>();
}
mailMessage.files.putAll(files);
return this;
}
public MailsBuilder setFiles(String fileName,String filePath){
if (mailMessage.files == null){
mailMessage.files = new HashMap<>();
}
mailMessage.files.put(fileName,filePath);
return this;
}
public MailsBuilder setHtmlPath(String htmlPath){
mailMessage.htmlPath = htmlPath;
return this;
}
public MailsBuilder setHtmlInputStream(InputStream htmlInputStream){
mailMessage.htmlInputStream = htmlInputStream;
return this;
}
public MailsBuilder setHtmlValues(String key, String value){
if (mailMessage.files == null){
mailMessage.files = new HashMap<>();
}
mailMessage.htmlValues.put(key,value);
return this;
}
public MailsBuilder setHtmlValues(Map<String,String> htmlValues){
if (mailMessage.files == null){
mailMessage.files = new HashMap<>();
}
mailMessage.htmlValues.putAll(htmlValues);
return this;
}
}
}

View File

@ -90,7 +90,8 @@ public final class HtmlUtil {
for (Map.Entry<String, String> s : parameter.entrySet()) {
String piece = piece(s.getKey());
if (data.get(i).contains(piece)){
data.set(i,s.getValue());
String replace = data.get(i).replace(piece, s.getValue());
data.set(i,replace);
}
}
}

View File

@ -0,0 +1,6 @@
/**
* <p> 邮件插件通用模块
* @author :Wind
* 2023/7/27 10:59
**/
package org.dromara.email.comm;

View File

@ -33,6 +33,7 @@ public class MailFactory{
}
}
/**
* createMailClient
* <p>从工厂获取一个邮件发送实例,该实例发送短信将依照黑名单中的数据进行过滤

View File

@ -0,0 +1,6 @@
/**
* <p> 邮件插件核心模块
* @author :Wind
* 2023/7/27 10:58
**/
package org.dromara.email.core;

View File

@ -72,10 +72,10 @@ public class MailBuild {
}
public static MailClient build(MailSmtpConfig config) throws MessagingException {
return MailService.NewMailService(new MailBuild(config));
return MailService.instance(new MailBuild(config));
}
public static MailClient build(MailSmtpConfig config,Blacklist blacklist)throws MessagingException {
return MailService.NewMailService(new MailBuild(config,blacklist));
return MailService.instance(new MailBuild(config,blacklist));
}
/**

View File

@ -1,5 +1,6 @@
package org.dromara.email.core.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
@ -7,13 +8,11 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import org.dromara.email.api.MailClient;
import org.dromara.email.api.Parameter;
import org.dromara.email.comm.constants.FileConstants;
import org.dromara.email.comm.entity.MailMessage;
import org.dromara.email.comm.errors.MailException;
import org.dromara.email.comm.utils.HtmlUtil;
import org.dromara.email.comm.utils.ZipUtils;
import org.dromara.email.core.ReflectUtil;
import javax.activation.DataHandler;
import javax.activation.DataSource;
@ -28,8 +27,6 @@ import javax.mail.util.ByteArrayDataSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -44,211 +41,37 @@ public class MailService implements MailClient {
this.mailBuild = mailBuild;
}
public static MailClient NewMailService(MailBuild mailBuild) {
public static MailClient instance(MailBuild mailBuild) {
return new MailService(mailBuild);
}
@Override
public void sendMail(String mailAddress, String title, String body) {
sendEmail(mailAddress, title, body, null);
}
@Override
public void sendMail(List<String> mailAddress, String title, String body) {
sendEmail(mailAddress, title, body, null);
}
@Override
public void sendEmail(String mailAddress, String title, String body, Map<String, String> files) {
sendEmail(Collections.singletonList(mailAddress), title, body, files);
}
@Override
public void sendEmail(List<String> mailAddress, String title, String body, Map<String, String> files) {
sendEmail(mailAddress, title, body, null, null, files);
}
@Override
public void sendEmail(String mailAddress, String title, String body, List<String> cc, List<String> bcc, Map<String, String> files) {
sendEmail(Collections.singletonList(mailAddress), title, body, cc, bcc, files);
}
@Override
public void sendEmail(MailMessage mailMessage) {
sendEmail(mailMessage.getMailAddress(),
public void send(MailMessage mailMessage) {
List<String> html;
if (mailMessage.getHtmlInputStream() == null) {
html = HtmlUtil.readHtml(mailMessage.getHtmlPath());
} else {
html = HtmlUtil.readHtml(mailMessage.getHtmlInputStream());
}
List<String> address;
if (CollUtil.isEmpty(mailMessage.getMailAddressList())) {
if (StrUtil.isBlank(mailMessage.getMailAddress())) {
throw new MailException("收件人地址不能为空!");
}
address = CollUtil.newArrayList(mailMessage.getMailAddress());
} else {
address = mailMessage.getMailAddressList();
}
send(address,
mailMessage.getTitle(),
mailMessage.getBody(),
html,
mailMessage.getHtmlValues(),
mailMessage.getZipName(),
mailMessage.getFiles(),
mailMessage.getCc(),
mailMessage.getBcc(),
mailMessage.getFiles());
}
@Override
public void sendHtml(String mailAddress, String title, String htmlName, Map<String, String> parameter) {
sendHtml(Collections.singletonList(mailAddress), title, htmlName, parameter);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String htmlName, Map<String, String> parameter) {
sendHtml(mailAddress, title, htmlName, parameter, null);
}
@Override
public void sendHtml(String mailAddress, String title, String htmlName, Parameter parameter) {
sendHtml(Collections.singletonList(mailAddress), title, htmlName, parameter);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String htmlName, Parameter parameter) {
sendHtml(mailAddress, title, htmlName, ReflectUtil.getValues(parameter));
}
@Override
public void sendHtml(String mailAddress, String title, String htmlName, Map<String, String> parameter, Map<String, String> files) {
sendHtml(mailAddress, title, "", htmlName, parameter, files);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String htmlName, Map<String, String> parameter, Map<String, String> files) {
sendHtml(mailAddress, title, "", htmlName, parameter, files);
}
@Override
public void sendHtml(String mailAddress, String title, String htmlName, Parameter parameter, Map<String, String> files) {
sendHtml(mailAddress, title, htmlName, ReflectUtil.getValues(parameter), files);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String htmlName, Parameter parameter, Map<String, String> files) {
sendHtml(mailAddress, title, htmlName, ReflectUtil.getValues(parameter), files);
}
@Override
public void sendHtml(String mailAddress, String title, String body, String htmlName, Map<String, String> parameter) {
sendHtml(mailAddress, title, body, htmlName, parameter, null);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String body, String htmlName, Map<String, String> parameter) {
sendHtml(mailAddress, title, body, htmlName, parameter, null);
}
@Override
public void sendHtml(String mailAddress, String title, String body, String htmlName, Parameter parameter) {
sendHtml(Collections.singletonList(mailAddress), title, body, htmlName, parameter);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String body, String htmlName, Parameter parameter) {
sendHtml(mailAddress, title, body, htmlName, parameter, null);
}
@Override
public void sendHtml(String mailAddress, String title, String body, String htmlName, Map<String, String> parameter, Map<String, String> files) {
sendHtml(Collections.singletonList(mailAddress), title, body, htmlName, parameter, files);
}
@Override
public void sendHtml(String mailAddress, String title, String body, String htmlName, Parameter parameter, String zipName, Map<String, String> files) {
sendHtml(mailAddress, title, body, htmlName, ReflectUtil.getValues(parameter), zipName, files);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String body, String htmlName, Map<String, String> parameter, Map<String, String> files) {
send(mailAddress, title, body, HtmlUtil.readHtml(htmlName), parameter, files, null, null);
}
@Override
public void sendHtml(String mailAddress, String title, String body, String htmlName, Parameter parameter, Map<String, String> files) {
sendHtml(Collections.singletonList(mailAddress), title, body, htmlName, parameter, files);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String body, String htmlName, Parameter parameter, Map<String, String> files) {
sendHtml(mailAddress, title, body, htmlName, ReflectUtil.getValues(parameter), files);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String body, InputStream html, Map<String, String> parameter, Map<String, String> files) {
send(mailAddress, title, body, HtmlUtil.readHtml(html), parameter, files, null, null);
}
@Override
public void sendHtml(List<String> mailAddress, String title, String body, InputStream html, Parameter parameter, Map<String, String> files) {
sendHtml(mailAddress, title, body, html, ReflectUtil.getValues(parameter), files);
}
@Override
public void sendHtml(List<String> mailAddress,
String title,
String body,
InputStream html,
List<String> cc,
List<String> bcc,
Map<String, String> parameter,
Map<String, String> files) {
send(mailAddress, title, body, HtmlUtil.readHtml(html), parameter, files, cc, bcc);
}
@Override
public void sendHtml(List<String> mailAddress,
String title,
String body,
InputStream html,
List<String> cc,
List<String> bcc,
Parameter parameter,
Map<String, String> files) {
send(mailAddress, title, body, HtmlUtil.readHtml(html), ReflectUtil.getValues(parameter), files, cc, bcc);
}
@Override
public void sendHtml(List<String> mailAddress,
String title,
String body,
String html,
List<String> cc,
List<String> bcc,
Map<String, String> parameter,
Map<String, String> files) {
send(mailAddress, title, body, HtmlUtil.readHtml(html), parameter, files, cc, bcc);
}
@Override
public void sendHtml(List<String> mailAddress,
String title,
String body,
String html,
List<String> cc,
List<String> bcc,
Parameter parameter,
Map<String, String> files) {
send(mailAddress, title, body, HtmlUtil.readHtml(html), ReflectUtil.getValues(parameter), files, cc, bcc);
}
@Override
public void sendHtml(MailMessage mailMessage) {
if (mailMessage.getHtmlInputStream() == null) {
sendHtml(mailMessage.getMailAddress(),
mailMessage.getTitle(),
mailMessage.getBody(),
mailMessage.getHtmlPath(),
mailMessage.getCc(),
mailMessage.getBcc(),
mailMessage.getHtmlValues(),
mailMessage.getFiles()
);
} else {
sendHtml(mailMessage.getMailAddress(),
mailMessage.getTitle(),
mailMessage.getBody(),
mailMessage.getHtmlInputStream(),
mailMessage.getCc(),
mailMessage.getBcc(),
mailMessage.getHtmlValues(),
mailMessage.getFiles()
);
}
mailMessage.getBcc()
);
}
private void forFiles(Multipart multipart, Map<String, String> files) throws MessagingException {
@ -261,7 +84,7 @@ public class MailService implements MailClient {
if (v.startsWith("http")) {
byte[] bytes = HttpUtil.downloadBytes(v);
source = new ByteArrayDataSource(bytes, FileConstants.IO_FILE_TYPE);
}else {
} else {
source = new FileDataSource(v);
}
messageBodyPart.setDataHandler(new DataHandler(source));
@ -287,72 +110,18 @@ public class MailService implements MailClient {
String body,
List<String> html,
Map<String, String> parameter,
String zipName,
Map<String, String> files,
List<String> cc,
List<String> bcc) {
try {
Message message = messageBuild(mailAddress, title, body, html, parameter, files, cc, bcc);
Message message = messageBuild(mailAddress, title, body, html, parameter, zipName, cc, bcc, files);
Transport.send(message);
logger.info("邮件发送成功!^_^");
} catch (MessagingException e) {
} catch (MessagingException | IOException e) {
// 防止 maxRetries 数值小于0带来的其他问题
if (mailBuild.getMaxRetries() > 0){
ReSendList(mailAddress,
title,
body,
html,
parameter,
files,
cc,
bcc);
} else {
logger.warning(e.getMessage());
throw new MailException(e);
}
}
}
@Override
public void sendEmail(List<String> mailAddress, String title, String body, List<String> cc, List<String> bcc, Map<String, String> files) {
try {
Message message = messageBuild(mailAddress, title, body, cc, bcc, files);
Transport.send(message);
logger.info("邮件发送成功!^_^");
} catch (MessagingException e) {
if (mailBuild.getMaxRetries() > 0) {
ReSendList(mailAddress, title, body, cc, bcc, files);
} else {
logger.warning(e.getMessage());
throw new MailException(e);
}
}
}
@Override
public void sendEmail(String mailAddress, String title, String body, String zipName, Map<String, String> files) {
try {
Message message = messageBuild(mailAddress, title, body, zipName, files);
Transport.send(message);
logger.info("邮件发送成功!^_^");
} catch (MessagingException | IOException e) {
if (mailBuild.getMaxRetries() > 1) {
ReSend(mailAddress, title, body, zipName, files);
} else {
logger.warning(e.getMessage());
throw new MailException(e);
}
}
}
@Override
public void sendHtml(String mailAddress, String title, String body, String htmlName, Map<String, String> parameter, String zipName, Map<String, String> files) {
try {
Message message = messageBuild(mailAddress, title, body, htmlName, parameter, zipName, files);
Transport.send(message);
logger.info("邮件发送成功!^_^");
} catch (MessagingException | IOException e) {
if (mailBuild.getMaxRetries() > 1) {
ReSend(mailAddress, title, body, htmlName, parameter, zipName, files);
ReSendList(mailAddress, title, body, html, parameter, zipName, files, cc, bcc);
} else {
logger.warning(e.getMessage());
throw new MailException(e);
@ -361,13 +130,14 @@ public class MailService implements MailClient {
}
private void ReSendList(List<String> mailAddress,
String title,
String body,
List<String> html,
Map<String, String> parameter,
Map<String, String> files,
List<String> cc,
List<String> bcc) {
String title,
String body,
List<String> html,
Map<String, String> parameter,
String zipName,
Map<String, String> files,
List<String> cc,
List<String> bcc) {
int maxRetries = mailBuild.getMaxRetries();
int retryCount = 1; // 初始值为1则while循环中少发送一次最后一次发送在判断 retryCount >= maxRetries 这里
boolean retryOnFailure = true;
@ -376,14 +146,14 @@ public class MailService implements MailClient {
try {
logger.warning("邮件第 {" + retryCount + "} 次重新发送");
Message message;
if (html != null || parameter != null){
message = messageBuild(mailAddress, title, body, html, parameter, files, cc, bcc);
if (html != null || parameter != null) {
message = messageBuild(mailAddress, title, body, html, parameter, zipName, cc, bcc, files);
} else {
message = messageBuild(mailAddress, title, body, cc, bcc, files);
message = messageBuild(mailAddress, title, body, null, null, zipName, cc, bcc, files);
}
Transport.send(message);
retryOnFailure = false; // 发送成功停止重试
} catch (MessagingException e) {
} catch (MessagingException | IOException e) {
retryCount++;
try {
// 间隔秒数
@ -397,180 +167,42 @@ public class MailService implements MailClient {
if (retryCount >= maxRetries && retryOnFailure) {
try {
Message message;
if (html != null || parameter != null){
message = messageBuild(mailAddress, title, body, html, parameter, files, cc, bcc);
if (html != null || parameter != null) {
message = messageBuild(mailAddress, title, body, html, parameter, null, cc, bcc, files);
} else {
message = messageBuild(mailAddress, title, body, cc, bcc, files);
message = messageBuild(mailAddress, title, body, null, null, null, cc, bcc, files);
}
Transport.send(message);
} catch (MessagingException e) {
} catch (MessagingException | IOException e) {
throw new MailException(e);
}
}
}
public void ReSendList(List<String> mailAddress,
String title,
String body,
List<String> cc,
List<String> bcc,
Map<String, String> files) {
ReSendList(mailAddress, title, body, null, null, files, cc, bcc);
}
private void ReSend(String mailAddress,
String title,
String body,
String htmlName,
Map<String, String> parameter,
String zipName,
Map<String, String> files) {
int maxRetries = mailBuild.getMaxRetries();
int retryCount = 1; // 初始值为1则while循环中少发送一次最后一次发送在判断 retryCount >= maxRetries 这里
boolean retryOnFailure = true;
while (retryOnFailure && retryCount < maxRetries) {
try {
logger.warning("邮件第 {" + retryCount + "} 次重新发送");
if (htmlName != null || parameter != null){
Message message = messageBuild(mailAddress, title, body, htmlName, parameter, zipName, files);
Transport.send(message);
} else {
Message message = messageBuild(mailAddress, title, body, zipName, files);
Transport.send(message);
}
retryOnFailure = false; // 发送成功停止重试
} catch (MessagingException e) {
retryCount++;
try {
// 间隔秒数
TimeUnit.SECONDS.sleep(mailBuild.getRetryInterval());
} catch (InterruptedException ex) {
ex.printStackTrace();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (retryCount >= maxRetries && retryOnFailure) {
try {
if (htmlName != null || parameter != null){
Message message = messageBuild(mailAddress, title, body, htmlName, parameter, zipName, files);
Transport.send(message);
} else {
Message message = messageBuild(mailAddress, title, body, zipName, files);
Transport.send(message);
}
} catch (MessagingException e) {
throw new MailException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private void ReSend(String mailAddress,
String title,
String body,
String zipName,
Map<String, String> files) {
ReSend(mailAddress, title, body, null, null, zipName, files);
}
// messageBuild 方法重载
private Message messageBuild(List<String> mailAddress,
String title,
String body,
List<String> html,
Map<String, String> parameter,
Map<String, String> files,
List<String> cc,
List<String> bcc) throws MessagingException {
Message message = mailBuild.getMessage();
message.setRecipients(Message.RecipientType.TO, mailBuild.eliminate(mailAddress));
message.setSubject(title);
Multipart multipart = new MimeMultipart("alternative");
// 读取模板并进行变量替换
List<String> strings = HtmlUtil.replacePlaceholder(html, parameter);
// 拼合HTML数据
String htmlData = HtmlUtil.pieceHtml(strings);
if (!body.isEmpty()) {
// 创建文本正文部分
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(body);
multipart.addBodyPart(textPart);
}
// 添加附件
if (files != null && files.size() != 0) {
forFiles(multipart, files);
}
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlData, "text/html;charset=UTF-8");
addCC(cc, bcc, message);
multipart.addBodyPart(htmlPart);
message.setContent(multipart);
return message;
}
public Message messageBuild(List<String> mailAddress,
String title,
String body,
List<String> html,
Map<String, String> parameter,
String zipName,
List<String> cc,
List<String> bcc,
Map<String, String> files) throws MessagingException {
Map<String, String> files) throws MessagingException, IOException {
Message message = mailBuild.getMessage();
message.setRecipients(Message.RecipientType.TO, mailBuild.eliminate(mailAddress));
message.setSubject(title);
if (StrUtil.isNotBlank(body)) {
message.setText(body);
}
if (files != null && files.size() != 0) {
Multipart multipart = new MimeMultipart();
forFiles(multipart, files);
message.setContent(multipart);
}
addCC(cc, bcc, message);
return message;
}
public Message messageBuild(String mailAddress,
String title,
String body,
String zipName,
Map<String, String> files) throws MessagingException, IOException {
Message message = mailBuild.getMessage();
message.setRecipients(Message.RecipientType.TO, mailBuild.eliminate(Convert.toList(String.class, mailAddress)));
message.setSubject(title);
if (StrUtil.isNotBlank(body)) {
message.setText(body);
}
if (files != null && files.size() != 0) {
Multipart multipart = new MimeMultipart();
zipFiles(multipart, zipName, files);
message.setContent(multipart);
}
return message;
}
public Message messageBuild(String mailAddress,
String title,
String body,
String htmlName,
Map<String, String> parameter,
String zipName,
Map<String, String> files) throws MessagingException, IOException {
Message message = mailBuild.getMessage();
message.setRecipients(Message.RecipientType.TO, mailBuild.eliminate(Convert.toList(String.class, mailAddress)));
message.setSubject(title);
Multipart multipart = new MimeMultipart("alternative");
//读取模板并进行变量替换
List<String> strings = HtmlUtil.replacePlaceholder(HtmlUtil.readHtml(htmlName), parameter);
//拼合HTML数据
String htmlData = HtmlUtil.pieceHtml(strings);
if (CollUtil.isNotEmpty(html) && MapUtil.isNotEmpty(parameter)) {
//读取模板并进行变量替换
List<String> strings = HtmlUtil.replacePlaceholder(html, parameter);
//拼合HTML数据
String htmlData = HtmlUtil.pieceHtml(strings);
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlData, "text/html;charset=UTF-8");
multipart.addBodyPart(htmlPart);
}
if (StrUtil.isNotBlank(body)) {
// 创建文本正文部分
MimeBodyPart textPart = new MimeBodyPart();
@ -578,21 +210,26 @@ public class MailService implements MailClient {
multipart.addBodyPart(textPart);
}
//添加附件
if (MapUtil.isNotEmpty(files)) {
if (MapUtil.isNotEmpty(files) && StrUtil.isNotBlank(zipName)) {
zipFiles(multipart, zipName, files);
} else {
if (MapUtil.isNotEmpty(files)) {
forFiles(multipart, files);
message.setContent(multipart);
}
}
if (CollUtil.isNotEmpty(cc) || CollUtil.isNotEmpty(bcc)) {
addCC(cc, bcc, message);
}
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlData, "text/html;charset=UTF-8");
multipart.addBodyPart(htmlPart);
message.setContent(multipart);
return message;
}
private void addCC(List<String> cc, List<String> bcc, Message message) throws MessagingException {
if (cc != null && cc.size() > 0) {
if (CollUtil.isNotEmpty(cc)) {
message.addRecipients(Message.RecipientType.CC, mailBuild.eliminate(cc));
}
if (bcc != null && bcc.size() > 0) {
if (CollUtil.isNotEmpty(bcc)) {
message.addRecipients(Message.RecipientType.BCC, mailBuild.eliminate(bcc));
}
}

View File

@ -19,10 +19,5 @@
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-comm</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -14,6 +14,10 @@ import java.util.List;
**/
public interface SmsBlend {
String getConfigId();
String getSupplier();
SmsResponse sendMessage(String phone, String message);
SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages);
@ -37,4 +41,5 @@ public interface SmsBlend {
void delayMassTexting(List<String> phones, String message, Long delayedTime);
void delayMassTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime);
}

View File

@ -0,0 +1,41 @@
package org.dromara.sms4j.api.dao;
/**
* DAO 接口
*
* @author Wind
* @author Charles7c
* @since 2023/8/5 20:03
*/
public interface SmsDao {
/**
* 存储
*
* @param key
* @param value
* @param cacheTime 缓存时间单位)
*/
void set(String key, Object value, long cacheTime);
/**
* 存储
*
* @param key
* @param value
*/
void set(String key, Object value);
/**
* 读取
*
* @param key
* @return
*/
Object get(String key);
/**
* 清空
*/
void clean();
}

View File

@ -0,0 +1,166 @@
package org.dromara.sms4j.api.dao;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* DAO 默认实现内部缓存
*
* @author Wind
* @author Charles7c
* @since 2023/8/5 20:36
*/
@Slf4j
public class SmsDaoDefaultImpl implements SmsDao {
private static volatile SmsDaoDefaultImpl INSTANCE;
private static final Timer TIMER = new Timer();
private static final ConcurrentHashMap<String, DataWrapper> DATA_MAP = new ConcurrentHashMap<>();
/**
* 缓存时间单位毫秒默认 24 小时
*/
private static final long DEFAULT_CACHE_TIME = 24 * 60 * 60 * 1000L;
/**
* 定时器执行频率单位毫秒默认 30
*/
private static final long TIMER_INTERVAL = 30 * 1000L;
private SmsDaoDefaultImpl() {}
/**
* 获取唯一实例
*
* @return 唯一实例
*/
public static SmsDaoDefaultImpl getInstance() {
if (null == INSTANCE) {
synchronized (SmsDaoDefaultImpl.class) {
if (null == INSTANCE) {
INSTANCE = new SmsDaoDefaultImpl();
// 初始化定时器
initTimer();
}
}
}
return INSTANCE;
}
@Override
public void set(String key, Object value, long cacheTime) {
cacheTime = cacheTime * 1000L;
DataWrapper dataWrapper = DATA_MAP.get(key);
if (null != dataWrapper) {
dataWrapper.update(value, cacheTime);
} else {
dataWrapper = new DataWrapper(value, cacheTime);
DATA_MAP.put(key, dataWrapper);
}
}
@Override
public void set(String key, Object value) {
this.set(key, value, DEFAULT_CACHE_TIME);
}
@Override
public Object get(String key) {
DataWrapper dataWrapper = DATA_MAP.get(key);
if (dataWrapper != null && !dataWrapper.isExpired()) {
return dataWrapper.data;
}
return null;
}
@Override
public void clean() {
DATA_MAP.clear();
}
/**
* 初始化定时器
*/
private static void initTimer() {
TIMER.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
clearExpiredData();
} catch (Exception e) {
log.error(e.getMessage());
throw new SmsBlendException(e.getMessage());
}
}
}, TIMER_INTERVAL, TIMER_INTERVAL);
}
/**
* 清除过期数据
*/
private static void clearExpiredData() {
List<String> expiredKeyList = new LinkedList<>();
for (Map.Entry<String, DataWrapper> entry : DATA_MAP.entrySet()) {
if (entry.getValue().isExpired()) {
expiredKeyList.add(entry.getKey());
}
}
for (String key : expiredKeyList) {
DATA_MAP.remove(key);
}
}
/**
* 数据封装
*/
@Data
private static class DataWrapper {
/**
* 数据
*/
private Object data;
/**
* 过期时间
*/
private long expiredTime;
/**
* 缓存时间
*/
private long cacheTime;
private DataWrapper(Object data, long cacheTime) {
this.update(data, cacheTime);
}
/**
* 更新数据及缓存时间
*
* @param data 数据
* @param cacheTime 缓存时间
*/
public void update(Object data, long cacheTime) {
this.data = data;
this.cacheTime = cacheTime;
this.expiredTime = System.currentTimeMillis() + cacheTime;
}
/**
* 数据是否过期
*
* @return true过期false未过期
*/
public boolean isExpired() {
if (this.expiredTime > 0) {
return System.currentTimeMillis() > this.expiredTime;
}
return true;
}
}
}

View File

@ -0,0 +1,18 @@
package org.dromara.sms4j.api.proxy;
import org.dromara.sms4j.comm.exception.SmsBlendException;
/**
* 短信拦截处理接口
*/
public interface RestrictedProcess {
/**
* 拦截校验过程
* @param phone
* @return
* @throws Exception
*/
SmsBlendException process(String phone) throws Exception;
}

View File

@ -1,43 +0,0 @@
package org.dromara.sms4j.api.smsProxy;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.comm.config.SmsConfig;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.comm.utils.TimeExpiredPoolCache;
@Slf4j
public class RestrictedProcess {
static Long minTimer = 60 * 1000L;
static Long accTimer = 24 * 60 * 60 * 1000L;
public SmsBlendException process(SmsConfig config, String args) throws Exception{
TimeExpiredPoolCache instance = TimeExpiredPoolCache.getInstance();//缓存实例
Integer accountMax = config.getAccountMax();//每日最大发送量
Integer minuteMax = config.getMinuteMax();//每分钟最大发送量
if (SmsUtil.isNotEmpty(accountMax)) { //是否配置了每日限制
Integer i = instance.get(args + "max");
if (SmsUtil.isEmpty(i)) {
instance.put(args + "max", 1, accTimer);
} else if (i > accountMax) {
log.info("The phone:" + args + ",number of short messages reached the maximum today");
return new SmsBlendException("The phone:" + args + ",number of short messages reached the maximum today");
} else {
instance.put(args + "max", i + 1, accTimer);
}
}
if (SmsUtil.isNotEmpty(minuteMax)) { //是否配置了每分钟最大限制
Integer o = instance.get(args);
if (SmsUtil.isNotEmpty(o)) {
if (o < minuteMax) {
instance.put(args, o + 1, minTimer);
} else {
log.info("The phone:", args + " Text messages are sent too often");
return new SmsBlendException("The phone:", args + " Text messages are sent too often");
}
} else {
instance.put(args, 1, minTimer);
}
}
return null;
}
}

View File

@ -1,29 +0,0 @@
package org.dromara.sms4j.api.universal;
/**
* SmsRedisUtil
* <p> redis工具接口用户可自主实现以更换redis的来源
*@since 2.2.0
* @author :Wind
* 2023/6/6 22:21
**/
public interface SmsRedisUtil {
/**
* setOrTime
* <p>设置带有过期时间的key
* @param key redis的key
* @param value redis 的value
* @param time 过期时间秒级单位
* @author :Wind
*/
public boolean setOrTime(String key, Object value, Long time);
/**
* getByKey
* <p>根据key获取redis中缓存的数据
* @param key redis的key
* @author :Wind
*/
public Object getByKey(String key);
}

View File

@ -2,9 +2,24 @@ package org.dromara.sms4j.api.universal;
/**
* SupplierConfig
* <p> 空接口无含义只为标定配置类的额外类型
* <p> 标定配置类的额外类型
* @author :Wind
* 2023/5/16 15:14
**/
public interface SupplierConfig {
/**
* 获取配置标识名
*
* @since 3.0.0
*/
String getConfigId();
/**
* 获取供应商
*
* @since 3.0.0
*/
String getSupplier();
}

View File

@ -25,5 +25,11 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,19 +0,0 @@
package org.dromara.sms4j.comm.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>类名: Restricted
* <p>说明 发送短信限制
*
* @author :Wind
* 2023/3/26 17:12
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Restricted {
}

View File

@ -1,51 +0,0 @@
package org.dromara.sms4j.comm.config;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 短信配置属性基类
*
* @author Charles7c
* @since 2023/4/20 23:03
*/
@Data
@SuperBuilder
@NoArgsConstructor
public class BaseConfig {
/**
* Access Key
*/
private String accessKeyId;
/**
* Access Key Secret
*/
private String accessKeySecret;
/**
* 短信签名
*/
private String signature;
/**
* 模板 ID
*/
private String templateId;
/**
* 权重
*/
@Builder.Default
private Integer weight = 1;
/**
* 配置标识名 如未配置取对应渠道名例如 Alibaba
*
* @since 3.0.0
*/
private String configId;
}

View File

@ -1,64 +0,0 @@
package org.dromara.sms4j.comm.config;
import lombok.Data;
/**
* SmsSqlConfig
* <p> sql配置信息
* @author :Wind
* 2023/4/5 18:28
**/
@Data
public class SmsSqlConfig {
/**
* 连接地址
*/
private String url;
/**
* 驱动
*/
private String driverClassName;
/**
* 账号名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 数据库名
*/
@Deprecated
private String databaseName;
/**
* 配置表 表名
*/
private String tableName;
/**
* 启用字段名
*/
private String startName;
/**
* 启用字段标识
*/
private String isStart;
/**
* 配置字段名
*/
private String configName;
/** 厂商配置字段名*/
private String supplierFieldName;
}

View File

@ -28,6 +28,8 @@ public abstract class Constant {
*/
public static final String FROM_URLENCODED = "application/x-www-form-urlencoded";
public static final String ACCEPT = "application/json";
public static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
/**
@ -43,6 +45,11 @@ public abstract class Constant {
*/
public static final String HTTPS_PREFIX = "https://";
/**
* 供应商配置键名
*/
public static final String SUPPLIER_KEY = "supplier";
private Constant() {
}
}

View File

@ -7,16 +7,10 @@ package org.dromara.sms4j.comm.enumerate;
* 2023/4/5 19:08
**/
public enum ConfigType {
/** 配置文件*/
CONFIG_FILE("configFile"),
/** setting配置文件*/
SETTINGS_FILE("settingsFile"),
/** 数据库配置*/
SQL_CONFIG("sqlConfig"),
/** nacos配置*/
NACOS_CONFIG("nacosConfig");
/** yaml配置文件 */
YAML("yaml"),
/** 接口 */
INTERFACE("interface");
private final String name;

View File

@ -1,72 +0,0 @@
package org.dromara.sms4j.comm.utils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FileTool {
private FileTool(){}
/**
* getPath
* <p>获取用户工作目录
* @author :Wind
*/
public static String getPath(){
return System.getProperty("user.dir");
}
/**
* createFile
* <p>创建文件
* @param path 要创建的文件路径
* @return 文件创建成功或存在则为true
* @author :Wind
*/
public static boolean createFile(String path){
File file = new File(path);
try {
file.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
return file.exists();
}
/**
* writeFile
* <p>写入文件
* @param isAppend 是否追加写入true追加写入false覆盖写入
* @author :Wind
*/
public static void writeFile(File file,String content,boolean isAppend){
Writer writer = null;
try {
writer = new FileWriter(file,isAppend);
writer.write(content);
}catch (IOException e){
throw new RuntimeException(e);
}finally {
try {
writer.close();
}catch (IOException e){
throw new RuntimeException(e);
}
}
}
/**
* readFile
* <p>读取文件为字符串
* @param path 文件路径
* @author :Wind
*/
public static String readFile(String path) throws IOException {
return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
}
}

View File

@ -1,151 +0,0 @@
package org.dromara.sms4j.comm.utils;
import cn.hutool.core.util.StrUtil;
import org.dromara.sms4j.comm.config.SmsSqlConfig;
import org.dromara.sms4j.comm.exception.SmsSqlException;
import org.dromara.sms4j.comm.factory.BeanFactory;
import java.sql.*;
import java.util.Hashtable;
import java.util.Map;
import java.util.Objects;
/**
* JDBCTool
* <p> 数据库相关工具
*
* @author :Wind
* 2023/4/4 22:34
**/
public class JDBCTool {
public static final String SELECT = "select {},{} from {} where {} = {}";
private static final String ERR_CONFIG = "One configuration was expected, but {} was found. Please check your database configuration";
private final SmsSqlConfig config;
/**
* 数据库链接
* */
private Connection connection;
public JDBCTool(SmsSqlConfig config,Connection connection) {
// if (config == null) {
// throw new SmsSqlException("The configuration file failed to be loaded");
// }
this.config = config;
this.connection = connection;
}
public JDBCTool(SmsSqlConfig config) {
this.config = config;
}
public void setConnection(Connection connection){
this.connection = connection;
}
public static Map<String, String> selectConfig() {
Map<String, String> select = BeanFactory.getJDBCTool().select();
if (select.size() == 0) {
throw new SmsSqlException("No valid configuration was scanned. Please check whether the configuration file or database link is normal");
}
return select;
}
/**
* 获取链接
*/
public Connection getConn() {
Connection connection;
String url;
if (SmsUtil.isEmpty(config.getUrl())) {
throw new SmsSqlException("The configuration file failed to be loaded");
}
try {
if (config.getDatabaseName().isEmpty()){
url = config.getUrl();
}else{
url = config.getUrl() + "/" + config.getDatabaseName();
}
connection = DriverManager.getConnection(url, config.getUsername(), config.getPassword());
} catch (SQLException e) {
throw new SmsSqlException(e.getMessage());
}
return connection;
}
/**
* select
* <p>查询封装
* @param sql 要查询的sql语句
* @author :Wind
*/
public Map<String, String> select(String sql) {
// Connection conn = null;
if (Objects.isNull(connection)){
connection = getConn();
}
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
Map<String, String> map = new Hashtable<>();
try {
// conn = getConn();
preparedStatement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
preparedStatement.setFetchSize(1000);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
String data = resultSet.getString(config.getConfigName());
String supplier = resultSet.getString(config.getSupplierFieldName());
map.put(supplier, data);
}
} catch (SQLException e) {
throw new SmsSqlException(e.getMessage());
} finally {
close(connection, preparedStatement, resultSet);
}
return map;
}
/**
* close
* <p>关闭链接方法
* @author :Wind
*/
private void close(Connection conn, PreparedStatement stmt, ResultSet rs) {
// 关闭连接
if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// throw new SmsSqlException(e.getMessage());
// }
}
// 关闭 statement
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
throw new SmsSqlException(e.getMessage());
}
}
// 关闭结果集
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new SmsSqlException(e.getMessage());
}
}
}
public Map<String, String> select() {
String format = StrUtil.format(SELECT,
config.getConfigName(),
config.getSupplierFieldName(),
config.getTableName(),
config.getStartName(),
config.getIsStart());
return select(format);
}
}

View File

@ -1,16 +0,0 @@
package org.dromara.sms4j.comm.utils;
import java.io.Serializable;
/**
* REST API 函数式接口
*
* @param <P> 请求参数
* @param <R> 响应
* @author Charles7c
* @since 2023/4/17 20:57
*/
@FunctionalInterface
public interface RestApiFunction<P, R> extends Serializable {
R apply(P param);
}

View File

@ -0,0 +1,79 @@
package org.dromara.sms4j.comm.utils;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import java.util.Map;
public class SmsHttpUtil {
private SmsHttpUtil() {
}
private static class SmsHttpHolder {
private static final SmsHttpUtil INSTANCE = new SmsHttpUtil();
}
public static SmsHttpUtil instance() {
return SmsHttpHolder.INSTANCE;
}
/**
* 发送post json请求
* @param url 请求地址
* @param headers 请求头
* @param body 请求体(json格式字符串)
* @return 返回体
*/
public JSONObject postJson(String url, Map<String, String> headers, String body){
try(HttpResponse response = HttpRequest.post(url)
.addHeaders(headers)
.body(body)
.execute()){
return JSONUtil.parseObj(response.body());
}catch (Exception e){
throw new SmsBlendException(e.getMessage());
}
}
/**
* 发送post json请求
* @param url 请求地址
* @param headers 请求头
* @param body 请求体(map格式请求体)
* @return 返回体
*/
public JSONObject postJson(String url, Map<String, String> headers, Map<String, Object> body){
return postJson(url, headers, JSONUtil.toJsonStr(body));
}
/**
* 发送post form 请求
* @param url 请求地址
* @param headers 请求头
* @param body 请求体(map格式请求体)
* @return 返回体
*/
public JSONObject postFrom(String url, Map<String, String> headers, Map<String, Object> body){
try(HttpResponse response = HttpRequest.post(url)
.addHeaders(headers)
.form(body)
.execute()){
return JSONUtil.parseObj(response.body());
}catch (Exception e){
throw new SmsBlendException(e.getMessage());
}
}
/**
* 线程睡眠
* @param retryInterval
*/
public void safeSleep(int retryInterval){
ThreadUtil.safeSleep(retryInterval * 1000L);
}
}

View File

@ -4,11 +4,13 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author wind
@ -122,7 +124,7 @@ public class SmsUtil {
* @return 结果字符串
*/
public static String arrayToString(List<String> list) {
return CollUtil.join(list, ",", "+86", "");
return CollUtil.join(list, ",", str -> StrUtil.addPrefixIfNot(str, "+86"));
}
/**
@ -134,9 +136,31 @@ public class SmsUtil {
public static String[] listToArray(List<String> list) {
List<String> toStr = new ArrayList<>();
for (String s : list) {
toStr.add("+86" + s);
toStr.add(StrUtil.addPrefixIfNot(s, "+86"));
}
return toStr.toArray(new String[list.size()]);
}
/**
* 将Map中所有key的分隔符转换为新的分隔符
* @param map map对象
* @param seperator 旧分隔符
* @param newSeperator 新分隔符
*/
public static void replaceKeysSeperator(Map<String, Object> map, String seperator, String newSeperator) {
if(CollUtil.isEmpty(map)) {
return;
}
List<String> keySet = new ArrayList<>(map.keySet());
for(String key : keySet) {
if(StrUtil.isEmpty(key) || !key.contains(seperator)) {
continue;
}
String value = String.valueOf(map.get(key));
String newKey = key.replaceAll(seperator, newSeperator);
map.putIfAbsent(newKey, value);
map.remove(key);
}
}
}

View File

@ -1,252 +0,0 @@
package org.dromara.sms4j.comm.utils;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
/**
* <p>类名: TimeExpiredPoolCache
* <p>说明 一个自实现的内部缓存可用于无法使用redis的场景
*
* @author :Wind
* 2023/3/25 18:26
**/
@Slf4j
public class TimeExpiredPoolCache {
/**
* 持久化文件格式
*/
private static final String FILE_TYPE = "persistence.data";
private static final long defaultCachedMillis = 24 * 60 * 60 * 1000L;//过期时间默认24小时
private static final long timerMillis = 30 * 1000L;//定时清理默认1分钟
/**
* 对象池
*/
private static ConcurrentHashMap<String, DataWrapper<?>> dataPool = null;
/**
* 对象单例
*/
private static TimeExpiredPoolCache instance = null;
/**
* 定时器定时清理过期缓存
*/
private static final Timer timer = new Timer();
private TimeExpiredPoolCache() {
}
private static synchronized void syncInit() {
if (instance == null) {
instance = new TimeExpiredPoolCache();
dataPool = new ConcurrentHashMap<>();
initTimer();
}
}
public static TimeExpiredPoolCache getInstance() {
if (instance == null) {
syncInit();
}
return instance;
}
/**
* 读取持久化文件
*/
private static boolean persistenceInit() {
String path = FileTool.getPath() + FILE_TYPE;
try {
DataWrapper d = JSONUtil.toBean(FileTool.readFile(path), DataWrapper.class);
if (dataPool != null) {
return true;
}
} catch (IOException e) {
log.error(e.getMessage());
}
return false;
}
private static void initTimer() {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
clearExpiredCaches();
} catch (Exception e) {
log.error(e.getMessage());
throw new SmsBlendException(e.getMessage());
}
}
}, timerMillis, timerMillis);
}
/** 写入持久化文件*/
private static void persistence() {
String path = FileTool.getPath() + FILE_TYPE;
FileTool.createFile(path);
FileTool.writeFile(new File(path), JSONUtil.toJsonStr(dataPool), false);
}
/**
* 清除过期的缓存
*/
private static void clearExpiredCaches() {
List<String> expiredKeyList = new LinkedList<>();
for (Entry<String, DataWrapper<?>> entry : dataPool.entrySet()) {
if (entry.getValue().isExpired()) {
expiredKeyList.add(entry.getKey());
}
}
for (String key : expiredKeyList) {
dataPool.remove(key);
}
}
/**
* 缓存数据
*
* @param key key值
* @param data 缓存数据
* @param cachedMillis 过期时间
* @param dataRenewer 刷新数据
*/
@SuppressWarnings("unchecked")
public <T> T put(String key, T data, long cachedMillis, DataRenewer<T> dataRenewer) throws Exception {
DataWrapper<T> dataWrapper = (DataWrapper<T>) dataPool.get(key);
if (data == null && dataRenewer != null) {
data = dataRenewer.renewData();
}
//当重新获取数据为空直接返回不做put
if (data == null) {
return null;
}
if (dataWrapper != null) {
//更新
dataWrapper.update(data, cachedMillis);
} else {
dataWrapper = new DataWrapper<>(data, cachedMillis);
dataPool.put(key, dataWrapper);
}
return data;
}
/**
* 直接设置缓存值和时间
*/
@SuppressWarnings("unchecked")
public <T> T put(String key, T data, long cachedMillis) throws Exception {
DataWrapper<T> dataWrapper = (DataWrapper<T>) dataPool.get(key);
if (dataWrapper != null) {
//更新
dataWrapper.update(data, cachedMillis);
} else {
dataWrapper = new DataWrapper<T>(data, cachedMillis);
dataPool.put(key, dataWrapper);
}
return data;
}
/**
* 默认构造时间的缓存数据
*/
@Deprecated
public <T> T put(String key, T data, DataRenewer<T> dataRenewer) throws Exception {
return put(key, data, defaultCachedMillis, dataRenewer);
}
/**
* 获取缓存
*/
@SuppressWarnings("unchecked")
public <T> T get(String key, long cachedMillis, DataRenewer<T> dataRenewer) throws Exception {
DataWrapper<T> dataWrapper = (DataWrapper<T>) dataPool.get(key);
if (dataWrapper != null && !dataWrapper.isExpired()) {
return dataWrapper.data;
}
return put(key, null, cachedMillis, dataRenewer);
}
@SuppressWarnings("unchecked")
public <T> T get(String key) {
DataWrapper<T> dataWrapper = (DataWrapper<T>) dataPool.get(key);
if (dataWrapper != null && !dataWrapper.isExpired()) {
return dataWrapper.data;
}
return null;
}
/**
* 清除缓存
*/
public void clear() {
dataPool.clear();
}
/**
* 删除指定key的value
*/
public void remove(String key) {
dataPool.remove(key);
}
/**
* 数据构造
*/
public interface DataRenewer<T> {
public T renewData();
}
/**
* 数据封装
*/
private static class DataWrapper<T> {
/**
* 数据
*/
private T data;
/**
* 到期时间
*/
private long expiredTime;
/**
* 缓存时间
*/
private long cachedMillis;
private DataWrapper(T data, long cachedMillis) {
this.update(data, cachedMillis);
}
public void update(T data, long cachedMillis) {
this.data = data;
this.cachedMillis = cachedMillis;
this.updateExpiredTime();
}
public void updateExpiredTime() {
this.expiredTime = System.currentTimeMillis() + cachedMillis;
}
/**
* 数据是否过期
*/
public boolean isExpired() {
if (this.expiredTime > 0) {
return System.currentTimeMillis() > this.expiredTime;
}
return true;
}
}
}

View File

@ -1,163 +0,0 @@
package org.dromara.sms4j.core;
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.cloopen.config.CloopenConfig;
import org.dromara.sms4j.comm.utils.JDBCTool;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.core.config.SupplierFactory;
import org.dromara.sms4j.ctyun.config.CtyunConfig;
import org.dromara.sms4j.emay.config.EmayConfig;
import org.dromara.sms4j.huawei.config.HuaweiConfig;
import org.dromara.sms4j.jdcloud.config.JdCloudConfig;
import org.dromara.sms4j.netease.config.NeteaseConfig;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.dromara.sms4j.tencent.config.TencentConfig;
import org.dromara.sms4j.unisms.config.UniConfig;
import org.dromara.sms4j.yunpian.config.YunpianConfig;
import java.util.Map;
/**
* SupplierSqlConfig
* <p> 处理sql配置源
* @author :Wind
* 2023/4/11 19:42
**/
public class SupplierSqlConfig {
private static Map<String, String> select;
/**
* readSqlConfig
* <p>读取数据库配置信息
* @author :Wind
*/
public static void readSqlConfig(){
select = JDBCTool.selectConfig();
}
/**
* refreshSqlConfig
* <p>读取并刷新数据库配置
* @author :Wind
*/
public static void refreshSqlConfig(){
readSqlConfig();
alibaba();
huawei();
jingdong();
tencent();
uniSms();
yunPian();
cloopen();
emay();
ctyun();
netease();
}
public SupplierSqlConfig() {
refreshSqlConfig();
}
public static SupplierSqlConfig newSupplierSqlConfig(){
return new SupplierSqlConfig();
}
/**
* alibaba
* <p>数据库读取并设置阿里云短信
* @author :Wind
*/
public static void alibaba(){
AlibabaConfig alibabaConfig = SmsUtil.jsonForObject(select.get(SupplierType.ALIBABA.getName()), AlibabaConfig.class);
SupplierFactory.setAlibabaConfig(alibabaConfig);
}
/**
* huawei
* <p>数据库读取并设置华为短信
* @author :Wind
*/
public static void huawei(){
HuaweiConfig huaweiConfig = SmsUtil.jsonForObject(select.get(SupplierType.HUAWEI.getName()), HuaweiConfig.class);
SupplierFactory.setHuaweiConfig(huaweiConfig);
}
/**
* jingdong
* <p>数据库读取并设置京东短信
* @author :Wind
*/
public static void jingdong(){
JdCloudConfig jdCloudConfig = SmsUtil.jsonForObject(select.get(SupplierType.JD_CLOUD.getName()), JdCloudConfig.class);
SupplierFactory.setJdCloudConfig(jdCloudConfig);
}
/**
* tencent
* <p>数据库读取并设置腾讯短信
* @author :Wind
*/
public static void tencent(){
TencentConfig tencentConfig = SmsUtil.jsonForObject(select.get(SupplierType.TENCENT.getName()), TencentConfig.class);
SupplierFactory.setTencentConfig(tencentConfig);
}
/**
* uniSms
* <p>数据库读取并设置合一短信配置
* @author :Wind
*/
public static void uniSms(){
UniConfig uniConfig = SmsUtil.jsonForObject(select.get(SupplierType.UNI_SMS.getName()), UniConfig.class);
SupplierFactory.setUniConfig(uniConfig);
}
/**
* yunPian
* <p>数据库读取并设置云片短信
* @author :Wind
*/
public static void yunPian(){
YunpianConfig yunpianConfig = SmsUtil.jsonForObject(select.get(SupplierType.YUNPIAN.getName()), YunpianConfig.class);
SupplierFactory.setYunpianConfig(yunpianConfig);
}
/**
* cloopen
* <p>数据库读取并设置容联云短信
* @author :Wind
*/
public static void cloopen(){
CloopenConfig cloopenConfig = SmsUtil.jsonForObject(select.get(SupplierType.CLOOPEN.getName()), CloopenConfig.class);
SupplierFactory.setCloopenConfig(cloopenConfig);
}
/**
* emay
* <p>数据库读取并设置亿美软通短信
*/
public static void emay() {
EmayConfig emayConfig = SmsUtil.jsonForObject(select.get(SupplierType.EMAY.getName()), EmayConfig.class);
SupplierFactory.setEmayConfig(emayConfig);
}
/**
* ctyun
* <p>数据库读取并设置天翼云短信
* @author :Wind
*/
public static void ctyun(){
CtyunConfig ctyunConfig = SmsUtil.jsonForObject(select.get(SupplierType.CTYUN.getName()), CtyunConfig.class);
SupplierFactory.setCtyunConfig(ctyunConfig);
}
/**
* netease
* <p>数据库读取并设置网易云短信
* @author : Adam
*/
public static void netease(){
NeteaseConfig neteaseConfig = SmsUtil.jsonForObject(select.get(SupplierType.NETEASE.getName()), NeteaseConfig.class);
SupplierFactory.setNeteaseConfig(neteaseConfig);
}
}

View File

@ -1,247 +0,0 @@
package org.dromara.sms4j.core.config;
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.aliyun.config.AlibabaFactory;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.cloopen.config.CloopenConfig;
import org.dromara.sms4j.cloopen.config.CloopenFactory;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.core.load.SmsLoad;
import org.dromara.sms4j.ctyun.config.CtyunConfig;
import org.dromara.sms4j.ctyun.config.CtyunFactory;
import org.dromara.sms4j.emay.config.EmayConfig;
import org.dromara.sms4j.emay.config.EmayFactory;
import org.dromara.sms4j.huawei.config.HuaweiConfig;
import org.dromara.sms4j.huawei.config.HuaweiFactory;
import org.dromara.sms4j.jdcloud.config.JdCloudConfig;
import org.dromara.sms4j.jdcloud.config.JdCloudFactory;
import org.dromara.sms4j.netease.config.NeteaseConfig;
import org.dromara.sms4j.netease.config.NeteaseFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.dromara.sms4j.tencent.config.TencentConfig;
import org.dromara.sms4j.tencent.config.TencentFactory;
import org.dromara.sms4j.unisms.config.UniConfig;
import org.dromara.sms4j.unisms.config.UniFactory;
import org.dromara.sms4j.yunpian.config.YunPianFactory;
import org.dromara.sms4j.yunpian.config.YunpianConfig;
import org.dromara.sms4j.zhutong.config.ZhutongConfig;
import org.dromara.sms4j.zhutong.config.ZhutongFactory;
/**
* SupplierFactory
* <p> 差异化配置工厂
* @author :Wind
* 2023/4/8 15:02
**/
public class SupplierFactory {
private SupplierFactory() {
}
/**
* 阿里云配置获取
*/
public static AlibabaConfig getAlibabaConfig() {
return AlibabaFactory.instance().getConfig();
}
/**
* 华为云配置获取
*/
public static HuaweiConfig getHuaweiConfig() {
return HuaweiFactory.instance().getConfig();
}
/**
* 合一短信配置获取
*/
public static UniConfig getUniConfig() {
return UniFactory.instance().getConfig();
}
/**
* 腾讯短信配置获取
*/
public static TencentConfig getTencentConfig() {
return TencentFactory.instance().getConfig();
}
/**
* 云片短信配置获取
*/
public static YunpianConfig getYunpianConfig() {
return YunPianFactory.instance().getConfig();
}
/**
* 京东云短信配置获取
*/
public static JdCloudConfig getJdCloudConfig() {
return JdCloudFactory.instance().getConfig();
}
/**
* 容联云短信配置获取
*/
public static CloopenConfig getCloopenConfig() {
return CloopenFactory.instance().getConfig();
}
/**
* 亿美软通配置获取
*/
public static EmayConfig getEmayConfig() {
return EmayFactory.instance().getConfig();
}
/**
* 天翼云配置获取
*/
public static CtyunConfig getCtyunConfig() {
return CtyunFactory.instance().getConfig();
}
/**
* 网易云信配置获取
*/
public static NeteaseConfig getNeteaseConfig() {
return NeteaseFactory.instance().getConfig();
}
/**
* 助通信配置获取
*/
public static ZhutongConfig getZhutongConfig() {
return ZhutongFactory.instance().getConfig();
}
/**
* setSupplierConfig
* <p>通用化set用于设置
* @param t 配置对象
* @author :Wind
*/
public static <T extends SupplierConfig> void setSupplierConfig(T t) {
if (t instanceof AlibabaConfig) {
setAlibabaConfig((AlibabaConfig) t);
} else if (t instanceof HuaweiConfig) {
setHuaweiConfig((HuaweiConfig) t);
} else if (t instanceof UniConfig) {
setUniConfig((UniConfig) t);
} else if (t instanceof TencentConfig) {
setTencentConfig((TencentConfig) t);
} else if (t instanceof YunpianConfig) {
setYunpianConfig((YunpianConfig) t);
} else if (t instanceof JdCloudConfig) {
setJdCloudConfig((JdCloudConfig) t);
} else if (t instanceof CloopenConfig) {
setCloopenConfig((CloopenConfig) t);
} else if (t instanceof EmayConfig) {
setEmayConfig((EmayConfig) t);
} else if (t instanceof CtyunConfig) {
setCtyunConfig((CtyunConfig) t);
} else if (t instanceof NeteaseConfig) {
setNeteaseConfig((NeteaseConfig) t);
} else if (t instanceof ZhutongConfig) {
setZhuTongConfig((ZhutongConfig) t);
} else {
throw new SmsBlendException("Loading failure! Please check the configuration type.");
}
}
/**
* 设置 alibabaConfig
*/
public static void setAlibabaConfig(AlibabaConfig alibabaConfig) {
AlibabaFactory.instance().setConfig(alibabaConfig);
SmsFactory.refresh(SupplierType.ALIBABA);
SmsLoad.starConfig(alibabaConfig,SupplierType.ALIBABA);
}
/**
* 设置 huaweiConfig
*/
public static void setHuaweiConfig(HuaweiConfig huaweiConfig) {
HuaweiFactory.instance().setConfig(huaweiConfig);
SmsFactory.refresh(SupplierType.HUAWEI);
SmsLoad.starConfig(huaweiConfig,SupplierType.HUAWEI);
}
/**
* 设置 uniConfig
*/
public static void setUniConfig(UniConfig uniConfig) {
UniFactory.instance().setConfig(uniConfig);
SmsFactory.refresh(SupplierType.UNI_SMS);
SmsLoad.starConfig(uniConfig,SupplierType.UNI_SMS);
}
/**
* 设置 tencentConfig
*/
public static void setTencentConfig(TencentConfig tencentConfig) {
TencentFactory.instance().setConfig(tencentConfig);
SmsFactory.refresh(SupplierType.TENCENT);
SmsLoad.starConfig(tencentConfig,SupplierType.TENCENT);
}
/**
* 设置 yunpianConfig
*/
public static void setYunpianConfig(YunpianConfig yunpianConfig) {
YunPianFactory.instance().setConfig(yunpianConfig);
SmsFactory.refresh(SupplierType.YUNPIAN);
SmsLoad.starConfig(yunpianConfig,SupplierType.YUNPIAN);
}
/**
* 设置 jdCloudConfig
*/
public static void setJdCloudConfig(JdCloudConfig jdCloudConfig) {
JdCloudFactory.instance().setConfig(jdCloudConfig);
SmsFactory.refresh(SupplierType.JD_CLOUD);
SmsLoad.starConfig(jdCloudConfig,SupplierType.JD_CLOUD);
}
/**
* 设置 cloopenConfig
*/
public static void setCloopenConfig(CloopenConfig cloopenConfig) {
CloopenFactory.instance().setConfig(cloopenConfig);
SmsFactory.refresh(SupplierType.CLOOPEN);
SmsLoad.starConfig(cloopenConfig,SupplierType.CLOOPEN);
}
/**
* 设置 emayConfig
*/
public static void setEmayConfig(EmayConfig emayConfig) {
EmayFactory.instance().setConfig(emayConfig);
SmsFactory.refresh(SupplierType.EMAY);
SmsLoad.starConfig(emayConfig,SupplierType.EMAY);
}
/**
* 设置 ctyunConfig
*/
public static void setCtyunConfig(CtyunConfig ctyunConfig) {
CtyunFactory.instance().setConfig(ctyunConfig);
SmsFactory.refresh(SupplierType.CTYUN);
SmsLoad.starConfig(ctyunConfig,SupplierType.CTYUN);
}
/**
* 设置 neteaseConfig
*/
public static void setNeteaseConfig(NeteaseConfig neteaseConfig) {
NeteaseFactory.instance().setConfig(neteaseConfig);
SmsFactory.refresh(SupplierType.NETEASE);
SmsLoad.starConfig(neteaseConfig,SupplierType.NETEASE);
}
public static void setZhuTongConfig(ZhutongConfig zhutongConfig){
ZhutongFactory.instance().setConfig(zhutongConfig);
SmsFactory.refresh(SupplierType.ZHUTONG);
SmsLoad.starConfig(zhutongConfig,SupplierType.ZHUTONG);
}
}

View File

@ -0,0 +1,32 @@
package org.dromara.sms4j.core.datainterface;
import org.dromara.sms4j.provider.config.BaseConfig;
import java.util.List;
/**
* SmsReadConfig
* <p> 读取配置接口实现该接口中的方法则可以按照自己的形式进行配置的读取
* <p>这样只关注最终的配置数据而不关注配置的来源用户可以自由的选择数据来源的方式</p>
* <p>该种方式读取配置并非在启动阶段完成而是在方法第一次调用期间完成</p>
* @author :Wind
* 2023/8/1 12:06
**/
public interface SmsReadConfig {
/**
* getSupplierConfig
* <p> 通过配置ID获取一个厂商的配置
* @param configId 配置id
* @author :Wind
*/
BaseConfig getSupplierConfig(String configId);
/**
* getSupplierConfigList
* <p> 获取多个厂商的配置会同时加载进框架中
* @author :Wind
*/
List<BaseConfig> getSupplierConfigList();
}

View File

@ -1,141 +1,299 @@
package org.dromara.sms4j.core.factory;
import cn.hutool.core.util.StrUtil;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.smsProxy.SmsInvocationHandler;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.core.SupplierSqlConfig;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.core.datainterface.SmsReadConfig;
import org.dromara.sms4j.core.load.SmsLoad;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.dromara.sms4j.core.proxy.SmsInvocationHandler;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.dromara.sms4j.provider.factory.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.BeanFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* SmsFactory
* <p>构造工厂用于获取一个厂商的短信实现对象
* 在调用对应厂商的短信发送方法前请先确保你的配置已经实现否则无法发送该厂商对应的短信一般情况下厂商会回执因缺少的配置所造成的的异常不会
* 以java异常的形式抛出
* 在调用对应厂商的短信发送方法前请先确保你的配置已经实现否则无法发送该厂商对应的短信一般情况下厂商会回执因缺少的配置所造成的的异常组件
* 不会处理
*
* @author :Wind
* 2023/4/8 15:55
**/
public abstract class SmsFactory {
private static final Map<SupplierType, SmsBlend> beans = new HashMap<>();
/**
* <p>框架维护的所有短信服务对象</p>
* <p>key: configId短信服务对象的唯一标识</p>
* <p>value: 短信服务对象</p>
*/
private final static Map<String, SmsBlend> blends = new ConcurrentHashMap<>();
private SmsFactory() {
}
/**
* createSmsBlend
* <p>获取各个厂商的实现类
* <p>创建各个厂商的实现类
*
* @param supplierType 厂商枚举
* @author :Wind
*/
public static SmsBlend createSmsBlend(SupplierType supplierType) {
BaseProviderFactory providerFactory = supplierType.getProviderFactory();
return providerFactory.createSms(providerFactory.getConfig());
}
/**
* createSmsBlend
* <p>获取各个厂商的多例实现对象
* @param supplierType 厂商枚举
* @param config 短信配置
* @author :Wind
*/
public static SmsBlend createSmsBlend(SupplierType supplierType,SupplierConfig config){
BaseProviderFactory providerFactory = supplierType.getProviderFactory();
return providerFactory.createMultitonSms(config);
*/
public static void createSmsBlend(SupplierConfig config) {
SmsBlend smsBlend = create(config);
register(smsBlend);
}
/**
* createSmsBlend
* <p>通过配置读取接口创建某个短信实例
* <p>该方法创建的短信实例将会交给框架进行托管后续可以通过getSmsBlend获取
* <p>该方法会直接调用接口实现
*
* @param smsReadConfig 读取额外配置接口
* @param configId 配置ID
* @author :Wind
*/
public static void createSmsBlend(SmsReadConfig smsReadConfig, String configId) {
BaseConfig supplierConfig = smsReadConfig.getSupplierConfig(configId);
SmsBlend smsBlend = create(supplierConfig);
register(smsBlend);
}
/**
* createSmsBlend
* <p>获取负载均衡器中的短信实例
* createSmsBlend
* <p>通过配置读取接口创建全部短信实例
* <p>该方法创建的短信实例将会交给框架进行托管后续可以通过getSmsBlend获取
* <p>该方法会直接调用接口实现
*
* @param smsReadConfig 读取额外配置接口
* @author :Wind
*/
public static SmsBlend createSmsBlend(){
*/
public static void createSmsBlend(SmsReadConfig smsReadConfig) {
List<BaseConfig> supplierConfigList = smsReadConfig.getSupplierConfigList();
supplierConfigList.forEach(supplierConfig -> {
SmsBlend smsBlend = create(supplierConfig);
register(smsBlend);
});
}
/**
* createRestrictedSmsBlend
* <p> 创建一个指定厂商开启短信拦截后的实例拦截的参数取决于配置参数
*
* @param config 短信配置
* @author :Wind
*/
public static void createRestrictedSmsBlend(SupplierConfig config) {
SmsBlend smsBlend = create(config);
smsBlend = renderWithRestricted(smsBlend);
register(smsBlend);
}
/**
* createRestrictedSmsBlend
* <p>通过配置读取接口创建某个开启短信拦截后的短信实例
* <p>该方法创建的短信实例将会交给框架进行托管后续可以通过getSmsBlend获取
* <p>该方法会直接调用接口实现
*
* @param smsReadConfig 读取额外配置接口
* @param configId 配置ID
* @author :Wind
*/
public static void createRestrictedSmsBlend(SmsReadConfig smsReadConfig, String configId) {
BaseConfig supplierConfig = smsReadConfig.getSupplierConfig(configId);
SmsBlend smsBlend = create(supplierConfig);
smsBlend = renderWithRestricted(smsBlend);
register(smsBlend);
}
/**
* createRestrictedSmsBlend
* <p>通过配置读取接口创建全部开启短信拦截后的短信实例
* <p>该方法创建的短信实例将会交给框架进行托管后续可以通过getSmsBlend获取
* <p>该方法会直接调用接口实现
*
* @param smsReadConfig 读取额外配置接口
* @author :Wind
*/
public static void createRestrictedSmsBlend(SmsReadConfig smsReadConfig) {
List<BaseConfig> supplierConfigList = smsReadConfig.getSupplierConfigList();
supplierConfigList.forEach(supplierConfig -> {
SmsBlend smsBlend = create(supplierConfig);
smsBlend = renderWithRestricted(smsBlend);
register(smsBlend);
});
}
private static SmsBlend create(SupplierConfig config) {
BaseProviderFactory factory = ProviderFactoryHolder.requireForSupplier(config.getSupplier());
if (factory == null) {
throw new SmsBlendException("不支持当前供应商配置");
}
return factory.createSms(config);
}
/**
* renderWithRestricted
* <p> 构建smsBlend对象的代理对象
*
* @author :Wind
*/
private static SmsBlend renderWithRestricted(SmsBlend sms) {
SmsInvocationHandler smsInvocationHandler = SmsInvocationHandler.newSmsInvocationHandler(sms);
return (SmsBlend) Proxy.newProxyInstance(sms.getClass().getClassLoader(), new Class[]{SmsBlend.class}, smsInvocationHandler);
}
/**
* 通过负载均衡服务获取短信服务对象
*
* @return 返回短信服务列表
*/
public static SmsBlend getSmsBlend() {
return SmsLoad.getBeanLoad().getLoadServer();
}
/**
* refresh
* <p>刷新配置用于切换配置后的刷新防止因厂商sdk内部的保存导致配置更新不及时
* 此方法会造成一定的性能损失不建议经常性调用
* 通过configId获取短信服务对象
*
* @author :Wind
* @param configId 唯一标识
* @return 返回短信服务对象如果未找到则返回null
*/
public static void refresh() {
for(SupplierType type : SupplierType.values()) {
refresh(type);
public static SmsBlend getSmsBlend(String configId) {
return blends.get(configId);
}
/**
* 通过供应商标识获取单个短信服务对象
* <p>当供应商有多个短信服务对象时无法保证获取顺序</p>
*
* @param supplier 供应商标识
* @return 返回短信服务对象如果未找到则返回null
*/
public static SmsBlend getBySupplier(String supplier) {
if (StrUtil.isEmpty(supplier)) {
throw new SmsBlendException("供应商标识不能为空");
}
return blends.values().stream().filter(smsBlend -> supplier.equals(smsBlend.getSupplier())).findFirst().orElse(null);
}
/**
* refresh
* <p>根据厂商类型枚举刷新对应厂商的配置此方法不会刷新全部厂商的配置对象只会重构所选厂商的对象性能损失相对较小
* 通过供应商标识获取短信服务对象列表
*
* @param supplierType 厂商类型枚举
* @author :Wind
* @param supplier 供应商标识
* @return 返回短信服务对象列表如果未找到则返回空列表
*/
public static void refresh(SupplierType supplierType) {
BaseProviderFactory providerFactory = supplierType.getProviderFactory();
providerFactory.refresh(providerFactory.getConfig());
}
/**
* refreshSqlConfig
* <p>重新读取sql配置
*
* @author :Wind
*/
public static void refreshSqlConfig() {
SupplierSqlConfig.refreshSqlConfig();
}
/**
* getRestrictedSmsBlend
* <p>获取某个厂商的带有短信拦截的实现
*
* @param supplierType 厂商枚举
* @author :Wind
*/
public static SmsBlend getRestrictedSmsBlend(SupplierType supplierType) {
SmsBlend smsBlend = beans.get(supplierType);
if (Objects.isNull(smsBlend)) {
smsBlend = getSmsBlend(supplierType);
beans.put(supplierType, smsBlend);
public static List<SmsBlend> getListBySupplier(String supplier) {
List<SmsBlend> list = null;
if (StrUtil.isEmpty(supplier)) {
throw new SmsBlendException("供应商标识不能为空");
}
return smsBlend;
list = blends.values().stream().filter(smsBlend -> supplier.equals(smsBlend.getSupplier())).collect(Collectors.toList());
return list;
}
/**
* refreshRestrictedSmsBlend
* <p>刷新带有短信拦截的对象实现
* @param supplierType 厂商枚举
* @author :Wind
*/
public static void refreshRestrictedSmsBlend(SupplierType supplierType) {
refresh(supplierType);
beans.put(supplierType,getSmsBlend(supplierType));
* 获取全部短信服务对象
*
* @return 短信服务对象列表
*/
public static List<SmsBlend> getAll() {
return new ArrayList<>(blends.values());
}
private static SmsBlend getSmsBlend(SupplierType supplierType) {
SmsBlend sms = createSmsBlend(supplierType);
SmsInvocationHandler smsInvocationHandler = SmsInvocationHandler.newSmsInvocationHandler(
sms,
BeanFactory.getSmsConfig()
);
return (SmsBlend) Proxy.newProxyInstance(
sms.getClass().getClassLoader(),
new Class[]{SmsBlend.class},
smsInvocationHandler
);
/**
* 注册短信服务对象
*
* @param smsBlend 短信服务对象
*/
public static void register(SmsBlend smsBlend) {
if (smsBlend == null) {
throw new SmsBlendException("短信服务对象不能为空");
}
blends.put(smsBlend.getConfigId(), smsBlend);
SmsLoad.starConfig(smsBlend, 1);
}
/**
* 注册短信服务对象
*
* @param smsBlend 短信服务对象
*/
public static void register(SmsBlend smsBlend, Integer weight) {
if (smsBlend == null) {
throw new SmsBlendException("短信服务对象不能为空");
}
blends.put(smsBlend.getConfigId(), smsBlend);
SmsLoad.starConfig(smsBlend, weight);
}
/**
* 以configId为标识当短信服务对象不存在时进行注册
*
* @param smsBlend 短信服务对象
* @return 是否注册成功
* <p>当对象不存在时进行注册并返回true</p>
* <p>当对象已存在时返回false</p>
*/
public static boolean registerIfAbsent(SmsBlend smsBlend) {
if (smsBlend == null) {
throw new SmsBlendException("短信服务对象不能为空");
}
String configId = smsBlend.getConfigId();
if (blends.containsKey(configId)) {
return false;
}
blends.put(configId, smsBlend);
SmsLoad.starConfig(smsBlend, 1);
return true;
}
/**
* registerIfAbsent
* <p> 以configId为标识当短信服务对象不存在时进行注册并添加至系统的负载均衡器
*
* @param smsBlend 短信服务对象
* @param weight 权重
* @return 是否注册成功
* <p>当对象不存在时进行注册并返回true</p>
* <p>当对象已存在时返回false</p>
* @author :Wind
*/
public static boolean registerIfAbsent(SmsBlend smsBlend, Integer weight) {
if (smsBlend == null) {
throw new SmsBlendException("短信服务对象不能为空");
}
String configId = smsBlend.getConfigId();
if (blends.containsKey(configId)) {
return false;
}
blends.put(configId, smsBlend);
SmsLoad.starConfig(smsBlend, weight);
return true;
}
/**
* 注销短信服务对象
* <p>与此同时会注销掉负载均衡器中已经存在的对象</p>
*
* @param configId 标识
* @return 是否注销成功
* <p>当configId存在时进行注销并返回true</p>
* <p>当configId不存在时返回false</p>
*/
public static boolean unregister(String configId) {
SmsBlend blend = blends.remove(configId);
SmsLoad.getBeanLoad().removeLoadServer(blend);
return blend != null;
}
}

View File

@ -3,11 +3,8 @@ package org.dromara.sms4j.core.load;
import cn.hutool.core.bean.BeanUtil;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -22,9 +19,6 @@ public class SmsLoad {
// 服务器列表每个服务器有一个权重和当前权重
private final List<LoadServer> LoadServers = new ArrayList<>();
// 实例列表
private static final Map<Object, SmsBlend> smsBlendMap = new HashMap<>();
private static final SmsLoad smsLoad = new SmsLoad();
private SmsLoad() {
@ -96,18 +90,20 @@ public class SmsLoad {
* starConfig
* <p> 创建smsBlend并加入到负载均衡器
*
* @param smsBlend 短信服务
* @param supplierConfig 厂商配置
* @param supplierType 厂商枚举
* @author :Wind
*/
public static void starConfig(SupplierConfig supplierConfig, SupplierType supplierType) {
BaseProviderFactory providerFactory = supplierType.getProviderFactory();
SmsBlend smsBlend = providerFactory.createMultitonSms(supplierConfig);
public static void starConfig(SmsBlend smsBlend, SupplierConfig supplierConfig) {
Map<String, Object> supplierConfigMap = BeanUtil.beanToMap(supplierConfig);
Object weight = supplierConfigMap.getOrDefault("weight", 1);
smsLoad.addLoadServer(smsBlend, Integer.parseInt(weight.toString()));
}
public static void starConfig(SmsBlend smsBlend,Integer weight) {
smsLoad.addLoadServer(smsBlend,weight);
}
public static SmsLoad getBeanLoad() {
return smsLoad;
}

View File

@ -0,0 +1,58 @@
package org.dromara.sms4j.core.proxy;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.dao.SmsDao;
import org.dromara.sms4j.api.proxy.RestrictedProcess;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.provider.factory.BeanFactory;
import java.util.Objects;
@Slf4j
public class RestrictedProcessDefaultImpl implements RestrictedProcess {
static Long minTimer = 60 * 1000L;
static Long accTimer = 24 * 60 * 60 * 1000L;
/**
* 缓存实例
*/
@Setter
private SmsDao smsDao;
public SmsBlendException process(String phone) throws Exception {
if (Objects.isNull(smsDao)) {
throw new SmsBlendException("The dao tool could not be found");
}
SmsConfig config = BeanFactory.getSmsConfig();
Integer accountMax = config.getAccountMax(); // 每日最大发送量
Integer minuteMax = config.getMinuteMax(); // 每分钟最大发送量
if (SmsUtil.isNotEmpty(accountMax)) { // 是否配置了每日限制
Integer i = (Integer) smsDao.get(phone + "max");
if (SmsUtil.isEmpty(i)) {
smsDao.set(phone + "max", 1, accTimer);
} else if (i >= accountMax) {
log.info("The phone:" + phone + ",number of short messages reached the maximum today");
return new SmsBlendException("The phone:" + phone + ",number of short messages reached the maximum today");
} else {
smsDao.set(phone + "max", i + 1, accTimer);
}
}
if (SmsUtil.isNotEmpty(minuteMax)) { // 是否配置了每分钟最大限制
Integer o = (Integer) smsDao.get(phone);
if (SmsUtil.isNotEmpty(o)) {
if (o < minuteMax) {
smsDao.set(phone, o + 1, minTimer);
} else {
log.info("The phone:" + phone + " Text messages are sent too often");
return new SmsBlendException("The phone:", phone + " Text messages are sent too often");
}
} else {
smsDao.set(phone, 1, minTimer);
}
}
return null;
}
}

View File

@ -1,8 +1,8 @@
package org.dromara.sms4j.api.smsProxy;
package org.dromara.sms4j.core.proxy;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.comm.config.SmsConfig;
import org.dromara.sms4j.api.proxy.RestrictedProcess;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import java.lang.reflect.InvocationHandler;
@ -12,16 +12,14 @@ import java.util.Objects;
@Slf4j
public class SmsInvocationHandler implements InvocationHandler {
private final SmsBlend smsBlend;
private final SmsConfig config;
private static RestrictedProcess restrictedProcess = new RestrictedProcess();
private static RestrictedProcess restrictedProcess = new RestrictedProcessDefaultImpl();
private SmsInvocationHandler(SmsBlend smsBlend, SmsConfig config) {
private SmsInvocationHandler(SmsBlend smsBlend) {
this.smsBlend = smsBlend;
this.config = config;
}
public static SmsInvocationHandler newSmsInvocationHandler(SmsBlend smsBlend, SmsConfig config){
return new SmsInvocationHandler(smsBlend,config);
public static SmsInvocationHandler newSmsInvocationHandler(SmsBlend smsBlend) {
return new SmsInvocationHandler(smsBlend);
}
@Override
@ -30,7 +28,7 @@ public class SmsInvocationHandler implements InvocationHandler {
if ("sendMessage".equals(method.getName()) || "massTexting".equals(method.getName())) {
//取手机号作为参数
String phone = (String) objects[0];
SmsBlendException smsBlendException = restrictedProcess.process(config,phone);
SmsBlendException smsBlendException = restrictedProcess.process(phone);
if (!Objects.isNull(smsBlendException)) {
throw smsBlendException;
}

View File

@ -2,26 +2,40 @@ package org.dromara.sms4j.javase.config;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.cloopen.config.CloopenConfig;
import org.dromara.sms4j.comm.config.SmsConfig;
import org.dromara.sms4j.aliyun.config.AlibabaFactory;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.dao.SmsDao;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.cloopen.config.CloopenFactory;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.core.proxy.RestrictedProcessDefaultImpl;
import org.dromara.sms4j.core.proxy.SmsInvocationHandler;
import org.dromara.sms4j.ctyun.config.CtyunFactory;
import org.dromara.sms4j.emay.config.EmayFactory;
import org.dromara.sms4j.huawei.config.HuaweiFactory;
import org.dromara.sms4j.jdcloud.config.JdCloudFactory;
import org.dromara.sms4j.netease.config.NeteaseFactory;
import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.core.config.SupplierFactory;
import org.dromara.sms4j.emay.config.EmayConfig;
import org.dromara.sms4j.huawei.config.HuaweiConfig;
import org.dromara.sms4j.provider.factory.BeanFactory;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.javase.util.YamlUtil;
import org.dromara.sms4j.jdcloud.config.JdCloudConfig;
import org.dromara.sms4j.netease.config.NeteaseConfig;
import org.dromara.sms4j.tencent.config.TencentConfig;
import org.dromara.sms4j.unisms.config.UniConfig;
import org.dromara.sms4j.yunpian.config.YunpianConfig;
import org.dromara.sms4j.zhutong.config.ZhutongConfig;
import org.dromara.sms4j.provider.factory.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import org.dromara.sms4j.tencent.config.TencentFactory;
import org.dromara.sms4j.unisms.config.UniFactory;
import org.dromara.sms4j.yunpian.config.YunPianFactory;
import org.dromara.sms4j.zhutong.config.ZhutongFactory;
import java.util.Map;
/**
* 初始化类
@ -46,126 +60,15 @@ public class SEInitializer {
return this;
}
/**
* 初始化阿里配置
*
* @param alibabaConfig 阿里配置
* @return 当前初始化类实例
*/
public SEInitializer initAlibaba(AlibabaConfig alibabaConfig) {
BeanUtil.copyProperties(alibabaConfig, SupplierFactory.getAlibabaConfig());
return this;
}
/**
* 初始化容连云配置
*
* @param cloopenConfig 容连云配置
* @return 当前初始化类实例
*/
public SEInitializer initCloopen(CloopenConfig cloopenConfig) {
BeanUtil.copyProperties(cloopenConfig, SupplierFactory.getCloopenConfig());
return this;
}
/**
* 初始化亿美软通配置
*
* @param emayConfig 亿美软通配置
* @return 当前初始化类实例
*/
public SEInitializer initEmay(EmayConfig emayConfig) {
BeanUtil.copyProperties(emayConfig, SupplierFactory.getEmayConfig());
return this;
}
/**
* 初始化华为配置
*
* @param huaweiConfig 华为配置
* @return 当前初始化类实例
*/
public SEInitializer initHuawei(HuaweiConfig huaweiConfig) {
BeanUtil.copyProperties(huaweiConfig, SupplierFactory.getHuaweiConfig());
return this;
}
/**
* 初始化京东配置
*
* @param jdCloudConfig 京东配置
* @return 当前初始化类实例
*/
public SEInitializer initJdCloud(JdCloudConfig jdCloudConfig) {
BeanUtil.copyProperties(jdCloudConfig, SupplierFactory.getJdCloudConfig());
return this;
}
/**
* 初始化腾讯配置
*
* @param tencentConfig 腾讯配置
* @return 当前初始化类实例
*/
public SEInitializer initTencent(TencentConfig tencentConfig) {
BeanUtil.copyProperties(tencentConfig, SupplierFactory.getTencentConfig());
return this;
}
/**
* 初始化合一配置
*
* @param uniConfig 合一配置
* @return 当前初始化类实例
*/
public SEInitializer initUniSms(UniConfig uniConfig) {
BeanUtil.copyProperties(uniConfig, SupplierFactory.getUniConfig());
return this;
}
/**
* 初始化云片配置
*
* @param yunpianConfig 云片配置
* @return 当前初始化类实例
*/
public SEInitializer initYunpian(YunpianConfig yunpianConfig) {
BeanUtil.copyProperties(yunpianConfig, SupplierFactory.getYunpianConfig());
return this;
}
/**
* initializer
* <p>初始化网易云短信配置
*
* @return 当前初始化类实例
* @author :Wind
*/
public SEInitializer initNetase(NeteaseConfig neteaseConfig) {
BeanUtil.copyProperties(neteaseConfig, SupplierFactory.getNeteaseConfig());
return this;
}
/**
* initZhuTong
* <p>初始化助通短信配置
*
* @return 当前初始化类实例
* @author :Wind
*/
public SEInitializer initZhuTong(ZhutongConfig zhutongConfig) {
BeanUtil.copyProperties(zhutongConfig, SupplierFactory.getZhutongConfig());
return this;
}
/**
* 默认从sms-aggregation.yml文件中读取配置
*
* @return
*/
public void fromYaml() {
public SEInitializer fromYaml() {
ClassPathResource yamlResouce = new ClassPathResource("sms4j.yml");
this.fromYaml(yamlResouce.readUtf8Str());
return this;
}
/**
@ -173,9 +76,10 @@ public class SEInitializer {
*
* @param yaml yaml配置字符串
*/
public void fromYaml(String yaml) {
public SEInitializer fromYaml(String yaml) {
InitConfig config = YamlUtil.toBean(yaml, InitConfig.class);
this.initConfig(config);
return this;
}
/**
@ -183,9 +87,33 @@ public class SEInitializer {
*
* @param json json配置字符串
*/
public void fromJson(String json) {
public SEInitializer fromJson(String json) {
InitConfig config = JSONUtil.toBean(json, InitConfig.class);
this.initConfig(config);
return this;
}
/**
* 注册供应商工厂
* @param factory
*/
public SEInitializer registerFactory(BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> factory) {
ProviderFactoryHolder.registerFactory(factory);
return this;
}
/**
* 注册DAO实例
* @param smsDao
*/
public SEInitializer registerSmsDao(SmsDao smsDao) {
if(smsDao == null) {
throw new SmsBlendException("注册DAO实例失败实例不能为空");
}
RestrictedProcessDefaultImpl process = new RestrictedProcessDefaultImpl();
process.setSmsDao(smsDao);
SmsInvocationHandler.setRestrictedProcess(process);
return this;
}
private void initConfig(InitConfig config) {
@ -199,49 +127,52 @@ public class SEInitializer {
throw new SmsBlendException("初始化配置失败");
}
//注册默认工厂
registerDefaultFactory();
//初始化SmsConfig整体配置文件
this.initSmsConfig(smsConfig);
AlibabaConfig alibabaConfig = smsConfig.getAlibaba();
if (alibabaConfig != null) {
this.initAlibaba(alibabaConfig);
}
CloopenConfig cloopenConfig = smsConfig.getCloopen();
if (cloopenConfig != null) {
this.initCloopen(cloopenConfig);
}
EmayConfig emayConfig = smsConfig.getEmay();
if (emayConfig != null) {
this.initEmay(emayConfig);
}
HuaweiConfig huaweiConfig = smsConfig.getHuawei();
if (huaweiConfig != null) {
this.initHuawei(huaweiConfig);
}
JdCloudConfig jdCloudConfig = smsConfig.getJdCloud();
if (jdCloudConfig != null) {
this.initJdCloud(jdCloudConfig);
}
TencentConfig tencentConfig = smsConfig.getTencent();
if (tencentConfig != null) {
this.initTencent(tencentConfig);
}
UniConfig uniConfig = smsConfig.getUni();
if (uniConfig != null) {
this.initUniSms(uniConfig);
}
YunpianConfig yunpianConfig = smsConfig.getYunpian();
if (yunpianConfig != null) {
this.initYunpian(yunpianConfig);
}
NeteaseConfig neteaseConfig = smsConfig.getNeteaseConfig();
if (neteaseConfig != null){
this.initNetase(neteaseConfig);
}
ZhutongConfig zhutongConfig = smsConfig.getZhutongConfig();
if (zhutongConfig != null){
this.initZhuTong(zhutongConfig);
// 解析供应商配置
Map<String, Map<String, Object>> blends = smsConfig.getBlends();
for(String configId : blends.keySet()) {
Map<String, Object> configMap = blends.get(configId);
Object supplierObj = configMap.get(Constant.SUPPLIER_KEY);
String supplier = supplierObj == null ? "" : String.valueOf(supplierObj);
supplier = StrUtil.isEmpty(supplier) ? configId : supplier;
BaseProviderFactory<SmsBlend, SupplierConfig> providerFactory = (BaseProviderFactory<SmsBlend, SupplierConfig>) ProviderFactoryHolder.requireForSupplier(supplier);
if(providerFactory == null) {
log.warn("创建\"{}\"的短信服务失败,未找到供应商为\"{}\"的服务", configId, supplier);
continue;
}
configMap.put("config-id", configId);
SmsUtil.replaceKeysSeperator(configMap, "-", "_");
JSONObject configJson = new JSONObject(configMap);
SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass());
if(Boolean.TRUE.equals(smsConfig.getRestricted())) {
SmsFactory.createRestrictedSmsBlend(supplierConfig);
} else {
SmsFactory.createSmsBlend(supplierConfig);
}
}
}
/**
* 注册默认工厂实例
*/
private void registerDefaultFactory() {
ProviderFactoryHolder.registerFactory(AlibabaFactory.instance());
ProviderFactoryHolder.registerFactory(CloopenFactory.instance());
ProviderFactoryHolder.registerFactory(CtyunFactory.instance());
ProviderFactoryHolder.registerFactory(EmayFactory.instance());
ProviderFactoryHolder.registerFactory(HuaweiFactory.instance());
ProviderFactoryHolder.registerFactory(JdCloudFactory.instance());
ProviderFactoryHolder.registerFactory(NeteaseFactory.instance());
ProviderFactoryHolder.registerFactory(TencentFactory.instance());
ProviderFactoryHolder.registerFactory(UniFactory.instance());
ProviderFactoryHolder.registerFactory(YunPianFactory.instance());
ProviderFactoryHolder.registerFactory(ZhutongFactory.instance());
}
/**
* 初始化配置bean
*/
@ -259,16 +190,7 @@ public class SEInitializer {
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public static class InitSmsConfig extends SmsConfig {
private AlibabaConfig alibaba;
private CloopenConfig cloopen;
private EmayConfig emay;
private HuaweiConfig huawei;
private JdCloudConfig jdCloud;
private TencentConfig tencent;
private UniConfig uni;
private YunpianConfig yunpian;
private NeteaseConfig neteaseConfig;
private ZhutongConfig zhutongConfig;
private Map<String, Map<String, Object>> blends;
}
}

View File

@ -24,6 +24,7 @@
<dependency>
<groupId>com.jdcloud.sdk</groupId>
<artifactId>sms</artifactId>
<scope>provided</scope>
</dependency>
<dependency>

View File

@ -6,18 +6,16 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.aliyun.service.AlibabaSmsImpl;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.provider.config.BaseConfig;
/**
* @author Wind
*/
@Data
@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class AlibabaConfig extends BaseConfig implements SupplierConfig {
@Data
public class AlibabaConfig extends BaseConfig {
/**
* 模板变量名称
@ -27,24 +25,31 @@ public class AlibabaConfig extends BaseConfig implements SupplierConfig {
/**
* 请求地址
*/
@Builder.Default
private String requestUrl = "dysmsapi.aliyuncs.com";
/**
* 接口名称
*/
@Builder.Default
private String action = "SendSms";
/**
* 接口版本号
*/
@Builder.Default
private String version = "2017-05-25";
/**
* 地域信息默认为 cn-hangzhou
*/
@Builder.Default
private String regionId = "cn-hangzhou";
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return AlibabaSmsImpl.SUPPLIER;
}
}

View File

@ -1,9 +1,10 @@
package org.dromara.sms4j.aliyun.config;
import lombok.extern.slf4j.Slf4j;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.aliyun.service.AlibabaSmsImpl;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
/**
* AlibabaSmsConfig
@ -12,20 +13,11 @@ import org.dromara.sms4j.provider.base.BaseProviderFactory;
* @author :Wind
* 2023/4/8 14:54
**/
@Slf4j
public class AlibabaFactory implements BaseProviderFactory<AlibabaSmsImpl, AlibabaConfig> {
private static AlibabaSmsImpl alibabaSms;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AlibabaFactory extends AbstractProviderFactory<AlibabaSmsImpl, AlibabaConfig> {
private static final AlibabaFactory INSTANCE = new AlibabaFactory();
private static final class ConfigHolder {
private static AlibabaConfig config = AlibabaConfig.builder().build();
}
private AlibabaFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -41,45 +33,16 @@ public class AlibabaFactory implements BaseProviderFactory<AlibabaSmsImpl, Aliba
*/
@Override
public AlibabaSmsImpl createSms(AlibabaConfig alibabaConfig) {
if (alibabaSms == null) {
alibabaSms = createMultitonSms(alibabaConfig);
}
return alibabaSms;
}
@Override
public AlibabaSmsImpl createMultitonSms(AlibabaConfig alibabaConfig) {
return new AlibabaSmsImpl(alibabaConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return new AlibabaSmsImpl(alibabaConfig);
}
/**
* 刷新短信实现对象
* @param alibabaConfig 短信配置对象
* @return 刷新后的短信实现对象
* 获取供应商
* @return 供应商
*/
@Override
public AlibabaSmsImpl refresh(AlibabaConfig alibabaConfig) {
//重新构造一个实现对象
alibabaSms = new AlibabaSmsImpl(alibabaConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return alibabaSms;
}
/**
* 获取配置
* @return 配置对象
*/
@Override
public AlibabaConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(AlibabaConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return AlibabaSmsImpl.SUPPLIER;
}
}

View File

@ -1,21 +1,20 @@
package org.dromara.sms4j.aliyun.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.aliyun.utils.AliyunUtils;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
@ -26,9 +25,10 @@ import java.util.concurrent.Executor;
* 2023/3/26 17:16
**/
@Slf4j
public class AlibabaSmsImpl extends AbstractSmsBlend {
public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
private final AlibabaConfig config;
public static final String SUPPLIER = "alibaba";
private int retry = 0;
/**
* AlibabaSmsImpl
@ -37,35 +37,43 @@ public class AlibabaSmsImpl extends AbstractSmsBlend {
* @author :Wind
*/
public AlibabaSmsImpl(AlibabaConfig config, Executor pool, DelayedTime delayedTime) {
super(pool, delayedTime);
this.config = config;
super(config, pool, delayedTime);
}
/**
* AlibabaSmsImpl
* <p>构造器用于构造短信实现模块
*/
public AlibabaSmsImpl(AlibabaConfig config) {
super(config);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put(config.getTemplateName(), message);
return sendMessage(phone, config.getTemplateId(), map);
map.put(getConfig().getTemplateName(), message);
return sendMessage(phone, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(phone, messageStr, templateId);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put(config.getTemplateName(), message);
return massTexting(phones, config.getTemplateId(), map);
map.put(getConfig().getTemplateName(), message);
return massTexting(phones, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(SmsUtil.arrayToString(phones), messageStr, templateId);
@ -75,27 +83,39 @@ public class AlibabaSmsImpl extends AbstractSmsBlend {
String requestUrl;
String paramStr;
try {
requestUrl = AliyunUtils.generateSendSmsRequestUrl(this.config, message, phone, templateId);
paramStr = AliyunUtils.generateParamBody(config, phone, message, templateId);
requestUrl = AliyunUtils.generateSendSmsRequestUrl(getConfig(), message, phone, templateId);
paramStr = AliyunUtils.generateParamBody(getConfig(), phone, message, templateId);
} catch (Exception e) {
log.error("aliyun send message error", e);
throw new SmsBlendException(e.getMessage());
}
log.debug("requestUrl {}", requestUrl);
try(HttpResponse response = HttpRequest.post(requestUrl)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(paramStr)
.execute()){
JSONObject body = JSONUtil.parseObj(response.body());
return this.getResponse(body);
try {
Map<String, String> headers = new LinkedHashMap<>(1);
headers.put("Content-Type", Constant.FROM_URLENCODED);
SmsResponse smsResponse = getResponse(http.postJson(requestUrl, headers, paramStr));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phone, message, templateId);
}catch (SmsBlendException e){
return requestRetry(phone, message, templateId);
}
}
private SmsResponse requestRetry(String phone, String message, String templateId) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return getSmsResponse(phone, message, templateId);
}
private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess("OK".equals(resJson.getStr("Code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}

View File

@ -6,8 +6,8 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.cloopen.service.CloopenSmsImpl;
import org.dromara.sms4j.provider.config.BaseConfig;
/**
* 容联云短信配置属性
@ -16,11 +16,8 @@ import org.dromara.sms4j.comm.config.BaseConfig;
* @since 2023/4/10 22:10
*/
@Data
@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class CloopenConfig extends BaseConfig implements SupplierConfig {
public class CloopenConfig extends BaseConfig {
/**
* 应用 ID
@ -30,7 +27,6 @@ public class CloopenConfig extends BaseConfig implements SupplierConfig {
/**
* REST API Base URL
*/
@Builder.Default
private String baseUrl = "https://app.cloopen.com:8883/2013-12-26";
/**
@ -50,4 +46,14 @@ public class CloopenConfig extends BaseConfig implements SupplierConfig {
*/
@Deprecated
private String serverPort;
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return CloopenSmsImpl.SUPPLIER;
}
}

View File

@ -3,8 +3,8 @@ package org.dromara.sms4j.cloopen.config;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.cloopen.service.CloopenSmsImpl;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
/**
* 容联云短信配置
@ -13,16 +13,10 @@ import org.dromara.sms4j.provider.base.BaseProviderFactory;
* @since 2023/4/10 22:10
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CloopenFactory implements BaseProviderFactory<CloopenSmsImpl, CloopenConfig> {
private static CloopenSmsImpl cloopenSms;
public class CloopenFactory extends AbstractProviderFactory<CloopenSmsImpl, CloopenConfig> {
private static final CloopenFactory INSTANCE = new CloopenFactory();
private static final class ConfigHolder {
private static CloopenConfig config = CloopenConfig.builder().build();
}
/**
* 获取建造者实例
* @return 建造者实例
@ -38,45 +32,16 @@ public class CloopenFactory implements BaseProviderFactory<CloopenSmsImpl, Cloop
*/
@Override
public CloopenSmsImpl createSms(CloopenConfig cloopenConfig) {
if (cloopenSms == null) {
cloopenSms = createMultitonSms(cloopenConfig);
}
return cloopenSms;
}
@Override
public CloopenSmsImpl createMultitonSms(CloopenConfig cloopenConfig) {
return new CloopenSmsImpl(cloopenConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return new CloopenSmsImpl(cloopenConfig);
}
/**
* 刷新容连云短信实现对象
* @param cloopenConfig 短信配置对象
* @return 刷新后的短信实现对象
* 获取供应商
* @return 供应商
*/
@Override
public CloopenSmsImpl refresh(CloopenConfig cloopenConfig) {
//重新构造一个实现对象
cloopenSms = new CloopenSmsImpl(cloopenConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return cloopenSms;
}
/**
* 获取配置
* @return 配置对象
*/
@Override
public CloopenConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(CloopenConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return CloopenSmsImpl.SUPPLIER;
}
}

View File

@ -1,14 +1,12 @@
package org.dromara.sms4j.cloopen.service;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.cloopen.config.CloopenConfig;
import org.dromara.sms4j.cloopen.util.CloopenHelper;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import java.util.Collections;
import java.util.LinkedHashMap;
@ -23,42 +21,46 @@ import java.util.concurrent.Executor;
* @since 2023/4/10 22:10
*/
@Slf4j
public class CloopenSmsImpl extends AbstractSmsBlend {
public class CloopenSmsImpl extends AbstractSmsBlend<CloopenConfig> {
private final CloopenConfig config;
public static final String SUPPLIER = "cloopen";
public CloopenSmsImpl(CloopenConfig config, Executor pool, DelayedTime delayed) {
super(pool,delayed);
this.config = config;
super(config, pool, delayed);
}
public CloopenSmsImpl(CloopenConfig config) {
super(config);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
return massTexting(Collections.singletonList(phone), message);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
return massTexting(Collections.singletonList(phone), templateId, messages);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(IdUtil.fastSimpleUUID(), message);
return massTexting(phones, config.getTemplateId(), map);
return massTexting(phones, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
CloopenHelper helper = new CloopenHelper(config);
Map<String, Object> paramMap = MapUtil.newHashMap(4);
CloopenHelper helper = new CloopenHelper(getConfig(), http);
Map<String, Object> paramMap = new LinkedHashMap<>(4);
paramMap.put("to", String.join(",", phones));
paramMap.put("appId", config.getAppId());
paramMap.put("appId", getConfig().getAppId());
paramMap.put("templateId", templateId);
paramMap.put("datas", messages.keySet().stream().map(messages::get).toArray(String[]::new));
return helper.smsResponse(paramMap);

View File

@ -4,14 +4,16 @@ import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.cloopen.config.CloopenConfig;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsHttpUtil;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@ -20,33 +22,48 @@ import java.util.Map;
* @author Charles7c
* @since 2023/4/17 20:57
*/
@Slf4j
public class CloopenHelper {
private final CloopenConfig config;
private final SmsHttpUtil http;
private int retry = 0;
public CloopenHelper(CloopenConfig config) {
public CloopenHelper(CloopenConfig config, SmsHttpUtil http) {
this.config = config;
this.http = http;
}
public SmsResponse smsResponse(Map<String, Object> paramMap){
String timestamp = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
try {
String timestamp = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
String url = String.format("%s/Accounts/%s/SMS/TemplateSMS?sig=%s",
config.getBaseUrl(),
config.getAccessKeyId(),
this.generateSign(config.getAccessKeyId(), config.getAccessKeySecret(), timestamp));
try(HttpResponse response = HttpRequest.post(url)
.header("Accept", "application/json")
.header("Content-Type", "application/json;charset=utf-8")
.header("Authorization", this.generateAuthorization(config.getAccessKeyId(), timestamp))
.body(JSONUtil.toJsonStr(paramMap))
.execute()){
JSONObject body = JSONUtil.parseObj(response.body());
return this.getResponse(body);
String url = String.format("%s/Accounts/%s/SMS/TemplateSMS?sig=%s",
config.getBaseUrl(),
config.getAccessKeyId(),
this.generateSign(config.getAccessKeyId(), config.getAccessKeySecret(), timestamp));
Map<String, String> headers = new LinkedHashMap<>(3);
headers.put("Accept", Constant.ACCEPT);
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8);
headers.put("Authorization", this.generateAuthorization(config.getAccessKeyId(), timestamp));
SmsResponse smsResponse = getResponse(http.postJson(url, headers, paramMap));
if(smsResponse.isSuccess() || retry == config.getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(paramMap);
}catch (SmsBlendException e){
return requestRetry(paramMap);
}
}
private SmsResponse requestRetry(Map<String, Object> paramMap) {
http.safeSleep(config.getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return smsResponse(paramMap);
}
private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess("000000".equals(resJson.getStr("statusCode")));

View File

@ -6,8 +6,8 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.ctyun.service.CtyunSmsImpl;
import org.dromara.sms4j.provider.config.BaseConfig;
/**
* <p>类名: CtyunConfig
@ -17,11 +17,8 @@ import org.dromara.sms4j.comm.config.BaseConfig;
* 2023/5/12 15:06
**/
@Data
@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class CtyunConfig extends BaseConfig implements SupplierConfig {
public class CtyunConfig extends BaseConfig {
/**
* 模板变量名称
@ -31,12 +28,20 @@ public class CtyunConfig extends BaseConfig implements SupplierConfig {
/**
* 请求地址
*/
@Builder.Default
private String requestUrl = "https://sms-global.ctapi.ctyun.cn/sms/api/v1";
/**
* 接口名称
*/
@Builder.Default
private String action = "SendSms";
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return CtyunSmsImpl.SUPPLIER;
}
}

View File

@ -1,9 +1,10 @@
package org.dromara.sms4j.ctyun.config;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.comm.factory.BeanFactory;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.ctyun.service.CtyunSmsImpl;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
/**
* <p>类名: CtyunSmsConfig
@ -12,20 +13,11 @@ import org.dromara.sms4j.provider.base.BaseProviderFactory;
* @author :bleachhtred
* 2023/5/12 15:06
**/
@Slf4j
public class CtyunFactory implements BaseProviderFactory<CtyunSmsImpl, CtyunConfig> {
private static CtyunSmsImpl ctyunSms;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CtyunFactory extends AbstractProviderFactory<CtyunSmsImpl, CtyunConfig> {
private static final CtyunFactory INSTANCE = new CtyunFactory();
private static final class ConfigHolder {
private static CtyunConfig config = CtyunConfig.builder().build();
}
private CtyunFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -42,45 +34,16 @@ public class CtyunFactory implements BaseProviderFactory<CtyunSmsImpl, CtyunConf
*/
@Override
public CtyunSmsImpl createSms(CtyunConfig ctyunConfig) {
if (ctyunSms == null) {
ctyunSms = createMultitonSms(ctyunConfig);
}
return ctyunSms;
}
@Override
public CtyunSmsImpl createMultitonSms(CtyunConfig ctyunConfig) {
return new CtyunSmsImpl(ctyunConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return new CtyunSmsImpl(ctyunConfig);
}
/**
* refresh
* <p> 刷新对象
*
* @author :bleachhtred
* 获取供应商
* @return 供应商
*/
@Override
public CtyunSmsImpl refresh(CtyunConfig ctyunConfig) {
//重新构造一个实现对象
ctyunSms = new CtyunSmsImpl( ctyunConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return ctyunSms;
public String getSupplier() {
return CtyunSmsImpl.SUPPLIER;
}
/**
* 获取配置
* @return 配置对象
*/
@Override
public CtyunConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(CtyunConfig config) {
ConfigHolder.config = config;
}
}

View File

@ -1,18 +1,15 @@
package org.dromara.sms4j.ctyun.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.ctyun.config.CtyunConfig;
import org.dromara.sms4j.ctyun.utils.CtyunUtils;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import java.util.LinkedHashMap;
import java.util.List;
@ -26,40 +23,46 @@ import java.util.concurrent.Executor;
* 2023/5/12 15:06
**/
@Slf4j
public class CtyunSmsImpl extends AbstractSmsBlend {
public class CtyunSmsImpl extends AbstractSmsBlend<CtyunConfig> {
private final CtyunConfig config;
public static final String SUPPLIER = "ctyun";
private int retry = 0;
public CtyunSmsImpl(CtyunConfig config, Executor pool, DelayedTime delayedTime) {
super(pool, delayedTime);
this.config = config;
super(config, pool, delayedTime);
}
public CtyunSmsImpl(CtyunConfig config) {
super(config);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put(config.getTemplateName(), message);
return sendMessage(phone, config.getTemplateId(), map);
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message);
return sendMessage(phone, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(phone, messageStr, templateId);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put(config.getTemplateName(), message);
return massTexting(phones, config.getTemplateId(), map);
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message);
return massTexting(phones, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(SmsUtil.arrayToString(phones), messageStr, templateId);
@ -69,27 +72,39 @@ public class CtyunSmsImpl extends AbstractSmsBlend {
String requestUrl;
String paramStr;
try {
requestUrl = config.getRequestUrl();
paramStr = CtyunUtils.generateParamJsonStr(config, phone, message, templateId);
requestUrl = getConfig().getRequestUrl();
paramStr = CtyunUtils.generateParamJsonStr(getConfig(), phone, message, templateId);
} catch (Exception e) {
log.error("ctyun send message error", e);
throw new SmsBlendException(e.getMessage());
}
log.debug("requestUrl {}", requestUrl);
try(HttpResponse response = HttpRequest.post(requestUrl)
.addHeaders(CtyunUtils.signHeader(paramStr, config.getAccessKeyId(), config.getAccessKeySecret()))
.body(paramStr)
.execute()){
JSONObject body = JSONUtil.parseObj(response.body());
return this.getResponse(body);
try {
SmsResponse smsResponse = getResponse(http.postJson(requestUrl,
CtyunUtils.signHeader(paramStr, getConfig().getAccessKeyId(), getConfig().getAccessKeySecret()),
paramStr));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phone, message, templateId);
}catch (SmsBlendException e){
return requestRetry(phone, message, templateId);
}
}
private SmsResponse requestRetry(String phone, String message, String templateId) {
http.safeSleep(getConfig().getRetryInterval());
retry ++;
log.warn("短信第 {" + retry + "} 次重新发送");
return getSmsResponse(phone, message, templateId);
}
private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess("OK".equals(resJson.getStr("code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}

View File

@ -6,6 +6,7 @@ import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.ctyun.config.CtyunConfig;
@ -35,7 +36,7 @@ public class CtyunUtils {
* 获取签名请求头
*/
public static Map<String, String> signHeader(String body, String key, String secret){
Map<String, String> map = new ConcurrentHashMap<>();
Map<String, String> map = new ConcurrentHashMap<>(4);
// 构造时间戳
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
@ -58,7 +59,7 @@ public class CtyunUtils {
// 构造签名
String signature = Base64.encode(hmacSHA256(signatureStr.getBytes(StandardCharsets.UTF_8), kDate));
String signHeader = String.format("%s Headers=ctyun-eop-request-id;eop-date Signature=%s", key, signature);
map.put("Content-Type", "application/json;charset=UTF-8");
map.put("Content-Type", Constant.APPLICATION_JSON_UTF8);
map.put("ctyun-eop-request-id", uuid);
map.put("Eop-date", signatureTime);
map.put("Eop-Authorization", signHeader);
@ -74,7 +75,7 @@ public class CtyunUtils {
* @param templateId 模板id
*/
public static String generateParamJsonStr(CtyunConfig ctyunConfig, String phone, String message, String templateId) {
Map<String, String> paramMap = new HashMap<>();
Map<String, String> paramMap = new HashMap<>(5);
paramMap.put("action", ctyunConfig.getAction());
paramMap.put("phoneNumber", phone);
paramMap.put("signName", ctyunConfig.getSignature());

View File

@ -6,28 +6,28 @@ import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.emay.service.EmaySmsImpl;
import org.dromara.sms4j.provider.config.BaseConfig;
/**
* @author Richard
* @date 2023-04-11 12:00
*/
@EqualsAndHashCode(callSuper = true)
@Data
@SuperBuilder
@ToString
@EqualsAndHashCode
@NoArgsConstructor
public class EmayConfig implements SupplierConfig {
/** appKey*/
private String appId ;
/** appSecret */
private String secretKey ;
public class EmayConfig extends BaseConfig {
/** APP接入地址*/
private String requestUrl;
/**
* 配置标识名 如未配置取对应渠道名例如 Alibaba
* 获取供应商
*
* @since 3.0.0
*/
private String configId;
@Override
public String getSupplier() {
return EmaySmsImpl.SUPPLIER;
}
}

View File

@ -1,8 +1,10 @@
package org.dromara.sms4j.emay.config;
import org.dromara.sms4j.comm.factory.BeanFactory;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.emay.service.EmaySmsImpl;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
/**
* EmaySmsConfig
@ -11,17 +13,11 @@ import org.dromara.sms4j.provider.base.BaseProviderFactory;
* @author Richard
* @date 2023/04/11 12:00
* */
public class EmayFactory implements BaseProviderFactory<EmaySmsImpl, EmayConfig> {
private static EmaySmsImpl emaySms;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EmayFactory extends AbstractProviderFactory<EmaySmsImpl, EmayConfig> {
private static final EmayFactory INSTANCE = new EmayFactory();
private static final class ConfigHolder {
private static EmayConfig config = EmayConfig.builder().build();
}
private EmayFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -37,44 +33,16 @@ public class EmayFactory implements BaseProviderFactory<EmaySmsImpl, EmayConfig>
*/
@Override
public EmaySmsImpl createSms(EmayConfig emayConfig) {
if (emaySms == null){
emaySms = createMultitonSms(emayConfig);
}
return emaySms;
}
@Override
public EmaySmsImpl createMultitonSms(EmayConfig emayConfig) {
return new EmaySmsImpl(emayConfig, BeanFactory.getExecutor(),BeanFactory.getDelayedTime());
return new EmaySmsImpl(emayConfig);
}
/**
* 刷新短信实现对象
* @param emayConfig 短信配置对象
* @return 刷新后的短信实现对象
* 获取供应商
* @return 供应商
*/
@Override
public EmaySmsImpl refresh(EmayConfig emayConfig){
emaySms = new EmaySmsImpl(emayConfig, BeanFactory.getExecutor(),BeanFactory.getDelayedTime());
return emaySms;
}
/**
* 获取配置
* @return 配置对象
*/
@Override
public EmayConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(EmayConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return EmaySmsImpl.SUPPLIER;
}
}

View File

@ -1,18 +1,15 @@
package org.dromara.sms4j.emay.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.emay.config.EmayConfig;
import org.dromara.sms4j.emay.util.EmayBuilder;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@ -25,31 +22,51 @@ import java.util.concurrent.Executor;
* @date 2023-04-11 12:00
*/
@Slf4j
public class EmaySmsImpl extends AbstractSmsBlend {
public class EmaySmsImpl extends AbstractSmsBlend<EmayConfig> {
public static final String SUPPLIER = "emay";
private int retry = 0;
public EmaySmsImpl(EmayConfig config, Executor pool, DelayedTime delayed) {
super(pool, delayed);
this.config = config;
super(config, pool, delayed);
}
private final EmayConfig config;
public EmaySmsImpl(EmayConfig config) {
super(config);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
String url = config.getRequestUrl();
Map<String, Object> params;
String url = getConfig().getRequestUrl();
Map<String, Object> params = EmayBuilder.buildRequestBody(getConfig().getAccessKeyId(), getConfig().getAccessKeySecret(), phone, message);
try {
params = EmayBuilder.buildRequestBody(config.getAppId(), config.getSecretKey(), phone, message);
} catch (SmsBlendException e) {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess(false);
return smsResponse;
Map<String, String> headers = new LinkedHashMap<>(1);
headers.put("Content-Type", Constant.FROM_URLENCODED);
SmsResponse smsResponse = getResponse(http.postFrom(url, headers, params));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phone, message);
}catch (SmsBlendException e){
return requestRetry(phone, message);
}
return getSendResponse(params, url);
}
private SmsResponse requestRetry(String phone, String message) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return sendMessage(phone, message);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
List<String> list = new ArrayList<>();
for (Map.Entry<String, String> entry : messages.entrySet()) {
@ -59,7 +76,6 @@ public class EmaySmsImpl extends AbstractSmsBlend {
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
if (phones.size() > 500) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于500");
@ -68,7 +84,6 @@ public class EmaySmsImpl extends AbstractSmsBlend {
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
if (phones.size() > 500) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于500");
@ -80,21 +95,11 @@ public class EmaySmsImpl extends AbstractSmsBlend {
return sendMessage(SmsUtil.listToString(phones), EmayBuilder.listToString(list));
}
private SmsResponse getSendResponse(Map<String, Object> body, String requestUrl) {
try(HttpResponse response = HttpRequest.post(requestUrl)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(JSONUtil.toJsonStr(body))
.execute()){
JSONObject res = JSONUtil.parseObj(response.body());
return this.getResponse(res);
}
}
private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess("success".equalsIgnoreCase(resJson.getStr("code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}

View File

@ -6,33 +6,27 @@ import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.huawei.service.HuaweiSmsImpl;
import org.dromara.sms4j.provider.config.BaseConfig;
@Data
@SuperBuilder
@ToString
@EqualsAndHashCode
@NoArgsConstructor
public class HuaweiConfig implements SupplierConfig {
/** appKey*/
private String appKey ;
/** appSecret */
private String appSecret ;
/** 短信签名*/
private String signature;
@EqualsAndHashCode(callSuper = true)
public class HuaweiConfig extends BaseConfig {
/** 国内短信签名通道号*/
private String sender;
/** 模板Id*/
private String templateId;
/** 短信状态报告接收地*/
private String statusCallBack;
/** APP接入地址*/
private String url;
/**
* 配置标识名 如未配置取对应渠道名例如 Alibaba
* 获取供应商
*
* @since 3.0.0
*/
private String configId;
@Override
public String getSupplier() {
return HuaweiSmsImpl.SUPPLIER;
}
}

View File

@ -1,8 +1,11 @@
package org.dromara.sms4j.huawei.config;
import org.dromara.sms4j.comm.factory.BeanFactory;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.aliyun.service.AlibabaSmsImpl;
import org.dromara.sms4j.huawei.service.HuaweiSmsImpl;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
/**
* HuaweiSmsConfig
@ -11,17 +14,11 @@ import org.dromara.sms4j.provider.base.BaseProviderFactory;
* @author :Wind
* 2023/4/8 15:27
**/
public class HuaweiFactory implements BaseProviderFactory<HuaweiSmsImpl, HuaweiConfig> {
private static HuaweiSmsImpl huaweiSms;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class HuaweiFactory extends AbstractProviderFactory<HuaweiSmsImpl, HuaweiConfig> {
private static final HuaweiFactory INSTANCE = new HuaweiFactory();
private static final class ConfigHolder {
private static HuaweiConfig config = HuaweiConfig.builder().build();
}
private HuaweiFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -33,40 +30,16 @@ public class HuaweiFactory implements BaseProviderFactory<HuaweiSmsImpl, HuaweiC
/** 建造一个华为短信实现*/
@Override
public HuaweiSmsImpl createSms(HuaweiConfig huaweiConfig) {
if (huaweiSms == null){
huaweiSms = createMultitonSms(huaweiConfig);
}
return huaweiSms;
}
@Override
public HuaweiSmsImpl createMultitonSms(HuaweiConfig huaweiConfig) {
return new HuaweiSmsImpl(huaweiConfig, BeanFactory.getExecutor(),BeanFactory.getDelayedTime());
}
/** 刷新对象*/
@Override
public HuaweiSmsImpl refresh(HuaweiConfig huaweiConfig){
huaweiSms = new HuaweiSmsImpl(huaweiConfig, BeanFactory.getExecutor(),BeanFactory.getDelayedTime());
return huaweiSms;
return new HuaweiSmsImpl(huaweiConfig);
}
/**
* 获取配置
* @return 配置对象
* 获取供应商
* @return 供应商
*/
@Override
public HuaweiConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(HuaweiConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return HuaweiSmsImpl.SUPPLIER;
}
}

View File

@ -1,75 +1,84 @@
package org.dromara.sms4j.huawei.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.huawei.config.HuaweiConfig;
import org.dromara.sms4j.huawei.utils.HuaweiBuilder;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.Executor;
import static org.dromara.sms4j.huawei.utils.HuaweiBuilder.listToString;
@Slf4j
public class HuaweiSmsImpl extends AbstractSmsBlend {
public class HuaweiSmsImpl extends AbstractSmsBlend<HuaweiConfig> {
public static final String SUPPLIER = "huawei";
private int retry = 0;
public HuaweiSmsImpl(HuaweiConfig config, Executor pool, DelayedTime delayed) {
super(pool, delayed);
this.config = config;
super(config, pool, delayed);
}
private final HuaweiConfig config;
public HuaweiSmsImpl(HuaweiConfig config) {
super(config);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
LinkedHashMap<String, String> mes = new LinkedHashMap<>();
mes.put(UUID.randomUUID().toString().replaceAll("-", ""), message);
return sendMessage(phone, config.getTemplateId(), mes);
return sendMessage(phone, getConfig().getTemplateId(), mes);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
String url = config.getUrl() + Constant.HUAWEI_REQUEST_URL;
String url = getConfig().getUrl() + Constant.HUAWEI_REQUEST_URL;
List<String> list = new ArrayList<>();
for (Map.Entry<String, String> entry : messages.entrySet()) {
list.add(entry.getValue());
}
String mess = listToString(list);
String requestBody = HuaweiBuilder.buildRequestBody(config.getSender(), phone, templateId, mess, config.getStatusCallBack(), config.getSignature());
Map<String, String> headers = new LinkedHashMap<>();
headers.put("Authorization", Constant.HUAWEI_AUTH_HEADER_VALUE);
headers.put("X-WSSE", HuaweiBuilder.buildWsseHeader(config.getAppKey(), config.getAppSecret()));
headers.put("Content-Type", Constant.FROM_URLENCODED);
try(HttpResponse response = HttpRequest.post(url)
.addHeaders(headers)
.body(requestBody)
.execute()){
JSONObject body = JSONUtil.parseObj(response.body());
return this.getResponse(body);
String requestBody = HuaweiBuilder.buildRequestBody(getConfig().getSender(), phone, templateId, mess, getConfig().getStatusCallBack(), getConfig().getSignature());
try {
Map<String, String> headers = new LinkedHashMap<>(3);
headers.put("Authorization", Constant.HUAWEI_AUTH_HEADER_VALUE);
headers.put("X-WSSE", HuaweiBuilder.buildWsseHeader(getConfig().getAccessKeyId(), getConfig().getAccessKeySecret()));
headers.put("Content-Type", Constant.FROM_URLENCODED);
SmsResponse smsResponse = getResponse(http.postJson(url, headers, requestBody));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phone, templateId, messages);
}catch (SmsBlendException e){
return requestRetry(phone, templateId, messages);
}
}
private SmsResponse requestRetry(String phone, String templateId, LinkedHashMap<String, String> messages) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return sendMessage(phone, templateId, messages);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
return sendMessage(listToString(phones), message);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
return sendMessage(listToString(phones), templateId, messages);
}
@ -78,7 +87,7 @@ public class HuaweiSmsImpl extends AbstractSmsBlend {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess("000000".equals(resJson.getStr("Code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}

View File

@ -6,8 +6,8 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.jdcloud.service.JdCloudSmsImpl;
import org.dromara.sms4j.provider.config.BaseConfig;
/**
* 京东云短信配置属性
@ -16,15 +16,22 @@ import org.dromara.sms4j.comm.config.BaseConfig;
* @since 2023/4/10 20:01
*/
@Data
@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class JdCloudConfig extends BaseConfig implements SupplierConfig {
public class JdCloudConfig extends BaseConfig {
/**
* 地域信息
*/
@Builder.Default
private String region = "cn-north-1";
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return JdCloudSmsImpl.SUPPLIER;
}
}

View File

@ -5,9 +5,10 @@ import com.jdcloud.sdk.auth.StaticCredentialsProvider;
import com.jdcloud.sdk.http.HttpRequestConfig;
import com.jdcloud.sdk.http.Protocol;
import com.jdcloud.sdk.service.sms.client.SmsClient;
import org.dromara.sms4j.comm.factory.BeanFactory;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.jdcloud.service.JdCloudSmsImpl;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
/**
* 京东云短信配置
@ -15,19 +16,11 @@ import org.dromara.sms4j.provider.base.BaseProviderFactory;
* @author Charles7c
* @since 2023/4/10 20:01
*/
public class JdCloudFactory implements BaseProviderFactory<JdCloudSmsImpl, JdCloudConfig> {
private static JdCloudSmsImpl jdCloudSms;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JdCloudFactory extends AbstractProviderFactory<JdCloudSmsImpl, JdCloudConfig> {
private static final JdCloudFactory INSTANCE = new JdCloudFactory();
private static final class ConfigHolder {
private static JdCloudConfig config = JdCloudConfig.builder().build();
}
private JdCloudFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -54,52 +47,19 @@ public class JdCloudFactory implements BaseProviderFactory<JdCloudSmsImpl, JdClo
*/
@Override
public JdCloudSmsImpl createSms(JdCloudConfig jdCloudConfig) {
if (jdCloudSms == null) {
jdCloudSms = createMultitonSms(jdCloudConfig);
}
return jdCloudSms;
}
@Override
public JdCloudSmsImpl createMultitonSms(JdCloudConfig jdCloudConfig) {
return new JdCloudSmsImpl(
this.client(jdCloudConfig),
jdCloudConfig,
BeanFactory.getExecutor(),
BeanFactory.getDelayedTime()
jdCloudConfig
);
}
/**
* 刷新对象
* 获取供应商
* @return 供应商
*/
@Override
public JdCloudSmsImpl refresh(JdCloudConfig jdCloudConfig) {
jdCloudSms = new JdCloudSmsImpl(
this.client(jdCloudConfig),
jdCloudConfig,
BeanFactory.getExecutor(),
BeanFactory.getDelayedTime()
);
return jdCloudSms;
}
/**
* 获取配置
* @return 配置对象
*/
@Override
public JdCloudConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(JdCloudConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return JdCloudSmsImpl.SUPPLIER;
}
}

View File

@ -5,12 +5,11 @@ import com.jdcloud.sdk.service.sms.client.SmsClient;
import com.jdcloud.sdk.service.sms.model.BatchSendRequest;
import com.jdcloud.sdk.service.sms.model.BatchSendResult;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.jdcloud.config.JdCloudConfig;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import java.util.Collections;
import java.util.LinkedHashMap;
@ -25,56 +24,80 @@ import java.util.stream.Collectors;
* @since 2023/4/10 20:01
*/
@Slf4j
public class JdCloudSmsImpl extends AbstractSmsBlend {
public class JdCloudSmsImpl extends AbstractSmsBlend<JdCloudConfig> {
public static final String SUPPLIER = "jdcloud";
private final SmsClient client;
private final JdCloudConfig config;
private int retry = 0;
public JdCloudSmsImpl(SmsClient client, JdCloudConfig config, Executor pool, DelayedTime delayed) {
super(pool, delayed);
super(config, pool, delayed);
this.client = client;
}
public JdCloudSmsImpl(SmsClient client, JdCloudConfig config) {
super(config);
this.client = client;
this.config = config;
}
@Override
@Restricted
public String getSupplier() {
return SUPPLIER;
}
@Override
public SmsResponse sendMessage(String phone, String message) {
return massTexting(Collections.singletonList(phone), message);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
return massTexting(Collections.singletonList(phone), templateId, messages);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put(IdUtil.fastSimpleUUID(), message);
return massTexting(phones, config.getTemplateId(), map);
return massTexting(phones, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
BatchSendRequest request;
try {
BatchSendRequest request = new BatchSendRequest();
request = new BatchSendRequest();
request.setPhoneList(phones);
request.setRegionId(config.getRegion());
request.setRegionId(getConfig().getRegion());
request.setTemplateId(templateId);
request.setSignId(config.getSignature());
request.setSignId(getConfig().getSignature());
List<String> params = messages.keySet().stream().map(messages::get)
.collect(Collectors.toList());
request.setParams(params);
BatchSendResult result = client.batchSend(request).getResult();
return getSmsResponse(result);
} catch (Exception e) {
throw new SmsBlendException(e.getMessage());
}
try {
BatchSendResult result = client.batchSend(request).getResult();
SmsResponse smsResponse = getSmsResponse(result);
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phones, templateId, messages);
}catch (SmsBlendException e){
return requestRetry(phones, templateId, messages);
}
}
private SmsResponse requestRetry(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return massTexting(phones, templateId, messages);
}
/**
@ -87,7 +110,7 @@ public class JdCloudSmsImpl extends AbstractSmsBlend {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess(res.getStatus() != null && res.getStatus());
smsResponse.setData(res);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}
}

View File

@ -5,17 +5,15 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.netease.service.NeteaseSmsImpl;
import org.dromara.sms4j.provider.config.BaseConfig;
/**
* @author adam
*/
@EqualsAndHashCode(callSuper = true)
@Data
@SuperBuilder
@NoArgsConstructor
public class NeteaseConfig extends BaseConfig implements SupplierConfig {
public class NeteaseConfig extends BaseConfig {
/**
* 模板变量名称
@ -25,20 +23,17 @@ public class NeteaseConfig extends BaseConfig implements SupplierConfig {
/**
* 模板短信请求地址
*/
@Builder.Default
private String templateUrl = "https://api.netease.im/sms/sendtemplate.action";
/**
* 验证码短信请求地址
*/
@Builder.Default
private String codeUrl = "https://api.netease.im/sms/sendcode.action";
/**
* 验证码验证请求地址
*/
@Builder.Default
private String verifyUrl = "https://api.netease.im/sms/verifycode.action";
/**
@ -47,4 +42,14 @@ public class NeteaseConfig extends BaseConfig implements SupplierConfig {
*/
private Boolean needUp;
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return NeteaseSmsImpl.SUPPLIER;
}
}

View File

@ -1,8 +1,10 @@
package org.dromara.sms4j.netease.config;
import org.dromara.sms4j.comm.factory.BeanFactory;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.netease.service.NeteaseSmsImpl;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
/**
* NeteaseSmsConfig
@ -11,19 +13,11 @@ import org.dromara.sms4j.provider.base.BaseProviderFactory;
* @author :adam
* 2023-05-30
**/
public class NeteaseFactory implements BaseProviderFactory<NeteaseSmsImpl, NeteaseConfig> {
private static NeteaseSmsImpl neteaseSms;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class NeteaseFactory extends AbstractProviderFactory<NeteaseSmsImpl, NeteaseConfig> {
private static final NeteaseFactory INSTANCE = new NeteaseFactory();
private static final class ConfigHolder {
private static NeteaseConfig config = NeteaseConfig.builder().build();
}
private NeteaseFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -37,50 +31,17 @@ public class NeteaseFactory implements BaseProviderFactory<NeteaseSmsImpl, Netea
*/
@Override
public NeteaseSmsImpl createSms(NeteaseConfig neteaseConfig) {
if (neteaseSms == null) {
neteaseSms = new NeteaseSmsImpl(
neteaseConfig,
BeanFactory.getExecutor(),
BeanFactory.getDelayedTime()
);
}
return neteaseSms;
}
@Override
public NeteaseSmsImpl createMultitonSms(NeteaseConfig neteaseConfig) {
return new NeteaseSmsImpl(neteaseConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return new NeteaseSmsImpl(neteaseConfig);
}
/**
* 刷新对象
* 获取供应商
*
* @since 3.0.0
*/
@Override
public NeteaseSmsImpl refresh(NeteaseConfig neteaseConfig) {
neteaseSms = new NeteaseSmsImpl(
neteaseConfig,
BeanFactory.getExecutor(),
BeanFactory.getDelayedTime()
);
return neteaseSms;
}
/**
* 获取配置
* @return 配置对象
*/
@Override
public NeteaseConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(NeteaseConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return NeteaseSmsImpl.SUPPLIER;
}
}

View File

@ -2,25 +2,18 @@ package org.dromara.sms4j.netease.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.netease.config.NeteaseConfig;
import org.dromara.sms4j.netease.utils.NeteaseUtils;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.Executor;
/**
@ -31,13 +24,22 @@ import java.util.concurrent.Executor;
*/
@Slf4j
public class NeteaseSmsImpl extends AbstractSmsBlend {
public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
private NeteaseConfig config;
public static final String SUPPLIER = "netease";
private int retry = 0;
public NeteaseSmsImpl(NeteaseConfig config, Executor pool, DelayedTime delayed) {
super(pool, delayed);
this.config = config;
super(config, pool, delayed);
}
public NeteaseSmsImpl(NeteaseConfig config) {
super(config);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
/**
@ -46,11 +48,10 @@ public class NeteaseSmsImpl extends AbstractSmsBlend {
* @return
*/
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
Optional.ofNullable(phone).orElseThrow(() -> new SmsBlendException("手机号不能为空"));
Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
return getSmsResponse(config.getTemplateUrl(), Collections.singletonList(phone), message, config.getTemplateId());
Optional.ofNullable(getConfig().getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
return getSmsResponse(getConfig().getTemplateUrl(), Collections.singletonList(phone), message, getConfig().getTemplateId());
}
@ -61,16 +62,14 @@ public class NeteaseSmsImpl extends AbstractSmsBlend {
* @return
*/
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
Optional.ofNullable(phone).orElseThrow(() -> new SmsBlendException("手机号不能为空"));
Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
Optional.ofNullable(getConfig().getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
String messageStr = messages.get("params");
return getSmsResponse(config.getTemplateUrl(), Collections.singletonList(phone), messageStr, templateId);
return getSmsResponse(getConfig().getTemplateUrl(), Collections.singletonList(phone), messageStr, templateId);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
if (phones.size() < 1) {
throw new SmsBlendException("手机号不能为空");
@ -78,45 +77,62 @@ public class NeteaseSmsImpl extends AbstractSmsBlend {
if (phones.size() > 100) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于100");
}
Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
return getSmsResponse(config.getTemplateUrl(), phones, config.getTemplateId(), message);
Optional.ofNullable(getConfig().getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
return getSmsResponse(getConfig().getTemplateUrl(), phones, getConfig().getTemplateId(), message);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
if (phones.size() > 100) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于100");
}
Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
Optional.ofNullable(getConfig().getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
String messageStr = messages.get("message");
return getSmsResponse(config.getTemplateUrl(), phones, messageStr, templateId);
return getSmsResponse(getConfig().getTemplateUrl(), phones, messageStr, templateId);
}
private SmsResponse getSmsResponse(String requestUrl, List<String> phones, String message, String templateId) {
private SmsResponse getSmsResponse(String requestUrl, List<String> phones, String message, String templateId) {
String nonce = IdUtil.fastSimpleUUID();
String curTime = String.valueOf(DateUtil.currentSeconds());
String checkSum = NeteaseUtils.getCheckSum(config.getAccessKeySecret(), nonce, curTime);
Map<String, String> body = NeteaseUtils.generateParamMap(config, phones, message, templateId);
String paramStr = NeteaseUtils.generateParamBody(body);
try(HttpResponse response = HttpRequest.post(requestUrl)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("AppKey", config.getAccessKeyId())
.header("Nonce", nonce)
.header("CurTime", curTime)
.header("CheckSum", checkSum)
.body(paramStr)
.execute()){
JSONObject res = JSONUtil.parseObj(response.body());
return this.getResponse(res);
String checkSum = NeteaseUtils.getCheckSum(getConfig().getAccessKeySecret(), nonce, curTime);
Map<String, Object> body = new LinkedHashMap<>(4);
body.put("templateid", templateId);
JSONArray jsonArray = new JSONArray();
jsonArray.addAll(phones);
body.put("mobiles", jsonArray.toString());
body.put("params", message);
body.put("needUp", getConfig().getNeedUp());
try {
Map<String, String> headers = new LinkedHashMap<>(5);
headers.put("Content-Type", Constant.FROM_URLENCODED);
headers.put("AppKey", getConfig().getAccessKeyId());
headers.put("Nonce", nonce);
headers.put("CurTime", curTime);
headers.put("CheckSum", checkSum);
SmsResponse smsResponse = getResponse(http.postJson(requestUrl, headers, body));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(requestUrl, phones, message, templateId);
}catch (SmsBlendException e){
return requestRetry(requestUrl, phones, message, templateId);
}
}
private SmsResponse requestRetry(String requestUrl, List<String> phones, String message, String templateId) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return getSmsResponse(requestUrl, phones, message, templateId);
}
private SmsResponse getResponse(JSONObject jsonObject) {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess(jsonObject.getInt("code") <= 200);
smsResponse.setData(jsonObject);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}

View File

@ -1,50 +0,0 @@
package org.dromara.sms4j.provider.base;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.universal.SupplierConfig;
/**
* AlibabaSmsConfig
* <p>短信对象建造者</p>
* @param <S> 短信对象
* @param <C> 短信配置对象
*/
public interface BaseProviderFactory<S extends SmsBlend, C extends SupplierConfig> {
/**
* 创建短信实现对象
* @param c 短信配置对象
* @return 短信实现对象
*/
S createSms(C c);
/**
* createMultitonSms
* <p> 创建多例的短信实现对象
* 在此方法中创建的短信实现对象框架将不再持有单例故而JVM中可以同时存在多个如果更改配置后需要重新调用该方法
* @param c 短信配置对象
* @return 短信实现对象
* @author :Wind
*/
S createMultitonSms(C c);
/**
* 刷新短信实现对象
* @param c 短信配置对象
* @return 刷新后的短信实现对象
*/
S refresh(C c);
/**
* 获取配置
* @return 配置对象
*/
C getConfig();
/**
* 设置配置
* @param config 配置对象
*/
void setConfig(C config);
}

View File

@ -0,0 +1,72 @@
package org.dromara.sms4j.provider.config;
import lombok.Data;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.exception.SmsBlendException;
/**
* 短信配置属性基类
*
* @author Charles7c
* @since 2023/4/20 23:03
*/
@Data
public abstract class BaseConfig implements SupplierConfig {
/**
* Access Key
*/
private String accessKeyId;
/**
* Access Key Secret
*/
private String accessKeySecret;
/**
* 短信签名
*/
private String signature;
/**
* 模板 ID
*/
private String templateId;
/**
* 权重
* @since 3.0.0
*/
private Integer weight = 1;
/**
* 配置标识名 如未配置取对应渠道名例如 Alibaba
*
* @since 3.0.0
*/
private String configId;
/**
* 重试间隔单位默认为5秒
*/
private int retryInterval = 5;
public void setRetryInterval(int retryInterval) {
if (retryInterval <= 0){
throw new SmsBlendException("重试间隔必须大于0秒");
}
this.retryInterval = retryInterval;
}
/**
* 重试次数默认为0次
*/
private int maxRetries = 0;
public void setMaxRetries(int maxRetries) {
if (maxRetries < 0){
throw new SmsBlendException("重试次数不能小于0次");
}
this.maxRetries = maxRetries;
}
}

View File

@ -1,4 +1,4 @@
package org.dromara.sms4j.comm.config;
package org.dromara.sms4j.provider.config;
public class SmsBanner {
private static final String banner =

View File

@ -1,4 +1,4 @@
package org.dromara.sms4j.comm.config;
package org.dromara.sms4j.provider.config;
import lombok.Data;
@ -8,7 +8,7 @@ import org.dromara.sms4j.comm.enumerate.ConfigType;
public class SmsConfig {
/** 配置源类型*/
private ConfigType configType = ConfigType.CONFIG_FILE;
private ConfigType configType = ConfigType.YAML;
/**
* 打印banner

View File

@ -1,72 +0,0 @@
package org.dromara.sms4j.provider.enumerate;
import org.dromara.sms4j.aliyun.config.AlibabaFactory;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.cloopen.config.CloopenFactory;
import org.dromara.sms4j.ctyun.config.CtyunFactory;
import org.dromara.sms4j.emay.config.EmayFactory;
import org.dromara.sms4j.huawei.config.HuaweiFactory;
import org.dromara.sms4j.jdcloud.config.JdCloudFactory;
import org.dromara.sms4j.netease.config.NeteaseFactory;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.tencent.config.TencentFactory;
import org.dromara.sms4j.unisms.config.UniFactory;
import org.dromara.sms4j.yunpian.config.YunPianFactory;
import org.dromara.sms4j.zhutong.config.ZhutongFactory;
/**
* SupplierType
* <p> 短信供应商枚举
* @author :Wind
* 2023/4/7 20:55
**/
public enum SupplierType {
/** 阿里云*/
ALIBABA("阿里云短信", AlibabaFactory.instance()),
/** 华为云*/
HUAWEI("华为云短信", HuaweiFactory.instance()),
/** 云片*/
YUNPIAN("云片短信", YunPianFactory.instance()),
/** 腾讯云*/
TENCENT("腾讯云短信", TencentFactory.instance()),
/** 合一短信*/
UNI_SMS("合一短信", UniFactory.instance()),
/** 京东云 */
JD_CLOUD("京东云短信", JdCloudFactory.instance()),
/** 容联云 */
CLOOPEN("容联云短信", CloopenFactory.instance()),
/** 亿美软通*/
EMAY("亿美软通", EmayFactory.instance()),
/** 天翼云 */
CTYUN("天翼云短信", CtyunFactory.instance()),
/** 网易云信 */
NETEASE("网易云短信", NeteaseFactory.instance()),
/** 助通短信 */
ZHUTONG("助通短信", ZhutongFactory.instance())
;
/**
* 渠道名称
*/
private final String name;
/**
* 短信对象配置
*/
private final BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> providerFactory;
SupplierType(String name, BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> providerFactory) {
this.name = name;
this.providerFactory = providerFactory;
}
public String getName() {
return name;
}
public BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> getProviderFactory() {
return providerFactory;
}
}

View File

@ -0,0 +1,32 @@
package org.dromara.sms4j.provider.factory;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.universal.SupplierConfig;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public abstract class AbstractProviderFactory<S extends SmsBlend, C extends SupplierConfig> implements BaseProviderFactory<S, C> {
private Class<C> configClass;
public AbstractProviderFactory() {
Type genericSuperclass = getClass().getGenericSuperclass();
if(genericSuperclass instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
Type[] typeArguments = paramType.getActualTypeArguments();
if(typeArguments.length > 1 && typeArguments[1] instanceof Class) {
configClass = (Class<C>) typeArguments[1];
}
}
}
/**
* 获取配置类
* @return 配置类
*/
public Class<C> getConfigClass() {
return configClass;
}
}

View File

@ -0,0 +1,33 @@
package org.dromara.sms4j.provider.factory;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.universal.SupplierConfig;
/**
* AlibabaSmsConfig
* <p>短信对象建造者</p>
* @param <S> 短信对象
* @param <C> 短信配置对象
*/
public interface BaseProviderFactory<S extends SmsBlend, C extends SupplierConfig> {
/**
* 创建短信实现对象
* @param c 短信配置对象
* @return 短信实现对象
*/
S createSms(C c);
/**
* 获取配置类
* @return 配置类
*/
Class<C> getConfigClass();
/**
* 获取供应商
* @return 供应商
*/
String getSupplier();
}

View File

@ -1,9 +1,7 @@
package org.dromara.sms4j.comm.factory;
package org.dromara.sms4j.provider.factory;
import org.dromara.sms4j.comm.config.SmsConfig;
import org.dromara.sms4j.comm.config.SmsSqlConfig;
import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.utils.JDBCTool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;
@ -28,15 +26,6 @@ public class BeanFactory {
/** 核心配置信息*/
private static SmsConfig smsConfig;
/** jdbc工具*/
private static JDBCTool jdbcTool;
/** 数据库配置*/
private static SmsSqlConfig smsSqlConfig;
/** 实例化自身对象防止被GC*/
private static final BeanFactory beanFactory = new BeanFactory();
private BeanFactory() {
}
@ -75,17 +64,4 @@ public class BeanFactory {
return smsConfig;
}
public static SmsSqlConfig getSmsSqlConfig(){
if (smsSqlConfig == null){
smsSqlConfig = new SmsSqlConfig();
}
return smsSqlConfig;
}
public static JDBCTool getJDBCTool(){
if (jdbcTool == null){
jdbcTool = new JDBCTool(getSmsSqlConfig());
}
return jdbcTool;
}
}

View File

@ -0,0 +1,45 @@
package org.dromara.sms4j.provider.factory;
import cn.hutool.core.collection.CollUtil;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 供应商工厂持有者
*
* @author xiaoyan
* @since 3.0.0
*/
public class ProviderFactoryHolder {
private static final Map<String, BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig>> factories = new ConcurrentHashMap<>();
public static void registerFactory(BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> factory) {
if(factory == null) {
throw new SmsBlendException("注册供应商工厂失败,工厂实例不能为空");
}
factories.put(factory.getSupplier(), factory);
}
public static void registerFactory(List<BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig>> factoryList) {
if(CollUtil.isEmpty(factoryList)) {
return;
}
for(BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> factory : factoryList) {
if(factory == null) {
continue;
}
registerFactory(factory);
}
}
public static BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> requireForSupplier(String supplier) {
return factories.getOrDefault(supplier, null);
}
}

View File

@ -1,10 +1,14 @@
package org.dromara.sms4j.api;
package org.dromara.sms4j.provider.service;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.callback.CallBack;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.provider.factory.BeanFactory;
import org.dromara.sms4j.comm.utils.SmsHttpUtil;
import java.util.LinkedHashMap;
import java.util.List;
@ -12,21 +16,37 @@ import java.util.TimerTask;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public abstract class AbstractSmsBlend implements SmsBlend{
public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsBlend {
@Getter
private final String configId;
private final C config;
protected final Executor pool;
protected final DelayedTime delayed;
protected AbstractSmsBlend(Executor pool, DelayedTime delayed) {
protected final SmsHttpUtil http = SmsHttpUtil.instance();
protected AbstractSmsBlend(C config, Executor pool, DelayedTime delayed) {
this.configId = StrUtil.isEmpty(config.getConfigId()) ? getSupplier() : config.getConfigId();
this.config = config;
this.pool = pool;
this.delayed = delayed;
}
protected AbstractSmsBlend() {
protected AbstractSmsBlend(C config) {
this.configId = StrUtil.isEmpty(config.getConfigId()) ? getSupplier() : config.getConfigId();
this.config = config;
this.pool = BeanFactory.getExecutor();
this.delayed = BeanFactory.getDelayedTime();
}
protected C getConfig() {
return config;
}
/**
* <p>说明发送固定消息模板短信
* <p>此方法将使用配置文件中预设的短信模板进行短信发送
@ -79,7 +99,6 @@ public abstract class AbstractSmsBlend implements SmsBlend{
* @param callBack 回调
* @author :Wind
*/
@Restricted
public final void sendMessageAsync(String phone, String message, CallBack callBack){
CompletableFuture<SmsResponse> smsResponseCompletableFuture = CompletableFuture.supplyAsync(() -> sendMessage(phone, message), pool);
smsResponseCompletableFuture.thenAcceptAsync(callBack::callBack);
@ -93,7 +112,6 @@ public abstract class AbstractSmsBlend implements SmsBlend{
* @param message 发送内容
* @author :Wind
*/
@Restricted
public final void sendMessageAsync(String phone, String message){
pool.execute(() -> {
sendMessage(phone, message);
@ -110,7 +128,6 @@ public abstract class AbstractSmsBlend implements SmsBlend{
* @author :Wind
*/
@Restricted
public final void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages, CallBack callBack){
CompletableFuture<SmsResponse> smsResponseCompletableFuture = CompletableFuture.supplyAsync(() -> sendMessage(phone,templateId, messages), pool);
smsResponseCompletableFuture.thenAcceptAsync(callBack::callBack);
@ -124,7 +141,6 @@ public abstract class AbstractSmsBlend implements SmsBlend{
* @param messages key为模板变量名称 value为模板变量值
* @author :Wind
*/
@Restricted
public final void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages){
pool.execute(() -> {
sendMessage(phone, templateId, messages);
@ -140,7 +156,6 @@ public abstract class AbstractSmsBlend implements SmsBlend{
* @param delayedTime 延迟时间
* @author :Wind
*/
@Restricted
public final void delayedMessage(String phone, String message, Long delayedTime){
this.delayed.schedule(new TimerTask() {
@Override
@ -160,7 +175,6 @@ public abstract class AbstractSmsBlend implements SmsBlend{
* @param delayedTime 延迟的时间
* @author :Wind
*/
@Restricted
public final void delayedMessage(String phone, String templateId, LinkedHashMap<String, String> messages, Long delayedTime){
this.delayed.schedule(new TimerTask() {
@Override
@ -177,7 +191,6 @@ public abstract class AbstractSmsBlend implements SmsBlend{
* @param phones 要群体发送的手机号码
* @author :Wind
*/
@Restricted
public final void delayMassTexting(List<String> phones, String message, Long delayedTime){
this.delayed.schedule(new TimerTask() {
@Override
@ -197,7 +210,6 @@ public abstract class AbstractSmsBlend implements SmsBlend{
* @param delayedTime 延迟的时间
* @author :Wind
*/
@Restricted
public final void delayMassTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime){
this.delayed.schedule(new TimerTask() {
@Override

View File

@ -6,15 +6,12 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.dromara.sms4j.tencent.service.TencentSmsImpl;
@Data
@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class TencentConfig extends BaseConfig implements SupplierConfig {
public class TencentConfig extends BaseConfig {
/**
* 短信sdkAppId
@ -24,32 +21,37 @@ public class TencentConfig extends BaseConfig implements SupplierConfig {
/**
* 地域信息默认为 ap-guangzhou
*/
@Builder.Default
private String territory = "ap-guangzhou";
/**
* 请求超时时间
*/
@Builder.Default
private Integer connTimeout = 60;
/** 请求地址*/
@Builder.Default
private String requestUrl = "sms.tencentcloudapi.com";
/**
* 接口名称
*/
@Builder.Default
private String action = "SendSms";
/**
* 接口版本
*/
@Builder.Default
private String version = "2021-01-11";
/**
* 服务名
*/
@Builder.Default
private String service = "sms";
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return TencentSmsImpl.SUPPLIER;
}
}

View File

@ -1,7 +1,7 @@
package org.dromara.sms4j.tencent.config;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import org.dromara.sms4j.tencent.service.TencentSmsImpl;
/**
@ -11,19 +11,10 @@ import org.dromara.sms4j.tencent.service.TencentSmsImpl;
* @author :Wind
* 2023/4/8 16:05
**/
public class TencentFactory implements BaseProviderFactory<TencentSmsImpl, TencentConfig> {
private static TencentSmsImpl tencentSms;
public class TencentFactory extends AbstractProviderFactory<TencentSmsImpl, TencentConfig> {
private static final TencentFactory INSTANCE = new TencentFactory();
private static final class ConfigHolder {
private static TencentConfig config = TencentConfig.builder().build();
}
private TencentFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -37,44 +28,17 @@ public class TencentFactory implements BaseProviderFactory<TencentSmsImpl, Tence
*/
@Override
public TencentSmsImpl createSms(TencentConfig tencentConfig) {
if (tencentSms == null) {
tencentSms = createMultitonSms(tencentConfig);
}
return tencentSms;
}
@Override
public TencentSmsImpl createMultitonSms(TencentConfig tencentConfig) {
return new TencentSmsImpl(tencentConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime()
);
return new TencentSmsImpl(tencentConfig);
}
/**
* 刷新对象
* 获取供应商
*
* @since 3.0.0
*/
@Override
public TencentSmsImpl refresh(TencentConfig tencentConfig) {
tencentSms = new TencentSmsImpl(tencentConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime()
);
return tencentSms;
}
/**
* 获取配置
* @return 配置对象
*/
@Override
public TencentConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(TencentConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return TencentSmsImpl.SUPPLIER;
}
}

View File

@ -1,18 +1,15 @@
package org.dromara.sms4j.tencent.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jdcloud.sdk.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import org.dromara.sms4j.tencent.config.TencentConfig;
import org.dromara.sms4j.tencent.utils.TencentUtils;
@ -26,50 +23,55 @@ import java.util.concurrent.Executor;
* @author wind
*/
@Slf4j
public class TencentSmsImpl extends AbstractSmsBlend {
public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
private final TencentConfig config;
public static final String SUPPLIER = "tencent";
private int retry = 0;
public TencentSmsImpl(TencentConfig tencentSmsConfig, Executor pool, DelayedTime delayed) {
super(pool, delayed);
this.config = tencentSmsConfig;
super(tencentSmsConfig, pool, delayed);
}
public TencentSmsImpl(TencentConfig tencentSmsConfig) {
super(tencentSmsConfig);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
String[] split = message.split("&");
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (int i = 0; i < split.length; i++) {
map.put(String.valueOf(i), split[i]);
}
return sendMessage(phone, config.getTemplateId(), map);
return sendMessage(phone, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
List<String> list = new ArrayList<>();
for (Map.Entry<String, String> entry : messages.entrySet()) {
list.add(entry.getValue());
}
String[] s = new String[list.size()];
return getSmsResponse(new String[]{"+86" + phone}, list.toArray(s), templateId);
return getSmsResponse(new String[]{StrUtil.addPrefixIfNot(phone, "+86")}, list.toArray(s), templateId);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
String[] split = message.split("&");
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (int i = 0; i < split.length; i++) {
map.put(String.valueOf(i), split[i]);
}
return massTexting(phones, config.getTemplateId(), map);
return massTexting(phones, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
List<String> list = new ArrayList<>();
for (Map.Entry<String, String> entry : messages.entrySet()) {
@ -83,32 +85,43 @@ public class TencentSmsImpl extends AbstractSmsBlend {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature;
try {
signature = TencentUtils.generateSignature(this.config, templateId, messages, phones, timestamp);
signature = TencentUtils.generateSignature(this.getConfig(), templateId, messages, phones, timestamp);
} catch (Exception e) {
log.error("tencent send message error", e);
throw new SmsBlendException(e.getMessage());
}
Map<String, String> headsMap = TencentUtils.generateHeadsMap(signature, timestamp, config.getAction(),
config.getVersion(), config.getTerritory(), config.getRequestUrl());
Map<String, Object> requestBody = TencentUtils.generateRequestBody(phones, config.getSdkAppId(),
config.getSignature(), templateId, messages);
String url = Constant.HTTPS_PREFIX + config.getRequestUrl();
try(HttpResponse response = HttpRequest.post(url)
.addHeaders(headsMap)
.body(JSONUtil.toJsonStr(requestBody))
.execute()){
JSONObject body = JSONUtil.parseObj(response.body());
return this.getResponse(body);
Map<String, String> headsMap = TencentUtils.generateHeadsMap(signature, timestamp, getConfig().getAction(),
getConfig().getVersion(), getConfig().getTerritory(), getConfig().getRequestUrl());
Map<String, Object> requestBody = TencentUtils.generateRequestBody(phones, getConfig().getSdkAppId(),
getConfig().getSignature(), templateId, messages);
String url = Constant.HTTPS_PREFIX + getConfig().getRequestUrl();
try {
SmsResponse smsResponse = getResponse(http.postJson(url, headsMap, requestBody));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phones, messages, templateId);
}catch (SmsBlendException e){
return requestRetry(phones, messages, templateId);
}
}
private SmsResponse requestRetry(String[] phones, String[] messages, String templateId) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return getSmsResponse(phones, messages, templateId);
}
private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse();
JSONObject response = resJson.getJSONObject("Response");
String error = response.getStr("Error");
smsResponse.setSuccess(StringUtils.isBlank(error));
smsResponse.setData(resJson);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}
}

View File

@ -6,24 +6,31 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.dromara.sms4j.unisms.service.UniSmsImpl;
@Data
@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class UniConfig extends BaseConfig implements SupplierConfig {
public class UniConfig extends BaseConfig {
/**
* 是否为简易模式
*/
@Builder.Default
private Boolean isSimple = true;
/**
* 模板变量名称
*/
private String templateName;
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return UniSmsImpl.SUPPLIER;
}
}

View File

@ -1,7 +1,9 @@
package org.dromara.sms4j.unisms.config;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import org.dromara.sms4j.unisms.core.Uni;
import org.dromara.sms4j.unisms.service.UniSmsImpl;
@ -11,19 +13,11 @@ import org.dromara.sms4j.unisms.service.UniSmsImpl;
* @author :Wind
* 2023/4/8 15:46
**/
public class UniFactory implements BaseProviderFactory<UniSmsImpl, UniConfig> {
private static UniSmsImpl uniSmsImpl;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UniFactory extends AbstractProviderFactory<UniSmsImpl, UniConfig> {
private static final UniFactory INSTANCE = new UniFactory();
private static final class ConfigHolder {
private static UniConfig config = UniConfig.builder().build();
}
private UniFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -49,46 +43,18 @@ public class UniFactory implements BaseProviderFactory<UniSmsImpl, UniConfig> {
*/
@Override
public UniSmsImpl createSms(UniConfig uniConfig){
if (uniSmsImpl == null){
this.buildSms(uniConfig);
uniSmsImpl = createMultitonSms(uniConfig);
}
return uniSmsImpl;
}
@Override
public UniSmsImpl createMultitonSms(UniConfig config) {
return new UniSmsImpl(config, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
}
/**
* refresh
* <p>刷新对象
* @author :Wind
*/
@Override
public UniSmsImpl refresh(UniConfig uniConfig){
this.buildSms(uniConfig);
uniSmsImpl = new UniSmsImpl(uniConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return uniSmsImpl;
return new UniSmsImpl(uniConfig);
}
/**
* 获取配置
* @return 配置对象
* 获取供应商
*
* @since 3.0.0
*/
@Override
public UniConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(UniConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return UniSmsImpl.SUPPLIER;
}
}

View File

@ -61,11 +61,11 @@ public class Uni {
* @return the Uni Client
* @author :Wind
*/
public static UniClient getClient() {
public static UniClient getClient(int retryInterval, int maxRetries) {
if (Uni.client == null) {
synchronized (Uni.class) {
if (Uni.client == null) {
Uni.client = buildClient();
Uni.client = buildClient(retryInterval, maxRetries);
}
}
}
@ -78,7 +78,7 @@ public class Uni {
}
}
private static UniClient buildClient() {
private static UniClient buildClient(int retryInterval, int maxRetries) {
UniClient.Builder builder = new UniClient.Builder(Uni.accessKeyId);
if (Uni.accessKeySecret != null) {
@ -87,7 +87,8 @@ public class Uni {
builder.endpoint(Uni.endpoint);
builder.signingAlgorithm(Uni.signingAlgorithm);
builder.setRetryInterval(retryInterval);
builder.setMaxRetries(maxRetries);
return builder.build();
}
}

View File

@ -3,10 +3,10 @@ package org.dromara.sms4j.unisms.core;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsHttpUtil;
import java.util.Comparator;
import java.util.Date;
@ -17,6 +17,7 @@ import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.UUID;
@Slf4j
public class UniClient {
public static final String USER_AGENT = "uni-java-sdk" + "/" + Uni.VERSION;
@ -24,12 +25,18 @@ public class UniClient {
private final String accessKeySecret;
private final String endpoint;
private final String signingAlgorithm;
private final int retryInterval;
private final int maxRetries;
private int retry = 0;
private final SmsHttpUtil http = SmsHttpUtil.instance();
protected UniClient(Builder b) {
this.accessKeyId = b.accessKeyId;
this.accessKeySecret = b.accessKeySecret;
this.endpoint = b.endpoint;
this.signingAlgorithm = b.signingAlgorithm;
this.retryInterval = b.retryInterval;
this.maxRetries = b.maxRetries;
}
private static String getSignature(final String message, final String secretKey) {
@ -81,23 +88,37 @@ public class UniClient {
public UniResponse request(final String action, final Map<String, Object> data) throws SmsBlendException {
Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", USER_AGENT);
headers.put("Content-Type", "application/json;charset=utf-8");
headers.put("Accept", "application/json");
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8);
headers.put("Accept", Constant.ACCEPT);
String url = this.endpoint + "?action=" + action + "&accessKeyId=" + this.accessKeyId;
try(HttpResponse response = HttpRequest.post(url)
.addHeaders(headers)
.body(JSONUtil.toJsonStr(data))
.execute()){
return new UniResponse(JSONUtil.parseObj(response.body()));
try {
UniResponse smsResponse = new UniResponse(http.postJson(url, headers, data));
if("Success".equals(smsResponse.message) || retry == maxRetries){
retry = 0;
return smsResponse;
}
return requestRetry(action, data);
}catch (SmsBlendException e){
return requestRetry(action, data);
}
}
private UniResponse requestRetry(String action, Map<String, Object> data) {
http.safeSleep(retryInterval);
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return request(action, data);
}
public static class Builder {
private String accessKeyId;
private String accessKeySecret;
private String endpoint;
private String signingAlgorithm;
private int retryInterval;
private int maxRetries;
public Builder(final String accessKeyId) {
this.accessKeyId = accessKeyId;
@ -123,6 +144,16 @@ public class UniClient {
return this;
}
public Builder setRetryInterval(int retryInterval) {
this.retryInterval = retryInterval;
return this;
}
public Builder setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}
public UniClient build() {
return new UniClient(this);
}

View File

@ -27,7 +27,7 @@ public class UniResponse {
this.raw = body;
this.data = body;
}
if (this.status != "400") {
if (!"400".equals(this.status)) {
String code = response.getStr("code");
if (!"0".equals(code)) {
this.message = response.getStr("message");

View File

@ -1,20 +1,15 @@
package org.dromara.sms4j.unisms.service;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import org.dromara.sms4j.unisms.config.UniConfig;
import org.dromara.sms4j.unisms.core.Uni;
import org.dromara.sms4j.unisms.core.UniResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.Executor;
/**
@ -25,57 +20,61 @@ import java.util.concurrent.Executor;
* 2023/3/26 17:10
**/
@Slf4j
public class UniSmsImpl extends AbstractSmsBlend {
public class UniSmsImpl extends AbstractSmsBlend<UniConfig> {
private final UniConfig config;
public static final String SUPPLIER = "unisms";
public UniSmsImpl(UniConfig config, Executor pool, DelayedTime delayed) {
super(pool, delayed);
this.config = config;
super(config, pool, delayed);
}
public UniSmsImpl(UniConfig config) {
super(config);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
if ("".equals(config.getTemplateId()) && "".equals(config.getTemplateName())) {
if ("".equals(getConfig().getTemplateId()) && "".equals(getConfig().getTemplateName())) {
throw new SmsBlendException("配置文件模板id和模板变量不能为空");
}
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put(config.getTemplateName(), message);
return sendMessage(phone, config.getTemplateId(), map);
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message);
return sendMessage(phone, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
Map<String, Object> data = new HashMap<>();
Map<String, Object> data = new LinkedHashMap<>(4);
data.put("to", Collections.singletonList(phone));
data.put("signature", config.getSignature());
data.put("signature", getConfig().getSignature());
data.put("templateId", templateId);
data.put("templateData", messages);
return getSmsResponse(data);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
if ("".equals(config.getTemplateId()) && "".equals(config.getTemplateName())) {
if ("".equals(getConfig().getTemplateId()) && "".equals(getConfig().getTemplateName())) {
throw new SmsBlendException("配置文件模板id和模板变量不能为空");
}
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put(config.getTemplateName(), message);
return massTexting(phones, config.getTemplateId(), map);
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message);
return massTexting(phones, getConfig().getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
if (phones.size() > 1000) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000");
}
Map<String, Object> data = new HashMap<>();
Map<String, Object> data = new LinkedHashMap<>(4);
data.put("to", phones);
data.put("signature", config.getSignature());
data.put("signature", getConfig().getSignature());
data.put("templateId", templateId);
data.put("templateData", messages);
return getSmsResponse(data);
@ -84,10 +83,10 @@ public class UniSmsImpl extends AbstractSmsBlend {
private SmsResponse getSmsResponse(Map<String, Object> data) {
SmsResponse smsResponse = new SmsResponse();
try {
UniResponse send = Uni.getClient().request("sms.message.send", data);
UniResponse send = Uni.getClient(getConfig().getRetryInterval(), getConfig().getMaxRetries()).request("sms.message.send", data);
smsResponse.setSuccess("Success".equals(send.message));
smsResponse.setData(send);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
} catch (Exception e) {
smsResponse.setSuccess(false);
}

View File

@ -1,22 +1,16 @@
package org.dromara.sms4j.yunpian.config;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import org.dromara.sms4j.yunpian.service.YunPianSmsImpl;
public class YunPianFactory implements BaseProviderFactory<YunPianSmsImpl, YunpianConfig> {
private static YunPianSmsImpl yunpianSmsImpl;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class YunPianFactory extends AbstractProviderFactory<YunPianSmsImpl, YunpianConfig> {
private static final YunPianFactory INSTANCE = new YunPianFactory();
private static final class ConfigHolder {
private static YunpianConfig config = YunpianConfig.builder().build();
}
private YunPianFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -30,40 +24,17 @@ public class YunPianFactory implements BaseProviderFactory<YunPianSmsImpl, Yunpi
*/
@Override
public YunPianSmsImpl createSms(YunpianConfig yunpianConfig){
if (yunpianSmsImpl == null){
yunpianSmsImpl = createMultitonSms(yunpianConfig);
}
return yunpianSmsImpl;
}
@Override
public YunPianSmsImpl createMultitonSms(YunpianConfig yunpianConfig) {
return new YunPianSmsImpl(yunpianConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
}
/** 刷新对象*/
@Override
public YunPianSmsImpl refresh(YunpianConfig yunpianConfig){
yunpianSmsImpl = new YunPianSmsImpl(yunpianConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return yunpianSmsImpl;
return new YunPianSmsImpl(yunpianConfig);
}
/**
* 获取配置
* @return 配置对象
* 获取供应商
*
* @since 3.0.0
*/
@Override
public YunpianConfig getConfig() {
return ConfigHolder.config;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(YunpianConfig config) {
ConfigHolder.config = config;
public String getSupplier() {
return YunPianSmsImpl.SUPPLIER;
}
}

View File

@ -5,14 +5,12 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.dromara.sms4j.yunpian.service.YunPianSmsImpl;
@Data
@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class YunpianConfig extends BaseConfig implements SupplierConfig {
public class YunpianConfig extends BaseConfig {
/**
* 短信发送后将向这个地址推送(运营商返回的)发送报告
@ -24,4 +22,14 @@ public class YunpianConfig extends BaseConfig implements SupplierConfig {
*/
private String templateName;
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return YunPianSmsImpl.SUPPLIER;
}
}

View File

@ -1,16 +1,13 @@
package org.dromara.sms4j.yunpian.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.dromara.sms4j.api.AbstractSmsBlend;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import org.dromara.sms4j.yunpian.config.YunpianConfig;
import java.util.HashMap;
@ -22,14 +19,24 @@ import java.util.concurrent.Executor;
/**
* @author wind
*/
public class YunPianSmsImpl extends AbstractSmsBlend {
@Slf4j
public class YunPianSmsImpl extends AbstractSmsBlend<YunpianConfig> {
public static final String SUPPLIER = "yunpian";
private int retry = 0;
public YunPianSmsImpl(YunpianConfig config, Executor pool, DelayedTime delayed) {
super(pool, delayed);
this.config = config;
super(config, pool, delayed);
}
private final YunpianConfig config;
public YunPianSmsImpl(YunpianConfig config) {
super(config);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
private SmsResponse getResponse(JSONObject execute) {
SmsResponse smsResponse = new SmsResponse();
@ -39,26 +46,59 @@ public class YunPianSmsImpl extends AbstractSmsBlend {
}
smsResponse.setSuccess(execute.getInt("code") == 0);
smsResponse.setData(execute);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
Map<String, String> body = setBody(phone, message, null, config.getTemplateId());
return getSendResponse(body);
Map<String, Object> body = setBody(phone, message, null, getConfig().getTemplateId());
Map<String, String> headers = getHeaders();
try {
SmsResponse smsResponse = getResponse(http.postFrom(Constant.YUNPIAN_URL + "/sms/tpl_single_send.json", headers, body));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phone, message);
}catch (SmsBlendException e){
return requestRetry(phone, message);
}
}
private SmsResponse requestRetry(String phone, String message) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return sendMessage(phone, message);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
Map<String, String> body = setBody(phone, "", messages, templateId);
return getSendResponse(body);
Map<String, Object> body = setBody(phone, "", messages, templateId);
Map<String, String> headers = getHeaders();
try {
SmsResponse smsResponse = getResponse(http.postFrom(Constant.YUNPIAN_URL + "/sms/tpl_single_send.json", headers, body));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phone, templateId, messages);
}catch (SmsBlendException e){
return requestRetry(phone, templateId, messages);
}
}
private SmsResponse requestRetry(String phone, String templateId, LinkedHashMap<String, String> messages) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return sendMessage(phone, templateId, messages);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
if (phones.size() > 1000) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000");
@ -67,7 +107,6 @@ public class YunPianSmsImpl extends AbstractSmsBlend {
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
if (phones.size() > 1000) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000");
@ -88,38 +127,27 @@ public class YunPianSmsImpl extends AbstractSmsBlend {
return str.toString();
}
private Map<String, String> setBody(String phone, String mes, LinkedHashMap<String, String> messages, String tplId) {
private Map<String, Object> setBody(String phone, String mes, LinkedHashMap<String, String> messages, String tplId) {
LinkedHashMap<String, String> message = new LinkedHashMap<>();
if (mes.isEmpty()) {
message = messages;
} else {
message.put(config.getTemplateName(), mes);
message.put(getConfig().getTemplateName(), mes);
}
Map<String, String> body = new HashMap<>();
body.put("apikey", config.getAccessKeyId());
Map<String, Object> body = new HashMap<>();
body.put("apikey", getConfig().getAccessKeyId());
body.put("mobile", phone);
body.put("tpl_id", tplId);
body.put("tpl_value", formattingMap(message));
if (config.getCallbackUrl() != null && !config.getCallbackUrl().isEmpty())
body.put("callback_url", config.getCallbackUrl());
if (getConfig().getCallbackUrl() != null && !getConfig().getCallbackUrl().isEmpty())
body.put("callback_url", getConfig().getCallbackUrl());
return body;
}
private Map<String, String> getHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("Accept", "application/json;charset=utf-8");
headers.put("Accept", Constant.APPLICATION_JSON_UTF8);
headers.put("Content-Type", Constant.FROM_URLENCODED);
return headers;
}
private SmsResponse getSendResponse(Map<String, String> body) {
Map<String, String> headers = getHeaders();
try(HttpResponse response = HttpRequest.post(Constant.YUNPIAN_URL + "/sms/tpl_single_send.json")
.addHeaders(headers)
.body(JSONUtil.toJsonStr(body))
.execute()){
JSONObject res = JSONUtil.parseObj(response.body());
return getResponse(res);
}
}
}

View File

@ -1,13 +1,9 @@
package org.dromara.sms4j.zhutong.config;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.comm.config.BaseConfig;
import org.dromara.sms4j.provider.config.BaseConfig;
import org.dromara.sms4j.zhutong.service.ZhutongSmsImpl;
/**
* 助通-自定义短信发送-配置
@ -19,12 +15,9 @@ import org.dromara.sms4j.comm.config.BaseConfig;
* 说明4templateId ====> 模板id可以为空为空发送自定义短信无需要提前创建短信模板; 不为空发送:模板短信
* 说明4templateName ====> 模板变量名称可以为空为空发送自定义短信无需要提前创建短信模板; 不为空发送:模板短信
*/
@Getter
@Setter
@SuperBuilder
@ToString(callSuper = true)
@NoArgsConstructor
public class ZhutongConfig extends BaseConfig implements SupplierConfig {
@EqualsAndHashCode(callSuper = true)
@Data
public class ZhutongConfig extends BaseConfig {
/**
* 模板变量名称
* 查看地址https://mix2.zthysms.com/index.html#/TemplateManagement
@ -35,6 +28,16 @@ public class ZhutongConfig extends BaseConfig implements SupplierConfig {
* 默认请求地址
* 不同区域可切换请求地址也可以不修改请参考官方文档https://doc.zthysms.com/web/#/1/236
*/
@Builder.Default
private String requestUrl = "https://api.mix2.zthysms.com/";
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return ZhutongSmsImpl.SUPPLIER;
}
}

View File

@ -1,21 +1,15 @@
package org.dromara.sms4j.zhutong.config;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.provider.base.BaseProviderFactory;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import org.dromara.sms4j.zhutong.service.ZhutongSmsImpl;
public class ZhutongFactory implements BaseProviderFactory<ZhutongSmsImpl, ZhutongConfig> {
private static ZhutongSmsImpl ZhutongSmsImpl;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ZhutongFactory extends AbstractProviderFactory<ZhutongSmsImpl, ZhutongConfig> {
private static final ZhutongFactory INSTANCE = new ZhutongFactory();
private static final class ConfigHolder {
private static ZhutongConfig config = ZhutongConfig.builder().build();
}
private ZhutongFactory() {
}
/**
* 获取建造者实例
* @return 建造者实例
@ -28,40 +22,18 @@ public class ZhutongFactory implements BaseProviderFactory<ZhutongSmsImpl, Zhuto
* 建造一个助通短信实现
*/
@Override
public ZhutongSmsImpl createSms(ZhutongConfig ZhutongConfig){
if (ZhutongSmsImpl == null){
ZhutongSmsImpl = createMultitonSms(ZhutongConfig);
}
return ZhutongSmsImpl;
}
@Override
public ZhutongSmsImpl createMultitonSms(ZhutongConfig zhutongConfig) {
return new ZhutongSmsImpl(zhutongConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
}
/** 刷新对象*/
@Override
public ZhutongSmsImpl refresh(ZhutongConfig zhutongConfig){
ZhutongSmsImpl = new ZhutongSmsImpl(zhutongConfig, BeanFactory.getExecutor(), BeanFactory.getDelayedTime());
return ZhutongSmsImpl;
public ZhutongSmsImpl createSms(ZhutongConfig zhutongConfig){
return new ZhutongSmsImpl(zhutongConfig);
}
/**
* 获取配置
* @return 配置对象
* 获取供应商
*
* @since 3.0.0
*/
@Override
public ZhutongConfig getConfig() {
return ZhutongFactory.ConfigHolder.config;
public String getSupplier() {
return ZhutongSmsImpl.SUPPLIER;
}
/**
* 设置配置
* @param config 配置对象
*/
@Override
public void setConfig(ZhutongConfig config) {
ZhutongFactory.ConfigHolder.config = config;
}
}

View File

@ -5,18 +5,14 @@ import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.AbstractSmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.annotation.Restricted;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import org.dromara.sms4j.zhutong.config.ZhutongConfig;
import java.util.HashMap;
@ -31,53 +27,64 @@ import java.util.concurrent.Executor;
* <p>2. 模板短信发送 需定义模板 https://doc.zthysms.com/web/#/1/13
*/
@Slf4j
public class ZhutongSmsImpl extends AbstractSmsBlend {
public class ZhutongSmsImpl extends AbstractSmsBlend<ZhutongConfig> {
private final ZhutongConfig config;
public static final String SUPPLIER = "zhutong";
private int retry = 0;
/**
* ZhutongSmsImpl
* <p>构造器用于构造短信实现模块
*/
public ZhutongSmsImpl(ZhutongConfig zhutongConfig, Executor pool, DelayedTime delayedTime) {
super(pool, delayedTime);
this.config = zhutongConfig;
super(zhutongConfig, pool, delayedTime);
}
/**
* ZhutongSmsImpl
* <p>构造器用于构造短信实现模块
*/
public ZhutongSmsImpl(ZhutongConfig zhutongConfig) {
super(zhutongConfig);
}
@Override
public String getSupplier() {
return SUPPLIER;
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
ZhutongConfig config = getConfig();
//如果模板id为空 or 模板变量名称为空使用无模板的自定义短信发送
if (StrUtil.hasBlank(config.getSignature(), config.getTemplateId(), config.getTemplateName())) {
return getSmsResponse(phone, message);
}
LinkedHashMap<String, String> map = new LinkedHashMap<>();
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(config.getTemplateName(), message);
return sendMessage(phone, config.getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
return getSmsResponseTemplate(templateId, phone, messages);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
ZhutongConfig config = getConfig();
//如果模板id为空 or 模板变量名称为空使用无模板的自定义短信发送
if (StrUtil.hasBlank(config.getSignature(), config.getTemplateId(), config.getTemplateName())) {
return getSmsResponse(phones, message);
}
LinkedHashMap<String, String> map = new LinkedHashMap<>();
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(config.getTemplateName(), message);
return massTexting(phones, config.getTemplateId(), map);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
return getSmsResponseTemplate(templateId, phones, messages);
}
@ -86,6 +93,7 @@ public class ZhutongSmsImpl extends AbstractSmsBlend {
* 发送 自定义短信https://doc.zthysms.com/web/#/1/14
*/
protected SmsResponse getSmsResponse(List<String> phones, String content) {
ZhutongConfig config = getConfig();
String requestUrl = config.getRequestUrl();
String username = config.getAccessKeyId();
String password = config.getAccessKeySecret();
@ -110,7 +118,7 @@ public class ZhutongSmsImpl extends AbstractSmsBlend {
String url = requestUrl + "v2/sendSms";
long tKey = System.currentTimeMillis() / 1000;
Map<String, String> json = new HashMap<>(5);
Map<String, Object> json = new HashMap<>(5);
//账号
json.put("username", username);
//密码
@ -122,15 +130,27 @@ public class ZhutongSmsImpl extends AbstractSmsBlend {
//内容
json.put("content", content);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", Constant.APPLICATION_JSON_UTF8)
.body(JSONUtil.toJsonStr(json))
.execute()){
JSONObject body = JSONUtil.parseObj(response.body());
return this.getResponse(body);
try {
Map<String, String> headers = new LinkedHashMap<>(1);
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8);
SmsResponse smsResponse = getResponse(http.postJson(requestUrl, headers, json));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(phones, content);
}catch (SmsBlendException e){
return requestRetry(phones, content);
}
}
private SmsResponse requestRetry(List<String> phones, String content) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return getSmsResponse(phones, content);
}
protected SmsResponse getSmsResponse(String mobile, String content) {
return getSmsResponse(ListUtil.of(mobile), content);
}
@ -139,6 +159,7 @@ public class ZhutongSmsImpl extends AbstractSmsBlend {
* 发送 模板短信https://doc.zthysms.com/web/#/1/13
*/
protected SmsResponse getSmsResponseTemplate(String templateId, List<String> phones, LinkedHashMap<String, String> messages) {
ZhutongConfig config = getConfig();
String requestUrl = config.getRequestUrl();
String username = config.getAccessKeyId();
String password = config.getAccessKeySecret();
@ -194,15 +215,27 @@ public class ZhutongSmsImpl extends AbstractSmsBlend {
}
requestJson.set("records", records);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", Constant.APPLICATION_JSON_UTF8)
.body(requestJson.toString())
.execute()){
JSONObject body = JSONUtil.parseObj(response.body());
return this.getResponse(body);
try {
Map<String, String> headers = new LinkedHashMap<>(1);
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8);
SmsResponse smsResponse = getResponse(http.postJson(requestUrl, headers, requestJson.toString()));
if(smsResponse.isSuccess() || retry == getConfig().getMaxRetries()){
retry = 0;
return smsResponse;
}
return requestRetry(templateId, phones, messages);
}catch (SmsBlendException e){
return requestRetry(templateId, phones, messages);
}
}
private SmsResponse requestRetry(String templateId, List<String> phones, LinkedHashMap<String, String> messages) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {" + retry + "} 次重新发送");
return getSmsResponseTemplate(templateId, phones, messages);
}
protected SmsResponse getSmsResponseTemplate(String templateId, String mobile, LinkedHashMap<String, String> content) {
return getSmsResponseTemplate(templateId, ListUtil.of(mobile), content);
}
@ -211,7 +244,7 @@ public class ZhutongSmsImpl extends AbstractSmsBlend {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess(jsonObject.getInt("code", -1) <= 200);
smsResponse.setData(jsonObject);
smsResponse.setConfigId(this.config.getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
}

View File

@ -1,53 +1,55 @@
package org.dromara.sms4j.solon.aop;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.smsProxy.RestrictedProcess;
import org.dromara.sms4j.api.universal.SmsRedisUtil;
import org.dromara.sms4j.comm.config.SmsConfig;
import org.dromara.sms4j.api.dao.SmsDao;
import org.dromara.sms4j.api.proxy.RestrictedProcess;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.provider.factory.BeanFactory;
import org.noear.solon.core.AopContext;
@Slf4j
public class SolonRestrictedProcess extends RestrictedProcess {
public class SolonRestrictedProcess implements RestrictedProcess {
private SmsRedisUtil redis;
private static final Long minTimer = 60 * 1000L;
private static final Long accTimer = 24 * 60 * 60 * 1000L;
private static final String REDIS_KEY = "sms:restricted:";
private SmsDao smsDao;
public SolonRestrictedProcess(AopContext context){
context.getBeanAsync(SmsRedisUtil.class, bean->{
redis = bean;
public SolonRestrictedProcess(AopContext context) {
context.getBeanAsync(SmsDao.class, bean -> {
smsDao = bean;
});
}
@Override
public SmsBlendException process(SmsConfig config, String args) {
Integer accountMax = config.getAccountMax();//每日最大发送量
Integer minuteMax = config.getMinuteMax();//每分钟最大发送量
if (SmsUtil.isNotEmpty(accountMax)) { //是否配置了每日限制
Integer i = (Integer) redis.getByKey(REDIS_KEY+args + "max");
public SmsBlendException process(String phone) {
SmsConfig config = BeanFactory.getSmsConfig();
Integer accountMax = config.getAccountMax(); // 每日最大发送量
Integer minuteMax = config.getMinuteMax(); // 每分钟最大发送量
if (SmsUtil.isNotEmpty(accountMax)) { // 是否配置了每日限制
Integer i = (Integer) smsDao.get(REDIS_KEY + phone + "max");
if (SmsUtil.isEmpty(i)) {
redis.setOrTime(REDIS_KEY+args + "max", 1,accTimer/1000);
} else if (i > accountMax) {
log.info("The phone:"+args +",number of short messages reached the maximum today");
return new SmsBlendException("The phone:"+args +",number of short messages reached the maximum today");
smsDao.set(REDIS_KEY + phone + "max", 1, accTimer / 1000);
} else if (i >= accountMax) {
log.info("The phone:" + phone + ",number of short messages reached the maximum today");
return new SmsBlendException("The phone:" + phone + ",number of short messages reached the maximum today");
} else {
redis.setOrTime(REDIS_KEY+args + "max", i + 1,accTimer/1000);
smsDao.set(REDIS_KEY + phone + "max", i + 1, accTimer / 1000);
}
}
if (SmsUtil.isNotEmpty(minuteMax)) { //是否配置了每分钟最大限制
Integer o = (Integer) redis.getByKey(REDIS_KEY+args);
if (SmsUtil.isNotEmpty(minuteMax)) { // 是否配置了每分钟最大限制
Integer o = (Integer) smsDao.get(REDIS_KEY + phone);
if (SmsUtil.isNotEmpty(o)) {
if (o < minuteMax) {
redis.setOrTime(REDIS_KEY+args, o + 1,minTimer/1000);
smsDao.set(REDIS_KEY + phone, o + 1, minTimer / 1000);
} else {
log.info("The phone:"+args +",number of short messages reached the maximum today");
return new SmsBlendException("The phone:", args + " Text messages are sent too often");
log.info("The phone:" + phone + ",number of short messages reached the maximum today");
return new SmsBlendException("The phone:", phone + " Text messages are sent too often");
}
} else {
redis.setOrTime(REDIS_KEY+args, 1,minTimer/1000);
smsDao.set(REDIS_KEY + phone, 1, minTimer / 1000);
}
}
return null;

View File

@ -1,20 +1,19 @@
package org.dromara.sms4j.solon.config;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.smsProxy.SmsInvocationHandler;
import org.dromara.sms4j.api.universal.SmsRedisUtil;
import org.dromara.sms4j.comm.config.SmsBanner;
import org.dromara.sms4j.comm.config.SmsConfig;
import org.dromara.sms4j.comm.config.SmsSqlConfig;
import org.dromara.sms4j.core.proxy.SmsInvocationHandler;
import org.dromara.sms4j.provider.config.SmsBanner;
import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.core.SupplierSqlConfig;
import org.dromara.sms4j.provider.factory.BeanFactory;
import org.dromara.sms4j.solon.aop.SolonRestrictedProcess;
import org.dromara.sms4j.solon.utils.SmsRedisUtils;
import org.noear.solon.Solon;
import org.noear.solon.Utils;
import org.noear.solon.annotation.*;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Condition;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.AopContext;
import org.noear.solon.core.Props;
import org.noear.solon.core.bean.LifecycleBean;
@ -34,11 +33,6 @@ public class SmsAutowiredConfig implements LifecycleBean {
return obj;
}
@Bean
public SmsSqlConfig smsSqlConfig() {
return injectObj("sms.sql", BeanFactory.getSmsSqlConfig());
}
@Bean
public SmsConfig smsConfig() {
return injectObj("sms", BeanFactory.getSmsConfig());
@ -70,21 +64,17 @@ public class SmsAutowiredConfig implements LifecycleBean {
return new SupplierConfig();
}
@Bean
@Condition(onProperty = "${sms.config-type}=sql_config")
public SupplierSqlConfig supplierSqlConfig(@Inject SmsSqlConfig smsSqlConfig) {
return new SupplierSqlConfig();
}
// @Bean
// @Condition(onProperty = "${sms.config-type}=sql_config")
// public SupplierSqlConfig supplierSqlConfig(@Inject SmsSqlConfig smsSqlConfig) {
// return new SupplierSqlConfig();
// }
//是在 solon 容器扫描完成之后执行的
@Override
public void start() throws Throwable {
/* 如果配置中启用了redis则注入redis工具*/
if (BeanFactory.getSmsConfig().getRedisCache()) {
//如果容器中不存在一个已经实现的redisUtil则自己注入一个
if (!Solon.context().hasWrap(SmsRedisUtil.class)) {
Solon.context().wrapAndPut(SmsRedisUtils.class);
}
SmsInvocationHandler.setRestrictedProcess(new SolonRestrictedProcess(aopContext));
log.debug("The redis cache is enabled for sms4j");
}

View File

@ -2,7 +2,6 @@ package org.dromara.sms4j.solon.config;
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.cloopen.config.CloopenConfig;
import org.dromara.sms4j.core.config.SupplierFactory;
import org.dromara.sms4j.ctyun.config.CtyunConfig;
import org.dromara.sms4j.emay.config.EmayConfig;
import org.dromara.sms4j.huawei.config.HuaweiConfig;
@ -29,43 +28,43 @@ public class SupplierConfig {
/** 阿里差异化配置*/
@Bean
public AlibabaConfig alibabaConfig(){
return injectObj("sms.alibaba", SupplierFactory.getAlibabaConfig());
return injectObj("sms.alibaba", new AlibabaConfig());
}
/** 华为差异化配置*/
@Bean
public HuaweiConfig huaweiConfig(){
return injectObj("sms.huawei", SupplierFactory.getHuaweiConfig());
return injectObj("sms.huawei", new HuaweiConfig());
}
/** 云片短信差异化配置*/
@Bean
public YunpianConfig yunpianConfig(){
return injectObj("sms.yunpian", SupplierFactory.getYunpianConfig());
return injectObj("sms.yunpian", new YunpianConfig());
}
/** 合一短信差异化配置*/
@Bean
public UniConfig uniConfig(){
return injectObj("sms.uni", SupplierFactory.getUniConfig());
return injectObj("sms.uni", new UniConfig());
}
/** 腾讯短信差异化配置*/
@Bean
public TencentConfig tencentConfig(){
return injectObj("sms.tencent", SupplierFactory.getTencentConfig());
return injectObj("sms.tencent", new TencentConfig());
}
/** 京东云短信差异化配置 */
@Bean
public JdCloudConfig jdCloudConfig(){
return injectObj("sms.jdcloud", SupplierFactory.getJdCloudConfig());
return injectObj("sms.jdcloud", new JdCloudConfig());
}
/** 容联云短信差异化配置 */
@Bean
public CloopenConfig cloopenConfig(){
return injectObj("sms.cloopen", SupplierFactory.getCloopenConfig());
return injectObj("sms.cloopen", new CloopenConfig());
}
/**
@ -73,12 +72,12 @@ public class SupplierConfig {
*/
@Bean
public EmayConfig emayConfig(){
return injectObj("sms.emay", SupplierFactory.getEmayConfig());
return injectObj("sms.emay", new EmayConfig());
}
/** 天翼云短信差异化配置 */
@Bean
public CtyunConfig ctyunConfig(){
return injectObj("sms.ctyun", SupplierFactory.getCtyunConfig());
return injectObj("sms.ctyun", new CtyunConfig());
}
}

View File

@ -0,0 +1,78 @@
package org.dromara.sms4j.solon.utils;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.dao.SmsDao;
import org.noear.solon.Solon;
import org.redisson.api.RedissonClient;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SmsRedisDaoImpl implements SmsDao {
private RedissonClient redisTemplate;
public SmsRedisDaoImpl() {
Thread t = new Thread(()->{
//如果获取到的bean为null则等待后重试最多重试五次
for(int i = 0; i < 5 ;i++){
RedissonClient bean = Solon.context().getBean(RedissonClient.class);
if (Objects.isNull(bean)){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
redisTemplate = bean;
return;
}
}
});
t.start();
}
public SmsRedisDaoImpl(RedissonClient redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void set(String key, Object value, long cacheTime) {
redisTemplate.getBucket(key).set(value, cacheTime, TimeUnit.SECONDS);
}
@Override
public void set(String key, Object value) {
redisTemplate.getBucket(key).set(value);
}
@Override
public Object get(String key) {
return redisTemplate.getBucket(key).get();
}
/**
* <p>说明将Map中的数据批量放置到redis中
* <p>
*
* @param valueMap 要放入的数据
* @name: multiSet
* @author :Wind
*/
public boolean multiSet(Map valueMap) {
try {
valueMap.forEach((key, val) -> redisTemplate.getBucket((String) key).set(val));
return true;
} catch (Exception e) {
log.error(e.toString());
return false;
}
}
@Override
public void clean() throws RuntimeException {
//TODO
}
}

View File

@ -1,466 +0,0 @@
package org.dromara.sms4j.solon.utils;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.universal.SmsRedisUtil;
import org.noear.solon.Solon;
import org.redisson.api.RedissonClient;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SmsRedisUtils implements SmsRedisUtil {
private RedissonClient redisTemplate;
public SmsRedisUtils() {
Thread t = new Thread(()->{
//如果获取到的bean为null则等待后重试最多重试五次
for(int i = 0; i < 5 ;i++){
RedissonClient bean = Solon.context().getBean(RedissonClient.class);
if (Objects.isNull(bean)){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
redisTemplate = bean;
return;
}
}
});
t.start();
}
public SmsRedisUtils(RedissonClient redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 说明设置redis的key的到期时间
*
* @param key redis的key
* @param time 到期时间
* @name: setTimeByKey
* @author :Wind
*/
public boolean setTimeByKey(String key, Long time) {
try {
if (time > 0) {
redisTemplate.getBucket(key).expire(time, TimeUnit.SECONDS);
return true;
}
return false;
} catch (Exception e) {
return false;
}
}
/**
* 说明放入redis
*
* @param key 要放入的key
* @param value 要放入的value
* @name: set
* @author :Wind
*/
public boolean set(String key, Object value) {
redisTemplate.getBucket(key).set(value);
return true;
}
/**
* 说明放入带过期时间的缓存
*
* @param time 到期时间()
* @name: setOrTime
* @author :Wind
*/
public boolean setOrTime(String key, Object value, Long time) {
try {
redisTemplate.getBucket(key).set(value, time, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
return false;
}
}
/**
* <p>说明将Map中的数据批量放置到redis中
* <p>
*
* @param valueMap 要放入的数据
* @name: multiSet
* @author :Wind
*/
public boolean multiSet(Map valueMap) {
try {
valueMap.forEach((key, val) -> redisTemplate.getBucket((String) key).set(val));
return true;
} catch (Exception e) {
log.error(e.toString());
return false;
}
}
/**
* 说明获取key对应的值
*
* @param key 要查询的key
* @name: getByKey
* @author :Wind
*/
public Object getByKey(String key) {
return redisTemplate.getBucket(key).get();
}
/**
* <p>说明获取字符串型值
*
* @param
* @name: getKyeString
* @author :Wind
*/
public String getKyeString(String key) {
return (String) getByKey(key);
}
/**
* 说明判断key是否存在
*
* @param key 要判断的key
* @name: hasKey
* @author :Wind
*/
public Boolean hasKey(String key) {
return redisTemplate.getBucket(key).isExists();
}
// /**
// * 说明根据key删除redis缓存可以批量删除
// *
// * @param key 要删除的key
// * @name: deleteKey
// * @author :Wind
// */
// public Boolean deleteKey(String... key) {
// if (key != null && key.length > 0) {
// if (key.length == 1) {
// return redisTemplate.getBucket(key[0]).delete();
// } else {
// Long delete = redisTemplate.(Arrays.asList(key));
// return delete >= 1L;
// }
// }
// return false;
// }
//
// public Boolean delete(String key) {
// Set<String> keys = redisTemplate.keys(key + "*");
// redisTemplate.removeByKeys(keys);
// return true;
// }
// /**
// * 根据key 获取key的过期时间
// *
// * @param key 不能为null
// * @return 时间() 返回-1, 代表为永久有效
// */
// public Long getKeyExpire(String key) {
// return redisTemplate.ttl(key);
// }
//
// /**
// * 修改redis中key的名称
// *
// * @param oldKey 旧的key值
// * @param newKey 新的key值
// */
// public void renameKey(String oldKey, String newKey) {
// redisTemplate..ren(oldKey, newKey);
// }
//
// /**
// * <p>说明将map对象存入redis
// * <p>
// *
// * @param map 要存入redis中的map
// * @name: setMap
// * @author :Wind
// */
// public <T, M> void MapSetMap(String key, Map<T, M> map) {
// redisClient.getHash(key).putAll(map);
// }
//
// /**
// * <p>说明获取所有hash表中字段
// * <p>
// *
// * @param
// * @name: getMapByKey
// * @author :Wind
// */
// public Set<Object> MapGetHashByKey(String key) {
// return redisTemplate.opsForHash().keys(key);
// }
//
// /**
// * <p>说明根据key和fieId获取对应的值
// * <p>
// *
// * @param fieId hash中的fieId也是Map的Key
// * @name: getValueByFieID
// * @author :Wind
// */
// public Object MapGetValueByFieID(String key, String fieId) {
// return redisTemplate.opsForHash().get(key, fieId);
// }
//
// /**
// * <p>说明根据key获取所有的键值对
// * <p>
// *
// * @param key redis中的key
// * @name: getMapByKey
// * @author :Wind
// */
// public Map<Object, Object> MapGetMapByKey(String key) {
// return redisTemplate.opsForHash().entries(key);
// }
//
// /**
// * <p>说明向key中添加一对新的键值对
// * <p>
// *
// * @param hashKey 键值对的key
// * @param value 键值对的value
// * @name: setNewMapValue
// * @author :Wind
// */
// public void MapSetNewMapValue(String key, String hashKey, Object value) {
// redisTemplate.opsForHash().put(key, hashKey, value);
// }
//
// /**
// * <p>说明根据key和field删除数据
// * <p>
// *
// * @param fields 要删除的fields
// * @return Long 影响的条数
// * @name: hashDelete
// * @author :Wind
// */
// public Long MapHashDelete(String key, Object... fields) {
// return redisTemplate.opsForHash().delete(key, fields);
// }
//
// /**
// * <p>说明查看key下存了多少条键值对
// * <p>
// *
// * @param key redis的key
// * @name: getMapValueSize
// * @author :Wind
// */
// public Long MapGetMapValueSize(String key) {
// return redisTemplate.opsForHash().size(key);
// }
//
// /**
// * 设置值到List中的头部
// *
// * @param key
// * @param value
// * @return
// * @author :Wind
// */
// public Boolean listAddInHead(String key, Object value) {
// try {
// redisTemplate.opsForList().leftPush(key, value);
// return true;
// } catch (Exception e) {
// log.error(e.getMessage());
// return false;
// }
// }
//
// /**
// * 批量设置值到List中的头部
// *
// * @param key List名字
// * @param values
// * @return
// * @author :Wind
// */
// public Boolean listAddAllInHead(String key, Collection<?> values) {
// try {
// redisTemplate.opsForList().leftPushAll(key, values);
// return true;
// } catch (Exception e) {
// log.error(e.getMessage());
// return false;
// }
// }
//
// /**
// * 如果存在List->key, 则设置值到List中的头部
// *
// * @param key List名字
// * @param value
// * @return
// * @author :Wind
// */
// public Boolean listAddIfPresent(String key, Object value) {
// try {
// redisTemplate.opsForList().leftPushIfPresent(key, value);
// return true;
// } catch (Exception e) {
// log.error(e.getMessage());
// return false;
// }
// }
//
// /**
// * 设置值到List中的尾部
// *
// * @param key List名字
// * @param value
// * @return
// */
// public Boolean listAddInEnd(String key, Object value) {
// try {
// redisTemplate.opsForList().rightPush(key, value);
// return true;
// } catch (Exception e) {
// log.error(e.getMessage());
// return false;
// }
// }
//
// /**
// * 批量设置值到List中的尾部
// *
// * @param key List名字
// * @param values 要设置的集合
// * @return
// */
// public Boolean listAddAllInEnd(String key, Collection<?> values) {
// try {
// redisTemplate.opsForList().rightPushAll(key, values);
// return true;
// } catch (Exception e) {
// log.error(e.getMessage());
// return false;
// }
// }
//
// /**
// * 通过索引去设置List->key中的值
// *
// * @param key redis的key
// * @param index 索引
// * @param value
// * @return
// * @author :Wind
// */
// public Boolean listAddByIndex(String key, long index, Object value) {
// try {
// redisTemplate.opsForList().set(key, index, value);
// return true;
// } catch (Exception e) {
// log.error(e.getMessage());
// return false;
// }
// }
//
//
// /**
// * 根据索引获取list中的值
// *
// * @param key list名字
// * @param index
// * @return
// * @author :Wind
// */
// public Object listGetByIndex(String key, long index) {
// return redisTemplate.opsForList().index(key, index);
// }
//
// /**
// * 根据索引范围获取list中的值
// *
// * @param key list名字
// * @param start
// * @param end
// * @return
// * @author :Wind
// */
// public List<Object> listGetByRange(String key, long start, long end) {
// return redisTemplate.opsForList().range(key, start, end);
// }
//
// /**
// * 移除并获取列表中第一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)
// *
// * @param key list名字
// * @return
// * @author :Wind
// */
// public Object listLeftPop(String key) {
// return redisTemplate.opsForList().leftPop(key);
// }
//
// /**
// * 移除并获取列表中最后一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)
// *
// * @param key list名字
// * @return
// * @author :Wind
// */
// public Object listRightPop(String key) {
// return redisTemplate.opsForList().rightPop(key);
// }
//
// /**
// * <p>说明获取列表元素的大小
// * <p>
// *
// * @param
// * @name: listGetSize
// * @author :Wind
// */
// public Long listGetSize(String key) {
// return redisTemplate.opsForList().size(key);
// }
//
// /**
// * 删除集合中值等于value的元素(
// * index=0, 删除所有值等于value的元素;
// * index>0, 从头部开始删除第一个值等于value的元素;
// * index<0, 从尾部开始删除第一个值等于value的元素)
// *
// * @param key
// * @param index
// * @param value
// * @return
// * @author :Wind
// */
// public Long listRemove(String key, long index, Object value) {
// return redisTemplate.opsForList().remove(key, index, value);
// }
//
// /**
// * <p>说明清除所有缓存
// * <p><b>该方法会清理掉redis中所有的缓存,谨慎使用</b>
// * <p>
// *
// * @name: empty
// * @author :Wind
// */
// public void empty() {
// redisTemplate.getConnectionFactory().getConnection().flushAll();
// }
}

View File

@ -1,9 +1,7 @@
package org.dromara.sms4j.test;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.config.SupplierFactory;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.dromara.sms4j.unisms.config.UniConfig;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
@ -16,15 +14,15 @@ public class Sms4jTest {
@Test
public void uniSmsTest() {
UniConfig build = UniConfig.builder()
.signature("***")
.accessKeyId("7Cr1***VJQ11Ap4***Mo7xmFg")
.templateId("2001")
.templateName("message")
.isSimple(true)
.build();
SupplierFactory.setUniConfig(build);
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.UNI_SMS).sendMessage("175***65952", "123123");
System.out.println(smsResponse);
// UniConfig build = UniConfig.builder()
// .signature("***")
// .accessKeyId("7Cr1***VJQ11Ap4***Mo7xmFg")
// .templateId("2001")
// .templateName("message")
// .isSimple(true)
// .build();
// SupplierFactory.setUniConfig(build);
// SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.UNI_SMS).sendMessage("175***65952", "123123");
// System.out.println(smsResponse);
}
}

View File

@ -5,10 +5,20 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.aliyun.service.AlibabaSmsImpl;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.cloopen.service.CloopenSmsImpl;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.dromara.sms4j.ctyun.service.CtyunSmsImpl;
import org.dromara.sms4j.emay.service.EmaySmsImpl;
import org.dromara.sms4j.huawei.service.HuaweiSmsImpl;
import org.dromara.sms4j.jdcloud.service.JdCloudSmsImpl;
import org.dromara.sms4j.netease.service.NeteaseSmsImpl;
import org.dromara.sms4j.tencent.service.TencentSmsImpl;
import org.dromara.sms4j.unisms.service.UniSmsImpl;
import org.dromara.sms4j.yunpian.service.YunPianSmsImpl;
import org.dromara.sms4j.zhutong.service.ZhutongSmsImpl;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@ -24,13 +34,19 @@ class Sms4jTest {
private static final String PHONE = "";
@Test
public void alibabaSmsTest() {
public void byLoadTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 通过负载均衡服务获取短信服务对象
SmsResponse smsResponse = SmsFactory.getSmsBlend().sendMessage(PHONE, SmsUtil.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void alibabaSmsTest() {
// 阿里
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.ALIBABA).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
SmsResponse smsResponse = SmsFactory.getBySupplier(AlibabaSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
}
@ -40,7 +56,7 @@ class Sms4jTest {
return;
}
// 华为
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.HUAWEI).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(HuaweiSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -51,7 +67,7 @@ class Sms4jTest {
return;
}
// 容联云
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.CLOOPEN).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(CloopenSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -62,7 +78,7 @@ class Sms4jTest {
return;
}
// 亿美软通
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.EMAY).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(EmaySmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -73,7 +89,7 @@ class Sms4jTest {
return;
}
// 京东云
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.JD_CLOUD).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(JdCloudSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -84,7 +100,7 @@ class Sms4jTest {
return;
}
// 云片
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.YUNPIAN).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(YunPianSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -95,7 +111,7 @@ class Sms4jTest {
return;
}
// 腾讯
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.TENCENT).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(TencentSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -106,7 +122,7 @@ class Sms4jTest {
return;
}
// 合一
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.UNI_SMS).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(UniSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -117,7 +133,7 @@ class Sms4jTest {
return;
}
// 天翼云
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.CTYUN).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(CtyunSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -128,7 +144,7 @@ class Sms4jTest {
return;
}
// 网易云短信
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.NETEASE).sendMessage(PHONE, SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(NeteaseSmsImpl.SUPPLIER).sendMessage(PHONE, SmsUtil.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -143,7 +159,7 @@ class Sms4jTest {
}
// 助通短信短信
String msg = StrUtil.format("【图书商城】您好,你的验证码是{}5分钟失效", SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.ZHUTONG).sendMessage(PHONE, msg);
SmsResponse smsResponse = SmsFactory.getBySupplier(ZhutongSmsImpl.SUPPLIER).sendMessage(PHONE, msg);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -159,7 +175,7 @@ class Sms4jTest {
// 助通短信短信
LinkedHashMap<String, String> messages = new LinkedHashMap<>(1);
messages.put("code", SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.ZHUTONG).sendMessage(PHONE, "59264", messages);
SmsResponse smsResponse = SmsFactory.getBySupplier(ZhutongSmsImpl.SUPPLIER).sendMessage(PHONE, "59264", messages);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -174,7 +190,7 @@ class Sms4jTest {
}
// 助通短信短信
String msg = StrUtil.format("【图书商城】您好,你的验证码是{}5分钟失效", SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.ZHUTONG).massTexting(ListUtil.of(PHONE, "180****1111"), msg);
SmsResponse smsResponse = SmsFactory.getBySupplier(ZhutongSmsImpl.SUPPLIER).massTexting(ListUtil.of(PHONE, "180****1111"), msg);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@ -190,8 +206,9 @@ class Sms4jTest {
// 助通短信短信
LinkedHashMap<String, String> messages = new LinkedHashMap<>(1);
messages.put("code", SmsUtil.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.createSmsBlend(SupplierType.ZHUTONG).massTexting(ListUtil.of(PHONE, "180****1111"), "59264", messages);
SmsResponse smsResponse = SmsFactory.getBySupplier(ZhutongSmsImpl.SUPPLIER).massTexting(ListUtil.of(PHONE, "180****1111"), "59264", messages);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
}

View File

@ -1,54 +0,0 @@
package org.dromara.sms4j.starter.aop;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.smsProxy.RestrictedProcess;
import org.dromara.sms4j.api.universal.SmsRedisUtil;
import org.dromara.sms4j.comm.config.SmsConfig;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.starter.utils.SmsSpringUtil;
import java.util.Objects;
@Slf4j
public class RestrictedProcessImpl extends RestrictedProcess {
private static final Long minTimer = 60 * 1000L;
private static final Long accTimer = 24 * 60 * 60 * 1000L;
private static final String REDIS_KEY = "sms:restricted:";
@Override
public SmsBlendException process(SmsConfig config,String args) throws Exception {
SmsRedisUtil redis = SmsSpringUtil.getBean(SmsRedisUtil.class);
if (Objects.isNull(redis)){
throw new SmsBlendException("The redis tool could not be found");
}
Integer accountMax = config.getAccountMax();//每日最大发送量
Integer minuteMax = config.getMinuteMax();//每分钟最大发送量
if (SmsUtil.isNotEmpty(accountMax)) { //是否配置了每日限制
Integer i = (Integer) redis.getByKey(REDIS_KEY + args + "max");
if (SmsUtil.isEmpty(i)) {
redis.setOrTime(REDIS_KEY + args + "max", 1, accTimer / 1000);
} else if (i > accountMax) {
log.info("The phone:" + args + ",number of short messages reached the maximum today");
return new SmsBlendException("The phone:" + args + ",number of short messages reached the maximum today");
} else {
redis.setOrTime(REDIS_KEY + args + "max", i + 1, accTimer / 1000);
}
}
if (SmsUtil.isNotEmpty(minuteMax)) { //是否配置了每分钟最大限制
Integer o = (Integer) redis.getByKey(REDIS_KEY + args);
if (SmsUtil.isNotEmpty(o)) {
if (o < minuteMax) {
redis.setOrTime(REDIS_KEY + args, o + 1, minTimer / 1000);
} else {
log.info("The phone:" + args + ",number of short messages reached the maximum today");
return new SmsBlendException("The phone:", args + " Text messages are sent too often");
}
} else {
redis.setOrTime(REDIS_KEY + args, 1, minTimer / 1000);
}
}
return null;
}
}

View File

@ -0,0 +1,56 @@
package org.dromara.sms4j.starter.aop;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.dao.SmsDao;
import org.dromara.sms4j.api.proxy.RestrictedProcess;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.provider.factory.BeanFactory;
import org.dromara.sms4j.starter.utils.SmsSpringUtil;
import java.util.Objects;
@Slf4j
public class SpringRestrictedProcess implements RestrictedProcess {
private static final Long minTimer = 60 * 1000L;
private static final Long accTimer = 24 * 60 * 60 * 1000L;
private static final String REDIS_KEY = "sms:restricted:";
@Override
public SmsBlendException process(String phone) throws Exception {
SmsConfig config = BeanFactory.getSmsConfig();
SmsDao smsDao = SmsSpringUtil.getBean(SmsDao.class);
if (Objects.isNull(smsDao)) {
throw new SmsBlendException("The dao tool could not be found");
}
Integer accountMax = config.getAccountMax(); // 每日最大发送量
Integer minuteMax = config.getMinuteMax(); // 每分钟最大发送量
if (SmsUtil.isNotEmpty(accountMax)) { // 是否配置了每日限制
Integer i = (Integer) smsDao.get(REDIS_KEY + phone + "max");
if (SmsUtil.isEmpty(i)) {
smsDao.set(REDIS_KEY + phone + "max", 1, accTimer / 1000);
} else if (i >= accountMax) {
log.info("The phone:" + phone + ",number of short messages reached the maximum today");
return new SmsBlendException("The phone:" + phone + ",number of short messages reached the maximum today");
} else {
smsDao.set(REDIS_KEY + phone + "max", i + 1, accTimer / 1000);
}
}
if (SmsUtil.isNotEmpty(minuteMax)) { // 是否配置了每分钟最大限制
Integer o = (Integer) smsDao.get(REDIS_KEY + phone);
if (SmsUtil.isNotEmpty(o)) {
if (o < minuteMax) {
smsDao.set(REDIS_KEY + phone, o + 1, minTimer / 1000);
} else {
log.info("The phone:" + phone + ",number of short messages reached the maximum today");
return new SmsBlendException("The phone:", phone + " Text messages are sent too often");
}
} else {
smsDao.set(REDIS_KEY + phone, 1, minTimer / 1000);
}
}
return null;
}
}

View File

@ -1,33 +1,21 @@
package org.dromara.sms4j.starter.config;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.smsProxy.SmsInvocationHandler;
import org.dromara.sms4j.api.universal.SmsRedisUtil;
import org.dromara.sms4j.comm.config.SmsBanner;
import org.dromara.sms4j.comm.config.SmsConfig;
import org.dromara.sms4j.comm.config.SmsSqlConfig;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.factory.BeanFactory;
import org.dromara.sms4j.core.SupplierSqlConfig;
import org.dromara.sms4j.core.config.SupplierFactory;
import org.dromara.sms4j.starter.aop.RestrictedProcessImpl;
import org.dromara.sms4j.provider.config.SmsBanner;
import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.provider.factory.BeanFactory;
import org.dromara.sms4j.starter.utils.ConfigUtil;
import org.dromara.sms4j.starter.utils.SmsRedisUtils;
import org.dromara.sms4j.starter.utils.SmsSpringUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
@ -40,10 +28,6 @@ public class SmsAutowiredConfig {
this.smsSpringUtil = smsSpringUtil;
}
@Bean
@ConfigurationProperties(prefix = "sms.sql")
protected SmsSqlConfig smsSqlConfig(){return BeanFactory.getSmsSqlConfig();}
@Bean
@Primary
@ConfigurationProperties(prefix = "sms") //指定配置文件注入属性前缀
@ -53,55 +37,59 @@ public class SmsAutowiredConfig {
/** 注入一个定时器*/
@Bean
@Lazy
protected DelayedTime delayedTime(){
return BeanFactory.getDelayedTime();
return BeanFactory.getDelayedTime();
}
/** 注入线程池*/
@Bean("smsExecutor")
@Lazy
protected Executor taskExecutor(SmsConfig config){
return BeanFactory.setExecutor(config);
return BeanFactory.setExecutor(config);
}
/** 注入一个配置文件读取工具*/
@Bean
@Bean("smsConfigUtil")
@Lazy
protected ConfigUtil configUtil(Environment environment){
return new ConfigUtil(environment);
}
/** smsConfig参数意义为确保注入时smsConfig已经存在*/
@Bean
@ConditionalOnProperty(prefix = "sms", name = "config-type", havingValue = "config_file")
@ConditionalOnProperty(prefix = "sms", name = "config-type", havingValue = "yaml")
protected SupplierConfig supplierConfig(SmsConfig smsConfig){
return new SupplierConfig();
}
@Bean
@ConditionalOnProperty(prefix = "sms", name = "config-type", havingValue = "sql_config")
protected SupplierSqlConfig supplierSqlConfig(SmsSqlConfig smsSqlConfig) throws SQLException {
DataSource bean = SmsSpringUtil.getBean(DataSource.class);
if (!Objects.isNull(bean)){
BeanFactory.getJDBCTool().setConnection(bean.getConnection());
}
return new SupplierSqlConfig();
}
// @Bean
// @ConditionalOnProperty(prefix = "sms", name = "config-type", havingValue = "sql_config")
// protected SupplierSqlConfig supplierSqlConfig(SmsSqlConfig smsSqlConfig) throws SQLException {
// DataSource bean = SmsSpringUtil.getBean(DataSource.class);
// if (!Objects.isNull(bean)){
// BeanFactory.getJDBCTool().setConnection(bean.getConnection());
// }
// return new SupplierSqlConfig();
// }
@PostConstruct
void init(){
/* 如果配置中启用了redis则注入redis工具*/
if (BeanFactory.getSmsConfig().getRedisCache()){
//如果用户没有实现RedisUtil接口则注入默认的实现
if (!SmsSpringUtil.interfaceExist(SmsRedisUtil.class)){
smsSpringUtil.createBean(SmsRedisUtils.class);
}
SmsInvocationHandler.setRestrictedProcess(new RestrictedProcessImpl());
log.debug("The redis cache is enabled for sms4j");
}
// 将spring中存在的所有配置设置到配置工厂并添加至负载均衡器
Map<String, org.dromara.sms4j.api.universal.SupplierConfig> beansOfType = SmsSpringUtil.getBeansOfType(org.dromara.sms4j.api.universal.SupplierConfig.class);
for (org.dromara.sms4j.api.universal.SupplierConfig s : beansOfType.values()) {
SupplierFactory.setSupplierConfig(s);
}
// /* 如果配置中启用了redis则注入redis工具*/
// if (BeanFactory.getSmsConfig().getRedisCache()){
// //如果用户没有实现RedisUtil接口则注入默认的实现
// if (!SmsSpringUtil.interfaceExist(SmsRedisUtil.class)){
// smsSpringUtil.createBean(SmsRedisUtils.class);
// }
// SmsInvocationHandler.setRestrictedProcess(new RestrictedProcessImpl());
// log.debug("The redis cache is enabled for sms4j");
// }
// // 将spring中存在的所有配置设置到配置工厂并添加至负载均衡器
// Map<String, org.dromara.sms4j.api.universal.SupplierConfig> beansOfType = SmsSpringUtil.getBeansOfType(org.dromara.sms4j.api.universal.SupplierConfig.class);
// for (org.dromara.sms4j.api.universal.SupplierConfig s : beansOfType.values()) {
// SupplierFactory.setSupplierConfig(s);
// }
//打印banner
if (BeanFactory.getSmsConfig().getIsPrint()){
SmsBanner.PrintBanner(Constant.VERSION);

View File

@ -0,0 +1,96 @@
package org.dromara.sms4j.starter.config;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.aliyun.config.AlibabaFactory;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.dao.SmsDao;
import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl;
import org.dromara.sms4j.api.proxy.RestrictedProcess;
import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.cloopen.config.CloopenFactory;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.utils.SmsUtil;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.core.proxy.SmsInvocationHandler;
import org.dromara.sms4j.ctyun.config.CtyunFactory;
import org.dromara.sms4j.emay.config.EmayFactory;
import org.dromara.sms4j.huawei.config.HuaweiFactory;
import org.dromara.sms4j.jdcloud.config.JdCloudFactory;
import org.dromara.sms4j.netease.config.NeteaseFactory;
import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.provider.factory.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import org.dromara.sms4j.starter.aop.SpringRestrictedProcess;
import org.dromara.sms4j.tencent.config.TencentFactory;
import org.dromara.sms4j.unisms.config.UniFactory;
import org.dromara.sms4j.yunpian.config.YunPianFactory;
import org.dromara.sms4j.zhutong.config.ZhutongFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
@Component
@RequiredArgsConstructor
@Slf4j
public class SmsBlendsInitializer {
private final List<BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig>> factoryList;
private final SmsConfig smsConfig;
private final Map<String, Map<String, Object>> blends;
@PostConstruct
public void initBlends() {
this.registerDefaultFactory();
// 注册短信对象工厂
ProviderFactoryHolder.registerFactory(factoryList);
// 解析供应商配置
for(String configId : blends.keySet()) {
Map<String, Object> configMap = blends.get(configId);
Object supplierObj = configMap.get(Constant.SUPPLIER_KEY);
String supplier = supplierObj == null ? "" : String.valueOf(supplierObj);
supplier = StrUtil.isEmpty(supplier) ? configId : supplier;
BaseProviderFactory<SmsBlend, SupplierConfig> providerFactory = (BaseProviderFactory<SmsBlend, org.dromara.sms4j.api.universal.SupplierConfig>) ProviderFactoryHolder.requireForSupplier(supplier);
if(providerFactory == null) {
log.warn("创建\"{}\"的短信服务失败,未找到供应商为\"{}\"的服务", configId, supplier);
continue;
}
configMap.put("config-id", configId);
SmsUtil.replaceKeysSeperator(configMap, "-", "_");
JSONObject configJson = new JSONObject(configMap);
org.dromara.sms4j.api.universal.SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass());
if(Boolean.TRUE.equals(smsConfig.getRestricted())) {
SmsFactory.createRestrictedSmsBlend(supplierConfig);
} else {
SmsFactory.createSmsBlend(supplierConfig);
}
}
//注册短信拦截实现
SmsInvocationHandler.setRestrictedProcess(new SpringRestrictedProcess());
}
/**
* 注册默认工厂实例
*/
private void registerDefaultFactory() {
ProviderFactoryHolder.registerFactory(AlibabaFactory.instance());
ProviderFactoryHolder.registerFactory(CloopenFactory.instance());
ProviderFactoryHolder.registerFactory(CtyunFactory.instance());
ProviderFactoryHolder.registerFactory(EmayFactory.instance());
ProviderFactoryHolder.registerFactory(HuaweiFactory.instance());
ProviderFactoryHolder.registerFactory(JdCloudFactory.instance());
ProviderFactoryHolder.registerFactory(NeteaseFactory.instance());
ProviderFactoryHolder.registerFactory(TencentFactory.instance());
ProviderFactoryHolder.registerFactory(UniFactory.instance());
ProviderFactoryHolder.registerFactory(YunPianFactory.instance());
ProviderFactoryHolder.registerFactory(ZhutongFactory.instance());
}
}

View File

@ -1,120 +1,18 @@
package org.dromara.sms4j.starter.config;
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.cloopen.config.CloopenConfig;
import org.dromara.sms4j.core.config.SupplierFactory;
import org.dromara.sms4j.ctyun.config.CtyunConfig;
import org.dromara.sms4j.emay.config.EmayConfig;
import org.dromara.sms4j.huawei.config.HuaweiConfig;
import org.dromara.sms4j.jdcloud.config.JdCloudConfig;
import org.dromara.sms4j.netease.config.NeteaseConfig;
import org.dromara.sms4j.tencent.config.TencentConfig;
import org.dromara.sms4j.unisms.config.UniConfig;
import org.dromara.sms4j.yunpian.config.YunpianConfig;
import org.dromara.sms4j.zhutong.config.ZhutongConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import java.util.LinkedHashMap;
import java.util.Map;
public class SupplierConfig {
/**
* 阿里差异化配置
*/
/** smsConfig参数意义为确保注入时smsConfig已经存在*/
@Bean
@ConfigurationProperties(prefix = "sms.alibaba")
protected AlibabaConfig alibabaConfig() {
return SupplierFactory.getAlibabaConfig();
@ConfigurationProperties(prefix = "sms.blends")
protected Map<String, Map<String, Object>> blends(){
return new LinkedHashMap<>();
}
/**
* 华为差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.huawei")
protected HuaweiConfig huaweiConfig() {
return SupplierFactory.getHuaweiConfig();
}
/**
* 云片短信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.yunpian")
protected YunpianConfig yunpianConfig() {
return SupplierFactory.getYunpianConfig();
}
/**
* 合一短信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.uni")
protected UniConfig uniConfig() {
return SupplierFactory.getUniConfig();
}
/**
* 腾讯短信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.tencent")
protected TencentConfig tencentConfig() {
return SupplierFactory.getTencentConfig();
}
/**
* 京东云短信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.jdcloud")
protected JdCloudConfig jdCloudConfig() {
return SupplierFactory.getJdCloudConfig();
}
/**
* 容联云短信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.cloopen")
protected CloopenConfig cloopenConfig() {
return SupplierFactory.getCloopenConfig();
}
/**
* 亿美软通短信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.emay")
protected EmayConfig emayConfig() {
return SupplierFactory.getEmayConfig();
}
/**
* 天翼云短信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.ctyun")
protected CtyunConfig ctyunConfig() {
return SupplierFactory.getCtyunConfig();
}
/**
* 网易云信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.netease")
protected NeteaseConfig neteaseConfig() {
return SupplierFactory.getNeteaseConfig();
}
/**
* 助通信差异化配置
*/
@Bean
@ConfigurationProperties(prefix = "sms.zhutong")
protected ZhutongConfig zhutongConfig() {
return SupplierFactory.getZhutongConfig();
}
}

View File

@ -1,479 +0,0 @@
package org.dromara.sms4j.starter.utils;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.universal.SmsRedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SmsRedisUtils implements SmsRedisUtil {
private RedisTemplate<String, Object> redisTemplate;
@Autowired
public void init(RedisConnectionFactory connectionFactory) {
if (connectionFactory == null){
log.error("RedisConnectionFactory is not found");
}
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer();
// 构建StringRedisTemplate
StringRedisTemplate stringTemplate = new StringRedisTemplate();
stringTemplate.setConnectionFactory(connectionFactory);
stringTemplate.afterPropertiesSet();
// 构建RedisTemplate
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
this.redisTemplate = template;
}
public SmsRedisUtils() {
}
public SmsRedisUtils(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 说明设置redis的key的到期时间
*
* @param key redis的key
* @param time 到期时间
* @name: setTimeByKey
* @author :Wind
*/
public boolean setTimeByKey(String key, Long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
return true;
}
return false;
} catch (Exception e) {
return false;
}
}
/**
* 说明放入redis
*
* @param key 要放入的key
* @param value 要放入的value
* @name: set
* @author :Wind
*/
public boolean set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
return true;
}
/**
* 说明放入带过期时间的缓存
*
* @param time 到期时间()
* @name: setOrTime
* @author :Wind
*/
public boolean setOrTime(String key, Object value, Long time) {
try {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
return false;
}
}
/**
* <p>说明将Map中的数据批量放置到redis中
* <p>
*
* @param valueMap 要放入的数据
* @name: multiSet
* @author :Wind
*/
public boolean multiSet(Map valueMap) {
try {
redisTemplate.opsForValue().multiSet(valueMap);
return true;
} catch (Exception e) {
log.error(e.toString());
return false;
}
}
/**
* 说明获取key对应的值
*
* @param key 要查询的key
* @name: getByKey
* @author :Wind
*/
public Object getByKey(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* <p>说明获取字符串型值
*
* @param
* @name: getKyeString
* @author :Wind
*/
public String getKyeString(String key) {
return (String) getByKey(key);
}
/**
* 说明判断key是否存在
*
* @param key 要判断的key
* @name: hasKey
* @author :Wind
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 说明根据key删除redis缓存可以批量删除
*
* @param key 要删除的key
* @name: deleteKey
* @author :Wind
*/
public Boolean deleteKey(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
return redisTemplate.delete(key[0]);
} else {
Long delete = redisTemplate.delete(Arrays.asList(key));
return delete >= 1L;
}
}
return false;
}
public Boolean delete(String key) {
Set<String> keys = redisTemplate.keys(key + "*");
redisTemplate.delete(keys);
return true;
}
/**
* 根据key 获取key的过期时间
*
* @param key 不能为null
* @return 时间() 返回-1, 代表为永久有效
*/
public Long getKeyExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 修改redis中key的名称
*
* @param oldKey 旧的key值
* @param newKey 新的key值
*/
public void renameKey(String oldKey, String newKey) {
redisTemplate.rename(oldKey, newKey);
}
/**
* <p>说明将map对象存入redis
* <p>
*
* @param map 要存入redis中的map
* @name: setMap
* @author :Wind
*/
public <T, M> void MapSetMap(String key, Map<T, M> map) {
redisTemplate.opsForHash().putAll(key, map);
}
/**
* <p>说明获取所有hash表中字段
* <p>
*
* @param
* @name: getMapByKey
* @author :Wind
*/
public Set<Object> MapGetHashByKey(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* <p>说明根据key和fieId获取对应的值
* <p>
*
* @param fieId hash中的fieId也是Map的Key
* @name: getValueByFieID
* @author :Wind
*/
public Object MapGetValueByFieID(String key, String fieId) {
return redisTemplate.opsForHash().get(key, fieId);
}
/**
* <p>说明根据key获取所有的键值对
* <p>
*
* @param key redis中的key
* @name: getMapByKey
* @author :Wind
*/
public Map<Object, Object> MapGetMapByKey(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* <p>说明向key中添加一对新的键值对
* <p>
*
* @param hashKey 键值对的key
* @param value 键值对的value
* @name: setNewMapValue
* @author :Wind
*/
public void MapSetNewMapValue(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
/**
* <p>说明根据key和field删除数据
* <p>
*
* @param fields 要删除的fields
* @return Long 影响的条数
* @name: hashDelete
* @author :Wind
*/
public Long MapHashDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* <p>说明查看key下存了多少条键值对
* <p>
*
* @param key redis的key
* @name: getMapValueSize
* @author :Wind
*/
public Long MapGetMapValueSize(String key) {
return redisTemplate.opsForHash().size(key);
}
/**
* 设置值到List中的头部
*
* @param key
* @param value
* @return
* @author :Wind
*/
public Boolean listAddInHead(String key, Object value) {
try {
redisTemplate.opsForList().leftPush(key, value);
return true;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
/**
* 批量设置值到List中的头部
*
* @param key List名字
* @param values
* @return
* @author :Wind
*/
public Boolean listAddAllInHead(String key, Collection<?> values) {
try {
redisTemplate.opsForList().leftPushAll(key, values);
return true;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
/**
* 如果存在List->key, 则设置值到List中的头部
*
* @param key List名字
* @param value
* @return
* @author :Wind
*/
public Boolean listAddIfPresent(String key, Object value) {
try {
redisTemplate.opsForList().leftPushIfPresent(key, value);
return true;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
/**
* 设置值到List中的尾部
*
* @param key List名字
* @param value
* @return
*/
public Boolean listAddInEnd(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
/**
* 批量设置值到List中的尾部
*
* @param key List名字
* @param values 要设置的集合
* @return
*/
public Boolean listAddAllInEnd(String key, Collection<?> values) {
try {
redisTemplate.opsForList().rightPushAll(key, values);
return true;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
/**
* 通过索引去设置List->key中的值
*
* @param key redis的key
* @param index 索引
* @param value
* @return
* @author :Wind
*/
public Boolean listAddByIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
/**
* 根据索引获取list中的值
*
* @param key list名字
* @param index
* @return
* @author :Wind
*/
public Object listGetByIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
/**
* 根据索引范围获取list中的值
*
* @param key list名字
* @param start
* @param end
* @return
* @author :Wind
*/
public List<Object> listGetByRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 移除并获取列表中第一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)
*
* @param key list名字
* @return
* @author :Wind
*/
public Object listLeftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* 移除并获取列表中最后一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)
*
* @param key list名字
* @return
* @author :Wind
*/
public Object listRightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
/**
* <p>说明获取列表元素的大小
* <p>
*
* @param
* @name: listGetSize
* @author :Wind
*/
public Long listGetSize(String key) {
return redisTemplate.opsForList().size(key);
}
/**
* 删除集合中值等于value的元素(
* index=0, 删除所有值等于value的元素;
* index>0, 从头部开始删除第一个值等于value的元素;
* index<0, 从尾部开始删除第一个值等于value的元素)
*
* @param key
* @param index
* @param value
* @return
* @author :Wind
*/
public Long listRemove(String key, long index, Object value) {
return redisTemplate.opsForList().remove(key, index, value);
}
/**
* <p>说明清除所有缓存
* <p><b>该方法会清理掉redis中所有的缓存,谨慎使用</b>
* <p>
*
* @name: empty
* @author :Wind
*/
public void empty() {
redisTemplate.getConnectionFactory().getConnection().flushAll();
}
}

View File

@ -2,5 +2,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.dromara.sms4j.starter.config.SmsMainConfig,\
org.dromara.sms4j.starter.config.SmsAutowiredConfig,\
org.dromara.sms4j.starter.config.SupplierConfig,\
org.dromara.sms4j.comm.config.SmsConfig,\
org.dromara.sms4j.comm.config.SmsSqlConfig
org.dromara.sms4j.provider.config.SmsConfig

Some files were not shown because too many files have changed in this diff Show More