第27章 分布式缓存数据库的定义实现

news2024/11/23 9:37:37

1 Core.HashHelper

using System.Security.Cryptography;

namespace Core

{

    /// <summary>

    /// 【哈希助手--类】

    /// <remarks>

    /// 摘要:

    ///     该类通过1个指定哈希加密算法生成1个唯一性的字符串(当前安全性较强的SHA-2包括有:SHA-224SHA-256SHA-384,和 SHA-512等; 比较常用但已经不安全的有:MD5SHA1)

    /// </remarks>

    /// </summary>

    public class HashHelper

    {

        /// <param name="data">一个字节数组实例,把任意类型的一个指定数据以二进制的格式储着到该实例中。</param>

        /// <param name="hashAlgorithm">

        ///     一个指定哈希加密算法名称的字符串常量(当前安全性较强的SHA-2包括有:SHA-224SHA-256SHA-384,和 SHA-512等; 比较常用但已经不安全的有:MD5SHA1)

        ///   </param>

        /// <param name="trimByteCount">

        ///     截断长度值,该值指示字节数组实例中,有多少个二进制数据被用于构建哈希字符串,默认值为:0,即不执行截断操作,字节数组实例中的所有二进制数据都将被用于构建哈希字符串。

        /// </param>

        /// <summary>

        /// 【创建哈希】

        /// <returns>

        /// 返回:

        ///     1个唯一性的字符串。

        /// </returns>

        /// <remarks>

        /// 摘要:

        ///     通过指定的哈希加密算法,生成1个唯一性的字符串。

        /// 注意:

        ///     1、该方法用于生成一个唯一性的字符串,该字符串相当于一个唯一个的编号值,此时该方法与“Guid.NewGuid()”方法的功能相当;或用于安全性求较高的加/解密操作。

        ///     2、该方法的应用场景取决于哈希加密算法名称,如果用于生成一个唯一个的编号值,则使用“MD5SHA1”即可;如果用于安全性求较高的加/解密操作,则只有“SHA512”

        /// </remarks>

        /// </summary>

        public static string CreateHash(byte[] data, string hashAlgorithm, int trimByteCount = 0)

        {

            if (string.IsNullOrEmpty(hashAlgorithm))

                throw new ArgumentNullException(nameof(hashAlgorithm));

            var algorithm = (HashAlgorithm)CryptoConfig.CreateFromName(hashAlgorithm);

            if (algorithm == null)

                throw new ArgumentException("使用未知的哈希加密算法,或不存该名称的哈希加密算法。");

            if (trimByteCount > 0 && data.Length > trimByteCount)

            {

                var newData = new byte[trimByteCount];

                Array.Copy(data, newData, trimByteCount);

                return BitConverter.ToString(algorithm.ComputeHash(newData)).Replace("-", string.Empty);

            }

            return BitConverter.ToString(algorithm.ComputeHash(data)).Replace("-", string.Empty);

        }

    }

}

2 Core.Caching.EntityCacheDefaults

using Core.Domain;

namespace Core.Caching

{

    /// <summary>

    /// 【默认实体缓存--类】

    /// <remarks>

    /// 摘要:

    ///     通过该类中属性成员为1个指定实体类构建相应的键字符串;或构建键字符串的前缀字符串。

    /// </remarks>

    /// </summary>

    public static class EntityCacheDefaults<TEntity> where TEntity : BaseEntity

    {

        /// <summary>

        /// 【实体类型名称】

        /// <remarks>

        /// 摘要:

        ///     获取1个指定实体类的名称字符串(该名称字符串被格式化为全小写),该字符串只能为其它相应完整键字符串的拼接操作提供数据支撑。

        /// </remarks>

        /// </summary>

        public static string EntityTypeName => typeof(TEntity).Name.ToLowerInvariant();

        /// <summary>

        /// 【编号缓存键】

        /// <remarks>

        /// 摘要:

        ///      获取缓存键类的1个指定实例,该实例支持拼接操作并包含: 1个指定实体类1个指定的编号值对应1个指定实例的键字符串、前缀列表实例,缓存时间等。

        /// {0} :  1个指定实体类1个指定的编号值。

        /// </remarks>

        /// </summary>

        public static CacheKey ByIdCacheKey => new($"{EntityTypeName}.byid.{{0}}", ByIdPrefix, Prefix);

        /// <summary>

        /// 【编号集缓存键】

        /// <remarks>

        /// 摘要:

        ///     获取缓存键类的1个指定实例,该实例支持拼接操作并包含: 1个指定实体类n个指定的编号值对应n个指定实例的键字符串、前缀列表实例,缓存时间等。

        /// {0} : 键字符串的拼接字符串,该字符串是把n个指定的编号值经过哈西加密算法操作后获取。

        /// </remarks>

        /// </summary>

        public static CacheKey ByIdsCacheKey => new($"{EntityTypeName}.byids.{{0}}", ByIdsPrefix, Prefix);

        /// <summary>

        /// 【所有缓存键】

        /// <remarks>

        /// 摘要:

        ///     获取缓存键类的1个指定实例,该实例包含:1个指定实体的所有实例的的键字符串、前缀列表实例,缓存时间等。

        /// </remarks>

        /// </summary>

        public static CacheKey AllCacheKey => new($"{EntityTypeName}.all.", AllPrefix, Prefix);

        /// <summary>

        /// 【根前缀】

        /// <remarks>

        /// 摘要:

        ///     1个指定实体类的名称字符串作为指定键字符串的根前缀,该字符串只能为其它相应完整键字符串的拼接操作提供数据支撑。

        /// </remarks>

        /// </summary>

        public static string Prefix => $"{EntityTypeName}.";

        /// <summary>

        /// 【编号前缀】

        /// <remarks>

        /// 摘要:

        ///     获取1个指定实体类1个指定的编号值对应1个指定实例的键字符串的根前缀,该字符串只能为其它相应完整键字符串的拼接操作提供数据支撑。

        /// </remarks>

        /// </summary>

        public static string ByIdPrefix => $"{EntityTypeName}.byid.";

        /// <summary>

        /// 【编号集前缀】

        /// <remarks>

        /// 摘要:

        ///    获取1个指定实体类n个指定的编号值对应n个指定实例的键字符串的根前缀,该字符串只能为其它相应完整键字符串的拼接操作提供数据支撑。

        /// </remarks>

        /// </summary>

        public static string ByIdsPrefix => $"{EntityTypeName}.byids.";

        /// <summary>

        /// 【所有前缀】

        /// <remarks>

        /// 摘要:

        ///     获取1个指定实体的所有实例的键字符串,该键字符串也可以作为其它相应键字符串的前缀字符串。

        /// </remarks>

        /// </summary>

        public static string AllPrefix => $"{EntityTypeName}.all.";

    }

}

3 Core.Caching.CacheKey

using Core.Configuration;

using Core.Infrastructure;

namespace Core.Caching

{

    /// <summary>

    /// 【缓存键--类】

    /// <remarks>

    /// 摘要:

    ///     该类用于实例化1个指定的缓存键实例,该实例包含:键字符串、前缀列表实例,缓存时间等。

    /// </remarks>

    /// </summary>

    public class CacheKey

    {

        #region 拷贝构造方法

        /// <param name="key">1个指定的键字符串</param>

        /// <param name="prefixes">数组实例,该实例存储着1缓存键实例所对应所有前缀字符串,为相应键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【拷贝构造方法】

        /// <remarks>

        /// 摘要:

        ///     通过拷贝构造方法,对当前类中的同名属性成员进行实例化。

        /// </remarks>

        /// </summary>

        public CacheKey(string key, params string[] prefixes)

        {

            Key = key;

            Prefixes.AddRange(prefixes.Where(prefix => !string.IsNullOrEmpty(prefix)));

        }

        #endregion

        #region 属性

        /// <summary>

        /// 【键】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定的键字符串。

        /// </remarks>

        /// </summary>

        public string Key { get; protected set; }

        /// <summary>

        /// 【前缀】

        /// <remarks>

        /// 摘要:

        ///     获取/设置列表实例,该实例存储着1缓存键实例所对应所有前缀字符串,为相应键字符串的拼接提供数据支撑。

        /// </remarks>

        /// </summary>

        public List<string> Prefixes { get; protected set; } = new List<string>();

        /// <summary>

        /// 【缓存时间】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定的键字符串在内存中的缓存时间(默认设置为:60分钟=1小时)

        /// 说明:

        ///     1个指定缓存项(/值对)在内存中的缓存时间,实际是通过键字符串的缓存时间进行控制的,即只要过了指定键字符串的缓存时间,其所对应的值也同时被销毁了,

        /// 那么其所构建的缓存项(/值对)在内存中也就不存在了。

        /// </remarks>

        /// </summary>

        public int CacheTime { get; set; } = Singleton<AppSettings>.Instance.Get<CacheConfig>().DefaultCacheTime;

        #endregion

        #region 方法

        /// <param name="createCacheKeyParameters">1个具有返回值的委托方法实例(这里特指:“CacheKeyService.CreateCacheKeyParameters”方法,该实例用于把下面的数组实例中的所有数据值转换为指定的格式。</param>

        /// <param name=" keyObjects">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。 </param>

        /// <summary>

        /// 【构建】

        ///  <remarks>

        /// 摘要:

        ///     根据数组实例,获取缓存键类的1个指定实例,,该实例包含:键字符串、前缀列表实例,缓存时间等。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     缓存键类的1个指定实例。

        /// </returns>

        /// </summary>

        public virtual CacheKey Create(Func<object, object> createCacheKeyParameters, params object[] keyObjects)

        {

            //获取缓存键类的1个指定实例。

            var cacheKey = new CacheKey(Key, Prefixes.ToArray());

            //如果数组实例中不存在任何的实例成员,则直接返回缓存键类的1个指定实例。

            if (!keyObjects.Any())

                return cacheKey;

            //如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接。

            cacheKey.Key = string.Format(cacheKey.Key, keyObjects.Select(createCacheKeyParameters).ToArray());

            //如果列表成员中的实例能够进行拼接操作,则对该实例中的键字符串进行重新拼接。

            for (var i = 0; i < cacheKey.Prefixes.Count; i++)

                cacheKey.Prefixes[i] = string.Format(cacheKey.Prefixes[i], keyObjects.Select(createCacheKeyParameters).ToArray());

            return cacheKey;

        }

        #endregion

        #region 嵌套类

        /// <summary>

        /// 【缓存键相等比较--类】

        /// <remarks>

        /// 摘要:

        ///    通过该类中的方法成员来验证缓存键类的两个实例是否相等。

        /// </remarks>

        /// </summary>

        public class CacheKeyEqualityComparer : IEqualityComparer<CacheKey>

        {

            /// <param name="x">缓存键类的1个指定实例。</param>

            /// <param name="y">缓存键类的另1个指定实例。</param>

            /// <summary>

            /// 【相等?】

            /// <remarks>

            /// 摘要:

            ///     获取1个值false(不相等)/true(相等),该值指示缓存键类的两个实例是否相等。

            /// </remarks>

            /// <returns>

            /// 返回:

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

            /// </returns>

            /// </summary>

            public bool Equals(CacheKey x, CacheKey y)

            {

                if (x == null && y == null)

                    return true;

                //如果缓存键类的1个指定实例中的键字符串与缓存键类的另1个指定实例中键字符串相等,则两个缓存键类的实例相等,反之则不等。

                return x?.Key.Equals(y?.Key, StringComparison.OrdinalIgnoreCase) ?? false;

            }

            /// <param name="obj">缓存键类的1个指定实例。</param>

            /// <summary>

            /// 【哈西转换】

            /// <remarks>

            /// 摘要:

            ///    缓存键类的1个指定实例中的键字符串经过了哈西加密操作,则把该键字符串转换为相应的整型值。

            /// 说明:

            ///    在该类中,相等比较方法是必须的,而哈西转换方法只是为了所继承接口同名方法的必须实现。

            /// </remarks>

            /// <returns>

            /// 返回:

            ///     经过哈西加密操作字符串所转换的整型值。

            /// </returns>

            /// </summary>

            public int GetHashCode(CacheKey obj)

            {

                return obj.Key.GetHashCode();

            }

        }

        #endregion

    }

}

4 Core.Caching.CacheKeyService

using System.Globalization;

using System.Text;

using Core.Configuration;

using Core.Domain;

namespace Core.Caching

{

    /// <summary>

    /// 【缓存键服务--类】

    /// <remarks>

    /// 摘要:

    ///     该类中的方法成员通过“appsettings.json”文件中缓存配置数据,实例化1个指定的缓存键实例,该实例包含:键字符串、前缀列表实例,缓存时间(来源于“appsettings.json”文件中缓存配置数据)等。

    /// </remarks>

    /// </summary>

    public abstract class CacheKeyService

    {

        #region 属性--私有/保护

        /// <summary>

        /// 【哈希加密算法】

        /// <remarks>

        /// 摘要:

        ///     一个指定哈希加密算法的名称字符串常量,该常量被设置为:“SHA1”(当前安全性较强的SHA-2包括有:SHA-224SHA-256SHA-384,和 SHA-512等; 比较常用但已经不安全的有:MD5SHA1)

        /// 说明:

        ///     由于在该类中哈希加密算法主要用于生成用于键拼接的字符串,所以不需要太强的安全性“SHA1”哈希算法,所获取的字符串已经足够使用。

        /// </remarks>

        /// </summary>

        private string HashAlgorithm => "SHA1";

        #endregion

        #region 变量--私有/保护

        /// <summary>

        /// 【应用配置】

        /// <remarks>

        /// 摘要:

        ///     应用配置类的1个指定实例。

        /// </remarks>

        /// </summary>

        protected readonly AppSettings _appSettings;

        #endregion

        #region 拷贝构造方法

        /// <param name="appSettings">应用配置类的1个指定实例。</param>

        /// <summary>

        /// 【拷贝构造方法】

        /// <remarks>

        /// 摘要:

        ///     通过拷贝构造方法,对当前类中的同名变量成员进行实例化。

        /// </remarks>

        /// </summary>

        protected CacheKeyService(AppSettings appSettings)

        {

            _appSettings = appSettings;

        }

        #endregion

        #region 方法--私有/保护

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【前缀键预处理】

        ///  <remarks>

        /// 摘要:

        ///     该方法用于前缀字符串的拼接,为键字符串的拼接操作提供数据支撑。

        /// </remarks>

        ///  <returns>

        /// 返回:

        ///     拼接后的前缀字符串。

        /// </returns>

        /// </summary>

        protected virtual string PrepareKeyPrefix(string prefix, params object[] prefixParameters)

        {

            //如果数组实例中存在实例成员,则把1个指定的前缀字符串与数组实例中的拼接字符串进行重新拼接,最终获取1个新的拼接字符串;如果数组实例中不存在任何的实例成员,则直接返回1个指定的前缀字符串。

            return prefixParameters?.Any() ?? false

                ? string.Format(prefix, prefixParameters.Select(CreateCacheKeyParameters).ToArray())

                : prefix;

        }

        /// <summary>

        /// 【编号集哈希构建】

        /// <param name="ids">可枚举实例,该实例中存储着n个长整型数据值。</param>

        /// <remarks>

        /// 摘要:

        ///      1个指定实体类n个指定的编号值的拼接字符串,进行西加密操作后,获取经过哈西加密操作后的字符串。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///      经过哈西加密操作后的字符串。

        /// </returns>

        /// </summary>

        protected virtual string CreateIdsHash(IEnumerable<long> ids)

        {

            //把可枚举实例中的所有长整型数据值赋值给列表实例。

            var identifiers = ids.ToList();

            //如果列表实例中没有任何实例项,则直返回1个空字符串。

            if (!identifiers.Any())

                return string.Empty;

            //把列表实例中的所有实例项进行顺序排序后,拼接成一个字符串,该字符串中,每实例项用“, ”进行间隔。

            var identifiersString = string.Join(", ", identifiers.OrderBy(id => id));

            //把拼接字符串进行哈西加密操作,最后返回操作后的字符串。

            return HashHelper.CreateHash(Encoding.UTF8.GetBytes(identifiersString), HashAlgorithm);

        }

        /// <param name="parameter">泛型的参数实例。</param>

        /// <summary>

        /// 【构建缓存键参数】

        /// <remarks>

        /// 摘要:

        ///     1个泛型的参数实例转换为指定的类型的实例,从而为键字符串的拼接提供数据支撑。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     类型转换后的实例。

        /// </returns>

        /// </summary>

        protected virtual object CreateCacheKeyParameters(object parameter)

        {

            return parameter switch

            {

                null => "null",//如果参数实例值为:null,则直接转换为“null”字符串。

                IEnumerable<long> ids => CreateIdsHash(ids),//如果参数实例值是多个编号值,把这些编号值转换为经过哈西加密操作后的字符串。

                IEnumerable<BaseEntity> entities => CreateIdsHash(entities.Select(entity => entity.Id)),//如果参数实例值是1个指定实体的n个实例,把这些实例的编号值转换为经过哈西加密操作后的字符串。

                BaseEntity entity => entity.Id,//如果参数实例值是1个指定实体的1个实例,则直接返回该实例的编号值。

                decimal param => param.ToString(CultureInfo.InvariantCulture),//如果参数实例值是十进制的,则返回该十进制值的本地化操作后的字符串。

                _ => parameter

            };

        }

        #endregion

        #region 方法

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【键预处理】

        ///  <remarks>

        /// 摘要:

        ///     如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接。

        /// </remarks>

        ///  <returns>

        /// 返回:

        ///    缓存键类的1个指定实例。

        /// </returns>

        /// </summary>

        public virtual CacheKey PrepareKey(CacheKey cacheKey, params object[] cacheKeyParameters)

        {

            return cacheKey.Create(CreateCacheKeyParameters, cacheKeyParameters);

        }

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【键默认缓存预处理】

        ///  <remarks>

        /// 摘要:

        ///     如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接,

        /// 同时对缓存键类的1个指定实例设定默认的缓存时间(默认值:60分钟=1小时,“appsettings.json”文件设定中获取)

        /// </remarks>

        ///  <returns>

        /// 返回:

        ///    缓存键类的1个指定实例,该实例包含:缓存时间。

        /// </returns>

        /// </summary>

        public virtual CacheKey PrepareKeyForDefaultCache(CacheKey cacheKey, params object[] cacheKeyParameters)

        {

            var key = cacheKey.Create(CreateCacheKeyParameters, cacheKeyParameters);

            key.CacheTime = _appSettings.Get<CacheConfig>().DefaultCacheTime;

            return key;

        }

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【键最短缓存预处理】

        ///  <remarks>

        /// 摘要:

        ///     如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接,

        /// 同时对缓存键类的1个指定实例设定默认的缓存时间(默认值:3分钟,“appsettings.json”文件设定中获取)

        /// </remarks>

        ///  <returns>

        /// 返回:

        ///    缓存键类的1个指定实例,该实例包含:缓存时间。

        /// </returns>

        /// </summary>

        public virtual CacheKey PrepareKeyForShortTermCache(CacheKey cacheKey, params object[] cacheKeyParameters)

        {

            var key = cacheKey.Create(CreateCacheKeyParameters, cacheKeyParameters);

            key.CacheTime = _appSettings.Get<CacheConfig>().ShortTermCacheTime;

            return key;

        }

        #endregion

    }

}

5 Core.Caching.ILocker

namespace Core.Caching

{

    /// <summary>

    /// 【锁--接口】

    /// <remarks>

    /// 摘要:

    ///     继承于该接口的具体体实现类指示已经通过内存/分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。

    /// 说明:

    ///     在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例;如果启用对分布式数据库的支持后,使用Redis分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。

    /// </remarks>

    /// </summary>

    public interface ILocker

    {

        /// <param name="resource">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)。</param>

        /// <param name="expirationTime">1个指定缓存项(键/值对,这里特指计划任务实例的键/值对缓存项)在Redis分布式缓存数据库中的缓存时间。</param>

        /// <param name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。</param>

        /// <summary>

        /// 【异步执行加锁操作?】

        /// <remarks>

        /// 摘要:

        ///     获取1个值false(失败)/true(成功),该值指示已经通过内存/分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。

        /// 说明:

        ///     在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例;如果启用对分布式数据库的支持后,使用Redis分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个值false(失败)/true(成功)

        /// </returns>

        /// </summary>

        Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action);

    }

}

6 Core.Caching.IStaticCacheManager

namespace Core.Caching

{

    /// <summary>

    /// 【内存缓存管理器--类】

    /// <remarks>

    /// 摘要:

    ///    通过继承于该接口的具体类中的方法成员实现了通过Microsoft.Extensions.Caching.Memory”程序集/Redis软件对缓存数据的管理操作,这些管理操作包含:缓存数据加载,缓存数据移除和缓存数据清理等。

    /// </remarks>

    /// </summary>

    public interface IStaticCacheManager : IDisposable

    {

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。</param>

        /// <summary>

        /// 【异步设置】

        /// <remarks>

        /// 摘要:

        ///     把构建的1个指定的缓存项(/(“JSON”编号格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。

        /// </remarks>

        /// </summary>

        Task SetAsync(CacheKey key, object data);

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型异步委托方法实例,该泛型异步委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【异步获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire);

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【异步获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        Task<T> GetAsync<T>(CacheKey key, Func<T> acquire);

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        T Get<T>(CacheKey key, Func<T> acquire);

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters);

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【异步通过前缀字符串移除实例】

        /// <remarks>

        /// 摘要:

        ///     该方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(/(“JSON”编号格式的)值对)

        /// </remarks>

        /// </summary>

        Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters);

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【通过前缀字符串移除实例】

        /// <remarks>

        /// 摘要:

        ///     该方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(/(“JSON”编号格式的)值对)

        /// </remarks>

        /// </summary>

        void RemoveByPrefix(string prefix, params object[] prefixParameters);

        /// <summary>

        /// 【异步清理】

        /// <remarks>

        /// 摘要:

        ///     该方法用于从缓存数据库中移除1/n个指定的缓存项(/(“JSON”编号格式的)值对),并销毁字典实例中存储着的所有键/值对实例。

        /// </remarks>

        /// </summary>

        Task ClearAsync();

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【键预处理】

        ///  <remarks>

        /// 摘要:

        ///     如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接。

        /// </remarks>

        ///  <returns>

        /// 返回:

        ///    缓存键类的1个指定实例。

        /// </returns>

        /// </summary>

        CacheKey PrepareKey(CacheKey cacheKey, params object[] cacheKeyParameters);

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【键默认缓存预处理】

        ///  <remarks>

        /// 摘要:

        ///     如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接,

        /// 同时对缓存键类的1个指定实例设定默认的缓存时间(默认值:60分钟=1小时,“appsettings.json”文件设定中获取)

        /// </remarks>

        ///  <returns>

        /// 返回:

        ///    缓存键类的1个指定实例,该实例包含:缓存时间。

        /// </returns>

        /// </summary>

        CacheKey PrepareKeyForDefaultCache(CacheKey cacheKey, params object[] cacheKeyParameters);

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【键最短缓存预处理】

        ///  <remarks>

        /// 摘要:

        ///     如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接,

        /// 同时对缓存键类的1个指定实例设定默认的缓存时间(默认值:3分钟,“appsettings.json”文件设定中获取)

        /// </remarks>

        ///  <returns>

        /// 返回:

        ///    缓存键类的1个指定实例,该实例包含:缓存时间。

        /// </returns>

        /// </summary>

        CacheKey PrepareKeyForShortTermCache(CacheKey cacheKey, params object[] cacheKeyParameters);

    }

}

