1. 缓存模块源码解析

个人觉得 ABP 分布式缓存模块有三个值得关注的核心点。首先是 AbpRedisCache 类继承了微软原生的 RedisCache,并 通过反射的方式获取RedisCache的私有方法对 RedisCache 进行扩展,实现了 ABP 分布式缓存中的批量操作方法。

为什么要这么做呢?因为基于 Redis 缓存的批量操作需要使用到 StackExchange.Redis 原生的SDK,而且在进行操作前总需要先进行 Redis 连接,而相应的方法和属性原生的 RedisCache 中都有,但是都是私有(private)的,在继承类中也无法使用,所以使用反射的方式提取出相应的方法和属性,以便在继承类中复用。这也是对于类功能进行继承扩展的时候的一种很有用的方式。

第二点是 ABP 缓存模块通过 IDistributedCache<CacheItem, CacheKey> 接口扩展了原生的 IDistributedCache 接口的功能,而在具体实现上是将原有的 IDistributedCache 服务注入进行复用相应功能的,并在前后增加额外的逻辑对功能进行扩展增强,实际上就是适配器模式。

而最常使用的 IDistributedCache<CacheItem> 接口,以前其实现类是直接继承 DistributedCache<CacheItem, CacheKey> 的,现在改成了直接用适配器模式。

最后一个是缓存的事务性,通过工作单元使缓存和其他事务操作保持原子性,避免缓存已经更新而其他事务失败回滚导致数据不一致的问题。实际上就是先不真正地更新缓存,而是将缓存数据通过字典保存在工作单元中,保证一个工作单元内拿到的缓存数据是最新的,同时注册工作单元提交事件,在工作单元正在提交成功的时候才执行真正更新缓存的逻辑。

工作单元相关的内容就后面再在专门的章节讲吧,这部分的内容比较复杂,一时半会比较难讲清。

2. 自己扩展的 IDistributedCache

ABP 框架扩展的 IDistributedCache 泛型接口在内部帮我们处理实例对象进行序列化/反序列化以及转码为 byte 数组的问题,大大提升了我们使用分布式缓存的方便性,但也存在一些问题。

  • 它是基于类的泛型,如果我们在一个服务中需要使用多个缓存类型的话,我们就得注入多个的泛型接口,还是有些不方便的
  • 它的缓存键是以默认是以泛型类型的全类名作为前缀,虽然我们可以通过特性指定类型名称,但是对于集合,缓存键就很不清晰了

    由于这些情况的存在,也基于我们在日常开发中的使用习惯,我在工作中又基于 ABP 的 IDistributedCache<CacheItem> 接口进行扩展。内部实现基本一致,主要就是将基于类的泛型,改成基于方法的泛型,并且提供自己的 IDistributedCacheKeyNormalizer 实现类,将缓存键的设置规则交给了缓存存取时进行设置。 代码如下:
public interface IWantDistributedCache
{
/// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache item, or null.</returns>
TCacheItem Get<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class; /// <summary>
/// Gets multiple cache items with the given keys.
///
/// The returned list contains exactly the same count of items specified in the given keys.
/// An item in the return list can not be null, but an item in the list has null value
/// if the related key not found in the cache.
/// </summary>
/// <param name="keys">The keys of cached items to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>List of cache items.</returns>
KeyValuePair<TCacheKey, TCacheItem>[] GetMany<TCacheItem, TCacheKey>(
IEnumerable<TCacheKey> keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class; /// <summary>
/// Gets multiple cache items with the given keys.
///
/// The returned list contains exactly the same count of items specified in the given keys.
/// An item in the return list can not be null, but an item in the list has null value
/// if the related key not found in the cache.
///
/// </summary>
/// <param name="keys">The keys of cached items to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>List of cache items.</returns>
Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync<TCacheItem, TCacheKey>(
IEnumerable<TCacheKey> keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class; /// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
Task<TCacheItem> GetAsync<TCacheItem, TCacheKey>(
[NotNull] TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class; /// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache item.</returns>
TCacheItem GetOrAdd<TCacheItem, TCacheKey>(
TCacheKey key,
Func<TCacheItem> factory,
string cacheName,
Func<DistributedCacheEntryOptions> optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class; /// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
Task<TCacheItem> GetOrAddAsync<TCacheItem, TCacheKey>(
[NotNull] TCacheKey key,
Func<Task<TCacheItem>> factory,
string cacheName,
Func<DistributedCacheEntryOptions> optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class; /// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
void Set<TCacheItem, TCacheKey>(
TCacheKey key,
TCacheItem value,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class; /// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task SetAsync<TCacheItem, TCacheKey>(
[NotNull] TCacheKey key,
[NotNull] TCacheItem value,
string cacheName,
[CanBeNull] DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class; /// <summary>
/// Sets multiple cache items.
/// Based on the implementation, this can be more efficient than setting multiple items individually.
/// </summary>
/// <param name="items">Items to set on the cache</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
void SetMany<TCacheItem, TCacheKey>(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class; /// <summary>
/// Sets multiple cache items.
/// Based on the implementation, this can be more efficient than setting multiple items individually.
/// </summary>
/// <param name="items">Items to set on the cache</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task SetManyAsync<TCacheItem, TCacheKey>(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class; /// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
void Refresh<TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null
); /// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task RefreshAsync<TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
CancellationToken token = default
); /// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
void Remove<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false
) where TCacheItem : class; /// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
Task RemoveAsync<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default
) where TCacheItem : class;
} public class WantDistributedCache : IWantDistributedCache
{
public const string UowCacheName = "WantDistributedCache"; public ILogger<WantDistributedCache> Logger { get; set; } protected string CacheName { get; set; } protected bool IgnoreMultiTenancy { get; set; } protected IDistributedCache Cache { get; } protected ICancellationTokenProvider CancellationTokenProvider { get; } protected IDistributedCacheSerializer Serializer { get; } protected IDistributedCacheKeyNormalizer KeyNormalizer { get; } protected IHybridServiceScopeFactory ServiceScopeFactory { get; } protected IUnitOfWorkManager UnitOfWorkManager { get; } protected SemaphoreSlim SyncSemaphore { get; } protected DistributedCacheEntryOptions DefaultCacheOptions; private readonly AbpDistributedCacheOptions _distributedCacheOption; public WantDistributedCache(
IOptions<AbpDistributedCacheOptions> distributedCacheOption,
IDistributedCache cache,
ICancellationTokenProvider cancellationTokenProvider,
IDistributedCacheSerializer serializer,
IDistributedCacheKeyNormalizer keyNormalizer,
IHybridServiceScopeFactory serviceScopeFactory,
IUnitOfWorkManager unitOfWorkManager)
{
_distributedCacheOption = distributedCacheOption.Value;
Cache = cache;
CancellationTokenProvider = cancellationTokenProvider;
Logger = NullLogger<WantDistributedCache>.Instance;
Serializer = serializer;
KeyNormalizer = keyNormalizer;
ServiceScopeFactory = serviceScopeFactory;
UnitOfWorkManager = unitOfWorkManager; SyncSemaphore = new SemaphoreSlim(1, 1); SetDefaultOptions();
} protected virtual string NormalizeKey<TCacheKey>(TCacheKey key, string cacheName)
{
return KeyNormalizer.NormalizeKey(
new DistributedCacheKeyNormalizeArgs(
key.ToString(),
cacheName,
IgnoreMultiTenancy
)
);
} protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions()
{
foreach (var configure in _distributedCacheOption.CacheConfigurators)
{
var options = configure.Invoke(CacheName);
if (options != null)
{
return options;
}
} return _distributedCacheOption.GlobalCacheEntryOptions;
} protected virtual void SetDefaultOptions()
{
//CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem)); ////IgnoreMultiTenancy
//IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); //Configure default cache entry options
DefaultCacheOptions = GetDefaultCacheEntryOptions();
} /// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache item, or null.</returns>
public virtual TCacheItem Get<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; if (ShouldConsiderUow(considerUow))
{
var value = GetUnitOfWorkCache<TCacheItem, TCacheKey>().GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
return value;
}
} byte[] cachedBytes; try
{
cachedBytes = Cache.Get(NormalizeKey(key, cacheName));
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return null;
} throw;
} return ToCacheItem<TCacheItem>(cachedBytes);
} public virtual KeyValuePair<TCacheKey, TCacheItem>[] GetMany<TCacheItem, TCacheKey>(
IEnumerable<TCacheKey> keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
var keyArray = keys.ToArray(); var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
return GetManyFallback<TCacheItem, TCacheKey>(
keyArray,
cacheName,
hideErrors,
considerUow
);
} var notCachedKeys = new List<TCacheKey>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem>>();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>();
foreach (var key in keyArray)
{
var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem>(key, value));
}
} notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList();
if (!notCachedKeys.Any())
{
return cachedValues.ToArray();
}
} hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[][] cachedBytes; var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
try
{
cachedBytes = cacheSupportsMultipleItems.GetMany(readKeys.Select(key => NormalizeKey(key, cacheName)));
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return ToCacheItemsWithDefaultValues<TCacheItem, TCacheKey>(keyArray);
} throw;
} return cachedValues.Concat(ToCacheItems<TCacheItem, TCacheKey>(cachedBytes, readKeys)).ToArray();
} protected virtual KeyValuePair<TCacheKey, TCacheItem>[] GetManyFallback<TCacheItem, TCacheKey>(
TCacheKey[] keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
return keys
.Select(key => new KeyValuePair<TCacheKey, TCacheItem>(
key,
Get<TCacheItem, TCacheKey>(key, cacheName, false, considerUow)
)
).ToArray();
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return ToCacheItemsWithDefaultValues<TCacheItem, TCacheKey>(keys);
} throw;
}
} public virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync<TCacheItem, TCacheKey>(
IEnumerable<TCacheKey> keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
var keyArray = keys.ToArray(); var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
return await GetManyFallbackAsync<TCacheItem, TCacheKey>(
keyArray,
cacheName,
hideErrors,
considerUow,
token
);
} var notCachedKeys = new List<TCacheKey>();
var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem>>();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>();
foreach (var key in keyArray)
{
var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
cachedValues.Add(new KeyValuePair<TCacheKey, TCacheItem>(key, value));
}
} notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList();
if (!notCachedKeys.Any())
{
return cachedValues.ToArray();
}
} hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
byte[][] cachedBytes; var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; try
{
cachedBytes = await cacheSupportsMultipleItems.GetManyAsync(
readKeys.Select(key => NormalizeKey(key, cacheName)),
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return ToCacheItemsWithDefaultValues<TCacheItem, TCacheKey>(keyArray);
} throw;
} return cachedValues.Concat(ToCacheItems<TCacheItem, TCacheKey>(cachedBytes, readKeys)).ToArray();
} protected virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyFallbackAsync<TCacheItem, TCacheKey>(
TCacheKey[] keys,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
var result = new List<KeyValuePair<TCacheKey, TCacheItem>>(); foreach (var key in keys)
{
result.Add(new KeyValuePair<TCacheKey, TCacheItem>(
key,
await GetAsync<TCacheItem, TCacheKey>(key, cacheName, false, considerUow, token: token))
);
} return result.ToArray();
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return ToCacheItemsWithDefaultValues<TCacheItem, TCacheKey>(keys);
} throw;
}
} /// <summary>
/// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item, or null.</returns>
public virtual async Task<TCacheItem> GetAsync<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; if (ShouldConsiderUow(considerUow))
{
var value = GetUnitOfWorkCache<TCacheItem, TCacheKey>().GetOrDefault(key)?.GetUnRemovedValueOrNull();
if (value != null)
{
return value;
}
} byte[] cachedBytes; try
{
cachedBytes = await Cache.GetAsync(
NormalizeKey(key, cacheName),
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return null;
} throw;
} if (cachedBytes == null)
{
return null;
} return Serializer.Deserialize<TCacheItem>(cachedBytes);
} /// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <returns>The cache item.</returns>
public virtual TCacheItem GetOrAdd<TCacheItem, TCacheKey>(
TCacheKey key,
Func<TCacheItem> factory,
string cacheName,
Func<DistributedCacheEntryOptions> optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
var value = Get<TCacheItem, TCacheKey>(key, cacheName, hideErrors, considerUow);
if (value != null)
{
return value;
} using (SyncSemaphore.Lock())
{
value = Get<TCacheItem, TCacheKey>(key, cacheName, hideErrors, considerUow);
if (value != null)
{
return value;
} value = factory(); if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>();
if (uowCache.TryGetValue(key, out var item))
{
item.SetValue(value);
}
else
{
uowCache.Add(key, new UnitOfWorkCacheItem<TCacheItem>(value));
}
} Set(key, value, cacheName, optionsFactory?.Invoke(), hideErrors, considerUow);
} return value;
} /// <summary>
/// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
/// provided by <paramref name="factory" /> delegate and returns the provided cache item.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
/// <param name="optionsFactory">The cache options for the factory delegate.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The cache item.</returns>
public virtual async Task<TCacheItem> GetOrAddAsync<TCacheItem, TCacheKey>(
TCacheKey key,
Func<Task<TCacheItem>> factory,
string cacheName,
Func<DistributedCacheEntryOptions> optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
token = CancellationTokenProvider.FallbackToProvider(token);
var value = await GetAsync<TCacheItem, TCacheKey>(key, cacheName, hideErrors, considerUow, token);
if (value != null)
{
return value;
} using (await SyncSemaphore.LockAsync(token))
{
value = await GetAsync<TCacheItem, TCacheKey>(key, cacheName, hideErrors, considerUow, token);
if (value != null)
{
return value;
} value = await factory(); if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>();
if (uowCache.TryGetValue(key, out var item))
{
item.SetValue(value);
}
else
{
uowCache.Add(key, new UnitOfWorkCacheItem<TCacheItem>(value));
}
} await SetAsync(key, value, cacheName, optionsFactory?.Invoke(), hideErrors, considerUow, token);
} return value;
} /// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
public virtual void Set<TCacheItem, TCacheKey>(
TCacheKey key,
TCacheItem value,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
void SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
Cache.Set(
NormalizeKey(key, cacheName),
Serializer.Serialize(value),
options ?? DefaultCacheOptions
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
} throw;
}
} if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>();
if (uowCache.TryGetValue(key, out _))
{
uowCache[key].SetValue(value);
}
else
{
uowCache.Add(key, new UnitOfWorkCacheItem<TCacheItem>(value));
} // ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(() =>
{
SetRealCache();
return Task.CompletedTask;
});
}
else
{
SetRealCache();
}
}
/// <summary>
/// Sets the cache item value for the provided key.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="value">The cache item value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
public virtual async Task SetAsync<TCacheItem, TCacheKey>(
TCacheKey key,
TCacheItem value,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
async Task SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
await Cache.SetAsync(
NormalizeKey(key, cacheName),
Serializer.Serialize(value),
options ?? DefaultCacheOptions,
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
} throw;
}
} if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>();
if (uowCache.TryGetValue(key, out _))
{
uowCache[key].SetValue(value);
}
else
{
uowCache.Add(key, new UnitOfWorkCacheItem<TCacheItem>(value));
} // ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(SetRealCache);
}
else
{
await SetRealCache();
}
} public void SetMany<TCacheItem, TCacheKey>(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
var itemsArray = items.ToArray(); var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
SetManyFallback(
itemsArray,
cacheName,
options,
hideErrors,
considerUow
); return;
} void SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
cacheSupportsMultipleItems.SetMany(
ToRawCacheItems(itemsArray, cacheName),
options ?? DefaultCacheOptions
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
} throw;
}
} if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>(); foreach (var pair in itemsArray)
{
if (uowCache.TryGetValue(pair.Key, out _))
{
uowCache[pair.Key].SetValue(pair.Value);
}
else
{
uowCache.Add(pair.Key, new UnitOfWorkCacheItem<TCacheItem>(pair.Value));
}
} // ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(() =>
{
SetRealCache();
return Task.CompletedTask;
});
}
else
{
SetRealCache();
}
} protected virtual void SetManyFallback<TCacheItem, TCacheKey>(
KeyValuePair<TCacheKey, TCacheItem>[] items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
foreach (var item in items)
{
Set(
item.Key,
item.Value,
cacheName,
options,
false,
considerUow
);
}
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
} throw;
}
} public virtual async Task SetManyAsync<TCacheItem, TCacheKey>(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
var itemsArray = items.ToArray(); var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
await SetManyFallbackAsync(
itemsArray,
cacheName,
options,
hideErrors,
considerUow,
token
); return;
} async Task SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
await cacheSupportsMultipleItems.SetManyAsync(
ToRawCacheItems(itemsArray, cacheName),
options ?? DefaultCacheOptions,
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
} throw;
}
} if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>(); foreach (var pair in itemsArray)
{
if (uowCache.TryGetValue(pair.Key, out _))
{
uowCache[pair.Key].SetValue(pair.Value);
}
else
{
uowCache.Add(pair.Key, new UnitOfWorkCacheItem<TCacheItem>(pair.Value));
}
} // ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(SetRealCache);
}
else
{
await SetRealCache();
}
} protected virtual async Task SetManyFallbackAsync<TCacheItem, TCacheKey>(
KeyValuePair<TCacheKey, TCacheItem>[] items,
string cacheName,
DistributedCacheEntryOptions options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
foreach (var item in items)
{
await SetAsync(
item.Key,
item.Value,
cacheName,
options,
false,
considerUow,
token: token
);
}
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
} throw;
}
} /// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
public virtual void Refresh<TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
Cache.Refresh(NormalizeKey(key, cacheName));
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
} throw;
}
} /// <summary>
/// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
public virtual async Task RefreshAsync<TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
CancellationToken token = default)
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
await Cache.RefreshAsync(NormalizeKey(key, cacheName), CancellationTokenProvider.FallbackToProvider(token));
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
} throw;
}
} /// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
public virtual void Remove<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false) where TCacheItem : class
{
void RemoveRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
Cache.Remove(NormalizeKey(key, cacheName));
}
catch (Exception ex)
{
if (hideErrors == true)
{
HandleException(ex);
return;
} throw;
}
} if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>();
if (uowCache.TryGetValue(key, out _))
{
uowCache[key].RemoveValue();
} // ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(() =>
{
RemoveRealCache();
return Task.CompletedTask;
});
}
else
{
RemoveRealCache();
}
} /// <summary>
/// Removes the cache item for given key from cache.
/// </summary>
/// <param name="key">The key of cached item to be retrieved from the cache.</param>
/// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
/// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
/// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
public virtual async Task RemoveAsync<TCacheItem, TCacheKey>(
TCacheKey key,
string cacheName,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default) where TCacheItem : class
{
async Task RemoveRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; try
{
await Cache.RemoveAsync(NormalizeKey(key, cacheName), CancellationTokenProvider.FallbackToProvider(token));
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
} throw;
}
} if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache<TCacheItem, TCacheKey>();
if (uowCache.TryGetValue(key, out _))
{
uowCache[key].RemoveValue();
} // ReSharper disable once PossibleNullReferenceException
UnitOfWorkManager.Current.OnCompleted(RemoveRealCache);
}
else
{
await RemoveRealCache();
}
} protected virtual void HandleException(Exception ex)
{
AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
} protected virtual async Task HandleExceptionAsync(Exception ex)
{
Logger.LogException(ex, LogLevel.Warning); using (var scope = ServiceScopeFactory.CreateScope())
{
await scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning));
}
} protected virtual KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItems<TCacheItem, TCacheKey>(byte[][] itemBytes, TCacheKey[] itemKeys) where TCacheItem: class
{
if (itemBytes.Length != itemKeys.Length)
{
throw new AbpException("count of the item bytes should be same with the count of the given keys");
} var result = new List<KeyValuePair<TCacheKey, TCacheItem>>(); for (int i = 0; i < itemKeys.Length; i++)
{
result.Add(
new KeyValuePair<TCacheKey, TCacheItem>(
itemKeys[i],
ToCacheItem<TCacheItem>(itemBytes[i])
)
);
} return result.ToArray();
} [CanBeNull]
protected virtual TCacheItem ToCacheItem<TCacheItem>([CanBeNull] byte[] bytes) where TCacheItem : class
{
if (bytes == null)
{
return null;
} return Serializer.Deserialize<TCacheItem>(bytes);
} protected virtual KeyValuePair<string, byte[]>[] ToRawCacheItems<TCacheItem, TCacheKey>(KeyValuePair<TCacheKey, TCacheItem>[] items, string cacheName)
{
return items
.Select(i => new KeyValuePair<string, byte[]>(
NormalizeKey(i.Key, cacheName),
Serializer.Serialize(i.Value)
)
).ToArray();
} private static KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItemsWithDefaultValues<TCacheItem, TCacheKey>(TCacheKey[] keys)
{
return keys
.Select(key => new KeyValuePair<TCacheKey, TCacheItem>(key, default))
.ToArray();
} protected virtual bool ShouldConsiderUow(bool considerUow)
{
return considerUow && UnitOfWorkManager.Current != null;
} protected virtual string GetUnitOfWorkCacheKey()
{
return UowCacheName + CacheName;
} protected virtual Dictionary<TCacheKey, UnitOfWorkCacheItem<TCacheItem>> GetUnitOfWorkCache<TCacheItem, TCacheKey>() where TCacheItem : class
{
if (UnitOfWorkManager.Current == null)
{
throw new AbpException($"There is no active UOW.");
} return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(),
key => new Dictionary<TCacheKey, UnitOfWorkCacheItem<TCacheItem>>());
}
} [Dependency(ReplaceServices = true)]
public class WantDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency
{
protected AbpDistributedCacheOptions DistributedCacheOptions { get; } public SuncereDistributedCacheKeyNormalizer(
IOptions<AbpDistributedCacheOptions> distributedCacheOptions)
{
DistributedCacheOptions = distributedCacheOptions.Value;
} public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args)
{
// 缓存格式: a:appname,c:cachename,k:key
var normalizedKey = $"a:{DistributedCacheOptions.KeyPrefix},c:{args.CacheName},k:{args.Key}";
return normalizedKey;
}
} [DependsOn(typeof(AbpCachingModule))]
public class WantAbpCacheModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//注入缓存类
context.Services.AddSingleton(typeof(ISuncereDistributedCache), typeof(SuncereDistributedCache));
}
}

