欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Redis驗(yàn)證碼發(fā)送及校驗(yàn)方案實(shí)現(xiàn)

 更新時(shí)間:2023年01月04日 15:09:44   作者:娃都會(huì)打醬油了  
本文主要介紹了基于Redis驗(yàn)證碼發(fā)送及校驗(yàn)方案實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

在我們的業(yè)務(wù)中,經(jīng)常存在需要通過發(fā)送驗(yàn)證碼、校驗(yàn)驗(yàn)證碼來完成的一些業(yè)務(wù)邏輯,比如賬號(hào)注冊(cè)、找回密碼、用戶身份確認(rèn)等。

在該類業(yè)務(wù)中,發(fā)送驗(yàn)證碼的方式可以有各種各樣,比如最常見的手機(jī)驗(yàn)證,最古老的郵箱驗(yàn)證,到現(xiàn)在相對(duì)少見的微信公眾號(hào)、釘釘通知等;而驗(yàn)證碼服務(wù)端存儲(chǔ)的方式也可以各式各樣,比如存儲(chǔ)在關(guān)系型數(shù)據(jù)庫(kù)中,當(dāng)然也可以如本文標(biāo)題所示,存儲(chǔ)在Redis中。

既然已經(jīng)預(yù)見到了各式各樣的發(fā)送方式,也預(yù)見到了各式各樣的存儲(chǔ)方式,所以,雖然本文標(biāo)題是基于Redis,但Redis其實(shí)只是其中的一種存儲(chǔ)方式,如果需要,我們也應(yīng)該可以和方便的切換到其它存儲(chǔ)方式。

上代碼前,我們先看下設(shè)計(jì)中的接口關(guān)系

在這里插入圖片描述

ICodeHelper是最終提供發(fā)送驗(yàn)證碼和校驗(yàn)驗(yàn)證碼的最終接口,其關(guān)聯(lián)了ICodeSenderICodeStorage,ICodeSender即為驗(yàn)證碼發(fā)送方式的約定接口,ICodeStorage則為驗(yàn)證碼服務(wù)端持久化方式的約定接口。我們可以看到ICodeSender同樣關(guān)聯(lián)了IContentFormatter,因?yàn)樽鳛榘l(fā)送方ICodeSender其實(shí)是不知道如何將要發(fā)送的內(nèi)容組織成一段完整的文本內(nèi)容的,這時(shí)候就需要IContentFormatter來組織文本內(nèi)容,至于繼承自IContentFormatterIComplexContentFormatter,則只是IContentFormatter一個(gè)容器封裝,畢竟對(duì)于不同的業(yè)務(wù)類型,我們需要組織成不同的文本內(nèi)容,通過IComplexContentFormatter,我們可以將不同業(yè)務(wù)類型文本內(nèi)容的組織過程,分散到不同的IContentFormatter中。

下面我們來看下上述接口的規(guī)范約定,考慮到代碼的簡(jiǎn)便性,此處我們簡(jiǎn)單的將receiver接收方定義為了string,而不是泛型<T>;業(yè)務(wù)標(biāo)志bizFlag為了方便接入時(shí)無(wú)需調(diào)整代碼,所以此處也沒有將該值定義為枚舉,而是同樣定義成了通用性最強(qiáng)的string

ICodeStorage

    /// <summary>
    /// 校驗(yàn)碼信息存儲(chǔ)接口
    /// </summary>
    public interface ICodeStorage
    {
        /// <summary>
        /// 將校驗(yàn)碼進(jìn)行持久化,如果接收方和業(yè)務(wù)標(biāo)志組合已經(jīng)存在,則進(jìn)行覆蓋
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="effectiveTime">校驗(yàn)碼有效時(shí)間范圍</param>
        /// <returns></returns>
        Task<bool> SetCode(string receiver, string bizFlag, string code, TimeSpan effectiveTime);
        /// <summary>
        /// 校驗(yàn)碼錯(cuò)誤次數(shù)+1,如果校驗(yàn)碼已過期,則不進(jìn)行任何操作
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <returns></returns>
        Task IncreaseCodeErrors(string receiver, string bizFlag);
        /// <summary>
        /// 校驗(yàn)碼發(fā)送次數(shù)周期持久化,如果接收方和業(yè)務(wù)標(biāo)志組合已經(jīng)存在,則進(jìn)行覆蓋
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="period">周期時(shí)間范圍</param>
        /// <returns></returns>
        Task<bool> SetPeriod(string receiver, string bizFlag, TimeSpan? period);
        /// <summary>
        /// 校驗(yàn)碼周期內(nèi)發(fā)送次數(shù)+1,如果周期已到,則不進(jìn)行任何操作
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <returns></returns>
        Task IncreaseSendTimes(string receiver, string bizFlag);
        /// <summary>
        /// 獲取校驗(yàn)碼及已嘗試錯(cuò)誤次數(shù),如果校驗(yàn)碼不存在或已過期,則返回null
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <returns></returns>
        Task<Tuple<string, int>> GetEffectiveCode(string receiver, string bizFlag);
        /// <summary>
        /// 獲取校驗(yàn)碼周期內(nèi)已發(fā)送次數(shù),如果周期已到或未發(fā)送過任何驗(yàn)證碼,則返回0
        /// </summary>
        /// <param name="receiver"></param>
        /// <param name="bizFlag"></param>
        /// <returns></returns>
        Task<int> GetAreadySendTimes(string receiver, string bizFlag);
    }