7 Core.Caching.MemoryCacheManager

using System.Collections.Concurrent;

using Microsoft.Extensions.Caching.Memory;

using Microsoft.Extensions.Primitives;

using Core.Configuration;

namespace Core.Caching

{

    /// <summary>

    /// 【内存缓存管理器--类】

    /// <remarks>

    /// 摘要:

    ///    通过该类中的方法成员实现了通过“Microsoft.Extensions.Caching.Memory”程序集对缓存数据的管理操作,这些管理操作包含:缓存数据加载,缓存数据移除和缓存数据清理等。

    /// 说明:

    ///     1、当前程序中所定义的通过时间进行触发的计划任务,在默认情况下是利用“Microsoft.Extensions.Caching.Memory”程序集的缓存时间的控制模式及其缓存时间,定时触发这些计划任务。

    /// 即如果当前程序没有启用分布式数据库,那么在默认状况下会通过当前类中的“PerformActionWithLockAsync”重写方法定时触发这些计划任务;

    /// 如果删除该类定义当前程序则必须的启用分布式数据库,才能保证计划任务进行定时触发,否则将不能定时触发计划任务。

    ///     2、为了保证在默认情况下定时触发计划任务,没有删除该类的定义,如果当前程序一开始就启用分布式数据库,可以删除该类的定义。

    /// </remarks>

    /// </summary>

    public partial class MemoryCacheManager : CacheKeyService, ILocker, IStaticCacheManager

    {

        #region 变量--私有/保护

        /// <summary>

        /// 【已经销毁?】

        /// <remarks>

        /// 摘要:

        ///     设置1个值false(默认值:未销毁)/true(已经销毁),该值指示当前类的实例(非托管资源)是否已经被操作系统标记为:已经销毁状态;或已经被操作系统所销毁。

        /// </remarks>

        /// </summary>

        private bool _disposed;

        /// <summary>

        /// 【内存缓存】

        /// <remarks>

        /// 摘要:

        ///    内存缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.Memory”程序集的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.Memory”程序集的实例与内存缓存数据库的交互操作。

        /// </remarks>

        /// </summary>

        private readonly IMemoryCache _memoryCache;

        /// <summary>

        /// 【项集】

        /// <remarks>

        /// 摘要:

        ///    线程安全字典实例,该实例以键/值对的形式存储着当前程序中的所有缓存键实例及其所对应的值。

        /// 线程安全字典:

        ///     1、如果不使用线程安全字典实例,则在多线程下每次实例的加载都要通过锁实例进行控制。

        ///     2、在默认情况下,如果使用线程安全字典实例,微软默认隐式设定锁实例数量是CPU核的个数。

        ///     3、当然开发者也可以自定义更多的线程(>CPU核的个数),但同时开发者也必须显式的为这些线程定义同样多的锁实例数量(>CPU核的个数)

        /// 说明:

        ///     字典实例中所存储键/值对中的值是1个指定实体的1/n实例;而分布式缓存数据库中所存储键/值对中的值是经过JSON格式编码后的1个指定实体的1/n实例。

        /// </remarks>

        /// </summary>

        private static readonly ConcurrentDictionary<string, CancellationTokenSource> _prefixes = new();

        /// <summary>

        /// 【取消标记】

        /// <remarks>

        /// 摘要:

        ///     一个取消标记资源(CancellationTokenSource)实例,当缓存项中的保留时间过期后,自动调用该实例的相应方法来移除已经过期的缓存项,该实例的的初始化/实例化操作是直接通过“new”关键字实现的。

        /// 为什么需要CancellationToken?:

        ///     因为Task没有方法支持在外部取消Task,只能通过一个公共变量存放线程的取消状态,在线程内部通过变量判断线程是否被取消,当CancellationToken是取消状态,

        /// Task内部未启动的任务不会启动新线程。取消标记(CancellationToken) ,正确并合理的使用 CancellationToken 可以让业务达到简化代码、

        /// 提升服务性能的效果;当在业务开发中,需要对一些特定的应用场景进行深度干预的时候,CancellationToken 将发挥非常重要的作用。

        /// 注意:

        ///     该实例被限定为静态,即如果不执行其它强制性的操作,该实例的生命周期,将会存在于程序执行的整个过程中。

        /// </remarks>

        /// </summary>

        private static CancellationTokenSource _clearToken = new();

        #endregion

        #region 拷贝构造方法

        /// <param name="appSettings">应用配置类的1个指定实例。</param>

        /// <param name="memoryCache">内存缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.Memory”程序集的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.Memory”程序集的实例与内存缓存数据库的交互操作。</param>

        /// <summary>

        /// 【拷贝构造方法】

        /// <remarks>

        /// 摘要:

        ///     通过拷贝构造方法,对当前类中的同名变量成员进行实例化。

        /// </remarks>

        /// </summary>

        public MemoryCacheManager(AppSettings appSettings, IMemoryCache memoryCache) : base(appSettings)

        {

            _memoryCache = memoryCache;

        }

        #endregion

        #region 方法----私有/保护

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <summary>

        /// 【入口操作预处理】

        /// <remarks>

        /// 摘要:

        ///     获取内存缓存入口操作实例,该实例为键字符串的缓存时间控制模式及其控制时间提供数据支撑。

        /// 说明:

        ///     1个指定缓存项(/值对)在内存中的缓存时间,实际是通过键字符串的缓存时间进行控制的,即只要过了指定键字符串的缓存时间,其所对应的值也同时被销毁了,

        /// 那么其所构建的缓存项(/值对)在内存中也就不存在了。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     内存缓存入口操作实例。

        /// </returns>

        /// </summary>

        private MemoryCacheEntryOptions PrepareEntryOptions(CacheKey key)

        {

            //AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(/值对)经过多少次调用,到缓存时间该指定缓存项(/值对)就要被销毁。

            //SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(/值对)才能被销毁。

            var options = new MemoryCacheEntryOptions

            {

                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime)

            };

            //设置取消标记资源(CancellationTokenSource)实例,当过了缓存时间过期后,该实例将自动释放内存缓存中所有的过期缓存项(/值对)

            options.AddExpirationToken(new CancellationChangeToken(_clearToken.Token));

            foreach (var keyPrefix in key.Prefixes.ToList())