参考文章:

ABP 官方文档 - 缓存

ABP 系列总结:

目录:ABP 系列总结

上一篇:ABP - 缓存模块(1)

ABP - 缓存模块(2)的更多相关文章

  1. 基于DDD的.NET开发框架 - ABP缓存Caching实现

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  2. 【Java EE 学习 78 上】【数据采集系统第十天】【Service使用Spring缓存模块】

    一.需求分析 调查问卷中或许每一个单击动作都会引发大量的数据库访问,特别是在参与调查的过程中,只是单击“上一页”或者“下一页”的按钮就会引发大量的查询,必须对这种问题进行优化才行.使用缓存策略进行查询 ...

  3. .NET 缓存模块设计

    上一篇谈了我对缓存的概念,框架上的理解和看法,这篇承接上篇讲讲我自己的缓存模块设计实践. 基本的缓存模块设计 最基础的缓存模块一定有一个统一的CacheHelper,如下: public interf ...

  4. ABP之模块

    ABP的反射 为什么先讲反射,因为ABP的模块管理基本就是对所有程序集进行遍历,再筛选出AbpModule的派生类,再按照以来关系顺序加载. ABP对反射的封装着重于程序集(Assembly)与类(T ...

  5. IOS编程 图片缓存模块设计

    手机客户端为什么会留存下来?而不是被一味的Wap替代掉?因为手机客户端有Wap无可替代的优势,就是自身较强的计算能力. 手机中不可避免的一环:图片缓存,在软件的整个运行过程中显得尤为重要. 先简单说一 ...

  6. 使用spring EL表达式+自定义切面封装缓存模块

    需求是这样的,业务代码需要使用到缓存功能以减少数据库压力,使用redis来实现,并且需要生成缓存的key由方法的传参拼接而成(貌似也只能这样才能保证同样的select查询可以使用缓存),简单的方式就是 ...

  7. 一步步实现一个基本的缓存模块·续, 添加Memcached调用实现

    jusfr 原创,转载请注明来自博客园. 在之前的实现中,我们初步实现了一个缓存模块:包含一个基于Http请求的缓存实现,一个基于HttpRuntime.Cache进程级的缓存实现,但观察代码,会发现 ...

  8. 简单的内存缓存模块 - Smache

    介绍 [sm]art + c[ache] = smache Smache 是一个方便的内存缓存模块,可以通过一些简单缓存策略避免无限占用更多的内存,同时确保最常用最应该被缓存的对象被缓存. GitHu ...

  9. 【构建Android缓存模块】(一)吐槽与原理分析

    http://my.oschina.net/ryanhoo/blog/93285 摘要:在我翻译的Google官方系列教程中,Bitmap系列由浅入深地介绍了如何正确的解码Bitmap,异步线程操作以 ...

  10. jQuery 源码分析(十) 数据缓存模块 data详解

    jQuery的数据缓存模块以一种安全的方式为DOM元素附加任意类型的数据,避免了在JavaScript对象和DOM元素之间出现循环引用,以及由此而导致的内存泄漏. 数据缓存模块为DOM元素和JavaS ...

随机推荐

  1. [Git/SVN] Gitee使用教程

    1 Gitee Gitee 提供免费的 Git 仓库,还集成了代码质量检测.项目演示等功能. 对于团队协作开发,Gitee 还提供了项目管理.代码托管.文档管理的服务,5 人以下小团队免费. CASE ...

  2. 系统建模之UML状态图[转载]

    1 状态图的简介(Instrduction) 状态图(Statechart Diagram)主要用于描述一个对象在其生存期间的动态行为,表现为一个对象所经历的状态序列,引起状态转移的事件(Event) ...

  3. 1.封装PageHelper实现分页

    前言 这几天想着动手将一些技术融合到项目中,昨天思考了会儿,想起了我与亲戚的对话:我说:"我想将若依项目完整的实现一遍",亲戚给我反馈到"你没必要完整复现若依项目,而且你 ...

  4. Linux(二)文件权限和压缩

    1 搜索查找类 1.1 查找定位文件 find <搜索范围.路径> <选项> find将从指定目录下递归地遍历其各个子目录,将满足条件的文件显示在终端. 选项说明 -name: ...

  5. 2023-05-07:给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。 返回执行此操作后,grid 中最大的岛屿面积是多少? 岛屿 由一组上、下、左、右四个方向相

    2023-05-07:给你一个大小为 n x n 二进制矩阵 grid .最多 只能将一格 0 变成 1 . 返回执行此操作后,grid 中最大的岛屿面积是多少? 岛屿 由一组上.下.左.右四个方向相 ...

  6. 2022-12-26:有一个数组包含0、1、2三种值, 有m次修改机会,第一种将所有连通的1变为0,修改次数-1, 第二种将所有连通的2变为1或0,修改次数-2, 返回m次修改机会的情况下,让最大的0

    2022-12-26:有一个数组包含0.1.2三种值, 有m次修改机会,第一种将所有连通的1变为0,修改次数-1, 第二种将所有连通的2变为1或0,修改次数-2, 返回m次修改机会的情况下,让最大的0 ...

  7. 2022-09-12:以下go语言代码输出什么?A:true;B:false;C:无法编译;D:运行时 panic。 package main func main() { var x chan<-

    2022-09-12:以下go语言代码输出什么?A:true:B:false:C:无法编译:D:运行时 panic. package main func main() { var x chan< ...

  8. 2021-11-06:3的幂。给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。整数 n 是 3 的幂次方需满足:存在整数 x 使得 n ==

    2021-11-06:3的幂.给定一个整数,写一个函数来判断它是否是 3 的幂次方.如果是,返回 true :否则,返回 false .整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == ...

  9. 2021-09-21:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。要

    2021-09-21:给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值在数组中的开始位置和结束位置.如果数组中不存在目标值 target,返回 [-1, -1].要 ...

  10. drf-spectacular

    介绍 drf-spectacular是为Django REST Framework生成合理灵活的OpenAPI 3.0模式.它可以自动帮我们提取接口中的信息,从而形成接口文档,而且内容十分详细,再也不 ...