天天看點

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

本文3.0版本文章

https://mp.weixin.qq.com/s/pjvleNGi_AazZ7COdxQyPQ

Redis 部分的内容,和netcore2.0一樣,不需要更新。

代碼已上傳Github+Gitee,文末有位址

  書說上文《從壹開始前後端分離【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】架構之十 || AOP面向切面程式設計淺解析:簡單日志記錄 + 服務切面緩存》,昨天咱們說到了AOP面向切面程式設計,簡單的舉出了兩個栗子,不知道大家有什麼想法呢,不知道是否與傳統的緩存的使用有做對比了麼?

  傳統的緩存是在Controller中,将擷取到的資料手動處理,然後當另一個controller中又使用的時候,還是Get,Set相關操作,當然如果小項目,有兩三個緩存還好,如果是特别多的接口調用,面向Service服務層還是很有必要的,不需要額外寫多餘代碼,隻需要正常調取Service層的接口就行,AOP結合Autofac注入,會自動的查找,然後傳回資料,不繼續往下走Repository倉儲了。

  昨天我釋出文章後,有一個網友提出了一個問題,他想的很好,就是如果面向到了Service層,那BaseService中的CURD等基本方法都被注入了,這樣會造成太多的代理類,不僅沒有必要,甚至還有問題,比如把Update也緩存了,這個就不是很好了,嗯,我也發現了這個問題,是以需要給AOP增加驗證特性,隻針對Service服務層中特定的常使用的方法資料進行緩存等。這樣既能保證切面緩存的高效性,又能手動控制,不知道大家有沒有其他的好辦法,如果有的話,歡迎留言,或者加群咱們一起讨論,一起解決平時的問題。

零、今天完成的大紅色部分

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

一、給緩存增加驗證篩選特性

1、自定義緩存特性

在解決方案中添加新項目Blog.Core.Common,然後在該Common類庫中添加 特性檔案夾 和 特性實體類,以後特性就在這裡

//CachingAttribute

  /// <summary>
    /// 這個Attribute就是使用時候的驗證,把它添加到要緩存資料的方法中,即可完成緩存的操作。注意是對Method驗證有效
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, Inherited = true)]
    public class CachingAttribute : Attribute
    {
        //緩存絕對過期時間
        public int AbsoluteExpiration { get; set; } = 30;

    }      

2、在AOP攔截器中進行過濾

添加Common程式集引用,然後修改緩存AOP類方法 BlogCacheAOP=》Intercept,簡單對方法的方法進行判斷

//qCachingAttribute 代碼

   //Intercept方法是攔截的關鍵所在,也是IInterceptor接口中的唯一定義
        public void Intercept(IInvocation invocation)
        {
            var method = invocation.MethodInvocationTarget ?? invocation.Method;
            //對目前方法的特性驗證
            var qCachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) as CachingAttribute;
            //隻有那些指定的才可以被緩存,需要驗證
            if (qCachingAttribute != null)
            {
                //擷取自定義緩存鍵
                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);
                }
            }
            else
            {
                invocation.Proceed();//直接執行被攔截方法
            }
        }      

可見在invocation參數中,包含了幾乎所有的方法,大家可以深入研究下,擷取到自己需要的資料

3、在service層中增加緩存特性

在指定的Service層中的某些類的某些方法上增加特性(一定是方法,不懂的可以看定義特性的時候AttributeTargets.Method)

    /// <summary>
        /// 擷取部落格清單
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [Caching(AbsoluteExpiration = 10)]//增加特性
        public async Task<List<BlogArticle>> getBlogs()
        {
            var bloglist = await dal.Query(a => a.bID > 0, a => a.bID);

            return bloglist;

        }      

4、特定緩存效果展示

運作項目,打斷點,就可以看到,普通的Query或者CURD等都不繼續緩存了,隻有咱們特定的 getBlogs()方法,帶有緩存特性的才可以

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

