/// <summary>
        /// 验证手机号码
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <remarks>
        /// 用户传递手机号码及认证类型
        /// 1、如果认证类型为注册:
        ///     先查询是否存在此手机号的缓存验证码信息,如果存在,抛出不能重复发送验证码异常
        ///     随机生成6位纯数字验证码,通过短信接口服务发送到用户手机,并缓存验证码,设定一个有效时长
        ///
        /// 2、如果认证类型为登录:
        ///     先查询是否存在此手机号的缓存验证码信息,如果存在,抛出不能重复发送验证码异常
        ///     通过手机号查询用户信息,如果用户不存在,抛出手机号未注册异常
        ///     调用PhoneNumberTokenProvider接口生成6位手机验证码,用途为 phone_verify
        ///     发送手机验证码到用户手机,并缓存验证码,设定一个有效时长
        ///
        ///     用户调用 IdentityServer4/connect/token 登录系统(需要引用LINGYUN.Abp.IdentityServer.SmsValidator模块)
        ///         参数1:grant_type=phone_verify
        ///         参数2:phone_number=手机号码
        ///         参数3:phone_verify_code=手机验证码
        ///         参数4:client_id=客户端标识
        ///         参数5:client_secret=客户端密钥
        /// </remarks>
        public virtual async Task VerifyPhoneNumberAsync(VerifyDto input)
        {
            // TODO: 借用TOTP算法生成6位动态验证码

            var verifyCodeExpiration = await SettingProvider.GetAsync <int>(AccountSettingNames.PhoneVerifyCodeExpiration);

            var phoneVerifyCacheKey = NormalizeCacheKey(input.PhoneNumber);
            var verifyCacheItem     = await Cache.GetAsync(phoneVerifyCacheKey);

            if (verifyCacheItem != null)
            {
                throw new UserFriendlyException(L["PhoneVerifyCodeNotRepeatSend", verifyCodeExpiration]);
            }
            verifyCacheItem = new AccountRegisterVerifyCacheItem
            {
                PhoneNumber = input.PhoneNumber,
            };
            switch (input.VerifyType)
            {
            case PhoneNumberVerifyType.Register:
                var phoneVerifyCode = new Random().Next(100000, 999999);
                verifyCacheItem.VerifyCode = phoneVerifyCode.ToString();
                var templateCode = await SettingProvider.GetOrDefaultAsync(AccountSettingNames.SmsRegisterTemplateCode, ServiceProvider);
                await SendPhoneVerifyMessageAsync(templateCode, input.PhoneNumber, phoneVerifyCode.ToString());

                break;

            case PhoneNumberVerifyType.Signin:
                var phoneSigninCode = await SendSigninVerifyCodeAsync(input.PhoneNumber);

                verifyCacheItem.VerifyCode = phoneSigninCode;
                break;

            case PhoneNumberVerifyType.ResetPassword:
                var resetPasswordCode = new Random().Next(100000, 999999);
                verifyCacheItem.VerifyCode = resetPasswordCode.ToString();
                var resetPasswordToken = await SendResetPasswordVerifyCodeAsync(input.PhoneNumber, verifyCacheItem.VerifyCode);

                verifyCacheItem.VerifyToken = resetPasswordToken;
                break;
            }

            var cacheOptions = new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(verifyCodeExpiration)
            };
            await Cache.SetAsync(phoneVerifyCacheKey, verifyCacheItem, cacheOptions);
        }
 public virtual async Task VerifyPhoneNumberAsync(VerifyDto input)
 {
     await AccountAppService.VerifyPhoneNumberAsync(input);
 }