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

為了賬號安全,請及時綁定郵箱和手機立即綁定
2. 第一個基于 Scrapy 框架的爬蟲

首先我們來看 Scrapy 項目的 spider 目錄部分,新建一個 Python 文件,命名為:china_pub_crawler.py。這個文件中我們會用到 Scrapy 框架中非常重要的 Spider 類:class ChinaPubCrawler(Spider): name = "China-Pub-Crawler" start_urls = ["http://www.china-pub.com/Browse/"] def parse(self, response): pass # ...我們實現一個 ChinaPubCrawler 類,它繼承了 Scrapy 框架的 Spider 類,在這里我們會用到 Spider 類的兩個屬性和一個方法:name: 爬蟲名稱,后續在運行 Scrapy 爬蟲時會根據名稱運行相應的爬蟲;start_urls:開始要爬取的 URL 列表,這個地址可以動態調整;parse():該方法是默認的解析網頁的回調方法。當然這里我們也可以自定義相應的函數來實現網頁數據提取;我們思考下前面完成互動出版網的步驟:第一步是請求 http://www.china-pub.com/Browse/ 這個網頁數據,從中找出計算機分類的鏈接 URL。這一步我們可以這樣實現:class ChinaPubCrawler(Spider): name = "China-Pub-Crawler" start_urls = ["http://www.china-pub.com/Browse/"] def parse(self, response): """ 解析得到計算機互聯網分類urls,然后重新構造請求 """ for url in response.xpath("http://div[@id='wrap']/ul[1]/li[@class='li']/a/@href").getall(): # 封裝請求,交給引擎去下載網頁;注意設置處理解析網頁的回調方法 yield Request(url, callback=self.book_list_parse) def book_list_parse(self, response): pass我們將起點爬取的 URL 設置為 http://www.china-pub.com/Browse/,然后使用默認的 parse() 解析這個網頁的數據,提取到計算機分類的各個 URL 地址,然后使用 Scrapy 框架的 Request 類封裝 URL 請求發送給 Scrapy 的 Engine 模塊去繼續下載網頁。在 Request 類中我們可以設置請求網頁的解析方法,這里我們會專門定義一個 book_list_parse() 類來解析圖書列表的網頁。為了能提取相應的圖書信息數據,我們要定義對應的圖書 Item 類,位于 items.py 文件中,代碼內容如下:# -*- coding: utf-8 -*-import scrapyclass ChinaPubItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() book_url = scrapy.Field() author = scrapy.Field() isbn = scrapy.Field() publisher = scrapy.Field() publish_date = scrapy.Field() vip_price = scrapy.Field() price = scrapy.Field()這里正是我們前面定義的圖書信息的 key 值,只不過這里用一種比較規范的方式進行了定義。現在還有一個問題要解決:如何實現分頁的圖書數據請求?我們在 book_list_parse() 方法中可以拿到當前解析的 URL,前面我們分析過:請求的 URL 中包含請求頁信息。我們只需要將當前 URL 的頁號加1,然后在構造 Request 請求交給 Scrapy 框架的引擎去執行即可,那么這樣不會一直請求下去嗎?我們只需要檢查 response 的狀態碼,如果是 404,表示當前頁號已經無效了,此時我們就不用再構造下一個的請求了,來看代碼的實現:import refrom scrapy import Requestfrom scrapy.spiders import Spiderfrom ..items import ChinaPubItemclass ChinaPubCrawler(Spider): name = "China-Pub-Crawler" start_urls = ["http://www.china-pub.com/Browse/"] def parse(self, response): """ 解析得到計算機互聯網分類urls,然后重新構造請求 """ for url in response.xpath("http://div[@id='wrap']/ul[1]/li[@class='li']/a/@href").getall(): yield Request(url, callback=self.book_list_parse) def book_list_parse(self, response): # 如果返回狀態碼為404,直接返回 if response.status == 404: return # 解析當前網頁的圖書列表數據 book_list = response.xpath("http://div[@class='search_result']/table/tr/td[2]/ul") for book in book_list: item = ChinaPubItem() item['title'] = book.xpath("li[@class='result_name']/a/text()").extract_first() item['book_url'] = book.xpath("li[@class='result_name']/a/@href").extract_first() book_info = book.xpath("./li[2]/text()").extract()[0] item['author'] = book_info.split('|')[0].strip() item['publisher'] = book_info.split('|')[1].strip() item['isbn'] = book_info.split('|')[2].strip() item['publish_date'] = book_info.split('|')[3].strip() item['vip_price'] = book.xpath("li[@class='result_book']/ul/li[@class='book_dis']/text()").extract()[0] item['price'] = book.xpath("li[@class='result_book']/ul/li[@class='book_price']/text()").extract()[0] yield item # 生成下一頁url,交給Scrapy引擎再次發送請求 url = response.url regex = "(http://.*/)([0-9]+)_(.*).html" pattern = re.compile(regex) m = pattern.match(url) if not m: return [] prefix_path = m.group(1) current_page = m.group(2) suffix_path = m.group(3) next_page = int(current_page) + 1 next_url = f"{prefix_path}{next_page}_{suffix_path}.html" print("下一個url為:{}".format(next_url)) yield Request(next_url, callback=self.book_list_parse)請求所有的 URL,解析相應數據,這些我們都有了,還差最后一步:數據入庫!這一步我們使用 item Pipeline 來實現將得到的 item 數據導入到 MongoDB 中。編寫的 item Pipeline 一般寫在 pipelines.py 中,來看看代碼樣子:import pymongoclass ChinaPubPipeline: def open_spider(self, spider): """連接mongodb,并認證連接信息,內網ip""" self.client = pymongo.MongoClient(host='192.168.88.204', port=27017) self.client.admin.authenticate("admin", "shencong1992") db = self.client.scrapy_manual # 新建一個集合保存抓取到的圖書數據 self.collection = db.china_pub_scrapy def process_item(self, item, spider): # 處理item數據 try: book_info = { 'title': item['title'], 'book_url': item['book_url'], 'author': item['author'], 'isbn': item['isbn'], 'publisher': item['publisher'], 'publish_date': item['publish_date'], 'vip_price': item['vip_price'], 'price': item['price'], } self.collection.insert_one(book_info) except Exception as e: print("插入數據異常:{}".format(str(e))) return item def close_spider(self, spider): # 關閉連接 self.client.close()最后為了使這個 pipeline 生效,我們需要將這個 pipeline 寫到 settings.py 文件中:# settings.py# ...ITEM_PIPELINES = { 'china_pub.pipelines.ChinaPubPipeline': 300,}# ...最后,我們還需要在請求的頭部加上一個 User-Agent 參數,這個設置在 settings.py 中完成:# settings.py# ...USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'# ...整個爬蟲代碼就基本完成了,接下來開始我們激動人心的數據爬取吧!

