完成华为云国内短信接入

修改基础http支持框架为Forest
重构云片短信该http框架为Forest
重构模块,添加自动配置模块
This commit is contained in:
wind 2023-04-02 01:17:59 +08:00
parent 1a561009d1
commit d55a27ff6f
37 changed files with 589 additions and 869 deletions

41
pom.xml
View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<name>sms-aggregation</name>
<packaging>pom</packaging>
<description>sms_aggregation</description>
@ -19,6 +19,7 @@
<module>sms-aggregation-yunpian</module>
<module>sms-aggregation-spring-boot-starter</module>
<module>sms-aggregation-huawei</module>
<module>sms-aggregation-autoimmit</module>
</modules>
<!-- 开源协议 apache 2.0 -->
<licenses>
@ -49,7 +50,7 @@
<properties>
<java.version>1.8</java.version>
<spring.boot.version>2.7.10</spring.boot.version>
<modules.version>1.0.2</modules.version>
<modules.version>1.0.3</modules.version>
<aliyun.version>2.0.23</aliyun.version>
<json.version>2.0.15</json.version>
<okhttp.version>3.14.9</okhttp.version>
@ -111,6 +112,12 @@
<version>${modules.version}</version>
</dependency>
<dependency>
<groupId>kim.wind</groupId>
<artifactId>sms-aggregation-autoimmit</artifactId>
<version>${modules.version}</version>
</dependency>
<!--通用模块-->
<dependency>
<groupId>kim.wind</groupId>
@ -130,12 +137,6 @@
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>${aliyun.version}</version>
<exclusions>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 阿里JSON解析器 -->
@ -143,20 +144,8 @@
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${json.version}</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- okhttp依赖-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!--Forest依赖 声明式HTTP客户端框架-->
<dependency>
<groupId>com.dtflys.forest</groupId>
@ -184,12 +173,6 @@
<groupId>com.apistd.uni</groupId>
<artifactId>uni-sdk</artifactId>
<version>${unisms.version}</version>
<exclusions>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 腾讯云短信-->
@ -197,12 +180,6 @@
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>${tencent.version}</version>
<exclusions>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

View File

@ -5,13 +5,13 @@
<parent>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms-aggregation-aliyun</artifactId>
<name>sms-aggregation-aliyun</name>
<description>sms-aggregation-aliyun</description>
<version>1.0.2</version>
<version>1.0.3</version>
<properties>
<java.version>1.8</java.version>
</properties>

View File

