小米 Redmi 系列首款支援雙 5G 的 Redmi Note 9T 5G 以及 6000mAh 大電量 Redmi 9T 在台推出(同場加映:小米10T Pro 將於1月29日開放預購)_網頁設計公司

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

今年初,小米才剛在海外舉行發表會推出中階的 Redmi Note 9T 5G 和 6000mAh 大電量的 Redmi 9T 入門手機,很快也在今(25)日稍早宣布將在台灣開賣囉! Redmi Not 9T 5G 搭載聯發科天璣 800U 5G 處理器,也是 Redmi 系列首款支援 5G+5G 雙卡雙待的 5G 手機,價位只要 NT$6,699 起。至於追求更大電量、更持久的用戶,這次也迎來入門的 Redmi 9T 作為重度使用者的新選擇。

小米 Redmi 系列首款支援雙 5G 的 Redmi Note 9T 5G 以及 6000mAh 大電量 Redmi 9T 在台推出

Redmi Note 9T 5G

Redmi Note 9T 5G 搭載八核心聯發科天璣 800U 處理器,支援 5G+5G 雙卡雙待,也是 Redmi Note 系列首款支援雙 5G 的機型。另外, Redmi Note 9T 5G 配備最高 4GB RAM 和 128GB ROM ,內建 5000mAh 大電量電池並支持 18W 快速充電,充電器和日前開賣的 POCO M3 一樣附贈 22.5W 快速充電器。
相機部份, Redmi Note 9T 5G 配備 4800 萬像素 AI 三鏡頭主相機,採用 4800 萬像素廣角鏡頭、 200 萬像素微距鏡頭和 200 萬像素景深鏡頭的組合,前置相機則配備 1300 萬像素自拍鏡頭。
螢幕方面, Redmi Note 9T 採用 6.53 吋 FHD+ 解析度打孔全螢幕,螢幕除通過德國萊茵 TÜV Rheinland 低藍光認證,表面覆蓋康寧第五代大猩猩保護。

其他應用方面, Redmi Note 9T 支援 NFC 、紅外線、立體聲雙揚聲器且保留 3.5mm 耳機插孔。在生物辨識方面,除了支援 AI 人臉解鎖也配備側面指紋感應器。
Redmi Note 9T 提供晨曦紫、日暮黑兩色選擇, 4GB+64GB 版本建議售價 NT$6,699 ,於2月1日起於中華電信、遠傳電信、亞太電信、小米專賣店、PChome24h購物小米旗艦館、燦坤3C、senaonline神腦生活、法雅客網路商店與friDay購物陸續上市;4GB+128GB 版本建議售價 NT$$7,499 ,於2月1日起於中華電信、遠傳電信、台灣之星、小米專賣店、PChome24h購物小米旗艦館、燦坤3C、senaonline神腦生活、法雅客網路商店與friDay購物陸續上市。

Redmi 9T

小米也針對入門手機市場帶來 Redmi 9T ,而它其實有許多規格和最近剛在台開賣掀起搶購熱潮的 POCO M3 十分相近,採用相同的處理器、螢幕尺寸、電池容量、機身重量也相同,最大差異除了品牌、外觀設計,還有主相機的鏡頭配置。
Redmi 9T 搭載 Qualcomm Snapdragon 662 處理器、6.53 吋 FHD+ 解析度水滴螢幕(擁有 L1 Widevine、德國萊茵 TÜV Rheinland低藍光認證)、6000mAh 大電量電池,不過 Redmi 9T 提供最高 6GB RAM 搭配 128GB ROM 的選擇。
相機部份, Redmi 9T 配備 4800 萬像素 AI 四鏡頭主相機,採用 4800 萬像素主鏡頭、 200 萬像素微距鏡頭、 200 萬像素景深鏡頭並加入 800 萬像素 120° 超廣角鏡頭搭配,前相機則一樣為 800 萬像素自拍鏡頭。

Redmi 9T 在電量部分採用 6000mAh 高充電循環電池,可待機 28 天、連續播放音樂 195 小時、通話時間 56 小時、影片播放 17 小時,並支援 18W 快速充電(附贈 22.5W 快速充電器)。在快速和高效率的處理下,機身也能保持低耗電量且不易過熱,且高循環週期電池更持久耐用。
其他方面, Redmi 9T 搭載立體聲雙揚聲器也同樣保留 3.5mm 耳機插孔,也支援 AI 人臉解鎖和側面指紋感應器。

Redmi 9T(4GB+64GB)提供碳纖灰、暮光藍、日出橙和海洋綠四種顏色,售價為 NT$4,699元;Redmi 9T(6GB+128GB)提供暮光藍、日出橙和海洋綠三種顏色,售價為 NT$5,499 ,將於二月中旬於小米專賣店、PChome24h購物小米旗艦館、燦坤3C及法雅客網路商店陸續開賣。

小米10T Pro

2020 年底,小米台灣預告在今年 1 月底會在台灣開放預購的小米 10T Pro ,現在也確定了預購和發售資訊。小米 10T Pro 搭載 Qualcomm Snapdragon 865 旗艦級 5G 處理器、配備 6.67 吋螢幕、8GB RAM、 256GB ROM ,相機配備 1 億像素 AI 三鏡頭,更擁有 5000mAh 超大電量及 33W 快速充電,更多關於小米10T 和小米10T Pro 的規格差異可參考我們之前的報導(點我前往)。

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

小米10T Pro 提供宇宙黑與極光藍共兩種顏色,售價為 NT$15,999,更享 2 年超長原廠保固。將於 1 月 29 日在小米專賣店、PChome24h購物小米旗艦館、燦坤3C、senaonline神腦生活與friDay購物開放預購,並於 2 月 5 日起正式於中華電信、遠傳電信、小米專賣店、PChome24h購物小米旗艦館、燦坤3C、senaonline神腦生活、法雅客網路商店與friDay購物陸續開賣(各通路開賣日期謹依實際到貨訊息為準,請洽詢各通路)。

圖片/消息來源:小米台灣

延伸閱讀:
POCO M3 開箱、評測|6000mAh 大電量超乎想像,極致性價比王者重返台灣市場

小米摺疊螢幕手機工程機曝光!再次於地鐵車廂被捕獲,只是螢幕摺痕有點顯眼

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

Web 三維組態的仿真運用案例:民航飛機的數據監控_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

前言 在飛機航行的過程中,客艙里座位上方的熒屏上,除了播放電視劇和廣告之外,還會時不時的切換到一個飛機航行的監控系統。這個監控系統的主要目的是,讓乘客可以了解到飛機在航行過程中的整體狀況、距離目的地的航線進程以及一些有可能出現的突發事件。飛機航行的監控系統在一定程度上,可以使乘客在旅途過程中得到準確的信息源,最重要的是可以使乘客在旅途中得到安心。在保障乘客獲取到實際信息源的同時,飛機航行的監控系統也能將採集到的數據及時的反饋給航務人員,而且將數據記錄保存下來,在飛機過站或航后供航務和機務人員使用和監測維護。介於 2D 組態和 3D 組態上,
Hightopo(以下簡稱 HT )的 
HT for Web 產品上的有着豐富的組態化可供選擇,本文將介紹如何運用 HT 豐富的 
2/3D 組態搭建出一個飛機航行的監控系統的解決方案。 預覽地址: flight-monitor  
系統分析 飛機航行監控是一種測量、跟蹤飛機性能變化趨勢的簡單而實用方法。在日常航班飛行中,用機載儀錶記錄飛機巡航時的有關參數,並將其與原有性能進行比較。監控的主要參數有:飛行單位時間消耗的燃油量、飛機的巡航飛行速度和消耗單位燃油量飛機飛過的水平距離。它們分別表示發動機、飛機及飛機發動機的綜合性能。它們分別表示發動機、飛機及飛機發動機的綜合性能。性能監控的結果,既可為飛機維修提供可能的故障信息,又為制定飛行計劃提供可靠的性能依據。 目前,先進的噴氣運輸機都已安裝飛行狀態監控系統,它記錄下有關參數值,並存儲在數據管理裝置中,這些數據可以發送到或在飛機着陸後送到地面站,用專用計算機程序進行計算和修正,使性能監控數據採集、發送、計箅分析工作自動化,節省了人力,提高了精確度。 1、
飛行數據記錄器:自從飛行數據記錄器 FDR(Flight Data Recorder)“俗稱黑匣子”誕生以來,其提供的詳實準確的飛行數據為事故調查、機務維護保障、安全運營監控提供了重要依據,成為航空安全運營的重要管理手段。 2、
ACMS(Aireraft Condition Monitoring System)的系統概述:ACMS 系統是飛機上安裝的先進機載數據採集和處理系統,它能以實時方式收集數據,對發動機狀態和飛行性能進行監控以及進行特殊的工程調查。由飛機通信尋址與報告系統(ACARS)通過地空數據鏈發送到地面接收站,最後傳送到航空公司的終端,也可通過快速存取記錄器(QAR)將數據記錄保存下來,在飛機過站或航后供航務和機務人員使用。 而 HT 實現的業務功能可以通過ACMS 系統採集到的數據,對接到显示終端去展示,通過實時監測的數據準確地显示出來,起到對飛機狀態的監控功能並即時反饋信息數據的變化。  
實現過程
雲中穿行效果 為了達到飛機雲中穿行的效果,最開始我遇到的問題是飛機飛行的層次感,也就通常所說的透視效果,這裏我採用的是雲通道和雲背景以不同的速度流動,製造一種飛行的透視效果。   雲我採用的是貼圖的方式呈現的,但是僅僅是貼圖會遮擋天空和飛機,非常影響飛機飛行的觀感,所以我開啟了相應圖元的 
transparent 和 
opacity,雲背景和雲通道設置不同的透明度,不僅增加了層次感,還會讓人產生雲朵從眼前飄過的錯覺。   雲通道採用的是 
ht.Polyline 類型,通道縮放拉大了 Y 軸的比例,使雲通道有更大的縱向空間,設置 
reverse.flip 背拷貝使雲通道內部也显示出貼圖,彷彿讓飛機置身於雲海中穿梭;雲背景採用 
ht.Node 類型,只設置一個面显示充當雲背景。   整體的雲流動效果採用 
offset 偏移實現,改變相應圖元或相應圖元面的貼圖偏移量來達到飛機雲中穿行的效果, 代碼如下:

var i = 1, p = 0; setInterval(() => { i -= 0.1; p += 0.005; clouds.s('shape3d.uv.offset', [i, 0]); cloudBackground.s('all.uv.offset', [p, 0]); }, 100);

 

升降顛簸效果 雖然達到了飛機雲中穿行的效果,但是如果飛機只是直直的飛行,那也會降低飛行的實感,相信坐過飛機的朋友肯定都遇到過因氣流產生的顛簸,也經常感受到飛機飛行途中的爬升和下降,這其實是因為飛機的航線並不是一直固定在一個高度上,有時會爬升有時會下降,所以我就用 
ht-animation.js HT 動畫擴展插件去實現飛機顛簸效果,代碼如下:

