diff --git a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/appsettings.json b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/appsettings.json index e65abda4..13bd024a 100644 --- a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/appsettings.json +++ b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/appsettings.json @@ -35,7 +35,7 @@ "DbConn": { "WriteUrl": "DataSource=yi-sqlsugar-dev.db", "ReadUrl": [ - "DataSource=[xxxx]",//sqlite + "DataSource=[xxxx]", //sqlite "server=[xxxx];port=3306;database=[xxxx];user id=[xxxx];password=[xxxx]", //mysql "Data Source=[xxxx];Initial Catalog=[xxxx];User ID=[xxxx];password=[xxxx]" //sqlserver ] @@ -47,6 +47,11 @@ "CacheList": [ "Redis", "MemoryCache" ], //选择缓存 "CacheSelect": "MemoryCache", + + //缓存Aop + "CacheAOP_Enabled": false, + + //缓存种子数据是否开启 "CacheSeed_Enabled": false, diff --git a/Yi.Framework.Net6/Yi.Framework.Common/Attribute/CachingAttribute.cs b/Yi.Framework.Net6/Yi.Framework.Common/Attribute/CachingAttribute.cs new file mode 100644 index 00000000..5804b822 --- /dev/null +++ b/Yi.Framework.Net6/Yi.Framework.Common/Attribute/CachingAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Yi.Framework.Common.Attribute +{ + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + public class CachingAttribute : System.Attribute + { + /// + /// 缓存绝对过期时间(分钟) + /// + public int AbsoluteExpiration { get; set; } = 30; + + } +} diff --git a/Yi.Framework.Net6/Yi.Framework.Common/Base/NullValue.cs b/Yi.Framework.Net6/Yi.Framework.Common/Base/NullValue.cs index de62622e..62e91eae 100644 --- a/Yi.Framework.Net6/Yi.Framework.Common/Base/NullValue.cs +++ b/Yi.Framework.Net6/Yi.Framework.Common/Base/NullValue.cs @@ -110,6 +110,11 @@ namespace Yi.Framework.Common.Base } + public static string TryStringNull(this object value) + { + return value == null ? "" : value.ToString()!.Trim(); + } + /// /// Object类型无值判断 /// diff --git a/Yi.Framework.Net6/Yi.Framework.Common/Helper/MD5Hepler.cs b/Yi.Framework.Net6/Yi.Framework.Common/Helper/MD5Hepler.cs index e050c8b8..44c7c0af 100644 --- a/Yi.Framework.Net6/Yi.Framework.Common/Helper/MD5Hepler.cs +++ b/Yi.Framework.Net6/Yi.Framework.Common/Helper/MD5Hepler.cs @@ -47,6 +47,63 @@ namespace Yi.Framework.Common.Helper return ConvertEx.ToUrlBase64String(bRet); } + + /// + /// 16位MD5加密 + /// + /// + /// + public static string MD5Encrypt16(string password) + { + var md5 = MD5.Create(); + string t2 = BitConverter.ToString(md5.ComputeHash(Encoding.Default.GetBytes(password)), 4, 8); + t2 = t2.Replace("-", string.Empty); + return t2; + } + + /// + /// 32位MD5加密 + /// + /// + /// + public static string MD5Encrypt32(string password = "") + { + string pwd = string.Empty; + try + { + if (!string.IsNullOrEmpty(password) && !string.IsNullOrWhiteSpace(password)) + { + MD5 md5 = MD5.Create(); //实例化一个md5对像 + // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择  + byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password)); + // 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得 + foreach (var item in s) + { + // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符 + pwd = string.Concat(pwd, item.ToString("X2")); + } + } + } + catch + { + throw new Exception($"错误的 password 字符串:【{password}】"); + } + return pwd; + } + + /// + /// 64位MD5加密 + /// + /// + /// + public static string MD5Encrypt64(string password) + { + // 实例化一个md5对像 + // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择  + MD5 md5 = MD5.Create(); + byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password)); + return Convert.ToBase64String(s); + } } public class ConvertEx { diff --git a/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/CacheAOPbase.cs b/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/CacheAOPbase.cs new file mode 100644 index 00000000..e123b52f --- /dev/null +++ b/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/CacheAOPbase.cs @@ -0,0 +1,90 @@ +using Castle.DynamicProxy; +using Newtonsoft.Json; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using Yi.Framework.Common.Base; +using Yi.Framework.Common.Helper; + +namespace Yi.Framework.Core.Cache.Aop +{ + public abstract class CacheAOPbase : IInterceptor + { + /// + /// AOP的拦截方法 + /// + /// + public abstract void Intercept(IInvocation invocation); + + /// + /// 自定义缓存的key + /// + /// + /// + protected string CustomCacheKey(IInvocation invocation) + { + var typeName = invocation.TargetType.Name; + var methodName = invocation.Method.Name; + var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,最多三个 + + string key = $"{typeName}:{methodName}:"; + foreach (var param in methodArguments) + { + key = $"{key}{param}:"; + } + + return key.TrimEnd(':'); + } + + /// + /// object 转 string + /// + /// + /// + protected static string GetArgumentValue(object arg) + { + if (arg is DateTime) + return ((DateTime)arg).ToString("yyyyMMddHHmmss"); + + if (!arg.IsNotNull()) + return arg.TryStringNull(); + + if (arg != null) + { + if (arg is Expression) + { + var obj = arg as Expression; + var result = Resolve(obj); + return MD5Helper.MD5Encrypt16(result); + } + else if (arg.GetType().IsClass) + { + return MD5Helper.MD5Encrypt16(JsonConvert.SerializeObject(arg)); + } + + return $"value:{arg.TryStringNull()}"; + } + return string.Empty; + } + + private static string Resolve(Expression expression) + { + ExpressionContext expContext = new ExpressionContext(); + expContext.Resolve(expression, ResolveExpressType.WhereSingle); + var value = expContext.Result.GetString(); + var pars = expContext.Parameters; + + pars.ForEach(s => + { + value = value.Replace(s.ParameterName, s.Value.TryStringNull()); + }); + + return value; + } + + } +} diff --git a/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/MemoryCacheAOP.cs b/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/MemoryCacheAOP.cs new file mode 100644 index 00000000..b347c0ee --- /dev/null +++ b/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/MemoryCacheAOP.cs @@ -0,0 +1,51 @@ +using Castle.DynamicProxy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Yi.Framework.Common.Attribute; +using Yi.Framework.Core.Cache; + +namespace Yi.Framework.Core.Cache.Aop +{ + public class MemoryCacheAOP : CacheAOPbase + { + private CacheInvoker _cache; + public MemoryCacheAOP(CacheInvoker cache) + { + _cache = cache; + } + + public override void Intercept(IInvocation invocation) + { + var method = invocation.MethodInvocationTarget ?? invocation.Method; + + var cachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)); + if (cachingAttribute is CachingAttribute qCachingAttribute) + { + //获取自定义缓存键 + var cacheKey = CustomCacheKey(invocation); + //根据key获取相应的缓存值 + var cacheValue = _cache.Get(cacheKey); + if (cacheValue != null) + { + //将当前获取到的缓存值,赋值给当前执行方法 + invocation.ReturnValue = cacheValue; + return; + } + //去执行当前的方法 + invocation.Proceed(); + //存入缓存 + if (!string.IsNullOrWhiteSpace(cacheKey)) + { + _cache.Set(cacheKey, invocation.ReturnValue, TimeSpan.FromMinutes(qCachingAttribute.AbsoluteExpiration)); + } + } + else + { + invocation.Proceed();//直接执行被拦截方法 + } + } + } +} diff --git a/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/RedisCacheAOP.cs b/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/RedisCacheAOP.cs new file mode 100644 index 00000000..53038ac8 --- /dev/null +++ b/Yi.Framework.Net6/Yi.Framework.Core/Cache/Aop/RedisCacheAOP.cs @@ -0,0 +1,84 @@ +using Castle.DynamicProxy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Yi.Framework.Core.Cache; +using Yi.Framework.Common.Attribute; + +namespace Yi.Framework.Core.Cache.Aop +{ + public class RedisCacheAOP : CacheAOPbase + { + private CacheInvoker _cacheDb; + public RedisCacheAOP(CacheInvoker cacheInvoker) + { + _cacheDb = cacheInvoker; + } + + public override void Intercept(IInvocation invocation) + { + var method = invocation.MethodInvocationTarget ?? invocation.Method; + if (method.ReturnType == typeof(void) || method.ReturnType == typeof(Task)) + { + invocation.Proceed(); + return; + } + + var qCachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) as CachingAttribute; + + if (qCachingAttribute != null) + { + //获取自定义缓存键 + var cacheKey = CustomCacheKey(invocation); + //注意是 string 类型,方法GetValue + var cacheValue = _cacheDb.Get(cacheKey); + if (cacheValue != null) + { + //将当前获取到的缓存值,赋值给当前执行方法 + Type returnType; + if (typeof(Task).IsAssignableFrom(method.ReturnType)) + { + returnType = method.ReturnType.GenericTypeArguments.FirstOrDefault(); + } + else + { + returnType = method.ReturnType; + } + + dynamic _result = Newtonsoft.Json.JsonConvert.DeserializeObject(cacheValue, returnType); + invocation.ReturnValue = (typeof(Task).IsAssignableFrom(method.ReturnType)) ? Task.FromResult(_result) : _result; + return; + } + //去执行当前的方法 + invocation.Proceed(); + + //存入缓存 + if (!string.IsNullOrWhiteSpace(cacheKey)) + { + object response; + + //Type type = invocation.ReturnValue?.GetType(); + var type = invocation.Method.ReturnType; + if (typeof(Task).IsAssignableFrom(type)) + { + var resultProperty = type.GetProperty("Result"); + response = resultProperty.GetValue(invocation.ReturnValue); + } + else + { + response = invocation.ReturnValue; + } + if (response == null) response = string.Empty; + + _cacheDb.Set(cacheKey, response, TimeSpan.FromMinutes(qCachingAttribute.AbsoluteExpiration)); + } + } + else + { + invocation.Proceed();//直接执行被拦截方法 + } + } + } +} diff --git a/Yi.Framework.Net6/Yi.Framework.Core/Yi.Framework.Core.csproj b/Yi.Framework.Net6/Yi.Framework.Core/Yi.Framework.Core.csproj index a28444ff..d6598f4f 100644 --- a/Yi.Framework.Net6/Yi.Framework.Core/Yi.Framework.Core.csproj +++ b/Yi.Framework.Net6/Yi.Framework.Core/Yi.Framework.Core.csproj @@ -12,6 +12,7 @@ + diff --git a/Yi.Framework.Net6/Yi.Framework.Model/RABC/Entitys/ConfigEntity.cs b/Yi.Framework.Net6/Yi.Framework.Model/RABC/Entitys/ConfigEntity.cs index cd5d8d4a..78cd11f4 100644 --- a/Yi.Framework.Net6/Yi.Framework.Model/RABC/Entitys/ConfigEntity.cs +++ b/Yi.Framework.Net6/Yi.Framework.Model/RABC/Entitys/ConfigEntity.cs @@ -15,7 +15,7 @@ namespace Yi.Framework.Model.RABC.Entitys { public ConfigEntity() { - CreateTime = DateTime.Now; + //CreateTime = DateTime.Now; } [JsonConverter(typeof(ValueToStringConverter))] [SugarColumn(ColumnName = "Id", IsPrimaryKey = true)] diff --git a/Yi.Framework.Net6/Yi.Framework.Service/RABC/ConfigService.cs b/Yi.Framework.Net6/Yi.Framework.Service/RABC/ConfigService.cs index 8491dde3..f8da1748 100644 --- a/Yi.Framework.Net6/Yi.Framework.Service/RABC/ConfigService.cs +++ b/Yi.Framework.Net6/Yi.Framework.Service/RABC/ConfigService.cs @@ -1,6 +1,7 @@ using SqlSugar; using System.Collections.Generic; using System.Threading.Tasks; +using Yi.Framework.Common.Attribute; using Yi.Framework.Common.Models; using Yi.Framework.Interface; using Yi.Framework.Interface.RABC; @@ -15,6 +16,8 @@ namespace Yi.Framework.Service.RABC public ConfigService(IRepository repository) : base(repository) { } + + [Caching(AbsoluteExpiration = 10)] public async Task>> SelctPageList(ConfigEntity config, PageParModel page) { RefAsync total = 0; diff --git a/Yi.Framework.Net6/Yi.Framework.WebCore/AspNetCoreExtensions/SqlsugarExtension.cs b/Yi.Framework.Net6/Yi.Framework.WebCore/AspNetCoreExtensions/SqlsugarExtension.cs index f2d3ac47..bd8becf4 100644 --- a/Yi.Framework.Net6/Yi.Framework.WebCore/AspNetCoreExtensions/SqlsugarExtension.cs +++ b/Yi.Framework.Net6/Yi.Framework.WebCore/AspNetCoreExtensions/SqlsugarExtension.cs @@ -92,6 +92,10 @@ namespace Yi.Framework.WebCore.AspNetCoreExtensions { //entityInfo.SetValue(new Guid(httpcontext.Request.Headers["TenantId"].ToString())); } + if (entityInfo.PropertyName == "CreateTime") + { + entityInfo.SetValue(DateTime.Now); + } break; case DataFilterType.UpdateByObject: if (entityInfo.PropertyName == "ModifyTime") diff --git a/Yi.Framework.Net6/Yi.Framework.WebCore/AutoFacExtend/CustomAutofacModule.cs b/Yi.Framework.Net6/Yi.Framework.WebCore/AutoFacExtend/CustomAutofacModule.cs index 0cc89fa6..2b76f188 100644 --- a/Yi.Framework.Net6/Yi.Framework.WebCore/AutoFacExtend/CustomAutofacModule.cs +++ b/Yi.Framework.Net6/Yi.Framework.WebCore/AutoFacExtend/CustomAutofacModule.cs @@ -12,11 +12,13 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Yi.Framework.Common.Abstract; +using Yi.Framework.Core.Cache.Aop; using Yi.Framework.Interface; using Yi.Framework.Job; using Yi.Framework.Repository; using Yi.Framework.Service; using Yi.Framework.WebCore.AutoFacExtend; +using Yi.Framework.WebCore.CommonExtend; using Yi.Framework.WebCore.Impl; using Module = Autofac.Module; @@ -43,14 +45,36 @@ namespace Yi.Framework.WebCore.AutoFacExtend containerBuilder.RegisterType().As().SingleInstance(); + var cacheType = new List(); + //containerBuilder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope(); //containerBuilder.RegisterGeneric(typeof(BaseService<>)).As(typeof(IBaseService<>)).InstancePerLifetimeScope(); ///反射注入服务层及接口层 var assemblysServices = GetDll("Yi.Framework.Service.dll"); - containerBuilder.RegisterAssemblyTypes(assemblysServices).PropertiesAutowired(new AutowiredPropertySelector()) - .AsImplementedInterfaces() - .InstancePerLifetimeScope() - .EnableInterfaceInterceptors(); + var regContainerBuilder = containerBuilder.RegisterAssemblyTypes(assemblysServices).PropertiesAutowired(new AutowiredPropertySelector()) + .AsImplementedInterfaces() + .InstancePerLifetimeScope() + .EnableInterfaceInterceptors(); + + if (Appsettings.appBool("CacheAOP_Enabled")) + { + var cacheSelect = Appsettings.app("CacheSelect"); + + switch (cacheSelect) + { + case "Redis": + containerBuilder.RegisterType(); + cacheType.Add(typeof(RedisCacheAOP)); + break; + case "MemoryCache": + containerBuilder.RegisterType(); + cacheType.Add(typeof(MemoryCacheAOP)); + break; + default: throw new ArgumentException("CacheSelect配置填的是什么东西?俺不认得"); + } + regContainerBuilder.InterceptedBy(cacheType.ToArray()); + } + //开启工作单元拦截 //.InterceptedBy(typeof(UnitOfWorkInterceptor));