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

為了賬號(hào)安全,請(qǐng)及時(shí)綁定郵箱和手機(jī)立即綁定
1. 背景

近年來,隨著 HTML5 和 w3c 的推廣開來,WebSocket 協(xié)議被提出,它實(shí)現(xiàn)了瀏覽器與服務(wù)器的實(shí)時(shí)通信,使服務(wù)端也能主動(dòng)向客戶端發(fā)送數(shù)據(jù)。在 WebSocket 協(xié)議提出之前,開發(fā)人員若要實(shí)現(xiàn)這些實(shí)時(shí)性較強(qiáng)的功能,經(jīng)常會(huì)使用一種替代性的解決方案——輪詢。輪詢的原理是采用定時(shí)的方式不斷的向服務(wù)端發(fā)送 HTTP 請(qǐng)求,頻繁地請(qǐng)求數(shù)據(jù)。明顯地,這種方法命中率較低,浪費(fèi)服務(wù)器資源。伴隨著 WebSocket 協(xié)議的推廣,真正實(shí)現(xiàn)了 Web 的即時(shí)通信。WebSocket 的原理是通過 JavaScript 向服務(wù)端發(fā)出建立 WebSocket 連接的請(qǐng)求,在 WebSocket 連接建立成功后,客戶端和服務(wù)端可以實(shí)現(xiàn)一個(gè)長(zhǎng)連接的網(wǎng)絡(luò)管道。因?yàn)?WebSocket 本質(zhì)上是 TCP 連接,它是一個(gè)長(zhǎng)連接,除非斷開連接否則無需重新創(chuàng)建連接,所以其開銷相對(duì) HTTP 節(jié)省了很多。

3. 小結(jié)

本小節(jié)主要給大家簡(jiǎn)單說明了 HTTPS 協(xié)議和傳統(tǒng) HTTP 協(xié)議的區(qū)別,另外給出了對(duì)稱加密和非對(duì)稱加密算法的流程,我們需要掌握不同加密算法的特點(diǎn),在下一章節(jié)中會(huì)給大家介紹 HTTPS 協(xié)議的具體流程。

<strong>2.2 工程實(shí)現(xiàn)</strong>

創(chuàng)建工程:為了區(qū)分 xml 工程,坐標(biāo)名稱換成 spring_an ,其實(shí)無所謂,大家自行創(chuàng)建即可。導(dǎo)入依賴:依賴的坐標(biāo)跟 xml 的工程坐標(biāo)一致即可,無需導(dǎo)入多余的依賴。<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency></dependencies>項(xiàng)目代碼:為了測(cè)試,在工程內(nèi)部創(chuàng)建 UserDao 的接口和 UserDao 的實(shí)現(xiàn)類 UserDaoImpl。UserDao 代碼如下:public interface UserDao { public void saveUser();}UserDaoImpl 的實(shí)現(xiàn)類代碼如下:@Repositorypublic class UserDaoImpl implements UserDao { public void saveUser() { System.out.println("執(zhí)行dao的保存方法"); }}注意事項(xiàng): 由于我們是基于注解的方式實(shí)現(xiàn)對(duì) bean 的管理,所以在實(shí)現(xiàn)類上面需要添加一個(gè)注解 @Repository,此注解的作用是為了 Spring 的容器啟動(dòng)后,需要要自動(dòng)檢測(cè)這些被注解的類并注冊(cè)相應(yīng)的 bean 實(shí)例到容器中。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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.wyan.dao"></context:component-scan></beans>上面是本案例的配置文件,那么可以看出跟 xml 的配置文件有很大的區(qū)別:?配置節(jié)點(diǎn):context-component-scan 標(biāo)簽,這是 Spring 框架自定義的 xml 標(biāo)簽,通過 base-package 的屬性,指明需要被自動(dòng)掃描實(shí)例化的類所在位置。如上圖所示,我們?cè)?com.wyan.dao 下的類是需要掃描自動(dòng)注入容器的。小細(xì)節(jié):不是在 com.wyan.dao 下的所有類都會(huì)自動(dòng)注入到容器,而是要搭配注解:比如我們的 @Repository 當(dāng)然還有其余的注解,我們后面章節(jié)會(huì)詳細(xì)講解。測(cè)試類測(cè)試結(jié)果:代碼解釋:測(cè)試類其實(shí)跟 xml 的方式一模一樣,我們本次測(cè)試的目的一樣也是通過 Spring 容器管理注冊(cè)的 bean 對(duì)象,只不過對(duì)象的實(shí)例化方式換成了注解,那么我們看到成功輸出在控制臺(tái)的測(cè)試語句,說明案例搭建完成。

1. Request 類

