SpringBoot集成腾讯云短信实现注册/登录功能

前言

笔记参考
Duktig丶

JavaSDK地址:短信 Java SDK - SDK 文档 - 文档中心 - 腾讯云

1.导入依赖

>
     >com.tencentcloudapi>
     >tencentcloud-sdk-java>
     <!-- 注:这里只是示例版本号,请获取并替换为 最新的版本号 -->
     3.1.132
>

2.编写SmsProperties类,读取yml中的配置

写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: 

3.提供枚举类方便操作

写枚举类的好处是方便校验,而且不允许随意更改(习惯用枚举类,会有非常多的好处)。

SmsLengthEnum 定义短信验证码长度

常用4位或6位。不允许有其他位数出现(可自定义)。

/**
 * description:
 *
 **/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum SmsLengthEnum {

    /** 4位短信验证码 */
    SMS_LENGTH_4(4),
    /** 6位短信验证码 */
    SMS_LENGTH_6(6),

    ;

    private int length;
}

SmsResponseCodeEnum 腾讯云短信服务返回状态码的校验

对应于腾讯云短信服务返回状态码,使用枚举进行校验,做特殊的返回状态的业务处理(可继续完善)。

/**
 * 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;

}

4.提供工具类

SmsCodeUtil 短信验证码工具类

/**
 * 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;
    }

}

SmsUtil 短信工具类

发送短信封装起来,方便后续更好的进行扩展。

/**
 * 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);
        }
    }


}

5.Redis配置

Redis相关可自行配置,目的就是缓存将验证码进行缓存,然后再进行校验,校验一次后,直接删除,使当前验证码失效(验证码可多次使用,则不用删除)。

6.短信服务类

注: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,"手机验证码失效");
        }
    }
}

7.Controller接口

/**
 * 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);
    }

}

8.测试注册

    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,"验证码失效");
        }
    }

登录功能大同小异,不再介绍。

你可能感兴趣的:(spring,boot,腾讯云,java)