當然,這裡還有一個小問題,就是所有的方法還是走的切面,隻是增加了過濾驗證,大家也可以直接把那些需要的注入,不需要的幹脆不注入Autofac容器,我之是以需要都經過的目的,就是想把它和日志結合,用來記錄Service層的每一個請求,包括CURD的調用情況。

二、什麼是Redis,為什麼使用它

  我個人有一個了解,關于Session或Cache等,在普通單伺服器的項目中,很簡單,有自己的生命周期等,想擷取Session就擷取,想拿啥就拿啥,但是在大型的分布式叢集中,有可能這一秒的點選的頁面和下一秒的都不在一個伺服器上,對不對!想想如果普通的辦法,怎麼保證session的一緻性,怎麼擷取相同的緩存資料,怎麼有效的進行消息隊列傳遞?

  這個時候就用到了Redis,這些内容,網上已經到處都是,但是還是做下記錄吧

Redis是一個key-value存儲系統。和Memcached類似,它支援存儲的value類型相對更多,包括string(字元串)、list(連結清單)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些資料類型都支援push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。它内置複制、Lua腳本、LRU收回、事務以及不同級别磁盤持久化功能,同時通過Redis Sentinel提供高可用,通過Redis Cluster提供自動分區。在此基礎上,Redis支援各種不同方式的排序。為了保證效率,資料都是緩存在記憶體中。差別的是redis會周期性的把更新的資料寫入磁盤或者把修改操作寫入追加的記錄檔案,并且在此基礎上實作了master-slave(主從)同步。

也就是說,緩存伺服器如果意外重新開機了,資料還都在,嗯!這就是它的強大之處,不僅在記憶體高吞吐,還能持久化。

Redis支援主從同步。資料可以從主伺服器向任意數量的從伺服器上同步,從伺服器可以是關聯其他從伺服器的主伺服器。這使得Redis可執行單層樹複制。存盤可以有意無意的對資料進行寫操作。由于完全實作了釋出/訂閱機制,使得從資料庫在任何地方同步樹時,可訂閱一個頻道并接收主伺服器完整的消息釋出記錄。同步對讀取操作的可擴充性和資料備援很有幫助。

Redis也是可以做為消息隊列的,與之相同功能比較優秀的就是Kafka

Redis還是有自身的缺點:

Redis隻能存儲key/value類型,雖然value的類型可以有多種,但是對于關聯性的記錄查詢,沒有Sqlserver、Oracle、Mysql等關系資料庫友善。

Redis記憶體資料寫入硬碟有一定的時間間隔,在這個間隔内資料可能會丢失,雖然後續會介紹各種模式來保證資料丢失的可能性,但是依然會有可能,是以對資料有嚴格要求的不建議使用Redis做為資料庫。

關于Redis的使用,看到網上一個流程圖:

1、儲存資料不經常變化

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

2、如果資料經常變化,就需要取操作Redis和持久化資料層的動作了,保證所有的都是最新的,實時更新Redis 的key到資料庫,data到Redis中,但是要注意高并發

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

三、Redis的安裝和調試使用

1.下載下傳最新版redis,選擇.msi安裝版本,或者.zip免安裝 (我這裡是.msi安裝)

下載下傳位址:https://github.com/MicrosoftArchive/redis/releases
從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

2.輕按兩下執行.msi檔案,一路next,中間有一個需要注冊服務,因為如果不注冊的話,把啟動的Dos視窗關閉的話,Redis就中斷連接配接了。

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

3.如果你是免安裝的,需要執行以下語句

啟動指令:redis-server.exe redis.windows.conf

注冊服務指令:redis-server.exe --service-install redis.windows.conf

去服務清單查詢服務,可以看到redis服務預設沒有開啟,開啟redis服務(可以設定為開機自動啟動)

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

還有要看Redis服務是否開啟

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

更新:這裡有個小插曲,如果你第一次使用,可以修改下 Redis 的預設端口 6079 ,之前有報導說可能存在被攻擊的可能性,不過個人開發,我感覺無可厚非。知道有這個事兒即可。

四、建立appsettings.json資料擷取類

如果你對.net 擷取app.config或者web.config得心應手的話,在.net core中就稍顯吃力,因為不支援直接對Configuration的操作,