dm.enableAnimation(20);
plane.setAnimation({
    back1: {
        from: 0, to: 160, easing: 'Cubic.easeInOut', duration: 8000, next: "up1", onUpdate: function (value) { value = parseInt(value); var p3 = this.p3(); this.p3(value, p3[1], p3[2]); } }, //...省略相似 start: ["back1"] });

 

球扇形視角限制 飛行效果完善之後,這時我就遇到了一個比較棘手的問題,因為實際上雖然看着飛機是在雲海中穿梭,但是僅僅是在通道中飛行,背景其實也只是平面貼圖,所以當視角到達某種程度的時候就會有強烈的違和感和不真實感,就需要一個視角限制,使視角的調整剛剛好在一個範圍內。   視角限制的話一般是限制 g3d 的 
eye 和 
center,不太了解的朋友可以去看 hightopo 官網中的 3d 手冊,裏面有詳細的說明,這裏我就不再贅述了;因為視角範圍的關係,所以我決定固定 
center 的位置,代碼如下:

g3d.addPropertyChangeListener(e => {
    // 固定中心點 if (e.property === 'center') { e.newValue[0] = center[0]; e.newValue[1] = center[1]; e.newValue[2] = center[2]; } }

然後再把 eye 限制在某一個範圍內就大功告成了,然而這裏卻並不是那麼簡單,最開始我把 eye 限制在一個立方體的空間內,但交互效果很不理想,考慮到 g3d 默認交互中,鼠標拖拽平移視角變換時,實際上 eye 是在一個以 center 為球心的球面上運動的,所以我決定從這個球中挖出來一塊作為 eye 的限制空間,也就是球扇形,不太理解的朋友可以參考這個圖:

 

球扇形視角限制,一共需要三個參數,分別是中心參考軸、中心軸和外邊所成角度、所在球限制半徑,其中中心參考軸可根據初始 eye 和 center 的連接延長線確定,所在球限制半徑又分最大限制和最小限制,代碼如下:

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

function limitEye(g3d, eye, center, options) { var limitMaxL = options.limitMaxL, limitMinL = options.limitMinL, limitA = options.limitA; g3d.addPropertyChangeListener(e => { // 固定中心點 if (e.property === 'center') { e.newValue[0] = center[0]; e.newValue[1] = center[1]; e.newValue[2] = center[2]; } // 限制視角 if (e.property === 'eye') { var newEyeV = new ht.Math.Vector3(e.newValue), centerV = new ht.Math.Vector3(center), refEyeV = new ht.Math.Vector3(eye), refVector = refEyeV.clone().sub(centerV), newVector = newEyeV.clone().sub(centerV); if (centerV.distanceTo(newEyeV) > limitMaxL) { newVector.setLength(limitMaxL); e.newValue[0] = newVector.x; e.newValue[1] = newVector.y; e.newValue[2] = newVector.z; } if (centerV.distanceTo(newEyeV) < limitMinL) { newVector.setLength(limitMinL); e.newValue[0] = newVector.x; e.newValue[1] = newVector.y; e.newValue[2] = newVector.z; } if (newVector.angleTo(refVector) > limitA) { var oldLength = newVector.length(), oldAngle = newVector.angleTo(refVector), refLength = oldLength * Math.cos(oldAngle), vertVector, realVector, realEye; refVector.setLength(refLength); newEyeV = newVector.clone().add(centerV); refEyeV = refVector.clone().add(centerV); vertVector = newEyeV.clone().sub(refEyeV); vertLength = refLength * Math.tan(limitA); vertVector.setLength(vertLength); realVector = vertVector.clone().add(refEyeV).sub(centerV); realVector.setLength(oldLength); realEye = realVector.clone().add(centerV); // 防止移動角度大於 180 度,視角反轉 if (oldAngle > Math.PI / 2) { realEye.negate(); } e.newValue[0] = realEye.x; e.newValue[1] = realEye.y; e.newValue[2] = realEye.z; } } }) }

 

飛機監控系統 當然作為監控系統,自然要有監控了,增加右下角的小地圖,並提供三種模式,分別是聚焦飛機,聚焦飛行軌跡和聚焦地圖,並根據飛機的飛行方向控制飛行軌跡的流動效果,其中聚焦飛機會跟隨飛機移動進行 
fitData,使飛機一直處於小地圖的中心,代碼如下:

var fitFlowP = function (e) { if (e.property === 'position' && e.data === plane) { mapGV.fitData(plane, false); } }; buttonP.s({ 'interactive': true, 'onClick': function (event, data, view, point, width, height) { map.a('fitDataTag', 'plane2D'); mapGV.fitData(plane, false); mapDM.md(fitFlowP); } }); buttonL.s({ 'interactive': true, 'onClick': function (event, data, view, point, width, height) { mapDM.umd(fitFlowP); map.a('fitDataTag', 'flyLine'); mapGV.fitData(flyLine, false); } }); // ...省略

增加鼠標移到飛機相應位置進行名稱的提示、雙擊后显示飛機相應位置的信息面板並將視角聚焦到面板上、點擊飛機任意地方切換回飛機飛行模式等效果。 左側增加監控面板替代上面提到的雙擊相應位置這步操作直接聚焦到相應位置的信息面板上,這裏按鈕開啟了交互並添加了相應的交互邏輯,代碼如下:

button_JC.s({
    'interactive': true, 'onClick': function (event, data, view, point, width, height) { event.preventDefault(); let g3d = G.g3d, g3dDM = G.g3d.dm(); g3d.fireInteractorEvent({ kind: 'doubleClickData', data: g3dDM.getDataByTag(data.getTag()) }) } }); //...省略

 

天空渲染效果 既然是監控系統肯定是 24 小時無差別的監控,這就涉及到一個問題,我總不可能半夜的時候飛機也從瓦藍瓦藍的天空上飛過,這就很欠缺真實性了,所以要有一個天空從亮到暗再從暗到亮的過程,這個過程我暫定到 06:00-06:30 和19:00-19:30 這兩個時間段。   天空採用的是 
shape3d : ‘sphere’ 球形,包裹整個場景,然後使用 
reverse.flip 背拷貝 和 
blend 染色,之後天空就可以渲染成我想要的顏色,如果按照時間改變天空明暗只要改變染色值就可以了。   但是由於白天和晚上光照情況的不同,雲反射光的強度也不同,就導致了白天和晚上雲的差異,所以也要調整雲道和雲背景的貼圖的 
opacity 透明度,晚間更為透明度,代碼如下:

if ((hour > 6 && hour < 19) || (hour == 6 && minutes >= 30)) { timePane && timePane.a({ 'morning.visible': false, 'day.visible': true, 'dusk.visible': false, 'night.visible': false, 'day.opacity': 1 }) skyBox.s({ "shape3d.blend": 'rgb(127, 200, 240)', }) cloudBackground.s({ "back.opacity": 0.7, }) clouds.s({ "shape3d.opacity": 0.7, }) } else if ((hour < 6 || hour > 19) || (hour == 19 && minutes >= 30)) { //...省略 } else if (hour == 6 && minutes < 15 ) { //...省略 } else if (hour == 6 && minutes >= 15 && minutes < 30) { //...省略 } else if (hour == 19 && minutes < 15) { //...省略 } else if (hour == 19 && minutes >= 15 && minutes < 30) { //...省略 }

 

這裏我還增加了對右上角時間面板時間狀態圖標的支持,並增加了圖標切換時的漸隱漸顯效果,同時給時間面板狀態圖標位置增加了點擊切換到下一時間狀態的功能。   為了演示效果我增加了時間倍速按鈕,下圖是 500 倍時間流速下的變化情況:  
總結 身處大數據時代的我們,在網絡科技發展越來越快的環境下,迎來了 5G 的新征程和工業4.0的新變革,在信息數據採集傳輸和數據可視化系統的融合下,可以發掘出許多行業管控可視化系統的解決方案。而 HT 長久以來一直致力於多樣化行業系統可視化的搭建,涉獵的技術行業所累積的經驗,通過自主創新研發的技術產品,已經打造出許多行業上具有代表性意義的可視化管控系統,例如同樣有關於飛機的監控可視化系統: 無人機 3D 可視化系統 2019 我們也更新了數百個工業互聯網 2D/3D 可視化案例集,在這裏你能發現許多新奇的實例,也能發掘出不一樣的工業互聯網: https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA 同時,你也可以查看更多案例及效果: https://www.hightopo.com/demos/index.html 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

容器技術之Dockerfile(二)_貨運

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

  前文我們聊到了什麼是dockerfile,它的主要作用以及dockerfile的一些基本指令的使用方法,回顧請參考https://www.cnblogs.com/qiuhom-1874/p/13019411.html;今天我們在來聊一聊dockerfile餘下指令的用法和作用;

  1、RUN:該指令用於docker build 過程中運行的程序,可以是任何命令;語法格式RUN <command> 或RUN [“<executable>”, “<param1>”, “<param2>”];第一種格式中,<command>通常是一個shell命令,且以“/bin/sh -c”來運行它,這意味着此進程在容器中的PID不為1,不能接收Unix信號,因此,當使用docker stop <container>命令停止容器時,此進程接收不到SIGTERM信號; 第二種語法格式中的參數是一個JSON格式的數組,其中<executable>為要運行的命令,後面的<paramN>為傳遞給命令的選項或參數;然而,此種格式指定的命令不會以“/bin/sh -c”來發起,因此常見的shell操作如變量替換以及通配符(?,*等)替換將不會進行;不過,如果要運行的命令依賴於此shell特性的話,可以將其替換為 RUN [“/bin/sh”, “-c”, “<executable>”, “<param1>”]這樣的格式;注意:json數組中,要使用雙引號;

  示例:

[root@node1 test]# cat Dockerfile 
FROM centos:7 

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}

EXPOSE 80/tcp 443/tcp

RUN mkdir -p /aaa/bbb/t{1..4}



[root@node1 test]# 

  提示:以上Dockerfile中,用RUN指令運行了mkdir命令,這種運行命令的方式在就可以利用shell的特性,如上大括號展開功能;

  驗證:build 該dockerfile后,運行該鏡像為容器,看看容器內部是否創建了/aaa/bbb/t1 t2 t3 t4?

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
busybox             latest              1c35c4412082        16 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker build . -t myimg:v1
Sending build context to Docker daemon   1.05MB
Step 1/9 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/9 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Running in 64c792ce6750
Removing intermediate container 64c792ce6750
 ---> 604899ef29f9
Step 3/9 : LABEL version="1.0"
 ---> Running in 6a3f9b4a9058
Removing intermediate container 6a3f9b4a9058
 ---> d9edea71fa22
Step 4/9 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Running in b191ab5e19f9
Removing intermediate container b191ab5e19f9
 ---> ee027bbdc04b
Step 5/9 : ARG web_home
 ---> Running in a4c86febf616
Removing intermediate container a4c86febf616
 ---> 5b25bb7421dd
Step 6/9 : COPY html ${web_home:-"/data/htdoc/"}
 ---> 7c7a667149fa
Step 7/9 : VOLUME ${web_home:-"/data/htdoc/"}
 ---> Running in f9ec02d8f736
Removing intermediate container f9ec02d8f736
 ---> 86c7226f6b21
Step 8/9 : EXPOSE 80/tcp 443/tcp
 ---> Running in ad82d389ac25
Removing intermediate container ad82d389ac25
 ---> 28dadea40aff
Step 9/9 : RUN mkdir -p /aaa/bbb/t{1..4}
 ---> Running in 1013a212d3f2
Removing intermediate container 1013a212d3f2
 ---> 7f109a34a4a5
Successfully built 7f109a34a4a5
Successfully tagged myimg:v1
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1                  7f109a34a4a5        4 seconds ago       203MB
busybox             latest              1c35c4412082        16 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name test --rm -it myimg:v1 /bin/bash
[root@fc89ca934ed5 /]# ls /
aaa                bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
anaconda-post.log  data  etc  lib   media  opt  root  sbin  sys  usr
[root@fc89ca934ed5 /]# ls /aaa/
bbb
[root@fc89ca934ed5 /]# ls /aaa/bbb/
t1  t2  t3  t4
[root@fc89ca934ed5 /]# exit
exit
[root@node1 test]# 

  提示:底層基礎鏡像的shell如果不支持大括號展開,那麼我們基於這種鏡像做出來的鏡像運行以上命令也就不支持shell的大括號展開功能;

  示例:

[root@node1 test]# cat Dockerfile
FROM centos:7

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}

EXPOSE 80/tcp 443/tcp

RUN mkdir -p /aaa/bbb/t{1..4}

RUN ["mkdir","-p","/ccc/ddd/f{1..4}"]

[root@node1 test]# 

  提示:以json數組格式的方式去運行命令,它默認是不支持shell的任何特性,這意味着運行該命令時,不是基於shell子進程的方式在執行命令,通常是內核直接執行了;所以上面的命令它會把大括號處理成字符,而不會展開;

  驗證:build成鏡像運行成容器,看看是否把大括號處理成字符了?

[root@node1 test]# docker build . -t myimg:v1.1
Sending build context to Docker daemon   1.05MB
Step 1/10 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/10 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> 604899ef29f9
Step 3/10 : LABEL version="1.0"
 ---> Using cache
 ---> d9edea71fa22
Step 4/10 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> ee027bbdc04b
Step 5/10 : ARG web_home
 ---> Using cache
 ---> 5b25bb7421dd
Step 6/10 : COPY html ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 7c7a667149fa
Step 7/10 : VOLUME ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 86c7226f6b21
Step 8/10 : EXPOSE 80/tcp 443/tcp
 ---> Using cache
 ---> 28dadea40aff
Step 9/10 : RUN mkdir -p /aaa/bbb/t{1..4}
 ---> Using cache
 ---> 7f109a34a4a5
Step 10/10 : RUN ["mkdir","-p","/ccc/ddd/f{1..4}"]
 ---> Running in 9da1e6bab59f
Removing intermediate container 9da1e6bab59f
 ---> ae463ec8cbd9
Successfully built ae463ec8cbd9
Successfully tagged myimg:v1.1
[root@node1 test]# docker run --name test --rm -it myimg:v1.1 /bin/bash
[root@02ec6e404100 /]# ls /
aaa                bin  data  etc   lib    media  opt   root  sbin  sys  usr
anaconda-post.log  ccc  dev   home  lib64  mnt    proc  run   srv   tmp  var
[root@02ec6e404100 /]# ls /ccc/ddd/
f{1..4}
[root@02ec6e404100 /]# 

  提示:可以看到在/ccc/ddd/目錄下並沒有把大括號展開,而是直接把它當成了字符處理了;如果我們想要用json數組這種方式運行命令,又想讓使用shell特性,我們可以使用”/bin/sh -c”來明確聲明後面的命令用shell子進程的方式運行;如下所示

[root@node1 test]# cat Dockerfile
FROM centos:7

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}

EXPOSE 80/tcp 443/tcp

RUN mkdir -p /aaa/bbb/t{1..4}

RUN ["/bin/bash","-c","mkdir -p /ccc/ddd/f{1..4}"]

[root@node1 test]# 

  提示:以上運行命令的方式就明確聲明使用shell子進程的方式運行命令;這裏需要注意一點的是,如果使用json數組的方式運行命令,後面真正執行的命令要一個整體當作參數傳給”/bin/bash”

  驗證:看看是否會把大括號展開?

[root@node1 test]# docker build . -t myimg:v1.2
Sending build context to Docker daemon   1.05MB
Step 1/10 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/10 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> 604899ef29f9
Step 3/10 : LABEL version="1.0"
 ---> Using cache
 ---> d9edea71fa22
Step 4/10 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> ee027bbdc04b
Step 5/10 : ARG web_home
 ---> Using cache
 ---> 5b25bb7421dd
Step 6/10 : COPY html ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 7c7a667149fa
Step 7/10 : VOLUME ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 86c7226f6b21
Step 8/10 : EXPOSE 80/tcp 443/tcp
 ---> Using cache
 ---> 28dadea40aff
Step 9/10 : RUN mkdir -p /aaa/bbb/t{1..4}
 ---> Using cache
 ---> 7f109a34a4a5
Step 10/10 : RUN ["/bin/bash","-c","mkdir -p /ccc/ddd/f{1..4}"]
 ---> Running in a5785a139e1f
Removing intermediate container a5785a139e1f
 ---> 30a5f5594104
Successfully built 30a5f5594104
Successfully tagged myimg:v1.2
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1.2                30a5f5594104        5 seconds ago       203MB
myimg               v1.1                ae463ec8cbd9        9 minutes ago       203MB
myimg               v1                  7f109a34a4a5        21 minutes ago      203MB
busybox             latest              1c35c4412082        16 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name test --rm -it myimg:v1.2 /bin/bash
[root@549f875aa4de /]# ls /
aaa                bin  data  etc   lib    media  opt   root  sbin  sys  usr
anaconda-post.log  ccc  dev   home  lib64  mnt    proc  run   srv   tmp  var
[root@549f875aa4de /]# ls /ccc/ddd/
f1  f2  f3  f4
[root@549f875aa4de /]# 

  提示:可以看到用”/bin/bash -c” 是可以明確聲明後面的命令用shell子進程的方式運行,這樣一來就可以在後面的命令使用shell特性的語法;

  2、CMD:該指令類似於RUN指令,CMD指令也可用於運行任何命令或應用程序,不過,二者的運行時間點不同; RUN指令運行於映像文件構建過程中,而CMD指令運行於基於Dockerfile構建出的新映像文件啟動一個容器時; CMD指令的首要目的在於為啟動的容器指定默認要運行的程序,且其運行結束后,容器也將終止;不過,CMD指定的命令其可以被docker run的命令行選項所覆蓋;在Dockerfile中可以存在多個CMD指令,但僅最後一個會生效;語法格式 CMD <command> 或 CMD [“<executable>”, “<param1>”, “<param2>”] 或 CMD [“<param1>”,”<param2>”];前兩種語法格式的意義同RUN,第三種則用於為ENTRYPOINT指令提供默認參數;

  示例:

[root@node1 test]# cat Dockerfile
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}

EXPOSE 80/tcp 443/tcp

CMD httpd -f -h /data/htdoc/
[root@node1 test]# 

  提示:docker容器內部運行的程序必須運行為前台;CMD是指定容器運行時要運行的命令;通常該命令或程序是以前台方式運行;如果不是前台運行,我們的容器就會存在一啟動就退出的情況;以上命令就表示前台運行httpd程序 並指定httpd 的工作目錄為${web_home}變量所指定的目錄;

  驗證:build后看看啟動為容器是否提供80訪問服務?

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1.2                30a5f5594104        23 minutes ago      203MB
myimg               v1.1                ae463ec8cbd9        32 minutes ago      203MB
myimg               v1                  7f109a34a4a5        44 minutes ago      203MB
busybox             latest              1c35c4412082        16 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker build . -t myimg:v1.3
Sending build context to Docker daemon   1.05MB
Step 1/9 : FROM busybox:latest
 ---> 1c35c4412082
Step 2/9 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Running in deb5e54eef87
Removing intermediate container deb5e54eef87
 ---> baf170e0c586
Step 3/9 : LABEL version="1.0"
 ---> Running in 433669185e0d
Removing intermediate container 433669185e0d
 ---> d96fb4ae3d58
Step 4/9 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Running in b5da74e27c69
Removing intermediate container b5da74e27c69
 ---> 62372d19daf3
Step 5/9 : ARG web_home
 ---> Running in 3f65a67bb15a
Removing intermediate container 3f65a67bb15a
 ---> 1ce797c7cde0
Step 6/9 : COPY html ${web_home:-"/data/htdoc/"}
 ---> 15848dea21b9
Step 7/9 : VOLUME ${web_home:-"/data/htdoc/"}
 ---> Running in 868f4c10e00f
Removing intermediate container 868f4c10e00f
 ---> f3ec40d1cb5e
Step 8/9 : EXPOSE 80/tcp 443/tcp
 ---> Running in 7f72c2612e92
Removing intermediate container 7f72c2612e92
 ---> 5ccfc6d604cc
Step 9/9 : CMD httpd -f -h /data/htdoc/
 ---> Running in 95a4fd578821
Removing intermediate container 95a4fd578821
 ---> 2e296b4f4500
Successfully built 2e296b4f4500
Successfully tagged myimg:v1.3
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1.3                2e296b4f4500        3 seconds ago       1.22MB
myimg               v1.2                30a5f5594104        23 minutes ago      203MB
myimg               v1.1                ae463ec8cbd9        33 minutes ago      203MB
myimg               v1                  7f109a34a4a5        44 minutes ago      203MB
busybox             latest              1c35c4412082        16 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name b1 -d myimg:v1.3
c3514f782cffd8140aa7c612293029f4d0302e8d697887dfc2696eea44a31700
[root@node1 test]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
c3514f782cff        myimg:v1.3          "/bin/sh -c 'httpd -…"   4 seconds ago       Up 3 seconds        80/tcp, 443/tcp     b1
[root@node1 test]# curl http://172.17.0.2/test1.html
this is test1 html
[root@node1 test]# 

  提示:可以看到httpd是可以正常提供服務的;從上面的信息我們也可以了解到運行容器后,它默認是把我們寫的命令當作shell子命令的方式在運行;

  示例:以json數組方式運行命令

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}

EXPOSE 80/tcp 443/tcp

CMD ["httpd","-f","-h","/data/htdoc/"]

[root@node1 test]# 

  提示:用json數組格式運行命令,需要把後面的每個選項當作參數傳給httpd;

  驗證:運行容器看看容器是否退出,是否能夠正常提供httpd服務?

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1.3                2e296b4f4500        24 minutes ago      1.22MB
myimg               v1.2                30a5f5594104        47 minutes ago      203MB
myimg               v1.1                ae463ec8cbd9        57 minutes ago      203MB
myimg               v1                  7f109a34a4a5        About an hour ago   203MB
busybox             latest              1c35c4412082        17 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker build . -t myimg:v1.4
Sending build context to Docker daemon   1.05MB
Step 1/9 : FROM busybox:latest
 ---> 1c35c4412082
Step 2/9 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> baf170e0c586
Step 3/9 : LABEL version="1.0"
 ---> Using cache
 ---> d96fb4ae3d58
Step 4/9 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 62372d19daf3
Step 5/9 : ARG web_home
 ---> Using cache
 ---> 1ce797c7cde0
Step 6/9 : COPY html ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 15848dea21b9
Step 7/9 : VOLUME ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> f3ec40d1cb5e
Step 8/9 : EXPOSE 80/tcp 443/tcp
 ---> Using cache
 ---> 5ccfc6d604cc
Step 9/9 : CMD ["httpd","-f","-h","/data/htdoc/"]
 ---> Running in 5bebdabfe2b7
Removing intermediate container 5bebdabfe2b7
 ---> 58e3b4c40ae7
Successfully built 58e3b4c40ae7
Successfully tagged myimg:v1.4
[root@node1 test]# docker run --name b1 -d myimg:v1.4
a32a05033a6dcb735363906bfcd2b84cfb290ca1b60c17d3ac2f81cdeceee705
[root@node1 test]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
a32a05033a6d        myimg:v1.4          "httpd -f -h /data/h…"   6 seconds ago       Up 5 seconds        80/tcp, 443/tcp     b1
[root@node1 test]# curl http://172.17.0.2/test1.html
this is test1 html
[root@node1 test]# 

  提示:可以看到httpd服務可以正常提供訪問,說明我們用json數組方式運行命令是正確的;總結一點,用CMD或RUN指令運行命令時,如果直接在CMD或RUN指令後面接命令,這種方式通常會被解釋為啟動一個shell子進程運行命令,RUN指令表現形式就是後面的命令可以使用shell特性的語法格式的命令,比如大括號展開等等;而CMD指令表現形式就是啟動為容器后,它默認會把我們指定運行的命令當作參數傳給“/bin/sh”來運行;CMD或RUN指令加中括號的形式就表示使用json數組格式方式運行命令;這種方式運行命令在CMD中表現形式是我們運行的命令的選項都要當作參數傳給該命令;RUN指令表現形式是不能使用shell特性的命令;如果非要使用shell特性的命令格式,我們需要把我們的命令當作參數傳給“/bin/sh”,當然前提是我們的基礎鏡像shell支持shell特性的語法;

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

  3、ENTRYPOINT:該指令類似CMD指令的功能,用於為容器指定默認運行程序,從而使得容器像是一個單獨的可執行程序;與CMD不同的是,由ENTRYPOINT啟動的程序不會被docker run命令行指定的參數所覆蓋,而且,這些命令行參數會被當作參數傳遞給ENTRYPOINT指定的程序(不過,docker run命令的–entrypoint選項的參數可覆蓋ENTRYPOINT指令指定的程序);語法格式 ENTRYPOINT <command>或 ENTRYPOINT [“<executable>”, “<param1>”, “<param2>”];docker run命令傳入的命令參數會覆蓋CMD指令的內容並且附加到ENTRYPOINT命令最後做為其參數使用;Dockerfile文件中也可以存在多個ENTRYPOINT指令,但僅有最後一個會生效;

  示例:

[root@node1 test]# cat Dockerfile
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}

EXPOSE 80/tcp 443/tcp

ENTRYPOINT httpd -f -h /data/htdoc/
[root@node1 test]# 

  提示:以上dockerfile中用ENTRYPOINT 來指定容器默認運行程序,它和CMD不同的是,CMD指定運行的命令,我們可以使用docker run 命令加要運行的的命令替代容器里默認運行的命令,而ENTRYPOINT指定的命令我們是不可隨便替換的,如果要替換必須要使用–entrypoint選項來指定;

  驗證:build成鏡像,我們啟動為容器直接運行/bin/sh 看看是否可行?

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1.4                58e3b4c40ae7        23 minutes ago      1.22MB
myimg               v1.3                2e296b4f4500        47 minutes ago      1.22MB
myimg               v1.2                30a5f5594104        About an hour ago   203MB
myimg               v1.1                ae463ec8cbd9        About an hour ago   203MB
myimg               v1                  7f109a34a4a5        2 hours ago         203MB
busybox             latest              1c35c4412082        17 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker build . -t myimg:v1.5
Sending build context to Docker daemon   1.05MB
Step 1/9 : FROM busybox:latest
 ---> 1c35c4412082
Step 2/9 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> baf170e0c586
Step 3/9 : LABEL version="1.0"
 ---> Using cache
 ---> d96fb4ae3d58
Step 4/9 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 62372d19daf3
Step 5/9 : ARG web_home
 ---> Using cache
 ---> 1ce797c7cde0
Step 6/9 : COPY html ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> 15848dea21b9
Step 7/9 : VOLUME ${web_home:-"/data/htdoc/"}
 ---> Using cache
 ---> f3ec40d1cb5e
Step 8/9 : EXPOSE 80/tcp 443/tcp
 ---> Using cache
 ---> 5ccfc6d604cc
Step 9/9 : ENTRYPOINT httpd -f -h /data/htdoc/
 ---> Running in de274d68686c
Removing intermediate container de274d68686c
 ---> 5825c2ec655f
Successfully built 5825c2ec655f
Successfully tagged myimg:v1.5
[root@node1 test]# docker run --name b1 --rm -it myimg:v1.5 /bin/sh

  提示:運行以上命令后,不會給我們一個shell終端,也不報錯;但是我們直接訪問httpd服務是可以正常訪問的;這意味我們用docker run 命令是不能替換我們用entrypoint指定指定的命令的;

  測試:用–entrypoint 選項來看看是否能夠覆蓋ENTRYPOINT指定所指定的命令程序?

[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg               v1.5                5825c2ec655f        12 minutes ago      1.22MB
myimg               v1.4                58e3b4c40ae7        35 minutes ago      1.22MB
myimg               v1.3                2e296b4f4500        About an hour ago   1.22MB
myimg               v1.2                30a5f5594104        About an hour ago   203MB
myimg               v1.1                ae463ec8cbd9        2 hours ago         203MB
myimg               v1                  7f109a34a4a5        2 hours ago         203MB
busybox             latest              1c35c4412082        17 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name b1 --rm -it --entrypoint "/bin/sh" myimg:v1.5
/ # ls
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    7 root      0:00 ps
/ # 

  提示:可以看到使用docker run 必須要加–entrypoint 選項才可以覆蓋ENTRYPOINT指令指定的命令;

  示例:使用json數組格式來指定命令

[root@node1 test]# cat Dockerfile 
FROM busybox:latest

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

ARG web_home

COPY html ${web_home:-"/data/htdoc/"}

VOLUME ${web_home:-"/data/htdoc/"}

EXPOSE 80/tcp 443/tcp

ENTRYPOINT ["httpd","-f","-h","/data/htdoc/"]

[root@node1 test]# 

  提示:使用json數組格式來指定命令時,都需要將後面的選項和參數當作該命令的參數傳進去;

  測試:使用docker run 直接加命令 看看是否能夠覆蓋ENTRYPOINT指令指定的命令?

  提示:可以看到我們直接使用命令是無法覆蓋ENTRYPOINT指令說指定的命令;

  示例:

[root@node1 test]# cat Dockerfile 
FROM centos:7

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

RUN yum install -y httpd

EXPOSE 80/tcp 

ENTRYPOINT ["/usr/sbin/httpd","-DFOREGROUND"]

[root@node1 test]# 

  測試:用docker run 命令覆蓋ENTRYPOINT指定的默認命令,看看是否可行?

[root@node1 test]# docker build . -t myimg:v1.7
Sending build context to Docker daemon  1.051MB
Step 1/7 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/7 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> 604899ef29f9
Step 3/7 : LABEL version="1.0"
 ---> Using cache
 ---> d9edea71fa22
Step 4/7 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> ee027bbdc04b
Step 5/7 : RUN yum install -y httpd
 ---> Running in 164240645e39
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Resolving Dependencies
--> Running transaction check
---> Package httpd.x86_64 0:2.4.6-93.el7.centos will be installed
--> Processing Dependency: httpd-tools = 2.4.6-93.el7.centos for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: system-logos >= 7.92.1-1 for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Running transaction check
---> Package apr.x86_64 0:1.4.8-5.el7 will be installed
---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed
---> Package centos-logos.noarch 0:70.0.6-3.el7.centos will be installed
---> Package httpd-tools.x86_64 0:2.4.6-93.el7.centos will be installed
---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package             Arch          Version                    Repository   Size
================================================================================
Installing:
 httpd               x86_64        2.4.6-93.el7.centos        base        2.7 M
Installing for dependencies:
 apr                 x86_64        1.4.8-5.el7                base        103 k
 apr-util            x86_64        1.5.2-6.el7                base         92 k
 centos-logos        noarch        70.0.6-3.el7.centos        base         21 M
 httpd-tools         x86_64        2.4.6-93.el7.centos        base         92 k
 mailcap             noarch        2.1.41-2.el7               base         31 k

Transaction Summary
================================================================================
Install  1 Package (+5 Dependent packages)

Total download size: 24 M
Installed size: 32 M
Downloading packages:
warning: /var/cache/yum/x86_64/7/base/packages/apr-util-1.5.2-6.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
Public key for apr-util-1.5.2-6.el7.x86_64.rpm is not installed
--------------------------------------------------------------------------------
Total                                              7.8 MB/s |  24 MB  00:03     
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Importing GPG key 0xF4A80EB5:
 Userid     : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
 Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5
 Package    : centos-release-7-8.2003.0.el7.centos.x86_64 (@CentOS)
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : apr-1.4.8-5.el7.x86_64                                       1/6 
  Installing : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Installing : httpd-tools-2.4.6-93.el7.centos.x86_64                       3/6 
  Installing : centos-logos-70.0.6-3.el7.centos.noarch                      4/6 
  Installing : mailcap-2.1.41-2.el7.noarch                                  5/6 
  Installing : httpd-2.4.6-93.el7.centos.x86_64                             6/6 
  Verifying  : mailcap-2.1.41-2.el7.noarch                                  1/6 
  Verifying  : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Verifying  : httpd-2.4.6-93.el7.centos.x86_64                             3/6 
  Verifying  : apr-1.4.8-5.el7.x86_64                                       4/6 
  Verifying  : httpd-tools-2.4.6-93.el7.centos.x86_64                       5/6 
  Verifying  : centos-logos-70.0.6-3.el7.centos.noarch                      6/6 

Installed:
  httpd.x86_64 0:2.4.6-93.el7.centos                                            

Dependency Installed:
  apr.x86_64 0:1.4.8-5.el7                                                      
  apr-util.x86_64 0:1.5.2-6.el7                                                 
  centos-logos.noarch 0:70.0.6-3.el7.centos                                     
  httpd-tools.x86_64 0:2.4.6-93.el7.centos                                      
  mailcap.noarch 0:2.1.41-2.el7                                                 

Complete!
Removing intermediate container 164240645e39
 ---> 63db91f4fe6a
Step 6/7 : EXPOSE 80/tcp
 ---> Running in 6585da71fc3b
Removing intermediate container 6585da71fc3b
 ---> eb671cf67f52
Step 7/7 : ENTRYPOINT ["/usr/sbin/httpd","-DFOREGROUND"]
 ---> Running in f6e7297025af
Removing intermediate container f6e7297025af
 ---> bac03b20761a
Successfully built bac03b20761a
Successfully tagged myimg:v1.7
[root@node1 test]# docker run --name m1 --rm -it myimg:v1.7 /bin/sh
Usage: /usr/sbin/httpd [-D name] [-d directory] [-f file]
                       [-C "directive"] [-c "directive"]
                       [-k start|restart|graceful|graceful-stop|stop]
                       [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S] [-X]
Options:
  -D name            : define a name for use in <IfDefine name> directives
  -d directory       : specify an alternate initial ServerRoot
  -f file            : specify an alternate ServerConfigFile
  -C "directive"     : process directive before reading config files
  -c "directive"     : process directive after reading config files
  -e level           : show startup errors of level (see LogLevel)
  -E file            : log startup errors to file
  -v                 : show version number
  -V                 : show compile settings
  -h                 : list available command line options (this page)
  -l                 : list compiled in modules
  -L                 : list available configuration directives
  -t -D DUMP_VHOSTS  : show parsed vhost settings
  -t -D DUMP_RUN_CFG : show parsed run settings
  -S                 : a synonym for -t -D DUMP_VHOSTS -D DUMP_RUN_CFG
  -t -D DUMP_MODULES : show all loaded modules 
  -M                 : a synonym for -t -D DUMP_MODULES
  -t                 : run syntax check for config files
  -T                 : start without DocumentRoot(s) check
  -X                 : debug mode (only one worker, do not detach)
[root@node1 test]# 

  提示:可以看到我們用docker run指定命令去覆蓋ENTRYPOINT指令指定的命令,它給我們打印了httpd命令的用法,這說明我們後面傳遞的/bin/sh 當作參數傳遞給ENTRYPOINT說指定的命令;這裏還需要說一下,上面的示例用docker run 去覆蓋ENTRYPOINT指令指定的命令,沒有報錯的原因應該是busybox里的httpd程序支持傳遞/bin/sh當作參數;

  示例:CMD指令同ENTRYPOINT一起使用

[root@node1 test]# cat Dockerfile 
FROM centos:7

MAINTAINER "qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

RUN yum install -y httpd

ADD entrypoint.sh /bin/

EXPOSE 80/tcp 

CMD ["/usr/sbin/httpd","-DFOREGROUND"]

ENTRYPOINT ["/bin/entrypoint.sh"]

[root@node1 test]# 

  提示:以上dockerfile使用了CMD和ENTRYPOINT指令來指定容器默認運行程序;此時CMD所指定的命令默認會以參數的形式傳給ENTRYPOINT指令所指定的命令;而上面ENTRYPOINT指定指定的是一個腳本,也就說上面dockerfile最終運行的命令是/bin/entrypoint.sh /usr/sbin/httpd -DFOREGROUND;這裏的腳本就相當於中間層,通過腳本設定一些參數,然後把CMD指定的命令當作參數傳給腳本,最終腳本運行起來;

  entrypoint腳本

[root@node1 test]# ll
total 1032
-rw-r--r-- 1 root root     307 Jun  3 11:28 Dockerfile
-rwxr-xr-x 1 root root     300 Jun  3 11:22 entrypoint.sh
drwxr-xr-x 2 root root      42 May 31 01:51 html
-rw-r--r-- 1 root root 1043748 May 26 11:07 nginx-1.19.0.tar.gz
-rw-r--r-- 1 root root      22 May 31 01:52 test.html
[root@node1 test]# cat entrypoint.sh 
#!/bin/bash

doc_root=${DOC_ROOT:-/var/www/html}
cat > /etc/httpd/conf.d/myweb.conf <<EOF
        <virtualhost *:80>
                servername "localhost"
                documentroot "${doc_root}"
                <directory "${doc_root}">
                        options none
                        allowoverride none
                        require  all granted
                </directory>
        </virtualhost>
EOF

exec "$@"
[root@node1 test]# 

  提示:這個腳本很簡單就是在/etc/httpd/conf.d/生成一個myweb.conf的配置文件,然後最後引用腳本的參數運行;exec “$@” 表示把腳本的所有參數獨立運行成一個守護進程;默認不使用exec就表示以shell子進程的方式運行,exec就表示運行為單獨的守護進程,不再是shell子進程的方式;

  測試:

[root@node1 test]# docker build . -t httpd:v1
Sending build context to Docker daemon  1.051MB
Step 1/9 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/9 : MAINTAINER "qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> 604899ef29f9
Step 3/9 : LABEL version="1.0"
 ---> Using cache
 ---> d9edea71fa22
Step 4/9 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> ee027bbdc04b
Step 5/9 : RUN yum install -y httpd
 ---> Using cache
 ---> 63db91f4fe6a
Step 6/9 : ADD entrypoint.sh /bin/
 ---> 49d1270c3aa3
Step 7/9 : EXPOSE 80/tcp
 ---> Running in 3dacf6acf23b
Removing intermediate container 3dacf6acf23b
 ---> edced77af5b5
Step 8/9 : CMD ["/usr/sbin/httpd","-DFOREGROUND"]
 ---> Running in 23bb32def296
Removing intermediate container 23bb32def296
 ---> 169a5e164ba5
Step 9/9 : ENTRYPOINT ["/bin/entrypoint.sh"]
 ---> Running in f3bf0c267c7b
Removing intermediate container f3bf0c267c7b
 ---> 0801db092665
Successfully built 0801db092665
Successfully tagged httpd:v1
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
httpd               v1                  0801db092665        35 seconds ago      307MB
myimg               v1.7                bac03b20761a        12 minutes ago      307MB
myimg               v1.6                5370df4238eb        2 hours ago         1.22MB
myimg               v1.5                5825c2ec655f        2 hours ago         1.22MB
myimg               v1.4                58e3b4c40ae7        2 hours ago         1.22MB
myimg               v1.3                2e296b4f4500        3 hours ago         1.22MB
myimg               v1.2                30a5f5594104        3 hours ago         203MB
myimg               v1.1                ae463ec8cbd9        3 hours ago         203MB
myimg               v1                  7f109a34a4a5        3 hours ago         203MB
busybox             latest              1c35c4412082        19 hours ago        1.22MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name h1 -d httpd:v1
cee14b04912822c33e7deeee361e1ce0c20d7daf6c0666bff319bf3f1bc69bdc
[root@node1 test]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
cee14b049128        httpd:v1            "/bin/entrypoint.sh …"   9 seconds ago       Up 9 seconds        80/tcp              h1
[root@node1 test]# 

  提示:可以看到我們build成鏡像后,直接運行為容器,容器正常;我們進入容器內部看看它到底運行的說明命令

[root@node1 test]# docker exec -it h1 /bin/bash
[root@cee14b049128 /]# ls /etc/httpd/conf.d/myweb.conf 
/etc/httpd/conf.d/myweb.conf
[root@cee14b049128 /]# cat /etc/httpd/conf.d/myweb.conf
        <virtualhost *:80>
                servername "localhost"
                documentroot "/var/www/html"
                <directory "/var/www/html">
                        options none
                        allowoverride none
                        require  all granted
                </directory>
        </virtualhost>
[root@cee14b049128 /]# ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.1 224080  5016 ?        Ss   16:26   0:00 /usr/sbin/httpd -D
apache        7  0.0  0.0 224212  2960 ?        S    16:26   0:00 /usr/sbin/httpd -D
apache        8  0.0  0.0 224212  2960 ?        S    16:26   0:00 /usr/sbin/httpd -D
apache        9  0.0  0.0 224212  2960 ?        S    16:26   0:00 /usr/sbin/httpd -D
apache       10  0.0  0.0 224212  2960 ?        S    16:26   0:00 /usr/sbin/httpd -D
apache       11  0.0  0.0 224212  2960 ?        S    16:26   0:00 /usr/sbin/httpd -D
root         12  0.0  0.0  11828  1932 pts/0    Ss   16:35   0:00 /bin/bash
root         27  0.0  0.0  51756  1720 pts/0    R+   16:36   0:00 ps aux
[root@cee14b049128 /]# httpd -t -D DUMP_VHOSTS
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
VirtualHost configuration:
*:80                   localhost (/etc/httpd/conf.d/myweb.conf:1)
[root@cee14b049128 /]# 

  提示:可以看到容器內部運行的就是/usr/sbin/httpd -DFOREGROUND這個命令;其實這個命令不是CMD直接運行的命令,而是通過腳本獲取參數而來的;我們通過腳本添加的配置文件都在對應的位置,並且也都生效了;總結一點,通常CMD和ENTRYPOINT應用在通過entrypoint腳本做中間層向容器內運行的程序提供配置文件的場景,通常這些應用程序不是雲原生的;

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

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

C#中的TemplateMethod模式_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

一個真實的故事

大學的時候就開過一門課程,講設計模式,可是大學生沒什麼編程實踐經驗,在大學裏面聽設計模式的感覺,就像聽天書。聽着都有道理,可是完全領會不到其中的奧妙,大抵原因就在於沒有走過彎路,沒有吃過設計不當的虧。古人云,“操千曲而後曉聲,觀千劍而後識器”,誠不欺我。
 
博主在之前的某個項目中,設計出了一些工具類,像屬性窗口,錯誤提示窗口,還有一個窗口管理類管理它們,當時我實現工具保存時候的代碼是這樣的:

    class WindowManager
    {
        private List<ITool> _Tools = new List<ITool>();        

        public void AddTool(ITool tool)
        {
            _Tools.Add(tool);
        }

        public void SaveAllTools()
        {
            foreach(var tool in _Tools)
            {
                tool.Save();
            }
        }
    }

    interface ITool
    {
        bool BeforeSave();
        void Save();
        void AfterSave();
    }

    class PropertyWindow : ITool
    {
        public bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        public void Save()
        {
            if (BeforeSave())
            {
                //do save
                AfterSave();
            }
        }

        public void AfterSave()
        {

        }
    }

    class ErrorLis : ITool
    {
        public bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        public void Save()
        {
            if (BeforeSave())
            {
                //do save
                AfterSave();
            }
        }

        public void AfterSave()
        {

        }
    }

當時博主對這段代碼還挺滿意,完全沒有看出這兒有什麼問題,覺得這簡直寫的太OO了,有類,有接口,有針對接口編程,至於新加的工具類,也不會影響原來的代碼,簡直太符合開閉原則了。老鐵,沒毛病!
 
好日子就這麼繼續下去,每當需要新添加一個工具,我就新加一個類,在類裏面實現Save的邏輯,直到有一天,添加了一個ResourceControl

    class ResourceControl : ITool
    {
        public bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        public void Save()
        {
            if (!BeforeSave())
            {
                //do save
                AfterSave();
            }
        }

        public void AfterSave()
        {

        }
    }

 
在它的save裏面,我把if(BeforeSave())寫成了if(!BeforeSave())。。。
於是,我又額外花了一些時間來找到這個問題,修改它並在下次添加新類的時候戰戰兢兢提醒自己不要犯這種低級的錯誤。那麼,我們有沒有好的辦法來解決這個問題呢?

問題分析

其實就算每次添加新類的時候我們都能仔細的小心避免維護相同的邏輯,這段代碼的設計也還是有可以改進的地方,比如,BeforeSave和AfterSave在這裏作為接口ITool的一部分而公開,意味着客戶代碼可以自由的調用BeforeSave和AfterSave,然而這很可能並不是代碼作者的本意,畢竟,不調用Save而單獨調用BeforeSave和AfterSave有什麼意義呢?讓客戶能夠看到更多不必要的方法,增加了客戶錯誤使用接口的可能性,不是么?
 
綜上所述,我們需要解決的問題如下:

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

  • 抽象出Save, BeforeSave和AfterSave的邏輯關係,在一個地方固定下來,確保新增加的類所實現的這三個方法,都能自動具有這種邏輯關係。
  • 對客戶代碼隱藏不必要的接口。
     
    這種場景下面,我們需要用到設計模式中的TemplateMethod(模版方法)模式。
     

TemplateMethod模式

在WIKI上面,TemplateMethod模式的定義如下,
In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm’s structure.

大概意思就是,模版方法模式是一種行為類設計模式,允許軟件在更高的層次定義程序骨架,但是可以在子類推遲實現某些步驟。
 
類圖如下:

這完全符合我們的需求,讓我們試着修改我們的代碼。
 

使用TemplateMethod重新實現的代碼

    class WindowManager
    {
        private List<AbstractTool> _Tools = new List<AbstractTool>();        

        public void AddTool(AbstractTool tool)
        {
            _Tools.Add(tool);
        }

        public void SaveAllTools()
        {
            foreach(var tool in _Tools)
            {
                tool.Save();
            }
        }
    }

    abstract class AbstractTool
    {
        protected abstract bool BeforeSave();
        protected abstract void DoSave();
        protected abstract void AfterSave();
        public void Save()
        {
            if(!BeforeSave())
            {
                DoSave();
                AfterSave();
            }

        }        
    }

    class PropertyWindow : AbstractTool
    {
        protected override bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        protected override void DoSave()
        {
            
        }

        protected override void AfterSave()
        {

        }
    }

    class ErrorLis : AbstractTool
    {
        protected override bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        protected override void DoSave()
        {

        }

        protected override void AfterSave()
        {

        }
    }

從上面我們可以看到,我們用一個抽象類AbstractTool代替之前的ITool接口,抽象類和接口的一個區別就是,抽象類可以在其中嵌入某些邏輯,所以我們在Save這個公共的非虛方法中,完全實現了我們的BeforeSave和AfterSave邏輯,僅僅留下了BeforeSave,AfterSave和DoSave給子類覆蓋。這樣我們得到的好處是:

  • 抽象類只公開了一個Save方法,所以客戶代碼不用擔心會調用其他錯誤的方法。
  • 抽象類完全固定了Save邏輯,先調用BeforeSave檢查,之後執行DoSave進行具體的Save事項,最後進行AfterSave行為。子類只需要重新依據子類的需求覆蓋這三個虛方法即可。新添加的工具類,只要覆蓋這三個虛方法,至於虛方法之間的邏輯,抽象類已經固定,不用擔心。

結論

“紙上得來終覺淺,絕知此事要躬行”,祖宗的話,不會錯的,如果沒有一定的編程實踐和總結,是沒有辦法領悟設計模式的,博主也是通過之前那個例子才領悟到TemplateMethod模式的妙用。希望大家多多編程,早日領悟。

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

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

過濾器 和 攔截器 6個區別,別再傻傻分不清了_台中搬家

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

本文收錄在個人博客:www.chengxy-nds.top,技術資料共享,同進步

周末有個小夥伴加我微信,向我請教了一個問題:老哥,過濾器 (Filter) 和 攔截器 (Interceptor) 有啥區別啊? 聽到題目我的第一感覺就是:簡單

畢竟這兩種工具開發中用到的頻率都相當高,應用起來也是比較簡單的,可當我準備回復他的時候,竟然不知道從哪說起,支支吾吾了半天,場面炒雞尷尬有木有,工作這麼久一個基礎問題答成這樣,丟了大人了。

平時覺得簡單的知識點,但通常都不會太關注細節,一旦被別人問起來,反倒說不出個所以然來。

歸根結底,還是對這些知識了解的不夠,一直停留在會用的階段,以至於現在一看就會一說就廢!這是典型基礎不紮實的表現,哎·~,其實我也就是個虛胖!

知恥而後勇,下邊結合實踐,更直觀的來感受一下兩者到底有什麼不同?

準備環境

我們在項目中同時配置 攔截器過濾器

1、過濾器 (Filter)

過濾器的配置比較簡單,直接實現Filter 接口即可,也可以通過@WebFilter註解實現對特定URL攔截,看到Filter 接口中定義了三個方法。

  • init() :該方法在容器啟動初始化過濾器時被調用,它在 Filter 的整個生命周期只會被調用一次。注意:這個方法必須執行成功,否則過濾器會不起作用。

  • doFilter() :容器中的每一次請求都會調用該方法, FilterChain 用來調用下一個過濾器 Filter

  • destroy(): 當容器銷毀 過濾器實例時調用該方法,一般在方法中銷毀或關閉資源,在過濾器 Filter 的整個生命周期也只會被調用一次

@Component
public class MyFilter implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("Filter 前置");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 處理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

        System.out.println("Filter 後置");
    }
}

2、攔截器 (Interceptor)

攔截器它是鏈式調用,一個應用中可以同時存在多個攔截器Interceptor, 一個請求也可以觸發多個攔截器 ,而每個攔截器的調用會依據它的聲明順序依次執行。

首先編寫一個簡單的攔截器處理類,請求的攔截是通過HandlerInterceptor 來實現,看到HandlerInterceptor 接口中也定義了三個方法。

  • preHandle() :這個方法將在請求處理之前進行調用。注意:如果該方法的返回值為false ,將視為當前請求結束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執行。

  • postHandle():只有在 preHandle() 方法返回值為true 時才會執行。會在Controller 中的方法調用之後,DispatcherServlet 返回渲染視圖之前被調用。 有意思的是postHandle() 方法被調用的順序跟 preHandle() 是相反的,先聲明的攔截器 preHandle() 方法先執行,而postHandle()方法反而會後執行。

  • afterCompletion():只有在 preHandle() 方法返回值為true 時才會執行。在整個請求結束之後, DispatcherServlet 渲染了對應的視圖之後執行。

@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("Interceptor 處理中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("Interceptor 後置");
    }
}

