標籤: USB CONNECTOR

  • 二叉搜索樹BST(C語言實現可用)

    二叉搜索樹BST(C語言實現可用)

    1:概述

    搜索樹是一種可以進行插入,搜索,刪除等操作的數據結構,可以用作字典或優先級隊列。二叉搜索樹是最簡單的搜索樹。其左子樹的鍵值<=根節點的鍵值,右子樹的鍵值>=根節點的鍵值。

    如果共有n個元素,那麼每次操作需要的O(log n)的時間.

     

     

     

    常用知識點

    • 滿二叉樹 : 一棵深度為k,且有2^k-1個節點的二叉樹,稱為滿二叉樹。這種樹的特點是每一層上的節點數都是最大節點數。
    • 完全二叉樹 : 而在一棵二叉樹中,除最後一層外,若其餘層都是滿的,並且最後一層要麼是滿的,要麼在右邊缺少連續若干節點,則此二叉樹為完全二叉樹。具有n個節點的完全二叉樹的深度為floor(log2n)+1。深度為k的完全二叉樹,至少有2^(k-1)個恭弘=叶 恭弘子節點,至多有2^k-1個節點。

    2.基本操作

    1. 查找(search)
    2. 插入(insert)
    3. 刪除(remove)

    3:操作原理

      

    查找

    假設查找的值為x,從根節點的值開始比對,如果小於根節點的值,則往左兒子繼續查找,如果大於根節點的值,則往右兒子繼續查找.依次類推.直到當前節點的值等於要查找的值.

     

      以查找數值10為例

    插入

    按照查找的步驟即可找到插入值應該在的位置

     

     

    以插入數值6為例

    刪除:

    有四種情況:

    1: // 當前節點無左節點 ,右字節點7覆蓋5, 

    : 3: // 當前節點無右節點 ,右字節點7覆蓋5, 

     

     : 4: // 刪除節點5的左節點沒有右節點, 只需要8作為3的右節點 ,3節點覆蓋5

     

     

    : 2:  如果以上3中情況都沒有,只需要尋找當前節點的左節點的所有字節點的最大值,用最大值填充5節點 4填充5

     

     

     

     

    5:完整代碼

    #include <stdio.h> 
    #include <stdlib.h>
    struct TNode{
        int data;
        struct TNode *lt;
        struct TNode *rt;    
    };
    struct TNode* insrtTree(struct TNode *t,int key,int i);
    void printTree(struct TNode *root);
    struct TNode* delTree(struct TNode* t,int key);
    int find(struct TNode* t,int key); 
    int arr[1000]={0};
    int main(){
        int n,m;
        int i,t;
        scanf("%d%d",&n,&m); 
        struct TNode *root=NULL;
        for(i=0;i<n;i++){
            scanf("%d",&arr[i]); 
            root=insrtTree(root,arr[i],i);
        }
        //t=arr[m-1];
        
        /*
        if(arr[m-1]==0){
            printf("Right child");
        }else{
            printf("Light child");
        }*/
        root=delTree(root,10);
        printTree(root);
        return 0;
    }
    
    int find(struct TNode* pt,int key){
        if(pt==NULL)return NULL;
        else if(pt->data==key)return 1;
        else if(pt->data>key) return find(pt->lt,key);
        else if(pt->data<key) return find(pt->rt,key);
    }
    // 刪除節點 
    struct TNode* delTree(struct TNode* pt,int key){
        if(pt==NULL)return NULL;
        else if(pt->data>key) pt->lt=delTree(pt->lt,key);//尋找左節點 
        else if(pt->data<key) pt->rt=delTree(pt->rt,key);//尋找右節點
        //  找到節點 處理四種情況  
        else if(pt->lt==NULL){ // 當前節點無左節點 
            struct TNode* curt=pt->rt;
            free(pt);
            return curt;
        }else if(pt->rt==NULL){// 當前節點無右節點 
            struct TNode* curt=pt->lt;
            free(pt);
            return curt;
        }else if(pt->lt->rt==NULL){// 當前節點的左節點無右節點 
            struct TNode* curt=pt->lt;
            curt->rt=pt->rt;
            free(pt);
            return curt;
        }else{ 
        // 以上不滿足就把左兒子的子孫中最大的節點, 即右子樹的右子樹的...右子樹, 
        //提到需要刪除的節點位置
                struct TNode* p;
                for(p=pt->lt;p->rt->rt!=NULL;p=p->rt);
                struct TNode* curt=p->lt;
                p->rt=curt->rt;
                curt->lt=pt->lt;
                curt->rt=pt->rt;
                free(p);
                return curt;
        }
        return pt;
    }
    struct TNode* insrtTree(struct TNode *t,int key,int i){
        if(t==NULL){ //處理第一個節點 以及子節點為NULL情況 
            t=(struct TNode*)malloc(sizeof(struct TNode));
            t->lt=t->rt=NULL;
            t->data=key;
            return t;
        }
        if(t->data>key){// 插入左子樹情況 
             arr[i]=1;
            t->lt=insrtTree(t->lt,key,i);
        }else{         // 插入右子樹情況
            arr[i]=0;
            t->rt=insrtTree(t->rt,key,i);
        }
        return t;
    }
    void printTree(struct TNode *root){
        if(root==NULL)return;
        printf("%d ",root->data);
        printTree(root->lt);
        printTree(root->rt);
    }

     

    說明: 本身學習了 https://blog.csdn.net/li_l_il/article/details/88677927 但是完善了代碼 

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

    【其他文章推薦】

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

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

  • Ansibile之playbook初識

    Ansibile之playbook初識

      一、playbook簡介

      ansiblie的任務配置文件被稱為playbook,俗稱“劇本”,每一個劇本(playbook)中都包含了一系列的任務,這每個任務在ansible中又被稱為“戲劇”(play),一個劇本中包含多齣戲劇。。

      前文我們了解了ansible有兩種執行方式ad-hoc和ansible-playbook,ad-hoc主要用於臨時命令的執行,而playbook我們可以理解為ad-hoc的集合,有點類似shell腳本,ad-hoc就相當於shell腳本里的某條任務語句,playbook就相當於整個shell腳本。playbook是由一個或多個“play”組成的列表,play的主要功能在於將預定義的一組主機,裝扮成事先通過ansible中的task定義好的角色。task實際是調用ansible的一個模塊,將多個play組織在一個playbook中,即可以讓他們聯合起來,按事先編排的機制執行預定義的動作。

     如以上圖示,用戶可以把多條任務(ad-hoc任務)寫到playbook中,用戶用ansible-playbook命令調用執行編排好的playbook,ansible會讀取playbook中的每一條play和task,並按照playbook中的順序從上至下依次執行,ansible會調用每個task中定義的模塊去依次執行相應的任務,並按照playbook中指定的主機去主機清單里匹配對應的主機,然後通過ssh認證,把編譯好的相應的任務文件發送到對應的主機或網絡設備上執行,最後返回執行的狀態。

      二、YAML簡介

      playbook採用yaml語言編寫,yaml是一個可讀性高的用來表達資料序列格式的語言,它參考了其他很多種語言,包括:XML、C語言、python、perl以及电子郵箱格式RFC2822等。Clark Evans在2001年首次發表了這種語言,另外Ingy döt Net與Oren Ben-Kiki也是這語言的共同設計者。YAML( YAML Ain’t Markup Language),即yaml不是標記語言。不過在開發這種語言時,yaml的意思其實是:”Yet Another Markup Language”(仍是一種標記語言)

      ymal特性

      1)YAML的可讀性好

      2)YAML和腳本語言的交互性好

      3)YAML使用實現語言的數據類型

      4)YAML有一個一致的信息模板

      5)YAML易於實現,可以基於流程處理,表達能力強,擴展性好

    更多的內容及規範請參考官方文檔

      三、playbook語法簡介

      1)需要以“—”(3個減號)開始,且需頂行首寫。另外還有選擇性的連續三個點號(…)用來表示文件的結尾。

      2)次行開始正常寫playbook的內容,建議次行寫該playbook的功能,當然不寫也是可以的。

      3)使用“#”號註釋代碼。

      4)縮進必須統一,不能空格tab混用。

      5)縮進的級別必須是一致的,同樣的縮進代表同樣級別,程序判別配置的級別是通過縮進結合換行來實現的。

      6)YAML文件內容和Linux系統大小寫判斷方式一直,區分大小寫(大小寫敏感),k/v的值均大小寫敏感。

      7)k/v的值可同行寫也可換行寫。同行使用“:”分隔,換行寫需要以“-”分隔。

      8)v可以是字符串,也可以另外一個列表,當然也可以是字典。

      9)一個完整的代碼塊功能最少需要有name:xxx(對任務的描述)。

      10)一個name只能包括一個task

      11)yaml文件擴展名通常為yml或yaml

    list:列表,其所有元素均使用“-”開頭

    示例:

    ---
    # A list of tasty fruits
    
    - apple
    - orange
    - strawberry
    - mango
    ~           

    dictionary:字典,通常由多個key與value構成

    示例:

    ---
    #An employee record
    name: example developer
    job: developer
    skill: elite  

    當然也可以將key:value放置於{}中進行表示,用“,”分隔多個key:value

    示例:

    ---
    #An employee record
    {name: example developer,job: developer, skill: elite}
    ~                                                         

      YAML的語法和其他高階語言類似,並且可以簡單表達清單、散列表、標量等數據結構。其結構(Structure)通過空格來展示,序列(Sequence)里的項用”-“來代表,Map里的鍵值對用”:”分隔。

    示例:

    ---
    name: John Smith
    age: 41
    gender: Male
    spouse:
      name: Jane Smith
      age: 37
      gender: Female
    children:
      - name: Jimmy Smith
        age: 17
        gender: Male
      - name: Jenny Smith
        age: 13
        gender: Female
    ~                    
    

      四、playbook核心元素

      1)hosts  :指定執行任務的遠程主機列表(主機清單定義的主機組或單個主機,支持前面的說的主機模式匹配)

      2)tasks  :任務集

      3)varniables  :內置變量或自定義變量在playbook中調用

      4)templates  :模板,可替換模板文件中的變量並實現一些簡單邏輯的文件

      5)handlers  和  notity結合使用,由特定條件出發的操作,滿足條件方才執行,否則不執行

      6)tags標籤  :給指定的任務貼上標籤,我們在執行playbook的時候可以根據標籤選擇性的挑選部分代碼執行,如 ansible-playbook -t tagsname useradd.yml  ,這條命令的意思就是在useradd.yml中挑選標籤名為tagsname的任務執行

       五、playbook基礎組件

      1)hosts:

        playbook中的每一個play的目的都是為了讓特定主機以某個指定的用戶身份執行任務。hosts用於指定要執行任務的主機,須事先定義在主機清單中。hosts指定主機的形式同樣支持像主機清單中定義的那樣,支持通配,支持主機模式匹配與或非,支持IP地址,當然也支持混合匹配與或非。

    示例:在websers組,但不再dbsers組,可以這樣定義hosts

    ---
    - hosts: websers:!dbsers
    

      2)remote_user:可用於host和task中,也可以通過指定其通過sudo的方式在遠程執行任務,其可用於play全局或某個任務;此外,甚至可以在sudo時使用用sudo_user指定sudo時切換的用戶,如下所示

    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: test connection
          ping:
          remote_user: qiuhom
          sudo: yes
          sudo_user: qiuping
    

      說明:默認sudo 為root,上例指定了sudo_user 為qiuping,上述任務同sudo -u qiuping ping xxxx(代表某主機)命令一樣的意思,當然在使用sudo 時 我們還需要在目標主機上對qiuhom授權,要讓qiuhom這個用戶具有代表qiuping的權限去執行ping命令。

      3)task列表和action:play的主體部分是task list,task list 中的各任務按次序逐個在hosts中指定的所有主機上執行,即在所有主機上完成第一個任務后,再開始第二個任務;task的目的是使用指定的參數執行模塊,而在模塊參數中可以使用變量,模塊執行時是冪等的,這意味着多次執行時是安全的,其結果均一致;每個task都應該有其name,用於playbook的執行結果輸出,建議其內容能清晰地描述任務步驟,如未提供name,則action的結果將用於輸出。

      tasks:任務列表,它有兩種格式如下

        (1)action: module arguments

        (2)module: arguments        ##建議使用

       注意:shell模塊和command模塊後面跟的是命令,而非key=value

    如果某項任務的狀態在運行後為changed時,可通過“notify”通知給相應的handlers;當然任務可以通過“tags”打標籤,可以在ansible-playbook命令上使用-t指定進行指定其標籤名調用。

    示例:

    [qiuhom@test ~]$cat test.yml 
    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: test connection
          ping:
          remote_user: qiuhom
          sudo: yes
          sudo_user: qiuping 
          tags: test
        - name: test command
          shell: /bin/ls /home/qiuhom/
    [qiuhom@test ~]$ansible-playbook -t test test.yml 
    

      說明:用-t 指定標籤名,表示只運行所指定標籤所在的任務,當然同名的標籤可以在多條任務中,一個任務也可以有多個標籤。

    如果命令或腳本的退出碼不為零,可用使用如下方式忽略或跳過繼續執行以下代碼

    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: run this command and ignore the result
          shell: /usr/sbin/ip addr show eth0 || /bin/true
        - name: run this command and ignore the result
          shell: /usr/sbin/ip addr show eth0
          ignore_errors: True
    

      說明:兩種方式都可以跳過出錯的命令而不打斷playbook,繼續執行以下的代碼,前者使用的短路或的特性,後者使用ignore_errors參數來控制

      六、playbook運行的方式

    ansible-playbook <filename.yml> ... [options]
    

    常用選項:

      -C , –check  : 只檢查可能會發生的改變,但不真正執行操作,相當於空跑一遍playbook,測試下是否和自己預想的結果一樣,但它不會真正的去遠端主機上執行。常用於測試寫的playbook語法是否有誤。

      –list-hosts  :列出playbook指定運行任務所匹配的主機

      –list-tags    :列出playbook中所有標籤名稱列表

      –list-tasks  :列出playbook中所有任務名稱及標籤名稱

      –limit 主機列表   :只針對指定主機列表中的主機執行當前playbook(指定主機列表必須是在playbook里定義的主機列表範圍內)

      -v,-vv,-vvv       :  显示執行playbook的過程,-v,显示較簡單,-vv显示較詳細,-vvv显示整個過程(非常詳細)

    [root@test ~]#cat test.yml 
    ---
    - hosts: websers
      remote_user: root
    
      tasks:
        - name: run this command 
          shell: hostname
          tags: hostname
          ignore_errors: True
        - name: show ip addr
          shell: /sbin/ip addr show
          tags: showip
    [root@test ~]#ansible-playbook test.yml --list-hosts
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
        pattern: [u'websers']
        hosts (2):
          192.168.0.128
          192.168.0.218
    [root@test ~]#ansible-playbook test.yml --list-tags
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
          TASK TAGS: [hostname, showip]
    [root@test ~]#ansible-playbook test.yml --list-tasks
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
        tasks:
          run this command  TAGS: [hostname]
          show ip addr      TAGS: [showip]
    [root@test ~]#ansible-playbook test.yml --limit 192.168.0.218
    
    PLAY [websers] ********************************************************************************************************
    
    TASK [Gathering Facts] ************************************************************************************************
    ok: [192.168.0.218]
    
    TASK [run this command] ***********************************************************************************************
    changed: [192.168.0.218]
    
    TASK [show ip addr] ***************************************************************************************************
    changed: [192.168.0.218]
    
    PLAY RECAP ************************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#ansible-playbook test.yml --limit 192.168.0.218 -v
    Using /etc/ansible/ansible.cfg as config file
    
    PLAY [websers] ********************************************************************************************************
    
    TASK [Gathering Facts] ************************************************************************************************
    ok: [192.168.0.218]
    
    TASK [run this command] ***********************************************************************************************
    changed: [192.168.0.218] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.002139", "end": "2019-11-16 23:11:02.996962", "rc": 0, "start": "2019-11-16 23:11:02.994823", "stderr": "", "stderr_lines": [], "stdout": "localhost.localdomain", "stdout_lines": ["localhost.localdomain"]}
    
    TASK [show ip addr] ***************************************************************************************************
    changed: [192.168.0.218] => {"changed": true, "cmd": "/sbin/ip addr show", "delta": "0:00:00.002604", "end": "2019-11-16 23:11:03.733004", "rc": 0, "start": "2019-11-16 23:11:03.730400", "stderr": "", "stderr_lines": [], "stdout": "1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n    inet6 ::1/128 scope host \n       valid_lft forever preferred_lft forever\n2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\n    link/ether 00:0c:29:e8:f6:7b brd ff:ff:ff:ff:ff:ff\n    inet 192.168.0.218/24 brd 192.168.0.255 scope global eth0\n    inet6 fe80::20c:29ff:fee8:f67b/64 scope link \n       valid_lft forever preferred_lft forever\n3: pan0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN \n    link/ether d2:7a:38:cf:27:60 brd ff:ff:ff:ff:ff:ff", "stdout_lines": ["1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN ", "    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00", "    inet 127.0.0.1/8 scope host lo", "    inet6 ::1/128 scope host ", "       valid_lft forever preferred_lft forever", "2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000", "    link/ether 00:0c:29:e8:f6:7b brd ff:ff:ff:ff:ff:ff", "    inet 192.168.0.218/24 brd 192.168.0.255 scope global eth0", "    inet6 fe80::20c:29ff:fee8:f67b/64 scope link ", "       valid_lft forever preferred_lft forever", "3: pan0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN ", "    link/ether d2:7a:38:cf:27:60 brd ff:ff:ff:ff:ff:ff"]}
    
    PLAY RECAP ************************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#
    

      說明:–limit 所指定的主機必須是在playbook中所指定的主機範圍內。

       七、playbook vs shell scripts

      1)shell腳本如下:

    #!/bin/bash
    # 安裝Apache
    yum install --quiet -y httpd
    # 複製配置文件
    cp /tmp/httpd.conf /etc/httpd/conf/httpd.conf
    cp/tmp/vhosts.conf /etc/httpd/conf.d/
    # 啟動Apache,並設置開機啟動
    service httpd start
    chkconfig httpd on
    

      2)playbook

    ---
    - hosts: websers
      remote_user: root
    
      tasks:
        - name: create apache group
          group: name=apache gid=80 system=yes
        - name: create apache user
          user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin home=/var/www/html 
        - name: install httpd
          yum: name=httpd
        - name: copy config file
          copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
        - name: copy config 2 file
          copy: src=/tmp/vhosts.conf dest=/etc/httpd/conf.d/
        - name: start httpd service
          service: name=httpd state=started enabled=yes     
    

      說明:兩者都是實現同樣的目的,很明顯playbook的優勢要比腳本的優勢多,playbook 可以針對很多台主機進行任務執行,而腳本只可以在某一台主機上執行;腳本重複執行沒有冪等性,很有可能帶來很多錯誤,而playbook卻不會有這樣的苦惱。

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

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

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

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

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

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

  • 快速搭建Jenkins集群

    快速搭建Jenkins集群

    關於Jenkins集群

    在Jenkins上同時執行多個任務時,單機性能可能達到瓶頸,使用Jenkins集群可以有效的解決此問題,讓多台機器同時處理這些任務可以將壓力分散,對單機版Jenkins的單點故障的隱患也有分散作用,今天就來實戰快速搭建Jenkins集群,Jenkins版本是2.190.2;

    如何做到快速搭建集群

    通過Docker可以省去大部分準備工作,您只需在Linux電腦上安裝docker,在輔以少量命令和操作即可完成集群搭建;

    環境信息

    本次實戰的環境一共要用三台電腦,它們的設置都是一樣的,如下:

    1. 操作系統:CentOS Linux release 7.6.1810
    2. 防火牆關閉
    3. docker:1.13.1

    三台電腦的信息如下:
    | 主機名 | IP地址 | 作用 |
    |–|–|–|
    | master | 192.168.133.131 | Jenkins集群的master節點,提供web服務 |
    | agent1 | 192.168.133.132 | Jenkins集群的一號工作接節點,標籤是maven |
    | agent2 | 192.168.133.133 | Jenkins集群的二號工作接節點,標籤是gradle |

    建議agent2節點的內存大於4G,因為下一篇的實戰操作會用agent2編譯構建spring-framework,對內存的需求略大;

    準備工作

    1. 後面的所有操作都是root賬號;
    2. 在每台電腦上創建文件夾/usr/local/jenkins

      創建Jenkins的master

    3. 登錄master機器,執行以下命令:
    docker run \
      -u root \
      -idt \
      --name master \
      -p 8080:8080 \
      -p 50000:50000 \
      -v /usr/local/jenkins:/var/jenkins_home \
      -v /var/run/docker.sock:/var/run/docker.sock \
      jenkinsci/blueocean:1.19.0
    1. 執行docker logs master,會在控制台显示jenkins的登錄秘鑰,如下圖紅框所示:
    2. 瀏覽器輸入地址: ,显示Jenkins登錄頁面,如下圖所示,在紅框位置輸入剛才複製的登錄秘鑰即可登錄:
    3. 選擇安裝推薦的插件
    4. 靜候插件在線安裝完成:
    5. 接下來是創建管理員和使用實例url的操作,這裏就不多說了,您按實際情況自行斟酌;

      至此,Jenkins的master已經搭建好,接下來將agent1和agent2作為工作節點加入集群;

      加入agent1

    6. 在Jenkins網頁上新增節點,操作如下圖,先進入節點管理頁面:
    7. 如下圖,新增一個節點,名為agent1
    8. 接下來的節點詳情信息如下圖,注意四個紅框中的內容要和圖中保持一致:
    9. 保存成功後會显示機器列表,如下圖,圖標上的紅叉表示機器不在線(此時agent1還沒有接入),點擊紅框:
    10. 如下圖所示,紅框中的命令就是agent1的啟動命令,執行該命令的機器會以agent1的身份加入集群:
    11. 注意上圖紅框中的agent.jar是個名為agent.jar的文件的下載鏈接,將此文件下載到agent1電腦的/usr/local/jenkins目錄下;
    12. ssh登錄agent1電腦,執行以下命令,即可將agent1加入Jenkins集群:
    docker run \
      -u root \
      -idt \
      --name agent \
      -v /usr/local/jenkins:/usr/local/jenkins \
      bolingcavalry/openjdk-with-sshpass:8u232 \
      java -jar /usr/local/jenkins/agent.jar \
      -jnlpUrl http://192.168.133.131:8080/computer/agent1/slave-agent.jnlp \
      -secret 44c3e8d1531754b8655b53294bbde6dd99b3aaa91a250092d0d3425534ae1058 \
      -workDir "/usr/local/jenkins"

    上述命令中的後半部分,即java -jar ……就是前面圖片紅框中的agent1啟動命令,唯一要改變的是將agent.jar改成絕對路徑/usr/local/jenkins/agent.jar

    1. 上述命令的鏡像是bolingcavalry/openjdk-with-sshpass:8u232,其Dockerfile內容如下,可見非常簡單,就是OpenJDK鏡像裏面安裝了sshpass,這樣的容器可以在執行ssh命令時帶上遠程機器的密碼,而不用等待用戶輸入密碼,這樣便於shell腳本執行ssh命令:
    FROM openjdk:8u232
    
    ARG DEBIAN_FRONTEND=noninteractive
    RUN apt-get update && apt-get install --assume-yes sshpass
    1. 去Jenkins的網頁上查看節點列表,如下圖,可見agent1已經成功加入:

      加入agent2

      agent2加入集群的方式和agent1大部分是一樣的,只有以下兩點要注意:

    2. 在Jenkins頁面上創建節點,名稱是agent2
    3. agent2的標籤是gradle,如下圖紅框所示:
    4. 此時agent2也加入成功:

      至此,Jenkins集群搭建完成,這兩個節點帶有不同的標籤,下一篇文章中,我們在這個集群環境創建pipeline任務,並通過標籤被分配到不同的節點上,實現多節點并行執行;

      歡迎關注公眾號:程序員欣宸

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

    【其他文章推薦】

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

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 數據結構之隊列and棧總結分析

    一、前言:

      數據結構中隊列和棧也是常見的兩個數據結構,隊列和棧在實際使用場景上也是相輔相成的,下面簡單總結一下,如有不對之處,多多指點交流,謝謝。

    二、隊列簡介

      隊列顧名思義就是排隊的意思,根據我們的實際生活不難理解,排隊就是有先後順序,先到先得,其實在程序數據結構中的隊列其效果也是一樣,及先進先出。

         隊列大概有如下一些特性:

         1、操作靈活,在初始化時不需要指定其長度,其長度自動增加(默認長度為32)

            注:在實際使用中,如果事先能夠預估其長度,那麼在初始化時指定長度,可以提高效率

            2、泛型的引入,隊列在定義時可以指定數據類型避免裝箱拆箱操作

         3、存儲數據滿足先進先出原則

           

       c#中有關隊列的幾個常用方法:

      • Count:Count屬性返回隊列中元素個數。
      • Enqueue:Enqueue()方法在隊列一端添加一個元素。
      • Dequeue:Dequeue()方法在隊列的頭部讀取和刪除元素。如果在調用Dequeue()方法時,隊列中不再有元素,就拋出一個InvalidOperationException類型的異常。
      • Peek:Peek()方法從隊列的頭部讀取一個元素,但不刪除它。
      • TrimExcess:TrimExcess()方法重新設置隊列的容量。Dequeue()方法從隊列中刪除元素,但它不會重新設置隊列的容量。要從隊列的頭部去除空元素,應使用TrimExcess()方法。
      • Clear:Clear()方法從隊列中移除所有的元素。
      • ToArray:ToArray()複製隊列到一個新的數組中。

      下面通過隊列來實例模擬消息隊列的實現流程:

     

    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    namespace dataStructureQueueTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("通過Queue來模擬消息隊列的實現");
                QueueTest queueTest = new QueueTest();
    
                while (true)
                {
                    Console.WriteLine("請輸入你操作的類型:1:代表生成一條消息,2:代表消費一條消息");
                    string type = Console.ReadLine();
                    if (type == "1")
                    {
                        Console.WriteLine("請輸入具體消息:");
                        string inforValue = Console.ReadLine();
                        queueTest.InformationProducer(inforValue);
                    }
                    else if (type == "2")
                    {
                        //// 在消費消息的時候,模擬一下,消費成功與消費失敗下次繼續消費的場景
    
                        object inforValue = queueTest.InformationConsumerGet();
                        if (inforValue == null)
                        {
                            Console.WriteLine("當前無可消息可消費");
                        }
                        else
                        {
                            Console.WriteLine("獲取到的消息為:" + inforValue);
    
                            Console.WriteLine("請輸入消息消費結果:1:成功消費消息,2:消息消費失敗");
                            string consumerState = Console.ReadLine();
    
                            ///// 備註:該操作方式線程不安全,在多線程不要直接使用
                            if (consumerState == "1")
                            {
                                queueTest.InformationConsumerDel();
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("操作有誤,請重新選擇");
                    }
                }
            }
        }
    
        /// <summary>
        /// 隊列練習
        /// </summary>
        public class QueueTest
        {
            /// <summary>
            /// 定義一個隊列
            /// </summary>
            public Queue<string> queue = new Queue<string>();
    
            /// <summary>
            /// 生成消息--入隊列
            /// </summary>
            /// <param name="inforValue"></param>
            public void InformationProducer(string inforValue)
            {
                queue.Enqueue(inforValue);
            }
    
            /// <summary>
            /// 消費消息---出隊列--只獲取數據,不刪除數據
            /// </summary>
            /// <returns></returns>
            public object InformationConsumerGet()
            {
                if (queue.Count > 0)
                {
                    return queue.Peek();
                }
    
                return null;
            }
    
            /// <summary>
            /// 消費消息---出隊列---獲取數據的同時刪除數據
            /// </summary>
            /// <returns></returns>
            public string InformationConsumerDel()
            {
                if (queue.Count > 0)
                {
                    return queue.Dequeue();
                }
    
                return null;
            }
        }
    }

     

     

    三、棧簡介

      棧和隊列在使用上很相似,只是棧的數據存儲滿足先進后出原則,棧有如下一些特性:

         1、操作靈活,在初始化時不需要指定其長度,其長度自動增加(默認長度為10)

            注:在實際使用中,如果事先能夠預估其長度,那麼在初始化時指定長度,可以提高效率

            2、泛型的引入,棧在定義時可以指定數據類型避免裝箱拆箱操作

         3、存儲數據滿足先進后出原則

        c#中有關棧的幾個常用方法:

    • Count:Count屬性返回棧中的元素個數。
    • Push:Push()方法在棧頂添加一個元素。
    • Pop:Pop()方法從棧頂刪除一個元素,並返回該元素。如果棧是空的,就拋出一個InvalidOperationException類型的異常。
    • Peek:Peek()方法返回棧頂的元素,但不刪除它。
    • Contains:Contains()方法確定某個元素是否在棧中,如果是,就返回true。

         下面通過一個棧來模擬瀏覽器的回退前進操作的實現

     

    using System;
    using System.Collections.Generic;
    
    namespace dataStructureStackTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                //// 通過棧來模擬瀏覽器回退前進操作
                ////   1、定義兩個棧,分別記錄回退的地址集合,和前進地址集合
                ////   2、在操作具體的回退或者前進操作時
                ////      如果和前一次操作相同,那麼就取出對應隊列的一條數據存儲到另外一個隊列
                Console.WriteLine("本練習模擬瀏覽器的回退前進操作:");
    
                /// 假設瀏覽器已瀏覽了20個網站記錄
                StackTest stackTestBack = new StackTest(20);
                StackTest stackTestGo = new StackTest(20);
                for (int i = 0; i < 20; i++)
                {
                    stackTestBack.PushStack("網站" + (i + 1).ToString());
                }
    
                //// 記錄上一次操作
                string beforOpert = "";
                while (true)
                {
                    Console.WriteLine("");
                    Console.WriteLine("請輸入你操作的類型:1:回退,2:前進");
                    string type = Console.ReadLine();
    
                    if (type == "1")
                    {
                        //// 出棧
                        if (beforOpert == type)
                        {
                            stackTestGo.PushStack(stackTestBack.GetAndDelStack());
                        }
                        string wbeSit = stackTestBack.GetStack();
                        Console.WriteLine("回退到頁面:" + wbeSit);
                        beforOpert = type;
                    }
                    else if (type == "2")
                    {
                        //// 出棧
                        if (beforOpert == type)
                        {
                            stackTestBack.PushStack(stackTestGo.GetAndDelStack());
                        }
                        string wbeSit = stackTestGo.GetStack();
    
                        Console.WriteLine("回退到頁面:" + wbeSit);
                        beforOpert = type;
                    }
                    else
                    {
                        Console.WriteLine("請輸入正確的操作方式!!");
                    }
                }
            }
        }
    
        /// <summary>
        /// 隊列練習
        /// </summary>
        public class StackTest
        {
            /// <summary>
            /// 定義一個棧
            /// </summary>
            public Stack<string> stack;
    
            /// <summary>
            ///無參數構造函數,棧初始化為默認長度
            /// </summary>
            public StackTest()
            {
                stack = new Stack<string>();
            }
    
            /// <summary>
            ///有參數構造函數,棧初始化為指定長度
            ///如果在定義隊列時,如果知道需要存儲的數據長度,那麼最好預估一個長度,並初始化指定的長度
            /// </summary>
            public StackTest(int stackLen)
            {
                stack = stackLen > 0 ? new Stack<string>(stackLen) : new Stack<string>();
            }
    
            /// <summary>
            /// 入棧
            /// </summary>
            /// <param name="inforValue"></param>
            public void PushStack(string inforValue)
            {
                stack.Push(inforValue);
            }
    
            /// <summary>
            /// 出棧(但不刪除)
            /// </summary>
            /// <returns></returns>
            public string GetStack()
            {
                if (stack.Count > 0)
                {
                    return stack.Peek();
                }
    
                return null;
            }
    
            /// <summary>
            /// 出棧(並刪除)
            /// </summary>
            /// <returns></returns>
            public string GetAndDelStack()
            {
                if (stack.Count > 0)
                {
                    return stack.Pop();
                }
    
                return null;
            }
        }
    }

     

    四、使用場景總結

      根據隊列和棧的特點,下面簡單總結一下隊列和棧的一些實際使用場景

       隊列:

        1、異步記錄日誌,此處會涉及到單例模式的使用

        2、消息隊列

        3、業務排隊,比如12306車票購買排隊等候

        4、其他符合先進先出原則的業務操作

       棧:

        1、可回退的操作記錄,比如:瀏覽器的回退操作

        2、計算表達式匹配,比如:計算器表達式計算

        3、其他符合先進后出原則的業務操作

     

    附件:

    關於這一些練習的代碼,上傳到github,有興趣的可以看一下:

     

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

    【其他文章推薦】

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

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

  • 盤點2016年起實施的新能源汽車新政策

    盤點2016年起實施的新能源汽車新政策

    電動汽車充電介面及通信協定5項新國標  

    第一版電動汽車充電介面等4項國家標準曾在2011年發佈,並於2012年3月1日起實施,包括《電動汽車傳導充電用連接裝置第1部分:通用要求》、《電動汽車傳導充電用連接裝置第2部分:交流充電介面》、《電動汽車傳導充電用連接裝置第3部分:直流充電介面》《電動汽車非車載傳導式充電機與電池管理系統之間的通信協定》等。此次發佈的5項國家標準在4項標準的修訂基礎上新增《電動汽車傳導充電系統 第1部分:一般要求》,於2016年1月1日起正式實施。

    標準內容:新修訂的5項國家標準主要在提高電動汽車充電設施安全性及相容性方面做出更進一步的要求。

    交流充電部分,更新禁止採用存在安全隱患的直通電纜加普通家用插頭的連接方式,大於16安培的充電方式要求在車輛插座和供電插座安裝電子鎖和溫度感測器等規範。

     

    直流充電部分,更新在直流充電槍內要求安裝電子鎖,同時預留車輛插座加裝電子鎖的機械結構,要求車輛和設施必須具備檢測和告警功能等規範。

     

    新能源車補貼政策  

    2015年4月29日,新一輪新能源汽車補貼政策正式出臺,其中指出在2016-2020年,對消費者購買的進入國家新能源車目錄的純電動汽車、插電式混合動力汽車和燃料電池汽車繼續給予購車補貼。新標準規定新能源車的補貼將分階段退坡,到2020年補貼標準將在2016年基礎上下降40%。

    政策內容:新的補貼標準將依據節能減排效果,並綜合考慮生產成本、規模效應、技術進步等因素逐步下調。具體下調辦法是,2017-2018年補貼標準將在2016年基礎上下降20%,2019-2020年補貼標準在2016年基礎上下降40%。而燃料電池汽車的補貼將從2015年的18萬元/輛提升至20萬元/輛。

     對比2013—2015年新能源車補貼標準,2016年起執行的新補貼標準針對續駛里程大於等於80km小於150km的純電動車和增程式在內的插電式混合動力乘用車的補貼金額分別降低了6500元和1500元,而續駛里程大於等於250km的電動車和燃料電池乘用車補貼金額分別提高了1000元和2萬元,因而您在購買新能源車的時候,續駛里程將是非常關鍵的價格影響因素。

    新補貼標準對補助範圍內的新能源汽車產品技術的要求也有所提高,其中純電動乘用車的最低續駛里程由大於等於80km提升至100km,同時在行駛速度方面,純電動乘用車30分鐘最高車速應不低於100km/h。

    車船稅管理新規  

    規程內容:規程中指出,已經繳納車船稅的車船,因品質原因,車船被退回生產企業或者經銷商的,納稅人可以向納稅所在地的主管稅務機關申請退還自退貨月份起至該納稅年度終了期間的稅款,退貨月份以退貨發票所載日期的當月為准。

    此外,已完稅車輛被盜搶、報廢、滅失而申請車船稅退稅的,由納稅人納稅所在地的主管稅務機關按照有關規定辦理。

    而對不屬於車船稅徵稅範圍的純電動乘用車和燃料電池乘用車,應當積極獲取車輛的相關資訊予以判斷,對其徵收了車船稅的應當及時予以退稅。

    第四階段乘用車燃料消耗量限值  

    政策內容:2012年6月28日,國務院發佈《節能與新能源汽車產業發展規劃(2012-2020年)》,明確了我國汽車節能標準的整體目標,要求2020年當年乘用車新車平均燃料消耗量達到5.0 L/100km。目前,第四階段的GB 19578-2014《乘用車燃料消耗量限值》和GB 27999-2014《乘用車燃料消耗量評價方法及指標》已於2014年12月22日正式發佈,於2016年1月1日起實施,要求汽車生產企業2016年平均燃料消耗標準需滿足6.7L/100km。

    除了以上提到的自2016年1月1日起實施的政策外,還有一些和我們密切相關的政策和措施已在2015年的最後一天終止或完成,讓我們也來關注一下。

    節能惠民補貼政策到期取消

    政策內容:2013年,國家財政部、發展改革委、工業和資訊化部發佈《關於開展1.6升及以下節能環保汽車推廣工作的通知》,決定從2013年10月1日起至2015年12月31日,繼續實施1.6升及以下節能環保汽車(乘用車)推廣政策,對購買符合條件節能汽車的消費者給予3000元補貼。截止2015年末,國家共實施了3個階段的汽車節能補貼,從每一輪政策的推出可以發現,國家對享受節能補貼的車型要求在逐年提高,推廣車輛要達到產品綜合燃料消耗量標準也由2011年的6.9L調整到2013年的5.9L,且需符合“國V”排放標準。

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

    【其他文章推薦】

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

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

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

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

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

  • 賓士傳將開發四款純電動車

    世界級車商賓士(Mercedes-Benz)被報導正在開發全新的電動車平台,並將以此平台為基礎,推出四款純電動汽車。

    ETToday引述外媒《Car Magazine》的報導,表示賓士正以MRA平台為基礎開發一款名為「EVA」的電動車平台,可供後續開發C-Class到S-Class尺碼的各式車款,最高能搭載400公斤的電池組。而在動力方面,後驅車型可能搭載一具402匹馬力的電動馬達,四驅車行可能還會再加上一具120匹馬力或者201匹馬力的馬達。主動懸載、扭力分配、動能回收等功能也將面面俱到。

    該報導表示,賓士官方並未證實這項消息,但將在2018年推出C-Class與E-Class之間的電動轎車,並以此為基礎再行打造一款休旅車。此後還有S-Class和GLS等級的電動車款會問世。《Car Magazine》表示,賓士目標為每年銷售2萬輛電動車,入門車款售價為7萬英鎊起跳。

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

    【其他文章推薦】

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

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 吉利:到2020年新能源汽車銷量將實現90%以上的占比

    日前,吉利控股集團董事長李書福在接受《中國經營報》記者採訪時表示,以後我們開發的新產品基本是新能源汽車和智慧互聯汽車,傳統汽車就逐漸不生產了。

    此話背後,實際就是吉利於近日提出的“藍色吉利行動”中的重要一環,到2020年,其新能源汽車銷量將實現90%以上的銷量占比。

    具體來說,根據“藍色吉利行動”,吉利新能源將在技術上主打純電動、油電混動、插電式混合動力三種路線,並通過兩大平臺——FE(中高端)與PE(緊湊級)平臺來發展純電動車板塊。

    從以上路徑上看,吉利與其他車企並無二致,但其公佈的“五大承諾”卻足以讓業內熱議——第一,提前全面實現2020年國家第四階段每百公里5.0L的企業平均燃油消耗限值;第二,實現消費者用傳統汽車的購買成本購買插電式混動汽車的夢想;第三,實現到2020年新能源汽車銷量占吉利整體銷量的90%以上;第四,在氫燃料及金屬燃料電池汽車研發方面取得實質性成果;第五,實現新能源技術,智慧化、輕量化技術在行業的領先地位。

    雖然言之鑿鑿,但不得不說的是,90%以上的新能源銷量加上此前吉利發佈的“2020年實現120萬輛”的銷量目標,同年吉利新能源汽車銷量目標竟逾100萬輛。

    對此,吉利控股集團總裁CEO安聰慧表示:“吉利制定這樣的目標並不是為了和其他企業進行對比,而是結合自身發展提出來的。”

    據其介紹,在技術領域,吉利汽車將以與沃爾沃合作打造的CMA中高級車基礎模組架構為核心打造新能源車型,該方面的設計研發工作主要由吉利汽車歐洲研發中心承擔,該中心在瑞典哥德堡已有1200名工程師,中國杭州也有300名工程師,負責架構開發、上車體開發、核心部件開發開發,整車設計、工程製造及新技術的研發。CMA基礎模組架構可以實現電機+發動機等核心部件的批量生產。

    此外,2015年初,吉利與新大洋機電集團成立合資公司並推出知豆電動車,加之此前其子公司上海華普國潤與康迪車業成立的合資公司,署名吉利旗下的新能源車型並不止吉利品牌。雖然,吉利在新能源領域的開疆破土頗有“借力而為”之感,但不論如何,2015年1~11月,在乘聯會統計的自主品牌新能源車銷量占比情況中,吉利節節攀升,的確實現了在自主品牌領域的市占率穩增,也正因如此,加之政策的多重鼓勵,吉利才許下到2020年實現新能源汽車逾百萬的戰略目標。

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

    【其他文章推薦】

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

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

  • Deepin 下 使用 Rider 開發 .NET Core

    Deepin 下 使用 Rider 開發 .NET Core

    目錄

    Deepin 下 使用 Rider 開發 .NET Core

    國產的 Deepin 不錯,安利一下。

    Deepin 用了也有一兩年,也只是玩玩,沒用在開發上面。後來 Win10 不太清真了,就想着能不能到 Deepin下擼碼。要搞開發,首先少不了 IDE,VS2019 用不來,Vs Code 太複雜、麻煩,後來發現了 Rider 這個神器,可是 Rider 是英文界面,筆者的英文是渣渣的。結果在 Windows 下 使用 Rider 開發一段時間后, 已經熟悉了 Rider ,於是計劃後面遷移到 Deepin 下開發 .NET Core 。筆者裝了雙系統 Windows10 + Deepin 15。

    安裝 Rider

    Rider 的Linux 下載地址

    下載壓縮包后,將壓縮包解壓,打開 bin 目錄,在目錄下打開終端,運行

    sh rider.sh

    或者直接點擊 rider.sh 文件,選擇執行即可。

    之後會彈出安裝界面。

    根據提示一步步安裝。

    最後會要求輸入賬號密碼或者激活碼激活 Rider 。

    我這個是高材生的福利~你們沒有的話就用 Github 開源項目免費申請使用,或者其他手段激活。

    安裝完畢后,點擊 New Solution ,發現只能創建 .NET Frameork 的項目(Mono)。

    先關閉 Rider ,接下來安裝 .NET Core

    安裝 .NET Core SDK

    有兩種安裝方法

    1. 自己下載二進制的 安裝包

    2. 使用軟件包形式安裝

      無論哪種方法,如果不把 SDK/Runtime 放到 /usr/share/dotnet 下,Rider 是無法識別的(默認路徑,可以進入Rider修改設置),下面兩種方法都是在 Linux 簡單二進制安裝 .NET Core SDK的方法。

    sudo ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet -f
    export DOTNET_ROOT=/usr/share/dotnet export 
    PATH=$PATH:/usr/share/dotnet

    推薦第一種方法,第二種方法的或,可以按照微軟的文檔自己試試。。。

    安裝完畢后就是這個樣子。

    如果要通過軟件包形式安裝,參考一下這裏 

    體驗開發

    想不到在 Deepin 下,Rider 竟然支持 Desktop Application(WPF)。

    不過這不是重點,我又不會 WPF,先試一下 ASP.NET Core ,晚一點再看看 WPF。

    運行的時候,報這個錯,是 Https 證書的問題,只需要任意位置打開終端,輸入下面的命令即可。

    dotnet dev-certs https

    不知道為什麼,瀏覽器打開 Blazor 應用一片空白。。。

    換成 MVC 試試。

    不知道為什麼 Blazor 打開會空白。不管了,試試 Desktop Application。

    創建 Wpf 項目后,提示要安裝插件,然後退出重新打開。

    不過最後重新打開項目還是報錯

    Project 'WpfApp1' load finished with warnings
                The imported project "/usr/share/dotnet/sdk/3.0.100/Sdks/Microsoft.NET.Sdk.WindowsDesktop/targets/Microsoft.WinFX.targets" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.  /usr/share/dotnet/sdk/3.0.100/Sdks/Microsoft.NET.Sdk.WindowsDesktop/targets/Microsoft.NET.Sdk.WindowsDesktop.targets at (26:3)
                Windows is required to build Windows desktop applications. at (59:5)

    算了~就這樣好了,反正我又不會 WPF ~

    本文是使用 Typora 寫的,很清真。

    好好學習唄~

    最後錄了個視頻玩,不知道說啥,看看內容界面就好~

    打不開的話,請點擊 

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

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

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

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

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

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

  • .NET Core 3.0 單元測試與 Asp.Net Core 3.0 集成測試

    .NET Core 3.0 單元測試與 Asp.Net Core 3.0 集成測試

    單元測試與集成測試

    測試必要性說明

    相信大家在看到單元測試與集成測試這個標題時,會有很多感慨,我們無數次的在實踐中提到要做單元測試、集成測試,但是大多數項目都沒有做或者僅建了項目文件。這裡有客觀原因,已經接近交付日期了,我們沒時間做白盒測試了。也有主觀原因,面對業務複雜的代碼我們不知道如何入手做單元測試,不如就留給黑盒測試吧。但是,當我們的代碼無法進行單元測試的時候,往往就是代碼開始散發出壞味道的時候。長此以往,將欠下技術債務。在實踐過程中,技術債務常常會存在,關鍵在於何時償還,如何償還。

    上圖說明了隨着時間的推移開發/維護難度的變化。

    測試框架選擇

    在 .NET Core 中,提供了 xUnit 、NUnit 、 MSTest 三種單元測試框架。

    MSTest UNnit xUnit 說明 提示
    [TestMethod] [Test] [Fact] 標記一個測試方法
    [TestClass] [TestFixture] n/a 標記一個 Class 為測試類,xUnit 不需要標記特性,它將查找程序集下所有 Public 的類
    [ExpectedException] [ExpectedException] Assert.Throws 或者 Record.Exception xUnit 去掉了 ExpectedException 特性,支持 Assert.Throws
    [TestInitialize] [SetUp] Constructor 我們認為使用 [SetUp] 通常來說不好。但是,你可以實現一個無參構造器直接替換 [SetUp]。 有時我們會在多個測試方法中用到相同的變量,熟悉重構的我們會提取公共變量,並在構造器中初始化。但是,這裏我要強調的是:在測試中,不要提取公共變量,這會破壞每個測試用例的隔離性以及單一職責原則。
    [TestCleanup] [TearDown] IDisposable.Dispose 我們認為使用 [TearDown] 通常來說不好。但是你可以實現 IDisposable.Dispose 以替換。 [TearDown] 和 [SetUp] 通常成對出現,在 [SetUp] 中初始化一些變量,則在 [TearDown] 中銷毀這些變量。
    [ClassInitialize] [TestFixtureSetUp] IClassFixture< T > 共用前置類 這裏 IClassFixture< T > 替換了 IUseFixture< T > ,
    [ClassCleanup] [TestFixtureTearDown] IClassFixture< T > 共用後置類 同上
    [Ignore] [Ignore] [Fact(Skip=”reason”)] 在 [Fact] 特性中設置 Skip 參數以臨時跳過測試
    [Timeout] [Timeout] [Fact(Timeout=n)] 在 [Fact] 特性中設置一個 Timeout 參數,當允許時間太長時引起測試失敗。注意,xUnit 的單位時毫秒。
    [DataSource] n/a [Theory], [XxxData] Theory(數據驅動測試),表示執行相同代碼,但具有不同輸入參數的測試套件 這個特性可以幫助我們少寫很多代碼。

    以上寫了 MSTest 、UNnit 、 xUnit 的特性以及比較,可以看出 xUnit 在使用上相對其它兩個框架來說提供更多的便利性。但是這裏最終實現還是看個人習慣以選擇。

    單元測試

    1. 新建單元測試項目

    2. 新建 Class

    3. 添加測試方法

              /// <summary>
              /// 添加地址
              /// </summary>
              /// <returns></returns>
              [Fact]
              public async Task Add_Address_ReturnZero()
              {
                  DbContextOptions<AddressContext> options = new DbContextOptionsBuilder<AddressContext>().UseInMemoryDatabase("Add_Address_Database").Options;
                  var addressContext = new AddressContext(options);
      
                  var createAddress = new AddressCreateDto
                  {
                      City = "昆明",
                      County = "五華區",
                      Province = "雲南省"
                  };
                  var stubAddressRepository = new Mock<IRepository<Domain.Address>>();
                  var stubProvinceRepository = new Mock<IRepository<Province>>();
                  var addressUnitOfWork = new AddressUnitOfWork<AddressContext>(addressContext);
      
                  var stubAddressService = new AddressServiceImpl.AddressServiceImpl(stubAddressRepository.Object, stubProvinceRepository.Object, addressUnitOfWork);
                  await stubAddressService.CreateAddressAsync(createAddress);
                  int addressAmountActual = await addressContext.Addresses.CountAsync();
                  Assert.Equal(1, addressAmountActual);
              }
      • 測試方法的名字包含了測試目的、測試場景以及預期行為。
      • UseInMemoryDatabase 指明使用內存數據庫。
      • 創建 createAddress 對象。
      • 創建 Stub 。在單元測試中常常會提到幾個概念 Stub , Mock 和 Fake ,那麼在應用中我們該如何選擇呢?
        • Fake – Fake 通常被用於描述 Mock 或 Stub ,如何判斷它是 Stub 還是 Mock 依賴於使用上下文,換句話說,Fake 即是 Stub 也是 Mock 。
        • Stub – Stub 是系統中現有依賴項的可控替代品。通過使用 Stub ,你可以不用處理依賴直接測試你的代碼。默認情況下, 偽造對象以stub 開頭。
        • Mock – Mock 對象是系統中的偽造對象,它決定單元測試是否通過或失敗。Mock 會以 Fake 開頭,直到被斷言為止。
      • Moq4 ,使用 Moq4 模擬我們在項目中依賴對象。
    4. 打開視圖 -> 測試資源管理器。

    5. 點擊運行,得到測試結果。

    6. 至此,一個單元測試結束。

    集成測試

    集成測試確保應用的組件功能在包含應用的基礎支持下是正確的,例如:數據庫、文件系統、網絡等。

    1. 新建集成測試項目。

    2. 添加工具類 Utilities 。

      using System.Collections.Generic;
      using AddressEFRepository;
      
      namespace Address.IntegrationTest
      {
          public static class Utilities
          {
              public static void InitializeDbForTests(AddressContext db)
              {
                  List<Domain.Address> addresses = GetSeedingAddresses();
                  db.Addresses.AddRange(addresses);
                  db.SaveChanges();
              }
      
              public static void ReinitializeDbForTests(AddressContext db)
              {
                  db.Addresses.RemoveRange(db.Addresses);
                  InitializeDbForTests(db);
              }
      
              public static List<Domain.Address> GetSeedingAddresses()
              {
                  return new List<Domain.Address>
                  {
                      new Domain.Address
                      {
                          City = "貴陽",
                          County = "測試縣",
                          Province = "貴州省"
                      },
                      new Domain.Address
                      {
                          City = "昆明市",
                          County = "武定縣",
                          Province = "雲南省"
                      },
                      new Domain.Address
                      {
                          City = "昆明市",
                          County = "五華區",
                          Province = "雲南省"
                      }
                  };
              }
          }
      }
    3. 添加 CustomWebApplicationFactory 類,

       using System;
       using System.IO;
       using System.Linq;
       using AddressEFRepository;
       using Microsoft.AspNetCore.Hosting;
       using Microsoft.AspNetCore.Mvc.Testing;
       using Microsoft.EntityFrameworkCore;
       using Microsoft.Extensions.Configuration;
       using Microsoft.Extensions.DependencyInjection;
       using Microsoft.Extensions.Logging;
      
       namespace Address.IntegrationTest
       {
           public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
           {
               protected override void ConfigureWebHost(IWebHostBuilder builder)
               {
                   string projectDir = Directory.GetCurrentDirectory();
                   string configPath = Path.Combine(projectDir, "appsettings.json");
                   builder.ConfigureAppConfiguration((context, conf) =>
                   {
                       conf.AddJsonFile(configPath);
                   });
      
                   builder.ConfigureServices(services =>
                   {
                       ServiceDescriptor descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AddressContext>));
      
                       if (descriptor != null)
                       {
                           services.Remove(descriptor);
                       }
      
                       services.AddDbContextPool<AddressContext>((options, context) =>
                       {
                           //var configuration = options.GetRequiredService<IConfiguration>();
                           //string connectionString = configuration.GetConnectionString("TestAddressDb");
                           //context.UseMySql(connectionString);
                           context.UseInMemoryDatabase("InMemoryDbForTesting");
      
                       });
      
                       // Build the service provider.
                       ServiceProvider sp = services.BuildServiceProvider();
                       // Create a scope to obtain a reference to the database
                       // context (ApplicationDbContext).
                       using IServiceScope scope = sp.CreateScope();
                       IServiceProvider scopedServices = scope.ServiceProvider;
                       var db = scopedServices.GetRequiredService<AddressContext>();
                       var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
      
                       // Ensure the database is created.
                       db.Database.EnsureCreated();
      
                       try
                       {
                           // Seed the database with test data.
                           Utilities.ReinitializeDbForTests(db);
                       }
                       catch (Exception ex)
                       {
                           logger.LogError(ex, "An error occurred seeding the " + "database with test messages. Error: {Message}", ex.Message);
                       }
                   });
               }
           }
       }
      • 這裏為什麼要添加 CustomWebApplicationFactory 呢?
        WebApplicationFactory 是用於在內存中引導應用程序進行端到端功能測試的工廠。通過引入自定義 CustomWebApplicationFactory 類重寫 ConfigureWebHost 方法,我們可以重寫我們在 StartUp 中定義的內容,換句話說我們可以在測試環境中使用正式環境的配置,同時可以重寫,例如:數據庫配置,數據初始化等等。
      • 如何準備測試數據?
        我們可以使用數據種子的方式加入數據,數據種子可以針對每個集成測試做數據準備。
      • 除了內存數據庫,還可以使用其他數據庫進行測試嗎?
        可以。
    4. 添加集成測試 AddressControllerIntegrationTest 類。

       using System.Collections.Generic;
       using System.Linq;
       using System.Net.Http;
       using System.Threading.Tasks;
       using Address.Api;
       using Microsoft.AspNetCore.Mvc.Testing;
       using Newtonsoft.Json;
       using Xunit;
      
       namespace Address.IntegrationTest
       {
           public class AddressControllerIntegrationTest : IClassFixture<CustomWebApplicationFactory<Startup>>
           {
               public AddressControllerIntegrationTest(CustomWebApplicationFactory<Startup> factory)
               {
                   _client = factory.CreateClient(new WebApplicationFactoryClientOptions
                   {
                       AllowAutoRedirect = false
                   });
               }
      
               private readonly HttpClient _client;
      
               [Fact]
               public async Task Get_AllAddressAndRetrieveAddress()
               {
                   const string allAddressUri = "/api/Address/GetAll";
                   HttpResponseMessage allAddressesHttpResponse = await _client.GetAsync(allAddressUri);
      
                   allAddressesHttpResponse.EnsureSuccessStatusCode();
      
                   string allAddressStringResponse = await allAddressesHttpResponse.Content.ReadAsStringAsync();
                   var addresses = JsonConvert.DeserializeObject<IList<AddressDto.AddressDto>>(allAddressStringResponse);
                   Assert.Equal(3, addresses.Count);
      
                   AddressDto.AddressDto address = addresses.First();
                   string retrieveUri = $"/api/Address/Retrieve?id={address.ID}";
                   HttpResponseMessage addressHttpResponse = await _client.GetAsync(retrieveUri);
      
                   // Must be successful.
                   addressHttpResponse.EnsureSuccessStatusCode();
      
                   // Deserialize and examine results.
                   string addressStringResponse = await addressHttpResponse.Content.ReadAsStringAsync();
                   var addressResult = JsonConvert.DeserializeObject<AddressDto.AddressDto>(addressStringResponse);
                   Assert.Equal(address.ID, addressResult.ID);
                   Assert.Equal(address.Province, addressResult.Province);
                   Assert.Equal(address.City, addressResult.City);
                   Assert.Equal(address.County, addressResult.County);
               }
           }
       }
    5. 在測試資源管理器中運行集成測試方法。

    6. 結果。

    7. 至此,集成測試完成。需要注意的是,集成測試往往耗時比較多,所以建議能使用單元測試時就不要使用集成測試。

    總結:當我們寫單元測試時,一般不會同時存在 Stub 和 Mock 兩種模擬對象,當同時出現這兩種對象時,表明單元測試寫的不合理,或者業務寫的太過龐大,同時,我們可以通過單元測試驅動業務代碼重構。當需要重構時,我們應盡量完成重構,不要留下欠下過多技術債務。集成測試有自身的複雜度存在,我們不要節約時間而打破單一職責原則,否則會引發不可預期後果。為了應對業務修改,我們應該在業務修改以後,進行回歸測試,回歸測試主要關注被修改的業務部分,同時測試用例如果有沒要可以重寫,運行整個和修改業務有關的測試用例集。

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

    【其他文章推薦】

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

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 源碼學習系列之SpringBoot自動配置(篇二)

    源碼學習系列之SpringBoot自動配置(篇二)

    源碼學習系列之SpringBoot自動配置(篇二)之HttpEncodingAutoConfiguration 源碼分析

    繼上一篇博客之後,本博客繼續跟一下SpringBoot的自動配置源碼

    ok,先複習一下上一篇的內容,從前面的學習,我們知道了SpringBoot的自動配置主要是由一個選擇器AutoConfigurationImportSelector,先通過選擇器將自動配置的類加載到Spring容器

    注意點:

    • List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);獲取的候選配置的類名
    • 由SpringFactoriesLoader加載器負責加載配置類名,已經裝載配置類到容器,SpringFactoriesLoader的loadSpringFactories方法讀取自動配置工程的META-INF/spring.factories配置文件,加載配置類的全類名,包裝成Properties對象,然後再加載到容器里
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        /* 將spring.factories的類都裝載到Spring容器*/
         public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
    
            try {
            //將META-INF/spring.factories文件里配置的屬性都裝載到Enumeration數據結構里
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                ArrayList result = new ArrayList();
                //遍歷獲取屬性,然後再獲取對應的配置類全類名
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                    String factoryClassNames = properties.getProperty(factoryClassName);
                    result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
                }
    
                return result;
            } catch (IOException var8) {
                throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
            }
        }
    

    ok,Springboot的自動配置類都在這個包里,源碼很多,所以本博客只是簡單跟一下源碼

    自動配置可以說是SpringBoot框架的一個很重要的功能,其強大的功能就是通過很多配置類實現的,當然這麼多配置,可以參考SpringBoot官方的配置參考:
    https://docs.spring.io/spring-boot/docs/2.1.10.RELEASE/reference/html/common-application-properties.html

    SpringBoot的自動配置類很多,顯然不是每個配置類都生效的,比如你沒引對應的jar,那對應的配置類肯定是不起效的,ok,本博客以HttpEncodingAutoConfiguration自動編碼配置類為實例,記錄一下SpringBoot的自動配置

    先補充一些@Conditional註解的用法:詳情可以參考我上篇博客

    @Conditional派生註解 作用(都是判斷是否符合指定的條件)
    @ConditionalOnJava 系統的java版本是否符合要求
    @ConditionalOnBean 有指定的Bean類
    @ConditionalOnMissingBean 沒有指定的bean類
    @ConditionalOnExpression 符合指定的SpEL表達式
    @ConditionalOnClass 有指定的類
    @ConditionalOnMissingClass 沒有指定的類
    @ConditionalOnSingleCandidate 容器只有一個指定的bean,或者這個bean是首選bean
    @ConditionalOnProperty 指定的property屬性有指定的值
    @ConditionalOnResource 路徑下存在指定的資源
    @ConditionalOnWebApplication 系統環境是web環境
    @ConditionalOnNotWebApplication 系統環境不是web環境
    @ConditionalOnjndi JNDI存在指定的項

    通過上篇博客的學習,我們已經知道了SpringBoot有很多自動配置類,所以本博客拿HttpEncodingAutoConfiguration類來看看

    補充:

    • @Configuration proxyBeanMethods屬性:默認是開啟的,開啟后允許其它配置類調用這個類的@bean方法,詳情參看Spring官方文檔:
    package org.springframework.boot.autoconfigure.web.servlet;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
    import org.springframework.boot.autoconfigure.http.HttpProperties;
    import org.springframework.boot.autoconfigure.http.HttpProperties.Encoding;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.boot.web.server.WebServerFactoryCustomizer;
    import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
    import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.web.filter.CharacterEncodingFilter;
    
    @Configuration(
        proxyBeanMethods = false
    )//指定是一個配置列,關了proxyBeanMethods,其它配置類就不能調相應的@bean類
    @EnableConfigurationProperties({HttpProperties.class})//讓使用 @ConfigurationProperties註解的HttpProperties類生效,HttpProperties類通過ConfigurationProperties註解,將屬性配置一個一個加載進來
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )//指定系統環境是Web環境配置才起效,並且指定類型是SERVLET
    @ConditionalOnClass({CharacterEncodingFilter.class})//系統有CharacterEncodingFilter過濾器類,則配置類起效,CharacterEncodingFilter類:SpringMVC中進行亂碼解決的過濾器
    @ConditionalOnProperty(
        prefix = "spring.http.encoding",
        value = {"enabled"},
        matchIfMissing = true
    )//判斷配置文件是否有spring.http.encoding.enabled屬性,如果沒配置,也是默認為true的,因為配置了`matchIfMissing =true`
    public class HttpEncodingAutoConfiguration {
        //創建一個Encoding對象
        private final Encoding properties;
        // 構造函數里通過properties.getEncoding();進行屬性映射,獲取默認的配置
        public HttpEncodingAutoConfiguration(HttpProperties properties) {
            this.properties = properties.getEncoding();
        }
    
        @Bean
        @ConditionalOnMissingBean//如果系統沒有CharacterEncodingFilter類,就執行characterEncodingFilter方法
        public CharacterEncodingFilter characterEncodingFilter() {
            //重新創建一個編碼過濾器
            OrderedCharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
            //設置默認的配置
            filter.setEncoding(this.properties.getCharset().name());
            filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
            filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
            return filter;
        }
    
        @Bean
        public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
            return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
        }
    
        private static class LocaleCharsetMappingsCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
            private final Encoding properties;
    
            LocaleCharsetMappingsCustomizer(Encoding properties) {
                this.properties = properties;
            }
    
            public void customize(ConfigurableServletWebServerFactory factory) {
                if(this.properties.getMapping() != null) {
                    factory.setLocaleCharsetMappings(this.properties.getMapping());
                }
    
            }
    
            public int getOrder() {
                return 0;
            }
        }
    }
    

    通過對HttpEncodingAutoConfiguration源碼的學習,可以看出,其實主要是用@Conditional及其派生註解,這些註解都是要在特定情況才會起效,起效了,才會將組件加載到Spring容器里

    ok,然後我們怎麼知道哪些配置是起效的?在SpringBoot項目里,是可以通過配置,開啟打印的,可以在application.properties加上debug=true屬性就可以

    控制台打印的Positive matches就表示有效的配置類

    console打印的Negative matches表示不起效的配置類:
    比如我的項目沒有加aop的,aop自動配置類就不起效

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

    【其他文章推薦】

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

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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