天天看點

3.NetDh架構之緩存操作類和二次開發模式簡單設計(附源碼和示例代碼)

前言

NetDh架構适用于C/S、B/S的服務端架構,可用于項目開發和學習。目前包含以下四個子產品

1.資料庫操作層封裝Dapper,支援多種資料庫類型、多庫執行個體,簡單強大;

此部分具體說明可參考部落格: https://www.cnblogs.com/michaeldonghan/p/9317078.html

2.提供簡單高效的日志操作類使用,支援日志寫入Db和txt、支援任何資料庫類型寫入(包括傳統sql資料庫和nosql資料庫等)、支援同步寫入日志和背景獨立線程異步處理日志隊列;

此部分具體說明可參考部落格: https://www.cnblogs.com/michaeldonghan/p/9321691.html

3.提供簡單緩存設計和使用;

此部分具體說明可參考部落格: 本文以下章節内容。

4.業務邏輯層服務簡單設計,可友善支援二次開發模式。

1.緩存操作類

項目中應當都要考慮緩存的設計,不管是小項目的記憶體緩存還是大項目中的Redis/Memcache等。緩存的媒體比較有可能切換,比如由于資料量的提高,會從記憶體緩存切換到memcache。這時候就要設計緩存接口,用接口操作緩存動作,如下圖的ICacheHandle接口。之前文章有講到資料庫操作是設計為抽象基類DbHandleBase,抽象類注重代碼的重用,接口定義類的行為,類可以實作多個接口,但隻能繼承一個抽象類。

3.NetDh架構之緩存操作類和二次開發模式簡單設計(附源碼和示例代碼)

緩存操作類比較簡單,上圖的記憶體緩存操作類RuntimeCacheHandle直接使用現成的System.Web.HttpRuntime.Cache實作,B/S、C/S都可以使用。上代碼(取緩存使用泛型操作,使用起來友善很多):

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;

namespace NetDh.Cache
{
    /*
     * 如果你的緩存媒體會有切換的可能,則建議用接口操作,
     * 此記憶體緩存操作類RuntimeCacheHandle是使用現成的System.Web.HttpRuntime實作,B/S、C/S都可以使用。
     */

    /// <summary>
    /// 記憶體緩存操作類。
    /// </summary>
    public class RuntimeCacheHandle : ICacheHandle
    {
        /// <summary>
        /// 取緩存。
        /// </summary>
        /// <typeparam name="T">T可以是引用類型,也可以是值類型</typeparam>
        /// <param name="key"></param>
        /// <returns>當緩存不存在時,引用類型傳回null;而值類型傳回的預設值,并不代表緩存存在。</returns>
        public T Get<T>(string key)
        {
            object value = HttpRuntime.Cache.Get(key);
            if (value != null)
            {
                return (T)value;
            }
            return default(T);//注意:值類型傳回的預設值 ,并不代表緩存存在。
        }

        /// <summary>
        /// 存入緩存。存入的是源value資料的備份,源資料修改不影響緩存。
        /// (一般直接寫Set("key1",obj),而不用Set<object>("key1",obj),因為.net會自動根據obj判斷T的類型)
        /// </summary>
        /// <typeparam name="T">T可以是引用類型,也可以是值類型</typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeOut">緩存的過期時間(秒),-1代表不過期。</param>
        /// <returns></returns>
        public bool Set<T>(string key, T value, int timeOut = -1)
        {
            if (timeOut == -1)
            {
                HttpRuntime.Cache.Insert(key, value);
            }
            else
            {
                var timeSpan = new TimeSpan(0, 0, timeOut);
                HttpRuntime.Cache.Insert(key, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, timeSpan);
            }
            return true;
        }

        /// <summary>
        /// 如果不存在key緩存,則添加,傳回true。如果已經存在key緩存,則不作操作,傳回false。
        /// (存入的是源value資料的備份,源資料修改不影響緩存。)
        /// </summary>
        /// <typeparam name="T">T可以是引用類型,也可以是值類型</typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeOut">緩存的過期時間(秒),-1代表不過期。</param>
        /// <returns></returns>
        public bool AddIfNotExist<T>(string key, T value, int timeOut = -1)
        {
            var timeSpan = timeOut > 0 ? new TimeSpan(0, 0, timeOut) : System.Web.Caching.Cache.NoSlidingExpiration;
            var oldCache = HttpRuntime.Cache.Add(key, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, timeSpan, CacheItemPriority.Normal, null);
            return oldCache == null;
        }

        /// <summary>
        /// 删除緩存
        /// </summary>
        /// <param name="key"></param>
        public void Remove(string key)
        {
            HttpRuntime.Cache.Remove(key);
        }

