2018年11月22日 星期四

《面試官別再問》ES6中的Promise以及Promise/A+規範

在Promise的知識體系中,jquery當然是必不可少的一環,所以本篇就來講講jquery中的Promise,也就是我們所知道的Deferred對象。

事實上,在此之前網上有很多文章在講jquery Deferred對象了,但是總喜歡把ajax和Deferred混在一起講,容易把人搞混。 when、done、promise、success、error、fail、then、resolve、reject、always這麼多方法不能揉在一起講,需要把他們捋一捋,哪些是Deferred對象的方法,哪些是ajax的語法糖,我們需要心知肚明。

先講$.Deferred
jquery用$.Deferred實現了Promise規範,$.Deferred是個什麼玩意呢?還是老方法,打印出來看看,先有個直觀印象:
var def = $.Deferred();
console.log(def);
輸出如下:

$.Deferred()返回一個對象,我們可以稱之為Deferred對象,上面掛著一些熟悉的方法如:done、fail、then等。jquery就是用這個Deferred對象來註冊異步操作的回調函數,修改並傳遞異步操作的狀態。

Deferred對象的基本用法如下,為了不與ajax混淆,我們依舊舉setTimeout的例子:
複製代碼
function runAsync(){
     var def = $.Deferred();
     // 做一些異步操作
    setTimeout( function (){
        console.log( '執行完成' );
        def.resolve( '隨便什麼數據' );
    }, 2000 );
     return def;
}
runAsync().then( function (data){
    console.log(data)
});

在runAsync函數中,我們首先定義了一個def對象,然後進行一個延時操作,在2秒後調用def.resolve(),最後把def作為函數的返回。調用runAsync的時候將返回def對象,然後我們就可以.then來執行回調函數。

是不是感覺和ES6的Promise很像呢?我們來回憶一下第一篇中ES6的例子:
複製代碼
function runAsync(){
     var p = new Promise( function (resolve, reject){
         // 做一些異步操作
        setTimeout( function (){
            console.log( '執行完成' );
            resolve( '隨便什麼數據' );
        }, 2000 );
    });
    return p;
}
runAsync()
區別在何處一看便知。由於jquery的def對象本身就有resolve方法,所以我們在創建def對象的時候並未像ES6這樣傳入了一個函數參數,是空的。在後面可以直接def.resolve()這樣調用。

這樣也有一個弊端,因為執行runAsync()可以拿到def對象,而def對像上又有resolve方法,那麼豈不是可以在外部就修改def的狀態了?比如我把上面的代碼修改如下:
複製代碼
var d = runAsync();
d.then( function (data){
    console.log(data)
});
d.resolve( '在外部結束');
複製代碼
現象會如何呢?並不會在2秒後輸出“執行完成”,而是直接輸出“在外部結束”。因為我們在異步操作執行完成之前,沒等他自己resolve,就在外部給resolve了。這顯然是有風險的,比如你定義的一個異步操作並指定好回調函數,有可能被別人給提前結束掉,你的回調函數也就不能執行了。

怎麼辦?jquery提供了一個promise方法,就在def對像上,他可以返回一個受限的Deferred對象,所謂受限就是沒有resolve、reject等方法,無法從外部來改變他的狀態,用法如下:
複製代碼
function runAsync(){
     var def = $.Deferred();
     // 做一些異步操作
    setTimeout( function (){
        console.log( '執行完成' );
        def.resolve( '隨便什麼數據' );
    }, 2000 );
     return def.promise(); // 就在這裡調用
}
複製代碼
這樣返回的對像上就沒有resolve方法了,也就無法從外部改變他的狀態了。這個promise名字起的有點奇葩,容易讓我們搞混,其實他就是一個返回受限Deferred對象的方法,與Promise規範沒有任何關係,僅僅是名字叫做promise罷了。雖然名字奇葩,但是推薦使用。

then的鍊式調用
既然Deferred也是Promise規範的實現者,那麼其他特性也必須是支持的。鍊式調用的用法如下:
複製代碼
var d = runAsync();

d.then( function (data){
    console.log(data);
    return runAsync2();
})
.then( function (data){
    console.log(data);
    return runAsync3();
})
.then( function (data){
    console.log(data);
});
複製代碼
與我們第一篇中的例子基本一樣,可以參照。

done與fail
我們知道,Promise規範中,then方法接受兩個參數,分別是執行完成和執行失敗的回調,而jquery中進行了增強,還可以接受第三個參數,就是在pending狀態時的回調,如下:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
除此之外,jquery還增加了兩個語法糖方法,done和fail,分別用來指定執行完成和執行失敗的回調,也就是說這段代碼:
複製代碼
d.then( function (){
    console.log( '執行完成' );
}, function (){
    console.log( '執行失敗' );
});
複製代碼
與這段代碼是等價的:
複製代碼
d.done( function (){
    console.log( '執行完成' );
})
.fail( function (){
    console.log( '執行失敗' );
});
複製代碼

always的用法
jquery的Deferred對像上還有一個always方法,不論執行完成還是執行失敗,always都會執行,有點類似ajax中的complete。不贅述了。

$.when的用法
jquery中,還有一個$.when方法來實現Promise,與ES6中的all方法功能一樣,並行執行異步操作,在所有的異步操作執行完後才執行回調函數。不過$.when並沒有定義在$.Deferred中,看名字就知道,$.when,它是一個單獨的方法。與ES6的all的參數稍有區別,它接受的並不是數組,而是多個Deferred對象,如下:
複製代碼
$.when(runAsync(), runAsync2(), runAsync3())
.then( function (data1, data2, data3){
    console.log( '全部執行完成' );
    console.log(data1, data2, data3);
});
複製代碼
jquery中沒有像ES6中的race方法嗎?就是以跑的快的為準的那個方法。對的,jquery中沒有。

