利用策略模式实现不同平台的短信发送

465 阅读3分钟

策略模式

策略模式是一种行为型设计模式,它允许在运行时选择算法的行为,从而实现更灵活、可扩展和可维护的代码。通常结合依赖注入放入到上下文中,使其更灵活的进行动态切换与扩展。 优点:

  • 可以在运行时动态切换算法,从而实现更灵活的代码。
  • 将算法封装到单独的类中,使得算法可以被更容易地复用、扩展和维护。
  • 通过将算法注入到上下文中,可以实现更松散的耦合,从而提高代码的可测试性和可维护性。

SmsServiceStrategyContext(策略服务上下文)

@Component
public class SmsServiceStrategyContext {

    private static ApplicationContext context;


    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        // 获取Spring容器的上下文,帮助我们寻找具体策略
        SmsServiceStrategyContext.context = applicationContext;
    }

    public static SmsHandler selectOne(Model model) {
        return (SmsHandler) context.getBean(model.getClazz());
    }
    public static SmsHandler selectRandom() throws Exception {
        Model[] models = Model.values();
        int randomIndex = (int) (Math.random() * 10) % models.length;
        return selectOne(models[randomIndex]);
    }

    public static void main(String[] args) {
        Model[] models = Model.values();
        for (int i = 0; i < 100; i++) {
            int randomIndex = (int) (Math.random() * 10) % models.length;
            System.out.println(randomIndex);
        }
    }

}

在这个里面就可以自定义一些策略选择模式,提供给外部调用,屏蔽具体实现。 当然,你还可以利用Spring容器实现自动注入,因为我们所有的短信服务都统一实现了一个接口。

Model(模式)

/**
 * 负责选择短信服务商
 * @author qianmo
 */
public enum Model {
    /** 阿里云短信服务 **/
    ALI(SmsAliHandlerImpl.class),
    /** 腾讯云短信服务 **/
    TX(SmsTxHandlerImpl.class),
    /** Mqtt短信服务 **/
    MQTT(SmsMqttHandlerImpl.class);

    final Class<?> clazz;

    Model(Class<?>  clazz){
        this.clazz = clazz;
    }

    public Class<?> getClazz() {
        return clazz;
    }
}

这里关联了具体策略执行对象与模式的关系。

SmsHandler(策略执行器)

/**
 * @Author qianmo
 * @Description 发送通知消息处理类
 */
public interface SmsHandler {
    /**
     * 生成指定长度验证码
     * @param len
     * @return
     */
    String buildCode(int len);

    /**
     * 发送短信信息
     * @param code 验证码
     * @param phone 手机号
     */
    void sendMessage(String code, String phone) throws Exception;

}

这里我们抽象出了一个消息策略执行器,如阿里的短信服务只需要实现这个接口并在Model里写好关系即可

SmsBaseHandlerImpl(短信服务基类)

/**
 * @Author qianmo
 * @Description 短信服务基础类
 */
@Slf4j
public abstract class SmsBaseHandlerImpl implements SmsHandler {
    @Override
    public String buildCode(int len) {
        len = Math.max(len, 4);
        String codeChars = "0123456789";
        StringBuilder code = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < len; i++) {
            int index = random.nextInt(codeChars.length());
            code.append(codeChars.charAt(index));
        }
        return code.toString();
    }


    /**
     * 重试策略
     * @param failModel 发送失败的服务商
     * @param phone 手机号
     * @param code 验证码
     */
    public void retry(Model failModel,String phone,String code){
        List<Model> models = Arrays.stream(Model.values()).filter(model -> {
           return failModel != model;
        }).collect(Collectors.toList());
        boolean isRetry = true;
        while (isRetry){
            if (models.isEmpty()){
                isRetry = false;
            }
            int randomIndex = new Random().nextInt(models.size());
            try {
                Model model = models.get(randomIndex);
                SmsServiceStrategyContext.selectOne(model).sendMessage(code,phone);
                isRetry = false;
            }catch (Exception e) {
                log.error(ExceptionUtils.getStack(e));
                if (models.isEmpty()){
                    isRetry = false;
                }else{
                    models.remove(randomIndex);
                }
            }
        }

    }


}

在这里我们实现了一个重试策略和生成验证码的具体方法,以后的实现类只需要继承这个抽象类就可以专注于发送短信的实现了。

SmsAliHandlerImpl(阿里云短信服务)

/**
 * @Author qianmo
 * @Description  阿里云短信接口服务
 */
@Component("aliSmsHandler")
@Slf4j
public class SmsAliHandlerImpl extends SmsBaseHandlerImpl {

    @Value("${sms.aliyun.accessKeyId}")
    private String accessKeyId;

    @Value("${sms.aliyun.accessKeySecret}")
    private String accessKeySecret;

    @Value("${sms.aliyun.signName}")
    private String signName;

    @Value("${sms.aliyun.templateCode}")
    private String templateCode;

    @Value("${sms.aliyun.templateParam}")
    private String templateParam;

    private Config config;

    @PostConstruct
    public void init() {
        this.config = new Config()
                .setAccessKeyId(accessKeyId)
                .setAccessKeySecret(accessKeySecret);
        config.endpoint = "dysmsapi.aliyuncs.com";
    }

    @Override
    public void sendMessage(String code, String phone) throws Exception {
        log.info("发送阿里云短信");
        log.info("phone is {},code is {}",phone,code);
        String param = buildParam(templateParam,code);
        log.info("param is {}",param);
        try {
            Client client = new Client(config);
            SendSmsRequest request = new SendSmsRequest()
                    .setPhoneNumbers(phone)
                    .setSignName(signName)
                    .setTemplateCode(templateCode)
                    .setTemplateParam(param);
            log.info("request is {}",JSONObject.toJSONString(request));
            SendSmsResponse sendSmsResponse = client.sendSms(request);
            log.info(JSONObject.toJSONString(sendSmsResponse));

            if (!"OK".equals(sendSmsResponse.getBody().getCode())) {
                log.error(JSONObject.toJSONString(sendSmsResponse.getBody()));
                throw new Exception("failed to send sms");
            }
        }catch (Exception e) {
            log.error(ExceptionUtils.getStack(e));
            retry(Model.ALI,code,phone);
        }
    }


    public String buildParam(String template,Object... params){
        template = template.replace("{}", "%s");
        return String.format(template,params);
    }

SmsMqttHandlerImpl(Mqtt短信服务发送)

/**
 * @Author qianmo
 * @Description Mqtt发送短信实现类
 */
@Component("mqttSmsHandler")
@Slf4j
public class SmsMqttHandlerImpl extends SmsBaseHandlerImpl{
    @Autowired
    private MqttHelper mqttHelper;
    @Override
    public void sendMessage(String code, String phone) throws Exception {
        MqttClientManager.getInstance().init(mqttHelper);
        MqttClientManager.getInstance().connect();
        Map<String,Object> data = new HashMap();
        data.put("phone", phone);
        data.put("code", code);
        String pub = JSON.toJSONString(data);
        // 发送
        MqttClientManager.getInstance().publish(MqttTopicConstants.TOPIC_PHONE_CODE,pub);
        log.info("publish  data:" + pub);
    }
}