            {

                var tokenSource = _prefixes.GetOrAdd(keyPrefix, new CancellationTokenSource());

                options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token));

            }

            return options;

        }

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【移除】

        /// <remarks>

        /// 摘要:

        ///     根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(/值对)

        /// </remarks>

        /// </summary>

        private void Remove(CacheKey cacheKey, params object[] cacheKeyParameters)

        {

            //拼接出缓存键类的1个新的指定实例。

            cacheKey = PrepareKey(cacheKey, cacheKeyParameters);

            //根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(/值对)

            _memoryCache.Remove(cacheKey.Key);

        }

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。</param>

        /// <summary>

        /// 【设置】

        /// <remarks>

        /// 摘要:

        ///     把构建的1个指定的缓存项(/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。

        /// </remarks>

        /// </summary>

        private void Set(CacheKey key, object data)

        {

            //如果缓存键实例的缓存缓存时间小于等于01个指定的泛型实例的实例值为:null,则不再执行任何缓存操作,直接退出该方法。

            if ((key?.CacheTime ?? 0) <= 0 || data == null)

                return;

            //把构建的1个指定的缓存项(/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。

            _memoryCache.Set(key!.Key, data, PrepareEntryOptions(key));

        }

        #endregion

        #region 方法--销毁

        /// <summary>

        /// 【销毁】

        /// <remarks>

        /// 摘要:

        ///     通过显式调用当前方法把当前类的实例被操作系统标(非托管资源)标记为:已经销毁状态;或已经被操作系统所销毁。

        /// </remarks>

        /// </summary>

        public void Dispose()

        {

            Dispose(true);

            //  SuppressFinalize:当开发者已经显式调用当前方法(Dispose或者Close),通过操作系统已经把当前类的实例(非托管资源)标记为:已经销毁状态时,

            // 如果开发者重复通过显式调用当前方法来销毁当前类的实例(非托管资源)时,由于当前类的实例(非托管资源)已经处于已经销毁状态,或已经被销毁,

            // 因而需要“ SuppressFinalize”强制通过终止执行当前类的析构方法来避免当前类的实例(非托管资源)1次的销毁操作,从而避免未知异常的产生。

            GC.SuppressFinalize(this);

        }

        /// <param name="disposing">指示当前类的实例(非托管资源)是否需要执行销毁操作,默认值:true,即执行销毁操作。</param>

        /// <summary>

        /// 【销毁】

        /// <remarks>

        /// 摘要:

        ///     为当前类的实例(非托管资源)被操作系统标(非托管资源)标记为:已经销毁状态;或已经被操作系统所销毁,提供方法支撑。

        /// </remarks>

        /// </summary>

        protected virtual void Dispose(bool disposing)

        {

            if (_disposed)

                return;

            //显式的把内存缓存接口实例(非托管资源)标记为:已经销毁状态。

            if (disposing)

                _memoryCache.Dispose();

            _disposed = true;

        }

        #endregion

        #region 方法--接口实现--ILocker

        /// <param name="resource">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)</param>

        /// <param name="expirationTime">1个指定缓存项(/值对,这里特指计划任务实例的键/值对缓存项)Redis分布式缓存数据库中的缓存时间。</param>

        /// <param name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。</param>

        /// <summary>

        /// 【异步执行加锁操作?】

        /// <remarks>

        /// 摘要:

        ///     获取1个值false(失败)/true(成功),该值指示已经通过内存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。

        /// 说明:

        ///     在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个值false(失败)/true(成功)

        /// </returns>

        /// </summary>

        public async Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action)

        {

            //AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(/值对)经过多少次调用,到缓存时间该指定缓存项(/值对)就要被销毁。

            //SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(/值对)才能被销毁。

            var isSet = await _memoryCache.GetOrCreateAsync(resource, cacheEntry =>

            {

                cacheEntry.AbsoluteExpiration = DateTimeOffset.Now;

                return Task.FromResult(false);

            });

            if (isSet)

                return false;

            try

            {

                //1指定的计划任务实例以键/值对的形式存储到内存缓存中,为指定的计划任务实例的定时触发,提供内存缓存时间的控制模式和缓存时间。

                await _memoryCache.GetOrCreateAsync(resource, cacheEntry =>

                {

                    cacheEntry.AbsoluteExpirationRelativeToNow = expirationTime;

                    return Task.FromResult(true);

                });

                //异步委托方法实例,通过内存缓存时间的控制模式和缓存时间, 定时触发指定的计划任务实例。

                await action();

                return true;

            }

            finally

            {

                //如果定时触发指定的计划任务实例失败,则从内存缓存中移除指定的计划任务实例的键/值对缓存项。

                _memoryCache.Remove(resource);

            }

        }

        /// <param name="key">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)</param>

        /// <param name="expirationTime">1个指定缓存项(/值对,这里特指计划任务实例的键/值对缓存项)Redis分布式缓存数据库中的缓存时间。</param>

        /// <param name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。</param>

        /// <summary>

        /// 【执行加锁操作?】

        /// <remarks>

        /// 摘要:

        ///     获取1个值false(失败)/true(成功),该值指示已经通过内存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。

        /// 说明:

        ///     在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个值false(失败)/true(成功)

        /// </returns>

        /// </summary>

        public bool PerformActionWithLock(string key, TimeSpan expirationTime, Action action)

        {

            //如果内存缓存已经存在指定的计划任务实例的键/值对缓存项,则不需要加载操作,并直接退出该方法。

            if (_memoryCache.TryGetValue(key, out _))

                return false;

            try

            {

                //如果内存缓存不存在指定的计划任务实例的键/值对缓存项,则把该指定的计划任务实例的键/值对缓存项加载到内存缓存中。

                _memoryCache.Set(key, key, expirationTime);

                //委托方法实例,通过内存缓存时间的控制模式和缓存时间, 定时触发指定的计划任务实例。

                action();

                return true;

            }

            finally

            {

                //如果定时触发指定的计划任务实例失败,则从内存缓存中移除指定的计划任务实例的键/值对缓存项。

                _memoryCache.Remove(key);

            }

        }

        #endregion

        #region 方法--接口实现--IStaticCacheManager

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【异步移除】

        /// <remarks>

        /// 摘要:

        ///     根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(/值对)

        /// </remarks>

        /// </summary>

        public Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)

        {

            Remove(cacheKey, cacheKeyParameters);

            return Task.CompletedTask;

        }

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型异步委托方法实例,该泛型异步委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【异步获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        public async Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire)

        {

            //如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if ((key?.CacheTime ?? 0) <= 0)

                return await acquire();

            //如果内存缓存中存在指定的键/值对,则获取1个指定实体的1/n实例。

            if (_memoryCache.TryGetValue(key!.Key, out T result))

                return result;

            //如果未缓存,则调用1个指定实体的1/n实例的委托方法。

            result = await acquire();

            //如果存在1个指定实体的1/n实例,则把该实例键/值对形式存储到内存缓存中进行管理。

            if (result != null)

                await SetAsync(key, result);

            //返回1个指定实体的1/n实例。

            return result;

        }

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【异步获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        public async Task<T> GetAsync<T>(CacheKey key, Func<T> acquire)

        {

            //如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if ((key?.CacheTime ?? 0) <= 0)

                return acquire();

            //如果内存缓存中存在指定的键/值对,则获取1个指定实体的1/n实例;如果内存缓存中不存在指定的键/值对,则把1个指定实体的1/n实例以键/值对的形式存储到内存缓存中,并返回1个指定实体的1/n实例。

            var result = _memoryCache.GetOrCreate(key!.Key, entry =>

            {

                entry.SetOptions(PrepareEntryOptions(key));

                return acquire();

            });

            //如果被缓存的实例值为:null,则从内存缓存中移除缓存键类的1个指定实例。

            if (result == null)

                await RemoveAsync(key);

            //返回1个指定实体的1/n实例。

            return result;

        }

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        public T Get<T>(CacheKey key, Func<T> acquire)

        {

            //如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if ((key?.CacheTime ?? 0) <= 0)

                return acquire();

            //如果内存缓存中存在1个指定实体的1/n实例,则获取1个指定实体的1/n实例。

            if (_memoryCache.TryGetValue(key!.Key, out T result))

                return result;

            //内存缓存中不存在1个指定实体的1/n实例,则调用1个指定实体的1/n实例的委托方法。

            result = acquire();

            //如果被缓存的实例值不为:null,则把1个指定实体的1/n实例以键/值对的形式存储到内存缓存中。

            if (result != null)

                Set(key, result);

            //返回1个指定实体的1/n实例。

            return result;

        }

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。</param>

        /// <summary>

        /// 【异步设置】

        /// <remarks>

        /// 摘要:

        ///     把构建的1个指定的缓存项(/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。

        /// </remarks>

        /// </summary>

        public Task SetAsync(CacheKey key, object data)

        {

            Set(key, data);

            return Task.CompletedTask;

        }

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【异步通过前缀字符串移除】

        /// <remarks>

        /// 摘要:

        ///     根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。。

        /// </remarks>

        /// </summary>

        public Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters)

        {

            RemoveByPrefix(prefix, prefixParameters);

            return Task.CompletedTask;

        }

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【通过前缀字符串移除】

        /// <remarks>

        /// 摘要:

        ///     根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。。

        /// </remarks>

        /// </summary>

        public void RemoveByPrefix(string prefix, params object[] prefixParameters)

        {

            //拼接出1个新的前缀字符串。

            prefix = PrepareKeyPrefix(prefix, prefixParameters);

            //根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。

            _prefixes.TryRemove(prefix, out var tokenSource);

            //显式销毁释放标记资源(CancellationTokenSource)实例。

            tokenSource?.Cancel();

            tokenSource?.Dispose();

        }

        /// <summary>

        /// 【异步清理】

        /// <remarks>

        /// 摘要:

        ///     销毁释放字典实例中存储着的所有键/值对实例。

        /// </remarks>

        /// </summary>

        public Task ClearAsync()

        {

            //销毁旧的取消标记资源(CancellationTokenSource)实例。

            _clearToken.Cancel();

            _clearToken.Dispose();

            //实例化一个新的取消标记资源(CancellationTokenSource)实例。

            _clearToken = new CancellationTokenSource();

            //销毁释放字典实例中存储着的所有键/值对实例,并抛出该字典实例中取消标记资源(CancellationTokenSource)实例(字典实例中的值)

            foreach (var prefix in _prefixes.Keys.ToList())

            {

                _prefixes.TryRemove(prefix, out var tokenSource);

                //显式销毁释放标记资源(CancellationTokenSource)实例。

                tokenSource?.Dispose();

            }

            return Task.CompletedTask;

        }

        #endregion

    }

}

8 Core.Caching.DistributedCacheManager

using System.Collections.Concurrent;

using System.Text.RegularExpressions;

using Microsoft.Extensions.Caching.Distributed;

using Newtonsoft.Json;

using Nito.AsyncEx;

using Core.Configuration;

using static Core.Caching.CacheKey;

namespace Core.Caching

{

    /// <summary>

    /// 【分布式缓存管理器--类】

    /// <remarks>

    /// 摘要:

    ///    通过该类中的方法成员实现了通过Redis软件对缓存数据的管理操作,这些管理操作包含:缓存数据加载,缓存数据移除和缓存数据清理等。

    /// 说明:

    ///     1、当前程序中所定义的通过时间进行触发的计划任务,如果当前程序启用分布式数据库,

    /// 将利用“Microsoft.Extensions.Caching.StackExchangeRedis”中间件实例的缓存时间的控制模式及其缓存时间和当前类中的“PerformActionWithLockAsync”重写方法,定时触发这些计划任务。

    /// 即如果当前程序没有启用分布式数据库,那么在默认状况下会利用“Microsoft.Extensions.Caching.Memory”程序集的缓存时间的控制模式及其缓存时间,定时触发这些计划任务。

    ///     2、该类是抽象类不被实例化,更不能依赖注入到内置容器中。

    /// </remarks>

    /// </summary>

    public abstract class DistributedCacheManager: CacheKeyService, ILocker, IStaticCacheManager

    {

        #region 变量--私有/保护

        /// <summary>

        /// 【分布式缓存】

        /// <remarks>

        /// 摘要:

        ///    分布式缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例与Redis分布式缓存数据库的交互操作。

        /// </remarks>

        /// </summary>

        protected readonly IDistributedCache _distributedCache;

        /// <summary>

        /// 【项集】

        /// <remarks>

        /// 摘要:

        ///    线程安全字典实例,该实例以键/值对的形式存储着当前程序中的所有缓存键实例及其所对应的值。

        /// 线程安全字典:

        ///     1、如果不使用线程安全字典实例,则在多线程下每次实例的加载都要通过锁实例进行控制。

        ///     2、在默认情况下,如果使用线程安全字典实例,微软默认隐式设定锁实例数量是CPU核的个数。

        ///     3、当然开发者也可以自定义更多的线程(>CPU核的个数),但同时开发者也必须显式的为这些线程定义同样多的锁实例数量(>CPU核的个数)

        /// 说明:

        ///     字典实例中所存储键/值对中的值是1个指定实体的1/n实例;而分布式缓存数据库中所存储键/值对中的值是经过JSON格式编码后的1个指定实体的1/n实例。

        /// </remarks>

        /// </summary>

        protected readonly ConcurrentDictionary<CacheKey, object> _items;

        /// <summary>

        /// 【锁】

        /// <remarks>

        /// 摘要:

        ///     异步锁实例,该实例实际上是:“Nito.AsyncEx.Coordination”中间件的实例,在异步多线程操作中,通过该实例来保证,在同1时间内,有且只有1个线程能够对缓存项(/值对)进行强制移除操作(在缓存项(/值对)实例长时间不被使用时,且在缓存时间内,则需要强制销毁,以释放内存空间)

        ///  说明:

        ///     1、如果不使用异步锁实例,在异步多线程操作中就会现出对1个已经销毁的实例重复性的进行强制销毁操作,从而产生不可预测的异常。

        ///     2、缓存项(/值对)实例的构建定义中对重复性构建同1缓存项(/值对)实例进行了排除操作,所以缓存项(/值对)实例的构建就不需要使用异步锁实例了。

        /// </remarks>

        /// </summary>

        protected static readonly AsyncLock _locker;

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <summary>

        /// 【缓存键委托变更事件】

        /// <remarks>

        /// 摘要:

        ///     1个委托事件方法,该方法通过缓存键实例,对事件中的操作进行实例化。

        /// </remarks>

        /// </summary>

        protected delegate void OnKeyChanged(CacheKey key);

        /// <summary>

        /// 【键加载委托事件】

        /// <remarks>

        /// 摘要:

        ///    1个委托事件方法,该方法通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。

        /// </remarks>

        /// </summary>

        protected OnKeyChanged _onKeyAdded;

        /// <summary>

        /// 【键移除委托事件】

        /// <remarks>

        /// 摘要:

        ///    1个委托事件方法,该方法通过缓存键实例,对缓存键委托移除事件中的操作进行实例化。

        /// </remarks>

        /// </summary>

        protected OnKeyChanged _onKeyRemoved;

        #endregion

        #region 构造方法

        /// <summary>

        /// 【默认构造方法】

        /// <remarks>

        /// 摘要:

        ///     通过默认构造方法,实例化异步锁实例,该实例实际上是:“Nito.AsyncEx.Coordination”中间件的实例,在异步多线程操作中,通过该实例来保证,在同1时间内,有且只有1个线程能够对缓存项(/值对)进行强制移除操作(在缓存项(/值对)实例长时间不被使用时,且在缓存时间内,则需要强制销毁,以释放内存空间)

        /// </remarks>

        /// </summary>

        static DistributedCacheManager()

        {

            _locker = new AsyncLock();

        }

        /// <param name="appSettings">应用配置类的1个指定实例。</param>

        /// <param name="distributedCache">分布式缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例与Redis分布式缓存数据库的交互操作。</param>

        /// <summary>

        /// 【拷贝构造方法】

        /// <remarks>

        /// 摘要:

        ///     通过拷贝构造方法,对当前类中的同名变量成员进行实例化。

        /// </remarks>

        /// </summary>

        protected DistributedCacheManager(AppSettings appSettings, IDistributedCache distributedCache) :base(appSettings)

        {

            _distributedCache = distributedCache;

            _items = new ConcurrentDictionary<CacheKey, object>(new CacheKeyEqualityComparer());

        }

        #endregion

        #region 方法----私有/保护

        /// <summary>

        /// 【清理实例】

        /// <remarks>

        /// 摘要:

        ///     销毁字典实例中存储着的所有数据实例。

        /// </remarks>

        /// </summary>

        protected void ClearInstanceData()

        {

            _items.Clear();

        }

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【异步通过前缀字符串移除实例】

        /// <remarks>