首先 Scrapy 中關(guān)于 Request 相關(guān)的源碼位置如下:scrapy 中 Request 相關(guān)的代碼可以看到 Request 定義相關(guān)的代碼并不多,這也方便我們?nèi)W(xué)習(xí)和探索。先來看 Request 類的定義:# 源碼位置:scrapy/http/request/__init__.pyfrom w3lib.url import safe_url_string# ...class Request(object_ref): def __init__(self, url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding='utf-8', priority=0, dont_filter=False, errback=None, flags=None, cb_kwargs=None): self._encoding = encoding # this one has to be set first self.method = str(method).upper() self._set_url(url) self._set_body(body) if not isinstance(priority, int): raise TypeError("Request priority not an integer: %r" % priority) self.priority = priority if callback is not None and not callable(callback): raise TypeError('callback must be a callable, got %s' % type(callback).__name__) if errback is not None and not callable(errback): raise TypeError('errback must be a callable, got %s' % type(errback).__name__) self.callback = callback self.errback = errback self.cookies = cookies or {} self.headers = Headers(headers or {}, encoding=encoding) self.dont_filter = dont_filter self._meta = dict(meta) if meta else None self._cb_kwargs = dict(cb_kwargs) if cb_kwargs else None self.flags = [] if flags is None else list(flags) # ...從上面的源碼中可以看到 Scrapy 框架使用了 w3lib 模塊來完成一些 Web 相關(guān)的功能,這里用到了 url 模塊的相關(guān)功能。safe_url_string() 方法是將 url 轉(zhuǎn)成合法的形式,也就是將一些特殊字符比如中文、空格等進(jìn)行想要的編碼。來看下面的例子:>>> from w3lib.url import safe_url_strin>>> url = "http://www.baidu.com/?xxx= zyz">>> safe_url_string(url)'http://www.baidu.com/?xxx=%20zyz'最后得到的 URL 形式和我們?cè)跒g覽器按下 Enter 鍵時(shí)一致。此外,對(duì)于 Request 類實(shí)例化時(shí)可以傳入多種初始屬性,常用的屬性含義如下:url:請(qǐng)求地址;method:請(qǐng)求類型,GET|POST|PUT|DELETE 等;callback: HTTP 請(qǐng)求的回調(diào)方法,用于指定該 HTTP 請(qǐng)求的解析響應(yīng)數(shù)據(jù)的方法;headers: 設(shè)置請(qǐng)求頭。一般而言時(shí)設(shè)置請(qǐng)求頭的 User-Agent 字段,模擬瀏覽器請(qǐng)求;body: 用于設(shè)置請(qǐng)求參數(shù),比如登錄請(qǐng)求需要帶上用戶名/密碼等參數(shù);cookies: 請(qǐng)求 cookies 信息,一般和登錄認(rèn)證相關(guān),帶上 cookies 用于表明身份信息。熟悉了這個(gè) Request 類后,我們來看一些在 Request 基礎(chǔ)上進(jìn)一步擴(kuò)展的請(qǐng)求類。其中一個(gè)是 FormRequest:# 源碼位置:scrapy/http/request/form.py# ...class FormRequest(Request): valid_form_methods = ['GET', 'POST'] def __init__(self, *args, **kwargs): formdata = kwargs.pop('formdata', None) if formdata and kwargs.get('method') is None: kwargs['method'] = 'POST' super(FormRequest, self).__init__(*args, **kwargs) if formdata: items = formdata.items() if isinstance(formdata, dict) else formdata querystr = _urlencode(items, self.encoding) if self.method == 'POST': self.headers.setdefault(b'Content-Type', b'application/x-www-form-urlencoded') self._set_body(querystr) else: self._set_url(self.url + ('&' if '?' in self.url else '?') + querystr) # ...FormRequest 類主要用于提交表單請(qǐng)求,比如登錄認(rèn)證、比如提交訂單等。它只支持 GET 和 POST 請(qǐng)求,且相比 Request 類,F(xiàn)ormRequest 類多了一個(gè)表單參數(shù)屬性,這個(gè)是檢查提交表單請(qǐng)求的數(shù)據(jù)。來分析實(shí)例化時(shí)對(duì)表單參數(shù)的處理,代碼如下:if formdata: items = formdata.items() if isinstance(formdata, dict) else formdata querystr = _urlencode(items, self.encoding) if self.method == 'POST': self.headers.setdefault(b'Content-Type', b'application/x-www-form-urlencoded') self._set_body(querystr) else: self._set_url(self.url + ('&' if '?' in self.url else '?') + querystr) # ...def _urlencode(seq, enc): values = [(to_bytes(k, enc), to_bytes(v, enc)) for k, vs in seq for v in (vs if is_listlike(vs) else [vs])] return urlencode(values, doseq=1)這個(gè)代碼的邏輯是非常清晰的,如果有表單數(shù)據(jù),會(huì)分成 GET 和 POST 請(qǐng)求處理:GET 請(qǐng)求:將請(qǐng)求參數(shù)添加到 url 后面,用 “?” 連接,參數(shù)之間用 “&” 連接;POST 請(qǐng)求:一方面設(shè)置請(qǐng)求的 header,另一方面將數(shù)據(jù)放到 body 體中;還有兩個(gè) JsonRequest 和 XmlRpcRequest 類,都是使用不同的形式來發(fā)送 HTTP 請(qǐng)求,我們來看兩個(gè)類中非常關(guān)鍵的幾行語句:# 源碼位置:scrapy/http/request/json_request.py# ...class JsonRequest(Request): def __init__(self, *args, **kwargs): # ... if body_passed and data_passed: # ... elif not body_passed and data_passed: kwargs['body'] = self._dumps(data) if 'method' not in kwargs: kwargs['method'] = 'POST' super(JsonRequest, self).__init__(*args, **kwargs) self.headers.setdefault('Content-Type', 'application/json') self.headers.setdefault('Accept', 'application/json, text/javascript, */*; q=0.01') # ...這里 JsonRequest 中主要講 data 數(shù)據(jù)轉(zhuǎn)成 json 格式,然后保存到 body 屬性中,然后設(shè)置了請(qǐng)求頭的 Content-Type 屬性為 “application/json”。# 源碼位置:scrapy/http/request/rpc.pyimport xmlrpc.client as xmlrpclib# ...class XmlRpcRequest(Request): def __init__(self, *args, **kwargs): # ... if 'body' not in kwargs and 'params' in kwargs: kw = dict((k, kwargs.pop(k)) for k in DUMPS_ARGS if k in kwargs) # 關(guān)鍵地方 kwargs['body'] = xmlrpclib.dumps(**kw) # ...XmlRpcRequest 用來發(fā)送 XML-RPC 請(qǐng)求,關(guān)鍵的地方在于請(qǐng)求數(shù)據(jù)設(shè)置,使用了 xmlrpc 模塊。

2.2 表單認(rèn)證