ICodeSender,請(qǐng)注意IsSupport方法約定。

    /// <summary>
    /// 校驗(yàn)碼實(shí)際發(fā)送接口
    /// </summary>
    public interface ICodeSender
    {
        /// <summary>
        /// 發(fā)送校驗(yàn)碼內(nèi)容模板
        /// </summary>
        IContentFormatter Formatter { get; }
        /// <summary>
        /// 判斷接收者是否符合發(fā)送條件,例如當(dāng)前發(fā)送者只支持郵箱,而接收方為手機(jī)號(hào),則返回結(jié)果應(yīng)當(dāng)為false
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <returns></returns>
        bool IsSupport(string receiver);
        /// <summary>
        /// 發(fā)送校驗(yàn)碼信息
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="effectiveTime">校驗(yàn)碼有效時(shí)間范圍</param>
        /// <returns></returns>
        Task<bool> Send(string receiver, string bizFlag, string code, TimeSpan effectiveTime);
    }

IContentFormatter

    /// <summary>
    /// 發(fā)送校驗(yàn)碼內(nèi)容模板接口
    /// </summary>
    public interface IContentFormatter
    {
        /// <summary>
        /// 將指定參數(shù)組織成待發(fā)送的文本內(nèi)容
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="effectiveTime">校驗(yàn)碼有效時(shí)間范圍</param>
        /// <returns></returns>
        string GetContent(string receiver, string bizFlag, string code, TimeSpan effectiveTime);
    }

IComplexContentFormatter

    /// <summary>
    /// 基于業(yè)務(wù)標(biāo)志的多內(nèi)容模板
    /// </summary>
    public interface IComplexContentFormatter : IContentFormatter
    {
        /// <summary>
        /// 設(shè)置指定業(yè)務(wù)對(duì)應(yīng)的內(nèi)容模板
        /// </summary>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="formatter">內(nèi)容模板</param>
        void SetFormatter(string bizFlag, IContentFormatter formatter);
        /// <summary>
        /// 移除指定業(yè)務(wù)對(duì)應(yīng)的內(nèi)容模板,如果沒有,則返回null
        /// </summary>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <returns></returns>
        IContentFormatter RemoveFormatter(string bizFlag);
    }

ICodeHelper

    /// <summary>
    /// 業(yè)務(wù)校驗(yàn)碼輔助接口
    /// </summary>
    public interface ICodeHelper
    {
        /// <summary>
        /// 校驗(yàn)碼實(shí)際發(fā)送者
        /// </summary>
        ICodeSender Sender { get; }
        /// <summary>
        /// 校驗(yàn)碼信息存儲(chǔ)者
        /// </summary>
        ICodeStorage Storage { get; }
        /// <summary>
        /// 發(fā)送校驗(yàn)碼
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="effectiveTime">校驗(yàn)碼有效時(shí)間范圍</param>
        /// <param name="maxSendLimit">周期內(nèi)最大允許發(fā)送配置,為null則表示無(wú)限制</param>
        /// <returns></returns>
        Task<SendResult> SendCode(string receiver, string bizFlag, string code, TimeSpan effectiveTime, PeriodLimit maxSendLimit);
        /// <summary>
        /// 驗(yàn)證校驗(yàn)碼是否正確
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="maxErrorLimit">最大允許錯(cuò)誤次數(shù)</param>
        /// <returns></returns>
        Task<VerificationResult> VerifyCode(string receiver, string bizFlag, string code, int maxErrorLimit);
    }

