Laravel-API實(shí)踐教程

概述

Laravel 是一個(gè)非常流行的 PHP 框架,我們可以使用它快速構(gòu)建一個(gè) WEB 應(yīng)用程序。而現(xiàn)在 WEB 應(yīng)用中多會(huì)采用前后端分離技術(shù),所以我們經(jīng)常會(huì)遇到使用 Laravel 搭建 API 項(xiàng)目的需求。
Laravel 在提供 API 這方面,很多地方都只是提供了一個(gè)規(guī)范,并沒(méi)有告訴我們?nèi)绾稳?shí)現(xiàn)它。這樣帶來(lái)的好處是 Laravel 放開(kāi)了限制,使大家可以按照自己的習(xí)慣去使用它。
但這樣做也給剛接觸 Laravel 不久的同學(xué)帶來(lái)了一些困擾:到底怎樣使用這個(gè)框架才更優(yōu)雅些呢,有沒(méi)有例子可以參考下呢。

本項(xiàng)目就是為了給大家提供一個(gè)參考而建立的,這是一個(gè)使用 Laravel 框架實(shí)現(xiàn)的 API 項(xiàng)目,項(xiàng)目中提供了一些常見(jiàn)功能的示例。
本項(xiàng)目從零開(kāi)始,在重要修改部分末尾都會(huì)添加 [commit] 鏈接, 可自行查看變更記錄。

需要注意的是:

  1. 這并不是一個(gè) Laravel 的新手教程,文中很多地方需要你了解 Laravel 的基礎(chǔ)知識(shí)。 Laravel官網(wǎng)文檔
  2. 如果你已經(jīng)有自己的實(shí)現(xiàn)方式,可以略過(guò)此教程,這個(gè)項(xiàng)目的實(shí)現(xiàn)方式可能也沒(méi)比你的實(shí)現(xiàn)方式更優(yōu)雅。
  3. Laravel 各個(gè)版本之前實(shí)現(xiàn)方式還是有一些區(qū)別的,要注意區(qū)分,但整體思路是一樣的。

安裝

這里使用 composer 安裝 Laravel ,下面命令會(huì)安裝最新版本的 Laravel 。此項(xiàng)目創(chuàng)建時(shí)的 Laravel 版本為 v8.21.0

composer create-project --prefer-dist laravel/laravel laravel-api-example

安裝好之后需要自行配置項(xiàng)目使其可以對(duì)外訪問(wèn),當(dāng)在瀏覽器中輸入項(xiàng)目地址進(jìn)入到 Laravel 的歡迎頁(yè)時(shí),就可以繼續(xù)向下閱讀了。

路由

在歡迎頁(yè)我們可以看到,Laravel 返回的信息是一個(gè) web 頁(yè)面,也就是 html 代碼。這個(gè)默認(rèn)的路由是在 routes/web.php 中定義的,我們需要把它給移除掉。[commit]

我們所有的路由都要定義到 routes/api.php 中,這個(gè)是專門用來(lái)定義 API 路由的文件,當(dāng)然如果你的路由特別多你也可以在 routes 中定義其他路由文件,然后在 RouteServiceProvider 中按照同樣的方式去加載它們。
RouteServiceProvider 中我們可以看到,我們?cè)?routes/api.php 中定義的路由會(huì)默認(rèn)加上 api 前綴,這對(duì) WEB 和 API 混寫(xiě)在同一個(gè)項(xiàng)目中很有必要,但單獨(dú)的 API 項(xiàng)目一般也會(huì)單獨(dú)域名。如:

https://api.apihubs.cn/holiday/get

所以我們要移除這個(gè) api 前綴或換成其他前綴如接口版本號(hào) V1 [commit]

routes/api.php 中默認(rèn)的路由是一個(gè)需要身份驗(yàn)證的 /user

我們使用瀏覽器或 postman 訪問(wèn)的時(shí)候,會(huì)得到一個(gè)錯(cuò)誤頁(yè)面,其中的主要信息為:Route [login] not defined.

我們使用 ajax 或者在 postman 的 header 中添加 X-Requested-With:XMLHttpRequest 頭信息后又會(huì)得到一個(gè) JSON 的錯(cuò)誤信息:{"message": "Unauthenticated."}

這實(shí)際上都是未登錄的原因,在未登錄訪問(wèn)需要鑒權(quán)的接口時(shí) Laravel 會(huì)拋出一個(gè) AuthenticationException ,而在響應(yīng)類 Response 中會(huì)根據(jù)請(qǐng)求的 header 頭自動(dòng)做出響應(yīng)。
也就是如果是以頁(yè)面形式調(diào)用的,就會(huì)跳轉(zhuǎn)到登錄頁(yè)面,因?yàn)轫?xiàng)目中還沒(méi)定義登錄頁(yè)面的路由就出現(xiàn)我們上面看到的那個(gè)錯(cuò)誤。如果是以接口形式訪問(wèn)的就會(huì) 401 狀態(tài)碼并返回 JSON 信息。

