整合營銷服務商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          spring cloud security oaut

          spring cloud security oauth2請求資源服務器401的排查過程

          文的環(huán)境是參考這篇《Spring Cloud OAuth2 實現(xiàn)用戶認證及單點登錄”》搭建的。

          https://www.cnblogs.com/fengzheng/p/11724625.html

          問題描述

          當前已經(jīng)獲取到了access_token,根據(jù)access_token請求自己的接口時報了401,token驗證有問題。

          我自己的接口是:

          /**
               * 一個 RESTful 方法,只有當訪問用戶具有 ROLE_ADMIN 權(quán)限時才能訪問,否則返回 401 未授權(quán)。
               *
               * 通過 Authentication 參數(shù)或者 SecurityContextHolder.getContext().getAuthentication() 可以拿到授權(quán)信息進行查看。
               * @param authentication
               * @return
               */
              @GetMapping("get")
              @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
              public Object get(Authentication authentication){
                  authentication.getCredentials();
                  OAuth2AuthenticationDetails details=(OAuth2AuthenticationDetails) authentication.getDetails();
                  String token=details.getTokenValue();
                  return token;
              }

          解決方式

          org.springframework.security.oauth2.provider.token.RemoteTokenServices

          我們將這個類復制出來,在java下創(chuàng)建相同的包名,類。

          源碼圖

          在源碼中,這里返回的active應該得為Boolean類型,但是這個map的值是個Object,返回直接返回了個String類型。

          他使用了Boolean和String類型進行比較,所以我得到的返回結(jié)果永遠都是false了,就一直走了這個異常。(具體他為什么會這樣來比較,我真不太懂,歡迎大佬們在評論里指點一下)

          修改后的圖

          那么我的處理就比較簡單粗暴了,直接把這個類提出來,然后把這個Boolean.TRUE.toString()一下。類型一致,比較就正常了。

          再次去請求,現(xiàn)在就沒有問題了,返回成功

          本文只是個人在學習過程中的一次排查處理過程,如果有更好的見解歡迎大佬們指點。

          者:redmed

          背景

          Web 項目中經(jīng)常會遇到處理 URL 中 Query 的情況,來看下下面問題你有疑惑嗎?

          • 項目中發(fā)現(xiàn)會用到 qsquery-stringURLSearchParams、甚至 querystring 幾種不同的庫,其到底差異在哪里,我該用哪個?
          • 在 query 中 key=a&key=b 這種情況 key 取值是什么?和 key[]=a&key[]=b 有區(qū)別嘛?
          • 在 query 中會有結(jié)構(gòu)如 %HH 的數(shù)據(jù),為什么是這樣形式的?我們?yōu)槭裁匆褂?encodeURIComponent 進行編碼?和過時的 escape 又有何區(qū)別?
          • Content-typex-www-form-urlencoded 的取值,是怎么一回事?

          于是梳理一下關(guān)于 URL Query 的相關(guān)知識點,用來去偽解惑。

          URL QueryString

          首先介紹下 Query String 的基本概念,這是一切問題的開始。下面是 wiki[1] 的描述:

          A query string is a part of a uniform resource locator[2] (URL) that assigns values to specified parameters.

          通常的理解就是 URL 中問號(?)后面的部分,其設計最初是用做 HTML form 表單提交時的傳參。

          基本結(jié)構(gòu)

          下面我們看下 query 的基本結(jié)構(gòu) field1=value1&field2=value2&field3=value3...

          包含了如下標準:

          1. Query String 由一組鍵值對(field-value)組成;
          2. 每組鍵值對的 fieldvalue=分割;
          3. 每組數(shù)據(jù)用&分割;

          補充個冷知識:除了使用&分割每對數(shù)據(jù)外,W3C 曾在 1999 年建議所有 Web 服務器同時支持分號;分割符:

          We recommend that HTTP server implementors, and in particular, CGI implementors support the use of ";" in place of "&" to save authors the trouble of escaping "&" characters in this manner.

          但在 2014 年以來,就只建議使用 & 作為分隔符了。也就目前我們用到的方式。

          1. 允許多個value被關(guān)聯(lián)到同一個field上,但 field 如何取值,其實并無明確的處理標準。

          例如:field=a&field=b時,field 的值應該是 a、b、['a', 'b']、'a, b' 并無任何權(quán)威解釋。

          關(guān)于處理標準這點實在令人出乎意料。通常這類情況會按照數(shù)組的方式處理,即 field 值為 ['a', 'b'],但這僅是不同的框架的決定了如何實現(xiàn)而已。

          關(guān)于這個問題可以前往 stackoverflow[3] 上查看。

          數(shù)據(jù)編碼

          前面定義好了整體結(jié)構(gòu),接下來我們看下數(shù)據(jù)是如何在 query 中傳輸?shù)摹?/span>

          由于某些字符集(如中文)和在 URL 中有特殊含義的字符(如 空格、%、&、=、?、# 等)無法直接在 Query String 中使用,因此使用了一種叫做「百分號編碼[4] Percent-encoding[5]」的方式先將這類特殊字符進行編碼后,再進行傳輸。

          其基本結(jié)構(gòu)就是 % + 2 個 16 進制數(shù)字(一個 Byte 的內(nèi)容),范圍 %00 - %FF。

          具體規(guī)則如下:

          • 對保留字符進行編碼,具體對應如下:

          ! # $ & ' ( ) * + , / : ; = ? @ [ ] %21 %23 %24 %26 %27 %28 %29 %2A %2B %2C %2F %3A %3B %3D %3F %40 %5B %5D

          其對應的就是這些字符的 ASCII 編碼的 16 進制格式;

          • 如下非保留字符不進行編碼,包含:[A-Z][a-z][0-9]-_.~
          • %百分號編碼為%25
          • 空格編碼為+%20
          • 其余字符數(shù)據(jù)使用某種編碼方式轉(zhuǎn)換為字節(jié)流,再用百分號編碼%HH方式表示。
            這里需要注意的是,由于早期規(guī)范中未明確應使用何種編碼,所以會導致如果不明確說明使用何種編碼,數(shù)據(jù)的解析會有歧義。因此在 2005 年發(fā)布的 RFC 3986 [6]建議是先轉(zhuǎn)成 UTF-8 編碼,再對每個字節(jié)進行%HH的編碼。

          注意,如果使用 from 表單 action 方式時,具體編碼會根據(jù) meta 頭的 charset 的選擇。

          當然上述只是標準,實踐中 JavaScript 內(nèi)置了使用 UTF-8 編碼的 encodeURI/encodeURIComponent 函數(shù),大大簡化的編碼過程。

          關(guān)于指定編碼,這里有個有趣的事情:

          在使用百度時,你會發(fā)現(xiàn) URL 中有個 ie 參數(shù),其實含義就是 Input Encoding(對,不是 IE 瀏覽器),目的就是指定關(guān)鍵詞 wd 的編碼格式。曾默認是 GB2312(因為當時很多網(wǎng)站還使用 GB2312 編碼),當然現(xiàn)在已經(jīng)默認成 UTF-8 。(不過百度結(jié)果里依然有不少文章還在說 ie 的默認值是 GB2312 )

          可以用 https://www.baidu.com/s?wd=%E4%B8%AD&ie=gb2312 和 https://www.baidu.com/s?wd=%E4%B8%AD 來感受下他們的差異吧~

          編碼實踐

          這一節(jié)我們挑重點地對比下各類 Query String 的函數(shù)庫,了解老虎老鼠的差異,避免開發(fā)時傻傻分不清楚。

          以下僅對常用 API 的部分用法做演示,更多用法可自行查找。

          瑞士軍刀 qs

          github[7]

          A querystring parsing and stringifying library with some added security.

          官方介紹很簡單:一個增加了安全性的 Query String 解析和序列化的函數(shù)庫。

          .parse(string, [options])

          1. 對于簡單 query,可以進行常規(guī)的轉(zhuǎn)換,同時會對 fieldvalue 進行 decode 解碼。
          qs.parse('a=c&b%201=d%26e');
          
          // { a: 'c', 'b 1': 'd&e' }
          

          注意 qs 不會忽略頭部的 ?,需要自行去掉,否則會當做 field 的一部分,例如:qs.parse('?a=b')會解析為 { '?a': 'b' }

          1. 支持 query 中的嵌套對象。
          qs.parse('foo[bar]=baz');
          
          // { foo: { bar: 'baz' } }
          

          但默認子元素最多嵌套 5 層,需要通過 parse(string, [options])opinion.depth 來修改。

          // defalut
          qs.parse('a[b][c][d][e][f][g][h][i]=j');
          
          // {a: {b: {c: {d: {e: {f: {'[g][h][i]': 'j'}}}}}}}
          
          // set depth
          qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
          // { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }
          
          1. 支持自定義除&以外的分隔符。
          var delimited=qs.parse('a=b;c=d', { delimiter: ';' });
          // { a: 'b', c: 'd' }
          

          這點符合 W3C 對;支持的建議,但大部分情況應該不會用到。

          1. 支持各種 array 的解析,雖然官方文檔寫了 [] 作為數(shù)組標識,但實際上不使用[]依然可以解析。
          var withArray=qs.parse('a[]=b&a[]=c');
          // { a: ['b', 'c'] }
          
          var withArray=qs.parse('a=b&a=c');
          // { a: ['b', 'c'] }
          

          同時也支持為數(shù)組指定索引順序。

          var withIndexes=qs.parse('a[1]=c&a[0]=b');
          // { a: ['b', 'c'] };
          

          并行支持 allowSparse 獲取抽稀形式的數(shù)組。

          var sparseArray=qs.parse('a[1]=2&a[3]=5', { allowSparse: true });
          // { a: [, '2', , '5'] };
          

          但默認指定的 index 最大值為 20,如果超過最大值,則按照 object 形式解析。使用 arrayLimit控制最大值。

          var withMaxIndex=qs.parse('a[100]=b');
          // { a: { '100': 'b' } }
          
          var withArrayLimit=qs.parse('a[1]=b', { arrayLimit: 0 });
          // { a: { '1': 'b' } }
          

          .stringify(object, [options])

          這里主要介紹下 array 類型的編碼。qs 默認會對 fieldvalue 都進行編碼,同時會使用[]作為數(shù)據(jù)的標識(且默認對[]進行百分號編碼),需指定 encodeValuesOnly: true才僅對 value 編碼。

          // defalut
          qs.stringify({key: ['a', 'b']});
          // key%5B0%5D=a&key%5B1%5D=b
          
          //
          qs.stringify({key: ['a', 'b']}, { encodeValuesOnly: true });
          // key[0]=a&key[1]=b
          

          去掉[]標識,可使用 { indices: false }

          qs.stringify({key: ['a', 'b']}, { indices: false });
          // key=a&key=b
          

          支持配置 charset

          默認使用 UTF-8,內(nèi)置了 ISO-8859-1 模式,也可以支持 encoder 擴展。

          而接下來的庫僅支持 UTF-8 的編碼方式。

          簡潔專注 query-string

          github[8]

          Parse and stringify URL query strings[1]

          For browser usage, this package targets the latest version of Chrome, Firefox, and Safari.

          官方名字看起來,依舊是處理 Query String 的。

          另外,官方還送上友(wei)情(xian)提示,各位同學不要看走眼。

          Not npm install querystring !!!!!

          .parse(string, [options])

          1. 基本的解析和 qs 一樣,會對 field 和 value 進行 decode

          不過,頭部的?#的部分將被忽略,因此可以直接將 location.searchlocation.hash 傳入。

          queryString.parse('a=c&b%201=d%26e');
          
          // { a: 'c', 'b 1': 'd&e' }
          
          1. 不支持嵌套,官方建議可以使用 JSON 序列化的方式傳值。

          This module intentionally doesn't support nesting as it's not spec'd and varies between implementations, which causes a lot of edge cases[10].

          You're much better off just converting the object to a JSON string:

          1. query 中 array 的解析,默認不支持[]形式,需要指定 { arrayFormat: 'bracket' }開啟。
          queryString.parse('key=a&key=b');
          // { key: ['a', 'b'] };
          
          queryString.parse('key[]=a&key[]=b');
          // { 'key[]': ['a', 'b'] };
          
          queryString.parse('key[]=a&key[]=b', { arrayFormat: 'bracket' });
          // { key: ['a', 'b'] };
          

          當然 query-string 也支持索引的方式標記的數(shù)組,{arrayFormat: 'index'}

          queryString.parse('foo[0]=1&foo[1]=2&foo[3]=3', {arrayFormat: 'index'});
          {foo: ['1', '2', '3']}
          

          .stringify(object, [options])

          依然重點介紹 array 類型的編碼,默認不使用[]標識。

          queryString.stringify({key: ['a', 'b']});
          // key=a&key=b
          

          需要[]的話,使用 {arrayFormat: 'bracket'}開啟,默認[]也不會被 encode

          queryString.stringify({key: ['a', 'b']}, {arrayFormat: 'bracket'});
          // key[]=a&key[]=b
          

          這點和 qs 是相反的,需要特別注意!

          歷史產(chǎn)物 querystring

          NodeJS 中解析 query 的模塊。

          NodeJS 14.x[11] 中明確標記為 Legacy,官方推薦 URLSearchaParms 代替。

          The querystring API is considered Legacy. New code should use the URLSearchParams[12] API instead.

          但在 15.x 以及以后的版本又改為 Stable,但指出這是非標準 API。

          querystring is more performant than ``[13] but is not a standardized API. Use <URLSearchParams> when performance is not critical or when compatibility with browser code is desirable.

          功能類似 query-string,不支持嵌套對象的解析,這里不再贅述。

          血統(tǒng)純正 URL / URLSearchParams

          URLURLSearchParamsURL API 規(guī)范[14] 中的兩個標準的接口。其提供了訪問、操作 URL 的 API。

          其中,URL 定義了像域名、主機和 IP 地址等概念,URLSearchParams 定義了一些常用的方法來處理 Query String。我們重點介紹下后者。

          URLSearchParams

          兩種方式創(chuàng)建 URLSearchParams 對象,URLSearchParams構(gòu)造函數(shù)會忽略 search 中的?

          // 1. 通過 URL
          const url=new URL('https://abc.com/path/v1?key=a&key=b%26c');
          const search1=url.searchParams;
          
          // 2. 直接構(gòu)造
          const search2=new URLSearchParams(location.search);
          

          .get(name)

          該方法獲取的值會被自動 decode,如果 name 不存在返回 null,如果 value 不存在返回空字符串。

          const search=new URLSearchParams('key=b%26c&key2');
          search.get('key'); // b&c
          search.get('key2'); // ''
          search.get('key3'); // null
          

          .getAll(name)

          需要特別注意,如果有多個相同的 name,get() 只能獲取第一個值。獲取全部需要使用 getAll(),該函數(shù)返回數(shù)組(即便只有一個 value)。

          const search=new URLSearchParams('key=a&key=b');
          search.get('key'); // a
          search.getAll('key'); // ['a', 'b']
          

          .set(name, string) / .append(name, string)

          URLSearchParams 中添加數(shù)據(jù),set() 會覆蓋原有值。如果需要添加重復的 name,需要使用 append()

          set()append() 僅支持 string 類型的 value。同時 field 和 value 都會被 encode,無需額外處理。

          const search=new URLSearchParams();
          search.append('key', 'a');
          search.append('key', 'b');
          
          search.toString(); // key=a&key=b
          

          .keys()

          返回一個 IterableIterator迭代器,可以使用for...of遍歷。需要注意,重復的 key 會出現(xiàn)多次

          const search=new URLSearchParams('key=a&key=b');
          for (const key of search.keys()) {
            console.log(key);
          }
          // key
          // key
          

          .toString()

          獲取的 Query String,會被自動 encode 處理。空格轉(zhuǎn)成+。對于重復 field,使用了 field=v1&field=v2 的方式。

          const search=new URLSearchParams();
          search.set('key', '?&=')
          search.set('key2', 'a b');
          search.toString(); // key=%3F%26%3D&&key2=a+b
          

          兼容

          關(guān)于兼容,目前瀏覽器占比基本上沒有問題。實際開發(fā)中遇到 iOS10 以下不兼容的情況,使用 polyfill 即可。

          總結(jié)對比

          從上面的總結(jié)來看,我們發(fā)現(xiàn) qsquery-string / URLSearchParams 最大的差異在于對于多層嵌套對象(Nested object)的支持與否。

          • qs被設計用于解析x-www-form-urlencoded數(shù)據(jù),擁有強大的序列化能力,可以處理復雜的類 JSON數(shù)據(jù)。
          • query-stringURLSearchParams 則使用簡單的序列化算法,適合常規(guī)的 Web 端數(shù)據(jù)傳輸,處理平面數(shù)據(jù)結(jié)構(gòu)。
          • 對于平面數(shù)據(jù),以上效果是一樣的。

          而當使用復雜的 JSON 數(shù)據(jù)結(jié)構(gòu)時,我們通常會使用JSON.stringify() 方法先將數(shù)據(jù)進行序列化(也稱字符串化),將復雜數(shù)據(jù)轉(zhuǎn)換成基本的字符串數(shù)據(jù)后,再進行傳輸。

          • 另外如果有特殊編碼需求,除qs外都僅支持 UTF-8 的編碼。

          因此通常情況下:

          • Web 項目中解析 GET 形式的 query,使用 URLSearchParams 就足夠了(可代替query-string);
          • 而在 NodeJS 項目中,除了解析 GET query 外,還要解析 POST body 中的數(shù)據(jù),因此使用 qs 可以獲得更好的兼容性。同時不少框架也依然使用了 querystring 這個原生 API。

          expressjsbody-parser 中,用戶可以自行選擇使用 qs 還是 querystring

          koajskoa-bodybodyparser所依賴的 co-body,都選擇了qs

          當然了解了他們差異后,選擇哪種方式就要根據(jù)你的實際情況而定了。

          延伸話題

          整理資料過程中,引申出更多有趣的問題,也稍作整理。

          空格編碼問題

          還記得前面提到的編碼規(guī)則里, 空格的編碼可以是 + 或者%20,這里描述的就很模糊。

          函數(shù)對比

          我們先來看下上面不同 API 是如何處理的?

          +%20的識別都沒問題(畢竟兼容還是能做到的),但是轉(zhuǎn)換空格URLSearchParams就有不同的邏輯了。至于為什么會有兩種編碼結(jié)果?

          這里要特別說明的是URLSearchParams采用了application/x-www-form-urlencoded編碼模式,而這個編碼采用了一個非常早期(RFC 1738)的通用百分號編碼方法——就是將 空格轉(zhuǎn)換為+。至于為什么會采用這種方式,我猜想是因為要考慮到歷史兼容問題——生成的 URL 需要被那些舊的僅支持+的程序識別。

          當然+已經(jīng)不推薦了,在 RFC 3986[15] 中已推薦使用%20

          特別說明

          這里特別說明下 decodeURIComponent,是無法解析+ 空格的,因此實際業(yè)務中,如果無法保證傳入空格的編碼方式,還是使用URLSearchParams或者query-string來解析數(shù)據(jù)吧。

          或者做一個簡單的兼容處理:

          function decodeQueryParam(p) {
          return decodeURIComponent(p.replace(/+/g, " "));
          }
          
          decodeQueryParam("search+query%20%28correct%29");
          // 'search query (correct)'
          

          擴展參考

          URLSearchParams+的問題,具體細節(jié)可參考 whatwg 的描述:

          As a URLSearchParams object uses the application/x-www-form-urlencoded format underneath there are some difference with how it encodes certain code points compared to a URL object (including href and search ). This can be especially surprising when using searchParams to operate on a URL[16]’s query[17].

          URLSearchParams objects will percent-encode anything in the application/x-www-form-urlencoded percent-encode set, and will encode U+0020 SPACE as U+002B (+).

          以及 whatwg 中關(guān)于 application/x-www-form-urlencoded 的描述:

          Control names and values are escaped. Space characters are replaced by '+', and then reserved characters are escaped as described in [RFC1738][18], section 2.2: Non-alphanumeric characters are replaced by %HH, a percent sign and two hexadecimal digits representing the ASCII code of the character. Line breaks are represented as "CR LF" pairs (i.e., %0D%0A).

          Content-type 中的 x-www-form-urlencoded

          當我們在HTTP中使用 MIME 類型為x-www-form-urlencoded格式提交數(shù)據(jù)時,所使用的就是前文所介紹的編碼方式。

          只是如果發(fā)送的是 GET 請求,數(shù)據(jù)會拼接在 Query 中;而發(fā)送 POST 請求則會將數(shù)據(jù)放置在消息體(body)中,通過Header中的Content-Type 來指定 MIME 類型。

          當然并不是所有的數(shù)據(jù)都適合使用 x-www-form-urlencoded,通常有二進制數(shù)據(jù)時,urlencoded使用百分號%HHUTF-8的編碼方式,會大大增加了數(shù)據(jù)的長度。為了節(jié)省傳輸數(shù)據(jù)的空間,會選擇form-data代替。

          原生 from 表單的編碼

          除了上面提到的各類函數(shù)外,原生 html 的 form 表單在提交數(shù)據(jù)時,本身也是可以進行編碼的。

          <html>
          <head>
            <meta charset="UTF-8">
            <!--  <meta charset="GBK">-->
          </head>
          <body>
            <form action="/search" method="get" enctype="application/x-www-form-urlencoded">
              <input type="text" name="name" required>
              <input type="submit" value="提交">
            </form>
          </body>
          </html>
          

          當點擊提交時, 表單內(nèi)的 input 數(shù)據(jù)會進行百分號編碼。但要注意的是編碼的格式是按照 meta 中設定的 charset 進行的。例如當輸入「中」時,UTF-8 是 name=%E4%B8%AD,GBK 則是 name=%D6%D0。空格 則是+

          encodeURI(Component) 和 escape

          前文提到過encodeURI(encodeURIComponent)使用 UTF-8 編碼,而 escape 是一個已經(jīng)被廢棄的非標準方式,其采用了 UTF-16 編碼,同時在碼點小于 255 的使用 %uXX 表示,碼點大于 255 的使用 %uXXXX 的方式。

          同時要注意當decodeURI(decodeURIComponent)解析非法的 %HH 格式數(shù)據(jù)時(如不合規(guī)范的 UTF-8 數(shù)據(jù)、被截斷的%HH 字符等),會包拋出URIError異常。

          try {
            const a=decodeURIComponent("%E0%A4%A");
          } catch (e) {
            console.error(e);
          }
          // URIError: malformed URI sequence
          

          因此如果無法保證數(shù)據(jù)的可用性,記得總是要 try...catch 一下比較保險。

          或者更推薦使用類似`safe-decode-uri-component`[19]的三方庫,來避免這類麻煩。

          至于 UTF-8 的合法格式是什么樣的,這就要涉及更多的編碼知識了。

          參考資料

          [1] https://en.wikipedia.org/wiki/Query_string

          [2] https://en.wikipedia.org/wiki/Uniform_resource_locator

          [3] https://bytedance.feishu.cn/docx/D6LTd2zgHo2S5NxaFE6cnh9knHf#Oo6UdgOqSog2G2xsp3VcSPdIn6d

          [4] https://zh.wikipedia.org/wiki/%E7%99%BE%E5%88%86%E5%8F%B7%E7%BC%96%E7%A0%81

          [5] https://en.wikipedia.org/wiki/Percent-encoding

          [6] https://datatracker.ietf.org/doc/html/rfc3986

          [7] https://github.com/ljharb/qs

          [8] https://www.npmjs.com/package/query-string

          [9] https://www.baeldung.com/postman-form-data-raw-x-www-form-urlencoded

          [10] https://github.com/visionmedia/node-querystring/issues

          [11] https://nodejs.org/docs/latest-v14.x/api/querystring.html

          [12] https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams

          [13] https://nodejs.org/dist/latest-v19.x/docs/api/url.html#class-urlsearchparams

          [14] https://url.spec.whatwg.org/#api

          [15] https://datatracker.ietf.org/doc/html/rfc3986#section-2.1

          [16] https://url.spec.whatwg.org/#concept-url

          [17] https://url.spec.whatwg.org/#concept-url-query

          [18] https://www.w3.org/TR/html4/references.html#ref-RFC1738

          [19] https://github.com/jridgewell/safe-decode-uri-component

          [20] https://stackoverflow.com/questions/29175465/body-parser-extended-option-qs-vs-querystring/29177740#29177740

          [21] https://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2.2

          [22] https://stackoverflow.com/questions/1746507/authoritative-position-of-duplicate-http-get-query-keys

          [23] https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1

          [24] https://url.spec.whatwg.org/#interface-urlsearchparams

          [25] https://datatracker.ietf.org/doc/html/rfc1738

          [26] https://www.w3.org/International/O-URL-code.html


          關(guān)注「字節(jié)前端 ByteFE」公眾號,追更不迷路!


          文末彩蛋 >>

          碼上掘金正在舉辦第 2 期月賽,在這里邀請大家參與「互動抽獎」活動,參與互動即可抽獎哦~活動玩法請看這里 >>
          https://juejin.cn/post/7213184860675571749/

          家好,我是站長 polarisxu。

          看到標題,大家應該知曉今天聊的主角是誰。是的,它就是 PHP。

          PHP 曾經(jīng)很輝煌,現(xiàn)在怎么樣?不做過多評價,前幾天好未來不剛組織了一屆 PHP 大會嗎?!正因為曾經(jīng)很輝煌,很多現(xiàn)在的 Go 愛好者曾經(jīng)都是 PHPer,應該還有不少還在用著 PHP。我覺得完全沒必要非得貶低一門語言去抬高另外一門語言,自己喜歡就好。而且掌握多門語言是自己的優(yōu)勢。

          為什么聊 PHP,因為我也寫了好幾年 PHP,而且現(xiàn)在也會關(guān)注 PHP 的一些動態(tài)。PHP 8 發(fā)布差不多半個月了,有些人可能根本不知曉,還停留在 PHP 5.x。沒想到吧,一眨眼,PHP 8 都發(fā)布了。

          關(guān)于版本的那些事,這里不探討,主要看看 PHP 8 有哪些新特性。另外,本文只會講述新特性的一些關(guān)鍵點,因為官方文檔對它們已經(jīng)有更詳細的介紹,你應該認真閱讀官方文檔。

          01 Union Types(聯(lián)合類型)

          說明一點,從 PHP 7 開始,支持下面這樣的語法:

          function sum(int $a, int $b): int {
              return $a + $b;
          }

          是不是越來越強類型的感覺?雖然如此,但在非嚴格類型模式下(strict_types=0,這是默認值),你依然可以這么調(diào)用:

          sum(1.2, 3);

          但因為函數(shù)參數(shù)接收 int 類型(返回值也是 int 類型),因此上面結(jié)果是 4,而不是 4.2。如果是嚴格模式下,只允許傳遞 int 類型了。(sum('1.2', 3.0) 結(jié)果也是 4)

          如果希望結(jié)果輸出 4.2,同時又保持類型約束,怎么辦?PHP 不支持方法重載。這就有了 PHP 8 的聯(lián)合類型。

          聯(lián)合類型接受多個不同的類型做為參數(shù)。聲明聯(lián)合類型的語法為 T1|T2|...。

          所以,上面代碼可以改為:

          function sum(int|float $a, int|float $b): int|float{
            return $a + $b;
          }

          這樣 sum(1.2, 3) 的結(jié)果就是 4.2 了。

          一些注意事項:

          • 聯(lián)合類型也可用于類成員變量;
          • null 可以用于聯(lián)合類型中,但不能單獨作為類型。比如 int|null 允許,但 null 作為類型不允許;
          • 下面的函數(shù)是合法的:function index(): int|false{
            return false;
            }但返回值改為:int|true 卻是非法的。這是出于歷史原因,很多內(nèi)部函數(shù)在失敗時返回了 false 而不是 null。這類函數(shù)的典型例子是 strpos()。因此允許聯(lián)合類型中使用 false,但不允許使用 true。注意 false 并非是類型,這里看出是偽類型,不能單獨使用。

          還有其他一些細節(jié)點,詳情請訪問官方文檔查看:https://www.php.net/manual/zh/language.types.declarations.php。

          不得不說,也許越來越意識到弱類型的問題,PHP 這是在做強類型的事情。然而,不少人要說了,搞這么費勁、這么復雜,還不如直接換強類型語言呢?!你覺得呢?

          當然,你完全可以忽略聯(lián)合類型,繼續(xù)使用 5.x 的方式寫 PHP。

          03 Named Arguments(命名參數(shù))

          這個特性還是很棒的。了解 Python 的朋友應該對這個特性很熟悉。這樣一來,PHP 的函數(shù)支持不定參數(shù)、參數(shù)默認值、命名參數(shù)等。相對來說,Go 的函數(shù)還是弱很多。

          比如 htmlspecialchars 函數(shù)簽名如下:

          htmlspecialchars ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = TRUE ]]] ) : string

          PHP 8 之前,如果想要最后一個參數(shù)傳遞 false,需要這么調(diào)用:

          htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);

          而有了命名參數(shù)后(PHP 8),可以這么調(diào)用:

          htmlspecialchars($string, double_encode: false);

          簡單清晰。

          總結(jié)一下就是:

          • 僅需指定必需的參數(shù),可跳過可選的參數(shù)。
          • 參數(shù)是與順序無關(guān)的且具有自記錄功能。

          命名參數(shù)確實帶來了不少便利。不過我覺得也有一些要注意的點:

          • 函數(shù)參數(shù)可能會很多,Python 中很多函數(shù)一大堆參數(shù),可維護性可能是一個問題;
          • 原本函數(shù)參數(shù)名稱是不重要的,但命名參數(shù)使得參數(shù)名稱不能隨便改,因為調(diào)用者可能依賴它了;

          04 Match 表達式

          實際中我們經(jīng)常通過 state 來表示各種狀態(tài),比如:0-待審核;1-上線;2-下線;3-刪除。因為數(shù)據(jù)庫中存的數(shù)字,但顯示希望是文字說明。這時一般有兩種做法:

          switch ($state) {
            case 0:
              $stateDesc = '待審核';
              break;
            case 1:
              $stateDesc = '上線';
              break;
            case 2:
              $stateDesc = '下線';
              break;
            case 3:
              $stateDesc = '刪除';
              break;
          }
          
          echo $stateDesc;

          我個人喜歡通過 map 來實現(xiàn):

          $stateMap = [
            0 => '待審核',
            1 => '上線',
            2 => '下線',
            3 => '刪除',
          ];
          
          echo $stateMap[$state];

          PHP 8 針對這樣的場景提供了 match 表達式:

          echo match($state) {
            0 => '待審核',
            1 => '上線',
            2 => '下線',
            3 => '刪除',
          };

          可見 match 類似于 switch 語句,有如下特點:

          • Match 是一個表達式,因此其結(jié)果可以存儲在變量中或返回;
          • Match 分支僅支持單行表達式,不需要 break 語句;
          • switch 相當于使用 == 比較,而 Match 使用 === 比較;
          • 如果沒匹配到任何項,會拋 UnhandledMatchError 錯誤;
          • 也支持 default;

          更多信息查看官方文檔:https://www.php.net/manual/zh/control-structures.match.php。

          05 Nullsafe 運算符(Nullsafe operator)

          了解 Swift 之類的語言,應該知曉其中的可選型。PHP 8 新增的這個特性,我覺得多少有點可選型的意思。

          在 PHP 7 中的如下代碼:

          $country =  null;
          
          if ($session !== null) {
            $user = $session->user;
          
            if ($user !== null) {
              $address = $user->getAddress();
           
              if ($address !== null) {
                $country = $address->country;
              }
            }
          }

          在 PHP 8 中簡化為:

          $country = $session?->user?->getAddress()?->country;

          06 構(gòu)造器屬性提升

          PHP 8 起構(gòu)造器的參數(shù)可以提升為類的屬性。構(gòu)造器的參數(shù)賦值給類屬性的行為很普遍,否則無法操作。而構(gòu)造器提升的功能則為這種場景提供了便利。例如下面的代碼:

          class Point {
            public float $x;
            public float $y;
            public float $z;
          
            public function __construct(
              float $x = 0.0,
              float $y = 0.0,
              float $z = 0.0
            ) {
              $this->x = $x;
              $this->y = $y;
              $this->z = $z;
            }
          }

          改為 PHP 8 的方式:

          class Point {
            public function __construct(
              public float $x = 0.0,
              public float $y = 0.0,
              public float $z = 0.0,
            ) {}
          }

          07 字符串與數(shù)字的比較更符合邏輯

          PHP 8 比較數(shù)字字符串(numeric string)時,會按數(shù)字進行比較。不是數(shù)字字符串時,將數(shù)字轉(zhuǎn)化為字符串,按字符串比較。

          這一點要注意,之前這樣的代碼:

          0 == 'foobar' // true

          現(xiàn)在是 false:

          0 == 'foobar' // fals

          更多說明參見這里:https://wiki.php.net/rfc/string_to_number_comparison。

          08 注解(attributes)

          現(xiàn)在可以用 PHP 原生語法來使用結(jié)構(gòu)化的元數(shù)據(jù),而非 PHPDoc 聲明。

          之前這么寫:

          class PostsController{
              /**
               * @Route("/api/posts/{id}", methods={"GET"})
               */
              public function get($id) { /* ... */ }
          }

          現(xiàn)在這么寫:

          class PostsController{
              #[Route("/api/posts/{id}", methods: ["GET"])]
              public function get($id) { /* ... */ }
          }

          09 即時編譯

          PHP 8 引入了兩個即時編譯引擎。Tracing JIT 在兩個中更有潛力,它在綜合基準測試中顯示了三倍的性能, 并在某些長時間運行的程序中顯示了 1.5-2 倍的性能改進。典型的應用性能則和 PHP 7.4 不相上下。

          官方給了一個性能測試:

          10 總結(jié)

          PHP 8 還有很多其他改動,在這里有詳細的說明:https://www.php.net/releases/8.0/zh.php。其中新增了 3 個函數(shù)實用的函數(shù):str_contains()、str_starts_with() 和 str_ends_with()。(Go 表示第一天就有了)


          這里面的新特性,命名參數(shù)我個人還是比較喜歡。你呢?


          主站蜘蛛池模板: 精品国产一区二区三区色欲| 精品无码人妻一区二区免费蜜桃| 日韩a无吗一区二区三区| 无码毛片一区二区三区中文字幕| 成人精品视频一区二区三区不卡 | 国产成人综合精品一区| 亚洲啪啪综合AV一区| 国产伦精品一区二区三区视频金莲| 精品国产一区二区三区久久久狼| 99精品国产一区二区三区| 蜜桃传媒一区二区亚洲AV| 精品一区二区三区四区在线播放 | 成人无号精品一区二区三区| 久久久一区二区三区| 国产色综合一区二区三区| 无码精品人妻一区二区三区影院| 天海翼一区二区三区高清视频| 日韩精品一区二区三区老鸭窝 | 亚洲色精品VR一区区三区| 亚洲一区精品无码| 久久久久无码国产精品一区| 精品人妻少妇一区二区| 中文字幕一区视频一线| 中文字幕一区二区三区视频在线| 国产日韩高清一区二区三区| 国产成人精品一区在线| 亚洲国产精品一区二区三区久久| 成人国产精品一区二区网站| 亚洲熟女乱综合一区二区| 精品国产香蕉伊思人在线在线亚洲一区二区| 亚洲AV日韩综合一区| 日韩一区二区三区免费播放| AV怡红院一区二区三区| 久久免费区一区二区三波多野| 亚洲视频在线一区| 国产伦精品一区二区三区免.费| 国产激情一区二区三区成人91| 亚洲色欲一区二区三区在线观看| 台湾无码一区二区| 极品少妇伦理一区二区| 久久精品道一区二区三区|