1、appsettings.json檔案配置參數

前幾篇文章中有一個網友說了這樣的方法,在Starup.cs中的ConfigureServices方法中,添加

Blog.Core.Repository.BaseDBConfig.ConnectionString = Configuration.GetSection("AppSettings:SqlServerConnection").Value;      

當然這是可行的,隻不過,如果配置的資料很多,比如這樣的,那就不好寫了。

{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  //使用者配置資訊
  "AppSettings": {
    //Redis緩存
    "RedisCaching": {
      "Enabled": true,
      "ConnectionString": "127.0.0.1:6379"
    },
    //資料庫配置
    "SqlServer": {
      "SqlServerConnection": "Server=.;Database=WMBlogDB;User ID=sa;Password=123;",
      "ProviderName": "System.Data.SqlClient"
    },
    "Date": "2018-08-28",
    "Author": "Blog.Core"
  }
}      

當然,我受到他的啟發,簡單做了下處理,大家看看是否可行

1、建立 appsettings 幫助類

在Blog.Core.Common類庫中,建立Helper檔案夾,建立Appsettings.cs操作類,然後引用 Microsoft.Extensions.Configuration.Json 的Nuget包

/// <summary>
    /// appsettings.json操作類
    /// </summary>
    public class Appsettings
    {
        static IConfiguration Configuration { get; set; }
        static Appsettings()
        {
            //ReloadOnChange = true 當appsettings.json被修改時重新加載
            Configuration = new ConfigurationBuilder()
            .Add(new JsonConfigurationSource { Path = "appsettings.json", ReloadOnChange = true })
            .Build();
        }
        /// <summary>
        /// 封裝要操作的字元
        /// </summary>
        /// <param name="sections"></param>
        /// <returns></returns>
        public static string app(params string[] sections)
        {
            try
            {
                var val = string.Empty;
                for (int i = 0; i < sections.Length; i++)
                {
                    val += sections[i] + ":";
                }

                return Configuration[val.TrimEnd(':')];
            }
            catch (Exception)
            {
                return "";
            }

        }
    }      

2、按照規則擷取指定參數

如何使用呢,直接引用類庫,傳遞想要的參數就行(這裡對參數是有順序要求的,這個順序就是json檔案中的層級)

/// <summary>
   /// 擷取部落格清單
   /// </summary>
   /// <returns></returns>
   [HttpGet]
   [Route("GetBlogs")]
   public async Task<List<BlogArticle>> GetBlogs()
   {
       var connect=Appsettings.app(new string[] { "AppSettings", "RedisCaching" , "ConnectionString" });//按照層級的順序,依次寫出來

       return await blogArticleServices.getBlogs();
   }          

3、将appsettings.json添加到bin生成檔案中

如果直接運作,會報錯,提示沒有權限,

操作:右鍵appsettings.json =》 屬性 =》 Advanced =》 複制到輸出檔案夾 =》 永遠複制 =》應用,儲存

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

4、運作項目,檢視效果

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

五、基于Controller的Redis緩存

1、自定義序列化幫助類

在Blog.Core.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);
        }
    }      
從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

2、定義Redis接口和實作類

在Blog.Core.Common類庫中,建立Redis檔案夾,建立IRedisBasketRepository接口和RedisBasketRepository類,并引用Nuget包StackExchange.Redis

namespace Blog.Core.Common
{
    /// <summary>
    /// Redis緩存接口
    /// </summary>
    public interface IRedisBasketRepository
    {

        //擷取 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();
    }
}      

因為在開發的過程中,通過ConnectionMultiplexer頻繁的連接配接關閉服務,是很占記憶體資源的,是以我們使用單例模式來實作:

這裡要引用 Redis 依賴,現在的線上項目已經把這個類遷移到了Common 層,大家知道怎麼用就行。

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

添加nuget包後,然後引用 

using StackExchange.Redis;

