The second round of FLY strategy: access Tencent cloud SMS service to realize mobile phone verification and registration

Posted by NTM on Fri, 11 Feb 2022 14:32:53 +0100

Access Tencent cloud SMS service to realize mobile phone verification and registration

Last time, I said that I had completed the access and login functions of security, but when I was ready to log in and try the power of security, I found that the registration function had not been completed, and there was no account to provide login. This time, I will realize the mobile phone number registration function.

The logic of mobile phone number registration is very simple. To put it bluntly, it is just to prove that the mobile phone number is your own. The specific process is that the user requests the service, the service generates a verification code, sends it to the specified mobile phone number in the form of SMS, and then requests the registration interface to carry the registration information, mobile phone number and verification code received by the mobile phone. The service receives the registration request, Check whether the verification code is issued by yourself before. After the verification is passed, save the registered user information to complete the registration. The logical process is very simple. Here is mainly to record the process of docking with cloud SMS service.

Cloud SMS service provider

Since you want to send text messages, it must be a direct access to cloud SMS services. Cloud service providers supporting SMS services grasp a large number of them, and the price is basically reasonable. However, basically all SMS service providers need to verify our business license and other materials in order to prevent bad uses. Generally speaking, it is still troublesome to access. However, it is not easy for me to make these materials for the purpose of learning, So here we choose the Tencent cloud's SMS service. First of all, Tencent's status and name are beyond doubt. Secondly, and most importantly, Tencent cloud can apply for SMS service through official account, and the official account is very convenient to open.

There is no introduction to the opening of SMS service here. Anyway, just follow the platform prompts step by step. Basically, all platforms are the same.

Create SMS sending service

Considering that there may be other businesses that need to use SMS service, we need to encapsulate the SMS service a little

Create a package under the project path and call it send. The purpose of calling send is to put the services related to sending e-mail in this package, so it is not suitable to call sms. Just call it send. There is nothing to say about the logic. Just go to the code below.

Create SendController, which is used to provide an interface for sending SMS or email. At present, only one interface is required

/**
 * @author: ZhangZhao
 * @date: 2022/2/10 11:59
 * @description: Provide SMS or email service
 */
@RestController
@RequiredArgsConstructor
@RequestMapping(SysApi.SEND)
public class SendController {

    private final SmsSendService smsSendService;

    /**
     * Send SMS verification code
     * @param req Mobile phone number and SMS type
     * @return Send results
     */
    @PostMapping("/verificationCode")
    public Result<Void> sendVerificationCode(@Valid @RequestBody SendVerificationCodeReq req){
        SmsTypeEnum smsType = SmsTypeEnum.getEnumByCode(req.getTypeCode());
        smsSendService.sendVerificationCode(smsType.getSmsTemplateId(),req.getPhoneNum());
        return Result.success();
    }

}

SendVerificationCodeReq class

/**
 * @author: ZhangZhao
 * @date: 2022/2/10 14:45
 * @description: Send verification code interface input parameter
 */
@Setter
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SendVerificationCodeReq {
	
    /**
     * This field corresponds to the identification code in the 'SmsTypeEnum' enumeration class
     */
    @NotNull(message = "Verification code type must be filled in!")
    private Integer typeCode;

    @NotNull(message = "Mobile phone number cannot be empty!")
    @Pattern(regexp = Constants.Regexp.PHONE_NUM,message = "Please fill in the correct mobile phone number!")
    private String phoneNum;

}

This interface is specially used to send verification code SMS. Different service verification codes and corresponding SMS templates are identified through SmsTypeEnum

/**
 * @author: ZhangZhao
 * @date: 2022/2/10 14:55
 * @description: SMS type enumeration: used to describe the purpose of SMS and the corresponding SMS template
 */
@Getter
@AllArgsConstructor
public enum  SmsTypeEnum {

    /**
     * Registration verification code
     */
    SMS_PIN_REGISTER(10001,"1299215","Registration verification code"),
    /**
     * Login verification code TODO login SMS template has not been created
     */
    SMS_PIN_LOGIN(10002,"","Login verification code"),;

    /**
     * Identification code
     */
    private final Integer code;
    /**
     * SMS template ID
     */
    private final String smsTemplateId;
    /**
     * describe
     */
    private final String message;

    private static final Set<SmsTypeEnum> ALL = EnumSet.allOf(SmsTypeEnum.class);

    public static SmsTypeEnum getEnumByCode(Integer code) {
        if(code==null){
            return null;
        }
        return ALL.stream()
                .filter(o->o.code.equals(code))
                .findAny()
                .orElseThrow(()->new BusinessException("Error code enumeration class: "+code+ "No relevant value matched!"));

    }