但我們這是一個(gè) API 項(xiàng)目,提供出去的都是接口地址,在接口地址中一會(huì)返回 JSON 一會(huì)又返回一個(gè)頁(yè)面這是不是顯得很尷尬。

這里我們需要新增一個(gè) Middleware 來(lái)解決這個(gè)問(wèn)題。然后在 Kernel 中注冊(cè)這個(gè) Middleware 使其全局生效。 [commit]

php artisan make:middleware JsonApplication

這樣我們已經(jīng)配置好一個(gè) JSON 應(yīng)用了,在 Laravel 拋出任何異常時(shí),無(wú)論我們以什么方式訪問(wèn)都會(huì)始終得到 JSON 響應(yīng)信息。

需要注意的是我們依然不能在路由的閉包或控制器中使用 return view($view) ,因?yàn)檫@會(huì)強(qiáng)制返回一個(gè) html 頁(yè)面響應(yīng)。
我們應(yīng)該在路由的閉包或控制器中始終 return 一個(gè)對(duì)象或數(shù)組,這兩種格式會(huì)使 Laravel 自動(dòng)為我們返回正確的 JSON 信息。

錯(cuò)誤碼

通過(guò)上面的配置,我們的應(yīng)用可以始終返回 JSON 信息了。比如在出現(xiàn)異常的時(shí)候:

  • 鑒權(quán)失敗會(huì)返回 http 狀態(tài)碼 401 的 {"message": "Unauthenticated."}
  • 請(qǐng)求的 method 不正確會(huì)返回 http 狀態(tài)碼 405 的 {"message":"The POST method is not supported for this route. Supported methods: GET, HEAD.","exception":"Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException",...}
  • 請(qǐng)求的 URL 地址不存在會(huì)返回 404 的 {"message":"","exception":"Symfony\Component\HttpKernel\Exception\NotFoundHttpException",...}
  • ...

但這樣的返回信息也有問(wèn)題:

  1. 返回信息的格式并不統(tǒng)一,JSON 中的 key 時(shí)有時(shí)無(wú),接口調(diào)用方在很多時(shí)候找不到要以什么作為依據(jù)進(jìn)行判斷
  2. 許多異常信息為服務(wù)端敏感信息,會(huì)直接報(bào)漏給用戶,存在安全隱患
  3. 異常信息都是通過(guò) HTTP 狀態(tài)碼拋出的,會(huì)導(dǎo)致許多錯(cuò)誤的 HTTP 狀態(tài)碼相同,比如 500

而通常的做法是需要根據(jù)不同的業(yè)務(wù)場(chǎng)景定義不同的錯(cuò)誤代碼和錯(cuò)誤信息, 然后始終返會(huì) http 狀態(tài)碼 200 的 {"code":"","msg":"","data":""}

在這里我們需要引入一個(gè)第三方的庫(kù)(這個(gè)庫(kù)會(huì)在項(xiàng)目中許多地方使用,也是本教程的核心,當(dāng)然這個(gè)庫(kù)的功能是仿照 Java 枚舉而來(lái)的) phpenum[commit]

composer require phpenum/phpenum

這是一個(gè)枚舉庫(kù),在這里用來(lái)定義和管理錯(cuò)誤碼和錯(cuò)誤信息,錯(cuò)誤碼的位數(shù)應(yīng)該是固定的,至少一個(gè)模塊下的錯(cuò)誤碼位數(shù)是固定的,這里使用 5 位錯(cuò)誤碼,你可以根據(jù)實(shí)際使用場(chǎng)景來(lái)定義。
我們?cè)?app 目錄下新建一個(gè) Enums 目錄,然后添加 ErrorEnum 為不同的錯(cuò)誤和異常定義不同的錯(cuò)誤碼。 [commit]

定義好錯(cuò)誤碼后,我們還需要借助 Laravel 的 渲染異常 來(lái)渲染自定義異常類 ApiException[commit]

php artisan make:exception ApiException

在上面這個(gè) commit 中,我們對(duì)常見(jiàn)的異常都做了處理,使他們返回固定的錯(cuò)誤碼和錯(cuò)誤信息,尤其對(duì)數(shù)據(jù)驗(yàn)證失敗在 data 中返回了詳細(xì)的錯(cuò)誤信息,你也可以在 Handler 中添加一些其他需要處理的異常。

