本文描述了在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);
}
}