@ -5,16 +5,16 @@ import com.aliyun.teaopenapi.models.Config;
import kim.wind.sms.aliyun.service.AlibabaSmsImpl;
import kim.wind.sms.api.SmsBlend;
import lombok.Data;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "sms.alibaba") //指定配置文件注入属性前缀
@Data
@ConditionalOnProperty(prefix = "sms", name = "supplier", havingValue = "alibaba")
@ConditionalOnProperty(name = "sms.supplier", havingValue = "alibaba")
public class AlibabaSmsConfig {
/** accessKey*/
@ -32,7 +32,7 @@ public class AlibabaSmsConfig {
@Bean
public Client config() throws Exception {
public Client client() throws Exception {
Config config = new Config()
// AccessKey ID
.setAccessKeyId(accessKeyId)
@ -43,8 +43,8 @@ public class AlibabaSmsConfig {
return new Client(config);
}
// @Bean
// public SmsBlend smsBlend(){
// return new AlibabaSmsImpl();
// }
@Bean
public SmsBlend smsBlend(){
return new AlibabaSmsImpl();
}
}

View File

@ -15,7 +15,7 @@ import kim.wind.sms.comm.annotation.Restricted;
import kim.wind.sms.comm.delayedTime.DelayedTime;
import kim.wind.sms.comm.entity.SmsResponse;
import kim.wind.sms.comm.exception.SmsBlendException;
import kim.wind.sms.comm.utils.HTTPUtils;
import kim.wind.sms.comm.utils.http.HttpJsonTool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@ -111,7 +111,7 @@ public class AlibabaSmsImpl implements SmsBlend {
try {
SendBatchSmsResponse sendBatchSmsResponse = client.sendBatchSmsWithOptions(sendBatchSmsRequest, runtime);
smsResponse.setBizId(sendBatchSmsResponse.body.getBizId());
smsResponse.setData(HTTPUtils.getJSONObject(sendBatchSmsResponse.body));
smsResponse.setData(HttpJsonTool.getJSONObject(sendBatchSmsResponse.body));
smsResponse.setCode(sendBatchSmsResponse.statusCode);
if (!"OK".equals(sendBatchSmsResponse.body.code)) {
smsResponse.setErrMessage((sendBatchSmsResponse.body.message));

View File

@ -5,13 +5,13 @@
<parent>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms-aggregation-api</artifactId>
<name>sms-aggregation-api</name>
<description>sms-aggregation-api</description>
<version>1.0.2</version>
<version>1.0.3</version>
<properties>
<java.version>1.8</java.version>

View File

@ -1,6 +1,7 @@
package kim.wind.sms.starter.config;
package kim.wind.sms.autoimmit.aop;
import kim.wind.sms.autoimmit.config.SmsConfig;
import kim.wind.sms.comm.exception.SmsBlendException;
import kim.wind.sms.comm.utils.RedisUtils;
import kim.wind.sms.comm.utils.SmsUtil;
@ -23,7 +24,10 @@ public class AopAdvice {
private static final String REDIS_KEY = "sms:restricted:";
@Autowired
private SmsMainConfig config;
private SmsConfig config;
@Autowired
private SpringUtil springUtil;
@Pointcut("@annotation(kim.wind.sms.comm.annotation.Restricted)")
@ -90,32 +94,36 @@ public class AopAdvice {
}
private SmsBlendException redisProcess(String args) throws Exception{
RedisUtils redis = SpringUtil.getBean(RedisUtils.class);
if (redis == null || config.getRedisCache().equals("false")){
if (config.getRedisCache().equals("false")){
return process(args);
}
// else {
// springUtil.createBean(RedisUtils.class.getName(),new RedisUtils());
// }
RedisUtils redis = SpringUtil.getBean(RedisUtils.class);
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.set(REDIS_KEY+args + "max", 1);
redis.setOrTime(REDIS_KEY+args + "max", 1,accTimer/1000);
} else if (i > accountMax) {
return new SmsBlendException("accountMax", args + "今日短信已达最大次数");
} else {
redis.set(REDIS_KEY+args + "max", i + 1);
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.set(REDIS_KEY+args, o + 1);
redis.setOrTime(REDIS_KEY+args, o + 1,minTimer/1000);
} else {
return new SmsBlendException("minuteMax", args + "短信发送过于频繁!");
}
} else {
redis.set(REDIS_KEY+args, 1);
redis.setOrTime(REDIS_KEY+args, 1,minTimer/1000);
}
}
return null;

View File

@ -0,0 +1,68 @@
package kim.wind.sms.autoimmit.config;
import kim.wind.sms.autoimmit.aop.AopAdvice;
import kim.wind.sms.comm.config.SmsBanner;
import kim.wind.sms.comm.delayedTime.DelayedTime;
import kim.wind.sms.comm.utils.RedisUtils;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Data
public class SmsAutowiredConfig {
public void init(){
SmsBanner.PrintBanner("V1.0.3");
}
@Bean
@ConfigurationProperties(prefix = "sms") //指定配置文件注入属性前缀
public SmsConfig smsConfig(){
return new SmsConfig();
}
/** 注入一个定时器*/
@Bean
public DelayedTime delayedTime(){
return new DelayedTime();
}
/** 如果启用了短信限制则注入Aop组件*/
@Bean
@ConditionalOnProperty(prefix = "sms", name = "restricted", havingValue = "true")
public AopAdvice aopAdvice(){
return new AopAdvice();
}
/** 如果启用了redis作为缓存则注入redis工具类*/
@Bean
@ConditionalOnProperty(prefix = "sms", name = "redis-cache", havingValue = "true")
public RedisUtils redisUtils(){
return new RedisUtils();
}
/** 注入线程池*/
@Bean("smsExecutor")
protected Executor taskExecutor(SmsConfig config){
// 创建一个线程池对象
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(config.getCorePoolSize());
executor.setMaxPoolSize(config.getMaxPoolSize());
executor.setQueueCapacity(config.getQueueCapacity());
executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
executor.setThreadNamePrefix(config.getThreadNamePrefix());
executor.setWaitForTasksToCompleteOnShutdown(true);
// 线程池对拒绝任务的处理策略,当线程池没有处理能力的时候该策略会直接在 execute 方法的调用线程中运行被拒绝的任务如果执行程序已关闭则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//初始化线程池
executor.initialize();
return executor;
}
}

View File

@ -0,0 +1,43 @@
package kim.wind.sms.autoimmit.config;
import lombok.Data;
@Data
public class SmsConfig {
/** 短信服务商*/
private String supplier;
/** 打印banner*/
private String isPrint = "true";
/** 是否开启短信限制*/
private String restricted;
/** 是否使用redis进行缓存*/
private String redisCache = "false";
/** 单账号每日最大发送量*/
private Integer accountMax;
/** 单账号每分钟最大发送*/
private Integer minuteMax;
/**核心线程池大小*/
private Integer corePoolSize = 10;
/** 最大线程数*/
private Integer maxPoolSize = 30;
/** 队列容量*/
private Integer queueCapacity = 50;
/** 活跃时间*/
private Integer keepAliveSeconds = 60;
/** 线程名字前缀*/
private String threadNamePrefix = "sms-executor-";
/** 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean*/
private Boolean shutdownStrategy = true;
}

View File

@ -5,13 +5,13 @@
<parent>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms-aggregation-comm</artifactId>
<name>sms-aggregation-comm</name>
<description>sms-aggregation-comm</description>
<version>1.0.2</version>
<version>1.0.3</version>
<properties>
<java.version>1.8</java.version>
</properties>
@ -27,11 +27,6 @@
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>

View File

@ -1,5 +1,7 @@
package kim.wind.sms.comm.config;
import kim.wind.sms.comm.constant.Constant;
public class SmsBanner {
private static final String banner =
@ -10,8 +12,8 @@ public class SmsBanner {
" ____) | | | | | ____) | / ____ \\ | |__| | | |__| | | | \\ \\ | |____ | |__| | / ____ \\ | | _| |_ | |__| | | |\\ |\n" +
" |_____/ |_| |_| |_____/ /_/ \\_\\ \\_____| \\_____| |_| \\_\\ |______| \\_____| /_/ \\_\\ |_| |_____| \\____/ |_| \\_|\n" +
" \n" +
" V1.0.2";
public static void PrintBanner() {
System.out.println(banner);
" ";
public static void PrintBanner(String version) {
System.out.println(banner+version);
}
}

View File

@ -0,0 +1,41 @@
package kim.wind.sms.comm.constant;
/**
* Constant
* <p> 短信应用常量
*
* @author :Wind
* 2023/3/31 19:33
**/
public abstract class Constant {
/**
* 用于格式化鉴权头域,"Authorization"参数赋值
*/
public static final String HUAWEI_AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";
/**
* 用于格式化鉴权头域,"X-WSSE"参数赋值
*/
public static final String HUAWEI_WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
/**
* 华为云国内短信访问URI
*/
public static final String HUAWEI_REQUEST_URL = "/sms/batchSendSms/v1";
/**
* Content-Type
*/
public static final String FROM_URLENCODED = "application/x-www-form-urlencoded";
/**
* 华为云规定 java时间格式
*/
public static final String HUAWEI_JAVA_DATE = "yyyy-MM-dd'T'HH:mm:ss'Z'";
/** 云片短信国内短信请求地址*/
public static final String YUNPIAN_URL = "https://sms.yunpian.com/v2";
private Constant() {
}
}

View File

@ -11,7 +11,7 @@ import lombok.Data;
@Data
public class SmsResponse {
/** 状态码*/
private Integer code;
private String code;
/** 返回消息*/
private String message;
/** 错误码,没有错误信息则为空*/

View File

@ -1,420 +0,0 @@
package kim.wind.sms.comm.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import kim.wind.sms.comm.utils.http.OKResponse;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Map;
/**
* <p>类名: HTTPUtils
* <p>说明 封装okhttp3简单化请求创建流程并且可以当做工具类进行自动装配使用
* <p>构建请求只需要获得OKHTTPUtils的对象然后逐步调用即可
* <p>
* <p>http.setBaseURL("http://www.baidu.com").builder().get("/wenku").sync();
* <p>构建一个post请求只需要
* <p>http.setBaseURL("http://www.baidu.com").builder().post("/wenku",object).sync();
* <p>如果在Spring Boot中作为组件自动装配使用{@code baseURL}将读取配置文件中的{@code okhttp.url}获取默认的请求路径则不需要再次调用 setBaseURL()方法进行设置
* <p>依赖于 {@code okhttp3} {@code alibaba.fastjson} {@code lombok.slf4j}
* <p>{@code
* <dependency>
* <groupId>com.squareup.okhttp3</groupId>
* <artifactId>okhttp</artifactId>
* <version>3.14.9</version>
* </dependency>}
* <p>{@code
* <dependency>
* <groupId>com.alibaba</groupId>
* <artifactId>fastjson</artifactId>
* <version>1.2.74</version>
*</dependency>}
*
* @author :Wind
* @date :2022/11/07 14:10
**/
@Slf4j
public class HTTPUtils {
/**
* 默认路径
*/
private String baseURL;
/**
* 保存的默认地址
*/
private String defaultURL;
/**
* 请求对象
*/
private volatile Request request;
/**
* 请求构建对象
*/
private Request.Builder builder;
/**
* okhttp客户端
*/
private volatile OkHttpClient client;
/**
* 最终返回的对象
*/
private OKResponse okResponse;
/**
* 最终请求的url
*/
private String url = "";
/**
* 请求头类型标注
*/
public MediaType json = MediaType.parse("application/json;charset=utf-8");
/**
* <p>说明请求头类型标注
* @name: setMediaType
* @param
* @author :Wind
*/
public HTTPUtils setMediaType(String json) {
this.json = MediaType.parse(json);
return this;
}
/**
* 标记子线程处理状态
*/
private boolean isOK = false;
/**
* 说明构建一个请求对象该方法将返回对象本身可以连锁调用只有调用该方法后才可以调用get post等方法
*
* @name: builder
* @author :Wind
*/
public HTTPUtils builder() {
this.builder = new Request.Builder();
this.client = new OkHttpClient();
return this;
}
/**
* <p>说明设置一个通用的请求地址
* <p>如果不设置该地址则以输入的地址作为请求地址如设置了改地址会自动拼接之后builder时设置的地址
* <p>一旦设置该地址后在重新获取对象或者调用{@link #defaultURL()}之前配置文件中的默认地址将会被覆盖
*
* @param baseURL 设置一个通用的请求地址
* @name: setBaseURL
* @author :Wind
*/
public HTTPUtils setBaseURL(String baseURL) {
this.baseURL = baseURL;
return this;
}
/**
* <p>说明设置回默认的请求路径配置文件中路径
* <p>
*
* @name: defaultURL
* @author :Wind
*/
public HTTPUtils defaultURL() {
this.baseURL = this.defaultURL;
return this;
}
/**
* 说明向请求中设置heard
*
* @param key header的名称
* @param value header的值
* @name: headers
* @author :Wind
*/
public HTTPUtils headers(String key, String value) {
this.builder = builder.header(key, value);
return this;
}
/**
* 说明向请求中设置heard
*
* @param map Map形式的header
* @name: headers
* @author :Wind
*/
public HTTPUtils headers(Map<String, String> map) {
for (Map.Entry<String, String> entry : map.entrySet()) {
this.builder = builder.header(entry.getKey(), entry.getValue());
}
return this;
}
/**
* 说明发送get请求
*
* @param url 要发送请求的url
* @param data 请求的参数
* @name: get
* @author :Wind
*/
public HTTPUtils get(String url, Map<String, String> data) {
Response response;
url = extracted(url);
url = getString(url, data);
log.info("请求路径:" + url);
this.request = builder.url(url).get().build();
return this;
}
/**
* 说明发送get请求
*
* @param url 要发送请求的url
* @name: get
* @author :Wind
*/
public HTTPUtils get(String url) {
return get(url, null);
}
/**
* 说明以json为参数发送post请求
* 该方法使用了JSON进行序列化一定确保传入的data为可序列化的
*
* @param url 请求路径
* @param data 请求数据尽量使用MapArray或实体类
* @name: post
* @author :Wind
*/
public HTTPUtils post(String url, Object data) {
url = extracted(url);
log.info("请求路径:" + url);
String s = JSON.toJSONString(data);
//将数据封装到RequestBody中
RequestBody fromBody = RequestBody.create(json, s);
this.request = builder.post(fromBody).url(url).build();
return this;
}
/**
* <p>说明发送格式为application/x-www-form-urlencoded的post请求
* @name: postOrBody
* @param
* @author :Wind
*/
public HTTPUtils postOrBody(String url, Map<String,String> data) {
url = extracted(url);
log.info("请求路径:" + url);
//将数据封装到RequestBody中
RequestBody fromBody = getPostRequestBody(data);
this.request = builder.post(fromBody).url(url).build();
return this;
}
private RequestBody getPostRequestBody(Map<String,String> data){
FormBody.Builder builder1 = new FormBody.Builder();
data.forEach(builder1::add);
return builder1.build();
}
/**
* 说明post请求第三个参数为true时则请求的参数在query中此时只接受 Map<String,String> 类型的参数
*
* @param url 请求路径
* @param query 在url中的参数
* @param isQuery 参数是否在query中
* @name: post
* @author :Wind
*/
public HTTPUtils post(String url, Map<String, String> query, boolean isQuery) {
if (isQuery) {
url = extracted(url);
String string = getString(url, query);
log.info("请求参数:" + string);
RequestBody fromBody = RequestBody.create(json, "");
this.request = builder.post(fromBody).url(url).build();
return this;
}
return post(url, query);
}
/**
* 说明使用同步形式发送请求
*
* @return OKResponse 返回参数
* @name: sync
* @author :Wind
*/
public OKResponse sync() {
isBuild();
Response response;
try {
response = client.newCall(this.request).execute();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
url = "";
okResponse = null;
}
return new OKResponse().setBody(response.body()).setCode(response.code()).setHeaders(response.headers());
}
/**
* <p>说明使用异步形式发送请求
* <p>该方法会启动一个高优先级子线程处理请求任务但不会等待处理结果直接返回调用对象如需获取结果可以调用异步回调方法
* <p>{@link #asyncCallback()}
* <p>回调方法将会始终阻塞线程直至子线程处理完成返回结果</p>
*
* @return {@code OKResponse}
* @name: async
* @author :Wind
*/
public HTTPUtils async() {
HTTPUtils that = this;
Thread t = new Thread(() -> {
log.info("子线程开始执行");
that.okResponse = sync();
that.isOK = true;
log.info("子线程请求任务结束");
});
t.setPriority(Thread.MAX_PRIORITY);
t.start();
return this;
}
/**
* <p>说明使用异步形式发送请求
* <p>该方法会启动一个高优先级子线程处理请求任务但不会等待处理结果同时不会返回任何对象处理完成的结果将会放置在调用对象的 {@link #okResponse}对象中</p>
* <p>回调方法将会始终阻塞线程直至子线程处理完成返回结果</p>
*
* @param NotWait
* @name: async
* @author :Wind
*/
public void async(boolean NotWait) {
async();
}
/**
* 说明每200毫秒检测一次异步线程是否处理完成否则将阻塞至此,直至尝试100次后抛出{@code RuntimeException("等待超时!")}
*
* @param
* @name: asyncCallback
* @author :Wind
*/
public OKResponse asyncCallback() {
OKResponse okResponse1 = null;
for (int i = 0; i <= 100; i++) {
if (isOK) break;
if (i == 100){
throw new RuntimeException("等待超时!");
}
log.info(""+i+"次尝试获取数据");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
okResponse1 = getOkResponse();
} finally {
isOK = false;
okResponse = null;
url = "";
}
return okResponse1;
}
/**
* 说明将map中的数据拼接到请求地址之后
*
* @param add 地址
* @param map 请求参数
* @name: getString
* @author :Wind
*/
private String getString(String add, Map<String, String> map) {
if (map != null) {
StringBuilder addBuilder = new StringBuilder(add);
addBuilder.append("?");
for (Map.Entry<String, String> entry : map.entrySet()) {
addBuilder.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
add = addBuilder.toString();
}
return add;
}
/**
* 说明拼接验证url地址
*
* @param url URL地址
* @name: extracted
* @author :Wind
*/
private String extracted(String url) {
this.url = "";
if (StringUtils.isEmpty(baseURL)) {
return this.url = url;
}
return this.url = baseURL + url;
}
public OKResponse getOkResponse() {
return this.okResponse;
}
private void isBuild(){
if(this.builder == null){
throw new RuntimeException("非法调用!未构建请求对象!");
}
}
/**
* <p>说明将返回结果序列化到实体类中
* <p>传入对象必须实现了getter和setter方法否则将序列化失败
* @name: getJSONBody
* @param t 要序列化的对象
* @author :Wind
*/
public static<T> T getJSONBody(OKResponse response, Class<T> t) {
try {
return JSONObject.parseObject(response.getBody().string(), t);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* <p>说明将返回结果序列化为一个json对象
* <p>
* @name: getJSONObject
* @param response
* @author :Wind
*/
public static JSONObject getJSONObject(OKResponse response){
try {
return JSONObject.parseObject(response.getBody().string());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static JSONObject getJSONObject(Object obj){
return JSONObject.parseObject(obj.toString());
}
}

View File

@ -2,10 +2,12 @@ package kim.wind.sms.comm.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
@ -13,9 +15,28 @@ import java.util.concurrent.TimeUnit;
@Slf4j
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
public void init(RedisConnectionFactory connectionFactory){
// 指定相应的序列化方案
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 RedisUtils() {
}

View File

@ -2,6 +2,7 @@ package kim.wind.sms.comm.utils;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.List;
import java.util.Random;
public class SmsUtil {
@ -81,4 +82,17 @@ public class SmsUtil {
return !isEmpty(str);
}
public static String listToString(List<String> list) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
if (i == list.size() - 1) {
str.append(list.get(i));
} else {
str.append(list.get(i));
str.append(",");
}
}
return str.toString();
}
}

View File

@ -1,6 +1,9 @@
package kim.wind.sms.comm.utils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@ -14,6 +17,9 @@ public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Autowired
DefaultListableBeanFactory beanFactory;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null) {
@ -49,4 +55,20 @@ public class SpringUtil implements ApplicationContextAware {
return null ;
}
}
/**
* <p>说明创建一个bean
* @name: createBean
* @param
* @author :Wind
*/
public void createBean(Class<?>clazz){
String name = clazz.getName();
beanFactory.createBean(clazz);
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
beanFactory.registerBeanDefinition(name, beanDefinitionBuilder.getBeanDefinition());
}
public void createBean(String name,Object o){
beanFactory.registerSingleton(name,o);
}
}

View File

@ -46,9 +46,7 @@ public class TimeExpiredPoolCache {
private static synchronized void syncInit() {
if (instance == null) {
instance = new TimeExpiredPoolCache();
if (!persistenceInit()){
dataPool = new ConcurrentHashMap<String, DataWrapper<?>>();
}
initTimer();
}
}
@ -60,12 +58,15 @@ public class TimeExpiredPoolCache {
return instance;
}
/** 读取持久化文件*/
/**
* 读取持久化文件
*/
private static boolean persistenceInit() {
String path = FileTool.getPath() + FILE_TYPE;
try {
dataPool = JSONObject.parseObject(FileTool.readFile(path), ConcurrentHashMap.class);
if (dataPool != null){
DataWrapper d = JSONObject.parseObject(FileTool.readFile(path), DataWrapper.class);
if (dataPool != null) {
return true;
}
} catch (IOException ignored) {
@ -79,7 +80,6 @@ public class TimeExpiredPoolCache {
public void run() {
try {
clearExpiredCaches();
persistence();
} catch (Exception e) {
throw new SmsBlendException(e.getMessage());
}
@ -87,10 +87,11 @@ public class TimeExpiredPoolCache {
}, timerMillis, timerMillis);
}
private static void persistence(){
/** 写入持久化文件*/
private static void persistence() {
String path = FileTool.getPath() + FILE_TYPE;
FileTool.createFile(path);
FileTool.writeFile(new File(path),JSONObject.toJSONString(dataPool),false);
FileTool.writeFile(new File(path), JSONObject.toJSONString(dataPool), false);
}
/**

View File

@ -0,0 +1,35 @@
package kim.wind.sms.comm.utils.http;
import com.alibaba.fastjson.JSONObject;
import java.io.IOException;
public class HttpJsonTool {
public static <T> T getJSONBody(Object response,Class<T>t){
return JSONObject.parseObject(response.toString(), t);
}
public static <T> T getJSONBody(String response,Class<T>t){
return JSONObject.parseObject(response, t);
}
/**
* <p>说明将返回结果序列化为一个json对象
* <p>
* @name: getJSONObject
* @param response
* @author :Wind
*/
public static JSONObject getJSONObject(OKResponse response){
try {
return JSONObject.parseObject(response.getBody().string());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static JSONObject getJSONObject(Object obj){
return JSONObject.parseObject(obj.toString());
}
}

View File

@ -5,11 +5,11 @@
<parent>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms-aggregation-huawei</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<name>sms-aggregation-huawei</name>
<description>sms-aggregation-huawei</description>

View File

@ -1,11 +1,19 @@
package kim.wind.sms.huawei.config;
import com.dtflys.forest.Forest;
import com.dtflys.forest.config.ForestConfiguration;
import kim.wind.sms.api.SmsBlend;
import kim.wind.sms.huawei.service.HuaweiSmsImpl;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ConfigurationProperties(prefix = "sms.huawei") //指定配置文件注入属性前缀
@Data
@Configuration
@ConfigurationProperties(prefix = "sms.huawei") //指定配置文件注入属性前缀
@ConditionalOnProperty(name = "sms.supplier", havingValue = "huawei")
public class HuaweiSmsConfig {
/** appKey*/
@ -22,5 +30,17 @@ public class HuaweiSmsConfig {
private String statusCallBack;
/** APP接入地址*/
private String url;
/** 是否打印http请求日志*/
private Boolean httpLog = false;
@Bean("forestConfiguration")
public ForestConfiguration forestConfiguration(){
return Forest.config().setBackendName("httpclient").setLogEnabled(httpLog);
}
@Bean
public SmsBlend smsBlend (){
return new HuaweiSmsImpl();
}
}

View File

@ -1,34 +0,0 @@
package kim.wind.sms.huawei.constant;
/**
* Constant
* <p> 华为云短信应用常量
*
* @author :Wind
* 2023/3/31 19:33
**/
public abstract class Constant {
/**
* 用于格式化鉴权头域,"Authorization"参数赋值
*/
public static final String AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";
/**
* 用于格式化鉴权头域,"X-WSSE"参数赋值
*/
public static final String WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
/**
* 访问URI
*/
public static final String REQUEST_URL = "/sms/batchSendSms/v1";
/**
* Content-Type
*/
public static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
/**
* 华为云规定 java时间格式
*/
public static final String JAVA_DATE = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private Constant() {
}
}

View File

@ -53,6 +53,6 @@ public enum HuaweiError {
case "E200041":
return E200041.getValue();
}
return "";
return "服务异常,请查看官方异常码,异常码:"+code;
}
}

View File

@ -22,5 +22,5 @@ public class HuaweiResponse {
/** 短信ID列表当目的号码存在多个时每个号码都会返回一个SmsID
当返回异常响应时不携带此字段*/
private List<SmsId> smsId;
private List<SmsId> result;
}

View File

@ -1,27 +1,25 @@
package kim.wind.sms.huawei.service;
import com.dtflys.forest.config.ForestConfiguration;
import kim.wind.sms.api.SmsBlend;
import kim.wind.sms.api.callback.CallBack;
import kim.wind.sms.comm.annotation.Restricted;
import kim.wind.sms.comm.constant.Constant;
import kim.wind.sms.comm.delayedTime.DelayedTime;
import kim.wind.sms.comm.entity.SmsResponse;
import kim.wind.sms.comm.utils.HTTPUtils;
import kim.wind.sms.comm.utils.http.OKResponse;
import kim.wind.sms.huawei.config.HuaweiSmsConfig;
import kim.wind.sms.huawei.constant.Constant;
import kim.wind.sms.huawei.entity.HuaweiError;
import kim.wind.sms.huawei.entity.HuaweiResponse;
import kim.wind.sms.huawei.entity.SmsId;
import kim.wind.sms.huawei.utils.HuaweiBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.Executor;
import static kim.wind.sms.comm.utils.SmsUtil.listToString;
@EnableConfigurationProperties({HuaweiSmsConfig.class})
@Slf4j
public class HuaweiSmsImpl implements SmsBlend {
@ -37,17 +35,21 @@ public class HuaweiSmsImpl implements SmsBlend {
private DelayedTime delayed;
@Autowired
private HTTPUtils http ;
@Qualifier("forestConfiguration")
private ForestConfiguration http;
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
return null;
LinkedHashMap<String,String> mes = new LinkedHashMap<>();
mes.put(UUID.randomUUID().toString().replaceAll("-",""),message);
return sendMessage(phone,config.getTemplateId(),mes);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
String url = config.getUrl() + Constant.HUAWEI_REQUEST_URL;
List<String> list = new ArrayList<>();
for (Map.Entry<String, String> entry : messages.entrySet()) {
list.add(entry.getValue());
@ -55,73 +57,114 @@ public class HuaweiSmsImpl implements SmsBlend {
String mess = HuaweiBuilder.listToString(list);
String requestBody = HuaweiBuilder.buildRequestBody(config.getSender(), phone, config.getTemplateId(), mess, config.getStatusCallBack(), config.getSignature());
Map<String,String> headers = new LinkedHashMap<>();
headers.put("Authorization",Constant.AUTH_HEADER_VALUE);
headers.put("Authorization",Constant.HUAWEI_AUTH_HEADER_VALUE);
headers.put("X-WSSE",HuaweiBuilder.buildWsseHeader(config.getAppKey(), config.getAppSecret()));
OKResponse response = http.setBaseURL(config.getUrl()).builder()
.setMediaType(Constant.CONTENT_TYPE)
.headers(headers)
.post(Constant.REQUEST_URL, requestBody)
.sync();
HuaweiResponse jsonBody = response.getJSONBody(HuaweiResponse.class);
headers.put("Content-Type",Constant.FROM_URLENCODED);
SmsResponse smsResponse = new SmsResponse();
smsResponse.setCode(response.getCode());
http.post(url)
.addHeader(headers)
.addBody(requestBody)
.onSuccess(((data,req,res)->{
HuaweiResponse jsonBody = res.get(HuaweiResponse.class);
smsResponse.setCode(jsonBody.getCode());
smsResponse.setMessage(jsonBody.getDescription());
SmsId smsId = jsonBody.getSmsId().get(0);
smsResponse.setBizId(smsId.getSmsMsgId());
smsResponse.setData(jsonBody);
if (response.getCode() != 200){
smsResponse.setErrMessage(HuaweiError.getValue(smsId.getStatus()));
}
smsResponse.setBizId(jsonBody.getResult().get(0).getSmsMsgId());
smsResponse.setData(jsonBody.getResult());
}))
.onError((ex,req,res)->{
HuaweiResponse huaweiResponse = res.get(HuaweiResponse.class);
smsResponse.setErrMessage(huaweiResponse.getDescription());
smsResponse.setErrorCode(huaweiResponse.getCode());
})
.execute();
return smsResponse;
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
return null;
return sendMessage(listToString(phones),message);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
return null;
return sendMessage(listToString(phones), templateId, messages);
}
@Override
@Restricted
public void sendMessageAsync(String phone, String message, CallBack callBack) {
pool.execute(() -> {
SmsResponse smsResponse = sendMessage(phone, message);
callBack.callBack(smsResponse);
});
}
@Override
@Restricted
public void sendMessageAsync(String phone, String message) {
pool.execute(() -> sendMessage(phone, message));
}
@Override
@Restricted
public void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages, CallBack callBack) {
pool.execute(() -> {
SmsResponse smsResponse = sendMessage(phone, templateId, messages);
callBack.callBack(smsResponse);
});
}
@Override
@Restricted
public void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages) {
pool.execute(() -> {
sendMessage(phone, templateId, messages);
});
}
@Override
@Restricted
public void delayedMessage(String phone, String message, Long delayedTime) {
this.delayed.schedule(new TimerTask() {
@Override
public void run() {
sendMessage(phone, message);
}
}, delayedTime);
}
@Override
@Restricted
public void delayedMessage(String phone, String templateId, LinkedHashMap<String, String> messages, Long delayedTime) {
this.delayed.schedule(new TimerTask() {
@Override
public void run() {
sendMessage(phone, templateId, messages);
}
}, delayedTime);
}
@Override
@Restricted
public void delayMassTexting(List<String> phones, String message, Long delayedTime) {
this.delayed.schedule(new TimerTask() {
@Override
public void run() {
massTexting(phones, message);
}
}, delayedTime);
}
@Override
@Restricted
public void delayMassTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime) {
this.delayed.schedule(new TimerTask() {
@Override
public void run() {
massTexting(phones, templateId, messages);
}
}, delayedTime);
}
}

View File

@ -1,7 +1,6 @@
package kim.wind.sms.huawei.service;
package kim.wind.sms.huawei.utils;
import kim.wind.sms.huawei.constant.Constant;
import kim.wind.sms.huawei.entity.HuaweiError;
import kim.wind.sms.comm.constant.Constant;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@ -25,7 +24,7 @@ public class HuaweiBuilder {
* <p>构造X-WSSE参数值
* @author :Wind
*/
static String buildWsseHeader(String appKey, String appSecret) {
public static String buildWsseHeader(String appKey, String appSecret) {
if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) {
System.out.println("buildWsseHeader(): appKey or appSecret is null.");
return null;
@ -46,7 +45,7 @@ public class HuaweiBuilder {
String passwordDigestBase64Str = Base64.getEncoder().encodeToString(passwordDigest); //PasswordDigest
//若passwordDigestBase64Str中包含换行符,请执行如下代码进行修正
//passwordDigestBase64Str = passwordDigestBase64Str.replaceAll("[\\s*\t\n\r]", "");
return String.format(Constant.WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, time);
return String.format(Constant.HUAWEI_WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, time);
}
static void trustAllHttpsCertificates() throws Exception {
@ -79,7 +78,7 @@ public class HuaweiBuilder {
* @param signature | 签名名称,使用国内短信通用模板时填写
* @author :Wind
*/
static String buildRequestBody(String sender, String receiver, String templateId, String templateParas,
public static String buildRequestBody(String sender, String receiver, String templateId, String templateParas,
String statusCallBack, String signature) {
if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
|| templateId.isEmpty()) {
@ -116,7 +115,7 @@ public class HuaweiBuilder {
return sb.deleteCharAt(sb.length()-1).toString();
}
static String listToString(List<String> list){
public static String listToString(List<String> list){
StringBuilder stringBuffer = new StringBuilder();
stringBuffer.append("[\"");
for (String s : list) {
@ -130,12 +129,12 @@ public class HuaweiBuilder {
}
static String dateFormat(Date date){
SimpleDateFormat sdf = new SimpleDateFormat(Constant.JAVA_DATE);
SimpleDateFormat sdf = new SimpleDateFormat(Constant.HUAWEI_JAVA_DATE);
return sdf.format(date);
}
static Date strForDate(String date){
SimpleDateFormat sdf = new SimpleDateFormat(Constant.JAVA_DATE);
SimpleDateFormat sdf = new SimpleDateFormat(Constant.HUAWEI_JAVA_DATE);
try {
return sdf.parse(date);
} catch (ParseException e) {

View File

@ -5,13 +5,13 @@
<parent>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms-aggregation-spring-boot-starter</artifactId>
<name>sms-aggregation-spring-boot-starter</name>
<description>sms-aggregation-spring-boot-starter</description>
<version>1.0.2</version>
<version>1.0.3</version>
<packaging>jar</packaging>
<properties>
@ -26,6 +26,11 @@
<scope>import</scope>
</dependency>
<dependency>
<groupId>kim.wind</groupId>
<artifactId>sms-aggregation-autoimmit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
@ -33,46 +38,13 @@
<dependency>
<groupId>kim.wind</groupId>
<artifactId>sms-aggregation-aliyun</artifactId>
<artifactId>sms-aggregation-autoimmit</artifactId>
</dependency>
<dependency>
<groupId>kim.wind</groupId>
<artifactId>sms-aggregation-tencent</artifactId>
</dependency>
<dependency>
<groupId>kim.wind</groupId>
<artifactId>sms-aggregation-unisms</artifactId>
</dependency>
<dependency>
<groupId>kim.wind</groupId>
<artifactId>sms-aggregation-yunpian</artifactId>
</dependency>
<dependency>
<groupId>kim.wind</groupId>
<artifactId>sms-aggregation-huawei</artifactId>
</dependency>
<!--aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,148 +1,26 @@
package kim.wind.sms.starter.config;
import kim.wind.sms.comm.utils.HTTPUtils;
import kim.wind.sms.huawei.config.HuaweiSmsConfig;
import kim.wind.sms.huawei.service.HuaweiSmsImpl;
import kim.wind.sms.unisms.service.UniSmsImpl;
import kim.wind.sms.aliyun.service.AlibabaSmsImpl;
import kim.wind.sms.api.SmsBlend;
import kim.wind.sms.comm.config.SmsBanner;
import kim.wind.sms.comm.delayedTime.DelayedTime;
import kim.wind.sms.comm.utils.RedisUtils;
import kim.wind.sms.autoimmit.config.SmsAutowiredConfig;
import kim.wind.sms.comm.utils.SpringUtil;
import kim.wind.sms.tencent.service.TencentSmsImpl;
import kim.wind.sms.yunpian.service.YunPianSmsImpl;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@ConfigurationProperties(prefix = "sms") //指定配置文件注入属性前缀
@EnableAsync
@Data
public class SmsMainConfig {
/** 短信服务商*/
@Value("${sms.supplier}")
private String supplier;
/** 打印banner*/
private String isPrint = "true";
/** 是否开启短信限制*/
private String restricted;
/** 是否使用redis进行缓存*/
private String redisCache = "false";
/** 单账号每日最大发送量*/
private Integer accountMax;
/** 单账号每分钟最大发送*/
private Integer minuteMax;
/**核心线程池大小*/
private Integer corePoolSize = 10;
/** 最大线程数*/
private Integer maxPoolSize = 30;
/** 队列容量*/
private Integer queueCapacity = 50;
/** 活跃时间*/
private Integer keepAliveSeconds = 60;
/** 线程名字前缀*/
private String threadNamePrefix = "sms-executor-";
/** 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean*/
private Boolean shutdownStrategy = true;
@Bean
public SpringUtil springUtil(){
return new SpringUtil();
}
@Bean
@ConditionalOnProperty(prefix = "sms", name = "supplier", havingValue = "huawei")
public HuaweiSmsConfig huaweiSmsConfig(){
return new HuaweiSmsConfig();
}
@Bean
public HTTPUtils okhttpBean(){
return new HTTPUtils();
}
@Bean
@ConditionalOnProperty(prefix = "sms", name = "restricted", havingValue = "true")
public AopAdvice aopAdvice(){
return new AopAdvice();
}
/** 如果启用了redis作为缓存则注入redis工具类*/
@Bean
@ConditionalOnProperty(prefix = "sms", name = "redisCache", havingValue = "true")
public RedisUtils redisUtils(){
return new RedisUtils();
}
/** 注入一个定时器*/
@Bean
public DelayedTime delayedTime(){
return new DelayedTime();
}
@Bean
public SmsBlend smsBlend(){
SmsBlend smsBlend = null;
switch (supplier){
case "alibaba":
smsBlend = new AlibabaSmsImpl();
break;
case "uniSms":
smsBlend = new UniSmsImpl();
break;
case "yunpian":
smsBlend = new YunPianSmsImpl();
break;
case "tencent":
smsBlend = new TencentSmsImpl();
break;
case "huawei":
smsBlend = new HuaweiSmsImpl();
break;
}
if ("true".equals(isPrint)){
SmsBanner.PrintBanner();
}
return smsBlend;
}
@Bean("smsExecutor")
protected Executor taskExecutor(){
// 创建一个线程池对象
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setWaitForTasksToCompleteOnShutdown(true);
// 线程池对拒绝任务的处理策略,当线程池没有处理能力的时候该策略会直接在 execute 方法的调用线程中运行被拒绝的任务如果执行程序已关闭则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//初始化线程池
executor.initialize();
return executor;
/** 主要配置注入*/
@Bean(initMethod = "init")
public SmsAutowiredConfig smsAutowiredConfig(){
return new SmsAutowiredConfig();
}
}

View File

@ -1,19 +0,0 @@
package kim.wind.sms.starter.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@Data
public class TaskPoolConfig {
}

View File

@ -1,3 +1,9 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
kim.wind.sms.starter.config.SmsMainConfig,\
kim.wind.sms.aliyun.config.AlibabaSmsConfig
kim.wind.sms.autoimmit.config.SmsAutowiredConfig,\
kim.wind.sms.aliyun.config.AlibabaSmsConfig,\
kim.wind.sms.huawei.config.HuaweiSmsConfig,\
kim.wind.sms.tencent.config.TencentSmsConfig,\
kim.wind.sms.unisms.config.UniSmsConfig,\
kim.wind.sms.yunpian.config.YunPianSmsConfig

View File

@ -5,13 +5,13 @@
<parent>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms-aggregation-tencent</artifactId>
<name>sms-aggregation-tencent</name>
<description>sms-aggregation-tencent</description>
<version>1.0.2</version>
<version>1.0.3</version>
<properties>
<java.version>1.8</java.version>
</properties>

View File

@ -4,16 +4,18 @@ import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.sms.v20210111.SmsClient;
import kim.wind.sms.api.SmsBlend;
import kim.wind.sms.tencent.service.TencentSmsImpl;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "sms.tencent") //指定配置文件注入属性前缀
@Data
@ConditionalOnProperty(prefix = "sms", name = "supplier", havingValue = "tencent")
@ConditionalOnProperty(name = "sms.supplier", havingValue = "tencent")
public class TencentSmsConfig {
/** 应用accessKey*/
@ -49,4 +51,9 @@ public class TencentSmsConfig {
clientProfile.setHttpProfile(httpProfile);
return new SmsClient(cred, territory,clientProfile);
}
@Bean
public SmsBlend smsBlend(){
return new TencentSmsImpl();
}
}

View File

@ -5,13 +5,13 @@
<parent>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms-aggregation-unisms</artifactId>
<name>sms-aggregation-unisms</name>
<description>sms-aggregation-unisms</description>
<version>1.0.2</version>
<version>1.0.3</version>
<properties>
<java.version>1.8</java.version>
</properties>

View File

@ -1,16 +1,18 @@
package kim.wind.sms.unisms.config;
import com.apistd.uni.Uni;
import kim.wind.sms.api.SmsBlend;
import kim.wind.sms.unisms.service.UniSmsImpl;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "sms.uni-sms") //指定配置文件注入属性前缀
@Data
@ConditionalOnProperty(prefix = "sms", name = "supplier", havingValue = "uni-sms")
@ConditionalOnProperty(name = "sms.supplier", havingValue = "uniSms")
public class UniSmsConfig {
/** 访问键标识*/
@ -35,4 +37,9 @@ public class UniSmsConfig {
Uni.init(accessKeyId,accessKeySecret);
}
}
@Bean
public SmsBlend smsBlend(){
return new UniSmsImpl();
}
}

View File

@ -3,14 +3,14 @@ package kim.wind.sms.unisms.service;
import com.apistd.uni.UniResponse;
import com.apistd.uni.sms.UniMessage;
import com.apistd.uni.sms.UniSMS;
import kim.wind.sms.unisms.config.UniSmsConfig;
import kim.wind.sms.api.SmsBlend;
import kim.wind.sms.api.callback.CallBack;
import kim.wind.sms.comm.annotation.Restricted;
import kim.wind.sms.comm.delayedTime.DelayedTime;
import kim.wind.sms.comm.entity.SmsResponse;
import kim.wind.sms.comm.exception.SmsBlendException;
import kim.wind.sms.comm.utils.HTTPUtils;
import kim.wind.sms.comm.utils.http.HttpJsonTool;
import kim.wind.sms.unisms.config.UniSmsConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@ -169,7 +169,7 @@ public class UniSmsImpl implements SmsBlend {
smsResponse.setErrorCode(send.code);
smsResponse.setMessage(send.message);
smsResponse.setBizId(send.requestId);
smsResponse.setData(HTTPUtils.getJSONObject(send));
smsResponse.setData(HttpJsonTool.getJSONObject(send));
}catch(Exception e){
smsResponse.setErrMessage(e.getMessage());
}

View File

@ -5,14 +5,14 @@
<parent>
<groupId>kim.wind</groupId>
<artifactId>sms_aggregation</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms-aggregation-yunpian</artifactId>
<name>sms-aggregation-yunpian</name>
<description>sms-aggregation-yunpian</description>
<version>1.0.2</version>
<version>1.0.3</version>
<properties>
<java.version>1.8</java.version>
</properties>

View File

@ -1,16 +1,52 @@
package kim.wind.sms.yunpian.config;
import com.dtflys.forest.Forest;
import com.dtflys.forest.config.ForestConfiguration;
import kim.wind.sms.api.SmsBlend;
import kim.wind.sms.yunpian.service.YunPianSmsImpl;
import lombok.Data;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "sms.yunpian") //指定配置文件注入属性前缀
@Data
@ConditionalOnProperty(prefix = "sms", name = "supplier", havingValue = "yunpian")
@ConditionalOnProperty(name = "sms.supplier", havingValue = "yunpian")
public class YunPianSmsConfig {
/** 账号唯一标识*/
/**
* 账号唯一标识
*/
private String apikey;
/**
* 短信发送后将向这个地址推送(运营商返回的)发送报告
*/
private String callbackUrl;
/**
* 模板Id
*/
private String templateId;
/**
* 模板变量名称
*/
private String templateName;
/**
* 是否打印http请求日志
*/
private Boolean httpLog = false;
@Bean
public ForestConfiguration forestConfiguration() {
return Forest.config().setBackendName("httpclient").setLogEnabled(httpLog);
}
@Bean
public SmsBlend smsBlend() {
return new YunPianSmsImpl();
}
}

View File

@ -1,20 +1,24 @@
package kim.wind.sms.yunpian.service;
import com.alibaba.fastjson.JSONObject;
import com.dtflys.forest.config.ForestConfiguration;
import kim.wind.sms.api.SmsBlend;
import kim.wind.sms.api.callback.CallBack;
import kim.wind.sms.comm.annotation.Restricted;
import kim.wind.sms.comm.constant.Constant;
import kim.wind.sms.comm.delayedTime.DelayedTime;
import kim.wind.sms.comm.entity.SmsResponse;
import kim.wind.sms.comm.exception.SmsBlendException;
import kim.wind.sms.comm.utils.HTTPUtils;
import kim.wind.sms.comm.utils.http.OKResponse;
import kim.wind.sms.yunpian.config.YunPianSmsConfig;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static kim.wind.sms.comm.utils.SmsUtil.listToString;
public class YunPianSmsImpl implements SmsBlend {
@ -26,75 +30,42 @@ public class YunPianSmsImpl implements SmsBlend {
private DelayedTime delayed;
@Autowired
private HTTPUtils http ;
private YunPianSmsConfig config;
@Autowired
private YunPianSmsConfig config;
private ForestConfiguration http;
@Override
@Restricted
public SmsResponse sendMessage(String phone, String message) {
Map<String,String> body = new HashMap<>();
body.put("apikey",config.getApikey());
body.put("mobile",phone);
return getSmsResponse(message, body);
Map<String, String> body = setBody(phone, message, null);
return getSendResponse(body);
}
@Override
@Restricted
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
Map<String,String> body = new HashMap<>();
body.put("apikey",config.getApikey());
body.put("mobile",phone);
body.put("tpl_id",templateId);
body.put("tpl_value",formattingMap(messages));
Map<String,String> map = new HashMap<>();
map.put("Accept","application/json;charset=utf-8");
OKResponse sync = http.setBaseURL("https://sms.yunpian.com/v2").builder()
.headers(map)
.postOrBody("/sms/tpl_single_send.json", body)
.sync();
SmsResponse smsResponse = new SmsResponse();
smsResponse.setCode(sync.getCode());
JSONObject jsonObject = HTTPUtils.getJSONObject(sync);
smsResponse.setData(jsonObject);
return smsResponse;
Map<String, String> body = setBody(phone, "", messages);
return getSendResponse(body);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String message) {
if (phones.size()>1000){
if (phones.size() > 1000) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000");
}
Map<String,String> body = new HashMap<>();
body.put("apikey",config.getApikey());
body.put("mobile",listToString(phones));
return getSmsResponse(message, body);
return sendMessage(listToString(phones),message);
}
@Override
@Restricted
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
if (phones.size()>1000){
if (phones.size() > 1000) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000");
}
Map<String,String> body = new HashMap<>();
body.put("apikey",config.getApikey());
body.put("mobile",listToString(phones));
body.put("tpl_id",templateId);
body.put("tpl_value",formattingMap(messages));
Map<String,String> map = new HashMap<>();
map.put("Accept","application/json;charset=utf-8");
OKResponse sync = http.setBaseURL("https://sms.yunpian.com/v2").builder()
.headers(map)
.postOrBody("/tpl_batch_send.json", body)
.sync();
SmsResponse smsResponse = new SmsResponse();
smsResponse.setCode(sync.getCode());
JSONObject jsonObject = HTTPUtils.getJSONObject(sync);
smsResponse.setData(jsonObject);
return smsResponse;
return sendMessage(listToString(phones), templateId, messages);
}
@Override
@ -115,8 +86,8 @@ public class YunPianSmsImpl implements SmsBlend {
@Override
@Restricted
public void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages, CallBack callBack) {
pool.execute(()->{
SmsResponse smsResponse = sendMessage(phone,templateId,messages);
pool.execute(() -> {
SmsResponse smsResponse = sendMessage(phone, templateId, messages);
callBack.callBack(smsResponse);
});
}
@ -124,8 +95,8 @@ public class YunPianSmsImpl implements SmsBlend {
@Override
@Restricted
public void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages) {
pool.execute(()->{
sendMessage(phone,templateId,messages);
pool.execute(() -> {
sendMessage(phone, templateId, messages);
});
}
@ -135,9 +106,9 @@ public class YunPianSmsImpl implements SmsBlend {
this.delayed.schedule(new TimerTask() {
@Override
public void run() {
sendMessage(phone,message);
sendMessage(phone, message);
}
},delayedTime);
}, delayedTime);
}
@Override
@ -146,9 +117,9 @@ public class YunPianSmsImpl implements SmsBlend {
this.delayed.schedule(new TimerTask() {
@Override
public void run() {
sendMessage(phone,templateId,messages);
sendMessage(phone, templateId, messages);
}
},delayedTime);
}, delayedTime);
}
@Override
@ -157,9 +128,9 @@ public class YunPianSmsImpl implements SmsBlend {
this.delayed.schedule(new TimerTask() {
@Override
public void run() {
massTexting(phones,message);
massTexting(phones, message);
}
},delayedTime);
}, delayedTime);
}
@Override
@ -168,49 +139,76 @@ public class YunPianSmsImpl implements SmsBlend {
this.delayed.schedule(new TimerTask() {
@Override
public void run() {
massTexting(phones,templateId,messages);
massTexting(phones, templateId, messages);
}
},delayedTime);
}, delayedTime);
}
private String formattingMap(Map<String,String> messages){
private String formattingMap(Map<String, String> messages) {
StringBuilder str = new StringBuilder();
for (Map.Entry<String,String> entry : messages.entrySet()) {
for (Map.Entry<String, String> entry : messages.entrySet()) {
str.append("#");
str.append(entry.getKey());
str.append("#=");
str.append(entry.getValue());
str.append("&");
}
str.deleteCharAt(str.length()-1);
str.deleteCharAt(str.length() - 1);
return str.toString();
}
private String listToString(List<String> list){
StringBuilder str = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
if (i == list.size() - 1) {
str.append(list.get(i));
private Map<String, String> setBody(String phone, String mes, LinkedHashMap<String, String> messages) {
LinkedHashMap<String, String> message = new LinkedHashMap<>();
if (mes.isEmpty()) {
message = messages;
} else {
str.append(list.get(i));
str.append(",");
message.put(config.getTemplateName(), mes);
}
}
return str.toString();
Map<String, String> body = new HashMap<>();
body.put("apikey", config.getApikey());
body.put("mobile", phone);
body.put("tpl_id", config.getTemplateId());
body.put("tpl_value", formattingMap(message));
if (!config.getCallbackUrl().isEmpty()) body.put("callback_url", config.getCallbackUrl());
return body;
}
private SmsResponse getSmsResponse(String message, Map<String, String> body) {
body.put("text",message);
Map<String,String> map = new HashMap<>();
map.put("Accept","application/json;charset=utf-8");
OKResponse sync = http.setBaseURL("http://sms.yunpian.com/v2").builder()
.headers(map)
.postOrBody("/sms/single_send.json", body)
.sync();
private Map<String, String> getHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("Accept", "application/json;charset=utf-8");
headers.put("Content-Type", Constant.FROM_URLENCODED);
return headers;
}
@NotNull
private static SmsResponse getSmsResponse(JSONObject execute) {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setCode(sync.getCode());
JSONObject jsonObject = HTTPUtils.getJSONObject(sync);
smsResponse.setData(jsonObject);
smsResponse.setCode(execute.getString("code"));
smsResponse.setMessage(execute.getString("msg"));
smsResponse.setBizId(execute.getString("sid"));
if (execute.getInteger("code") != 0) {
smsResponse.setErrMessage(execute.getString("msg"));
}
smsResponse.setData(execute);
return smsResponse;
}
private SmsResponse getSendResponse(Map<String, String> body) {
Map<String, String> headers = getHeaders();
AtomicReference<SmsResponse> smsResponse = null;
http.post(Constant.YUNPIAN_URL + "/sms/tpl_single_send.json")
.addHeader(headers)
.addBody(body)
.onSuccess(((data,req,res)->{
JSONObject jsonBody = res.get(JSONObject.class);
smsResponse.set(getSmsResponse(jsonBody));
}))
.onError((ex,req,res)->{
JSONObject jsonBody = res.get(JSONObject.class);
smsResponse.set(getSmsResponse(jsonBody));
})
.execute();
return smsResponse.get();
}
}