2.2.1 表單認(rèn)證的過程說明Spring Security 支持從 HTML 的 Form 表單形式提交登錄用戶信息。表單認(rèn)證可分為以下步驟:用戶請(qǐng)求受保護(hù)資源;Spring Security 的 FilterSecurityInterceptor 對(duì)象,檢測(cè)到當(dāng)前用戶認(rèn)證未通過,應(yīng)予以拒絕,并拋出 AccessDeniedException;當(dāng) AccessDeniedException 被 ExceptionTranslationFilter 接收后,其認(rèn)定需要發(fā)起認(rèn)證流程,此時(shí)用戶被要求登錄,認(rèn)證服務(wù)器將登錄地址(默認(rèn)由 LoginUrlAuthenticationEntryPoint)返回給客戶端;客戶端瀏覽重定向到登錄頁面;登錄頁面有服務(wù)端渲染生成。圖 2 表單登錄流程當(dāng)用戶提交登錄信息,認(rèn)證服務(wù)器端的 UsernamePasswordAuthenticationFilter 就會(huì)被執(zhí)行。此過程的具體執(zhí)行過程如下:UsernamePasswordAuthenticationFilter 產(chǎn)生 UsernamePasswordAuthenticationToken,并存入從請(qǐng)求中獲取的用戶名、密碼等信息;創(chuàng)建出的 Token 被傳遞給 AuthenticationManager 用于認(rèn)證;認(rèn)證成功或失敗的后續(xù)流程同上一小節(jié)中關(guān)于 AbstractAuthenticationProcessingFilter 的執(zhí)行過程一致。2.2.2 表單認(rèn)證的開啟默認(rèn)情況下,Spring Security 開啟了表單認(rèn)證功能。如果我們需要顯式配置,可用如下方式實(shí)現(xiàn)。創(chuàng)建 Security 配置文件: src/main/java/imooc/springsecurity/usernamepassword/config/WebSecurityConfig.java,并在其中添加 http.formLogin(withDefaults()) 的配置,完整代碼如下:package imooc.springsecurity.usernamepassword.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.Customizer;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { http.formLogin(Customizer.withDefaults()); }}訪問 http://localhost:8080/user/me ,網(wǎng)頁會(huì)自動(dòng)跳轉(zhuǎn)到登錄頁面。登錄頁面輸入默認(rèn)生成的用戶名 「user」, 默認(rèn)生成密碼可在控制臺(tái)日志中找到。如下圖:提交登錄后,通過認(rèn)證,我們將在瀏覽器看到當(dāng)前登錄的用戶名。當(dāng)前登錄用戶為:「user」2.2.3 表單認(rèn)證的配置默認(rèn)情況下,表單登錄的跳轉(zhuǎn)地址是 /login,登錄參數(shù)中用戶名變量名為 username,密碼變量名為 password。如果我們需要修改這些配置信息,可以通過如下方式實(shí)現(xiàn):在 configure(HttpSecurity http) 方法中,為 http 的 formLogin 項(xiàng)修改配置。 protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login").permitAll() // 表單認(rèn)證頁面不需要權(quán)限 .anyRequest().authenticated(); // 其他頁面需要登錄用戶才能訪問 http.formLogin() .loginPage("/login") // 自定義表單認(rèn)證頁面地址 .usernameParameter("user") .passwordParameter("pass"); http.csrf().disable(); // 關(guān)閉 csrf 以通過認(rèn)證,注意,這不是最好的做法,后續(xù)章節(jié)會(huì)有介紹。 }當(dāng)然這一步中配置 /login 頁面需要我們自己去實(shí)現(xiàn)。這里有幾個(gè)需要注意的地方:自定義表單提交地址為 /login ,提交方法僅支持 POST;表單需要支持 CSRF 票據(jù),即附帶 _csrf 參數(shù);用戶名字段需要命名為 user;密碼字段需要命名為 pass;當(dāng)認(rèn)證失敗時(shí),表單頁面會(huì)收到 error 參數(shù);當(dāng)用戶退出成功時(shí),表單頁面會(huì)收到 logout 參數(shù)。為了測(cè)試上述配置,我們創(chuàng)建一個(gè)測(cè)試登錄頁:新建 src/main/java/imooc/springsecurity/usernamepassword/controller/LoginController.java。package imooc.springsecurity.usernamepassword.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;@Controllerpublic class LoginController { @RequestMapping("/login") public String viewLogin2() { return "/login.html"; }}新建 src/main/resources/templates/login.html<form method="post" action="/login"> <input type="text" name="user"> <input type="password" name="pass"> <input type="submit" value="登錄"></form>訪問測(cè)試:http://localhost:8080/user/me ,此時(shí)跳轉(zhuǎn)到我們新建的登錄頁面。登錄頁面輸入用戶名密碼后可看到用戶信息。

4.5 protocols ()、hidden () 和 code () 屬性