    public boolean is(Integer code){
        return getCode().equals(code);
    }
}

Create SmsSendService to send SMS specifically. The subsequent businesses related to SMS are also involved. At present, there is only one method to send SMS verification code

After the SMS verification code is sent, it is stored in redis. The specific code of sending SMS refers to the Tencent cloud document[ https://cloud.tencent.com/document/product/382/43194 ]:

    /**
     * Send SMS verification code
     * @param smsTemplateId SMS template ID
     * @param phoneNum      cell-phone number
     */
    @Override
    public void sendVerificationCode(String smsTemplateId, String phoneNum) {
        //Generate verification code
        String vCode = RandomUtil.randomNumbers(Constants.PIN_LENGTH);
        log.debug("{} generate vCode {}",phoneNum,vCode);
        //Assembly sending information sending verification code
        SendSmsInfoDto sendSmsInfo = SendSmsInfoDto.builder()
                .templateId(smsTemplateId)
                .phoneNumList(Collections.singletonList(phoneNum))
                //Set the SMS template parameters here and add them in the order of {1} and {2} corresponding to the template
                .contextParamArray(
           			 new String[]{vCode, String.valueOf(CacheConstants.CacheExpire.PHONE_PIN_EXPIRE)}
        		)
                .build();
        this.sendSms(sendSmsInfo);
        //Stored in redis for 5 minutes
        // Save key as ` PIN_CODE_`+  Incoming mobile number
        redisService.setCacheObject(CacheConstants.CacheKey.PHONE_PIN_KEY_PREFIX+phoneNum,
                                            vCode,
                                            CacheConstants.CacheExpire.PHONE_PIN_EXPIRE, 
                                            TimeUnit.MINUTES);
    }
	/**
     * Send SMS 
     * @param sendSmsInfo Send message
     */
    private void sendSms(SendSmsInfoDto sendSmsInfo){
        //Calibration parameters
        String templateId = sendSmsInfo.getTemplateId();
        AssertUtil.isMeets(templateId, StrUtil::isNotBlank,"SMS template cannot be empty!");
        List<String> phoneNumList = sendSmsInfo.getPhoneNumList();
        AssertUtil.isMeets(phoneNumList, CollectionUtil::isNotEmpty,"The issued mobile phone number cannot be empty!");

        //E.164 standard shall be adopted for processing mobile phone number, + [country or region code] [mobile phone number] the method of processing mobile phone number here will not be repeated
        String[] standardPhoneNumList = phoneNumList.stream()
            .filter(StrUtil::isNotBlank)
            .map(this::formatPhoneNum)
            .toArray(String[]::new);

        try {
            //Instantiate an authentication object
            Credential cred = new Credential(smsConfig.getSecretId(), smsConfig.getSecretKey());
            // *Instantiate the client object to request the product (take sms as an example)
            SmsClient client = new SmsClient(cred, smsConfig.getSmsServiceRegion());

            //*Instantiate a request object, and further set the request parameters according to the calling interface and the actual situation
            SendSmsRequest req = new SendSmsRequest();
            req.setSmsSdkAppId(smsConfig.getSdkAppId());
            req.setSignName(smsConfig.getSignName());
            //*International / Hong Kong, Macao and Taiwan SMS SenderId: fill in the blank for domestic SMS. It is not opened by default,
            req.setSenderId(Constants.Str.EMPTY);
            //*User's session content: it can carry context information such as user side ID, and the server will return it as it is
            req.setSessionContext(sendSmsInfo.getExtendInfo());
            //*SMS number extension number: not opened by default. If you need to open, please contact [sms helper]
            req.setExtendCode(Constants.Str.EMPTY);
            req.setTemplateId(templateId);
            //*Issue mobile phone numbers, no more than 200 at most
            req.setPhoneNumberSet(standardPhoneNumList);
            //*Template parameter: if there is no template parameter, it is set to null
            req.setTemplateParamSet(sendSmsInfo.getContextParamArray());

            //*The request is initiated by calling the SendSms method through the client object.
            SendSmsResponse res = client.SendSms(req);

            // Output json format string back package
            log.debug("Sms send Info :{}",SendSmsResponse.toJsonString(res));
        } catch (TencentCloudSDKException e) {
            log.error("Sms send fail! param:{},Exception:{}",sendSmsInfo.toString() ,e.getMessage());
            throw new BusinessException("SMS sending exception,Please try again later!");
        }
    }

Now call the / verificationCode interface to send SMS

Add registration interface

Go back to our user module and add the registration interface in UserController

    /**
     * User registration
     * @param req Registration information
     * @return Registration status
     */
    @PostMapping("/register")
    public Result<Void> register(@Valid @RequestBody RegisterReq req){
        userService.register(req);
        return  Result.success();
    }

RegisterReq class

/**
 * @author: Li Zhongyang
 * @date: 2021/12/29 13:08
 * @description: Register interface input parameters
 */
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RegisterReq {

    /**
     * user name
     */
    @NotNull(message = "User name must be filled in!")
    @Length(min = 6,max = 14,message = "User name length must be 6-14 character!")
    private String nickName;

    /**
     * phone number
     */
    @NotNull(message = "Mobile phone number cannot be empty!")
    @Pattern(regexp = Constants.Regexp.PHONE_NUM,message = "Please fill in the correct mobile phone number!")
    private String phoneNum;

    /**
     * password
     */
    @NotNull(message = "Mobile phone number cannot be empty!")
    private String password;

    /**
     * Verification Code
     */
    @NotNull(message = "Verification code cannot be empty!")
    private String verificationCode;

}

Implement the registration method in the service

    /**
     *  register
     * <p>
     * Mobile number registration
     * @param req Registration information
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(RegisterReq req) {
        //Check whether the verification code is correct
        String phoneNum = req.getPhoneNum();
        //Take the previously sent verification code from redis according to the mobile phone number and compare it with the incoming verification code
        String phonePin = redisService.getCacheObject(CacheConstants.CacheKey.PHONE_PIN_KEY_PREFIX+phoneNum);
        AssertUtil.isMeets(req.getVerificationCode(),s-> StrUtil.equals(s,phonePin),"Verification code error");

        String encodePassword = SecureUtil.md5(req.getPassword());
        String nickName = req.getNickName();

        //Judge whether the current mobile phone number has been registered and whether the user name already exists
        UserAuthsInfo getAuthsInfoByPhone = userAuthsInfoMapper.getUserAuthsInfo(phoneNum);
        AssertUtil.isNull(getAuthsInfoByPhone,"The current mobile phone number has been bound to an account,Please go to login!");

        UserAuthsInfo getAuthsInfoByUsername = userAuthsInfoMapper.getUserAuthsInfo(nickName);
        AssertUtil.isNull(getAuthsInfoByUsername,"The current user name is already occupied!");

        //User information warehousing
        //Add user basic information
        UserBaseInfo userBaseInfo = UserBaseInfo.builder()
            	//Constants.DEFAULT_AVATAR_URL this constant is the default avatar URL
                .avatarUrl(Constants.DEFAULT_AVATAR_URL[0])
                .nickName(req.getNickName())
                .build();
        userBaseInfoMapper.saveUserBaseInfo(userBaseInfo);

        //Add authorization information and mobile number registration. Mobile number login and user name login are supported by default
        List<UserAuthsInfo> userAuthsInfoList=new ArrayList<>();
        UserAuthsInfo userAuthsInfoByPhone = UserAuthsInfo.builder()
                .userId(userBaseInfo.getId())
                //Mobile number login account is mobile number
                .identityType(IdentityTypeEnum.PHONE.getCode())
                .identifier(phoneNum)
                .credential(encodePassword)
                .encryptType(EncryptTypeEnum.MD5.getCode())
                .build();
        userAuthsInfoList.add(userAuthsInfoByPhone);

        UserAuthsInfo userAuthsInfoByUsername = UserAuthsInfo.builder()
                .userId(userBaseInfo.getId())
                //User name login account is user name
                .identityType(IdentityTypeEnum.USERNAME.getCode())
                .identifier(nickName)
                .credential(encodePassword)
                .encryptType(EncryptTypeEnum.MD5.getCode())
                .build();
        userAuthsInfoList.add(userAuthsInfoByUsername);

        userAuthsInfoMapper.batchSaveUserAuthInfo(userAuthsInfoList);

        //Set default details
        UserDetailInfo userDetailInfo = UserDetailInfo.builder()
                .userId(userBaseInfo.getId())
                .nickName(nickName)
                .phoneNum(phoneNum)
                .build();
        userDetailInfoMapper.saveUserDetailInfo(userDetailInfo);
    }

summary

At present, the services provided by third-party service providers are very intelligent and basically have complete access documents. There will be no problem according to the documents. For SMS services, all cloud service providers are similar.

Topics: cloud computing