        /// 摘要:

        ///     根据字典实例中与前缀字符串验证规则相匹配的缓存键类的所有实例,从字典实例中依次移除相匹配的键/值对。

        /// </remarks>

        /// </summary>

        protected async Task RemoveByPrefixInstanceDataAsync(string prefix, params object[] prefixParameters)

        {

            //在异步多线程操作中,先启用异步锁操作,然后再执行当前方法中的移除操作。

            using var _ = await _locker.LockAsync();

            //拼接出1个新的前缀字符串。

            prefix = PrepareKeyPrefix(prefix, prefixParameters);

            //构建前缀字符串的验证规则实例。

            //RegexOptions.Singleline:指定单行模式,该模式下只能对1行字符串进行匹配验证操作(对\n之外的每个字符将不进行匹配验证操作)。

            //RegexOptions.Compiled:指在匹配验证操作时,对每1个字符都需要进行匹配验证,注意:最好不要使用RegexOptions.Compiled进行字符串的匹配验证操作,因为它被称为性能杀手。

            //RegexOptions.IgnoreCase:在匹配验证操作时忽略字符串中每个字符的大小写。

            var regex = new Regex(prefix,

                RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);

            //实例化1个列表实例,该实例存储着缓存键类的n个实例。

            var matchesKeys = new List<CacheKey>();

            //把字典实例中与前缀字符串验证规则相匹配的缓存键类的所有实例,存储到列表实例中。

            matchesKeys.AddRange(_items.Keys.Where(key => regex.IsMatch(key.Key)).ToList());

            //根据列表实例,从字典实例中依次移除相匹配的键/值对。

            if (matchesKeys.Any())

                foreach (var key in matchesKeys)

                    _items.TryRemove(key, out var _);

        }

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【通过前缀字符串移除实例】

        /// <remarks>

        /// 摘要:

        ///     根据字典实例中与前缀字符串验证规则相匹配的缓存键类的所有实例,从字典实例中依次移除相匹配的键/值对。

        /// </remarks>

        /// </summary>

        protected void RemoveByPrefixInstanceData(string prefix, params object[] prefixParameters)

        {

            //在异步多线程操作中,先启用异步锁操作,然后再执行当前方法中的移除操作。

            using var _ = _locker.Lock();

            //拼接出1个新的前缀字符串。

            prefix = PrepareKeyPrefix(prefix, prefixParameters);

            //构建前缀字符串的验证规则实例。

            //RegexOptions.Singleline:指定单行模式,该模式下只能对1行字符串进行匹配验证操作(对\n之外的每个字符将不进行匹配验证操作)。

            //RegexOptions.Compiled:指在匹配验证操作时,对每1个字符都需要进行匹配验证,注意:最好不要使用RegexOptions.Compiled进行字符串的匹配验证操作,因为它被称为性能杀手。

            //RegexOptions.IgnoreCase:在匹配验证操作时忽略字符串中每个字符的大小写。

            var regex = new Regex(prefix,

                RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);

            //实例化1个列表实例,该实例存储着缓存键类的n个实例。

            var matchesKeys = new List<CacheKey>();

            //把字典实例中与前缀字符串验证规则相匹配的缓存键类的所有实例,存储到列表实例中。

            matchesKeys.AddRange(_items.Keys.Where(key => regex.IsMatch(key.Key)).ToList());

            //根据列表实例,从字典实例中依次移除相匹配的键/值对。

            if (matchesKeys.Any())

                foreach (var key in matchesKeys)

                    _items.TryRemove(key, out var _);

        }

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <summary>

        /// 【入口操作预处理】

        /// <remarks>

        /// 摘要:

        ///     获取分布式缓存入口操作实例,该实例为键字符串的缓存时间控制模式及其控制时间提供数据支撑。

        /// 说明:

        ///     1个指定缓存项(/值对)在内存中的缓存时间,实际是通过键字符串的缓存时间进行控制的,即只要过了指定键字符串的缓存时间,其所对应的值也同时被销毁了,

        /// 那么其所构建的缓存项(/值对)在内存中也就不存在了。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///      分布式缓存入口操作实例。

        /// </returns>

        /// </summary>

        private DistributedCacheEntryOptions PrepareEntryOptions(CacheKey key)

        {

            //AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(/值对)经过多少次调用,到缓存时间该指定缓存项(/值对)就要被销毁。

            //SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(/值对)才能被销毁。

            //如果AbsoluteExpirationRelativeToNowSlidingExpiration都未设定,则Redis中指定键字符串的absexpsldexp所对应的值都=-1

            var options = new DistributedCacheEntryOptions

            {

                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime)

            };

            return options;

        }

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <summary>

        /// 【异步尝试获取缓存项】

        /// <remarks>

        /// 摘要:

        ///     如果(JSON编码格式的)缓存值不为空,则获取true1个指定实体的1/n个实例;如果(JSON编码格式的)缓存值为空,则获取false1个指定实体的空实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    true1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        private async Task<(bool isSet, T item)> TryGetItemAsync<T>(CacheKey key)

        {

            var json = await _distributedCache.GetStringAsync(key.Key);

            //如果(JSON编码格式的)缓存值为空,则获取false1个指定实体的空实例。

            if (string.IsNullOrEmpty(json))

                return (false, default);

            //通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。

            _onKeyAdded?.Invoke(key);

            //如果(JSON编码格式的)缓存值不为空,则获取true1个指定实体的1/n个实例。

            return (true, JsonConvert.DeserializeObject<T>(json));

        }

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <summary>

        /// 【尝试获取缓存项】

        /// <remarks>

        /// 摘要:

        ///     如果(JSON编码格式的)缓存值不为空,则获取true1个指定实体的1/n个实例;如果(JSON编码格式的)缓存值为空,则获取false1个指定实体的空实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    true1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        private (bool isSet, T item) TryGetItem<T>(CacheKey key)

        {

            var json = _distributedCache.GetString(key.Key);

            //如果(JSON编码格式的)缓存值为空,则获取false1个指定实体的空实例。

            if (string.IsNullOrEmpty(json))

                return (false, default);

            //通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。

            _onKeyAdded?.Invoke(key);

            //如果(JSON编码格式的)缓存值不为空,则获取true1个指定实体的1/n个实例。

            return (true, JsonConvert.DeserializeObject<T>(json));

        }

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。</param>

        /// <summary>

        /// 【设置】

        /// <remarks>

        /// 摘要:

        ///     把构建的1个指定的缓存项(/(“JSON”编号格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。

        /// </remarks>

        /// </summary>

        private void Set(CacheKey key, object data)

        {

            //如果缓存键实例的缓存缓存时间小于等于01个指定的泛型实例的实例值为:null,则不再执行任何缓存操作,直接退出该方法。

            if ((key?.CacheTime ?? 0) <= 0 || data == null)

                return;

            //把构建的1个指定的缓存项(/(“JSON”编号格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。

            _distributedCache.SetString(key!.Key, JsonConvert.SerializeObject(data), PrepareEntryOptions(key));

            //把缓存键类的1个指定实例和1个指定实体的1/n实例,以键/值对的形式加载到字典实例中。

            _items.TryAdd(key, data);

            //通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。

            _onKeyAdded?.Invoke(key);

        }

        #endregion

        #region 方法--销毁

        /// <summary>

        /// 【销毁】

        /// <remarks>

        /// 摘要:

        ///     如果操作中有非托管资源实例产生,则通过显式调用该方法以保证销毁这些有非托管资源实例。

        /// </remarks>

        /// </summary>

        public void Dispose()

        {

        }

        #endregion

        #region 方法--接口实现--ILocker

        /// <param name="resource">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)</param>

        /// <param name="expirationTime">1个指定缓存项(/值对,这里特指计划任务实例的键/值对缓存项)Redis分布式缓存数据库中的缓存时间。</param>

        /// <param name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。</param>

        /// <summary>

        /// 【异步执行加锁操作?】

        /// <remarks>

        /// 摘要:

        ///     获取1个值false(失败)/true(成功),该值指示已经通过Redis分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。

        /// 说明:

        ///     如果想要通过Redis分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例,当前程序必须先启用对分布式数据库的支持;如果不启用则使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个值false(失败)/true(成功)

        /// </returns>

        /// </summary>