定義:protocols () 屬性就是對(duì)接口所使用的網(wǎng)絡(luò)協(xié)議進(jìn)行一個(gè)約定,常用的網(wǎng)絡(luò)協(xié)議有:http、https。hidden () 屬性就是控制接口在 Swagger 界面中的顯隱性。code () 屬性就是控制接口的返回狀態(tài),常見的有:200,201,404,500 等。使用方法:protocols () 屬性默認(rèn)值為空,但是 Swagger 在處理時(shí)會(huì)默認(rèn)獲取項(xiàng)目所采用的網(wǎng)絡(luò)協(xié)議,我們可以不用專門設(shè)置,如果一定要設(shè)置該屬性,則只允許設(shè)置 http 協(xié)議規(guī)定的屬性,不能隨意設(shè)置,http, https, ws, wss 這些都是被允許的。code () 屬性一般不用特定設(shè)置, Swagger 會(huì)自動(dòng)生成接口返回狀態(tài),這里不再演示。hidden () 屬性允許我們?cè)?Swagger 生成的接口列表界面上控制接口是否需要顯示,默認(rèn)值為 false,即接口顯示,為 true 時(shí)則接口不顯示,如下代碼段所示。@ApiOperation(hidden = true)public CommonResponse<User> userLogin(HttpSession session, @RequestBody User user){ return userService.login(session, user);}代碼解釋:第 1 行,我們?cè)?userLogin 方法的上方使用了 @ApiOperation 注解的 hidden 屬性來隱藏我們的接口。顯示結(jié)果:可以看到在接口列表界面,已經(jīng)看不到我們的用戶登錄接口了,這就是當(dāng) hidden 屬性設(shè)置為 true 時(shí)所起的作用。Tips :接口的顯隱控制應(yīng)該根據(jù)特定安全策略和特定客戶需求來決定顯隱,不能無故隱藏接口,更不能頻繁的切換接口的顯隱。在實(shí)際工作中,如果需要隱藏接口則需要和項(xiàng)目組報(bào)備情況,說明原因。

4.7 編寫映射文件

