第52章 短信验证服务和登录的后端定义实现

1 Services.Messages.SmsValidate

using Core.Domain.Messages;

using Data;

using Microsoft.EntityFrameworkCore;

namespace Services.Messages

{

    /// <summary>

    /// 【短信验证服务--类】

    /// <remarks>

    /// 摘要:

    ///    通过类中的方法成员实现当前程序与短信验证表之间的CURD操作。

    /// </remarks>

    /// </summary>

    public class SmsValidateService : ISmsValidateService

    {

        #region 拷贝构造方法与变量

        private readonly IRepository<SmsValidate> _smsValidateRepository;

        /// <summary>

        /// 【拷贝构建方法】

        /// <remarks>

        /// 摘要:

        ///     依赖注入容器通过拷贝构造方法,实例化该类中的变量成员。

        /// </remarks>

        /// </summary>

        public SmsValidateService(

            IRepository<SmsValidate> smsValidateRepository)

        {

            _smsValidateRepository = smsValidateRepository;

        }

        #endregion

        /// <param name="phone">1个指定的手机号。</param>

        /// <param name="validateCode">1个输入短信验证码。</param>

        /// <summary>

        /// 【异步短信验证?】

        /// <remarks>

        /// 摘要:

        ///     获取1个值false(不等)/true(相等),该值指示用户输入的短信验证码与持久化存储的短信验证码是否相等,如果相等则用户通过验证可以执行登录操作;反之用户未通过验证不能执行登录操作。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    1个值false(不等)/true(相等)

        /// </returns>

        /// </summary>

        public async Task<bool> ValidateSmsValidateAsync(string phone, string validateCode)

        {

            if (string.IsNullOrWhiteSpace(phone)|| string.IsNullOrWhiteSpace(validateCode))

                return false;

            SmsValidate _smsValidate = await SmsValidateByPhoneAndCode(phone, validateCode);

            if (_smsValidate!=null)

                return true;

            return false;

        }

        /// <param name="phone">1个指定的手机号。</param>

        /// <param name="validateCode">1个输入短信验证码。</param>

        /// <summary>

        /// 【异步通过手机号和短信验证码获取】

        /// <remarks>

        /// 摘要:

        ///    直接从短信验证表中获用户实体的1个指定实例;或从分布式缓存数据库获取短信验证实体的1个指定实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    短信验证实体的1个指定实例。

        /// </returns>

        /// </summary>

        public async Task<SmsValidate> SmsValidateByPhoneAndCode(string phone, string validateCode)

        {

            if (string.IsNullOrWhiteSpace(phone) || string.IsNullOrWhiteSpace(validateCode))

                return null;

            return await _smsValidateRepository.Table

                .Where(x => x.Phone == phone

                && x.Code == validateCode

                && !x.IsValidate

                && x.ExpiryDateTime >= DateTime.Now

                && x.Count < 3).OrderByDescending(x => x.CreatedDateTime)

                .LastOrDefaultAsync();

        }

        /// <param name="smsValidate">短信验证实体的1个指定实例。</param>

        /// <summary>

        /// 【异步插入短信验证】

        /// <remarks>

        /// 摘要:

        ///     把短信验证实体的1个指定实例持久化插入到短信验证表中后,并从缓存数据库中移除与短信验证相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public async Task InsertSmsValidateAsync(SmsValidate smsValidate)

        {

            await _smsValidateRepository.InsertAsync(smsValidate);

        }

        /// <param name="smsValidateList">列表实例,该实例存储着短信验证实体的1/n个指定实例。</param>

        /// <summary>

        /// 【异步插入短信验证】

        /// <remarks>

        /// 摘要:

        ///     把短信验证实体的1/n个指定实例持久化插入到短信验证表中后,并从缓存数据库中移除与短信验证相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public async Task InsertSmsValidateAsync(IList<SmsValidate> smsValidateList)

        {

            await _smsValidateRepository.InsertAsync(smsValidateList);

        }