將自定義好的攔截器處理類進行註冊,並通過addPathPatternsexcludePathPatterns等屬性設置需要攔截或需要排除的 URL

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
    }
}

我們不一樣

過濾器 和 攔截器 均體現了AOP的編程思想,都可以實現諸如日誌記錄、登錄鑒權等功能,但二者的不同點也是比較多的,接下來一一說明。

1、實現原理不同

過濾器和攔截器 底層實現方式大不相同,過濾器 是基於函數回調的,攔截器 則是基於Java的反射機制(動態代理)實現的。

這裏重點說下過濾器!

在我們自定義的過濾器中都會實現一個 doFilter()方法,這個方法有一個FilterChain 參數,而實際上它是一個回調接口。ApplicationFilterChain是它的實現類, 這個實現類內部也有一個 doFilter() 方法就是回調方法。

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain裏面能拿到我們自定義的xxxFilter類,在其內部回調方法doFilter()里調用各個自定義xxxFilter過濾器,並執行 doFilter() 方法。

public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
            ...//省略
            internalDoFilter(request,response);
    }
 
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (pos < n) {
            //獲取第pos個filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
 
}

而每個xxxFilter 會先執行自身的 doFilter() 過濾邏輯,最後在執行結束前會執行filterChain.doFilter(servletRequest, servletResponse),也就是回調ApplicationFilterChaindoFilter() 方法,以此循環執行實現函數回調。

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        filterChain.doFilter(servletRequest, servletResponse);
    }

