第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號安全,請及時(shí)綁定郵箱和手機(jī)立即綁定
1. Web 服務(wù)中的常用術(shù)語

在正式開始 Django 項(xiàng)目開發(fā)之前,我們需要掌握一些 Web 開發(fā)中常見的術(shù)語。Web 服務(wù)和網(wǎng)站在某種程度上是等價(jià)的,因此后面描述時(shí)并不區(qū)分這兩個概念??蛻舳耍河脩糁鳈C(jī)上運(yùn)行并連接到互聯(lián)網(wǎng)的應(yīng)用程序,一般而言是指瀏覽器。用戶通過瀏覽器實(shí)現(xiàn)和網(wǎng)站的數(shù)據(jù)交互;服務(wù):服務(wù)主要接受和處理來自互聯(lián)網(wǎng)的請求。服務(wù)一般部署在某臺主機(jī)上,監(jiān)聽某個端口,等待用戶請求;域名:用于標(biāo)識一個或者多個 IP 地址;IP:互聯(lián)網(wǎng)協(xié)議地址?;ヂ?lián)網(wǎng)上的每臺計(jì)算機(jī)都有一個 IP 地址,用于識別和通信。IP 地址由 4 組數(shù)組組成,以小數(shù)點(diǎn)分割,這些被稱為邏輯地址;DNS:域名系統(tǒng)服務(wù),主要用于網(wǎng)絡(luò)域名與 IP 地址的相互轉(zhuǎn)換;ISP:互聯(lián)網(wǎng)服務(wù)提供商;TCP/IP:傳輸控制協(xié)議 / 網(wǎng)際協(xié)議,是當(dāng)前互聯(lián)網(wǎng)使用的主要通信協(xié)議。除了上述基礎(chǔ)術(shù)語之外,我們還有兩個非常重要的知識點(diǎn)需要掌握,分別是 HTTP 協(xié)議和 URL 的組成。

2. 解壓并啟動 Nginx

到下載目錄下解壓 Nginx-1.16.1.zip,并打開 cmd 窗口,進(jìn)入該目錄:在 console 控制臺中,有個小問題要注意下:如果 Nginx 安裝包解壓到不同的磁盤,進(jìn)入該目錄的方式是先輸入對應(yīng)的磁盤編號加冒號(比如進(jìn)入E盤目錄,輸入e:),然后再使用 cd 進(jìn)入 Nginx 對應(yīng)的目錄中,如下圖:啟動非常簡單,進(jìn)入 Nginx 的解壓包目錄,會發(fā)現(xiàn)下面有一個 Nginx.exe 文件,這個就非常類似我們后續(xù)再 linux 系統(tǒng)上編譯出來的二進(jìn)制文件,我們要使用該可執(zhí)行程序啟動、停止 Nginx 服務(wù)。比較優(yōu)雅的啟動方式是使用start Nginx,這樣會出現(xiàn)黑屏一閃而過,Nginx 服務(wù)就已經(jīng)啟動了。我們可以使用 console 的命令netstat -ano | findstr :80 | findstr LISTENING查看80端口是否已經(jīng)被監(jiān)聽,參考下圖??梢钥吹较到y(tǒng)已經(jīng)監(jiān)聽80端口了,我們打開瀏覽器輸入http://localhost,就可以看到 Nginx 的歡迎頁面了!停止 Nginx 也是非常簡單,進(jìn)入該解壓目錄(在 Nginx.exe 所在目錄),控制臺執(zhí)行nginx -s stop,即可停止 Nginx 服務(wù),再看服務(wù)監(jiān)聽端口,已經(jīng)沒有了 80 端口,說明 Nginx 服務(wù)已經(jīng)停止。

1.前言

ThinkCMF 是一款支持 Swoole 的開源內(nèi)容管理框架(CMF),它是基于 ThinkPHP 開發(fā)的,它一直秉承 ThinkPHP 的大道至簡的理念,堅(jiān)持做最簡約的 ThinkPHP 開源軟件,多應(yīng)用化開發(fā)方式,讓您更快地完成自己的創(chuàng)業(yè)項(xiàng)目,用 ThinkCMF 可以開發(fā)后臺管理系統(tǒng)、企業(yè)建站系統(tǒng)、微信小程序開發(fā),、小程序API、cms 等制定化的功能,本小節(jié)主要介紹如何使用 ThinkCMF 完成學(xué)生的信息管理。1. 1 來到 ThinkCMF官網(wǎng),如下圖所示點(diǎn)擊 下載:1.2 在新打開的頁面中點(diǎn)擊 下載 Source code (zip):Tips: 需要注冊登錄才能開始下載。1.3 將下載好的文件解壓,部署的時(shí)候?qū)⑷肟诼窂脚渲玫?public,如下圖所示:1.4 瀏覽器訪問部署好的 ThinkCMF 開始安裝:1.5 點(diǎn)擊 接受 之后來到安裝向?qū)Ы缑妫?.6 點(diǎn)擊下一步之后開始配置數(shù)據(jù)庫相關(guān)的配置:1.7 配置好數(shù)據(jù)庫之后點(diǎn)擊 創(chuàng)建數(shù)據(jù)庫:1.8 安裝完成之后出現(xiàn)如下圖界面表示安裝成功:1.9 在瀏覽器訪問 http://xxx.com(域名)/admin開始登錄后臺:1.10 輸入之前安裝的時(shí)候設(shè)置的密碼即進(jìn)入后臺管理界面:

3. 集成成功與否的必要性測試

無論使用上述哪種方式,實(shí)現(xiàn)的效果都是一樣的。在配置好之后,我們需要啟動剛剛配置的項(xiàng)目,來檢測我們的配置是否成功了。單獨(dú)的引入 Hystrix 依賴,我們是無法檢測是否配置成功的,所以我們又引入了 Hystrix-Dashboard 微服務(wù)監(jiān)控依賴,我們可以通過訪問 Hystrix-Dashboard 微服務(wù)監(jiān)控平臺來檢測 Hystrix 是否配置成功。Hystrix 集成成功演示-Hystrix Dashboard如果我們的 Hystrix 是配置成功的話,那么,可以通過輸入應(yīng)用實(shí)例地址加端口,且最后以 /hystrix 形式結(jié)尾的鏈接,即可看到我們的 Hystrix Dashboard 監(jiān)控面板。就我本機(jī)而言,我的應(yīng)用訪問地址是 http://localhost:8093/prefix/hystrix ,每個人的訪問地址可能都不一樣。Tips: 我們可以查看項(xiàng)目的配置文件,來獲取項(xiàng)目的 ip 地址、端口號信息,ip 地址 + 端口號就組成了項(xiàng)目地址,在項(xiàng)目地址最后,添加 /hystrix 后綴,即可訪問 Hystrix Dashboard 監(jiān)控面板。如果當(dāng)我們的項(xiàng)目運(yùn)行之后,可以看到 Hystrix Dashboard 監(jiān)控面板,就說明我們的 Hystrix 已經(jīng)成功集成到了 Spring Cloud 框架中去,我們就可以愉快的來使用 Hystrix 了。

2. 書寫程序

我們先來看一下代碼的架構(gòu):接下來,讓我們看看所有代碼。import requestsfrom bs4 import BeautifulSoup from pprint import pprintimport osimport lxmlimport pymongoheaders = {'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36'} # 請求頭部def get_course_info(): """ get all course basic unit """ url = "http://idcbgp.cn/course/list" #慕課網(wǎng)免費(fèi)課地址 r = requests.get(url, headers= headers) # 發(fā)送請求 bs = BeautifulSoup(r.text, "lxml") # 解析網(wǎng)頁 course_data = bs.find_all("div", class_="course-card-container") # 定位課程信息 return course_datadef save_pics(course_data): """ save pics from imooc free course """ for each_item in course_data: img = each_item.find("img") image_link = img.attrs["data-original"].split("/") image_address = "http:" + img.attrs["data-original"] with open(image_link[-1],'wb+') as f: res = requests.get(image_address, headers= headers) # 發(fā)送請求 f.write(res.content)def save_courses_to_mongodb(mongod_con, course_data): """ save info to mongodb """ for each_item in course_data: imgs = each_item.find("img") desc = each_item.find("p", class_="course-card-desc") # 定位課程信息 class_name = each_item.find("h3", class_="course-card-name") # 定位課程信息 imooc_dict = { "class_name": class_name.getText(), "class_pics": imgs.attrs["data-original"], "people":desc.getText()} x = mongod_con.insert_one(imooc_dict)def create_local_pic_dir(): """ if don't have local dir, create one for holding the pics which download from the imooc. """ directory = os.path.dirname(os.path.realpath(__file__)) + '/imooc_pics/' if not os.path.exists(directory): os.makedirs(directory) os.chdir(directory)def db_connectin(): """ Connection to local mongo db service.""" try: myclient = pymongo.MongoClient("mongodb://localhost:27017/") mydb = myclient["practice"] mongod_con = mydb["imooc_courses"] except Exception as e: print("ERROR(MongoPipeline):", e) return mongod_condef main(): """ This is the main entry for running code.""" create_local_pic_dir() mycol = db_connectin() data = get_course_info() if data: save_pics(data) save_courses_to_mongodb(mycol, data)if __name__ == "__main__": main()代碼主要有 6 個函數(shù):create_local_pic_dir: 用來創(chuàng)建本地文件夾,來存儲爬取的圖片;db_connectin: 用來連接 mongodb;get_course_info :用來獲取課程基本信息;save_pics :函數(shù)將圖片存儲在本地;save_courses_to_mongodb :將數(shù)據(jù)存儲到 MongoDB;main :負(fù)責(zé)運(yùn)行程序。

4. 前端開發(fā)流程