下面則是接口約定中的一些定義的類和枚舉。

    /// <summary>
    /// 校驗(yàn)碼發(fā)送周期設(shè)置
    /// </summary>
    public class PeriodLimit
    {
        /// <summary>
        /// 周期內(nèi)允許的最大次數(shù)
        /// </summary>
        public int MaxLimit { get; set; }
        /// <summary>
        /// 周期時(shí)間,如果不設(shè)置,則表示無(wú)周期,此時(shí)<see cref="MaxLimit"/>代表總共只允許發(fā)送多少次
        /// </summary>
        public TimeSpan? Period { get; set; }
    }
    /// <summary>
    /// 校驗(yàn)碼發(fā)送結(jié)果
    /// </summary>
    public enum SendResult
    {
        /// <summary>
        /// 發(fā)送成功
        /// </summary>
        [Description("成功")]
        Success = 0,
        /// <summary>
        /// 超出最大發(fā)送次數(shù)
        /// </summary>
        [Description("超出最大發(fā)送次數(shù)")]
        MaxSendLimit = 11,
        /// <summary>
        /// 發(fā)送失敗,指<see cref="ICodeSender"/>的發(fā)送結(jié)果為false
        /// </summary>
        [Description("發(fā)送失敗")]
        FailInSend = 12,
        /// <summary>
        /// 無(wú)法發(fā)送,<see cref="ICodeSender.IsSupport(string)"/>結(jié)果為false
        /// </summary>
        [Description("無(wú)法發(fā)送")]
        NotSupprot = 13,
    }
    /// <summary>
    /// 校驗(yàn)碼校驗(yàn)結(jié)果
    /// </summary>
    public enum VerificationResult
    {
        /// <summary>
        /// 校驗(yàn)成功
        /// </summary>
        [Description("成功")]
        Success = 0,
        /// <summary>
        /// 校驗(yàn)碼已過期
        /// </summary>
        [Description("校驗(yàn)碼已過期")]
        Expired = 31,
        /// <summary>
        /// 校驗(yàn)碼不一致,校驗(yàn)失敗
        /// </summary>
        [Description("校驗(yàn)失敗")]
        VerificationFailed = 32,
        /// <summary>
        /// 已經(jīng)達(dá)到了最大錯(cuò)誤嘗試次數(shù),需重新發(fā)送新的校驗(yàn)碼
        /// </summary>
        [Description("超出最大錯(cuò)誤次數(shù)")]
        MaxErrorLimit = 33,
    }

再下來就是具體的接口實(shí)現(xiàn)了,當(dāng)然這些實(shí)現(xiàn)也是通用實(shí)現(xiàn)

ContentFormatter

    /// <summary>
    /// 通用的內(nèi)容模板
    /// </summary>
    public class ContentFormatter : IContentFormatter
    {
        private Func<string, string, string, TimeSpan, string> _func;
        /// <summary>
        /// 通用實(shí)現(xiàn),這樣就無(wú)需每種業(yè)務(wù)類型都要實(shí)現(xiàn)<see cref="IContentFormatter"/>
        /// </summary>
        /// <param name="func">傳遞的委托,參數(shù)順序與<see cref="GetContent(string, string, string, TimeSpan)"/>一致</param>
        public ContentFormatter(Func<string, string, string, TimeSpan, string> func)
        {
            this._func = func ?? throw new ArgumentNullException(nameof(func));
        }
        /// <summary>
        /// 將指定參數(shù)組織成待發(fā)送的文本內(nèi)容
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="effectiveTime">校驗(yàn)碼有效時(shí)間范圍</param>
        /// <returns></returns>
        public string GetContent(string receiver, string bizFlag, string code, TimeSpan effectiveTime)
        {
            return this._func.Invoke(receiver, bizFlag, code, effectiveTime);
        }
    }