        /// <param name="smsValidate">短信验证实体的1个指定实例。</param>

        /// <summary>

        /// 【异步更新用户】

        /// <remarks>

        /// 摘要:

        ///     把短信验证实体实体的1个指定实例持久化更新到短信验证表中后,并从缓存数据库中移除与短信验证实体相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public async Task UpdateSmsValidateAsync(SmsValidate smsValidate)

        {

            await _smsValidateRepository.UpdateAsync(smsValidate);

        }

        /// <param name="smsValidateList">列表实例,该实例存储着短信验证实体的1/n个指定实例。</param>

        /// <summary>

        /// 【异步更新短信验证】

        /// <remarks>

        /// 摘要:

        ///     把短信验证实体实体的1/n个指定实例持久化更新到短信验证表中后,并从缓存数据库中移除与短信验证实体相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public async Task UpdateSmsValidateAsync(IList<SmsValidate> smsValidateList)

        {

            await _smsValidateRepository.UpdateAsync(smsValidateList);

        }

    }

}

2 Services.Messages.ISmsValidate

using Core.Domain.Messages;

namespace Services.Messages

{

    /// <summary>

    /// 【短信验证服务--接口】

    /// <remarks>

    /// 摘要:

    ///    通过继承于该接口具体实现类中的方法成员实现当前程序与短信验证表之间的CURD操作。

    /// </remarks>

    /// </summary>

    public interface ISmsValidateService

    {

        /// <param name="phone">1个指定的手机号。</param>

        /// <param name="validateCode">1个输入短信验证码。</param>

        /// <summary>

        /// 【异步短信验证?】

        /// <remarks>

        /// 摘要:

        ///     获取1个值false(不等)/true(相等),该值指示用户输入的短信验证码与持久化存储的短信验证码是否相等,如果相等则用户通过验证可以执行登录操作;反之用户未通过验证不能执行登录操作。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    1个值false(不等)/true(相等)

        /// </returns>

        /// </summary>

       Task<bool> ValidateSmsValidateAsync(string phone, string validateCode);

        /// <param name="phone">1个指定的手机号。</param>

        /// <param name="validateCode">1个输入短信验证码。</param>

        /// <summary>

        /// 【异步通过手机号和短信验证码获取】

        /// <remarks>

        /// 摘要:

        ///    直接从短信验证表中获用户实体的1个指定实例;或从分布式缓存数据库获取短信验证实体的1个指定实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    短信验证实体的1个指定实例。

        /// </returns>

        /// </summary>

        Task<SmsValidate> SmsValidateByPhoneAndCode(string phone, string validateCode);

        /// <param name="smsValidate">短信验证实体的1个指定实例。</param>

        /// <summary>

        /// 【异步插入短信验证】

        /// <remarks>

        /// 摘要:

        ///     把短信验证实体的1个指定实例持久化插入到短信验证表中后,并从缓存数据库中移除与短信验证相关的所有缓存项。

        /// </remarks>

        /// </summary>

        Task InsertSmsValidateAsync(SmsValidate smsValidate);

        /// <param name="smsValidateList">列表实例,该实例存储着短信验证实体的1/n个指定实例。</param>

        /// <summary>

        /// 【异步插入短信验证】

        /// <remarks>

        /// 摘要:

        ///     把短信验证实体的1/n个指定实例持久化插入到短信验证表中后,并从缓存数据库中移除与短信验证相关的所有缓存项。

        /// </remarks>

        /// </summary>

        Task InsertSmsValidateAsync(IList<SmsValidate> smsValidateList);

        /// <param name="smsValidate">短信验证实体的1个指定实例。</param>

        /// <summary>

        /// 【异步更新用户】

        /// <remarks>

        /// 摘要:

        ///     把短信验证实体实体的1个指定实例持久化更新到短信验证表中后,并从缓存数据库中移除与短信验证实体相关的所有缓存项。

        /// </remarks>

        /// </summary>

        Task UpdateSmsValidateAsync(SmsValidate smsValidate);

