From cc41c0fd04de52bf4389d071ee64e42f804c5b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=B9=E6=B5=B7?= Date: Wed, 22 May 2024 13:35:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Framework/YLErp.Cache/CacheConfig.cs | 31 +++ Framework/YLErp.Cache/CacheModule.cs | 21 ++ Framework/YLErp.Cache/IYLCache.cs | 78 ++++++++ Framework/YLErp.Cache/YLErp.Cache.csproj | 16 ++ Framework/YLErp.Cache/YLRedisCache.cs | 239 +++++++++++++++++++++++ 5 files changed, 385 insertions(+) create mode 100644 Framework/YLErp.Cache/CacheConfig.cs create mode 100644 Framework/YLErp.Cache/CacheModule.cs create mode 100644 Framework/YLErp.Cache/IYLCache.cs create mode 100644 Framework/YLErp.Cache/YLErp.Cache.csproj create mode 100644 Framework/YLErp.Cache/YLRedisCache.cs diff --git a/Framework/YLErp.Cache/CacheConfig.cs b/Framework/YLErp.Cache/CacheConfig.cs new file mode 100644 index 00000000..e5dc0bc3 --- /dev/null +++ b/Framework/YLErp.Cache/CacheConfig.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YLErp.Cache +{ + /// + /// 缓存配置 + /// + public class CacheConfig + { + /// + /// 是否可用 + /// + public bool Enable { get; set; } = false; + /// + /// 连接字符串 + /// + public string ConnectionString { get; set; } + /// + /// 数据库号 + /// + public int DataBaseNum { get; set; } = 0; + /// + /// 缓存key的自定义前缀(Option) + /// + public string CustomPrefix { get; set; } + } +} diff --git a/Framework/YLErp.Cache/CacheModule.cs b/Framework/YLErp.Cache/CacheModule.cs new file mode 100644 index 00000000..f4e85363 --- /dev/null +++ b/Framework/YLErp.Cache/CacheModule.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace YLErp.Cache +{ + public class CacheModule + { + public static void InitModule(IServiceCollection services) + { + services.AddYLCache(option => { }); + } + } + + public static class CacheServiceExtensions + { + public static void AddYLCache(this IServiceCollection services, Action optionAction) + { + services.AddTransient(); + services.Configure(optionAction); + } + } +} diff --git a/Framework/YLErp.Cache/IYLCache.cs b/Framework/YLErp.Cache/IYLCache.cs new file mode 100644 index 00000000..19047c9a --- /dev/null +++ b/Framework/YLErp.Cache/IYLCache.cs @@ -0,0 +1,78 @@ +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace YLErp.Cache +{ + public interface IYLCache + { + /// + /// 缓存是否可用 + /// + /// + bool CacheEnable(); + /// + /// 字符串数据类型的保存 + /// + /// + /// + /// + bool StringSet(string key,string value); + + /// + /// 字符串数据类型的查询 + /// + /// + /// + string StringGet(string key); + + + /// + /// 字符串数据类型的保存 + /// + /// + /// + /// + bool StringSet(string key, object value); + /// + /// 字符串数据类型的查询 + /// + /// + /// + /// + T StringGet(string key) where T : class; + + bool StringSetWithNoPrefix(string key, object value) where T : class; + T StringGetWithNoPrefix(string key) where T : class; + #region Batch Operate + /// + /// 批量增加 + /// + /// + /// + /// + /// + Task BatchAdd(string preKey, List values, int listSize = 1000); + + /// + /// 批量删除 + /// + /// + /// + bool BatchDelete(string pattern); + + /// + /// 批量查询 + /// + /// + /// + /// + List BatchQuery(string pattern); + + /// + /// 模糊查询 + /// + /// + IEnumerable QueryKeys(string pattern); + #endregion + } +} \ No newline at end of file diff --git a/Framework/YLErp.Cache/YLErp.Cache.csproj b/Framework/YLErp.Cache/YLErp.Cache.csproj new file mode 100644 index 00000000..7a9033db --- /dev/null +++ b/Framework/YLErp.Cache/YLErp.Cache.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + NU1803 + + + + + + + + + + diff --git a/Framework/YLErp.Cache/YLRedisCache.cs b/Framework/YLErp.Cache/YLRedisCache.cs new file mode 100644 index 00000000..e153156c --- /dev/null +++ b/Framework/YLErp.Cache/YLRedisCache.cs @@ -0,0 +1,239 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using StackExchange.Redis; +using StackExchange.Redis.KeyspaceIsolation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace YLErp.Cache +{ + public class YLRedisCache : IYLCache + { + private readonly IOptions _cacheConfig; + private readonly ConnectionMultiplexer _redisConnector; + private readonly IServer redisServer; + private IDatabase db; + + public YLRedisCache(IOptions cacheConfig) + { + _cacheConfig = cacheConfig; + if (_cacheConfig.Value.Enable) + { + var redisOptions= ConfigurationOptions.Parse(cacheConfig.Value.ConnectionString); + _redisConnector = ConnectionMultiplexer.Connect(redisOptions); + redisServer = _redisConnector.GetServer(redisOptions.EndPoints.FirstOrDefault()); + db = _redisConnector.GetDatabase(cacheConfig.Value.DataBaseNum); + //db.WithKeyPrefix(GenerateKey("")); + } + } + /// + /// 缓存是否可用 + /// + /// + public bool CacheEnable() + { + return _cacheConfig.Value.Enable; + } + + public bool StringSet(string key, string value) + { + return db.StringSet(GenerateKey(key), value); + } + + public string StringGet(string key) + { + return db.StringGet(GenerateKey(key)); + } + + public bool StringSet(string key, object value) + { + return StringSet(key, JsonConvert.SerializeObject(value)); + } + + public T StringGet(string key) where T : class + { + var value = StringGet(key); + if (string.IsNullOrEmpty(value)) + { + return null; + } + return JsonConvert.DeserializeObject(value); + } + + public T StringGetWithNoPrefix(string key) where T : class + { + var value = db.StringGet(key); + if (string.IsNullOrEmpty(value)) + { + return null; + } + return JsonConvert.DeserializeObject(value); + } + + #region Batch Operate + private async Task> SerializeObject(List objs) + { + return await Task.Factory.StartNew(() => + { + List redisValues = new List(); + foreach (T obj in objs) + { + redisValues.Add(JsonConvert.SerializeObject(obj)); + } + return redisValues; + }); + } + + private async Task SerializeObject(T obj) + { + return await Task.Factory.StartNew(() => + { + return JsonConvert.SerializeObject(obj); + }); + } + + /// + /// 批量增加 + /// + /// + /// + /// + /// + public async Task BatchAdd(string preKey,List values, int setSize = 1000) + { + //Stopwatch stopwatch1= Stopwatch.StartNew(); + //List tasktest=new List(); + //foreach (var obj in values) + //{ + // tasktest.Add(SerializeObject(obj)); + //} + //await Task.WhenAll(tasktest); + //stopwatch1.Stop(); + //Console.WriteLine($"测试序列化耗时:{stopwatch1.ElapsedMilliseconds}"); + + //for (var i = 0; i < values.Count; i++) + //{ + // int page = i / setSize; + // if (!dicRedisValue.ContainsKey(page)) + // { + // dicRedisValue.Add(page, new List()); + // } + // dicRedisValue[page].Add(JsonConvert.SerializeObject(values[i])); + //} + + + //var batch = db.CreateBatch(); + //List tasks = new List(); + //for (int i = 0; i < values.Count; i++) + //{ + // int page = i / setSize; + // var key = GenerateKey($"{preKey}:{page}"); + // tasks.Add(batch.SetAddAsync(key, JsonConvert.SerializeObject(values[i]))); + //} + + Stopwatch stopwatch = Stopwatch.StartNew(); + // 数据分组 + Dictionary>> dicRedisValue = new Dictionary>>(); + var valueCount = values.Count; + var pageCount = valueCount % setSize > 0 ? (valueCount / setSize) + 1 : (valueCount / setSize); + var page = 0; + while (page <=pageCount) + { + var takeDataCount = page * setSize > valueCount ? valueCount - (page - 1) * setSize : setSize; + var datas = values.Skip(page * setSize).Take(takeDataCount); + dicRedisValue.Add(page, SerializeObject(datas.ToList())); + page++; + } + await Task.WhenAll(dicRedisValue.Values); + stopwatch.Stop(); + Console.WriteLine($"Redis批量保存序列化耗时:{stopwatch.ElapsedMilliseconds}"); + + // 保存数据 + stopwatch.Restart(); + List tasks = new List(); + // 每组对应一个Set + var batch = db.CreateBatch(); + foreach (int itemKey in dicRedisValue.Keys) + { + var key = GenerateKey($"{preKey}:{itemKey}"); + var value = dicRedisValue[itemKey].Result.ToArray(); + tasks.Add(batch.SetAddAsync(key, value)); + } + + batch.Execute(); + await Task.WhenAll(tasks); + stopwatch.Stop(); + Console.WriteLine($"redis 添加执行耗时:{stopwatch.ElapsedMilliseconds}"); + } + + /// + /// 批量删除 + /// + /// + /// + public bool BatchDelete(string pattern) + { + var keys = QueryKeys(pattern); + return db.KeyDelete(keys.ToArray())>0; + } + + /// + /// 批量查询 + /// + /// + /// + /// + public List BatchQuery(string pattern) + { + var keys = QueryKeys(pattern); + if (!keys.Any()) + { + return new List(); + } + var redisValues = db.SetCombine(SetOperation.Union, keys.ToArray()); + return redisValues + ?.Select(g => JsonConvert.DeserializeObject(g.ToString())) + ?.ToList(); + } + + /// + /// 模糊查询 + /// + /// + public IEnumerable QueryKeys(string pattern) + { + return redisServer.Keys(db.Database, GenerateKey(pattern)); + } + #endregion + + /// + /// 生成带固定前缀Otc的Key + /// + /// + /// + public string GenerateKey(string originKey) + { + return $"Otc:{originKey}"; + } + /// + /// 保存不带前缀的key + /// + /// + /// + /// + /// + public bool StringSetWithNoPrefix(string key, object value) where T : class + { + return db.StringSet(key, JsonConvert.SerializeObject(value)); + } + } +}