以上就是jquery中Deferred對象的常用方法了,還有一些其他的方法用的也不多,乾脆就不記它了。接下來該說說ajax了。

ajax與Deferred的關係
jquery的ajax返回一個受限的Deferred對象,還記得受限的Deferred對象吧,也就是沒有resolve方法和reject方法,不能從外部改變狀態。想想也是,你發一個ajax請求,別人從其他地方給你取消掉了,也是受不了的。

既然是Deferred對象,那麼我們上面講到的所有特性,ajax也都是可以用的。比如鍊式調用,連續發送多個請求:
複製代碼
req1 = function (){
     return $.ajax( /* ... */ );
}
req2 = function (){
     return $.ajax( /* ... */ );
}
req3 = function (){
     return $.ajax( /* ... */ );
}

req1().then(req2).then(req3).done( function (){
    console.log( '請求發送完畢' );
});
複製代碼
明白了ajax返回對象的實質,那我們用起來就得心應手了。

success、error與complete
這三個方法或許是我們用的最多的,使用起來是這樣的:
$.ajax( /* ... */ )
.success( function (){ /* ... */ })
.error( function (){ /* ... */ })
.complete( function (){ /* ... */ })
分別表示ajax請求成功、失敗、結束的回調。這三個方法與Deferred又是什麼關係呢?其實就是語法糖,success對應done,error對應fail,complete對應always,就這樣,只是為了與ajax的參數名字上保持一致而已,更方便大家記憶,看一眼源碼:
deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
complete那一行那麼寫,是為了減少重複代碼,其實就是把done和fail又調用一次,與always中的代碼一樣。deferred.promise( jqXHR )這句也能看出,ajax返回的是受限的Deferred對象。

jquery加了這麼些個語法糖,雖然上手門檻更低了,但是卻造成了一定程度的混淆。一些人雖然這麼寫了很久,卻一直不知道其中的原理,在面試的時候只能答出一些皮毛,這是很不好的。這也是我寫這篇文章的緣由。

jquery中Deferred對象涉及到的方法很多,本文盡量分門別類的來介紹,希望能幫大家理清思路。總結一下就是:$.Deferred實現了Promise規範,then、done、fail、always是Deferred對象的方法。$.when是一個全局的方法,用來並行運行多個異步任務,與ES6的all是一個功能。ajax返回一個Deferred對象,success、error、complete是ajax提供的語法糖,功能與Deferred對象的done、fail、always一致。

《面試官別再問》php之api接口的設計

GET從服務器取出資源
POST在服務器新建一個資源
PUT在服務器更新資源(客戶端提供改變後的完整資源)
PATCH在服務器更新資源(客戶端提供改變的屬性)
DELETE從服務器刪除資源
HEAD獲取資源的源數據
OPTION獲取信息關於資源的那些屬性是客戶端可以改變的

接口的安全性

選https協議代替http協議這樣,避免明文傳輸數據

對一些開放的接口做數字簽名

對有登錄操作的接口,先通過登錄獲取訪問的令牌,之後訪問其他需要登錄的接口
通過攜帶這個令牌進行訪問。

登錄認證機制中session和jwt對比

session的登錄原理:客戶端(client)提交用戶名密碼(或驗證碼)給服務端(server),服務端校驗通過後,
服務端會生成身份認證相關的session數據,服務端將session數據保存在文件或者內存中,並且將session_id返
回給客戶端,客戶端將返回的session_id保存在cookie中。此後客戶端請求服務端時都會在cookie中攜帶session_id。
服務端通過校驗session信息是否存在,如果存在判斷用戶是否處於登錄狀態以及用戶具有的權限。通過校驗就返回該有的
數據,沒通過校驗返回登錄頁。
session的優勢和劣勢
優勢:
1. session可以主動清除
2. session保存在服務端相對安全
3.結合cookie使用較為靈活兼容性好
劣勢:
1. cookie+ session在跨域場景下表現不好
2.如果是分佈式部署,需要多機器共享session機制,實現方法可將session存儲到數據庫或者redis
中,這樣查詢session信息時需要進行數據庫的操作
3.基於cookie的機制很容易被CSRF攻擊

jwt的登錄原理:客戶端(client)提交用戶名密碼(或驗證碼)給服務端(server),服務端校驗通過後,將用戶的id和其它信息做為JWT playload(負載),將其與頭部分別進行Base64編碼拼接後簽名,形成一個JWT,形成的JWT就是形同,lll.zzz.xxx的字符串。後端將JWT字符串作為登錄成功的返回結果,返回給前端。前端可以將返回結果保存在localStorge或sessionStorage上,退出登錄前刪除保存的JWT即可。前端在每次請求時將JWT放在http header中的Authorization位(解決XSS和XSRF攻擊)。後端檢查是否存在,如果存在驗證JWT的有效性。例如檢查簽名是否正確,檢查token是否過期檢查token的接受方是否是自己(可選)驗證通過後,後端使用JWT中包含的用戶信息進行其他的邏輯操作,返回相應的結果。

jwt和session的區別
session的狀態是存儲在服務端的,客戶端只存儲session_id ,而token的狀態存儲在客戶端
(因為信息全部放在客戶端所以需要密碼學的方法進行簽名和加密)分佈式系統中session共享是一個問題,session存在服務端,隨著用戶數量增多,會大幅降低服務端的性能在多個域名下,會存在跨域問題,安全性方面cookie保存在本地,容易被人利用進行CSRF攻擊

2018年11月20日 星期二

《面試官別再問》什麼是索引,作用是什麼?常見索引類型有那些?Mysql 建立索引的原則?

索引是一種特殊的文件,它們包含著對數據表裡所有記錄的引用指針,相當於書本的目錄。其作用就是加快數據的檢索效率。常見索引類型有主鍵、唯一索引、複合索引、全文索引。