        public async Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action)

        {

            if (!string.IsNullOrEmpty(await _distributedCache.GetStringAsync(resource)))

                return false;

            try

            {

                //AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(/值对)经过多少次调用,到缓存时间该指定缓存项(/值对)就要被销毁。

                //SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(/值对)才能被销毁。

                //如果AbsoluteExpirationRelativeToNowSlidingExpiration都未设定,则Redis中指定键字符串的absexpsldexp所对应的值都=-1

                await _distributedCache.SetStringAsync(resource, resource, new DistributedCacheEntryOptions

                {

                    AbsoluteExpirationRelativeToNow = expirationTime

                });

                //异步委托方法实例,通过Redis分布式数据库缓存时间的控制模式和缓存时间, 定时触发指定的计划任务实例。

                await action();

                return true;

            }

            finally

            {

                //如果定时触发指定的计划任务实例失败,则从Redis分布式缓存数据库中移除指定的计划任务实例的键/值对缓存项。

                await _distributedCache.RemoveAsync(resource);

            }

        }

        #endregion

        #region 方法--接口实现--IStaticCacheManager

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型异步委托方法实例,该泛型异步委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【异步获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        public async Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire)

        {

            //对重复性构建同1缓存项(/(“JSON”编号格式的)值对)实例进行排除操作,如果字典实例中存在指定的键/值对,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if (_items.ContainsKey(key))

                return (T)_items.GetOrAdd(key, acquire);

            //如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if (key.CacheTime <= 0)

                return await acquire();

            //查看分布式数据库中是否已经缓存了该缓存项(/(“JSON”编号格式的)值对)

            var (isSet, item) = await TryGetItemAsync<T>(key);

            //如果已经缓存则把取1个指定实体的1/n实例,以键/值对的形式存储到字典实例中后,获取1个指定实体的1/n实例。

            if (isSet)

            {

                if (item != null)

                    _items.TryAdd(key, item);

                return item;

            }

            //如果未缓存,则调用1个指定实体的1/n实例的委托方法。

            var result = await acquire();

            //如果存在1个指定实体的1/n实例,则把该实例以“JSON”编号格式存储到分布式数据库中进行管理。

            if (result != null)

                await SetAsync(key, result);

            //返回1个指定实体的1/n实例。

            return result;

        }

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【异步获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        public async Task<T> GetAsync<T>(CacheKey key, Func<T> acquire)

        {

            //对重复性构建同1缓存项(/(“JSON”编号格式的)值对)实例进行排除操作,如果字典实例中存在指定的键/值对,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if (_items.ContainsKey(key))

                return (T)_items.GetOrAdd(key, acquire);

            //如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if (key.CacheTime <= 0)

                return acquire();

            //查看分布式数据库中是否已经缓存了该缓存项(/(“JSON”编号格式的)值对)

            var (isSet, item) = await TryGetItemAsync<T>(key);

            //如果已经缓存则把取1个指定实体的1/n实例,以键/值对的形式存储到字典实例中后,获取1个指定实体的1/n实例。

            if (isSet)

            {

                if (item != null)

                    _items.TryAdd(key, item);

                return item;

            }

            //如果未缓存,则调用1个指定实体的1/n实例的委托方法。

            var result = acquire();

            //如果存在1个指定实体的1/n实例,则把该实例以“JSON”编号格式存储到分布式数据库中进行管理。

            if (result != null)

                await SetAsync(key, result);

            //返回1个指定实体的1/n实例。

            return result;

        }

        /// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)</typeparam>

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。</param>

        /// <summary>

        /// 【获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        public T Get<T>(CacheKey key, Func<T> acquire)

        {

            //对重复性构建同1缓存项(/(“JSON”编号格式的)值对)实例进行排除操作,如果字典实例中存在指定的键/值对,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if (_items.ContainsKey(key))

                return (T)_items.GetOrAdd(key, acquire);

            //如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。

            if (key.CacheTime <= 0)

                return acquire();

            //查看分布式数据库中是否已经缓存了该缓存项(/(“JSON”编号格式的)值对)

            var (isSet, item) = TryGetItem<T>(key);

            //如果已经缓存则把取1个指定实体的1/n实例,以键/值对的形式存储到字典实例中后,获取1个指定实体的1/n实例。

            if (isSet)

            {

                if (item != null)

                    _items.TryAdd(key, item);

                return item;

            }

            //如果未缓存,则调用1个指定实体的1/n实例的委托方法。

            var result = acquire();

            //如果存在1个指定实体的1/n实例,则把该实例以“JSON”编号格式存储到分布式数据库中进行管理。

            if (result != null)

                Set(key, result);

            //返回1个指定实体的1/n实例。

            return result;

        }

        /// <param name="cacheKey">缓存键类的1个指定实例。</param>

        /// <param name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【获取】

        /// <remarks>

        /// 摘要:

        ///     获取一个指定的实体的1个指定实体的1/n个实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     1个指定实体的1/n个实例。

        /// </returns>

        /// </summary>

        public async Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)

        {

            //拼接出缓存键类的1个新的指定实例。

            cacheKey = PrepareKey(cacheKey, cacheKeyParameters);

            //根据缓存键类的1个新的指定实例,从分布式数据库中移除1个指定的缓存项(/(“JSON”编号格式的)值对)

            await _distributedCache.RemoveAsync(cacheKey.Key);

            //根据缓存键类的1个新的指定实例,从字典实例中移除1个指定的键/值对。

            _items.TryRemove(cacheKey, out _);

            //通过缓存键实例,对缓存键委托移除事件中的操作进行实例化。

            _onKeyRemoved?.Invoke(cacheKey);

        }

        /// <param name="key">缓存键类的1个指定实例。</param>

        /// <param name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。</param>

        /// <summary>

        /// 【异步设置】

        /// <remarks>

        /// 摘要:

        ///     把构建的1个指定的缓存项(/(“JSON”编号格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。

        /// </remarks>

        /// </summary>

        public async Task SetAsync(CacheKey key, object data)

        {

            //如果缓存键实例的缓存缓存时间小于等于01个指定的泛型实例的实例值为:null,则不再执行任何缓存操作,直接退出该方法。

            if ((key?.CacheTime ?? 0) <= 0 || data == null)

                return;

            //把构建的1个指定的缓存项(/(“JSON”编号格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。

            await _distributedCache.SetStringAsync(key!.Key, JsonConvert.SerializeObject(data), PrepareEntryOptions(key));

            //把缓存键类的1个指定实例和1个指定实体的1/n实例,以键/值对的形式加载到字典实例中。

            _items.TryAdd(key, data);

            //通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。

            _onKeyAdded?.Invoke(key);

        }

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【异步通过前缀字符串移除实例】

        /// <remarks>

        /// 摘要:

        ///     该方法是抽象方法,该方法的覆写方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(/(“JSON”编号格式的)值对)

        /// </remarks>

        /// </summary>

        public abstract Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters);

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【通过前缀字符串移除实例】

        /// <remarks>

        /// 摘要:

        ///     该方法是抽象方法,该方法的覆写方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(/(“JSON”编号格式的)值对)

        /// </remarks>

        /// </summary>

        public abstract void RemoveByPrefix(string prefix, params object[] prefixParameters);

        /// <summary>

        /// 【异步清理】

        /// <remarks>

        /// 摘要:

        ///     该方法是抽象方法,该方法的覆写方法用于从缓存数据库中移除1/n个指定的缓存项(/(“JSON”编号格式的)值对),并销毁字典实例中存储着的所有键/值对实例。

        /// </remarks>

        /// </summary>

        public abstract Task ClearAsync();

        #endregion

    }

}

9 Services.Caching.RedisCacheManager

using System.Net;

using Microsoft.Extensions.Caching.Distributed;

using Core.Caching;

using Core.Configuration;

using StackExchange.Redis;

namespace Services.Caching

{

    /// <summary>

    /// Redis分布式缓存数据库软件管理器--类】

    /// <remarks>

    /// 摘要:

    ///    通过该类中的方法成员实现了通过“StackExchange.Redis”中间件实例对Redis软件缓存数据更加底层的操作及其的“DistributedCacheManager”抽象类的实例化。

    /// </remarks>

    /// </summary>

    public class RedisCacheManager : DistributedCacheManager

    {

        #region 变量--私有/保护

        /// <summary>

        /// Redis连接封装器】

        /// <remarks>

        /// 摘要:

        ///     Redis连接封装器的1个指定实例。

        /// </remarks>

        /// </summary>

        private static RedisConnectionWrapper _connectionWrapper;

        /// <summary>

        /// 【数据库】

        /// <remarks>

        /// 摘要:

        ///     用于建立当前程序与Redis分布式缓存数据库软件中分布式缓存数据库的连接。

        /// </remarks>

        /// </summary>

        private readonly IDatabase _db;

        #endregion

        #region 拷贝构造方法

        /// <param name="appSettings">应用配置类的1个指定实例。</param>

        /// <param name="distributedCache">分布式缓存接口实例,该实例实际上是:“StackExchange.Redis”中间件的实例,通过该实例实现当前程序通过“StackExchange.Redis”中间件的实例与Redis分布式缓存数据库的交互操作。</param>

        /// <summary>

        /// 【拷贝构造方法】

        /// <remarks>

        /// 摘要:

        ///     通过拷贝构造方法,对当前类中的同名变量成员进行实例化。

        /// </remarks>

        /// </summary>

        public RedisCacheManager(AppSettings appSettings, IDistributedCache distributedCache) : base(appSettings,distributedCache)

        {

            _connectionWrapper ??= new RedisConnectionWrapper(appSettings.Get<DistributedCacheConfig>().ConnectionString);

            _db = _connectionWrapper.GetDatabase();

        }

        #endregion

        #region 方法--私有/保护

        /// <param name="endPoint">Redis分布式缓存数据库软件中所有可用的终结点。</param>

        /// <param name="prefix">1个指定的前缀字符串,默认值:null,即获取所有的Redis缓存键实例。</param>

        /// <summary>

        /// 【获取服务】

        /// <remarks>

        /// 摘要:

        ///     Redis分布式缓存数据库软件所有的分布式缓存数据库(0-15)中,获取与指定前缀字符串相匹配的所有的Redis缓存键实例。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     与指定前缀字符串相匹配的所有的Redis缓存键实例。

        /// </returns>

        /// </summary>

        protected virtual IEnumerable<RedisKey> GetKeys(EndPoint endPoint, string prefix = null)

        {

            var server = _connectionWrapper.GetServer(endPoint);

            var keys = server.Keys(_db.Database, string.IsNullOrEmpty(prefix) ? null : $"{prefix}*");

            return keys;

        }

        #endregion

        #region 方法

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【异步通过前缀字符串移除实例】

        /// <remarks>

        /// 摘要:

        ///    该方法的覆写方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(/(“JSON”编号格式的)值对)

        /// </remarks>

        /// </summary>

        public override async Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters)

        {

            prefix = PrepareKeyPrefix(prefix, prefixParameters);

            foreach (var endPoint in _connectionWrapper.GetEndPoints())

            {

                var keys = GetKeys(endPoint, prefix);

                _db.KeyDelete(keys.ToArray());

            }

            await RemoveByPrefixInstanceDataAsync(prefix);

        }

        /// <param name="prefix">1个指定的前缀字符串。</param>

        /// <param name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。</param>

        /// <summary>

        /// 【通过前缀字符串移除实例】

        /// <remarks>

        /// 摘要:

        ///     该方法是抽象方法,该方法的覆写方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(/(“JSON”编号格式的)值对)

        /// </remarks>

        /// </summary>

        public override void RemoveByPrefix(string prefix, params object[] prefixParameters)

        {

            prefix = PrepareKeyPrefix(prefix, prefixParameters);

            foreach (var endPoint in _connectionWrapper.GetEndPoints())

            {

                var keys = GetKeys(endPoint, prefix);

                _db.KeyDelete(keys.ToArray());

            }

            RemoveByPrefixInstanceData(prefix);

        }

        /// <summary>

        /// 【异步清理终结点】

        /// <remarks>

        /// 摘要:

        ///     清理释放Redis分布式缓存数据库软件分布式缓存数据库(0-15)中所有缓存项。

        /// </remarks>

        /// </summary>

        public override async Task ClearAsync()

        {

            await _connectionWrapper.FlushDatabaseAsync();

            ClearInstanceData();

        }

        #endregion

        #region 嵌套类

        /// <summary>

        /// Redis连接封装器--类】

        /// <remarks>

        /// 摘要:

        ///    通过该类中的方法成员实现了调用“StackExchange.Redis”中间件的实例,从而Redis软件对缓存数据的管理操作,这些管理操作包含:缓存数据加载,缓存数据移除和缓存数据清理等。。

        /// </remarks>

        /// </summary>

        protected class RedisConnectionWrapper

        {

            #region 变量--私有/保护

            /// <summary>

            /// 【锁】

            /// <remarks>

            /// 摘要:

            ///     锁实例。

            /// </remarks>

            /// </summary>

            private readonly object _lock = new();

            /// <summary>

            /// 【分布式缓存】

            /// <remarks>

            /// 摘要:

            ///     Redis分布式缓存数据库软件连接器实例,该实例实际上是:“StackExchange.Redis”中间件的实例,通过该实例实现当前程序通过“StackExchange.Redis”中间件的实例与Redis分布式缓存数据库的交互操作。

            /// </remarks>

            /// </summary>

            private volatile ConnectionMultiplexer _connection;

            /// <summary>

            /// 【连接字符串】

            /// <remarks>

            /// 摘要:

            ///    Redis分布式缓存数据库软件的连接字符串。

            /// </remarks>

            /// </summary>

            private readonly Lazy<string> _connectionString;

            #endregion

            #region 构造方法

            /// <param name="connectionString">应用配置类的1个指定实例。</param>

            /// <summary>

            /// 【默认构造方法】

            /// <remarks>

            /// 摘要:

            ///     通过默认构造方法,实例化Redis分布式缓存数据库软件的连接字符串。

            /// </remarks>

            /// </summary>

            public RedisConnectionWrapper(string connectionString)

            {

                _connectionString = new Lazy<string>(connectionString);

            }

            #endregion

            #region 方法----私有/保护

            /// <summary>

            /// 【获取连接】

            /// <remarks>

            /// 摘要:

            ///     建立当前程序与Redis分布式缓存数据库软件的连接。

            /// </remarks>

            /// <returns>

            /// 返回:

            ///     Redis分布式缓存数据库软件的连接器实例。

            /// </returns>

            /// </summary>

            protected ConnectionMultiplexer GetConnection()

            {

                if (_connection != null && _connection.IsConnected)

                    return _connection;

                lock (_lock)

                {

                    if (_connection != null && _connection.IsConnected)

                        return _connection;

                    //显式销毁释放内存中Redis分布式缓存数据库软件的连接器实例,该实例实际上是:显式销毁释放内存中的“StackExchange.Redis”中间件实例。

                    _connection?.Dispose();

                    //实例化Redis分布式缓存数据库软件的连接器实例,该实例实际上是:“StackExchange.Redis”中间件实例。

                    _connection = ConnectionMultiplexer.Connect(_connectionString.Value);

                }

                return _connection;

            }

