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

為了賬號(hào)安全,請(qǐng)及時(shí)綁定郵箱和手機(jī)立即綁定
2. 第一個(gè)基于 Scrapy 框架的爬蟲(chóng)

首先我們來(lái)看 Scrapy 項(xiàng)目的 spider 目錄部分,新建一個(gè) Python 文件,命名為:china_pub_crawler.py。這個(gè)文件中我們會(huì)用到 Scrapy 框架中非常重要的 Spider 類(lèi):class ChinaPubCrawler(Spider): name = "China-Pub-Crawler" start_urls = ["http://www.china-pub.com/Browse/"] def parse(self, response): pass # ...我們實(shí)現(xiàn)一個(gè) ChinaPubCrawler 類(lèi),它繼承了 Scrapy 框架的 Spider 類(lèi),在這里我們會(huì)用到 Spider 類(lèi)的兩個(gè)屬性和一個(gè)方法:name: 爬蟲(chóng)名稱(chēng),后續(xù)在運(yùn)行 Scrapy 爬蟲(chóng)時(shí)會(huì)根據(jù)名稱(chēng)運(yùn)行相應(yīng)的爬蟲(chóng);start_urls:開(kāi)始要爬取的 URL 列表,這個(gè)地址可以動(dòng)態(tài)調(diào)整;parse():該方法是默認(rèn)的解析網(wǎng)頁(yè)的回調(diào)方法。當(dāng)然這里我們也可以自定義相應(yīng)的函數(shù)來(lái)實(shí)現(xiàn)網(wǎng)頁(yè)數(shù)據(jù)提?。晃覀兯伎枷虑懊嫱瓿苫?dòng)出版網(wǎng)的步驟:第一步是請(qǐng)求 http://www.china-pub.com/Browse/ 這個(gè)網(wǎng)頁(yè)數(shù)據(jù),從中找出計(jì)算機(jī)分類(lèi)的鏈接 URL。這一步我們可以這樣實(shí)現(xiàn):class ChinaPubCrawler(Spider): name = "China-Pub-Crawler" start_urls = ["http://www.china-pub.com/Browse/"] def parse(self, response): """ 解析得到計(jì)算機(jī)互聯(lián)網(wǎng)分類(lèi)urls,然后重新構(gòu)造請(qǐng)求 """ for url in response.xpath("http://div[@id='wrap']/ul[1]/li[@class='li']/a/@href").getall(): # 封裝請(qǐng)求,交給引擎去下載網(wǎng)頁(yè);注意設(shè)置處理解析網(wǎng)頁(yè)的回調(diào)方法 yield Request(url, callback=self.book_list_parse) def book_list_parse(self, response): pass我們將起點(diǎn)爬取的 URL 設(shè)置為 http://www.china-pub.com/Browse/,然后使用默認(rèn)的 parse() 解析這個(gè)網(wǎng)頁(yè)的數(shù)據(jù),提取到計(jì)算機(jī)分類(lèi)的各個(gè) URL 地址,然后使用 Scrapy 框架的 Request 類(lèi)封裝 URL 請(qǐng)求發(fā)送給 Scrapy 的 Engine 模塊去繼續(xù)下載網(wǎng)頁(yè)。在 Request 類(lèi)中我們可以設(shè)置請(qǐng)求網(wǎng)頁(yè)的解析方法,這里我們會(huì)專(zhuān)門(mén)定義一個(gè) book_list_parse() 類(lèi)來(lái)解析圖書(shū)列表的網(wǎng)頁(yè)。為了能提取相應(yīng)的圖書(shū)信息數(shù)據(jù),我們要定義對(duì)應(yīng)的圖書(shū) Item 類(lèi),位于 items.py 文件中,代碼內(nèi)容如下:# -*- 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()這里正是我們前面定義的圖書(shū)信息的 key 值,只不過(guò)這里用一種比較規(guī)范的方式進(jìn)行了定義。現(xiàn)在還有一個(gè)問(wèn)題要解決:如何實(shí)現(xiàn)分頁(yè)的圖書(shū)數(shù)據(jù)請(qǐng)求?我們?cè)?book_list_parse() 方法中可以拿到當(dāng)前解析的 URL,前面我們分析過(guò):請(qǐng)求的 URL 中包含請(qǐng)求頁(yè)信息。我們只需要將當(dāng)前 URL 的頁(yè)號(hào)加1,然后在構(gòu)造 Request 請(qǐng)求交給 Scrapy 框架的引擎去執(zhí)行即可,那么這樣不會(huì)一直請(qǐng)求下去嗎?我們只需要檢查 response 的狀態(tài)碼,如果是 404,表示當(dāng)前頁(yè)號(hào)已經(jīng)無(wú)效了,此時(shí)我們就不用再構(gòu)造下一個(gè)的請(qǐng)求了,來(lái)看代碼的實(shí)現(xiàn):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): """ 解析得到計(jì)算機(jī)互聯(lián)網(wǎng)分類(lèi)urls,然后重新構(gòu)造請(qǐng)求 """ 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): # 如果返回狀態(tài)碼為404,直接返回 if response.status == 404: return # 解析當(dāng)前網(wǎng)頁(yè)的圖書(shū)列表數(shù)據(jù) 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 # 生成下一頁(yè)url,交給Scrapy引擎再次發(fā)送請(qǐng)求 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("下一個(gè)url為:{}".format(next_url)) yield Request(next_url, callback=self.book_list_parse)請(qǐng)求所有的 URL,解析相應(yīng)數(shù)據(jù),這些我們都有了,還差最后一步:數(shù)據(jù)入庫(kù)!這一步我們使用 item Pipeline 來(lái)實(shí)現(xiàn)將得到的 item 數(shù)據(jù)導(dǎo)入到 MongoDB 中。編寫(xiě)的 item Pipeline 一般寫(xiě)在 pipelines.py 中,來(lái)看看代碼樣子:import pymongoclass ChinaPubPipeline: def open_spider(self, spider): """連接mongodb,并認(rèn)證連接信息,內(nèi)網(wǎng)ip""" self.client = pymongo.MongoClient(host='192.168.88.204', port=27017) self.client.admin.authenticate("admin", "shencong1992") db = self.client.scrapy_manual # 新建一個(gè)集合保存抓取到的圖書(shū)數(shù)據(jù) self.collection = db.china_pub_scrapy def process_item(self, item, spider): # 處理item數(shù)據(jù) 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("插入數(shù)據(jù)異常:{}".format(str(e))) return item def close_spider(self, spider): # 關(guān)閉連接 self.client.close()最后為了使這個(gè) pipeline 生效,我們需要將這個(gè) pipeline 寫(xiě)到 settings.py 文件中:# settings.py# ...ITEM_PIPELINES = { 'china_pub.pipelines.ChinaPubPipeline': 300,}# ...最后,我們還需要在請(qǐng)求的頭部加上一個(gè) User-Agent 參數(shù),這個(gè)設(shè)置在 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'# ...整個(gè)爬蟲(chóng)代碼就基本完成了,接下來(lái)開(kāi)始我們激動(dòng)人心的數(shù)據(jù)爬取吧!