ComplexContentFormatter

    using System.Collections.Concurrent;
    /// <summary>
    /// 基于業(yè)務(wù)標(biāo)志的多內(nèi)容模板實(shí)現(xiàn)
    /// </summary>
    public class ComplexContentFormatter : IComplexContentFormatter
    {
        private ConcurrentDictionary<string, IContentFormatter> _dic = new ConcurrentDictionary<string, IContentFormatter>();
        /// <summary>
        /// 設(shè)置指定業(yè)務(wù)對(duì)應(yīng)的內(nèi)容模板
        /// </summary>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="formatter">內(nèi)容模板</param>
        public void SetFormatter(string bizFlag, IContentFormatter formatter)
        {
            if (!string.IsNullOrWhiteSpace(bizFlag) && formatter != null)
            {
                this._dic.AddOrUpdate(bizFlag, formatter, (k, v) => formatter);
            }
        }
        /// <summary>
        /// 移除指定業(yè)務(wù)對(duì)應(yīng)的內(nèi)容模板,如果沒有,則返回null
        /// </summary>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <returns></returns>
        public IContentFormatter RemoveFormatter(string bizFlag)
        {
            if (!string.IsNullOrWhiteSpace(bizFlag)
                && this._dic.TryRemove(bizFlag, out IContentFormatter formatter))
            {
                return formatter;
            }
            return null;
        }
        /// <summary>
        /// 將指定參數(shù)組織成待發(fā)送的文本內(nèi)容
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="effectiveTime">校驗(yàn)碼有效時(shí)間范圍</param>
        /// <returns></returns>
        public string GetContent(string receiver, string bizFlag, string code, TimeSpan effectiveTime)
        {
            if (string.IsNullOrWhiteSpace(bizFlag))
            {
                throw new ArgumentNullException(nameof(bizFlag));
            }
            this._dic.TryGetValue(bizFlag, out IContentFormatter formatter);
            if (formatter == null)
            {
                throw new KeyNotFoundException(nameof(formatter));
            }
            return formatter.GetContent(receiver, bizFlag, code, effectiveTime);
        }
    }

CodeHelper,注意該類除了實(shí)現(xiàn)ICodeHelper外,還提供了一個(gè)用于生成隨機(jī)驗(yàn)證碼的靜態(tài)方法GetRandomNumber。

    /// <summary>
    /// 業(yè)務(wù)校驗(yàn)碼輔助接口實(shí)現(xiàn)
    /// </summary>
    public class CodeHelper : ICodeHelper
    {
        /// <summary>
        /// 基于接口實(shí)現(xiàn),可依賴注入
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="storage"></param>
        public CodeHelper(ICodeSender sender, ICodeStorage storage)
        {
            this.Sender = sender ?? throw new ArgumentNullException(nameof(sender));
            this.Storage = storage ?? throw new ArgumentNullException(nameof(storage));
        }
        /// <summary>
        /// 校驗(yàn)碼實(shí)際發(fā)送者
        /// </summary>
        public ICodeSender Sender { get; }
        /// <summary>
        /// 校驗(yàn)碼信息存儲(chǔ)者
        /// </summary>
        public ICodeStorage Storage { get; }
        /// <summary>
        /// 發(fā)送校驗(yàn)碼
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="effectiveTime">校驗(yàn)碼有效時(shí)間范圍</param>
        /// <param name="maxSendLimit">周期內(nèi)最大允許發(fā)送配置,為null則表示無(wú)限制</param>
        public async Task<SendResult> SendCode(string receiver, string bizFlag, string code, TimeSpan effectiveTime, PeriodLimit maxSendLimit)
        {
            var result = SendResult.NotSupprot;
            if (this.Sender.IsSupport(receiver))
            {
                result = SendResult.MaxSendLimit;
                bool canSend = maxSendLimit == null;
                int sendTimes = 0;
                if (!canSend)
                {
                    sendTimes = await this.Storage.GetAreadySendTimes(receiver, bizFlag).ConfigureAwait(false);
                    canSend = sendTimes < maxSendLimit.MaxLimit;
                }
                if (canSend)
                {
                    result = SendResult.FailInSend;
                    if (await this.Sender.Send(receiver, bizFlag, code, effectiveTime).ConfigureAwait(false)
                        && await this.Storage.SetCode(receiver, bizFlag, code, effectiveTime).ConfigureAwait(false))
                    {
                        result = SendResult.Success;
                        if (maxSendLimit != null)
                        {
                            if (sendTimes == 0)
                            {
                                await this.Storage.SetPeriod(receiver, bizFlag, maxSendLimit.Period).ConfigureAwait(false);
                            }
                            else
                            {
                                await this.Storage.IncreaseSendTimes(receiver, bizFlag).ConfigureAwait(false);
                            }
                        }
                    }
                }
            }
            return result;
        }
        /// <summary>
        /// 驗(yàn)證校驗(yàn)碼是否正確
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="maxErrorLimit">最大允許錯(cuò)誤次數(shù)</param>
        /// <returns></returns>
        public async Task<VerificationResult> VerifyCode(string receiver, string bizFlag, string code, int maxErrorLimit)
        {
            var result = VerificationResult.Expired;
            var vCode = await this.Storage.GetEffectiveCode(receiver, bizFlag).ConfigureAwait(false);
            if (vCode != null && !string.IsNullOrWhiteSpace(vCode.Item1))
            {
                result = VerificationResult.MaxErrorLimit;
                if (vCode.Item2 < maxErrorLimit)
                {
                    result = VerificationResult.Success;
                    if (!string.Equals(vCode.Item1, code, StringComparison.OrdinalIgnoreCase))
                    {
                        result = VerificationResult.VerificationFailed;
                        await this.Storage.IncreaseCodeErrors(receiver, bizFlag).ConfigureAwait(false);
                    }
                }
            }
            return result;
        }
        /// <summary>
        /// 獲取由數(shù)字組成的校驗(yàn)碼
        /// </summary>
        /// <param name="maxLength">校驗(yàn)碼長(zhǎng)度</param>
        /// <returns></returns>
        public static string GetRandomNumber(int maxLength = 6)
        {
            if (maxLength <= 0 || maxLength >= 10)
            {
                throw new ArgumentOutOfRangeException($"{nameof(maxLength)} must between {1} and {9}.");
            }
            var rd = Math.Abs(Guid.NewGuid().GetHashCode());
            var tmpX = (int)Math.Pow(10, maxLength);
            return (rd % tmpX).ToString().PadLeft(maxLength, '0');
        }
    }

