前言
刚果商城,用户登录 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标识(子类自行实现)】
@Override
public 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;
@Resource
private MessageSendRemoteService messageSendRemoteService;
@Resource
private 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> {
@Override
public String mark() {
return "customer_user_login_verify_mail";
}
// 直接调用父抽象类中方法即可 【核心代码】
@Override
public void execute(UserVerifyCodeCommand requestParam) {
mailVerifySend(requestParam);
}
@Override
protected String getCachePrefixKey() {
return CacheConstant.LOGIN_USER_VERIFY_CODE;
}
}
注册策略
@Component
@RequiredArgsConstructor
public class MailRegisterVerifyCommandHandler extends AbstractMailVerifySender implements AbstractExecuteStrategy<UserVerifyCodeCommand, Void> {
@Override
public String mark() {
return "customer_user_register_mail";
}
@Override
public void execute(UserVerifyCodeCommand requestParam) {
mailVerifySend(requestParam);
}
@Override
protected 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对应上),根据策略上下文获取对应策略执行逻辑即可。
策略选择执行
@Override
public 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);
}