2、使用範圍不同

我們看到過濾器 實現的是 javax.servlet.Filter 接口,而這個接口是在Servlet規範中定義的,也就是說過濾器Filter 的使用要依賴於Tomcat等容器,導致它只能在web程序中使用。

而攔截器(Interceptor) 它是一個Spring組件,並由Spring容器管理,並不依賴Tomcat等容器,是可以單獨使用的。不僅能應用在web程序中,也可以用於ApplicationSwing等程序中。

3、觸發時機不同

過濾器攔截器的觸發時機也不同,我們看下邊這張圖。

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

台中搬家公司推薦超過30年經驗,首選台中大展搬家

過濾器Filter是在請求進入容器后,但在進入servlet之前進行預處理,請求結束是在servlet處理完以後。

攔截器 Interceptor 是在請求進入servlet后,在進入Controller之前進行預處理的,Controller 中渲染了對應的視圖之後請求結束。

4、攔截的請求範圍不同

在上邊我們已經同時配置了過濾器和攔截器,再建一個Controller接收請求測試一下。

@Controller
@RequestMapping()
public class Test {

    @RequestMapping("/test1")
    @ResponseBody
    public String test1(String a) {
        System.out.println("我是controller");
        return null;
    }
}

項目啟動過程中發現,過濾器的init()方法,隨着容器的啟動進行了初始化。