除了上述標(biāo)準(zhǔn)通用實(shí)現(xiàn),還有一些半通用實(shí)現(xiàn),比如本文標(biāo)題中的Redis,所謂半通用,就是指你可以直接拿來用,但有可能不符合你的技術(shù)場(chǎng)景,此時(shí)你需要自己重寫一份。

CodeStorageWithRedisCache,注意該類庫(kù)采用了StackExchange.Redis.Extensions.Core,你可以在nuget上下載該類庫(kù),如果你對(duì)默認(rèn)的Redis鍵值生成方式不滿意,你也可以通過重寫GetKey方法來指定新的鍵值生成方式。當(dāng)然,因?yàn)閷?shí)際存儲(chǔ)在Redis中的數(shù)據(jù)都只是一些簡(jiǎn)單數(shù)據(jù),并不需要額外的序列化過程,實(shí)際你也可以直接使用StackExchange.Redis。

    /// <summary>
    /// 校驗(yàn)碼信息存儲(chǔ)到Redis
    /// </summary>
    public class CodeStorageWithRedisCache : ICodeStorage
    {
        private readonly IRedisCacheClient _client;
        private const string CodeValueHashKey = "Code";
        private const string CodeErrorHashKey = "Error";
        private const string PeriodHashKey = "Period";
        /// <summary>
        /// Code緩存Key值前綴
        /// </summary>
        public string CodeKeyPrefix { get; set; } = "CC";
        /// <summary>
        /// Period緩存Key值前綴
        /// </summary>
        public string PeriodKeyPrefix { get; set; } = "CCT";
        /// <summary>
        /// 緩存寫入Redis哪個(gè)庫(kù)
        /// </summary>
        public int DbNumber { get; set; } = 8;
        /// <summary>
        /// 基于RedisCacheClient的構(gòu)造函數(shù)
        /// </summary>
        /// <param name="client"></param>
        public CodeStorageWithRedisCache(IRedisCacheClient client)
        {
            this._client = client;
        }
        /// <summary>
        /// 獲取校驗(yàn)碼周期內(nèi)已發(fā)送次數(shù),如果周期已到或未發(fā)送過任何驗(yàn)證碼,則返回0
        /// </summary>
        /// <param name="receiver"></param>
        /// <param name="bizFlag"></param>
        /// <returns></returns>
        public async Task<int> GetAreadySendTimes(string receiver, string bizFlag)
        {
            var db = this.GetDatabase();
            var key = this.GetPeriodKey(receiver, bizFlag);
            var times = await db.HashGetAsync<int>(key, PeriodHashKey).ConfigureAwait(false);
#if DEBUG
            Console.WriteLine("Method:{0} Result:{1}", nameof(GetAreadySendTimes), times);
#endif
            return times;
        }
        /// <summary>
        /// 獲取校驗(yàn)碼及已嘗試錯(cuò)誤次數(shù),如果校驗(yàn)碼不存在或已過期,則返回null
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <returns></returns>
        public async Task<Tuple<string, int>> GetEffectiveCode(string receiver, string bizFlag)
        {
            var db = this.GetDatabase();
            var key = this.GetCodeKey(receiver, bizFlag);
            if (await db.ExistsAsync(key).ConfigureAwait(false))
            {
                var code = await db.HashGetAsync<string>(key, CodeValueHashKey).ConfigureAwait(false);
                var errors = await db.HashGetAsync<int>(key, CodeErrorHashKey).ConfigureAwait(false);
#if DEBUG
                Console.WriteLine("Method:{0} Result:  Code {1} Errors {2} ", nameof(GetEffectiveCode), code, errors);
#endif
                return Tuple.Create(code, errors);
            }
            return null;
        }
        /// <summary>
        /// 校驗(yàn)碼錯(cuò)誤次數(shù)+1,如果校驗(yàn)碼已過期,則不進(jìn)行任何操作
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <returns></returns>
        public async Task IncreaseCodeErrors(string receiver, string bizFlag)
        {
            var db = this.GetDatabase();
            var key = this.GetCodeKey(receiver, bizFlag);
            if (await db.ExistsAsync(key).ConfigureAwait(false))
            {
                var errors = await db.HashGetAsync<int>(key, CodeErrorHashKey).ConfigureAwait(false);
                await db.HashSetAsync(key, CodeErrorHashKey, errors + 1).ConfigureAwait(false);
            }
        }
        /// <summary>
        /// 校驗(yàn)碼周期內(nèi)發(fā)送次數(shù)+1,如果周期已到,則不進(jìn)行任何操作
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <returns></returns>
        public async Task IncreaseSendTimes(string receiver, string bizFlag)
        {
            var db = this.GetDatabase();
            var key = this.GetPeriodKey(receiver, bizFlag);
            if (await db.ExistsAsync(key).ConfigureAwait(false))
            {
                var times = await db.HashGetAsync<int>(key, PeriodHashKey).ConfigureAwait(false);
                await db.HashSetAsync(key, PeriodHashKey, times + 1).ConfigureAwait(false);
            }
        }
        /// <summary>
        /// 將校驗(yàn)碼進(jìn)行持久化,如果接收方和業(yè)務(wù)標(biāo)志組合已經(jīng)存在,則進(jìn)行覆蓋
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="code">校驗(yàn)碼</param>
        /// <param name="effectiveTime">校驗(yàn)碼有效時(shí)間范圍</param>
        /// <returns></returns>
        public async Task<bool> SetCode(string receiver, string bizFlag, string code, TimeSpan effectiveTime)
        {
            var db = this.GetDatabase();
            var key = this.GetCodeKey(receiver, bizFlag);
            await db.RemoveAsync(key).ConfigureAwait(false);
            var ret = await db.HashSetAsync(key, CodeValueHashKey, code).ConfigureAwait(false)
            && await db.HashSetAsync(key, CodeErrorHashKey, 0).ConfigureAwait(false)
            && await db.UpdateExpiryAsync(key, effectiveTime);
#if DEBUG
            Console.WriteLine("Method:{0} Result:{1}", nameof(SetCode), ret);
#endif
            return ret;
        }
        /// <summary>
        /// 校驗(yàn)碼發(fā)送次數(shù)周期持久化,如果接收方和業(yè)務(wù)標(biāo)志組合已經(jīng)存在,則進(jìn)行覆蓋
        /// </summary>
        /// <param name="receiver">接收方</param>
        /// <param name="bizFlag">業(yè)務(wù)標(biāo)志</param>
        /// <param name="period">周期時(shí)間范圍</param>
        /// <returns></returns>
        public async Task<bool> SetPeriod(string receiver, string bizFlag, TimeSpan? period)
        {
            var db = this.GetDatabase();
            var key = this.GetPeriodKey(receiver, bizFlag);
            await db.RemoveAsync(key).ConfigureAwait(false);
            var ret = await db.HashSetAsync(key, PeriodHashKey, 1).ConfigureAwait(false);
            if (period.HasValue)
            {
                ret = ret && await db.UpdateExpiryAsync(key, period.Value);
            }
#if DEBUG
            Console.WriteLine("Method:{0} Result:{1}", nameof(SetPeriod), ret);
#endif
            return ret;
        }
        /// <summary>
        /// 組織Redis鍵值
        /// </summary>
        /// <param name="receiver"></param>
        /// <param name="bizFlag"></param>
        /// <param name="prefix"></param>
        /// <returns></returns>
        protected virtual string GetKey(string receiver, string bizFlag, string prefix)
        {
            return string.Format("{0}:{1}:{2}", prefix, bizFlag, receiver);
        }
        private string GetPeriodKey(string receiver, string bizFlag)
        {
            return this.GetKey(receiver, bizFlag, this.PeriodKeyPrefix);
        }
        private string GetCodeKey(string receiver, string bizFlag)
        {
            return this.GetKey(receiver, bizFlag, this.CodeKeyPrefix);
        }
        private IRedisDatabase GetDatabase()
        {
            return this._client.GetDb(this.DbNumber);
        }
    }