前后端分離開發(fā),實(shí)際上前端工作就簡化了。我們直接新建項(xiàng)目文件夾 shop-front (商城前端項(xiàng)目文件夾),然后將前端頁面放到該文件夾即可。注意該頁面不需要放到 Spring Boot 項(xiàng)目目錄下,隨便找個目錄放置即可。實(shí)際開發(fā)過程中,后端和前端的項(xiàng)目可能都不在一臺計(jì)算機(jī)上。前端核心業(yè)務(wù)代碼如下,由于前端技術(shù)不是本節(jié)介紹的重點(diǎn),所以不再詳細(xì)解釋,感興趣的同學(xué)可以從 Git倉庫 查看完整代碼 。實(shí)例: //初始化方法 $(function () { var row = ""; $.ajax({ type: "GET", url: "http://127.0.0.1:8080/goods", //后端接口地址 dataType: "json", contentType: "application/json; charset=utf-8", success: function (res) { $.each(res, function (i, v) { row = "<tr>"; row += "<td>" + v.id + "</td>"; row += "<td>" + v.name + "</td>"; row += "<td>" + v.price + "</td>"; row += "<td>" + v.pic + "</td>"; row += "</tr>"; $("#goodsTable").append(row); }); }, error: function (err) { console.log(err); } }); });開發(fā)完該頁面后,直接使用瀏覽器雙擊打開,查看控制臺發(fā)現(xiàn)有錯誤信息提示。瀏覽器控制臺返回錯誤信息考驗(yàn)英文水平的時(shí)候到了!關(guān)鍵是 has been blocked by CORS policy ,意味著被 CORS 策略阻塞了。我們的前端頁面請求被 CORS 阻塞了,所以沒成功獲取到后端接口返回的數(shù)據(jù)。

2.2 read_csv()函數(shù)

read_csv() 函數(shù)為 Pandas 讀取 txt、csv 數(shù)據(jù)文件提供了強(qiáng)力的支持,該函數(shù)含有四五十個參數(shù),默認(rèn)是 從文件、URL、文件新對象中加載帶有分隔符的數(shù)據(jù),默認(rèn)分隔符是逗號。下面我們列舉出它最常用的幾個參數(shù)。參數(shù)名稱描述filepath_or_buffer可以是url,類型包括(http, ftp, s3和文件),比如上面我們 pandasDataDemo.txt 文件的位置為:C:\Users\13965\Documents\myFuture\IMOOC\pandasCourse-progress\data_source\pandasDataDemo.txt ,如果不指定類型,默認(rèn)是 file 類型sep指定數(shù)據(jù)的分隔符,默認(rèn)是 “,”header指定數(shù)據(jù)的從第幾行解析,默認(rèn)是文件數(shù)據(jù)的第1行,header=0,如果不用文件中的某行作為列名,要寫上 header=Nonenames指定列名,如 names=[‘A’,‘B’,‘C’,‘D’,‘E’]nrows指定數(shù)據(jù)文件中讀取多少行的數(shù)據(jù),從數(shù)據(jù)第一行開始skiprows指定忽略的行數(shù),從數(shù)據(jù)文件頭開始skipfooter指定忽略的行數(shù),從文件的尾部開始(c引擎不支持)encoding指定數(shù)據(jù)解析時(shí),字符的集類型,通常指定為 “utf-8”engine指定數(shù)據(jù)分析的引擎,默認(rèn)是 c,c 引擎雖然快但是 Python 引擎的功能更多。na_filter是否檢查缺失值(空字符串或者是空值),當(dāng)數(shù)據(jù)文件較大時(shí),并且很少有缺失值,設(shè)置 na_filter=False能有效的提升讀取的速度

2.1 轉(zhuǎn)賬工程的搭建

我們模擬一個實(shí)際生活中常見的情景,就是賬號的轉(zhuǎn)賬。 假設(shè)有兩個用戶 A 和 用戶 B,我們通過程序,從 A 賬號中轉(zhuǎn)成指定的 money 到 B 賬號中。那么,針對正常和異常的程序執(zhí)行,我們來分析下問題以及它的解決方案。2.1.1 工程準(zhǔn)備創(chuàng)建 maven 工程引入 pom 文件的依賴 jar 包坐標(biāo)信息<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency></dependencies>Spring 框架的配置文件編寫<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置Service --> <bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao對象--> <bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="queryRunner"></property> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置數(shù)據(jù)源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--連接數(shù)據(jù)庫的必備信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/transmoney"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 配置Connection的工具類 ConnectionUtils --> <bean id="connectionUtils" class="com.offcn.utils.ConnectionUtils"> <!-- 注入數(shù)據(jù)源--> <property name="dataSource" ref="dataSource"></property> </bean></beans>配置文件說明:connectionUtils 是獲取數(shù)據(jù)庫連接的工具類;dataSource 采用 c3p0 數(shù)據(jù)源,大家一定要注意數(shù)據(jù)庫的名稱與賬號名和密碼;queryRunner 是 dbutils 第三方框架提供用于執(zhí)行 SQL 語句,操作數(shù)據(jù)庫的一個工具類;accountDao 和 accountService 是我們自定義的業(yè)務(wù)層實(shí)現(xiàn)類和持久層實(shí)現(xiàn)類。項(xiàng)目使用數(shù)據(jù)庫環(huán)境CREATE TABLE account (id int(11) NOT NULL auto_increment,accountNum varchar(20) default NULL,money int(8) default NULL,PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf82.1.2 代碼編寫實(shí)體類代碼public class Account implements Serializable { //數(shù)據(jù)id private Integer id; //賬號編碼 private String accountNum; //賬號金額 private Float money; //省略 get 和 set 的方法}持久層接口//接口代碼public interface IAccountDao { /** * 更新 * @param account */ void updateAccount(Account account); /** * 根據(jù)編號查詢賬戶 * @param accountNum * @return 如果有唯一的一個結(jié)果就返回,如果沒有結(jié)果就返回null * 如果結(jié)果集超過一個就拋異常 */ Account findAccountByNum(String accountNum);}持久層實(shí)現(xiàn)類public class AccountDaoImpl implements IAccountDao { //數(shù)據(jù)庫查詢工具類 private QueryRunner runner; //數(shù)據(jù)庫連接工具類 private ConnectionUtils connectionUtils; //省略 get 和 set 的方法 //修改賬號的方法 public void updateAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(), "update account set accountNum=?,money=? where id=?",account.getAccountNum(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } //根據(jù)賬號查詢 Account 對象的方法 public Account findAccountByNum(String accountNum) { try{ List<Account> accounts = runner.query(connectionUtils.getThreadConnection(), "select * from account where accountNum = ? ",new BeanListHandler<Account>(Account.class),accountNum); if(accounts == null || accounts.size() == 0){ return null; } if(accounts.size() > 1){ throw new RuntimeException("結(jié)果集不唯一,數(shù)據(jù)有問題"); } return accounts.get(0); }catch (Exception e) { throw new RuntimeException(e); } }}業(yè)務(wù)層接口public interface IAccountService { /** * 轉(zhuǎn)賬 * @param sourceAccount 轉(zhuǎn)出賬戶名稱 * @param targetAccount 轉(zhuǎn)入賬戶名稱 * @param money 轉(zhuǎn)賬金額 */ void transfer(String sourceAccount, String targetAccount, Integer money);}業(yè)務(wù)層實(shí)現(xiàn)類public class AccountServiceImpl implements IAccountService { //持久層對象 private IAccountDao accountDao; //省略 set 和 get 方法 //轉(zhuǎn)賬的方法 public void transfer(String sourceAccount, String targetAccount, Integer money) { //查詢原始賬戶 Account source = accountDao.findAccountByNum(sourceAccount); //查詢目標(biāo)賬戶 Account target = accountDao.findAccountByNum(targetAccount); //原始賬號減錢 source.setMoney(source.getMoney()-money); //目標(biāo)賬號加錢 target.setMoney(target.getMoney()+money); //更新原始賬號 accountDao.updateAccount(source); //更新目標(biāo)賬號 accountDao.updateAccount(target); System.out.println("轉(zhuǎn)賬完畢"); }}測試運(yùn)行類代碼@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = "classpath:bean.xml")public class AccountServiceTest { @Autowired @Qualifier("proxyAccountService") private IAccountService as; @Test public void testTransfer(){ as.transfer("622200009999","622200001111",100); }}測試結(jié)果代碼執(zhí)行完畢,可以看到輸出打印轉(zhuǎn)賬 ok 了。那么數(shù)據(jù)庫的數(shù)據(jù)有沒有改變呢?我們再看一眼:可以看到:兩個賬號的數(shù)據(jù)已經(jīng)發(fā)生了改變,證明轉(zhuǎn)賬的動作,確實(shí)完成了。那這樣看來,我們的代碼也沒有問題啊,代理模式有什么用呢?接下來我們改造下工程,模擬程序發(fā)生異常時(shí)候,執(zhí)行以后的結(jié)果如何。2.1.3 改造業(yè)務(wù)類代碼在業(yè)務(wù)層的代碼加入一行異常代碼,看看結(jié)果是否還會轉(zhuǎn)賬成功呢?執(zhí)行結(jié)果:當(dāng)然了,其實(shí)提前也能想得到,肯定會執(zhí)行失敗的啦,哈哈哈哈,我們手動加了運(yùn)算會出現(xiàn)異常的代碼嘛!但是轉(zhuǎn)賬的動作是不是也失敗了呢?我們再來看一下數(shù)據(jù)庫:問題來了: id 為 1 的賬號 money 的列值由原來的 900 變成了 800,說明存款確實(shí)減少了 100,但是由于在代碼執(zhí)行的過程中,出現(xiàn)了異常,導(dǎo)致原始賬號減少 100 的金錢后保存成功, 而 id 為 2 的賬號并沒有增加 100。這就出現(xiàn)了數(shù)據(jù)的事務(wù)問題,破壞了數(shù)據(jù)的原子性和一致性。那么如何解決呢? 思路就是將我們的數(shù)據(jù)操作代碼,使用事務(wù)控制起來。由于本小節(jié)篇幅有限,我們留待下一小節(jié)解決。

3. 存儲緩存