        /// <param name="smsValidateList">列表实例,该实例存储着短信验证实体的1/n个指定实例。</param>

        /// <summary>

        /// 【异步更新短信验证】

        /// <remarks>

        /// 摘要:

        ///     把短信验证实体实体的1/n个指定实例持久化更新到短信验证表中后,并从缓存数据库中移除与短信验证实体相关的所有缓存项。

        /// </remarks>

        /// </summary>

        Task UpdateSmsValidateAsync(IList<SmsValidate> smsValidateList);

    }

}

3 向Services.Customers.CustomerService添加定义

     /// <param name="phone">1个指定的手机号字符串。</param>

        /// <summary>

        /// 【异步通过手机号获取用户】

        /// <remarks>

        /// 摘要:

        ///    直接从用户表中获用户实体的1个指定实例;或从分布式缓存数据库获取用户实体的1个指定实例(即使该实例处于逻辑删除状态,也获取该实例)

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    用户实体的1个指定实例。

        /// </returns>

        /// </summary>

        public async Task<Customer> GetCustomerByPhoneAsync(string phone)

        {

            if (string.IsNullOrWhiteSpace(phone))

                return null;

            return await _customerRepository.Table.Where(x => x.Phone == phone).FirstOrDefaultAsync();

        }

 /// <param name="phone">1个指定的手机号。</param>

        /// <param name="password">1个将要被加密的密码(明码)字符串。</param>

        /// <summary>

        /// 【异步通过手机号进行用户验证?】

        /// <remarks>

        /// 摘要:

        ///     获取1个值false(不等)/true(相等),该值指示用户输入的密码通过指定的加密方式加密后是否与持久化存储的密码相相等,如果相等则用户通过验证可以执行登录操作;反之用户未通过验证不能执行登录操作。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    1个值false(不等)/true(相等)

        /// </returns>

        /// </summary>

        public async Task<bool> ValidateCustomerByPhoneAsync(string phone, string password)

        {

            Customer _customer = await GetCustomerByPhoneAsync(phone);

            if (_customer == null)

                return false;

            CustomerPassword _customerPassword = await GetCustomerPasswordsAsync(_customer.Id);

            if (!PasswordsMatch(_customerPassword, password))

                return false;

            return true;

        }

4 WebApi.Models.Customer.RegisterModel

namespace WebApi.Models.Customer

{

    /// <summary>

    /// 【用户注册模型--纪录】

    /// <remarks>

    /// 摘要:

    ///     通过该纪录中的属性成员实例存储用于用户注册操作。

    /// </remarks>

    /// </summary>

    public record RegisterModel

    {

        /// <summary>

        /// 【用户名】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定的用户名(账户、昵称)

        /// </remarks>

        /// </summary>

        public string Name { get; set; }

        /// <summary>

        /// 【电子邮箱】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户所对应的电子邮箱。

        /// </remarks>

        /// </summary>

        public string Email { get; set; }

        /// <summary>

        /// 【手机号】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户所对应的手机号。

        /// </remarks>

        /// </summary>

        public string Phone { get; set; }

        /// <summary>

        /// 【密码】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个用于登录操作的密码。

        /// </remarks>

        /// </summary>

        public string Password { get; set; }

    }

}

5 WebApi.Models.Customer.LoginSmsModel

namespace WebApi.Models.Customer

{

    /// <summary>

    /// 【短信验证登录模型--纪录】

    /// <remarks>

    /// 摘要:

    ///     通过该纪录中的属性成员实例存储用于短信验证登录操作账户名、密码及其验证码。

    /// </remarks>

    /// </summary>

    public record LoginSmsModel

    {

        /// <summary>

        /// 【手机号】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个用于短信验证登录操作的手机号。

        /// </remarks>

        /// </summary>

        public string Phone { get; set; }

        /// <summary>

        /// 【密码】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个用于短信验证登录操作的密码。

        /// </remarks>

        /// </summary>

        public string Password { get; set; }

        /// <summary>

        /// 【短信验证码】