此時瀏覽器發送請求,F12 看到居然有兩個請求,一個是我們自定義的 Controller 請求,另一個是訪問靜態圖標資源的請求。

看到控制台的打印日誌如下:

執行順序 :Filter 處理中 -> Interceptor 前置 -> 我是controller -> Interceptor 處理中 -> Interceptor 處理后

Filter 處理中
Interceptor 前置
Interceptor 處理中
Interceptor 後置
Filter 處理中

過濾器Filter執行了兩次,攔截器Interceptor只執行了一次。這是因為過濾器幾乎可以對所有進入容器的請求起作用,而攔截器只會對Controller中請求或訪問static目錄下的資源請求起作用。

5、注入Bean情況不同

在實際的業務場景中,應用到過濾器或攔截器,為處理業務邏輯難免會引入一些service服務。

下邊我們分別在過濾器和攔截器中都注入service,看看有什麼不同?

@Component
public class TestServiceImpl implements TestService {

    @Override
    public void a() {
        System.out.println("我是方法A");
    }
}

過濾器中注入service,發起請求測試一下 ,日誌正常打印出“我是方法A”

@Autowired
    private TestService testService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 處理中");
        testService.a();
        filterChain.doFilter(servletRequest, servletResponse);
    }
Filter 處理中
我是方法A
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor 後置

