前言
刚果商城,用户登录 Or 注册 发送邮箱验证码场景,使用
抽象策略模式
实现
什么是抽象策略模式
抽象策略模式
是一种行为型设计模式,它允许定义一系列算法,将每个算法封装起来,并使它们可以互相替换。这使得客户端代码可以独立于具体的算法实现而变化。
该模式主要包含三个角色。
三个角色
- 策略接口(Strategy Interface): 定义了一组算法的接口,具体的策略类实现这个接口,以便可以在上下文中互相替换。
- 具体策略类(Concrete Strategies): 实现了策略接口的具体算法。
- 上下文(Context): 包含一个对策略接口的引用,可以在运行时切换不同的策略。上下文通常包含一个方法,该方法使用策略接口调用具体的算法。
类图
首先
策略执行抽象接口(策略接口)
/*** 策略执行抽象*/
public interface AbstractExecuteStrategy<REQUEST, RESPONSE> {/*** 执行策略标识*/String mark();/*** 执行策略** @param requestParam 执行策略入参*/default void execute(REQUEST requestParam) {}/*** 执行策略,带返回值** @param requestParam 执行策略入参* @return 执行策略后返回值*/default RESPONSE executeResp(REQUEST requestParam) {return null;}
}
策略选择器(上下文)
/*** 策略选择器*/
public class AbstractStrategyChoose implements ApplicationListener<ApplicationInitializingEvent> {/*** 执行策略集合*/private final Map<String, AbstractExecuteStrategy> abstractExecuteStrategyMap = new HashMap<>();/*** 根据 mark 查询具体策略** @param mark 策略标识* @return 实际执行策略*/public AbstractExecuteStrategy choose(String mark) {return Optional.ofNullable(abstractExecuteStrategyMap.get(mark)).orElseThrow(() -> new ServiceException(String.format("[%s] 策略未定义", mark)));}/*** 根据 mark 查询具体策略并执行** @param mark 策略标识* @param requestParam 执行策略入参* @param <REQUEST> 执行策略入参范型*/public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) {AbstractExecuteStrategy executeStrategy = choose(mark);executeStrategy.execute(requestParam);}/*** 根据 mark 查询具体策略并执行,带返回结果** @param mark 策略标识* @param requestParam 执行策略入参* @param <REQUEST> 执行策略入参范型* @param <RESPONSE> 执行策略出参范型* @return*/public <REQUEST, RESPONSE> RESPONSE chooseAndExecuteResp(String mark, REQUEST requestParam) {AbstractExecuteStrategy executeStrategy = choose(mark);return (RESPONSE) executeStrategy.executeResp(requestParam);}// 项目初始化时,会执行该方法,将所有策略对象都置于map集合中【key是mark标识(子类自行实现)】@Overridepublic void onApplicationEvent(ApplicationInitializingEvent event) {Map<String, AbstractExecuteStrategy> actual = ApplicationContextHolder.getBeansOfType(AbstractExecuteStrategy.class);actual.forEach((beanName, bean) -> {AbstractExecuteStrategy beanExist = abstractExecuteStrategyMap.get(bean.mark());if (beanExist != null) {throw new ServiceException(String.format("[%s] Duplicate execution policy", bean.mark()));}abstractExecuteStrategyMap.put(bean.mark(), bean);});}
}
登录/注册发送邮箱策略(具体策略类)
抽象公共邮箱验证码发送(公共逻辑)
将公共发邮箱的代码抽象为一个类,便于复用
public abstract class AbstractMailVerifySender {@Value("${customer.user.register.verify.sender}")private String sender;@Value("${customer.user.register.verify.template-id}")private String templateId;@Resourceprivate MessageSendRemoteService messageSendRemoteService;@Resourceprivate DistributedCache distributedCache;/*** 用户注册验证码超时时间*/private static final long REGISTER_USER_VERIFY_CODE_TIMEOUT = 300000;/*** 获取缓存前缀 Key*/protected abstract String getCachePrefixKey();/*** 邮箱验证发送*/public void mailVerifySend(UserVerifyCodeCommand requestParam) {String verifyCode = RandomUtil.randomNumbers(6);// 模板方法模式: 验证码放入缓存,并设置超时时间distributedCache.put(CacheUtil.buildKey(getCachePrefixKey(), requestParam.getReceiver()), verifyCode, REGISTER_USER_VERIFY_CODE_TIMEOUT);MailSendRemoteCommand remoteCommand = new MailSendRemoteCommand();remoteCommand.setTitle("刚果商城邮箱验证码提醒").setReceiver(requestParam.getReceiver()).setSender(sender).setTemplateId(templateId).setParamList(Lists.newArrayList(verifyCode));messageSendRemoteService.mailMessageSend(remoteCommand);}
}
登录注册策略分别继承 AbstractMailVerifySender
抽象类和实现 AbstractExecuteStrategy
抽象策略接口
登录策略
@Component
public class MailLoginVerifyCommandHandler extends AbstractMailVerifySender implements AbstractExecuteStrategy<UserVerifyCodeCommand, Void> {@Overridepublic String mark() {return "customer_user_login_verify_mail";}// 直接调用父抽象类中方法即可 【核心代码】@Overridepublic void execute(UserVerifyCodeCommand requestParam) {mailVerifySend(requestParam);}@Overrideprotected String getCachePrefixKey() {return CacheConstant.LOGIN_USER_VERIFY_CODE;}
}
注册策略
@Component
@RequiredArgsConstructor
public class MailRegisterVerifyCommandHandler extends AbstractMailVerifySender implements AbstractExecuteStrategy<UserVerifyCodeCommand, Void> {@Overridepublic String mark() {return "customer_user_register_mail";}@Overridepublic void execute(UserVerifyCodeCommand requestParam) {mailVerifySend(requestParam);}@Overrideprotected String getCachePrefixKey() {return CacheConstant.REGISTER_USER_VERIFY_CODE;}
}
登录注册逻辑仅需各自定义mark标识以及对应存储redis验证码的key前缀即可,代码简洁清爽、十分优雅。
接口调用
入参:
@Data
@ApiModel("用户验证码")
public class UserVerifyCodeCommand {@ApiModelProperty(value = "验证类型", notes = "登录验证码,注册认证验证码等", example = "customer_user_login_verify")private String type;@ApiModelProperty(value = "验证平台", notes = "手机短信,邮箱,电话等", example = "mail")private String platform;@NotBlank(message = "接收者不能为空")@ApiModelProperty(value = "接收者", example = "m7798432@163.com", notes = "实际发送时更改为自己邮箱")private String receiver;
}
type + platform 拼接为对应策略mark(与调用策略mark对应上),根据策略上下文获取对应策略执行逻辑即可。
策略选择执行
@Overridepublic void verifyCodeSend(UserVerifyCodeCommand requestParam) {String mark = requestParam.getType() + "_" + requestParam.getPlatform();// 策略模式: 根据 mark 选择用户登录或者注册逻辑abstractStrategyChoose.chooseAndExecute(mark, requestParam);}public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) {AbstractExecuteStrategy executeStrategy = choose(mark);// 执行策略核心代码executeStrategy.execute(requestParam);}