public class RedisBasketRepository : IRedisBasketRepository
    {
        private readonly ILogger<RedisBasketRepository> _logger;
        private readonly ConnectionMultiplexer _redis;
        private readonly IDatabase _database;

        public RedisBasketRepository(ILogger<RedisBasketRepository> logger, ConnectionMultiplexer redis)
        {
            _logger = logger;
            _redis = redis;
            _database = redis.GetDatabase();
        }

        private IServer GetServer()
        {
            var endpoint = _redis.GetEndPoints();
            return _redis.GetServer(endpoint.First());
        }

        public async Task Clear()
        {
            foreach (var endPoint in _redis.GetEndPoints())
            {
                var server = GetServer();
                foreach (var key in server.Keys())
                {
                    await _database.KeyDeleteAsync(key);
                }
            }
        }

        public async Task<bool> Exist(string key)
        {
            return await _database.KeyExistsAsync(key);
        }

        public async Task<string> GetValue(string key)
        {
            return await _database.StringGetAsync(key);
        }

        public async Task Remove(string key)
        {
            await _database.KeyDeleteAsync(key);
        }

        public async Task Set(string key, object value, TimeSpan cacheTime)
        {
            if (value != null)
            {
                //序列化,将object值生成RedisValue
               await _database.StringSetAsync(key, SerializeHelper.Serialize(value), cacheTime);
            }
        }

        public async Task<TEntity> Get<TEntity>(string key)
        {
            var value = await _database.StringGetAsync(key);
            if (value.HasValue)
            {
                //需要用的反序列化,将Redis存儲的Byte[],進行反序列化
                return SerializeHelper.Deserialize<TEntity>(value);
            }
            else
            {
                return default(TEntity);
            }
        }




        /// <summary>
        /// 根據key擷取RedisValue
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey"></param>
        /// <returns></returns>
        public async Task<RedisValue[]> ListRangeAsync(string redisKey)
        {
            return await _database.ListRangeAsync(redisKey);
        }

        /// <summary>
        /// 在清單頭部插入值。如果鍵不存在,先建立再插入值
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="redisValue"></param>
        /// <returns></returns>
        public async Task<long> ListLeftPushAsync(string redisKey, string redisValue, int db = -1)
        {
            return await _database.ListLeftPushAsync(redisKey, redisValue);
        }
        /// <summary>
        /// 在清單尾部插入值。如果鍵不存在,先建立再插入值
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="redisValue"></param>
        /// <returns></returns>
        public async Task<long> ListRightPushAsync(string redisKey, string redisValue, int db = -1)
        {
            return await _database.ListRightPushAsync(redisKey, redisValue);
        }

        /// <summary>
        /// 在清單尾部插入數組集合。如果鍵不存在,先建立再插入值
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="redisValue"></param>
        /// <returns></returns>
        public async Task<long> ListRightPushAsync(string redisKey, IEnumerable<string> redisValue, int db = -1)
        {
            var redislist = new List<RedisValue>();
            foreach (var item in redisValue)
            {
                redislist.Add(item);
            }
            return await _database.ListRightPushAsync(redisKey, redislist.ToArray());
        }


        /// <summary>
        /// 移除并傳回存儲在該鍵清單的第一個元素  反序列化
        /// </summary>
        /// <param name="redisKey"></param>
        /// <returns></returns>
        public async Task<T> ListLeftPopAsync<T>(string redisKey, int db = -1) where T : class
        {
            return JsonConvert.DeserializeObject<T>(await _database.ListLeftPopAsync(redisKey));
        }

        /// <summary>
        /// 移除并傳回存儲在該鍵清單的最後一個元素   反序列化
        /// 隻能是對象集合
        /// </summary>
        /// <param name="redisKey"></param>
        /// <returns></returns>
        public async Task<T> ListRightPopAsync<T>(string redisKey, int db = -1) where T : class
        {
            return JsonConvert.DeserializeObject<T>(await _database.ListRightPopAsync(redisKey));
        }

        /// <summary>
        /// 移除并傳回存儲在該鍵清單的第一個元素   
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public async Task<string> ListLeftPopAsync(string redisKey, int db = -1)
        {
            return await _database.ListLeftPopAsync(redisKey);
        }

        /// <summary>
        /// 移除并傳回存儲在該鍵清單的最後一個元素   
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public async Task<string> ListRightPopAsync(string redisKey, int db = -1)
        {
            return await _database.ListRightPopAsync(redisKey);
        }

        /// <summary>
        /// 清單長度
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public async Task<long> ListLengthAsync(string redisKey, int db = -1)
        {
            return await _database.ListLengthAsync(redisKey);
        }

        /// <summary>
        /// 傳回在該清單上鍵所對應的元素
        /// </summary>
        /// <param name="redisKey"></param>
        /// <returns></returns>
        public async Task<IEnumerable<string>> ListRangeAsync(string redisKey, int db = -1)
        {
            var result = await _database.ListRangeAsync(redisKey);
            return result.Select(o => o.ToString());
        }

        /// <summary>
        /// 根據索引擷取指定位置資料
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="start"></param>
        /// <param name="stop"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public async Task<IEnumerable<string>> ListRangeAsync(string redisKey, int start, int stop, int db = -1)
        {
            var result = await _database.ListRangeAsync(redisKey, start, stop);
            return result.Select(o => o.ToString());
        }

        /// <summary>
        /// 删除List中的元素 并傳回删除的個數
        /// </summary>
        /// <param name="redisKey">key</param>
        /// <param name="redisValue">元素</param>
        /// <param name="type">大于零 : 從表頭開始向表尾搜尋,小于零 : 從表尾開始向表頭搜尋,等于零:移除表中所有與 VALUE 相等的值</param>
        /// <param name="db"></param>
        /// <returns></returns>
        public async Task<long> ListDelRangeAsync(string redisKey, string redisValue, long type = 0, int db = -1)
        {
            return await _database.ListRemoveAsync(redisKey, redisValue, type);
        }

        /// <summary>
        /// 清空List
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="db"></param>
        public async Task ListClearAsync(string redisKey, int db = -1)
        {
            await _database.ListTrimAsync(redisKey, 1, 0);
        }
    }      

