public virtual async Task BeforeSendAsync(string type, string receiver)
        {
            var exist = await CaptchaStore.FindAsync(type, receiver, CurrentTenant.Id);

            if (exist == null)
            {
                return;
            }
            int expireSeconds = await SettingProvider.GetAsync(AbpCaptchaSettings.CaptchaFrequencyLimitSeconds, 60);

            if (exist.CreationTime.AddSeconds(expireSeconds) > Clock.Now)//检查是否超过请求频率限制
            {
                throw new BusinessException(CaptchaErrorCodes.FrequencyLimit);
            }
        }
        public virtual async Task SendAsync(string type, string receiver)
        {
            await BeforeSendAsync(type, receiver);

            var code = await CaptchaGenerator.CreateAsync();

            var receiverType = receiver.Contains("@") ? EnumReceiverType.Email : EnumReceiverType.PhoneNumber;

            string expireSecondSettingName = receiverType == EnumReceiverType.Email ? AbpCaptchaSettings.EmailCaptchaExpireSeconds : AbpCaptchaSettings.SmsCaptchaExpireSeconds;
            int    expireSeconds           = await SettingProvider.GetAsync(expireSecondSettingName, 60);

            var captcha = new Captcha(type, code, receiver, receiverType, Clock.Now, expireSeconds, CurrentTenant.Id);

            bool sendResult = receiverType == EnumReceiverType.Email
                ? await EmailCaptchaSender.SendAsync(captcha)
                : await SmsCaptchaSender.SendAsync(captcha);

            if (!sendResult)
            {
                throw new BusinessException(CaptchaErrorCodes.SendFailed);
            }
            await CaptchaStore.CreateAsync(captcha);
        }
        public virtual async Task VerifyAsync(string type, string receiver, string code)
        {
            var captcha = await CaptchaStore.FindAsync(type, receiver, CurrentTenant.Id);

            if (captcha == null)
            {
                throw new BusinessException(CaptchaErrorCodes.Error);
            }
            if (captcha.Code != code)
            {
                throw new BusinessException(CaptchaErrorCodes.Error);
            }
            if (captcha.IsUsed)
            {
                throw new BusinessException(CaptchaErrorCodes.Used);
            }

            if (captcha.IsExpire(Clock.Now))
            {
                throw new BusinessException(CaptchaErrorCodes.Expired);
            }
            captcha.Use(Clock.Now);
            await CaptchaStore.UsedAsync(captcha);
        }