部落格

  • 萬向電動汽車獲得中國工信部資質認證

    萬向電動汽車獲得中國工信部資質認證

    中國工信部今(22)日發布《車輛生產企業及產品公告》(第254批),公示了三家新增車輛生產企業的名單,公示時間為22日-28日。其中萬向電動汽車有限公司名列其中。

    此前,萬向在電動汽車相關領域投入重資,雖已經初步形成車載電子系統、動力電池和電動汽車領等較完整的產業鏈,但在國內市場上,作為從事電動汽車相關業務的公司,仍缺少工信部認證的資質。

    2013年1月,萬向成功收購美國A123,完善了電池產業鏈條,借全球知名企業品牌優勢,擴大了其在美國市場的銷售份額。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※新北清潔公司,居家、辦公、裝潢細清專業服務

    ※別再煩惱如何寫文案,掌握八大原則!

    ※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    ※網頁設計最專業,超強功能平台可客製化

  • 中國國務院批復新一輪新能源汽車推廣方案

    中國科技部部長萬鋼上周在2013中國汽車產業發展(泰達)國際論壇上透露,國務院已正式批復新一輪的新能源汽車示范推廣方案,四部委正在制定實施細則,並將盡快正式啟動。「直接補貼到企業」將是新一輪補貼政策最大的變化。

    新一輪補貼政策的內容主要有:以試點城市為核心,設立試點區域,擴大輻射范圍,加速區域電動汽車推廣;改善原有財政資金補貼方式,加快資金補貼落實力度;混合動力客車將向全國推廣;對充電站的建設進行財政支持。

    另外,發改委、工信部、商務部等多部委對特斯拉總部進行了訪問考察。此次訪問或對特斯拉順利進入中國市場有促進作用。

    中國國內電動汽車也在快速發展。以杭州為例,在國內率先啟用了可出租的純電動微公交,每小時20元(人民幣),一次充滿電可跑80-100公裏,每次充電大約耗時30分鐘。杭州市未來設想,每5-10公裏有1個立體車庫站點,建成綠色的微公交網點係統。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • Tesla執行長稱燃料電池不適合推廣 純電動車才有未來

    據外媒本週二報導,美國電動跑車製造商特斯拉(Tesla)執行長Elon Musk本週在慕尼黑特斯拉展示中心表示,很多人都說純電動車根本沒有未來,但他認為,氫燃料電池基本上只是一種行銷的伎倆,氫是一種危險性頗高的氣體,比較適合用來推動火箭。

    Elon Musk在演說中提到,旗下價格較低的大眾車種(可能會被命名為「Model E」)預料將在12-15個月內開發完成、2016年開賣,而休旅車「Model X」則會在明(2014)年問世。

    此前,豐田汽車(Toyota) 董事長內山田武(Takeshi Uchiyamada)曾與9月30日在華盛頓特區經濟俱樂部(Economic Club of Washington, D.C.)發表演說後表示,Toyota之所以並未推出任何一款重量級純電動車,是因為該公司不認為這種產品會有市場。

    目前全球燃料電池車的研發以日系車廠為軸心形成三大陣營,其中Toyota已表明計劃於2015年開賣燃料電池車。據日經新聞報導,預估2025年日本國內的燃料電池車普及數量將達200萬台。

    另據日經新聞報導,日本政府也計劃於2015年度結束前,在國內整備100座氫燃料充填據點「氫氣站」,其他日本企業也紛紛著手進行相關技術的研發。為促進燃料電池車的普及,千代田化工建設(CHIYODA)計畫投下約300億日圓,於2015年在川崎市興建全球首座大規模氫燃料供應基地。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    ※FB行銷專家,教你從零開始的技巧

  • 在運行時生成C# .NET類

    在運行時生成C# .NET類

    ​本文譯自​:​Generating C# .NET Classes at Runtime
    作者:WedPort

    在我的C#職業生涯中,有幾次我不得不在運行時生成新的類型。希望把它寫下來能幫助有相同應用需求的人。這也意味着我以後不必在查找相同問題的StackOverflow文章了。我最初是在.NET 4.6.2中這樣做的,但我已經更新到為.NET Core 3.0提供了示例。所有代碼都可以在我的GitHub上面找到。
    GitHub:https://github.com/cheungt6/public/tree/master/ReflectionEmitClassGeneration

    為什麼我需要在運行時生成類?

    在運行時生產新類型的需求通常是由於運行時才知道類屬性,滿足性能要求以及需要在新類型中添加功能。當你嘗試這樣做的時候,你應該考慮的第一件事是:這是否真的是一個明智的解決方案。在深入思考之前,還有很多其他事情可以嘗試,問你自己這樣的問題:

    1. 我可以使用普通的類嗎
    2. 我可以使用Dictionary、Tuple或者對象數組(Array)?
    3. 我是否可以使用擴展對象
    4. 我確定我不能使用一個普通的類嗎?

    如果你認為這仍然是必要的,請繼續閱讀下面的內容。

    示例用例

    作為一名開發人員,我將大量數據綁定到各種WPF Grids中。大多數時候屬性是固定的,我可以使用預定義的類。有時候,我不得不動態的構建網格,並且能夠在應用程序運行時更改數據。採取以下显示ID和一些財務數據的類(FTSE和CAC是指數,其屬性代表指數價格):

    public class PriceHolderViewModel : ViewModelBase
    {
        public long Id { get; set; }
        public decimal FTSE100 { get; set; }
        public decimal CAC40 { get; set; }
    }
    

    如果我們僅對其中的屬性感興趣,該類定義的非常棒。但是,如果要使用更多屬性擴展此類,則需要在代碼中添加它,重新編譯並在新版本中進行部署。

    相反的,我們可以做的是跟蹤對象所需的屬性,並在運行時構建類。這將允許我們在需要是不斷的添加和刪除屬性,並使用反射來更新它們的值。

    // Keep track of my properties
    var _properties = new Dictionary<string, Type>(new[]{
       new KeyValuePair<string, Type>( "FTSE100", typeof(Decimal) ),
       new KeyValuePair<string, Type>( "CAC40", typeof(Decimal) ) });
    

    創建你的類型

    下面的示例向您展示了如何在運行時構建新類型。你需要使用**System.Reflection.Emit**庫來構造一個新的動態程序集,您的類將在其中創建,然後是模塊和類型。與舊的** .NET Framework**框架不同,在舊的版本中,你需要在當前程序的AppDomain中創建程序集 ,而在** .NET Core** 中,AppDomain不再可用。你將看到我使用GUID創建了一個新類型名稱,以便於跟蹤類型的版本。在以前,你不能創建具有相同名稱的兩個類型,但是現在似乎不是這樣了。

    public Type GeneratedType { private set; get; }
    
    private void Initialise()
    {
        var newTypeName = Guid.NewGuid().ToString();
        var assemblyName = new AssemblyName(newTypeName);
        var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var dynamicModule = dynamicAssembly.DefineDynamicModule("Main");
        var dynamicType = dynamicModule.DefineType(newTypeName,
                TypeAttributes.Public |
                TypeAttributes.Class |
                TypeAttributes.AutoClass |
                TypeAttributes.AnsiClass |
                TypeAttributes.BeforeFieldInit |
                TypeAttributes.AutoLayout,
                typeof(T));     // This is the type of class to derive from. Use null if there isn't one
        dynamicType.DefineDefaultConstructor(MethodAttributes.Public |
                                            MethodAttributes.SpecialName |
                                            MethodAttributes.RTSpecialName);
        foreach (var property in Properties)
            AddProperty(dynamicType, property.Key, property.Value);
    
        GeneratedType = dynamicType.CreateType();
    }
    

    在定義類型時,你可以提供一種類型,從中派生新的類型。如果你的基類具有要包含在新類型中的某些功能或屬性,這將非常有用。之前,我曾使用它在運行時擴展ViewModel和Serializable類型。

    在你創建了TypeBuilder后,你可以使用下面提供的代碼開始添加屬性。它創建了支持字段和所需的中間語言,以便通過Getter和Setter訪問它們。為每個屬性完成此操作后,可以使用CreateType()創建類型的實例。

    private static void AddProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType)
    {
        var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
        var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
        
        var getMethod = typeBuilder.DefineMethod("get_" + propertyName,
            MethodAttributes.Public |
            MethodAttributes.SpecialName |
            MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
        var getMethodIL = getMethod.GetILGenerator();
        getMethodIL.Emit(OpCodes.Ldarg_0);
        getMethodIL.Emit(OpCodes.Ldfld, fieldBuilder);
        getMethodIL.Emit(OpCodes.Ret);
    
        var setMethod = typeBuilder.DefineMethod("set_" + propertyName,
              MethodAttributes.Public |
              MethodAttributes.SpecialName |
              MethodAttributes.HideBySig,
              null, new[] { propertyType });
        var setMethodIL = setMethod.GetILGenerator();
        Label modifyProperty = setMethodIL.DefineLabel();
        Label exitSet = setMethodIL.DefineLabel();
    
        setMethodIL.MarkLabel(modifyProperty);
        setMethodIL.Emit(OpCodes.Ldarg_0);
        setMethodIL.Emit(OpCodes.Ldarg_1);
        setMethodIL.Emit(OpCodes.Stfld, fieldBuilder);
        setMethodIL.Emit(OpCodes.Nop);
        setMethodIL.MarkLabel(exitSet);
        setMethodIL.Emit(OpCodes.Ret);
    
        propertyBuilder.SetGetMethod(getMethod);
        propertyBuilder.SetSetMethod(setMethod);
    }
    

    有了類型后,就很容易通過使用Activator.CreateInstance()來創建它的實例。但是,你希望能夠更改已創建的屬性的值,為了做到這一點,你可以再次使用反射來獲取propertyInfos並提取Set方法。一旦有了這些屬性,電影它們類設置屬性值就相對簡單了。

    foreach (var property in Properties)
    {
        var propertyInfo = GeneratedType.GetProperty(property.Key);
        var setMethod = propertyInfo.GetSetMethod();
        setMethod.Invoke(objectInstance, new[] { propertyValue });
    }
    

    現在,您可以在運行時使用自定義屬性來創建自己的類型,並具有更新其值的功能,一切就緒。 我發現的唯一障礙是創建一個可以存儲新類型實例的列表。 WPF中的DataGrid傾向於只讀取List的常規參數類型的屬性。 這意味着即使您使用新屬性擴展了基類,使用AutoGenerateProperties也只能看到基類中的屬性。 解決方案是使用生成的類型顯式創建一個新的List。 我在下面提供了如何執行此操作的示例:

    var listGenericType = typeof(List<>);
    var list = listGenericType.MakeGenericType(GeneratedType);
    var constructor = list.GetConstructor(new Type[] { });
    var newList = (IList)constructor.Invoke(new object[] { });
    foreach (var value in values)
        newList.Add(value);
    

    結論

    我已經在GitHub中創建了一個示例應用程序。它包含一個UI來幫助您調試和理解運行時新類型的創建,以及如何更新值。如果您有任何問題或意見,請隨時與我們聯繫。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • 一個線上問題的思考:Eureka註冊中心集群如何實現客戶端請求負載及故障轉移?

    一個線上問題的思考:Eureka註冊中心集群如何實現客戶端請求負載及故障轉移?

    前言

    先拋一個問題給我聰明的讀者,如果你們使用微服務SpringCloud-Netflix進行業務開發,那麼線上註冊中心肯定也是用了集群部署,問題來了:

    你了解Eureka註冊中心集群如何實現客戶端請求負載及故障轉移嗎?

    可以先思考一分鐘,我希望你能夠帶着問題來閱讀此篇文章,也希望你看完文章後會有所收穫!

    背景

    前段時間線上Sentry平台報警,多個業務服務在和註冊中心交互時,例如續約和註冊表增量拉取等都報了Request execution failed with message : Connection refused 的警告:

    緊接着又看到 Request execution succeeded on retry #2 的日誌。

    看到這裏,表明我們的服務在嘗試兩次重連后和註冊中心交互正常了。

    一切都顯得那麼有驚無險,這裏報Connection refused 是註冊中心網絡抖動導致的,接着觸發了我們服務的重連,重連成功后一切又恢復正常。

    這次的報警雖然沒有對我們線上業務造成影響,並且也在第一時間恢復了正常,但作為一個愛思考的小火雞,我很好奇這背後的一系列邏輯:Eureka註冊中心集群如何實現客戶端請求負載及故障轉移?

    註冊中心集群負載測試

    線上註冊中心是由三台機器組成的集群,都是4c8g的配置,業務端配置註冊中心地址如下(這裏的peer來代替具體的ip地址):

    eureka.client.serviceUrl.defaultZone=http://peer1:8080/eureka/,http://peer2:8080/eureka/,http://peer3:8080/eureka/
    

    我們可以寫了一個Demo進行測試:

    註冊中心集群負載測試

    1、本地通過修改EurekaServer服務的端口號來模擬註冊中心集群部署,分別以8761和8762兩個端口進行啟動
    2、啟動客戶端SeviceA,配置註冊中心地址為:http://localhost:8761/eureka,http://localhost:8762/eureka

    3、啟動SeviceA時在發送註冊請求的地方打斷點:AbstractJerseyEurekaHttpClient.register(),如下圖所示:

    這裏看到請求註冊中心時,連接的是8761這個端口的服務。

    4、更改ServiceA中註冊中心的配置:http://localhost:8762/eureka,http://localhost:8761/eureka
    5、重新啟動SeviceA然後查看端口,如下圖所示:

    此時看到請求註冊中心是,連接的是8762這個端口的服務。

    註冊中心故障轉移測試

    以兩個端口分別啟動EurekaServer服務,再啟動一個客戶端ServiceA。啟動成功后,關閉一個8761端口對應的服務,查看此時客戶端是否會自動遷移請求到8762端口對應的服務:

    1、以8761和8762兩個端口號啟動EurekaServer
    2、啟動ServiceA,配置註冊中心地址為:http://localhost:8761/eureka,http://localhost:8762/eureka
    3、啟動成功后,關閉8761端口的EurekaServer
    4、在EurekaClient端發送心跳請求的地方打上斷點:AbstractJerseyEurekaHttpClient.sendHeartBeat()
    5、查看斷點處數據,第一次請求的EurekaServer是8761端口的服務,因為該服務已經關閉,所以返回的response是null

    6、第二次會重新請求8762端口的服務,返回的response為狀態為200,故障轉移成功,如下圖:

    思考

    通過這兩個測試Demo,我以為EurekaClient每次都會取defaultZone配置的第一個host作為請求EurekaServer的請求的地址,如果該節點故障時,會自動切換配置中的下一個EurekaServer進行重新請求。

    那麼疑問來了,EurekaClient每次請求真的是以配置的defaultZone配置的第一個服務節點作為請求的嗎?這似乎也太弱了!!?

    EurekaServer集群不就成了偽集群!!?除了客戶端配置的第一個節點,其它註冊中心的節點都只能作為備份和故障轉移來使用!!?

    真相是這樣嗎?NO!我們眼見也不一定為實,源碼面前毫無秘密!

    翠花,上乾貨!

    客戶端請求負載原理

    原理圖解

    還是先上結論,負載原理如圖所示:

    這裡會以EurekaClient端的IP作為隨機的種子,然後隨機打亂serverList,例如我們在商品服務(192.168.10.56)中配置的註冊中心集群地址為:peer1,peer2,peer3,打亂后的地址可能變成peer3,peer2,peer1。

    用戶服務(192.168.22.31)中配置的註冊中心集群地址為:peer1,peer2,peer3,打亂后的地址可能變成peer2,peer1,peer3。

    EurekaClient每次請求serverList中的第一個服務,從而達到負載的目的。

    代碼實現

    我們直接看最底層負載代碼的實現,具體代碼在
    com.netflix.discovery.shared.resolver.ResolverUtils.randomize() 中:

    這裏面random 是通過我們EurekaClient端的ipv4做為隨機的種子,生成一個重新排序的serverList,也就是對應代碼中的randomList,所以每個EurekaClient獲取到的serverList順序可能不同,在使用過程中,取列表的第一個元素作為server端host,從而達到負載的目的。

    思考

    原來代碼是通過EurekaClient的IP進行負載的,所以剛才通過DEMO程序結果就能解釋的通了,因為我們做實驗都是用的同一個IP,所以每次都是會訪問同一個Server節點。

    既然說到了負載,這裏肯定會有另一個疑問:

    通過IP進行的負載均衡,每次請求都會均勻分散到每一個Server節點嗎?

    比如第一次訪問Peer1,第二次訪問Peer2,第三次訪問Peer3,第四次繼續訪問Peer1等,循環往複……

    我們可以繼續做個試驗,假如我們有10000個EurekaClient節點,3個EurekaServer節點。

    Client節點的IP區間為:192.168.0.0 ~ 192.168.255.255,這裏面共覆蓋6w多個ip段,測試代碼如下:

    /**
     * 模擬註冊中心集群負載,驗證負載散列算法
     *
     *  @author 一枝花算不算浪漫
     *  @date 2020/6/21 23:36
     */
    public class EurekaClusterLoadBalanceTest {
    
        public static void main(String[] args) {
            testEurekaClusterBalance();
        }
    
        /**
         * 模擬ip段測試註冊中心負載集群
         */
        private static void testEurekaClusterBalance() {
            int ipLoopSize = 65000;
            String ipFormat = "192.168.%s.%s";
            TreeMap<String, Integer> ipMap = Maps.newTreeMap();
            int netIndex = 0;
            int lastIndex = 0;
            for (int i = 0; i < ipLoopSize; i++) {
                if (lastIndex == 256) {
                    netIndex += 1;
                    lastIndex = 0;
                }
    
                String ip = String.format(ipFormat, netIndex, lastIndex);
                randomize(ip, ipMap);
                System.out.println("IP: " + ip);
                lastIndex += 1;
            }
    
            printIpResult(ipMap, ipLoopSize);
        }
    
        /**
         * 模擬指定ip地址獲取對應註冊中心負載
         */
        private static void randomize(String eurekaClientIp, TreeMap<String, Integer> ipMap) {
            List<String> eurekaServerUrlList = Lists.newArrayList();
            eurekaServerUrlList.add("http://peer1:8080/eureka/");
            eurekaServerUrlList.add("http://peer2:8080/eureka/");
            eurekaServerUrlList.add("http://peer3:8080/eureka/");
    
            List<String> randomList = new ArrayList<>(eurekaServerUrlList);
            Random random = new Random(eurekaClientIp.hashCode());
            int last = randomList.size() - 1;
            for (int i = 0; i < last; i++) {
                int pos = random.nextInt(randomList.size() - i);
                if (pos != i) {
                    Collections.swap(randomList, i, pos);
                }
            }
    
            for (String eurekaHost : randomList) {
                int ipCount = ipMap.get(eurekaHost) == null ? 0 : ipMap.get(eurekaHost);
                ipMap.put(eurekaHost, ipCount + 1);
                break;
            }
        }
    
        private static void printIpResult(TreeMap<String, Integer> ipMap, int totalCount) {
            for (Map.Entry<String, Integer> entry : ipMap.entrySet()) {
                Integer count = entry.getValue();
                BigDecimal rate = new BigDecimal(count).divide(new BigDecimal(totalCount), 2, BigDecimal.ROUND_HALF_UP);
                System.out.println(entry.getKey() + ":" + count + ":" + rate.multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP) + "%");
            }
        }
    }
    

    負載測試結果如下:

    可以看到第二個機器會有50%的請求,最後一台機器只有17%的請求,負載的情況並不是很均勻,我認為通過IP負載並不是一個好的方案。

    還記得我們之前講過Ribbon默認的輪詢算法RoundRobinRule,【一起學源碼-微服務】Ribbon 源碼四:進一步探究Ribbon的IRule和IPing 。

    這種算法就是一個很好的散列算法,可以保證每次請求都很均勻,原理如下圖:

    故障轉移原理

    原理圖解

    還是先上結論,如下圖:

    我們的serverList按照client端的ip進行重排序后,每次都會請求第一個元素作為和Server端交互的host,如果請求失敗,會嘗試請求serverList列表中的第二個元素繼續請求,這次請求成功后,會將此次請求的host放到全局的一個變量中保存起來,下次client端再次請求 就會直接使用這個host。

    這裏最多會重試請求兩次。

    代碼實現

    直接看底層交互的代碼,位置在
    com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute() 中:

    我們來分析下這個代碼:

    1. 第101行,獲取client上次成功server端的host,如果有值則直接使用這個host
    2. 第105行,getHostCandidates()是獲取client端配置的serverList數據,且通過ip進行重排序的列表
    3. 第114行,candidateHosts.get(endpointIdx++),初始endpointIdx=0,獲取列表中第1個元素作為host請求
    4. 第120行,獲取返回的response結果,如果返回的狀態碼是200,則將此次請求的host設置到全局的delegate變量中
    5. 第133行,執行到這裏說明第120行執行的response返回的狀態碼不是200,也就是執行失敗,將全局變量delegate中的數據清空
    6. 再次循環第一步,此時endpointIdx=1,獲取列表中的第二個元素作為host請求
    7. 依次執行,第100行的循環條件numberOfRetries=3,最多重試2次就會跳出循環

    我們還可以第123和129行,這也正是我們業務拋出來的日誌信息,所有的一切都對應上了。

    總結

    感謝你看到這裏,相信你已經清楚了開頭提問的問題。

    上面已經分析完了Eureka集群下Client端請求時負載均衡的選擇以及集群故障時自動重試請求的實現原理。

    如果還有不懂的問題,可以添加我的微信或者給我公眾號留言,我會單獨和你討論交流。

    本文首發自:一枝花算不算浪漫 公眾號,如若轉載請在文章開頭標明出處,如需開白可直接公眾號回復即可。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    ※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心

  • 說說TCP的三次握手和四次揮手

    說說TCP的三次握手和四次揮手

    一、傳輸控制協議TCP簡介

    1.1 簡介

    TCP(Transmission Control Protocol) 傳輸控制協議,是一種 面向連接的、可靠的、基於字節流的傳輸層 通信協議。

    TCP是一種面向連接(連接導向)的、可靠的基於字節流的傳輸層通信協議。TCP將用戶數據打包成報文段,它發送后啟動一個定時器,另一端收到的數據進行確認、對失序的數據重新排序、丟棄重複數據。

    TCP把連接作為最基本的對象,每一條TCP連接都有兩個端點,這種端點我們叫作套接字(socket),將端口號拼接到IP地址即構成了套接字,例如 192.1.1.6:50030

    1.2 特點

    • 面向連接的、可靠的、基於字節流的 傳輸層 通信協議
    • 將應用層的數據流分割成文段併發送給目標節點的TCP層
    • 數據包都有序號,對方收到則發送ACK確認,未收到則重傳
    • 使用校驗和來檢驗數據在傳輸過程中是否有誤

    二、TCP報文頭

    1、源端口(Source Port)/ 目的端口(Destination Port):他們各佔2個字節,標示該段報文來自哪裡(源端口)以及要傳給哪個上層協議或應用程序(目的端口)。進行tcp通信時,一般client是通過系統自動選擇的臨時端口號,而服務器一般是使用知名服務端口號或者自己指定的端口號(比如DNS協議對應端口53,HTTP協議對應80)

    2、序號(Sequence Number):佔據四個字節,TCP是面向字節流的,TCP連接中傳送的字節流中的每個字節都按順序編號,例如如一段報文的序號字段值是107,而攜帶的數據共有100個字段,如果有下一個報文過來,那麼序號就從207(100+107)開始,整個要傳送的字節流的起始序號必須要在連接建立時設置。首部中的序號字段值指的是本報文段所發送的數據的第一個字節的序號

    3、確認序號(Acknowledgment Number):4個字節,是期望收到對方下一個報文段的第一個數據字節的序號,若確認號=N,則表明:到序號N-1為止的所有數據都已正確收到,例如:B收到A發送過來的報文,其序列號字段是301,而數據長度是200字節,這表明了B正確的收到了A到序號500(301+200-1)為止的數據,因此B希望收到A的下一個數據序號是501,於是B在發送給A的確認報文段中,會把ACK確認號設置為501

    4、數據偏移(Offset):4個字節。指出TCP報文段的數據起始處距離報文段的起始處有多遠,這個字段實際上是指出TCP報文段的首部長度。由於首部中還有長度不確定的選項字段,因此數據偏移字段是必要的。單位是32位字,也就是4字節,4位二進制最大表示15,所以數據偏移也就是TCP首部最大60字節

    5、保留(Reserved):6個字節。保留域

    6、TCP Flags:控制位,由八個標誌位組成,每個標誌位表示控制的功能,我們主要來介紹TCP Flags中常用的六個,

    • URG(緊急指針標誌):當URG=1時,表明緊急指針字段有效。它告訴系統此報文段中有緊急數據,應儘快傳送(相當於高優先級的數據),而不要按原來的排隊順序來傳送。例如,已經發送了很長的一個程序在主機上運行。但後來發現了一些問題,需要取消該程序的運行。因此用戶從鍵盤發出中斷命令。如果不使用緊急數據,那麼這兩個字符將存儲在接收TCP的緩存末尾。只有在所有的數據被處理完畢后這兩個字符才被交付接收方的應用進程。這樣做就浪費了許多時間

    • ACK(確認序號標誌):當ACK=1時確認號字段有效。當ACK=0時,確認號無效。TCP規定,在連接建立后所有的傳送的報文段都必須把ACK置1

    • PSH(push標誌):當兩個應用進程進行交互式的通信時,有時在一端的應用進程希望在鍵入一個命令后立即就能收到對方的響應。在這種情況下,TCP就可以使用推送操作。這時,發送方TCP把PSH置1,並立即創建一個報文段發送出去。接收方TCP收到PSH=1的報文段,就儘快地交付接收應用進程,而不再等到整個緩存都填滿了後向上交付

    • RST(重置連接標誌):TCP連接中出現嚴重差錯(如由於主機崩潰或其他原因),必須釋放連接,然後再重新建立運輸連接,可以用來拒絕一個非法的報文段或拒絕打開一個連接

    • SYN(同步序號,用於建立連接過程):在連接建立時用來同步序號。當SYN=1而ACK=0時,表明這是一個連接請求報文段。對方若同意建立連接,則應在相應的報文段中使用SYN=1和ACK=1。因此,SYN置為1就表示這是一個連接請求或連接接受保溫。

    • FIN(finish標誌,用於釋放連接):當FIN=1時,表明此報文段的發送方的數據已發送完畢,並要求釋放運輸連接

    7、窗口(Window):是TCP流量控制的一個手段。這裏說的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告訴對方本端的TCP接收緩衝區還能容納多少字節的數據,這樣就可以控制發送數據的速度

    8、檢驗和(Checksum):檢驗範圍包括首部和數據兩部分,由發送端填充,接收端對TCP報文段執行CRC算法以檢驗TCP報文段在傳輸過程中是否損壞。這也是TCP可靠傳輸的一個重要保障

    9、緊急指針(Urgent Pointer):緊急指針僅在URG=1時才有意義,它指出本報文段中的緊急數據的字節數(緊急數據結束后就是普通數據)。因此,緊急指針指出了緊急數據的末尾在報文段中的位置。當所有緊急數據都處理完時,TCP就告訴應用程序恢復到正常操作。值得注意的是,即使窗口為零時也可發送緊急數據。

    10、TCP可選項(TCP Options):長度可變,最長可達40字節。當沒有使用“選項”時,TCP的首部長度是20字節。

    三、TCP的三次握手

    所謂三次握手(Three-Way Handshake)即建立TCP連接,就是指建立一個TCP連接時,需要客戶端和服務端總共發送3個包以確認連接的建立。在socket編程中,這一過程由客戶端執行connect來觸發,整個流程如下圖所示:

    在TCP/IP協議中,TCP協議提供可靠的連接服務,採用三次握手建立一個連接。

    第一次握手: 建立連接時,客戶端發送SYN包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認,SYN:同步序列編號(Synchronize Sequence Numbers)。

    第二次握手: 服務器收到 SYN 包,必須確認客戶的 SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

    第三次握手: 客戶端收到服務器的SYN + ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。

    3.1 為什麼需要三次握手才能建立連接

    • 為了初始化Sequence Number 的初始值,實現可靠數據傳輸, TCP 協議的通信雙方, 都必須維護一個序列號, 以標識發送出去的數據包中, 哪些是已經被對方收到的。 三次握手的過程即是通信雙方相互告知序列號起始值, 並確認對方已經收到了序列號起始值的必經步驟
    • 如果只是兩次握手, 至多只有連接發起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認

    3.2 首次握手的隱患——SYN超時

    一、問題起因分析:
    1. 服務器收到客戶端的SYN,回復SYN和ACK的時候未收到ACK確認
    2. 服務器不斷重試直至超時,Linux默認等待63秒才斷開連接;(重複5次【不包括第一次】,從1秒開始,每次重試都翻倍:1+2+4+8+16+32=63秒)
    二、針對SYN Flood的防護措施:
    1. SYN隊列滿后,通過tcp_syncookies參數會發SYN cookie【源端口+目標端口+時間戳組成】
    2. 若為正常連接則Client會回發SYN Cookie,直接建立連接;

    3.3 保活機制:

    當我們建立連接后,Client出現故障怎麼辦?

    1. 向對方發送保活探測報文,如果未收到響應則繼續發送;
    2. 嘗試次數達到保活探測數仍未收到相應則中斷連接;

    四、TCP的四次揮手

    所謂四次揮手(Four-Way Wavehand)即終止TCP連接,就是指斷開一個TCP連接時,需要客戶端和服務端總共發送4個包以確認連接的斷開。在socket編程中,這一過程由客戶端或服務端任一方執行close來觸發,整個流程如下圖所示:

    由於TCP連接時全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務后,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味着這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉。

    • 第一次揮手: Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態
    • 第二次揮手: Server收到FIN后,發送一個ACK給Client,確認序號為收到序號+1(與SYN相同,一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態
    • 第三次揮手: Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態
    • 第四次揮手: Client收到FIN后,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態,完成四次揮手
    一、為什麼會有TIME_WAIT狀態

    客戶端連接在收到服務器的結束報文段之後,不會直接進入CLOSED狀態,而是轉移到TIME_WAIT狀態。在這個狀態,客戶端連接要等待一段長為2MSL,即兩倍的報文段最大生存時間,才能完全關閉,其原因主要有兩點:

    • 確保有足夠的時間放對方收到ACK包
    • 避免新舊連接混淆
    二、為什麼需要四次握手才能斷開連接

    因為TCP連接是全雙工的網絡協議,允許同時通信的雙方同時進行數據的收發,同樣也允許收發兩個方向的連接被獨立關閉,以避免client數據發送完畢,向server發送FIN關閉連接,而server還有發送到client的數據沒有發送完畢的情況。所以關閉TCP連接需要進行四次握手,每次關閉一個方向上的連接需要FIN和ACK兩次握手,發送發和接收方都需要FIN報文和ACK報文

    三、服務器出現大量CLOSE_WAIT狀態的原因

    是由於對方關閉socket連接,我方忙於讀或寫,沒有及時關閉連接

    當客戶端因為某種原因先於服務端發出了FIN信號,就會導致服務端被動關閉,若服務端不主動關閉socket發FIN給Client,此時服務端Socket會處於CLOSE_WAIT狀態(而不是LAST_ACK狀態)。通常來說,一個CLOSE_WAIT會維持至少2個小時的時間(系統默認超時時間的是7200秒,也就是2小時)。如果服務端程序因某個原因導致系統造成一堆CLOSE_WAIT消耗資源,那麼通常是等不到釋放那一刻,系統就已崩潰

    解決:
    1、檢查代碼,特別是釋放資源的代碼
    2、檢查配置,特別是處理請求的線程配置

    Linux的檢查代碼:netstat -n | awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'

    五、總結

    到這裏TCP的三次握手四次揮手就講完了,好久都沒有寫技術文章了,寫了一下,感覺還挺好的,上面是博主的認識,有寫的不好的地方,大家可以在評論區討論或者提問,博主看到了會第一時間回復大家,最近也準備開始面試了,先好好準備一下,希望今年可以找到心滿意足的工作,也希望今年面試的小夥伴們都有一個好的office,大家一起加油,我是牧小農,我喂自己帶鹽,大家加油。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    ※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • Jmeter(十二) – 從入門到精通 – JMeter邏輯控制器 – 終篇(詳解教程)

    Jmeter(十二) – 從入門到精通 – JMeter邏輯控制器 – 終篇(詳解教程)

    1.簡介

    Jmeter官網對邏輯控制器的解釋是:“Logic Controllers determine the order in which Samplers are processed.”。

    意思是說,邏輯控制器可以控制採樣器(samplers)的執行順序。由此可知,控制器需要和採樣器一起使用,否則控制器就沒有什麼意義了。放在控制器下面的所有的採樣器都會當做一個整體,執行時也會一起被執行。

    JMeter邏輯控制器可以對元件的執行邏輯進行控制,除僅一次控制器外,其他可以嵌套別的種類的邏輯控制器。

    2.邏輯控制器分類

    JMeter中的Logic Controller分為兩類:
    (1)控制測試計劃執行過程中節點的邏輯執行順序,如:Loop Controller、If Controller等;
    (2)對測試計劃中的腳本進行分組、方便JMeter統計執行結果以及進行腳本的運行時控制等,如:Throughput Controller、Transaction Controller。

    3.預覽邏輯控制器 

    首先我們來看一下JMeter的邏輯控制器,路徑:線程組(用戶)->添加->邏輯控制器(Logic Controller);我們可以清楚地看到JMeter5中共有17個邏輯控制器,如下圖所示:

    如果上圖您看得不是很清楚的話,宏哥總結了一個思維導圖,關於JMeter5的邏輯控制器類型,如下圖所示: 

     通過以上的了解,我們對邏輯控制器有了一個大致的了解和認識。下面宏哥就給小夥伴或則童鞋們分享講解一些通常在工作中會用到的邏輯控制器。 

    4.常用邏輯控制器詳解

      這一小節,宏哥就由上而下地詳細地講解一下常用的邏輯控制器。

    4.1Runtime Controller

    運行控制器用來控制其子元件的執行時長。市場單位是秒。

     1、我們先來看看這個Runtime Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 運行控制器,如下圖所示:

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空;

    Runtime:默認為1,去掉1則默認為0,此時不執行其節點下的元件。 與線程組中的調度器的持續時間 效果一致。不填 或 0,不會執行樣例

    4.1.1Runtime控制器控制其下取樣器執行2s

    1、創建測試計劃,設置 Runtime 控制器的運行時間 為 2,線程組設置默認不變,如下圖所示:

    Runtime 控制器設置

    線程組設置

    2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

    4.1.2使用線程組中的調度器控制樣例運行3s

    1、創建測試計劃,設置 Runtime 控制器的運行時間 為 2,線程組設置運行時間3,如下圖所示:

    線程組設置

    Runtime 控制器設置

    2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

    線程組設置3,Runtime控制器設置2,但是運行時間是2s。所以從上邊的運行時間得出結論:如果線程組中設置了持續時間,Runtime 控制器也設置了 運行時間,那麼會優先於線程組中的設置。

    4.2Simple Controller

    Simple Controller用來指定了一個執行單元,它不改變元件的執行順序。在它下邊還可以嵌套其他控制器。簡單控制器可以編輯只有名稱和註釋。就像他的名字一樣,簡單,可以理解為一個文件夾,就是分組用的,沒有其他特殊功能,但相比不添加簡單控制器,區別在於簡單控制器可以被模塊控制器所引用。其作用就是分組,比如QQ好友列表,可分為家人、同學、等。一般是請求較多,需要分組時採用。

     1、我們先來看看這個Simple Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 簡單控制器,如下圖所示:

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空。

    4.2.1簡單實例

    1、創建測試計劃,線程組設置循環10,如下圖所示:

    2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

    4.3Throughput Controller

    用來控制其下元件的執行次數,並無控制吞吐量的功能,想要控制吞吐量可以使用Constant Throughput Timer,後邊會講解到。吞吐量控制器有兩種模式:Total Executions:設置運行次數與Percent Executions:設置運行比例(1~100之間)。

    1、我們先來看看這個Throughput Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 吞吐量控制器,如下圖所示: 

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空;

    Total Executions:執行百分比(1-100);

    percent Executions:執行數量;

    Throughput:根據上邊選擇的方式填寫,百分比為0~100;

    Per User:線程數,當選Total Executions時,是線程數;當選percent Executions時,是線程數*循環次數。

    4.3.1不勾選Per User

    1、線程組中設置 線程數量 2,循環次數 10,吞吐量控制器 設置 Total Executions,吞吐量設置為 2,其下添加一個取樣器,如下圖所示:

    2、配置好以後,運行JMeter,然後查看結果樹(執行了2次),如下圖所示:

    3、現在將 吞吐量控制器 設置為百分比的控制方式,吞吐量設置為:50%,如下圖所示:

    4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了10次,計算方式:10=吞吐量50% * 循環次數10 * 線程數 2),如下圖所示:

    4.3.2勾選Per User

    1、線程組中設置 線程數量 2,循環次數 10,吞吐量控制器 設置 Total Executions,吞吐量設置為 2,其下添加一個取樣器,勾選Per User,如下圖所示:

    線程組設置

    吞吐量控制器

    2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹(總共執行了4次,其中吞吐量設置為2,執行2次,線程設置為2,執行2次,總共4次),函數 __threadNum 只是簡單地返回當前線程的編號,如下圖所示:

    3、現在將 吞吐量控制器 設置為百分比的控制方式,吞吐量設置為:50,如下圖所示:

    4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了10次,計算方式:10=吞吐量50% * 循環次數10 * 線程數 2),如下圖所示:

    綜上所述:

    勾選Per User:

    1.線程數*循環次數>=線程數*吞吐量時,Total Executions模式的執行次數=線程數*吞吐量。

    2.線程數*循環次數<線程數*吞吐量時,Total Executions模式的執行次數=當線程數*循環次數。

    不勾選Per User:

    1.線程數*循環次數<=吞吐量時,Total Executions模式的執行次數=線程數*循環次數。

    2.線程數*循環次數>吞吐量時,Total Executions模式的執行次數=吞吐量。

    l Percent Executions:設置運行比例(1~100之間),單位為%

    不管Per User是否勾選,按Percent Executions模式的執行次數都不受Per User影響,Percent Executions模式的執行次數=線程數*循環次數*吞吐量%。(循環次數=線程組循環次數*循環控制器循環次數)

    l Per User:勾選該項的話則按虛擬用戶數(線程數)來計算執行次數,不勾選則按所有虛擬用戶數來計算執行次數

    測試計劃

    序號 線程數 循環次數 模式 Throughput Per User 執行次數
    1 2 10 Percent 50 Y 10
    2 2 10 Percent 50 N 10
    3 2 10 Total 7 Y 14
    4 2 10 Total 7 N 7
    5 2 2 Total 7 Y 4
    6 2 2 Total 7 N 4

    下面說明一下這6個場景:
    (1)序號1和2場景,Per User 對總執行次數沒有影響。
    (2)序號3場景,Per User勾選,每個虛擬用戶(線程)執行7次,共執行14次。
    (3)序號4場景,Per User不勾選,則所有虛擬用戶執行7次。
    (4)序號5場景,Per User勾選,每個虛擬用戶(線程)執行7次,共執行14次,由於Thread Group計劃循環次數是4(2線程*2循環)次,所以最多只能執行4次。
    (5)序號6場景,Per User不勾選,所有虛擬用戶執行7次,由於Thread Group計劃循環次數是4(2線程*2循環)次,所以最多只能執行4次。

    4.4Module Controller

    模塊控制器可以快速的切換腳本,不用來回的新建,方便腳本調試。 

    可以理解為引用、調用的意思,執行內容為Module To Run種所選的內容,引用範圍為當前測試計劃內的測試片段、邏輯控制器<模塊控制器除外>
    被引用的邏輯控制器、測試片段可以為禁用狀態,被引用后仍然會被執行。
    可以將模塊控制器與包括控制器一起學習比較,模塊控制器是從內部文件中引用,引用上相對比較靈活,可以只引用部分測試片段或模塊內容,包括控制器是從外部文件引用,只能引用整個測試片段的內容。
    注意:被應用的模塊位置不可隨意變更,變更後會執行時出現提示引用失敗
    找到目標元素:快速查找與跳轉的作用,點擊後會立即跳轉到所選的邏輯控制器的內容詳情

    1、我們先來看看這個Module Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 >  模塊控制器,如下圖所示: 

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空;

    Forever:勾選上這一項表示一直循環下去。

    4.4.1實例

    1、創建測試計劃,添加兩個測試片段,並且在每個測試片段下添加一個取樣器,然後,添加線程組,再添加模塊控制器,最後添加查看結果樹,如下圖所示:

    2、配置模塊控制器,選擇第一個測試片段,如下圖所示:

    3、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第1個測試片段的取樣器),如下圖所示:

    4、配置模塊控制器,選擇第二個測試片段,如下圖所示: 

    5、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第2個測試片段的取樣器),如下圖所示:

    4.5Switch Controller

    Switch Controller:開關控制器,通過其下樣例順序數值或名稱 控制執行某一個樣例。

     1、我們先來看看這個if Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 如果 (if) 控制器,如下圖所示:

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空;

    Switch Value:指定請求的索引或者名稱,索引從0開始,如果沒有賦值,或者索引超過請求個數的話就執行第0個請求。可以是数字,也可以是字符,為字符時匹配取樣器名稱,如果匹配不上就會默認並找取樣器名稱為default的取樣器,如果沒有則不運行。

    4.5.1數值

    數值:表示將執行其下第 數值+1個取樣器,例如:填1,將執行第2個取樣器;填0或者不填,將執行第1個取樣器;數值超出其下取樣器數目時,執行第1個取樣器。

    1、創建一個測試計劃,設置線程組和Switch控制器,如下圖所示:

    線程組

    Switch控制器

    2、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第3<數值+1>個取樣器),如下圖所示:

    3、修改Switch控制器的數值為0或者不填,如下圖所示:

    4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第1<數值為0或者不填,執行第1個取樣器>個取樣器),如下圖所示:

    4.5.2字符

    1、創建一個測試計劃,設置線程組和Switch控制器(直接使用取樣器名字),如下圖所示:

    線程組

    Switch控制器

    2、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了使用名字的取樣器),如下圖所示:

    5.小結

    好了,今天關於邏輯控制器的上篇就講解到這裏,這一篇主要介紹了 Runtime Controller 、 Simple Controller 、Throughput Controller、Module Controller 和  Switch Controller。

     

    您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波  推薦  不要忘記哦!!!

    別忘了點 推薦 留下您來過的痕迹

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    ※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 基於領域驅動設計(DDD)超輕量級快速開發架構(二)動態linq查詢的實現方式

    基於領域驅動設計(DDD)超輕量級快速開發架構(二)動態linq查詢的實現方式

    -之動態查詢,查詢邏輯封裝復用

    基於領域驅動設計(DDD)超輕量級快速開發架構詳細介紹請看

    https://www.cnblogs.com/neozhu/p/13174234.html

    需求

    1. 配合EasyUI datagird filter實現多字段(任意字段)的篩選
    2. 根據業務需求篩選特定的狀態或條件,如:查看結案的訂單,最近30天的訂單,查看屬於我的訂單.等等,這些邏輯是固定也是可以被重用,但又不想每次寫相同的條件,那麼下面我會給我的解決方案.

    需求1隻是一個偷懶的實現方式,因為datagrid自帶這個功能,但又不想根據具體的需求來畫查詢條件,如果需求必須要再datagrid上面做一塊查詢條件的輸入那目前只能在前端自己手工添加,在組織後傳入後台,暫時不在這裏討論

    需求2可能不太好解釋,看完代碼就自然理解為什麼要這麼做了,這麼做的好處有哪些

    具體實現的方式

     

     默認情況下 datagrid 有幾列就可以對這幾列進行篩選,對於日期型的字段會採用between,選擇2個時間之間進行篩選,数字類型會提供大於小於等符號選擇,可以自行嘗試,其原理是datagrid 會根據datagrid 頭部輸入的值生成一個Json字符串發送後台請求數據

    JSON:格式

    filterRules: [
    {field:field,op:op,value:value},
    {field:field,op:op,value:value},
    ] 
    • 通常的做法是一個一個判斷加條件
      1 var filters = JsonConvert.DeserializeObject<IEnumerable<filterRule>>(filterRules); 
      2 foreach (var rule in filters)
      3         {
      4           if (rule.field == "Id" && !string.IsNullOrEmpty(rule.value) && rule.value.IsInt())
      5           {
      6             var val = Convert.ToInt32(rule.value);
      7             switch (rule.op)
      8             {
      9               case "equal":
     10                 this.And(x => x.Id == val);
     11                 break;
     12               case "notequal":
     13                 this.And(x => x.Id != val);
     14                 break;
     15               case "less":
     16                 this.And(x => x.Id < val);
     17                 break;
     18               case "lessorequal":
     19                 this.And(x => x.Id <= val);
     20                 break;
     21               case "greater":
     22                 this.And(x => x.Id > val);
     23                 break;
     24               case "greaterorequal":
     25                 this.And(x => x.Id >= val);
     26                 break;
     27               default:
     28                 this.And(x => x.Id == val);
     29                 break;
     30             }
     31           }
     32           if (rule.field == "Name" && !string.IsNullOrEmpty(rule.value))
     33           {
     34             this.And(x => x.Name.Contains(rule.value));
     35           }
     36           if (rule.field == "Code" && !string.IsNullOrEmpty(rule.value))
     37           {
     38             this.And(x => x.Code.Contains(rule.value));
     39           }
     40 
     41           if (rule.field == "Address" && !string.IsNullOrEmpty(rule.value))
     42           {
     43             this.And(x => x.Address.Contains(rule.value));
     44           }
     45 
     46           if (rule.field == "Contect" && !string.IsNullOrEmpty(rule.value))
     47           {
     48             this.And(x => x.Contect.Contains(rule.value));
     49           }
     50 
     51           if (rule.field == "PhoneNumber" && !string.IsNullOrEmpty(rule.value))
     52           {
     53             this.And(x => x.PhoneNumber.Contains(rule.value));
     54           }
     55 
     56           if (rule.field == "RegisterDate" && !string.IsNullOrEmpty(rule.value))
     57           {
     58             if (rule.op == "between")
     59             {
     60               var datearray = rule.value.Split(new char[] { '-' });
     61               var start = Convert.ToDateTime(datearray[0]);
     62               var end = Convert.ToDateTime(datearray[1]);
     63 
     64               this.And(x => SqlFunctions.DateDiff("d", start, x.RegisterDate) >= 0);
     65               this.And(x => SqlFunctions.DateDiff("d", end, x.RegisterDate) <= 0);
     66             }
     67           }
     68           if (rule.field == "CreatedDate" && !string.IsNullOrEmpty(rule.value))
     69           {
     70             if (rule.op == "between")
     71             {
     72               var datearray = rule.value.Split(new char[] { '-' });
     73               var start = Convert.ToDateTime(datearray[0]);
     74               var end = Convert.ToDateTime(datearray[1]);
     75 
     76               this.And(x => SqlFunctions.DateDiff("d", start, x.CreatedDate) >= 0);
     77               this.And(x => SqlFunctions.DateDiff("d", end, x.CreatedDate) <= 0);
     78             }
     79           }
     80 
     81 
     82           if (rule.field == "CreatedBy" && !string.IsNullOrEmpty(rule.value))
     83           {
     84             this.And(x => x.CreatedBy.Contains(rule.value));
     85           }
     86 
     87          if (rule.field == "LastModifiedDate" && !string.IsNullOrEmpty(rule.value))
     88           {
     89             if (rule.op == "between")
     90             {
     91               var datearray = rule.value.Split(new char[] { '-' });
     92               var start = Convert.ToDateTime(datearray[0]);
     93               var end = Convert.ToDateTime(datearray[1]);
     94 
     95               this.And(x => SqlFunctions.DateDiff("d", start, x.LastModifiedDate) >= 0);
     96               this.And(x => SqlFunctions.DateDiff("d", end, x.LastModifiedDate) <= 0);
     97             }
     98           }
     99 
    100           if (rule.field == "LastModifiedBy" && !string.IsNullOrEmpty(rule.value))
    101           {
    102             this.And(x => x.LastModifiedBy.Contains(rule.value));
    103           }
    104 
    105         }

    View Code

    • 新的做法是動態根據field,op,value生成一個linq 表達式,不用再做繁瑣的判斷,這塊代碼也可以被其它項目使用,非常好用
    namespace SmartAdmin
    {
     
      public static class PredicateBuilder
      {
    
        public static Expression<Func<T, bool>> FromFilter<T>(string filtergroup) {
          Expression<Func<T, bool>> any = x => true;
          if (!string.IsNullOrEmpty(filtergroup))
          {
            var filters = JsonSerializer.Deserialize<filter[]>(filtergroup);
    
              foreach (var filter in filters)
              {
                if (Enum.TryParse(filter.op, out OperationExpression op) && !string.IsNullOrEmpty(filter.value))
                {
                  var expression = GetCriteriaWhere<T>(filter.field, op, filter.value);
                  any = any.And(expression);
                }
              }
          }
    
          return any;
        }
    
        #region -- Public methods --
        public static Expression<Func<T, bool>> GetCriteriaWhere<T>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
        {
          var name = GetOperand<T>(e);
          return GetCriteriaWhere<T>(name, selectedOperator, fieldValue);
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
        {
          var name = GetOperand<T>(e);
          return GetCriteriaWhere<T, T2>(name, selectedOperator, fieldValue);
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T>(string fieldName, OperationExpression selectedOperator, object fieldValue)
        {
          var props = TypeDescriptor.GetProperties(typeof(T));
          var prop = GetProperty(props, fieldName, true);
          var parameter = Expression.Parameter(typeof(T));
          var expressionParameter = GetMemberExpression<T>(parameter, fieldName);
          if (prop != null && fieldValue != null)
          {
           
            BinaryExpression body = null;
            switch (selectedOperator)
            {
              case OperationExpression.equal:
                body = Expression.Equal(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType)?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.notequal:
                body = Expression.NotEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.less:
                body = Expression.LessThan(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.lessorequal:
                body = Expression.LessThanOrEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.greater:
                body = Expression.GreaterThan(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.greaterorequal:
                body = Expression.GreaterThanOrEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.contains:
                var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                var bodyLike = Expression.Call(expressionParameter, contains, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodyLike, parameter);
              case OperationExpression.endwith:
                var endswith = typeof(string).GetMethod("EndsWith",new[] { typeof(string) });
                var bodyendwith = Expression.Call(expressionParameter, endswith, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodyendwith, parameter);
              case OperationExpression.beginwith:
                var startswith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
                var bodystartswith = Expression.Call(expressionParameter, startswith, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodystartswith, parameter);
              case OperationExpression.includes:
                return Includes<T>(fieldValue, parameter, expressionParameter, prop.PropertyType);
              case OperationExpression.between:
                return Between<T>(fieldValue, parameter, expressionParameter, prop.PropertyType);
              default:
                throw new Exception("Not implement Operation");
            }
          }
          else
          {
            Expression<Func<T, bool>> filter = x => true;
            return filter;
          }
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(string fieldName, OperationExpression selectedOperator, object fieldValue)
        {
    
    
          var props = TypeDescriptor.GetProperties(typeof(T));
          var prop = GetProperty(props, fieldName, true);
    
          var parameter = Expression.Parameter(typeof(T));
          var expressionParameter = GetMemberExpression<T>(parameter, fieldName);
    
          if (prop != null && fieldValue != null)
          {
            switch (selectedOperator)
            {
              case OperationExpression.any:
                return Any<T, T2>(fieldValue, parameter, expressionParameter);
    
              default:
                throw new Exception("Not implement Operation");
            }
          }
          else
          {
            Expression<Func<T, bool>> filter = x => true;
            return filter;
          }
        }
    
    
    
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> or)
        {
          if (expr == null)
          {
            return or;
          }
    
          return Expression.Lambda<Func<T, bool>>(Expression.OrElse(new SwapVisitor(expr.Parameters[0], or.Parameters[0]).Visit(expr.Body), or.Body), or.Parameters);
        }
    
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> and)
        {
          if (expr == null)
          {
            return and;
          }
    
          return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(new SwapVisitor(expr.Parameters[0], and.Parameters[0]).Visit(expr.Body), and.Body), and.Parameters);
        }
    
        #endregion
        #region -- Private methods --
    
        private static string GetOperand<T>(Expression<Func<T, object>> exp)
        {
          if (!( exp.Body is MemberExpression body ))
          {
            var ubody = (UnaryExpression)exp.Body;
            body = ubody.Operand as MemberExpression;
          }
    
          var operand = body.ToString();
    
          return operand.Substring(2);
    
        }
    
        private static MemberExpression GetMemberExpression<T>(ParameterExpression parameter, string propName)
        {
          if (string.IsNullOrEmpty(propName))
          {
            return null;
          }
    
          var propertiesName = propName.Split('.');
          if (propertiesName.Count() == 2)
          {
            return Expression.Property(Expression.Property(parameter, propertiesName[0]), propertiesName[1]);
          }
    
          return Expression.Property(parameter, propName);
        }
    
        private static Expression<Func<T, bool>> Includes<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression ,Type type)
        {
          var safetype= Nullable.GetUnderlyingType(type) ?? type;
    
          switch (safetype.Name.ToLower())
          {
            case  "string":
              var strlist = (IEnumerable<string>)fieldValue;
              if (strlist == null || strlist.Count() == 0)
              {
                return x => true;
              }
              var strmethod = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
              var strcallexp = Expression.Call(Expression.Constant(strlist.ToList()), strmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(strcallexp, parameterExpression);
            case "int32":
              var intlist = (IEnumerable<int>)fieldValue;
              if (intlist == null || intlist.Count() == 0)
              {
                return x => true;
              }
              var intmethod = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
              var intcallexp = Expression.Call(Expression.Constant(intlist.ToList()), intmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(intcallexp, parameterExpression);
            case "float":
              var floatlist = (IEnumerable<float>)fieldValue;
              if (floatlist == null || floatlist.Count() == 0)
              {
                return x => true;
              }
              var floatmethod = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
              var floatcallexp = Expression.Call(Expression.Constant(floatlist.ToList()), floatmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(floatcallexp, parameterExpression);
            default:
              return x => true;
          }
          
        }
        private static Expression<Func<T, bool>> Between<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression, Type type)
        {
          
          var safetype = Nullable.GetUnderlyingType(type) ?? type;
          switch (safetype.Name.ToLower())
          {
            case "datetime":
              var datearray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var start = Convert.ToDateTime(datearray[0] + " 00:00:00", CultureInfo.CurrentCulture);
              var end = Convert.ToDateTime(datearray[1] + " 23:59:59", CultureInfo.CurrentCulture);
              var greater = Expression.GreaterThan(memberExpression, Expression.Constant(start, type));
              var less = Expression.LessThan(memberExpression, Expression.Constant(end, type));
              return Expression.Lambda<Func<T, bool>>(greater, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(less, parameterExpression));
            case "int":
            case "int32":
              var intarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var min = Convert.ToInt32(intarray[0] , CultureInfo.CurrentCulture);
              var max = Convert.ToInt32(intarray[1], CultureInfo.CurrentCulture);
              var maxthen = Expression.GreaterThan(memberExpression, Expression.Constant(min, type));
              var minthen = Expression.LessThan(memberExpression, Expression.Constant(max, type));
              return Expression.Lambda<Func<T, bool>>(maxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(minthen, parameterExpression));
            case "decimal":
              var decarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var dmin = Convert.ToDecimal(decarray[0], CultureInfo.CurrentCulture);
              var dmax = Convert.ToDecimal(decarray[1], CultureInfo.CurrentCulture);
              var dmaxthen = Expression.GreaterThan(memberExpression, Expression.Constant(dmin, type));
              var dminthen = Expression.LessThan(memberExpression, Expression.Constant(dmax, type));
              return Expression.Lambda<Func<T, bool>>(dmaxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(dminthen, parameterExpression));
            case "float":
              var farray = ((string)fieldValue).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var fmin = Convert.ToDecimal(farray[0], CultureInfo.CurrentCulture);
              var fmax = Convert.ToDecimal(farray[1], CultureInfo.CurrentCulture);
              var fmaxthen = Expression.GreaterThan(memberExpression, Expression.Constant(fmin, type));
              var fminthen = Expression.LessThan(memberExpression, Expression.Constant(fmax, type));
              return Expression.Lambda<Func<T, bool>>(fmaxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(fminthen, parameterExpression));
            case "string":
              var strarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var smin = strarray[0];
              var smax = strarray[1];
            
              var strmethod = typeof(string).GetMethod("Contains");
              var mm = Expression.Call(memberExpression, strmethod, Expression.Constant(smin, type));
              var nn = Expression.Call(memberExpression, strmethod, Expression.Constant(smax, type));
    
    
              return Expression.Lambda<Func<T, bool>>(mm, parameterExpression)
                .Or(Expression.Lambda<Func<T, bool>>(nn, parameterExpression));
            default:
              return x => true;
          }
    
        }
    
    
    
        private static Expression<Func<T, bool>> Any<T, T2>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression)
        {
          var lambda = (Expression<Func<T2, bool>>)fieldValue;
          var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
          .First(m => m.Name == "Any" && m.GetParameters().Count() == 2).MakeGenericMethod(typeof(T2));
    
          var body = Expression.Call(anyMethod, memberExpression, lambda);
    
          return Expression.Lambda<Func<T, bool>>(body, parameterExpression);
        }
    
        private static PropertyDescriptor GetProperty(PropertyDescriptorCollection props, string fieldName, bool ignoreCase)
        {
          if (!fieldName.Contains('.'))
          {
            return props.Find(fieldName, ignoreCase);
          }
    
          var fieldNameProperty = fieldName.Split('.');
          return props.Find(fieldNameProperty[0], ignoreCase).GetChildProperties().Find(fieldNameProperty[1], ignoreCase);
    
        }
        #endregion
      }
    
      internal class SwapVisitor : ExpressionVisitor
      {
        private readonly Expression from, to;
        public SwapVisitor(Expression from, Expression to)
        {
          this.from = from;
          this.to = to;
        }
        public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
      }
      public enum OperationExpression
      {
        equal,
        notequal,
        less,
        lessorequal,
        greater,
        greaterorequal,
        contains,
        beginwith,
        endwith,
        includes,
        between,
        any
      }
    }

    View Code

     1 public async Task<JsonResult> GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
     2     {
     3       try
     4       {
     5         var filters = PredicateBuilder.FromFilter<Company>(filterRules);
     6         var total = await this.companyService
     7                              .Query(filters)
     8                              .AsNoTracking()
     9                              .CountAsync()
    10                               ;
    11         var pagerows = (await this.companyService
    12                              .Query(filters)
    13                               .AsNoTracking()
    14                            .OrderBy(n => n.OrderBy(sort, order))
    15                            .Skip(page - 1).Take(rows)
    16                            .SelectAsync())
    17                            .Select(n => new
    18                            {
    19                              Id = n.Id,
    20                              Name = n.Name,
    21                              Code = n.Code,
    22                              Address = n.Address,
    23                              Contect = n.Contect,
    24                              PhoneNumber = n.PhoneNumber,
    25                              RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
    26                            }).ToList();
    27         var pagelist = new { total = total, rows = pagerows };
    28         return Json(pagelist);
    29       }
    30       catch(Exception e) {
    31         throw e;
    32         }
    33 
    34     }

    配合使用的代碼

    • 對於固定查詢邏輯的封裝和復用,當然除了復用還可以明顯的提高代碼的可讀性.
    public class OrderSalesQuery : QueryObject<Order>
    {
        public decimal Amount { get; set; }
        public string Country { get; set; }
        public DateTime FromDate { get; set; }
        public DateTime ToDate { get; set; }
    
        public override Expression<Func<Order, bool>> Query()
        {
            return (x => 
                x.OrderDetails.Sum(y => y.UnitPrice) > Amount &&
                x.OrderDate >= FromDate &&
                x.OrderDate <= ToDate &&
                x.ShipCountry == Country);
        }
    }

    查看訂單的銷售情況,條件 金額,國家,日期

    var orderRepository = new Repository<Order>(this);
        
    var orders = orderRepository
        .Query(new OrderSalesQuery(){ 
            Amount = 100, 
            Country = "USA",
            FromDate = DateTime.Parse("01/01/1996"), 
            ToDate = DateTime.Parse("12/31/1996" )
        })
        .Select();

    調用查詢方法

    public class CustomerLogisticsQuery : QueryObject<Customer>
    {
        public CustomerLogisticsQuery FromCountry(string country)
        {
            Add(x => x.Country == country);
            return this;
        }
    
        public CustomerLogisticsQuery LivesInCity(string city)
        {   
            Add(x => x.City == city);
            return this;
        }
    }

    客戶查詢 根據國家和城市查詢

    public class CustomerSalesQuery : QueryObject<Customer>
    {
        public CustomerSalesQuery WithPurchasesMoreThan(decimal amount)
        {
            Add(x => x.Orders
                .SelectMany(y => y.OrderDetails)
                .Sum(z => z.UnitPrice * z.Quantity) > amount);
    
            return this;
        }
    
        public CustomerSalesQuery WithQuantitiesMoreThan(decimal quantity)
        {
            Add(x => x.Orders
                .SelectMany(y => y.OrderDetails)
                .Sum(z => z.Quantity) > quantity);
    
            return this;
        }
    }

    客戶的銷售情況,金額和數量

    var customerRepository = new Repository<Customer>(this);
    
    var query1 = new CustomerLogisticsQuery()
        .LivesInCity("London");
    
    var query2 = new CustomerSalesQuery()
        .WithPurchasesMoreThan(100)
        .WithQuantitiesMoreThan(10);
    
    customerRepository
        .Query(query1.And(query2))
        .Select()
        .Dump();

    復用上面的定義的查詢方法

    以上這些都是改項目提供的方法,非常的好用

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    ※網頁設計最專業,超強功能平台可客製化

  • 三文搞懂學會Docker容器技術(下)

    三文搞懂學會Docker容器技術(下)

    接着上面一篇:三文搞懂學會Docker容器技術(上)

                             三文搞懂學會Docker容器技術(中)

    7,Docker容器目錄掛載

      7.1 簡介

    容器目錄掛載:

    我們可以在創建容器的時候,將宿主機的目錄與容器內的目錄進行映射,這樣我們就可以實現宿主機和容器目錄的雙向數據自動同步;

      7.2 作用

    前面學過cp命令來實現數據傳遞,這種方式比較麻煩;

    我們通過容器目錄掛載,能夠輕鬆實現代碼上傳,配置修改,日誌同步等需求;

      7.3 實現

    語法:

    docker run -it -v  /宿主機目錄:/容器目錄 鏡像名

    多目錄掛載

    docker run -it -v /宿主機目錄:/容器目錄 -v /宿主機目錄2:/容器目錄2  鏡像名

    注意:

    如果你同步的是多級目錄,可能會出現權限不足的提示;

    這是因為Centos7中的安全模塊selinux把權限禁掉了,我們需要添加  –privileged=true 來解決掛載的目錄沒有權限的問題;

      7.4 掛載目錄只讀

    docker run -it -v  /宿主機目錄:/容器目錄:ro 鏡像名

     

    8,Docker遷移與備份

      8.1 概述

    我們開發的時候,經常自定義鏡像,然後commit提交成鏡像到本地倉庫,但是我們發布到客戶服務器的時候,可以用前面講得搞到hub官方,或者阿里雲,但是有些機密性的項目,是禁止公網存儲的,所以我們只能通過docker鏡像備份和遷移實現;

      8.2 實現

    備份鏡像:

    docker save -o 備份鏡像的名稱  源鏡像名稱:tag版本

     docker save -o mytomcat7.1.tar java1234/tomcat7:7.1

     

    恢復鏡像:

    docker load -i 鏡像文件

    docker load -i mytomcat7.1.tar

     

    9,DockerFile詳解

      9.1 DockerFile簡介

    Dockerfile是由一系列命令和參數構成的腳本,這些命令應用於操作系統(centos或者Ubuntu)基礎鏡像並最終創建的一個新鏡像;

    我們前面講過的用手工的方式,修改配置文件,或者添加,刪除文件目錄的方式,來構建一種新鏡像;這種手工方式麻煩,容易出錯,而且不能復用;

    我們這裏講Dockerfile,用腳本方式來構建自動化,可復用的,高效率的創建鏡像方式,是企業級開發的首選方式;

     

    再軟件系統開發生命周期中,採用Dockerfile來構建鏡像;

    1、對於開發人員:可以為開發團隊提供一個完全一致的開發環境;

    2、對於測試人員:可以直接拿開發時所構建的鏡像或者通過Dockerfile文件構建一個新的鏡像開始工作;

    3、對於運維人員:在部署時,可以實現應用的無縫移植。

      9.2 DockerFile常用指令

    FROM image_name:tag 定義了使用哪個基礎鏡像啟動構建流程
    MAINTAINER user_info 聲明鏡像維護者信息
    LABEL key value 鏡像描述元信息(可以寫多條)
    ENV key value 設置環境變量(可以寫多條)
    RUN command 構建鏡像時需要運行的命令(可以寫多條)
    WORKDIR path_dir 設置終端默認登錄進來的工作目錄
    EXPOSE port 當前容器對外暴露出的端口
    ADD source_dir/file dest_dir/file 將宿主機的文件複製到容器內,如果是一個壓縮文件,將會在複製后自動解壓
    COPY source_dir/file dest_dir/file 和ADD相似,但是如果有壓縮文件是不能解壓
    VOLUME 創建一個可以從本地主機或其他容器掛載的掛載點,一般用來存放數據庫和需要保持的數據等
    CMD 指定容器啟動時要運行的命令,假如有多個CMD,最後一個生效
    ENTRYPOINT 指定容器啟動時要運行的命令
    ONBUILD 當構建一個被繼承的Dockerfile時運行的命令,父鏡像在被子鏡像繼承後父鏡像的onbuild被觸發。可以把ONBUID理解為一個觸發器。

     

    10,Docker私有倉庫

      10.1 簡介

    Docker私有倉庫主要是企業內部用來存放鏡像的倉庫,相對官方倉庫以及阿里雲倉庫,具有更高的保密安全級別;

      10.2 私有倉庫搭建

    第一步:拉取私有倉庫鏡像 (私有倉庫程序本身就是一個鏡像)

    docker pull registry

    第二步:啟動私有倉庫容器

    docker run -di –name=myRegistry -p 5000:5000 registry

    第三步:測試

    http://192.168.1.112:5000/v2/_catalog

    看到這個 說明啟動OK。因為倉庫里還沒有鏡像,所以就是空的;

    第四步:etc/docker 修改daemon.json,讓docker信任私有倉庫地址

    “insecure-registries”: [“192.168.1.112:5000”]

     

    第五步:修改配置后重啟docker;

     systemctl restart docker

      10.3 私有倉庫測試

    第一步:標記此鏡像為私有倉庫的鏡像

    docker tag tomcat:7 192.168.1.112:5000/mytomcat7

    第二步:上傳鏡像到私有倉庫

    docker push 192.168.1.112:5000/mytomcat7

    此時私有倉庫里已經有了這個鏡像;

    第三步:刪除192.168.1.112:5000/mytomcat7本地倉庫鏡像

    docker rmi -f 192.168.1.112:5000/mytomcat7

    第四步:從私有倉庫拉取192.168.1.112:5000/mytomcat7鏡像,並運行;

    docker run -it -p 8080:8080 192.168.1.112:5000/mytomcat7

    第五步:瀏覽器運行 http://192.168.1.112:8080測試

     

    ——————————————————————————————————————————

    作者: java1234_小鋒

    出處:https://www.cnblogs.com/java688/p/13174647.html

    版權:本站使用「CC BY 4.0」創作共享協議,轉載請在文章明顯位置註明作者及出處。

    ——————————————————————————————————————————

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    ※新北清潔公司,居家、辦公、裝潢細清專業服務

    ※教你寫出一流的銷售文案?

  • lin-cms-dotnetcore功能模塊的設計

    lin-cms-dotnetcore功能模塊的設計

    lin-cms-dotnetcore功能模塊的設計

    先來回答以下問題。可拉到最下面查看預覽圖。

    1.什麼是cms?

    Content Management System,內容管理系統。

    2.dotnetcore是什麼?

    .NET Core,是由Microsoft開發,目前在.NET Foundation(一個非營利的開源組織)下進行管理,採用寬鬆的MIT協議,可構建各種軟件,包括Web應用程序、移動應用程序、桌面應用程序、雲服務、微服務、API、遊戲和物聯網應用程序。

    3.lin-cms 是什麼?

    Lin-CMS 是林間有風團隊經過大量項目實踐所提煉出的一套內容管理系統框架。Lin-CMS 可以有效的幫助開發者提高 CMS 的開發效率,

    Lin的定位在於實現一套 CMS的解決方案,管理系統的基礎框架,提供了不同的後端,不同的前端實現,後端也支持不同的數據庫,是一套前後端完整的解決方案

    目前官方團隊維護 lin-cms-vue,lin-cms-spring-boot,lin-cms-koa,lin-cms-flask 社區維護了 lin-cms-tp5,lin-cms-react,lin-cms-dotnetcore,即已支持vue,react二種前端框架,java,nodejs,python,php,c#等五種後端語言。

    lin-cms-vue(官方)

    • https://github.com/TaleLin/lin-cms-vue
    • Vue+ElementUI構建的CMS開發框架,
    • 林間有風團隊經過大量項目實踐所提煉出的一套內容管理系統框架
    • 內置了 CMS 中最為常見的需求:用戶管理、權限管理、日誌系統等

    lin-cms-koa(官方)

    • python
    • https://github.com/TaleLin/lin-cms-koa
    • 使用Node.JS KOA構建的CMS開發框架

    lin-cms-flask(官方)

    • node.js
    • https://github.com/TaleLin/lin-cms-flask
    • A simple and practical CMS implememted by flask

    lin-cms-spring-boot(官方)

    • java
    • https://github.com/TaleLin/lin-cms-spring-boot
    • 基於SpringBoot的CMS/DMS/管理系統開發框架

    lin-cms-tp5(社區)

    • php 被官方fork。
    • https://github.com/TaleLin/lin-cms-tp5
    • A simple and practical CMS implememted by ThinkPHP 5.1

    lin-cms-react(社區)

    • https://github.com/Bongkai/lin-cms-react
    • React+Antd構建的CMS開發框架

    lin-cms-dotnetcore(社區)

    • C#
    • A simple and practical CMS implemented by .NET Core 3.1 一個簡單實用、基於.NET Core
    • https://github.com/luoyunchong/lin-cms-dotnetcore
    • .NET Core 3.1實現的CMS;前後端分離、Docker部署、OAtuh2授權登錄、自動化部署DevOps、GitHub Action同步至Gitee

    4.lin-cms-dotnetcore有哪些特點?

    基於.NET Core3.1實現的LIN-CMS-VUE後端API,並增加了博客模塊。目前實現簡約的權限管理系統、基礎字典項管理、隨筆專欄,評論點贊、關注用戶、技術頻道(標籤分類)、消息通知,標籤等仿掘金模塊。

    功能模塊的設計

    基礎權限模塊

    • 用戶信息:郵件、用戶名(唯一)、昵稱、頭像、分組、是否激活、手機號、是否是Admin、個性簽名
      • [x] 註冊/登錄
      • [x] 上傳頭像
      • [x] 修改個人密碼
      • [x] 用戶基本信息修改
      • [x] 用戶增刪改,配置分組
    • 綁定第三方賬號
      • [x] GitHub登錄
      • [x] QQ 登錄
      • [ ] Gitee登錄
    • 分組信息:是否靜態分組(無法刪除,無法修改分組編碼)、名稱可以修改
      • [x] 分組增刪改
      • [x] 分組配置權限
    • 文件管理
      • [x] 本地文件上傳
      • [x] 七牛雲存儲
      • [x] 文件去重,秒傳
    • 系統日誌:請求方法、路徑、http返回碼、時間、用戶昵稱、用戶id、訪問哪個權限、 日誌信息
      • [x] 記錄系統請求的日誌
      • [ ] 異常日誌
    • 設置管理:name(鍵),value(值),provider_name(提供名),provider_key(提供者值)
      • [x] 設置新增修改刪除
      • [x] 所有設置

    比如存某用戶選擇的是markdown還是富文本。

    name="Article.Editor",
    value="markdown" 或 "富文本",
    provider_name為"User",
    provider_key為用戶Id
    

    或存儲七牛雲的某一個配置

    name="Qiniu.AK",
    value="asfadsfadf23rft66S4XM2GIK7FxfqefauYkcAyNGDAc" ,
    provider_name為"Qiniu"或自己定義的字符串
    provider_key為空
    

    cms 管理員維護模塊

    • [x] 標籤管理:名稱、圖片,是否啟用/禁用,排序、文章數量、用戶關注數量。
      • [x] 標籤增刪改
      • [x] 標籤列表,禁用
      • [x] 校正文章數量
    • [x] 技術頻道:封面圖、名稱、是否啟用/禁用、排序、編碼、備註描述、下屬標籤.一個技術頻道對應多個標籤
      • [x] 技術頻道增刪改
      • [x] 列表、禁用
    • [x] 隨筆管理:
      • [x] 審核隨筆/拉黑
      • [x] 管理員刪除隨筆
    • [x] 評論管理
      • [x] 後台審核通過/拉黑
      • [x] 管理員刪除評論
    • [x] 字典類別管理:編碼,名稱,排序
      • [x] 增刪改查
    • [x] 字典管理::編碼,名稱,排序,類別:如隨筆類型(原創、轉載、翻譯)
      • [x] 增刪改查

    cms 用戶端模塊

    • 技術頻道
      • [x] 首頁展示技術頻道
      • [x] 選擇技術頻道后,可再根據標籤查詢文章
    • 分類專欄管理:發布隨筆時可選擇單個分類。
      • [x] 分類增刪改(隨筆數量、圖片、名稱、排序)
      • [x] 分類列表,僅查看、編輯自己創建的分類專欄
    • 標籤:統計每個標籤下多少個文章、多少人關注
      • [x] 標籤列表
      • [x] 無限加載
      • [x] 最新/最熱 根據標籤名稱模糊查詢
      • [x] 已關注的標籤
      • [x] 熱門標籤
    • 隨筆
      • [x] 支持markdown,增刪改(僅自己的隨筆),修正分類專欄中的隨筆數量
      • [x] 支持富文本編輯隨筆
      • [x] 列表無限加載,按標籤查詢隨筆
      • [x] 點贊隨筆
      • 隨筆詳情頁
        • [x] 支持目錄導航(滾動時,固定至頂部位置),展示字數統計、預計閱讀時長;
        • [x] 作者介紹:頭像,昵稱,簽名,隨筆數;
        • [x] 展示文章類型:原創、轉載、翻譯
        • [ ] 相關文章
        • [ ] 推薦文章
    • 評論
      • [ ] 用戶關閉評論時,無法對隨筆進行評論
      • [ ] 評論隨筆(內容支持超鏈接、emoji)
      • [x] 刪除自己的評論
      • [x] 點贊評論
      • [x] 回複評論
    • 關注
      • [x] 關注/取消關注用戶
      • [x] 關注/取消關註標簽
      • [x] 我關注的用戶發隨筆
    • 個人主頁
      • 隨筆
        • [x] 用戶專欄分類展示
        • [x] 最新發布的隨筆
      • 關注
        • [x] 關注的用戶
        • [x] 粉絲
        • [x] 關注的標籤
    • 設置
      • 個人主頁設置
        • [x] 個人資料更新
      • 安全設置
        • [x] 密碼修改:快速登錄的賬號,初次設置時可留空
      • 博客設置
        • [x] 編輯器設置,(可切換markdown/富文本)
        • [x] 代碼風格配置(tango、native、monokai、github、solarized-light、vs)
    • 消息
      • [x] 評論:點贊評論、評論隨筆、回複評論
      • [x] 喜歡和贊:點贊隨筆、點贊評論
      • [x] 關注,誰誰關注了你

    腦圖分享

    http://naotu.baidu.com/file/6532431a2e1f0c37c93c5ffd1dd5b49c?token=87690a9bc64fbae1

    分組

    分為三種

    id  name        info
    1	Admin	    系統管理員
    2	CmsAdmin	內容管理員
    3	User	    普通用戶
    

    審計日誌

    大多數表存在如下8個字段,用於記錄行的變化狀態,is_deleted為軟刪除,執行刪除操作時,將其狀態置為true,默認實體類繼承 FullAduitEntity 即可擁有以下8個字段。該設計參考ABP中的實現。FullAduitEntity為泛型,默認id為long類型,FullAduitEntity<Guid>,即可改變主鍵類型,默認LinUser表主鍵long,保持create_user_id,delete_user_id,update_user_id都與LinUser的主鍵相同

    
    id	                bigint
    create_user_id  	bigint
    create_time	        datetime
    is_deleted	        bit
    delete_user_id  	bigint
    delete_time	        datetime
    update_user_id	    bigint
    update_time	        datetime
    
    
    

    相關技術

    • 數據庫相關:ORM:FreeSql+DataBase:MySQL5.6
    • ASP.NET Core3.1+WebAPI+RESTful
    • 簡化對象映射:AutoMapper
    • 身份認證框架:IdentityServer4
    • Json Web令牌:JWT
    • 文檔API:Swagger(Swashbuckle.AspNetCore)
    • 序列化:Newtonsoft.Json
    • 測試框架:Xunit
    • 日誌 Serilog
    • 依賴注入服務AutoFac
    • 通用擴展方法 Z.ExtensionMethods
    • 雲存儲:七牛雲 MQiniu.Core
    • 分佈式事務、EventBus:DotNeteCore.CAP
    • GitHub第三方授權登錄AspNet.Security.OAuth.GitHub
    • QQ第三方授權登錄AspNet.Security.OAuth.QQ
    • Docker
    • Azure DevOps
    • 健康檢查AspNetCore.HealthChecks.UI.Client
    • GitHub Action同步至Gitee

    分層結構(Layers)

    • framework
      • IGeekfan.CAP.MySql:為CAP實現了配合FreeSql的事務一致性擴展
    • identityserver4
      • LinCms.IdentityServer4:使用id4授權登錄
    • src
      • LinCms.Web:接口API(ASP.NET Core)
      • LinCms.Application:應用服務
      • LinCms.Application.Contracts:DTO,數據傳輸對象,應用服務接口
      • LinCms.Infrastructure:基礎設施,數據庫持久性的操作
      • LinCms.Core:該應用的核心,實體類,通用操作類,AOP擴展,分頁對象,基礎依賴對象接口,時間擴展方法,當前用戶信息,異常類,值對象
      • LinCms.Plugins 使用單項目實現某個業務的擴展,不需要主要項目結構,可暫時忽略。
    • test
      • LinCms.Test:對倉儲,應用服務或工具類進行測試

    功能特性

    • [x] Azure Devops CI/CD構建
    • [x] GitHub Action實現 GitHub Gitee代碼同步
    • [x] .Net Core結合AspNetCoreRateLimit實現限流
    • [x] 方法級別權限控制
    • 社交賬號管理:支持多種第三社交賬號登錄,不干涉原用戶數據,實現第三方賬號管理
    • 多語言
    • [x] 全局敏感詞處理
    • 日誌記錄,方便線上排查錯誤
    • [ ] 支持多種數據庫,並測試,
      • [x] Mysql
      • [ ] Postgresql
      • [ ] Sql Server
      • [ ] SQlite

    產品設計-評論模塊的設計

    下面我們來設計一個評論模塊,需要注意的是,一個評論模塊也有不同的方式。從展示形式,排序規則,按鈕功能設計,操作等方式都有詳細的分析設計,大家可以參考who shi pm 中的文章http://www.woshipm.com/pd/3548139.html,這裏主要講解下展式方式中的主題式的應用。

    1.主題式

    相信很多人都刷過抖音,他的評論主題式的強化版。

    特點為前三個是熱門評論(喜歡最多的評論),將評論分為二級,第一級採用時間倒序,第二級按照時間正序,有助於理解上下文關係。

    可以總結為如下功能點

    用戶操作:

    • [x] 評論隨筆(內容支持超鏈接、emoji)
    • [x] 點贊評論/取消點贊
    • [x] 回複評論
    • [x] 刪除自己的評論

    運營操作:

    • [x] 審核通過/拉黑評論
    • [x] 刪除任何評論
    • [x] 拉黑后的显示邏輯。(保留當前區塊、显示內容為:該評論因違規被拉黑)
    • 刪除:(如果是二級評論,直接軟刪除,如果是一級評論,軟刪除子評論和當前評論-需要提前提醒用戶)

    交互設計

    • 評論的字數長度(500)、emoji。
    • 點贊交互-動畫、消息通知/推送
    • 評論區域元素,需要有明確可點擊的區域,會跳轉到哪個地方。

    優化

    • 精選或者叫置頂評論
    • 該文章是否開放評論功能。
    • 熱門評論(點贊最多的評論)
    • 標註哪些評論是作者,標準哪些評論被用戶點贊。
    • @邏輯,emoji,舉報等

    2 平鋪式

    特點是不區分子父節點關係,比如現在博客園(大多數的主題),微信朋友圈,github。
    不過博客園,github,回復時,可選擇引用回復,方便用戶理解上下文關係。

    3.蓋樓式

    這種方式有點這種感覺 》>-,即.net core中間件請求方式。當回復越來越多時,显示的效果就越誇張。一層套一層的显示上下文的關係。下圖是網易新聞的評論效果。

    排行榜見解

    排行榜從心理學上分析,主要從四個方面影響着您:尋找權威 、參与比較 、關注主流 、自我確認。

    如何設計一個簡單的排行榜呢。。

    在一個博客隨筆中,我們設計一個3天、七天(周榜)、30天(月榜)、全部的榜單。以瀏覽量(權重1)、點贊量(20)、評論量(30)。權重可自己定義。

    1.默認取最新的隨筆

    前台傳create_time時,使用如下sql

    select * from `blog_article` order by create_time desc;
    

    2.傳排序方式為最近n天的熱榜時。

    參數:THREE_DAYS_HOTTEST(三天)、WEEKLY_HOTTEST(七天)、MONTHLY_HOTTEST(一個月)、HOTTEST(全部)

    mysql 查詢當前日期時間前三天數據

    select date_sub(now() ,interval 3 day);
    

    根據權重查詢

    select * from `blog_article` a 
    where a.`create_time`>(select date_sub(now() ,interval 3 day))
    order by (a.`view_hits` + a.`likes_quantity` * 20 + a.`comment_quantity` * 30) DESC, a.`create_time` DESC
    
    

    更多參考

    • 評論區如何設計?
    • 萬字長文深度分析:產品排行榜的設計和玩法
    • 想知道誰是你的最佳用戶?基於Redis實現排行榜周期榜與最近N期榜

    lin-cms 開源地址分享

    • 後端接口 https://github.com/luoyunchong/lin-cms-dotnetcore
    • 管理後台UI https://github.com/luoyunchong/lin-cms-vue
    • 前端UIhttps://github.com/luoyunchong/lin-cms-vvlog

    Demo

    • 用戶端 lin-cms-vvlog https://vvlog.baimocore.cn

      • 普通用戶:710277267@qq.com
      • 密碼:123qwe
    • 管理員 lin-cms-vue https://cms.baimocore.cn/

      • 管理員: admin
      • 密碼:123qwe

    預覽圖

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!