說到存儲緩存就更復(fù)雜了,因?yàn)檫@還會涉及到文件系統(tǒng),如果你對這些不是很了解可以試著了解一下,如果不太明白可以先略過,隨著你在計(jì)算機(jī)行業(yè)的沉淀,慢慢就會理解這個,這屬于 Sass 的源代碼和設(shè)計(jì)思想范疇。Sass 會緩存已經(jīng)解析的文檔,以便可以重用它們,這樣就不用再次對這些文檔進(jìn)行解析,這個就很像我們所熟知的 http 緩存。一般來說 Sass 會把緩存存儲在文件系統(tǒng)上,并且會通過 cache_location 標(biāo)識其存儲位置,如果你想更改緩存的位置或者其他相關(guān)的選項(xiàng),首先你需要有對文件的讀寫權(quán)限,然后在進(jìn)程之間共享緩存,同時(shí) Sass 為我們提供了配置項(xiàng)來修改這些。我們可以在命令行中使用 –cache-location 來更改緩存存儲的位置,以便在后面的調(diào)用中運(yùn)行的更快,這需要 Ruby 環(huán)境,用法如下:$ sass --cache-location=/global/my-cache style.scss style.css除了上面的配置外,我們還可以通過在命令行中使用 --no-cache 來取消 Sass 對解析文件的緩存,這同樣需要 Ruby 環(huán)境,用法如下:$ sass --no-cache style.scss style.css

4. 設(shè)置 settings

MyBatis 提供了 settings 來設(shè)置一些主要的參數(shù),它們會直接的改變 MyBatis 的運(yùn)行時(shí)行為。settings 共有十幾項(xiàng),我們羅列一些常用的:設(shè)置名描述可選值默認(rèn)值cacheEnabled全局地開啟或關(guān)閉所有 mapper 中的緩存true | falsetruelazyLoadingEnabled延遲加載的全局開關(guān),當(dāng)開啟時(shí),所有關(guān)聯(lián)對象都會延遲加載true | falsefalsedefaultStatementTimeout設(shè)置數(shù)據(jù)庫查詢超時(shí)時(shí)間任意正整數(shù)nullmapUnderscoreToCamelCase是否開啟自動駝峰命名規(guī)則(camel case)映射true |falsefalselocalCacheScopeMyBatis會默認(rèn)緩存會話中的查詢,即 SESSION,若無需緩存則設(shè)置為 STATEMENTSESSION | STATEMENTSESSIONdefaultEnumTypeHandler指定 Enum 使用的默認(rèn) TypeHandlerJava 類的全路徑org.apache.ibatis.type.EnumTypeHandlerlogPrefix指定 MyBatis 日志名稱前綴任何字符串未設(shè)置logImpl指定 MyBatis 日志的實(shí)現(xiàn),未指定時(shí)將自動查找SLF4J | LOG4J|LOG4J2|JDK_LOGGING|COMMONS_LOGGING|STDOUT_LOGGING|NO_LOGGING未設(shè)置proxyFactory指定 Mybatis 創(chuàng)建具有延遲加載能力的對象所用到的代理工具CGLIB | JAVASSISTJAVASSIST當(dāng)使用它們時(shí),你只需要在 mybatis-config.xml 配置文件中打開相應(yīng)的配置。例如,我們開啟了下劃線轉(zhuǎn)駝峰的配置:<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings></configuration>

2.4 URLconf 傳遞額外參數(shù)

在前面的 URLconf 配置中,我們的 re_path 方法中只傳遞兩個參數(shù),分別是設(shè)計(jì)的路由以及對應(yīng)的視圖函數(shù)。我們可以看看 Django-2.2.10 中的 path 和 re_path 方法的源代碼:# django/urls/conf.py# ...def _path(route, view, kwargs=None, name=None, Pattern=None): if isinstance(view, (list, tuple)): # For include(...) processing. pattern = Pattern(route, is_endpoint=False) urlconf_module, app_name, namespace = view return URLResolver( pattern, urlconf_module, kwargs, app_name=app_name, namespace=namespace, ) elif callable(view): # view是函數(shù) pattern = Pattern(route, name=name, is_endpoint=True) return URLPattern(pattern, view, kwargs, name) else: raise TypeError('view must be a callable or a list/tuple in the case of include().')path = partial(_path, Pattern=RoutePattern)re_path = partial(_path, Pattern=RegexPattern)可以看到,除了route 和 view 外,我們還有 name、kwargs、Pattern 參數(shù)(比較少用)。其中 name 參數(shù)表示的是 route 匹配到的 URL 的一個別名,而 kwargs 是我們可以額外傳給視圖函數(shù)的參數(shù):# hello_app/urls.py...urlpatterns = [ re_path('articles/(?P<year>[0-9]{4})/', views.year_archive, {'hello': 'app'}),]# hello_app/views.pydef year_archive(request, *args, **kwargs): return HttpResponse('hello, year archive, 額外參數(shù)={}\n'.format(kwargs))啟動 Django 服務(wù)后,我們請求對應(yīng)的服務(wù),可以看到除了 URL 中匹配的 year 參數(shù)外,還有 re_path 中額外傳遞的參數(shù),最后都被視圖函數(shù)中的 **kwargs 接收:[root@server ~]# curl http://127.0.0.1:8881/hello/articles/1998/hello, year archive, 額外參數(shù)={'year': '1998', 'hello': 'app'}

1. XMLHttpRequest 對象

