diff --git a/pom.xml b/pom.xml index db23b5a6..af5bda5c 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,9 @@ 1.8 2.7.10 1.0.0 + 2.0.23 + 2.0.15 + 3.14.9 @@ -67,11 +70,6 @@ spring-boot-starter ${spring.boot.version} - - kim.wind - sms-aggregation-api - ${modules.version} - kim.wind @@ -96,11 +94,67 @@ sms-aggregation-yunpian ${modules.version} + kim.wind sms-aggregation-spring-boot-starter ${modules.version} + + + kim.wind + sms-aggregation-comm + ${modules.version} + + + + kim.wind + sms-aggregation-api + ${modules.version} + + + + + com.aliyun + dysmsapi20170525 + ${aliyun.version} + + + + + com.alibaba + fastjson + ${json.version} + + + com.squareup.okhttp3 + okhttp + + + + + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + + + org.springframework.boot + spring-boot-starter-aop + ${spring.boot.version} + + + + + org.springframework.boot + spring-boot-starter-data-redis + ${spring.boot.version} + provided + + @@ -112,10 +166,11 @@ - kim.wind - sms-aggregation-comm - ${modules.version} + org.springframework.boot + spring-boot-configuration-processor + true + @@ -202,6 +257,19 @@ maven-release-plugin 2.5.1 + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + + + + true + + + + + diff --git a/sms-aggregation-aliyun/pom.xml b/sms-aggregation-aliyun/pom.xml index ea593ac6..61b1ff9c 100644 --- a/sms-aggregation-aliyun/pom.xml +++ b/sms-aggregation-aliyun/pom.xml @@ -11,13 +11,25 @@ sms-aggregation-aliyun sms-aggregation-aliyun sms-aggregation-aliyun - ${modules.version} + 1.0.0 1.8 - + + + com.aliyun + dysmsapi20170525 + + + kim.wind + sms-aggregation-comm + + + kim.wind + sms-aggregation-api + diff --git a/sms-aggregation-aliyun/src/main/java/kim/wind/sms/aliyun/config/AlibabaSmsConfig.java b/sms-aggregation-aliyun/src/main/java/kim/wind/sms/aliyun/config/AlibabaSmsConfig.java index 255e5053..db238321 100644 --- a/sms-aggregation-aliyun/src/main/java/kim/wind/sms/aliyun/config/AlibabaSmsConfig.java +++ b/sms-aggregation-aliyun/src/main/java/kim/wind/sms/aliyun/config/AlibabaSmsConfig.java @@ -2,6 +2,8 @@ package kim.wind.sms.aliyun.config; import com.aliyun.dysmsapi20170525.Client; 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; @@ -10,10 +12,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration -@ConfigurationProperties(prefix = "sms-blend.alibaba") //指定配置文件注入属性前缀 +@ConfigurationProperties(prefix = "sms.alibaba") //指定配置文件注入属性前缀 @Data -@AutoConfigureAfter({SmsBlendMainConfig.class}) -@ConditionalOnProperty(prefix = "sms-blend", name = "supplier", havingValue = "alibaba") +@ConditionalOnProperty(prefix = "sms", name = "supplier", havingValue = "alibaba") public class AlibabaSmsConfig { private String accessKeyId; @@ -26,6 +27,8 @@ public class AlibabaSmsConfig { /** 模板变量名称*/ private String templateName; + private String requestUrl = "dysmsapi.aliyuncs.com"; + @Bean public Client config() throws Exception { @@ -35,7 +38,12 @@ public class AlibabaSmsConfig { // AccessKey Secret .setAccessKeySecret(accessKeySecret); // 访问的域名 - config.endpoint = "dysmsapi.aliyuncs.com"; + config.endpoint = requestUrl; return new Client(config); } + +// @Bean +// public SmsBlend smsBlend(){ +// return new AlibabaSmsImpl(); +// } } diff --git a/sms-aggregation-aliyun/src/main/java/kim/wind/sms/aliyun/service/AlibabaSmsImpl.java b/sms-aggregation-aliyun/src/main/java/kim/wind/sms/aliyun/service/AlibabaSmsImpl.java new file mode 100644 index 00000000..3908b100 --- /dev/null +++ b/sms-aggregation-aliyun/src/main/java/kim/wind/sms/aliyun/service/AlibabaSmsImpl.java @@ -0,0 +1,140 @@ +package kim.wind.sms.aliyun.service; + +import com.alibaba.fastjson.JSONObject; +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendBatchSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendBatchSmsResponse; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.tea.TeaException; +import com.aliyun.teautil.models.RuntimeOptions; +import kim.wind.sms.aliyun.config.AlibabaSmsConfig; +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.entity.SmsResponse; +import kim.wind.sms.comm.exception.SmsBlendException; +import kim.wind.sms.comm.utils.HTTPUtils; +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 org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.Executor; + +@EnableConfigurationProperties({AlibabaSmsConfig.class}) +@Slf4j +public class AlibabaSmsImpl implements SmsBlend { + + @Autowired + private Client client; + @Autowired + private AlibabaSmsConfig alibabaSmsConfig; + + @Autowired + @Qualifier("smsExecutor") + private Executor pool; + + @Override + @Restricted + public SmsResponse sendMessage(String phone, String message) { + LinkedHashMap map = new LinkedHashMap<>(); + map.put(alibabaSmsConfig.getTemplateName(), message); + return sendMessage(phone, alibabaSmsConfig.getTemplateId(), map); + } + + @Override + @Restricted + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + SendSmsRequest sendSmsRequest = new SendSmsRequest(); + String s = JSONObject.toJSONString(messages); + sendSmsRequest.setPhoneNumbers(phone) + .setTemplateCode(alibabaSmsConfig.getTemplateId()) + .setTemplateParam(s) + .setSignName(alibabaSmsConfig.getSignature()); + RuntimeOptions runtime = new RuntimeOptions(); + SmsResponse smsResponse = new SmsResponse(); + try { + SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime); + smsResponse.setBizId(sendSmsResponse.body.getBizId()); + smsResponse.setData(sendSmsResponse.body); + smsResponse.setCode(sendSmsResponse.statusCode); + if (!"OK".equals(sendSmsResponse.body.code)) { + smsResponse.setErrMessage((sendSmsResponse.body.message)); + smsResponse.setErrorCode(sendSmsResponse.body.code); + } else { + smsResponse.setMessage(sendSmsResponse.body.message); + } + } catch (TeaException error) { + throw new SmsBlendException(error.message); + // 如有需要,请打印 error + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + // 如有需要,请打印 error + throw new SmsBlendException(error.message); + } + return smsResponse; + } + + @Override + @Restricted + public SmsResponse massTexting(List phones, String message) { + LinkedHashMap map = new LinkedHashMap<>(); + map.put(alibabaSmsConfig.getTemplateName(), message); + return massTexting(phones, alibabaSmsConfig.getTemplateId(), map); + } + + @Override + @Restricted + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + SendBatchSmsRequest sendBatchSmsRequest = new SendBatchSmsRequest(); + sendBatchSmsRequest.setPhoneNumberJson(JSONObject.toJSONString(phones))//群发的手机号 + .setTemplateCode(alibabaSmsConfig.getTemplateId())//模板id + .setTemplateParamJson(JSONObject.toJSONString(messages))//消息内容 + .setSignNameJson(alibabaSmsConfig.getSignature());//短信签名 + RuntimeOptions runtime = new RuntimeOptions(); + SmsResponse smsResponse = new SmsResponse(); + try { + SendBatchSmsResponse sendBatchSmsResponse = client.sendBatchSmsWithOptions(sendBatchSmsRequest, runtime); + smsResponse.setBizId(sendBatchSmsResponse.body.getBizId()); + smsResponse.setData(HTTPUtils.getJSONObject(sendBatchSmsResponse.body)); + smsResponse.setCode(sendBatchSmsResponse.statusCode); + if (!"OK".equals(sendBatchSmsResponse.body.code)) { + smsResponse.setErrMessage((sendBatchSmsResponse.body.message)); + smsResponse.setErrorCode(sendBatchSmsResponse.body.code); + } else { + smsResponse.setMessage(sendBatchSmsResponse.body.message); + } + } catch (TeaException error) { + throw new SmsBlendException(error.message); + // 如有需要,请打印 error + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + // 如有需要,请打印 error + throw new SmsBlendException(error.message); + } + return smsResponse; + } + + @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 sendMessage(String phone, String templateId, LinkedHashMap messages, CallBack callBack) { + pool.execute(()->{ + SmsResponse smsResponse = sendMessage(phone,templateId,messages); + callBack.callBack(smsResponse); + }); + } +} diff --git a/sms-aggregation-api/pom.xml b/sms-aggregation-api/pom.xml index 69ab904c..0e3dd65b 100644 --- a/sms-aggregation-api/pom.xml +++ b/sms-aggregation-api/pom.xml @@ -11,13 +11,18 @@ sms-aggregation-api sms-aggregation-api sms-aggregation-api - ${modules.version} + 1.0.0 + 1.8 + + kim.wind + sms-aggregation-comm + diff --git a/sms-aggregation-api/src/main/java/kim/wind/sms/api/SmsBlend.java b/sms-aggregation-api/src/main/java/kim/wind/sms/api/SmsBlend.java index 71659c7b..e967e881 100644 --- a/sms-aggregation-api/src/main/java/kim/wind/sms/api/SmsBlend.java +++ b/sms-aggregation-api/src/main/java/kim/wind/sms/api/SmsBlend.java @@ -1,5 +1,7 @@ package kim.wind.sms.api; +import kim.wind.sms.api.callback.CallBack; +import kim.wind.sms.comm.annotation.Restricted; import kim.wind.sms.comm.entity.SmsResponse; import java.util.LinkedHashMap; @@ -17,6 +19,7 @@ public interface SmsBlend { * message 消息内容 * @author :Wind */ + SmsResponse sendMessage(String phone,String message); /** @@ -26,6 +29,7 @@ public interface SmsBlend { * @param messages key为模板变量名称 value为模板变量值 * @author :Wind */ + SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages); /** @@ -33,6 +37,7 @@ public interface SmsBlend { * massTexting * @author :Wind */ + SmsResponse massTexting(List phones, String message); /** @@ -40,7 +45,28 @@ public interface SmsBlend { * massTexting * @author :Wind */ + SmsResponse massTexting(List phones,String templateId, LinkedHashMap messages); - void sendMessageAsync(String phone,String message); + /** + *

