1 Core.HashHelper
using System.Security.Cryptography;
namespace Core
{
/// <summary>
/// 【哈希助手--类】
/// <remarks>
/// 摘要:
/// 该类通过1个指定哈希加密算法生成1个唯一性的字符串(当前安全性较强的SHA-2包括有:SHA-224、SHA-256、SHA-384,和 SHA-512等; 比较常用但已经不安全的有:MD5、SHA1等)。
/// </remarks>
/// </summary>
public class HashHelper
{
/// <param name="data">一个字节数组实例,把任意类型的一个指定数据以二进制的格式储着到该实例中。</param>
/// <param name="hashAlgorithm">
/// 一个指定哈希加密算法名称的字符串常量(当前安全性较强的SHA-2包括有:SHA-224、SHA-256、SHA-384,和 SHA-512等; 比较常用但已经不安全的有:MD5、SHA1等)。
/// </param>
/// <param name="trimByteCount">
/// 截断长度值,该值指示字节数组实例中,有多少个二进制数据被用于构建哈希字符串,默认值为:0,即不执行截断操作,字节数组实例中的所有二进制数据都将被用于构建哈希字符串。
/// </param>
/// <summary>
/// 【创建哈希】
/// <returns>
/// 返回:
/// 1个唯一性的字符串。
/// </returns>
/// <remarks>
/// 摘要:
/// 通过指定的哈希加密算法,生成1个唯一性的字符串。
/// 注意:
/// 1、该方法用于生成一个唯一性的字符串,该字符串相当于一个唯一个的编号值,此时该方法与“Guid.NewGuid()”方法的功能相当;或用于安全性求较高的加/解密操作。
/// 2、该方法的应用场景取决于哈希加密算法名称,如果用于生成一个唯一个的编号值,则使用“MD5、SHA1”即可;如果用于安全性求较高的加/解密操作,则只有“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-224、SHA-256、SHA-384,和 SHA-512等; 比较常用但已经不安全的有:MD5、SHA1等)。
/// 说明:
/// 由于在该类中哈希加密算法主要用于生成用于键拼接的字符串,所以不需要太强的安全性“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)
{
//如果缓存键实例的缓存缓存时间小于等于0或1个指定的泛型实例的实例值为: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个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(键/值对)才能被销毁。
//如果AbsoluteExpirationRelativeToNow和SlidingExpiration都未设定,则Redis中指定键字符串的absexp和sldexp所对应的值都=-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编码格式的)缓存值不为空,则获取true和1个指定实体的1/n个实例;如果(JSON编码格式的)缓存值为空,则获取false和1个指定实体的空实例。
/// </remarks>
/// <returns>
/// 返回:
/// true和1个指定实体的1/n个实例。
/// </returns>
/// </summary>
private async Task<(bool isSet, T item)> TryGetItemAsync<T>(CacheKey key)
{
var json = await _distributedCache.GetStringAsync(key.Key);
//如果(JSON编码格式的)缓存值为空,则获取false和1个指定实体的空实例。
if (string.IsNullOrEmpty(json))
return (false, default);
//通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。
_onKeyAdded?.Invoke(key);
//如果(JSON编码格式的)缓存值不为空,则获取true和1个指定实体的1/n个实例。
return (true, JsonConvert.DeserializeObject<T>(json));
}
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <param name="key">缓存键类的1个指定实例。</param>
/// <summary>
/// 【尝试获取缓存项】
/// <remarks>
/// 摘要:
/// 如果(JSON编码格式的)缓存值不为空,则获取true和1个指定实体的1/n个实例;如果(JSON编码格式的)缓存值为空,则获取false和1个指定实体的空实例。
/// </remarks>
/// <returns>
/// 返回:
/// true和1个指定实体的1/n个实例。
/// </returns>
/// </summary>
private (bool isSet, T item) TryGetItem<T>(CacheKey key)
{
var json = _distributedCache.GetString(key.Key);
//如果(JSON编码格式的)缓存值为空,则获取false和1个指定实体的空实例。
if (string.IsNullOrEmpty(json))
return (false, default);
//通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。
_onKeyAdded?.Invoke(key);
//如果(JSON编码格式的)缓存值不为空,则获取true和1个指定实体的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)
{
//如果缓存键实例的缓存缓存时间小于等于0或1个指定的泛型实例的实例值为: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个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(键/值对)才能被销毁。
//如果AbsoluteExpirationRelativeToNow和SlidingExpiration都未设定,则Redis中指定键字符串的absexp和sldexp所对应的值都=-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)
{
//如果缓存键实例的缓存缓存时间小于等于0或1个指定的泛型实例的实例值为: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=true或Integrated 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时,超时设置非常大也解决不了。
//DefaultDatabase:Redis软件的分布式数据库的编号值,一般Redis软件的分布式数据库的编号值默认是:0到15,当前配置为: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(分布式缓存数据库的定义实现)。