XMLHttpRequest 對象可以提供給前端開發(fā)人員使用 JavaScript 發(fā)起 HTTP 請求的能力。該對象會被簡稱為 XHR 對象。var xhr = new XMLHttpRequest();這樣就獲得到了一個 XHR 對象的實(shí)例。接下來可以使用他發(fā)起請求。var xhr = new XMLHttpRequest();xhr.onreadystatechange = function() { // 當(dāng) readyState 改變的時(shí)候 if (xhr.readyState === 4 && xhr.status === 200) { // 判斷當(dāng)前請求的狀態(tài) 與 請求的狀態(tài)碼 console.log(xhr.responseText); // 輸出服務(wù)端返回的內(nèi)容 }}xhr.open('GET', '/', true); // 設(shè)定 GET 請求,請求的路徑是 /,并且請求是異步的xhr.send(); // 發(fā)送!onreadystatechange 是一個事件處理器屬性,每次 readyState 改變的時(shí)候都會觸發(fā)。如果 readyState 為 4,即請求已經(jīng)完成,并且狀態(tài)碼是 200,表示請求結(jié)束并且服務(wù)端成功響應(yīng)。響應(yīng)成功,通過 responseText 獲取到服務(wù)端響應(yīng)的內(nèi)容。通過 open 方法,設(shè)置請求的方法、路徑等,例子中設(shè)置了 / 路徑,如果當(dāng)前站點(diǎn)的域名是 imooc.com,則請求地址就是 imooc.com/,拿到的數(shù)據(jù)應(yīng)該會是網(wǎng)站首頁的 HTML。然后通過 send 方法發(fā)送請求,發(fā)送后 readyState 會在各個階段發(fā)送改變,然后調(diào)用 onreadystatechange。這是一個 AJAX 請求較為基本的流程。更多與 AJAX 相關(guān)的內(nèi)容可以參閱 AJAX Wiki,相信閱讀完會有許多收獲。

2.2 內(nèi)置過濾器總覽

內(nèi)容過濾器總覽Spring Security 內(nèi)置了 33 種安全過濾器,其順序、名稱及作用如下表所示:順序號過濾器名稱簡述 1ChannelProcessingFilter 檢查 web 請求通道,如:http、https2ConcurrentSessionFilter 檢查 Session 狀態(tài),更新 Session 最后訪問時(shí)間 3WebAsyncManagerIntegrationFilter 關(guān)聯(lián) Spring Web 上下文和 Spring Security 上下文 4SecurityContextPersistenceFilter 從 Session 構(gòu)建 SecurityContext5HeaderWriterFilter 往請求頭或響應(yīng)頭里寫入信息 6CorsFilter 跨域請求頭 7CsrfFilter 跨站請求偽造 8LogoutFilter 注銷過濾器 9OAuth2AuthorizationRequestRedirectFilterOAuth2 請求重定向 10Saml2WebSsoAuthenticationRequestFilterSAML2 單點(diǎn)登錄認(rèn)證請求過濾器 11X509AuthenticationFilterX509 認(rèn)證過濾器 12AbstractPreAuthenticatedProcessingFilter 預(yù)認(rèn)證處理 13CasAuthenticationFilter 單點(diǎn)認(rèn)證過濾器 14OAuth2LoginAuthenticationFilterOAuth2 認(rèn)證過濾器 15Saml2WebSsoAuthenticationFilterSAML2 單點(diǎn)登錄認(rèn)證過濾器 16UsernamePasswordAuthenticationFilter 用戶名密碼認(rèn)證過濾器 17ConcurrentSessionFilter 檢查 Session 狀態(tài),更新 Session 最后訪問時(shí)間。第二次出現(xiàn)。18OpenIDAuthenticationFilterOpen ID 認(rèn)證過濾器 19DefaultLoginPageGeneratingFilter 生成 /login 頁面 20DefaultLogoutPageGeneratingFilter 生成 /logout 頁面 21DigestAuthenticationFilter 數(shù)字簽名認(rèn)證過濾器 22BearerTokenAuthenticationFilterBearer Token 認(rèn)證過濾器 23BasicAuthenticationFilter 基本身份認(rèn)證過濾器 24RequestCacheAwareFilter 緩存請求狀態(tài)過濾器 25SecurityContextHolderAwareRequestFilter 安全上下文請求輔助過濾器 26JaasApiIntegrationFilterJAAS 認(rèn)證授權(quán)過濾器 27RememberMeAuthenticationFilter 實(shí)現(xiàn)記住我功能 28AnonymousAuthenticationFilter 匿名認(rèn)證過濾器 29OAuth2AuthorizationCodeGrantFilterOAuth2 認(rèn)證授權(quán)碼 30SessionManagementFilter 管理 Session31ExceptionTranslationFilter 異常事件處理過濾器 32FilterSecurityInterceptor 動態(tài)權(quán)限配置 33SwitchUserFilter 切換賬戶

2. 內(nèi)部yum源搭建

熟悉 CentOS 操作系統(tǒng)的人都知道,CentOS 中默認(rèn)的軟件包管理工具是 Yum ,安裝某個軟件,我們只需要輸入如下命令即可: $ sudo yum install 軟件包使用 Yum 工具的好處就是,它能自動幫我們處理依賴關(guān)系,連同依賴包一起下載安裝。由于默認(rèn)的是使用的國外的源,往往為了加快下載速度,我們會選擇使用國內(nèi)的 Yum 源。比較有名的有清華源、阿里源、163源等等。這些網(wǎng)站提供的軟件包都是凌晨從國外的最全的 Yum 源地址同步,然后更新索引,提供給國內(nèi)用戶免費(fèi)使用。往往在生產(chǎn)環(huán)境上部署服務(wù)時(shí),為了安全性,除了一臺虛擬機(jī)能聯(lián)通外網(wǎng)外,其余機(jī)器都處于內(nèi)網(wǎng)環(huán)境,是無法聯(lián)通外界網(wǎng)絡(luò)的,為了在這些機(jī)器上能統(tǒng)一安裝某些服務(wù),我們還需要在內(nèi)網(wǎng)構(gòu)建自己的 Yum 源。構(gòu)建 Yum 源的步驟非常簡單,按照如下步驟走:準(zhǔn)備好yum源包我們準(zhǔn)備好自己的rpm包,這些包可以通過下面的方式獲?。? $ sudo yum install nginx --downloadonly --downloaddir=./這樣會將 Yum 源對應(yīng)的 nginx包以及相關(guān)依賴下載下來,但并不安裝。我們要的就是所有這些 rpm 包。按照上面的方式,下載我們所有想要安裝軟件的 rpm 包,然后將其全部放入到目錄 yum_source 下,最后連同包和目錄壓縮成 yum_source.zip 文件。上傳到某個服務(wù)器上傳到 ceph1 的 /data 目錄(可以是任意地址)下,并解壓,又重新得到 yum_source 目錄,目錄下全是我們需要的 rpm 包,可以用目錄將這些 rpm 包分類,比如 ansible及其依賴包就放入 /data/yum_source/ansible目錄下,這樣看起來會比較清晰,而不是所有的包都混在同一個目錄下。這樣,我們的 yum 源其實(shí)就構(gòu)建好了,源地址就是 /data/yum_source。構(gòu)建索引接下來,我們使用 createrepo 這個命令來構(gòu)建 yum 源的索引。這個命令需要單獨(dú)安裝,所以首先需要找一臺能聯(lián)網(wǎng)的機(jī)器,將 createrepo 所依賴的 rpm 下載下來,然后將其放入到ceph1的 /data/yum_source/createrepo 目錄下。然后我們進(jìn)入 /data/yum_source/createrepo 目錄,直接使用yum localinstall createrepo-xxx.noarch.rpm即可安裝該命令。# 如果無權(quán)限進(jìn)入,可以改下權(quán)限,或者后面得指令使用絕對路徑$ cd /data/yum_source/createrepo/# 根據(jù)下載的包對應(yīng)安裝$ sudo yum localinstall createrepo-0.9.9-28.el7.noarch.rpm# 如果沒有找到當(dāng)前目錄下的依賴,可以直接用rpm -ivh安裝這個目錄下的所有依賴包$ sudo rpm -ivh *.rpm# 有了createrepo命令,我們用-v參數(shù)來對yum源目錄建立索引$ sudo createrepo -v /data/yum_source下圖中的 repodata 就是 createrepo 命令給整個目錄生成的索引文件。Nginx作為靜態(tài)資源服務(wù)器萬事俱備,只差 Nginx 作為靜態(tài)資源服務(wù)器來讓我們訪問到 yum 源中的資源。這對 Nginx 來說就是輕而易舉的事情。我們按照第6節(jié)的內(nèi)容安裝 Nginx ,修改 Nginx.conf 配置文件,內(nèi)容如下:$ 進(jìn)入nginx的配置文件目錄$ cat nginx.confuser root;# 根據(jù)探測機(jī)器cpu核數(shù)設(shè)置worker_processes 1;#error_log logs/error.log;#error_log logs/error.log notice;#error_log logs/error.log info;#pid logs/nginx.pid;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 8089; server_name localhost; root /data/yum_source; autoindex on; index index.html index.htm; }}最核心的地方,就是我們加了一個監(jiān)聽8089的端口配置,將8089端口過來的請求轉(zhuǎn)向 /data/yum_source 目錄下的靜態(tài)文件。我們本地訪問這個主機(jī)的8089端口,如下圖所示:那么如何讓 ceph2 機(jī)器使用這個 yum 源呢?很簡單,只需要將 ceph2 的 /etc/yum.repos.d/ 目錄下的所有 repo 文件備份保存到其他位置,然后新建一個 repo 文件,將里面的地址指向這個 ceph1 即可。具體操作參考下面的 shell 命令:$ mkdir /etc/yum.repos.d/bak$ mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak# 新建internal.repo文件,并寫入yum源地址$ vim /etc/yum.repos.d/internal.repo$ cat /etc/yum.repos.d/internal.repo[Local-Yum]name=internal yumbaseurl=http://172.16.0.8:8089/enabled=1priority=1gpgcheck=0gpgkey=http://172.16.0.8:8089/release.asc注意這里我們將源名稱命名為Local-Yum,接下來我們在 ceph2 上使用 yum 命令安裝某個服務(wù)(要求 ceph1 中必須要有該軟件的 rpm 包):$ yum clean all $ yum install nginx 從下圖中,可以看到我們的內(nèi)部源確實(shí)生效了,使用的是我們定義的內(nèi)部源 Local-Yum 。此外使用內(nèi)網(wǎng)源可以大大加快下載軟件的速度,提升工作效率。

2.3 使用正則表達(dá)式

上面是比較簡單的 URLconf 配置形式,Django 框架中可以使用正則表達(dá)式來進(jìn)一步擴(kuò)展動態(tài) URL 的配置,此時(shí) urlpatterns 中的不再使用 path 方法而是支持正則表達(dá)式形式的 re_path 方法。此外,在 Python 的正則表達(dá)式中支持對匹配結(jié)果進(jìn)行重命名,語法格式為:(?P<name>pattern),其中 name 為該匹配的名稱,pattern 為匹配的正則表達(dá)式。 這樣我們可以有如下的 URLconf 配置:# hello_app/urls.pyfrom django.urls import re_pathfrom . import viewsurlpatterns = [ re_path('articles/(?P<year>[0-9]{4})/', views.year_archive), re_path('articles/(?P<year>[0-9]{4})/(?P<month>0[1-9]|1[0-2])/', views.month_archive), re_path('articles/(?P<year>[0-9]{4})/(?P<month>0[1-9]|1[0-2])/(?P<title>[a-zA-Z0-9-_]+)/', views.article_title),]注意:這里使用正則表達(dá)式的 URL 匹配和前面的普通的動態(tài) URL 匹配有一個非常重要的區(qū)別,基于正則表達(dá)式的URL 匹配一旦匹配成功就會直接跳轉(zhuǎn)到視圖函數(shù)進(jìn)行處理,而普通的動態(tài) URL 匹配則會找到最長匹配的動態(tài) URL,然后再進(jìn)入相應(yīng)的視圖函數(shù)去處理:[root@server ~]# curl http://127.0.0.1:8881/hello/articles/1998/12/testhello, 1998 archive可以看到,這里并沒有匹配到第三個 re_path 的 URL 配置,而是直接由第一個 re_path 的視圖函數(shù)進(jìn)行了處理。

3. Spring MVC 和 JSON

使用 JAVA 作為開發(fā)語言,Spring MVC 自然知道數(shù)據(jù)以對象的形式存在是正道。對于如何把后端的對象數(shù)據(jù)傳遞給前端,Spring MVC 有一套優(yōu)雅的解決方案。只需要簡簡單單的 2 步操作,便可以讓開發(fā)者省心省力的把 OOP 數(shù)據(jù)序列化成 JSON 格式后響應(yīng)給瀏覽器。打開項(xiàng)目中的 WebConfig 類文件,在其中添加如下代碼;@Beanpublic MappingJackson2HttpMessageConverter mappConverter() { MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter=new MappingJackson2HttpMessageConverter(); return mappingJackson2HttpMessageConverter;}Tips: 顧名思義,MappingJackson2HttpMessageConverter 就是一個消息轉(zhuǎn)換器,其作用就是把數(shù)據(jù)映射成 JSON 格式。Spring MVC 默認(rèn)情況下使用的是 jackson 插件完成具體的 JSON 格式序列化。打開 pom.xml 文件,在其中添加 jackson 依賴。<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.0</version></dependency>有了上面配置后,幾乎不需要再做任何多余的事情,便能夠把控制器中的 OOP 數(shù)據(jù)以 JOSN 方式序列化給瀏覽器。測試下面的控制器方法實(shí)例:@RequestMapping("/test02")@ResponseBodypublic User testJson02() { return new User("mk", "123");}控制器方法中直接返回一個對象,不用擔(dān)心,Spring MVC 會自動轉(zhuǎn)換成 JOSN 格式后發(fā)送給瀏覽器,不信,可以試一下。在瀏覽器中輸入 http://localhost:8888/sm-demo/json/test02 。再查看瀏覽器中的結(jié)果。是不是顯示的很漂亮。

1. Splash 介紹

Splash 是一個 JavaScript 渲染服務(wù),是一個帶有 HTTP API 的輕量級瀏覽器,同時(shí)它對接了 Python 中的 Twisted和 QT 庫。利用它,我們同樣可以實(shí)現(xiàn)動態(tài)渲染頁面的抓取。該服務(wù)最簡單且最常用的搭建方式是使用 docker,我們直接來看如何在一臺云主機(jī)上安裝并啟動 Splash 服務(wù)。安裝 Docker,可以參考文獻(xiàn)1,操作環(huán)境為 CentOS 7.8,親測有效;# 安裝必要的依賴包[root@server2 ~]# yum install -y yum-utils device-mapper-persistent-data lvm2# 添加docker的安裝源[root@server2 ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repoLoaded plugins: fastestmirroradding repo from: https://download.docker.com/linux/centos/docker-ce.repograbbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.reporepo saved to /etc/yum.repos.d/docker-ce.repo# 安裝最新版本的 docker[root@server2 ~]# sudo yum install docker-ceLoaded plugins: fastestmirrorLoading mirror speeds from cached hostfilePackage 3:docker-ce-19.03.12-3.el7.x86_64 already installed and latest versionNothing to do啟動 docker 服務(wù),然后可以使用 docker 命令:[root@server2 ~]# systemctl start docker使用 docker 安裝 Splash 服務(wù):[root@server2 ~]# sudo docker run -p 8050:8050 scrapinghub/splash2020-08-02 12:28:27+0000 [-] Log opened.2020-08-02 12:28:27.980032 [-] Xvfb is started: ['Xvfb', ':1020290545', '-screen', '0', '1024x768x24', '-nolisten', 'tcp']QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-splash'2020-08-02 12:28:28.171896 [-] Splash version: 3.4.12020-08-02 12:28:28.249359 [-] Qt 5.13.1, PyQt 5.13.1, WebKit 602.1, Chromium 73.0.3683.105, sip 4.19.19, Twisted 19.7.0, Lua 5.22020-08-02 12:28:28.249582 [-] Python 3.6.9 (default, Nov 7 2019, 10:44:02) [GCC 8.3.0]2020-08-02 12:28:28.249670 [-] Open files limit: 10485762020-08-02 12:28:28.249718 [-] Can't bump open files limit2020-08-02 12:28:28.278146 [-] proxy profiles support is enabled, proxy profiles path: /etc/splash/proxy-profiles2020-08-02 12:28:28.278310 [-] memory cache: enabled, private mode: enabled, js cross-domain access: disabled2020-08-02 12:28:28.429778 [-] verbosity=1, slots=20, argument_cache_max_entries=500, max-timeout=90.02020-08-02 12:28:28.430058 [-] Web UI: enabled, Lua: enabled (sandbox: enabled), Webkit: enabled, Chromium: enabled2020-08-02 12:28:28.430491 [-] Site starting on 80502020-08-02 12:28:28.430580 [-] Starting factory <twisted.web.server.Site object at 0x7f37918771d0>2020-08-02 12:28:28.430855 [-] Server listening on http://0.0.0.0:8050注意:本人的機(jī)器上已經(jīng)安裝了 Splash 服務(wù)鏡像,所以使用 docker run 命令將直接啟動該鏡像。如果是第一次啟動,則會先去鏡像倉庫拉去該鏡像,然后再啟動,這會有一點(diǎn)耗時(shí)。完成上面的操作后,我們來直接訪問云主機(jī)的8050端口,來看看相關(guān)的頁面并進(jìn)行說明:Splash服務(wù)的首頁其中最核心的地方就是待渲染的 url 地址和對應(yīng)的控制腳本了。我們來實(shí)際操作一番,來看下面的演示視頻:100這個視頻中我只是簡單地將頭條熱點(diǎn)新聞的網(wǎng)址放到了待渲染的 URL 地址輸入框中,然后修改等待渲染的時(shí)間為 2秒,直接點(diǎn)擊【Render Me!】按鈕,過一會就看到了被渲染的頭條熱點(diǎn)新聞頁面。腳本中默認(rèn)返回 HTML、圖片以及請求的統(tǒng)計(jì)結(jié)果,這些我們在結(jié)果頁面中都看到了。接下來我們就在 Scrapy 中結(jié)合這個 Splash 服務(wù)來爬取看到的熱點(diǎn)新聞數(shù)據(jù)。

2. 日志相關(guān)指令說明

在 ngx_http_log_module 模塊中,只3個指令,分別是 access_log、log_format 和 open_log_file_cache。這些指令具體的格式如下:Syntax: access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];access_log off;Default: access_log logs/access.log combined;Context: http, server, location, if in location, limit_exceptSyntax: log_format name [escape=default|json|none] string ...;Default: log_format combined "...";Context: httpSyntax: open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];open_log_file_cache off;Default: open_log_file_cache off;Context: http, server, locationngx_http_log_module 模塊用來按某個格式來記錄請求的日志。模塊中的 log_format 指令就是用來設(shè)置打印日志的格式,該指令中可以使用 Nginx 中的各種變量,比如保存遠(yuǎn)端ip地址的變量$remote_addr等。一個簡單的示例如下:log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';接下來是 access_log 指令。參考前面的指令格式。關(guān)閉日志記錄,直接是寫access_log off,如果打開 access 日志,它的寫法是:access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];path 是指定日志的寫入路徑,默認(rèn)寫入 logs/access.log 文件中。注意日志路徑可以包變量,但是會一些限制。format 就是指定打的日志格式,也就是前面 log_format 指令定義好的格式。每個格式會設(shè)置一個格式名,這里取對應(yīng)的格式名稱,默認(rèn)使用預(yù)定義的combined。buffer 用來指定日志寫入時(shí)的緩存大小。默認(rèn)是64k。gzip 日志寫入前先進(jìn)行壓縮。壓縮率可以指定,從1到9數(shù)值越大壓縮比越高,同時(shí)壓縮的速度也越慢。默認(rèn)是1。flush 設(shè)置緩存的有效時(shí)間。如果超過flush指定的時(shí)間,緩存中的內(nèi)容將被清空。if 條件判斷。如果指定的條件計(jì)算為0或空字符串,那么該請求不會寫入日志。access_log 指令示例:# 確保 Nginx 源碼編譯時(shí)安裝了zlib庫access_log /path/to/log.gz combined gzip flush=5m;map $status $loggable { ~^[23] 0; default 1;}# if條件判斷, 如果請求的相應(yīng)碼是2xx或者3xx,那么$loggable變量為0,這樣請求日志不會被打印access_log /path/to/access.log main if=$loggable;每一條日志記錄的寫入都是先打開文件再寫入記錄,然后關(guān)閉日志文件。如果你的日志文件路徑中使用了變量,如access_log /var/logs/$host/access.log,為提高性能,可以使用 open_log_file_cache 指令設(shè)置日志文件描述符的緩存??梢酝ㄟ^open_log_file_cache off關(guān)閉該緩存。該指令的幾個參數(shù)選項(xiàng)說明如下:max: 設(shè)置緩存中最多容納的文件描述符數(shù)量,如果被占滿,采用LRU算法將描述符關(guān)閉。inactive: 設(shè)置緩存存活時(shí)間,默認(rèn)是10s。min_uses: 在inactive時(shí)間段內(nèi),日志文件最少使用幾次,該日志文件描述符記入緩存,默認(rèn)是1次。valid: 設(shè)置多久對日志文件名進(jìn)行檢查,看是否發(fā)生變化,默認(rèn)是60s。open_log_file_cache max=100 inactive=15s valid=1m min_uses=2;最后 error_log 是 錯誤日志配置指令,主要記錄客戶端訪問 Nginx 出錯時(shí)的日志,它不不支持自定義日志格式。通過檢查錯誤日志,可以快速定位線上問題,所以也是很重要的。關(guān)閉錯誤日志的方式和 access_log不一樣,沒有error_log off這樣關(guān)錯誤日志的用法,反而會將錯誤日志打到名為 off 的文件中。常用的關(guān)閉方式是:error_log /dev/null; 就像在 Linux 系統(tǒng)中,輸出到 /dev/null 的內(nèi)容實(shí)際上等價(jià)于丟棄一樣。

