策略模式短信模板业务场景
业务代码
本文主要是介绍策略模式,重点就只在于短信发送时拒绝策略逻辑的处理,不相关的代码就不介绍了。
主要的接口有两个 SmsTemplatePlaceHolderHandler 短信模板占位符处理器接口,SmsSendRejectStrategy短信发送拒绝策略接口,SmsTemplatePlaceHolderHandler有一个默认的实现类DefaultSmsTemplatePlaceHolderHandler,其关联了一个SmsSendRejectStrategy实例,在发送短信时,具体的短信发送拒绝策略实现类将进行具体的发送拒绝逻辑的处理,如果允许发送,则由DefaultSmsTemplatePlaceHolderHandler将替换了占位符的短信模板内容发出。
其中,DefaultSmsTemplatePlaceHolderHandler与SmsSendRejectStrategy的关系就是一个具体的策略模式的体现,DefaultSmsTemplatePlaceHolderHandler无需关注拒绝发送的处理逻辑,调用SmsSendRejectStrategy实现类的实例进行处理即可。DefaultSmsTemplatePlaceHolderHandlerpackage com.cube.share.sms.handler; import com.cube.share.base.utils.JacksonUtils; import com.cube.share.base.utils.PlaceHolderUtils; import com.cube.share.sms.constant.SmsConstant; import com.cube.share.sms.model.param.SmsPlaceHolderParameter; import com.cube.share.sms.strategy.SmsSendRejectStrategy; import com.cube.share.sms.strategy.SmsTemplateContext; /** * @author cube.li * @date 2021/9/4 12:27 * @description 默认的短信模板占位符处理器 */ public class DefaultSmsTemplatePlaceHolderHandler implements SmsTemplatePlaceHolderHandler { private SmsSendRejectStrategy rejectStrategy; public DefaultSmsTemplatePlaceHolderHandler(SmsSendRejectStrategy rejectStrategy) { this.rejectStrategy = rejectStrategy; } @Override public String handle(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter) { //发送拒绝策略 rejectStrategy.reject(templateContext, parameter); return PlaceHolderUtils.replacePlaceHolder(templateContext.getTemplateContent(), JacksonUtils.toMap(parameter), SmsConstant.DEFAULT_PLACE_HOLDER_REGEX, SmsConstant.DEFAULT_PLACE_HOLDER_KEY_REGEX); } }SmsSendRejectStrategypackage com.cube.share.sms.strategy; import com.cube.share.base.utils.JacksonUtils; import com.cube.share.sms.model.param.SmsPlaceHolderParameter; import org.springframework.lang.NonNull; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author cube.li * @date 2021/9/4 9:49 * @description 短信发送的拒绝策略 */ public interface SmsSendRejectStrategy { /** * 判断是否拒绝发送短信 * * @param templateContext 短信模板上下文 * @param parameter 填充占位符的参数 */ void reject(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter); /** * 获取短信发送占位符替换参数Set(不包含value为null) * * @param parameter 填充占位符的参数 * @return Set */ @NonNull default Set getParameterSet(SmsPlaceHolderParameter parameter) { Map parameterMap = getParameterMap(parameter); return parameterMap.keySet(); } /** * 获取短信发送占位符替换参数Map(不包含value为null) * * @param parameter 填充占位符的参数 * @return Map */ @NonNull default Map getParameterMap(SmsPlaceHolderParameter parameter) { Map parameterMap = JacksonUtils.toMap(parameter); Map filteredParameterMap = new HashMap<>(4); if (parameterMap != null) { Set> entrySet = parameterMap.entrySet(); entrySet.forEach(stringObjectEntry -> { if (stringObjectEntry.getValue() != null) { filteredParameterMap.put(stringObjectEntry.getKey(), stringObjectEntry.getValue()); } }); } return filteredParameterMap; } }
三种拒绝策略的实现类package com.cube.share.sms.strategy; import com.cube.share.sms.model.param.SmsPlaceHolderParameter; /** * @author cube.li * @date 2021/9/4 11:54 * @description 短信发送拒绝策略-忽略策略,无论短信发送入参与模板是否匹配,都允许发送 */ public class SmsSendIgnoreStrategy implements SmsSendRejectStrategy { @Override public void reject(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter) { //do nothing } }package com.cube.share.sms.strategy; import com.cube.share.base.templates.CustomException; import com.cube.share.sms.model.param.SmsPlaceHolderParameter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import java.util.Set; /** * @author cube.li * @date 2021/9/4 11:45 * @description SmsSendAnyMatchStrategy, 只要占位符参数匹配了短信模板中的任意一个占位符key,就允许发送 */ @Slf4j public class SmsSendAnyMatchStrategy implements SmsSendRejectStrategy { @Override public void reject(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter) { Set parameterKeySet = getParameterSet(parameter); if (CollectionUtils.intersection(templateContext.getPlaceHolderKeySet(), parameterKeySet).size() <= 0) { log.error("短信占位符替换参数与短信模板完全不匹配,templateContent = {},parameter = {}", templateContext.getTemplateContent(), parameter); throw new CustomException("短信占位符替换参数与短信模板完全不匹配"); } } }package com.cube.share.sms.strategy; import com.cube.share.base.templates.CustomException; import com.cube.share.sms.model.param.SmsPlaceHolderParameter; import lombok.extern.slf4j.Slf4j; import java.util.Set; /** * @author cube.li * @date 2021/9/4 11:57 * @description 短信发送拒绝策略-完全匹配,只有当短信入参与短信模板占位符完全匹配时才允许发送 */ @Slf4j public class SmsSendTotallyMatchStrategy implements SmsSendRejectStrategy { @Override public void reject(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter) { Set parameterKeySet = getParameterSet(parameter); if (!parameterKeySet.containsAll(templateContext.getPlaceHolderKeySet())) { log.error("短信占位符替换参数与短信模板不完全匹配,templateContent = {},parameter = {}", templateContext.getTemplateContent(), parameter); throw new CustomException("短信占位符替换参数与短信模板不完全匹配"); } } }拒绝策略实例的创建工厂package com.cube.share.sms.factory; import com.cube.share.sms.constant.SmsSendRejectStrategyEnum; import com.cube.share.sms.strategy.SmsSendAnyMatchStrategy; import com.cube.share.sms.strategy.SmsSendIgnoreStrategy; import com.cube.share.sms.strategy.SmsSendRejectStrategy; import com.cube.share.sms.strategy.SmsSendTotallyMatchStrategy; /** * @author cube.li * @date 2021/9/4 12:49 * @description 拒绝策略工厂 */ public class SmsSendRejectStrategyFactory { private static final SmsSendIgnoreStrategy IGNORE_STRATEGY = new SmsSendIgnoreStrategy(); private static final SmsSendAnyMatchStrategy ANY_MATCH_STRATEGY = new SmsSendAnyMatchStrategy(); private static final SmsSendTotallyMatchStrategy TOTALLY_MATCH_STRATEGY = new SmsSendTotallyMatchStrategy(); public static SmsSendRejectStrategy getStrategy(SmsSendRejectStrategyEnum strategyEnum) { switch (strategyEnum) { case IGNORE: return IGNORE_STRATEGY; case ANY_MATCH: return ANY_MATCH_STRATEGY; case TOTALLY_MATCH: return TOTALLY_MATCH_STRATEGY; default: throw new IllegalArgumentException("Illegal StrategyEnum Param"); } } }短信发送服务package com.cube.share.sms.service; import com.cube.share.base.templates.CustomException; import com.cube.share.sms.config.SmsConfig; import com.cube.share.sms.constant.SmsSendRejectStrategyEnum; import com.cube.share.sms.factory.SmsSendRejectStrategyFactory; import com.cube.share.sms.handler.DefaultSmsTemplatePlaceHolderHandler; import com.cube.share.sms.handler.SmsTemplatePlaceHolderHandler; import com.cube.share.sms.model.param.SmsSendParam; import com.cube.share.sms.strategy.SmsTemplateContext; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * @author cube.li * @date 2021/9/4 9:03 * @description 短信服务 */ @Service @Slf4j public class SmsService { @Resource private SmsConfig smsConfig; private SmsTemplatePlaceHolderHandler placeHolderHandler = new DefaultSmsTemplatePlaceHolderHandler(SmsSendRejectStrategyFactory.getStrategy(SmsSendRejectStrategyEnum.ANY_MATCH)); public void send(SmsSendParam param) { String templateContent = smsConfig.getTemplates().get(param.getTemplateCode()); if (templateContent == null) { throw new CustomException("不正确的短信模板"); } SmsTemplateContext templateContext = SmsTemplateContext.from(templateContent, param.getTemplateCode()); String sendContent = placeHolderHandler.handle(templateContext, param.getParameter()); log.info("短信发送: {}", sendContent); } }测试
短信模板在配置文件中#短信 sms: #模板 templates: 1: "尊敬的用户您好,{companyName}定于{address}开展主题为{title}的营销活动,活动时间{startTime}-{endTime},欢迎您的光临!" 2: "尊敬的用户您好,{address}开展主题为{title}的营销活动将于明天开始,欢迎您的光临!"
单元测试类package com.cube.share.sms.service; import com.cube.share.sms.model.param.SmsPlaceHolderParameter; import com.cube.share.sms.model.param.SmsSendParam; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; /** * @author cube.li * @date 2021/9/4 12:00 * @description */ @SpringBootTest class SmsServiceTest { @Resource SmsService smsService; @Test void send() { SmsSendParam smsSendParam = new SmsSendParam(); smsSendParam.setTemplateCode(1); SmsPlaceHolderParameter placeHolderParameter = new SmsPlaceHolderParameter(); placeHolderParameter.setAddress("上海"); smsSendParam.setParameter(placeHolderParameter); smsService.send(smsSendParam); } }
更改拒绝策略,发送短信时日志如下:SmsSendAnyMatchStrategy2021-09-04 14:34:36.261 INFO 5528 --- [ main] com.cube.share.sms.service.SmsService : 短信发送: 尊敬的用户您好,{companyName}定于上海开展主题为{title}的营销活动,活动时间{startTime}-{endTime},欢迎您的光临!
可以看出,当拒绝策略为SmsSendAnyMatchStrategy时,只要占位符入参与短信模板中的占位符有一个匹配,就能够发送成功SmsSendTotallyMatchStrategy
占位符参数与模板占位符不完全匹配时发送失败
2021-09-04 14:38:16.133 ERROR 3896 --- [ main] c.c.s.s.s.SmsSendTotallyMatchStrategy : 短信占位符替换参数与短信模板不完全匹配,templateContent = 尊敬的用户您好,{companyName}定于{address}开展主题为{title}的营销活动,活动时间{startTime}-{endTime},欢迎您的光临!,parameter = SmsPlaceHolderParameter(companyName=null, title=null, startTime=null, endTime=null, address=上海, url=null) com.cube.share.base.templates.CustomException: 短信占位符替换参数与短信模板不完全匹配 at com.cube.share.sms.strategy.SmsSendTotallyMatchStrategy.reject(SmsSendTotallyMatchStrategy.java:22)
占位符参数与模板占位符完全匹配时发送成功
完全匹配.png
代码示例:https://gitee.com/li-cube/share/tree/master/sms总结
业务逻辑说到底就是if-else,使用设计模式能够使代码更易维护、更易拓展,并且代码的阅读性更强;虽然不使用设计模式照样能够实现业务,不过就是多套几层if-else而已,但是人活着总归要有点追求,只有做到不止于业务、不止于代码,才能成为一个脱离低级CRUD的程序员。