在攔截器中注入service,發起請求測試一下 ,竟然TM的報錯了,debug跟一下發現注入的service怎麼是Null啊?

這是因為加載順序導致的問題,攔截器加載的時間點在springcontext之前,而Bean又是由spring進行管理。

攔截器:老子今天要進洞房;
Spring:兄弟別鬧,你媳婦我還沒生出來呢!

解決方案也很簡單,我們在註冊攔截器之前,先將Interceptor 手動進行注入。注意:在registry.addInterceptor()註冊的是getMyInterceptor() 實例。

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public MyInterceptor getMyInterceptor(){
        System.out.println("注入了MyInterceptor");
        return new MyInterceptor();
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
    }
}

6、控制執行順序不同

實際開發過程中,會出現多個過濾器或攔截器同時存在的情況,不過,有時我們希望某個過濾器或攔截器能優先執行,就涉及到它們的執行順序。

過濾器用@Order註解控制執行順序,通過@Order控制過濾器的級別,值越小級別越高越先執行。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {

攔截器默認的執行順序,就是它的註冊順序,也可以通過Order手動設置控制,值越小越先執行。

 @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
    }

看到輸出結果發現,先聲明的攔截器 preHandle() 方法先執行,而postHandle()方法反而會後執行。

postHandle() 方法被調用的順序跟 preHandle() 居然是相反的!如果實際開發中嚴格要求執行順序,那就需要特別注意這一點。

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor2 處理中
Interceptor1 處理中
Interceptor 後置
Interceptor2 處理后
Interceptor1 處理后

