Springboot整合阿里云腾讯云发送短信验证码 可随时切换短信运营商

本文描述了在springboot项目中整合实现对接阿里云 和 腾讯云的短信验证码发送,可通过更改配置文件达到切换短信发送运营商(申请签名、短信模版这些本文不在叙述)。

首先看下大体结构:

一、需要导入的jar
复制代码
<dependency>
    <groupId>com.tencentcloudapi</groupId>
    <artifactId>tencentcloud-sdk-java</artifactId>
    <version>4.0.11</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>dysmsapi20170525</artifactId>
    <version>2.0.24</version>
</dependency>
复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.18.3</version>
</dependency>
二、 短信枚举类
java 复制代码
/**
 * 短信平台枚举类
 */
public enum UnitySmsType {
    Qcloud,
    Aliyun,
    //其它运营商xxx;

    private UnitySmsType() {
    }
}
三、短信配置类
java 复制代码
package com.starlink.phonerent.modules.common.sms;

import java.util.Map;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 短信配置类
 */
@Data
@Component
@ConfigurationProperties("my_project.sms")
public class UnitySmsProperties {
    public static final String PREFIX = "jip.sms";
    private UnitySmsType unitySmsType;
    private String accessKeyId;
    private String accessKeySecret;
    private Map<String, Object> extend;
    private Map<String, SignTemplate> configs;
    private String region;
    private String smsSdkAppid;

    public UnitySmsProperties() {
        this.unitySmsType = UnitySmsType.Aliyun;
    }

    @Data
    public static class SignTemplate {
        private static final Integer DEFAULT_LENGTH = 6;
        private static final Integer DEFAULT_TIMEOUT = 60;
        private static final Integer DEFAULT_EXPIRE = 300;
        private String sign;
        private String templateCode;
        private Integer length;
        private Integer timeout;
        private Integer expire;
        private boolean enabledTest;

        public boolean getEnabledTest() {
            return this.enabledTest;
        }

        public SignTemplate() {
            this.length = DEFAULT_LENGTH;
            this.timeout = DEFAULT_LENGTH;
            this.expire = DEFAULT_EXPIRE;
            this.enabledTest = false;
        }
    }
}

yml配置

复制代码
my_project: #自定义配置前缀(配置类用得到)
  sms:
    smsType: Aliyun # 类型 枚举类中的类型
    accessKeyId: xxxxxx  #短信平台申请的accessKeyId
    accessKeySecret: xxxxx  #短信平台申请的accessKeySecret
    smsSdkAppid: xxxxx      #腾讯云需配置该参数
    configs:
      SMS_MOBILE_LOGIN:  #场景值(自定义,可设置多个)
        enabledTest: true  #是否测试(不会真实发短信,也不会效验)
        sign: 测试平台    #短信签名
        template-code: xxxxxxx  #模板编号
        timeout: 60   #xxx时间内不能重新发送
        expire: 300   #验证码有效时间  
        length: 4     #生成的验证码长度
      SMS_MOBILE_REG:  #场景值(自定义,可设置多个)
        enabledTest: true  #是否测试(不会真实发短信,也不会效验)
        sign: 测试平台    #短信签名
        template-code: xxxxxxx  #模板编号
        timeout: 60   #xxx时间内不能重新发送
        expire: 300   #验证码有效时间  
        length: 6     #生成的验证码长度
四、短信接口服务类
java 复制代码
import java.util.List;
import java.util.Map;

/**
 * 短信服务类
 */
public interface UnitySmsService {

    Object getSmsClient();

    UnitySmsProperties getSmsConfig();

    JsonResult sendSms(String var1, String var2, Map var3, String var4);

    JsonResult sendSms(String var1, String[] var2, List<Map> var3, String[] var4);

}
五、短信实现类

1.阿里云

java 复制代码
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendBatchSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendBatchSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;
import com.google.gson.Gson;
import java.util.List;
import java.util.Map;
import com.starlink.phonerent.common.JsonResult;
import com.starlink.phonerent.modules.common.sms.UnitySmsProperties;
import com.starlink.phonerent.modules.common.sms.UnitySmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 阿里云短信服务实现类
 */
@Slf4j
@Service
public class UnitySmsServiceAliyunImpl implements UnitySmsService {
    private Client client;
    private UnitySmsProperties unitySmsProperties;

