笔记参考
Duktig丶
JavaSDK地址:短信 Java SDK - SDK 文档 - 文档中心 - 腾讯云
>
>com.tencentcloudapi >
>tencentcloud-sdk-java >
<!-- 注:这里只是示例版本号,请获取并替换为 最新的版本号 -->
3.1.132
>
写Properties类的目的是直接提供JavaBean对象去获取配置,并教由Spring管理,而不是每次都是用@Value去读取(显得比较冗余)。
注:属性可根据实际情况修改。
/**
* description: 腾讯云短信服务,配置
*
* author:frozenpenguin
**/
@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;
private String templateId;
/** redis存储前缀 */
private String phonePrefix;
}
yml
# 腾讯云短信服务配置
sms-config:
# 腾讯云账户密钥对 secretId 和 secretKey
secretId:
secretKey:
# 短信应用appId和appKey
appId:
appKey:
# 签名
smsSign:
# 过期时间
expireTime: 10 # 10min
# redis存储前缀
phonePrefix:
# 模板id
templateId:
写枚举类的好处是方便校验,而且不允许随意更改(习惯用枚举类,会有非常多的好处)。
常用4位或6位。不允许有其他位数出现(可自定义)。
/**
* description:
*
**/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum SmsLengthEnum {
/** 4位短信验证码 */
SMS_LENGTH_4(4),
/** 6位短信验证码 */
SMS_LENGTH_6(6),
;
private int length;
}
对应于腾讯云短信服务返回状态码,使用枚举进行校验,做特殊的返回状态的业务处理(可继续完善)。
/**
* description: 腾讯云短信服务返回状态码
*
**/
@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: 腾讯云短信服务工具类
*
**/
@Configuration
public class SmsCodeUtil {
/**
* 随机生成指定长度的短信的验证码
*
* @param smsLengthEnum 短信验证码长度枚举
* @return 随机验证码
*/
public static String createSmsRandomCode(SmsLengthEnum smsLengthEnum) {
return RandomUtil.randomNumbers(smsLengthEnum.getLength());
}
/**
* 创建短信验证码,缓存键策略
* 策略:前缀_业务名_手机号
*
* @param prefix 前缀
* @param phone 手机号
* @param businessStr 业务名
* @return 短信验证码,缓存键策略
*/
public static String createSmsCacheKey(String prefix, String phone, String businessStr) {
return prefix + "_" + businessStr + "_" + phone;
}
}
发送短信封装起来,方便后续更好的进行扩展。
/**
* description:
*
* 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
**/
public interface ISmsService {
/**
* description: 发送短信验证码
*
* @param phone 手机号
*/
void sendSmsCode(String phone);
/**
* description:验证短信验证码
*
* @param phone 手机号
* @param code 验证码
*/
void verifyCode(String phone, String code);
}
/**
* description: 短信业务
*
**/
@Service
@Slf4j
@RequiredArgsConstructor
public class SmsServiceImpl implements ISmsService {
// private final SmsProperties smsProperties;
private final RedisTemplate redisTemplate;
@Autowired
private SmsProperties smsProperties;
/**
* description: 发送短信验证码
*
* @param phone 手机号
* @author frozenpenguin
*/
@Override
public void sendSmsCode(String phone) {
// redis缓存的键
final String smsKey = SmsCodeUtil.createSmsCacheKey(smsProperties.getPhonePrefix(), phone, "User_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()};
System.out.println(smsProperties);
SendStatus[] sendStatuses = SmsUtil.sendSms(smsProperties, smsProperties.getTemplateId().toString(), templateParams, phoneNumbers);
String resCode = sendStatuses[0].getCode();
if (resCode.equals(SmsResponseCodeEnum.OK.getCode())) {
// 返回ok,缓存
// redisUtils.set(smsKey, smsRandomCode, Long.parseLong(smsProperties.getExpireTime()), TimeUnit.MINUTES);
redisTemplate.opsForValue().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 验证码
*/
@Override
public Result verifyCode(String phone, String code) {
// redis缓存的键
final String smsKey = SmsCodeUtil.createSmsCacheKey(smsProperties.getPhonePrefix(), phone,
"User_sign");
// 如果key存在(存在并且未过期)
// if (redisUtils.hasKey(smsKey)) {
if(redisTemplate.hasKey(smsKey)){
// String cacheCode = redisUtils.get(smsKey).toString();
String cacheCode=redisTemplate.opsForValue().get(smsKey).toString();
if (cacheCode.equals(code)) {
//验证码正确
//删除验证码缓存
redisTemplate.delete(smsKey);
// redisUtils.del(smsKey);
log.info("【短信业务-手机认证成功】phone:" + phone);
return new Result(200,"手机认证成功");
} else {
//验证码不正确
log.error("【短信业务-手机认证失败】验证码错误。phone:" + phone);
// throw new BaseException(ResponseEnum.SMS_CODE_VERITY_FAIL);
return new Result(200,"手机认证失败");
}
} else {
log.error("【短信业务-手机认证失败】验证码失效。phone:" + phone);
// throw new BaseException(ResponseEnum.SMS_CODE_EXPIRE);
return new Result(201,"手机验证码失效");
}
}
}
/**
* description: 测试腾讯云SDK3.0
*
**/
@RestController
@RequestMapping("/sms")
public class SmsController {
@Autowired
private ISmsService smsService;
@Test
public void testSendCode() {
smsService.sendSmsCode("13143366666");
}
@ApiOperation("发送短信测试")
@PostMapping("/sendMessage")
public void sendMessage(String phone){
smsService.sendSmsCode(phone);
}
}
public Result registerByMobile(@RequestBody RegisterVo register) {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getUsername,register.getUsername());
User one = userService.getOne(lqw);
if (!ObjectUtils.isEmpty(one)){
return new Result(201,"注册失败,用户名已存在");
}
Result result = iSmsService.verifyCode(register.getPhone(), register.getCode());
if (result.getResultCode()==200) {
User user = BeanCopyUtils.copyBean(register, User.class);
user.setPassword(new BCryptPasswordEncoder().encode(register.getPassword()));
userService.save(user);
return new Result(200,"注册成功");
}else {
return new Result(201,"验证码失效");
}
}
登录功能大同小异,不再介绍。