2.1 創(chuàng)建連接

通過(guò)使用新建一個(gè) websocket 對(duì)象的方式創(chuàng)建一個(gè)新的連接,不過(guò)在創(chuàng)建之前需要檢測(cè)一下瀏覽器是否支持 Websocket,因?yàn)橹挥兄С?HTML5 的瀏覽器才能支持 Websocket,如下:if(typeof window.WebSocket == 'function'){ var ws = new WebSocket('http://127.0.0.1:8003');//創(chuàng)建基于本地的8003端口的websocket連接}else alert("您的瀏覽器不支持websocket");上述代碼會(huì)對(duì)本地的 8003 接口請(qǐng)求 Websocket 連接,前提是本地的服務(wù)器有進(jìn)程監(jiān)聽(tīng) 8003 端口,不然的話會(huì)連接失敗。

事件相關(guān)的優(yōu)化

大部分的事件觸發(fā)依賴(lài)于用戶(hù)與瀏覽器的交互,但用戶(hù)的行為是不可控的,許多交互設(shè)計(jì)上的缺陷與無(wú)法考慮到的因素會(huì)導(dǎo)致事件的頻繁觸發(fā)。當(dāng)事件處理器內(nèi)部包含大量的操作,又不需要如此快速的響應(yīng)事件時(shí),就需要采用一些手段來(lái)限制事件處理器的執(zhí)行。事件的優(yōu)化主要有兩個(gè)目的:減少不必要的 HTTP 請(qǐng)求減少本機(jī)性能的消耗

2.3 服務(wù)消費(fèi)者

我們?cè)诜?wù)提供者的同級(jí)新建項(xiàng)目服務(wù)消費(fèi)者,使用 Spring Initializr 來(lái)初始化,以下是服務(wù)消費(fèi)者的項(xiàng)目信息:pom.xml初始化完成,我們?cè)?pom.xml 文件中加入需要的依賴(lài)。<?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 客戶(hù)端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.1.0</version> </dependency> <!-- curator 客戶(hù)端 --> <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依賴(lài)導(dǎo)入后,我們?cè)?application.properties 配置端口信息server.port=9090接下來(lái)我們編寫(xiě)使用 Curator 客戶(hù)端連接 Zookeeper 服務(wù)的代碼。CuratorService在項(xiàng)目主類(lèi)的同級(jí)新建目錄 service 目錄,在 service 目錄下新建 CuratorService 類(lèi):@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 { // 構(gòu)建 CuratorFramework 客戶(hù)端,并開(kāi)啟會(huì)話 buildCuratorClient(); // 獲取服務(wù)列表 getAListOfServiceAddresses(); // 開(kāi)啟對(duì) PROVIDER_NODE 子節(jié)點(diǎn)變化事件的監(jiān)聽(tīng) startMonitoring(); } /** * 構(gòu)建 CuratorFramework 客戶(hù)端,并開(kāi)啟會(huì)話 */ private void buildCuratorClient() { // 使用 CuratorFrameworkFactory 構(gòu)建 CuratorFramework client = CuratorFrameworkFactory.builder() .sessionTimeoutMs(10000) // Zookeeper 地址 .connectString("127.0.0.1:2181") // 重連策略 .retryPolicy(new RetryForever(10000)) .build(); // 開(kāi)啟會(huì)話 client.start(); System.out.println(">>> 服務(wù)消費(fèi)者連接 Zookeeper "); } /** * 獲取服務(wù)列表 * * @throws Exception Exception */ private void getAListOfServiceAddresses() throws Exception { Stat stat = client.checkExists().forPath(PROVIDER_NODE); if (stat == null) { throw new RuntimeException("服務(wù)地址未注冊(cè)到 Zookeeper"); } else { PROVIDER_SERVER_LIST = client.getChildren().forPath(PROVIDER_NODE); } } /** * 開(kāi)啟對(duì) PROVIDER_NODE 子節(jié)點(diǎn)變化事件的監(jiān)聽(tīng) */ public void startMonitoring() { // 構(gòu)建 CuratorCache 實(shí)例 CuratorCache cache = CuratorCache.build(client, PROVIDER_NODE); // 使用 Fluent 風(fēng)格和 lambda 表達(dá)式來(lái)構(gòu)建 CuratorCacheListener 的事件監(jiān)聽(tīng) CuratorCacheListener listener = CuratorCacheListener.builder() // 開(kāi)啟對(duì) PROVIDER_NODE 節(jié)點(diǎn)的子節(jié)點(diǎn)變化事件的監(jiān)聽(tīng) .forPathChildrenCache(PROVIDER_NODE, client, (curator, event) -> // 重新獲取服務(wù)列表 PROVIDER_SERVER_LIST = curator.getChildren().forPath(PROVIDER_NODE)) // 初始化 .forInitialized(() -> System.out.println(">>> CuratorCacheListener 初始化")) // 構(gòu)建 .build(); // 注冊(cè) CuratorCacheListener 到 CuratorCache cache.listenable().addListener(listener); // CuratorCache 開(kāi)啟緩存 cache.start(); } /** * 輪詢(xún)策略,按順序獲取服務(wù)地址 * * @return 服務(wù)地址 */ public String roundRobin() { if (PROVIDER_SERVER_LIST.isEmpty()){ throw new RuntimeException(">>> 服務(wù)提供者地址列表為空"); } int i = NUMBER_OF_REQUESTS % PROVIDER_SERVER_LIST.size(); NUMBER_OF_REQUESTS++; return PROVIDER_SERVER_LIST.get(i); }}在 CuratorService 中,我們提供了創(chuàng)建 Curator 客戶(hù)端的方法,獲取服務(wù)地址列表的方法,對(duì)父節(jié)點(diǎn)的子節(jié)點(diǎn)變化事件開(kāi)啟監(jiān)聽(tīng)的方法,以及對(duì)服務(wù)的負(fù)載均衡策略的方法輪詢(xún)策略。在服務(wù)消費(fèi)者啟動(dòng)時(shí),連接 Zookeeper 服務(wù),獲取已注冊(cè)的服務(wù)地址列表,并對(duì)服務(wù)地址臨時(shí)節(jié)點(diǎn)的父節(jié)點(diǎn)開(kāi)啟監(jiān)聽(tīng)。監(jiān)聽(tīng)到子節(jié)點(diǎn)的變化事件時(shí),則重新獲取服務(wù)地址列表。ConsumerController這里我們使用 RESTful 的風(fēng)格來(lái)調(diào)用服務(wù)消費(fèi)者的方法,在 service 同級(jí)創(chuàng)建目錄 controller ,在 controller 中創(chuàng)建 ConsumerController 類(lèi):@RestController@RequestMapping("/consumer")public class ConsumerController { @Autowired private CuratorService curatorService; @Autowired private RestTemplate restTemplate; /** * 調(diào)用方法 * http://localhost:9090/consumer/callMethod * * @return String */ @GetMapping("/callMethod") public String callMethod() { // 輪詢(xún)策略獲取服務(wù)地址 String s = curatorService.roundRobin(); // 使用 RestTemplate 遠(yuǎn)程調(diào)用服務(wù)的 /provider/callMethod 方法,String.class 為返回值類(lèi)型 return restTemplate.getForObject("http://" + s + "/provider/callMethod", String.class); }}我們使用了 RestTemplate 來(lái)遠(yuǎn)程調(diào)用 RESTful 風(fēng)格的接口,所以我們需要把 RestTemplate 注入到 Spring IOC 容器中。RestTemplate我們?cè)诜?wù)消費(fèi)者項(xiàng)目的主類(lèi)中注入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 編寫(xiě)完畢后,我們就可以對(duì)我們的服務(wù)消費(fèi)者進(jìn)行測(cè)試了。

4.6 添加 MyBatis 映射文件

編寫(xiě) GoodsDao 、 OrderDao 對(duì)應(yīng)的映射文件, 首先我們通過(guò) application.properties 指定映射文件的位置:實(shí)例:# 指定MyBatis配置文件位置mybatis.mapper-locations=classpath:mapper/*.xml然后在 resources/mapper 目錄下新建 GoodsMapper.xml 文件,該文件就是 goods 表對(duì)應(yīng)的映射文件,內(nèi)容如下:實(shí)例:<?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"><!-- 本映射文件對(duì)應(yīng)GoodsDao接口 --><mapper namespace="com.imooc.springboottransaction.GoodsDao"> <!-- 對(duì)應(yīng)GoodsDao中的selectForUpdate方法 --> <select id="selectForUpdate" resultMap="resultMapBase" parameterType="java.lang.Long"> select <include refid="sqlBase" /> from goods where id = #{id} for update </select> <!-- 對(duì)應(yīng)GoodsDao中的update方法 --> <update id="update" parameterType="com.imooc.springboottransaction.GoodsDo"> update goods set name=#{name},num=#{num} where id=#{id} </update> <!-- 可復(fù)用的sql模板 --> <sql id="sqlBase"> id,name,num </sql> <!-- 保存SQL語(yǔ)句查詢(xún)結(jié)果與實(shí)體類(lèi)屬性的映射 --> <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>同樣我們?cè)?resources/mapper 目錄下新建 OrderMapper.xml 文件,該文件是 order 表對(duì)應(yīng)的映射文件,內(nèi)容如下:實(shí)例:<?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"><!-- 本映射文件對(duì)應(yīng)OrderDao接口 --><mapper namespace="com.imooc.springboottransaction.OrderDao"> <!-- 對(duì)應(yīng)OrderDao中的insert方法 --> <insert id="insert" parameterType="com.imooc.springboottransaction.OrderDo"> insert into `order` (goods_id,count) values (#{goodsId},#{count}) </insert></mapper>

4.1 設(shè)置成 0 dp(重點(diǎn))

這個(gè)是最直接,最常用的設(shè)置方式,也是我們需要掌握的重中之重。如果我們將高度設(shè)置成 0 dp,那么系統(tǒng)就會(huì)直接使用 weight 的比值作為尺寸比例分配給各個(gè)子 View。我們直接在上面的代碼中進(jìn)一步修改,不考慮內(nèi)嵌的 LinearLayout,對(duì) 3 個(gè)子 View 添加 weight 屬性,并加上背景色方便區(qū)分:<?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 個(gè)子 View 的高度正好是按照 1:2:3 排列。按照上面給出的計(jì)算方法,各個(gè)View的高度是 0,所以直接就是按照比例排列。將高度/寬度寫(xiě)成 0 再使用 weight 屬性是最直接最簡(jiǎn)單的方法,也是最常用最重要的方法,大家今后會(huì)經(jīng)常用到,務(wù)必掌握!

4. Swagger Editor 安裝是否成功的必要性測(cè)試及注意事項(xiàng)

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

3.2 設(shè)置形狀樣式

和 TextView 類(lèi)似,我們首先創(chuàng)建 drawable 資源:依次進(jìn)入“src” -> “main” -> “res” -> “drawable”目錄,在里面右鍵新建一個(gè)“Drawable Resource File”,輸入文件名:button_background。編寫(xiě) button_background.xml 的內(nèi)容如下:<?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 標(biāo)簽中,我們?cè)O(shè)置了填充的背景色拐角的弧度描邊的顏色和寬度然后在xml中通過(guò)android:background設(shè)置 button 的 background 樣式:android:background="@drawable/button_background"效果如下:

2.4 Trailer

Trailer 是拖車(chē)的意思,正常的報(bào)文是 首部字段+回車(chē)符+請(qǐng)求體,Trailer 允許在請(qǐng)求體的后面再添加首部信息。Trailer 的值會(huì)先表明請(qǐng)求體后面的首部字段是什么。HTTP/1.1 200 OKTrailer: Expires--報(bào)文--Expires: May, 1 Sep 2020 23:59:59 GMT使用場(chǎng)景:首部字段的值是動(dòng)態(tài)生成的,事先無(wú)法知道。如 content-length 請(qǐng)求體的長(zhǎng)度,在分塊傳輸中一開(kāi)始無(wú)法確定內(nèi)容的長(zhǎng)度。還有一些可能是消息的數(shù)字簽名,完整性校驗(yàn)等。

3.1 limit_conn 模塊實(shí)驗(yàn)

本次案例將使用 limit_conn 模塊中的指令完成限速功能實(shí)驗(yàn)。實(shí)驗(yàn)配置塊如下:...http { ... # 沒(méi)有這個(gè)會(huì)報(bào)錯(cuò),必須要定義共享內(nèi)存 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 相應(yīng)速度,每秒返回 50 個(gè)字節(jié),然后是限制并發(fā)數(shù)為 2,這樣方便展示效果。當(dāng)我們打開(kāi)一個(gè)瀏覽器請(qǐng)求該端口下的根路徑時(shí),由于相應(yīng)會(huì)比較慢,迅速打開(kāi)另一個(gè)窗口請(qǐng)求同樣的地址,會(huì)發(fā)現(xiàn)再次請(qǐng)求時(shí),正好達(dá)到了同時(shí)并發(fā)數(shù)為 2,啟動(dòng)限制功能,第二個(gè)窗口返回 503 錯(cuò)誤(默認(rèn))。訪問(wèn)第一次快速訪問(wèn)第二次

1. 網(wǎng)格布局

這種幾行幾列的布局最適合用網(wǎng)格布局來(lái)寫(xiě)啦!來(lái)看一下語(yǔ)法:1222運(yùn)行結(jié)果:由于grid布局較為復(fù)雜,一言難盡,所以在這里貼上兩個(gè)較為流行的grid入門(mén)教程地址:阮一峰博客:http://www.ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html張?chǎng)涡癫┛停篽ttps://www.zhangxinxu.com/wordpress/2018/11/display-grid-css-css3/很多人擔(dān)心Grid的兼容性:其實(shí)可以看到絕大部分瀏覽器都已經(jīng)支持了,即使是最被吐槽的IE瀏覽器,也可以通過(guò)增加-ms-前綴來(lái)進(jìn)行支持,如:display: -ms-grid;

1.1 WEB網(wǎng)絡(luò)

WWW (World Wide Web),英文名 World 看出來(lái)這東西很宏大,顧名思義就是全世界都在一個(gè)網(wǎng)絡(luò)里,因?yàn)樗澜绲木嚯x被拉近。但是這家伙最早也是從單細(xì)胞慢慢演變而來(lái)的,它是誕生于科學(xué)家的物理實(shí)驗(yàn)室中用于檔案的存儲(chǔ),后來(lái)慢慢演變成大學(xué)里知識(shí)交流的一個(gè)網(wǎng)絡(luò),再后來(lái)這個(gè)網(wǎng)絡(luò)的規(guī)模越變?cè)酱螅黄茖蛹?jí)構(gòu)架成了如今的互聯(lián)網(wǎng)。Web 網(wǎng)絡(luò)方便了我們的信息傳遞,背后依托的就是 Http 這項(xiàng)協(xié)議。