    public UnitySmsServiceAliyunImpl(UnitySmsProperties unitySmsProperties) throws Exception {
        this.unitySmsProperties = unitySmsProperties;
        Config smsConfig = (new Config()).setAccessKeyId(unitySmsProperties.getAccessKeyId()).setAccessKeySecret(unitySmsProperties.getAccessKeySecret());
        smsConfig.endpoint = "dysmsapi.aliyuncs.com";
        this.client = new Client(smsConfig);
    }

    public Object getSmsClient() {
        return this.client;
    }

    public UnitySmsProperties getSmsConfig() {
        return this.unitySmsProperties;
    }

    public JsonResult sendSms(String templateCode, String phoneNumber, Map templateParam, String signName) {
        SendSmsRequest sendSmsRequest = (new SendSmsRequest()).setPhoneNumbers(phoneNumber).setSignName(signName).setTemplateCode(templateCode).setTemplateParam((new Gson()).toJson(templateParam));

        try {
            SendSmsResponse sendSmsResponse = this.client.sendSms(sendSmsRequest);
            return "OK".equals(sendSmsResponse.getBody().getCode()) ? JsonResult.success(sendSmsResponse.getBody().toMap()) : JsonResult.failed(sendSmsResponse.getBody().getMessage());
        } catch (Exception var7) {
            log.error("发送短信失败", var7);
            return JsonResult.failed("发送短信失败," + var7.getMessage());
        }
    }

    public JsonResult sendSms(String templateCode, String[] phoneNumber, List<Map> templateParams, String[] signNames) {
        if (phoneNumber != null && templateParams != null && signNames != null) {
            if (phoneNumber.length == templateParams.size() && templateParams.size() == signNames.length) {
                Gson gson = new Gson();
                SendBatchSmsRequest sendBatchSmsRequest = (new SendBatchSmsRequest()).setTemplateCode(templateCode).setPhoneNumberJson(gson.toJson(phoneNumber)).setSignNameJson(gson.toJson(signNames)).setTemplateParamJson(gson.toJson(templateParams));

                try {
                    SendBatchSmsResponse sendBatchSmsResponse = this.client.sendBatchSms(sendBatchSmsRequest);
                    return "ok".equals(sendBatchSmsResponse.getBody().getCode().toLowerCase()) ? JsonResult.success(sendBatchSmsResponse.getBody().toMap()) : JsonResult.failed(sendBatchSmsResponse.getBody().getMessage());
                } catch (Exception var8) {
                    log.error("发送短信失败", var8);
                    return JsonResult.failed("发送短信失败," + var8.getMessage());
                }
            } else {
                return JsonResult.failed("短信签名名称、手机号码、内容需保持个数相同,内容一一对应");
            }
        } else {
            return JsonResult.failed("短信签名名称、手机号码、内容需保持个数相同,内容一一对应");
        }
    }
}

2.腾讯云