分別針對(duì) OrderDao 、 ErpOrderDao 編寫對(duì)應(yīng)的映射文件,然后按照配置類指定的位置,兩個(gè)文件分別放到 resources/mapper1 和 resources/mapper2 目錄下。實(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.springbootmultidb.mapper1.OrderDao"> <!-- 對(duì)應(yīng)OrderDao中的insert方法 --> <insert id="insert" parameterType="com.imooc.springbootmultidb.mapper1.OrderDo" useGeneratedKeys="true" keyProperty="id"> insert into `order` (goods_id,count) values (#{goodsId},#{count}) </insert></mapper>實(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)ErpOrderDao接口 --><mapper namespace="com.imooc.springbootmultidb.mapper2.ErpOrderDao"> <!-- 對(duì)應(yīng)ErpOrderDao中的insert方法 --> <insert id="insert" parameterType="com.imooc.springbootmultidb.mapper2.ErpOrderDo"> insert into erp_order (out_id,goods_id,count) values (#{outId},#{goodsId},#{count}) </insert></mapper>

2.1 expires指令

Nginx 中的 expires 指令通過控制 HTTP 相應(yīng)中的" Expires" 和 "Cache-Control"的頭部值,達(dá)到控制瀏覽器緩存時(shí)間的效果。指令格式如下:Syntax: expires [modified] time;expires epoch | max | off;Default: expires off;Context: http, server, location, if in locationNginx 中的時(shí)間單位有s(秒), m(分), h(小), d(天)。指令參數(shù)說明:epoch: 指定"Expires"的值為1, 即 January,1970,00:00:01 GMT;max: 指定"Expires"的值為31 December2037 23:59:59GMT, "Cache-Control"的值為10年;-1:指定"Expires"的值為當(dāng)前服務(wù)器時(shí)間-1s,即永遠(yuǎn)過期;off:不修改"Expires"和"Cache-Control"的值time中出現(xiàn)@表示具體的時(shí)間,比如@18h30m表示的是下午6點(diǎn)半;官方的示例如下:expires 24h; # 24小時(shí)過期expires modified +24h;expires @24h;expires 0; # 不緩存,立即過期expires -1; # 用不過期expires epoch;expires $expires;

1.2 XMLHttpRequest.status

服務(wù)器響應(yīng)完成之后,我們通常會(huì)使用 XMLHttpRequest.status 來查看當(dāng)前 XMLHttpRequest 響應(yīng)中的數(shù)字狀態(tài)碼。這個(gè)數(shù)字狀態(tài)碼是一個(gè)無符號(hào)短整型狀態(tài)碼,代表著我們的 Ajax 請(qǐng)求的狀態(tài)成功與否。在 XMLHttpRequest 中, status 碼對(duì)應(yīng)著標(biāo)準(zhǔn)的 HTTP 狀態(tài)碼。并且在請(qǐng)求完成前,該值為 0。HTTP 狀態(tài)碼很多,這里就不做過多的鋪開,具體可以到 HTTP 響應(yīng)代碼 進(jìn)行學(xué)習(xí)和查閱。接下來我們來講幾個(gè)常見的狀態(tài)碼。是的,這也是很常見的兩個(gè)狀態(tài)碼。1.2.1 200 和 304 狀態(tài)碼在 HTTP 狀態(tài)碼中,200 代表著 HTTP 請(qǐng)求成功,而 304 代表著由于瀏覽器緩存原因,GET 請(qǐng)求命中并返回了緩存中的數(shù)據(jù)。結(jié)合 上面 XMLHttpRequest.readyState , 假設(shè)請(qǐng)求成功,我們的響應(yīng)模塊應(yīng)該如下:xhr.open("GET", "http://localhost:8080/simple/get?mk=慕課網(wǎng)");xhr.send();xhr.onreadystatechange = function() { // 當(dāng)前 this 為 xhr if (this.readyState == 4) { if (this.status === 200 || this.status === 304) { // code ... } }};在后端設(shè)置了協(xié)商緩存的情況下,我們來看看效果:第一次請(qǐng)求資源:刷新頁面,進(jìn)行第二次請(qǐng)求同樣的資源:由于瀏覽器的緩存機(jī)制,GET請(qǐng)求有可能會(huì)緩存我們的請(qǐng)求內(nèi)容。上面前后兩次請(qǐng)求中,第一次請(qǐng)求的時(shí)候獲取新的內(nèi)容,返回的是 200 的狀態(tài)碼;而第二次再進(jìn)行獲取,我們就有可能獲取第二圖的結(jié)果,使用的是本地緩存。因此,在對(duì) Ajax 成功的判斷中,我們不應(yīng)該遺漏 304 狀態(tài)碼的判斷。1.2.2 404 和 500 狀態(tài)碼有正確的返回,那當(dāng)然也會(huì)有錯(cuò)誤的返回。打個(gè)比方,讓我們來假設(shè)這樣的場(chǎng)景:客戶端發(fā)送一個(gè)請(qǐng)求,剛好請(qǐng)求的接口找不到,因?yàn)榉?wù)端并沒有提供??蛻舳税l(fā)送一個(gè)請(qǐng)求,服務(wù)端內(nèi)部發(fā)生錯(cuò)誤了。如果遇到這樣的情況,Ajax 當(dāng)然不能坐以待斃——我們總不該不把任何響應(yīng)告訴用戶吧!真實(shí)的情況是,Ajax 會(huì)返回我們相應(yīng)的 status ,客戶端根據(jù)該 status 進(jìn)行必要的操作。首先,我們來請(qǐng)求一個(gè)捏造的接口,即服務(wù)端并沒有支持的接口。html 關(guān)鍵容器:<div id="container"></div>JavaScript 腳本關(guān)鍵代碼:var container = document.getElementById('container')xhr.onreadystatechange = function() { // 當(dāng)前 this 為 xhr if (this.readyState == 4) { if (this.status === 200 || this.status === 304) { container.innerHTML = "當(dāng)前狀態(tài)碼為: " + this.status; } else { container.innerHTML = "當(dāng)前錯(cuò)誤狀態(tài)碼為: " + this.status; // 主要看這里,出現(xiàn)非 200 和 304 狀態(tài)會(huì)在這邊進(jìn)行顯示 } }};看看運(yùn)行后的效果圖:404 Not Found,顯而易見,當(dāng)我們?cè)诓樵兊臅r(shí)候,服務(wù)端找不到對(duì)應(yīng)的資源的時(shí)候就會(huì)返回該狀態(tài)碼,表示你要找的東西沒有,不存在。在我們的實(shí)際工作中,我們經(jīng)常會(huì)遇到這樣的錯(cuò)誤,往往這個(gè)時(shí)候你就應(yīng)該警惕:是不是你的請(qǐng)求 url 寫錯(cuò)了?是不是前后端線上資源不同步?比方說后端還沒上線對(duì)應(yīng)接口而你已經(jīng)在開始在代碼中請(qǐng)求了。講完 404 狀態(tài)碼,我們接下來繼續(xù)來看看一個(gè)很常見的場(chǎng)景,服務(wù)器內(nèi)部發(fā)生錯(cuò)誤了?。?!代碼沿用上一個(gè)示例,接口改為服務(wù)端提供的接口,這次我們會(huì)在服務(wù)端假設(shè)發(fā)生錯(cuò)誤,并返回 500 錯(cuò)誤。來看看請(qǐng)求的結(jié)果:事實(shí)上,500 錯(cuò)誤碼也是非常常見的,500 Internal Server Error 代表著服務(wù)端錯(cuò)誤,如果我們?cè)陂_發(fā)過程中遇到這樣的錯(cuò)誤,那么,就需要后端的同學(xué)來查找原因了。除此之外,HTTP 狀態(tài)碼還有很多,每個(gè)都有不同的含義,這里也不會(huì)做過多的展開,有興趣的同學(xué)可以做一個(gè)額外的學(xué)習(xí)查閱。HTTP 協(xié)議中,狀態(tài)碼可以讓我們?cè)谡?qǐng)求之后,獲知請(qǐng)求的狀態(tài)??蛻舳艘材軌蛞源俗龀鱿鄳?yīng)的響應(yīng)。

2.1 TextView 的陰影效果

下面我們加上一些高級(jí)屬性,可以做出不同尋常的效果:<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:padding="20dp" android:shadowColor="#5919C9" android:shadowDx="20.0" android:shadowDy="20.0" android:shadowRadius="4.5" android:text="跟著超哥學(xué)Android" android:textColor="#BECF0B" android:textSize="40sp" />我們?cè)诘谝粋€(gè) demo 的基礎(chǔ)之上加上了陰影效果,如圖:

4. 小結(jié)

本章節(jié)和大家講解了 Spring MVC 中如何優(yōu)雅的處理異常。主要有 3 種方案:將異常映射成為 HTTP 狀態(tài)碼;使用全局異常處理組件。建議大家使用這種方式,具有很多的隔離性、統(tǒng)一性;使用注解的方式處理異常。

5. 別名 typeAliases

MyBatis 在指定 Java 類時(shí)需要使用到類的全路徑,如 com.imooc.mybatis.model.Blog,typeAliases 可以為全路徑定義一個(gè)別名,這樣就能減少一定的重復(fù)工作。例如,將 com.imooc.mybatis.model.Blog 的別名定義為 Blog:<?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> <typeAliases> <typeAlias type="com.imooc.mybatis.model.Blog" alias="Blog"/> </typeAliases></configuration>MyBatis 還支持為一個(gè)包下所有類定義別名:<?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> <typeAliases> <package name="com.imooc.mybatis.model"/> </typeAliases></configuration>這樣在 com.imooc.mybatis.model 包中的所有類都有了別名,每個(gè)類的別名都是其類的名稱首字母小寫,如 Author 類的別名為 author。

3. 現(xiàn)狀

目前大家依然比較習(xí)慣用 SSL 稱呼,但是 SSL 系列的所有版本都已經(jīng)棄用了。TLS 可以認(rèn)為是 SSL 的后續(xù)版本,低版本的 TLS 有存在較為明顯的漏洞,所以并不是選擇了 Https 就是決定安全的,建議至少選擇 TLS 1.2 以上版本。近年來各個(gè)大廠都不留余力的推動(dòng) Https ,騰訊小程序更是要求全站啟用 Https 。在選擇了 Https 的情況下我們要明白一點(diǎn),Https 與 Http 相比,需要更多的 CPU 資源去計(jì)算加密算法的。我們的架構(gòu)在設(shè)計(jì)的時(shí)候我是比較建議網(wǎng)關(guān)對(duì)外部分用 Https 算法,站點(diǎn)內(nèi)部的服務(wù)間通信用 Http 或 Rpc 協(xié)議。

5. 訪問接口文檔網(wǎng)頁

瀏覽器訪問 http://127.0.0.1:8000/docs/,即可看到自動(dòng)生成的接口文檔 。注意:視圖集 ViewSet 中的 retrieve 方法,在接口文檔中被稱作 read;參數(shù)的 Description 需要在模型類或序列化器類的字段中以 help_text 選項(xiàng)定義,如:class StudentsModel(models.Model): ... s_name = models.CharField(max_length=8, verbose_name='學(xué)生姓名', help_text='學(xué)生姓名') ...

2.5 測(cè)試

現(xiàn)在我們的后端 Spring Boot 應(yīng)用已啟動(dòng),前端項(xiàng)目也通過 nginx 啟動(dòng)起來。我們?cè)跒g覽器地址欄打開 http://x.x.x.x/shop-front/goods.html ,效果如下,說明我們的項(xiàng)目全部部署成功。項(xiàng)目部署成功后頁面顯示效果

