using SiteManagementSystem_SoftwareEngineering_.Interface; using StackExchange.Redis; using System.Configuration; namespace SiteManagementSystem_SoftwareEngineering_.Service { public class RedisService(ILogger> logger, RedisUtils redisHelper) : ICacheService { private readonly ILogger> _logger = logger; private readonly RedisUtils _redisHelper = redisHelper; private readonly LinkedList _values = []; private readonly string _categoryName = typeof(TCategoryName).Name; public int Capacity { get; set; } = int.MaxValue; private Action? _expiredCallbackFunc = null; public Action? ExpiredCallbackFunc { set { _expiredCallbackFunc = value; _redisHelper.SetExpiredCallbackFunction(_categoryName, KeyDrop); } } public async Task 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 GetAsync(string key) => await _redisHelper.GetAsync(_categoryName, key); public async Task RemoveAsync(string key) => await _redisHelper.RemoveAsync(_categoryName, key); /// /// 删除现有的最早加入的数据 /// /// public async Task 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 _logger; private readonly Dictionary?> _expiredCallbackFunctions = []; private readonly IDatabase _database; private readonly int _numberOfDB; public RedisUtils(string connectionString,int redisDB, ILogger 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 SetAsync( string typeName, string key, string value, TimeSpan? expiry = null ) => await _database.StringSetAsync(typeName + " " + key, value, expiry); public async Task GetAsync(string typeName, string key) => await _database.StringGetAsync(typeName + " " + key); public async Task RemoveAsync(string typeName, string key) => await _database.KeyDeleteAsync(typeName + " " + key); public void SetExpiredCallbackFunction(string typeName, Action? 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]); } } }