Software_Engineering_Field_.../SiteManagementSystem(Softwa.../Service/RedisService.cs

117 lines
4.6 KiB
C#

using SiteManagementSystem_SoftwareEngineering_.Interface;
using StackExchange.Redis;
using System.Configuration;
namespace SiteManagementSystem_SoftwareEngineering_.Service
{
public class RedisService<TCategoryName>(ILogger<RedisService<TCategoryName>> logger, RedisUtils redisHelper) : ICacheService<TCategoryName>
{
private readonly ILogger<RedisService<TCategoryName>> _logger = logger;
private readonly RedisUtils _redisHelper = redisHelper;
private readonly LinkedList<string> _values = [];
private readonly string _categoryName = typeof(TCategoryName).Name;
public int Capacity { get; set; } = int.MaxValue;
private Action<string>? _expiredCallbackFunc = null;
public Action<string>? ExpiredCallbackFunc
{
set
{
_expiredCallbackFunc = value;
_redisHelper.SetExpiredCallbackFunction(_categoryName, KeyDrop);
}
}
public async Task<bool> SetAsync(string key, string value, TimeSpan? expiry = null)
{
// 现在默认容量是最大值,除非调用方设置容量,否则不会调用 RemoveLatestKeyAsync()
while (_values.Count >= Capacity)
await RemoveLatestKeyAsync();
_values.AddLast(key);
return await _redisHelper.SetAsync(_categoryName, key, value, expiry);
}
public async Task<string?> GetAsync(string key) =>
await _redisHelper.GetAsync(_categoryName, key);
public async Task<bool> RemoveAsync(string key) =>
await _redisHelper.RemoveAsync(_categoryName, key);
/// <summary>
/// 删除现有的最早加入的数据
/// </summary>
/// <returns></returns>
public async Task<bool> RemoveLatestKeyAsync()
{
if (_values.Count == 0)
return false;
var key = _values.First();
_logger.LogWarning($"{key} dropped due to limited capacity.");
KeyDrop(key);
return await RemoveAsync(key);
}
private void KeyDrop(string key)
{
_values.Remove(key);
_expiredCallbackFunc?.Invoke(key);
}
}
// Redis: CONFIG set notify-keyspace-events Ex
// Key style: typeName+ ' ' + key
// IDistributedCache 确实封装了一些有用的方法 但是其最大的问题是无法在键过期后主动发出事件
// 内部的RedisCache类没有保留IConnectionMultiplexer 而是获取了 dataBase 后就舍弃了
// 因此 继承再反射不是一个很好的选项,不如重新封装
public class RedisUtils
{
private readonly ConnectionMultiplexer _redis;
private readonly ILogger<RedisUtils> _logger;
private readonly Dictionary<string, Action<string>?> _expiredCallbackFunctions = [];
private readonly IDatabase _database;
private readonly int _numberOfDB;
public RedisUtils(string connectionString,int redisDB, ILogger<RedisUtils> logger)
{
_numberOfDB = redisDB;
_logger = logger;
_redis = ConnectionMultiplexer.Connect(connectionString);
_redis
.GetSubscriber()
.Subscribe(
RedisChannel.Pattern($"__keyevent@{_numberOfDB}__:expired"),
(_, message) =>
{
DataExpiredHandler(message);
}
);
_database = _redis.GetDatabase(_numberOfDB);
// TODO: create a func to check if Redis setting is correct
}
// typeName+ ' ' + key
// key is Hash, value is type.When key expired, you can't get value!
public async Task<bool> SetAsync(
string typeName,
string key,
string value,
TimeSpan? expiry = null
) => await _database.StringSetAsync(typeName + " " + key, value, expiry);
public async Task<string?> GetAsync(string typeName, string key) =>
await _database.StringGetAsync(typeName + " " + key);
public async Task<bool> RemoveAsync(string typeName, string key) =>
await _database.KeyDeleteAsync(typeName + " " + key);
public void SetExpiredCallbackFunction(string typeName, Action<string>? func) =>
_expiredCallbackFunctions[typeName] = func;
private void DataExpiredHandler(RedisValue val)
{
var temp = val.ToString().Split(' ');
if (_expiredCallbackFunctions.TryGetValue(temp[0], out var func))
func?.Invoke(temp[1]);
}
}
}