標籤: 聚甘新

  • 軟件架構的核心思想

    軟件架構的核心思想

    目錄

    • 前言
    • 一、軟件架構的定義
    • 二、軟件架構與設計
    • 三、軟件架構的幾個方面
      • 系統
      • 結構
      • 環境
      • 利益相關者
    • 四、軟件架構的結構特徵
      • 運行時結構
      • 模塊結構
    • 五、軟件架構的質量屬性
      • 可修改性
      • 可測試性
      • 可擴展性
      • 性能
      • 可用性
      • 安全性
      • 可部署性
    • 六、其他常見概念
      • 內聚
      • 耦合
      • 企業架構
      • 系統架構

    前言

    welcome to chenqionghe’s blog,架構能力其實更像是一種內功,需要我們不斷地去學習,讓我們用一張正能量的圖片開啟美好的學習生活,let’s do it~

    一、軟件架構的定義

    架構是一個系統的基本組織,涵蓋所包含的組件,組件之間的關係、組件與環境的關係,以及指導架構設計和演進的原則等內容

    二、軟件架構與設計

    軟件架構和軟件設計中都有“設計”的意思,但與後者相比,前者具有更高的抽象性和更廣的範圍

    • 軟件架構關注如何對系統中的結構和交互進行較高級別的描述,它關注的是那些與系統骨架相關的決策問題,例如:功能、組織 、技術、業務和質量標準等。
    • 軟件設計關注構成系統的部件或組件,以及子系統是如何組織的,關注的問題通常更接近代碼或模塊本身,例如:
      • 將代碼分成哪些模塊?如何組織?
      • 給不同的功能分配哪些類(或模塊)?
      • 對於類“C”應該使用哪種設計模式?
      • 在運行時對象之間是如何交互的?傳遞什麼消息?如何實施交互?

    三、軟件架構的幾個方面

    系統

    系統是以特定方式組織的組件集合,以實現特定的功能。
    軟件系統是其軟件組件的集合。一個系統通常可以劃分為若干個子系統。

    結構

    結構是根據某個指導規則或原則來組成或組織在一起的一組元素的集合,可以是軟件或硬件系統。
    軟件架構可以根據觀察者的上下文展示各個層次的結構

    環境

    軟件系統所在的上下文或環境對其軟件架構有直接的影響。
    這樣的上下因素可以是技術、商業、專業、操作等

    利益相關者

    任何對某個系統及其成功與否感興趣或關心的個體或團體,都是利益相關者。
    例如:架構師、開發團隊、客戶、項目經理和營銷團隊等

    四、軟件架構的結構特徵

    運行時結構

    在運行時創建的對象及其之間的交互方式經常決定部署架構。
    部署架構與可擴展性、性能、安全性和交互操作性等質量屬性密切相關

    模塊結構

    為了分解任務,如何拆分代碼並把代碼組織到模塊和包中,這與系統的可維護性和可修改性密切相關,因為

    • 在代碼組織過程中考慮到可擴展性的話,通常會將父類放在單獨定義好的具有恰當文檔和配置的包中,這樣就可以輕易地通過增加外部模塊進行擴展,而不需要處理太多的依賴關係
    • 對於那些依賴於外部或第三方開發者(庫、框架等)的代碼,通過會根據提供的安裝或部署步驟,從外部源手動或自動地獲取並補丁全部各種依賴。此類代碼還提供多種文檔(例如README、INSTALL)等,它們清楚地記錄了這些步驟

    五、軟件架構的質量屬性

    質量屬性是系統的可度量和可測試的特性,可用於評估系統在其指定環境中的非功能性需求方面的達成情況

    可修改性

    定義為對系統進行修改的容易程度,以及系統對理髮進行調整的靈活性。
    這是討論的修改不光是代碼的修改、部署的修改,而是任何層次上的修改。
    一般架構師對可修改性的興趣點如下:

    • 難點:對系統進行修改的難易程度
    • 成本:進行修改需要的時間和資源
    • 風險:任何與系統修改相關的風險

    代碼可讀性越強,其可修改性就越強,代碼的可修改性與可讀性成正比

    可測試性

    指一個軟件系統支持通過測試來檢測故障的程度。
    可測試性也可以認為是一個軟件系統向最終用戶和集成測試隱藏了多少bug
    一個系統的可測試性越好,它能隱藏的bug就越少。

    可擴展性

    指系統能夠適應不斷增長的負載需求,但同時要保證可接受的處理性能,一般分為兩大類:

    • 橫向(水平)擴展性。意味着通過向其中添加更多多的計算節點。
    • 縱向(垂直)擴展性。涉及系統單個節點中資源的添加或移除

    性能

    指系統在給定的計算資源內完成的工作量,完成的工作量和計算資源的比例(work/unit)越高,性能越高。

    計算資源的單位有以下幾種

    • 響應時間。一個函數或執行單元運行所需要的時間。
    • 延遲。某個系統被激活並提供響應所需的時間。
    • 吞吐量。系統處理信息的某種比率。

    可用性

    指系統處於完全可操作狀態的程度,以便在任何時候獲得調用請求時可以執行的能力

    • 可靠性
      系統的可用性和可靠性密切相關,系統越可靠,可用性就越高。
    • 故障恢復能力
      影響可用性的另一個因素是從故障中恢復的能力,包括了故障檢測、故障恢復、故障預防
    • 數據一致性
      CAP定理指出,系統的可用性與其數據一致性有密切聯繫。一致性和可用性一般膛會同時成立,因為可能通信失敗,系統可以在一致性或可用性之間進行選擇

    安全性

    避免被未經過身份驗證的訪問損害數據和邏輯,同時繼續向通過誰的其他系統和角色提供服務的一種能力。

    可部署性

    指軟件從開發環境到產品運行環境移交的難易程度。
    有以下相關因素:

    • 模塊結構。
      將系統劃分為易於部署的一個個子單元,則部署會容易
    • 產品運行環境與開發環境。與
      開發環境結構非常相似的產品運行環境會使部署
    • 開發生態系統支持。
      為系統提供成熟的工具鏈支持,允許各種依賴關係自動建立和驗證等配置項內容,從而提高可部署性。
    • 標準配置。
      一個好的方式是開發者保持開發環境的配置結構和產品運行環境一致。
    • 標準化基礎設施。
      將部署保持在一個標準化的基礎設施上,提高可部署性
    • 容器使用
      隨着Docker容器技術的普及,可以規範軟件,減少啟動/停止的開銷,從而使部署更容易。

    六、其他常見概念

    內聚

    一個模塊內相關聯程度的度量,描述的是模塊內的功能聯繫
    若一個模塊之間各元素聯繫緊密,則內聚性就高(高內聚)
    如果能做到將模塊做成一個功能類聚、獨立性強、內部緊密結合才是一個理想的類聚模塊,這對初學都來說,非常不容易,不光是個人技術能力的挑戰,更是對某個領域業務水平的挑戰。

    耦合

    各模塊間相互聯繫緊密程度的一種度量。
    模塊之間聯繫少,耦合性就越低,模塊之間的相對獨立性就越強。

    企業架構

    企業架構是一個定義企業結構和行為的概念藍圖。它確定了企業結構、流程、人員和信息流動如何與其核心目標相一致,以便有效地實現當前和未來的目標

    系統架構

    系統架構是系統的基本組織形式,由其結構和行為視圖表示。該結構由兩部分確定:構成系統的組件和組件的行為。組件的行為是指組件之間的交互,以及組件與外部系統之間的交互

    以上內容由chenqionghe整理,參考《軟件架構-Python語言實現》,light weight baby~

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

    【其他文章推薦】

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

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

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

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

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

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

    聚甘新

  • spring源碼分析——BeanPostProcessor接口

    spring源碼分析——BeanPostProcessor接口

     

      BeanPostProcessor是處理bean的後置接口,beanDefinitionMaps中的BeanDefinition實例化完成后,完成populateBean,屬性設置,完成

    初始化后,這個接口支持對bean做自定義的操作。

    一:BeanPostProcessor的使用

    定義一個測試用的model對象,name屬性默認為hello

    public class BeanDemo {
    
    	private String name = "hello";
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	@Override
    	public String toString() {
    		final StringBuffer sb = new StringBuffer("BeanDemo{");
    		sb.append("name='").append(name).append('\'');
    		sb.append('}');
    		return sb.toString();
    	}
    }
    

      

    自定義一個MyBeanPostProcessor類,實現BeanPostProcessor接口

    @Service
    public class MyBeanPostProcessor implements BeanPostProcessor {
    
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return null;
    	}
    
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		if(beanName.equals("beanDemo")){
    			BeanDemo beanDemo = (BeanDemo)bean;
    			beanDemo.setName("kitty");
    			return beanDemo;
    		}
    		return bean;
    	}
    }
    

      

     

     

    從運行結果看,spring中維護的beanName為beanDemo的對象,name屬性為ketty

     

     

    二:看看源碼怎麼實現的

    1:實例化並且註冊到beanPostProcessors集合中

     

     

     

     

    主要的實例化邏輯在這個接口,這個接口的作用就是把所有實現BeanPostProcessor接口的類實例化,然後註冊到 beanPostProcessors這個緩存中

     

     

    	public static void registerBeanPostProcessors(
    			ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
    
    		// 獲取所有實現接口BeanPostProcessor的beanName
    		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    
    		// Register BeanPostProcessorChecker that logs an info message when
    		// a bean is created during BeanPostProcessor instantiation, i.e. when
    		// a bean is not eligible for getting processed by all BeanPostProcessors.
    		int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
    		beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
    
    		// Separate between BeanPostProcessors that implement PriorityOrdered,
    		// Ordered, and the rest.
    		/**
    		 * 把實現PriorityOrdered 和 Ordered 和 其他的處理器分開
    		 */
    		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    		List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
    		List<String> orderedPostProcessorNames = new ArrayList<>();
    		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    		/**
    		 * 1:遍歷集合postProcessorNames
    		 * 2:判斷類型,如果是PriorityOrdered,則實例化對象放入priorityOrderedPostProcessors集合,
    		 * Ordered 則放入orderedPostProcessorNames集合,其他的放入nonOrderedPostProcessorNames集合
     		 */
    		for (String ppName : postProcessorNames) {
    			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    				BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    				priorityOrderedPostProcessors.add(pp);
    				if (pp instanceof MergedBeanDefinitionPostProcessor) {
    					internalPostProcessors.add(pp);
    				}
    			}
    			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
    				orderedPostProcessorNames.add(ppName);
    			}
    			else {
    				nonOrderedPostProcessorNames.add(ppName);
    			}
    		}
    
    		// First, register the BeanPostProcessors that implement PriorityOrdered.
    		// 首先對priorityOrderedPostProcessors集合中實例對象排序,然後註冊,放入beanFactory中緩存下來
    		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
    
    		// Next, register the BeanPostProcessors that implement Ordered.
    		// 然後再實例化實現Ordered接口的對象,完成註冊
    		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
    		for (String ppName : orderedPostProcessorNames) {
    			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    			orderedPostProcessors.add(pp);
    			if (pp instanceof MergedBeanDefinitionPostProcessor) {
    				internalPostProcessors.add(pp);
    			}
    		}
    		sortPostProcessors(orderedPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, orderedPostProcessors);
    
    		// Now, register all regular BeanPostProcessors.
    		// 最後實例化什麼都沒有實現的,完成實例化並註冊
    		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
    		for (String ppName : nonOrderedPostProcessorNames) {
    			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    			nonOrderedPostProcessors.add(pp);
    			if (pp instanceof MergedBeanDefinitionPostProcessor) {
    				internalPostProcessors.add(pp);
    			}
    		}
    		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
    
    		// Finally, re-register all internal BeanPostProcessors.
    		// 最後再次註冊內部postProcessor
    		sortPostProcessors(internalPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, internalPostProcessors);
    
    		// Re-register post-processor for detecting inner beans as ApplicationListeners,
    		// moving it to the end of the processor chain (for picking up proxies etc).
    		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    	}
    

      

     

     

    定義四類容器,高優先級有序、有序、無序、內部

     

     分類放入四種容器:

     

     

    註冊BeanPostProcessor,將實現BeanPostProcessor接口的對象放入beanPostProcessors緩存中

     

     

     

     

     

     

    註冊完PriorityOrdered的實現類后,再處理Ordered的實現類

     

     

    註冊什麼都沒有實現的BeanPostProcessor接口實現類,

     

     

    最後註冊內部的BeanPostProcessor對象

     

     到這裏BeanPostProcessor的實例化以及註冊工作完成,在beanFactory的beanPostProcessors集合中已經緩存了所有的beanPostProcessor的對象

     

    2:BeanPostProcessor的使用

    因為這個接口是bean的後置接口,所以需要bean創建並初始化完成,才可以發揮作用,上一步的緩存只是埋好點,以備使用,因為bean的實例化流程我們

    還沒有分析,這裏直接看一下怎麼使用的

     

     

    我們看一下init方法后的攔截,因為這個時候已經init完成,可以在後置接口中對bean做一下修改的操作

     

     

    調用到我們自定義的MyBeanPostProcessor實現類:

     

     

    把這個beanDemo對象屬性修改一下,修改完,再返回,將這個對象緩存到spring的一級緩存中。

     

     

    總結:

      BeanPostProcessor接口主要是對bean對象做一些自定義的操作,修改bean對象的信息,aop代理也是通過這種方式實現的,

    在refresh的registryBeanPostProcessor方法中實例化BeanPostProcessor對象,並且註冊到beanFactory容器的beanPostProcessors的緩存中,

    然後在後續的操作中攔截使用。

     

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

    【其他文章推薦】

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

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

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

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

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

    聚甘新

  • [經驗棧]C#監測IPv4v6網速及流量

    [經驗棧]C#監測IPv4v6網速及流量

    1、前言

      最近做項目需要用到監測網速及流量,我經過百度和牆內谷歌都沒能快速發現監測IPV6流量和網速的用例;也經過自己的一番查詢和調試,浪費了不少時間,現在作為經驗分享出來希望大家指正。

    2、C#代碼

    using System.Net.NetworkInformation;
    using System.Timers;
    
    namespace Monitor
    {
        public class MonitorNetwork
        {      
            public string UpSpeed { get; set; }   
            public string DownSpeed { get; set; }
            public string AllTraffic { get; set; }            
            private string NetCardDescription { get; set; }    
            //建立連接時上傳的數據量
            private long BaseTraffic { get; set; }    
            private long OldUp { get; set; }    
            private long OldDown { get; set; }
            private NetworkInterface networkInterface { get; set; }
            private Timer timer = new Timer() { Interval = 1000 };
        
            public void Close()
            {
                timer.Stop();   
            }
        
            public MonitorNetwork(string netCardDescription)
            {   
                timer.Elapsed += Timer_Elapsed;    
                NetCardDescription = netCardDescription;    
                timer.Interval = 1000;     
            }
    
            public bool Start()
            {
                networkInterface = null;    
                NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();    
                foreach (var var in nics)
                {
                    if (var.Description.Contains(NetCardDescription))
                    {
                        networkInterface = var;
                        break;
                    }
                }    
                if (networkInterface == null)
                {
                    return false;
                }
                else
                {    
                    BaseTraffic = (networkInterface.GetIPStatistics().BytesSent +
                                   networkInterface.GetIPStatistics().BytesReceived);    
                    OldUp = networkInterface.GetIPStatistics().BytesSent;    
                    OldDown = networkInterface.GetIPStatistics().BytesReceived;   
                    timer.Start();    
                    return true;
                }
        
            }
    
            private string[] units = new string[] {"KB/s","MB/s","GB/s" };
    
            private void CalcUpSpeed()
            {
                long nowValue = networkInterface.GetIPStatistics().BytesSent;    
                int num = 0;
                double value = (nowValue - OldUp) / 1024.0;
                while (value > 1023)
                {
                    value = (value / 1024.0);
                    num++;
                }   
                UpSpeed = value.ToString("0.0") + units[num];    
                OldUp = nowValue;    
            }
        
            private void CalcDownSpeed()
            {
                long nowValue = networkInterface.GetIPStatistics().BytesReceived;   
                int num = 0;
                double value = (nowValue - OldDown) / 1024.0;     
                while (value > 1023)
                {
                    value = (value / 1024.0);
                    num++;
                }    
                DownSpeed = value.ToString("0.0") + units[num];    
                OldDown = nowValue;    
            }
        
            private string[] unitAlls = new string[] { "KB", "MB", "GB" ,"TB"};
        
            private void CalcAllTraffic()
            {
                long nowValue = OldDown+OldUp;    
                int num = 0;
                double value = (nowValue- BaseTraffic) / 1024.0;
                while (value > 1023)
                {
                    value = (value / 1024.0);
                    num++;
                }   
                AllTraffic = value.ToString("0.0") + unitAlls[num];
            }
    
            private void Timer_Elapsed(object sender, ElapsedEventArgs e)
            {
                CalcUpSpeed();
                CalcDownSpeed();
                CalcAllTraffic();
            }
        }
    }
    

    3、胡說八道

      雖然沒能直接快速地百度到方法,但是實現這個需求的時候,心裏是有個譜,Windows系統能監測到這個網速和流量,沒理由實現不了,只需要一個方法將這個信息讀取出來就好。最後實現這個需求是利用了System.Net.NetworkInformation這個程序集,但是這個程序集沒有隻接提供網速監測的方法,而是提供了接收和發送數據量的屬性,需要自己計算出即使網速,所以這個網速不是特別的準確。

      這個程序集其實一開始就看到了,前輩方法中使用的是IPv4InterfaceStatistics類中的BytesReceived屬性和BytesSent屬性實現的,但是在這個程序集里沒有對應的IPv6類,恍恍惚惚。

      然後呢,我就下意識以為這個程序集比較老舊,不支持IPv6統計信息讀取,然後也是各種搜索無果,之後呢不死心想再來研究研究,東點點西瞅瞅,然後在NetworkInterface 類中發現了一個GetIPStatistics()方法,它的描述是“獲取此 NetworkInterface 實例的 IP 統計信息。”。

      然後就順理成章的事了,根據GetIPStatistics()返回的IPInterfaceStatistics實例中的BytesReceived屬性和BytesSent屬性就能獲取到收發的數據總量,然後根據這個信息就能計算出大約的網速。

      經測試,利用IPInterfaceStatistics實例是能讀取到IPv4和IPv6的總數據量的,因為這次的需求就是監測總量,如果需要單獨監測IPv6的可以用總量減去IPv4部分。

    4、後記

    ​  老師以前喊我認真念書,我心想有百度還不夠嗎,再念能有百度聰明,有百度懂得多,後來漸漸明白,百度懂得多都是前輩的搬磚添瓦來的,共勉。

    參考資料

      System.Net.NetworkInformation 命名空間

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

    聚甘新

  • ASP.NET Core 對Controller進行單元測試

    單元測試對我們的代碼質量非常重要。很多同學都會對業務邏輯或者工具方法寫測試用例,但是往往忽略了對Controller層寫單元測試。我所在的公司沒見過一個對Controller寫過測試的。今天來演示下如果對Controller進行單元測試。以下內容默認您對單元測試有所了解,比如如何mock一個接口。在這裏多叨叨一句,面向接口的好處,除了能夠快速的替換實現類(其實大部分接口不會有多個實現),最大的好處就是可以進行mock,可以進行單元測試。

    測試Action

    下面的Action非常簡單,非常常見的一種代碼。根據用戶id去獲取用戶信息然後展示出來。下面看看如何對這個Action進行測試。

       public class UserController : Controller
        {
            private readonly IUserService _userService;
            public UserController(IUserService userService)
            {
                _userService = userService;
            }
    
            public IActionResult UserInfo(string userId)
            {
                if (string.IsNullOrEmpty(userId))
                {
                    throw new ArgumentNullException(nameof(userId));
                }
    
                var user = _userService.Get(userId);
                return View(user);
            }
          
        }
    

    測試代碼:

      [TestMethod()]
            public void UserInfoTest()
            {
    
                var userService = new Mock<IUserService>();
                userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User());
    
                var ctrl = new UserController(userService.Object);
                //對空參數進行assert
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo(null);
                });
                //對空參數進行assert
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo("");
                });
    
                var result = ctrl.UserInfo("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(ViewResult));
            }
    

    我們對一個Action進行測試主要的思路就是模擬各種入參,使測試代碼能夠到達所有的分支,並且Assert輸出是否為空,是否為指定的類型等。

    對ViewModel進行測試

    我們編寫Action的時候還會涉及ViewModel給視圖傳遞數據,這部分也需要進行測試。修改測試用例,加入對ViewModel的測試代碼:

      [TestMethod()]
            public void UserInfoTest()
            {
                var userService = new Mock<IUserService>();
                userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User()
                {
                    Id = "x"
                }) ;
    
                var ctrl = new UserController(userService.Object);
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo(null);
                });
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo("");
                });
    
                var result = ctrl.UserInfo("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(ViewResult));
                //對viewModel進行assert
                var vr = result as ViewResult;
                Assert.IsNotNull(vr.Model);
                Assert.IsInstanceOfType(vr.Model, typeof(User));
                var user = vr.Model as User;
                Assert.AreEqual("x", user.Id);
            }
    

    對ViewData進行測試

    我們編寫Action的時候還會涉及ViewData給視圖傳遞數據,這部分同樣需要測試。修改Action代碼,對ViewData進行賦值:

       public IActionResult UserInfo(string userId)
            {
                if (string.IsNullOrEmpty(userId))
                {
                    throw new ArgumentNullException(nameof(userId));
                }
    
                var user = _userService.Get(userId);
    
                ViewData["title"] = "user_info";
    
                return View(user);
            }
          
    

    修改測試用例,加入對ViewData的測試代碼:

       [TestMethod()]
            public void UserInfoTest()
            {
                var userService = new Mock<IUserService>();
                userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User()
                {
                    Id = "x"
                }) ;
    
                var ctrl = new UserController(userService.Object);
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo(null);
                });
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo("");
                });
    
                var result = ctrl.UserInfo("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(ViewResult));
    
                var vr = result as ViewResult;
                Assert.IsNotNull(vr.Model);
                Assert.IsInstanceOfType(vr.Model, typeof(User));
                var user = vr.Model as User;
                Assert.AreEqual("x", user.Id);
                //對viewData進行assert
                Assert.IsTrue(vr.ViewData.ContainsKey("title"));
                var title = vr.ViewData["title"];
                Assert.AreEqual("user_info", title);
            }
    

    對ViewBag進行測試

    因為ViewBag事實上是ViewData的dynamic類型的包裝,所以Action代碼不用改,可以直接對ViewBag進行測試:

         [TestMethod()]
            public void UserInfoTest()
            {
                var userService = new Mock<IUserService>();
                userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User()
                {
                    Id = "x"
                }) ;
    
                var ctrl = new UserController(userService.Object);
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo(null);
                });
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo("");
                });
    
                var result = ctrl.UserInfo("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(ViewResult));
    
                var vr = result as ViewResult;
                Assert.IsNotNull(vr.Model);
                Assert.IsInstanceOfType(vr.Model, typeof(User));
                var user = vr.Model as User;
                Assert.AreEqual("x", user.Id);
    
                Assert.IsTrue(vr.ViewData.ContainsKey("title"));
                var title = vr.ViewData["title"];
                Assert.AreEqual("user_info", title);
                //對viewBag進行assert
                string title1 = ctrl.ViewBag.title;
                Assert.AreEqual("user_info", title1);
            }
    

    設置HttpContext

    我們編寫Action的時候很多時候需要調用基類里的HttpContext,比如獲取Request對象,獲取Path,獲取Headers等等,所以有的時候需要自己實例化HttpContext以進行測試。

        var ctrl = new AccountController();
        ctrl.ControllerContext = new ControllerContext();
        ctrl.ControllerContext.HttpContext = new DefaultHttpContext();
    

    對HttpContext.SignInAsync進行mock

    我們使用ASP.NET Core框架進行登錄認證的時候,往往使用HttpContext.SignInAsync進行認證授權,所以單元測試的時候也需要進行mock。下面是一個典型的登錄Action,對密碼進行認證后調用SignInAsync在客戶端生成登錄憑證,否則跳到登錄失敗頁面。

       public async Task<IActionResult> Login(string password)
            {
                if (password == "123")
                {
                    var claims = new List<Claim>
                    {
                      new Claim("UserName","x")
                    };
                    var authProperties = new AuthenticationProperties
                    {
                    };
                    var claimsIdentity = new ClaimsIdentity(
                      claims, CookieAuthenticationDefaults.AuthenticationScheme);
                    await HttpContext.SignInAsync(
                        CookieAuthenticationDefaults.AuthenticationScheme,
                        new ClaimsPrincipal(claimsIdentity),
                        authProperties);
                    return Redirect("login_success");
                }
    
                return Redirect("login_fail");
            }
    

    HttpContext.SignInAsync其實個時擴展方法,SignInAsync其實最終是調用了IAuthenticationService里的SignInAsync方法。所以我們需要mock的就是IAuthenticationService接口,否者代碼走到HttpContext.SignInAsync會提示找不到IAuthenticationService的service。而IAuthenticationService本身是通過IServiceProvider注入到程序里的,所以同時需要mock接口IServiceProvider。

        [TestMethod()]
            public async Task LoginTest()
            {
                var ctrl = new AccountController();
    
                var authenticationService = new Mock<IAuthenticationService>();
                var sp = new Mock<IServiceProvider>();
                sp.Setup(s => s.GetService(typeof(IAuthenticationService)))
                    .Returns(() => {
                        return authenticationService.Object;
                    });
                ctrl.ControllerContext = new ControllerContext();
                ctrl.ControllerContext.HttpContext = new DefaultHttpContext();
                ctrl.ControllerContext.HttpContext.RequestServices = sp.Object;
    
               var result = await ctrl.Login("123");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(RedirectResult));
                var rr = result as RedirectResult;
                Assert.AreEqual("login_success", rr.Url);
    
                result = await ctrl.Login("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(RedirectResult));
                rr = result as RedirectResult;
                Assert.AreEqual("login_fail", rr.Url);
            }
    

    對HttpContext.AuthenticateAsync進行mock

    HttpContext.AuthenticateAsync同樣比較常用。這個擴展方法同樣是在IAuthenticationService里,所以測試代碼跟上面的SignInAsync類似,只是需要對AuthenticateAsync繼續mock返回值success or fail。

         public async Task<IActionResult> Login()
            {
                if ((await HttpContext.AuthenticateAsync()).Succeeded)
                {
                    return Redirect("/home");
                }
    
                return Redirect("/login");
            }
    

    測試用例:

    
            [TestMethod()]
            public async Task LoginTest1()
            {
                var authenticationService = new Mock<IAuthenticationService>();
                //設置AuthenticateAsync為success
                authenticationService.Setup(s => s.AuthenticateAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
                    .ReturnsAsync(AuthenticateResult.Success(new AuthenticationTicket(new System.Security.Claims.ClaimsPrincipal(), "")));
                var sp = new Mock<IServiceProvider>();
                sp.Setup(s => s.GetService(typeof(IAuthenticationService)))
                    .Returns(() => {
                        return authenticationService.Object;
                    });
    
                var ctrl = new AccountController();
                ctrl.ControllerContext = new ControllerContext();
                ctrl.ControllerContext.HttpContext = new DefaultHttpContext();
                ctrl.ControllerContext.HttpContext.RequestServices = sp.Object;
    
                var act = await ctrl.Login();
                Assert.IsNotNull(act);
                Assert.IsInstanceOfType(act, typeof(RedirectResult));
                var rd = act as RedirectResult;
                Assert.AreEqual("/home", rd.Url);
                //設置AuthenticateAsync為fail
                authenticationService.Setup(s => s.AuthenticateAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
                   .ReturnsAsync(AuthenticateResult.Fail(""));
    
                act = await ctrl.Login();
                Assert.IsNotNull(act);
                Assert.IsInstanceOfType(act, typeof(RedirectResult));
                rd = act as RedirectResult;
                Assert.AreEqual("/login", rd.Url);
    
            }
    

    Filter進行測試

    我們寫Controller的時候往往需要配合很多Filter使用,所以Filter的測試也很重要。下面演示下如何對Fitler進行測試。

        public class MyFilter: ActionFilterAttribute
        {
            public override void OnActionExecuting(ActionExecutingContext context)
            {
                if (context.HttpContext.Request.Path.Value.Contains("/abc/"))
                {
                    context.Result = new ContentResult() {
                        Content = "拒絕訪問"
                    };
                }
    
                base.OnActionExecuting(context);
            }
        }
    

    對Filter的測試最主要的是模擬ActionExecutingContext參數,以及其中的HttpContext等,然後對預期進行Assert。

           [TestMethod()]
            public void OnActionExecutingTest()
            {
                var filter = new MyFilter();
                var actContext = new ActionContext(new DefaultHttpContext(),new RouteData(), new ActionDescriptor());
                actContext.HttpContext.Request.Path = "/abc/123";
                var listFilters = new List<IFilterMetadata>();
                var argDict = new Dictionary<string, object>();
                var actExContext = new ActionExecutingContext(
                    actContext ,
                    listFilters ,
                    argDict ,
                    new AccountController()
                    );
                 filter.OnActionExecuting(actExContext);
    
                Assert.IsNotNull(actExContext.Result);
                Assert.IsInstanceOfType(actExContext.Result, typeof(ContentResult));
                var cr = actExContext.Result as ContentResult;
                Assert.AreEqual("拒絕訪問", cr.Content);
    
                actContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
                actContext.HttpContext.Request.Path = "/1/123";
                listFilters = new List<IFilterMetadata>();
                argDict = new Dictionary<string, object>();
                actExContext = new ActionExecutingContext(
                    actContext,
                    listFilters,
                    argDict,
                    new AccountController()
                    );
                filter.OnActionExecuting(actExContext);
                Assert.IsNull(actExContext.Result);
            }
    

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

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

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

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

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

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

    ※超省錢租車方案

    聚甘新

  • 001.OpenShift介紹

    001.OpenShift介紹

    一 OpenShift特性

    1.1 OpenShift概述


    Red Hat OpenShijft Container Platform (OpenShift)是一個容器應用程序平台,它為開發人員和IT組織提供了一個雲應用程序平台,用於在安全的、可伸縮的資源上部署新應用程序,而配置和管理開銷最小。

    OpenShift構建於Red Hat Enterprise Linux、Docker和Kubernetes之上,為當今的企業級應用程序提供了一個安全且可伸縮的多租戶操作系統,同時還提供了集成的應用程序運行時和庫。

    OpenShift帶來了健壯、靈活和可伸縮的特性。容器平台到客戶數據中心,使組織能夠實現滿足安全性、隱私性、遵從性和治理需求的平台。不願意管理自己的OpenShift集群的客戶可以使用Red Hat提供的公共雲平台OpenShift Online。

    1.2 OpenShift特性


    OpenShift容器平台和OpenShift Online都是基於OpenShift Origin開源軟件項目的,該項目本身使用了許多其他開源項目,如Docker和Kubernetes。

    應用程序作為容器運行,容器是單個操作系統內的隔離分區。容器提供了許多與虛擬機相同的好處,比如安全性、存儲和網絡隔離,同時需要的硬件資源要少得多,啟動和終止也更快。OpenShift使用容器有助於提高平台本身及其承載的應用程序的效率、靈活性和可移植性。

    OpenShift的主要特性如下:

    • 自助服務平台:OpenShift允許開發人員使用Source-to-Image(S21)從模板或自己的源代碼管理存儲庫創建應用程序。系統管理員可以為用戶和項目定義資源配額和限制,以控制系統資源的使用。
    • 多語言支持:OpenShift支持Java、Node.js、PHP、Perl以及直接來自Red Hat的Ruby。同時也包括來自合作夥伴和更大的Docker社區的許多其他代碼。MySQL、PostgreSQL和MongoDB數據庫。Red Hat還支持在OpenShift上本地運行的中間件產品,如Apache httpd、Apache Tomcat、JBoss EAP、ActiveMQ和Fuse。
    • 自動化:OpenShift提供應用程序生命周期管理功能,當上游源或容器映像發生更改時,可以自動重新構建和重新部署容器。根據調度和策略擴展或故障轉移應用程序。
    • 用戶界面:OpenShift提供用於部署和監視應用程序的web UI,以及用於遠程管理應用程序和資源的CLi。它支持Eclipse IDE和JBoss Developer Studio插件,以便開發人員可以繼續使用熟悉的工具,並支持REST APl與第三方或內部工具集成。
    • 協作:OpenShift允許在組織內或與更大的社區共享項目。
    • 可伸縮性和高可用性:OpenShift提供了容器多租戶和一個分佈式應用程序平台,其中包括彈性,以處理隨需增加的流量。它提供了高可用性,以便應用程序能夠在物理機器宕機等事件中存活下來。OpenShift提供了對容器健康狀況的自動發現和自動重新部署。
    • 容器可移植性:在OpenShift中,應用程序和服務使用標準容器映像進行打包,組合應用程序使用Kubernetes進行管理。這些映像可以部署到基於這些基礎技術的其他平台上。
    • 開源:沒有廠商鎖定。
    • 安全性:OpenShift使用SELinux提供多層安全性、基於角色的訪問控制以及與外部身份驗證系統(如LDAP和OAuth)集成的能力。
    • 動態存儲管理:OpenShift使用Kubernetes持久卷和持久卷聲明的方式為容器數據提供靜態和動態存儲管理
    • 基於雲(或不基於雲):可以在裸機服務器、活來自多個供應商的hypervisor和大多數IaaS雲提供商上部署OpenShift容器平台。
    • 企業級:Red Hat支持OpenShift、選定的容器映像和應用程序運行時。可信的第三方容器映像、運行時和應用程序由Red Hat認證。可以在OpenShift提供的高可用性的強化安全環境中運行內部或第三方應用程序。
    • 日誌聚合和metrics:可以在中心節點收集、聚合和分析部署在OpenShift上的應用程序的日誌信息。OpenShift能夠實時收集關於應用程序的度量和運行時信息,並幫助不斷優化性能。
    • 其他特性:OpenShift支持微服務體繫結構,OpenShift的本地特性足以支持DevOps流程,很容易與標準和定製的持續集成/持續部署工具集成。

    二 OpenShift架構

    2.1 OpenShift架構概述


    OpenShift容器平台是一組構建在Red Hat Enterprise Linux、Docker和Kubernetes之上的模塊化組件和服務。OpenShift增加了遠程管理、多租戶、增強的安全性、應用程序生命周期管理和面向開發人員的自服務接口。

    OpenShift的架構:



    • RHEL:基本操作系統是Red Hat Enterprise Linux;
    • Docker:提供基本的容器管理API和容器image文件格式;
    • Kubernetes:管理運行容器的主機集群(物理或虛擬主機)。它處理描述由多個資源組成的多容器應用程序的資源,以及它們如何互連;
    • Etcd:一個分佈式鍵值存儲,Kubernetes使用它來存儲OpenShift集群中容器和其他資源的配置和狀態信息。


    OpenShift在Docker + Kubernetes基礎設施之上添加了提供容器應用程序平台所需的更富豐的功能:

    OpenShift-Kubernetes extensions:其它資源類型存儲在Etcd中,由Kubernetes管理。這些額外的資源類型形成OpenShift內部狀態和配置,以及由標準 Kubernetes管理的應用程序資源;

    Containerized services:完成許多基礎設施功能,如網絡和授權。其中一些一直運行,另一些則按需啟動。OpenShift使用Docker和Kubernetes來實現大多數內部功能。即大多數OpenShift內部服務作為由Kubernetes管理的容器;

    Runtimes and xPaaS:供開發人員使用的 base image,每個image都預配置了特定的runtime或db。xPaaS提供了一組用於JBoss中間件產品(如JBoss EAP和ActiveMQ)的 base image;

    DevOps tools and user experience:OpenShift提供了Web UI和CLI管理工具,從而實現配置和監視應用程序、OpenShift服務和資源。Web和CLI工具都是由相同的REST api構建的,可供IDE和CI平台等外部工具使用。OpenShift 還可以訪問外部SCM存儲庫和容器registry,並將它們的構件引入OpenShift Cloud。

    OpenShift不會向開發人員和系統管理員屏蔽Docker和Kubernetes的核心基礎設施。相反,它將它們用於內部服務,並允許將Docker和Kubernetes資源導入OpenShift集群,同時原始Docker和資源可以從OpenShift集群導出,並導入到其他基於docker的基礎設施中。

    OpenShift添加到Docker + Kubernetes的主要價值是自動化開發工作流,因此應用程序的構建和部署在OpenShift集群中按照標準流程進行。開發者不需要知道底層Docker的細節。OpenShift接受應用程序,打包它,並將其作為容器啟動。

    2.2 Master和nodes


    OpenShift集群是一組節點服務器,它們運行容器,並由一組主服務器集中管理。服務器可以同時充當master和node,但是為了增加穩定性,這些角色通常是分開的。

    OpenShift工作原理和交互視圖:




    master節點運行OpenShift核心服務,如身份驗證,並未管理員提供API入口。

    nodes節點運行包含應用程序的容器,容器又被分組成pod。

    OpenShift master運行Kubernetes master服務和Etcd守護進程;

    node運行Kubernetes kubelet和kube-proxy守護進程。

    雖然在描述中通常沒有聲明,但實際上master本身也是node。

    scheduler和management/replication是Kubernetes主服務,而Data Store是Etcd守護進程。

    Kubernetes的調度單元是pod,它是一組共享虛擬網絡設備、內部IP地址、TCP/UDP端口和持久存儲的容器。pod可以是任何東西,從完整的企業應用程序(包括作為不同容器的每一層)到單個容器中的單個微服務。例如,一個pod,一個容器在Apache下運行PHP,另一個容器運行MySQL。

    Kubernetes管理replicas來縮放pods。副本是一組共享相同定義的pod。

    三 管理OpenShift

    3.1 OpenShift項目及應用


    除了Kubernetes的資源(如pods和services)之外,OpenShift還管理projects和users。一個projects對Kubernetes資源進行分組,以便用戶可以使用訪問權限。還可以為projects分配配額,從而限制了已定義的pod、volumes、services和其他資源。

    OpenShift中沒有application的概念,OpenShift client提供了一個new-app命令。此命令在projects中創建資源,但它們都不是應用程序資源。這個命令是為標準開發人員工作流配置帶有公共資源的proiect的快捷方式。

    OpenShift使用lables(標籤)對集群中的資源進行分類。默認情況下,OpenShift使用app標籤將相關資源分組到應用程序中。

    3.2 使用Source-to-image構建映像


    OpenShift允許開發人員使用標準源代碼管理倉庫(SCM)和集成開發環境(ide)來發布應用。

    OpenShift中的source -to-lmage (S2I)流程從SCM倉庫中提取代碼,自動判斷所需的runtime,基於runtime啟動一個pod,在pod中編譯應用。

    當編譯成功時,將在runtime image中添加層並形成新的image,推送進入OpenShift internal registry倉庫,接着基於這個image將創建新的pod,運行應用程序。

    S2I可被視為已經內置到OpenShift中的完整的CI/CD管道。

    CI/CD有不同的形式,根據具體場景表現不同。例如,可以使用外部CI工具(如Jenkins)啟動構建並運行測試,然後將新構建的映像標記為成功或失敗,將其推送到QA或生產。

    3.2 管理OpenShift資源

    OpenShift資源定義,如image、container、pod、service、builder、template等,都存儲在Etcd中,可以由OpenShift CLI, web控制台或REST API進行管理。

    OpenShift的資源科通過JSON或YAML文件查看,並且在類似Git或版本控制的SCM中共享。OpenShift甚至可以直接從外部SCM檢索這些資源定義。

    大多數OpenShift操作不需要實時響應,OpenShift命令和APIs通常創建或修改存儲在Etcd中的資源描述。Etcd然後通知OpenShift控制器,OpenShift控制器會就更改警告這些資源。

    這些控制器採取行動,以便使得資源的最終態反應達到更改效果。例如,如果創建了一個新的pod資源,Kubernetes將在node上調度並啟動該pod,使用pod資源確定要使用哪個映像、要公開哪個端口,等等。或者一個模板被更改,從而指定應該有更多的pod來處理負載,OpenShift會安排額外的pod(副本)來滿足更新后的模板定義。

    注意:雖然Docker和Kubernetes是OpenShift的底層,但是必須主要使用OpenShift CLi和OpenShift APls來管理應用程序和基礎設施。OpenShift增加了額外的安全和自動化功能,當直接使用Docker或Kubernetes命令和APls時,這些功能必須手動配置,或者根本不可用。因此強烈建議不要使用docker或Kubernetes的命令直接管理應用。

    四 OpenShift網絡

    4.1 OpenShift網絡概述


    Docker網絡相對簡單,Docker創建一個虛擬內核橋接器(docker0網卡),並將每個容器網絡接口連接到它。

    Docker本身沒有提供允許一個主機上的pod連接到另一個主機上的pod的方法。Docker也沒有提供嚮應用程序分配公共固定IP地址的方法,以便外部用戶可以訪問它。

    但Kubernetes提供service和route資源來管理pods之間的網絡,以及從外部到pods的路由流量。service在不同pods之間提供負載均衡用於接收網絡請求,同時為service的所有客戶機(通常是其他pods)提供一個內部IP地址。

    container和pods不需要知道其他pods在哪裡,它們只連接到service。route為service提供一個固定的惟一DNS名稱,使其對OpenShift集群之外的客戶端可見。

    Kubernetes service和route資源需要外部(功能)插件支持。service需要軟件定義的網絡(SDN),它將在不同主機上的pod之間提供通信,route需要轉發或重定向來自外部客戶端的包到服務內部IP。

    OpenShift提供了一個基於Open vSwitch的SDN,路由由分佈式HAProxy farm提供。

    五 OpenShift持久性存儲

    5.1 永久存儲


    pod可以在一個節點上停止,並隨時在另一個節點上重新啟動。同時pod的默認存儲是臨時存儲,通過對於類似數據庫需要永久保存數據的應用不適合。

    Kubernetes為管理容器的外部持久存儲提供了一個框架。Kubernetes提供了PersistentVolume資源,它可以在本地或網絡中定義存儲。pod資源可以使用PersistentVolumeClaim資源來訪問對應的持久存儲卷。

    Kubernetes還指定了一個PersistentVolume資源是否可以在pod之間共享,或者每個pod是否需要具有獨佔訪問權的自己PersistentVolume。當pod移動到另一個節點時,它將保持與相同的PersistentVolumeClaim和PersistentVolumne資源的關聯。這意味着pod的持久存儲數據跟隨它,而不管它將在哪個節點上運行。

    OpenShift向Kubernetes提供了多種VolumeProvider,如NFS、iSCSI、FC、Gluster或OpenStack Cinder。

    OpenShift還通過StorageClass資源為應用程序提供動態存儲。使用動態存儲,可以選擇不同類型的後端存儲。後面存儲根據應用程序的需要劃分為不同的“tiers”。例如,可以定義一個名為“fast”的存儲類和另一個名為“slow”的存儲類,前者使用更高速的後端存儲,後者提供普通的存儲。當請求存儲時,最終用戶可以指定一個Persistentvolumeclaim,並使用一個註釋指定他們所需的StorageClass。

    六 OpenShift高可用

    6.1 OpenShift高可用概述


    OpenShift平台集群的高可用性(HA)有兩個不同的方面:

    OpenShift基礎設施本身的HA(即主機);

    以及在OpenShift集群中運行的應用程序的HA。

    默認情況下,OpenShift為master提供了完全支持的本機HA機制。

    對於應用程序或“pods”,如果pod因任何原因丟失,Kubernetes將調度另一個副本,將其連接到服務層和持久存儲。如果整個節點丟失,Kubernetes會為它所有的pod安排替換節點,最終所有的應用程序都會重新可用。pod中的應用程序負責它們自己的狀態,因此它們需要自己維護應用程序狀態(如HTTP會話複製或數據庫複製)。

    七 Image Streams

    7.1 Image Streams


    要在OpenShift中創建一個新的應用程序,除了應用程序源代碼之外,還需要一個base image(S2I builder image)。如果源代碼或image任何一個更新,就會生成一個新的image,並且基於此新image創建新的pod,同時替換舊的pod。

    即當應用程序代碼發生更改時,容器映像需要更新,但如果構建器映像發生更改,則部署的pod也需要更新。

    Image Streams包括由tag標識的大量的image。應用程序是針對Image Streams構建的。Image Streams可用於在創建新image時自動執行操作。構建和部署可以監視Image Streams,以便在添加新image時接收通知,並分別執行構建或部署。

    OpenShift默認情況下提供了幾個Image Streams,包括許多流行的runtime和frameworks。

    Image Streams tag是指向Image Streams中的image的別名。通常縮寫為istag。它包含一個image歷史記錄,表示為tag曾經指向的所有images的堆棧。

    每當使用特定的istag標記一個新的或現有的image時,它都會被放在歷史堆棧的第一個位置(標記為latest)。之前tag再次指向舊的image。同時允許簡單的回滾,使標籤再次指向舊的image。 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

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

    聚甘新

  • EOS基礎全家桶(十三)智能合約基礎

    簡介

    智能合約是現在區塊鏈的一大特色,而不同的鏈使用的智能合約的虛擬機各不相同,編碼語言也有很大差異。而今天我們開始學習EOS的智能合約,我也是從EOS初期一直開發合約至今,期間踩過無數坑,也在Stack Overflow上提過問(最後自己解決了),在實際生產中也積累了很多經驗,所以我會連續幾周分多次分享合約開發的經驗,今天先來點基礎的。

    一些C++的編程基礎

    EOS就是使用C++開發的,這也為它帶來了諸多好處,而合約也沿用C++作為開發語言,雖然合約中無法直接使用Boost等框架(你可以自己引入,但這也意味着合約會很大,會佔用大量賬號的內存),但是我們還是可以使用很多C++的小型庫,並伴隨着eosio.cdt的發展,融入了更多實用的合約功能。

    如果你之前沒有使用C系列的開發語言做過開發,比如:C語言、C++或者是C#,那麼你需要先學習下C語言的基本語法和數據結構,這裏我不做展開,在我們的系列文章的開篇就介紹了我推薦的Learn EOS – c/c++ 教程英文版,有一定英語基礎的朋友可以直接看這個,其他朋友也可以在網上找一些C++的入門教程看下。

    如果你已經有了一定的C語言基礎,那麼寫合約的話,你會發現需要的基礎也並不多,依葫蘆畫瓢就能寫出各種基礎功能了,所以,你並不需要擔心太多語言上的門檻,畢竟合約只是一個特定環境下運行的程序,你能用到的東西並不會很多。

    CDT選擇

    EOS的早期版本進行合約開發還沒有CDT工具,那時的合約藉助的是源碼中的工具eosiocpp,所以你看2018年的博客,進行合約編譯都是用它,但你現在是見不到了。隨着官方CDT的迭代,在CDT的1.4版本開始被官方推薦使用,CDT後面也經歷了幾個大的版本更新,逐步改善合約編寫方式,更加趨於簡潔、直觀。

    但是不同的CDT版本,也意味着編譯器的不同,所以合約開發也會有所區別,比如一些語法變了,一些庫名稱變了,增加了一些新的標註……

    我們的教程側重還是介紹最新的語法,所以推薦使用1.6以上的版本。我也會盡量在後面的介紹中補充說明老的CDT的寫法,方便大家對照網上其他老博客的合約。

    來個HelloWorld

    學習任何編程,我們都不能少了Mr.HelloWorld,先來給大家打個招呼吧。

    #include <eosio/eosio.hpp>
    
    using namespace eosio;
    
    class [[eosio::contract]] hello : public contract
    {
    public:
        using contract::contract;
    
        [[eosio::action]] void hi(name user)
        {
            print("Hello, ", user);
        }
    };
    

      

    基本合約結構及類型

    hello合約就是一個最簡單的合約了,而且還有一個可調用的action為hi。我們首先還是來介紹下一個合約的程序結構吧。

    • 程序頭

    包含了引入的頭文件、庫文件等,還有全局的命名空間的引入等。

    #include <eosio/eosio.hpp>
    
    using namespace eosio;
    

      

    這裏eosio庫是我們的合約基礎庫,所有和eos相關的類型和方法,都在這個庫裏面,而這個庫裏面eosio.hpp是基礎,包含了contract等的定義,所以所有的合約都要引入。

    【CDT老版本】早期cdt版本中庫名稱不是eosio,而是eosiolib

    默認的,我們引入了eosio的命名空間,因為eosio的所有內容都是在這個命名空間下的,所以我們全局引入,會方便我們後續的代碼編寫。

    • 合約類定義

    其實就是定義了一個class,繼承contract,並通過[[eosio::contract]]標註這個類是一個合約。使用using引入contract也是為了後續代碼可以更簡潔。

    class [[eosio::contract]] hello : public contract{
    public:
        using contract::contract;
    }
    

      

    【CDT老版本】早期cdt版本中直接使用了CONTRACT來定義合約類,比如:CONTRACT hello: public contract {}

    • action定義

    寫一個public的方法,參數盡量用簡單或者是eosio內置的類型定義,無返回值(合約調用無法返回任何結果,除非報錯),然後在用[[eosio::action]]標註這個方法是一個合約action就行。

    注意:action的名稱要求符合name類型的規則,name規則請看下面的常用類型中的說明。

    [[eosio::action]]
    void hi( name user ) {
        print( "Hello, ", user);
    }
    

      

    因為合約無法調試,所以只能通過print來打印信息,或者直接通過斷言拋出異常來進行調試。

    【CDT老版本】早期cdt版本中直接使用ACTION來定義方法,比如:ACTION hi( name user ){}

    • 常用類型
    類型 說明 示例
    name 名稱類型,賬號名、表名、action名都是該類型,只能使用26個小寫字母和1到5的数字,特殊可以使用小數點,總長不超過13。 name("hi") 或者 "hi"_n
    asset 資產類型,Token都是使用該類型,包含了Token符號和小數位,是一個複合類型,字符形式為1.0000 EOS asset(10000, symbol("TADO", 4)就是1.0000 TADO)
    uint64_t 無符號64位整型,主要數據類型,表主鍵、name實質都是改類型 uint64_t amount = 10000000;
    • 內置常用對象或方法

    在合約中,contract基類提供了一些方便的內置對象。

    首先是get_self()或者是_self,這個方法可以獲取到當前合約所在的賬號,比如你把hello合約部署到了helloworld111這個賬號,那麼get_self()就可以獲取到helloworld111。

    然後是get_code()或者是_code,這個方法可以獲取到當前交易請求的action方法名,這個在進行內聯action調用時可以用於判斷入口action。

    最後是get_datastream()或者_ds,這個方法獲取的是數據流,如果你使用的是複雜類型,或者是自定義類型,那麼你無法在方法的參數上直接獲取到反序列化的變量值,你必須自己通過數據流來解析。

    常用的還有獲取當前時間current_time_point(),這個需要引入#include <eosio/transaction.hpp>

    數據持久化

    當然,合約裏面,我們總會有些功能需要把數據存下來,在鏈上持久化存儲。所以我們就需要定義合約表了。

    合約的表存在相應的合約賬號中,可以劃分表範圍(scope),每個表都有一個主鍵,uint64_t類型的,還可以有多個其他索引,表的查詢都是基於索引的。

    這裏先提一句,表數據所佔用的內存,默認是合約賬號的內存,也可以使用其他賬號的,但需要權限,這個以後我們再介紹。

    我們擴展一下hello合約。

    #include <eosio/eosio.hpp>
    #include <eosio/transaction.hpp>
    
    using namespace eosio;
    
    class [[eosio::contract]] hello : public contract
    {
    public:
        using contract::contract;
    
        hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds), friend_table(get_self(), get_self().value)
        {
        }
    
        [[eosio::action]] void hi(name user)
        {
            print("Hello, ", user);
    
            uint32_t now = current_time_point().sec_since_epoch();
    
            auto friend_itr = friend_table.find(user.value);
            if (friend_itr == friend_table.end())
            {
                friend_table.emplace(get_self(), [&](auto &f) {
                    f.friend_name = user;
                    f.visit_time = now;
                });
            }
            else
            {
                friend_table.modify(friend_itr, get_self(), [&](auto &f) {
                    f.visit_time = now;
                });
            }
        }
    
        [[eosio::action]] void nevermeet(name user)
        {
            print("Never see you again, ", user);
    
            auto friend_itr = friend_table.find(user.value);
            check(friend_itr != friend_table.end(), "I don't know who you are.");
    
            friend_table.erase(friend_itr);
        }
    
    private:
        struct [[eosio::table]] my_friend
        {
            name friend_name;
            uint64_t visit_time;
    
            uint64_t primary_key() const { return friend_name.value; }
        };
    
        typedef eosio::multi_index<"friends"_n, my_friend> friends;
    
        friends friend_table;
    };
    

      

    可以看到,我們已經擴充了不少東西了,包括構造函數,表定義,多索引表配置,並完善了原先的hi方法,增加了nevermeet方法。

    我們現在模擬的是這樣一個使用場景,我們遇到一個朋友的時候,就會和他打招呼(調用hi),如果這個朋友是一個新朋友,就會插入一條記錄到我們的朋友表中,如果是一個老朋友了,我們就會更新這個朋友的記錄中的訪問時間。當我們決定不再見這個朋友了,就是絕交了(調用nevermeet),我們就會把這個朋友的記錄刪除。

    • 表定義

    首先我們需要聲明我們的朋友表。定義一個結構體,然後用[[eosio::table]]標註這個結構體是一個合約表。在結構體里定義一個函數名primary_key,返回uint64_t類型,作為主鍵的定義。

    private:
        struct [[eosio::table]] my_friend
        {
            name friend_name;
            uint64_t visit_time;
    
            uint64_t primary_key() const { return friend_name.value; }
        };
    

      

    我們這裏聲明了一個my_friend的表,合約的表名不在這裏定義,所以結構體的名稱不必滿足name的規則。我們定義了兩個字段,friend_name(朋友的名稱)和visit_time(拜訪時間),主鍵我們直接使用了friend_name,這個字段是name類型的,而name類型的實質就是一個uint64_t的類型(所以name的規則那麼苛刻)。

    【CDT老版本】早期cdt版本中直接使用TABLE來定義合約表,比如:TABLE my_friend{}

    • 多索引表配置

    合約里的表都是通過多索引來定義的,這是合約表的結構基礎。所以這裏才是定義表名和查詢索引的地方。

    typedef eosio::multi_index<"friends"_n, my_friend> friends;
    

      

    我們現在只介紹最簡單的單索引的定義,以後再介紹多索引的定義方式,這裏的"friends"_n就是定義表名,所以使用了name類型,之後my_friend是表的結構類型,typedef實質上就是聲明了一個類型別名,名字是friends的類型。

    • 構造函數

    構造函數這裏並不是必須,但是為了我們能在全局直接使用合約表,所以我們要在構造函數進行表對象的實例化。

    public:
        hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds), friend_table(get_self(), get_self().value)
        {
        }
    
    private:
        friends friend_table;
    

      

    這一段是標準合約構造函數,hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds),合約類型實例化時會傳入receiver也就是我們的合約賬號(一般情況下),code就是我們的action名稱,ds就是數據流。

    friend_table(get_self(), get_self().value)這一段就是對我們定義的friend_table變量的實例化,friend_table變量就是我們定義的多索引表的friends類型的實例。在合約里我們就可以直接使用friend_table變量來進行表操作了。實例化時傳遞的兩個參數正是表所在合約的名稱和表範圍(scope),這裏都使用的是當前合約的名稱。

    • 查詢記錄

    查詢有多種方式,也就是多索引表提供了多種查詢的方式,默認的,使用findget方法是直接使用主鍵進行查詢,下次我們會介紹使用第二、第三等索引來進行查詢。find返回的是指針,數據是否存在,需要通過判斷指針是否是指到了表末尾,如果等於表末尾,就說明數據不存在,否則,指針的值就是數據對象。get直接返回的就是數據對象,所以在調用get時,就必須傳遞數據不存在時的錯誤信息。

    auto friend_itr = friend_table.find(user.value);
    if (friend_itr == friend_table.end())
    {
        //數據不存在
    }else
    {
        //數據存在
    }
    

      

    我們在hi方法中先查詢了user是否存在。如果不存在,我們就添加數據,如果存在了,就修改數據中的visit_time字段的值為當前時間。

    • 添加記錄

    多索引的表對象添加記錄使用emplace方法,第一個參數就是內存使用的對象,第二個參數就是添加表對象時的委託方法。

    uint32_t now = current_time_point().sec_since_epoch();
    
    auto friend_itr = friend_table.find(user.value);
    if (friend_itr == friend_table.end())
    {
        friend_table.emplace(get_self(), [&](auto &f) {
            f.friend_name = user;
            f.visit_time = now;
        });
    }
    else
    {
        //數據存在
    }
    

      

    這裏先定義了一個變量now來表示當前時間,正是使用的內置方法current_time_point(),這個還是用了它的sec_since_epoch()方法,是為了直接獲取秒單位的值。

    我們查詢后發現這個user的數據不存在,所以就進行插入操作,內存直接使用的合約賬號的,所以使用get_self(),然後對錶數據對象進行賦值。

    • 修改記錄

    多索引的表對象修改記錄使用modify方法,第一個參數是傳遞需要修改的數據指針,第二個參數是內存使用的對象,第二個參數就是表對象修改時的委託方法。

    friend_table.modify(friend_itr, get_self(), [&](auto &f) {
        f.visit_time = now;
    });
    

      

    我們將查詢到的用戶對象的指針friend_itr傳入,然後內存還是使用合約賬號的,委託中,我們只修改visit_time的值(主鍵是不能修改的)。

    • 刪除記錄
    • 多索引的表對象刪除記錄使用erase方法,只有一個參數,就是要刪除的對象指針,有返回值,是刪除數據后的指針偏移,也就是下一條數據的指針。
    auto friend_itr = friend_table.find(user.value);
    check(friend_itr != friend_table.end(), "I don't know who you are.");
    
    friend_table.erase(friend_itr);
    

      

    我們的示例中,將查詢到的這條數據直接刪除,併為使用變量來接收下一條數據的指針,在連續刪除數據時,你會需要獲取下一條數據的指針,因為已刪除的數據的指針已經失效了。

    編譯

    編譯我們再之前也有過介紹,安裝了eosio.cdt后,我們就有了eosio-cpp命令,進入到合約文件夾中,直接執行以下命令就會在當前目錄生成wasm和abi文件。

    eosio-cpp -abigen hello.cpp -o hello.wasm

    注意:替換命令中使用的hello.cpp為實際合約代碼文件名,而hello.wasm為實際合約的wasm文件名。

    當然,編譯不通過的時候,你就要看看錯誤是什麼了,這可能會考驗一下你的C++功底。

    發布

    決定了要發布的賬號后,記得要購買足夠的內存和抵押足夠的資源。合約的內存消耗我們可以大致這樣估算,看下編譯好了的合約wasm文件有多大,然後乘以10,就是你發布到鏈上大概所需的內存大小了。

    發布合約我們使用cleos set contract命令,其後跟合約賬號名和合約目錄,為了方便,我建議你把合約的目錄名保持和合約文件名一致。

    cleos set contract helloworld111 ./hello -p helloworld111
    

    這裏我們給出的代碼是將hello目錄下的hello合約發布到helloworld111。我這裏的文件夾是hello,裏面的abi和wasm也都是hello,這樣你不用手動指定合約文件了。

    總結

    至此,我想大家應該對合約的編寫有了一個大致的了解了,至少你可以參照着寫個簡單的合約出來了,這其中還有很多技巧和高級用法,我會在後續的文章中繼續和大家分享。

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

    【其他文章推薦】

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

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

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

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

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

    聚甘新

  • 人臉識別和手勢識別應用(face++)開發

    人臉識別和手勢識別應用(face++)開發

    基礎認識

    本項目使用的是face++平台,人臉識別+手勢識別雙確認显示。

    python編程,代碼簡介,方便擴展。

     

    該項目適用於Windows系統和Linux系統,但必須安裝相應的模塊,其中包括

     

    l  Python3  python 庫,邏輯編寫

    l  Pillow   窗口開發實現

    l  opencv-python python的opencv接口

    l  Opencv庫   用於人臉檢測

     

    本次測試是在win 10電腦上

     

    視頻演示:

    https://www.bilibili.com/video/BV1Wk4y1z7H7

     

    安裝python3

    這個網上到處都是資料,找一找就知道啦

    官網:

    https://www.python.org/

     安裝pillow

    該庫用於python做界面開發,詳細參考:https://www.cnblogs.com/dongxiaodong/p/9971974.html

    這個庫一般電腦都自帶有了,可以先不安裝,直接運行代碼。

    如果出現以下錯誤,則必須手動安裝

    ModuleNotFoundError: No module named ‘PIL’

    安裝命令:

    pip install pillow

    安裝opencv-python

    Opencv可以實現人臉檢測、人臉對比識別等功能,但在次只是用它來實現了人臉檢測並做人臉框圖,並沒有更多功能的實現,想要獲取更多功能的學習參考,請訪問:https://www.cnblogs.com/dongxiaodong/p/10134904.html

    pip install opencv-python

    如果出現紅色字體,表示安裝出錯了,必須從新運行安裝命令

     

     Face++

    Face++在項目中用於人臉識別和手勢識別

    系統流程主要為如下:

     

    測試

    (一)  獲取人臉標識

    工程目錄:

     

    l  運行項目,攝像頭將開啟,實時展示所拍攝的畫面

    l  按下空格鍵即可獲取人臉標識,輸出人臉標識和存儲到data文件目錄下

    l  此時按下ESC鍵則退出程序

    l  同一個人的人臉標識很有可能是不一樣的,因為它更多的是基於本次照片計算

     

    (二)  創建人臉庫&人臉標識添加到人臉庫

     

     

     

    l  創建人臉標識庫,標識名自定義,但同一用戶內不可有相同的人臉標識庫

    l  在函數填寫自己賬戶下唯一的人臉庫標識名

     

     

    l  將人臉標識添加到人臉庫中

    l  在函數中填寫人臉庫標識和我們第一步獲取的人臉標識,將人臉標識添加到人臉庫中

    l  人臉庫可以添加多個不同的人臉標識

     

    (三)  人臉庫搜索結果比對

     

    l  修改為我們剛剛所創建的人臉庫,進行接下來的人臉識別查找

    l  運行工程后將開啟攝像頭進行照片實時捕獲識別,並在屏幕中显示識別結果

    l  識別包括人臉識別和手勢識別

    l  只有在人臉識別正確的情況下才會開啟手勢識別

     

    人臉識別失敗

    人臉識別成功,無手勢

    人臉識別成功,手勢為合攏

    人臉識別成功,手勢為打開

     

     

    視頻演示:

    https://www.bilibili.com/video/BV1Wk4y1z7H7

     

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

    【其他文章推薦】

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

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

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

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

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

    聚甘新

  • Linux nohup命令詳解,終端關閉程序依然可以在執行!

    Linux nohup命令詳解,終端關閉程序依然可以在執行!

    大家好,我是良許。

    在工作中,我們很經常跑一個很重要的程序,有時候這個程序需要跑好幾個小時,甚至需要幾天,這個時候如果我們退出終端,或者網絡不好連接中斷,那麼程序就會被中止。而這個情況肯定不是我們想看到的,我們希望即使終端關閉,程序依然可以在跑。

    這時我們就可以使用 nohup 這個命令。

    nohup 命令是英語詞組 no hangup 的縮寫,意思是不掛斷,也就是指程序不退出。這個命令會使程序忽略 HUP 信號,保證程序能夠正常進行。HUP 信號有些人可能比較陌生,它是在終端被中止的時候向它所關聯的進程所發出的信號,進程收到這個信號后就會中止運行。所以如果你不希望進程被這個信號幹掉的話,就可以忽略這個信號。而 nohup 命令做的就是這個事情。

    本文我們將詳細介紹 nohup 命令的具體用法。

    nohup命令基本語法

    nohup 命令的基本語法如下:

    $ nohup command arguments
    

    或者:

    $ nohup options
    

    如果你想要得到更多關於 nohup 的用法介紹,可以查看它的幫助頁面:

    $ nohup --help
    

    如果你需要查看它的版本號,可以使用 --version 選項。

    $ nohup --version
    

    使用nohup命令啟動一個程序

    如果你需要運行一個程序,即使對應的 Shell 被退出后依然保持運行,可以這樣使用 nohup 運行這個程序:

    $ nohup command
    

    當這個程序進行起來之後,這個程序對應的 log 輸出及其錯誤日誌都將被記錄在 nohup.out 文件里,這個文件一般位於家目錄或者當前目錄。

    重定向程序的輸出

    如果我不想把程序的輸出保存在家目錄或者當前目錄,我想保存在我指定的路徑,並且自定義文件名,要怎麼操作?這時我們就可以使用重定向操作 >

    比如,我現在有個腳本 myScript.sh 我想把它的輸出保存在家目錄下的 output 目錄下,文件名為 myOutput.txt ,可以這樣運行:

    $ nohup ./myScript.sh > ~/output/myOutput.txt
    

    使用nohup命令後台啟動一個程序

    如果想讓程序在後台運行,可以加上 & 符號。但這樣運行之後,程序就無影無蹤了。想要讓程序重新回到終端,可以使用 fg 命令。

    這個命令的輸出 log 將保存在 nohup.out 文件里,你可以使用 cat 或其它命令查看。第二行里 8699 這個数字代表這個命令對應的進程號,也就是 pid 。我們可以使用 ps 命令來找到這個進程。

    使用nohup同時運行多個程序

    如果你需要同時跑多個程序,沒必要一個個運行,直接使用 && 符號即可。比如,你想同時跑 mkdir ,ping,ls 三個命令,可以這樣運行:

    $ nohup bash -c 'mkdir files &&
    ping -c 1 baidu.com && ls'> output.txt
    

    終止跑在後台的進程

    上面有提到,nohup 命令結合 & 符號可以使進程在後台運行,即使關閉了終端依然不受影響。這時,如果想要終止這個進程,要怎麼操作呢?

    最簡單的當屬 kill 命令,相信大家用過很多次了。

    $ kill -9 PID
    

    那要如何找到進程對應的 pid 呢?我們可以使用 ps 命令。

    $ ps aux | grep myScript.sh
    

    或者你使用 pgrep 命令也行。

    接下來,再使用 kill 命令就可以終止該進程了。

    $ kill -9 14942
    

    公眾號:良許Linux

    有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

    聚甘新

  • SpringBoot + Mybatis + Redis 整合入門項目

    SpringBoot + Mybatis + Redis 整合入門項目

    這篇文章我決定一改以往的風格,以幽默風趣的故事博文來介紹如何整合 SpringBoot、Mybatis、Redis。

    很久很久以前,森林里有一隻可愛的小青蛙,他邁着沉重的步伐走向了找工作的道路,結果發現許多的招聘要求都要會 Redis。

    小青蛙就想啥是 Redis 呢,為什麼要用 Redis 呢?難道是因為 Mysql 的幣格不夠高嗎,小青蛙點開了收藏已久的網站:十萬個為什麼

    發現原來隨着使用網站的用戶越來越多,表中的數據也越來越多,查詢速度越來越慢。

    MySql 的性能遇到了瓶頸,所以許多網站都用 Redis 作緩存。

     

    然而能作緩存的不僅只有 Redis,還有 Memcache,那為什麼要用 Redis 呢?

    1、性能方面:它們都是將數據存放在內存中,所以性能基本相似。

    2、數據類型方面:Redis 支持五種數據數據類型:字符串、散列、列表、集合、有序集合,而 Memcache 僅僅支持簡單的 key-value。

    3、數據持久化方面:Redis 可以通過 RDB快照、AOF日誌 等方式進行數據持久化,但是 Memcache 不可以。

    4、數據備份方面:Redis  支持 master-slave 主從模式的數據備份。

     

    在了解到許多 Redis 的好處后,小青蛙已經迫不及待的想了解它了。

    為了更好的使用 Redis,了解 Redis 的五種數據類型適應場景是很有必要的。

    1、String 類型:一個 key 對應一個 value,而 value 不僅僅是 String,也可以是数字、甚至是一個序列化對象。

    2、Hash 類型:一個 key 對應 多個 field,一個 field 對應 yige value,實際上該類型最適合存儲序列化對象。

               key 相當於數據庫表名字,field  相當於主鍵,value 也就是序列化對象。

    3、List 類型:簡單的字符串列表,按照插入順序排序,該結構類似於數據結構中的雙向鏈表,可以從頭部插也可以從尾部插。

    4、Set 類型:它是字符串集合,只不過它是無序的且不存在重複的字符串,但是它可以實現 交集、並集、差集。

    5、ZSet 類型:它也是字符串集合,它和 Set 的區別是該集合的元素存在 score 屬性,按照 score 屬性的高低排序。可以應用在排行榜上。

    值得注意的是,不要習慣性的認為 Redis 字符串只能存字符串,實際上,它可以存儲任何序列化后的對象,當然也可以讀出來。

     

    小青蛙知道了 Redis 的五種數據類型應用場景后,迫不及待的想要實踐它了。

    為了知道如何讓它作為緩存,以及如何操作數據,小青蛙打開了珍藏已久的視頻網站來學習:青蛙愛學習

    在該視頻網站上,小青蛙沉迷其中無法自拔,額,呸呸。緩過神來,發現了一個很好的視頻。小青蛙幽默的說:快進我的收藏夾吃灰去吧。

    小青蛙向來都不是一個收藏從未停止,學習從未開始的青蛙。它模仿着視頻建了一個 SpringBoot 項目。

    此時,小青蛙想為什麼要勾選 Lombok 呢,因為它可以簡化類,並且提供了 log 等功能。

    之後小青蛙為了連上 Mysql 和 Redis 就開始配置 application.properties 文件:

    # 數據源配置
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=UTC&characterEncoding=utf-8
    spring.datasource.username=root
    spring.datasource.password=lemon@mango
    
    # Redis 配置
    spring.redis.database=0
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=
    spring.redis.lettuce.pool.max-active=8
    spring.redis.lettuce.pool.max-wait=-1
    spring.redis.lettuce.pool.max-idle=8
    spring.redis.lettuce.pool.min-idle=0
    spring.redis.lettuce.shutdown-timeout=100

    配置好該文件后,需要 redisTemplate 模板 Bean,因為自動配置的 redisTemplate Bean 沒有提供序列化操作:(因為是入門版的,所以這樣最好理解)

    package com.demo.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration
    public class RedisConfig {
        
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
                    template.setConnectionFactory(factory);
                    GenericJackson2JsonRedisSerializer genericJsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
                    template.setKeySerializer(new StringRedisSerializer());
                    template.setValueSerializer(genericJsonRedisSerializer);
                    template.setHashKeySerializer(genericJsonRedisSerializer);
                    template.setHashValueSerializer(genericJsonRedisSerializer);
                    template.afterPropertiesSet();
                    return template;
        }
    }

    至此就整合完成了,小青蛙心想這就完事了?!!!,不信?那就來演示一下:(先建一個簡單的類測試一下)

    package com.test.serviceImpl;
    
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.beans.factory.annotation.Autowired;
    import lombok.extern.slf4j.Slf4j;
    
    @Service
    @Slf4j
    public class JustForTest {
        
        @Autowired
        private RedisTemplate redisTemplate;
        
        public void test(String username) {
            
            if(redisTemplate.hasKey(username)) {
                log.info((String)redisTemplate.opsForValue().get(username));
                log.info("get value from redis");
            }else {
                String password = "password";
                log.info(password);
                log.info("get value from mysql");
                log.info("set value to redis");
                redisTemplate.opsForValue().set(username, password);
            }
            
        }
    
    }
    package com.test;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.test.serviceImpl.JustForTest;
    
    @SpringBootTest
    class TestApplicationTests {
        
        @Autowired
        private JustForTest justFortest;
    
        @Test
        void contextLoads() {
            justFortest.test("username");
        }
    }

    哦嚯,報錯了,原來是 pom.xml 中少了 commons.pool 依賴,咱給它加上:

            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>

    再來一次:

    再再來一次:

    可以看到確實存入 Redis 了,小青蛙便去 Redis 數據庫中看看有沒有:

    事實證明確實整合完畢了,小青蛙仍然表示不解,說好的是SpringBoot、Mybatis、Redis的整合呢,怎麼只看到 Redis 的?

    小青蛙剛這麼想,然後視頻里就說了,心急吃不了熱豆腐,需要慢慢來。緊接着,小青蛙就看到了完整的項目結構:

    為了讓青蛙們只關注有關整合的部分,視頻里僅僅只給出 serviceImpl 中的代碼:

    package com.demo.serviceImpl;
    
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.demo.pojo.SimpleUser;
    import com.demo.dao.SimpleUserDao;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import lombok.extern.slf4j.Slf4j;
    
    @Service
    @Slf4j
    @SuppressWarnings({"rawtypes","unchecked"})
    public class SimpleUserServiceImpl implements UserDetailsService {
    
        @Autowired
        private RedisTemplate redisTemplate;
        @Autowired
        private SimpleUserDao userDao;
        @Override 
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // TODO Auto-generated method stub
            if(redisTemplate.opsForHash().hasKey("user",username)) {
                SimpleUser user = (SimpleUser)redisTemplate.opsForHash().get("user",username);
                return new User(user.getUsername(),user.getPassword(),user.getAuthorities());
            }else {
                SimpleUser user = userDao.findUserByUsername(username);
                if(user != null) {
                    redisTemplate.opsForHash().put("user", "username", user);
                    return new User(user.getUsername(),user.getPassword(),user.getAuthorities());
                }else {
                    throw new UsernameNotFoundException("Username or Password is not correct");
                }
            }
        }
        
        public int addSimpleUser(SimpleUser user) {
            user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
            return userDao.addSimpleUser(user);
        }
    }

    和上面測試的簡單小例子相似,僅僅是把 String 換成了對象。小青蛙對 Mysql 的表結構和Redis中存入的數據比較感心趣:

    由於對 String 類型帶有 ” 符號,所以需要對其進行轉義。

    小青蛙是一個有分享精神的蛙,每次它覺得有價值的東西,它都會分享給它的朋友們,項目地址為:GitHub

    小青娃逐漸的弄懂了怎麼去進行整合,但是它還是不太明白為什麼這樣就能整合,也就是 know how but don’t konw why!

    小青蛙知道:萬事開頭難,但它不知道的是,後面也很難…從此,小青蛙踏上了探尋源碼的道路!呱呱呱……

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

    聚甘新

  • 基於NACOS和JAVA反射機制動態更新JAVA靜態常量非@Value註解

    基於NACOS和JAVA反射機制動態更新JAVA靜態常量非@Value註解

    1.前言

    項目中都會使用常量類文件, 這些值如果需要變動需要重新提交代碼,或者基於@Value註解實現動態刷新, 如果常量太多也是很麻煩; 那麼 能不能有更加簡便的實現方式呢?

    本文講述的方式是, 一個JAVA類對應NACOS中的一個配置文件,優先使用nacos中的配置,不配置則使用程序中的默認值;

    2.正文

    nacos的配置如下圖所示,為了滿足大多數情況,配置了 namespace命名空間和group;

     

     

     新建個測試工程 cloud-sm.

    bootstrap.yml 中添加nacos相關配置;

    為了支持多配置文件需要注意ext-config節點,group對應nacos的添加的配置文件的group; data-id 對應nacos上配置的data-id

    配置如下:

    server:
      port: 9010
      servlet:
        context-path: /sm
    spring:
      application:
        name: cloud-sm
      cloud:
        nacos:
          discovery:
            server-addr: 192.168.100.101:8848 #Nacos服務註冊中心地址
            namespace: 1
          config:
            server-addr: 192.168.100.101:8848 #Nacos作為配置中心地址
            namespace: 1
            ext-config:
              - group: TEST_GROUP
                data-id: cloud-sm.yaml
                refresh: true
              - group: TEST_GROUP
                data-id: cloud-sm-constant.properties
                refresh: true

    接下來是本文重點:

    1)新建註解ConfigModule,用於在配置類上;一個value屬性;

    2)新建個監聽類,用於獲取最新配置,並更新常量值

    實現流程:

    1)項目初始化時獲取所有nacos的配置

    2)遍歷這些配置文件,從nacos上獲取配置

    3)遍歷nacos配置文件,獲取MODULE_NAME的值

    4)尋找配置文件對應的常量類,從spring容器中尋找 常量類 有註解ConfigModule 且值是 MODULE_NAME對應的

    5)使用JAVA反射更改常量類的值

    6)增加監聽,用於動態刷新

     

    import org.springframework.stereotype.Component;
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    @Component
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    public @interface ConfigModule {
        /**
         *  對應配置文件裏面key為( MODULE_NAME ) 的值
         * @return
         */
        String value();
    }
    import com.alibaba.cloud.nacos.NacosConfigProperties;
    import com.alibaba.druid.support.json.JSONUtils;
    import com.alibaba.nacos.api.NacosFactory;
    import com.alibaba.nacos.api.PropertyKeyConst;
    import com.alibaba.nacos.api.config.ConfigService;
    import com.alibaba.nacos.api.config.listener.Listener;
    import com.alibaba.nacos.api.exception.NacosException;
    import com.alibaba.nacos.client.utils.LogUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.PostConstruct;
    import java.io.IOException;
    import java.io.StringReader;
    import java.lang.reflect.Field;
    import java.util.Enumeration;
    import java.util.List;
    import java.util.Map;
    import java.util.Properties;
    import java.util.concurrent.Executor;
    
    /**
     * nacos 自定義監聽
     *
     * @author zch
     */
    @Component
    public class NacosConfigListener {
        private Logger LOGGER = LogUtils.logger(NacosConfigListener.class);
        @Autowired
        private NacosConfigProperties configs;
        @Value("${spring.cloud.nacos.config.server-addr:}")
        private String serverAddr;
        @Value("${spring.cloud.nacos.config.namespace:}")
        private String namespace;
        @Autowired
        private ApplicationContext applicationContext;
        /**
         * 目前只考慮properties 文件
         */
        private String fileType = "properties";
        /**
         * 需要在配置文件中增加一條 MODULE_NAME 的配置,用於找到對應的 常量類
         */
        private String MODULE_NAME = "MODULE_NAME";
    
        /**
         * NACOS監聽方法
         *
         * @throws NacosException
         */
        public void listener() throws NacosException {
            if (StringUtils.isBlank(serverAddr)) {
                LOGGER.info("未找到 spring.cloud.nacos.config.server-addr");
                return;
            }
            Properties properties = new Properties();
            properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr.split(":")[0]);
            if (StringUtils.isNotBlank(namespace)) {
                properties.put(PropertyKeyConst.NAMESPACE, namespace);
            }
    
            ConfigService configService = NacosFactory.createConfigService(properties);
            // 處理每個配置文件
            for (NacosConfigProperties.Config config : configs.getExtConfig()) {
                String dataId = config.getDataId();
                String group = config.getGroup();
                //目前只考慮properties 文件
                if (!dataId.endsWith(fileType)) continue;
    
                changeValue(configService.getConfig(dataId, group, 5000));
    
                configService.addListener(dataId, group, new Listener() {
                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        changeValue(configInfo);
                    }
    
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }
                });
            }
        }
    
        /**
         * 改變 常量類的 值
         *
         * @param configInfo
         */
        private void changeValue(String configInfo) {
            if(StringUtils.isBlank(configInfo)) return;
            Properties proper = new Properties();
            try {
                proper.load(new StringReader(configInfo)); //把字符串轉為reader
            } catch (IOException e) {
                e.printStackTrace();
            }
            String moduleName = "";
            Enumeration enumeration = proper.propertyNames();
            //尋找MODULE_NAME的值
            while (enumeration.hasMoreElements()) {
                String strKey = (String) enumeration.nextElement();
                if (MODULE_NAME.equals(strKey)) {
                    moduleName = proper.getProperty(strKey);
                    break;
                }
            }
            if (StringUtils.isBlank(moduleName)) return;
            Class curClazz = null;
            // 尋找配置文件對應的常量類
            // 從spring容器中 尋找類的註解有ConfigModule 且值是 MODULE_NAME對應的
            for (String beanName : applicationContext.getBeanDefinitionNames()) {
                Class clazz = applicationContext.getBean(beanName).getClass();
                ConfigModule configModule = (ConfigModule) clazz.getAnnotation(ConfigModule.class);
                if (configModule != null && moduleName.equals(configModule.value())) {
                    curClazz = clazz;
                    break;
                }
            }
            if (curClazz == null) return;
            // 使用JAVA反射機制 更改常量
            enumeration = proper.propertyNames();
            while (enumeration.hasMoreElements()) {
                String key = (String) enumeration.nextElement();
                String value = proper.getProperty(key);
                if (MODULE_NAME.equals(key)) continue;
                try {
                    Field field = curClazz.getDeclaredField(key);
                    //忽略屬性的訪問權限
                    field.setAccessible(true);
                    Class<?> curFieldType = field.getType();
                    //其他類型自行拓展
                    if (curFieldType.equals(String.class)) {
                        field.set(null, value);
                    } else if (curFieldType.equals(List.class)) { // 集合List元素
                        field.set(null, JSONUtils.parse(value));
                    } else if (curFieldType.equals(Map.class)) { //Map
                        field.set(null, JSONUtils.parse(value));
                    }
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    LOGGER.info("設置屬性失敗:{} {} = {} ", curClazz.toString(), key, value);
                }
            }
        }
    
        @PostConstruct
        public void init() throws NacosException {
            listener();
        }
    }

     3.測試

    1)新建常量類Constant,增加註解@ConfigModule(“sm”),盡量測試全面, 添加常量類型有 String, List,Map

    @ConfigModule("sm")
    public class Constant {
    
        public static volatile String TEST = new String("test");
    
        public static volatile List<String> TEST_LIST = new ArrayList<>();
        static {
            TEST_LIST.add("默認值");
        }
        public static volatile Map<String,Object> TEST_MAP = new HashMap<>();
        static {
            TEST_MAP.put("KEY","初始化默認值");
        }
        public static volatile List<Integer> TEST_LIST_INT = new ArrayList<>();
        static {
            TEST_LIST_INT.add(1);
        }
    }

    2)新建個Controller用於測試這些值

    @RestController
    public class TestController {
    
        @GetMapping("/t1")
        public Map<String, Object> test1() {
            Map<String, Object> result = new HashMap<>();
    
            result.put("string" , Constant.TEST);
            result.put("list" , Constant.TEST_LIST);
            result.put("map" , Constant.TEST_MAP);
            result.put("list_int" , Constant.TEST_LIST_INT);
            result.put("code" , 1);
            return result;
        }
    }

    3)當前nacos的配置文件cloud-sm-constant.properties為空

     4)訪問測試路徑localhost:9010/sm/t1,返回為默認值

    {
        "code": 1,
        "string": "test",
        "list_int": [
            1
        ],
        "list": [
            "默認值"
        ],
        "map": {
            "KEY": "初始化默認值"
        }
    }

    5)然後更改nacos的配置文件cloud-sm-constant.properties;

     6)再次訪問測試路徑localhost:9010/sm/t1,返回為nacos中的值

    {
        "code": 1,
        "string": "12351",
        "list_int": [
            1,
            23,
            4
        ],
        "list": [
            "123",
            "sss"
        ],
        "map": {
            "A": 12,
            "B": 432
        }
    }

    4.結語

    這種實現方式優點如下:

    1)動態刷新配置,不需要重啟即可改變程序中的靜態常量值

    2)使用簡單,只需在常量類上添加一個註解

    3)避免在程序中大量使用@Value,@RefreshScope註解

     不足:

    此代碼是個人業餘時間的想法,未經過生產驗證,實現的數據類型暫時只寫幾個,其餘的需要自行拓展

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

    聚甘新