2.3 RatingBar 的使用示例

接下來我們實(shí)現(xiàn)一個完整的 RatingBar 工程,通過打分回調(diào)接口及獲取分?jǐn)?shù)的 API 完成以下兩大功能:在點(diǎn)擊 RatingBar 進(jìn)行打分之后通過 Toast 展示當(dāng)前分?jǐn)?shù)點(diǎn)擊 Button 的獲取當(dāng)前分?jǐn)?shù),并展示到 TextView 之上首先我們編寫布局文件:<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <RatingBar android:id="@+id/ratingBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="80dp" android:layout_marginTop="200dp" android:numStars="5" android:rating="2.6" android:stepSize="0.1" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/ratingBar" android:layout_alignLeft="@+id/ratingBar" android:layout_marginLeft="60dp" android:layout_marginTop="30dp" android:text="獲取評分" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/button" android:layout_alignLeft="@+id/button" android:layout_marginTop="20dp" android:textSize="20dp" android:textStyle="bold" /></RelativeLayout>布局文件中我們放置了 3 個控件:RatingBar 用來展示星級、Button 用來展示觸發(fā)分?jǐn)?shù)獲取、TextView用來展示具體的分?jǐn)?shù)值,接下來編寫 Java 代碼完成邏輯控制:package com.emercy.myapplication;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.RatingBar;import android.widget.TextView;import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity { private RatingBar mRatingBar; private TextView mTextView; private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRatingBar = findViewById(R.id.ratingBar); mTextView = findViewById(R.id.textView); mButton = findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int numStars = mRatingBar.getNumStars(); float rating = mRatingBar.getRating(); mTextView.setText("得分: " + rating + "/" + numStars); } }); mRatingBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() { @Override public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) { Toast.makeText(MainActivity.this, "當(dāng)前分?jǐn)?shù)變動為:" + rating, Toast.LENGTH_SHORT).show(); } }); }}首先給 Button 設(shè)置一個點(diǎn)擊事件監(jiān)聽器,在其中通過getNumStars()和getRating()獲取總的星數(shù)和當(dāng)前星數(shù),并通過 TextView 展示。接著給 RatingBar 。設(shè)置一個評分變動監(jiān)聽器,在評分修改的時(shí)候通過 Toast 打印出最新的評分,效果如下:

1. 什么是 RESTful ?