第一,通過創建唯一性的索引,可以保證數據庫表中每一行數據的唯一性。
第二,可以大大加快數據的檢索速度,這也使創建索引的最主要的原因。
第三,可以加速表和表之間的連接,特別是在實現數據的參考完整性方面特別有意義。
第四,在使用分組和排序子句進行數據檢索時,同樣可以顯著的減少查詢中查詢中分組和排序的時間。
第五,通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的性能。

增加索引也有許多不利的方面。
第一,創建索引和維護索引需要消耗時間,這種時間隨著數量的增加而增加。
第二,索引需要佔物理空間,除了數據表佔據數據空間之外,每一個索引還要佔一定的物理空間,如果要建立聚簇索引,那麼需要額空間就會更大。
第三,當對錶中的數據進行增加,刪除和修改的時候,索引也要動態的維護,這樣就降低了數據的維護速度。

應該對如下的列建立索引

在作為主鍵的列上,強制該列的唯一性和組織表中數據的排列結構。
在經常用在連接的列上,這些列主要是一些外鍵,可以加快連接的速度。
在經常需要根據范圍進行搜索的列上創建索引,因為索引已經排序,其指定的範圍是連續的。
在經常需要排序的列上創建索引,因為索引已經排序,這樣查詢可以利用索引的排序,加快排序查詢時間。
在經常使用在where子句中的列上面創建索引,加快條件的判斷速度。
有些列不應該創建索引

在查詢中很少使用或者作為參考的列不應該創建索引。
對於那些只有很少數據值的列也不應該增加索引(比如性別,結果集的數據行佔了表中數據行的很大比例,即需要在表中搜索的數據行的比例很大。增加索引,並不能明顯加快檢索速度)。
對於那些定義為text,image和bit數據類型的列不應該增加索引。這是因為,這些列的數據量要么相當大,要么取值很少。
當修改性能遠遠大於檢索性能時,不應該創建索引,因為修改性能和檢索性能是矛盾的。
創建索引的方法:直接創建和間接創建(在表中定義主鍵約束或者唯一性約束時,同時也創建了索引)。
索引的特徵:
唯一性索引和復合索引。唯一性索引保證在索引列中的全部數據是唯一的,不會包含冗餘數據。複合索引就是一個索引創建在兩個列或者多個列上。可以減少一在一個表中所創建的索引數量。

《面試官別再問》Laravel框架一:原理機制篇

一. 請求週期

Laravel 採用了單一入口模式,應用的所有請求入口都是 public/index.php 文檔。

註冊類文檔自動加載器:Laravel通過composer進行依賴管理,並在bootstrap/autoload.php中註冊了Composer Auto Loader (PSR-4),應用中類的命名空間將被映射到類文檔實際路徑,不再需要開發者手動導入各種類文檔,而由自動加載器自行導入。因此,Laravel允許你在應用中定義的類可以自由放置在Composer Auto Loader能自動加載的任何目錄下,但大多數時候還是建議放置在app目錄下或app的某個子目錄下
創建服務容器:從 bootstrap/app.php 文檔中取得 Laravel 應用實例 $app (服務容器)
創建 HTTP / Console 內核:傳入的請求會被髮送給 HTTP 內核或者 console 內核進行處理,HTTP 內核繼承自 Illuminate\Foundation\Http\Kernel 類。它定義了一個bootstrappers 數組,數組中的類在請求真正執行前進行前置執行,這些引導進程配置了錯誤處理,日誌記錄,檢測應用進程環境,以及其他在請求被處理前需要完成的工作;HTTP內核同時定義了一個HTTP 中間件列表,所有的請求必須在處理前通過這些中間件處理HTTP session 的讀寫,判斷應用是否在維護模式, 驗證CSRF token 等等
載入服務提供者至容器:在內核引導啟動的過程中最重要的動作之一就是載入服務提供者到你的應用,服務提供者負責引導啟動框架的全部各種組件,例如數據庫、隊列、驗證器以及路由組件。因為這些組件引導和配置了框架的各種功能,所以服務提供者是整個 Laravel 啟動過程中最為重要的部分,所有的服務提供者都配置在 config/app.php 文檔中的 providers 數組中。首先,所有提供者的 register 方法會被調用;一旦所有提供者註冊完成,接下來,boot 方法將會被調用
分發請求:一旦應用完成引導和所有服務提供者都註冊完成,Request 將會移交給路由進行分發。路由將分發請求給一個路由或控制器,同時運行路由指定的中間件

二. 服務容器和服務提供者

服務容器是Laravel 管理類依賴和運行依賴注入的有力工具,在類中可通過$this->app 來訪問容器,在類之外通過$app 來訪問容器;服務提供者是Laravel 應用進程引導啟動的中心,關係到服務提供者自身、事件監聽器、路由以及中間件的啟動運行。應用進程中註冊的路由通過RouteServiceProvider實例來加載;事件監聽器在EventServiceProvider類中進行註冊;中間件又稱路由中間件,在app/Http/Kernel.php類文檔中註冊,調用時與路由進行綁定。在新創建的應用中,AppServiceProvider 文檔中方法實現都是空的,這個提供者是你添加應用專屬的引導和服務的最佳位置,當然,對於大型應用你可能希望創建幾個服務提供者,每個都具有粒度更精細的引導。服務提供者在 config/app.php 配置文檔中的providers數組中進行註冊

三、Artisan Console

Laravel利用PHP的CLI構建了強大的Console工具artisan,artisan幾乎能夠創建任何你想要的模板類以及管理配置你的應用,在開發和運維管理中扮演着極其重要的角色,artisan是Laravel開發不可或缺的工具。在Laravel根目錄下運行:PHP artisan list可查看所有命令行表。用好artisan能極大地簡化開發工作,並減少錯誤發生的可能;另外,還可以編寫自己的命令。下面列舉部分比較常用的命令:

