117 lines
4.6 KiB
C#
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]);
|
|
}
|
|
}
|
|
}
|