当前位置: 首页 > news >正文

第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(短信验证服务和登录的后端定义实现)。

相关文章:

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

1 Services.Messages.SmsValidate using Core.Domain.Messages; using Data; using Microsoft.EntityFrameworkCore; namespace Services.Messages { /// <summary> /// 【短信验证服务--类】 /// <remarks> /// 摘要&#xff1a; /// 通过类中的方法成员实…...

谷歌验证码的使用

1. 表单重复提交之验证码 1.1 表单重复提交三种常见情况 提交完表单。服务器使用请求转来进行页面跳转。这个时候&#xff0c;用户按下功能键 F5&#xff0c;就会发起最后一次的请求。造成表单重复提交问题。解决方法&#xff1a;使用重定向来进行跳转用户正常提交服务器&…...

Git学习入门(1)- git的安装与配置

title: git学习&#xff08;1&#xff09; - git的安装与配置CSDN: https://blog.csdn.net/jj6666djdbbd?typeblogBlog: https://helloylh.comGithub: https://github.com/luumodtags: gitabbrlink: 12001description: 本文主要讲解了git的安装&#xff0c;配置基本工作date: …...

【Python】使用Playwright断言方法验证网页和Web应用程序状态

作为测试框架&#xff0c;Playwright 提供了一系列断言方法&#xff0c;您可以使用它们来验证网页和 Web 应用程序的状态。在这篇博客中&#xff0c;田辛老师将介绍 Playwright 中可用的各种断言方法&#xff0c;并为每种方法提供示例。 assert page.url() expected_url &…...

libgdx导入blender模型

具体就是参考 官网 https://libgdx.com/wiki/graphics/3d/importing-blender-models-in-libgdx blender 教程可以看八个案例教程带你从0到1入门blender【已完结】 这里贴一下过程图。 1.初始环境搭建略过。 2.打开blender 选中摄像机和灯光&#xff0c;右键进行删除。 3.选中…...

【20230227】回溯算法小结

回溯法又叫回溯搜索法&#xff0c;是搜索的一种方式。回溯法本质是穷举所有可能。如果想让回溯法高效一些&#xff0c;可以加一些剪枝操作。回溯算法解决的经典问题&#xff1a;组合问题切割问题子集问题排列问题棋盘问题如何去理解回溯法&#xff1f;回溯法解决的问题都可以抽…...

centos安装rocketmq

centos安装rocketmq1 下载rocketmq二进制包2 解压二进制包3 修改broker.conf4 修改runbroker.sh和runserver.sh的JVM参数5 启动NameServer和Broker6 安装rockermq dashboard(可视化控制台)1 下载rocketmq二进制包 点击rocketmq二进制包下载地址&#xff0c;下载完成之后通过ft…...

汇编语言程序设计(二)之寄存器

系列文章 汇编语言程序设计&#xff08;一&#xff09; 寄存器 在学习汇编的过程中&#xff0c;我们经常需要操作寄存器&#xff0c;那么寄存器又是什么呢&#xff1f;它是用来干什么的&#xff1f; 它有什么分类&#xff1f;又该如何操作&#xff1f;… 你可能会有许多的…...

华为OD机试Golang解题 - 单词接龙 | 独家

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典文章目录 华为Od必看系列使用说明本期题目…...

Elasticsearch的搜索命令

Elasticsearch的搜索命令 文章目录Elasticsearch的搜索命令数据准备URI Searchq&#xff08;查询字符串&#xff09;analyzer&#xff08;指定查询字符串时使用的分析器&#xff09;df&#xff08;指定查询字段&#xff09;_source&#xff08;指定返回文档的字段&#xff09;s…...

为什么人们宁可用Lombok,也不把成员设为public?

目录专栏导读一、从零了解JavaBean1、基本概念2、JavaBean的特征3、JavaBean的优点二、定义最简单的JavaBean三、思考一个问题&#xff0c;为何属性是private&#xff0c;然后用get/set方法&#xff1f;四、下面系统的分析以下&#xff0c;why?五、不和谐的声音&#xff0c;禁…...

【Redis】Redis 如何实现分布式锁

Redis 如何实现分布式锁1. 什么是分布式锁1.1 分布式锁的特点1.2 分布式锁的场景1.3 分布式锁的实现方式2. Redis 实现分布式锁2.1 setnx expire2.2 set ex px nx2.3 set ex px nx 校验唯一随机值&#xff0c;再删除2.4 Redisson 实现分布式锁1. 什么是分布式锁 分布式锁其实…...

C++ 断言

文章目录前言assertstatic_assert前言 断言(Assertion)是一种常用的编程手段&#xff0c;用于排除程序中不应该出现的逻辑错误。它是一种很好的Debug工具。其作用是判断表达式是否为真。C提供了assert和static_assert来进行断言。在C库中也有断言&#xff0c;其中断言与C的相同…...

C++修炼之练气期第五层——引用

目录 1.引用的概念 2.引用的性质 3.常量引用 4.使用场景 1.作参数 2.作返回值 5.传值与传引用的效率比较 6.值和引用作为返回值的性能比较 7.引用与指针 指针与引用的不同点 要说C语言中哪个知识点最难学难懂&#xff0c;大部分人可能和我一样的答案——指针。C既然…...

从企业数字化发展的四个阶段,看数字化创新战略

《Edge: Value-Driven Digital Transformation》一书根据信息技术与企业业务发展的关系把企业的数字化分为了四个阶段&#xff1a; 技术与业务无关技术作为服务提供者开始合作科技引领差异化优势以技术为业务核心 下图展示了这四个阶段的特点&#xff1a; 通过了解和分析各个…...

vulnhub five86-1

总结:私钥登录&#xff0c;隐藏文件很多 目录 下载地址 漏洞分析 信息收集 网站渗透 爆破密码 提权 下载地址 Five86-1.zip (Size: 865 MB)Download (Mirror): https://download.vulnhub.com/five86/Five86-1.zip使用&#xff1a;下载以后打开压缩包&#xff0c;使用vm直…...

28个案例问题分析---01---redis没有及时更新问题--Redis

redis没有及时更新问题一&#xff1a;背景介绍二&#xff1a;前期准备pom依赖连接Redis工具类连接mysql工具类三&#xff1a;过程使用redis缓存&#xff0c;缓存用户年龄业务对应流程图使用redis缓存用户年龄对应代码四&#xff1a;总结一&#xff1a;背景介绍 业务中使用redis…...

[1.3_3]计算机系统概述——系统调用

文章目录第一章 计算机系统概述系统调用&#xff08;一&#xff09;什么是系统调用&#xff0c;有何作用&#xff08;二&#xff09;系统调用与库函数的区别&#xff08;三&#xff09;小例子&#xff1a;为什么系统调用是必须的&#xff08;四&#xff09;什么功能要用到系统调…...

Vue基础学习 第一个Vue程序 el挂载点 v-指令(1)

Vue简介 Vue是一个Javascript框架Vue框架可以简化Dom操作响应式数据驱动 &#xff1a; 页面是由数据生成的&#xff0c;当数据出现改动&#xff0c;页面也会即时改变 第一个Vue程序 Vue中文文档官网&#xff1a;https://v2.cn.vuejs.org/v2/guide/ 根据官方文档的说法&#…...

前端页面性能

提升页面性能的方法资源压缩合并&#xff0c;减少HTTP请求非核心代码异步加载异步加载方式&#xff1f;1)动态脚本加载、2)defer、3)async&#xff08;在加载js的时候在script标签上添加这两个属性,<script src"./test.js" charset"utf-8" defer><…...

2023-03-04 反思

摘要: 当前的时期确实比较特殊&#xff0c;不但是对于一个生命周期的最后的挣扎&#xff0c;更是在经历了各种浮浮沉沉的波澜之后还有更多的波浪。 精神分析-GRY: 非常奇怪的一个跳梁小丑, 不过我个人认为用这个标签是对跳梁小丑的侮辱和上层管理者对于这种人的纵容有很大关系…...

奇思妙想:超链接唤起本地应用

文章目录分析实现参考很多人的博客都有这样的小玩意&#xff0c;点击之后就可以直接与博主进行对话&#xff0c;而且无需添加好友。 先研究一下网页源代码&#xff1a; <a href"tencent://message/?uin88888888&Siteqq&Menuyes">联系我</a>很明…...

初识数据结构——“数据结构与算法”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰进入一个全新的内容的学习&#xff0c;就是算法和数据结构啦&#xff0c;话不多说&#xff0c;让我们进入数据结构的世界吧 什么是数据结构&#xff1f; 什么是算法&#xff1f; 数据结构和算法的重要性 如何学好数据结构和算…...

华为OD机试Golang解题 - 计算网络信号

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典文章目录 华为Od必看系列使用说明本期题目…...

ESP32编译及运行错误记录

1、打印格式不对 一般都是因为日志中某个参数打印格式不匹配造成。 ESP_LOGI(TAG, "[APP] Free memory: %lu bytes", esp_get_free_heap_size());//将之前的%d 改为%lu 2、配置载不对 这里选择了蓝牙模块需要引入蓝牙组件才能编译通过 idf.py menuconfig Component…...

GEE开发之降雨(CHIRPS)数据获取和分析

GEE开发之降雨CHIRPS数据获取和分析1.数据介绍2.初识CHIRPS2.1 代码一2.2 代码二3.逐日数据分析和获取4.逐月数据分析和获取4.1 代码一4.2 代码二(简洁)5.逐年数据分析和获取5.1 代码一5.2 代码二(简洁)前言&#xff1a;主要获取和分析UCSB-CHG/CHIRPS/DAILY的日数据、月数据和…...

TypeScript中面向对象

面向对象 要想面向对象&#xff0c;操作对象&#xff0c;首先便要拥有对象&#xff1b; 要创建对象&#xff0c;必须要先定义类&#xff0c;所谓的类可以理解为对象的模型&#xff1b; 程序中可以根据类创建指定类型的对象&#xff1b; 举例来说&#xff1a; 可以通过Perso…...

Transformer 模型:入门详解(1)

动动发财的小手&#xff0c;点个赞吧&#xff01; 简介 众所周知&#xff0c;transformer 架构是自然语言处理 (NLP) 领域的一项突破。它克服了 seq-to-seq 模型&#xff08;如 RNN 等&#xff09;无法捕获文本中的长期依赖性的局限性。事实证明&#xff0c;transformer 架构是…...

深入理解js中的new关键字

在js中我们经常会使用到new关键字&#xff0c;那我们在使用new关键字的时候&#xff0c;new到底做了什么呢&#xff1f;今天我们就来深入探究一下 1.初步使用 我们先来使用一下&#xff0c;这是一个正常操作 function Person() {this.name "John";}let person new…...

RT-Thread Nano(2) - 线程

参考:RT-Thread API参考手册: 线程管理 线程的分类:动态线程,静态线程 动态线程是系统自动从动态内存堆上分配栈空间的线程句柄(程序运行时再分配空间),静态线程是由用户分配栈空间与线程句柄(可以说是程序编译时已经分配好空间) 1.创建线程 创建一个动态线程 rt_thread_t …...