java 复制代码
import com.google.gson.Gson;
import com.starlink.phonerent.common.JsonResult;
import com.starlink.phonerent.modules.common.sms.UnitySmsProperties;
import com.starlink.phonerent.modules.common.sms.UnitySmsService;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.sms.v20190711.SmsClient;
import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class UnitySmsServiceQcloudImpl implements UnitySmsService {
    private static final Logger log = LoggerFactory.getLogger(UnitySmsServiceQcloudImpl.class);
    private SmsClient client;
    private UnitySmsProperties unitySmsProperties;

    public UnitySmsServiceQcloudImpl(UnitySmsProperties unitySmsProperties) {
        this.unitySmsProperties = unitySmsProperties;
        Credential cred = new Credential(unitySmsProperties.getAccessKeyId(), unitySmsProperties.getAccessKeySecret());
        HttpProfile httpProfile = new HttpProfile();
        httpProfile.setEndpoint("sms.tencentcloudapi.com");
        ClientProfile clientProfile = new ClientProfile();
        clientProfile.setHttpProfile(httpProfile);
        this.client = new SmsClient(cred, unitySmsProperties.getRegion(), clientProfile);
    }

    public Object getSmsClient() {
        return this.client;
    }

    public UnitySmsProperties getSmsConfig() {
        return this.unitySmsProperties;
    }

    public JsonResult sendSms(String templateCode, String phoneNumber, Map templateParam, String signName) {
        if (!phoneNumber.startsWith("86") && phoneNumber.length() == 11) {
            phoneNumber = "86" + phoneNumber;
        }

        SendSmsRequest req = new SendSmsRequest();

        try {
            req.setSmsSdkAppid(this.unitySmsProperties.getSmsSdkAppid());
            req.setSign(signName);
            req.setTemplateID(templateCode);
            req.setPhoneNumberSet(new String[]{phoneNumber});
            req.setTemplateParamSet((String[]) ((String[]) templateParam.values().stream().map((s) -> {
                return String.valueOf(s);
            }).toArray((x$0) -> {
                return new String[x$0];
            })));
            SendSmsResponse res = this.client.SendSms(req);
            if ("ok".equals(res.getSendStatusSet()[0].getCode().toLowerCase())) {
                HashMap<String, String> rm = new HashMap();
                res.getSendStatusSet()[0].toMap(rm, "");
                return JsonResult.success(rm);
            } else {
                return JsonResult.failed("发送短信失败," + res.getSendStatusSet()[0].getCode() + "," + res.getSendStatusSet()[0].getMessage());
            }
        } catch (TencentCloudSDKException var8) {
            return JsonResult.failed("发送短信失败," + var8.getMessage());
        }
    }

    public JsonResult sendSms(String templateCode, String[] phoneNumber, List<Map> templateParams, String[] signNames) {
        for (int i = 0; i < phoneNumber.length; ++i) {
            if (!phoneNumber[i].startsWith("86") && phoneNumber[i].length() == 11) {
                phoneNumber[i] = "86" + phoneNumber[i];
            }
        }

        SendSmsRequest req = new SendSmsRequest();
        Gson gson = new Gson();

        try {
            req.setSmsSdkAppid(this.unitySmsProperties.getSmsSdkAppid());
            if (signNames != null) {
                req.setSign(signNames[0]);
            }

            req.setTemplateID(templateCode);
            req.setPhoneNumberSet(phoneNumber);
            Stream var10001 = templateParams.stream();
            gson.getClass();
            req.setTemplateParamSet((String[]) ((String[]) var10001.map(gson::toJson).toArray()));
            SendSmsResponse res = this.client.SendSms(req);
            if ("ok".equals(res.getSendStatusSet()[0].getCode().toLowerCase())) {
                HashMap<String, String> rm = new HashMap();
                res.getSendStatusSet()[0].toMap(rm, "");
                return JsonResult.success(rm);
            } else {
                return JsonResult.failed("发送短信失败");
            }
        } catch (TencentCloudSDKException var9) {
            return JsonResult.failed("发送短信失败," + var9.getMessage());
        }
    }
}

3.其它运营商可编写各自的服务实现类即可

六、短信发送处理类
java 复制代码
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

/**
 * 短信发送处理类
 */
@Slf4j
@Component
public class SmsSendHandler {
    @Autowired
    private UnitySmsService unitySmsService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 发送短信验证码
     *
     * @param scene
     * @param phoneNumber
     * @return
     */
    public String sendCode(String scene, String phoneNumber) {
        if (!Validator.isMobile(phoneNumber)) {
            throw new AppException("不正确的手机号码");
        } else {
            UnitySmsProperties config = this.unitySmsService.getSmsConfig();
            UnitySmsProperties.SignTemplate signTemplate = (UnitySmsProperties.SignTemplate) config.getConfigs().get(scene);
            if (signTemplate == null) {
                throw new AppException("短信配置错误,未找到场景值:" + scene);
            } else {
                String lockKey = RedisKey.SMS_MOBILE_LOCK.get(phoneNumber);
                boolean check = this.redisTemplate.opsForValue().setIfAbsent(lockKey, phoneNumber, (long) signTemplate.getTimeout(), TimeUnit.SECONDS);
                String code;
                if (!check) {
                    code = "触发[" + signTemplate.getTimeout() + "s]流控";
                    throw new AppException(code);
                } else {
                    code = RandomUtil.randomNumbers(signTemplate.getLength());
                    Map<String, String> paramMap = new HashMap(1);
                    paramMap.put("code", code);
                    if (!signTemplate.getEnabledTest()) {
                        JsonResult rs = this.unitySmsService.sendSms(signTemplate.getTemplateCode(), phoneNumber, paramMap, signTemplate.getSign());
                        if (ObjectUtil.notEqual(BaseApiCode.SUCCESS.getCode(), rs.getCode())) {
                            throw new AppException("短信发送失败:" + rs.getMsg());
                        }
                    }
                    this.redisTemplate.opsForValue().set(RedisKey.get(scene, phoneNumber), code, (long) signTemplate.getExpire(), TimeUnit.SECONDS);
                    return code;
                }
            }
        }
    }