啟用維護模式:php artisan down --message='Upgrading Database' --retry=60
關閉維護模式:php artisan up
生成路由緩存:php artisan route:cache
清除路由緩存:php artisan route:clear
數據庫遷移 Migrations:php artisan make:migration create_users_table --create=users
創建資源控制器:php artisan make:controller PhotoController --resource --model=Photo
創建模型及遷移:php artisan make:model User -m

四、表單驗證機制

  表單驗證在web開發中是不可或缺的,其重要性也不言而喻,也算是每個web框架的標配部件了。Laravel表單驗證擁有標準且龐大的規則集,通過規則調用來完成數據驗證,多個規則組合調用須以“|”符號連接,一旦驗證失敗將自動回退並可自動綁定視圖。

  下例中,附加bail規則至title屬性,在第一次驗證required失敗後將立即停止驗證;“.”語法符號在Laravel中通常表示嵌套包含關係,這個在其他語言或框架語法中也比較常見

$this->validate($request, [
    'title' => 'bail|required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
]);

五、Eloquent 模型

  Eloquent ORM 以ActiveRecord形式來和數據庫進行交互,擁有全部的數據表操作定義,單個模型實例對應數據表中的一行

1 $flights = App\Flight::where('active', 1)
2 ->orderBy('name', 'desc')
3 ->take(10)
4 ->get();

config/database.php中包含了模型的相關配置項。Eloquent 模型約定:

數據表名:模型以單數形式命名(CamelCase),對應的數據表為蛇形複數名(snake_cases),模型的$table屬性也可用來指定自定義的數據表名稱
主鍵:模型默認以id為主鍵且假定id是一個遞增的整數值,也可以通過$primaryKey來自定義;如果主鍵非遞增數字值,應設置$incrementing = false
時間戳:模型會默認在你的數據庫表有 created_at 和 updated_at 字段,設置$timestamps = false可關閉模型自動維護這兩個字段;$dateFormat 屬性用於在模型中設置自己的時間戳格式
數據庫連接:模型默認會使用應用進程中配置的數據庫連接,如果你想為模型指定不同的連接,可以使用 $connection 屬性自定義
批量賦值:當用户通過 HTTP 請求傳入了非預期的參數,並藉助這些參數 create 方法更改了數據庫中你並不打算要更改的字段,這時就會出現批量賦值(Mass-Assignment)漏洞,所以你需要先在模型上定義一個 $fillable(白名單,允許批量賦值字段名數組) 或 $guarded(黑名單,禁止批量賦值字段名數組)
1 // 用屬性取回航班,當結果不存在時創建它...
2 $flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
3
4 // 用屬性取回航班,當結果不存在時實例化一個新實例...
5 $flight = App\Flight::firstOrNew(['name' => 'Flight 10']);

六、Laravel的Restful風格

  一般認為Restful風格的資源定義不包含操作,但是在Laravel中操作(動詞)也可作為一種資源來定義。下圖是對Laravel中資源控制器操作原理的描述,可以看到,create、edit就直接出現在了URI中,它們是一種合法的資源。對於create和edit這兩種資源的訪問都採用GET方法來實現,第一眼看到頓感奇怪,後來嘗試通過artisan console生成資源控制器,並注意到其對create、edit給出註釋“ Show the form for ”字樣,方知它們只是用來展現表單而非提交表單的。

《面試官別再問》什麼是CGI?什麼是FastCGI?php-fpm,FastCGI,Nginx 之間是什麼關係?

CGI,通用網關接口,用於WEB服務器和應用程序間的交互,定義輸入輸出規範,用戶的請求通過WEB服務器轉發給FastCGI進程,FastCGI進程再調用應用程序進行處理,如php解析器,應用程序的處理結果如html返回給FastCGI,FastCGI返回給Nginx 進行輸出。假設這裡WEB服務器是Nginx,應用程序是PHP,而php-fpm 是管理FastCGI 的,這也就是php-fpm,FastCGI,和Nginx 之間的關係。

FastCGI 用來提高cgi 程序性能,啟動一個master,再啟動多個worker,不需要每次解析php.ini. 而php-fpm 實現了FastCGI 協議,是FastCGI 的進程管理器,支持平滑重啟,可以啟動的時候預先生成多個進程。

《面試官別再問》Redis、Memecache這兩者有什麼區別?

Redis 支持更加豐富的數據存儲類型,String、Hash、List、Set 和Sorted Set。 Memcached 僅支持簡單的key-value 結構。
Memcached key-value存儲比Redis 採用hash 結構來做key-value 存儲的內存利用率更高。
Redis 提供了事務的功能,可以保證一系列命令的原子性
Redis 支持數據的持久化,可以將內存中的數據保持在磁盤中
Redis 只使用單核,而Memcached 可以使用多核,所以平均每一個核上Redis 在存儲小數據時比Memcached 性能更高。

Redis如何實現持久化?

RDB 持久化,將redis 在內存中的的狀態保存到硬碟中,相當於備份數據庫狀態。
AOF 持久化(Append-Only-File),AOF 持久化是通過保存Redis 服務器鎖執行的寫狀態來記錄數據庫的。相當於備份數據庫接收到的命令,所有被寫入AOF 的命令都是以redis 的協議格式來保存的。


基本命令

Memcached的命令或者説通訊協議非常簡單,Server所支持的命令基本就是對特定key的添加,刪除,替換,原子更新,讀取等,具體包括 Set, Get, Add, Replace, Append, Inc/Dec 等等

Memcached的通訊協議包括文本格式和二進制格式,用於滿足簡單網絡客户端工具(如telnet)和對性能要求更高的客户端的不同需求