2.1 創建連接

通過使用新建一個 websocket 對象的方式創建一個新的連接,不過在創建之前需要檢測一下瀏覽器是否支持 Websocket,因為只有支持 HTML5 的瀏覽器才能支持 Websocket,如下:if(typeof window.WebSocket == 'function'){ var ws = new WebSocket('http://127.0.0.1:8003');//創建基于本地的8003端口的websocket連接}else alert("您的瀏覽器不支持websocket");上述代碼會對本地的 8003 接口請求 Websocket 連接,前提是本地的服務器有進程監聽 8003 端口,不然的話會連接失敗。

事件相關的優化

大部分的事件觸發依賴于用戶與瀏覽器的交互,但用戶的行為是不可控的,許多交互設計上的缺陷與無法考慮到的因素會導致事件的頻繁觸發。當事件處理器內部包含大量的操作,又不需要如此快速的響應事件時,就需要采用一些手段來限制事件處理器的執行。事件的優化主要有兩個目的:減少不必要的 HTTP 請求減少本機性能的消耗

2.3 服務消費者

我們在服務提供者的同級新建項目服務消費者,使用 Spring Initializr 來初始化,以下是服務消費者的項目信息:pom.xml初始化完成,我們在 pom.xml 文件中加入需要的依賴。<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.cdd</groupId> <artifactId>zookeeper-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zookeeper-consumer</name> <description>zookeeper-consumer Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- curator 客戶端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.1.0</version> </dependency> <!-- curator 客戶端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>5.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>application.properties依賴導入后,我們在 application.properties 配置端口信息server.port=9090接下來我們編寫使用 Curator 客戶端連接 Zookeeper 服務的代碼。CuratorService在項目主類的同級新建目錄 service 目錄,在 service 目錄下新建 CuratorService 類:@Componentpublic class CuratorService implements ApplicationRunner { private static CuratorFramework client; private static final String PROVIDER_NODE = "/imooc/provider"; private static List<String> PROVIDER_SERVER_LIST; private static int NUMBER_OF_REQUESTS = 0; @Override public void run(ApplicationArguments args) throws Exception { // 構建 CuratorFramework 客戶端,并開啟會話 buildCuratorClient(); // 獲取服務列表 getAListOfServiceAddresses(); // 開啟對 PROVIDER_NODE 子節點變化事件的監聽 startMonitoring(); } /** * 構建 CuratorFramework 客戶端,并開啟會話 */ private void buildCuratorClient() { // 使用 CuratorFrameworkFactory 構建 CuratorFramework client = CuratorFrameworkFactory.builder() .sessionTimeoutMs(10000) // Zookeeper 地址 .connectString("127.0.0.1:2181") // 重連策略 .retryPolicy(new RetryForever(10000)) .build(); // 開啟會話 client.start(); System.out.println(">>> 服務消費者連接 Zookeeper "); } /** * 獲取服務列表 * * @throws Exception Exception */ private void getAListOfServiceAddresses() throws Exception { Stat stat = client.checkExists().forPath(PROVIDER_NODE); if (stat == null) { throw new RuntimeException("服務地址未注冊到 Zookeeper"); } else { PROVIDER_SERVER_LIST = client.getChildren().forPath(PROVIDER_NODE); } } /** * 開啟對 PROVIDER_NODE 子節點變化事件的監聽 */ public void startMonitoring() { // 構建 CuratorCache 實例 CuratorCache cache = CuratorCache.build(client, PROVIDER_NODE); // 使用 Fluent 風格和 lambda 表達式來構建 CuratorCacheListener 的事件監聽 CuratorCacheListener listener = CuratorCacheListener.builder() // 開啟對 PROVIDER_NODE 節點的子節點變化事件的監聽 .forPathChildrenCache(PROVIDER_NODE, client, (curator, event) -> // 重新獲取服務列表 PROVIDER_SERVER_LIST = curator.getChildren().forPath(PROVIDER_NODE)) // 初始化 .forInitialized(() -> System.out.println(">>> CuratorCacheListener 初始化")) // 構建 .build(); // 注冊 CuratorCacheListener 到 CuratorCache cache.listenable().addListener(listener); // CuratorCache 開啟緩存 cache.start(); } /** * 輪詢策略,按順序獲取服務地址 * * @return 服務地址 */ public String roundRobin() { if (PROVIDER_SERVER_LIST.isEmpty()){ throw new RuntimeException(">>> 服務提供者地址列表為空"); } int i = NUMBER_OF_REQUESTS % PROVIDER_SERVER_LIST.size(); NUMBER_OF_REQUESTS++; return PROVIDER_SERVER_LIST.get(i); }}在 CuratorService 中,我們提供了創建 Curator 客戶端的方法,獲取服務地址列表的方法,對父節點的子節點變化事件開啟監聽的方法,以及對服務的負載均衡策略的方法輪詢策略。在服務消費者啟動時,連接 Zookeeper 服務,獲取已注冊的服務地址列表,并對服務地址臨時節點的父節點開啟監聽。監聽到子節點的變化事件時,則重新獲取服務地址列表。ConsumerController這里我們使用 RESTful 的風格來調用服務消費者的方法,在 service 同級創建目錄 controller ,在 controller 中創建 ConsumerController 類:@RestController@RequestMapping("/consumer")public class ConsumerController { @Autowired private CuratorService curatorService; @Autowired private RestTemplate restTemplate; /** * 調用方法 * http://localhost:9090/consumer/callMethod * * @return String */ @GetMapping("/callMethod") public String callMethod() { // 輪詢策略獲取服務地址 String s = curatorService.roundRobin(); // 使用 RestTemplate 遠程調用服務的 /provider/callMethod 方法,String.class 為返回值類型 return restTemplate.getForObject("http://" + s + "/provider/callMethod", String.class); }}我們使用了 RestTemplate 來遠程調用 RESTful 風格的接口,所以我們需要把 RestTemplate 注入到 Spring IOC 容器中。RestTemplate我們在服務消費者項目的主類中注入Bean RestTemplate :package cn.cdd.zookeeper.consumer;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@SpringBootApplicationpublic class ZookeeperConsumerApplication { public static void main(String[] args) { SpringApplication.run(ZookeeperConsumerApplication.class, args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }}controller 編寫完畢后,我們就可以對我們的服務消費者進行測試了。

4.6 添加 MyBatis 映射文件

編寫 GoodsDao 、 OrderDao 對應的映射文件, 首先我們通過 application.properties 指定映射文件的位置:實例:# 指定MyBatis配置文件位置mybatis.mapper-locations=classpath:mapper/*.xml然后在 resources/mapper 目錄下新建 GoodsMapper.xml 文件,該文件就是 goods 表對應的映射文件,內容如下:實例:<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 本映射文件對應GoodsDao接口 --><mapper namespace="com.imooc.springboottransaction.GoodsDao"> <!-- 對應GoodsDao中的selectForUpdate方法 --> <select id="selectForUpdate" resultMap="resultMapBase" parameterType="java.lang.Long"> select <include refid="sqlBase" /> from goods where id = #{id} for update </select> <!-- 對應GoodsDao中的update方法 --> <update id="update" parameterType="com.imooc.springboottransaction.GoodsDo"> update goods set name=#{name},num=#{num} where id=#{id} </update> <!-- 可復用的sql模板 --> <sql id="sqlBase"> id,name,num </sql> <!-- 保存SQL語句查詢結果與實體類屬性的映射 --> <resultMap id="resultMapBase" type="com.imooc.springboottransaction.GoodsDo"> <id column="id" property="id" /> <result column="name" property="name" /> <result column="num" property="num" /> </resultMap></mapper>同樣我們在 resources/mapper 目錄下新建 OrderMapper.xml 文件,該文件是 order 表對應的映射文件,內容如下:實例:<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 本映射文件對應OrderDao接口 --><mapper namespace="com.imooc.springboottransaction.OrderDao"> <!-- 對應OrderDao中的insert方法 --> <insert id="insert" parameterType="com.imooc.springboottransaction.OrderDo"> insert into `order` (goods_id,count) values (#{goodsId},#{count}) </insert></mapper>

4.1 設置成 0 dp(重點)

這個是最直接,最常用的設置方式,也是我們需要掌握的重中之重。如果我們將高度設置成 0 dp,那么系統就會直接使用 weight 的比值作為尺寸比例分配給各個子 View。我們直接在上面的代碼中進一步修改,不考慮內嵌的 LinearLayout,對 3 個子 View 添加 weight 屬性,并加上背景色方便區分:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="#EBA2A2" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#E71B0C" android:text="Here" android:textSize="30sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#E7430F" android:text="Is" android:textSize="30sp" /> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="2" android:background="#E6D11B" android:text="Imooc" android:textSize="30sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="3" android:background="#AACCE7" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#88F10D" android:text="Android" android:textSize="30sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#03A9F4" android:text="Study" android:textSize="30sp" /> </LinearLayout></LinearLayout>效果如下:可以看到,3 個子 View 的高度正好是按照 1:2:3 排列。按照上面給出的計算方法,各個View的高度是 0,所以直接就是按照比例排列。將高度/寬度寫成 0 再使用 weight 屬性是最直接最簡單的方法,也是最常用最重要的方法,大家今后會經常用到,務必掌握!

4. Swagger Editor 安裝是否成功的必要性測試及注意事項

無論使用哪種環境安裝的 Swagger Editor ,檢測安裝是否成功的標志已經在上述內容中提及,接下來我們需要做的是訪問我們已經安裝并運行的 Swagger Editor 服務。一般而言,當我們的 Swagger Editor 服務啟動之后,可以通過以下方式來訪問: http://localhost如果在啟動 Swagger Editor 服務的時候,你自定了服務的端口,則要通過以下方式來訪問: http://localhost:8080(端口號)在瀏覽器中輸入訪問地址之后,我們可以看到如下 Swagger Editor 的引導界面就表明 Swagger Editor 已經成功安裝并且可以正常使用了:Tips :在安裝 Swagger Editor 之前,都需要我們首先將 Node 環境和 Docker 環境安裝好,這是使用 Swagger Editor 的前提,在安裝 Node 環境和 Docker 環境時,一定要注意版本的差異,一本推薦使用最新版本來安裝一般在使用 Swagger Editor 的時候,很多情況都需要我們從零開始配置我們的 API 配置文件,很少情況會在基于已經配置好的 API 配置文件上進行修改。由于種種原因導致安裝 Swagger Editor 失敗的同學,可以通過訪問 Swagger 官方提供的 Swagger Editor 在線使用地址(https://editor.swagger.io/)來了解 Swagger Editor:

3.2 設置形狀樣式

和 TextView 類似,我們首先創建 drawable 資源:依次進入“src” -> “main” -> “res” -> “drawable”目錄,在里面右鍵新建一個“Drawable Resource File”,輸入文件名:button_background。編寫 button_background.xml 的內容如下:<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#B9B911" /> <corners android:bottomLeftRadius="30dp" android:bottomRightRadius="30dp" android:topLeftRadius="30dp" android:topRightRadius="30dp" /> <stroke android:width="3dp" android:color="#99CCFF" /></shape>在 shape 標簽中,我們設置了填充的背景色拐角的弧度描邊的顏色和寬度然后在xml中通過android:background設置 button 的 background 樣式:android:background="@drawable/button_background"效果如下:

2.4 Trailer

Trailer 是拖車的意思,正常的報文是 首部字段+回車符+請求體,Trailer 允許在請求體的后面再添加首部信息。Trailer 的值會先表明請求體后面的首部字段是什么。HTTP/1.1 200 OKTrailer: Expires--報文--Expires: May, 1 Sep 2020 23:59:59 GMT使用場景:首部字段的值是動態生成的,事先無法知道。如 content-length 請求體的長度,在分塊傳輸中一開始無法確定內容的長度。還有一些可能是消息的數字簽名,完整性校驗等。

3.1 limit_conn 模塊實驗

本次案例將使用 limit_conn 模塊中的指令完成限速功能實驗。實驗配置塊如下:...http { ... # 沒有這個會報錯,必須要定義共享內存 limit_conn_zone $binary_remote_addr zone=addr:10m; ... server { server_name localhost; listen 8010; location / { limit_rate 50; limit_conn addr 1; } } ...}...使用 limit_rate 指令用于限制 Nginx 相應速度,每秒返回 50 個字節,然后是限制并發數為 2,這樣方便展示效果。當我們打開一個瀏覽器請求該端口下的根路徑時,由于相應會比較慢,迅速打開另一個窗口請求同樣的地址,會發現再次請求時,正好達到了同時并發數為 2,啟動限制功能,第二個窗口返回 503 錯誤(默認)。訪問第一次快速訪問第二次

1. 網格布局

這種幾行幾列的布局最適合用網格布局來寫啦!來看一下語法:1222運行結果:由于grid布局較為復雜,一言難盡,所以在這里貼上兩個較為流行的grid入門教程地址:阮一峰博客:http://www.ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html張鑫旭博客:https://www.zhangxinxu.com/wordpress/2018/11/display-grid-css-css3/很多人擔心Grid的兼容性:其實可以看到絕大部分瀏覽器都已經支持了,即使是最被吐槽的IE瀏覽器,也可以通過增加-ms-前綴來進行支持,如:display: -ms-grid;

1.1 WEB網絡

WWW (World Wide Web),英文名 World 看出來這東西很宏大,顧名思義就是全世界都在一個網絡里,因為它世界的距離被拉近。但是這家伙最早也是從單細胞慢慢演變而來的,它是誕生于科學家的物理實驗室中用于檔案的存儲,后來慢慢演變成大學里知識交流的一個網絡,再后來這個網絡的規模越變越大,突破層級構架成了如今的互聯網。Web 網絡方便了我們的信息傳遞,背后依托的就是 Http 這項協議。

1.1 明文傳輸

Http 的整個報文信息,從客戶端到服務端到都是明文傳輸到。信息從我們的電腦發出去,中間需要經歷哪些設備才能被另一臺電腦接收,我們是不好確定也很難評估的。有可能跨越多個運營商機房,有可能通過海底光纜橫跨大洋,有可能穿過 某某學校 / 某某廠房 / 某某集團 的內部 路由器/ 交換機 / 集線器 等。中間這么多節點我們都是控制不到的,所以只要別人有心,我們就很難保證我們的信息不被泄漏。

4.過濾的使用

有時,在訪問接口時,需要的是符合一定條件的數據。此時可以通過過濾來實現,Django Rest framework中,可以使用 django-fitlter 來實現過濾功能。在使用該功能前,需要提前安裝和注冊 django-filter。在終端輸入以下內容完成 django-filter 的安裝:pip install django-filter在配置文件中配置以下內容:INSTALLED_APPS = [ ... 'django_filters', # 注冊應用]REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)}在視圖中添加 filter_fields 屬性,指定可以過濾的字段:class StudentViewSet(ModelViewSet): queryset = StudentsModel.objects.all() serializer_class = StudentsSerializer filter_fields = ('s_age')此時,可以通過訪問 http://127.0.0.1:8000/api/students/?s_age=11 來獲取所有年齡為 11 的學生信息。

3.2 啟用并配置 Swagger2 功能

我們添加一個配置類,專門用于配置 Swagger2 相關功能,這樣比較清晰點。通過 @EnableSwagger2 注解開啟 Swagger2 功能,通過 @Bean 標注的方法將對 Swagger2 功能的設置放入容器。實例:@Configuration // 告訴Spring容器,這個類是一個配置類@EnableSwagger2 // 啟用Swagger2功能public class Swagger2Config { /** * 配置Swagger2相關的bean */ @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com"))// com包下所有API都交給Swagger2管理 .paths(PathSelectors.any()).build(); } /** * 此處主要是API文檔頁面顯示信息 */ private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("演示項目API") // 標題 .description("學習Swagger2的演示項目") // 描述 .termsOfServiceUrl("http://www.xianlaiwan.cn") // 服務網址,一般寫公司地址 .version("1.0") // 版本 .build(); }}

7. 小結

本節介紹了如何安裝 postman,在 route 目錄下創建路由文件,分別定義了 POST、GET、PUT 三種請求方式,delete 方式可按照其他請求方式定義。本小節的演示圖中可以看到請求 http://tp6.com/study url 地址,使用不同的請求方式,對應到 ThinkPHP 框架中的響應方法是不相同的,在實際項目中 POST、GET、PUT、DELETE 四種請求方式分別對應數據的 新增、獲取、修改、刪除,這是一種數據規范,可以很好的管理自己的項目代碼。Tips: 代碼倉庫Excel導入學生信息Excel導出學生信息后臺處理數據

4.2 注入 OutputStream

在控制器的方法中注入 OutputStream 對象,只需要在方法中添加參數聲明。如下實例:可使用 OutputStream 對象讀取指定文件中的內容后直接響應給瀏覽器。@RequestMapping(value = "/testApi05")public void hello(OutputStream outputStream) throws IOException { Resource res = new ClassPathResource("/test.txt"); FileCopyUtils.copy(res.getInputStream(), outputStream);}test.txt 文件的內容是”this is a test’。文件直接放在項目的 src/main/java 目錄下。在瀏覽器中輸入請求路徑 http://localhost:8888/sm-demo/testApi05 。你將在瀏覽器中看到:有句話叫做 “條條道路通羅馬”,用在 Spring MVC 中真的是合適,依靠 Spring 強大的注入功能,只要原生開發中能有的對象基本上都能注入進去。

1. 什么是接口?

接口(軟件類接口)是指對協定進行定義的引用類型。其他類型實現接口,以保證它們支持某些操作。接口通常用 API 替代。這個概念不太好理解,接下來,我們用一個例子幫助大家更好地理解什么是接口。假如在一個學生管理系統中,我們想要查詢一個學生的信息,我們輸入http://www.demo.com/students/2020,此時我們將獲得編號為 2020 的學生信息。我們通過鏈接與服務器交互,并獲取到了想要的數據,那么與服務器交互的這個鏈接就可以稱作是一個接口(API)。

2.1 轉發

如下面的實例:@RequestMapping("/response02")public String response02(ModelMap model) throws IOException { //發送給客戶端的響應數據 String hello="Hello"; model.addAttribute("data", hello); return "hello";}上面代碼是一個典型的 Spring MVC 控制器代碼:變量 hello 中保存的就是要發送給瀏覽器的數據。ModelMap 類型的數據模型可以說是一個中間載體,用來臨時保存 hello 中的數據;return 后面的 “hello” 是視圖的邏輯名,由視圖解析器解析并找到真正的頁面。下面是一個標準的視圖模板,頁面中已經提供了樣式,只等數據的到來。 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <div style="color:red"> ${data} </div> </body> </html>有了這些信息后,視圖解析器把數據和視圖合二為一后進行渲染,得到純 HTML 后寫入響應包,發送給瀏覽器。這便于轉發或者叫派發。默認情況下,控制器的響應方式使用的是轉發。轉發是在一次請求中一氣呵成完成的。如上代碼,在瀏覽器輸入請求:http://localhost:8888/sm-demo/response02。服務端響應包中的數據這就是響應給客戶端的最終數據。Tips: Spring MVC 的整個請求和響應過程是由多個組件協作完成的,這里不深究細節。

3.1 refer模塊防盜

Nginx 用于實現防盜鏈功能的模塊為 refer 模塊,其依據的原理是: 如果網站盜用了你的圖片,那么用戶在點擊或者查看這個盜鏈內容時,發送 http 請求的頭部中的 referer 字段將為該盜版網站的 url。這樣我們通過獲取這個頭部信息,知道 http 發起請求的頁面,然后判斷這個地址是否是我們的合法頁面,不是則判斷為盜鏈。Nginx 的 referer 模塊中有3個指令,用法分別如下:Syntax: referer_hash_bucket_size size;Default: referer_hash_bucket_size 64;Context: server, locationSyntax: referer_hash_max_size size;Default: referer_hash_max_size 2048;Context: server, locationSyntax: valid_referers none | blocked | server_names | string ...;Default: —Context: server, location最重要的是 valid_referers 指令,它后面可以帶上多個參數,表示多個 referer 頭都是有效的。它的參數形式有:none: 允許缺失 referer 頭部的請求訪問blocked: 有 referer 這個字段,但是其值被防火墻或者是代理給刪除了server_names: 若 referer 中的站點域名和 server_names 中的某個域名匹配,則允許訪問任意字符或者正則表達式Nginx 會通過查看 referer 字段和 valid_referers 后面的 referer 列表進行匹配,如果匹配到了就將內置的變量$invalid_referer值設置為0,否則設置該值為1這樣一個簡單的 Nginx 防盜鏈配置如下:... location / { valid_referers none blocked *.domain.pub www.domain.com/nginx server_names ~\.baidu\.; if ($invalid_referer) { return 403; } return 200 "valid\n"; }...

2. 需求分析

業務場景: 模擬微信聊天,每個客戶端和服務端建立連接,并且可以實現點對點通信(單聊),點對多點通信(群聊)。設計思路: 我們要實現的是點(客戶端)對點(客戶端)的通訊,但是我們大部分情況下接觸的業務都是客戶端和服務端之間的通訊,客戶端只需要知道服務端的 IP 地址和端口號即可發起通訊了,那么客戶端和客戶端應該怎么去設計呢?思考:難道是手機和手機之間建立通訊連接,互相發送消息嗎?這種方案顯然不是很好的方案,第一: 客戶端和客戶端之間通訊,首先需要確定對方的 IP 地址和端口號,顯然不是很現實。第二: 即使有辦法拿到對方的 IP 地址和端口號,那么每個點(客戶端)既作為服務端還得作為客戶端,無形之中增加了客戶端的壓力。其實,我們可以使用服務端作為中轉站,由服務端主動往指定客戶端推送消息,如果是這種模式的話,那么 Http 協議是無法支持的,Http 是無狀態的,只能一請求一響應的模式,只能使用 TCP 協議去實現了。

4.1 路由配置

在 Web 開發過程中,經常會遇到 “路由” 的概念。簡單來說,路由就是 URL 到處理函數的映射。Web 后端處理大致流程可以看成這樣:瀏覽器發出請求服務器端監聽到 80 端口的請求,解析請求的 url 路徑根據服務器的路由配置,找到對應 url 對應的處理函數運行處理函數生成一段 HTML 文本,并返回給瀏覽器假設一個論壇系統由如下數據構成:主題,每個主題包含有標題和內容,使用 topicID 標識該主題用戶,每個用戶包含姓名和密碼,使用 userID 標識該用戶論壇的域名是 www.bbs.com,它向外界提供了若干可訪問的 URL:URL功能http://www.bbs.com/topics/12373訪問 topicID 為 12373 的主題http://www.bbs.com/users/1353訪問 userID 為 1353 的用戶頁面在服務器端有兩個處理頁面函數:showTopic(topicId) 顯示指定 topicId 的主題內容showUser(userId) 顯示指定 userId 的用戶信息在下圖中,當用戶請求形式為 /topics/xxx 的 URL 時,服務器需要找到 showTopic 函數處理該請求;當用戶請求形式為 /users/xxx 的 URL 時,服務器需要找到 showUser 函數處理該請求。URL 到處理函數的映射,就被稱為路由。Web 開發框架提供了路由配置的功能,可以方便的指定處理 URL 的函數。

1. 前言

前面幾個章節主要解析了 Netty 的編碼、解碼問題,那么是否有了編解碼器,我們的 Netty 通信就能正常了呢?TCP 協議在傳輸數據時沒有辦法判斷數據是什么時候結束的,它無法識別一段完整的信息,因此可能會導致接受到的數據和發送時的數據不一致的情況。因此需要人為的指定一種規范的協議,從而保證數據的安全性,比如:我們所熟悉的 HTTP 協議。本節內容,我們主要需要以下兩點知識TCP 拆包、粘包的原因;TCP 拆包、粘包的解決方案。

3. URL

通過前面我們知道 URI 是網絡中用于標識某個對象的規約,URI 包含了多個 <scheme>,所以 URL 是 scheme = http 的 URI。URL 是 URI 的子集,只要是 URL 一定就是 URI ,反過來不成立。URL 和 URI 只差了一個字母,Location 和 Identifier:Location:定位,著重強調的是位置信息;Identifier:標識,只是一種全局唯一的昵稱。舉例:美國是一個國家,它只是一種標識,通過美國這兩個字我們無法知道這個國家在哪里。如果這個標識換成了經緯度,那我們就能知道這個經緯度對應的是美國,并且知道美國所處的位置信息。

7. 對象工廠 objectFactory

MyBatis 每次創建結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)來完成。MyBatis 默認的對象工廠僅僅只是實例化目標類,我們可以自定義一個對象工廠類來覆蓋默認的對象工廠。配置如下:<?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> <objectFactory type="org.mybatis.example.ExampleObjectFactory"/></configuration>絕大多數情況下,這個操作都是極其危險的,改變了 MyBatis 默認的對象創建行為可能會帶來一定的兼容錯誤,所以我們不做過多介紹,如果你確實需要它,可以查閱相關的資料。

3. 打碼平臺對接例子

接下來我們用一個簡單的例子來具體演示一下上面的步驟:這里,我們隨便選擇了一個打碼平臺的接口例子來進行講解。關于如何在打碼平臺上進行注冊和查找接口文檔,由于打碼平臺的不穩定性,這里不做推薦,讀者可以自行百度,選擇適合自己的平臺進行注冊和使用。在確定打碼平臺的可靠性的前提下,再進行充值。謹防被騙!我們接下來通過打碼平臺驗證如下驗證碼,驗證碼圖片如下:代碼如下:#!/usr/bin/env python# coding:utf-8import requestsfrom hashlib import md5#客戶端類class My_Client(object): #初始化 def __init__(self, username, password, soft_id): self.username = username self.password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } #上傳圖片 def PostPic(self, im, codetype): """ im: 圖片字節 codetype: 題目類型 """ params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() #錯誤反饋 def ReportError(self, im_id): """ im_id:報錯題目的圖片ID """ params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json()if __name__ == '__main__': chaojiying = My_Client('XXX', 'XXX', 'XXX') im = open('a.jpg', 'rb').read() print(chaojiying.PostPic(im, XXX))上述代碼,我們總共有三個方法,分別是初始化,上傳圖片和錯誤反饋。初始化主要是初始化一些基本的信息,這些可以在打碼平臺注冊的地方獲取。另外,打碼平臺還會提供一個打碼平臺提供的識別碼SoftId。初始化成功后,我們上傳驗證碼,最后打印出結果。可以看到結果為7261,跟驗證碼里面的文字一致。運行結果如下:

4.2 secure_link 防盜鏈測試

我們準備一個靜態圖片, 名為 test.png,放到搭建了 Nginx 的服務器上,全路徑為 /root/test/test.png。我們準備 Nginx 配置如下:...http { ... server { listen 8000; location / { # return 200 "$remote_addr"; root /root/test; } } server { listen 8001; location ~* .(jpg|png|flv|mp4)$ { secure_link $arg_md5,$arg_expires; secure_link_md5 "$secure_link_expires$uri$remote_addr secret"; # 空字符串,校驗不通過 if ($secure_link = "") { return 403; } # 時間過期 if ($secure_link = "0") { return 410; } # 校驗通過,訪問對的靜態資源 root /root/test; } }}...首先,在瀏覽器上訪問8000端口我們可以獲取對應的 $remote_addr 變量值(打開 return 的注釋配置),結果為103.46.244.69, 這是客戶端請求時的對外 IP。訪問瀏覽器上訪問8000端口,URI=/test.png, 可以看到這個靜態圖片。接下來,我們在訪問8001端口,URI=/test.png時,可以發現返回403頁面,說明安全模塊生效。當前時間為2020年02月05日晚上9點半,我們找一個過期時間晚上10點,得到相應的時間戳為1580911200。按照 secure_link_md5 指令格式,使用如下 shell 命令生成 md5 值:[shen@shen Desktop]$ echo -n '1580911200/test.png103.46.244.69 secret' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =KnJx3J6fN_0Qc1W5TqEVXw這樣可以得到我們的安全訪問 URL 為:# 訪問靜態資源test.png的安全URL為:http://180.76.152.113:8001/test.png?md5=KnJx3J6fN_0Qc1W5TqEVXw&expires=1580911200再次到瀏覽器上訪問時候,我就可以看到靜態圖片了。此外,我們還可以等到10點之后,測試過期后的結果。在過期之后再用這個 URL 訪問時無法查看圖片,而且返回的是 410 的狀態碼,這說明 Nginx 成功檢測到這個密鑰值已經過期。

1. Package Control

插件的安裝離不開插件管理器,我們首先需要安裝一下 Sublime 的包管理器 Package Control,這個就像 npm。工欲善其事,必先利其器,鍛造一把趁手的兵器尤其重要。默認安裝的 Sublime 編輯器沒有帶包管理器,所以我們來安裝一下:1. 安裝 Package Control:打開 Sublime Text 3,快捷鍵 ctrl+` (反引號,鍵盤左上角,ESC 的下邊那個鍵)或者 View > Show Console。打開控制臺,將下邊的 python 代碼貼進去并回車,稍等片刻即可安裝成功,如下圖:import urllib.request,os,hashlib; h = '6f4c264a24d933ce70df5dedcf1dcaee' + 'ebe013ee18cced0ef93d5f746d80ef60'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.cn/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)2. 設置國內源:Preferences > Package Settings > Package Control > Settings User,添加如下代碼并保存,搞定。"channels": [ "http://packagecontrol.cn/channel_v3.json"]3. 喚出包管理列表:打開包管理器,準備安裝插件啦!Preferences > Package Control,點擊下拉框里的Package Control: Install Package 或者快捷鍵 Ctrl+Shift+p,模糊搜索install,點擊,等待片刻,包管理列表就出來了, 如下圖:4. 搜索&安裝插件:我們可以在這里找到我們想要的各種插件了,支持模糊搜索,點擊哪個就代表安裝哪個插件,快去嘗嘗鮮吧!左下角會顯示當前安裝的插件,安裝成功,重啟 Sublime 編輯器即可使用該插件5. 卸載插件:卸載插件和安裝插件一樣簡單,如下圖:

5. CORS 跨域介紹

跨域實際上源自瀏覽器的同源策略,所謂同源,指的是協議、域名、端口都相同的源(域)。瀏覽器會阻止一個域的 JavaScript 腳本向另一個不同的域發出的請求,這也是為了保護瀏覽器的安全。在上面的例子中,發起請求的網頁與請求資源的 URL 協議、域名、端口均不同,所以該請求就被瀏覽器阻止了。CORS 的意思就是跨域資源共享,是一種允許跨域 HTTP 請求的機制,在這種情況下我們就要想辦法實現 CORS 跨域了。

2.2 運行及測試

我們用 curl 工具測試 OAuth2.0 資源服務器。測試流程如下:利用前一小節的指令向認證服務器獲取票據 Token;curl reader:secret@localhost:8080/oauth/token -d grant_type=password -d username=admin -d password=123456獲得結果:{ "access_token": "8b7a6968-cf6e-40d0-a988-f3260f7836a6", "token_type": "bearer", "expires_in": 599995027, "scope": "message:read"}使用 Token 作為參數,請求資源服務器上的內容,此時需要在請求中增加認證頭信息「Authorization」。curl --location --request GET 'http://localhost:8081/' \--header 'Authorization: Bearer 8b7a6968-cf6e-40d0-a988-f3260f7836a6'如果驗證成功,返回資源服務器內對應內容;如果驗證失敗,返回 401 錯誤信息,提示 token 驗證失敗。{ "error":"invalid_token", "error_description":"8b7a6968-cf6e-40d0-a988-f3260f7836a6"}

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

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

幫助反饋 APP下載

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

公眾號

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