那為什麼會這樣呢? 得到答案就只能看源碼了,我們要知道controller 中所有的請求都要經過核心組件DispatcherServlet路由,都會執行它的 doDispatch() 方法,而攔截器postHandle()preHandle()方法便是在其中調用的。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
        try {
         ...........
            try {
           
                // 獲取可以執行當前Handler的適配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 注意: 執行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 注意:執行Handle【包括我們的業務邏輯,當拋出異常時會被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);

                // 注意:執行Interceptor中PostHandle 方法【拋出異常時無法執行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

看看兩個方法applyPreHandle()applyPostHandle()具體是如何被調用的,就明白為什麼postHandle()preHandle() 執行順序是相反的了。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if(!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }

        return true;
    }
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = interceptors.length - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

發現兩個方法中在調用攔截器數組 HandlerInterceptor[] 時,循環的順序竟然是相反的。。。,導致postHandle()preHandle() 方法執行的順序相反。

總結

我相信大部分人都能熟練使用濾器和攔截器,但兩者的差別還是需要多了解下,不然開發中使用不當,時不時就會出現奇奇怪怪的問題,以上內容比較簡單,新手學習老鳥複習,有遺漏的地方還望大家积極補充,如有理解錯誤之處,還望不吝賜教。

原創不易,燃燒秀髮輸出內容

整理了幾百本各類技術电子書, 送給小夥伴們, 我的同名公眾號自行領取。和一些小夥伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我們吧!

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

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

掄牆鞭死5歲童 養母求處囚終身_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

新聞出處: 蘋果日報   記者:孫友廉、黃哲民

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。



毆打虐待養子致死的樊女,遭求處無期徒刑。資料照片

新北市樊姓女子因曾凌虐年幼養子,今 年5月,遭法院判刑確定,不料,今年9月,她竟再持水管鞭打5歲養子,更腳踹、手掐養子下體,還抓養子頭去撞牆,致養子傷重死亡。板橋地檢署痛斥樊女手段 殘忍,虐待成性,昨依殺人罪起訴,求處無期徒刑,也請法院繼續羈押獲准。

曾遭判刑仍未悔悟

33 歲的樊女與夫因無法生育,陸續領養1子1女,前年7月起,樊女便常以年僅3歲的養子隨地便溺、不受管教等理由,多次毆打養子,造成他身體、生殖器等多處受 傷;8月10日,更打傷養子頭部,造成長約11公分的撕裂傷,今年5月才遭台灣高等法院依傷害罪判刑4月,得易科罰金12萬元,緩刑5年,緩刑期間交付保 護管束確定。

不料樊女仍未悔悟,今年9月14日,在觀護人探視輔導3天後,竟再持水管鞭打養子,還手捏下體,腳踹胸腹,將養子推倒在地,再抓養子 頭去撞牆;樊女見養子沒反應也未送醫,直到丈夫下班發現,連忙開車載養子、樊女,前去警局報案,樊夫到警局便大喊:「誰會CPR?趕快救救我兒子!」但養 子已氣絕身亡。

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

泳池變態男猥褻小六童 還說:你可以摸我_網頁設計

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

台中出現泳池變態男,竟然在公共浴室洗澡時,伸手撫摸旁邊小六男童的下體,甚至還開口跟他說「你也可以摸我的」,男童嚇得大聲叫阿公,男子這才趕緊收拾東西離開,看到男童追了出來,他還警告男童不准跟著他。事後男童身心受傷,跟媽媽哭訴後,家人趕緊帶著他報警,並且調閱監視器,揪出這名泳池色狼。

游泳池浴室,卡其色上衣、綠色短褲男子匆忙跑了出來,快步離開,後面1名男童顧不得頭髮還濕濕的,也跟著衝出來找人,原來這名男子,竟然在游泳池浴室伸手猥褻他!

受害男童母親說:「電話中他就開始哭了,整個崩潰大哭,然後就跟我講說,他摸我的小鳥,然後我把他手撥開。」

男童身心靈受傷,事後打電話給媽媽時崩潰大哭,家人這才知道,他在游泳池遭到猥褻。8日傍晚,這名小六男童跟阿公一起到豐原這間游泳池游泳,結束後在大眾澡堂淋浴時,1名男子突然伸手抓他下體,還告訴他說「你也可以摸我的啊」,男童嚇得大叫阿公求救,男子才嚇得匆忙離開,看到男童跑出浴室,還轉頭警告他不准跟上來。

事後家人帶著男童報警,調閱監視器,男童一眼就認出猥褻男子。警方依照車牌,將他約談到案到案,男子坦承有對男童伸手,被依妨害性自主罪名,函送法辦。

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

OkHttp,一次無奈的使用_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

一次使用OKHTTP的心痛歷程

最近由於一些不得已的原因,接觸到了OKHttp,說起來也挺Dan疼的,之前同事將生產附件上傳地址配置成了測試地址,還好數量不多,沒有造成太大的影響,況且的是這位同事又離職了,當時只能在心中
默默的問候了他N遍,當然問候完了之後,也不得不繼續數據同步的工作。

OKHTTP官方地址:okHttp

本文源地址:一次使用OKHTTP的心痛歷程

介紹

由於OkHttp官網的介紹十分詳細,這裏只能貼上一段翻譯過後的introduce:

HTTP是現代應用網絡的一種方式。這就是我們交換數據和媒體的方式。高效地使用HTTP可以讓你的東西更快地加載並節省帶寬。

OkHttp是一個高效的Http客戶端,在默認的情況下:

  • HTTP / 2支持允許對同一主機的所有請求共享一個套接字。
  • 連接池可減少請求延遲(如果HTTP / 2不可用)。
  • 透明的GZIP縮小了下載大小。
  • 響應緩存可以完全避免網絡重複請求。

不過在我使用下來,OkHttp比 apache-http 好用太多,層次結構較直觀。

使用場景

本次場景是將上傳到測試環境的文件信息,下載到本地,然後再上傳到生產環境。

解決過程如下:

  • 將錯誤數據從數據庫表中粘貼到本地新建的一個Excel文件中。(畢竟直接連接數據庫風險更大)

  • 讀取Excel內的信息,獲取文件地址。

  • 請求文件地址,獲取到流文件信息。

  • 拿到流文件信息,拼接上傳數據,上傳到新的生產環境中。

  • 上傳完成后,獲取到生產環境文件地址。

  • 獲取到生產文件地址的同時,生成更新的SQL語句。

  • 到數據庫中執行SQL語句。

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

    網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

使用過程

本次使用沒有搭建新的工程,直接再 src/test/java 目錄下新建一個Java類。

引入OKHttp的依賴:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.3.1</version>
</dependency>

在引入了 okhttp 的jar包后,基本上就可以開始隨心所欲的進行自己任意喪心病狂的Http請求了。

比如,它直接同步和異步的請求:

同步GET

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/helloworld.txt")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      Headers responseHeaders = response.headers();
      for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
      }

      System.out.println(response.body().string());
    }
  }

異步GET

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        try (ResponseBody responseBody = response.body()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

          Headers responseHeaders = response.headers();
          for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
          }

          System.out.println(responseBody.string());
        }
      }
    });
  }

Header信息

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println("Server: " + response.header("Server"));
      System.out.println("Date: " + response.header("Date"));
      System.out.println("Vary: " + response.headers("Vary"));
    }
  }

POST請求流信息

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

POST請求File信息

  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

Post表單提交

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody formBody = new FormBody.Builder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

POST多個Body請求

  /**
   * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
   * these examples, please request your own client ID! https://api.imgur.com/oauth2
   */
  private static final String IMGUR_CLIENT_ID = "...";
  private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

因為使用過程中大多數都是按照官網的例子來進行,所以這次使用的代碼是類似於官方提供的例子,當然也是不太好意思貼出來,哈哈。

總結

OkHttp算得上是相見恨晚,之前一遍一遍寫 apache-http 的時候就覺得 apche 有點冗餘,就是想有一個輕量級的,比較好上手,容易懂的http-client,不過現在接觸到了 okhttp,還是得感謝那位配錯地址的兄弟。

以上更多請求例子可以訪問:OKhttp-Request-example

參考資料:

OkHttp.io

OKhttp-Request-example

OKHttp-Github

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

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

Jmeter(七) – 從入門到精通 – 建立數據庫測試計劃實戰(詳解教程)_網頁設計公司

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

1.簡介

   在實際工作中,我們經常會聽到數據庫的性能和穩定性等等,這些有時候也需要測試工程師去評估和測試,上一篇文章宏哥主要介紹了jmeter連接和創建數據庫測試計劃的過程,宏哥在文中通過示例和代碼非常詳細地介紹給大家,希望對各位小夥伴和童鞋們的學習或者工作具有一定的指導和參考學習價值,遇到類似的問題腦子一片空白的童鞋們可以參考一下。這一篇宏哥就以MySQL數據為例結合上一篇的理論知識在這裏帶領小夥伴和童鞋們實戰一下。這裏宏哥為了增加小夥伴們的學習興趣和便於記憶理解,因此列舉了一個諜戰劇中執行刺殺任務的場景,首先組成刺殺任務的小隊,然後通過接頭暗號建立聯繫,其次就開始執行刺殺任務,期間有核查組員的人物背景、其他組員支援、以及自己組員的犧牲、任務的變更等等,最後確認暗殺任務是否執行成功。

2.環境準備

1、MySQL數據庫
2、下載mysql jdbc 驅動
3、JMeter

2.1安裝MySQL

首先確保你已經安裝好數據庫MySQL。如果沒有可以參考宏哥的這篇文章:傳送門。查看有沒有安裝MySQL命令:net start,打開控制台(在開始,運行輸入cmd)然後出入“net start” 就是打開了服務看看列出來的有沒有 MySQL之類的如果沒有,就是沒有安裝。如下圖所示:

2.2JMeter

JMeter安裝啟動好待用。

2.3下載MySQL驅動

1、下載MySQL驅動。下載地址:https://dev.mysql.com/downloads/connector/j/
常用的包如下:
Windows 下 mysql-connector-java-5.1.7-bin.jar
Mac下 MySQL Connector/J沒有對應的 Mac 版,可以選擇Platform Independent:mysql-connector-java-8.0.15.zip
注意:驅動包的版本一定要與你數據庫的版本匹配,驅動版本低於mysql版本有可能會導致連接失敗報錯

2、解壓下載的MySQL驅動,如下圖所示:

3、將解壓的MySQL的jdbc驅動(mysql-connector-java-8.0.20.jar),將其放到D:\software\apache-jmeter-5.1.1\lib目錄下。如下圖所示:

注意:敲黑板,敲腦殼啦!!!放完驅動以後,要記得重啟jmeter

3.建立數據庫測試計劃

  在本節中,您將學習如何創建基本的測試計劃以測試數據庫服務器和操作數據庫(增、刪、改、查)。本示例使用MySQL數據庫驅動程序。要使用該驅動程序,必須將其包含的.jar文件(例如mysql-connector-java-XXX-bin.jar)複製到JMeter ./lib目錄。

3.1新建測試計劃

首先我們新建一個測試計劃,並將其命名為:Test MySQLDB Plan,如下圖所示:

3.2在測試計劃下添加驅動地址

在建立好測試計劃以後,點擊“Browse…”,選擇我們前邊下載解壓好的驅動路徑,我們需要將驅動的地址(路徑)添加到測試計劃下邊,如下圖所示:

3.3添加用戶

新建完測試計劃以後,我們前邊也講過了,這時候就要添加用戶了。你要對每個JMeter測試計劃進行的第一步是添加一個線程組(用戶)。線程組告訴JMeter您要模擬的用戶數量,用戶應多久發送一次請求以及應發送多少次請求。這裏就相當於諜戰片中我們開始選擇隊員組隊的過程,默認是一人一個小組,如果你覺得不夠可以在控制面板修改人數等等。如下圖所示:

3.4添加JDBC連接配置