Redis的命令在KV(String類型)上提供與Memcached類似的基本操作,在其它數據結構上也支持基本類似的操作(當然還有這些數據結構所特有的操作,如Set的union,List的pop等)而支持更多的數據結構,在一定程度上也就意味着更加廣泛的應用場合

除了多種數據結構的支持,Redis相比Memcached還提供了許多額外的特性,比如Subscribe/publish命令,以支持發佈/訂閲模式這樣的通知機制等等,這些額外的特性同樣有助於拓展它的應用場景

Redis的客户端-服務器通訊協議完全採用文本格式(在將來可能的服務器間通訊會採用二進制格式)

事務

redis通過Multi / Watch /Exec等命令可以支持事務的概念,原子性的執行一批命令。在2.6以後的版本中由於添加了對Script腳本的支持,而腳本固有的是以transaction事務的方式執行的,並且更加易於使用,所以不排除將來取消Multi等命令接口的可能性

Memcached的應用模式中,除了increment/decrement這樣的原子操作命令,不存在對事務的支持

數據備份,有效性,持久化等

memcached不保證存儲的數據的有效性,Slab內部基於LRU也會自動淘汰舊數據,客户端不能假設數據在服務器端的當前狀態,這應該説是Memcached的Feature設定,用户不必太多關心或者自己管理數據的淘汰更新工作,當然是否適合你的應用,取決於具體的需求,它也可能成為你需要精確自行控制Cache生命週期的一個障礙

 Memcached也不做數據的持久化工作,但是有許多基於memcached協議的項目實現了數據的持久化,例如memcacheDB使用BerkeleyDB進行數據存儲,但本質上它已經不是一個Cache Server,而只是一個兼容Memcached的協議key-valueData Store了

Redis可以以master-slave的方式配置服務器,Slave節點對數據進行replica備份,Slave節點也可以充當Read only的節點分擔數據讀取的工作

《面試官別再問》Laravel 模組

服務提供者是什麼?
服務提供者是所有Laravel 應用程序引導啟動的中心, Laravel 的核心服務器、註冊服務容器綁定、事件監聽、中間件、路由註冊以及我們的應用程序都是由服務提供者引導啟動的。
Contract的原理?
Contract(契約)是laravel 定義框架提供的核心服務的接口。 Contract 和Facades 並沒有本質意義上的區別,其作用就是使接口低耦合、更簡單。
IoC容器是什麼?

IoC(Inversion of Control)譯為「控制反轉」,也被叫做「依賴注入」(DI)。什麼是「控制反轉」?對象A 功能依賴於對象B,但是控制權由對象A 來控制,控制權被顛倒,所以叫做「控制反轉」,而「依賴注入」是實現IoC 的方法,就是由IoC 容器在運行期間,動態地將某種依賴關係注入到對象之中。
其作用簡單來講就是利用依賴關係注入的方式,把複雜的應用程序分解為互相合作的對象,從而降低解決問題的複雜度,實現應用程序代碼的低耦合、高擴展。

Laravel中的服務容器是用於管理類的依賴和執行依賴注入的工具。
參考網址

依賴注入的原理?

@overtrue一句話解釋:依賴注入只是一種模式:把當前類依賴的第三方實例通過參數傳入的形式引入,但是如果手寫依賴注入會比較費勁,管理起來也比較麻煩,因為要關心那麼多類的依賴,於是就有了一個容器來自動解決這個問題,利用反射API檢查類型,然後遞歸解決依賴。
Facade是什麼?
Facades(一種設計模式,通常翻譯為外觀模式)提供了一個"static"(靜態)接口去訪問註冊到IoC 容器中的類。提供了簡單、易記的語法,而無需記住必須手動注入或配置的長長的類名。此外,由於對PHP 動態方法的獨特用法,也使測試起來非常容易。
了解過Composer?實現原理是什麼?
Composer 是PHP 的一個依賴管理工具。工作原理就是將已開發好的擴展包從packagist.org composer 倉庫下載到我們的應用程序中,並聲明依賴關係和版本控制。

《面試官別再問》HTTP狀態碼有哪些?區別?

10x 信息,服務器收到請求,需要請求者繼續執行操作(收到請求了,繼續操作)

100 繼續。客戶端應繼續其請求
101 切換協議。服務器根據客戶端的請求切換協議。只能切換到更高級的協議,例如,切換到HTTP的新版本協議
20x 成功,操作被成功接收並處理(請求成功了)

200 請求成功。一般用於GET與POST請求
201 已創建。成功請求並創建了新的資源
202 已接受。已經接受請求,但未處理完成
203 非授權信息。請求成功。但返回的meta信息不在原始的服務器,而是一個副本
204 無內容。服務器成功處理,但未返回內容。在未更新網頁的情況下,可確保瀏覽器繼續顯示當前文檔
205 重置內容。服務器處理成功,用戶終端(例如:瀏覽器)應重置文檔視圖。可通過此返回碼清除瀏覽器的表單域
206 部分內容。服務器成功處理了部分GET請求
30x 重定向,需要進一步的操作以完成請求(收到請求了,去別的地方處理吧)

300 多種選擇。請求的資源可包括多個位置,相應可返回一個資源特徵與地址的列表用於用戶終端(例如:瀏覽器)選擇
301 永久移動。請求的資源已被永久的移動到新URI,返回信息會包括新的URI,瀏覽器會自動定向到新URI。今後任何新的請求都應使用新的URI代替
302 臨時移動。與301類似。但資源只是臨時被移動。客戶端應繼續使用原有URI
303 查看其它地址。與301類似。使用GET和POST請求查看
304 未修改。所請求的資源未修改,服務器返回此狀態碼時,不會返回任何資源。客戶端通常會緩存訪問過的資源,通過提供一個頭信息指出客戶端希望只返回在指定日期之後修改的資源
305 使用代理。所請求的資源必須通過代理訪問
306 已經被廢棄的HTTP狀態碼
307 臨時重定向。與302類似。使用GET請求重定向
40x 客戶端錯誤請求包含語法錯誤或無法完成請求(你丫自己操作錯了,會不會玩?)

