diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/AbstractGenericMethodInterceptor.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/AbstractGenericMethodInterceptor.java new file mode 100644 index 00000000..fd0d15f6 --- /dev/null +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/AbstractGenericMethodInterceptor.java @@ -0,0 +1,471 @@ +package org.dromara.sms4j.api.proxy; + +import org.dromara.sms4j.api.callback.CallBack; + +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; + +/** + * {@link SmsMethodInterceptor}的通用实现, + * 能够根据{@link SmsMethodType}将调用分发到某个具体的拦截方法上。 + * + * @author sh1yu + * @since 2023/10/27 13:03 + */ +public abstract class AbstractGenericMethodInterceptor implements SmsMethodInterceptor { + + /** + * 前置拦截,在方法执行前调用 + * + * @param methodType 方法类型,若不是{@link SmsMethodType}则可能为{@code null} + * @param method 方法 + * @param target 调用对象 + * @param params 调用参数 + * @return 调用参数 + * @implNote 若重写此方法,则务必调用{@link #doBeforeInvoke}方法实现调用分发 + */ + @Override + public Object[] beforeInvoke(SmsMethodType methodType, Method method, Object target, Object[] params) { + if (Objects.nonNull(methodType)) { + // 将方法分发到具体的调用 + doBeforeInvoke(params, methodType); + } + return params; + } + + @SuppressWarnings("unchecked") + protected final void doBeforeInvoke(Object[] params, SmsMethodType methodType) { + Objects.requireNonNull(methodType); + switch (methodType) { + case SEND_MESSAGE: + beforeSendMessage((String)params[0], (String)params[1]); + break; + case SEND_MESSAGE_WITH_TEMPLATE: + beforeSendMessageWithTemplate((String)params[0], (LinkedHashMap)params[1]); + break; + case SEND_MESSAGE_WITH_CUSTOM_TEMPLATE: + beforeSendMessageWithCustomTemplate((String)params[0], (String)params[1], (LinkedHashMap)params[2]); + break; + case MASS_TEXTING: + beforeMassTexting((List)params[0], (String)params[1]); + break; + case MASS_TEXTING_WITH_TEMPLATE: + beforeMassTextingWithTemplate((List)params[0], (String)params[1], (LinkedHashMap)params[2]); + break; + case SEND_MESSAGE_ASYNC: + beforeSendMessageAsync((String)params[0], (String)params[1], (CallBack)params[2]); + break; + case SEND_MESSAGE_ASYNC_NO_CALLBACK: + beforeSendMessageAsyncNoCallback((String)params[0], (String)params[1]); + break; + case SEND_MESSAGE_ASYNC_WITH_TEMPLATE: + beforeSendMessageAsyncWithTemplate((String)params[0], (String)params[1], (LinkedHashMap)params[2], (CallBack)params[3]); + break; + case SEND_MESSAGE_ASYNC_WITH_TEMPLATE_NO_CALLBACK: + beforeSendMessageAsyncWithTemplateNoCallback((String)params[0], (String)params[1], (LinkedHashMap)params[2]); + break; + case DELAYED_MESSAGE: + beforeDelayedMessage((String)params[0], (String)params[1], (Long)params[2]); + break; + case DELAYED_MESSAGE_WITH_TEMPLATE: + beforeDelayedMessageWithTemplate((String)params[0], (String)params[1], (LinkedHashMap)params[2], (Long)params[3]); + break; + case DELAY_MASS_TEXTING: + beforeDelayMassTexting((List)params[0], (String)params[1], (Long)params[2]); + break; + case DELAY_MASS_TEXTING_WITH_TEMPLATE: + beforeDelayMassTextingWithTemplate((List)params[0], (String)params[1], (LinkedHashMap)params[2], (Long)params[3]); + break; + default: // do nothing + } + } + + /** + * 后置拦截,在方法执行后,无论是否发生异常都会调用 + * + * @param methodType 方法类型,若不是{@link SmsMethodType}则可能为{@code null} + * @param method 调用方法 + * @param params 调用参数 + * @param result 返回值 + * @param ex 调用过程捕获的异常,可能为{@code null} + * @return 返回值,不为{@code null}时将覆盖原有的返回值 + * @implNote 若重写此方法,则务必调用{@link ##doAfterCompletion}方法实现调用分发 + */ + @Override + public Object afterCompletion(SmsMethodType methodType, Method method, Object[] params, Object result, Exception ex) { + if (Objects.nonNull(methodType)) { + // 将方法分发到具体的调用... + doAfterCompletion(params, result, ex, methodType); + } + return result; + } + + @SuppressWarnings("unchecked") + private void doAfterCompletion(Object[] params, Object result, Exception ex, SmsMethodType methodType) { + Objects.requireNonNull(methodType); + switch (methodType) { + case SEND_MESSAGE: + afterSendMessage((String)params[0], (String)params[1], result, ex); + break; + case SEND_MESSAGE_WITH_TEMPLATE: + afterSendMessageWithTemplate((String)params[0], (LinkedHashMap)params[1], result, ex); + break; + case SEND_MESSAGE_WITH_CUSTOM_TEMPLATE: + afterSendMessageWithCustomTemplate((String)params[0], (String)params[1], (LinkedHashMap)params[2], result, ex); + break; + case MASS_TEXTING: + afterMassTexting((List)params[0], (String)params[1], result, ex); + break; + case MASS_TEXTING_WITH_TEMPLATE: + afterMassTextingWithTemplate((List)params[0], (String)params[1], (LinkedHashMap)params[2], result, ex); + break; + case SEND_MESSAGE_ASYNC: + afterSendMessageAsync((String)params[0], (String)params[1], (CallBack)params[2], result, ex); + break; + case SEND_MESSAGE_ASYNC_NO_CALLBACK: + afterSendMessageAsyncNoCallback((String)params[0], (String)params[1], result, ex); + break; + case SEND_MESSAGE_ASYNC_WITH_TEMPLATE: + afterSendMessageAsyncWithTemplate((String)params[0], (String)params[1], (LinkedHashMap)params[2], (CallBack)params[3], result, ex); + break; + case SEND_MESSAGE_ASYNC_WITH_TEMPLATE_NO_CALLBACK: + afterSendMessageAsyncWithTemplateNoCallback((String)params[0], (String)params[1], (LinkedHashMap)params[2], result, ex); + break; + case DELAYED_MESSAGE: + afterDelayedMessage((String)params[0], (String)params[1], (Long)params[2], result, ex); + break; + case DELAYED_MESSAGE_WITH_TEMPLATE: + afterDelayedMessageWithTemplate((String)params[0], (String)params[1], (LinkedHashMap)params[2], (Long)params[3], result, ex); + break; + case DELAY_MASS_TEXTING: + afterDelayMassTexting((List)params[0], (String)params[1], (Long)params[2], result, ex); + break; + case DELAY_MASS_TEXTING_WITH_TEMPLATE: + afterDelayMassTextingWithTemplate((List)params[0], (String)params[1], (LinkedHashMap)params[2], (Long)params[3], result, ex); + break; + default: // do nothing + } + } + + // region ===== before ===== + + /** + * {@link org.dromara.sms4j.api.SmsBlend#sendMessage(String, String)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + */ + protected void beforeSendMessage(String phone, String message) { + // do nothing + } + + /** + * {@link org.dromara.sms4j.api.SmsBlend#sendMessage(String, LinkedHashMap)} + * + * @param phone 接收短信的手机号 + * @param messages 模板内容 + */ + protected void beforeSendMessageWithTemplate(String phone, LinkedHashMap messages) { + // do nothing + } + + /** + * {@link org.dromara.sms4j.api.SmsBlend#sendMessage(String, String, LinkedHashMap)} + * + * @param phone 接收短信的手机号 + * @param templateId 模板ID + * @param messages 内容 + */ + protected void beforeSendMessageWithCustomTemplate( + String phone, String templateId, LinkedHashMap messages) { + // do nothing + } + + /** + * {@link org.dromara.sms4j.api.SmsBlend#massTexting(List, String)} + * + * @param phones 接收短信的手机号 + * @param message 内容 + */ + protected void beforeMassTexting(List phones, String message) { + // do nothing + } + + /** + * {@link org.dromara.sms4j.api.SmsBlend#massTexting(List, String, LinkedHashMap)} + * + * @param phones 接收短信的手机号 + * @param templateId 模板ID + * @param messages 内容 + */ + protected void beforeMassTextingWithTemplate( + List phones, String templateId, LinkedHashMap messages) { + // do nothing + } + + /** + * 前置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessageAsync(String, String, CallBack)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param callBack 回调 + */ + protected void beforeSendMessageAsync(String phone, String message, CallBack callBack) { + // do nothing + } + + /** + * 前置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessageAsync(String, String)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + */ + protected void beforeSendMessageAsyncNoCallback(String phone, String message) { + // do nothing + } + + /** + * 前置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessageAsync(String, String, LinkedHashMap, CallBack)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param template 模板 + * @param callBack 回调 + */ + protected void beforeSendMessageAsyncWithTemplate(String phone, String message, LinkedHashMap template, CallBack callBack) { + // do nothing + } + + /** + * 前置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessageAsync(String, String, LinkedHashMap)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param template 模板 + */ + protected void beforeSendMessageAsyncWithTemplateNoCallback(String phone, String message, LinkedHashMap template) { + // do nothing + } + + /** + * 前置处理 {@link org.dromara.sms4j.api.SmsBlend#delayedMessage(String, String, Long)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param delay 延迟时间 + */ + protected void beforeDelayedMessage(String phone, String message, Long delay) { + // do nothing + } + + /** + * 前置处理 {@link org.dromara.sms4j.api.SmsBlend#delayedMessage(String, String, LinkedHashMap, Long)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param template 模板 + * @param delay 延迟时间 + */ + protected void beforeDelayedMessageWithTemplate(String phone, String message, LinkedHashMap template, Long delay) { + // do nothing + } + + /** + * 前置处理 {@link org.dromara.sms4j.api.SmsBlend#delayMassTexting(List, String, Long)} + * + * @param phones 接收短信的手机号 + * @param message 内容 + * @param delay 延迟时间 + */ + protected void beforeDelayMassTexting(List phones, String message, Long delay) { + // do nothing + } + + /** + * 前置处理 {@link org.dromara.sms4j.api.SmsBlend#delayMassTexting(List, String, LinkedHashMap, Long)} + * + * @param phones 接收短信的手机号 + * @param message 内容 + * @param template 模板 + * @param delay 延迟时间 + */ + protected void beforeDelayMassTextingWithTemplate(List phones, String message, LinkedHashMap template, Long delay) { + // do nothing + } + + // endregion + + // region ===== after ===== + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessage(String, String)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterSendMessage(String phone, String message, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessage(String, LinkedHashMap)} + * + * @param phone 接收短信的手机号 + * @param messages 模板内容 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterSendMessageWithTemplate(String phone, LinkedHashMap messages, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessage(String, String, LinkedHashMap)} + * + * @param phone 接收短信的手机号 + * @param templateId 模板ID + * @param messages 内容 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterSendMessageWithCustomTemplate(String phone, String templateId, LinkedHashMap messages, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#massTexting(List, String)} + * + * @param phones 接收短信的手机号 + * @param message 内容 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterMassTexting(List phones, String message, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#massTexting(List, String, LinkedHashMap)} + * + * @param phones 接收短信的手机号 + * @param templateId 模板ID + * @param messages 内容 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterMassTextingWithTemplate(List phones, String templateId, LinkedHashMap messages, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessageAsync(String, String, CallBack)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param callBack 回调 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterSendMessageAsync(String phone, String message, CallBack callBack, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessageAsync(String, String)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterSendMessageAsyncNoCallback(String phone, String message, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessageAsync(String, String, LinkedHashMap, CallBack)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param template 模板 + * @param callBack 回调 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterSendMessageAsyncWithTemplate(String phone, String message, LinkedHashMap template, CallBack callBack, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#sendMessageAsync(String, String, LinkedHashMap)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param template 模板 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterSendMessageAsyncWithTemplateNoCallback(String phone, String message, LinkedHashMap template, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#delayedMessage(String, String, Long)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param delay 延迟时间 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterDelayedMessage(String phone, String message, Long delay, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#delayedMessage(String, String, LinkedHashMap, Long)} + * + * @param phone 接收短信的手机号 + * @param message 内容 + * @param template 模板 + * @param delay 延迟时间 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterDelayedMessageWithTemplate(String phone, String message, LinkedHashMap template, Long delay, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#delayMassTexting(List, String, Long)} + * + * @param phones 接收短信的手机号 + * @param message 内容 + * @param delay 延迟时间 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterDelayMassTexting(List phones, String message, Long delay, Object result, Exception ex) { + // do nothing + } + + /** + * 后置处理 {@link org.dromara.sms4j.api.SmsBlend#delayMassTexting(List, String, LinkedHashMap, Long)} + * + * @param phones 接收短信的手机号 + * @param message 内容 + * @param template 模板 + * @param delay 延迟时间 + * @param result 方法返回值 + * @param ex 异常信息 + */ + protected void afterDelayMassTextingWithTemplate(List phones, String message, LinkedHashMap template, Long delay, Object result, Exception ex) { + // do nothing + } + + // endregion +} diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/CoreMethodProcessor.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/CoreMethodProcessor.java deleted file mode 100644 index b3056207..00000000 --- a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/CoreMethodProcessor.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.dromara.sms4j.api.proxy; - -import org.dromara.sms4j.comm.constant.NumberOfParasmeters; - -import java.lang.reflect.Method; -import java.util.LinkedHashMap; -import java.util.List; -/** - * 核心方法执行器接口 - * - * @author sh1yu - * @since 2023/10/27 13:03 - */ -public interface CoreMethodProcessor extends SmsProcessor { - default Object[] preProcessor(Method method, Object source, Object[] param) { - String name = method.getName(); - int parameterCount = method.getParameterCount(); - if ("sendMessage".equals(name)) { - if (NumberOfParasmeters.TWO == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) { - sendMessagePreProcess((String) param[0], param[1]); - return param; - } - if (NumberOfParasmeters.THREE == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) { - sendMessageByTemplatePreProcess((String)param[0],(String) param[1],(LinkedHashMap)param[2]); - return param; - } - } - if ("massTexting".equals(name)) { - if (NumberOfParasmeters.TWO == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) { - massTextingPreProcess((List)param[0],(String)param[1]); - return param; - } - if (NumberOfParasmeters.THREE == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) { - massTextingByTemplatePreProcess((List)param[0],(String)param[1],(LinkedHashMap)param[2]); - return param; - } - } - return param; - } - void sendMessagePreProcess(String phone, Object message); - void sendMessageByTemplatePreProcess(String phone, String templateId, LinkedHashMap messages); - void massTextingPreProcess(List phones, String message); - void massTextingByTemplatePreProcess(List phones, String templateId, LinkedHashMap messages); -} diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/Order.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/Order.java index c6b49e91..c12364e0 100644 --- a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/Order.java +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/Order.java @@ -1,4 +1,5 @@ package org.dromara.sms4j.api.proxy; + /** * 排序接口 * @@ -6,7 +7,13 @@ package org.dromara.sms4j.api.proxy; * @since 2023/10/27 13:03 */ public interface Order { - default public int getOrder(){ - return 999; + + /** + * 获取排序值,排序值越大的对象则优先级越低 + * + * @return 排序值 + */ + default int getOrder(){ + return Integer.MAX_VALUE; } } diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsMethodInterceptor.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsMethodInterceptor.java new file mode 100644 index 00000000..a266e1bd --- /dev/null +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsMethodInterceptor.java @@ -0,0 +1,46 @@ +package org.dromara.sms4j.api.proxy; + +import org.dromara.sms4j.api.SmsBlend; + +import java.lang.reflect.Method; + +/** + *

方法拦截器,用于拦截{@link SmsBlend}中的方法。
+ * 推荐基于{@link AbstractGenericMethodInterceptor}实现自定义拦截器。 + * + * @author sh1yu + * @since 2023/10/27 13:03 + * @see AbstractGenericMethodInterceptor + * @see SmsMethodType + */ +public interface SmsMethodInterceptor extends Order { + + /** + * 前置拦截,在方法执行前调用 + * + * @param methodType 方法类型,若不是{@link SmsMethodType}则可能为{@code null} + * @param method 方法 + * @param target 调用对象 + * @param params 调用参数 + * @return 调用参数 + */ + default Object[] beforeInvoke( + SmsMethodType methodType, Method method, Object target, Object[] params) { + return params; + } + + /** + * 后置拦截,在方法执行后,无论是否发生异常都会调用 + * + * @param methodType 方法类型,若不是{@link SmsMethodType}则可能为{@code null} + * @param method 调用方法 + * @param params 调用参数 + * @param result 返回值 + * @param ex 调用过程捕获的异常,可能为{@code null} + * @return 返回值,不为{@code null}时将覆盖原有的返回值 + */ + default Object afterCompletion( + SmsMethodType methodType, Method method, Object[] params, Object result, Exception ex) { + return result; + } +} diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsMethodType.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsMethodType.java new file mode 100644 index 00000000..4266838f --- /dev/null +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsMethodType.java @@ -0,0 +1,140 @@ +package org.dromara.sms4j.api.proxy; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ReflectUtil; +import lombok.Getter; +import org.dromara.sms4j.api.SmsBlend; +import org.dromara.sms4j.api.callback.CallBack; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * {@link SmsBlend}中的主要方法 + * + * @author huangchengxing + */ +@Getter +public enum SmsMethodType { + + /** + * {@link SmsBlend#sendMessage(String, String)} + */ + SEND_MESSAGE("sendMessage", String.class, String.class), + + /** + * {@link SmsBlend#sendMessage(String, LinkedHashMap)} + */ + SEND_MESSAGE_WITH_TEMPLATE("sendMessage", String.class, LinkedHashMap.class), + + /** + * {@link SmsBlend#sendMessage(String, String, LinkedHashMap)} + */ + SEND_MESSAGE_WITH_CUSTOM_TEMPLATE("sendMessage", String.class, String.class, LinkedHashMap.class), + + /** + * {@link SmsBlend#massTexting(List, String)} + */ + MASS_TEXTING("massTexting", List.class, String.class), + + /** + * {@link SmsBlend#massTexting(List, String, LinkedHashMap)} + */ + MASS_TEXTING_WITH_TEMPLATE("massTexting", List.class, String.class, LinkedHashMap.class), + + /** + * {@link SmsBlend#sendMessageAsync(String, String, CallBack)} + */ + SEND_MESSAGE_ASYNC("sendMessageAsync", String.class, String.class, CallBack.class), + + /** + * {@link SmsBlend#sendMessageAsync(String, String)} + */ + SEND_MESSAGE_ASYNC_NO_CALLBACK("sendMessageAsync", String.class, String.class), + + /** + * {@link SmsBlend#sendMessageAsync(String, String, LinkedHashMap, CallBack)} + */ + SEND_MESSAGE_ASYNC_WITH_TEMPLATE("sendMessageAsync", String.class, String.class, LinkedHashMap.class, CallBack.class), + + /** + * {@link SmsBlend#sendMessageAsync(String, String, LinkedHashMap)} + */ + SEND_MESSAGE_ASYNC_WITH_TEMPLATE_NO_CALLBACK("sendMessageAsync", String.class, String.class, LinkedHashMap.class), + + /** + * {@link SmsBlend#delayedMessage(String, String, Long)} + */ + DELAYED_MESSAGE("delayedMessage", String.class, String.class, Long.class), + + /** + * {@link SmsBlend#delayedMessage(String, String, LinkedHashMap, Long)} + */ + DELAYED_MESSAGE_WITH_TEMPLATE("delayedMessage", String.class, String.class, LinkedHashMap.class, Long.class), + + /** + * {@link SmsBlend#delayMassTexting(List, String, Long)} + */ + DELAY_MASS_TEXTING("delayMassTexting", List.class, String.class, Long.class), + + /** + * {@link SmsBlend#delayMassTexting(List, String, LinkedHashMap, Long)} + */ + DELAY_MASS_TEXTING_WITH_TEMPLATE("delayMassTexting", List.class, String.class, LinkedHashMap.class, Long.class); + + /** + * 方法 + */ + private final Method method; + + /** + * 获取方法名称 + * + * @return 方法名称 + */ + public String getName() { + return method.getName(); + } + + /** + * 检查指定方法是否与{@link SmsBlend}中的方法匹配 + * + * @param target 目标方法 + * @return 是否 + */ + public boolean isMatch(Method target) { + if (target == method) { + return true; + } + return Objects.nonNull(target) + && Objects.equals(method.getName(), target.getName()) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), target.getParameterTypes()) + && ClassUtil.isAssignable(method.getReturnType(), target.getReturnType()); + } + + /** + * 获取与目标方法对应的{@link SmsBlend}方法 + * + * @param targetMethod 目标方法 + * @return {@link SmsMethodType} + */ + public static SmsMethodType of(Method targetMethod) { + return Stream.of(values()) + .filter(m -> m.isMatch(targetMethod)) + .findFirst() + .orElse(null); + } + + SmsMethodType(String methodName, Class... parameterTypes) { + this.method = ReflectUtil.getMethod(SmsBlend.class, methodName, parameterTypes); + Assert.notNull( + method, "Cannot find best match method from SmsBlend: ({})[{}]", + methodName, Arrays.asList(parameterTypes) + ); + } +} diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsProcessor.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsProcessor.java deleted file mode 100644 index 8f506597..00000000 --- a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SmsProcessor.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.dromara.sms4j.api.proxy; - - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -/** - * 执行器接口 - * - * @author sh1yu - * @since 2023/10/27 13:03 - */ -public interface SmsProcessor extends Order { - default Object[] preProcessor(Method method, Object source, Object[] param) { - return null; - } - - default Object postProcessor(Object result, Object[] param) { - return null; - } - - default Object exceptionHandleProcessor(Method method, Object source, Object[] param,Exception exception) throws InvocationTargetException, IllegalAccessException { - return null; - } -} diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SupplierSupportedMethodInterceptor.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SupplierSupportedMethodInterceptor.java new file mode 100644 index 00000000..ed87b81a --- /dev/null +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SupplierSupportedMethodInterceptor.java @@ -0,0 +1,19 @@ +package org.dromara.sms4j.api.proxy; + +import java.util.Set; + +/** + * 限制拦截器仅针对哪些厂商生效,如果拦截器需要根据支持厂商加载,那可以实现此接口 + * + * @author sh1yu + * @since 2023/10/27 13:03 + */ +public interface SupplierSupportedMethodInterceptor extends SmsMethodInterceptor { + + /** + * 获取支持的供应商名称 + * + * @return 供应商名称 + */ + Set getSupportedSuppliers(); +} diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SuppotFilter.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SuppotFilter.java deleted file mode 100644 index 58453afa..00000000 --- a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/SuppotFilter.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.dromara.sms4j.api.proxy; - -import java.util.List; -/** - * 支持接口,如果执行器需要根据支持厂商加载,那可以实现此接口 - * - * @author sh1yu - * @since 2023/10/27 13:03 - */ -public interface SuppotFilter{ - List getSupports(); -} diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/aware/SmsConfigAware.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/aware/SmsConfigAware.java index 34b6b38a..37578617 100644 --- a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/aware/SmsConfigAware.java +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/aware/SmsConfigAware.java @@ -1,6 +1,5 @@ package org.dromara.sms4j.api.proxy.aware; - /** * 系统配置感知接口 * diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/aware/SmsDaoAware.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/aware/SmsDaoAware.java index f17a6297..b1ff40ed 100644 --- a/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/aware/SmsDaoAware.java +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/proxy/aware/SmsDaoAware.java @@ -1,6 +1,7 @@ package org.dromara.sms4j.api.proxy.aware; import org.dromara.sms4j.api.dao.SmsDao; + /** * 缓存感知接口 * diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/factory/SmsFactory.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/factory/SmsFactory.java index f9e1bf12..e990ec74 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/factory/SmsFactory.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/factory/SmsFactory.java @@ -142,8 +142,6 @@ public abstract class SmsFactory { } - - /** * renderWithRestricted *

构建smsBlend对象的代理对象 @@ -152,7 +150,7 @@ public abstract class SmsFactory { */ @Deprecated private static SmsBlend renderWithProxy(SmsBlend sms) { - return SmsProxyFactory.getProxySmsBlend(sms); + return SmsProxyFactory.getProxiedSmsBlend(sms); } /** diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/EnvirmentHolder.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/EnvirmentHolder.java deleted file mode 100644 index 57a19d1d..00000000 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/EnvirmentHolder.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.dromara.sms4j.core.proxy; - -import org.dromara.sms4j.provider.config.SmsConfig; - -import java.util.Map; - -/** - * 环境信息持有 - * - * @author sh1yu - * @since 2023/10/27 13:03 - */ -public class EnvirmentHolder { - private static SmsConfig smsConfig = null; - private static Map> blends = null; - - public static void frozenEnvirmet(SmsConfig smsConfig, Map> blends) { - if (null!=EnvirmentHolder.smsConfig||null!=EnvirmentHolder.blends){ - return; - } - EnvirmentHolder.smsConfig = smsConfig; - EnvirmentHolder.blends = blends; - } - - //只有核心包执行器部分才能获取 - static SmsConfig getSmsConfig() { - return smsConfig; - } - - //只有核心包执行器部分才能获取 - static Map> getBlends() { - return blends; - } -} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/EnvironmentHolder.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/EnvironmentHolder.java new file mode 100644 index 00000000..2c98dba6 --- /dev/null +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/EnvironmentHolder.java @@ -0,0 +1,50 @@ +package org.dromara.sms4j.core.proxy; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.provider.config.SmsConfig; + +import java.util.Map; +import java.util.Objects; + +/** + * 环境信息持有 + * + * @author sh1yu + * @since 2023/10/27 13:03 + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EnvironmentHolder { + + private static SmsConfig smsConfig = null; + private static Map> blends = null; + + public static void frozen(SmsConfig smsConfig, Map> blends) { + if (Objects.nonNull(EnvironmentHolder.smsConfig) || Objects.nonNull(EnvironmentHolder.blends)) { + log.warn("The environmental information has been loaded and cannot be overwritten!"); + return; + } + EnvironmentHolder.smsConfig = smsConfig; + EnvironmentHolder.blends = blends; + } + + /** + * 配置信息 + * + * @return 配置 + */ + static SmsConfig getSmsConfig() { + return smsConfig; + } + + /** + * 获取短信配置信息 + * + * @return 短信配置信息 + */ + static Map> getBlends() { + return blends; + } +} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsInvocationHandler.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsInvocationHandler.java index 493d5893..0edf1cf6 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsInvocationHandler.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsInvocationHandler.java @@ -1,69 +1,54 @@ package org.dromara.sms4j.core.proxy; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.SmsBlend; -import org.dromara.sms4j.api.proxy.SmsProcessor; -import org.dromara.sms4j.api.proxy.SuppotFilter; +import org.dromara.sms4j.api.proxy.SmsMethodType; +import org.dromara.sms4j.api.proxy.SmsMethodInterceptor; import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.LinkedList; import java.util.List; - /** - * SmsBlend增强,封装smsblend和执行器 + * {@link SmsBlend}代理,用于织入{@link SmsMethodInterceptor}实现前置和后置拦截 * * @author sh1yu * @since 2023/10/27 13:03 */ @Slf4j +@RequiredArgsConstructor public class SmsInvocationHandler implements InvocationHandler { - private final SmsBlend smsBlend; - private final LinkedList processors; - - public SmsInvocationHandler(SmsBlend smsBlend, LinkedList processors) { - this.smsBlend = smsBlend; - this.processors = processors; - } + private final SmsBlend delegate; + private final List interceptors; @Override - public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + public Object invoke(Object target, Method method, Object[] params) { + SmsMethodType methodType = SmsMethodType.of(method); Object result = null; - //前置执行器 - objects = doPreProcess(smsBlend, method, objects); + // 前置拦截 + params = invokePreHandle(methodType, delegate, method, params); + Exception ex = null; try { - result = method.invoke(smsBlend, objects); + result = method.invoke(delegate, params); } catch (Exception e) { - //错误执行器 - doErrorHandleProcess(smsBlend, method, objects,e); + ex = e; } - //后置执行器 - doPostrocess(smsBlend, method, objects, result); - return result; + // 后置拦截 + return invokeAfterCompletion(methodType, method, params, result, ex); } - public Object[] doPreProcess(Object o, Method method, Object[] objects) { - for (SmsProcessor processor : processors) { - objects = processor.preProcessor(method, o, objects); + private Object[] invokePreHandle(SmsMethodType methodType, Object o, Method method, Object[] objects) { + for (SmsMethodInterceptor interceptor : interceptors) { + objects = interceptor.beforeInvoke(methodType, method, o, objects); } return objects; } - public void doErrorHandleProcess(Object o, Method method, Object[] objects,Exception e) throws InvocationTargetException, IllegalAccessException { - for (SmsProcessor processor : processors) { - processor.exceptionHandleProcessor(method, o, objects,e); - } - } - - public Object doPostrocess(Object o, Method method, Object[] objects, Object result) { - for (SmsProcessor processor : processors) { - Object overrideResult = processor.postProcessor(result, objects); - if (overrideResult != null) { - return overrideResult; - } + private Object invokeAfterCompletion(SmsMethodType methodType, Method method, Object[] params, Object result, Exception ex) { + for (SmsMethodInterceptor interceptor : interceptors) { + result = interceptor.afterCompletion(methodType, method, params, result, ex); } return result; } diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsProxyFactory.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsProxyFactory.java index 5da1a4ba..b742d50e 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsProxyFactory.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsProxyFactory.java @@ -1,12 +1,14 @@ package org.dromara.sms4j.core.proxy; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.dao.SmsDao; import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl; import org.dromara.sms4j.api.proxy.Order; -import org.dromara.sms4j.api.proxy.SmsProcessor; -import org.dromara.sms4j.api.proxy.SuppotFilter; +import org.dromara.sms4j.api.proxy.SmsMethodInterceptor; +import org.dromara.sms4j.api.proxy.SupplierSupportedMethodInterceptor; import org.dromara.sms4j.api.proxy.aware.SmsBlendConfigAware; import org.dromara.sms4j.api.proxy.aware.SmsDaoAware; import org.dromara.sms4j.api.proxy.aware.SmsConfigAware; @@ -14,9 +16,10 @@ import org.dromara.sms4j.api.proxy.aware.SmsConfigAware; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.ArrayList; import java.util.Comparator; -import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -24,68 +27,80 @@ import java.util.stream.Collectors; * * @author sh1yu * @since 2023/10/27 13:03 + * @see SmsMethodInterceptor + * @see SmsInvocationHandler */ @Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) public abstract class SmsProxyFactory { - private static final LinkedList processors = new LinkedList<>(); - public static SmsBlend getProxySmsBlend(SmsBlend smsBlend) { - LinkedList ownerProcessors = processors.stream().filter(processor -> !shouldSkipProcess(processor,smsBlend)).collect(Collectors.toCollection(LinkedList::new)); - return (SmsBlend) Proxy.newProxyInstance(smsBlend.getClass().getClassLoader(), new Class[]{SmsBlend.class}, new SmsInvocationHandler(smsBlend, ownerProcessors)); + private static final List INTERCEPTORS = new ArrayList<>(); + private static final String SPRING_SMS_DAO_LOAD_PATH = "org.dromara.sms4j.starter.holder.SpringSmsDaoHolder"; + private static final String SOLON_SMS_DAO_LOAD_PATH = "org.dromara.sms4j.solon.holder.SolonSmsDaoHolder"; + + public static final int CORE_PARAM_VALIDATE_METHOD_INTERCEPTOR_ORDER = -1; + public static final int BLACK_LIST_METHOD_INTERCEPTOR_ORDER = 0; + public static final int BLACK_LIST_RECORDING_METHOD_INTERCEPTOR_ORDER = 1; + public static final int RESTRICTED_METHOD_INTERCEPTOR = 3; + public static final int SINGLE_BLEND_RESTRICTED_METHOD_INTERCEPTOR_ORDER = 2; + + public static SmsBlend getProxiedSmsBlend(SmsBlend smsBlend) { + Objects.requireNonNull(smsBlend); + // 若已被代理则直接返回,避免重复代理 + if (smsBlend instanceof Proxied) { + return smsBlend; + } + List appliedInterceptors = INTERCEPTORS.stream() + .filter(processor -> canApply(processor, smsBlend)) + .collect(Collectors.toList()); + return (SmsBlend) Proxy.newProxyInstance( + smsBlend.getClass().getClassLoader(), + new Class[]{ SmsBlend.class, Proxied.class }, + new SmsInvocationHandler(smsBlend, appliedInterceptors) + ); } /** * 增加拦截器 + * + * @param processor 处理器 + * TODO 当在SpringBoot或Solon环境中使用时,应当在完成加载后,主动从IOC容器获取并注册用户自定义的方法拦截器 */ - public static void addProcessor(SmsProcessor processor) { - //校验拦截器是否正确 - processorValidate(processor); + public static void addProcessor(SmsMethodInterceptor processor) { + Objects.requireNonNull(processor); + // 调用Aware接口,将必要参数传递给处理器 awareTransfer(processor); - processors.add(processor); - processors.sort(Comparator.comparingInt(Order::getOrder)); + // 尝试移除旧拦截器避免重复添加 + INTERCEPTORS.remove(processor); + INTERCEPTORS.add(processor); + INTERCEPTORS.sort(Comparator.comparingInt(Order::getOrder)); } - /* - * @see SuppotFilter - */ - public static boolean shouldSkipProcess(SmsProcessor processor, SmsBlend smsBlend) { - //判断当前的执行器有没有开厂商过滤,支不支持当前厂商 - if (processor instanceof SuppotFilter) { - List supports = ((SuppotFilter) processor).getSupports(); - boolean exsit = supports.stream().filter(support -> support.equals(smsBlend.getSupplier())).findAny().isPresent(); - if (!exsit) { - return true; - } - } - return false; + private static boolean canApply(SmsMethodInterceptor processor, SmsBlend smsBlend) { + // 判断当前的执行器有没有开厂商过滤,支不支持当前厂商 + return !(processor instanceof SupplierSupportedMethodInterceptor) + || ((SupplierSupportedMethodInterceptor)processor).getSupportedSuppliers().contains(smsBlend.getSupplier()); } - //所有处理器需要的各个参数可以通过这种aware接口形式传给对象 - private static void awareTransfer(SmsProcessor processor) { + private static void awareTransfer(SmsMethodInterceptor processor) { if (processor instanceof SmsDaoAware){ ((SmsDaoAware) processor).setSmsDao(getSmsDaoFromFramework()); } if (processor instanceof SmsConfigAware){ - ((SmsConfigAware) processor).setSmsConfig(EnvirmentHolder.getSmsConfig()); + ((SmsConfigAware) processor).setSmsConfig(EnvironmentHolder.getSmsConfig()); } if (processor instanceof SmsBlendConfigAware){ - ((SmsBlendConfigAware) processor).setSmsBlendsConfig(EnvirmentHolder.getBlends()); + ((SmsBlendConfigAware) processor).setSmsBlendsConfig(EnvironmentHolder.getBlends()); } } - //校验拦截器是否正确 - private static void processorValidate(SmsProcessor processor) { - - } - - //获取Sms的实现 private static SmsDao getSmsDaoFromFramework() { SmsDao smsDao; - smsDao = getSmsDaoFromFramework("org.dromara.sms4j.starter.holder.SpringSmsDaoHolder", "SpringBoot"); + smsDao = getSmsDaoFromFramework(SPRING_SMS_DAO_LOAD_PATH, "SpringBoot"); if (null != smsDao) { return smsDao; } - smsDao = getSmsDaoFromFramework("org.dromara.sms4j.solon.holder.SolonSmsDaoHolder", "Solon"); + smsDao = getSmsDaoFromFramework(SOLON_SMS_DAO_LOAD_PATH, "Solon"); if (null != smsDao) { return smsDao; } @@ -93,16 +108,16 @@ public abstract class SmsProxyFactory { return SmsDaoDefaultImpl.getInstance(); } - //获取Sms的实现 private static SmsDao getSmsDaoFromFramework(String className, String frameworkName) { try { Class clazz = Class.forName(className); - Method getSmsDao = clazz.getMethod("getSmsDao", null); - return (SmsDao) getSmsDao.invoke(null, null); + Method getSmsDao = clazz.getMethod("getSmsDao"); + return (SmsDao) getSmsDao.invoke(null); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { log.error("{}:加载SmsDao失败,尝试其他框架加载......", frameworkName); } return null; } + public interface Proxied {} } diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/BlackListMethodInterceptor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/BlackListMethodInterceptor.java new file mode 100644 index 00000000..d82e89fb --- /dev/null +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/BlackListMethodInterceptor.java @@ -0,0 +1,70 @@ +package org.dromara.sms4j.core.proxy.interceptor; + +import cn.hutool.core.collection.CollUtil; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.dao.SmsDao; +import org.dromara.sms4j.api.proxy.AbstractGenericMethodInterceptor; +import org.dromara.sms4j.api.proxy.aware.SmsDaoAware; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.core.proxy.SmsProxyFactory; + +import java.util.*; + +/** + * 黑名单前置拦截执行器 + * + * @author sh1yu + * @since 2023/10/27 13:03 + */ +@Slf4j +public class BlackListMethodInterceptor extends AbstractGenericMethodInterceptor implements SmsDaoAware { + + private static final String CONFIG_PROPERTIES_PREFIX = "sms:blacklist:global"; + + @Setter + private SmsDao smsDao; + + @Override + public int getOrder() { + return SmsProxyFactory.BLACK_LIST_METHOD_INTERCEPTOR_ORDER; + } + + @Override + protected void beforeSendMessage(String phone, String message) { + doRestricted(Collections.singletonList(phone)); + } + + @Override + protected void beforeSendMessageWithTemplate(String phone, LinkedHashMap messages) { + doRestricted(Collections.singletonList(phone)); + } + + @Override + protected void beforeSendMessageWithCustomTemplate(String phone, String templateId, LinkedHashMap messages) { + doRestricted(Collections.singletonList(phone)); + } + + @Override + protected void beforeMassTexting(List phones, String message) { + doRestricted(phones); + } + + @Override + protected void beforeMassTextingWithTemplate(List phones, String templateId, LinkedHashMap messages) { + doRestricted(phones); + } + + @SuppressWarnings("unchecked") + private void doRestricted(List phones) { + List blackList = (List) smsDao.get(CONFIG_PROPERTIES_PREFIX); + if (CollUtil.isEmpty(blackList)) { + return; + } + for (String phone : phones) { + if (blackList.stream().anyMatch(black -> black.replace("-","").equals(phone))) { + throw new SmsBlendException("The phone:", phone + " hit global blacklist!"); + } + } + } +} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/BlackListRecordingMethodInterceptor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/BlackListRecordingMethodInterceptor.java new file mode 100644 index 00000000..ecf8db56 --- /dev/null +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/BlackListRecordingMethodInterceptor.java @@ -0,0 +1,105 @@ +package org.dromara.sms4j.core.proxy.interceptor; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.SmsBlend; +import org.dromara.sms4j.api.dao.SmsDao; +import org.dromara.sms4j.api.proxy.SmsMethodType; +import org.dromara.sms4j.api.proxy.SmsMethodInterceptor; +import org.dromara.sms4j.api.proxy.aware.SmsDaoAware; +import org.dromara.sms4j.core.proxy.SmsProxyFactory; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * 黑名单前置拦截执行器 + * + * @author sh1yu + * @since 2023/10/27 13:03 + * @see SmsBlend#batchJoinBlacklist + * @see SmsBlend#batchRemovalFromBlacklist + * @see SmsBlend#joinInBlacklist + * @see SmsBlend#removeFromBlacklist + */ +@Slf4j +public class BlackListRecordingMethodInterceptor implements SmsMethodInterceptor, SmsDaoAware { + + private static final String REDIS_KEY_PREFIX = "sms:blacklist:global"; + private static final String CONFIG_PROPERTIES_PREFIX = "sms:blacklist:global"; + + private static final String JOIN_IN_BLACKLIST_METHOD = "joinInBlacklist"; + private static final String REMOVE_FROM_BLACKLIST_METHOD = "removeFromBlacklist"; + private static final String BATCH_JOIN_BLACKLIST_METHOD = "batchJoinBlacklist"; + private static final String BATCH_REMOVAL_FROM_BLACKLIST_METHOD = "batchRemovalFromBlacklist"; + + @Setter + private SmsDao smsDao; + + @Override + public int getOrder(){ + return SmsProxyFactory.BLACK_LIST_RECORDING_METHOD_INTERCEPTOR_ORDER; + } + + @SuppressWarnings("unchecked") + @Override + public Object[] beforeInvoke(SmsMethodType methodType, Method method, Object target, Object[] params) { + // TODO 并发操作是否可以保证安全性? + // TODO 不同的SmsBlend对象操作的确是同一份黑名单配置是否合理? + // TODO 是否应该同时支持黑白名单? + //添加到黑名单 + String methodName = method.getName(); + if (JOIN_IN_BLACKLIST_METHOD.equals(methodName)) { + String cacheKey = REDIS_KEY_PREFIX; + List blackList = getBlackList(cacheKey); + blackList.add((String) params[0]); + flushBlackList(cacheKey,blackList); + } + //从黑名单移除 + else if (REMOVE_FROM_BLACKLIST_METHOD.equals(methodName)) { + String cacheKey = REDIS_KEY_PREFIX; + List blackList = getBlackList(cacheKey); + blackList.remove((String) params[0]); + flushBlackList(cacheKey,blackList); + } + //批量添加到黑名单 + else if (BATCH_JOIN_BLACKLIST_METHOD.equals(methodName)) { + String cacheKey = REDIS_KEY_PREFIX; + List blackList = getBlackList(cacheKey); + blackList.addAll((List) params[0]); + flushBlackList(cacheKey,blackList); + } + //批量从黑名单移除 + else if (BATCH_REMOVAL_FROM_BLACKLIST_METHOD.equals(methodName)) { + String cacheKey = REDIS_KEY_PREFIX; + List blackList = getBlackList(cacheKey); + blackList.removeAll((List) params[0]); + flushBlackList(cacheKey, blackList); + } + return params; + } + + @SuppressWarnings("unchecked") + private List getBlackList(String cacheKey) { + List blackList; + Object cache = smsDao.get(cacheKey); + if (null != cache) { + blackList = (List) cache; + return blackList; + } + blackList = new ArrayList<>(); + smsDao.set(CONFIG_PROPERTIES_PREFIX, blackList); + return blackList; + } + + /** + * 刷新黑名单 + * + * @param cacheKey cacheKey + * @param blackList 新的黑名单列表 + */ + private void flushBlackList(String cacheKey, List blackList) { + smsDao.set(cacheKey, blackList); + } +} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/RestrictedMethodInterceptor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/RestrictedMethodInterceptor.java new file mode 100644 index 00000000..f4f63d86 --- /dev/null +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/RestrictedMethodInterceptor.java @@ -0,0 +1,132 @@ +package org.dromara.sms4j.core.proxy.interceptor; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.dao.SmsDao; +import org.dromara.sms4j.api.proxy.AbstractGenericMethodInterceptor; +import org.dromara.sms4j.api.proxy.aware.SmsDaoAware; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.core.proxy.SmsProxyFactory; +import org.dromara.sms4j.provider.config.SmsConfig; +import org.dromara.sms4j.provider.factory.BeanFactory; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; + + +/** + * 短信发送账号级上限前置拦截执行器 + * + * @author sh1yu + * @since 2023/10/27 13:03 + */ +@Slf4j +public class RestrictedMethodInterceptor extends AbstractGenericMethodInterceptor implements SmsDaoAware { + + /** + * 每分钟最多可发送条数 + * TODO 每分钟最多可发送条数应当可配置 + */ + private static final Long MINUTE_MAXIMUM_COUNT = 60 * 1000L; + /** + * 每个账号最多可发送条数 + * TODO 每个账号最多可发送条数应当可配置 + */ + private static final Long ACCOUNT_MAXIMUM_COUNT = 24 * 60 * 60 * 1000L; + /** + * redis缓存前缀 + * TODO redis缓存前缀应当可配置 + */ + private static final String REDIS_KEY = "sms:restricted:"; + + /** + * 缓存实例 + */ + @Setter + private SmsDao smsDao; + + @Override + public int getOrder() { + return SmsProxyFactory.RESTRICTED_METHOD_INTERCEPTOR; + } + + @Override + public void beforeSendMessage(String phone, String message) { + restricted(Collections.singletonList(phone)); + } + + @Override + protected void beforeSendMessageWithTemplate(String phone, LinkedHashMap messages) { + restricted(Collections.singletonList(phone)); + } + + @Override + public void beforeSendMessageWithCustomTemplate(String phone, String templateId, LinkedHashMap messages) { + restricted(Collections.singletonList(phone)); + } + + @Override + public void beforeMassTexting(List phones, String message) { + restricted(phones); + } + + @Override + public void beforeMassTextingWithTemplate(List phones, String templateId, LinkedHashMap messages) { + restricted(phones); + } + + public void restricted(List phones) { + if (Objects.isNull(smsDao)) { + throw new SmsBlendException("The dao tool could not be found"); + } + SmsConfig config = BeanFactory.getSmsConfig(); + // 每日最大发送量 + Integer accountMax = config.getAccountMax(); + // 每分钟最大发送量 + Integer minuteMax = config.getMinuteMax(); + for (String phone : phones) { + doRestricted(accountMax, minuteMax, phone); + } + } + + private void doRestricted(Integer accountMax, Integer minuteMax, String phone) { + // 是否配置了每日限制 + if (SmsUtils.isNotEmpty(accountMax)) { + checkAccountMax(accountMax, phone); + } + // 是否配置了每分钟最大限制 + if (SmsUtils.isNotEmpty(minuteMax)) { + Integer count = (Integer) smsDao.get(REDIS_KEY + phone); + checkMinuteMax(minuteMax, phone, count); + } + } + + private void checkMinuteMax(Integer minuteMax, String phone, Integer count) { + if (SmsUtils.isEmpty(count)) { + smsDao.set(REDIS_KEY + phone, 1, MINUTE_MAXIMUM_COUNT / 1000); + return; + } + if (count < minuteMax) { + smsDao.set(REDIS_KEY + phone, count + 1, MINUTE_MAXIMUM_COUNT / 1000); + return; + } + log.info("The phone: {}, number of short messages reached the maximum today", phone); + throw new SmsBlendException("The phone:", phone + " Text messages are sent too often!"); + } + + private void checkAccountMax(Integer accountMax, String phone) { + Integer i = (Integer) smsDao.get(REDIS_KEY + phone + "max"); + if (SmsUtils.isEmpty(i)) { + smsDao.set(REDIS_KEY + phone + "max", 1, ACCOUNT_MAXIMUM_COUNT / 1000); + return; + } + if (i >= accountMax) { + log.info("The phone: {}, number of short messages reached the maximum today", phone); + throw new SmsBlendException("The phone:" + phone + ",number of short messages reached the maximum today"); + } + smsDao.set(REDIS_KEY + phone + "max", i + 1, ACCOUNT_MAXIMUM_COUNT / 1000); + } +} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/SingleBlendRestrictedProcessor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/SingleBlendRestrictedMethodInterceptor.java similarity index 50% rename from sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/SingleBlendRestrictedProcessor.java rename to sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/SingleBlendRestrictedMethodInterceptor.java index 8ba4f583..5b712fe5 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/SingleBlendRestrictedProcessor.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/SingleBlendRestrictedMethodInterceptor.java @@ -1,17 +1,16 @@ -package org.dromara.sms4j.core.proxy.processor; +package org.dromara.sms4j.core.proxy.interceptor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.dao.SmsDao; -import org.dromara.sms4j.api.proxy.CoreMethodProcessor; -import org.dromara.sms4j.api.proxy.SmsProcessor; +import org.dromara.sms4j.api.proxy.SmsMethodType; +import org.dromara.sms4j.api.proxy.SmsMethodInterceptor; import org.dromara.sms4j.api.proxy.aware.SmsBlendConfigAware; import org.dromara.sms4j.api.proxy.aware.SmsDaoAware; import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.utils.SmsUtils; -import org.dromara.sms4j.provider.config.BaseConfig; -import org.dromara.sms4j.provider.service.AbstractSmsBlend; +import org.dromara.sms4j.core.proxy.SmsProxyFactory; import java.lang.reflect.Method; import java.util.*; @@ -24,9 +23,11 @@ import java.util.*; * @since 2023/10/27 13:03 */ @Slf4j -public class SingleBlendRestrictedProcessor implements SmsProcessor, SmsDaoAware, SmsBlendConfigAware { +public class SingleBlendRestrictedMethodInterceptor implements SmsMethodInterceptor, SmsDaoAware, SmsBlendConfigAware { private static final String REDIS_KEY = "sms:restricted:"; + private static final String SEND_MESSAGE_METHOD = "sendMessage"; + private static final String MASS_TEXT_METHOD = "massText"; /** * 缓存实例 @@ -35,30 +36,31 @@ public class SingleBlendRestrictedProcessor implements SmsProcessor, SmsDaoAware private SmsDao smsDao; @Setter - Map smsBlendsConfig; + private Map> smsBlendsConfig; @Override public int getOrder() { - return 2; + return SmsProxyFactory.SINGLE_BLEND_RESTRICTED_METHOD_INTERCEPTOR_ORDER; } @Override - public Object[] preProcessor(Method method, Object source, Object[] param) { - String name = method.getName(); - if (!"sendMessage".equals(name) && !"massText".equals(name)) { - return param; + public Object[] beforeInvoke(SmsMethodType methodType, Method method, Object target, Object[] params) { + if (Objects.isNull(methodType) + || !Objects.equals(methodType.getName(), SEND_MESSAGE_METHOD) + || !Objects.equals(methodType.getName(), MASS_TEXT_METHOD)) { + return params; } - SmsBlend smsBlend = (SmsBlend) source; + SmsBlend smsBlend = (SmsBlend)target; String configId = smsBlend.getConfigId(); - Map targetConfig = (Map) smsBlendsConfig.get(configId); + Map targetConfig = smsBlendsConfig.get(configId); Object maximumObj = targetConfig.get("maximum"); if (SmsUtils.isEmpty(maximumObj)) { - return param; + return params; } int maximum = 0; - try{ - maximum = (int) maximumObj ; - }catch (Exception e){ + try { + maximum = (int)maximumObj; + } catch (Exception e) { log.error("获取厂商级发送上限参数错误!请检查!"); throw new IllegalArgumentException("获取厂商级发送上限参数错误"); } @@ -66,11 +68,11 @@ public class SingleBlendRestrictedProcessor implements SmsProcessor, SmsDaoAware if (SmsUtils.isEmpty(i)) { smsDao.set(REDIS_KEY + configId + "maximum", 1); } else if (i >= maximum) { - log.info("The channel:" + configId + ",messages reached the maximum"); - throw new SmsBlendException("The channel:" + configId + ",messages reached the maximum"); + log.info("The channel: {}, messages reached the maximum", configId); + throw new SmsBlendException("The channel: " + configId + ", messages reached the maximum", configId); } else { smsDao.set(REDIS_KEY + configId + "maximum", i + 1); } - return param; + return params; } } diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/SyncMethodParamValidateMethodInterceptor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/SyncMethodParamValidateMethodInterceptor.java new file mode 100644 index 00000000..be53250b --- /dev/null +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/interceptor/SyncMethodParamValidateMethodInterceptor.java @@ -0,0 +1,91 @@ +package org.dromara.sms4j.core.proxy.interceptor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.CharSequenceUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.proxy.AbstractGenericMethodInterceptor; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.core.proxy.SmsProxyFactory; + +import java.util.*; + + +/** + * 同步调用方法参数校验前置拦截执行器 + * + * @author sh1yu + * @since 2023/10/27 13:03 + * TODO 异步调用和延迟调用的参数是否需要校验? + */ +@Slf4j +public class SyncMethodParamValidateMethodInterceptor extends AbstractGenericMethodInterceptor { + + @Override + public int getOrder() { + return SmsProxyFactory.CORE_PARAM_VALIDATE_METHOD_INTERCEPTOR_ORDER; + } + + @Override + public void beforeSendMessage(String phone, String message) { + validatePhone(phone); + validateMessage(message); + } + + @Override + protected void beforeSendMessageWithTemplate(String phone, LinkedHashMap messages) { + validatePhone(phone); + validateMessage(messages); + } + + @Override + public void beforeSendMessageWithCustomTemplate(String phone, String templateId, LinkedHashMap messages) { + validatePhone(phone); + validateMessages(templateId, messages); + } + + @Override + public void beforeMassTexting(List phones, String message) { + validateMessage(message); + validatePhones(phones); + } + + @Override + public void beforeMassTextingWithTemplate(List phones, String templateId, LinkedHashMap messages) { + validatePhones(phones); + validateMessages(templateId, messages); + } + + public void validateMessage(Object messageObj) { + if (messageObj instanceof String){ + String message = (String) messageObj; + if (CharSequenceUtil.isEmpty(message)) { + throw new SmsBlendException("can't send a null message!"); + } + } + if (messageObj instanceof Map){ + Map message = (Map) messageObj; + if (CollUtil.isEmpty(message)) { + throw new SmsBlendException("can't send a null message!"); + } + } + } + + public void validatePhone(String phone) { + if (CharSequenceUtil.isEmpty(phone)) { + throw new SmsBlendException("can't send message to null!"); + } + } + + public void validatePhones(List phones) { + if (CollUtil.isEmpty(phones)) { + throw new SmsBlendException("can't send message to null!"); + } + phones.forEach(this::validatePhone); + } + + public void validateMessages(String templateId, LinkedHashMap messages) { + if (CharSequenceUtil.isNotEmpty(templateId) && CollUtil.isEmpty(messages)) { + throw new SmsBlendException("can't use template without template param"); + } + } +} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/BlackListProcessor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/BlackListProcessor.java deleted file mode 100644 index 556ff1c2..00000000 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/BlackListProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.dromara.sms4j.core.proxy.processor; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.dromara.sms4j.api.dao.SmsDao; -import org.dromara.sms4j.api.proxy.CoreMethodProcessor; -import org.dromara.sms4j.api.proxy.aware.SmsConfigAware; -import org.dromara.sms4j.api.proxy.aware.SmsDaoAware; -import org.dromara.sms4j.comm.exception.SmsBlendException; -import org.dromara.sms4j.provider.config.SmsConfig; - -import java.util.*; - -/** - * 黑名单前置拦截执行器 - * - * @author sh1yu - * @since 2023/10/27 13:03 - */ -@Slf4j -public class BlackListProcessor implements CoreMethodProcessor, SmsDaoAware { - @Setter - SmsDao smsDao; - - @Override - public int getOrder() { - return 0; - } - - @Override - public void sendMessagePreProcess(String phone, Object message) { - doRestricted(Collections.singletonList(phone)); - } - - @Override - public void sendMessageByTemplatePreProcess(String phone, String templateId, LinkedHashMap messages) { - doRestricted(Collections.singletonList(phone)); - } - - @Override - public void massTextingPreProcess(List phones, String message) { - doRestricted(phones); - } - - @Override - public void massTextingByTemplatePreProcess(List phones, String templateId, LinkedHashMap messages) { - doRestricted(phones); - } - - public void doRestricted(List phones) { - ArrayList blackList = (ArrayList) smsDao.get("sms:blacklist:global"); - if(null==blackList){ - return; - } - for (String phone : phones) { - if (blackList.stream().filter(black -> black.replace("-","").equals(phone)).findAny().isPresent()) { - throw new SmsBlendException("The phone:", phone + " hit global blacklist!"); - } - } - } -} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/BlackListRecordingProcessor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/BlackListRecordingProcessor.java deleted file mode 100644 index fb0dd3c2..00000000 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/BlackListRecordingProcessor.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.dromara.sms4j.core.proxy.processor; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.dromara.sms4j.api.dao.SmsDao; -import org.dromara.sms4j.api.proxy.SmsProcessor; -import org.dromara.sms4j.api.proxy.aware.SmsConfigAware; -import org.dromara.sms4j.api.proxy.aware.SmsDaoAware; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -/** - * 黑名单前置拦截执行器 - * - * @author sh1yu - * @since 2023/10/27 13:03 - */ -@Slf4j -public class BlackListRecordingProcessor implements SmsProcessor, SmsDaoAware, SmsConfigAware { - @Setter - SmsDao smsDao; - - @Setter - Object smsConfig; - - public int getOrder(){ - return 1; - } - - @Override - public Object[] preProcessor(Method method, Object source, Object[] param) { - //添加到黑名单 - if (method.getName().equals("joinInBlacklist")) { - String cacheKey = getCacheKey(); - ArrayList blackList = getBlackList(cacheKey); - blackList.add((String) param[0]); - flushBlackList(cacheKey,blackList); - } - //从黑名单移除 - if (method.getName().equals("removeFromBlacklist")) { - String cacheKey = getCacheKey(); - ArrayList blackList = getBlackList(cacheKey); - blackList.remove((String) param[0]); - flushBlackList(cacheKey,blackList); - } - //批量添加到黑名单 - if (method.getName().equals("batchJoinBlacklist")) { - String cacheKey = getCacheKey(); - ArrayList blackList = getBlackList(cacheKey); - blackList.addAll((List) param[0]); - flushBlackList(cacheKey,blackList); - } - //批量从黑名单移除 - if (method.getName().equals("batchRemovalFromBlacklist")) { - String cacheKey = getCacheKey(); - ArrayList blackList = getBlackList(cacheKey); - blackList.removeAll((List) param[0]); - flushBlackList(cacheKey,blackList); - } - return param; - } - - //构建cachekey - public String getCacheKey(){ - return "sms:blacklist:global"; - } - - //获取黑名单,没有就新建 - public ArrayList getBlackList(String cacheKey) { - ArrayList blackList; - Object cache = smsDao.get(cacheKey); - if (null != cache) { - blackList = (ArrayList) cache; - return blackList; - } - blackList = new ArrayList<>(); - smsDao.set("sms:blacklist:global", blackList); - return blackList; - } - - //让黑名单生效 - public void flushBlackList(String cacheKey ,ArrayList blackList) { - smsDao.set(cacheKey, blackList); - } -} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/CoreMethodParamValidateProcessor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/CoreMethodParamValidateProcessor.java deleted file mode 100644 index 3de952bd..00000000 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/CoreMethodParamValidateProcessor.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.dromara.sms4j.core.proxy.processor; - -import lombok.extern.slf4j.Slf4j; -import org.dromara.sms4j.api.proxy.CoreMethodProcessor; -import org.dromara.sms4j.comm.exception.SmsBlendException; - -import java.util.*; - - -/** - * 核心方法参数校验前置拦截执行器 - * - * @author sh1yu - * @since 2023/10/27 13:03 - */ -@Slf4j -public class CoreMethodParamValidateProcessor implements CoreMethodProcessor { - @Override - public int getOrder() { - return -1; - } - - @Override - public void sendMessagePreProcess(String phone, Object message) { - validatePhone(phone); - validateMessage(message); - } - - @Override - public void sendMessageByTemplatePreProcess(String phone, String templateId, LinkedHashMap messages) { - validatePhone(phone); - validateMessages(templateId, messages); - } - - @Override - public void massTextingPreProcess(List phones, String message) { - validateMessage(message); - validatePhones(phones); - } - - @Override - public void massTextingByTemplatePreProcess(List phones, String templateId, LinkedHashMap messages) { - validatePhones(phones); - validateMessages(templateId, messages); - } - - public void validateMessage(Object messageObj) { - if (messageObj instanceof String){ - String message = (String) messageObj; - if (null == message || "".equals(message)) { - throw new SmsBlendException("cant send a null message!"); - } - } - if (messageObj instanceof Map){ - Map message = (Map) messageObj; - if (message.size()<1) { - throw new SmsBlendException("cant send a null message!"); - } - } - } - - public void validatePhone(String phone) { - if (null == phone || "".equals(phone)) { - throw new SmsBlendException("cant send message to null!"); - } - } - - public void validatePhones(List phones) { - if (null == phones) { - throw new SmsBlendException("cant send message to null!"); - } - for (String phone : phones) { - if (null != phone && !"".equals(phone)) { - return; - } - } - throw new SmsBlendException("cant send message to null!"); - } - - public void validateMessages(String templateId, LinkedHashMap messages) { - if (null != templateId && !"".equals(templateId) && (messages == null || messages.size() < 1)) { - throw new SmsBlendException("cant use template without template param"); - } - } -} diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/RestrictedProcessor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/RestrictedProcessor.java deleted file mode 100644 index 5f0b752f..00000000 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/RestrictedProcessor.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.dromara.sms4j.core.proxy.processor; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.dromara.sms4j.api.dao.SmsDao; -import org.dromara.sms4j.api.proxy.CoreMethodProcessor; -import org.dromara.sms4j.api.proxy.aware.SmsDaoAware; -import org.dromara.sms4j.comm.exception.SmsBlendException; -import org.dromara.sms4j.comm.utils.SmsUtils; -import org.dromara.sms4j.provider.config.SmsConfig; -import org.dromara.sms4j.provider.factory.BeanFactory; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Objects; - - -/** - * 短信发送账号级上限前置拦截执行器 - * - * @author sh1yu - * @since 2023/10/27 13:03 - */ -@Slf4j -public class RestrictedProcessor implements CoreMethodProcessor, SmsDaoAware { - static Long minTimer = 60 * 1000L; - static Long accTimer = 24 * 60 * 60 * 1000L; - private static final String REDIS_KEY = "sms:restricted:"; - - /** - * 缓存实例 - */ - @Setter - private SmsDao smsDao; - - @Override - public int getOrder() { - return 3; - } - - @Override - public void sendMessagePreProcess(String phone, Object message) { - doRestricted(Collections.singletonList(phone)); - } - - @Override - public void sendMessageByTemplatePreProcess(String phone, String templateId, LinkedHashMap messages) { - doRestricted(Collections.singletonList(phone)); - } - - @Override - public void massTextingPreProcess(List phones, String message) { - doRestricted(phones); - } - - @Override - public void massTextingByTemplatePreProcess(List phones, String templateId, LinkedHashMap messages) { - doRestricted(phones); - } - - public void doRestricted(List phones) { - if (Objects.isNull(smsDao)) { - throw new SmsBlendException("The dao tool could not be found"); - } - SmsConfig config = BeanFactory.getSmsConfig(); - Integer accountMax = config.getAccountMax(); // 每日最大发送量 - Integer minuteMax = config.getMinuteMax(); // 每分钟最大发送量 - for (String phone : phones) { - if (SmsUtils.isNotEmpty(accountMax)) { // 是否配置了每日限制 - Integer i = (Integer) smsDao.get(REDIS_KEY + phone + "max"); - if (SmsUtils.isEmpty(i)) { - smsDao.set(REDIS_KEY + phone + "max", 1, accTimer / 1000); - } else if (i >= accountMax) { - log.info("The phone:" + phone + ",number of short messages reached the maximum today"); - throw new SmsBlendException("The phone:" + phone + ",number of short messages reached the maximum today"); - } else { - smsDao.set(REDIS_KEY + phone + "max", i + 1, accTimer / 1000); - } - } - if (SmsUtils.isNotEmpty(minuteMax)) { // 是否配置了每分钟最大限制 - Integer o = (Integer) smsDao.get(REDIS_KEY + phone); - if (SmsUtils.isNotEmpty(o)) { - if (o < minuteMax) { - smsDao.set(REDIS_KEY + phone, o + 1, minTimer / 1000); - } else { - log.info("The phone:" + phone + ",number of short messages reached the maximum today"); - throw new SmsBlendException("The phone:", phone + " Text messages are sent too often!"); - } - } else { - smsDao.set(REDIS_KEY + phone, 1, minTimer / 1000); - } - } - } - } -} diff --git a/sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java b/sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java index f6720cb7..a06e6b9d 100644 --- a/sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java +++ b/sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java @@ -19,9 +19,9 @@ import org.dromara.sms4j.cloopen.config.CloopenFactory; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.utils.SmsUtils; -import org.dromara.sms4j.core.proxy.EnvirmentHolder; +import org.dromara.sms4j.core.proxy.EnvironmentHolder; import org.dromara.sms4j.core.factory.SmsFactory; -import org.dromara.sms4j.core.proxy.processor.*; +import org.dromara.sms4j.core.proxy.interceptor.*; import org.dromara.sms4j.core.proxy.SmsProxyFactory; import org.dromara.sms4j.ctyun.config.CtyunFactory; import org.dromara.sms4j.emay.config.EmayFactory; @@ -154,14 +154,14 @@ public class SEInitializer { Map> blends = smsConfig.getBlends(); //持有初始化配置信息 - EnvirmentHolder.frozenEnvirmet(smsConfig, blends); + EnvironmentHolder.frozen(smsConfig, blends); //注册执行器实现 - SmsProxyFactory.addProcessor(new RestrictedProcessor()); - SmsProxyFactory.addProcessor(new BlackListProcessor()); - SmsProxyFactory.addProcessor(new BlackListRecordingProcessor()); - SmsProxyFactory.addProcessor(new SingleBlendRestrictedProcessor()); - SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor()); + SmsProxyFactory.addProcessor(new RestrictedMethodInterceptor()); + SmsProxyFactory.addProcessor(new BlackListMethodInterceptor()); + SmsProxyFactory.addProcessor(new BlackListRecordingMethodInterceptor()); + SmsProxyFactory.addProcessor(new SingleBlendRestrictedMethodInterceptor()); + SmsProxyFactory.addProcessor(new SyncMethodParamValidateMethodInterceptor()); blends.forEach((configId, configMap) -> { Object supplierObj = configMap.get(Constant.SUPPLIER_KEY); String supplier = supplierObj == null ? "" : String.valueOf(supplierObj); diff --git a/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/config/SmsBlendsInitializer.java b/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/config/SmsBlendsInitializer.java index 667a916a..71f068fe 100644 --- a/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/config/SmsBlendsInitializer.java +++ b/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/config/SmsBlendsInitializer.java @@ -10,9 +10,9 @@ import org.dromara.sms4j.api.universal.SupplierConfig; import org.dromara.sms4j.cloopen.config.CloopenFactory; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.utils.SmsUtils; -import org.dromara.sms4j.core.proxy.EnvirmentHolder; +import org.dromara.sms4j.core.proxy.EnvironmentHolder; import org.dromara.sms4j.core.factory.SmsFactory; -import org.dromara.sms4j.core.proxy.processor.*; +import org.dromara.sms4j.core.proxy.interceptor.*; import org.dromara.sms4j.core.proxy.SmsProxyFactory; import org.dromara.sms4j.ctyun.config.CtyunFactory; import org.dromara.sms4j.emay.config.EmayFactory; @@ -60,15 +60,15 @@ public class SmsBlendsInitializer { // 注册短信对象工厂 ProviderFactoryHolder.registerFactory(factoryList); //持有初始化配置信息 - EnvirmentHolder.frozenEnvirmet(smsConfig, blends); + EnvironmentHolder.frozen(smsConfig, blends); //框架依赖持有缓存扩展 new SolonSmsDaoHolder(context); //注册执行器实现 - SmsProxyFactory.addProcessor(new RestrictedProcessor()); - SmsProxyFactory.addProcessor(new BlackListProcessor()); - SmsProxyFactory.addProcessor(new BlackListRecordingProcessor()); - SmsProxyFactory.addProcessor(new SingleBlendRestrictedProcessor()); - SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor()); + SmsProxyFactory.addProcessor(new RestrictedMethodInterceptor()); + SmsProxyFactory.addProcessor(new BlackListMethodInterceptor()); + SmsProxyFactory.addProcessor(new BlackListRecordingMethodInterceptor()); + SmsProxyFactory.addProcessor(new SingleBlendRestrictedMethodInterceptor()); + SmsProxyFactory.addProcessor(new SyncMethodParamValidateMethodInterceptor()); // 解析供应商配置 for(String configId : blends.keySet()) { Map configMap = blends.get(configId); diff --git a/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsProcessorTest.java b/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsMethodInterceptorTest.java similarity index 57% rename from sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsProcessorTest.java rename to sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsMethodInterceptorTest.java index 05152de4..829b7690 100644 --- a/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsProcessorTest.java +++ b/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsMethodInterceptorTest.java @@ -8,6 +8,7 @@ import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.utils.SmsUtils; import org.dromara.sms4j.core.factory.SmsFactory; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -20,7 +21,7 @@ import java.util.LinkedHashMap; */ @Slf4j @SpringBootTest -public class SmsProcessorTest { +public class SmsMethodInterceptorTest { /** * 填测试手机号 */ @@ -31,7 +32,7 @@ public class SmsProcessorTest { @Test public void test1() { System.out.println("------------"); - SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.UNISMS); + SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.LOCAL); SmsResponse smsResponse = smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6)); Assert.isTrue(smsResponse.isSuccess()); System.out.println(smsResponse.getData()); @@ -44,7 +45,7 @@ public class SmsProcessorTest { public void test2() { System.out.println("------------"); - SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.HUAWEI); + SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.LOCAL); SmsResponse smsResponse = smsBlend.sendMessage(PHONE1, SmsUtils.getRandomInt(6)); Assert.isTrue(smsResponse.isSuccess()); System.out.println(smsResponse.getData()); @@ -56,62 +57,28 @@ public class SmsProcessorTest { public void test3() { System.out.println("------------"); - SmsBlendException knowEx = null; - try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, new LinkedHashMap<>()); - } catch (SmsBlendException e) { - knowEx = e; - System.out.println(knowEx.getMessage()); - } - Assert.notNull(knowEx); - knowEx = null; - try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage("", SmsUtils.getRandomInt(6)); - } catch (SmsBlendException e) { - knowEx = e; - System.out.println(knowEx.getMessage()); - } - Assert.notNull(knowEx); - knowEx = null; - try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, ""); - } catch (SmsBlendException e) { - knowEx = e; - System.out.println(knowEx.getMessage()); - } - Assert.notNull(knowEx); - knowEx = null; - try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, "111", new LinkedHashMap<>()); - } catch (SmsBlendException e) { - knowEx = e; - System.out.println(knowEx.getMessage()); - } - Assert.notNull(knowEx); - knowEx = null; - try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(Collections.singletonList(PHONE), ""); - } catch (SmsBlendException e) { - knowEx = e; - System.out.println(knowEx.getMessage()); - } - Assert.notNull(knowEx); - knowEx = null; - try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(Collections.singletonList(PHONE), "2222", new LinkedHashMap<>()); - } catch (SmsBlendException e) { - knowEx = e; - System.out.println(knowEx.getMessage()); - } - Assert.notNull(knowEx); - knowEx = null; - try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(new ArrayList(), "321321"); - } catch (SmsBlendException e) { - knowEx = e; - System.out.println(knowEx.getMessage()); - } - Assert.notNull(knowEx); + SmsBlend sb = SmsFactory.getBySupplier(SupplierConstant.LOCAL); + Assertions.assertThrows(SmsBlendException.class, + () -> sb.sendMessage(PHONE, new LinkedHashMap<>()) + ); + Assertions.assertThrows(SmsBlendException.class, + () -> sb.sendMessage("", SmsUtils.getRandomInt(6)) + ); + Assertions.assertThrows(SmsBlendException.class, + () -> sb.sendMessage(PHONE, "") + ); + Assertions.assertThrows(SmsBlendException.class, + () -> sb.sendMessage(PHONE, "111", new LinkedHashMap<>()) + ); + Assertions.assertThrows(SmsBlendException.class, + () -> sb.massTexting(Collections.singletonList(PHONE), "") + ); + Assertions.assertThrows(SmsBlendException.class, + () -> sb.massTexting(Collections.singletonList(PHONE), "2222", new LinkedHashMap<>()) + ); + Assertions.assertThrows(SmsBlendException.class, + () -> sb.massTexting(new ArrayList<>(), "321321") + ); } //黑名单测试 @@ -119,7 +86,7 @@ public class SmsProcessorTest { public void test4() { System.out.println("------------"); - SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.UNISMS); + SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.LOCAL); //单黑名单添加 smsBlend.joinInBlacklist(PHONE); SmsBlendException knowEx = null; @@ -155,17 +122,10 @@ public class SmsProcessorTest { @Test public void test5() { System.out.println("------------"); - - SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + SmsBlend sb = SmsFactory.getBySupplier(SupplierConstant.LOCAL); + SmsResponse smsResponse = sb.sendMessage(PHONE, SmsUtils.getRandomInt(6)); Assert.isTrue(smsResponse.isSuccess()); - SmsBlendException knowEx = null; - try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, SmsUtils.getRandomInt(6)); - } catch (SmsBlendException e) { - knowEx = e; - System.out.println(knowEx.getMessage()); - } - Assert.notNull(knowEx); + Assertions.assertThrows(SmsBlendException.class, () -> sb.sendMessage(PHONE, SmsUtils.getRandomInt(6))); } //渠道级上限测试、需成功发送6笔,再发就会报错 参数配置 6 @@ -173,12 +133,13 @@ public class SmsProcessorTest { public void test6() { System.out.println("------------"); - SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE1, SmsUtils.getRandomInt(6)); + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.LOCAL) + .sendMessage(PHONE1, SmsUtils.getRandomInt(6)); Assert.isTrue(smsResponse.isSuccess()); SmsBlendException knowEx = null; try { - SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE1, SmsUtils.getRandomInt(6)); + SmsFactory.getBySupplier(SupplierConstant.LOCAL).sendMessage(PHONE1, SmsUtils.getRandomInt(6)); } catch (SmsBlendException e) { knowEx = e; System.out.println(knowEx.getMessage()); diff --git a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/config/SmsBlendsInitializer.java b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/config/SmsBlendsInitializer.java index 460a773d..a2397386 100644 --- a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/config/SmsBlendsInitializer.java +++ b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/config/SmsBlendsInitializer.java @@ -11,9 +11,9 @@ import org.dromara.sms4j.cloopen.config.CloopenFactory; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.enumerate.ConfigType; import org.dromara.sms4j.comm.utils.SmsUtils; -import org.dromara.sms4j.core.proxy.EnvirmentHolder; +import org.dromara.sms4j.core.proxy.EnvironmentHolder; import org.dromara.sms4j.core.factory.SmsFactory; -import org.dromara.sms4j.core.proxy.processor.*; +import org.dromara.sms4j.core.proxy.interceptor.*; import org.dromara.sms4j.core.proxy.SmsProxyFactory; import org.dromara.sms4j.ctyun.config.CtyunFactory; import org.dromara.sms4j.emay.config.EmayFactory; @@ -58,13 +58,13 @@ public class SmsBlendsInitializer { if(ConfigType.YAML.equals(this.smsConfig.getConfigType())) { //持有初始化配置信息 - EnvirmentHolder.frozenEnvirmet(smsConfig, blends); + EnvironmentHolder.frozen(smsConfig, blends); //注册执行器实现 - SmsProxyFactory.addProcessor(new RestrictedProcessor()); - SmsProxyFactory.addProcessor(new BlackListProcessor()); - SmsProxyFactory.addProcessor(new BlackListRecordingProcessor()); - SmsProxyFactory.addProcessor(new SingleBlendRestrictedProcessor()); - SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor()); + SmsProxyFactory.addProcessor(new RestrictedMethodInterceptor()); + SmsProxyFactory.addProcessor(new BlackListMethodInterceptor()); + SmsProxyFactory.addProcessor(new BlackListRecordingMethodInterceptor()); + SmsProxyFactory.addProcessor(new SingleBlendRestrictedMethodInterceptor()); + SmsProxyFactory.addProcessor(new SyncMethodParamValidateMethodInterceptor()); // 解析供应商配置 blends.forEach((configId, configMap) -> { Object supplierObj = configMap.get(Constant.SUPPLIER_KEY);