代碼還是很簡單的,網上都有很多資源,就是普通的CURD

3、将Redis服務注入到容器中,并在Controller中調用

将redis接口和類 在ConfigureServices中 進行注入,

services.AddTransient<IRedisBasketRepository, RedisBasketRepository>();

            // 配置啟動Redis服務,雖然可能影響項目啟動速度,但是不能在運作的時候報錯,是以是合理的
            services.AddSingleton<ConnectionMultiplexer>(sp =>
               {
                   //擷取連接配接字元串
                   string redisConfiguration = Appsettings.app(new string[] { "Redis", "ConnectionString" });

                   var configuration = ConfigurationOptions.Parse(redisConfiguration, true);

                   configuration.ResolveDns = true;

                   return ConnectionMultiplexer.Connect(configuration);
               });      

關于為啥我使用了 Scoped 的,可能是想多了,想到了分布式裡邊了,這裡有個博問:Redis多執行個體建立連接配接開銷的一些疑問?大家自己看看就好,用單例就可以。

注意是構造函數注入,然後在controller中添加代碼測試

     /// <summary>
        /// 擷取部落格清單
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("GetBlogs")]
        public async Task<List<BlogArticle>> GetBlogs()
        {
            var connect=Appsettings.app(new string[] { "AppSettings", "RedisCaching" , "ConnectionString" });//按照層級的順序,依次寫出來

            List<BlogArticle> blogArticleList = new List<BlogArticle>();

            if (RedisBasketRepository.Get<object>("Redis.Blog") != null)
            {
                blogArticleList = RedisBasketRepository.Get<List<BlogArticle>>("Redis.Blog");
            }
            else
            {
                blogArticleList = await blogArticleServices.Query(d => d.bID > 5);
                RedisBasketRepository.Set("Redis.Blog", blogArticleList, TimeSpan.FromHours(2));//緩存2小時
            }

            return blogArticleList;
        }      

4、運作,執行Redis緩存,看到結果