3.1 定義 Spinner 布局

布局文件很簡(jiǎn)單,直接在根布局中放置一個(gè) Spinner 即可:<?xml version="1.0" encoding="utf-8"?><Spinner xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/spinner" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" />Spinner 的屬性都比較好理解,大家可以在閱讀的同時(shí)自行添加嘗試。

1. 調(diào)試工具

Http 協(xié)議及其請(qǐng)求過程是用于瀏覽器與后臺(tái)服務(wù)的數(shù)據(jù)交互的,選擇一個(gè)瀏覽器,筆者下面用 chrome 進(jìn)行演示。打開瀏覽器;快捷鍵按下 F12;地址欄輸入 http://idcbgp.cn/;觀察底部 network 欄目的網(wǎng)絡(luò)請(qǐng)求信息。

10. Age

Age 主要記錄的是代理服務(wù)器跟原站的響應(yīng)時(shí)間差,如果 Age: 0,它可能只是從原始服務(wù)器獲取; 否則它通常是根據(jù)代理的當(dāng)前日期和Date HTTP 響應(yīng)中包含的通用頭部之間的差異來計(jì)算的。

4. 數(shù)據(jù)庫訪問 <a href="http://db.py">db.py</a>

在 db.py 中完成數(shù)據(jù)庫訪問相關(guān)的函數(shù),db.py 分為如下幾個(gè)部分:

6. 小結(jié)

這里介紹了 Nginx 處理 Http 請(qǐng)求的 11 個(gè)階段,并重點(diǎn)介紹了 前三個(gè)階段POST_READ、REWRITE以及FIND_CONFIG以及這些階段中涉及到的模塊和指令。前面講到的指令都是 Nginx 中的高頻指令,必須要熟練掌握。

1.2 接收數(shù)據(jù)

Socket 監(jiān)聽連接,在沒有連接到來之前一直是阻塞在 serverSocket.accept(); 有請(qǐng)求過來就可以運(yùn)行到下面的代碼,然后可以根據(jù)我們的輸入流讀取信息,根據(jù) Http 協(xié)議拆開獲取我們要的請(qǐng)求數(shù)據(jù)。

2.1 資源

在通訊錄的 RESTful 架構(gòu)中,聯(lián)系人被抽象為一種資源,使用 URI 表示如下:http://localhost/users/123每個(gè)聯(lián)系人都有自己的 URI,同時(shí),每個(gè)聯(lián)系人有一個(gè)唯一的 id,/users/123 是 id 為 123 的聯(lián)系人對(duì)應(yīng)的 URI。

6. gradle.projects 文件

這個(gè)文檔一般我們?cè)谌粘i_發(fā)中不需要去動(dòng)它,這個(gè)文檔主要是項(xiàng)目范圍的梯度設(shè)置,通過 AndroidStudio 配置的漸變?cè)O(shè)置將覆蓋此文件中指定的任何設(shè)置。# Project-wide Gradle settings.# IDE (e.g. Android Studio) users:# Gradle settings configured through the IDE *will override*# any settings specified in this file.# For more details on how to configure your build environment visit# http://www.gradle.org/docs/current/userguide/build_environment.html# Specifies the JVM arguments used for the daemon process.# The setting is particularly useful for tweaking memory settings.org.gradle.jvmargs=-Xmx1024m# When configured, Gradle will run in incubating parallel mode.# This option should only be used with decoupled projects. More details, visit# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects# org.gradle.parallel=true通過上面的代碼我們看到主要有一句沒有注釋,第 9 行,這句的作用就是設(shè)置運(yùn)行時(shí)的最大內(nèi)存。Tips: 這里分享一個(gè)經(jīng)驗(yàn),如果你的電腦編譯項(xiàng)目耗時(shí)比較久,我們可以修改這個(gè)文件的配置,適當(dāng)增加編譯時(shí)的內(nèi)存,使 Gradle 獨(dú)立運(yùn)行。筆者親測(cè)有效:第11、12行。# Project-wide Gradle settings.# IDE (e.g. Android Studio) users:# Gradle settings configured through the IDE *will override*# any settings specified in this file.# For more details on how to configure your build environment visit# http://www.gradle.org/docs/current/userguide/build_environment.html# Specifies the JVM arguments used for the daemon process.# The setting is particularly useful for tweaking memory settings.org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8# 提高AndroidStudio的并發(fā)性,使Gradle獨(dú)立運(yùn)行。org.gradle.parallel=trueorg.gradle.daemon=true