而未特殊定義狀態(tài)碼的異常會(huì)統(tǒng)一返回錯(cuò)誤碼 99999 的 未知錯(cuò)誤,生產(chǎn)環(huán)境中是不應(yīng)該出現(xiàn)這個(gè)錯(cuò)誤碼的,這個(gè)異常一般出現(xiàn)在調(diào)試階段,我們需要解決掉它。

由于接口不再返回任何錯(cuò)誤信息了,我們排查問(wèn)題的方式也只能通過(guò)日志來(lái)排查 默認(rèn)的日志在你本地的這個(gè)目錄下 Laravel的日志也是非常強(qiáng)大,你可以隨意更改存儲(chǔ)的位置和介質(zhì),這里就不展開(kāi)介紹了。

到這里我們就配置好統(tǒng)一錯(cuò)誤碼了,接下來(lái)無(wú)論在項(xiàng)目中出現(xiàn)什么錯(cuò)誤,拋出什么異常,接口返回的信息始終保持為http狀態(tài)碼200的 {"code":"","msg":"","data":""}

但這些狀態(tài)碼都是系統(tǒng)產(chǎn)生異常時(shí)返回的,我們要自己返回自定義狀態(tài)碼要怎么做呢? 非常簡(jiǎn)單,你只需要在任何你想返回自定義狀態(tài)碼的地方拋出自定義異常就可以了(但除了 controller 層,其他層可能會(huì)以非 web 的方式掉用,比如 console ,它不應(yīng)該捕獲到 ApiException ,所以盡量保證在 controller 拋出 ApiException)

throw new ApiException(ErrorEnum::UNKNOWN_ERROR()); // {"code":99999,"msg":"服務(wù)器繁忙,請(qǐng)稍后再試","data":""}
throw new ApiException(ErrorEnum::UNKNOWN_ERROR(), 'This is an data'); // {"code":99999,"msg":"服務(wù)器繁忙,請(qǐng)稍后再試","data":"This is an data"}

那要是返回成功信息要怎么辦呢,這個(gè)實(shí)現(xiàn)方式有很多,可以用官方文檔示例中的 響應(yīng)宏 , 也可以使用幫助類,還可以使用...
這里我們選 Laravel 的 Resource , 新建一個(gè) Resource 類 JsonResponse, 在里面處理了常見(jiàn)的 Laravel 對(duì)象和添加分頁(yè)處理。 [commit]

這樣當(dāng)我們想返回成功信息時(shí)只需要 return 這個(gè)實(shí)例就可以了。

return new JsonResponse($mixed);

配置信息

Laravel 的配置信息都保存在 config 中,你也可以自定義自己的配置信息,獲取時(shí)只需要使用 config('filename.array_key') (支持多層級(jí))就能輕松的獲取到配置信息。
Laravel 的配置信息多是搭配了各個(gè)服務(wù)的門面模式來(lái)定義的,就比如 cache ,你只需要在 config 中修改 default driver ,就可以輕松的在 file 或 redis 或 database 等等等諸多存儲(chǔ)介質(zhì)之間切換,你還可以自定義存儲(chǔ)介質(zhì)。關(guān)于門面模式你可以自行查看 文檔源碼 ,這里不展開(kāi)介紹了。

這里還有個(gè)問(wèn)題,就是比如數(shù)據(jù)庫(kù)配置,一般我們都會(huì)區(qū)分開(kāi)發(fā)、測(cè)試、生產(chǎn)環(huán)境,但是 config 中的配置只能在倉(cāng)庫(kù)中保存一份,這里我們就要用到另外一個(gè)特殊配置文件 .env

所有配置文件中以類似于 env('DB_HOST', '127.0.0.1') 這種方式定義的都會(huì)讀取 .env 文件,第一個(gè)參數(shù)作為配置名稱,如果未在 .env 文件中定義,則使用第二個(gè)參數(shù)默認(rèn)值返回

一般我們會(huì)把所有區(qū)分環(huán)境的配置都定義在這個(gè)文件中

注意這個(gè)文件是不能提交到倉(cāng)庫(kù)的,所以你拉去代碼后很有可能看不到這個(gè)文件,只需要將 .env.example copy 一份為 .env 即可 (首次安裝 Laravel 會(huì)自動(dòng)執(zhí)行 copy) 然后配置好正確的數(shù)據(jù)庫(kù)連接信息

數(shù)據(jù)庫(kù)遷移(生產(chǎn)環(huán)境使用需謹(jǐn)慎)

文檔

