標籤: 包裝設計

  • 印度全國封鎖後 恆河上游部分河段可飲用

    摘錄自2020年4月24日中央社報導

    一般帶大量超級細菌的恆河,在印度全國封鎖近一個月後,因為幾乎沒有人類污染,瑜伽聖城瑞詩凱詩(Rishikesh)這段恆河河水,經政府檢測水質達到可飲用的標準。

    印度北部北阿坎德省(Uttarakhand)污染控制局最近從瑞詩凱詩及哈里德瓦(Haridwar)的恆河河段抽樣進行化驗,發現水質可以飲用。

    官方報告表示,哈基寶里河壇段的恆河水,生化需氧量(Biochemical oxygen demand)也下降20%。專家說,這意味著恆河水含氧量變高,水族可舒適地在河中呼吸。

    印度中央污染控制局(Central Pollution Control Board)近日發表的調查報告顯示,恆河36個監測站中,有27個監測站測得恆河水質已變為可安全沐浴和水中生物可安全生活的水準。

    生活環境
    土地水文
    土地利用
    國際新聞
    印度
    封城
    武漢肺炎
    恆河

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※回頭車貨運收費標準

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

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

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

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

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 廣汽傳祺打出SUV“組合拳”,2017款GS4蛻變成全能选手

    廣汽傳祺打出SUV“組合拳”,2017款GS4蛻變成全能选手

    與此同時,還能通過手機遠程控制安防系統(T-BOX),隨時隨地了解控制車內外各種信息。新增的愛信6AT變速箱, 最大的優點就是平順性處於行業領先水平,質量穩定可靠,駕駛舒適性得到了提升。i-4WD智能適時四驅系統,具有兩驅、智能適時四驅及強制四驅等三種模式,極大地提高了傳祺GS4的越野性能。

    如果說自主SUV的崛起,是得益於精品車型的話,那麼就不得不提傳祺GS4了,不俗的原創外觀+成熟的動力組合+大空間,正好符合了我們對一輛緊湊SUV的所有需求。

    縱使傳祺GS4一經推出就取得了成功,但作為一款新車,還存在着很多進步的空間 。比如當時呼聲很高的“動力如果能有更多選擇就好了”,在今年初的時候,傳祺GS4 就順勢推出了235T(1.5T)動力配置,繼續鞏固了GS4銷量地位,在10月銷量中,傳祺GS4的成績已突破3.5萬。

    後來,有更高要求的消費者又提出 “能不能增配6AT+四驅+後排出風口呢”?傳祺這次依舊沒讓我們失望!在半個月前,傳祺GS4就發布了2017新款,上述的功能都如我們所願,總共超過15項性能與配置升級!

    2017款 傳祺GS4 235T 6AT版

    指導價格:13.38-16.18萬

    在廣州車展期間,小編也前往了傳祺展台目睹了新款GS4,但那個人山人海的場景依然歷歷在目,作為本地車企,在主場的人氣還是非常火爆的。

    傳祺GS4 2017款15項配置升級中

    最大的變化主要有以下3點

    ●車載互聯繫統

    ●愛信6AT+i-4WD

    ●後排出風口+USB接口

    2017款GS4整合了百度CarLife車載互聯繫統,理論上支持所有Andriod系統手機,可實現語音控制,觸摸屏控制電話、音樂、地圖及其它App應用。

    與此同時,還能通過手機遠程控制安防系統(T-BOX),隨時隨地了解控制車內外各種信息。

    新增的愛信6AT變速箱, 最大的優點就是平順性處於行業領先水平,質量穩定可靠,駕駛舒適性得到了提升。

    i-4WD智能適時四驅系統,具有兩驅、智能適時四驅及強制四驅等三種模式,極大地提高了傳祺GS4的越野性能。只需輕轉控制旋鈕,即可自由切換,兼顧燃油經濟性和通過性,最大爬坡度輕鬆≥40%。

    後排新增出風口、雙USB接口與手機儲物盒,對於後排乘客而言,也豐富了乘車樂趣,因為都是非常實用的配置。同時,後排中央扶手及中央頭枕與後排隱私玻璃在2017款GS4中配車型即有所體現。

    除這幾項大的升級之外,其餘的功能、性能升級在這就一一闡述了,想了解的朋友可以自己查一下產品資料。

    小編最後點評

    放眼所有汽車品牌,幾乎每個品牌都有屬於自己的品牌符號,像高爾夫之於大眾、卡羅拉之於豐田、3系之於寶馬。GS4之于于傳祺正是這樣,一款細分市場明星車型全面打響了品牌,成為了家喻戶曉的標杆產品。隨後又推出了GS8這款豪華大七座SUV,據小編了解,到目前為止已經收穫2萬個訂單了!在國產高端SUV銷量普遍不濟的情況下,GS8的勢頭非常奪人眼球。

    在國產車裡,小編也不止一次推薦過GS4了,它和其他品牌不一樣,傳祺的原創設計、做工和自主核心技術非常成熟,不僅有了與合資同台競技的硬實力,還有了以後長久發展的根基,小編對它還是比較看好的。

    2017款GS4的改款是成功的,豐富的產品線也趨於完善,配置升級非常實用,滿足了更多人的選車訴求。

    更重要的是,價格方面也沒想象中大幅上漲,而是與1.5T 7速雙離合配置的價格保持了一致,聰明的定價相信也能俘虜不少新的消費者,繼續鞏固傳祺GS4在銷量榜的領先地位。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

    ※回頭車貨運收費標準

    台中搬家公司費用怎麼算?

  • 曹工說JDK源碼(1)–ConcurrentHashMap,擴容前大家同在一個哈希桶,為啥擴容后,你去新數組的高位,我只能去低位?

    曹工說JDK源碼(1)–ConcurrentHashMap,擴容前大家同在一個哈希桶,為啥擴容后,你去新數組的高位,我只能去低位?

    如何計算,一對key/value應該放在哪個哈希桶

    大家都知道,hashmap底層是數組+鏈表(不討論紅黑樹的情況),其中,這個數組,我們一般叫做哈希桶,大家如果去看jdk的源碼,會發現裏面有一些變量,叫做bin,這個bin,就是桶的意思,結合語境,就是哈希桶。

    這裏舉個例子,假設一個hashmap的數組長度為4(0000 0100),那麼該hashmap就有4個哈希桶,分別為bucket[0]、bucket[1]、bucket[2]、bucket[3]。

    現在有兩個node,hashcode分別是1(0000 0001),5(0000 0101). 我們當然知道,這兩個node,都應該放入第一個桶,畢竟1 mod 4,5 mod 4的結果,都是1。

    但是,在代碼里,可不是用取模的方法來計算的,而是使用下面的方式:

    int entryNodeIndex = (tableLength - 1) & hash;
    

    應該說,在tableLength的值,為2的n次冪的時候,兩者是等價的,但是因為位運算的效率更高,因此,代碼一般都使用位運算,替代取模運算。

    下面我們看看具體怎麼計算:

    此處,tableLength即為哈希表的長度,此處為4. 4 – 1為3,3的二進製表示為:

    0000 0011

    那麼,和我們的1(0000 0001)相與:

    0000 0001 -------- 1
    0000 0011 -------- 3(tableLength - 1)
        相與(同為1,則為1;否則為0)
    0000 0001 -------- 1     
    

    結果為1,所以,應該放在第1個哈希桶,即數組下標為1的node。

    接下來,看看5這個hashcode的節點要放在什麼位置,是怎麼計算:

    0000 0101 -------- 5
    0000 0011 -------- 3(tableLength - 1)
        相與(同為1,則為1;否則為0)后結果:
    0000 0001 -------- 1     
    

    擴容時,是怎麼對一個hash桶進行transfer的

    此處,具體的整個transfer的細節,我們本講不會涉及太多,不過,大體的邏輯,我們可以來想一想。

    以前面為例,哈希表一共4個桶,其中bucket[1]裏面,存放了兩個元素,假設是a、b,其hashcode分別是1,5.

    現在,假設我們要擴容,一般來說,擴容的時候,都是新建一個bucket數組,其容量為舊錶的一倍,這裏舊錶為4,那新表就是8.

    那,新表建立起來了,舊錶里的元素,就得搬到新表裡面去,等所有元素都搬到新表了,就會把新表和舊錶的指針交換。如下:

    java.util.concurrent.ConcurrentHashMap#transfer
    
        private transient volatile Node<K,V>[] nextTable;
    
    	transient volatile Node<K,V>[] table;
    
    if (finishing) {
        // 1
        nextTable = null;
        // 2
        table = nextTab;
        // 3
        sizeCtl = (tabLength << 1) - (tabLength >>> 1);
        return;
    }
    
    • 1處,將field:nextTable(也就是新表)設為null,擴容完了,這個field就會設為null

    • 2處,將局部變量nextTab,賦值給table,這個局部變量nextTab里,就是當前已經擴容完畢的新表

    • 3處,修改表的sizeCtl為:假設此處tabLength為4,tabLength << 1 左移1位,就是8;tabLength >>> 1,右移一位,就是2,。8 – 2 = 6,正好就等於 8(新表容量) * 0.75。

      所以,這裏的sizeCtl就是,新表容量 * 負載因子,超過這個容量,基本就會觸發擴容。

    ok,接着說,我們要怎麼從舊錶往新表搬呢? 那以前面的bucket[1]舉例,遍歷這個鏈表,計算各個node,應該放到新表的什麼位置,不就完了嗎?是的,理論上這麼寫就完事了。

    但是,我們會怎麼寫呢?

    用hashcode對新bucket數組的長度取余嗎?

    jdk對效率的追求那麼高,肯定不會這麼寫的,我們看看,它怎麼寫的:

    java.util.concurrent.ConcurrentHashMap#transfer
    
    // 1
    for (Node<K,V> p = entryNode; p != null; p = p.next) {
        // 2
        int ph = p.hash;
        K pk = p.key;
        V pv = p.val;
        
    	// 3
        if ((ph & tabLength) == 0){
            lowEntryNode = new Node<K,V>(ph, pk, pv, lowEntryNode);
        }
        else{
            highEntryNode = new Node<K,V>(ph, pk, pv, highEntryNode);
        }
    }
    
    • 1處,即遍歷舊的哈希表的某個哈希桶,假設就是遍歷前面的bucket[1],裏面有a/b兩個元素,hashcode分別為1,5那個。

    • 2處,獲取該節點的hashcode,此處分別為1,5

    • 3處,如果hashcode 和 舊錶長度相與,結果為0,則,將該節點使用頭插法,插入新表的低位;如果結果不為0,則放入高位。

      ok,什麼是高位,什麼是低位。擴容后,新的bucket數組,長度為8,那麼,前面bucket[1]中的兩個元素,將分別放入bucket[1]和bucket[5].

      ok,這裏的bucket[1]就是低位,bucket[5]為高位。

    首先,大家要知道,hashmap中,容量總是2的n次方,請牢牢記住這句話。

    為什麼要這麼做?你想想,這樣是不是擴容很方便?

    以前,hashcode 為1,5的,都在bucket[1];而現在,擴容為8后,hashcode為1的,還是在newbucket[1],hashcode為5的,則在newbucket[5];這樣的話,是不是有一半的元素,根本不用動?

    這就是我覺得的,最大的好處;另外呢,運算也比較方便,都可以使用位運算代替,效率更高。

    好的,那我們現在問題來了,下面這句的原理是什麼?

        if ((ph & tabLength) == 0){
            lowEntryNode = new Node<K,V>(ph, pk, pv, lowEntryNode);
        } else{
            highEntryNode = new Node<K,V>(ph, pk, pv, highEntryNode);
        }
    

    為啥,hashcode & 舊哈希表的容量, 結果為0的,擴容后,就會在低位,也就是維持位置不變呢?而結果不為0的,擴容后,位置在高位呢?

    背後的位運算原理(大白話)

    代碼里用的如下判斷,滿足這個條件,去低位;否則,去高位。

     if ((ph & tabLength) == 0)
    

    還是用前面的例子,假設當前元素為a,hashcode為1,和哈希桶大小4,去進行運算。

    0000 0001  ---- 1
    0000 0100  ---- 舊哈希表容量4
    &運算(同為1則為1,否則為0)
    結果:
    0000 0000  ---- 結果為0    
    

    ok,這裏算出來,結果為0;什麼情況下,結果會為0呢?

    那我們現在開始倒推,什麼樣的數,和 0000 0100 相與,結果會為0?

    ???? ????  ---- 
    0000 0100  ---- 舊哈希表容量
    &運算(同為1則為1,否則為0)
    結果:
    0000 0000  ---- 結果為0    
    

    因為與運算的規則是,同為1,則為1;否則都為0。那麼,我們這個例子里,舊哈希表容量為 0000 0100,假設表示為2的n次方,此處n為2,我們僅有第三位(第n+1)為1,那如果對方這一位為0,那結果中的這一位,就會為0,那麼,整個數,就為0.

    所以,我們的結論是:假設哈希表容量,為2的n次方,表示為二進制后,第n+1位為1;那麼,只要我們節點的hashcode,在第n+1位上為0,則最終結果是0.

    反之,如果我們節點的hashcode,在第n+1位為1,則最終結果不會是0.

    比如,hashcode為5的時候,會是什麼樣子?

    0000 0101  ---- 5
    0000 0100  ---- 舊哈希表容量
    &運算(同為1則為1,否則為0)
    結果:
    0000 0100  ---- 結果為4    
    

    此時,5這個hashcode,在第n+1位上為1,所以結果不為0。

    至此,我們離答案好像還很遠。ok,不慌,繼續。

    假設現在擴容了,新bucket數組,長度為8.

    a元素,hashcode依然是1,a元素應該放到新bucket數組的哪個bucket里呢?

    我們用前面說的這個算法來計算:

    int entryNodeIndex = (tableLength - 1) & hash;
    
    0000 0001  ---- 1
    0000 0111  ---- 8 - 1 = 7
    &運算(同為1則為1,否則為0)
    結果:
    0000 0001  ---- 結果為1
    

    結果沒錯,確實應該放到新bucket[1],但怎麼推論出來呢?

        // 1
    	if ((ph & tabLength) == 0){
            // 2
            lowEntryNode = new Node<K,V>(ph, pk, pv, lowEntryNode);
        }
    

    也就是說,假設一個數,滿足1處的條件:(ph & tabLength) == 0,那怎麼推論出2呢,即應該在低位呢?

    ok,條件1,前面分析了,可以得出:

    這個數,第n+1位為0.

    接下來,看看數組長度 – 1這個數。

    數組長度 2的n次方 二進製表示 1出現的位置 數組長度-1 數組長度-1的二進制
    2 2的1次方 0000 0010 第2位 1 0000 0001
    4 2的2次方 0000 0100 第3位 3 0000 0011
    8 2的3次方 0000 1000 第4位 7 0000 0111

    好了,兩個數都有了,

    ???????0???????   -- 1 節點的hashcode,第n + 1位為0
    000000010000000   -- 2 老數組    
    000000100000000   -- 3 新數組的長度,等於老數組長度 * 2
    000000011111111   -- 4 新數組的長度 - 1
        
        運算:1和4相與
        
    
    

    大家注意看紅字部分,還有框出來的那一列,這一列為0,導致,最終結果,肯定是比2那一行的数字小,2這行,不就是老數組的長度嗎,那你比老數組小;你比這一行小,在新數組裡,就只能在低位了。

    反之,如果節點的hashcode,這一位為1,那麼,最終結果,至少是大於等於2這一行的数字,所以,會放在高位。

    參考資料

    https://www.jianshu.com/p/2829fe36a8dd

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

    ※回頭車貨運收費標準

  • HashMap源碼閱讀(java1.8.0)

    HashMap源碼閱讀(java1.8.0)

    1.1 背景知識

    1.1.1 紅黑樹

      二叉查找樹可能因為多次插入新節點導致失去平衡,使得查找效率低,查找的複雜度甚至可能會出現線性的,為了解決因為新節點的插入而導致查找樹不平衡,此時就出現了紅黑樹。

    紅黑樹它一種特殊的二叉查找樹。紅黑樹的每個節點上都有存儲位表示節點的顏色,可以是紅(Red)或黑(Black)。它具有以下特點:

    (1)每個節點或者是黑色,或者是紅色。

    (2)根節點是黑色。

    (3)每個恭弘=叶 恭弘子節點(恭弘=叶 恭弘子節點,是指為空(NIL或NULL)的恭弘=叶 恭弘子節點)是黑色。

    (4)如果一個節點是紅色的,則它的子節點一定是黑色(即從根節點到恭弘=叶 恭弘子節點的路徑上不能有兩個重複的紅色節點)。

    (5)從一個節點到其上每一個恭弘=叶 恭弘節點的所有路徑都具有相同的黑色節點個數。

    紅黑樹的基本操作–添加

    ① 將紅黑樹當作一顆二叉查找樹,將節點插入。

    ② 將插入的節點着色為”紅色”。(因為條件5,從一個節點到其中每一個節點的的所有路徑都具有相同的黑色節點)。

    ③通過一系列的旋轉(左旋或右旋操作)或着色等操作,使之重新成為一顆紅黑樹。

                           

    1.2 源碼

      在java 1.7之前是用數組和鏈表一起組合構成HashMap,在java1.8之後就使用當鏈表長度超過8之後,就會將鏈錶轉化為紅黑樹,縮小查找的時間(紅黑樹維護也會花費大量時間,包含左旋、右旋和變色過程)。

    1.2.1 HashMap的初始化

    hashmap構造函數會初始化三個值:

    • 初始容量initialCapacity:默認值是16,當儲存的數據越來越多的時候,就必須進行擴容操作。
    • 閾值threshold:hashmap的數組結構中所能存放的最大數量,超過該數量,則會對數組進行擴容。閾值的計算方式為:容量(initialCapacity)*負載因子(loadFactor)。
    • 負載因子loadFactor:當負載因子很大時,閾值會很大,table數組擴容的可能性比較小,會使得一個數組中的鏈表(紅黑樹)存放過多的數據,雖然節省了一定的空間,但會導致查詢時間很長。相反負載因子很小時,擴容的可能性會很高,使得數組中的數據變得相對少,查詢時間會縮短,但會花費較長的時間。

      在初始化一個hashmap對象的時候,指定鍵值對的同時,也可以指定初始map的容量大小,假設此處我們指定大小為11,則會在構造函數中調用tableSizeFor將容量改為2的n冪次,即比當前容量大,而且必須是2的指數次冪的最小數,就會變成16。這是因為2的指數次冪便於計算進行位運算操作,提升運行效率問題(位運算>加法>乘法>除法>取模)。

      hashmap的的默認值是16,負載因子默認是0.75,代碼如下:

    //HashMap<String,String> hashMap = new HashMap<String, String>(11);
    
    /**
     * Returns a power of two size for the given target capacity.
     **/
    static final int tableSizeFor(int cap) {
        int n = cap - 1;   //10 防止在cap已經是2的n次冪的情況下
        // >>> 表示不關心符號位,對數據的二進制形式進行右移  |表示或運算
        n |= n >>> 1;	  //15
        n |= n >>> 2;     //15
        n |= n >>> 4;     //15
        n |= n >>> 8;     //15
        n |= n >>> 16;    //15
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; //16
    }
    

    1.2.2 HashMap的put操作

       這裏可以介紹一下&位運算,當我們將一對KV存儲到hashmap當中時,會通過(n – 1) & hash運算來定位將要插入的鍵值對放入到哈希表的某個桶中。其中n表示哈希表的長度,通常n為2的倍數,通過n-1即可n所表示的二進制數,除最高位外,全部轉化為1,藉助與運算即可快速完成取模操作。

     //hashMap.put("2020", "good luck");
    
     /**
      * Implements Map.put and related methods.
      *
      * @param hash hash for key
      * @param key the key
      * @param value the value to put
      * @param onlyIfAbsent if true, don't change existing value
      * @param evict if false, the table is in creation mode.
      * @return previous value, or null if none
      */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //如果hashtable沒有初始化,則初始化該table數組
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length; 
        //通過位運算找到數組中的下標位置,如果數組中對應下標為空,則可以直接存放下去
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //數組元素對應的位置已經有元素,產生碰撞
            Node<K,V> e; K k;
            //如果插入的元素key是已經存在的,則將新的value替換掉原來的舊值
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果此時table數組對應的位置是紅黑樹結構,則將該節點插入紅黑樹中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //如果此時table數組對應的位置是鏈表結構
                for (int binCount = 0; ; ++binCount) {
    				//遍歷到數組尾端,沒有與插入鍵值對相同的key,則將新的鍵值對插入鏈表尾部
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //鏈表過長,將鏈錶轉化為紅黑樹
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //發現鏈表中的某個節點有與插入鍵值對相同的key,則跳出循環,在循環外部重新賦值
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //該key在hashmap已存在,更新與在鏈表跳出循環節點對應的值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //超過閾值則更新
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    

    1.2.3 HashMap的get操作

    /**
         * Implements Map.get and related methods.
         *
         * @param hash hash for key
         * @param key the key
         * @return the node, or null if none
         */
        final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            //table數組不為空,且對應的下標位置也不為空。
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
                //如果第一個位置是對應的key,則返回
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                //遍歷其他元素
                if ((e = first.next) != null) {
                    //紅黑樹
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    //鏈表
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }

    1.2.4 HashMap的擴容操作

        /**
         * Initializes or doubles table size.  If null, allocates in
         * accord with initial capacity target held in field threshold.
         * Otherwise, because we are using power-of-two expansion, the
         * elements from each bin must either stay at same index, or move
         * with a power of two offset in the new table.
         *
         * @return the table
         */
        final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            //table不為空,且容量大於0
            if (oldCap > 0) {
                //如果舊的容量到達閾值,則不再擴容,閾值直接設置為最大值
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                //如果舊的容量沒有到達閾值,直接操作
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            //閾值大於0,直接使用舊的閾值
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            //如果閾值為零,則使用默認的初始化值
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            //更新數組桶
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            //將之前舊數組桶的數據重新移到新數組桶中
            if (oldTab != null) {
                //依次遍歷舊table中每個數組桶的元素
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    //如果數組桶中含有元素
                    if ((e = oldTab[j]) != null) {
                        //將下標數據清空
                        oldTab[j] = null;
                        //如果元組的某一桶中只有一個元素,則直接將該元素移到新的位置去
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        //如果是紅黑樹結構
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                         //鏈表 -- 對舊桶里鏈表中的每一個元素重新計算哈希值得到下標
                        else { // preserve order
                            //將原先桶中的鏈表分為兩個鏈表
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                /*
                                 * e.hash & oldCap 對hash取模運算,
                                 * 雖然數組大小擴大了一倍,
                                 * 但是同一個key在新舊table中對應的index卻存在一定聯繫: 
                                 * 要麼一致,要麼相差一個 oldCap。
                                 */
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }
    

      此處在處理鏈表的時候,如何將鏈表中的節點重新分配到新的哈希表需要做一些解釋。在擴容的時候,將原來的哈希表擴大了一倍,原來屬於同一個桶中的數據會被重新分配,此時取模運算時(a mod b),會注意到,b會擴大兩倍(a mod 2b),此時如果該桶中的某一個數據的哈希值是c1(0<c<b),則它必定還是會落入原來的位置,而如果桶中的某一個數據的哈希值是c2(b<c2<2b),則它會被重新分配到一個新的位置(這個位置是原先的哈希桶位置+舊桶的大小)。

    HashMap在多線程的情況下出現的死循環現象

      在某些java版本中擴容機制如果使用鏈表,且再插入時使用尾插法會出現死循環,具體原因可以參考老生常談,HashMap的死循環,在本文中所參考的java版本使用了頭插法的方式將元素添加到鏈表當中,可以避免死循環的出現,但是會出現一部分節點丟失的問題。如圖:

      假設原始的哈希map的某個桶的數據如下,此時線程開始擴容,將桶中的數據分配到lo和hi桶的鏈表中。

       初始時刻線程1和線程2開始運行,線程1在執行完以下代碼后,線程1的時間片運行結束。線程1運行的結果如圖所示

      線程2與線程1同時運行,線程2的時間片未用完,還在繼續執行,根據代碼的分配策略,線程2直到時間片運行結束,出現如圖所示的結果:

       此時CPU的時間片又被分配到了線程1,線程1繼續運行,因為此時A所在的鏈表結構已經發生了變化,只能處理A,B,D三個元素。此時線程1創建的hashmap如圖:

     

     參考資料

      教你初步了解紅黑樹

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 深入正則表達式(3):正則表達式工作引擎流程分析與原理釋義

    作為正則的使用者也一樣,不懂正則引擎原理的情況下,同樣可以寫出滿足需求的正則,但是不知道原理,卻很難寫出高效且沒有隱患的正則。所以對於經常使用正則,或是有興趣深入學習正則的人,還是有必要了解一下正則引擎的匹配原理的。

    有興趣可以回顧《深入正則表達式(0):正則表達式概述》

    正則引擎類型

    正則引擎主要可以分為兩大類:一種是DFA(Deterministic Finite Automatons/確定性有限自動機—),一種是NFA(Nondeterministic Finite Automatons/非確定性有限自動機)。總的來說,

    • DFA可以稱為文本主導的正則引擎

    • NFA可以稱為表達式主導的正則引擎

    NFA與DFA工作的區別:

    我們常常說用正則去匹配文本,這是NFA的思路,DFA本質上其實是用文本去匹配正則

    'for tonight's'.match(/to(nite|knite|night)/);
    • 如果是NFA引擎,表達式佔主導地位。在字符串先查找字符串中的t,然後依次匹配,如果是o,則繼續(以此循環)。匹配到to后,到n,就面臨三種選擇,每一種都去嘗試匹配一下(它也不嫌累),第一個分支也是依次匹配,到t這裏停止(nite分到t這裏直接被淘汰);同理,接着第二個分支在k這裏也停止了;終於在第三個分支柳暗花明,找到了自己的歸宿。 NFA 工作方式是以正則表達式為標準,反覆測試字符串,這樣同樣一個字符串有可能被反覆測試了很多次!

    • 如果是DFA引擎呢,文本佔主導地位。從整個字符串第一個字符開始f開始查找t,查找到t后,定位到t,以知其後為o,則去查看正則表達式其相應位置后是否為o,如果是,則繼續(以此循環),再去查正則表達式o后是否為n(此時淘汰knite分支),再后是否為g(淘汰nite分支),這個時候只剩一個分支,直接匹配到終止即可。

    只有正則表達式才有分支和範圍,文本僅僅是一個字符流。這帶來什麼樣的後果?就是NFA引擎在匹配失敗的時候,如果有其他的分支或者範圍,它會返回,記住,返回,去嘗試其他的分支而DFA引擎一旦匹配失敗,就結束了,它沒有退路。

    這就是它們之間的本質區別。其他的不同都是這個特性衍生出來的。

    NFA VS DFA

    首先,正則表達式在計算機看來只是一串符號,正則引擎首先肯定要解析它。NFA引擎只需要編譯就好了;而DFA引擎則比較繁瑣,編譯完還不算,還要遍歷出表達式中所有的可能。因為對DFA引擎來說機會只有一次,它必須得提前知道所有的可能,才能匹配出最優的結果。

    所以,在編譯階段,NFA引擎比DFA引擎快

     

    其次,DFA引擎在匹配途中一遍過,溜得飛起。相反NFA引擎就比較苦逼了,它得不厭其煩的去嘗試每一種可能性,可能一段文本它得不停返回又匹配,重複好多次。當然運氣好的話也是可以一遍過的。

    所以,在運行階段,NFA引擎比DFA引擎慢

     

    最後,因為NFA引擎是表達式佔主導地位,所以它的表達能力更強,開發者的控制度更高,也就是說開發者更容易寫出性能好又強大的正則來,當然也更容易造成性能的浪費甚至撐爆CPU。DFA引擎下的表達式,只要可能性是一樣的,任何一種寫法都是沒有差別(可能對編譯有細微的差別)的,因為對DFA引擎來說,表達式其實是死的。而NFA引擎下的表達式,高手寫的正則和新手寫的正則,性能可能相差10倍甚至更多。

    也正是因為主導權的不同,正則中的很多概念,比如非貪婪模式、反向引用、零寬斷言等只有NFA引擎才有。

    所以,在表達能力上,NFA引擎秒殺DFA引擎

     

    但是NFA以表達式為主導,因而NFA更容易操縱,因此一般程序員更偏愛NFA引擎!

    當今市面上大多數正則引擎都是NFA引擎,應該就是勝在表達能力上。

     

    總體來說,兩種引擎的工作方式完全不同,一個(NFA)以表達式為主導,一個(DFA)以文本為主導!兩種引擎各有所長,而真正的引用則取決與你的需要以及所使用的語言。

    這兩種引擎都有了很久的歷史(至今二十多年),當中也由這兩種引擎產生了很多變體!

    因為NFA引擎比較靈活,很多語言在實現上有細微的差別。所以後來大家弄了一個標準,符合這個標準的正則引擎就叫做POSIX NFA引擎,其餘的就只能叫做傳統型NFA引擎咯。

    Deterministic finite automaton,Non-deterministic finite automaton,Traditional NFA,Portable Operating System Interface for uniX NFA

    於是POSIX的出台規避了不必要變體的繼續產生。這樣一來,主流的正則引擎又分為3類:DFA,傳統型NFA,POSIX NFA。

    正則引擎三國

    DFA引擎

    DFA引擎在線性時狀態下執行,因為它們不要求回溯(並因此它們永遠不測試相同的字符兩次)。

    DFA引擎還可以確保匹配最長的可能的字符串。但是,因為 DFA 引擎只包含有限的狀態,所以它不能匹配具有反向引用的模式;並且因為它不構造显示擴展,所以它不可以捕獲子表達式。

    DFN不回溯,所以匹配快速,因而不支持捕獲組,支持反向引用和$number引用

    傳統的 NFA引擎

    傳統的 NFA 引擎運行所謂的“貪婪的”匹配回溯算法,以指定順序測試正則表達式的所有可能的擴展並接受第一個匹配項。因為傳統的 NFA 構造正則表達式的特定擴展以獲得成功的匹配,所以它可以捕獲子表達式匹配和匹配的反向引用。但是,因為傳統的 NFA 回溯,所以它可以訪問完全相同的狀態多次(如果通過不同的路徑到達該狀態)。因此,在最壞情況下,它的執行速度可能非常慢。因為傳統的 NFA 接受它找到的第一個匹配,所以它還可能會導致其他(可能更長)匹配未被發現

    大多數編程語言和工具使用的是傳統型的NFA引擎,它有一些DFA不支持的特性:

    • 捕獲組、反向引用和$number引用方式;

    • 環視(Lookaround,(?<=…)、(?<!…)、(?=…)、(?!…)),或者有的有文章叫做預搜索;

    • 忽略優化量詞(??、*?、+?、{m,n}?、{m,}?),或者有的文章叫做非貪婪模式;

    • 佔有優先量詞(?+、*+、++、{m,n}+、{m,}+,目前僅Java和PCRE支持),固化分組(?>…)。

    POSIX NFA引擎

    POSIX NFA引擎主要指符合POSIX標準的NFA引擎,與傳統的 NFA 引擎類似,不同的一點在於:提供longest-leftmost匹配,也就是在找到最左側最長匹配之前,它將繼續回溯(可以確保已找到了可能的最長的匹配之前它們將繼續回溯)。因此,POSIX NFA 引擎的速度慢於傳統的 NFA 引擎;並且在使用 POSIX NFA 時,您恐怕不會願意在更改回溯搜索的順序的情況下來支持較短的匹配搜索,而非較長的匹配搜索。

    同DFA一樣,非貪婪模式或者說忽略優先量詞對於POSIX NFA同樣是沒有意義的。

    三種引擎的使用情況

    • 使用傳統型NFA引擎的程序主要有(主流):

      • Java、Emacs(JavaScript/actionScript)、Perl、PHP、Python、Ruby、.NET語言

      • VI,GNU Emacs,PCRE library,sed;

    • 使用POSIX NFA引擎的程序主要有:mawk,Mortice Kern Systems’ utilities,GNU Emacs(使用時可以明確指定);

    • 使用DFA引擎的程序主要有:awk,egrep,flex,lex,MySQL,Procmail等;

    • 也有使用DFA/NFA混合的引擎:GNU awk,GNU grep/egrep,Tcl。

     

    《精通正則表達式》書中說POSIX NFA引擎不支持非貪婪模式,很明顯JavaScript不是POSIX NFA引擎。

    '123456'.match(/\d{3,6}/);
    // ["123456", index: 0, input: "123456", groups: undefined]
    '123456'.match(/\d{3,6}?/);
    // ["123", index: 0, input: "123456", groups: undefined]

    JavaScript的正則引擎是傳統型NFA引擎。

    為什麼POSIX NFA引擎不支持也沒有必要支持非貪婪模式?

    回溯

    現在我們知道,NFA引擎是用表達式去匹配文本,而表達式又有若干分支和範圍,一個分支或者範圍匹配失敗並不意味着最終匹配失敗,正則引擎會去嘗試下一個分支或者範圍。

    正是因為這樣的機制,引申出了NFA引擎的核心特點——回溯。

    首先我們要區分備選狀態和回溯。

    什麼是備選狀態?就是說這一個分支不行,那我就換一個分支,這個範圍不行,那我就換一個範圍。正則表達式中可以商榷的部分就叫做備選狀態。

    備選狀態可以實現模糊匹配,是正則表達能力的一方面。

    回溯可不是個好東西。想象一下,面前有兩條路,你選擇了一條,走到盡頭髮現是條死路,你只好原路返回嘗試另一條路。這個原路返回的過程就叫回溯,它在正則中的含義是吐出已經匹配過的文本。

    我們來看兩個例子:

    'abbbc'.match(/ab{1,3}c/);
    // ["abbbc", index: 0, input: "abbbc", groups: undefined]
    'abc'.match(/ab{1,3}c/);
    // ["abc", index: 0, input: "abc", groups: undefined]

    第一個例子,第一次a匹配a成功,接着碰到貪婪匹配,不巧正好是三個b貪婪得逞,最後用c匹配c成功。

    正則 文本
    /a/ a
    /ab{1,3}/ ab
    /ab{1,3}/ abb
    /ab{1,3}/ abbb
    /ab{1,3}c/ abbbc

    第二個例子的區別在於文本只有一個b。所以表達式在匹配第一個b成功後繼續嘗試匹配b,然而它見到的只有黃臉婆c。不得已將c吐出來,委屈一下,畢竟貪婪匹配也只是盡量匹配更多嘛,還是要臣服於匹配成功這個目標。最後不負眾望用c匹配c成功。

    正則 文本
    /a/ a
    /ab{1,3}/ ab
    /ab{1,3}/ abc
    /ab{1,3}/ ab
    /ab{1,3}c/ abc

    請問,第二個例子發生回溯了嗎?

    並沒有。

    誒,你這樣就不講道理了。不是把c吐出來了嘛,怎麼就不叫回溯了?

    回溯是吐出已經匹配過的文本。匹配過程中造成的匹配失敗不算回溯

    為了讓大家更好的理解,我舉一個例子:

    你和一個女孩子(或者男孩子)談戀愛,接觸了半個月後發現實在不合適,於是提出分手。這不叫回溯,僅僅是不合適而已。

    你和一個女孩子(或者男孩子)談戀愛,這段關係維持了兩年,並且已經同居。但由於某些不可描述的原因,疲憊掙扎之後,兩人最終還是和平分手。這才叫回溯。

    雖然都是分手,但你們應該能理解它們的區別吧。

    為了讓大家更好的理解,我舉一個例子:

    你和一個女孩子(或者男孩子)談戀愛,接觸了半個月後發現實在不合適,於是提出分手。這不叫回溯,僅僅是不合適而已。

    你和一個女孩子(或者男孩子)談戀愛,這段關係維持了兩年,並且已經同居。但由於某些不可描述的原因,疲憊掙扎之後,兩人最終還是和平分手。這才叫回溯。

    雖然都是分手,但你們應該能理解它們的區別吧。

    網絡上有很多文章都認為上面第二個例子發生了回溯。至少根據我查閱的資料,第二個例子發生的情況不能被稱為回溯。當然也有可能我([馬蹄疾]是錯的,歡迎討論。

    我們再來看一個真正的回溯例子:

    'ababc'.match(/ab{1,3}c/);
    // ["abc", index: 2, input: "ababc", groups: undefined]

    匹配文本到ab為止,都沒什麼問題。後面既匹配不到b,也匹配不到c。引擎只好將文本ab吐出來,從下一個位置開始匹配。因為上一次是從第一個字符a開始匹配,所以下一個位置當然就是從第二個字符b開始咯。

    正則 文本
    /a/ a
    /ab{1,3}/ ab
    /ab{1,3}/ aba
    /ab{1,3}/ ab
    /ab{1,3}c/ aba
    /a/ ab
    /a/ aba
    /ab{1,3}/ abab
    /ab{1,3}/ ababc
    /ab{1,3}/ abab
    /ab{1,3}c/ ababc

    一開始引擎是以為會和最早的ab走完餘生的,然而命運弄人,從此天涯。

    這他媽才叫回溯!

    還有一個細節。上面例子中的回溯並沒有往回吐呀,吐出來之後不應該往回走嘛,怎麼往後走了?

    我們再來看一個例子:

    '"abc"def'.match(/".*"/);
    // [""abc"", index: 0, input: ""abc"def", groups: undefined]

    因為.*是貪婪匹配,所以它把後面的字符都吞進去了。直到發現目標完不成,不得已往回吐,吐到第二個”為止,終於匹配成功。這就好比結了婚還在外面養小三,幾經折騰才發現家庭才是最重要的,自己的行為背離了初衷,於是幡然悔悟。

    正則 文本
    /”/
    /”.*/ “a
    /”.*/ “ab
    /”.*/ “abc
    /”.*/ “abc”
    /”.*/ “abc”d
    /”.*/ “abc”de
    /”.*/ “abc”def
    /”.*”/ “abc”def
    /”.*”/ “abc”de
    /”.*”/ “abc”d
    /”.*”/ “abc”

    我想說的是,不要被回溯的回字迷惑了。它的本質是把已經吞進去的字符吐出來。至於吐出來之後是往回走還是往後走,是要根據情況而定的。

    優化正則表達式

    現在我們知道了控制回溯是控制正則表達式性能的關鍵。

    控制回溯又可以拆分成兩部分:第一是控製備選狀態的數量,第二是控製備選狀態的順序。

    備選狀態的數量當然是核心,然而如果備選狀態雖然多,卻早早的匹配成功了,早匹配早下班,也就沒那麼多糟心事了。

    傳統NFA工作流程

    許多因素影響正則表達式的效率,首先,正則表達式適配的文本千差萬別,部分匹配時比完全不匹配所用的時間要長。上面提到過,JavaScript是傳統NFA引擎,當然每種瀏覽器的正則表達式引擎也有不同的內部優化。

    為了有效地使用正則表達式,重要的是理解它們的工作原理。下面是一個正則表達式處理的基本步驟:

    第一步:編譯

    當你創建了一個正則表達式對象之後(使用一個正則表達式直接量或者RegExp構造器),瀏覽器檢查你的模板有沒有錯誤,然後將它轉換成一個本機代碼例程,用於執行匹配工作。如果你將正則表達式賦給一個變量,你可以避免重複執行此步驟。

    第二步:設置起始位置

    當一個正則表達式投入使用時,首先要確定目標字符串中開始搜索的位置。它是字符串的起始位置,或由正則表達式的lastIndex屬性指定,但是當它從第四步返回到這裏的時候(因為嘗試匹配失敗),此位置將位於最後一次嘗試起始位置推后一個字符的位置上。

          瀏覽器優化正則表達式引擎的辦法是,在這一階段中通過早期預測跳過一些不必要的工作。例如,如果一個正則表達式以^開頭,IE 和Chrome通常判斷在字符串起始位置上是否能夠匹配,然後可避免愚蠢地搜索後續位置。另一個例子是匹配第三個字母是x的字符串,一個聰明的辦法是先找到x,然後再將起始位置回溯兩個字符。

    第三步:匹配每個正則表達式的字元

          正則表達式一旦找好起始位置,它將一個一個地掃描目標文本和正則表達式模板。當一個特定字元匹配失敗時,正則表達式將試圖回溯到掃描之前的位置上,然後進入正則表達式其他可能的路徑上。

          第四步:匹配成功或失敗

          如果在字符串的當前位置上發現一個完全匹配,那麼正則表達式宣布成功。如果正則表達式的所有可能路徑都嘗試過了,但是沒有成功地匹配,那麼正則表達式引擎回到第二步,從字符串的下一個字符重新嘗試。只有字符串中的每個字符(以及最後一個字符後面的位置)都經歷了這樣的過程之後,還沒有成功匹配,那麼正則表達式就宣布徹底失敗。

          牢記這一過程將有助於您明智地判別那些影響正則表達式性能問題的類型。

     

    工具

    [ regex101 ]是一個很多人推薦過的工具,可以拆分解釋正則的含義,還可以查看匹配過程,幫助理解正則引擎。如果只能要一個正則工具,那就是它了。

    [ regexper ]是一個能讓正則的備選狀態可視化的工具,也有助於理解複雜的正則語法。

     

    參考文章:

     https://baike.baidu.com/item/正則表達式

    正則表達式工作原理 https://www.cnblogs.com/aaronjs/archive/2012/06/30/2570800.html

    一次性搞懂JavaScript正則表達式之引擎 https://juejin.im/post/5becc2aef265da6110369c93

     

    轉載本站文章《深入正則表達式(3):正則表達式工作引擎流程分析與原理釋義》,
    請註明出處:https://www.zhoulujun.cn/html/theory/algorithm/IntroductionAlgorithms/8430.html

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

    ※推薦台中搬家公司優質服務,可到府估價

  • Spring Boot 集成 Swagger 構建接口文檔

    Spring Boot 集成 Swagger 構建接口文檔

    在應用開發過程中經常需要對其他應用或者客戶端提供 RESTful API 接口,尤其是在版本快速迭代的開發過程中,修改接口的同時還需要同步修改對應的接口文檔,這使我們總是做着重複的工作,並且如果忘記修改接口文檔,就可能造成不必要的麻煩。

    為了解決這些問題,Swagger 就孕育而生了,那讓我們先簡單了解下。

    Swagger 簡介

    Swagger 是一個規範和完整的框架,用於生成、描述、調用和可視化 RESTful 風格的 Web 服務

    總體目標是使客戶端和文件系統作為服務器,以同樣的速度來更新。文件的方法、參數和模型緊密集成到服務器端的代碼中,允許 API 始終保持同步。

    下面我們在 Spring Boot 中集成 Swagger 來構建強大的接口文檔。

    Spring Boot 集成 Swagger

    Spring Boot 集成 Swagger 主要分為以下三步:

    1. 加入 Swagger 依賴
    2. 加入 Swagger 文檔配置
    3. 使用 Swagger 註解編寫 API 文檔

    加入依賴

    首先創建一個項目,在項目中加入 Swagger 依賴,項目依賴如下所示:

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>2.9.2</version>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>2.9.2</version>
            </dependency>
    

    加入配置

    接下來在 config 包下創建一個 Swagger 配置類 Swagger2Configuration,在配置類上加入註解 @EnableSwagger2,表明開啟 Swagger,注入一個 Docket 類來配置一些 API 相關信息,apiInfo() 方法內定義了幾個文檔信息,代碼如下:

    @Configuration
    @EnableSwagger2
    public class Swagger2Configuration {
    
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    // swagger 文檔掃描的包
                    .apis(RequestHandlerSelectors.basePackage("com.wupx.interfacedoc.controller"))
                    .paths(PathSelectors.any())
                    .build();
        }
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("測試接口列表")
                    .description("Swagger2 接口文檔")
                    .version("v1.0.0")
                    .contact(new Contact("wupx", "https://www.tianheyu.top", "wupx@qq.com"))
                    .license("Apache License, Version 2.0")
                    .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
                    .build();
        }
    }
    

    列舉其中幾個文檔信息說明下:

    • title:接口文檔的標題
    • description:接口文檔的詳細描述
    • termsOfServiceUrl:一般用於存放公司的地址
    • version:API 文檔的版本號
    • contact:維護人、維護人 URL 以及 email
    • license:許可證
    • licenseUrl:許可證 URL

    編寫 API 文檔

    domain 包下創建一個 User 實體類,使用 @ApiModel 註解表明這是一個 Swagger 返回的實體,@ApiModelProperty 註解表明幾個實體的屬性,代碼如下(其中 getter/setter 省略不显示):

    @ApiModel(value = "用戶", description = "用戶實體類")
    public class User {
    
        @ApiModelProperty(value = "用戶 id", hidden = true)
        private Long id;
    
        @ApiModelProperty(value = "用戶姓名")
        private String name;
    
        @ApiModelProperty(value = "用戶年齡")
        private String age;
    
        // getter/setter
    }
    

    最後,在 controller 包下創建一個 UserController 類,提供用戶 API 接口(未使用數據庫),代碼如下:

    @RestController
    @RequestMapping("/users")
    @Api(tags = "用戶管理接口")
    public class UserController {
    
        Map<Long, User> users = Collections.synchronizedMap(new HashMap<>());
    
        @GetMapping("/")
        @ApiOperation(value = "獲取用戶列表", notes = "獲取用戶列表")
        public List<User> getUserList() {
            return new ArrayList<>(users.values());
        }
    
        @PostMapping("/")
        @ApiOperation(value = "創建用戶")
        public String addUser(@RequestBody User user) {
            users.put(user.getId(), user);
            return "success";
        }
    
        @GetMapping("/{id}")
        @ApiOperation(value = "獲取指定 id 的用戶")
        @ApiImplicitParam(name = "id", value = "用戶 id", paramType = "query", dataTypeClass = Long.class, defaultValue = "999", required = true)
        public User getUserById(@PathVariable Long id) {
            return users.get(id);
        }
    
        @PutMapping("/{id}")
        @ApiOperation(value = "根據 id 更新用戶")
        @ApiImplicitParams({
                @ApiImplicitParam(name = "id", value = "用戶 id", defaultValue = "1"),
                @ApiImplicitParam(name = "name", value = "用戶姓名", defaultValue = "wupx"),
                @ApiImplicitParam(name = "age", value = "用戶年齡", defaultValue = "18")
        })
        public User updateUserById(@PathVariable Long id, @RequestParam String name, @RequestParam Integer age) {
            User user = users.get(id);
            user.setName(name);
            user.setAge(age);
            return user;
        }
    
        @DeleteMapping("/{id}")
        @ApiOperation(value = "刪除用戶", notes = "根據 id 刪除用戶")
        @ApiImplicitParam(name = "id", value = "用戶 id", dataTypeClass = Long.class, required = true)
        public String deleteUserById(@PathVariable Long id) {
            users.remove(id);
            return "success";
        }
    }
    

    啟動項目,訪問 http://localhost:8080/swagger-ui.html,可以看到我們定義的文檔已經在 Swagger 頁面上显示了,如下圖所示:

    到此為止,我們就完成了 Spring Boot 與 Swagger 的集成。

    同時 Swagger 除了接口文檔功能外,還提供了接口調試功能,以創建用戶接口為例,單擊創建用戶接口,可以看到接口定義的參數、返回值、響應碼等,單擊 Try it out 按鈕,然後點擊 Execute 就可以發起調用請求、創建用戶,如下圖所示:

    註解介紹

    由於 Swagger 2 提供了非常多的註解供開發使用,這裏列舉一些比較常用的註解。

    @Api

    @Api 用在接口文檔資源類上,用於標記當前類為 Swagger 的文檔資源,其中含有幾個常用屬性:

    • value:定義當前接口文檔的名稱。
    • description:用於定義當前接口文檔的介紹。
    • tag:可以使用多個名稱來定義文檔,但若同時存在 tag 屬性和 value 屬性,則 value 屬性會失效。
    • hidden:如果值為 true,就會隱藏文檔。

    @ApiOperation

    @ApiOperation 用在接口文檔的方法上,主要用來註解接口,其中包含幾個常用屬性:

    • value:對API的簡短描述。
    • note:API的有關細節描述。
    • esponse:接口的返回類型(注意:這裏不是返回實際響應,而是返回對象的實際結果)。
    • hidden:如果值為 true,就會在文檔中隱藏。

    @ApiResponse、@ApiResponses

    @ApiResponses 和 @ApiResponse 二者配合使用返回 HTTP 狀態碼。@ApiResponses 的 value 值是 @ApiResponse 的集合,多個 @ApiResponse 用逗號分隔,其中 @ApiResponse 包含的屬性如下:

    • code:HTTP狀態碼。
    • message:HTTP狀態信息。
    • responseHeaders:HTTP 響應頭。

    @ApiParam

    @ApiParam 用於方法的參數,其中包含以下幾個常用屬性:

    • name:參數的名稱。
    • value:參數值。
    • required:如果值為 true,就是必傳字段。
    • defaultValue:參數的默認值。
    • type:參數的類型。
    • hidden:如果值為 true,就隱藏這個參數。

    @ApiImplicitParam、@ApiImplicitParams

    二者配合使用在 API 方法上,@ApiImplicitParams 的子集是 @ApiImplicitParam 註解,其中 @ApiImplicitParam 註解包含以下幾個參數:

    • name:參數的名稱。
    • value:參數值。
    • required:如果值為 true,就是必傳字段。
    • defaultValue:參數的默認值。
    • dataType:數據的類型。
    • hidden:如果值為 true,就隱藏這個參數。
    • allowMultiple:是否允許重複。

    @ResponseHeader

    API 文檔的響應頭,如果需要設置響應頭,就將 @ResponseHeader 設置到 @ApiResponseresponseHeaders 參數中。@ResponseHeader 提供了以下幾個參數:

    • name:響應頭名稱。
    • description:響應頭備註。

    @ApiModel

    設置 API 響應的實體類,用作 API 返回對象。@ApiModel 提供了以下幾個參數:

    • value:實體類名稱。
    • description:實體類描述。
    • subTypes:子類的類型。

    @ApiModelProperty

    設置 API 響應實體的屬性,其中包含以下幾個參數:

    • name:屬性名稱。
    • value:屬性值。
    • notes:屬性的註釋。
    • dataType:數據的類型。
    • required:如果值為 true,就必須傳入這個字段。
    • hidden:如果值為 true,就隱藏這個字段。
    • readOnly:如果值為 true,字段就是只讀的。
    • allowEmptyValue:如果為 true,就允許為空值。

    到此為止,我們就介紹完了 Swagger 提供的主要註解。

    總結

    Swagger 可以輕鬆地整合到 Spring Boot 中構建出強大的 RESTful API 文檔,可以減少我們編寫接口文檔的工作量,同時接口的說明內容也整合入代碼中,可以讓我們在修改代碼邏輯的同時方便的修改接口文檔說明,另外 Swagger 也提供了頁面測試功能來調試每個 RESTful API。

    如果項目中還未使用,不防嘗試一下,會發現效率會提升不少。

    本文的完整代碼在 https://github.com/wupeixuan/SpringBoot-Learn 的 interface-doc 目錄下。

    最好的關係就是互相成就,大家的在看、轉發、留言三連就是我創作的最大動力。

    參考

    http://swagger.io

    https://github.com/wupeixuan/SpringBoot-Learn

    《Spring Boot 2 實戰之旅》

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

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

  • 容器技術之Docker私有鏡像倉庫docker-distribution

    容器技術之Docker私有鏡像倉庫docker-distribution

      在前邊的博客中我們說到docker的架構由docker客戶端、服務端以及倉庫組成;docker倉庫就是用來存放鏡像的地方;其實docker registry我們理解為存放docker鏡像倉庫的倉庫比較準確吧;因為docker的鏡像倉庫通常是把同一類的鏡像用不同的版本來區別,而registry則是用來存放這些倉庫的倉庫;默認安裝docker都是從dockerhub鏡像倉庫下載鏡像;其實在生產環境中,我們很少去公有倉庫上下載鏡像,原因之一是公有倉庫中的鏡像在生產環境中使用,有些不適配,通常我們是去公有倉庫下載基礎鏡像,然後基於基礎鏡像構建適合自己生產環境中的鏡像;其次公有倉庫鏡像有很多都不是安全的鏡像,這麼說吧,我們不確定自己下載的鏡像是否有後門,是否有挖礦代碼,所以基於種種因素,我們還是有必要搭建自己私有的鏡像倉庫;今天我們就來聊一聊docker的私有鏡像倉庫的搭建;

      1、查看docker-distribution包簡介

    [root@docker_registry ~]# yum info docker-distribution
    Loaded plugins: fastestmirror
    Loading mirror speeds from cached hostfile
     * base: mirrors.aliyun.com
     * extras: mirrors.aliyun.com
     * updates: mirrors.aliyun.com
    Available Packages
    Name        : docker-distribution
    Arch        : x86_64
    Version     : 2.6.2
    Release     : 2.git48294d9.el7
    Size        : 3.5 M
    Repo        : extras/7/x86_64
    Summary     : Docker toolset to pack, ship, store, and deliver content
    URL         : https://github.com/docker/distribution
    License     : ASL 2.0
    Description : Docker toolset to pack, ship, store, and deliver content
    
    [root@docker_registry ~]# 
    

      提示:docker-distribution這個包就是提供簡單倉庫服務軟件實現;

      2、安裝docker-distribution

    [root@docker_registry ~]# yum install -y docker-distribution
    Loaded plugins: fastestmirror
    Loading mirror speeds from cached hostfile
     * base: mirrors.aliyun.com
     * extras: mirrors.aliyun.com
     * updates: mirrors.aliyun.com
    Resolving Dependencies
    There are unfinished transactions remaining. You might consider running yum-complete-transaction, or "yum-complete-transaction --cleanup-only" and "yum history redo last", first to finish them. If those don't work you'll have to try removing/installing packages by hand (maybe package-cleanup can help).
    The program yum-complete-transaction is found in the yum-utils package.
    --> Running transaction check
    ---> Package docker-distribution.x86_64 0:2.6.2-2.git48294d9.el7 will be installed
    --> Finished Dependency Resolution
    
    Dependencies Resolved
    
    ===================================================================================================================
     Package                         Arch               Version                               Repository          Size
    ===================================================================================================================
    Installing:
     docker-distribution             x86_64             2.6.2-2.git48294d9.el7                extras             3.5 M
    
    Transaction Summary
    ===================================================================================================================
    Install  1 Package
    
    Total download size: 3.5 M
    Installed size: 12 M
    Downloading packages:
    docker-distribution-2.6.2-2.git48294d9.el7.x86_64.rpm                                       | 3.5 MB  00:00:03     
    Running transaction check
    Running transaction test
    Transaction test succeeded
    Running transaction
      Installing : docker-distribution-2.6.2-2.git48294d9.el7.x86_64                                               1/1 
      Verifying  : docker-distribution-2.6.2-2.git48294d9.el7.x86_64                                               1/1 
    
    Installed:
      docker-distribution.x86_64 0:2.6.2-2.git48294d9.el7                                                              
    
    Complete!
    [root@docker_registry ~]# 
    

      3、查看docker-distribution安裝了那些文件

    [root@docker_registry ~]# rpm -ql docker-distribution
    /etc/docker-distribution/registry/config.yml
    /usr/bin/registry
    /usr/lib/systemd/system/docker-distribution.service
    /usr/share/doc/docker-distribution-2.6.2
    /usr/share/doc/docker-distribution-2.6.2/AUTHORS
    /usr/share/doc/docker-distribution-2.6.2/CONTRIBUTING.md
    /usr/share/doc/docker-distribution-2.6.2/LICENSE
    /usr/share/doc/docker-distribution-2.6.2/MAINTAINERS
    /usr/share/doc/docker-distribution-2.6.2/README.md
    /var/lib/registry
    [root@docker_registry ~]# 
    

      提示:/etc/docker-distribution/registry/config.yml這個文件用於配置registry的配置文件;/usr/bin/registry是二進制應用程序;/usr/lib/systemd/system/docker-distribution.service 這個文件是docker-distribution的unit file;/var/lib/registry這個目錄用於存放我們上傳到registry上的鏡像存放地;

      4、查看配置文件

    [root@docker_registry ~]# cat /etc/docker-distribution/registry/config.yml
    version: 0.1
    log:
      fields:
        service: registry
    storage:
        cache:
            layerinfo: inmemory
        filesystem:
            rootdirectory: /var/lib/registry
    http:
        addr: :5000
    [root@docker_registry ~]# 
    

      提示:這個配置文件是一個yml語法的配置文件,從上面的信息可以看到,默認情況docker-distribution監聽在tcp的5000端口;存放鏡像的目錄是/var/lib/registry/目錄下;

      5、啟動docker-distribution

    [root@docker_registry ~]# systemctl start docker-distribution
    [root@docker_registry ~]# ss -tnl
    State       Recv-Q Send-Q            Local Address:Port                           Peer Address:Port              
    LISTEN      0      128                           *:22                                        *:*                  
    LISTEN      0      100                   127.0.0.1:25                                        *:*                  
    LISTEN      0      128                          :::22                                       :::*                  
    LISTEN      0      100                         ::1:25                                       :::*                  
    LISTEN      0      128                          :::5000                                     :::*                  
    [root@docker_registry ~]# 
    

      提示:可以看到5000端口已經處於監聽狀態了;到此docker-distribution就啟動起來了;這個倉庫服務很簡陋,沒有用戶認證功能,默認是基於http通信而非https,所以從某些角度講,不是一個安全的倉庫;所以一般不見在互聯網上使用,在自己的內外環境中可以使用;

      這裏補充一點,docker的鏡像通常是 registry地址加repository名稱加版本這三部分組成,registry可以是域名,可以是ip地址加端口,也可以說域名加端口,默認https是443端口,http是80端口,如果不寫端口默認是443而非80(原因是docker默認不支持從http協議的倉庫下載/上傳鏡像);例如 quay.io/coreos/flannel:v0.12.0-s390x  從這個鏡像名我們就可以知道registry是https://quay.io;repository名稱為coreos/flannel 版本是v0.12.0-s390x;

      示例:下載第三方倉庫鏡像到本地

      提示:可以看到下載下來的鏡像名稱就是我們剛才說的registry+repository+版本;從上面的信息我們可以總結一點,docker鏡像的名稱(標籤)反應了該鏡像來自哪個registry的那個倉庫;所以我們要下載私有鏡像倉庫中的鏡像就需要把加上私有registry的名稱或地址+repository+版本來下載私有鏡像倉庫中的鏡像;同理上傳鏡像也需要寫明上傳到那個registry中的那個repository中去;

      示例:上傳本地鏡像到私有倉庫

      提示:要把本地倉庫鏡像傳到私有倉庫中去,首先我們要把本地鏡像打一個新的標籤,按照我們剛才上面說的邏輯,然後在上傳新打到標籤的鏡像到私有倉庫就可以了;從上面的信息我們看到當我們打好標籤后,上傳鏡像時報錯了,提示我們倉庫不是https的;默認情況docker不支持http明文上傳/下載鏡像;如果我們非要用http上傳下載鏡像我們需要在配置文件中明確的告訴docker非安全倉庫地址;

      配置docker支持私有倉庫上傳下載鏡像

    [root@docker_registry ~]# cat /etc/docker/daemon.json
    {
            "registry-mirrors": ["https://registry.docker-cn.com","https://cyr1uljt.mirror.aliyuncs.com"],
            "insecure-registries": ["192.168.0.99:5000"]
    }
    
    [root@docker_registry ~]# systemctl daemon-reload    
    [root@docker_registry ~]# systemctl restart docker 
    

      提示:我們通過在配置文件中配置insecure-registries來告訴docker192.168.0.99:5000這個registry是不安全的,但是我們信任這個倉庫,大概就是這個意思嘛;通常我們是寫主機名然後配合hosts文件來解析的方式來對registry解析;從而把鏡像命名為主機名+倉庫名+版本的形式;如下所示;這裏還需要注意一點insecure-registries後面的列表中的倉庫如果有域名,域名不能有下劃線(“_”),否則重啟docker會起不來;

    [root@docker_registry ~]# cat /etc/docker/daemon.json 
    {
            "registry-mirrors": ["https://registry.docker-cn.com","https://cyr1uljt.mirror.aliyuncs.com"],
            "insecure-registries": ["192.168.0.99:5000","docker-registry.io:5000"]
    
    }
    [root@docker_registry ~]# cat /etc/hosts
    127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
    192.168.0.99 docker-registry.io registry
    192.168.0.22 docker-node01.io node01
    192.168.0.23 docker-node02.io node02
    [root@docker_registry ~]# systemctl restart docker
    [root@docker_registry ~]# docker info
    Client:
     Debug Mode: false
    
    Server:
     Containers: 0
      Running: 0
      Paused: 0
      Stopped: 0
     Images: 1
     Server Version: 19.03.11
     Storage Driver: overlay2
      Backing Filesystem: xfs
      Supports d_type: true
      Native Overlay Diff: true
     Logging Driver: json-file
     Cgroup Driver: cgroupfs
     Plugins:
      Volume: local
      Network: bridge host ipvlan macvlan null overlay
      Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
     Swarm: inactive
     Runtimes: runc
     Default Runtime: runc
     Init Binary: docker-init
     containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429
     runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
     init version: fec3683
     Security Options:
      seccomp
       Profile: default
     Kernel Version: 3.10.0-693.el7.x86_64
     Operating System: CentOS Linux 7 (Core)
     OSType: linux
     Architecture: x86_64
     CPUs: 4
     Total Memory: 1.785GiB
     Name: docker_registry
     ID: R34V:IG2F:23I6:6WG6:FFQ4:75SV:3UKZ:RFH7:DGCO:QS7V:CS7K:NSH6
     Docker Root Dir: /var/lib/docker
     Debug Mode: false
     Registry: https://index.docker.io/v1/
     Labels:
     Experimental: false
     Insecure Registries:
      192.168.0.99:5000
      docker-registry.io:5000
      127.0.0.0/8
     Registry Mirrors:
      https://registry.docker-cn.com/
      https://cyr1uljt.mirror.aliyuncs.com/
     Live Restore Enabled: false
    
    [root@docker_registry ~]#
    

      提示:重啟docker后,如果在docker info 中能夠看到我們配置的內容說明配置生效了;現在我們再來傳我們新打的標籤的鏡像,看看是否能夠傳到我們的私有倉庫呢?

    [root@docker_registry ~]# docker images
    REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
    docker-registry.io:5000/centos   7                   b5b4d78bc90c        4 weeks ago         203MB
    centos                           7                   b5b4d78bc90c        4 weeks ago         203MB
    192.168.0.99:5000/flannel        v0.12.0-s390x       57eade024bfb        2 months ago        56.9MB
    quay.io/coreos/flannel           v0.12.0-s390x       57eade024bfb        2 months ago        56.9MB
    [root@docker_registry ~]# docker push 192.168.0.99:5000/flannel:v0.12.0-s390x
    The push refers to repository [192.168.0.99:5000/flannel]
    b67de7789e55: Pushed 
    4c4bfa1b47e6: Pushed 
    3b7ae8a9c323: Pushed 
    fbd88a276dca: Pushed 
    271ca11ef489: Pushed 
    1f106b41b4d6: Pushed 
    v0.12.0-s390x: digest: sha256:3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6 size: 1579
    [root@docker_registry ~]# docker push docker-registry.io:5000/centos:7
    The push refers to repository [docker-registry.io:5000/centos]
    edf3aa290fb3: Pushed 
    7: digest: sha256:c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941 size: 529
    [root@docker_registry ~]# 
    

      提示:可以看到我們上傳的兩個鏡像都完成了上傳沒有報錯,接下來我們去/var/lib/registry/這個目錄,看看是否有這兩個鏡像相關目錄?

    [root@docker_registry ~]# tree /var/lib/registry/
    /var/lib/registry/
    └── docker
        └── registry
            └── v2
                ├── blobs
                │   └── sha256
                │       ├── 13
                │       │   └── 13b80a37370b57f558a2e06092c39224e5d1ebac50e48df0afdeb43cf2303e60
                │       │       └── data
                │       ├── 17
                │       │   └── 176bad61a3a435da03ec603d2bd8f7a69286d92f21f447b17f21f0bc4e085bde
                │       │       └── data
                │       ├── 1b
                │       │   └── 1b56fbc8a8e10830867455c0794a70f5469c154cdc013554daf501aeda3f30fe
                │       │       └── data
                │       ├── 26
                │       │   └── 266247e2e603e1c840f97cb4d97a08b9184344e9802966cb42c93d21c407839f
                │       │       └── data
                │       ├── 3c
                │       │   └── 3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6
                │       │       └── data
                │       ├── 42
                │       │   └── 42d8e66fa893de4beb5d136b787cf182b24b7f4972c4212b9493b661ad1d7e85
                │       │       └── data
                │       ├── 52
                │       │   └── 524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
                │       │       └── data
                │       ├── 57
                │       │   └── 57eade024bfbd48c45ef2bad996c4d6a0fa41b692247294745265af738066813
                │       │       └── data
                │       ├── 85
                │       │   └── 85ecb68de4693bb4093d923f6d1062766e4fa7cbb3bf456a2bc19dd3e6c5e6c6
                │       │       └── data
                │       ├── b5
                │       │   └── b5b4d78bc90ccd15806443fb881e35b5ddba924e2f475c1071a38a3094c3081d
                │       │       └── data
                │       └── c2
                │           └── c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
                │               └── data
                └── repositories
                    ├── centos
                    │   ├── _layers
                    │   │   └── sha256
                    │   │       ├── 524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
                    │   │       │   └── link
                    │   │       └── b5b4d78bc90ccd15806443fb881e35b5ddba924e2f475c1071a38a3094c3081d
                    │   │           └── link
                    │   ├── _manifests
                    │   │   ├── revisions
                    │   │   │   └── sha256
                    │   │   │       └── c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
                    │   │   │           └── link
                    │   │   └── tags
                    │   │       └── 7
                    │   │           ├── current
                    │   │           │   └── link
                    │   │           └── index
                    │   │               └── sha256
                    │   │                   └── c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
                    │   │                       └── link
                    │   └── _uploads
                    └── flannel
                        ├── _layers
                        │   └── sha256
                        │       ├── 13b80a37370b57f558a2e06092c39224e5d1ebac50e48df0afdeb43cf2303e60
                        │       │   └── link
                        │       ├── 176bad61a3a435da03ec603d2bd8f7a69286d92f21f447b17f21f0bc4e085bde
                        │       │   └── link
                        │       ├── 1b56fbc8a8e10830867455c0794a70f5469c154cdc013554daf501aeda3f30fe
                        │       │   └── link
                        │       ├── 266247e2e603e1c840f97cb4d97a08b9184344e9802966cb42c93d21c407839f
                        │       │   └── link
                        │       ├── 42d8e66fa893de4beb5d136b787cf182b24b7f4972c4212b9493b661ad1d7e85
                        │       │   └── link
                        │       ├── 57eade024bfbd48c45ef2bad996c4d6a0fa41b692247294745265af738066813
                        │       │   └── link
                        │       └── 85ecb68de4693bb4093d923f6d1062766e4fa7cbb3bf456a2bc19dd3e6c5e6c6
                        │           └── link
                        ├── _manifests
                        │   ├── revisions
                        │   │   └── sha256
                        │   │       └── 3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6
                        │   │           └── link
                        │   └── tags
                        │       └── v0.12.0-s390x
                        │           ├── current
                        │           │   └── link
                        │           └── index
                        │               └── sha256
                        │                   └── 3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6
                        │                       └── link
                        └── _uploads
    
    65 directories, 26 files
    [root@docker_registry ~]# 
    

      提示:可以看到對應目錄下有兩個子目錄就是以我們上傳的鏡像名稱命名的;

      示例:查看私有倉庫中存在的進行列表

    [root@docker_registry ~]# curl docker-registry.io:5000/v2/_catalog
    {"repositories":["centos","flannel"]}
    [root@docker_registry ~]# 

      示例:下載私有倉庫中的鏡像到本地

    [root@docker_node01 ~]# ip a l ens33
    2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
        link/ether 00:0c:29:22:36:7f brd ff:ff:ff:ff:ff:ff
        inet 192.168.0.22/24 brd 192.168.0.255 scope global ens33
           valid_lft forever preferred_lft forever
        inet6 fe80::20c:29ff:fe22:367f/64 scope link 
           valid_lft forever preferred_lft forever
    [root@docker_node01 ~]# docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    linux1874/myimg     v0.1                e408b1c6e04f        2 weeks ago         1.22MB
    wordpress           latest              c3fa1c8546fb        5 weeks ago         540MB
    mysql               5.7                 f965319e89de        5 weeks ago         448MB
    alpine              v3                  f70734b6a266        6 weeks ago         5.61MB
    nginx               1.14-alpine         8a2fb25a19f5        14 months ago       16MB
    httpd               2.4.37-alpine       dfd436f9a5d8        17 months ago       91.8MB
    [root@docker_node01 ~]# docker pull 192.168.0.99:5000/flannel:v0.12.0-s390x
    v0.12.0-s390x: Pulling from flannel
    176bad61a3a4: Pull complete 
    13b80a37370b: Pull complete 
    42d8e66fa893: Pull complete 
    266247e2e603: Pull complete 
    1b56fbc8a8e1: Pull complete 
    85ecb68de469: Pull complete 
    Digest: sha256:3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6
    Status: Downloaded newer image for 192.168.0.99:5000/flannel:v0.12.0-s390x
    192.168.0.99:5000/flannel:v0.12.0-s390x
    [root@docker_node01 ~]# docker images
    REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
    linux1874/myimg             v0.1                e408b1c6e04f        2 weeks ago         1.22MB
    wordpress                   latest              c3fa1c8546fb        5 weeks ago         540MB
    mysql                       5.7                 f965319e89de        5 weeks ago         448MB
    alpine                      v3                  f70734b6a266        6 weeks ago         5.61MB
    192.168.0.99:5000/flannel   v0.12.0-s390x       57eade024bfb        2 months ago        56.9MB
    nginx                       1.14-alpine         8a2fb25a19f5        14 months ago       16MB
    httpd                       2.4.37-alpine       dfd436f9a5d8        17 months ago       91.8MB
    [root@docker_node01 ~]# 
    

      提示:下載私有倉庫中的鏡像,默認情況docker也是不支持直接訪問http協議的倉庫,需要我們手動去配置insecure-registries,然後重啟docker才可以;

      示例:刪除私有倉庫中的鏡像

      1、獲取對應鏡像的sha256的值 curl –header “Accept:application/vnd.docker.distribution.manifest.v2+json” -I -X GET http://<registry addr>/v2/<image name>/manifests/<image tag>

      2、刪除對應鏡像版本元數據 curl -I -X DELETE http://<registry addr>/v2/<image name>/manifests/<image digest>

      提示:如果響應405方法不被允許;我們需要修改私有倉庫的配置文件,將其配置為允許刪除;如下

    [root@docker_registry ~]# cat /etc/docker-distribution/registry/config.yml
    version: 0.1
    log:
      fields:
        service: registry
    storage:
        delete:
            enabled: true
        cache:
            layerinfo: inmemory
        filesystem:
            rootdirectory: /var/lib/registry
    http:
        addr: :5000
    [root@docker_registry ~]# systemctl restart docker-distribution           
    [root@docker_registry ~]# curl -IX DELETE http://docker-registry.io:5000/v2/centos/manifests/sha256:c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
    HTTP/1.1 202 Accepted
    Docker-Distribution-Api-Version: registry/2.0
    Date: Sat, 06 Jun 2020 19:55:52 GMT
    Content-Length: 0
    Content-Type: text/plain; charset=utf-8
    
    [root@docker_registry ~]#
    

      提示:degest值包含”sha256:”

      3、垃圾回收清理

    [root@docker_registry ~]# registry garbage-collect /etc/docker-distribution/registry/config.yml 
    centos
    flannel
    flannel: marking manifest sha256:3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6 
    flannel: marking blob sha256:57eade024bfbd48c45ef2bad996c4d6a0fa41b692247294745265af738066813
    flannel: marking blob sha256:176bad61a3a435da03ec603d2bd8f7a69286d92f21f447b17f21f0bc4e085bde
    flannel: marking blob sha256:13b80a37370b57f558a2e06092c39224e5d1ebac50e48df0afdeb43cf2303e60
    flannel: marking blob sha256:42d8e66fa893de4beb5d136b787cf182b24b7f4972c4212b9493b661ad1d7e85
    flannel: marking blob sha256:266247e2e603e1c840f97cb4d97a08b9184344e9802966cb42c93d21c407839f
    flannel: marking blob sha256:1b56fbc8a8e10830867455c0794a70f5469c154cdc013554daf501aeda3f30fe
    flannel: marking blob sha256:85ecb68de4693bb4093d923f6d1062766e4fa7cbb3bf456a2bc19dd3e6c5e6c6
    myweb
    myweb: marking manifest sha256:aaf04cf567a776e36eb3b0bafaec17ed8d9e0a743bdb897dca13f251250ae493 
    myweb: marking blob sha256:4f406abeaab7f848178867409142090d1a551b22b968be6a6dae733c8403738e
    myweb: marking blob sha256:524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
    myweb: marking blob sha256:c941076f9075280c41b502283f37ab8bafef3a66f4a7ba299838dce07641a48d
    test
    test: marking manifest sha256:5ecad23ab8a52e55f93968f708d325261032dd613287aec92e7cf8ddd6426635 
    test: marking blob sha256:370e3a843c3cb12700301e3f87f929939146cd8b676260bedcd83aaa7fcc2939
    test: marking blob sha256:524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
    test: marking manifest sha256:da8b53210bf1f4dc4873bbd5589abad616663cda45205ae3a4fffb0729d2730d 
    test: marking blob sha256:461f6ceabc885e2e90b5f9ee82aefc9a30a39510c40e7cd8fb7436a4d340fe1d
    test: marking blob sha256:524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
    test: marking blob sha256:035e026f1d6b0acba3413ba616dcbabf75d20e945778c52716e601255452b7b7
    
    17 blobs marked, 2 blobs eligible for deletion
    blob eligible for deletion: sha256:b5b4d78bc90ccd15806443fb881e35b5ddba924e2f475c1071a38a3094c3081d
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/b5/b5b4d78bc90ccd15806443fb881e35b5ddba924e2f475c1071a38a3094c3081d  go.version=go1.9.4 instance.id=b3029d7f-99e8-4941-8c87-989514b584ea
    blob eligible for deletion: sha256:c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/c2/c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941  go.version=go1.9.4 instance.id=b3029d7f-99e8-4941-8c87-989514b584ea
    [root@docker_registry ~]# 
    

      測試:下載docker-registry.io:5000/centos:7 看看是否還能下載?

    [root@docker_node01 ~]# docker pull 192.168.0.99:5000/centos:7
    Error response from daemon: manifest for 192.168.0.99:5000/centos:7 not found: manifest unknown: manifest unknown
    [root@docker_node01 ~]# 
    

      提示:以上提示告訴我們沒有對應鏡像的元數據信息;說明我們私有倉庫沒有對應鏡像;以上方法適合精準刪除某個鏡像的某個版本,如果是刪除一個倉庫,直接刪除 /var/lib/registry/docker/registry/v2/repositories/下對應倉庫的目錄,然後在用registry命令做垃圾回收;如下

    [root@docker_registry ~]# ll /var/lib/registry/docker/registry/v2/repositories/
    total 0
    drwxr-xr-x 5 root root 55 Jun  6 14:16 centos
    drwxr-xr-x 5 root root 55 Jun  6 14:15 flannel
    drwxr-xr-x 5 root root 55 Jun  6 15:25 myweb
    drwxr-xr-x 5 root root 55 Jun  6 15:24 test
    [root@docker_registry ~]# rm -rf /var/lib/registry/docker/registry/v2/repositories/test
    [root@docker_registry ~]# rm -rf /var/lib/registry/docker/registry/v2/repositories/myweb
    [root@docker_registry ~]# ll /var/lib/registry/docker/registry/v2/repositories/
    total 0
    drwxr-xr-x 5 root root 55 Jun  6 14:16 centos
    drwxr-xr-x 5 root root 55 Jun  6 14:15 flannel
    [root@docker_registry ~]# registry garbage-collect /etc/docker-distribution/registry/config.yml 
    centos
    flannel
    flannel: marking manifest sha256:3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6 
    flannel: marking blob sha256:57eade024bfbd48c45ef2bad996c4d6a0fa41b692247294745265af738066813
    flannel: marking blob sha256:176bad61a3a435da03ec603d2bd8f7a69286d92f21f447b17f21f0bc4e085bde
    flannel: marking blob sha256:13b80a37370b57f558a2e06092c39224e5d1ebac50e48df0afdeb43cf2303e60
    flannel: marking blob sha256:42d8e66fa893de4beb5d136b787cf182b24b7f4972c4212b9493b661ad1d7e85
    flannel: marking blob sha256:266247e2e603e1c840f97cb4d97a08b9184344e9802966cb42c93d21c407839f
    flannel: marking blob sha256:1b56fbc8a8e10830867455c0794a70f5469c154cdc013554daf501aeda3f30fe
    flannel: marking blob sha256:85ecb68de4693bb4093d923f6d1062766e4fa7cbb3bf456a2bc19dd3e6c5e6c6
    
    8 blobs marked, 9 blobs eligible for deletion
    blob eligible for deletion: sha256:370e3a843c3cb12700301e3f87f929939146cd8b676260bedcd83aaa7fcc2939
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/37/370e3a843c3cb12700301e3f87f929939146cd8b676260bedcd83aaa7fcc2939  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/52/524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:5ecad23ab8a52e55f93968f708d325261032dd613287aec92e7cf8ddd6426635
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/5e/5ecad23ab8a52e55f93968f708d325261032dd613287aec92e7cf8ddd6426635  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:aaf04cf567a776e36eb3b0bafaec17ed8d9e0a743bdb897dca13f251250ae493
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/aa/aaf04cf567a776e36eb3b0bafaec17ed8d9e0a743bdb897dca13f251250ae493  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:035e026f1d6b0acba3413ba616dcbabf75d20e945778c52716e601255452b7b7
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/03/035e026f1d6b0acba3413ba616dcbabf75d20e945778c52716e601255452b7b7  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:461f6ceabc885e2e90b5f9ee82aefc9a30a39510c40e7cd8fb7436a4d340fe1d
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/46/461f6ceabc885e2e90b5f9ee82aefc9a30a39510c40e7cd8fb7436a4d340fe1d  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:4f406abeaab7f848178867409142090d1a551b22b968be6a6dae733c8403738e
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/4f/4f406abeaab7f848178867409142090d1a551b22b968be6a6dae733c8403738e  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:c941076f9075280c41b502283f37ab8bafef3a66f4a7ba299838dce07641a48d
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/c9/c941076f9075280c41b502283f37ab8bafef3a66f4a7ba299838dce07641a48d  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:da8b53210bf1f4dc4873bbd5589abad616663cda45205ae3a4fffb0729d2730d
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/da/da8b53210bf1f4dc4873bbd5589abad616663cda45205ae3a4fffb0729d2730d  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    [root@docker_registry ~]# 
    

      提示:這種方式比較粗暴簡單,通常是一個倉庫里只有一個版本鏡像可以使用這種方式刪除,如果一個倉庫有多個版本,那麼還是建議使用第一種方式;

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

    ※推薦台中搬家公司優質服務,可到府估價

  • 用雲開發Cloudbase,實現小程序多圖片內容安全監測

    用雲開發Cloudbase,實現小程序多圖片內容安全監測

    前言

    相比於文本的安全檢測,圖片的安全檢測要稍微略複雜一些,當您讀完本篇,將get到

    • 圖片安全檢測的應用場景
    • 解決圖片的安全校驗的方式
    • 使用雲調用方式對圖片進行檢測
    • 如何對上傳圖片大小進行限制
    • 如何解決多圖上傳覆蓋問題

    示例效果

    當用戶上傳敏感違規圖片時,禁止用戶上傳發布,並且做出相對應的用戶友好提示

    應用場景

    通常,在校驗一張圖片是否含有違法違規內容相比於文本安全的校驗,同樣重要,有如下應用

    • 圖片智能鑒黃:涉及拍照的工具類應用(如美拍,識圖類應用)用戶拍照上傳檢測;電商類商品上架圖片檢測;媒體類用戶文章里的圖片檢測等
    • 敏感人臉識別:用戶頭像;媒體類用戶文章里的圖片檢測;社交類用戶上傳的圖片檢測等,凡是有用戶自發生產內容的都應當提前做檢測

    解決圖片的安全手段

    在小程序開發中,提供了兩種方式

    • HTTPS調用
    • 雲調用

    HTTPS 調用的請求接口地止

    https://api.weixin.qq.com/wxa/img_sec_check?access_token=ACCESS_TOKEN
    

    檢測圖片審核,根據官方文檔得知,需要兩個必傳的參數:分別是:access_token(接口調用憑證),media(要檢測的圖片文件)

    對於HTTPS調用方式,願意折騰的小夥伴可以參考文本內容安全檢測(上篇)的處理方式,處理大同小異,本篇主要以雲開發的雲調用為主

    功能實現:小程序端邏輯

    對於wxml與wxss,大家可以自行任意修改,本文重點在於圖片安全的校驗

    <view class="image-list">
    <!-- 显示圖片 -->
       <block wx:for="{{images}}" wx:key="*this"><view class="image-wrap">
           <image class="image" src="{{item}}" mode="aspectFill" bind:tap="onPreviewImage" data-imgsrc="{{item}}"></image><i class="iconfont icon-shanchu" bind:tap="onDelImage" data-index="{{index}}"></i></view>
       </block>
       <!-- 選擇圖片 -->
       <view class="image-wrap selectphoto" hidden="{{!selectPhoto}}" bind:tap="onChooseImage"><i class="iconfont icon-add"></i></view>
       </view>
       <view class="footer"><button class="send-btn"  bind:tap="send">發布</button>
       </view>
    

    對應的wxss代碼

    .footer {
      display: flex;
      align-items: center;
      width: 100%;
      box-sizing: border-box;
      background: #34bfa3;
    }
    
    .send-btn {
      width: 100%;
      color: #fff;
      font-size: 32rpx;
      background: #34bfa3;
    }
    
    button {
      border-radius: 0rpx;
    }
    
    button::after {
      border-radius: 0rpx !important;
    }
    
    /* 圖片樣式 */
    .image-list {
      display: flex;
      flex-wrap: wrap;
      margin-top: 20rpx;
    }
    
    .image-wrap {
      width: 220rpx;
      height: 220rpx;
      margin-right: 10rpx;
      margin-bottom: 10rpx;
      position: relative;
      overflow: hidden;
      text-align: center;
    }
    
    .image {
      width: 100%;
      height: 100%;
    }
    
    .icon-shanchu {
      position: absolute;
      top: 0;
      right: 0;
      width: 40rpx;
      height: 40rpx;
      background-color: #000;
      opacity: 0.4;
      color: #fff;
      text-align: center;
      line-height: 40rpx;
      font-size: 38rpx;
      font-weight: bolder;
    }
    
    .selectphoto {
      border: 2rpx dashed #cbd1d7;
      position: relative;
    }
    
    .icon-add {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      color: #cbd1d7;
      font-size: 60rpx;
    }
    

    最終呈現的UI,由於只是用於圖片檢測演示,UI方面可忽略,如下所示

    對應的JS代碼

    /*
    * 涉及到的API:wx.chooseImage  從本地相冊選擇圖片或使用相機拍照
    *(https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseImage.html)
    
    
    *
    *
    */// 最大上傳圖片數量
    const MAX_IMG_NUM = 9;
    
    const db = wx.cloud.database(); // 初始化雲數據庫
    Page({
    
      /**
       * 頁面的初始數據
       */
      data: {
        images: [],  // 把上傳的圖片存放在一個數組對象裏面
        selectPhoto: true, // 添加+icon元素是否显示
      },
    
      /**
       * 生命周期函數--監聽頁面加載
       */
      onLoad: function (options) {
    
      },
    
      // 選擇圖片
      onChooseImage() {
        // 還能再選幾張圖片,初始值設置最大的數量-當前的圖片的長度
        let max = MAX_IMG_NUM - this.data.images.length; 
        wx.chooseImage({
          count: max,               // count表示最多可以選擇的圖片張數
          sizeType: ['original', 'compressed'], //  所選的圖片的尺寸
          sourceType: ['album', 'camera'],  // 選擇圖片的來源
          success: (res) => {                     // 接口調用成功的回調函數console.log(res)
            this.setData({                       // tempFilePath可以作為img標籤的src屬性显示圖片,下面是將后添加的圖片與之前的圖片給追加起來
              images: this.data.images.concat(res.tempFilePaths)
            })
            // 還能再選幾張圖片
            max = MAX_IMG_NUM - this.data.images.length
            this.setData({
              selectPhoto: max <= 0 ? false : true  // 當超過9張時,加號隱藏
            })
          },
        })
      },
    
      // 點擊右上方刪除圖標,刪除圖片操作
      onDelImage(event) {
        const index = event.target.dataset.index;
        // 點擊刪除當前圖片,用splice方法,刪除一張,從數組中移除一個                                                       
        this.data.images.splice(index, 1)
        this.setData({
          images: this.data.images
        })
        // 當添加的圖片達到設置最大的數量時,添加按鈕隱藏,不讓新添加圖片
        if (this.data.images.length == MAX_IMG_NUM - 1) {
          this.setData({
            selectPhoto: true,
          })
        }
      },
    })
    

    最終實現的前端UI效果如下所是:

    您現在看到的效果,沒有任何雲函數代碼,只是前端的純靜態展示,對於一些涉嫌敏感圖片,是有必要進行做過濾處理的

    功能實現:雲函數側邏輯

    在cloudfunctions目錄文件夾下創建雲函數imgSecCheck

    並在該目錄下創建config.json,配置參數如下所示

    {
      "permissions": {
        "openapi": [
          "security.imgSecCheck"
        ]
      }
    }
    

    配置完后,在主入口index.js中,如下所示,通過security.imgSecCheck接口,並傳入media對象

    // 雲函數入口文件
    const cloud = require('wx-server-sdk');
    cloud.init({
      env: cloud.DYNAMIC_CURRENT_ENV
    })
    
    // 雲函數入口函數
    exports.main = async (event, context) => {
      const wxContext = cloud.getWXContext()
      try {
        const result = await cloud.openapi.security.imgSecCheck({
          media: {
            contentType: 'image/png',
            value: Buffer.from(event.img)   // 這裏必須要將小程序端傳過來的進行Buffer轉化,否則就會報錯,接口異常
          }
          
        })
    
        if (result && result.errCode.toString() === '87014') {
          return { code: 500, msg: '內容含有違法違規內容', data: result }
        } else {
          return { code: 200, msg: '內容ok', data: result }
        }
      } catch (err) {
        // 錯誤處理
        if (err.errCode.toString() === '87014') {
          return { code: 500, msg: '內容含有違法違規內容', data: err }
        }
        return { code: 502, msg: '調用imgSecCheck接口異常', data: err }
      }
    }
    

    您會發現在雲函數端,就這麼幾行代碼,就完成了圖片安全校驗

    而在小程序端,代碼如下所示

    // miniprogram/pages/imgSecCheck/imgSecCheck.js
    // 最大上傳圖片數量
    const MAX_IMG_NUM = 9;
    
    const db = wx.cloud.database()
    Page({
    
      /**
       * 頁面的初始數據
       */
      data: {
        images: [],
        selectPhoto: true, // 添加圖片元素是否显示
      },
    
      /**
       * 生命周期函數--監聽頁面加載
       */
      onLoad: function (options) {
    
      },
      // 選擇圖片
      onChooseImage() {
        // const that = this;  // 如果下面用了箭頭函數,那麼這行代碼是不需要的,直接用this就可以了的// 還能再選幾張圖片,初始值設置最大的數量-當前的圖片的長度
        let max = MAX_IMG_NUM - this.data.images.length; 
        wx.chooseImage({
          count: max,
          sizeType: ['original', 'compressed'],
          sourceType: ['album', 'camera'],
          success: (res) => {  // 這裏若不是箭頭函數,那麼下面的this.setData的this要換成that上面的臨時變量,作用域的問題,不清楚的,可以看下this指向相關的知識
           console.log(res)
           // tempFilePath可以作為img標籤的src屬性显示圖片
            const  tempFiles = res.tempFiles;
            this.setData({
              images: this.data.images.concat(res.tempFilePaths)
            })
            // 在選擇圖片時,對本地臨時存儲的圖片,這個時候,進行圖片的校驗,當然你放在最後點擊發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程序端請求雲函數方式// 圖片轉化buffer后,調用雲函數
            console.log(tempFiles);
            tempFiles.forEach(items => {
              console.log(items);
              // 圖片轉化buffer后,調用雲函數
              wx.getFileSystemManager().readFile({
                filePath: items.path,
                success: res => {
                      console.log(res);
                       wx.cloud.callFunction({  // 小程序端請求imgSecCheck雲函數,並傳遞img參數進行檢驗
                        name: 'imgSecCheck',
                        data: {
                          img: res.data
                        }
                })
                .then(res => {
                   console.log(res);
                   let { errCode } = res.result.data;
                   switch(errCode) {
                     case 87014:
                       this.setData({
                          resultText: '內容含有違法違規內容'
                       })
                       break;
                     case 0:
                       this.setData({
                         resultText: '內容OK'
                       })
                       break;
                     default:
                       break;
                   }
     
                })
                .catch(err => {
                   console.error(err);
                })
                },
                fail: err => {
                  console.error(err);
                }
              })
            })
            
                
            // 還能再選幾張圖片
            max = MAX_IMG_NUM - this.data.images.length
            this.setData({
              selectPhoto: max <= 0 ? false : true  // 當超過9張時,加號隱藏
            })
          },
        })
      },
    
      // 刪除圖片
      onDelImage(event) {
        const index =  event.target.dataset.index;
        // 點擊刪除當前圖片,用splice方法,刪除一張,從數組中移除一個
        this.data.images.splice(index, 1);
        this.setData({
          images: this.data.images
        })
        // 當添加的圖片達到設置最大的數量時,添加按鈕隱藏,不讓新添加圖片
        if (this.data.images.length == MAX_IMG_NUM - 1) {
          this.setData({
            selectPhoto: true,
          })
        }
      },
    })
    

    示例效果如下所示:

    至此,關於圖片安全檢測就已經完成了,您只需要根據檢測的結果,做一些友好的用戶提示,或者做一些自己的業務邏輯判斷即可

    常見問題

    如何對上傳的圖片大小進行限制

    有時候,您需要對用戶上傳圖片的大小進行限制,限制用戶任意上傳超大圖片,那怎麼處理呢,在微信小程序裏面,主要藉助的是wx.chooseImage這個接口成功返回后臨時路徑的res.tempFiles中的size大小判斷即可進行處理

    具體實例代碼如下所示

    // 選擇圖片
      onChooseImage() {
        // 還能再選幾張圖片,初始值設置最大的數量-當前的圖片的長度
        let max = MAX_IMG_NUM - this.data.images.length; 
        wx.chooseImage({
          count: max,
          sizeType: ['original', 'compressed'],
          sourceType: ['album', 'camera'],
          success: (res) => {
            console.log(res)
            const  tempFiles = res.tempFiles;
            this.setData({
              images: this.data.images.concat(res.tempFilePaths)  // tempFilePath可以作為img標籤的src屬性显示圖片
            })
            // 在選擇圖片時,對本地臨時存儲的圖片,這個時候,進行圖片的校驗,當然你放在最後點擊發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程序端請求雲函數方式// 圖片轉化buffer后,調用雲函數
            console.log(tempFiles);
            tempFiles.forEach(items => {
              if (items && items.size > 1 * (1024 * 1024)) {  // 限製圖片的大小
                wx.showToast({
                  icon: 'none',
                  title: '上傳的圖片超過1M,禁止用戶上傳',
                  duration: 4000
                })
                // 超過1M的圖片,禁止用戶上傳
              }
              console.log(items);
              // 圖片轉化buffer后,調用雲函數
              wx.getFileSystemManager().readFile({
                filePath: items.path,
                success: res => {
                      console.log(res);
                       wx.cloud.callFunction({   // 請求調用雲函數imgSecCheck
                        name: 'imgSecCheck',
                        data: {
                          img: res.data
                        }
                })
                .then(res => {
                   console.log(res);
                   let { errCode } = res.result.data;
                   switch(errCode) {
                     case 87014:
                       this.setData({
                          resultText: '內容含有違法違規內容'
                       })
                       break;
                     case 0:
                       this.setData({
                         resultText: '內容OK'
                       })
                       break;
                     default:
                       break;
                   }
                })
                .catch(err => {
                   console.error(err);
                })
                },
                fail: err => {
                  console.error(err);
                }
              })
            })
           
            // 還能再選幾張圖片
            max = MAX_IMG_NUM - this.data.images.length
            this.setData({
              selectPhoto: max <= 0 ? false : true  // 當超過9張時,加號隱藏
            })
          },
        })
      },
    

    注意: 使用微信官方的圖片內容安全接口進行校驗,限製圖片大小限制:1M,否則的話就會報錯

    也就是說,對於超過1M大小的違規圖片,微信官方提供的這個圖片安全接口是無法進行校驗的

    這個根據自己的業務而定,在小程序端對用戶上傳圖片的大小進行限制如果您覺得微信官方提供的圖片安全接口滿足不了自己的業務需求,那麼可以選擇一些其他的圖片內容安全校驗的接口的

    這個圖片安全校驗是非常有必要的,用戶一旦上傳非法圖片,一旦通過網絡進行傳播,產生了社會影響,平台是有責任的,這種前車之鑒是有的

    如何解決多圖上傳覆蓋的問題

    對於上傳圖片來說,這個wx.cloud.uploadFileAPI接口只能上傳一張圖片,但是很多時候,是需要上傳多張圖片到雲存儲當中的,當點擊發布的時候,我們是希望將多張圖片都上傳到雲存儲當中去的

    這個API雖然只能每次上傳一張,但您可以循環遍歷多張圖片,然後一張一張的上傳的

    在cloudPath上傳文件的參數當中,它的值:需要注意:文件的名稱

    那如何保證上傳的圖片不被覆蓋,文件不重名的情況下就不會被覆蓋

    而在選擇圖片的時候,不應該上傳,因為用戶可能有刪除等操作,如果直接上傳的話會造成資源的浪費

    而應該在點發布按鈕的時候,才執行上傳操作,文件不重名覆蓋的示例代碼如下所示

          let promiseArr = []
          let fileIds = []      // 將圖片的fileId存放到一個數組中
          let imgLength = this.data.images.length;
          // 圖片上傳
          for (let i = 0; i < imgLength; i++) {
            let p = new Promise((resolve, reject) => {
            let item = this.data.images[i]
              // 文件擴展名
              let suffix = /\.\w+$/.exec(item)[0]; // 取文件后拓展名
              wx.cloud.uploadFile({      // 利用官方提供的上傳接口
                cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,  // 雲存儲路徑,您也可以使用es6中的模板字符串進行拼接的
                filePath: item,   // 要上傳文件資源的路徑
                success: (res) => {
                  console.log(res);
                  console.log(res.fileID)
                  fileIds = fileIds.concat(res.fileID)       // 將新上傳的與之前上傳的給拼接起來
                  resolve()
                },
                fail: (err) => {
                  console.error(err)
                  reject()
                }
              })
            })
            promiseArr.push(p)
          }
          // 存入到雲數據庫,其中這個Promise.all(),等待裏面所有的任務都執行之後,在去執行後面的任務,也就是等待上傳所有的圖片上傳完后,才能把相對應的數據存到數據庫當中,具體與promise相關問題,可自行查漏
          Promise.all(promiseArr).then((res) => {
              db.collection('blog').add({ // 查找blog集合,將img,時間等數據添加到這個集合當中
                data: {
                  img: fileIds,
                  createTime: db.serverDate(), // 服務端的時間
                }
              }).then((res) => {
                console.log(res);
                this._hideToastTip();
                this._successTip();
              })
            })
            .catch((err) => {
              // 發布失敗console.error(err);
            })
    

    上面通過利用當前時間+隨機數的方式進行了一個區分,規避了上傳文件同名的問題

    因為這個上傳接口,一次性只能上傳一張圖片,所以需要循環遍歷圖片,然後一張張的上傳

    一個是上傳到雲存儲中,另一個是添加到雲數據庫集合當中,要分別注意下這兩個操作,雲數據庫中的圖片是從雲存儲中拿到的,然後再添加到雲數據庫當中去的

    示例效果如下所示:

    將上傳的圖片存儲到雲數據庫中

    注意:添加數據到雲數據庫中,需要手動創建集合,不然是無法上傳不到雲數據庫當中的,會報錯

    至此,關於敏感圖片的檢測,以及多圖片的上傳到這裏就已經完成了

    如下是完整的小程序端邏輯示例代碼

    // miniprogram/pages/imgSecCheck/imgSecCheck.js
    // 最大上傳圖片數量
    const MAX_IMG_NUM = 9;
    const db = wx.cloud.database()
    Page({
    
      /**
       * 頁面的初始數據
       */
      data: {
        images: [],
        selectPhoto: true, // 添加圖片元素是否显示
      },
    
      /**
       * 生命周期函數--監聽頁面加載
       */
      onLoad: function (options) {
    
      },
    
      // 選擇圖片
      onChooseImage() {
        // 還能再選幾張圖片,初始值設置最大的數量-當前的圖片的長度
        let max = MAX_IMG_NUM - this.data.images.length;
        wx.chooseImage({
          count: max,
          sizeType: ['original', 'compressed'],
          sourceType: ['album', 'camera'],
          success: (res) => {
            console.log(res)
            const tempFiles = res.tempFiles;
            this.setData({
              images: this.data.images.concat(res.tempFilePaths) // tempFilePath可以作為img標籤的src屬性显示圖片
            })
            // 在選擇圖片時,對本地臨時存儲的圖片,這個時候,進行圖片的校驗,當然你放在最後點擊發布時,進行校驗也是可以的,只不過是一個前置校驗和後置校驗的問題,我個人傾向於在選擇圖片時就進行校驗的,選擇一些照片時,就應該在選擇時階段做安全判斷的, 小程序端請求雲函數方式
            // 圖片轉化buffer后,調用雲函數
            console.log(tempFiles);
            tempFiles.forEach(items => {
              if (items && items.size > 1 * (1024 * 1024)) {
                wx.showToast({
                  icon: 'none',
                  title: '上傳的圖片超過1M,禁止用戶上傳',
                  duration: 4000
                })
                // 超過1M的圖片,禁止上傳
              }
              console.log(items);
              // 圖片轉化buffer后,調用雲函數
              wx.getFileSystemManager().readFile({
                filePath: items.path,
                success: res => {
                  console.log(res);
                  this._checkImgSafe(res.data); // 檢測圖片安全校驗
                },
                fail: err => {
                  console.error(err);
                }
              })
            })
    
    
            // 還能再選幾張圖片
            max = MAX_IMG_NUM - this.data.images.length
            this.setData({
              selectPhoto: max <= 0 ? false : true // 當超過9張時,加號隱藏
            })
          },
        })
      },
    
      // 刪除圖片
      onDelImage(event) {
        const index = event.target.dataset.index;
        // 點擊刪除當前圖片,用splice方法,刪除一張,從數組中移除一個
        this.data.images.splice(index, 1);
        this.setData({
          images: this.data.images
        })
        // 當添加的圖片達到設置最大的數量時,添加按鈕隱藏,不讓新添加圖片
        if (this.data.images.length == MAX_IMG_NUM - 1) {
          this.setData({
            selectPhoto: true,
          })
        }
      },
    
      // 點擊發布按鈕,將圖片上傳到雲數據庫當中
      send() {
        const images = this.data.images.length;
        if (images) {
          this._showToastTip();
          let promiseArr = []
          let fileIds = []
          let imgLength = this.data.images.length;
          // 圖片上傳
          for (let i = 0; i < imgLength; i++) {
            let p = new Promise((resolve, reject) => {
              let item = this.data.images[i]
              // 文件擴展名
              let suffix = /\.\w+$/.exec(item)[0]; // 取文件后拓展名
              wx.cloud.uploadFile({   // 上傳圖片至雲存儲,循環遍歷,一張張的上傳
                cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
                filePath: item,
                success: (res) => {
                  console.log(res);
                  console.log(res.fileID)
                  fileIds = fileIds.concat(res.fileID)
                  resolve()
    
                },
                fail: (err) => {
                  console.error(err)
                  reject()
                }
              })
            })
            promiseArr.push(p)
          }
          // 存入到雲數據庫
          Promise.all(promiseArr).then((res) => {
              db.collection('blog').add({ // 查找blog集合,將數據添加到這個集合當中
                data: {
                  img: fileIds,
                  createTime: db.serverDate(), // 服務端的時間
                }
              }).then((res) => {
                console.log(res);
                this._hideToastTip();
                this._successTip();
              })
            })
            .catch((err) => {
              // 發布失敗
              console.error(err);
            })
        } else {
          wx.showToast({
            icon: 'none',
            title: '沒有選擇任何圖片,發布不了',
          })
        }
    
      },
    
      // 校驗圖片的安全
      _checkImgSafe(data) {
        wx.cloud.callFunction({
            name: 'imgSecCheck',
            data: {
              img: data
            }
          })
          .then(res => {
            console.log(res);
            let {
              errCode
            } = res.result.data;
            switch (errCode) {
              case 87014:
                this.setData({
                  resultText: '內容含有違法違規內容'
                })
                break;
              case 0:
                this.setData({
                  resultText: '內容OK'
                })
                break;
              default:
                break;
            }
          })
          .catch(err => {
            console.error(err);
          })
      },
    
      _showToastTip() {
        wx.showToast({
          icon: 'none',
          title: '發布中...',
        })
      },
    
      _hideToastTip() {
        wx.hideLoading();
      },
    
      _successTip() {
        wx.showToast({
          icon: 'none',
          title: '發布成功',
        })
      },
    })
    

    完整的示例wxml,如下所示

    <view class="image-list">
    <!-- 显示圖片 -->
    <block wx:for="{{images}}" wx:key="*this">
         <view class="image-wrap"><image class="image" src="{{item}}" mode="aspectFill" bind:tap="onPreviewImage" data-imgsrc="{{item}}"></image><i class="iconfont icon-shanchu" bind:tap="onDelImage" data-index="{{index}}"></i>
         </view>
    </block>
    <!-- 選擇圖片 -->
    <view class="image-wrap selectphoto" hidden="{{!selectPhoto}}" bind:tap="onChooseImage"><i class="iconfont icon-add"></i></view>
    </view>
    <view class="footer">
       <button class="send-btn"  bind:tap="send">發布</button>
    </view>
    <view>
        檢測結果显示: {{ resultText }}
    </view>
    

    您可以根據自己的業務邏輯需要,一旦檢測到圖片違規時,禁用按鈕狀態,或者給一些用戶提示,都是可以的,在發布之前或者點擊發布時,進行圖片內容安全的校驗都可以,一旦發現圖片有違規時,就不讓繼續後面的操作的

    結語

    本文主要通過藉助官方提供的圖片security.imgSecCheck

    接口,實現了對圖片安全的校驗,實現起來,是相當的方便的,對於基礎性的校驗,利用官方提供的這個接口,已經夠用了的,但是如果想要更加嚴格的檢測,可以引入一些第三方的內容安全強強校驗,確保內容的安全

    實現了如何對上傳的圖片大小進行限制,以及解決同名圖片上傳覆蓋的問題

    如果大家對文本內容安全校驗以及圖片安全校驗仍然有什麼問題,可以在下方留言,一起探討。

    雲開發公眾號:騰訊云云開發

    雲開發技術文檔:https://cloudbase.net

    雲開發技術交流加Q群:601134960

    更多精彩
    掃描二維碼了解更多

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

    【其他文章推薦】

    ※回頭車貨運收費標準

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

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

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

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

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 疫情下的奇景!孟買市區湧入大量紅鶴 數量估創紀錄

    摘錄自2020年5月2日自由時報綜合報導

    根據《CNN》報導,每年9月至翌年5月都會觀測到紅鶴族群遷徙至孟買覓食,然而,今(2020)年在人類活動大幅下降的狀況下,遷徙至當地的紅鶴數量預估將超過13萬4000隻,創下歷史新高。

    孟買自然歷史學會(BNHS)副主任科特(Rahul Khot)表示,在人類社交活動暫停後,當地不僅出現破紀錄數量的紅鶴,牠們選定的棲地也與往常相異,已有族群擴展至以往少見紅鶴蹤跡的濕地。

    印度境內陸續傳出野生動物受益於武漢肺炎疫情的消息,不只德里湧入大量猴群,極瀕危的恆河江豚也在多年來首度被觀測到活體行為;顯示出人類活動暫停,讓我們的地球鄰居們產生明顯變化。

    生物多樣性
    生態保育
    國際新聞
    印度
    孟買
    紅鶴
    江豚
    動物與大環境變遷
    武漢肺炎

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

    ※回頭車貨運收費標準

  • 山地大猩猩的家園不平靜 剛果維龍加國家公園12名護管員遭殺害

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!