4.1 Shape Drawables

Shape Drawables 可以用來定義一個(gè) View 的外形、顏色、漸變等等屬性,它的最大的有點(diǎn)就是可以根據(jù)任意尺寸的 View進(jìn)行自適應(yīng),代碼示例如下:<?xml version="1.0" encoding="UTF-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <stroke android:width="10dp" android:color="#FFFFFFFF" /> <gradient android:endColor="#EF3434AB" android:startColor="#FF98df9d" android:angle="45" /> <corners android:bottomRightRadius="10dp" android:bottomLeftRadius="10dp" android:topLeftRadius="5dp" android:topRightRadius="5dp" /></shape>以上代碼分別為背景設(shè)置了邊框、漸變、角弧度,編寫完直接作為背景資源設(shè)置即可:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/myshape" android:orientation="vertical" > <EditText android:id="@+id/editText1" android:layout_width="match_parent" android:layout_height="wrap_content" > </EditText> <RadioGroup android:id="@+id/radioGroup1" android:layout_width="match_parent" android:layout_height="wrap_content" > <RadioButton android:id="@+id/radio0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="@string/celsius" > </RadioButton> <RadioButton android:id="@+id/radio1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/fahrenheit" > </RadioButton> </RadioGroup> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/calc" android:onClick="myClickHandler"> </Button></LinearLayout>

2.3 后端服務(wù) <a href="http://app.py">app.py</a>

2.3.1 引入相關(guān)模塊#!/usr/bin/python3from flask import Flask, request, session, render_template, redirectimport os, base64import sysapp = Flask(__name__)app.config['SECRET_KEY'] = os.urandom(24)在第 2 行,引入 os 模塊和 base64 模塊,需要使用 os.urandom 和 b64encode 用于生成 CSRF Token。在第 7 行,F(xiàn)lask 程序在使用 Session 時(shí),需要配置 SECRET_KEY。2.3.2 用戶數(shù)據(jù)庫class User: def __init__(self, name, password, amount): self.name = name self.password = password self.amount = amountusers = [ User('victim', '123', 100), User('hacker', '123', 100)]def findUser(name): for user in users: if user.name == name: return user return Nonedef checkUser(name, password): for user in users: if user.name == name and user.password == password: return user return None在第 1 行,定義類 User 用于描述銀行賬戶信息,包括:姓名、密碼、賬戶余額等屬性。在第 7 行,預(yù)定義了兩個(gè)用戶:victim 和 hacker,將它們存儲(chǔ)在全局變量 users 中。在第 12 行,定義函數(shù) findUser,在 users 中根據(jù)姓名查找 user。在第 18 行,定義函數(shù) checkUser,在 users 中根據(jù)姓名和密碼查找 user。2.3.3 首頁面@app.route('/')def index(): hasLogin = session.get('hasLogin') name = session.get('name') user = findUser(name) csrfToken = getCsrfToken() session['csrfToken'] = csrfToken return render_template('index.html', hasLogin = hasLogin, user = user, csrfToken = csrfToken)設(shè)置首頁面 / 的處理函數(shù)為 index,函數(shù)在 session 中查找 hasLogin、name、csrfToken 變量,將它們傳遞給頁面模板 index.html。在第 6 行,調(diào)用函數(shù) getCsrfToken() 生成一個(gè)隨機(jī)的、不可預(yù)測(cè)的 CSRF Token,并將其存儲(chǔ)在 Session 中。2.3.4 登錄頁面@app.route('/login', methods = ['POST'])def login(): name = request.form['name'] password = request.form['password'] user = checkUser(name, password) if user != None: session['hasLogin'] = True session['name'] = name return redirect('/') else: return '登錄失敗'設(shè)置頁面 /login 的處理函數(shù)為 login,該函數(shù)首先提取請(qǐng)求中的 name 和 password,然后調(diào)用 checkUser 在所有的 users 中查找匹配的 User。如果找到了匹配的 User,則設(shè)置 Session 中的 hasLogin 為真,調(diào)用 redirect(’/’) 讓客戶端瀏覽器重定向到首頁面。2.3.5 退出頁面@app.route('/logout', methods = ['POST'])def logout(): session['hasLogin'] = False session['name'] = None return redirect('/')設(shè)置頁面 /logout 的處理函數(shù)為 logout,該函數(shù)設(shè)置 Session 中的 hasLogin 為假,調(diào)用 redirect(’/’) 讓客戶端瀏覽器重定向到首頁面。2.3.5 檢查 CSRF 攻擊def getCsrfToken(): return bytes.decode(base64.b64encode(os.urandom(16)))def checkCsrfAttack(): csrfTokenFromRequest = request.form.get('csrfToken') csrfTokenFromSession = session.get('csrfToken') return csrfTokenFromRequest != csrfTokenFromSession函數(shù) getCsrfToken 返回一個(gè)隨機(jī)的字符串,os.urandom(16) 產(chǎn)生一個(gè)包含 16 個(gè)字節(jié)的 bytes,base64.b64encode 將 bytes 轉(zhuǎn)換為 base64 編碼的字符串。函數(shù) checkCsrfAttack 檢測(cè) CSRF 攻擊,在第 5 行,從請(qǐng)求的表單中獲取參數(shù) csrfToken,在第 6 行,從 Session 中獲取變量 csrfToken。對(duì)兩者進(jìn)行比較,如果相等,表示此次請(qǐng)求合法;如果不相等,表示此次請(qǐng)求是 CSRF 攻擊。2.3.6 轉(zhuǎn)賬頁面@app.route('/transfer', methods = ['POST'])def transfer(): if not session.get('hasLogin'): return '請(qǐng)先登錄' if checkFlag and checkCsrfAttack(): print('警告:檢測(cè)到 CSRF 攻擊!') return '轉(zhuǎn)賬失敗' sourceName = session['name'] sourceUser = findUser(sourceName) targetName = request.form['name'] amount = int(request.form['amount']) targetUser = findUser(targetName) if targetUser != None: sourceUser.amount -= amount targetUser.amount += amount return redirect('/') else: return '轉(zhuǎn)賬失敗'設(shè)置頁面 /transfer 的處理函數(shù)為 transfer。在第 3 行,如果 Session 中的 hasLogin 變量未假,表示請(qǐng)求來自于未登錄的用戶,返回 ‘轉(zhuǎn)賬失敗’。在第 6 行,如果 checkFlag 為真并且 checkCsrfAttack 函數(shù)檢測(cè)到了 CSRF 攻擊,在控制臺(tái)打印 CSRF 的警告,返回 ‘轉(zhuǎn)賬失敗’。如果 checkFlag 為假,程序不檢測(cè) CSRF 攻擊。在第 10 行到第 15 行,獲取來源賬戶,并從轉(zhuǎn)賬請(qǐng)求中獲取參數(shù):轉(zhuǎn)賬數(shù)量、接受賬戶。在第 18 行和第 19 行,進(jìn)行轉(zhuǎn)賬操作,最后調(diào)用 redirect(’/’) 讓客戶端瀏覽器重定向到首頁面。2.3.6 設(shè)置選項(xiàng)checkFlag = Falseif len(sys.argv) == 2 and sys.argv[1] == 'check': checkFlag = Trueapp.run(debug = True, port = 8888)設(shè)置全局變量 checkFlag,如果 checkFlag 為真,程序檢測(cè) CSRF 攻擊;如果 checkFlag 為假,程序不檢測(cè) CSRF 攻擊。

