WebAPI学习(八):基于AOP切面的redis缓存

  1. Redis的安装:在appsetting.json里面添加redis的配置
    "RedisCaching": {
      "Enabled": true,
      "ConnectionString": "172.0.0.1:6379,abortConnect=false"
    },
  2. 在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);
        }
    }
  3. Common类库中,新建Redis文件夹,新建IRedisCacheManager接口和RedisCacheManager类,并引用Nuget包StackExchange.Redis

    1

    /// <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));
        }
    }
  4. 将Redis注入到program.cs中:
    //注册Redis
    builder.Services.AddSingleton<IRedisCacheManager, RedisCacheManager>();
  5. 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;
    
    }
  6. 在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;
        }
    
    }
  7. 在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();//直接执行被拦截方法
            }
        }
    }
  8. 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;
    
    
    }
  9. 新建接口测试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);
    }
  10. IStudentService新建接口:
    /// <summary>
    /// 根据ID查询学生信息
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    Task<Student> GetStudentById(object id);
  11. 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
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容