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);
    }
}
相关推荐
Java中文社群3 分钟前
服务器被攻击!原因竟然是他?真没想到...
java·后端
Full Stack Developme14 分钟前
java.nio 包详解
java·python·nio
零千叶30 分钟前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝39 分钟前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li37149089044 分钟前
nginx报400bad request 请求头过大异常处理
java·运维·nginx
摇滚侠1 小时前
Spring Boot 项目, idea 控制台日志设置彩色
java·spring boot·intellij-idea
Aevget1 小时前
「Java EE开发指南」用MyEclipse开发的EJB开发工具(二)
java·ide·java-ee·eclipse·myeclipse
黄昏晓x2 小时前
C++----多态
java·jvm·c++
Brookty2 小时前
【算法】前缀和
java·学习·算法·前缀和·动态规划
少许极端3 小时前
算法奇妙屋(七)-字符串操作
java·开发语言·数据结构·算法·字符串操作