2. 請(qǐng)求作用域

WEB 程序的應(yīng)用層使用的是 HTTP 協(xié)議,HTTP 協(xié)議有一個(gè)特點(diǎn),無狀態(tài)。所謂無狀態(tài)指上一次請(qǐng)求與下一次請(qǐng)求之間是隔離的,沒有內(nèi)在的聯(lián)系。更通俗的講,可理解為一個(gè)患有健忘癥的人,只記得當(dāng)前自己在做什么,不記得自己曾經(jīng)做過什么,更不會(huì)知道自己將來要做什么。HTTP 協(xié)議的這種無狀態(tài),最初設(shè)計(jì)時(shí)是從安全角度考慮。但是,在某些應(yīng)用場(chǎng)景下,如購物車的應(yīng)用場(chǎng)景下,卻顯得無能為力。購物車中的商品不一定是一次請(qǐng)求下的結(jié)果,往往是多次請(qǐng)求下的結(jié)果。也就是說,購物車需要保存每一次請(qǐng)求獲取到的數(shù)據(jù)。顯然,直接使用 HTTP 協(xié)議是無法做到的。就需從技術(shù)層面上提供解決方案。原生 Servlet 提供了 3 個(gè)作用域,可以根據(jù)用戶的需要來保存每一次請(qǐng)求過程中產(chǎn)生的數(shù)據(jù)。請(qǐng)求作用域: 使用 HttpServletRequest 組件存儲(chǔ)的數(shù)據(jù)可以在每一次的請(qǐng)求周期內(nèi)存在。 請(qǐng)求結(jié)束,數(shù)據(jù)也將消失;會(huì)話作用域: 使用 HttpSession 組件保存的數(shù)據(jù)能在整個(gè)會(huì)話生命周期內(nèi)存在。如購物車就可以保存在會(huì)話作用域中;應(yīng)用程序作用域: 使用 ServletContext 組件保存的數(shù)據(jù)在整個(gè)應(yīng)用程序生命周期之內(nèi)存在。Spring MVC 中,把數(shù)據(jù)保存在請(qǐng)求作用域,或是說在整個(gè)請(qǐng)求過程中數(shù)據(jù)都有效。有 2 種解決方案直接使用 HttpServletRequest 組件;使用 Spriing MVC 提供的高級(jí)數(shù)據(jù)模型組件。

3.1 編寫 Activity 的布局文件

和前幾節(jié)的例子一樣,我們僅需要在根布局中防止一個(gè) ExpandableListView 即可,然后設(shè)置上相應(yīng)的屬性,如下:<ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/expandableListView" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="@android:color/darker_gray" android:dividerHeight="0.5dp" android:indicatorLeft="?android:attr/expandableListPreferredItemIndicatorLeft" android:padding="30dp" />

2.1 創(chuàng)建倉庫

創(chuàng)建代碼倉庫的步驟跟之前一樣,但是要注意的是,這里起名很有講究,格式是http://username.github.io,其中 username 是你 GitHub 的用戶名,這個(gè)名字不能出錯(cuò),如果出錯(cuò),則需要?jiǎng)h除倉庫重新來。

3.1 Curator 依賴

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.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.cdd</groupId> <artifactId>curator-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>curator-demo</name> <description>curator-demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.1.0</version> </dependency> <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>引入 Curator 的依賴后,我們先來介紹一下 Curator 的 API ,然后再編寫測(cè)試用例進(jìn)行 API 測(cè)試。

6.1 常見錯(cuò)誤

在 Windows 操作系統(tǒng)中,使用 Python 輸出中文時(shí),可能會(huì)出現(xiàn)亂碼的的情況,例如,使用 Windows 自帶的記事本編寫程序 chinese.py,內(nèi)容如下:10運(yùn)行該程序,輸出結(jié)果如下:C:\>python chinese.py File "chinese.py", line 1SyntaxError: Non-UTF-8 code starting with '\xd6' in file chinese.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

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

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

幫助反饋 APP下載

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

公眾號(hào)

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