從壹開始前後端分離【 .NET Core2.0/3.0 +Vue2.0 】架構之十一 || AOP自定義篩選,Redis入門 11.1

六、基于AOP的Redis緩存

 旁白:這一塊終于解決了,時間大概經過了4個月,終于被群裡的小夥伴@JoyLing 給解決了,我個人感覺還是很不錯的,這裡記錄一下:

1、核心:Redis緩存切面攔截器

 在上篇文章中,我們已經定義過了一個攔截器,隻不過是基于記憶體Memory緩存的,并不适應于Redis,上邊咱們也說到了Redis必須要存入指定的值,比如字元串,而不能将異步對象 Task<T> 儲存到硬碟上,是以我們就修改下攔截器方法,一個專門應用于 Redis 的切面攔截器:

//通過注入的方式,把Redis緩存操作接口通過構造函數注入
      private IRedisBasketRepository _cache;
      public BlogRedisCacheAOP(IRedisBasketRepository cache)
      {
          _cache = cache;
      }


      //Intercept方法是攔截的關鍵所在,也是IInterceptor接口中的唯一定義
       public void Intercept(IInvocation invocation)
        {
            var method = invocation.MethodInvocationTarget ?? invocation.Method;
            //對目前方法的特性驗證
            var qCachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) as CachingAttribute;
            if (qCachingAttribute != null)
            {
                //擷取自定義緩存鍵,這個和Memory記憶體緩存是一樣的,不細說
                var cacheKey = CustomCacheKey(invocation);
                //核心1:注意這裡和之前不同,是擷取的string值,之前是object
                var cacheValue = _cache.GetValue(cacheKey);
                if (cacheValue != null)
                {
                    //将目前擷取到的緩存值,指派給目前執行方法
                    var type = invocation.Method.ReturnType;
                    var resultTypes = type.GenericTypeArguments;
                    if (type.FullName == "System.Void")
                    {
                        return;
                    }
                    object response;
                    if (type != null && typeof(Task).IsAssignableFrom(type))
                    {
                        //核心2:傳回異步對象Task<T>
                        if (resultTypes.Count() > 0)
                        {
                            var resultType = resultTypes.FirstOrDefault();
                            // 核心3,直接序列化成 dynamic 類型,之前我一直糾結特定的實體
                            dynamic temp = Newtonsoft.Json.JsonConvert.DeserializeObject(cacheValue, resultType);
                            response = Task.FromResult(temp);

                        }
                        else
                        {
                            //Task 無傳回方法 指定時間内不允許重新運作
                            response = Task.Yield();
                        }
                    }
                    else
                    {
                        // 核心4,要進行 ChangeType
                        response = System.Convert.ChangeType(_cache.Get<object>(cacheKey), type);
                    }

                    invocation.ReturnValue = response;
                    return;
                }
                //去執行目前的方法
                invocation.Proceed();

                //存入緩存
                if (!string.IsNullOrWhiteSpace(cacheKey))
                {
                    object response;

                    //Type type = invocation.ReturnValue?.GetType();
                    var type = invocation.Method.ReturnType;
                    if (type != null && typeof(Task).IsAssignableFrom(type))
                    {
                        var resultProperty = type.GetProperty("Result");
                        response = resultProperty.GetValue(invocation.ReturnValue);
                    }
                    else
                    {
                        response = invocation.ReturnValue;
                    }
                    if (response == null) response = string.Empty;
                    // 核心5:将擷取到指定的response 和特性的緩存時間,進行set操作
                    _cache.Set(cacheKey, response, TimeSpan.FromMinutes(qCachingAttribute.AbsoluteExpiration));
                }
            }
            else
            {
                invocation.Proceed();//直接執行被攔截方法
            }
        }      

上邊紅色标注的,是和之前不一樣的,整體結構還是差不多的,相信都能看的懂的,最後我們就可以很任性的在Autofac容器中,進行任意緩存切換了,是不是很棒!

再次感覺小夥伴JoyLing,不知道他部落格園位址。

七、CODE

https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core