            #endregion

            #region 方法--销毁

            /// <summary>

            /// 【销毁】

            /// <remarks>

            /// 摘要:

            ///     显式销毁释放内存中的连接器实例,该实例实际上是:显式销毁释放内存中的“StackExchange.Redis”中间件实例。

            /// </remarks>

            /// </summary>

            public void Dispose()

            {

                //显式销毁释放内存中的连接器实例,该实例实际上是:显式销毁释放内存中的“StackExchange.Redis”中间件实例。

                _connection?.Dispose();

            }

            #endregion

            #region 方法

            /// <summary>

            /// 【获取数据库】

            /// <remarks>

            /// 摘要:

            ///     建立当前程序与Redis分布式缓存数据库软件中分布式缓存数据库的连接。

            /// </remarks>

            /// <returns>

            /// 返回:

            ///    数据库连接接口实例,该实例用于建立当前程序与Redis分布式缓存数据库软件中分布式缓存数据库的连接。

            /// </returns>

            /// </summary>

            public IDatabase GetDatabase()

            {

                return GetConnection().GetDatabase();

            }

            /// <param name="endPoint">Redis分布式缓存数据库软件中所有可用的终结点。</param>

            /// <summary>

            /// 【获取服务】

            /// <remarks>

            /// 摘要:

            ///     获取Redis分布式缓存数据库软件中所有可用的终结点,即所有的分布式缓存数据库(0-15)

            /// </remarks>

            /// <returns>

            /// 返回:

            ///     服务接口实例,该实例用于建立当前程序与Redis分布式缓存数据库软件中分布式缓存数据库的连接。

            /// </returns>

            /// </summary>

            public IServer GetServer(EndPoint endPoint)

            {

                return GetConnection().GetServer(endPoint);

            }

            /// <summary>

            /// 【获取终结点】

            /// <remarks>

            /// 摘要:

            ///     获取Redis分布式缓存数据库软件中所有可用的终结点,即所有的分布式缓存数据库(0-15)

            /// </remarks>

            /// <returns>

            /// 返回:

            ///     数组实例,该实例存储着Redis分布式缓存数据库软件中所有可用的终结点,即所有的分布式缓存数据库(0-15)

            /// </returns>

            /// </summary>

            public EndPoint[] GetEndPoints()

            {

                return GetConnection().GetEndPoints();

            }

            /// <summary>

            /// 【异步清理终结点】

            /// <remarks>

            /// 摘要:

            ///     清理释放Redis分布式缓存数据库软件分布式缓存数据库(0-15)中所有缓存项。

            /// </remarks>

            /// </summary>

            public async Task FlushDatabaseAsync()

            {

                var endPoints = GetEndPoints();

                foreach (var endPoint in endPoints)

                    await GetServer(endPoint).FlushDatabaseAsync();

            }

            #endregion

        }

        #endregion

    }

}

10 Framework.Infrastructure.NopCommonStartup

using Core.Infrastructure;

using Framework.Infrastructure.Extensions;

using Microsoft.AspNetCore.Builder;

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.DependencyInjection;

namespace Framework.Infrastructure

{

    /// <summary>

    /// 【通用启动--类】

    /// <remarks>

    /// 摘要:

    ///     通过该类中的依赖注入和管道方法成员,把一些常用操作的实例注入到内置依赖注入容器实例中和集成到内置管道接口实例中。

    /// </remarks>

    /// </summary>

    public partial class CommonStartup : IStartup

    {

        #region 属性--接口实现

        /// <summary>

        /// 【顺序】

        /// <remarks>

        /// 摘要:

        ///     获取数据库启动类中管道中间件实例,被集成到内置管道实例中的顺序,默认值:10

        /// 注意:

        ///     .NetCore框架默定义了一些常用的内置管道中间件,并默认规定了这些内置管道中间件实例在内置管道实例中被调用的顺序,

        /// 如果不按照该默认顺序调用这些内置管道中间件,在程序执行时就会出现逻辑异常。

        /// </remarks>

        /// </summary>

        public int Order => 100; //通用管道中间件实例,被.NetCore框架内置管道中间件所调用或者被实例化的顺序,必须在自定义/内置错误处理程序管道中间件实例之后,即第2个被集成到置管道中间件中,否则在程序执行时就会出现逻辑异常。

        #endregion

        #region 方法--接口实现

        /// <param name="services">.Net(Core)框架内置依赖注入容器实例。</param>

        /// <param name="configuration">.NetCore框架内置配置接口实例(存储着当前程序中所有*.json文件中的数据)</param>

        /// <summary>

        /// 【配置服务】

        /// <remarks>

        /// 摘要:

        ///     通过该方法成员,把一些常用的依赖注入操作注入到.Net(Core)框架内置依赖注入容器实例中。

        /// </remarks>

        /// </summary>

        public void ConfigureServices(IServiceCollection services, IConfiguration configuration)

        {

            //分布式缓存依赖注入操作,注入到.Net(Core)框架内置依赖注入容器实例中, 为当前程序添加内存缓存,提供实例支持。

            services.AddDistributedCache();

        }

        /// <param name="application">.NetCore框架内置管道接口实例。</param>

        /// <summary>

        /// 【配置】

        /// <remarks>

        /// 摘要:

        ///     把一些常用的管道实例集到到.NetCore框架内置管道接口实例中。

        /// </remarks>

        /// </summary>

        public void Configure(IApplicationBuilder application)

        {

           

        }

        #endregion

    }

}

11 Framework.Infrastructure.Extensions.ServiceCollectionExtensions. AddDistributedCache

/// <param name="services">.Net(Core)框架内置依赖注入容器实例。</param>

        /// <summary>

        ///  【添加分布式缓存】

        /// <remarks>

        /// 摘要:

        ///     动态对“EntityFrameworkCore”中间件进行实例化,为当前程序通过“EntityFrameworkCore”中间件实例与指定数据库软件中指定数据库的CURD交互操作,提供实例支持。

        /// </remarks>

        /// </summary>

        public static void AddDistributedCache(this IServiceCollection services)

        {

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

            var appSettings = Singleton<AppSettings>.Instance;

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

            var distributedCacheConfig = appSettings.Get<DistributedCacheConfig>();

            //  如果当前程序使用“API”模板,则必先把“AddMemoryCache”中间件实现注入到内置容器中,否则由于Core.Caching.MemoryCacheManager拷贝构造方法中的IMemoryCache memoryCache参数实例不能实例化而产生异常:

            //“System.AggregateException:“Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Core.Caching.ILocker Lifetime:

            //Singleton ImplementationType: Core.Caching.MemoryCacheManager': Unable to resolve service for type

            //'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'Core.Caching.MemoryCacheManager'.)

            //(Error while validating the service descriptor 'ServiceType: Core.Caching.IStaticCacheManager

            //Lifetime: Singleton ImplementationType: Core.Caching.MemoryCacheManager':

            //Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'Core.Caching.MemoryCacheManager'.)””

            //  如果当前程序使用“MVC”模板,则不必先把“AddMemoryCache”中间件实现注入到内置容器中,但先把“AddMemoryCache”中间件实现注入到内置容器中,即可以支持“MVC”模板也支持“API”模板。

            services.AddMemoryCache();

            if (!distributedCacheConfig.Enabled)

                return;

            switch (distributedCacheConfig.DistributedCacheType)

            {

                //通过“appsettings.json”文件中内存缓存配置相关的实例值,实例化“Microsoft.Extensions.Caching.Memory”程序集,为构建当前程序与内存缓存的交互操作提供支撑。

                case DistributedCacheType.Memory:

                    services.AddDistributedMemoryCache();

                    break;

                //通过“appsettings.json”文件中分布式缓存配置相关的实例值,实例化“Microsoft.Extensions.Caching.StackExchangeRedis”中件间,为构建当前程序与Redis分布式数据库的交互操作提供支撑。

                case DistributedCacheType.Redis:

                    services.AddStackExchangeRedisCache(options =>

                    {

                        options.Configuration = distributedCacheConfig.ConnectionString;

                    });

                    break;

            }

        }

12 重构Framework.Infrastructure.DependencyInjectionStartup.Controllers

public virtual void ConfigureServices(IServiceCollection services, IConfiguration configuration)

        {

            services.AddScoped<INopFileProvider, NopFileProvider>();

            var appSettings = Singleton<AppSettings>.Instance;

            var distributedCacheConfig = appSettings.Get<DistributedCacheConfig>();

            if (distributedCacheConfig.Enabled)

            {

                switch (distributedCacheConfig.DistributedCacheType)

                {

                    case DistributedCacheType.Redis:

                        services.AddScoped<ILocker, RedisCacheManager>();

                        services.AddScoped<IStaticCacheManager, RedisCacheManager>();

                        break;

                }

            }

            else

            {

                services.AddSingleton<ILocker, MemoryCacheManager>();

                services.AddSingleton<IStaticCacheManager, MemoryCacheManager>();

            }

        }

13 WebApi.Controllers.DistributedCacheTestController

using Core.Caching;

using Core.Domain.Users;

using Data;

using Data.Extensions;

using Microsoft.AspNetCore.Mvc;

using WebApi.Models;

namespace WebApi.Controllers

{

    [Route("[controller]/[action]")]

    [ApiController]

    public class DistributedCacheTestController : ControllerBase

    {

        #region 拷贝构造方法与变量

        private readonly EFCoreContext _context;

        private readonly IStaticCacheManager _staticCacheManager;

        public DistributedCacheTestController(EFCoreContext context, IStaticCacheManager staticCacheManager)

        {

            _context = context;

            _staticCacheManager = staticCacheManager;

        }

        #endregion

        [HttpGet]

        public async Task<MessageModel<bool>> DistributedCacheTestAsync()

        {

           

            List<Role> _roleList = await _context.GetDbSet<Role>().ToListAsync();

            await _staticCacheManager.SetAsync(EntityCacheDefaults<Role>.AllCacheKey, _roleList);

            return null;

        }

    }

}

14 重构appsettings.json文件

