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>
二、 短信枚举类
/*** 短信平台枚举类*/
public enum UnitySmsType {Qcloud,Aliyun,//其它运营商xxx;private UnitySmsType() {}
}
三、短信配置类
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;}@Datapublic 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 #短信平台申请的accessKeyIdaccessKeySecret: xxxxx #短信平台申请的accessKeySecretsmsSdkAppid: 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 #生成的验证码长度
四、短信接口服务类
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.阿里云
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.腾讯云
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.其它运营商可编写各自的服务实现类即可
六、短信发送处理类
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 {@Autowiredprivate UnitySmsService unitySmsService;@Autowiredprivate 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(其他的异常类可根据自己项目设置)
@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);}
}