400 客戶端請求的語法錯誤,服務器無法理解
401 未認證
402 保留,將來使用
403 服務器理解請求客戶端的請求,但是拒絕執行此請求
404 服務器無法根據客戶端的請求找到資源(網頁)。通過此代碼,網站設計人員可設置"您所請求的資源無法找到"的個性頁面
405 客戶端請求中的方法被禁止
406 服務器無法根據客戶端請求的內容特性完成請求
407 請求要求代理的身份認證,與401類似,但請求者應當使用代理進行授權
408 服務器等待客戶端發送的請求時間過長,超時
409 服務器完成客戶端的PUT請求是可能返回此代碼,服務器處理請求時發生了衝突
410 客戶端請求的資源已經不存在。 410不同於404,如果資源以前有現在被永久刪除了可使用410代碼,網站設計人員可通過301代碼指定資源的新位置
411 服務器無法處理客戶端發送的不帶Content-Length的請求信息
412 客戶端請求信息的先決條件錯誤
413 由於請求的實體過大,服務器無法處理,因此拒絕請求。為防止客戶端的連續請求,服務器可能會關閉連接。如果只是服務器暫時無法處理,則會包含一個Retry-After的響應信息
414 請求的URI過長(URI通常為網址),服務器無法處理
415 服務器無法處理請求附帶的媒體格式
416 客戶端請求的範圍無效
417 服務器無法滿足Expect的請求頭信息
50x 服務器錯誤,服務器在處理請求的過程中發生了錯誤(開發和運維還沒死快抬上來)
500 服務器內部錯誤,無法完成請求
501 服務器不支持請求的功能,無法完成請求
502 充當網關或代理的服務器,從遠端服務器接收到了一個無效的請求
503 由於超載或系統維護,服務器暫時的無法處理客戶端的請求。延時的長度可包含在服務器的Retry-After頭信息中
504 充當網關或代理的服務器,未及時從遠端服務器獲取請求
505 服務器不支持請求的HTTP協議的版本,無法完成處理

2018年11月12日 星期一

《面試官別再問》PHP Laravel 5如何使用JWT api authentication

php composer require tymon/jwt-auth 0.5.*
設定
安裝完成後,需要在配置/ app.php中註冊相應的服務提供者:
  1. 'providers' => [
  2. ...,
  3. Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class
  4. ]
  1. 'aliases' => [
  2. ...,
  3. 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
  4. 'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class
  5. composer require tymon/jwt-auth 0.5.*]

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
php artisan jwt:generate
app\Http\Kernel.php
  1. protected $routeMiddleware = [
  2. ...,
  3. 'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
  4. 'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class
  5. ];
app\Http\routes.php
  1. Route::group(['prefix' => 'api'], function()
  2. {
  3. Route::get('auth', 'AuthController@index');
  4. Route::post('auth', 'AuthController@auth');
  5. });

建立controller artisan make:controller AuthController

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use App\Http\Requests;
  5. use Auth;
  6. use JWTAuth;
  7. class AuthController extends Controller
  8. {
  9. public function auth(Request $request)
  10. {
  11. $credentials = $request->only('email', 'password');
  12. try {
  13. if (! $token = JWTAuth::attempt($credentials)) {
  14. return response()->json(['error' => 'invalid_credentials'], 401);
  15. }
  16. } catch (JWTException $e) {
  17. return response()->json(['error' => 'could_not_create_token'], 500);
  18. }
  19. return response()->json(compact('token'));
  20. }
  21. public function __construct()
  22. {
  23. $this->middleware('jwt.auth', ['except' => ['auth']]);
  24. }
  25. public function index()
  26. {
  27. return response()->json(Auth::user()->all());
  28. }
  29. }
Laravel - CSRF token禁用方法
打開文件:app\Http\Middleware\VerifyCsrfToken.php
protected $except = [
        'api/*',
        'http://loaclhost/foo/bar',
    ];

在config/jwt.php中,你可以配置以下選項:

ttl:token有效期(分鐘)
refresh_ttl:刷新token時間(分鐘)
algo:token簽名算法
user:指向User模型的命名空間路徑
identifier:用於從token的sub中獲取用戶
require_claims:必須出現在token的payload中的選項,否則會拋出TokenInvalidException異常
blacklist_enabled:如果該選項被設置為false,那麼我們將不能廢止token,即使我們刷新了token,前一個token仍然有效
providers:完成各種任務的具體實現,如果需要的話你可以重寫他們
User —— providers.user:基於sub獲取用戶的實現
JWT —— providers.jwt:加密/解密token
Authentication —— providers.auth:通過證書/ID獲取認證用戶
Storage —— providers.storage:存儲token直到它們失效

2018年11月6日 星期二

《面試官別再問》PHP Coding Style指南PSR-2

這個指南是在PSR-1上的延伸和擴展,基本的編碼標準

本指南的目的是減少認知摩擦(風格不統一而造成不便),當瀏覽不同作者的代碼時。它做的這些關於如何格式化PHP代碼通過列舉一個共享的規則設置和期望

此處樣式規則衍生均來自各成員項目間的共性。當不同作者在多個項目間合作時,有一個設置的指南,它有助於被用來在所有這些項目中使用。因此,本指南的好處不在規則本身,而是這個規則的共享

