前言: 目前市場上,PHP好像主流在做爲API開發的形式存在, 如很火的小程序、H5等,天然的,編寫可靠、安全的api接口是必不可少的。 因爲我目前剛入職沒多久,查看了市場上的一些閉源的一些產品。 大多數都具備一個問題,散亂(接口不規範),開放(不安全),耦合度低 固然,這也是爲何喜歡噴PHP的緣由, 強調快速開發,且雜亂無章的插入代碼,耦合度、複用度低。 由此衍生了衆多的垃圾程序員,包括我本身也存在這樣的問題php
這裏,首選 RESTful
, 爲何 ?html
強調HTTP應當以資源爲中心,而且規範了資源URI的風格;前端
規範了HTTP請求動做(PUT,POST等)的使用,具備對應的語義;ios
遵循REST規範的Web應用將會得到下面好處:laravel
簡而言之,經過RESTful,增長接口代碼可讀性。能夠更方便的經過資源(post | GET 等)來控制咱們的接口。 儘管他不是一個標準,但咱們應該向他看齊git
首先建議你們導讀一下如下系列文章程序員
(RESTful API 最佳實踐 阮一峯)[www.ruanyifeng.com/blog/2018/1…]github
(Github 的 Restful HTTP API 設計分解)[laravel-china.org/courses/lar…]web
api資源控制,強調動詞,我在幹什麼,我須要怎麼幹json
我認爲一套接口應該儘可能知足如下幾個原則:
在 Laravel
的場景中,其實自5.5版本迭代以後, 自帶的 response
足以知足咱們的須要
dingo Api 目前尚未穩定版本, 建議你們自我斟酌
使用 dingo Api
大概有如下幾個好處
版本控制更方便(封裝了一系列的方法供你使用) - 不要重複的造輪子
響應設置更加爲所欲爲
神奇的 Transformers
(提升耦合度, 複用代碼率簡直直線上升)
節流設置
異常處理管理
實踐的話,固然開始咱們的實踐之旅啦
導讀: laravel-china.org/docs/larave… |
咱們採用的是 vagrant + Homestead + composer
的本地環境 | 項目包這裏忽略,自行安裝
採用 laravel_china
中國鏡像
composer config repo.packagist composer https://packagist.laravel-china.org
複製代碼
安裝包
composer require dingo/api:^2.0.0-alpha2
composer require liyu/dingo-serializer-switch
複製代碼
發佈
php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"
# 詳細的相關配置,這裏不作介紹
API_STANDARDS_TREE=vnd # 項目環境
API_VERSION=v1 # 版本號
API_NAME="My API" # 項目名稱
API_STRICT=false # 是否開啓嚴格模式【嚴格模式要求客戶端發送 Accept 頭】
API_DEFAULT_FORMAT=json # 響應格式
複製代碼
開始寫代碼
api.php 【僅供參考】
$api = app('Dingo\Api\Routing\Router');
$api->version('v1',[
'namespace' => 'App\Api\Controllers',
'middleware' => ['serializer:array','bindings'],
'name' => 'api.'
], function ($api) {
$api->post('/login','AuthController@login')->name('login'); # 登錄api
$api->post('/user' ,'AuthController@index')->name('user'); # 獲取用戶的信息
$api->get('/articles' ,'ArticleController@list')->name('articles'); # 獲取文章列表
$api->get('/article/{id}' ,'ArticleController@detail')->name('article.detail'); # 獲取文章詳細信息
$api->get('/keywords' ,'KeywordController@list')->name('keywords'); # 獲取標籤列表
$api->get('/keyword/{keyword}' ,'KeywordController@detail')->name('keyword.detail'); # 獲取標籤下的文章列表
$api->group(['middleware' => ['jwt.auth','bindings']], function ($api) {
$api->patch('/reply/{reply}', 'ReplyController@edit')->name('reply.edit'); # 修改評論的狀態
$api->delete('/reply/{reply}', 'ReplyController@delete')->name('reply.delete'); # 刪除評論的狀態
$api->put('/reply/batch', 'ReplyController@batch')->name('reply.batch'); # 批量修改評論的狀態
$api->delete('/reply/batch', 'ReplyController@deleteBybatch')->name('reply.deleteBybatch'); # 批量刪除評論的狀態
$api->post('/refresh', 'AuthController@refresh')->name('refresh.token'); # 刷新token
$api->post('/logout', 'AuthController@logout')->name('logout'); # 註銷
$api->get('/todolists', 'UserController@todolists')->name('todolists'); # 查看個人todolists
$api->get('/replies', 'ReplyController@list')->name('replies'); # 獲取評論列表
$api->post('/index', 'IndexController@index')->name('index'); # 獲取儀表盤相關數據
});
});
複製代碼
serializer:array
需安裝 composer require liyu/dingo-serializer-switch ,當渲染數據到前端的時候,會默認的加data = {} , 安裝此項東西能夠減輕一些前端的麻煩
bindings
中間件因爲被dingo接管了, 因此若是使用模型綁定的話, 需加入bindings
這個中間件
響應方法
在控制器中需繼承 `Helpers`
namespace App\Api\Controllers;
use Dingo\Api\Routing\Helpers;
use App\Http\Controllers\Controller;
class BaseController extends Controller
{
use Helpers;
}
# 方法
// 一個自定義消息和狀態碼的普通錯誤。
return $this->response->error('This is an error.', 404);
// 一個沒有找到資源的錯誤,第一個參數能夠傳遞自定義消息。
return $this->response->errorNotFound();
// 一個 bad request 錯誤,第一個參數能夠傳遞自定義消息。
return $this->response->errorBadRequest();
// 一個服務器拒絕錯誤,第一個參數能夠傳遞自定義消息。
return $this->response->errorForbidden();
// 一個內部錯誤,第一個參數能夠傳遞自定義消息。
return $this->response->errorInternal();
// 一個未認證錯誤,第一個參數能夠傳遞自定義消息。
return $this->response->errorUnauthorized();
// 添加額外的頭信息
return $this->response->item($user, new UserTransformer)->withHeader('X-Foo', 'Bar')
// 添加 Meta 信息
return $this->response->item($user, new UserTransformer)->addMeta('foo', 'bar');
// 設置響應狀態碼
return $this->response->item($user, new UserTransformer)->setStatusCode(200);
複製代碼
咱們這裏追求實踐,暫時忽略相關的原理性問題
這個以爲是好東西,首先個人目錄結構以下
查看 文章的控制器
# ArticleController.php
....
namespace App\Api\Controllers;
use App\Api\Transformers\ArticleTransformer;
use App\Model\Article;
class ArticleController extends BaseController
{
/**
* 獲取文章列表
*/
public function list()
{
$articles = Article::query()->orderByDesc('created_at')->get();
return $this->response->collection($articles, new ArticleTransformer());
}
public function detail($id)
{
if( !$article = Article::find($id) ) {
return $this->response->errorNotFound('文章未找到或者已刪除');
}
return $this->response->item($article, new ArticleTransformer());
}
}
# ArticleTransformer.php
...
namespace App\Api\Transformers;
use App\Model\Article;
use Carbon\Carbon;
use League\Fractal\TransformerAbstract;
class ArticleTransformer extends TransformerAbstract
{
protected $availableIncludes = ['keywords', 'category'];
public function transform(Article $article)
{
return [
'id' => $article->id,
'title' => $article->title,
'body' => $article->body,
'category_id' => $article->category_id,
'readCount' => $article->readCount,
'create_time' => Carbon::make($article->created_at)->toDateTimeString()
];
}
/**
* 包含文章標籤字段
*/
public function includeKeywords(Article $article)
{
return $this->collection($article->keywords, new KeywordsTransformer());
}
public function includeCategory(Article $article)
{
return $this->item($article->category, new CategoryTransformer());
}
}
# Article.php
amespace App\Model;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Article extends Model
{
use SoftDeletes;
/**
* 查看文章對應的標籤 遠程多對多
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function keywords()
{
return $this->hasManyThrough(Keyword::class, ArticleKeyword::class, 'article_id','id');
}
.....
複製代碼
有沒有發現,代碼輕量不少? transform 傳遞對應的模型,數據 。他會自動根據你對應的 collection 【集合】 或者 item 【單個模型】來返回你所須要的數據
其中 collection
和 item
須要注意的地方。 【我在這裏吃了點虧】
collection
是一個集合,當操做返回的是多個數據的時候使用它, 例如
$articles = Article::all()
$this->collection($articles, new ArticleTransformer());
複製代碼
若是這裏使用item
則會報一個error
級別的錯誤
關於 include
,必須繼承 TransformerAbstract
且 使用 $availableIncludes
如上面中對於的接口爲
http://surest.test/api/articles
那麼咱們產生的數據以下
http://surest.test/api/articles?include=category,keywords
對吧,一目瞭然, 經過 include
想要什麼數據,就拿什麼數據, 並且 經過 ArticleTransformer
, 你同時能夠在如何地方,重複使用這一套代碼。
而無需在進行編輯,或者爲了對應某個接口或者要求而去寫代碼 , 直接 include
了事
當咱們規定了相關的響應參數的時候, 直接使用 $this->response
便可
# example
# 會拋出一個 404 錯誤,能夠參看源碼, 很簡單
return $this->response->errorNotFound();
複製代碼
節流設置
當咱們須要防止某個接口重複的被使用,例如常見的攻擊之類的, 能夠有效的預防
在 Kernel.php
中 設置
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
# 添加以下
'api' => [
'throttle:60,1',
'bindings',
],
];
複製代碼
由此, 簡單的dingoapi操做完美成功了, 基本上能應付平常的需求, 如更深層次的,能夠參考dingapi文檔laravel-china.org/docs/dingo-…
關於這一段代碼的修改版
public function detail($id)
{
if( !$article = Article::find($id) ) {
return $this->response->errorNotFound('文章未找到或者已刪除');
}
return $this->response->item($article, new ArticleTransformer());
}
複製代碼
如上, 在 laravel
中彷佛能夠找到更好的辦法來進行替代他, findOrFail
- findOrFail , 查看官方的api得知,它會拋出一個 ModelNotFoundException 錯誤。
回到上面, 說到, 因爲dingo接管了laravel自帶的錯誤訊息, 咱們能夠這樣使用
# AppServiceProvider.php
....
class AppServiceProvider extends ServiceProvider
{
...
public function register()
{
\API::error(function (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
abort('404','模型未找到');
});
}
}
複製代碼
如上, 纔可使用咱們的 findOrFail
。 可是,在對文章的增刪改查中咱們發現,大量運用到了檢查文章是否存在的代碼塊, 咱們來優化一下代碼, 能夠這樣使用
建立一箇中間件
php artisan make:middleware FilterArticle
# FilterArticle.php
...
use Dingo\Api\Routing\Helpers;
..
class FilterArticle
{
use Helpers;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if( $aid = $request->id ) {
if( Article::find($aid) ) {
return $next($request);
}else{
return $this->response->errorNotFound('文章未找到');
}
}else{
return $this->response->errorNotFound('參數錯誤');
}
}
...
}
# 註冊中間件 | Kernel.php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
.....
'article.check' => FilterArticle::class
];
# ArticleController.php
class ArticleController extends BaseController
{
public function __construct()
{
$this->middleware('article.check',
['only' => ['edit','create','delete','detail']]);
}
.....
}
複製代碼
由此,咱們的代碼將變成這樣
/**
* 查看文章詳情
* @param $id
* @return \Dingo\Api\Http\Response|void
*/
public function detail(Article $article)
{
return $this->response->item($article, new ArticleTransformer());
}
複製代碼
怎麼樣, 爽不爽呢~~, 使用 Article::find($aid)
的緣由, 是由於可以更加定製化的看到本身的錯誤緣由。 更加通俗易懂,固然,findOrFail
也是很好滴
下期預告: 將介紹
JWT 的安裝使用、原理、最佳操做方法等
VUE 下如何實現token
的讀取變化刷新, 無狀態變化token 、OAuth模式等
VUE 下 axios
攔截器 、 Promise
、 餓了麼組件的
的部分應用場景
: 面朝大海,春暖花開 | 一切以新手角度出發,講一些文檔你不知道的應用場景
個人博客: surest.cn - 維護中