大多數(shù)框架都有數(shù)據(jù)庫(kù)遷移功能,它對(duì)保持?jǐn)?shù)據(jù)庫(kù)結(jié)構(gòu)的一致性起到非常大的作用,但這個(gè)功能如果沒(méi)有合理使用則風(fēng)險(xiǎn)非常大,我之前就有同事使用了這個(gè)功能不小心把所有表都給重置了,還好數(shù)據(jù)是有備份的,及時(shí)進(jìn)行了恢復(fù)。
Laravel 的數(shù)據(jù)遷移文件在 database/migrations 中,由于 Laravel 框架是國(guó)外開(kāi)發(fā)者開(kāi)發(fā)的,他們對(duì)用戶的信息是以 email 為主,我們要在 user 表中增加手機(jī)號(hào)碼字段。[commit]

添加完成后要在 model 添加該字段 [commit]

這里我們是直接對(duì)表遷移文件進(jìn)行修改,是因?yàn)槲覀冞€沒(méi)有進(jìn)行數(shù)據(jù)庫(kù)遷移,當(dāng)你執(zhí)行過(guò)數(shù)據(jù)庫(kù)遷移后,Laravel 會(huì)在數(shù)據(jù)庫(kù)中記錄你已經(jīng)遷移過(guò)的文件,這時(shí)如果再想修改應(yīng)使用 更新表 的操作

接下來(lái)就可以執(zhí)行數(shù)據(jù)庫(kù)遷移操作了 (開(kāi)發(fā)環(huán)境)

php artisan migrate

驗(yàn)證規(guī)則

Laravel 提供了非常多的 驗(yàn)證規(guī)則 ,這些驗(yàn)證規(guī)則可以滿足大數(shù)據(jù)的驗(yàn)證場(chǎng)景,部分特殊驗(yàn)證需要我們自定義驗(yàn)證規(guī)則,比如手機(jī)號(hào)碼
我們通過(guò)以下命令來(lái)創(chuàng)建一個(gè) 手機(jī)號(hào)碼的驗(yàn)證規(guī)則 [commit]

php artisan make:rule PhoneNumber

添加好規(guī)則后我們可以在 ServiceProvider 中為規(guī)則配置別名 [commit]

參數(shù)驗(yàn)證

驗(yàn)證規(guī)則一般可以直接寫(xiě)在 controller 中,也可以單獨(dú)定義 Requests 進(jìn)行管理,這里我們使用第二種方式統(tǒng)一在 Requests 中定義管理驗(yàn)證邏輯。

我們先來(lái)創(chuàng)建一個(gè)獲取驗(yàn)證碼的 Request [commit]

php artisan make:request GetSmsCodeRequest

Request 中一般我們要在 messages 方法中重新定義錯(cuò)誤信息,添加好 Request 后,我們就可以直接在 controller 中使用,結(jié)合之前我們的配置,當(dāng)驗(yàn)證不通過(guò)時(shí)會(huì)返回以下信息

{
    "code": 10004,
    "msg": "數(shù)據(jù)驗(yàn)證失敗",
    "data": {
        "phone_number": [
            "請(qǐng)輸入您的手機(jī)號(hào)碼"
        ]
    }
}

獲取驗(yàn)證碼

到這里我們的基礎(chǔ)配置就完成了,讓我們來(lái)實(shí)際的添加一個(gè)接口吧,首先在 routes/api.php 中添加一條獲取驗(yàn)證碼的路由(限制訪問(wèn)頻次 30 分鐘 100 次) [commit]

Route::middleware('throttle:100,30')->post('getSmsCode', [AuthController::class, 'getSmsCode']);

然后在添加 Controller Contract Service 以及在 ServiceProvider 中綁定 Contract 和 Service 的關(guān)系 [commit]

php artisan make:controller AuthController

這種實(shí)現(xiàn)方式是參考了 Laravel 的 自動(dòng)注入 和 Laravel 的 將接口綁定到實(shí)現(xiàn) 而實(shí)現(xiàn)的

這里為了方便測(cè)試,驗(yàn)證碼直接在接口中返回,實(shí)際使用需要修改為短信發(fā)送

注冊(cè)

按照上面的教程,我們先來(lái)添加一個(gè)注冊(cè)的路由 [commit]

Route::middleware('throttle:100,30')->post('register', [AuthController::class, 'register']);

然后添加一個(gè)驗(yàn)證碼的 rule 并為其添加別名 [commit]

使用驗(yàn)證碼規(guī)則創(chuàng)建注冊(cè)的 request [commit]

最后在添加 Controller Contract Service 中的方法 [commit]

未完待續(xù),優(yōu)先在GITHUB更新

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容