PSR是PHP Standards Recommendations(PHP標準建議) 的簡寫,是由PHP-FIG組織(PHP Framework Interop Group-PHP框架交互操作組織)提出。 PHP-FIG的工作是尋找項目之間的共性,以及讓開發者能更好協同工作的方式。讀者可能在瀏覽一些PHP技術文章的時候,可能會看到PSR-1,PSR-2,PSR-4,PSR-7等,這些是PHP-FIG的標準建議,這些標準建議的命名構成是以' PSR-'+'序號',每個PHP_FIG標準建議都是為了解決大部分框架頻繁遇到的一個特定的問題,而與此同時框架不需要自己再去重複解決問題,而是遵循PSR標準建議,採納共享的解決方案.

在此介紹一項工具

PHP Code Sniffer 簡稱 phpcs,可以用來檢查你寫的PHP是否符合PSR-2我們可以用Composer 來安裝他

安裝
composer require squizlabs/php_codesniffer

測試
phpcs --version

檢查程式

phpcs --standard=PSR2 專案資料夾

OverView (概覽)

PHP文件(MUST)必須只使用<?php 和<?= 標籤.
PHP文件(MUST)必須只能使用無BOM頭的UTF-8編碼格式.
PHP文件(SHOULD)應該要么聲明classe,functions。 constants 等等,或者general ouput(通用輸出?),改變php.ini的配置等等,但是(SHOULD Not)不應該兩個都有.
命名空間和類(MUST)必須要遵循自動加載的PSR規範[PSR-0,PSR-4];
類名明明(MUST)必須以大駝峰式.
類中的常量的命名(MUST)必須為以下劃線分割的大寫字母.
方法名(MUST)必須為小駝峰式.
PHP代碼(MUST)必須只使用無BOM頭的UTF-8編碼格式.

Side Effects

一個PHP文件(SHOULD)必須要聲明一些class,function,constant等,並且沒有side effects (執行邏輯),或者(SHOULD)應該有side effects(執行邏輯),但是(SHOULD NOT)不應該兩個都同時存在.

   'side effects'代表的就是和聲明class,function,constant等不直接相關聯的執行邏輯,僅僅來自這個包含的文件。

  'Side effects' 包含以下操作,但不僅限於此:general output(通用輸出), 'require'和'include'的使用,連接外部服務,修改php.ini的設置,觸發錯誤或者拋出異常,修改全局或者靜態變量,讀取或者寫入文件操作等等.

  以下是一個我們要避免的例子,就是一個具有'declarations'和side effects的PHP文件。

<?php
// side effect: change ini settings
ini_set('error_reporting', E_ALL);

// side effect: loads a file
include "file.php";

// side effect: generates output
echo "<html>\n";

// declaration
function foo()
{
    // function body
}
以下是一個是正確的例子,只包含了'declaraion'沒有'side effects'.

<?php
// declaration
function foo()
{
    // function body
}

General - 約定

基本編碼標準
代碼必須遵循PSR-1中的所有規範

文件
所有PHP文件必須使用Unix LF(linefeed)作為行的結束符

所有PHP文件必須以一個空白行作為結束

只包含PHP代碼的文件結束?>標記必須被忽略


行的長度一定不能有硬性約束

行長度的軟限制必須在120字符內;自動樣式檢查必須警告但一定不能提示錯誤在軟限制上

行不應該超過80個字符;超過80個字符的長度的行應當拆分成多個不超過80個字符的子行

非空行後一定不能尾隨空格符

空行可以改善代碼閱讀,並且表明相關的代碼塊

每行一定不能存在多於一條語句

縮進
編碼必須使用4個空格,並且一定不能使用tab縮進

僅使用空格,並且不要混淆空格用tab,有助於避免比較差異,打補丁,重讀和註解問題。使用空格也使插入細粒度的子縮進為了跨線對齊變得容易

關鍵字:True,False,Null
PHP 關鍵字必須小寫

PHP常量true,false,null必須小寫

Namespace and Use Declarations - 命名空間和使用聲明
在目前,namespace聲明之後必須有一個空行

在目前,所有use聲明必須在namespace聲明之後

每個聲明必須有一個use關鍵字

use 塊之後必須有一個空行

<?php
namespace Vendor \ Package ;

use FooClass ;
use BarClass as Bar ;
use OtherVendor \ OtherPackage \ BazClass ;

// ... additional PHP code ...

Classes,Properties,and Methods - 類,屬性和方法
術語class指的是所有的class,interface,trait

繼承和實現
extends和implements關鍵字必須被聲明在作為類名的同一行

類的開花括號必須獨佔一行; 類的閉花括號必須在類體之後獨佔一行

implements的列表可以被分隔成多行,每個子行要縮進。當這樣做,列表中的第一個條目必須獨佔一行,並且必須是僅僅一個接口獨佔一行

<?php
namespace Vendor \ Package ;

use FooClass ;
use BarClass as Bar ;
use OtherVendor \ OtherPackage \ BazClass ;