最后,就是不可能通用的實(shí)現(xiàn)了,對(duì)于ICodeSender而言,先不說發(fā)送方式不同,就算相同,比如都是手機(jī),那也還有不同的短信供應(yīng)商,所以此處必須要使用者按自己的實(shí)際業(yè)務(wù)來實(shí)現(xiàn),為了方便舉例,這里我寫了一個(gè)在控制臺(tái)輸出驗(yàn)證碼內(nèi)容的實(shí)現(xiàn)。

ConsoleSender,注意IsSupport在此處輸出true,代表支持任意receiver

    /// <summary>
    /// 在控制臺(tái)輸出校驗(yàn)碼
    /// </summary>
    public class ConsoleSender : ICodeSender
    {
        public ConsoleSender(IContentFormatter formatter)
        {
            this.Formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
        }
        public IContentFormatter Formatter { get; }

        public bool IsSupport(string receiver) => true;

        public Task<bool> Send(string receiver, string bizFlag, string code, TimeSpan effectiveTime)
        {
            var content = this.Formatter.GetContent(receiver, bizFlag, code, effectiveTime);
            Console.WriteLine("發(fā)送內(nèi)容:{0}", content);
            return Task.FromResult(true);
        }
    }

最后則是如何使用的代碼例子,注意此處Redis序列化方式采用了StackExchange.Redis.Extensions.Newtonsoft,你可以根據(jù)實(shí)際需要采用其它序列化方式,比如StackExchange.Redis.Extensions.Protobuf等,你同樣可以在nuget上下載到這些類庫(kù)。