        public List<T> GetList<T>(List<string> keys)
        {
            //記憶體緩存不實作此接口函數,直接多次使用Get函數。
            //memcache/redis一般會實作此接口函數,是為了一次連接配接可取回多個值。
            throw new NotImplementedException();
        }
    }
}      

memcache的操作類,網上代碼很多,這邊不再介紹。

2.二次開發模式簡單設計

需求場景:多個客戶需要同一個項目産品,但是客戶之間對該産品的需求點又有些不一樣。如果為多個客戶都建立一個.net項目,那通用功能的代碼就要維護多份,如果隻建立一個.net項目,然後在同一個項目裡加if判斷,那改一個客戶的需求,可能會影響到其它客戶的功能。

解決方案:設計一種“二次開發模式”,即寫一套通用功能的.net通用項目(實際環境中,如果一開始隻有一個客戶,那就以第一個客戶的需求為通用項目,具體問題具體分析),不同客戶都建立一個.net項目,但隻處理客戶定制的功能,這就涉及到override通用項目功能。

上示例代碼來說明:

#region 正常調用服務和調用二次開發服務
            //可以用服務工廠調用相應方法
            ServiceFactory.Get<UserService>().TestFunc();
            //也可以直接調用服務靜态方法
            UserService.TestStaticFunc();

            //二次開發模式
            //1.調用的是UserService中的TestVirtualFunc方法
            ServiceFactory.Get<UserService>().TestVirtualFunc();
            //2.場景:後續不改原系統代碼,隻是在原來基礎上做二次開發
            //注冊二次開發Service
            //ServiceFactory.AddSecondaryAssembly(typeof(UserServiceX).Assembly);//其中UserServiceX繼承自UserService
            //3.假如執行了ServiceFactory.AddSecondaryAssembly,則下行代碼會調用到UserServiceX中的TestVirtualFunc方法
            ServiceFactory.Get<UserService>().TestVirtualFunc();
            #endregion      

當注冊了二次開發的程式集Assembly,就可以不改變通用項目的代碼,而運作到二次開發程式集中的代碼。

上ServiceFactory源碼:

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Reflection;
using System.Text;

namespace NetDh.TestService
{
    /// <summary>
    /// 擷取Service對象幫助類
    /// </summary>
    public class ServiceFactory
    {
        private static readonly ConcurrentDictionary<Type, BaseService> _services = new ConcurrentDictionary<Type, BaseService>();

        static ServiceFactory()
        {
            //預設添加本程式集Service
            var types = Assembly.GetExecutingAssembly().GetTypes();
            var baseType = typeof(BaseService);
            foreach (var type in types)
            {
                if (type.IsSubclassOf(baseType))
                {
                    //不會執行個體化服務對象。隻有用到時才會執行個體化
                    _services.TryAdd(type, null);
                }
            }
        }

        /// <summary>
        /// 擷取服務,T一定是繼承自BaseService
        /// </summary>
        /// <typeparam name="T">BaseService子類</typeparam>
        /// <returns></returns>
        public static T Get<T>() where T : BaseService
        {
            Type type = typeof(T);
            BaseService service;
            if (!_services.TryGetValue(type, out service))
            {
                throw new Exception("This service cannot be found");
            }
            if (service == null)
            {
                service = Activator.CreateInstance(type) as BaseService;
                _services[type] = service;
            }
            return (T)service;
        }

        /// <summary>
        /// 添加二次開發Service程式集
        /// </summary>
        /// <param name="assembly"></param>
        public static void AddSecondaryAssembly(Assembly secondaryAssembly)
        {
            if (secondaryAssembly == null) return;

            var secTypes = secondaryAssembly.GetTypes();
            var baseType = typeof(BaseService);
            foreach (var secType in secTypes)
            {
                if (secType.IsSubclassOf(baseType))
                {
                    Type parentType = null;
                    foreach (var type in _services.Keys)
                    {
                        if (secType.IsSubclassOf(type))
                        {
                            parentType = type;
                            break;
                        }
                    }
                    if (parentType != null)
                    {//如果二次開發重寫了原Service類
                        //優先使用二次開發的Service對象。需要在初始化時就執行個體化。
                        _services[parentType] = Activator.CreateInstance(secType) as BaseService;
                    }
                    else
                    {//如果二次開發的Service類是新增的,則直接添加,使用時再執行個體化
                        _services.TryAdd(secType, null);
                    }
                }
            }
        }

    }
}      

3.NetDh架構完整源碼

國外有github,國内有碼雲,在國内使用碼雲速度非常快。NetDh架構源碼放在碼雲上:

https://gitee.com/donghan/NetDh-Framework

分享、互相交流學習