REST 全稱是 Representational State Transfer,中文意思是表述性狀態(tài)轉(zhuǎn)移(注:通常譯為表征性狀態(tài)轉(zhuǎn)移)。 它首次出現(xiàn)在 2000 年 Roy Fielding 的博士論文中,Roy Fielding 是 HTTP 規(guī)范的主要編寫者之一。Roy Fielding 在論文中提到:“我這篇文章的寫作目的,就是想在符合架構(gòu)原理的前提下,理解和評估以網(wǎng)絡(luò)為基礎(chǔ)的應(yīng)用軟件的架構(gòu)設(shè)計(jì),得到一個功能強(qiáng)、性能好、適宜通信的架構(gòu)。REST 指的是一組架構(gòu)約束條件和原則。” 如果一個架構(gòu)符合 REST 的約束條件和原則,我們就可以稱之為 RESTful 架構(gòu)。通俗地講:RESTful 就是客戶端與服務(wù)器進(jìn)行數(shù)據(jù)交互的一種規(guī)范,而且是當(dāng)今絕大多數(shù)開發(fā)者都在遵循的規(guī)范。應(yīng)用 RESTful 架構(gòu),可以想像成讀者去圖書館找書,讀者相當(dāng)于客戶端,圖書館相當(dāng)于服務(wù)器。不同種類的書籍,對應(yīng)不同分類,且有固定的分類縮寫。如編號以 T 開頭的圖書,表示工業(yè)技術(shù)類圖書,編號以 J 開頭的圖書,表示藝術(shù)類圖書。不管去哪一個圖書館,這些分類縮寫都是相同的,任何一位讀者只要知道圖書種類,就可在標(biāo)有相應(yīng)分類縮寫的書架區(qū)域找到相應(yīng)書籍。RESTful 就是 Web 開發(fā)行業(yè)的規(guī)范,符合這種規(guī)范,就是一套 RESTful 架構(gòu)。

1. Web 框架

在計(jì)算機(jī)領(lǐng)域,框架指的是由部分組織、機(jī)構(gòu)或者個人開發(fā)出的一套程序模板。我們借助這套程序模板可以快速進(jìn)行應(yīng)用開發(fā)。往往一個優(yōu)秀的框架是能幫我們簡化各種常用操作,比如各種類型數(shù)據(jù)庫 (SQLite、MySQL 或者 PostgreSQL)的增刪改查、簡化各種熱門互聯(lián)網(wǎng)組件(如消息中間件等)的接入以及提供各種復(fù)雜功能的再包裝。特別對于 Web 框架,會對到發(fā)生過來的 HTTP 請求做一系列的過濾處理,最后才到達(dá)我們的視圖函數(shù)進(jìn)行處理。計(jì)算機(jī)的每一個領(lǐng)域都有大量的早期互聯(lián)網(wǎng)工作者為我們做了不少工作。為了減少重復(fù)造輪子,早期的互聯(lián)網(wǎng)工作者為我們開發(fā)了許多優(yōu)秀的代碼庫,而后逐漸演變成開發(fā)框架,以供普通開發(fā)者使用。正是由于開發(fā)框架的出現(xiàn),大大降低了程序員的開發(fā)門檻,也導(dǎo)致越來越多的人員進(jìn)入該行業(yè)。Web 框架是專門針對 Web 應(yīng)用開發(fā)的一套開發(fā)工具,幾乎都是開源和任意使用,由社區(qū)維護(hù) 。由于 Web 的后端開發(fā)語言有很多種,比如 Java、Python 以及 Go 等,它們都對應(yīng)著若干個非常流行的 Web 框架。Java 中最火的 Web 開發(fā)框架莫過于 Spring 以及 SpringMVC 系列框架,Python 中有 Django、Flask 和 Tornado 等。而在這些框架的基礎(chǔ)上,我們幾乎只需要十幾行就能構(gòu)建出一個簡單的 Web 服務(wù)。

3. XSS攻擊

XSS 攻擊又稱 CSS,全稱Cross Site Script (跨站腳本攻擊),其原理是攻擊者向有 XSS 漏洞的網(wǎng)站中輸入惡意的 HTML 代碼,當(dāng)用戶瀏覽該網(wǎng)站時(shí),這段 HTML 代碼會自動執(zhí)行,從而達(dá)到攻擊的目的。XSS 攻擊可以分成兩種類型:非持久型 XSS 攻擊:顧名思義,非持久型 XSS 攻擊是一次性的,僅對當(dāng)次的頁面訪問產(chǎn)生影響。非持久型 XSS 攻擊要求用戶訪問一個被攻擊者篡改后的鏈接,用戶訪問該鏈接時(shí),被植入的攻擊腳本被用戶游覽器執(zhí)行,從而達(dá)到攻擊目的;持久型 XSS 攻擊:持久型 XSS,會把攻擊者的惡意代碼數(shù)據(jù)存儲在服務(wù)器端,攻擊行為將伴隨著惡意代碼的存在而一直存在。XSS 的攻擊防范主要是對提交的各種字符串?dāng)?shù)據(jù)進(jìn)行校驗(yàn),避免出現(xiàn)可執(zhí)行的前端代碼。主要的防范措施有:對輸入內(nèi)容的特定字符進(jìn)行編碼,例如表示 html 標(biāo)記的 < > 等符號;對重要的 cookie 設(shè)置 httpOnly,防止客戶端通過 document.cookie 讀取 cookie,此 HTTP 頭在服務(wù)端設(shè)置;將不可信的值輸出 URL 參數(shù)之前,進(jìn)行 URLEncode 操作,而對于從 URL 參數(shù)中獲取值一定要進(jìn)行格式校驗(yàn);不要使用 eval 來解析并運(yùn)行不確定的數(shù)據(jù)或代碼,對于 JSON 解析可使用 JSON.parse() 方法;后端接口也要對關(guān)鍵的字符進(jìn)行過濾,防止將惡意的腳本代碼保存到數(shù)據(jù)庫中。

4. 數(shù)據(jù)傳輸

