月份: 2020 年 9 月

  • 將電動車行程提升超10% 英公司開發MSYS動力系統

    據英國媒體報導,英國動力系統設計公司宣佈,該公司正在開發一套名為MSYS的電動汽車動力系統,可以將電動汽車行駛里程增加10%至15%。MSYS系統預計將於2016年投產。

    在英國即將舉辦的未來動力系統會議上,英國動力系統設計公司將發佈高效電動汽車動力系統報告。該動力系統提供了一種新的汽車換擋路徑,而且不會引起扭矩中斷。

    該公司技術總監阿萊克斯•泰利•博達爾表示,該技術可提供55千瓦的持續電力供應,超過2000牛/米的扭矩,其電力動力系統效率可達91%。

    阿萊克斯還透露,MSYS系統避免了其他傳動方法的弊端。雙離合器變速箱(DCT)在離合器開啟或關閉狀態時,都會持續消耗能量。而在換擋時,自動變速器(AMT)會受損於扭矩中斷。此外,基於行星齒輪變速器的自動裝置,則增加了複雜性和成本,並會拖慢速度。

    MSYS系統將提供三種速度傳動裝置,使其換擋和快速換擋都能運用重疊換擋技術。隨著技術的完善,多重比例的選擇對電動汽車的變速會非常有益。

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

  • 曠世提出類別正則化的域自適應目標檢測模型,緩解場景多樣的痛點 | CVPR 2020

    曠世提出類別正則化的域自適應目標檢測模型,緩解場景多樣的痛點 | CVPR 2020

    論文基於DA Faster R-CNN系列提出類別正則化框架,充分利用多標籤分類的弱定位能力以及圖片級預測和實例級預測的類一致性,從實驗結果來看,類該方法能夠很好地提升DA Faster R-CNN系列的性能

    來源:曉飛的算法工程筆記 公眾號

    論文: Exploring Categorical Regularization for Domain Adaptive Object Detection

    • 論文地址:https://arxiv.org/pdf/2003.09152.pdf
    • 論文代碼:https://github.com/Megvii-Nanjing/CR-DA-DET

    Introduction

      由於標註成本大,在訓練好檢測算法后,面對差異較大的新場景(類別不變),若想獲取大量的帶標註圖片進行再訓練是很不方便的。對於這種情況,無監督的域自適應方法能夠靈活地自適應新場景,從包含豐富標註信息的源域轉移到無標註的目標域。其中,域自適應方法中比較有代表性的是Donamin Adaptive(DA) Faster R-CNN系列,利用對抗訓練來對齊圖片和實例的分佈,使得模型能夠做到域不變性,具體可以看上一篇介紹。
      但是這些方法大都把無法轉化的背景內容也進行了對齊,而且在實例對齊時,沒有從包含較多低質量的proposal集合中識別出難樣本。為了解決上面的問題,論文提出類別正則化框架,幫助DA Faster R-CNN專註於對齊跨域中的關鍵區域和重要目標。
      論文的主要貢獻如下:

    • 提出新的類別正則化框架,作為域自適應目標檢測算法的插件,不需要額外的標註和超參數。
    • 設計了兩個正則化模塊,分別用於榨取卷積分類器的弱定位能力以及圖像級別預測和實例級別預測間的類別一致性,能夠幫助分類器專註於對齊目標相關區域以及難對齊實例。
    • 對多種域轉移場景進行實驗,驗證論文提出的方法的有效性。從實驗結果來看,類別正則化框架能夠提出DA Faster R-CNN系列方法的性能,並在基礎數據集上達到SOTA。

    Approach

    Framework Overview

      論文方法的整體架構如圖2,在DA Faster R-CNN基礎上添加了ICR(image-level categorical regularization)和CCR(categorical consistency regularization),能夠更好地對齊域間的關鍵區域和重要實例。

    Image-Level Categorical Regularization

      ICR的主要目的是提高主幹網絡的目標特徵提取能力,同時降低背景的激活。結構如圖2b所示,ICR使用源域數據進行有監督訓練,對主幹網絡的特徵輸出進行全局池化,再使用多標籤分類器($1\times 1$卷積)進行分類,損失函數使用標準交叉熵多標籤損失:

      $C$為類別總數,$yc$為GT標籤,$\hat{y}c$為預測標籤,$y^c=1$表示圖片至少包含一個類別$c$物體。

     ICR模塊利用多標籤分類器的弱定位能力,能夠有監督地引導主幹網絡只激活類相關特徵。如圖3所示,類相關的特徵會有較高的激活值。在圖像級對齊時,能夠對齊域間關鍵區域,同時,由於背景沒有參与到圖像級多標籤分類器中,能夠有效減少擬合不可對齊的源背景的可能性。

    Categorical Consistency Regularization

      CCR負責發現難對齊實例,調整實例級對齊損失的權重,基於兩點考慮:

    • 由於不能區分前景和後景,實例對齊模塊可能被低質量背景proposal佔據。
    • 添加的圖像級分類器和實例檢測head是互補的,前者負責獲取所有圖像級上下文信息,後者使用精確的RoI特徵,當兩者預測不一致時,該實例就是難樣本。

      基於以上考慮,論文採用圖像級預測和實例級預測的類別一致性作為目標分類難易程度的判斷,並在目標域中使用該一致性作為正則因子,調節難對齊樣本在實例對齊中的權重。假定$\hat{p}{c}_j$為預測第$j$個實例為類別$c$的概率,$\hat{y}c$為實例預測包含類別$c$的概率,類別一致性的計算為

      使用公式5來加權實例級對抗損失

      需要注意,僅對目標域的檢測head預測為前景的實例使用公式5加權,源域的所有實例和目標域的背景實例均使用$d_j=1$,前者因為是有監督的,而後者則是因為不重要。

    Integration with DA Faster R-CNN Series

      將論文提出的方法加入到DA Faster R-CNN中,ICR為直接加入,CCR為對原損失的修改,最終的損失函數為

      論文也對比了另外一種主流的DA -Faster改進SW-Faster,該方法使用弱全局對齊模型來提升DA-Faster的強圖像對齊模塊,直接加入ICR和CCR,最終的損失函數為

    Experiments

    Comparison Results

      Faster R-CNN(Source)僅使用源域訓練,Faster R-CNN(Oracle)僅使用目標域訓練。

    • Weather Adaptation

      這裏對比模型對天氣的自適應性。

    • Scene Adaptation

      這裏對比模型對不同城市的場景的自適應性。

    • Dissimilar Domain Adaptation

      這裏對比模型對真實圖片和卡通圖片的自適應性。

    Visualization and Analyses

      對前面對比實驗的目標域測試圖片進行了可視化。

      將特徵降維並可視化,藍點為源域樣本,紅點為目標域樣本,可以看到論文的方法能夠讓域間的同分類實例距離更近。
      論文也計算了域間距離,使用Earth Movers Distance (EMD) 測量,SW-Faster, SW-Faster-ICR and SW-FasterICR-CCR的結果分別是8.84、8.59和8.15。

    CONCLUSION

      論文基於DA Faster R-CNN系列提出類別正則化框架,充分利用多標籤分類的弱定位能力以及圖片級預測和實例級預測的類一致性,從實驗結果來看,類該方法能夠很好地提升DA Faster R-CNN系列的性能。



    如果本文對你有幫助,麻煩點個贊或在看唄~
    更多內容請關注 微信公眾號【曉飛的算法工程筆記】

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

    【其他文章推薦】

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

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

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

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

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

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

  • 一對多分頁的SQL到底應該怎麼寫?

    一對多分頁的SQL到底應該怎麼寫?

    1. 前言

    MySQL一對多的數據分頁是非常常見的需求,比如我們要查詢商品和商品的圖片信息。但是很多人會在這裏遇到分頁的誤區,得到不正確的結果。今天就來分析並解決這個問題。

    2. 問題分析

    我們先創建一個簡單商品表和對應的商品圖片關係表,它們之間是一對多的關係:

    然後我分別寫入了一些商品和這些商品對應的圖片,通過下面的左連接查詢可以看出它們之間具有明顯的一對多關係:

    SELECT P.PRODUCT_ID, P.PROD_NAME, PI.IMAGE_URL
    FROM PRODUCT_INFO P
             LEFT JOIN PRODUCT_IMAGE PI
                       ON P.PRODUCT_ID = PI.PRODUCT_ID
    

    按照傳統的思維我們的分頁語句會這麼寫:

        <resultMap id="ProductDTO" type="cn.felord.mybatis.entity.ProductDTO">
            <id property="productId" column="product_id"/>
            <result property="prodName" column="prod_name"/>
            <collection property="imageUrls"  ofType="string">
                <result column="image_url"/>
            </collection>
        </resultMap>
    
        <select id="page" resultMap="ProductDTO">
            SELECT P.PRODUCT_ID, P.PROD_NAME,PI.IMAGE_URL
            FROM PRODUCT_INFO P
                     LEFT JOIN PRODUCT_IMAGE PI
                               ON P.PRODUCT_ID = PI.PRODUCT_ID
            LIMIT #{current},#{size}
        </select>               
    

    當我按照預想傳入了(0,2)想拿到前兩個產品的數據,結果並不是我期望的:

    2020-06-21 23:35:54.515 DEBUG 10980 --- [main] c.f.m.mappers.ProductInfoMapper.page     : ==>  Preparing: SELECT P.PRODUCT_ID, P.PROD_NAME,PI.IMAGE_URL FROM PRODUCT_INFO P LEFT JOIN PRODUCT_IMAGE PI ON P.PRODUCT_ID = PI.PRODUCT_ID limit ?,? 
    2020-06-21 23:35:54.541 DEBUG 10980 --- [main] c.f.m.mappers.ProductInfoMapper.page     : ==> Parameters: 0(Long), 2(Long)
    2020-06-21 23:35:54.565 DEBUG 10980 --- [main] c.f.m.mappers.ProductInfoMapper.page     : <==      Total: 2
    page = [ProductDTO{productId=1, prodName='杯子', imageUrls=[http://asset.felord.cn/cup1.png, http://asset.felord.cn/cup2.png]}]
    

    我期望的兩條數據是杯子和筆記本,但是結果卻只有一條。原來當一對多映射時結果集會按照多的一側進行輸出(期望4條數據,實際上會有7條),而前兩條展示的只會是杯子的數據(如上圖),合併后就只有一條結果了,這樣分頁就對不上了。那麼如何才能達到我們期望的分頁效果呢?

    3. 正確的方式

    正確的思路是應該先對主表進行分頁,再關聯從表進行查詢。

    拋開框架,我們的SQL應該先對產品表進行分頁查詢然後再左關聯圖片表進行查詢:

    SELECT P.PRODUCT_ID, P.PROD_NAME, PI.IMAGE_URL
    FROM (SELECT PRODUCT_ID, PROD_NAME
          FROM PRODUCT_INFO
          LIMIT #{current},#{size}) P
             LEFT JOIN PRODUCT_IMAGE PI
                       ON P.PRODUCT_ID = PI.PRODUCT_ID
    

    這種寫法的好處就是通用性強一些。但是MyBatis提供了一個相對優雅的路子,思路依然是開頭所說的思路。只不過我們需要改造上面的Mybatis XML配置:

    <resultMap id="ProductDTO" type="cn.felord.mybatis.entity.ProductDTO">
        <id property="productId" column="product_id"/>
        <result property="prodName" column="prod_name"/>
         <!-- 利用 collection 標籤提供的 select 特性 和 column   -->
        <collection property="imageUrls" ofType="string" select="selectImagesByProductId" column="product_id"/>
    </resultMap>
    <!-- 先查詢主表的分頁數據    -->
    <select id="page" resultMap="ProductDTO">
        SELECT PRODUCT_ID, PROD_NAME
        FROM PRODUCT_INFO
        LIMIT #{current},#{size}
    </select>
    <!--根據productId 查詢對應的圖片-->
    <select id="selectImagesByProductId" resultType="string">
        SELECT IMAGE_URL
        FROM PRODUCT_IMAGE
        WHERE PRODUCT_ID = #{productId}
    </select>
    

    4. 總結

    大部分情況下分頁是很容易的,但是一對多還是有一些小小的陷阱的。一旦我們了解了其中的機制,也並不難解決。當然如果你有更好的解決方案可以留言討論,集思廣益。多多關注:碼農小胖哥,獲取更多開發技巧。

    關注公眾號:Felordcn 獲取更多資訊

    個人博客:https://felord.cn

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

    【其他文章推薦】

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

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

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

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

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

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

  • 深入理解進程,線程,協程

    深入理解進程,線程,協程

    今日得到

    • 計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決

    • 併發:Do not communicate by sharing memory; instead, share memory by communicate. (不要以共享內存的方式來通信,相反,要通過通信來共享內存)

    1. 進程

    進程是系統進行資源分配和調度的一個獨立單位,程序段、數據段、PCB三部分組成了進程實體(進程映像),PCB是進程存在的唯一標準

    1.1 進程的組織方式:

    • 鏈接方式
      • 按照進程狀態將PCB分為多個隊列,就緒隊列,阻塞隊列等
      • 操作系統持有指向各個隊列的指針
    • 索引方式
      • 根據進程狀態的不同,建立幾張索引表
      • 操作系統持有指向各個索引表的指針

    1.2 進程的狀態

    • 創建態: 操作系統為進程分配資源,初始化PCB

    • 就緒態:運行資源等條件都滿足,存儲在就緒隊列中,等待CPU調度

    • 運行態:CPU正在執行進程

    • 阻塞態:等待某些條件滿足,等待消息回復,等待同步鎖,sleep等,阻塞隊列

    • 終止態 :回收進程擁有的資源,撤銷PCB

    1.3 進程的切換和調度

    進程在操作系統內核程序臨界區中不能進行調度與切換

    臨界資源:一個時間段內只允許一個進程使用資源,各進程需要互斥地訪問臨界資源

    臨界區:訪問臨界資源的代碼

    內核程序臨界區:訪問某種內核數據結構,如進程的就緒隊列(存儲各進程的PCB)

    進程調度的方式:

    • 非剝奪調度方式(非搶佔方式),只允許進程主動放棄處理機,在運行過程中即便有更緊迫的任務到達,當前進程依然會繼續使用處理機,直到該進程終止或者主動要求進入阻塞態
    • 剝奪調度方式(又稱搶佔方式)當一個進程正在處理機上執行時,如果有一個優先級更高的進程需要處理機,則立即開中斷暫停正在執行的進程,將處理機飯呢陪給優先級高的那個進程

    進程的切換與過程:進程的調度、切換是有代價的

    1. 對原來運行進程各種數據的保存
    2. 對新的進程各種數據恢復(程序計數器,程序狀態字,各種數據寄存器等處理機的現場)

    進程調度算法的相關參數:

    • CPU利用率:CPU忙碌時間/作業完成的總時間
    • 系統吞吐量:單位時間內完成作業的數量
    • 周轉時間:從作業被提交給系統開始,到作業完成為止的時間間隔 = 作業完成時間-作業提交時間
    • 帶權周轉時間:(由於周轉時間相同的情況下,可能實際作業的運行時間不一樣,這樣就會給用戶帶來不一樣的感覺) 作業周轉時間/作業實際運行時間, 帶權周轉時間>=1, 越小越好
    • 平均帶權周轉時間:各作業帶權周轉時間之和/作業數
    • 等待時間
    • 響應時間

    調度算法:

    算法思想,用於解決什麼問題?

    算法規則,用於作業(PCB作業)調度還是進程調度?

    搶佔式還是非搶佔式的?

    優缺點?是否會導致飢餓?

    以下調度算法是適用於當前交互式操作系統

    • 時間片輪轉(Round-Robin)
      • 算法思想:公平地、輪流地為各個進程服務,讓每個進程在一定時間間隔內可以得到相應
      • 算法規則:按照各進程到達就緒隊列的順序,輪流讓各個進程執行一個時間片(如100ms)。若進程未在一個時間片內執行完,則剝奪處理機,將進程重新放到就緒隊列隊尾重新排隊。
      • 用於作業/進程調度:用於進程的調度(只有作業放入內存建立相應的進程后,才會被分配處理機時間片)
      • 是否可搶佔?若進程未能在規定時間片內完成,將被強行剝奪處理機使用權,由時鐘裝置發出時鐘中斷信號來通知CPU時間片到達
      • 優缺點:適用於分時操作系統,由於高頻率的進程切換,因此有一定開銷;不區分任務的緊急程度
      • 是否會導致飢餓? 不會
    • 優先級調度算法
      • 算法思想:隨着計算機的發展,特別是實時操作系統的出現,越來越多的應用場景需要根據任務的進程成都決定處理順序
      • 算法規則:每個作業/進程有各自的優先級,調度時選擇優先級最高的作業/進程
      • 用於作業/進程調度:即可用於作業調度(處於外存後備隊列中的作業調度進內存),也可用於進程調度(選擇就緒隊列中的進程,為其分配處理機),甚至I/O調度
      • 是否可搶佔? 具有可搶佔版本,也有非搶佔式的
      • 優缺點:適用於實時操作系統,用優先級區分緊急程度,可靈活地調整對各種作業/及進程的偏好程度。缺點:若源源不斷地提供高優先級進程,則可能導致飢餓
      • 是否會導致飢餓: 會
    • 多級反饋隊列調度算法
      • 算法思想:綜合FCFS、SJF(SPF)、時間片輪轉、優先級調度

      • 算法規則:

        • 1.設置多級就緒隊列,各級別隊列優先級從高到底,時間片從小到大
        • 2.新進程到達時先進入第1級隊列,按照FCFS原則排隊等待被分配時間片,若用完時間片進程還未結束,則進程進入下一級隊列隊尾
        • 3.只有第k級別隊列為空時,才會為k+1級對頭的進程分配時間片
      • 用於作業/進程調度:用於進程調度

      • 是否可搶佔? 搶佔式算法。在k級隊列的進程運行過程中,若更上級別的隊列(1-k-1級)中進入一個新進程,則由於新進程處於優先級高的隊列中,因此新進程會搶佔處理機,原理運行的進程放回k級隊列隊尾。

      • 優缺點:對各類型進程相對公平(FCFS的有點);每個新到達的進程都可以很快就得到相應(RR優點);短進程只用較少的時間就可完成(SPF)的有點;不必實現估計進程的運行時間;可靈活地調整對各類進程的偏好程度,比如CPU密集型進程、I/O密集型進程(拓展:可以將因I/O而阻塞的進程重新放回原隊列,這樣I/O型進程就可以保持較高優先級)

      • 是否會導致飢餓: 會

    2. 線程

    引入線程之後,進程只作為除CPU之外的系統資源的分配單元(如:打印機,內存地址空間等都是分配給進程的)

    線程的是實現方式:

    • 用戶級線程(User-Level Thread),用戶級線程由應用程序通過線程庫是實現如python (import thread), 線程的管理工作由應用程序負責。
    • 內核級線程(kernel-Level Thread),內核級線程的管理工作由操作系統內核完成,線程調度,切換等工作都由內核負責,因此內核級線程的切換必然需要在核心態下才能完成

    進程和線程的關係:一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程并行執行不同的任務。CPU的最小調度單元是線程,所以單進程多線程是可以利用多核CPU的。

    2.1 線程模型:

    • 用戶級線程模型(一對多模型)

    多個用戶態的線程對應着一個內核線程,程序線程的創建、終止、切換或者同步等線程工作必須自身來完成。python就是這種。雖然可以實現異步,但是不能有效利用多核(GIL)

    • 內核級線程模型 (一對一)

    這種模型直接調用操作系統的內核線程,所有線程的創建、終止、切換、同步等操作,都由內核來完成。C++就是這種

    • 兩級線程模型(M:N)

    這種線程模型會先創建多個內核級線程,然後用自身的用戶級線程去對應創建的多個內核級線程,自身的用戶級線程需要本身程序去調度,內核級的線程交給操作系統內核去調度。GO語言就是這種。

    python中的多線程因為GIL的存在,並不能利用多核CPU優勢,但是在阻塞的系統調用中,如sock.connect(), sock.recv()等耗時的I/O操作,當前的線程會釋放GIL,讓出處理器。但是單個線程內,阻塞調用上還是阻塞的。除了GIL之外,所有的多線程還有通病,他們都是被OS調用的,調度策略是搶佔式的,以保證同等有限級的線程都有機執行,帶來的問題就是:並不知道下一刻執行那個線程,也不知道正在執行什麼代碼,會存在競態條件

    3. 協程

    協程通過在線程中實現調度,避免了陷入內核級別的上下文切換造成的性能損失,進而突破了線程在IO上的性能瓶頸。

    python的協程源於yield指令

    • yield item 用於產出一個值,反饋給next()的調用方法
    • 讓出處理機,暫停執行生成器,讓調用方繼續工作,直到需要使用另一個值時再調用next()

    協程式對線程的調度,yield類似惰性求職方式可以視為一種流程控制工具,實現協作式多任務,python3.5引入了async/await表達式,使得協程證實在語言層面得到支持和優化,大大簡化之前的yield寫法。線程正式在語言層面得到支持和優化。線程是內核進行搶佔式調度的,這樣就確保每個線程都有執行的機會。而coroutine運行在同一個線程中,有語言層面運行時中的EventLoop(事件循環)來進行調度。在python中協程的調度是非搶佔式的,也就是說一個協程必須主動讓出執行機會,其他協程才有機會運行。讓出執行的關鍵字 await, 如果一個協程阻塞了,持續不讓出CPU處理機,那麼整個線程就卡住了,沒有任何併發。

    PS: 作為服務端,event loop最核心的就是I/O多路復用技術,所有來自客戶端的請求都由I/O多路復用函數來處理;作為客戶端,event loop的核心在於Future對象延遲執行,並使用send函數激發協程,掛起,等待服務端處理完成返回后再調用Callback函數繼續執行。[python 協程與go協程的區別]

    3.1 Golang 協程

    Go 天生在語言層面支持,和python類似都是用關鍵字,而GO語言使用了go關鍵字,go協程之間的通信,採用了channel關鍵字。

    go實現了兩種併發形式:

    • 多線程共享內存:如Java 或者C++在多線程中共享數據的時候,通過鎖來訪問
    • Go語言特有的,也是Go語言推薦的 CSP(communicating sequential processes)併發模型。
    package main 
    
    import ("fmt")
    
    func main() {
        jobs := make(chan int)
        done := make(chan bool)  // end flag
        
        go func() {
            for {
                j, ok := <- jobs 
                fmt.Println("---->:", j, ok)
                if ok {
                    fmt.Println("received job")
                } else {
                    fmt.Println("end received jobs")
                    done <- true
                    return
                }
            }
        }()
        
        go func() {
            for j:= 1; j <= 3; j++ {
                jobs <-j
                fmt.Println("sent job", j)
            }
            close(jobs)
            fmt.Println("close(jobs)")
        }()
        
        fmt.Println("sent all jobs")
        <-done  // 阻塞 讓main等待協程完成
    }
    

    Go的CSP併發模型是通過goroutine 和 channel來實現的。

    • goroutine是go語言中併發的執行單位。
    • channel是Go語言中各個併發結構體之間的通信機制。
      • channel -< data 寫數據
      • <- channel 讀數據

    協程本質上來說是一種用戶態的線程,不需要系統來執行搶佔式調度,而是在語言測個面實現線程的調度。

    4. 併發

    併發:Do not communicate by sharing memory; instead, share memory by communicate.

    4.1 Actor模型

    Actor模型和CSP模型的區別:

    • CSP並不Focus發送消息的實體/Task, 而是關注發送消息時消息所使用的載體,即channel。
    • 在Actor的設計中,Actor與信箱是耦合的,而在CSP中channel是作為first-class獨立存在的
    • Actor中有明確的send/receive關係,而channel中並不區分這樣的關係,執行快可以任意選擇發送或者取消息

    好文推薦:Go/Python/Erlang編程語言對比分析及示例

    4.4 Go 協程調度器 GPM

    • G 指的是Goroutine,其本質上也是一種輕量級的線程
    • P proessor, 代表M所需要的上下文環境,也是處理用戶級代碼邏輯處理器。同一時間只有一個線程(M)可以擁有P, P中的數據都是鎖自由(lock free)的, 讀寫這些數據的效率會非常的高
    • M Machine,一個M直接關聯一個內核線程,可以運行go代碼 即goroutine, M運行go代碼需要一個P, 另外就是運行原生代碼,如 syscall。運行原生代碼不需要P。

    一個M會對應一個內核線程,一個M也會連接一個上下文P,一個上下文P相當於一個“處理器”,一個上下文連接一個或者多個Goroutine。P(Processor)的數量是在啟動時被設置為環境變量GOMAXPROCS的值,或者通過運行時調用函數runtime.GOMAXPROCS()進行設置

    erlang和golang都是採用CSP模型,python中協程是eventloop模型。但是erlang是基於進程的消息通信,go是基於goroutine和channel通信。

    python和golang都引入了消息調度系統模型,來避免鎖的影響和進程線程的開銷問題。

    計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決 — G-P-M模型正是此理論踐行者,此理論也用到了python的asyncio對地獄回調的處理上(使用Task+Future避免回調嵌套),是不是巧合?
    其實異步≈可中斷的函數+事件循環+回調,go和python都把嵌套結構轉換成列表結構有點像算法中的遞歸轉迭代.

    調度器在計算機中是分配工作時所需要的資源,Linux的調度是CPU找到可運行的線程,Go的調度是為M線程找到P(內存,執行票據)和可運行的G(協程)

    Go協程是輕量級的,棧初始2KB(OS操作系統的線程一般都是固有的棧內存2M), 調度不涉及系統調用,用戶函數調用前會檢查棧空間是否足夠,不夠的話,會進行站擴容,棧大小限制可以達到1GB。

    Go的網絡操作是封裝了epoll, 為NonBlocking模式,切換協程不阻塞線程。

    Go語言相比起其他語言的優勢在於OS線程是由OS內核來調度的,goroutine則是由Go運行時(runtime)自己的調度器調度的,這個調度器使用一個稱為m:n調度的技術(復用/調度m個goroutine到n個OS線程)。 其一大特點是goroutine的調度是在用戶態下完成的, 不涉及內核態與用戶態之間的頻繁切換,包括內存的分配與釋放,都是在用戶態維護着一塊大的內存池, 不直接調用系統的malloc函數(除非內存池需要改變),成本比調度OS線程低很多。 另一方面充分利用了多核的硬件資源,近似的把若干goroutine均分在物理線程上, 再加上本身goroutine的超輕量,以上種種保證了go調度方面的性能。點我了解更多

    4.5 Go 調度器的實現 以及搶佔式調度

    legendtkl阿里雲技術專家

    Golang源碼探索(二) 協程的實現原理

    相關參考文獻:

    王道操作系統

    操作系統中調度算法(FCFS、RR、SPN、SRT、HRRN)

    Python協程與Go協程的區別二

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

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

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

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

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

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

    ※回頭車貨運收費標準

  • LeetCode 79,這道走迷宮問題為什麼不能用寬搜呢?

    LeetCode 79,這道走迷宮問題為什麼不能用寬搜呢?

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天是LeetCode專題第48篇文章,我們一起來看看LeetCode當中的第79題,搜索單詞(Word Search)。

    這一題官方給的難度是Medium,通過率是34.5%,點贊3488,反對170。單從這份數據上來看,這題的質量很高,並且難度比之前的題目稍稍大一些。我個人覺得通過率是比官方給的題目難得更有參考意義的指標,10%到20%可以認為是較難的題,30%左右是偏難的題。50%是偏易題,所以如果看到某題標着Hard,但是通過率有50%,要麼說明題目很水,要麼說明數據很水,總有一點很水。

    題意

    廢話不多說,我們來看題意:

    這題的題面挺有意思,給定一個二維的字符型數組,以及一個字符串,要求我們來判斷能否在二維數組當中找到一條路徑,使得這條路徑上的字符連成的字符串和給定的字符串相等?

    樣例

    board =
    [
      ['A','B','C','E'],
      ['S','F','C','S'],
      ['A','D','E','E']
    ]
    
    Given word = "ABCCED", return true.
    Given word = "SEE", return true.
    Given word = "ABCB", return false.
    

    比如第一個字符串ABCCED,我們可以在數組當中找到這樣一條路徑:

    題解

    不知道大家看到題面和這個樣例有什麼樣的感覺,如果你刷過許多題,經常思考的話,我想應該不難發現,這道題的本質其實和走迷宮問題是一樣的。

    我們拿到的這個二維的字符型數組就是一個迷宮, 我們是要在這個迷宮當中找一條“出路”。不過我們的目的不是找到終點,而是找到一條符合題意的路徑。在走迷宮問題當中,迷宮中不是每一個點都可以走的,同樣在當前問題當中,也不是每一個點都符合字符串的要求的。這兩個問題雖然題面看起來大相徑庭,但是核心的本質是一樣的。

    我們來回憶一下,走迷宮問題應該怎麼解決?

    這個答案應該已經非常確定了,當然是搜索算法。我們需要搜索解可能存在的空間去尋找存在的解,也就是說我們面臨的是一個解是否存在的問題,要麼找到解,要麼遍歷完所有的可能性發現解不存在。確定了是搜索算法之後,剩下的就簡單了,我們只有兩個選項,深度優先或者是廣度優先。

    理論上來說,一般判斷解的存在性問題,我們使用廣度優先搜索更多,因為一般來說它可以更快地找到解。但是本題當中有一個小問題是,廣度優先搜索需要在隊列當中存儲中間狀態,需要記錄地圖上行走過的信息,每有一個狀態就需要存儲一份地圖信息,這會帶來比較大的內存開銷,同樣存儲的過程也會帶來計算開銷,在這道題當中,這是不可以接受的。拷貝狀態帶來的空間消耗還是小事,關鍵是拷貝帶來的時間開銷,就足夠讓這題超時了。所以我們別無選擇,只能深度優先。

    明確了算法之後,只剩下了最後一個問題,在這個走迷宮問題當中,我們怎麼找到迷宮的入口呢?因為題目當中並沒有規定我們起始點的位置,這也不難解決,我們遍歷二維的字符數組,和字符串開頭相匹配的位置都可以作為迷宮的入口。

    最後,我們來看代碼,並沒有什麼技術含量,只是簡單的回溯法而已。

    class Solution:
        def exist(self, board: List[List[str]], word: str) -> bool:
            fx = [[0, 1], [0, -1], [1, 0], [-1, 0]]
            def dfs(x, y, l):
                if l == len(word):
                    return True
                for i in range(4):
                    nx = x + fx[i][0]
                    ny = y + fx[i][1]
                    # 出界或者是走過的時候,跳過
                    if nx < 0 or nx == n or ny < 0 or ny == m or visited[nx][ny]:
                        continue
                    if board[nx][ny] == word[l]:
                        visited[nx][ny] = 1
                        if dfs(nx, ny, l+1):
                            return True
                        visited[nx][ny] = 0
                return False
                    
            n = len(board)
            if n == 0:
                return False
            m = len(board[0])
            if m == 0:
                return False
            
            visited = [[0 for i in range(m)] for j in range(n)]
            
            for i in range(n):
                for j in range(m):
                    # 找到合法的起點
                    if board[i][j] == word[0]:
                        visited = [[0 for _ in range(m)] for _ in range(n)]
                        visited[i][j] = 1
                        if dfs(i, j, 1):
                            return True
                        
            return False
    

    總結

    如果能夠想通回溯法,並且對於回溯法的實現足夠熟悉,那麼這題的難度是不大的。實際上至今為止,我們一路刷來,已經做了好幾道回溯法的問題了,我想對你們來說,回溯法的問題應該已經小菜一碟了。

    相比於回溯法來說,我覺得更重要的是我們能夠通過分析想清楚,為什麼廣度優先搜索不行,底層核心的本質原因是什麼。這個思考的過程往往比最後的結論來得重要。

    如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • Blazor帶我重玩前端(一)

    Blazor帶我重玩前端(一)

    寫在前面

    曾經我和前端朋友聊天的時候,我說我希望有一天可以用C#寫前端,不過當時更多的是美好的想象,而現在這一切正變得真實……

     

    什麼是Blazor

    我們知道瀏覽器可以正確解釋並執行JavaScript代碼,那麼瀏覽器是如何執行C#代碼的呢?答案是通過WebAssembly。通過WebAssembly,我們可以讓瀏覽器運行很多的高級語言,如 C#、C、C++、GO等,並使他們運行在基於內存安全的沙箱環境中。如下圖所示:

    作為一個已經五六年沒有寫過前端的.NET程序員,遇到Blazor實在是幸運中的幸運。它又讓我可以很愉快的寫前端了,而且還是用C#去寫,我也就不用再分出精力去學習其他的JS框架了。

    我們可以認為Blazor是.NET對WebAssembly的實現。通過使用Blazor,我們可以使用C#語言來取代JS去開發交互式Web UI。

    值得一提的是,Blazor是由Browser和Razor這兩個單詞合併而成的,意思就是Blazor可以基於客戶端執行Razor視圖后將HTML呈現給瀏覽器。所以想要更好的理解Blazor,就要首先更好的了解瀏覽器和Razor。

    Blazor有以下幾個優點:
    • 使用C#來取代JavaScript創建豐富的交互式UI
    • 基於.NET及其生態編寫服務器端和客戶端應用程序邏輯
    • 糅合現有HTML和CSS技術,提供了廣泛的瀏覽器支持,包括移動瀏覽器(注意:Blazor取代的是基於JavaScript的UI交互,而其他部分如HTML、CSS,這些是我們的技術基礎)
    • 與現代託管平台(例如Docker)集成。
    • Blazor是開源的,其源碼位置在GitHub上

    另外需要注意的,Blazor和Silverlight不可混為一談,Blazor是基於開放標準而構建的,本身不需要任何額外插件。而Silverlight帶有太多自有特性,所以不得不在瀏覽器上安裝插件以更好的支持其運行。

    什麼是WebAssembly

    概覽

    WebAssembly是一種二進制格式的指令集,其設計目標是能夠在解釋或者將其編譯為本地機器代碼並執行他們的機器上運行,這類似於我們.NET編譯后的IL。

    WebAssembly可以作為編譯高級編程語言的可移植目標,通過節省大小和加載時間,充分利用各種平台(移動平台和IOT平台)上的通用應用功能,使得WebAssembly可以以接近於本機(接近於本機的英語單詞是:near-native,在語言學里意思是精通語言的人,所說的話和說母語的人沒有什麼區別)的運行速度運行。

    支持

    WebAssembly已經獲得了大部分瀏覽器的支持。詳細內容可以移步至Can I Use

    手寫一個例子

    接下來我們看一個例子,方便起見,我們直接使用在線的WebAssembly編譯工具,地址是:https://mbebenita.github.io/WasmExplorer/。目前,這個工具只支持C和C++。不過也沒有什麼關係,我們寫一個簡單的方法用於測試即可。

    • 首先我們定義了一個計算兩個數和的方法:
    1 int Addition(int a, int b)
    2 {
    3   return a + b; 
    4 }
    • 然後點擊COMPILE

    在中間的框里會生成WAT(即WebAssembly文本格式)的代碼,最右邊的是二進制了。中間的代碼部分可以幫助我們查看在編譯的過程中發生了什麼,會看到生成了一個名為_Z8Additionii的function,其中8表示這個方法名的長度,後面的i表示有多個參數,接下來我們會去調用它。

     1 (module
     2  (table 0 anyfunc)
     3  (memory $0 1)
     4  (export "memory" (memory $0))
     5  (export "_Z8Additionii" (func $_Z8Additionii))
     6  (func $_Z8Additionii (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
     7   (i32.add
     8    (get_local $1)
     9    (get_local $0)
    10   )
    11  )
    12 )
    • 點擊Download,下載.WAT文件

    • 接下來我們再寫一個HTML網頁出來,就用那種最簡單的HTML代碼,代碼如下:
     1 <HTML>
     2 <HEAD>
     3     <TITLE>WebAssembly Sample: Call C++ Code</TITLE>
     4     <script type="text/javascript">
     5         let addition = fetch('test.wasm')
     6             .then(response => response.arrayBuffer())
     7             .then(buffer => WebAssembly.compile(buffer))
     8             .then(module => { return new WebAssembly.Instance(module) })
     9             .then(instance => { addition = instance.exports._Z8Additionii }); 
    10     </script>
    11 </HEAD>
    12 <BODY BGCOLOR="FFFFFF">
    13     <h1>WebAssembly Sample: Call C++ Code</h1>
    14 </BODY>
    15 </HTML>
    • 最終的效果圖
    
    

    通過以上示例,我們基本上對Blazor和WebAssembly的部分運行機制有了一個比較清晰的認識了,接下來,我們繼續討論有關Blazor的內容。

    參考鏈接:

    https://webassembly.org/

    https://webassembly.github.io/spec/js-api/index.html

    https://caniuse.com/#search=wasm

    https://webassembly.github.io/spec/js-api/index.html

    
    

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

    【其他文章推薦】

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

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

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

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

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

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

  • 010.OpenShift綜合實驗及應用

    010.OpenShift綜合實驗及應用

    實驗一 安裝OpenShift

    1.1 前置準備

    [student@workstation ~]$ lab review-install setup

    1.2 配置規劃

    OpenShift集群有三個節點:

    • master.lab.example.com:OpenShift master節點,是一個不可調度pod的節點。
    • node1.lab.example.com:一個OpenShift節點,它可以同時運行應用程序和基礎設施pod。
    • node2.lab.example.com:另一個OpenShift節點,它可以同時運行應用程序和基礎設施pod。

    所有節點都使用帶有overlay2驅動程序的OverlayFS來存儲Docker,每個節點中的第二個磁盤(vdb)保留給Docker存儲。

    所有節點都將使用基於rpm的安裝,使用release v3.9和OpenShift image tag version v3.9.14。

    路由的默認域是apps.lab.example.com。Classroom DNS服務器已經配置為將此域中的所有主機名解析為node1.lab.example.com。

    OpenShift集群使用的所有容器image都存儲在registry.lab.example.com提供的私有倉庫中。

    使用兩個基於HTPasswd身份驗證的初始用戶:developer和admin,起密碼都是redhat,developer作為普通用戶,admin作為集群管理員。

    services.lab.example.com中的NFS卷作為OpenShift內部倉庫的持久存儲支持。

    services.lab.example.com也為集群存儲提供NFS服務。

    etcd也部署在master節點上,同時存儲使用services.lab.example.com主機提供的NFS共享存儲。

    集群必須與Internet斷開連接,即使用離線包形式。

    內部OpenShift倉庫應該由NFS持久存儲支持,存儲位於services.lab.example.com。

    master API和控制台將在端口443上運行。

    安裝OpenShift所需的RPM包由已經在所有主機上使用Yum配置文件定義完成。

    /home/student/DO280/labs/review-install文件夾為OpenShift集群的安裝提供了一個部分完成的Ansible目錄文件。這個文件夾中包含了執行安裝前和安裝後步驟所需的Ansible playbook。

    測試應用程序由Git服務器http://services.lab.example.com/phphelloworld提供。這是一個簡單的“hello, world”應用程序。可以使用Source-to-Image來部署這個應用程序,以驗證OpenShift集群是否已部署成功。

    1.3 確認Ansible

      1 [student@workstation ~]$ cd /home/student/DO280/labs/review-install/
      2 [student@workstation review-install]$ sudo yum -y install ansible
      3 [student@workstation review-install]$ ansible --version
      4 [student@workstation review-install]$ cat ansible.cfg
      5 [defaults]
      6 remote_user = student
      7 inventory = ./inventory
      8 log_path = ./ansible.log
      9 
     10 [privilege_escalation]
     11 become = yes
     12 become_user = root
     13 become_method = sudo

    1.4 檢查Inventory

      1 [student@workstation review-install]$ cp inventory.preinstall inventory		#此為準備工作的Inventory
      2 [student@workstation review-install]$ cat inventory
      3 [workstations]
      4 workstation.lab.example.com
      5 
      6 [nfs]
      7 services.lab.example.com
      8 
      9 [masters]
     10 master.lab.example.com
     11 
     12 [etcd]
     13 master.lab.example.com
     14 
     15 [nodes]
     16 master.lab.example.com
     17 node1.lab.example.com
     18 node2.lab.example.com
     19 
     20 [OSEv3:children]
     21 masters
     22 etcd
     23 nodes
     24 nfs
     25 
     26 #Variables needed by the prepare_install.yml playbook.
     27 [nodes:vars]
     28 registry_local=registry.lab.example.com
     29 use_overlay2_driver=true
     30 insecure_registry=false
     31 run_docker_offline=true
     32 docker_storage_device=/dev/vdb

    提示:

    Inventory定義了六個主機組:

    • nfs:為集群存儲提供nfs服務的環境中的vm;
    • masters:OpenShift集群中用作master角色的節點;
    • etcd:用於OpenShift集群的etcd服務的節點,本環境中使用master節點;
    • node:OpenShift集群中的node節點;
    • OSEv3:組成OpenShift集群的所有接待,包括master、etcd、node或nfs組中的節點。

    注意:默認情況下,docker使用在線倉庫下載容器映像。本環境內部無網絡,因此將docker倉庫配置為內部私有倉庫。在yml中使用變量引入倉庫配置。

    此外,安裝會在每個主機上配置docker守護進程,以使用overlay2 image驅動程序存儲容器映像。Docker支持許多不同的image驅動。如AUFS、Btrfs、Device mapper、OverlayFS。

    1.5 確認節點

      1 [student@workstation review-install]$ cat ping.yml
      2 ---
      3 - name: Verify Connectivity
      4   hosts: all
      5   gather_facts: no
      6   tasks:
      7     - name: "Test connectivity to machines."
      8       shell: "whoami"
      9       changed_when: false
     10 [student@workstation review-install]$ ansible-playbook -v ping.yml

    1.6 準備工作

      1 [student@workstation review-install]$ cat prepare_install.yml
      2 ---
      3 - name: "Host Preparation: Docker tasks"
      4   hosts: nodes
      5   roles:
      6     - docker-storage
      7     - docker-registry-cert
      8     - openshift-node
      9 
     10   #Tasks below were not handled by the roles above.
     11   tasks:
     12     - name: Student Account - Docker Access
     13       user:
     14         name: student
     15         groups: docker
     16         append: yes
     17 
     18 ...
     19 [student@workstation review-install]$ ansible-playbook prepare_install.yml

    提示:如上yml引入了三個role,具體role內容參考《002.OpenShift安裝與部署》2.5步驟。

    1.7 確認驗證

      1 [student@workstation review-install]$ ssh node1 'docker pull rhel7:latest' #驗證是否可以正常pull image

    1.8 檢查Inventory

      1 [student@workstation review-install]$ cp inventory.partial inventory		#此為正常安裝的完整Inventory
      2 [student@workstation review-install]$ cat inventory
      3 [workstations]
      4 workstation.lab.example.com
      5 
      6 [nfs]
      7 services.lab.example.com
      8 
      9 [masters]
     10 master.lab.example.com
     11 
     12 [etcd]
     13 master.lab.example.com
     14 
     15 [nodes]
     16 master.lab.example.com
     17 node1.lab.example.com openshift_node_labels="{'region':'infra', 'node-role.kubernetes.io/compute':'true'}"
     18 node2.lab.example.com openshift_node_labels="{'region':'infra', 'node-role.kubernetes.io/compute':'true'}"
     19 
     20 [OSEv3:children]
     21 masters
     22 etcd
     23 nodes
     24 nfs
     25 
     26 #Variables needed by the prepare_install.yml playbook.
     27 [nodes:vars]
     28 registry_local=registry.lab.example.com
     29 use_overlay2_driver=true
     30 insecure_registry=false
     31 run_docker_offline=true
     32 docker_storage_device=/dev/vdb
     33 
     34 
     35 [OSEv3:vars]
     36 #General Variables
     37 openshift_disable_check=disk_availability,docker_storage,memory_availability
     38 openshift_deployment_type=openshift-enterprise
     39 openshift_release=v3.9
     40 openshift_image_tag=v3.9.14
     41 
     42 #OpenShift Networking Variables
     43 os_firewall_use_firewalld=true
     44 openshift_master_api_port=443
     45 openshift_master_console_port=443
     46 #default subdomain
     47 openshift_master_default_subdomain=apps.lab.example.com
     48 
     49 #Cluster Authentication Variables
     50 openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', 'challenge': 'true', 'kind': 'HTPasswdPasswordIdentityProvider', 'filename': '/etc/origin/master/htpasswd'}]
     51 openshift_master_htpasswd_users={'admin': '$apr1$4ZbKL26l$3eKL/6AQM8O94lRwTAu611', 'developer': '$apr1$4ZbKL26l$3eKL/6AQM8O94lRwTAu611'}
     52 
     53 #Need to enable NFS
     54 openshift_enable_unsupported_configurations=true
     55 #Registry Configuration Variables
     56 openshift_hosted_registry_storage_kind=nfs
     57 openshift_hosted_registry_storage_access_modes=['ReadWriteMany']
     58 openshift_hosted_registry_storage_nfs_directory=/exports
     59 openshift_hosted_registry_storage_nfs_options='*(rw,root_squash)'
     60 openshift_hosted_registry_storage_volume_name=registry
     61 openshift_hosted_registry_storage_volume_size=40Gi
     62 
     63 #etcd Configuration Variables
     64 openshift_hosted_etcd_storage_kind=nfs
     65 openshift_hosted_etcd_storage_nfs_options="*(rw,root_squash,sync,no_wdelay)"
     66 openshift_hosted_etcd_storage_nfs_directory=/exports
     67 openshift_hosted_etcd_storage_volume_name=etcd-vol2
     68 openshift_hosted_etcd_storage_access_modes=["ReadWriteOnce"]
     69 openshift_hosted_etcd_storage_volume_size=1G
     70 openshift_hosted_etcd_storage_labels={'storage': 'etcd'}
     71 
     72 #Modifications Needed for a Disconnected Install
     73 oreg_url=registry.lab.example.com/openshift3/ose-${component}:${version}
     74 openshift_examples_modify_imagestreams=true
     75 openshift_docker_additional_registries=registry.lab.example.com
     76 openshift_docker_blocked_registries=registry.access.redhat.com,docker.io
     77 openshift_web_console_prefix=registry.lab.example.com/openshift3/ose-
     78 openshift_cockpit_deployer_prefix='registry.lab.example.com/openshift3/'
     79 openshift_service_catalog_image_prefix=registry.lab.example.com/openshift3/ose-
     80 template_service_broker_prefix=registry.lab.example.com/openshift3/ose-
     81 ansible_service_broker_image_prefix=registry.lab.example.com/openshift3/ose-
     82 ansible_service_broker_etcd_image_prefix=registry.lab.example.com/rhel7/
     83 [student@workstation review-install]$ lab review-install verify		#本環境使用腳本驗證

    1.9 安裝OpenShift Ansible playbook

      1 [student@workstation review-install]$ rpm -qa | grep atomic-openshift-utils
      2 [student@workstation review-install]$ sudo yum -y install atomic-openshift-utils

    1.10 Ansible安裝OpenShift

      1 [student@workstation review-install]$ ansible-playbook \
      2 /usr/share/ansible/openshift-ansible/playbooks/prerequisites.yml

      1 [student@workstation review-install]$ ansible-playbook \
      2 /usr/share/ansible/openshift-ansible/playbooks/deploy_cluster.yml

    1.11 確認驗證

    通過web控制台使用developer用戶訪問https://master.lab.example.com,驗證集群已成功配置。

    1.12 授權

      1 [student@workstation review-install]$ ssh root@master
      2 [root@master ~]# oc whoami
      3 system:admin
      4 [root@master ~]# oc adm policy add-cluster-role-to-user cluster-admin admin

    提示:master節點的root用戶,默認為集群管理員。

    1.13 登錄測試

      1 [student@workstation ~]$ oc login -u admin -p redhat \
      2 https://master.lab.example.com
      3 [student@workstation ~]$ oc get nodes			#驗證節點情況

    1.14 驗證pod

      1 [student@workstation ~]$ oc get pods -n default #查看內部pod

    1.15 測試S2I

      1 [student@workstation ~]$ oc login -u developer -p redhat \
      2 https://master.lab.example.com
      3 [student@workstation ~]$ oc new-project test-s2i	#創建項目
      4 [student@workstation ~]$ oc new-app --name=hello \
      5 php:5.6~http://services.lab.example.com/php-helloworld

    1.16 測試服務

      1 [student@workstation ~]$ oc get pods			#查看部署情況
      2 NAME            READY     STATUS    RESTARTS   AGE
      3 hello-1-build   1/1       Running   0          39s
      4 [student@workstation ~]$ oc expose svc hello		#暴露服務
      5 [student@workstation ~]$ curl hello-test-s2i.apps.lab.example.com	#測試訪問
      6 Hello, World! php version is 5.6.25

    1.17 實驗判斷

      1 [student@workstation ~]$ lab review-install grade #本環境使用腳本判斷
      2 [student@workstation ~]$ oc delete project test-s2i #刪除測試項目

    實驗二 部署一個應用

    2.1 前置準備

      1 [student@workstation ~]$ lab review-deploy setup

    2.2 應用規劃

    部署一個TODO LIST應用,包含以下三個容器:

    一個MySQL數據庫容器,它在TODO列表中存儲關於任務的數據。

    一個Apache httpd web服務器前端容器(todoui),它具有應用程序的靜態HTML、CSS和Javascript。

    基於Node.js的API後端容器(todoapi),將RESTful接口公開給前端容器。todoapi容器連接到MySQL數據庫容器來管理應用程序中的數據

    2.3 設置策略

      1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com
      2 [student@workstation ~]$ oc adm policy remove-cluster-role-from-group \
      3 self-provisioner system:authenticated system:authenticated:oauth
      4 #將項目創建限製為僅集群管理員角色,普通用戶不能創建新項目。

    2.4 創建項目

      1 [student@workstation ~]$ oc new-project todoapp
      2 [student@workstation ~]$ oc policy add-role-to-user edit developer	#授予developer用戶可訪問權限的角色edit

    2.5 設置quota

      1 [student@workstation ~]$ oc project todoapp
      2 [student@workstation ~]$ oc create quota todoapp-quota --hard=pods=1	#設置pod的quota

    2.6 創建應用

      1 [student@workstation ~]$ oc login -u developer -p redhat \
      2 https://master.lab.example.com						#使用developer登錄
      3 [student@workstation ~]$ oc new-app --name=hello \
      4 php:5.6~http://services.lab.example.com/php-helloworld			#創建應用
      5 [student@workstation ~]$ oc logs -f bc/hello				#查看build log

    2.7 查看部署

      1 [student@workstation ~]$ oc get pods
      2 NAME             READY     STATUS      RESTARTS   AGE
      3 hello-1-build    0/1       Completed   0          2m
      4 hello-1-deploy   1/1       Running     0          1m
      5 [student@workstation ~]$ oc get events
      6 ……
      7 2m          2m           7         hello.15b54ba822fc1029            DeploymentConfig
      8 Warning   FailedCreate            deployer-controller              Error creating deployer pod: pods "hello-1-deploy" is forbidden: exceeded quota: todoapp-quota, requested: pods=1, used: pods=1, limited: pods=
      9 [student@workstation ~]$ oc describe quota
     10 Name:       todoapp-quota
     11 Namespace:  todoapp
     12 Resource    Used  Hard
     13 --------    ----  ----
     14 pods        1     1

    結論:由於pod的硬quota限制,導致部署失敗。

    2.8 擴展quota

      1 [student@workstation ~]$ oc rollout cancel dc hello	#修正quota前取消dc
      2 [student@workstation ~]$ oc login -u admin -p redhat
      3 [student@workstation ~]$ oc project todoapp
      4 [student@workstation ~]$ oc patch resourcequota/todoapp-quota --patch '{"spec":{"hard":{"pods":"10"}}}'

    提示:也可以使用oc edit resourcequota todoapp-quota命令修改quota配置。

      1 [student@workstation ~]$ oc login -u developer -p redhat
      2 [student@workstation ~]$ oc describe quota		#確認quota
      3 Name:       todoapp-quota
      4 Namespace:  todoapp
      5 Resource    Used  Hard
      6 --------    ----  ----
      7 pods        0     10

    2.9 重新部署

      1 [student@workstation ~]$ oc rollout latest dc/hello
      2 [student@workstation ~]$ oc get pods			#確認部署成功
      3 NAME            READY     STATUS      RESTARTS   AGE
      4 hello-1-build   0/1       Completed   0          9m
      5 hello-2-qklrr   1/1       Running     0          12s
      6 [student@workstation ~]$ oc delete all -l app=hello	#刪除hello

    2.10 配置NFS

      1 [kiosk@foundation0 ~]$ ssh root@services
      2 [root@services ~]# mkdir -p /var/export/dbvol
      3 [root@services ~]# chown nfsnobody:nfsnobody /var/export/dbvol
      4 [root@services ~]# chmod 700 /var/export/dbvol
      5 [root@services ~]# echo "/var/export/dbvol *(rw,async,all_squash)" > /etc/exports.d/dbvol.exports
      6 [root@services ~]# exportfs -a
      7 [root@services ~]# showmount -e

    提示:本實驗使用services上的NFS提供的共享存儲為後續實驗提供持久性存儲。

    2.11 測試NFS

      1 [kiosk@foundation0 ~]$ ssh root@node1
      2 [root@node1 ~]# mount -t nfs services.lab.example.com:/var/export/dbvol /mnt
      3 [root@node1 ~]# ls -la /mnt ; mount | grep /mnt		#測試是否能正常掛載

    提示:建議node2做同樣測試,測試完畢需要卸載,後續使用持久卷會自動進行掛載。

    2.12 創建PV

      1 [student@workstation ~]$ vim /home/student/DO280/labs/review-deploy/todoapi/openshift/mysql-pv.yaml
      2 apiVersion: v1
      3 kind: PersistentVolume
      4 metadata:
      5  name: mysql-pv
      6 spec:
      7  capacity:
      8   storage: 2G
      9  accessModes:
     10   -  ReadWriteMany
     11  nfs:
     12   path: /var/export/dbvol
     13   server: services.lab.example.com
     14 [student@workstation ~]$ oc login -u admin -p redhat
     15 [student@workstation ~]$ oc create -f /home/student/DO280/labs/review-deploy/todoapi/openshift/mysql-pv.yaml
     16 [student@workstation ~]$ oc get pv

    2.13 導入模板

      1 [student@workstation ~]$ oc apply -n openshift -f /home/student/DO280/labs/review-deploy/todoapi/openshift/nodejs-mysql-template.yaml

    提示:模板文件見附件。

    2.14 使用dockerfile創建image

      1 [student@workstation ~]$ vim /home/student/DO280/labs/review-deploy/todoui/Dockerfile
      2 FROM  rhel7:7.5
      3 
      4 MAINTAINER Red Hat Training <training@redhat.com>
      5 
      6 # DocumentRoot for Apache
      7 ENV HOME /var/www/html
      8 
      9 # Need this for installing HTTPD from classroom yum repo
     10 ADD training.repo /etc/yum.repos.d/training.repo
     11 RUN yum downgrade -y krb5-libs libstdc++ libcom_err && \
     12     yum install -y --setopt=tsflags=nodocs \
     13     httpd \
     14     openssl-devel \
     15     procps-ng \
     16     which && \
     17     yum clean all -y && \
     18     rm -rf /var/cache/yum
     19 
     20 # Custom HTTPD conf file to log to stdout as well as change port to 8080
     21 COPY conf/httpd.conf /etc/httpd/conf/httpd.conf
     22 
     23 # Copy front end static assets to HTTPD DocRoot
     24 COPY src/ ${HOME}/
     25 
     26 # We run on port 8080 to avoid running container as root
     27 EXPOSE 8080
     28 
     29 # This stuff is needed to make HTTPD run on OpenShift and avoid
     30 # permissions issues
     31 RUN rm -rf /run/httpd && mkdir /run/httpd && chmod -R a+rwx /run/httpd
     32 
     33 # Run as apache user and not root
     34 USER 1001
     35 
     36 # Launch apache daemon
     37 CMD /usr/sbin/apachectl -DFOREGROUND
     38 [student@workstation ~]$ cd /home/student/DO280/labs/review-deploy/todoui/
     39 [student@workstation todoui]$ docker build -t todoapp/todoui .
     40 [student@workstation todoui]$ docker images
     41 REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
     42 todoapp/todoui                   latest              0249e1c69e38        39 seconds ago      239 MB
     43 registry.lab.example.com/rhel7   7.5                 4bbd153adf84        12 months ago       201 MB

    2.15 推送倉庫

      1 [student@workstation todoui]$ docker tag todoapp/todoui:latest \
      2 registry.lab.example.com/todoapp/todoui:latest
      3 [student@workstation todoui]$ docker push \
      4 registry.lab.example.com/todoapp/todoui:latest

    提示:將從dockerfile創建的image打標,然後push至內部倉庫。

    2.16 導入IS

      1 [student@workstation todoui]$ oc whoami -c
      2 todoapp/master-lab-example-com:443/admin
      3 [student@workstation todoui]$ oc import-image todoui \
      4 --from=registry.lab.example.com/todoapp/todoui \
      5 --confirm -n todoapp					#將docker image導入OpenShift的Image Streams
      6 [student@workstation todoui]$ oc get is -n todoapp
      7 NAME      DOCKER REPO                                       TAGS      UPDATED
      8 todoui    docker-registry.default.svc:5000/todoapp/todoui   latest    13 seconds ago
      9 [student@workstation todoui]$ oc describe is todoui -n todoapp	#查看is

    2.17 創建應用

    瀏覽器登錄https://master.lab.example.com,選擇todoapp的項目。

    查看目錄。

    語言——>JavaScript——Node.js + MySQL (Persistent)。

    參考下錶建立應用:

    名稱
    Git Repository URL http://services.lab.example.com/todoapi
    Application Hostname todoapi.apps.lab.example.com
    MySQL Username todoapp
    MySQL Password todoapp
    Database name todoappdb
    Database Administrator Password redhat

    create進行創建。

    Overview進行查看。

    2.18 測試數據庫

      1 [student@workstation ~]$ oc port-forward mysql-1-6hq4d 3306:3306		#保持端口轉發
      2 [student@workstation ~]$ mysql -h127.0.0.1 -u todoapp -ptodoapp todoappdb < /home/student/DO280/labs/review-deploy/todoapi/sql/db.sql
      3 #導入測試數據至數據庫
      4 [student@workstation ~]$ mysql -h127.0.0.1 -u todoapp -ptodoapp todoappdb -e "select id, description, case when done = 1 then 'TRUE' else 'FALSE' END as done from Item;"
      5 #查看是否導入成功

    2.19 訪問測試

      1 [student@workstation ~]$ curl -s http://todoapi.apps.lab.example.com/todo/api/host | python -m json.tool	#curl訪問
      2 {
      3     "hostname": "todoapi-1-kxlnx",
      4     "ip": "10.128.0.12"
      5 }
      6 [student@workstation ~]$ curl -s http://todoapi.apps.lab.example.com/todo/api/items | python -m json.tool	#curl訪問

    2.20 創建應用

      1 [student@workstation ~]$ oc new-app --name=todoui -i todoui	#使用todoui is創建應用
      2 [student@workstation ~]$ oc get pods
      3 NAME              READY     STATUS      RESTARTS   AGE
      4 mysql-1-6hq4d     1/1       Running     0          9m
      5 todoapi-1-build   0/1       Completed   0          9m
      6 todoapi-1-kxlnx   1/1       Running     0          8m
      7 todoui-1-wwg28    1/1       Running     0          32s

    2.21 暴露服務

      1 [student@workstation ~]$ oc expose svc todoui --hostname=todo.apps.lab.example.com

    瀏覽器訪問:http://todo.apps.lab.example.com

    2.22 實驗判斷

      1 [student@workstation ~]$ lab review-deploy grade #本環境使用腳本判斷

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

    【其他文章推薦】

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

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

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

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

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

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

  • 完美解決asp.net core 3.1 兩個AuthenticationScheme(cookie,jwt)共存在一個項目中,基於領域驅動設計(DDD)超輕量級快速開發架構

    完美解決asp.net core 3.1 兩個AuthenticationScheme(cookie,jwt)共存在一個項目中,基於領域驅動設計(DDD)超輕量級快速開發架構

    內容

    在我的項目中有mvc controller(view 和 razor Page)同時也有webapi,那麼就需要網站同時支持2種認證方式,web頁面的需要傳統的cookie認證,webapi則需要使用jwt認證方式,兩種默認情況下不能共存,一旦開啟了jwt認證,cookie的登錄界面都無法使用,原因是jwt是驗證http head “Authorization” 這屬性.所以連login頁面都無法打開.

    解決方案

    實現web通過login頁面登錄,webapi 使用jwt方式獲取認證,支持refreshtoken更新過期token,本質上背後都使用cookie認證的方式,所以這樣的結果是直接導致token沒用,認證不是通過token唯一的作用就剩下refreshtoken了

    通過nuget 安裝組件包

    Microsoft.AspNetCore.Authentication.JwtBearer

    下面是具體配置文件內容

    //Jwt Authentication
          services.AddAuthentication(opts =>
          {
            //opts.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            //opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
          })
          //這裡是關鍵,添加一個Policy來根據http head屬性或是/api來確認使用cookie還是jwt chema
            .AddPolicyScheme(settings.App, "Bearer or Jwt", options =>
            {
              options.ForwardDefaultSelector = context =>
              {
                var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith("Bearer ") ?? false;
                // You could also check for the actual path here if that's your requirement:
                // eg: if (context.HttpContext.Request.Path.StartsWithSegments("/api", StringComparison.InvariantCulture))
                if (bearerAuth)
                  return JwtBearerDefaults.AuthenticationScheme;
                else
                  return CookieAuthenticationDefaults.AuthenticationScheme;
              };
            })
    //這裏和傳統的cookie認證一致       .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
           {
             options.LoginPath = "/Identity/Account/Login";
             options.LogoutPath = "/Identity/Account/Logout";
             options.AccessDeniedPath = "/Identity/Account/AccessDenied";
             options.Cookie.Name = "CustomerPortal.Identity";
             options.SlidingExpiration = true;
             options.ExpireTimeSpan = TimeSpan.FromSeconds(10); //Account.Login overrides this default value
           })
            .AddJwtBearer(x =>
          {
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
              ValidateIssuerSigningKey = true,
              IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Jwt:Key"])),
              ValidateIssuer = true,
              ValidateAudience = true,
              ValidateLifetime = true,
              ValidIssuer = Configuration["Jwt:Issuer"],
              ValidAudience = Configuration["Jwt:Issuer"],
            };
          });
    
     //這裏需要對cookie做一個配置
          services.ConfigureApplicationCookie(options =>
          {
            // Cookie settings
            options.Cookie.Name = settings.App;
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromSeconds(10);
            options.LoginPath = "/Identity/Account/Login";
            options.LogoutPath = "/Identity/Account/Logout";
            options.Events = new CookieAuthenticationEvents()
            {
              OnRedirectToLogin = context =>
              {
               //這裏區分當訪問/api 如果cookie過期那麼 不重定向到login登錄界面
                if (context.Request.Path.Value.StartsWith("/api"))
                {
                  context.Response.Clear();
                  context.Response.StatusCode = 401;
                  return Task.FromResult(0);
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.FromResult(0);
              }
            };
            //options.AccessDeniedPath = "/Identity/Account/AccessDenied";
          });        

    startup.cs

    下面userscontroller 認證方式

    重點:我簡化了refreshtoken的實現方式,原本規範的做法是通過第一次登錄返回一個token和一個唯一的隨機生成的refreshtoken,下次token過期后需要重新發送過期的token和唯一的refreshtoken,同時後台還要比對這個refreshtoken是否正確,也就是說,第一次生成的refreshtoken必須保存到數據庫里,這裏我省去了這個步驟,這樣做是不嚴謹的的.

    [ApiController]
      [Route("api/users")]
      public class UsersEndpoint : ControllerBase
      {
        private readonly ILogger<UsersEndpoint> _logger;
        private readonly ApplicationDbContext _context;
        private readonly UserManager<ApplicationUser> _manager;
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly SmartSettings _settings;
        private readonly IConfiguration _config;
    
        public UsersEndpoint(ApplicationDbContext context,
          UserManager<ApplicationUser> manager,
          SignInManager<ApplicationUser> signInManager,
          ILogger<UsersEndpoint> logger,
          IConfiguration config,
          SmartSettings settings)
        {
          _context = context;
          _manager = manager;
          _settings = settings;
          _signInManager = signInManager;
          _logger = logger;
          _config = config;
        }
        [Route("authenticate")]
        [AllowAnonymous]
        [HttpPost]
        public async Task<IActionResult> Authenticate([FromBody] AuthenticateRequest model)
        {
          try
          {
            //Sign user in with username and password from parameters. This code assumes that the emailaddress is being used as the username. 
            var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, true, true);
    
            if (result.Succeeded)
            {
              //Retrieve authenticated user's details
              var user = await _manager.FindByNameAsync(model.UserName);
    
              //Generate unique token with user's details
              var accessToken = await GenerateJSONWebToken(user);
              var refreshToken = GenerateRefreshToken();
              //Return Ok with token string as content
              _logger.LogInformation($"{model.UserName}:JWT登錄成功");
              return Ok(new { accessToken = accessToken, refreshToken = refreshToken });
            }
            return Unauthorized();
          }
          catch (Exception e)
          {
            return StatusCode(500, e.Message);
          }
        }
        [Route("refreshtoken")]
        [AllowAnonymous]
        [HttpPost]
        public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest model)
        {
          var principal = GetPrincipalFromExpiredToken(model.AccessToken);
          var nameId = principal.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
          var user = await _manager.FindByNameAsync(nameId);
          await _signInManager.RefreshSignInAsync(user);
    
            //Retrieve authenticated user's details
            //Generate unique token with user's details
            var accessToken = await GenerateJSONWebToken(user);
            var refreshToken = GenerateRefreshToken();
            //Return Ok with token string as content
            _logger.LogInformation($"{user.UserName}:RefreshToken");
            return Ok(new { accessToken = accessToken, refreshToken = refreshToken });
    
    
        }
    
        private async Task<string> GenerateJSONWebToken(ApplicationUser user)
        {
          //Hash Security Key Object from the JWT Key
          var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
          var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    
          //Generate list of claims with general and universally recommended claims
          var claims = new List<Claim>  {
               new Claim(ClaimTypes.NameIdentifier, user.UserName),
               new Claim(ClaimTypes.Name, user.UserName),
                    new Claim(JwtRegisteredClaimNames.Sub, user.Email),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                    new Claim(ClaimTypes.NameIdentifier, user.Id),
                    //添加自定義claim
                    new Claim(ClaimTypes.GivenName, string.IsNullOrEmpty(user.GivenName) ? "" : user.GivenName),
                    new Claim(ClaimTypes.Email, user.Email),
                    new Claim("http://schemas.microsoft.com/identity/claims/tenantid", user.TenantId.ToString()),
                    new Claim("http://schemas.microsoft.com/identity/claims/avatars", string.IsNullOrEmpty(user.Avatars) ? "" : user.Avatars),
                    new Claim(ClaimTypes.MobilePhone, user.PhoneNumber)
          };
          //Retreive roles for user and add them to the claims listing
          var roles = await _manager.GetRolesAsync(user);
          claims.AddRange(roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r)));
          //Generate final token adding Issuer and Subscriber data, claims, expriation time and Key
          var token = new JwtSecurityToken(_config["Jwt:Issuer"]
              , _config["Jwt:Issuer"],
              claims,
              null,
              expires: DateTime.Now.AddDays(30),
              signingCredentials: credentials
          );
    
          //Return token string
          return new JwtSecurityTokenHandler().WriteToken(token);
        }
    
        public string GenerateRefreshToken()
        {
          var randomNumber = new byte[32];
          using (var rng = RandomNumberGenerator.Create())
          {
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
          }
        }
    
        private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
        {
          var tokenValidationParameters = new TokenValidationParameters
          {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_config["Jwt:Key"])),
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidIssuer = _config["Jwt:Issuer"],
            ValidAudience = _config["Jwt:Issuer"],
          };
    
          var tokenHandler = new JwtSecurityTokenHandler();
          SecurityToken securityToken;
          var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
          var jwtSecurityToken = securityToken as JwtSecurityToken;
          if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
          {
            throw new SecurityTokenException("Invalid token");
          }
    
          return principal;
        }
    ....
    }
    }

    ControllerBase

    下面是測試

    獲取token

     refreshtoken

     

    獲取數據

     

     這裏獲取數據的時候,其實可以不用填入token,因為調用authenticate或refreshtoken是已經記錄了cookie到客戶端,所以在postman測試的時候都可以不用加token也可以訪問

     推廣一下我的開源項目

    基於領域驅動設計(DDD)超輕量級快速開發架構

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

    源代碼

    https://github.com/neozhu/smartadmin.core.urf

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

    【其他文章推薦】

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

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

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

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

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

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

  • Spring Boot 2.x基礎教程:Spring Data JPA的多數據源配置

    上一篇我們介紹了在使用JdbcTemplate來做數據訪問時候的多數據源配置實現。接下來我們繼續學習如何在使用Spring Data JPA的時候,完成多數據源的配置和使用。

    添加多數據源的配置

    先在Spring Boot的配置文件application.properties中設置兩個你要鏈接的數據庫配置,比如這樣:

    spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1
    spring.datasource.primary.username=root
    spring.datasource.primary.password=123456
    spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
    
    spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2
    spring.datasource.secondary.username=root
    spring.datasource.secondary.password=123456
    spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
    
    # 日誌打印執行的SQL
    spring.jpa.show-sql=true
    # Hibernate的DDL策略
    spring.jpa.hibernate.ddl-auto=create-drop
    

    這裏除了JPA自身相關的配置之外,與JdbcTemplate配置時候的數據源配置完全是一致的

    說明與注意

    1. 多數據源配置的時候,與單數據源不同點在於spring.datasource之後多設置一個數據源名稱primarysecondary來區分不同的數據源配置,這個前綴將在後續初始化數據源的時候用到。
    2. 數據源連接配置2.x和1.x的配置項是有區別的:2.x使用spring.datasource.secondary.jdbc-url,而1.x版本使用spring.datasource.secondary.url。如果你在配置的時候發生了這個報錯java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.,那麼就是這個配置項的問題。

    初始化數據源與JPA配置

    完成多數據源的配置信息之後,就來創建個配置類來加載這些配置信息,初始化數據源,以及初始化每個數據源要用的JdbcTemplate。

    由於JPA的配置要比JdbcTemplate的負責很多,所以我們將配置拆分一下來處理:

    1. 單獨建一個多數據源的配置類,比如下面這樣:
    @Configuration
    public class DataSourceConfiguration {
    
        @Primary
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.primary")
        public DataSource primaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.secondary")
        public DataSource secondaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    
    }
    

    可以看到內容跟JdbcTemplate時候是一模一樣的。通過@ConfigurationProperties可以知道這兩個數據源分別加載了spring.datasource.primary.*spring.datasource.secondary.*的配置。@Primary註解指定了主數據源,就是當我們不特別指定哪個數據源的時候,就會使用這個Bean真正差異部分在下面的JPA配置上。

    1. 分別創建兩個數據源的JPA配置。

    Primary數據源的JPA配置:

    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
            entityManagerFactoryRef="entityManagerFactoryPrimary",
            transactionManagerRef="transactionManagerPrimary",
            basePackages= { "com.didispace.chapter38.p" }) //設置Repository所在位置
    public class PrimaryConfig {
    
        @Autowired
        @Qualifier("primaryDataSource")
        private DataSource primaryDataSource;
    
        @Autowired
        private JpaProperties jpaProperties;
        @Autowired
        private HibernateProperties hibernateProperties;
    
        private Map<String, Object> getVendorProperties() {
            return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
        }
    
        @Primary
        @Bean(name = "entityManagerPrimary")
        public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
            return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
        }
    
        @Primary
        @Bean(name = "entityManagerFactoryPrimary")
        public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
            return builder
                    .dataSource(primaryDataSource)
                    .packages("com.didispace.chapter38.p") //設置實體類所在位置
                    .persistenceUnit("primaryPersistenceUnit")
                    .properties(getVendorProperties())
                    .build();
        }
    
        @Primary
        @Bean(name = "transactionManagerPrimary")
        public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
            return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
        }
    
    }
    

    Secondary數據源的JPA配置:

    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
            entityManagerFactoryRef="entityManagerFactorySecondary",
            transactionManagerRef="transactionManagerSecondary",
            basePackages= { "com.didispace.chapter38.s" }) //設置Repository所在位置
    public class SecondaryConfig {
    
        @Autowired
        @Qualifier("secondaryDataSource")
        private DataSource secondaryDataSource;
    
        @Autowired
        private JpaProperties jpaProperties;
        @Autowired
        private HibernateProperties hibernateProperties;
    
        private Map<String, Object> getVendorProperties() {
            return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
        }
    
        @Bean(name = "entityManagerSecondary")
        public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
            return entityManagerFactorySecondary(builder).getObject().createEntityManager();
        }
    
        @Bean(name = "entityManagerFactorySecondary")
        public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
            return builder
                    .dataSource(secondaryDataSource)
                    .packages("com.didispace.chapter38.s") //設置實體類所在位置
                    .persistenceUnit("secondaryPersistenceUnit")
                    .properties(getVendorProperties())
                    .build();
        }
    
        @Bean(name = "transactionManagerSecondary")
        PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
            return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
        }
    
    }
    

    說明與注意

    • 在使用JPA的時候,需要為不同的數據源創建不同的package來存放對應的Entity和Repository,以便於配置類的分區掃描
    • 類名上的註解@EnableJpaRepositories中指定Repository的所在位置
    • LocalContainerEntityManagerFactoryBean創建的時候,指定Entity所在的位置
    • 其他主要注意在互相注入時候,不同數據源不同配置的命名,基本就沒有什麼大問題了

    測試一下

    完成了上面之後,我們就可以寫個測試類來嘗試一下上面的多數據源配置是否正確了,比如下面這樣:

    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Chapter38ApplicationTests {
    
        @Autowired
        private UserRepository userRepository;
        @Autowired
        private MessageRepository messageRepository;
    
        @Test
        public void test() throws Exception {
            userRepository.save(new User("aaa", 10));
            userRepository.save(new User("bbb", 20));
            userRepository.save(new User("ccc", 30));
            userRepository.save(new User("ddd", 40));
            userRepository.save(new User("eee", 50));
    
            Assert.assertEquals(5, userRepository.findAll().size());
    
            messageRepository.save(new Message("o1", "aaaaaaaaaa"));
            messageRepository.save(new Message("o2", "bbbbbbbbbb"));
            messageRepository.save(new Message("o3", "cccccccccc"));
    
            Assert.assertEquals(3, messageRepository.findAll().size());
        }
    
    }
    

    說明與注意

    • 測試驗證的邏輯很簡單,就是通過不同的Repository往不同的數據源插入數據,然後查詢一下總數是否是對的
    • 這裏省略了Entity和Repository的細節,讀者可以在下方代碼示例中下載完整例子對照查看

    代碼示例

    本文的相關例子可以查看下面倉庫中的chapter3-8目錄:

    • Github:https://github.com/dyc87112/SpringBoot-Learning/
    • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

    如果您覺得本文不錯,歡迎Star支持,您的關注是我堅持的動力!

    相關閱讀

    • Spring Boot 1.x基礎教程:多數據源配置

    本文首發:Spring Boot 2.x基礎教程:Spring Data JPA的多數據源配置,轉載請註明出處。
    歡迎關注我的公眾號:程序猿DD,獲得獨家整理的學習資源和日常乾貨推送。
    如果您對我的其他專題內容感興趣,直達我的個人博客:didispace.com。

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

    【其他文章推薦】

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

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

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

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

    ※超省錢租車方案

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

  • 手把手教你基於SqlSugar4編寫一個可視化代碼生成器(生成實體,以SqlServer為例,文末附源碼)

    手把手教你基於SqlSugar4編寫一個可視化代碼生成器(生成實體,以SqlServer為例,文末附源碼)

      在開發過程中免不了創建實體類,字段少的表可以手動編寫,但是字段多還用手動創建的話不免有些浪費時間,假如一張表有100多個字段,手寫有些不現實。

    這時我們會藉助一些工具,如:動軟代碼生成器、各種ORM框架自帶的代碼生成器等等,都可以使用。

    我們現在就基於SqlSugar(ORM框架)自己動手製造一個輪子,以SqlServer為例。我們先看一下成品效果,

     

    使用流程:

      配置好數據庫鏈接,點擊【鏈接數據庫】獲取指定服務器上的數據庫名,點擊數據庫名,動態獲取數據庫下面的所有表,

    點擊數據表,如果生成過了的會自動獲取生成的實體,如果沒有生成過,點擊【生成實體】自動生成显示,直接複製即可使用。

    注:server=xxx.xxx.x.xxx這裏如果是本地沒有配置的話直接server=.即可。

     

     

     

     

    開發環境:

    編譯器:Visual Studio 2017

    運行環境:windows7 x64

    數據庫:SqlServer2012

     

    代碼實現步驟:

    一、創建一個ASP.NET Web應用,命名為GenerateEntity

     

     

     

     

     

     

    二、應用SqlSugar動態鏈接庫

     

     

     

    三、編寫代碼

    這裏分為前端和後端,前端頁面展示,後端後台邏輯(注:由於我們是代碼展示,所以就不搞三層架構、工廠模式這些,直接在控制器中完成,有需要的同學可以根據項目需求進行更改

    內部實現邏輯:

    • 在頁面上配置數據庫鏈接,點擊【鏈接數據庫】按鈕獲取指定數據庫的所有數據庫名显示在左邊;
    • 點擊左邊的數據庫名稱,動態獲取指定數據庫下面所有的表显示出來;
    • 點擊表名,生成過的就显示生成的實體,沒有的則點擊【生成實體】按鈕生成(支持生成單表和數據庫表全部生成);

    這裏我直接貼出代碼,直接拷貝即可使用:

    前端html頁面

    @{
        ViewBag.Title = "Home Page";
    }
    
    <script src="~/Scripts/jquery-3.3.1.js"></script>
    
    <div style="margin-top:10px;font-family:'Microsoft YaHei';font-size:18px; ">
        <div style="height:100px;width:100%;border:1px solid gray;padding:10px">
            <div>
                <span>鏈接數據庫:</span>
                <input style="width:800px;max-width:800px;" id="Link" value="server=xxx.xxx.x.xxx;uid=sa;pwd=xxx" />
                <a href="javascript:void(0)" onclick="LinkServer()">鏈接數據庫</a>
            </div>
            <div style="margin-top:10px">
                <span>數據庫名:</span>
                <input style="color:red;font-weight:600" id="ServerName" />
    
                <span>表名:</span>
                <input style="color:red;font-weight:600" id="TableName" />
    
                <span>生成類型:</span>
                <select id="type">
                    <option value="0">生成單個表</option>
                    <option value="1">生成所有表</option>
                </select>
                <a  href="javascript:void(0)" onclick="GenerateEntity()" style="margin-left:20px;font-weight:600;">生成實體</a>
                <br />
    
            </div>
        </div>
        <div style="height:720px;width:100%;">
            <div style="height:100%;width:40%;float:left; border:1px solid gray;font-size:20px">
    
                <div id="leftserver" style="float:left;border:1px solid gray;height:100%;width:40%;padding:10px;overflow: auto;">
    
                </div>
                <div id="lefttable" style="float:left;border:1px solid gray;height:100%;width:60%;padding:10px;overflow: auto;">
    
                </div>
            </div>
            <div  style="height:100%;width:60%;float:left;border:1px solid gray;overflow: auto;">
                <textarea style="width:100%;height:100%;max-width:10000px" id="righttable"></textarea>
            </div>
        </div>
    </div>
    
    <script type="text/javascript">
    
        //鏈接數據庫
        function LinkServer() {
            $.ajax({
                url: "/Home/LinkServer",
                data: { Link: $("#Link").val() },
                type: "POST",
                async: false,
                dataType: "json",
                success: function (data) {
                    if (data.res) {
                        if (data.info != "") {
                            $("#leftserver").html("");
                            var leftserver = "<span>數據庫名</span><hr />";
                            var info = eval("(" + data.info + ")");
                            for (var i = 0; i < info.length; i++) {
                                leftserver += "<a onclick=\"leftserver('" + info[i].Name + "')\">" + info[i].Name + "</a><br />";
                            }
    
                            $("#leftserver").html(leftserver);
                        }
                    }
                    else {
                        alert(data.msg);
                    }
                }
            });
        }
    
        //查詢指定數據庫的表
        function leftserver(Name) {
            $("#ServerName").val(Name)
            $.ajax({
                url: "/Home/GetTable",
                data: { Link: $("#Link").val(), Name: Name },
                type: "POST",
                async: false,
                dataType: "json",
                success: function (data) {
                    if (data.res) {
                        if (data.info != "") {
                            $("#lefttable").html("");
                            var lefttable = "<span>表名</span><hr />";
                            var info = eval("(" + data.info + ")");
                            for (var i = 0; i < info.length; i++) {
                                lefttable += "<a onclick=\"lefttable('" + info[i].Name + "')\">" + info[i].Name + "</a><br />";
                            }
    
                            $("#lefttable").html(lefttable);
                        }
                    }
                    else {
                        alert(data.msg);
                    }
                }
            });
        }
    
        //查詢指定數據庫的表
        function lefttable(Name) {
            $("#TableName").val(Name);
            $.ajax({
                url: "/Home/GetGenerateEntity",
                data: { TableName: Name },
                type: "POST",
                async: false,
                dataType: "json",
                success: function (data) {
                    if (data.res) {
                        document.getElementById("righttable").innerHTML = data.info;
                    }
                    else {
                        alert(data.msg);
                    }
                }
            });
        }
    
        //生成實體
        function GenerateEntity() {
    
            $.ajax({
                url: "/Home/GenerateEntity",
                data: {
                    Link: $("#Link").val(),
                    Name: $("#ServerName").val(),
                    TableName: $("#TableName").val(),
                    type: $("#type").val()
                },
                type: "POST",
                async: false,
                dataType: "json",
                success: function (data) {
                    if (data.res) {
                        document.getElementById("righttable").innerHTML = data.info;
                    }
                    else {
                        alert(data.msg);
                    }
                }
            });
        }
    
    </script>

     

    後端控制器數據

    using SqlSugar;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Web.Mvc;
    
    namespace GenerateEntity.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View();
            }
    
            public ActionResult About()
            {
                ViewBag.Message = "Your application description page.";
    
                return View();
            }
    
            public ActionResult Contact()
            {
                ViewBag.Message = "Your contact page.";
    
                return View();
            }
    
    
         
            //鏈接數據庫
            public JsonResult LinkServer(string Link)
            {
                ResultInfo result = new ResultInfo();
                try
                {
                    //配置數據庫連接
                    SqlSugarClient db = new SqlSugarClient(
                                        new ConnectionConfig()
                                        {
                                            ConnectionString = ""+ Link + ";database=master",
                                            DbType = DbType.SqlServer,//設置數據庫類型
                                        IsAutoCloseConnection = true,//自動釋放數據務,如果存在事務,在事務結束后釋放
                                        InitKeyType = InitKeyType.Attribute //從實體特性中讀取主鍵自增列信息
                                    });
                    string sql = @"SELECT top 100000 Name FROM Master..SysDatabases ORDER BY Name";  //查詢所有鏈接的所有數據庫名
                    var strList = db.SqlQueryable<databaseName>(sql).ToList();
                    result.info = Newtonsoft.Json.JsonConvert.SerializeObject(strList);
                    result.res = true;
                    result.msg = "鏈接成功!";
                }
                catch (Exception ex)
                {
                    result.msg = ex.Message;
                }
    
                return Json(result, JsonRequestBehavior.AllowGet);
            }
    
            //根據數據庫名查詢所有表
            public JsonResult GetTable(string Link,string Name)
            {
    
                ResultInfo result = new ResultInfo();
                try
                {
                    //配置數據庫連接
                    SqlSugarClient db = new SqlSugarClient(
                                        new ConnectionConfig()
                                        {
                                            ConnectionString = "" + Link + ";database="+ Name + "",
                                            DbType = DbType.SqlServer,//設置數據庫類型
                                            IsAutoCloseConnection = true,//自動釋放數據務,如果存在事務,在事務結束后釋放
                                            InitKeyType = InitKeyType.Attribute //從實體特性中讀取主鍵自增列信息
                                        });
    
                    string sql = @"SELECT top 10000 Name FROM SYSOBJECTS WHERE TYPE='U' ORDER BY Name";  //查詢所有鏈接的所有數據庫名
                    var strList = db.SqlQueryable<databaseName>(sql).ToList();
                    result.info = Newtonsoft.Json.JsonConvert.SerializeObject(strList);
                    result.res = true;
                    result.msg = "查詢成功!";
                }
                catch (Exception ex)
                {
                    result.msg = ex.Message;
                }
    
                return Json(result, JsonRequestBehavior.AllowGet);
            }
    
            //生成實體
            public JsonResult GenerateEntity(string Link, string Name,string TableName,string type)
            {
    
                ResultInfo result = new ResultInfo();
                try
                {
                    //配置數據庫連接
                    SqlSugarClient db = new SqlSugarClient(
                                        new ConnectionConfig()
                                        {
                                            ConnectionString = "" + Link + ";database=" + Name + "",
                                            DbType = DbType.SqlServer,//設置數據庫類型
                                            IsAutoCloseConnection = true,//自動釋放數據務,如果存在事務,在事務結束后釋放
                                            InitKeyType = InitKeyType.Attribute //從實體特性中讀取主鍵自增列信息
                                        });
    
                    string path = "C:\\Demo\\2";
    
                    if (type == "0")
                    {
                        path = "C:\\Demo\\2";
                        db.DbFirst.Where(TableName).CreateClassFile(path);
                        result.info = System.IO.File.ReadAllText(@"" + path + "\\" + TableName + ".cs" + "", Encoding.UTF8);
                    }
                    else if (type == "1")
                    {
                        path = "C:\\Demo\\3";
                        db.DbFirst.IsCreateAttribute().CreateClassFile(path);
                        result.info = "";
                    }
    
                    
                    
                    result.res = true;
                    result.msg = "生成成功!";
                }
                catch (Exception ex)
                {
                    result.msg = ex.Message;
                }
    
                return Json(result, JsonRequestBehavior.AllowGet);
            }
    
            //生成全部表時查看
            public JsonResult GetGenerateEntity(string TableName)
            {
    
                ResultInfo result = new ResultInfo();
                try
                {
                    string path = "C:\\Demo\\3";
                    result.info = System.IO.File.ReadAllText(@"" + path + "\\" + TableName + ".cs" + "", Encoding.UTF8);
                    result.res = true;
                    result.msg = "查詢成功!";
                }
                catch (Exception ex)
                {
                    result.msg = ex.Message;
                    try
                    {
                        if (result.msg.Contains("未能找到文件"))
                        {
                           string path = "C:\\Demo\\2";
                            result.info = System.IO.File.ReadAllText(@"" + path + "\\" + TableName + ".cs" + "", Encoding.UTF8);
                            result.res = true;
                            result.msg = "查詢成功!";
                        }
                    }
                    catch (Exception)
                    {
                        result.msg = ex.Message;
                    }
                }
    
                return Json(result, JsonRequestBehavior.AllowGet);
            }
    
            //數據庫名
            public class databaseName
            {
                public string Name { get; set; }
            }
    
            //封裝返回信息數據
            public class ResultInfo
            {
                public ResultInfo()
                {
                    res = false;
                    startcode = 449;
                    info = "";
                }
                public bool res { get; set; }  //返回狀態(true or false)
                public string msg { get; set; }  //返回信息
                public int startcode { get; set; }  //返回http的狀態碼
                public string info { get; set; }  //返回的結果(res為true時返回結果集,res為false時返回錯誤提示)
            }
    
        }
    }

     

     

     

    這樣一套可視化代碼生成器就出來了,我們把他發布到IIS上面,然後設置為瀏覽器標籤(收藏),這樣就可以快捷使用了。

    我們運行一下看看,是不是感覺很方便呀!

     

     

     

    歡迎關注訂閱我的微信公眾平台【熊澤有話說】,更多好玩易學知識等你來取
    作者:熊澤-學習中的苦與樂
    公眾號:熊澤有話說
    出處: https://www.cnblogs.com/xiongze520/p/13181241.html
    創作不易,版權歸作者和博客園共有,轉載或者部分轉載、摘錄,請在文章明顯位置註明作者和原文鏈接。  

     

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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