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));
+ }
+ }
+}