{

  "ConnectionStrings": {

    //Trusted_Connection=trueIntegrated Security=true/SSPI:“Windows凭据SQL Server进行身份验证,表示可以在不知道数据库用户名和密码的情况下时,依然可以连接SQL Server数据库。

    //"integrated":"security=true是通过“Windows身份认证SQL Server数据库进行身份验证,并与SQL Server数据库进行连接;表示可以在不知道数据库用户名和密码的情况下时,依然可以连接SQL Server数据库,如果integrated", "security=false","或者不写,表示一定要输入正确的数据库登录名和密码。": null

    //Persist Security Info:该配置只用于通过“SQL Server身份认证SQL Server数据库进行身份验证,并与SQL Server数据库进行连接;简单的理解为"ADO在数据库连接成功后是否保存密码信息"True表示保存,False表示不保存.ADO缺省为True(ADO.net缺省为False,未测试,根据参考资料上说的)

    //MultipleActiveResultSets:它允许在单个连接上执行多重的数据库查询或存储过程,目前只适用于Sql Server 2005及其以上版本;如果不用MultipleActiveResultSets ,则一般报错为sqldatareader未关闭,即需要关闭了之后才能打开另一个。

    //Trust Server Certificate:是否使用SSL证书和加密方式,对SQL Server数据库的连接字符串进行加密,该操作属性安全性配置,目前只适用于Sql Server 2005及其以上版本;

    //"SqlServerWindows": "Data Source=.;Initial Catalog=ShopDemo;Integrated Security=true;MultipleActiveResultSets=true;Trust Server Certificate=True"

    //IIS发布部署连接字符串必须使用“SQL Server身份认证数据库连接方式,才能实现发布部署程序与数据库的CURD的操作

    //SqlServer数据库软件的连接字符串。

    "ConnectionString": "Data Source=.;Initial Catalog=ShopDemo;Integrated Security=False;Persist Security Info=False;User ID=zz;Password=zz;MultipleActiveResultSets=true;Trust Server Certificate=True",

    "DataProvider": "sqlserver",

    //Allow User Variables=True:MySql数据库软件是否支持执行带有参数的SQL命令语句,True:支持。

    //MySql数据库软件的连接字符串。

    // "ConnectionString": "Server=localhost;User ID=root;Password=zhoujian;Database=ShopDemo;Allow User Variables=True",

    // "DataProvider": "mysql",

    "SQLCommandTimeout": "" //注意该值被设定为:空字符串,在“appsettings.json”中如果设定为:null,则会出现警告信息,即使设定为:空字符串,在反序列化操作时依然会实例化为:null

  },

  "CacheConfig": {

    "DefaultCacheTime": 60,

    "ShortTermCacheTime": 3,

    "BundledFilesCacheTime": 120

  },

  "DistributedCacheConfig": {

    "DistributedCacheType": "redis",

    "Enabled": true,

    //"Name:是别名,可以任意起。

    //Ip:是Redis的服务端地址,例如安装本地,就是:127.0.0.1"

    //Port:端口号默认值:6379

    //Password:可以在配置字符串字符串中设置;也可以通过Redis安装的根目录下的配置文件(redis.windows.conf)进行设置,那么该密码就是全局性的。

    //connectRetry:连接的超时时后的,自动重新连接次数:3次,注意:该配置只在连接操作时有效。

    //syncTimeout:超时会影响redis取值的超时,但是开多个task时,超时设置非常大也解决不了。

    //DefaultDatabaseRedis软件的分布式数据库的编号值,一般Redis软件的分布式数据库的编号值默认是:015,当前配置为:10。注意:此处的配置使用的是数组,用于将来进行Redis分布式操作的可拓展。

    "ConnectionString": "127.0.0.1:6379,ssl=False,connectRetry=3,syncTimeout=10000,DefaultDatabase=10",

    //"ConnectionString": "127.0.0.1:6379,ssl=False",

    "SchemaName": "dbo",

    "TableName": "DistributedCache"

  }

}

15说明:

    1、当前程序中的所有复用代码来源于“nopCommerce_4.60.1”。

    2、在“nopCommerce_4.40.4-- 第14章 HttpContext?.Items数据读写_zhoujian_911的博客-CSDN博客”中本人有一个猜测:PerRequestCache套嵌类通过通过HttpContext?.Items为取代:WebApi+Swagger +Json +跨域(Cors),向nopCommerce程序的App前端提供数据,但随着前后端分离开发技术的发展已经不用这样复杂且精巧的实现了来向App前端提供数据了,所在“nopCommerce_4.60.1”中删除了与:PerRequestCache套嵌类相关的定义,这不但是一种好的重构,同时也简化了程序的定义实现,也证明了本人猜测的正确性。

    3、在“nopCommerce_4.60.1”中为了实现当前程序对Redis分布式数据库更加底层操作,把DistributedCacheManager类定义为抽象类,同时为了能够实例化和对Redis分布式数据库更加底层操作以增加定义了RedisCacheManager。实际上“nopCommerce_4.40.4”和“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例所定义的DistributedCacheManager类都可以满足当前程序对缓存功能的需要,而通过“StackExchange.Redis”中间件实例对Redis分布式数据库更加底层操作在当前程序中不是必须定义需要的,这样的定义方式使分布式缓存的定义更加复杂且不必须。

    4、计划任务实例的定时触发操作,必须依赖缓存的缓存时间模式和缓存时间,所以如果程序中如果定义计划任务则必须至少定义MemoryCacheManager类,这种耦合紧密的定义方式,不符合程序设计的基本原理,如果有更好的计划任务实例的定时触发操作的定义实现方式必须进行重构。

对以上功能更为具体实现和注释见230131_021shopDemo(分布式缓存数据库的定义实现)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/189310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

九龙证券|三胎概念股拉升…港股跳水,恒生科指重挫近5%

兔年首个交易日&#xff0c;A股迎来开门红&#xff0c;沪指开盘即打破3300点&#xff0c;创业板指一度涨近3%&#xff1b;港股却大幅下挫&#xff0c;恒生科技指数一度跌超5%。 详细来看&#xff0c;A股方面&#xff0c;两市股指全线高开&#xff0c;沪指开盘即打破3300点&…

WebDAV之葫芦儿·派盘+Keepass2Android

Keepass2Android 支持WebDAV方式连接葫芦儿派盘。 推荐一款密码管理器,允许人们使用复杂的组合进行登录,而不必记住所有的组合。 Keepass2Android可以支持大多数安卓互联网浏览器, Android设备上同步软件,还支持通过WebDAV添加葫芦儿派盘。

Versal系列0-AI Engine与Systolic Array

最近在开发VCK190时&#xff0c;发现Xilinx Versal系列的AI engine&#xff08;AIE&#xff09;&#xff0c;其实和Systolic Array&#xff08;SA&#xff09;有着很相似的地方。Xilinx工程师在研发AIE时&#xff0c;应该是有所借鉴SA的。Systolic Array最早是H. T. Kung于1982…

k8s工具kubepi介绍

目录 部署安装 登录 配置 日常操作 Kubepi是一个简单高效的k8s集群图形化管理工具&#xff0c;方便日常管理K8S集群&#xff0c;高效快速的查询日志定位问题的工具。 部署安装 持久化部署 # 创建持久化目录 mkdir -p /opt/kubepi # 安装 sudo docker run --privileged …

通信原理笔记—绪论

目录 通信的基本概念&#xff1a; 通信的目的&#xff1a;要克服某种障碍&#xff0c;实现信息高效、准确地传递。 狭义的通信系统&#xff1a; 广义的通信系统&#xff1a; 数字通信系统的基本组成&#xff1a; 数字通信的特点&#xff1a; (1)抗噪声和干扰能力强&#…

【自学Docker】Docker commit命令

Docker commit命令 大纲 docker commit命令教程 docker commit 命令用于根据 Docker容器 的更改创建一个新的 Dokcer镜像。该命令后面的 CONTAINER 可以是容器Id&#xff0c;或者是容器名。 docker commit命令语法 haicoder(www.haicoder.net)# docker commit [OPTIONS] CO…

day02_java入门

今日内容 零、 复习昨日 一、程序介绍 二、Java发展及特点 三、安装环境 四、运行机制 五、第一个程序 六、Java语言规范 七、了解DOS命令 八、作业 一、程序介绍 生活中程序: 为了到达某个目的,规定一些步骤. 计算机程序:为了完成某个功能,规定一些步骤. 模拟现实世界&#…

React的基本使用(及脚手架使用)

基本使用 1 React 的安装 安装命令&#xff1a;npm i react react-dom react 包是核心&#xff0c;提供创建元素、组件等功能react-dom 包提供 DOM 相关功能等 1. 引入 react 和 react-dom 两个 js 文件 <script src"./node_modules/react/umd/react.development.…

图、邻接矩阵、广度与深度优先、生成树

最近突然被问到这个问题&#xff0c;于是复习一下&#xff0c;用最通俗的语言解释。 图 无向图&#xff1a;如下左图各个顶点之间用不带箭头的边连接的图&#xff1b;相应的右图就是有向图 邻接矩阵 可以理解为表示上述图中顶点与顶点之间是否有直接相连的边&#xff08;有则…

定时任务组件Quartz

1 定时任务组件Quartz 1.1 Quartz介绍 Quartz是Job scheduling&#xff08;作业调度&#xff09;领域的一个开源项目&#xff0c;Quartz既可以单独使用也可以跟spring框架整合使用&#xff0c;在实际开发中一般会使用后者。使用Quartz可以开发一个或者多个定时任务&#xff0c;…

计算机网络第四章 网络层数据平面

4.0 目录[TOC]4.1 概述作用&#xff1a;主机到主机之间传输TCP segment或UDP datagram将段封装成IP datagram以及解封装IP datagram【在网络边缘和路由器上都要进行】A.两大功能&#xff1a;转发路由转发&#xff1a;从不同的端口接收数据&#xff0c;再通过合适的端口发送出去…

WPS表格:函数公式

文章目录1. ROW()、ROWS(array)1&#xff09;ROW()2&#xff09;ROWS(array)2. COUNT(参数)、COUNTA(参数)、COUNTIF(参数)1&#xff09;COUNT()2&#xff09;COUNTA()3&#xff09;COUNTIF()3. VLOOKUP(参数)、LOOKUP(参数)1&#xff09;VLOOKUP(参数)2&#xff09;LOOKUP(向量…

数据分析有发展前景吗?,零基础能学得会吗?

数据分析这门专业是近几年因大数据的出现而产生的新兴职业&#xff0c;分为大数据分析和数据分析师&#xff0c;区别在于大数据分析师要求更高&#xff0c;不仅需要数据分析的基本能力&#xff0c;还要具备编程能力、机器学习技能&#xff0c;以及本身所接触到处理的都是海量数…

webpack打包构建工具的使用和相关的配置

目录 一、 webpack的基础使用步骤 二、webpack的配置 1、入口和出口 2、 webpack打包后自动生成html文件并自动引入打包后的js 3、加载器loader 3.1、处理css文件 3.2、处理less文件 3.3、处理图片文件 3.4、处理字体文字 3.5、处理高版本js语法&#xff08;降级&#xff…

Linux locate命令

Linux locate命令用于查找符合条件的文档&#xff0c;他会去保存文档和目录名称的数据库内&#xff0c;查找合乎范本样式条件的文档或目录。一般情况我们只需要输入 locate your_file_name 即可查找指定文件。语法locate [-d ][--help][--version][范本样式...]参数&#xff1a…

Notepad++ 代码格式化插件工具

因为notepad的NppAStyle插件只支持格式化C、C、C#、Java这四种编程语言的代码&#xff0c;所以推荐使用这个CoolFormat的插件&#xff0c;相比于NPPAStyle&#xff0c;CoolFormat支持C\C\C#\CSS\HTML\Java\JavaScript\JSON\Objective-C\PHP\SQL\XML代码格式化工具。还可以作为V…

后端java模拟前端RSA.js加密登录爬虫

项目开发过程中&#xff0c;经常会遇到数据爬取需求&#xff0c;但是对于某些网站&#xff0c;由于前端加密&#xff0c;导致数据爬取不容易。比如某网站&#xff0c;前端使用RSA.js加密&#xff0c;并且后端返回对应的公钥的指数和模数&#xff0c;通过后端返回的指数和模数对…

电商如何打开数字化的破局之路

电商网购已经成为我们的日常生活&#xff0c;在如此高节奏的工作下&#xff0c;打开手机或者电脑从网上挑选自己需要的物品&#xff0c;方便快捷&#xff0c;伴随着移动互联网和月的高速发展&#xff0c;电子商务作为现今的产业在我国快速增长和兴起。 如今的电商模式多种多样&…

Elasticsearch7.8.0版本入门——JavaAPI操作(批量操作文档)

目录一、pom文件依赖二、批量操作文档 代码示例2.1、批量创建文档 代码示例2.2、批量删除文档 代码示例一、pom文件依赖 引入相关依赖 <!-- elasticsearch 依赖 --> <dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch…

网络分层:OSI模型与TCP/IP模型

前言 这部分个人还是觉得有点难&#xff0c;之前也看过类似的文章&#xff0c;还是没有理解&#xff0c;更多的是概念掌握 OSI模型&#xff1a;Open System Interconnection 这是一个概念模型&#xff0c;存在于理论上&#xff0c;而没有真正实现。需要参考这样的模型&#x…