1.1 明文傳輸

Http 的整個(gè)報(bào)文信息,從客戶(hù)端到服務(wù)端到都是明文傳輸?shù)?。信息從我們的電腦發(fā)出去,中間需要經(jīng)歷哪些設(shè)備才能被另一臺(tái)電腦接收,我們是不好確定也很難評(píng)估的。有可能跨越多個(gè)運(yùn)營(yíng)商機(jī)房,有可能通過(guò)海底光纜橫跨大洋,有可能穿過(guò) 某某學(xué)校 / 某某廠房 / 某某集團(tuán) 的內(nèi)部 路由器/ 交換機(jī) / 集線器 等。中間這么多節(jié)點(diǎn)我們都是控制不到的,所以只要?jiǎng)e人有心,我們就很難保證我們的信息不被泄漏。

4.過(guò)濾的使用

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

3.2 啟用并配置 Swagger2 功能

我們添加一個(gè)配置類(lèi),專(zhuān)門(mén)用于配置 Swagger2 相關(guān)功能,這樣比較清晰點(diǎn)。通過(guò) @EnableSwagger2 注解開(kāi)啟 Swagger2 功能,通過(guò) @Bean 標(biāo)注的方法將對(duì) Swagger2 功能的設(shè)置放入容器。實(shí)例:@Configuration // 告訴Spring容器,這個(gè)類(lèi)是一個(gè)配置類(lèi)@EnableSwagger2 // 啟用Swagger2功能public class Swagger2Config { /** * 配置Swagger2相關(guān)的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文檔頁(yè)面顯示信息 */ private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("演示項(xiàng)目API") // 標(biāo)題 .description("學(xué)習(xí)Swagger2的演示項(xiàng)目") // 描述 .termsOfServiceUrl("http://idcbgp.cn") // 服務(wù)網(wǎng)址,一般寫(xiě)公司地址 .version("1.0") // 版本 .build(); }}