说明:异步短信发送,固定消息模板短信 + * sendMessageAsync + * @param phone 要发送的号码 + * @param message 发送内容 + * @param callBack 回调 + * @author :Wind + */ + + void sendMessageAsync(String phone, String message, CallBack callBack); + + /** + *

说明:异步短信发送,使用自定义模板发送短信 + * sendMessage + * @param templateId 模板id + * @param messages key为模板变量名称 value为模板变量值 + * @param callBack 回调 + * @author :Wind + */ + + void sendMessage(String phone, String templateId, LinkedHashMap messages, CallBack callBack); } diff --git a/sms-aggregation-api/src/main/java/kim/wind/sms/api/callback/CallBack.java b/sms-aggregation-api/src/main/java/kim/wind/sms/api/callback/CallBack.java index 64c10dfe..be3af6cc 100644 --- a/sms-aggregation-api/src/main/java/kim/wind/sms/api/callback/CallBack.java +++ b/sms-aggregation-api/src/main/java/kim/wind/sms/api/callback/CallBack.java @@ -1,6 +1,8 @@ package kim.wind.sms.api.callback; +import kim.wind.sms.comm.entity.SmsResponse; + @FunctionalInterface public interface CallBack { - void callBack(); + void callBack(SmsResponse smsResponse); } diff --git a/sms-aggregation-comm/pom.xml b/sms-aggregation-comm/pom.xml index bae55933..2bd2a8e5 100644 --- a/sms-aggregation-comm/pom.xml +++ b/sms-aggregation-comm/pom.xml @@ -11,7 +11,7 @@ sms-aggregation-comm sms-aggregation-comm sms-aggregation-comm - ${modules.version} + 1.0.0 1.8 @@ -21,6 +21,21 @@ org.springframework.boot spring-boot-starter + + + com.alibaba + fastjson + + + + com.squareup.okhttp3 + okhttp + + + + org.springframework.boot + spring-boot-starter-data-redis + diff --git a/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/annotation/Restricted.java b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/annotation/Restricted.java new file mode 100644 index 00000000..251f87cd --- /dev/null +++ b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/annotation/Restricted.java @@ -0,0 +1,12 @@ +package kim.wind.sms.comm.annotation; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Restricted { +} diff --git a/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/exception/SmsBlendException.java b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/exception/SmsBlendException.java new file mode 100644 index 00000000..dbe77136 --- /dev/null +++ b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/exception/SmsBlendException.java @@ -0,0 +1,25 @@ +package kim.wind.sms.comm.exception; + +public class SmsBlendException extends RuntimeException{ + public String code; + public String message; + public String requestId; + + public SmsBlendException(String message) { + super(message); + this.message = message; + } + + public SmsBlendException(String code, String message) { + super("[" + code + "] " + message); + this.message = message; + this.code = code; + } + + public SmsBlendException(String code, String message, String requestId) { + super("[" + code + "] " + message); + this.message = message; + this.code = code; + this.code = requestId; + } +} diff --git a/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/HTTPUtils.java b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/HTTPUtils.java new file mode 100644 index 00000000..b9a6889d --- /dev/null +++ b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/HTTPUtils.java @@ -0,0 +1,496 @@ +package kim.wind.sms.comm.utils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.util.Map; + + +/** + *

类名: HTTPUtils + *

说明: 封装okhttp3,简单化请求创建流程,并且可以当做工具类进行自动装配使用 + *

构建请求只需要获得OKHTTPUtils的对象,然后逐步调用即可 + *

如: + *

http.setBaseURL("http://www.baidu.com").builder().get("/wenku").sync(); + *

构建一个post请求只需要: + *

http.setBaseURL("http://www.baidu.com").builder().post("/wenku",object).sync(); + *

如果在Spring Boot中作为组件自动装配使用,{@code baseURL}将读取配置文件中的{@code okhttp.url}获取默认的请求路径,则不需要再次调用 setBaseURL()方法进行设置 + *

依赖于 {@code okhttp3} {@code alibaba.fastjson} {@code lombok.slf4j} + *

{@code + * + * com.squareup.okhttp3 + * okhttp + * 3.14.9 + * } + *

{@code + * + * com.alibaba + * fastjson + * 1.2.74 + *} + * + * @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"); + + /** + *

说明:请求头类型标注 + * @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; + } + + /** + *

说明:设置一个通用的请求地址 + *

如果不设置该地址则以输入的地址作为请求地址,如设置了改地址,会自动拼接之后builder时设置的地址 + *

一旦设置该地址后,在重新获取对象或者调用{@link #defaultURL()}之前,配置文件中的默认地址将会被覆盖 + * + * @param baseURL 设置一个通用的请求地址 + * @name: setBaseURL + * @author :Wind + */ + public HTTPUtils setBaseURL(String baseURL) { + this.baseURL = baseURL; + return this; + } + + /** + *

说明:设置回默认的请求路径(配置文件中路径) + *

+ * + * @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 map) { + for (Map.Entry 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 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 请求数据尽量使用Map、Array、或实体类 + * @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; + } + + /** + *

说明:发送格式为application/x-www-form-urlencoded的post请求 + * @name: postOrBody + * @param + * @author :Wind + */ + public HTTPUtils postOrBody(String url, Map 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 data){ + FormBody.Builder builder1 = new FormBody.Builder(); + data.forEach(builder1::add); + return builder1.build(); + } + + /** + * 说明:post请求,第三个参数为true时则请求的参数在query中,此时只接受 Map 类型的参数 + * + * @param url 请求路径 + * @param query 在url中的参数 + * @param isQuery 参数是否在query中 + * @name: post + * @author :Wind + */ + public HTTPUtils post(String url, Map 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()); + } + + + /** + *

说明:使用异步形式发送请求 + *

该方法会启动一个高优先级子线程处理请求任务,但不会等待处理结果,直接返回调用对象,如需获取结果可以调用异步回调方法 + *

{@link #asyncCallback()} + *

回调方法将会始终阻塞线程直至子线程处理完成返回结果

+ * + * @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; + } + + /** + *

说明:使用异步形式发送请求 + *

该方法会启动一个高优先级子线程处理请求任务,但不会等待处理结果,同时不会返回任何对象,处理完成的结果将会放置在调用对象的 {@link #okResponse}对象中

+ *

回调方法将会始终阻塞线程直至子线程处理完成返回结果

+ * + * @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 map) { + if (map != null) { + StringBuilder addBuilder = new StringBuilder(add); + addBuilder.append("?"); + for (Map.Entry 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("非法调用!未构建请求对象!"); + } + } + + /** + *

说明:将返回结果序列化到实体类中 + *

传入对象必须实现了getter和setter方法,否则将序列化失败 + * @name: getJSONBody + * @param t 要序列化的对象 + * @author :Wind + */ + public static T getJSONBody(OKResponse response, Class t) { + try { + return JSONObject.parseObject(response.getBody().string(), t); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + *

说明:将返回结果序列化为一个json对象 + *

+ * @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()); + } + + /** + *

类名: OkHTTPUtils + *

说明: 用于封装请求后返回的参数 + * + * @author :Wind + * @date :2022/7/11 16:12 + **/ + public class OKResponse { + private ResponseBody body; + private Headers headers; + private Integer code; + + public ResponseBody getBody() { + return body; + } + + public OKResponse setBody(ResponseBody body) { + this.body = body; + return this; + } + + public Headers getHeaders() { + return headers; + } + + public OKResponse setHeaders(Headers headers) { + this.headers = headers; + return this; + } + + public Integer getCode() { + return code; + } + + public OKResponse setCode(Integer code) { + this.code = code; + return this; + } + + /** + *

说明:将返回结果序列化到实体类中 + *

传入对象必须实现了getter和setter方法,否则将序列化失败 + * @name: getJSONBody + * @param t 要序列化的对象 + * @author :Wind + */ + public T getJSONBody(Class t) { + try { + return JSONObject.parseObject(this.getBody().string(), t); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + /** + *

说明:将返回结果序列化为一个json对象 + *

+ * @name: getJSONObject + * @author :Wind + */ + public JSONObject getJSONObject(){ + try { + return JSONObject.parseObject(this.getBody().string()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return "OKResponse{" + + "body=" + body + + ", headers=" + headers + + ", code=" + code + + '}'; + } + } +} diff --git a/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/RedisUtils.java b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/RedisUtils.java new file mode 100644 index 00000000..642ef898 --- /dev/null +++ b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/RedisUtils.java @@ -0,0 +1,445 @@ +package kim.wind.sms.comm.utils; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.TimeUnit; + + +@Slf4j +public class RedisUtils { + + private final RedisTemplate redisTemplate; + + public RedisUtils(RedisTemplate 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; + } + } + + /** + *

说明:将Map中的数据批量放置到redis中 + *

+ * + * @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); + } + + /** + *

说明:获取字符串型值 + * @name: getKyeString + * @param + * @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 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); + } + + /** + *

说明:将map对象存入redis + *

+ * + * @param map 要存入redis中的map + * @name: setMap + * @author :Wind + */ + public void MapSetMap(String key, Map map) { + redisTemplate.opsForHash().putAll(key, map); + } + + /** + *

说明:获取所有hash表中字段 + *

+ * + * @param + * @name: getMapByKey + * @author :Wind + */ + public Set MapGetHashByKey(String key) { + return redisTemplate.opsForHash().keys(key); + } + + /** + *

说明:根据key和fieId获取对应的值 + *

+ * + * @param fieId hash中的fieId也是Map的Key + * @name: getValueByFieID + * @author :Wind + */ + public Object MapGetValueByFieID(String key, String fieId) { + return redisTemplate.opsForHash().get(key, fieId); + } + + /** + *

说明:根据key获取所有的键值对 + *

+ * + * @param key redis中的key + * @name: getMapByKey + * @author :Wind + */ + public Map MapGetMapByKey(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + *

说明:向key中添加一对新的键值对 + *

+ * + * @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); + } + + /** + *

说明:根据key和field删除数据 + *

+ * + * @param fields 要删除的fields + * @return Long 影响的条数 + * @name: hashDelete + * @author :Wind + */ + public Long MapHashDelete(String key, Object... fields) { + return redisTemplate.opsForHash().delete(key, fields); + } + + /** + *

说明:查看key下存了多少条键值对 + *

+ * + * @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 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); + } + + /** + *

说明:获取列表元素的大小 + *

+ * @name: listGetSize + * @param + * @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); + } + + /** + *

说明:清除所有缓存 + *

该方法会清理掉redis中所有的缓存,谨慎使用 + *

+ * + * @name: empty + * @author :Wind + */ + public void empty() { + redisTemplate.getConnectionFactory().getConnection().flushAll(); + } +} diff --git a/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/SmsUtil.java b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/SmsUtil.java new file mode 100644 index 00000000..fe88b92d --- /dev/null +++ b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/SmsUtil.java @@ -0,0 +1,74 @@ +package kim.wind.sms.comm.utils; + +import java.util.Random; + +public class SmsUtil { + private SmsUtil() { + } //私有构造防止实例化 + + + /** + *

说明:生成一个指定长度的随机字符串,包含大小写英文字母和数字 + * @name: getRandomString + * @param len 要生成的字符串的长度 + * @author :Wind + */ + public static String getRandomString(int len){ + String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < len; i++) { + int number = random.nextInt(62); + sb.append(str.charAt(number)); + } + return sb.toString(); + } + + /** + *

说明:获取一个长度为6的随机字符串 + * @name: getRandomString + * @param + * @author :Wind + */ + public static String getRandomString(){ + return getRandomString(6); + } + + /** + *

说明:生成一个指定长度的只有数字组成的随机字符串 + * @name: getRandomInt + * @param len 要生成的长度 + * @author :Wind + */ + public static String getRandomInt(int len){ + String str = "0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < len; i++) { + int number = random.nextInt(10); + sb.append(str.charAt(number)); + } + return sb.toString(); + } + + /** + * 指定元素是否为null或者空字符串 + * @param str 指定元素 + * @return 是否为null或者空字符串 + * @author :Wind + */ + public static boolean isEmpty(Object str) { + return str == null || "".equals(str); + } + + /** + * 指定元素是否不为 (null或者空字符串) + * @param str 指定元素 + * @return 是否为null或者空字符串 + * @author :Wind + */ + public static boolean isNotEmpty(Object str) { + return !isEmpty(str); + } + +} diff --git a/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/TimeExpiredPoolCache.java b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/TimeExpiredPoolCache.java new file mode 100644 index 00000000..e3959636 --- /dev/null +++ b/sms-aggregation-comm/src/main/java/kim/wind/sms/comm/utils/TimeExpiredPoolCache.java @@ -0,0 +1,218 @@ +package kim.wind.sms.comm.utils; + + +import kim.wind.sms.comm.exception.SmsBlendException; + +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; + +/** + *

类名: TimeExpiredPoolCache + *

说明: 一个自实现的内部缓存,可用于无法使用redis的场景 + * @author :Wind + * 2023/3/25 18:26 + **/ +public class TimeExpiredPoolCache { + private static long defaultCachedMillis = 24 * 60 * 60 * 1000L;//过期时间默认24小时 + private static long timerMillis = 30 * 1000L;//定时清理默认1分钟 + /** + * 对象池 + */ + private static ConcurrentHashMap> dataPool = null; + /** + * 对象单例 + */ + private static TimeExpiredPoolCache instance = null; + + private TimeExpiredPoolCache() { + dataPool = new ConcurrentHashMap>(); + } + + private static synchronized void syncInit() { + if (instance == null) { + instance = new TimeExpiredPoolCache(); + initTimer(); + } + } + + public static TimeExpiredPoolCache getInstance() { + if (instance == null) { + syncInit(); + } + return instance; + } + + /** + * 定时器定时清理过期缓存 + */ + private static Timer timer = new Timer(); + + private static void initTimer() { + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + clearExpiredCaches(); + } catch (Exception e) { + throw new SmsBlendException(e.getMessage()); + } + } + }, timerMillis, timerMillis); + } + + /** + * 缓存数据 + * + * @param key key值 + * @param data 缓存数据 + * @param cachedMillis 过期时间 + * @param dataRenewer 刷新数据 + */ + @SuppressWarnings("unchecked") + public T put(String key, T data, long cachedMillis, DataRenewer dataRenewer) throws Exception { + DataWrapper dataWrapper = (DataWrapper) 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 put(String key, T data, long cachedMillis) throws Exception { + DataWrapper dataWrapper = (DataWrapper) dataPool.get(key); + if (dataWrapper != null) { + //更新 + dataWrapper.update(data, cachedMillis); + } else { + dataWrapper = new DataWrapper(data, cachedMillis); + dataPool.put(key, dataWrapper); + } + return data; + } + + /** + * 默认构造时间的缓存数据 + */ + @Deprecated + public T put(String key, T data, DataRenewer dataRenewer) throws Exception { + return put(key, data, defaultCachedMillis, dataRenewer); + } + + /** + * 获取缓存 + */ + @SuppressWarnings("unchecked") + public T get(String key, long cachedMillis, DataRenewer dataRenewer) throws Exception { + DataWrapper dataWrapper = (DataWrapper) dataPool.get(key); + if (dataWrapper != null && !dataWrapper.isExpired()) { + return dataWrapper.data; + } + return put(key, null, cachedMillis, dataRenewer); + } + + @SuppressWarnings("unchecked") + public T get(String key) { + DataWrapper dataWrapper = (DataWrapper) 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); + } + + /** + * 数据封装 + */ + private class DataWrapper { + /** + * 数据 + */ + 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; + } + } + + /** + * 数据构造 + */ + public interface DataRenewer { + public T renewData(); + } + + /** + * 清除过期的缓存 + */ + private static void clearExpiredCaches() { + List expiredKeyList = new LinkedList(); + + for (Entry> entry : dataPool.entrySet()) { + if (entry.getValue().isExpired()) { + expiredKeyList.add(entry.getKey()); + } + } + for (String key : expiredKeyList) { + dataPool.remove(key); + } + } +} diff --git a/sms-aggregation-spring-boot-starter/pom.xml b/sms-aggregation-spring-boot-starter/pom.xml index 110398e5..4335db06 100644 --- a/sms-aggregation-spring-boot-starter/pom.xml +++ b/sms-aggregation-spring-boot-starter/pom.xml @@ -11,7 +11,7 @@ sms-aggregation-spring-boot-starter sms-aggregation-spring-boot-starter sms-aggregation-spring-boot-starter - ${modules.version} + 1.0.0 jar @@ -31,11 +31,6 @@ spring-boot-starter - - kim.wind - sms-aggregation-api - - kim.wind sms-aggregation-aliyun @@ -55,6 +50,17 @@ kim.wind sms-aggregation-yunpian + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-data-redis + diff --git a/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/AopAdvice.java b/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/AopAdvice.java new file mode 100644 index 00000000..ed349577 --- /dev/null +++ b/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/AopAdvice.java @@ -0,0 +1,124 @@ +package kim.wind.sms.starter.config; + + +import kim.wind.sms.comm.exception.SmsBlendException; +import kim.wind.sms.comm.utils.RedisUtils; +import kim.wind.sms.comm.utils.SmsUtil; +import kim.wind.sms.comm.utils.SpringUtil; +import kim.wind.sms.comm.utils.TimeExpiredPoolCache; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; + +@Aspect +public class AopAdvice { + + private static final Long minTimer = 60 * 1000L; + private static final Long accTimer = 24 * 60 * 60 * 1000L; + + private static final String REDIS_KEY = "sms:restricted:"; + + @Autowired + private SmsMainConfig config; + + + @Pointcut("@annotation(kim.wind.sms.comm.annotation.Restricted)") + public void restricted() { + } + + @Around("restricted()") + public Object restrictedSendMessage(ProceedingJoinPoint p) throws Throwable { + + String args = ""; + ArrayList argsList = new ArrayList<>(); + try { + args = (String) p.getArgs()[0]; + } catch (Exception e) { + for (Object o : (ArrayList) p.getArgs()[0]) { + argsList.add((String) o); + } + } + SmsBlendException process = redisProcess(args); + if (process != null) { + throw process; + } + argsList.forEach(f -> { + SmsBlendException proce = null; + try { + proce = redisProcess(f); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (proce != null) { + throw proce; + } + }); + return p.proceed(); + } + + private SmsBlendException process(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) { + return new SmsBlendException("accountMax", args + "今日短信已达最大次数"); + } 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 { + return new SmsBlendException("minuteMax", args + "短信发送过于频繁!"); + } + } else { + instance.put(args, 1, minTimer); + } + } + return null; + } + + private SmsBlendException redisProcess(String args) throws Exception{ + RedisUtils redis = SpringUtil.getBean(RedisUtils.class); + if (redis == null || config.getRedisCache().equals("false")){ + return process(args); + } + 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); + } else if (i > accountMax) { + return new SmsBlendException("accountMax", args + "今日短信已达最大次数"); + } else { + redis.set(REDIS_KEY+args + "max", i + 1); + } + } + 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); + } else { + return new SmsBlendException("minuteMax", args + "短信发送过于频繁!"); + } + } else { + redis.set(REDIS_KEY+args, 1); + } + } + return null; + } + +} diff --git a/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/SmsMainConfig.java b/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/SmsMainConfig.java index 92dfd56f..c32265a0 100644 --- a/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/SmsMainConfig.java +++ b/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/SmsMainConfig.java @@ -1,28 +1,104 @@ package kim.wind.sms.starter.config; +import kim.wind.sms.aliyun.config.AlibabaSmsConfig; +import kim.wind.sms.aliyun.service.AlibabaSmsImpl; +import kim.wind.sms.api.SmsBlend; +import kim.wind.sms.comm.utils.RedisUtils; 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 kim.wind.sms.comm.utils.SpringUtil; +import org.springframework.data.redis.core.RedisTemplate; +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; + /** 是否开启短信限制*/ 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 + public SmsBlend smsBlend(){ + SmsBlend smsBlend = null; + switch (supplier){ + case "alibaba": + smsBlend = new AlibabaSmsImpl(); + } + return smsBlend; + } + + @Bean("smsExecutor") + protected Executor taskExecutor(){ + // 创建一个线程池对象 + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(100); + executor.setKeepAliveSeconds(60); + executor.setThreadNamePrefix(threadNamePrefix); + executor.setWaitForTasksToCompleteOnShutdown(true); + // 线程池对拒绝任务的处理策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + //初始化线程池 + executor.initialize(); + return executor; + } + + @Bean + @ConditionalOnProperty(prefix = "sms", name = "restricted", havingValue = "true") + public AopAdvice aopAdvice(){ + return new AopAdvice(); + } + + @Bean + @ConditionalOnProperty(prefix = "sms", name = "redisCache", havingValue = "true") + public RedisUtils redisUtils(RedisTemplate redisTemplate){ + return new RedisUtils(redisTemplate); + } } diff --git a/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/TaskPoolConfig.java b/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/TaskPoolConfig.java new file mode 100644 index 00000000..a8cd6432 --- /dev/null +++ b/sms-aggregation-spring-boot-starter/src/main/java/kim/wind/sms/starter/config/TaskPoolConfig.java @@ -0,0 +1,19 @@ +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 { + + + +} diff --git a/sms-aggregation-spring-boot-starter/src/main/resources/META-INF/spring.factories b/sms-aggregation-spring-boot-starter/src/main/resources/META-INF/spring.factories index 2ef7e534..519aee3a 100644 --- a/sms-aggregation-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/sms-aggregation-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - + kim.wind.sms.starter.config.SmsMainConfig,\ + kim.wind.sms.aliyun.config.AlibabaSmsConfig diff --git a/sms-aggregation-tencent/pom.xml b/sms-aggregation-tencent/pom.xml index fc7a18c9..30544b60 100644 --- a/sms-aggregation-tencent/pom.xml +++ b/sms-aggregation-tencent/pom.xml @@ -11,7 +11,7 @@ sms-aggregation-tencent sms-aggregation-tencent sms-aggregation-tencent - ${modules.version} + 1.0.0 1.8 diff --git a/sms-aggregation-unisms/pom.xml b/sms-aggregation-unisms/pom.xml index 65e678ca..db8d2a1b 100644 --- a/sms-aggregation-unisms/pom.xml +++ b/sms-aggregation-unisms/pom.xml @@ -11,7 +11,7 @@ sms-aggregation-unisms sms-aggregation-unisms sms-aggregation-unisms - ${modules.version} + 1.0.0 1.8 diff --git a/sms-aggregation-yunpian/pom.xml b/sms-aggregation-yunpian/pom.xml index 9e32716f..d46ae66c 100644 --- a/sms-aggregation-yunpian/pom.xml +++ b/sms-aggregation-yunpian/pom.xml @@ -12,7 +12,7 @@ sms-aggregation-yunpian sms-aggregation-yunpian sms-aggregation-yunpian - ${modules.version} + 1.0.0 1.8