static void CheckCodeHelperDemo()
{
    var redisConfig = new RedisConfiguration
    {
        Hosts = new RedisHost[] {
            new RedisHost{
                    Host="127.0.0.1",
                    Port=6379
            }
        }
    };
    var bizFlag = "forgetPassword";
    var receiver = "Receiver";
    var effectiveTime = TimeSpan.FromMinutes(1);
    var redisManager = new RedisCacheConnectionPoolManager(redisConfig);
    var redisClient = new RedisCacheClient(redisManager,
        new NewtonsoftSerializer(), redisConfig);//new ProtobufSerializer();
    var storage = new CodeStorageWithRedisCache(redisClient);
    var simpleFormatter = new ContentFormatter(
            (r, b, c, e) => $"{r}您好,您的忘記密碼驗(yàn)證碼為{c},有效期為{(int)e.TotalSeconds}秒.");
    var formatter = new ComplexContentFormatter();
    formatter.SetFormatter(bizFlag, simpleFormatter);
    var sender = new ConsoleSender(formatter); //如果就一個(gè)業(yè)務(wù)場(chǎng)景,也可以直接用simpleFormatter
    //var tmp = storage.SetPeriod(receiver, bizFlag, TimeSpan.FromMinutes(20)).Result;
    var helper = new CodeHelper(sender, storage);
    var code = CodeHelper.GetRandomNumber();
    var sendResult = helper.SendCode(receiver, bizFlag, code, effectiveTime, new PeriodLimit
    {
        MaxLimit = 5,
        Period = TimeSpan.FromMinutes(20)
    }).Result;
    Console.WriteLine("發(fā)送結(jié)果:{0}", sendResult);
    if (sendResult == SendResult.Success)
    {
        Console.WriteLine("*****************************");
        while (true)
        {
            Console.WriteLine("請(qǐng)輸入校驗(yàn)碼:");
            var vCode = Console.ReadLine();
            var vResult = helper.VerifyCode(receiver, bizFlag, vCode, 3).Result;
            Console.WriteLine("校驗(yàn)碼 {0} 校驗(yàn)結(jié)果:{1}", vCode, vResult);
            if (vResult != VerificationResult.VerificationFailed)
            {
                break;
            }
        }
    }
    redisManager.Dispose();
}