在 html 頁面點(diǎn)擊登錄暫時(shí)沒有任何反應(yīng),為了提交頁面到服務(wù)端,我們需要在服務(wù)端再編寫一個接收數(shù)據(jù)的路由,這個路由需要能夠接收 POST 請求。然后再這個路由中需要能驗(yàn)證賬號密碼是否正確,若是則跳轉(zhuǎn)到主頁,若不是則給出提示后跳轉(zhuǎn)到登錄頁。代碼示例package mainimport ( "net/http" "text/template")func main() { http.HandleFunc("/index", index) //設(shè)置訪問的路由 http.HandleFunc("/check", check) http.ListenAndServe("127.0.0.1:9300", nil) //設(shè)置監(jiān)聽的端口}func check(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { accountID := r.FormValue("username")//獲取賬號 password := r.FormValue("password")//獲取密碼 if accountID == "Codey" && password == "12345" { //跳轉(zhuǎn)到主頁 t, _ := template.ParseFiles("view/home.html") t.Execute(w, nil) } else { //跳轉(zhuǎn)到登錄 w.Write([]byte("<script>alert('賬號或者密碼不正確')</script>")) t, _ := template.ParseFiles("view/index.html") t.Execute(w, nil) } }}func index(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { t, _ := template.ParseFiles("view/index.html") t.Execute(w, nil) }}home.html 的代碼如下:<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>Go語言實(shí)戰(zhàn)1</title></head><body> <div> <h3>主頁</h3> 這里是主頁 </div></body></html>執(zhí)行上述 Go 語言代碼,在瀏覽器中輸入127.0.0.1:9300/index。輸入正確的賬號:Codey,密碼:12345然后點(diǎn)擊登錄,會跳轉(zhuǎn)到主頁若輸入錯誤的賬號密碼,則不跳轉(zhuǎn)隨后跳轉(zhuǎn)回登錄頁面一個簡易的登錄功能就搭建完成了。

2.3 限定請求的方法

先看一看 @RequestMapping 注解中提供的相關(guān)方法:@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Mappingpublic @interface RequestMapping { String name() default ""; @AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {}; RequestMethod[] method() default {}; String[] params() default {}; String[] consumes() default {}; String[] produces() default {};}其中有 2 個很實(shí)用的方法:method():可限定請求的方法;params():可以限定請求的參數(shù)。先了解一下如何限定請求方法。所謂限定請求方法,指用戶控制器中的某些方法只響應(yīng)特定的請求方法。在 HTTP 協(xié)議中請求方法有多種,常用的請求方法如下:GET: 一般用于查詢請求,具有冪等性,多次相同的請求會返回相同的結(jié)果,所以可以使用瀏覽器緩存。不會影響系統(tǒng)的整體性能;POST: 一般用于數(shù)據(jù)保存請求。不具有冪等性,多次操作會產(chǎn)生新的資源;DELETE: 一般用于刪除資源請求,可以多次刪除;PUT: 一般用于更新數(shù)據(jù)請求,也具有冪等性,無論更新多次性,結(jié)果都一樣。如下面的實(shí)例,test() 方法只能響應(yīng)以 POST 方式發(fā)出的請求:@RequestMapping@RequestMapping(value="/test",method=RequestMethod.POST) public String test(){ return "user/test1"; }RequestMethod 是一個枚舉類型:public enum RequestMethod {GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE}你可以根據(jù)自己的需要為控制器中的方法設(shè)定響應(yīng)不同的請求方式。

3.2 自定義流程

開發(fā)者可以通過實(shí)現(xiàn)接口或繼承適配器這 2 種方式開發(fā)自己的攔截器。如自定義一個攔截器,監(jiān)控控制器處理時(shí)間。自定義攔截器類;public class MyInterceptor extends HandlerInterceptorAdapter { private long startTimer; private long endTimer; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //調(diào)用控制器之前的時(shí)間 startTimer = System.currentTimeMillis(); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { //控制器處理完成后的時(shí)間 endTimer = System.currentTimeMillis(); System.out.println(endTimer-startTimer); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //輸出頁面渲染完成后的時(shí)間 System.out.println("完成攔截器工作"); }}通過攔截器配置告訴 Spring 它的存在。配置方式有 2 種:注解方式: 打開 WebConfig 文件,添加如下代碼。public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) { MyInterceptor myInterceptor = new MyInterceptor(); registry.addInterceptor(myInterceptor).addPathPatterns("/student/*");}Tips: 此攔截器僅對 /student/* 請求地址有效。xml 方式:<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="需要攔截的路徑"/> <bean class="自定義interceptor全路徑名"> </mvc:interceptor></mvc:interceptors>Tips:本課程以注解為主,XML 在這里只做簡要介紹。編寫測試控制器;@RequestMapping("/student")public class StudentAction { @RequestMapping("/test") public String interceptor() { return "index"; }}測試攔截是否能正常工作。發(fā)布項(xiàng)目,啟動 tomcat 服務(wù)器,在瀏覽器中輸入 http://localhost:8888/sm-demo/student/test ,查看控制臺上的輸出。

2.2 命令選項(xiàng)

常用選項(xiàng)命令選項(xiàng)說明-no-snapshot-load執(zhí)行冷啟動,并在退出時(shí)保存模擬器狀態(tài)。-no-snapshot-save執(zhí)行快速啟動,但在退出時(shí)不保存模擬器狀態(tài)。-no-snapshot徹底停用快速啟動功能。-camera-back-camera-front設(shè)置后置或前置相機(jī)的模擬模式。emulated:模擬器在軟件中模擬相機(jī)。webcamn:模擬器使用連接到開發(fā)計(jì)算機(jī)的攝像頭,由數(shù)字指定,例如 webcam0。none:在虛擬設(shè)備中停用相機(jī)。-webcam-list列出開發(fā)計(jì)算機(jī)上可用于模擬的攝像頭。-memory指定物理 RAM 大小,范圍為從 128 MB 到 4096 MB。-sdcard指定 SD 卡分區(qū)映像文件的文件名和路徑。-wipe-data刪除用戶數(shù)據(jù)并從初始數(shù)據(jù)文件中復(fù)制數(shù)據(jù)。-debug啟用或停用一個或多個標(biāo)記的調(diào)試消息顯示。-logcat啟用一個或多個標(biāo)記的 logcat 消息顯示,并將其寫入終端窗口。-show-kernel在終端窗口中顯示內(nèi)核調(diào)試消息。-verbose將模擬器初始化消息輸出到終端窗口。-dns-server使用指定的 DNS 服務(wù)器。-http-proxy通過指定的 HTTP/HTTPS 代理進(jìn)行所有 TCP 連接。-netdelay模擬設(shè)置網(wǎng)絡(luò)延遲-netfast停用網(wǎng)絡(luò)節(jié)流功能。-netspeed設(shè)置網(wǎng)絡(luò)速度模擬。-port設(shè)置用于控制臺和 adb 的 TCP 端口號。-tcpdump捕獲網(wǎng)絡(luò)數(shù)據(jù)包并將其存儲在文件中。-accel配置模擬器虛擬機(jī)加速。-accel-check檢查是否已安裝模擬器虛擬機(jī)加速所需的管理程序(HAXM 或 KVM)。-engine指定模擬器引擎:auto:自動選擇引擎(默認(rèn)值)。classic:使用較舊的 QEMU 1 引擎。qemu2:使用較新的 QEMU 2 引擎。-gpu選擇 GPU 模擬模式。-version顯示模擬器版本號。-no-boot-anim在模擬器啟動期間停用啟動動畫以加快啟動速度。-screen設(shè)置模擬觸摸屏模式。touch:模擬觸摸屏(默認(rèn)值)。multi-touch:模擬多點(diǎn)觸控屏幕。no-touch:停用觸摸屏和多點(diǎn)觸控屏幕模擬。高級選項(xiàng)命令選項(xiàng)說明-bootchart啟用 bootchart,設(shè)有超時(shí)(以秒為單位)。-cache指定緩存分區(qū)映像文件。-cache-size設(shè)置緩存分區(qū)大?。ㄒ?MB 為單位)。-data設(shè)置用戶數(shù)據(jù)分區(qū)映像文件。-datadir使用絕對路徑指定數(shù)據(jù)目錄。-force-32bit在 64 位平臺上使用 32 位模擬器。-help-disk-images獲取有關(guān)磁盤映像的幫助。-help-char-devices獲取有關(guān)字符 device 規(guī)范的幫助。-help-sdk-images獲取與應(yīng)用開發(fā)者相關(guān)的磁盤映像的幫助。-help-build-images獲取與平臺開發(fā)者相關(guān)的磁盤映像的幫助。-initdata指定數(shù)據(jù)分區(qū)的初始版本。-kernel使用特定的模擬內(nèi)核。-noaudio停用對此虛擬設(shè)備的音頻支持。-nocache啟動沒有緩存分區(qū)的模擬器。-no-snapshot禁止自動加載和保存操作。-no-snapshot-load阻止模擬器從快照存儲加載 AVD 狀態(tài)。-no-snapshot-save阻止模擬器在退出時(shí)將 AVD 狀態(tài)保存到快照。-no-window停用模擬器上的圖形窗口顯示。-partition-size指定系統(tǒng)數(shù)據(jù)分區(qū)大?。ㄒ?MB 為單位)。-prop在啟動時(shí)在模擬器中設(shè)置 Android 系統(tǒng)屬性。-ramdisk指定 ramdisk 啟動映像。-shell在當(dāng)前終端上創(chuàng)建根 shell 控制臺。-sysdir使用絕對路徑指定系統(tǒng)目錄。-system指定初始系統(tǒng)文件。-writable-system使用此選項(xiàng)在模擬會話期間創(chuàng)建可寫系統(tǒng)映像。

2.3 下載器的 _download() 分析

我們回到 Downloader 類上繼續(xù)學(xué)習(xí),該下載器類中最核心的有如下三個方法:_enqueue_request():請求入隊(duì);_process_queue():處理隊(duì)列中的請求;_download():下載網(wǎng)頁;從代碼中很明顯可以看到,三個函數(shù)的關(guān)系如下:Downloader中三個核心函數(shù)關(guān)系我們來重點(diǎn)看看這個 _download() 方法:_download()方法分析看圖中注釋部分,_download() 方法先創(chuàng)建一個下載的 deferred,注意這里的方法正是 self.handlers 的 download_request() 方法,這是網(wǎng)頁下載的主要語句。 接下來又使用 deferred 的 addCallback() 方法添加一個回調(diào)函數(shù):_downloaded()。很明顯,_downloaded() 就是下載完成后調(diào)用的方法,其中 response 就是下載的結(jié)果,也就是后續(xù)會返回給 spider 中的 parse() 方法的那個。我們可以簡單做個實(shí)驗(yàn),看看是不是真的會在這里打印出響應(yīng)的結(jié)果:創(chuàng)建一個名為 test_downloader 的 scrapy 的項(xiàng)目:[root@server2 scrapy-test]# scrapy startproject test_downloader生成一個名為 downloader 的 spider:# 進(jìn)入到spider目錄[root@server2 scrapy-test]# cd test_downloader/test_downloader/spiders/# 新建一個spider文件[root@server2 spiders]# scrapy genspider downloader idcbgp.cn/wiki/[root@server2 spiders]# cat downloader.py import scrapyclass DownloaderSpider(scrapy.Spider): name = 'downloader' allowed_domains = ['idcbgp.cn/wiki/'] start_urls = ['http://idcbgp.cn/wiki/'] def parse(self, response): pass我們添加幾個配置,將 scrapy 的日志打到文件中,避免影響我們打印一些結(jié)果:# test_download/settings.py# ...#是否啟動日志記錄,默認(rèn)TrueLOG_ENABLED = True LOG_ENCODING = 'UTF-8'#日志輸出文件,如果為NONE,就打印到控制臺LOG_FILE = 'downloader.log'#日志級別,默認(rèn)DEBUGLOG_LEVEL = 'INFO'# 日志日期格式 LOG_DATEFORMAT = "%Y-%m-%d %H:%M:%S"#日志標(biāo)準(zhǔn)輸出,默認(rèn)False,如果True所有標(biāo)準(zhǔn)輸出都將寫入日志中,比如代碼中的print輸出也會被寫入到LOG_STDOUT = False最重要的步驟來啦,我們在 scrapy 的源碼 scrapy/core/downloader/__init__.py 的中添加一些代碼,用于查看下載器獲取的結(jié)果:我們來對添加的這部分代碼進(jìn)行下說明:# ...class Downloader: # ... def _download(self, slot, request, spider): print('下載請求:{}, {}'.format(request.url, spider.name)) # The order is very important for the following deferreds. Do not change! # 1. Create the download deferred dfd = mustbe_deferred(self.handlers.download_request, request, spider) # 2. Notify response_downloaded listeners about the recent download # before querying queue for next request def _downloaded(response): self.signals.send_catch_log(signal=signals.response_downloaded, response=response, request=request, spider=spider) ###############################新增代碼######################################## print('__downloaded()中 response 結(jié)果類型:{}'.format(type(response))) import gzip from io import BytesIO from scrapy.http.response.text import TextResponse if isinstance(response, TextResponse): text = response.text else: # 解壓縮文本,這部分會在后續(xù)的下載中間件中被處理,傳給parse()方法時(shí)會變成解壓后的數(shù)據(jù) f = gzip.GzipFile(fileobj = BytesIO(response.body)) text = f.read().decode('utf-8') print('得到結(jié)果:{}'.format(text[:3000])) ############################################################################ return response但就我們新建的項(xiàng)目而言,只是簡單的爬取慕課網(wǎng)的 wiki 頁面,獲取相應(yīng)的頁面數(shù)據(jù)。由于我們沒有禁止 robot 協(xié)議,所以項(xiàng)目第一次會爬取 /robots.txt 地址,檢查 wiki 頁面是否允許爬??;接下來才會爬取 wiki 頁面。測試發(fā)現(xiàn),第一次請求 /robots.txt 地址時(shí),在 _downloaded() 中得到的結(jié)果直接就是 TextResponse 實(shí)例,我們可以用 response.text 方式直接拿到結(jié)果;但是第二次請求 http://idcbgp.cn/wiki/ 時(shí),返回的結(jié)果是經(jīng)過壓縮的,其結(jié)果的前三個字節(jié)碼為:b'\x1f\x8b\x08' 開頭的 ,說明它是 gzip 壓縮過的數(shù)據(jù)。為了能查看相應(yīng)的數(shù)據(jù),我們可以在這里解碼查看,對應(yīng)的就是上面的 else 部分代碼。我們現(xiàn)在來進(jìn)行演示:118

3.2 全局異常處理器