class ClassName extends ParentClass implements
     \ ArrayAccess ,
    \ Countable ,
    \ Serializable
{ // constants, properties, methods }

控制結構(Control Structures)

控制結構的通用編碼風格規範:
控制結構的關鍵字之後應該有一個空格
左括號之後不應該有空格
右括號之前不應該有空格
右花括號和左花括號之間應該有一個空格
結構體應該縮印一次
右花括號應該在結構體的下一行
每一個結構體應該被花括號包圍;這樣子,結構看起來很標準,並且當加入新行的時候,gi 可以減少引入錯誤的可能性。

if, elseif, else
一個if結構看起來像下面那樣。注意圓括號的位置,空格和花括號;並且else和elseif與來自之前的結構體閉花括號在相同的行

<?php
if ($expr1) { // if body } elseif ($expr2) { // elseif body } else { // else body; }

關鍵字elseif應該被使用而不是else if以至於所有的控制關鍵字看起來像單個單詞

switch, case
一個switch結構看起來像下面那樣。注意圓括號的位置,空格和花括號。 case語句必須被縮進一次在switch中,並且break關鍵字(或者其它終止關鍵字)必須被縮進相同的級別在case體內。當故意落空一個非空case體時必須有一個註釋,例如:// no break
<?php
switch ($expr) { case 0 : echo 'First case, with a break' ; break ; case 1 : echo 'Second case, which falls through' ; // no break case 2 : case 3 : case 4 : echo 'Third case, return instead of break' ; return ; default : echo 'Default case' ; break ; }

2018年11月4日 星期日

《面試官別再問》PHP實現Websocket

WebSocket是一個持久化的協議

http1.0的生命週期是以request作為界定的,也就是一個request,一個response,對於http來說,本次client與server的會話到此結束;而在http1.1中,稍微有所改進,即添加了keep-alive,也就是在一個http連接中可以進行多個request請求和多個response接受操作。然而在實時通信中,並沒有多大的作用,http只能由client發起請求,server才能返回信息,即server不能主動向client推送信息,無法滿足實時通信的要求。而WebSocket可以進行持久化連接,即client只需進行一次握手,成功後即可持續進行數據通信,值得關注的是WebSocket實現client與server之間全雙工通信,即server端有數據更新時可以主動推送給client端。

client與server建立socket時握手的會話內容,即request與response

a、client建立WebSocket時向服務器端請求的信息

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket //告訴服務器現在發送的是WebSocket協議

Connection: Upgrade

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一個Base64 encode的值,這個是瀏覽器隨機生成的,用於驗證服務器端返回數據是否是WebSocket助理

Sec-WebSocket-Protocol: chat, superchat

Sec-WebSocket-Version: 13

Origin: http://example.com

b、服務器獲取到client請求的信息後,根據WebSocket協議對數據進行處理並返回,其中要對Sec-WebSocket-Key進行加密等操作

HTTP/1.1 101 Switching Protocols

Upgrade: websocket //依然是固定的,告訴客戶端即將升級的是Websocket協議,而不是mozillasocket,lurnarsocket或者shitsocket

Connection: Upgrade

Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //這個則是經過服務器確認,並且加密過後的Sec-WebSocket-Key,也就是client要求建立WebSocket驗證的憑證

PHP中建立socket的過程講解


1.client與server之間建立socket通訊,首先在PHP中建立socket並監聽Port

//傳相應的IP與端口進行創建socket操作
function WebSocket($address,$port){
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受所有的數據包
socket_bind($server, $address, $port);
socket_listen($server);
return $server;
}

2.設計一個迴圈掛起WebSocket通道,進行資料的接收

//對建立的socket迴圈進行監聽,處理數據
function run(){
//死循環,直到socket斷開
while(true){
$changes=$this->sockets;
$write=NULL;
$except=NULL;
         
/*
//這個函數是同時接受多個連接的關鍵,我的理解它是為了阻塞程序繼續往下執行。
socket_select ($sockets, $write = NULL, $except = NULL, NULL);
$sockets可以理解為一個數組,這個數組中存放的是文件描述符。當它有變化(就是有新消息到或者有客戶端連接/斷開)時,socket_select函數才會返回,繼續往下執行。
$write是監聽是否有客戶端寫數據,傳入NULL是不關心是否有寫變化。
$except是$sockets裡面要被排除的元素,傳入NULL是”監聽”全部。
最後一個參數是超時時間
如果為0:則立即結束
如果為n>1: 則最多在n秒後結束,如遇某一個連接有新動態,則提前返回
如果為null:如遇某一個連接有新動態,則返回
*/
socket_select($changes,$write,$except,NULL);
foreach($changes as $sock){
             
//如果有新的client連接進來,則
if($sock==$this->master){

//接受一個socket連接
$client=socket_accept($this->master);

//給新連接進來的socket一個唯一的ID
$key=uniqid();
$this->sockets[]=$client; //將新連接進來的socket存進連接池
$this->users[$key]=array(
'socket'=>$client, //記錄新連接進來client的socket信息
'shou'=>false //標誌該socket資源沒有完成握手
);
//否則1.為client斷開socket連接,2.client發送信息
}else{
$len=0;
$buffer='';
//讀取該socket的信息,注意:第二個參數是引用傳參即接收數據,第三個參數是接收數據的長度
do{
$l=socket_recv($sock,$buf,1000,0);
$len+=$l;
$buffer.=$buf;
}while($l==1000);

//根據socket在user池裡面查找相應的$k,即健ID
$k=$this->search($sock);

//如果接收的信息長度小於7,則該client的socket為斷開連接
if($len<7){
//給該client的socket進行斷開操作,並在$this->sockets和$this->users裡面進行刪除
$this->send2($k);
continue;
}
//判斷該socket是否已經握手
if(!$this->users[$k]['shou']){
//如果沒有握手,則進行握手處理
$this->woshou($k,$buffer);
}else{
//走到這裡就是該client發送信息了,對接受到的信息進行uncode處理
$buffer = $this->uncode($buffer,$k);
if($buffer==false){
continue;
}
//如果不為空,則進行消息推送操作
$this->send($k,$buffer);
}
}
}

}

3.伺服器端完成的WebSocket的前期工作後,就等著client連接進行,client建立WebSocket很簡單

var ws = new WebSocket("ws://IP:端口");
//握手監聽函數
ws.onopen=function(){
//狀態為1證明握手成功,然後把client自定義的名字發送過去
if(so.readyState==1){
//握手成功後對服務器發送信息
so.send('type=add&ming='+n);
}
}
//錯誤返回信息函數
ws.onerror = function(){
    console.log("error");
};
//監聽服務器端推送的消息
ws.onmessage = function (msg){
    console.log(msg);
}
//斷開WebSocket連接
ws.onclose = function(){
    ws = false;
}