    /**
     * 验证码校验
     *
     * @param code
     * @param phoneNumber
     * @param scene
     * @return
     */
    public boolean checkCode(String code, String phoneNumber, String scene) {
        UnitySmsProperties config = this.unitySmsService.getSmsConfig();
        UnitySmsProperties.SignTemplate signTemplate = (UnitySmsProperties.SignTemplate) config.getConfigs().get(scene);
        if (signTemplate == null) {
            throw new AppException("短信配置错误,未找到场景值:" + scene);
        } else if (signTemplate.getEnabledTest()) {
            return true;
        } else {
            String cacheKey = RedisKey.get(scene, phoneNumber);
            String cacheCode = (String) this.redisTemplate.opsForValue().get(cacheKey);
            boolean result = StrUtil.equals(code, cacheCode);
            if (result && code != null) {
                this.redisTemplate.delete(cacheKey);
                return true;
            } else {
                return false;
            }
        }
    }
}

补充统一结果返回类JsonResult(其他的异常类可根据自己项目设置)

java 复制代码
@Data
public class JsonResult<T> implements Serializable {
    private int code;
    private T data;
    private String msg;
    private long time;

    public JsonResult(String msg, T result) {
        this.msg = msg;
        this.data = result;
        this.time = System.currentTimeMillis();
    }

    public JsonResult(ApiCode apiCode, String msg, T data) {
        this.msg = msg;
        this.code = apiCode.getCode();
        this.data = data;
        this.time = System.currentTimeMillis();
    }

    public JsonResult(ApiCode apiCode, String msg) {
        this.msg = msg;
        this.code = apiCode.getCode();
        this.time = System.currentTimeMillis();
    }

    public JsonResult(Integer code, String msg, T data) {
        this.msg = msg;
        this.code = code;
        this.data = data;
        this.time = System.currentTimeMillis();
    }

    public static JsonResult success(Object data, String msg) {
        return new JsonResult(BaseApiCode.SUCCESS, msg, data);
    }

    public static JsonResult success(Object data) {
        return new JsonResult(BaseApiCode.SUCCESS, null, data);
    }

    public static JsonResult success() {
        return new JsonResult(BaseApiCode.SUCCESS, (String) null);
    }

    public static JsonResult failed(ApiCode apiCode, String msg) {
        return new JsonResult(apiCode, msg);
    }

    public static JsonResult failed(String msg) {
        return new JsonResult(BaseApiCode.FAILED, msg);
    }

    public static JsonResult failed(Integer code, String msg) {
        return new JsonResult(code, msg, (Object) null);
    }
}
相关推荐
赶路人儿40 分钟前
mybatis传递多个不同类型的参数到mapper xml文件
java·mybatis
我命由我123451 小时前
MQTT - Android MQTT 编码实战(MQTT 客户端创建、MQTT 客户端事件、MQTT 客户端连接配置、MQTT 客户端主题)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
zwz宝宝1 小时前
第三次作业(密码学)
java·数据结构·算法
源码集结号1 小时前
java智慧城管综合管理系统源码,前端框架:vue+element;后端框架:springboot;移动端:uniapp开发,技术前沿,可扩展性强
java·vue.js·spring boot·源代码·大数据分析·城管·电子办案
琢磨先生David1 小时前
Java 24 深度解析:云原生时代的性能更新与安全重构
java
xybDIY2 小时前
【亚马逊云】AWS Wavelength 从理论讲解到实验演练
云计算·aws
刘翔在线犯法2 小时前
如何在idea中写spark程序
java·spark·intellij-idea
佬乔2 小时前
JWT-验证
java·服务器·前端
编程毕设2 小时前
【含文档+PPT+源码】基于SpringBoot电脑DIY装机教程网站的设计与实现
java·spring boot·后端
不当菜虚困2 小时前
JAVA设计模式——(九)工厂模式
java·开发语言·设计模式