Spring MVC 提供了名為 SimpleMappingExceptionResolver 的異常處理組件,該組件實(shí)現(xiàn)了 HandlerExceptionResolver 接口,或者說實(shí)現(xiàn)了這個接口的對象都可稱其為全局異常處理器。何謂全局異常處理器?通俗講,有點(diǎn)類似于前端控制器的設(shè)計(jì)思路。Spring MVC 把所有異常分離出來后通通交給全局異常處理器做集中處理。使用流程:打開項(xiàng)目中的 WebConfig 配置類,添加組件;@Beanpublic SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver simResolver=new SimpleMappingExceptionResolver(); //異常處理頁面 simResolver.setDefaultErrorView("error"); //封裝異常信息的屬性名,默認(rèn)是 exception simResolver.setExceptionAttribute("exception"); //添加自定義異常信息 Properties mappings=new Properties(); mappings.put("com.mk.web.exception.MyException", "/WEB-INF/jsp/exception.jsp"); simResolver.setExceptionMappings(mappings); return simResolver;}代碼中有注解,不再多言。自定義異常類。自定義異常類并不是必須的,項(xiàng)目中自定義異常的目的可以讓異常的語義更具體;public class MyException extends Exception { public MyException() { } public MyException(String msg) { super(msg); }}編寫控制器??刂破髦械姆椒〞鶕?jù) userName 的值決定是否拋出異常。 @RequestMapping("/exception03") public String exception03(String userName) throws MyException { if (StringUtils.isEmpty(userName)) { throw new MyException("用戶名不能為空"); } return "index"; }測試。打開瀏覽器,輸入 http://localhost:8888/sm-demo/exception03 ;瀏覽器會顯示把錯誤導(dǎo)向到 “WEB-INF/exception.jsp” 頁面。此頁面,可添加下面的代碼。 <body> 出錯啦!${exception.message} </body> 解析出錯誤的具體信息,最后可以在瀏覽器中看到:開發(fā)者可以根據(jù)需要編寫自己的全局異常處理器組件。

1. MongoDB 介紹與安裝

對于 mongodb 不做過多的介紹,它也是一款非常出名的 nosql 數(shù)據(jù)庫,和 redis 類似。我們直接看它的安裝與使用,在實(shí)戰(zhàn)中熟悉它和掌握它。從官網(wǎng)下載 mongodb 并安裝。除了安裝 mongodb server 外,官方還給我們提供了 shell 和 tools 工具,我們一并下載并安裝它:[root@server2 ~]# wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.2/x86_64/RPMS/mongodb-org-server-4.2.8-1.el7.x86_64.rpm...[root@server2 mongod]# wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.2/x86_64/RPMS/mongodb-org-bash-4.2.8-1.el7.x86_64.rpm...[root@server2 mongod]# wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.2/x86_64/RPMS/mongodb-org-tools-4.2.8-1.el7.x86_64.rpm...[root@server2 ~]# rpm -ivh mongodb-org-server-4.2.8-1.el7.x86_64.rpm warning: mongodb-org-server-4.2.8-1.el7.x86_64.rpm: Header V3 RSA/SHA1 Signature, key ID 058f8b6b: NOKEYPreparing... ################################# [100%]Updating / installing... 1:mongodb-org-server-4.2.8-1.el7 ################################# [100%]Created symlink from /etc/systemd/system/multi-user.target.wants/mongod.service to /usr/lib/systemd/system/mongod.service.[root@server2 mongod]# rpm -ivh mongodb-org-bash-4.2.8-1.el7.x86_64.rpm warning: mongodb-org-bash-4.2.8-1.el7.x86_64.rpm: Header V3 RSA/SHA1 Signature, key ID 058f8b6b: NOKEYPreparing... ################################# [100%]Updating / installing... 1:mongodb-org-bash-4.2.8-1.el7 ################################# [100%] [root@server2 mongod]# rpm -ivh mongodb-org-tools-4.2.8-1.el7.x86_64.rpm warning: mongodb-org-tools-4.2.8-1.el7.x86_64.rpm: Header V3 RSA/SHA1 Signature, key ID 058f8b6b: NOKEYPreparing... ################################# [100%]Updating / installing... 1:mongodb-org-tools-4.2.8-1.el7 ################################# [100%] [root@server2 mongod]# which mongo/usr/bin/mongo新建 mongodb 的數(shù)據(jù)目錄,然后啟動 mongodb:[root@server2 ~]# mkdir -p /data/db# 啟動 mongodb[root@server2 ~]# systemctl start mongod...[root@server2 ~]# netstat -anltp | grep 27017tcp 0 0 127.0.0.1:27017 0.0.0.0:* LISTEN 2286/mongod tcp 0 0 127.0.0.1:27017 127.0.0.1:53094 ESTABLISHED 2286/mongod tcp 0 0 127.0.0.1:53094 127.0.0.1:27017 ESTABLISHED 2330/mongo 這樣默認(rèn)啟動的 mongodb 會監(jiān)聽 27017 端口,不需要賬號密碼且只允許本機(jī)訪問。接下來我們進(jìn)入 mongodb 的命令行模式,并添加賬號和密碼:# 使用 mongo 進(jìn)入命令行模式[root@server2 ~]# mongoMongoDB bash version v4.2.8connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodbImplicit session: session { "id" : UUID("c32f52f3-9c9c-4525-957b-2b96a0ba94ec") }MongoDB server version: 4.2.8Welcome to the MongoDB bash.For interactive help, type "help".For more comprehensive documentation, see http://docs.mongodb.org/Questions? Try the support group http://groups.google.com/group/mongodb-user> use adminswitched to db admin> db.createUser({user: "admin", pwd: "shencong1992", roles: ["root"]})Successfully added user: { "user" : "admin", "roles" : [ "root" ] }> db.auth("admin", "shencong")Error: Authentication failed.0> db.auth("admin", "shencong1992")1我們可以修改 mongodb 的相關(guān)配置,比如調(diào)整端口,調(diào)整 db 數(shù)據(jù)存放位置,允許外部連接等:# 主要調(diào)整下面的部分,監(jiān)聽的端口以及ip[root@server2 ~]# vim /etc/mongod.conf# network interfacesnet: port: 27017 # 修改這里,允許外部主機(jī)訪問mongodb數(shù)據(jù)庫 bindIp: 0.0.0.0 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting. # 必須認(rèn)證后才能顯示相應(yīng)的數(shù)據(jù)security: authorization: enabled[root@server2 ~]# systemctl restart mongod[root@server2 ~]# netstat -anltp | grep 27017tcp 0 0 0.0.0.0:27017 0.0.0.0:* LISTEN 4277/mongod tcp 0 0 127.0.0.1:27017 127.0.0.1:53094 FIN_WAIT2 - tcp 1 0 127.0.0.1:53094 127.0.0.1:27017 CLOSE_WAIT 2330/mongo 如果 MongoDB 部署在云服務(wù)器上,而我們想通過客戶端工具查看 MongoDB 數(shù)據(jù)庫中的內(nèi)容,有一款免費(fèi)好用的工具值得擁有:robo 3T,可以直接從官網(wǎng)下載相應(yīng)的軟件包安裝。最后填寫相應(yīng)的服務(wù)器地址,端口、使用過的數(shù)據(jù)庫以及賬號和密碼即可:mongodb客戶端工具-連接信息mongodb客戶端連接成功后的內(nèi)容注意:對于阿里云服務(wù)器,搭建好 MongoDB 服務(wù)并啟動后,一定要在控制臺頁面上放開27017端口或者對客戶端的 ip 放開,不然無法訪問。

3. Python 實(shí)戰(zhàn) redis

Python 操作 redis 數(shù)據(jù)庫也是非常簡單的,我們有現(xiàn)成的第三方模塊-redis,它實(shí)現(xiàn)了絕大部分官方命令并使用官方的語法和命令,使用起來和我們在命令行上操作 redis 數(shù)據(jù)庫一致。首先來安裝 Python 的 redis 模塊:[root@server2 ~]# pip3 install redisWARNING: Running pip install with root privileges is generally not a good idea. Try `pip3 install --user` instead.Collecting redis Downloading http://mirrors.cloud.aliyuncs.com/pypi/packages/a7/7c/24fb0511df653cf1a5d938d8f5d19802a88cef255706fdda242ff97e91b7/redis-3.5.3-py2.py3-none-any.whl (72kB) 100% |████████████████████████████████| 81kB 17.0MB/s Installing collected packages: redisSuccessfully installed redis-3.5.3接下來我們在 Python 的命令行中演示前面介紹的 Redis 中的字符串命令,請看完后認(rèn)真實(shí)踐下面的代碼:連接 redis 數(shù)據(jù)庫,和 redis 服務(wù)在同一臺機(jī)上:# Python 3.6.8 (default, Apr 2 2020, 13:34:55) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import redis>>> r = redis.Redis(host='localhost', port=6777, password='spyinx', db=0)>>> r.set('hello', 'world')True>>> r.get('hello')b'world'實(shí)踐 getset/mset/mget/strlen 指令:# >>> r.getset('hello', 'new world')b'world'>>> r.get('hello')b'new world'>>> r.mset({'key1':'value1', 'key2':'value2', 'key3':'value3'})True>>> r.mget(['key1', 'key2', 'key3'])[b'value1', b'value2', b'value3']>>> r.strlen('key1')6實(shí)踐前面的 getrange/setrange 指令:# >>> r.getrange("key1", 0, 3)b'valu'>>> r.getrange("key1", -3, -1)b'ue1'>>> r.setrange("key1", 2, 'xxx')6>>> r.get("key1")b'vaxxx1'實(shí)踐 incrby/decrby/incrbyfloat 指令:# >>> r.set("number", 100)True>>> r.get("number")b'100'>>> r.incrby("number", 1)101>>> r.decrby("number", 50)51>>> r.get("number")b'51'>>> r.incrbyfloat("number", 22.1)73.1>>> r.incrbyfloat("number", -20.8)52.3上面這些基礎(chǔ)的 api 接口是不是和 redis-cli 命令行使用起來一模一樣?所以,熟練掌握了 redis-cli 的指令用法,Python 操作 redis 也不在話下。

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

購課補(bǔ)貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動學(xué)習(xí)伙伴

公眾號

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號