通過上邊的操作,我們已經定義了用戶挑選了隊員組成了精幹小組,然後我們必須定義這些用戶(隊員)所要去的目的地,和目的地建立聯繫。不要這些用戶累死累活的幹了半天的活,知不道是為誰幹得活。在本部分中,你需要和目的地建立聯繫。我們需要設置一些字段,這些字段相當於諜戰片中的接頭暗號,例如:《智取威虎山》楊子榮與坐山雕的接頭暗號:臉紅什麼?精神煥發。怎麼又黃拉?防冷塗的蠟;長江長江我是黃河,等等。暗號對上了,才可以建立聯繫。否則認為有危險,不是建立聯繫,具體在測試中的表現就是報錯了!!!,下邊控制面板的參数字段在上一篇已經說過了,這裏就不贅述了,宏哥直接填寫了,有不明白的可以看看上一篇文章。如下圖所示:

3.5添加JDBC請求

 通過上邊的操作,我們已經定義了用戶並且已經知道目的地和接頭人建立了聯繫之後,然後我們就需要給這些用戶分配具體的任務了(誰負責監視,誰負責刺殺,誰負責放哨)。在本部分中,你將指定要執行的JDBC請求(刺殺任務)。這裏就開始執行刺殺任務,期間有核查組員的人物背景、其他組員支援、以及自己組員的犧牲、任務的變更等等

3.5.1查詢(核查組員信息)

3.5.2插入(其他組員支援)

3.5.3修改(組員檔案變更)

3.5.4刪除(自己組員犧牲)

3.6添加監聽器以查看/存儲測試結果(監聽器-查看任務是否成功)

您需要添加到測試計劃中的最後一個元素是 Listener。該元素負責將JDBC請求的所有結果存儲在文件中並显示結果。如下圖所示:

3.7保存與執行測試計劃

保存與執行測試計劃,查看任務結果,如下圖所示:

宏哥為了讓小夥伴們看得清楚,講後邊的三個請求都修改成disable了,修改後由黑色變成了灰色。然後慢慢的逐個再將其修改成enable。如下圖所示:

3.7.1核查人員信息情況

1、首先查詢MySQL數據,如下圖所示:

2、看一下JMeter執行后與上邊的查詢結果一致,說明核查人員信息成功。如下圖所示:

3.7.2核查人員支援情況

1、首先查詢MySQL數據,只有四個人員,如下圖所示:

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

2、JMeter執行后,北京宏哥前來支援,我們分別查看MySQL和JMeter的結果,如下圖所示:

JMeter:

MySQL:

3.7.3核查人員變更情況

1、首先查詢MySQL數據,只有五個人員,如下圖所示:

2、JMeter執行后,將“趙六”變更成“趙麗”,我們分別查看MySQL和JMeter的結果,如下圖所示:

JMeter:

MySQL:

3.7.4核查人員犧牲情況

1、首先查詢MySQL數據,只有五個人員,如下圖所示:

2、JMeter執行后,李四在任務中犧牲,我們分別查看MySQL和JMeter的結果,如下圖所示:

JMeter:

MySQL:

4.小結

1、Cannot load JDBC driver class ‘com.mysql.jdbc.Driver’

原因:未在jmeter安裝目錄下的./lib目錄下放入mysql-connector-java-X.X.X-bin.jar

解決方法:將mysql-connector-java-X.X.X-bin.jar放入到./lib目錄,並重啟jmeter

2、CLIENT_PLUGIN_AUTH is required 

原因:導入的 mysql-connector-java-X.X.X-bin.jar版本問題(原來導入mysql-connector-java-8.0.17.jar),上網查資料,知驅動和mysql數據庫的版本也有關係(參考mysql-connector-java之6.0.6版本,SQLNonTransientConnectionException: CLIENT_PLUGIN_AUTH is required異常問題 – wenqi0501的個人空間 – OSCHINA  https://my.oschina.net/u/3640994/blog/3000068)

mysql官網驅動版本和數據庫版本說明,地址:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-versions.html

解決方法:替換成mysql-connector-java-5.1.47.jar后問題解決
3、Variable Name must not be null in Insert

原因:未在JDBC Request的控制面板里填寫綁定的連接池

解決辦法:填寫和JDBC Connection Configuration一樣的連接池即可

4、以下是各數據庫Database URL、JDBC Driver class 填寫方式

數據庫名 Database URL  Driver class
MySQL jdbc:mysql://host[:port]/dbname com.mysql.jdbc.Driver
PostgreSQL jdbc:postgresql:{dbname} org.postgresql.Driver
Oracle jdbc:oracle:thin:@//host:port/service OR jdbc:oracle:thin:@(description=(address=(host={mc-name})(protocol=tcp)(port={port-no}))(connect_data=(sid={sid}))) oracle.jdbc.OracleDriver
Ingress (2006) jdbc:ingres://host:port/db[;attr=value] ingres.jdbc.IngresDriver
Microsoft SQL Server (MS JDBC driver) jdbc:sqlserver://host:port;DatabaseName=dbname com.microsoft.sqlserver.jdbc.SQLServerDriver
Apache Derby jdbc:derby://server[:port]/databaseName[;URLAttributes=value[;…]] org.apache.derby.jdbc.ClientDriver

 

   好了今天的建立數據庫測試計劃實戰<MySQL數據庫>就分享到這裏。

 

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

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

 

 

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

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

LeetCode 74,直擊BAT經典面試題_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

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

今天是LeetCode專題43篇文章,我們今天來看一下LeetCode當中的74題,搜索二維矩陣,search 2D Matrix。

這題的官方難度是Medium,通過率是36%,和之前的題目不同,這題的點贊比非常高,1604個贊,154個反對。可見這題的質量還是很高的,事實上也的確如此,這題非常有意思。

題意

這題的題意也很簡單,給定一個二維的數組matrix和一個整數target,這個數組當中的每一行和每一列都是遞增的,並且還滿足每一行的第一個元素大於上一行的最後一個元素。要求我們返回一個bool變量,代表這個target是否在數組當中。

也就是說這個是一個典型的判斷元素存在的問題,我們下面來看看兩個樣例:

Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
Output: true
Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
Output: false

題解

這題剛拿到手可能會有些蒙,我們當然很容易可以看出來這是一個二分的問題,但是我們之前做的二分都是在一個一維的數組上,現在的數據是二維的,我們怎麼二分呢?

我們仔細閱讀一下題意,再觀察一下樣例,很容易發現,如果一個二維數組滿足每一行和每一列都有序,並且保證每一行的第一個元素大於上一行的最後一個元素,那麼如果我們把這個二維數組reshape到一維,它依然是有序的。

比如說有這樣一個二維數組:

[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]

它reshape成一維之後會變成這樣:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

reshape是numpy當中的說法,也可以簡單理解成把每一行串在一起。所以這題最簡單的做法就是把矩陣降維,變成一位的數組之後再通過二分法來判斷元素是否存在。如果偷懶的話可以用numpy來reshape,如果不會numpy的話,可以看下我之前關於numpy的教程,也可以自己用循環來處理。

reshape之後就是簡單的二分了,完全沒有任何難度:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        import numpy as np
        arr = np.array(matrix)
        # 通過numpy可以直接reshape
        arr = arr.reshape((-1, ))
        l, r = 0, arr.shape[0]
        if r == 0:
            return False
        # 套用二分
        while l+1 < r:
            m = (l + r) >> 1
            if arr[m] <= target:
                l = m
            else:
                r = m
        return arr[l] == target

正經做法

引入numpy reshape只是給大家提供一個解決的思路,這顯然不是一個很好的做法。那正確的方法應該是怎樣的呢?

還是需要我們對問題進行深入分析,正向思考感覺好像沒什麼頭緒,我們可以反向思考。這也是解題常用的套路,假設我們已經知道了target這個数字存在矩陣當中,並且它的行號是i,列號是j。那麼根據題目當中的條件,我們能夠得出什麼結論呢?

我們分析一下元素的大小關係,可以得出行號小於i的所有元素都小於它,行號大於i的所有元素都大於它。同行的元素列號小於j的元素小於它,列號大於j的元素大於它。

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

也就是說,行號i就是一條隱形的分界線,將matrix分成了兩個部分,i上面的小於target,i下方的大於target。所以我們能不能通過二分找到這個i呢?

想到這裏就很簡單了,我們可以通過每行的最後一個元素來找到i。對於一個二維數組而言,每行的最後一個元素連起來就是一個一維的數組,就可以很簡單地進行二分了。

找到了行號i之後,我們再如法炮製,在i行當中進行二分來查找j的位置。找到了之後,再判斷matrix[i][j]是否等於target,如果相等,那麼說明元素在矩陣當中。

整個的思路應該很好理解,但是實現的時候有一個小小的問題,就是我們查找行的時候,找的是大於等於target的第一行的位置。也就是說我們查找的是右端點,那麼二分的時候維護的是一個左開右閉的區間。在邊界的處理上和平常使用的左閉右開的寫法相反,注意了這點,就可以很順利地實現算法了:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        # 初始化,左開右閉,所以設置成-1, n-1
        l, r = -1, n-1
        
        while l+1 < r:
            mid = (l + r) >> 1
            # 小於target的時候移動左邊界
            if matrix[mid][m-1] < target:
                l = mid
            else:
                r = mid
                
        row = r
        
        # 正常的左閉右開的二分
        l, r = 0, m
        
        while l+1 < r:
            mid = (l + r) >> 1
            if matrix[row][mid] <= target:
                l = mid
            else:
                r = mid
                
        return matrix[row][l] == target

我們用了兩次二分,查找到了結果,每一次二分都是一個O(logN)的算法,所以整體也是log級的算法。

優化

上面的算法沒有問題,但是我們進行了兩次二分,感覺有些麻煩,能不能減少一次,只使用一次二分呢?

如果想要只使用一次二分就找到答案,也就是說我們能找到某個方法來切分整個數組,並且切分出來的數組也存在大小關係。這個條件是使用二分的基礎,必須要滿足。

我們很容易在數組當中找到這樣的切分屬性,就是元素的位置。在矩陣元素的問題當中,我們經常用到的一種方法就是對矩陣當中的元素進行編號。比如說一個點處於i行j列,那麼它的編號就是i * m + j,這裏的m是每行的元素個數。這個編號其實就是將二維數組壓縮到一維之後元素的下標。

我們可以直接對這個編號進行二分,編號的取值範圍是確定的,是[0, mn)。我們有了編號之後,可以還原出它的行號和列號。而且根據題目中的信息,我們可以確定這個矩陣當中的元素按照編號也存在遞增順序。所以我們可以大膽地使用二分了:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        l, r = 0, m*n
        
        while l+1 < r:
            mid = (l + r) >> 1
            # 還原行號和列號
            x, y = mid // m, mid % m
            if matrix[x][y] <= target:
                l = mid
            else:
                r = mid
        return matrix[l // m][l % m] == target

這樣一來我們的代碼大大簡化,並且代碼運行的效率也提升了,要比使用兩次二分的方法更快。

總結

這道題到這裏就結束了,這題難度並不大,想出答案來還是不難的。但是如果在面試當中碰到,想要第一時間想到最優解法還是不太容易。這一方面需要我們積累經驗,看到題目大概有一個猜測應該使用什麼類型的算法,另一方面也需要我們對問題有足夠的理解和分析,從而讀到題目當中的隱藏信息

關於這題還有一個變種,就是去掉其中每行的第一個元素大於上一行最後一個元素的限制。那麼矩陣當中元素按照編號順序遞增的性質就不存在了,對於這樣的情況, 我們該怎麼樣運用二分呢?這個問題是LeetCode的240題,感興趣的話可以去試着做一下這題,看看究竟解法有多大的變化。

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

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

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單