关于Springboot集成腾讯云短信服务,百度出来的写法基本都还是停留在2.0的版本。但是腾讯官方更加推荐使用3.0版本:腾讯云短信服务Java SDK3.0。
参考:腾讯云-国内短信快速入门
根据自己的需求创建签名,在签名管理中就可以看到刚刚创建的短信签名。当显示的状态变为已通过后该签名就可以使用了。
创建短信的模板,以发送短信验证码为例。其中短信内容部分{1}{2}等变量参数为动态参数,需要我们在代码中请求腾讯云短信服务时携带。
短信内容可填写:
{
1}为您的验证码,请于{
2}分钟内填写,如非本人操作,请忽略本短信。
创建完成模板状态显示已通过即可使用。
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<!-- 注:这里只是示例版本号,请获取并替换为 最新的版本号 -->
<version>3.1.132</version>
</dependency>
注:下文中使用的很多工具类出自hutool,参考文档:https://hutool.cn/docs/#/
>
>cn.hutool >
>hutool-all >
>5.4.2 >
>
下列配置全为自定义配置,这样从配置文件里读取配置,提高可扩展性和可维护性。
# 腾讯云短信服务配置
sms-config:
# 腾讯云账户密钥对 secretId 和 secretKey
secretId:
secretKey:
# 短信应用appId和appKey
appId:
appKey:
# 签名
smsSign:
# 过期时间
expireTime: 10 # 10min
# redis存储前缀
phonePrefix: pinganky_sms
# 模板id
templateIds:
code: 721446
在腾讯云短信服务3.0需要配置secretId 和 secretKey的访问密钥(2.0则不需要),进一步提高安全性。
模板id根据业务需求配置,都是自定义。对应的是正文模板管理的ID。
写Properties类的目的是直接提供JavaBean对象去获取配置,并教由Spring管理,而不是每次都是用@Value去读取(显得比较冗余)。
注:属性可根据实际情况修改。
/**
* description: 腾讯云短信服务,配置
*
* Date: 2020/9/16 10:53
**/
@Data
@Configuration
@ConfigurationProperties(prefix = "sms-config")
public class SmsProperties {
/** 腾讯云账户密钥对secretId(在访问管理中配置) */
private String secretId;
/** 腾讯云账户密钥对secretKey(在访问管理中配置) */
private String secretKey;
/** 短信应用appId */
private String appId;
/** 短信应用appKey */
private String appKey;
/** 签名 */
private String smsSign;
/** 过期时间 */
private String expireTime;
/** redis存储前缀 */
private String phonePrefix;
}
写枚举类的好处是方便校验,而且不允许随意更改(习惯用枚举类,会有非常多的好处)。
常用4位或6位。不允许有其他位数出现(可自定义)。
/**
* description:
*
* Date: 2020/9/16 11:59
**/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum SmsLengthEnum {
/** 4位短信验证码 */
SMS_LENGTH_4(4),
/** 6位短信验证码 */
SMS_LENGTH_6(6),
;
private int length;
}
对应于腾讯云短信服务返回状态码,使用枚举进行校验,做特殊的返回状态的业务处理(可继续完善)。
/**
* description: 腾讯云短信服务返回状态码
*
* Date: 2020/9/16 20:20
**/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum SmsResponseCodeEnum {
/**
* 发送成功
* 数据结构 "SendStatusSet":[{"Code":"Ok"}]
*/
OK("Ok", "send success"),
/*
失败数据结构
{
"Response": {
"Error": {
"Code": "AuthFailure.SignatureFailure",
"Message": "The provided credentials could not be validated. Please check your signature is
correct."
},
"RequestId": "ed93f3cb-f35e-473f-b9f3-0d451b8b79c6"
}
}
详情参考:https://cloud.tencent.com/document/product/382/38780
*/
;
/** 状态码 */
private String code;
/** 描述信息 */
private String message;
}
/**
* description: 腾讯云短信服务工具类
*
* Date: 2020/9/16 10:53
**/
@Configuration
public class SmsCodeUtil {
/**
* 随机生成指定长度的短信的验证码
*
* @param smsLengthEnum 短信验证码长度枚举
* @return 随机验证码
* @author RenShiWei
* Date: 2020/9/16 10:53
*/
public static String createSmsRandomCode(SmsLengthEnum smsLengthEnum) {
return RandomUtil.randomNumbers(smsLengthEnum.getLength());
}
/**
* 创建短信验证码,缓存键策略
* 策略:前缀_业务名_手机号
*
* @param prefix 前缀
* @param phone 手机号
* @param businessStr 业务名
* @return 短信验证码,缓存键策略
* @author RenShiWei
* Date: 2020/9/16 10:53
*/
public static String createSmsCacheKey(String prefix, String phone, String businessStr) {
return prefix + "_" + businessStr + "_" + phone;
}
}
发送短信封装起来,方便后续更好的进行扩展。
/**
* description:
*
* @author RenShiWei
* Date: 2020/10/20 15:03
**/
public class SmsUtil {
/**
* 腾讯云发送短信的基础服务
*
* @param smsProperties 腾讯云短信服务配置
* @param templateId 腾讯云短信模板id
* @param templateParams 腾讯云模板需要的参数
* @param phoneNumbers 手机号集合
* @return 发送短信后的状态的set集合
*/
public static SendStatus[] sendSms(SmsProperties smsProperties, String templateId, String[] templateParams,
String[] phoneNumbers) {
/*
实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId 和 secretKey
CAM 密钥查询:https://console.cloud.tencent.com/cam/capi
*/
Credential cred = new Credential(smsProperties.getSecretId(), smsProperties.getSecretKey());
/*
实例化要请求产品(以sms为例)的client对象
第二个参数是地域信息,可以直接填写字符串ap-guangzhou,或者引用预设的常量
*/
SmsClient client = new SmsClient(cred, "");
/*
实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
*/
SendSmsRequest req = new SendSmsRequest();
//短信应用ID: 短信appId在 [短信控制台] 添加应用后生成的实际SdkAppid,示例如1400006666
req.setSmsSdkAppid(smsProperties.getAppId());
//短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看
req.setSign(smsProperties.getSmsSign());
//模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看
req.setTemplateID(templateId);
//下发手机号码,采用 e.164 标准,+[国家或地区码][手机号] 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
req.setPhoneNumberSet(phoneNumbers);
//模板参数: 若无模板参数,则设置为空
req.setTemplateParamSet(templateParams);
/* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的
返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应
*/
SendSmsResponse res = null;
try {
//发送短信
res = client.SendSms(req);
return res.getSendStatusSet();
} catch (TencentCloudSDKException e) {
e.printStackTrace();
//发送短信失败
throw new BaseException(ResponseEnum.SMS_NOT_SEND);
}
}
}
Redis相关可自行配置,目的就是缓存将验证码进行缓存,然后再进行校验,校验一次后,直接删除,使当前验证码失效(验证码可多次使用,则不用删除)。
注:redis相关需要自行配置,不是本文重点,就不在阐述。
/**
* description: 短信业务
*
* @author RenShiWei
* Date: 2020/9/15 22:06
**/
public interface ISmsService {
/**
* description: 发送短信验证码
*
* @param phone 手机号
* @author RenShiWei
* Date: 2020/9/16 10:11
*/
void sendSmsCode(String phone);
/**
* description:验证短信验证码
*
* @param phone 手机号
* @param code 验证码
* @author RenShiWei
* Date: 2020/9/16 10:11
*/
void verifyCode(String phone, String code);
}
/**
* description: 短信业务
*
* @author RenShiWei
* Date: 2020/9/15 22:06
**/
@Service
@Slf4j
@RequiredArgsConstructor
public class SmsServiceImpl implements ISmsService {
private final SmsProperties smsProperties;
private final RedisUtils redisUtils;
/** 腾讯云短信模板id-短信验证码 */
@Value("${
sms-config.templateIds.code}")
private String templateIdCode;
/**
* description: 发送短信验证码
*
* @param phone 手机号
* @author RenShiWei
* Date: 2020/9/16 10:11
*/
@Override
public void sendSmsCode(String phone) {
// redis缓存的键
final String smsKey = SmsCodeUtil.createSmsCacheKey(smsProperties.getPhonePrefix(), phone, "wxUser_sign");
//下发手机号码,采用 e.164 标准,+[国家或地区码][手机号] 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
String[] phoneNumbers = {
"+86" + phone};
//模板参数: 若无模板参数,则设置为空(第一个参数为随机验证码,第二个参数为有效时间)
final String smsRandomCode = SmsCodeUtil.createSmsRandomCode(SmsLengthEnum.SMS_LENGTH_4);
String[] templateParams = {
smsRandomCode,
smsProperties.getExpireTime()};
SendStatus[] sendStatuses = SmsUtil.sendSms(smsProperties, templateIdCode, templateParams, phoneNumbers);
String resCode = sendStatuses[0].getCode();
if (resCode.equals(SmsResponseCodeEnum.OK.getCode())) {
// 返回ok,缓存
redisUtils.set(smsKey, smsRandomCode, Long.parseLong(smsProperties.getExpireTime()), TimeUnit.MINUTES);
} else {
log.error("【短信业务-发送失败】phone:" + phone + "errMsg:" + sendStatuses[0].getMessage());
throw new BaseException(ResponseEnum.SMS_NOT_SEND);
}
}
/**
* description:验证短信验证码
*
* @param phone 手机号
* @param code 验证码
* @author RenShiWei
* Date: 2020/9/16 10:11
*/
@Override
public void verifyCode(String phone, String code) {
// redis缓存的键
final String smsKey = SmsCodeUtil.createSmsCacheKey(smsProperties.getPhonePrefix(), phone,
"wxUser_sign");
// 如果key存在(存在并且未过期)
if (redisUtils.hasKey(smsKey)) {
String cacheCode = redisUtils.get(smsKey).toString();
if (cacheCode.equals(code)) {
//验证码正确
//删除验证码缓存
redisUtils.del(smsKey);
log.info("【短信业务-微信公众号手机认证成功】phone:" + phone);
} else {
//验证码不正确
log.error("【短信业务-微信公众号手机认证失败】验证码错误。phone:" + phone);
throw new BaseException(ResponseEnum.SMS_CODE_VERITY_FAIL);
}
} else {
log.error("【短信业务-微信公众号手机认证失败】验证码失效。phone:" + phone);
throw new BaseException(ResponseEnum.SMS_CODE_EXPIRE);
}
}
}
Controller自定编写,调用Service进行业务处理即可。
可编写Springboot的测试类,或者使用接口调用进行测试
/**
* description: 测试腾讯云SDK3.0
*
* Date: 2020/9/16 9:10
**/
@SpringBootTest
@RunWith(SpringRunner.class)
public class SmsTest {
@Autowired
private ISmsService smsService;
@Test
public void testSendCode() {
smsService.sendSmsCode("15611111111");
}
}
至此完成!