7. 小結(jié)

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

4.2 注入 OutputStream

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

1. 什么是接口?

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

2.1 轉(zhuǎn)發(fā)

如下面的實(shí)例:@RequestMapping("/response02")public String response02(ModelMap model) throws IOException { //發(fā)送給客戶(hù)端的響應(yīng)數(shù)據(jù) String hello="Hello"; model.addAttribute("data", hello); return "hello";}上面代碼是一個(gè)典型的 Spring MVC 控制器代碼:變量 hello 中保存的就是要發(fā)送給瀏覽器的數(shù)據(jù)。ModelMap 類(lèi)型的數(shù)據(jù)模型可以說(shuō)是一個(gè)中間載體,用來(lái)臨時(shí)保存 hello 中的數(shù)據(jù);return 后面的 “hello” 是視圖的邏輯名,由視圖解析器解析并找到真正的頁(yè)面。下面是一個(gè)標(biāo)準(zhǔn)的視圖模板,頁(yè)面中已經(jīng)提供了樣式,只等數(shù)據(jù)的到來(lái)。 <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>有了這些信息后,視圖解析器把數(shù)據(jù)和視圖合二為一后進(jìn)行渲染,得到純 HTML 后寫(xiě)入響應(yīng)包,發(fā)送給瀏覽器。這便于轉(zhuǎn)發(fā)或者叫派發(fā)。默認(rèn)情況下,控制器的響應(yīng)方式使用的是轉(zhuǎn)發(fā)。轉(zhuǎn)發(fā)是在一次請(qǐng)求中一氣呵成完成的。如上代碼,在瀏覽器輸入請(qǐng)求:http://localhost:8888/sm-demo/response02。服務(wù)端響應(yīng)包中的數(shù)據(jù)這就是響應(yīng)給客戶(hù)端的最終數(shù)據(jù)。Tips: Spring MVC 的整個(gè)請(qǐng)求和響應(yīng)過(guò)程是由多個(gè)組件協(xié)作完成的,這里不深究細(xì)節(jié)。

3.1 refer模塊防盜

Nginx 用于實(shí)現(xiàn)防盜鏈功能的模塊為 refer 模塊,其依據(jù)的原理是: 如果網(wǎng)站盜用了你的圖片,那么用戶(hù)在點(diǎn)擊或者查看這個(gè)盜鏈內(nèi)容時(shí),發(fā)送 http 請(qǐng)求的頭部中的 referer 字段將為該盜版網(wǎng)站的 url。這樣我們通過(guò)獲取這個(gè)頭部信息,知道 http 發(fā)起請(qǐng)求的頁(yè)面,然后判斷這個(gè)地址是否是我們的合法頁(yè)面,不是則判斷為盜鏈。Nginx 的 referer 模塊中有3個(gè)指令,用法分別如下: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 指令,它后面可以帶上多個(gè)參數(shù),表示多個(gè) referer 頭都是有效的。它的參數(shù)形式有:none: 允許缺失 referer 頭部的請(qǐng)求訪問(wèn)blocked: 有 referer 這個(gè)字段,但是其值被防火墻或者是代理給刪除了server_names: 若 referer 中的站點(diǎn)域名和 server_names 中的某個(gè)域名匹配,則允許訪問(wèn)任意字符或者正則表達(dá)式Nginx 會(huì)通過(guò)查看 referer 字段和 valid_referers 后面的 referer 列表進(jìn)行匹配,如果匹配到了就將內(nèi)置的變量$invalid_referer值設(shè)置為0,否則設(shè)置該值為1這樣一個(gè)簡(jiǎn)單的 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. 需求分析

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

4.1 路由配置

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

1. 前言

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

3. URL

通過(guò)前面我們知道 URI 是網(wǎng)絡(luò)中用于標(biāo)識(shí)某個(gè)對(duì)象的規(guī)約,URI 包含了多個(gè) <scheme>,所以 URL 是 scheme = http 的 URI。URL 是 URI 的子集,只要是 URL 一定就是 URI ,反過(guò)來(lái)不成立。URL 和 URI 只差了一個(gè)字母,Location 和 Identifier:Location:定位,著重強(qiáng)調(diào)的是位置信息;Identifier:標(biāo)識(shí),只是一種全局唯一的昵稱(chēng)。舉例:美國(guó)是一個(gè)國(guó)家,它只是一種標(biāo)識(shí),通過(guò)美國(guó)這兩個(gè)字我們無(wú)法知道這個(gè)國(guó)家在哪里。如果這個(gè)標(biāo)識(shí)換成了經(jīng)緯度,那我們就能知道這個(gè)經(jīng)緯度對(duì)應(yīng)的是美國(guó),并且知道美國(guó)所處的位置信息。

7. 對(duì)象工廠 objectFactory

MyBatis 每次創(chuàng)建結(jié)果對(duì)象的新實(shí)例時(shí),它都會(huì)使用一個(gè)對(duì)象工廠(ObjectFactory)來(lái)完成。MyBatis 默認(rèn)的對(duì)象工廠僅僅只是實(shí)例化目標(biāo)類(lèi),我們可以自定義一個(gè)對(duì)象工廠類(lèi)來(lái)覆蓋默認(rèn)的對(duì)象工廠。配置如下:<?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>絕大多數(shù)情況下,這個(gè)操作都是極其危險(xiǎn)的,改變了 MyBatis 默認(rèn)的對(duì)象創(chuàng)建行為可能會(huì)帶來(lái)一定的兼容錯(cuò)誤,所以我們不做過(guò)多介紹,如果你確實(shí)需要它,可以查閱相關(guān)的資料。

3. 打碼平臺(tái)對(duì)接例子

接下來(lái)我們用一個(gè)簡(jiǎn)單的例子來(lái)具體演示一下上面的步驟:這里,我們隨便選擇了一個(gè)打碼平臺(tái)的接口例子來(lái)進(jìn)行講解。關(guān)于如何在打碼平臺(tái)上進(jìn)行注冊(cè)和查找接口文檔,由于打碼平臺(tái)的不穩(wěn)定性,這里不做推薦,讀者可以自行百度,選擇適合自己的平臺(tái)進(jìn)行注冊(cè)和使用。在確定打碼平臺(tái)的可靠性的前提下,再進(jìn)行充值。謹(jǐn)防被騙!我們接下來(lái)通過(guò)打碼平臺(tái)驗(yàn)證如下驗(yàn)證碼,驗(yàn)證碼圖片如下:代碼如下:#!/usr/bin/env python# coding:utf-8import requestsfrom hashlib import md5#客戶(hù)端類(lèi)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: 圖片字節(jié) codetype: 題目類(lèi)型 """ 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() #錯(cuò)誤反饋 def ReportError(self, im_id): """ im_id:報(bào)錯(cuò)題目的圖片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))上述代碼,我們總共有三個(gè)方法,分別是初始化,上傳圖片和錯(cuò)誤反饋。初始化主要是初始化一些基本的信息,這些可以在打碼平臺(tái)注冊(cè)的地方獲取。另外,打碼平臺(tái)還會(huì)提供一個(gè)打碼平臺(tái)提供的識(shí)別碼SoftId。初始化成功后,我們上傳驗(yàn)證碼,最后打印出結(jié)果。可以看到結(jié)果為7261,跟驗(yàn)證碼里面的文字一致。運(yùn)行結(jié)果如下:

4.2 secure_link 防盜鏈測(cè)試

我們準(zhǔn)備一個(gè)靜態(tài)圖片, 名為 test.png,放到搭建了 Nginx 的服務(wù)器上,全路徑為 /root/test/test.png。我們準(zhǔn)備 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"; # 空字符串,校驗(yàn)不通過(guò) if ($secure_link = "") { return 403; } # 時(shí)間過(guò)期 if ($secure_link = "0") { return 410; } # 校驗(yàn)通過(guò),訪問(wèn)對(duì)的靜態(tài)資源 root /root/test; } }}...首先,在瀏覽器上訪問(wèn)8000端口我們可以獲取對(duì)應(yīng)的 $remote_addr 變量值(打開(kāi) return 的注釋配置),結(jié)果為103.46.244.69, 這是客戶(hù)端請(qǐng)求時(shí)的對(duì)外 IP。訪問(wèn)瀏覽器上訪問(wèn)8000端口,URI=/test.png, 可以看到這個(gè)靜態(tài)圖片。接下來(lái),我們?cè)谠L問(wèn)8001端口,URI=/test.png時(shí),可以發(fā)現(xiàn)返回403頁(yè)面,說(shuō)明安全模塊生效。當(dāng)前時(shí)間為2020年02月05日晚上9點(diǎn)半,我們找一個(gè)過(guò)期時(shí)間晚上10點(diǎn),得到相應(yīng)的時(shí)間戳為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這樣可以得到我們的安全訪問(wèn) URL 為:# 訪問(wèn)靜態(tài)資源test.png的安全URL為:http://180.76.152.113:8001/test.png?md5=KnJx3J6fN_0Qc1W5TqEVXw&expires=1580911200再次到瀏覽器上訪問(wèn)時(shí)候,我就可以看到靜態(tài)圖片了。此外,我們還可以等到10點(diǎn)之后,測(cè)試過(guò)期后的結(jié)果。在過(guò)期之后再用這個(gè) URL 訪問(wèn)時(shí)無(wú)法查看圖片,而且返回的是 410 的狀態(tài)碼,這說(shuō)明 Nginx 成功檢測(cè)到這個(gè)密鑰值已經(jīng)過(guò)期。

1. Package Control

插件的安裝離不開(kāi)插件管理器,我們首先需要安裝一下 Sublime 的包管理器 Package Control,這個(gè)就像 npm。工欲善其事,必先利其器,鍛造一把趁手的兵器尤其重要。默認(rèn)安裝的 Sublime 編輯器沒(méi)有帶包管理器,所以我們來(lái)安裝一下:1. 安裝 Package Control:打開(kāi) Sublime Text 3,快捷鍵 ctrl+` (反引號(hào),鍵盤(pán)左上角,ESC 的下邊那個(gè)鍵)或者 View > Show Console。打開(kāi)控制臺(tái),將下邊的 python 代碼貼進(jìn)去并回車(chē),稍等片刻即可安裝成功,如下圖: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. 設(shè)置國(guó)內(nèi)源:Preferences > Package Settings > Package Control > Settings User,添加如下代碼并保存,搞定。"channels": [ "http://packagecontrol.cn/channel_v3.json"]3. 喚出包管理列表:打開(kāi)包管理器,準(zhǔn)備安裝插件啦!Preferences > Package Control,點(diǎn)擊下拉框里的Package Control: Install Package 或者快捷鍵 Ctrl+Shift+p,模糊搜索install,點(diǎn)擊,等待片刻,包管理列表就出來(lái)了, 如下圖:4. 搜索&安裝插件:我們可以在這里找到我們想要的各種插件了,支持模糊搜索,點(diǎn)擊哪個(gè)就代表安裝哪個(gè)插件,快去嘗嘗鮮吧!左下角會(huì)顯示當(dāng)前安裝的插件,安裝成功,重啟 Sublime 編輯器即可使用該插件5. 卸載插件:卸載插件和安裝插件一樣簡(jiǎn)單,如下圖:

5. CORS 跨域介紹

跨域?qū)嶋H上源自瀏覽器的同源策略,所謂同源,指的是協(xié)議、域名、端口都相同的源(域)。瀏覽器會(huì)阻止一個(gè)域的 JavaScript 腳本向另一個(gè)不同的域發(fā)出的請(qǐng)求,這也是為了保護(hù)瀏覽器的安全。在上面的例子中,發(fā)起請(qǐng)求的網(wǎng)頁(yè)與請(qǐng)求資源的 URL 協(xié)議、域名、端口均不同,所以該請(qǐng)求就被瀏覽器阻止了。CORS 的意思就是跨域資源共享,是一種允許跨域 HTTP 請(qǐng)求的機(jī)制,在這種情況下我們就要想辦法實(shí)現(xiàn) CORS 跨域了。

2.2 運(yùn)行及測(cè)試

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

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

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

幫助反饋 APP下載

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

公眾號(hào)

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