- Redis的安装:在appsetting.json里面添加redis的配置
"RedisCaching": { "Enabled": true, "ConnectionString": "172.0.0.1:6379,abortConnect=false" },
- 在Common的Helper文件夹中,添加SerializeHelper.cs 对象序列化操作
public class SerializeHelper { /// <summary> /// 序列化 /// </summary> /// <param name="item"></param> /// <returns></returns> public static byte[] Serialize(object item) { var jsonString = JsonConvert.SerializeObject(item); return Encoding.UTF8.GetBytes(jsonString); } /// <summary> /// 反序列化 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="value"></param> /// <returns></returns> public static TEntity Deserialize<TEntity>(byte[] value) { if(value == null) { return default(TEntity); } var jsonString = Encoding.UTF8.GetString(value); return JsonConvert.DeserializeObject<TEntity>(jsonString); } }
- Common类库中,新建Redis文件夹,新建IRedisCacheManager接口和RedisCacheManager类,并引用Nuget包StackExchange.Redis
/// <summary> /// Redis缓存接口 /// </summary> public interface IRedisCacheManager { //获取 Reids 缓存值 string GetValue(string key); //获取值,并序列化 TEntity Get<TEntity>(string key); //保存 void Set(string key, object value, TimeSpan cacheTime); //判断是否存在 bool Get(string key); //移除某一个缓存值 void Remove(string key); //全部清除 void Clear(); //获取 Reids 缓存值 Task<string> GetValueAsync(string key); //获取值,并序列化 Task<TEntity> GetAsync<TEntity>(string key); //保存 Task SetAsync(string key, object value, TimeSpan cacheTime); //判断是否存在 Task<bool> GetAsync(string key); //移除某一个缓存值 Task RemoveAsync(string key); //根据关键字移除 Task RemoveByKey(string key); //全部清除 Task ClearAsync(); }
public class RedisCacheManager : IRedisCacheManager { /// <summary> ///连接字符串 /// </summary> private static readonly string ConnectionString = AppSettings.App(new string[] { "RedisCaching", "ConnectionString" }); /// <summary> /// 锁 /// </summary> private readonly object _lock = new object(); /// <summary> /// 连接对象 /// </summary> private volatile IConnectionMultiplexer _connection; /// <summary> /// 数据库 /// </summary> private IDatabase _db; public RedisCacheManager() { _connection = ConnectionMultiplexer.Connect(ConnectionString); _db = GetDatabase(); } /// <summary> /// 获取连接 /// </summary> /// <returns></returns> protected IConnectionMultiplexer GetConnection() { if (_connection != null && _connection.IsConnected) { return _connection; } lock (_lock) { if (_connection != null && _connection.IsConnected) { return _connection; } if (_connection != null) { _connection.Dispose(); } _connection = ConnectionMultiplexer.Connect(ConnectionString); } return _connection; } /// <summary> /// 获取数据库 /// </summary> /// <param name="db"></param> /// <returns></returns> public IDatabase GetDatabase(int? db = null) { return GetConnection().GetDatabase(db ?? -1); } public void Clear() { foreach (var endPoint in _connection.GetEndPoints()) { var server = this.GetConnection().GetServer(endPoint); foreach (var key in server.Keys()) { _db.KeyDelete(key); } } } public bool Get(string key) { return _db.KeyExists(key); } public string GetValue(string key) { return _db.StringGet(key); } public TEntity Get<TEntity>(string key) { var value = _db.StringGet(key); if (value.HasValue) { //需要用的反序列化,将Redis存储的Byte[],进行反序列化 return SerializeHelper.Deserialize<TEntity>(value); } else { return default(TEntity); } } public void Remove(string key) { _db.KeyDelete(key); } public void Set(string key, object value, TimeSpan cacheTime) { if (value != null) { //序列化,将object值生成RedisValue _db.StringSet(key, SerializeHelper.Serialize(value), cacheTime); } } public bool SetValue(string key, byte[] value) { return _db.StringSet(key, value, TimeSpan.FromSeconds(120)); } public async Task ClearAsync() { foreach (var endPoint in this.GetConnection().GetEndPoints()) { var server = this.GetConnection().GetServer(endPoint); foreach (var key in server.Keys()) { await _db.KeyDeleteAsync(key); } } } public async Task<bool> GetAsync(string key) { return await _db.KeyExistsAsync(key); } public async Task<string> GetValueAsync(string key) { return await _db.StringGetAsync(key); } public async Task<TEntity> GetAsync<TEntity>(string key) { var value = await _db.StringGetAsync(key); if (value.HasValue) { //需要用的反序列化,将Redis存储的Byte[],进行反序列化 return SerializeHelper.Deserialize<TEntity>(value); } else { return default; } } public async Task RemoveAsync(string key) { await _db.KeyDeleteAsync(key); } public async Task RemoveByKey(string key) { var redisResult = await _db.ScriptEvaluateAsync(LuaScript.Prepare( //Redis的keys模糊查询: " local res = redis.call('KEYS', @keypattern) " + " return res "), new { @keypattern = key }); if (!redisResult.IsNull) { var keys = (string[])redisResult; foreach (var k in keys) _db.KeyDelete(k); } } public async Task SetAsync(string key, object value, TimeSpan cacheTime) { if (value != null) { //序列化,将object值生成RedisValue await _db.StringSetAsync(key, SerializeHelper.Serialize(value), cacheTime); } } public async Task<bool> SetValueAsync(string key, byte[] value) { return await _db.StringSetAsync(key, value, TimeSpan.FromSeconds(120)); } }
- 将Redis注入到program.cs中:
//注册Redis builder.Services.AddSingleton<IRedisCacheManager, RedisCacheManager>();
- Common层新增Attributes文件夹,并新建CachingAttribute来定义我们的redis缓存特性
/// 这个Attribute就是使用时候的验证,把它添加到要缓存数据的方法中,即可完成缓存的操作。 [AttributeUsage(AttributeTargets.Method, Inherited = true)] public class CachingAttribute : Attribute { //过期时间 public int AbsoluteExpiration { get; set; } = 3600; /// <summary> /// 自定义key /// </summary> public string CustomKeyValue { get; set; } /// <summary> /// 是否删除 /// </summary> public bool IsDelete { get; set; } = false; }
- 在TEST.API主程序中新增AOP文件夹,新建CacheAOPbase.cs和RedisCacheAOP.cs
public abstract class CacheAPOBase : IInterceptor { /// <summary> /// AOP的拦截方法 /// </summary> /// <param name="invocation"></param> public abstract void Intercept(IInvocation invocation); /// <summary> /// 自定义缓存的key /// </summary> /// <param name="invocation"></param> /// <returns></returns> 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(':'); } /// <summary> /// object 转 string /// </summary> /// <param name="arg"></param> /// <returns></returns> protected static string GetArgumentValue(object arg) { if (arg is DateTime || arg is DateTime?) return ((DateTime)arg).ToString("yyyyMMddHHmmss"); if (arg is string || arg is ValueType || arg is Nullable) return arg.ToString(); 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(Newtonsoft.Json.JsonConvert.SerializeObject(arg)); } } return string.Empty; } private static string Resolve(Expression expression) { if (expression is LambdaExpression) { LambdaExpression lambda = expression as LambdaExpression; expression = lambda.Body; return Resolve(expression); } if (expression is BinaryExpression) { BinaryExpression binary = expression as BinaryExpression; if (binary.Left is MemberExpression && binary.Right is ConstantExpression)//解析x=>x.Name=="123" x.Age==123这类 return ResolveFunc(binary.Left, binary.Right, binary.NodeType); if (binary.Left is MethodCallExpression && binary.Right is ConstantExpression)//解析x=>x.Name.Contains("xxx")==false这类的 { object value = (binary.Right as ConstantExpression).Value; return ResolveLinqToObject(binary.Left, value, binary.NodeType); } if ((binary.Left is MemberExpression && binary.Right is MemberExpression) || (binary.Left is MemberExpression && binary.Right is UnaryExpression))//解析x=>x.Date==DateTime.Now这种 { LambdaExpression lambda = Expression.Lambda(binary.Right); Delegate fn = lambda.Compile(); ConstantExpression value = Expression.Constant(fn.DynamicInvoke(null), binary.Right.Type); return ResolveFunc(binary.Left, value, binary.NodeType); } } if (expression is UnaryExpression) { UnaryExpression unary = expression as UnaryExpression; if (unary.Operand is MethodCallExpression)//解析!x=>x.Name.Contains("xxx")或!array.Contains(x.Name)这类 return ResolveLinqToObject(unary.Operand, false); if (unary.Operand is MemberExpression && unary.NodeType == ExpressionType.Not)//解析x=>!x.isDeletion这样的 { ConstantExpression constant = Expression.Constant(false); return ResolveFunc(unary.Operand, constant, ExpressionType.Equal); } } if (expression is MemberExpression && expression.NodeType == ExpressionType.MemberAccess)//解析x=>x.isDeletion这样的 { MemberExpression member = expression as MemberExpression; ConstantExpression constant = Expression.Constant(true); return ResolveFunc(member, constant, ExpressionType.Equal); } if (expression is MethodCallExpression)//x=>x.Name.Contains("xxx")或array.Contains(x.Name)这类 { MethodCallExpression methodcall = expression as MethodCallExpression; return ResolveLinqToObject(methodcall, true); } //已经修改过代码body应该不会是null值了 if (!(expression is BinaryExpression body)) return string.Empty; var Operator = GetOperator(body.NodeType); var Left = Resolve(body.Left); var Right = Resolve(body.Right); string Result = string.Format("({0} {1} {2})", Left, Operator, Right); return Result; } private static string GetOperator(ExpressionType expressiontype) { return expressiontype switch { ExpressionType.And => "and", ExpressionType.AndAlso => "and", ExpressionType.Or => "or", ExpressionType.OrElse => "or", ExpressionType.Equal => "=", ExpressionType.NotEqual => "<>", ExpressionType.LessThan => "<", ExpressionType.LessThanOrEqual => "<=", ExpressionType.GreaterThan => ">", ExpressionType.GreaterThanOrEqual => ">=", _ => throw new Exception(string.Format("不支持{0}此种运算符查找!" + expressiontype)), }; } private static string ResolveFunc(Expression left, Expression right, ExpressionType expressiontype) { var Name = (left as MemberExpression).Member.Name; var Value = (right as ConstantExpression).Value; var Operator = GetOperator(expressiontype); return Name + Operator + Value ?? "null"; } private static string ResolveLinqToObject(Expression expression, object value, ExpressionType? expressiontype = null) { var MethodCall = expression as MethodCallExpression; var MethodName = MethodCall.Method.Name; switch (MethodName) { case "Contains": if (MethodCall.Object != null) return Like(MethodCall); return In(MethodCall, value); case "Count": return Len(MethodCall, value, expressiontype.Value); case "LongCount": return Len(MethodCall, value, expressiontype.Value); default: throw new Exception(string.Format("不支持{0}方法的查找!", MethodName)); } } private static string In(MethodCallExpression expression, object isTrue) { var Argument1 = (expression.Arguments[0] as MemberExpression).Expression as ConstantExpression; var Argument2 = expression.Arguments[1] as MemberExpression; var Field_Array = Argument1.Value.GetType().GetFields().First(); object[] Array = Field_Array.GetValue(Argument1.Value) as object[]; List<string> SetInPara = new List<string>(); for (int i = 0; i < Array.Length; i++) { string Value = Array[i].ToString(); SetInPara.Add(Value); } string Name = Argument2.Member.Name; string Operator = Convert.ToBoolean(isTrue) ? "in" : " not in"; string CompName = string.Join(",", SetInPara); string Result = string.Format("{0} {1} ({2})", Name, Operator, CompName); return Result; } private static string Like(MethodCallExpression expression) { var Temp = expression.Arguments[0]; LambdaExpression lambda = Expression.Lambda(Temp); Delegate fn = lambda.Compile(); var tempValue = Expression.Constant(fn.DynamicInvoke(null), Temp.Type); string Value = string.Format("%{0}%", tempValue); string Name = (expression.Object as MemberExpression).Member.Name; string Result = string.Format("{0} like {1}", Name, Value); return Result; } private static string Len(MethodCallExpression expression, object value, ExpressionType expressiontype) { object Name = (expression.Arguments[0] as MemberExpression).Member.Name; string Operator = GetOperator(expressiontype); string Result = string.Format("len({0}){1}{2}", Name, Operator, value.ToString()); return Result; } }
- 在Common下面的Helper文件夹下新建MD5Helper.cs帮助类
/// <summary> /// MD5帮助类 /// </summary> public class MD5Helper { /// <summary> /// 16位MD5加密 /// </summary> /// <param name="password"></param> /// <returns></returns> public static string MD5Encrypt16(string password) { var md5 = new MD5CryptoServiceProvider(); string t2 = BitConverter.ToString(md5.ComputeHash(Encoding.Default.GetBytes(password)), 4, 8); return t2; } /// <summary> /// 32位MD5加密 /// </summary> /// <param name="password"></param> /// <returns></returns> public static string MD5Decrypt32(string password = "") { var pwd = string.Empty; try { if (!string.IsNullOrEmpty(password) && !string.IsNullOrWhiteSpace(password)) { var md5 = MD5.Create(); Byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password)); foreach (var item in s) { pwd = string.Concat(pwd, item.ToString("X2")); } } } catch { throw new Exception($"错误的 password 字符串:【{password}】"); } return pwd; } /// <summary> /// 64位MD5加密 /// </summary> /// <param name="password"></param> /// <returns></returns> public static string MD5Encrypt64(string password) { var md5 = MD5.Create(); byte[] S = md5.ComputeHash(Encoding.UTF8.GetBytes(password)); return Convert.ToBase64String(S); } }
/// <summary> /// 面向切面的缓存使用 /// </summary> public class RedisCacheAOP : CacheAPOBase { private readonly IRedisCacheManager _cache; public RedisCacheAOP(IRedisCacheManager cache) { _cache = cache; } //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义 //代码已经合并 ,学习pr流程 public override void Intercept(IInvocation invocation) { var method = invocation.MethodInvocationTarget ?? invocation.Method; if (method.ReturnType == typeof(void) || method.ReturnType == typeof(Task)) { invocation.Proceed(); return; } //对当前方法的特性验证 if (method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) is CachingAttribute qCachingAttribute) { //获取自定义缓存键 var cacheKey = qCachingAttribute.CustomKeyValue ?? CustomCacheKey(invocation); //注意是 string 类型,方法GetValue var cacheValue = _cache.GetValue(cacheKey); if (cacheValue != null) { if (qCachingAttribute.IsDelete) { //删除Redis里面的数据 _cache.Remove(cacheKey); } else { //将当前获取到的缓存值,赋值给当前执行方法 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) && qCachingAttribute.IsDelete == false) { 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; _cache.Set(cacheKey, response, TimeSpan.FromMinutes(qCachingAttribute.AbsoluteExpiration)); } } else { invocation.Proceed();//直接执行被拦截方法 } } }
- AutofacModuleRegister文件注入redisaop缓存
protected override void Load(ContainerBuilder builder) { //注册RedisAOP builder.RegisterType<RedisCacheAOP>(); //注册Service var assemblysServices = Assembly.Load("TEST.Services"); builder.RegisterAssemblyTypes(assemblysServices) .InstancePerDependency() // 瞬时单例 .AsImplementedInterfaces() //自动以实现的所有接口类型暴露(包括IDisposable接口) .EnableInterfaceInterceptors() .InterceptedBy(typeof(RedisCacheAOP)); //引用Autofac.Extras.DynamicProxy; //注册Repository var assemblyRepository = Assembly.Load("TEST.Respository"); builder.RegisterAssemblyTypes(assemblyRepository) .InstancePerDependency() // 瞬时单例 .AsImplementedInterfaces() //自动以实现的所有接口类型暴露(包括IDisposable接口) .EnableInterfaceInterceptors(); //引用Autofac.Extras.DynamicProxy; }
- 新建接口测试AOP缓存:
/// <summary> /// 测试AOP缓存Redis /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpPost] [AllowAnonymous] public async Task<IActionResult> AopTest(object id) { var sucess = await _stuService.GetStudentById(id); return Ok(sucess); }
- IStudentService新建接口:
/// <summary> /// 根据ID查询学生信息 /// </summary> /// <param name="id"></param> /// <returns></returns> Task<Student> GetStudentById(object id);
- StudentService新建方法:
/// <summary> /// 根据ID查询学生信息 /// </summary> /// <param name="id"></param> /// <returns></returns> [Caching(AbsoluteExpiration = 10)] public async Task<Student> GetStudentById(object id) { return await _studentRepository.QueryByID(id); }
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容