最后則是不同測(cè)試場(chǎng)景的一些截圖

驗(yàn)證碼校驗(yàn)失敗達(dá)到允許次數(shù)上限

錯(cuò)誤上限

校驗(yàn)碼已過期

過期

校驗(yàn)碼驗(yàn)證成功

校驗(yàn)通過

校驗(yàn)碼周期內(nèi)允許的發(fā)送次數(shù)已達(dá)到上限

發(fā)送上限

最后,上述完整的代碼可見github。

到此這篇關(guān)于基于Redis驗(yàn)證碼發(fā)送及校驗(yàn)方案實(shí)現(xiàn) 的文章就介紹到這了,更多相關(guān)Redis驗(yàn)證碼發(fā)送及校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • RedisTemplate常用操作方法總結(jié)(set、hash、list、string等)

    RedisTemplate常用操作方法總結(jié)(set、hash、list、string等)

    本文主要介紹了RedisTemplate常用操作方法總結(jié),主要包括了6種常用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Windows環(huán)境下打開Redis閃退的解決方案

    Windows環(huán)境下打開Redis閃退的解決方案

    每次使用完Redis后,我們習(xí)慣性的動(dòng)作是直接叉掉doc頁(yè)面,這樣導(dǎo)致的結(jié)果是Redis在后臺(tái)繼續(xù)運(yùn)行,沒有關(guān)閉,所以當(dāng)再次打開的時(shí)候直接閃退,文中有詳細(xì)的解決方案,需要的朋友可以參考下
    2024-03-03
  • 攔截Redis命令導(dǎo)致的Lua腳本執(zhí)行失敗的問題解決

    攔截Redis命令導(dǎo)致的Lua腳本執(zhí)行失敗的問題解決

    本文主要介紹了攔截Redis命令導(dǎo)致的Lua腳本執(zhí)行失敗的問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Redis7.0部署集群的實(shí)現(xiàn)步驟

    Redis7.0部署集群的實(shí)現(xiàn)步驟

    本文主要介紹了Redis7.0部署集群的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • Redis權(quán)限和訪問控制的實(shí)現(xiàn)示例

    Redis權(quán)限和訪問控制的實(shí)現(xiàn)示例

    Redis提供了一些機(jī)制來保護(hù)敏感數(shù)據(jù)和限制對(duì)Redis服務(wù)器的訪問,本文主要介紹了Redis權(quán)限和訪問控制的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • Redis中一些最常見的面試問題總結(jié)

    Redis中一些最常見的面試問題總結(jié)

    Redis在互聯(lián)網(wǎng)技術(shù)存儲(chǔ)方面使用如此廣泛,幾乎所有的后端技術(shù)面試官都要在Redis的使用和原理方面對(duì)小伙伴們進(jìn)行各種刁難。下面這篇文章主要給大家總結(jié)介紹了關(guān)于Redis中一些最常見的面試問題,需要的朋友可以參考下
    2018-09-09
  • Redis數(shù)據(jù)類型string和Hash詳解

    Redis數(shù)據(jù)類型string和Hash詳解

    大家都知道Redis中有五大數(shù)據(jù)類型分別是String、List、Set、Hash和Zset,本文給大家分享Redis數(shù)據(jù)類型string和Hash的相關(guān)操作,感興趣的朋友跟隨小編一起看看吧
    2022-03-03
  • Redis源碼設(shè)計(jì)剖析之事件處理示例詳解

    Redis源碼設(shè)計(jì)剖析之事件處理示例詳解

    這篇文章主要為大家介紹了Redis源碼設(shè)計(jì)剖析之事件處理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • redis中修改配置文件中的端口號(hào) 密碼方法

    redis中修改配置文件中的端口號(hào) 密碼方法

    今天小編就為大家分享一篇redis中修改配置文件中的端口號(hào) 密碼方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • Redisson延時(shí)隊(duì)列RedissonDelayed的具體使用

    Redisson延時(shí)隊(duì)列RedissonDelayed的具體使用

    定時(shí)調(diào)度基本是每個(gè)項(xiàng)目都會(huì)遇到的業(yè)務(wù)場(chǎng)景,一般地,都會(huì)通過任務(wù)調(diào)度工具執(zhí)行定時(shí)任務(wù)完成,但是會(huì)有一定的缺點(diǎn),本文主要介紹了Redisson延時(shí)隊(duì)列RedissonDelayed的具體使用,感興趣的可以了解一下
    2024-02-02

最新評(píng)論