        /// <remarks>

        /// 摘要:

        ///     获取/设置向1个指定手机所发送的1个指定短信验证码。

        /// </remarks>

        /// </summary>

        public string Code { get; set; }

    }

}

6 WebApi.Controllers.CustomerController添加定义

/// <param name="phone">1个指定的手机号。</param>

        /// <summary>

        /// 【异步短信验证码发送】

        /// </summary>

        /// <remarks>

        /// 摘要:

        ///     通过第3方短信验证码发送服务平台,向1个指定的手机中发送1个指定的短信验证码后,把短信验证码实体的1个指定实例持久化到短信验证码表中。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定的手机中所发送的1个指定的短信验证码。

        /// </returns>

        private async Task<int> SmsValidateSendAsync(string phone)

        {

            //从单例实例的字典成员实例中获取当前程序所有配置相关数据。

            var appSettings = Singleton<AppSettings>.Instance;

            //从应用配置类实例中获取分布式缓存连接相关数据。

            SmsConfig _smsConfig = appSettings.Get<SmsConfig>();

            using var random = new SecureRandomNumberGenerator();

            int _code = random.Next(100000, 999999);

            Config config = new Config

            {

                AccessKeyId = _smsConfig.AccessKeyId,

                AccessKeySecret = _smsConfig.AccessKeySecret,

            };

            config.Endpoint = _smsConfig.Endpoint;

            Client _client = new Client(config);

            IDictionary<string, string> data = new Dictionary<string, string>

            {

                { "code", _code.ToString() }

            };

            string _templateParam = JsonConvert.SerializeObject(data);

            SendSmsRequest _sendSmsRequest = new SendSmsRequest

            {

                PhoneNumbers = phone,

                SignName = _smsConfig.SignName,

                TemplateCode = _smsConfig.TemplateCode,

                TemplateParam = _templateParam,  //验证码信息

            };

            SendSmsResponse _sendSmsResponse = await _client.SendSmsAsync(_sendSmsRequest);

            if (_sendSmsResponse.StatusCode==200)

            {

                SmsValidate _smsValidate = new SmsValidate()

                {

                    Phone = phone,

                    Content = $"您验证码是{ _code},请不要将验证码泄露给他人!",

                    Code = _code.ToString(),

                    Count = 0,

                    CreatedDateTime = DateTime.Now,

                    ExpiryDateTime = DateTime.Now.AddMinutes(5),

                };

                await _smsValidateService.InsertSmsValidateAsync(_smsValidate);

                return _code;

            }

            return 0;

        }

        /// <param name="phone">1个指定的手机号。</param>

        /// <summary>

        /// 【新建短信验证码发送--无需权限】

        /// </summary>

        /// <remarks>

        /// 摘要:

        ///     通过第3方短信验证码发送服务平台,向1个指定的手机中发送1个指定的短信验证码后,把短信验证码实体的1个指定实例持久化到短信验证码表中。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定的手机中所发送的1个指定的短信验证码。

        /// </returns>

        [HttpPost]

        public async Task<MessageModel<string>> SmsValidateCreate([FromBody]  string phone)

        {

            int _code = await SmsValidateSendAsync(phone);

            if (_code>= 100000 && _code <=999999)

                return MessageModel<string>.GetSuccess($"验证码:{ _code}已经成功发送到手机,请在5分钟内使用手机中的验证码进行登录!", _code.ToString());

            return MessageModel<string>.Fail("验证码发送失败!", 500);

        }

        /// <param name="registerModel">用户注册记录的1个指定实例。</param>

        /// <summary>

        /// 【用户注册--无需权限】

        /// </summary>

        /// <remarks>

        /// 摘要:

        ///     通过用户注册操作把1个指定用户持久化到用户注册表中。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     用户实体的1个指定实例。

        /// </returns>

        [HttpPost]

        public virtual async Task<MessageModel<Customer>> Register([FromBody] RegisterModel registerModel)

        {

            Customer _customer = new Customer() {

                Name = registerModel.Name,

                Email = registerModel.Email,

                Phone = registerModel.Phone,

                CreatedDateTime = DateTime.Now,

                UpdatedDateTime = DateTime.Now,

            };

            await _customerService.InsertCustomerAsync(_customer);

             CustomerPassword _customerPassword = new CustomerPassword

             {

                 CustomerId = _customer.Id,

                 PasswordFormat = PasswordFormat.Hashed,

             };

             var saltKey = _encryptionService.CreateSaltKey(CustomerPassword.PasswordSaltKeySize);

             _customerPassword.PasswordSalt = saltKey;

             _customerPassword.Password = _encryptionService.CreatePasswordHash(registerModel.Password, saltKey, CustomerPassword.DefaultHashedPasswordFormat);

             await _customerService.InsertCustomerPasswordAsync(_customerPassword);

             await _customerService.AddCustomerRoleAsync(new CustomerRole { CustomerId = _customer.Id, RoleId = 2});

            int _code = await SmsValidateSendAsync(registerModel.Phone);

            //注意:由于该用户实例有512位的密码,会造成异常:“This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.”

            if (_code >= 100000 && _code <= 999999)

                 return MessageModel<Customer>.GetSuccess($"用户{ registerModel.Phone}成功注册,请在5分钟内使用手机中的验证码进行登录对当前注册进行激活操作!", _customer);

            return MessageModel<Customer>.Fail($"用户{ registerModel.Phone}成功注册失败!", 500);

        }

        /// <param name="email">1个指定的电子邮箱。</param>

        /// <summary>

        /// 【电子邮箱已被注册?--无需权限】

        /// </summary>

        /// <remarks>

        /// 摘要:

        ///      获取1个值false(未被注册)/true(已被注册),该值指示1个指定的电子邮箱是否已被注册,为用户注册和登录操作提供数据支撑。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///      1个值false(未被注册)/true(已被注册)

        /// </returns>

        [HttpGet]

        public virtual async Task<MessageModel<bool>> IsEmail(string email)

        {

            Customer _customer = await _customerService.GetCustomerByEmailAsync(email);

            if (_customer == null)

                return MessageModel<bool>.Fail($"指定电邮箱:{ email}不存在!", 500);

            return MessageModel<bool>.GetSuccess($"成功获取指定电邮箱:{ email}", true);

        }

        /// <param name="phone">1个指定的手机号。</param>

        /// <summary>

        /// 【手机号已被注册?--无需权限】

        /// </summary>

        /// <remarks>

        /// 摘要:

        ///      获取1个值false(未被注册)/true(已被注册),该值指示1个指定的手机号是否已被注册,为用户注册和登录操作提供数据支撑。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///      1个值false(未被注册)/true(已被注册)

        /// </returns>

        [HttpGet]

        public virtual async Task<MessageModel<bool>> IsPhone(string phone)

        {

            Customer _customer = await _customerService.GetCustomerByPhoneAsync(phone);

            if (_customer == null)

                return MessageModel<bool>.Fail($"指定手机号:{ phone}不存在!", 500);

            return MessageModel<bool>.GetSuccess($"成功获取指定手机号:{ phone}", true);

        }

        /// <param name="phone">1个指定的手机号。</param>

        /// <param name="code">1个指定的短信验证码。</param>

        /// <summary>

        /// 【短信验证码有有效?--无需权限】

        /// </summary>

        /// <remarks>

        /// 摘要:

        ///      获取 1个值false(无效)/true(有效),该值指示1个指定的短信验证码是否还有效,为登录操作提供数据支撑。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///       1个值false(无效)/true(有效)

        /// </returns>

        [HttpGet]

        public virtual async Task<MessageModel<bool>> IsCode(string phone, string code)

        {

            if (await _smsValidateService.ValidateSmsValidateAsync(phone, code))

                return MessageModel<bool>.GetSuccess($"成功获取指定短信验证码:{ code}", true);

            return MessageModel<bool>.Fail($"指定短信验证码:{ code}不存在!", 500);

        }

 /// <param name="loginSms">短信验证登录模型记录的1个指定实例。</param>

        /// <summary>

        /// 【短信验证登录--无需权限】

        /// </summary>

        /// <remarks>

        /// 摘要:

        ///     通过登录操作获取1个指定用户的1个指定令牌(Token)字符串实例,为访问指定权限的Api提供数据支撑。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定用户的1个指定令牌(Token)字符串实例。

        /// </returns>

        [HttpPost]

        public async Task<MessageModel<TokenViewModel>> LoginSms([FromBody] LoginSmsModel loginSms)

        {

            if (await _customerService.ValidateCustomerByPhoneAsync(loginSms.Phone, loginSms.Password))

            {

                if(await _smsValidateService.ValidateSmsValidateAsync(loginSms.Phone, loginSms.Code))

                {

                    Customer _customer = await _customerService.GetCustomerByPhoneAsync(loginSms.Phone);

                    //激活注册用户。

                    _customer.IsActive = true;

                    _customer.UpdatedDateTime = DateTime.Now;

                    await _customerService.UpdateCustomerAsync(_customer);

                    SmsValidate _smsValidate = await _smsValidateService.SmsValidateByPhoneAndCode(loginSms.Phone, loginSms.Code);

                    _smsValidate.IsValidate = true;

                    await _smsValidateService.UpdateSmsValidateAsync(_smsValidate);

                    string _roleList = await _customerService.GetRoleNameByCustomerIdAsync(_customer.Id);

                    var claims = new List<Claim> {

                        new Claim(ClaimTypes.Email, _customer.Email),

                        new Claim(JwtRegisteredClaimNames.Jti, _customer.Id.ToString()),

                        new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(TimeSpan.FromSeconds(60 * 60).TotalSeconds).ToString()) };

                    claims.AddRange(_roleList.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));

                    //从单例实例的字典成员实例中获取当前程序所有配置相关数据。

                    AppSettings _appSettings = Singleton<AppSettings>.Instance;

                    //从应用配置类实例中获取JwtBearer身份认证相关数据。

                    JwtBearerConfig _jwtBearerConfig = _appSettings.Get<JwtBearerConfig>();

                    //获取秘钥。

                    SymmetricSecurityKey _symmetricSecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_jwtBearerConfig.SecretKey));

                    //通过1个指定的加密算法名称,实例化。

                    SigningCredentials _signingCredentials = new SigningCredentials(_symmetricSecurityKey, SecurityAlgorithms.HmacSha256);

                    JwtSecurityToken _jwtSecurityToken = new JwtSecurityToken(

                        issuer: _jwtBearerConfig.Issuer,

                        audience: _jwtBearerConfig.Audience,

                        claims: claims,

                        notBefore: DateTime.Now,

                        expires: DateTime.Now.Add(TimeSpan.FromSeconds(60 * 60)),

                        signingCredentials: _signingCredentials

                    );

                    string _token = new JwtSecurityTokenHandler().WriteToken(_jwtSecurityToken);

                    TokenViewModel _tokenViewModel = new TokenViewModel

                    {

                        Success = true,

                        Token = _token,

                        ExpiresIn = TimeSpan.FromSeconds(60 * 60).TotalSeconds,

                        TokenType = "Bearer"//基于JwtBearer身份认证方式。

                    };

                    return MessageModel<TokenViewModel>.GetSuccess("成功登录!", _tokenViewModel);

                }

               

            }

            return MessageModel<TokenViewModel>.Fail("登录失败!", 500);

        }

7 循环引用异常:“This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.”

解决方案:

      //忽略循环引用,解决异常:“This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.”

            services.AddControllers().AddNewtonsoftJson(option =>

                option.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore

            );

对以上功能更为具体实现和注释见:230304_043shopDemo(短信验证服务和登录的后端定义实现)。

猜你喜欢

转载自blog.csdn.net/zhoujian_911/article/details/129337658