高效掌握API+PostMan+Dingo+Jwt
作者:
秒速五厘米
REST是所有Web应用都应该遵守的架构设计指导原则。 Representational State Transfer,翻译是”表现层状态转化”。
面向资源是REST最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。
GET (SELECT):从服务器检索特定资源,或资源列表 POST (CREATE):在服务器上创建一个新的资源 PUT (UPDATE):更新服务器上的资源,提供整个资源PATCH (UPDATE):更新服务器上的资源,仅提供更改的属性DELETE (DELETE):从服务器删除资源
路径又称"终点"(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
接口尽量使用名词,禁止使用动词,下面是一些例子。
GET /zoos:列出所有动物园 POST /zoos:新建一个动物园 GET /zoos/ID:获取某个指定动物园的信息 PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息) PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)DELETE /zoos/ID:删除某个动物园GET /zoos/ID/animals:列出某个指定动物园的所有动物DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
再比如,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。
应该将API的版本号放入URL。如:
https://api.example.com/v1
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。 下面是一些常见的参数。
?limit=10:指定返回记录的数量 ?offset=10:指定返回记录的开始位置。 ?page_number=2&page_size=100:指定第几页,以及每页的记录数。 ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。 ?animal_type_id=1:指定筛选条件
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务) 204 NO CONTENT - [DELETE]:用户删除数据成功。 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。 429 Too Many Requests - 由于请求频次达到上限而被拒绝访问 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
从可读性和通用性来讲 JSON
是最好的响应数据格式,下面是一个错误消息响应数据结构。
{ 'message' => ':message', 'errors' => ':errors', 'code' => ':code', 'status_code' => ':status_code', 'debug' => ':debug'}
message:表示在API调用失败的情况下详细的错误信息,这个信息可以由客户端直接呈现给用户
errors:参数具体错误,比如字段较对错误内容
code:自定义错误码
status_code:http状态码
debug:debug调试信息
错误返回值根据情况进行删减
postman可以高效的测试和维护接口。https://www.getpostman.com/apps
使用 postMan
工具测试结果
我们在本地和服务器上都想测试接口,可以将域名定义为环境变量,这样我们只要改变环境域名就会自动变化。
Dingo Api 是致力于提供给开发者一套工具,帮助你方便快捷的建造你自己的API。这个包的目标是保持尽可能的灵活,它并不能覆盖所有的情况,也不能解决所有的问题。
官网:https://github.com/dingo/api/
文档:https://github.com/dingo/api/wiki/Configuration
composer require dingo/api:2.0.0-alpha2
执行下面命令生成配置文件 /config/api.php
php artisan vendor:publish
配置统一定义在 config/api.php
文档中
#接口围绕:[x]本地和私有环境 [prs]公司内部app使用 [vnd]公开接口'standardsTree' => env('API_STANDARDS_TREE', 'x')#项目名称'subtype' => env('API_SUBTYPE', 'hdcms')#Api前缀 通过 www.hdcms.com/api 来访问 API。'prefix' => env('API_PREFIX', 'api')#api域名'domain' => env('API_DOMAIN', 'api.hdcms.com'),#版本号'version' => env('API_VERSION', 'v1')#开发时开启DEBUG便于发现错误'debug' => env('API_DEBUG', false)
prefix 与 domain 只能二选一
在 routes/api.php
文件定义
$api = app(\Dingo\Api\Routing\Router::class);#默认配置指定的是v1版本,可以直接通过 {host}/api/version 访问到$api->version('v1', function ($api) { $api->get('version', function () { return 'v1'; }); });#如果v2不是默认版本,需要设置请求头 #Accept: application/[配置项 standardsTree].[配置项 subtype].v2+json$api->version('v2', function ($api) { $api->get('version', function () { return 'v2'; }); });
php artisan make:controller Api/Controller
修改内容如下
namespace App\Http\Controllers\Api;use Dingo\Api\Routing\Helpers;use Illuminate\Http\Request;use App\Http\Controllers\Controller as SysController;class Controller extends SysController{ use Helpers; }
Transformers 允许你便捷地、始终如一地将对象转换为一个数组。通过使用一个 transformer 你可以对整数和布尔值,包括分页结果和嵌套关系进行类型转换。
一个 transformer 是一个类,它会获取原始数据并将返回一个格式化之后的标准数组。
namespace App\Transformers;use App\User;use League\Fractal\TransformerAbstract;class UserTransformer extends TransformerAbstract{ public function transform(User $user) { return [ 'id' => $user['id'], 'name' => $user['name'], 'created_at'=> $user->created_at->toDateTimeString() ]; } }
返回单个数据
return $this->response->item(User::find(1),new UserTransformer());
返回集合
return $this->response->collection(User::get(),new UserTransformer());
分页数据
return $this->response->paginator(User::paginate(2),new UserTransformer());
获取文章时我们希望获取文章的栏目数据,include的特性就非常方便了。
下面是ContentTransformer中的定义,
class ContentTransformer extends TransformerAbstract{ # 定义可以include可使用的字段 protected $availableIncludes = ['category']; public function transform(Content $content) { return [ 'id' => $content['id'], 'name' => $content['title'], ]; } public function includeCategory(Content $content) { return $this->item($content->category,new CategoryTransformer()); } }
当我们调用 {host}/api/contents?include=category
接口时,栏目数据也一并会返回
return $this->response->paginator(Content::paginate(1),new ContentTransformer());
返回结果如下
{ "data": [ { "id": 1, "name": "后盾人 人人做后盾", "category": { "data": { "id": 2, "name": "编程" } } } ], "meta": { "pagination": { "total": 100, "count": 1, "per_page": 1, "current_page": 1, "total_pages": 100, "links": { "next": "http://xiang.houdunren.com/api/contents?page=2" } } } }
设置响应状态码
return $this->response->array(User::get())->setStatusCode(200);
return response()->json(['error' => 'Unauthorized'], 401);
错误响应
// 一个自定义消息和状态码的普通错误。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('帐号或密码错误');
使用 api.throttle
中间件结合 limit、expires
参数可实现接口次数限制。下面是定义在 routes/api.php
路由文件中的示例。
$api->version('v1', ['namespace' => '\App\Api'], function ($api) { $api->group(['middleware' => 'api.throttle', 'limit' => 2, 'expires' => 1], function ($api) { $api->get('user', 'UserController@all'); }); });
限制1分钟只能访问2次。
可以通过 api.auth
路由中间件来启用路由或者路由群组的保护,我们使用下面讲解的jwt组件完成接口验证。
在所有的路由上启用
$api->version('v1', ['middleware' => 'api.auth'], function ($api) { // 在这个版本群组下的所有路由将进行身份验证。});
特定的路由上启用
$api->version('v1', function ($api) { $api->get('user', ['middleware' => 'api.auth', function () { // 这个路由将进行身份验证。 }]); $api->get('posts', function () { // 这个路由不会验证身份。 }); });
控制器上进行身份验证
Laravel
可以在控制器里启用中间件。您可以在构造函数里使用 middleware
的方法。
class UserController extends Illuminate\Routing\Controller{ use Helpers; public function __construct() { $this->middleware('api.auth'); // 这个中间件只在 index 中启用 $this->middleware('api.auth', ['only' => ['index']]); } ...
Jwt是高效简单的接口验证组件,使用非常广泛。
GitHub:https://github.com/tymondesigns/jwt-auth
Packagist:https://packagist.org/packages/tymon/jwt-auth
在线文档: http://jwt-auth.readthedocs.io/en/develop/quick-start/
目前2.0版本正在开发中还不可以正常使用,所以我们使用 1.0.0-rc.2。
composer require tymon/jwt-auth:1.0.0-rc.2
生成配置文件
php artisan vendor:publish
生成密钥
这是用来给你的token签名的钥匙,使用以下命令生成一个密钥:
php artisan jwt:secret
这将用 JWT_SECRET=foobar
更新.env文件
JWT配置文件是 config/jwt.php
,下面有部分配置项进行说明:
#令牌过期时间(单位分钟),设置null为永不过期'ttl' => env('JWT_TTL', 60)#刷新令牌时间(单位分钟),设置为null可永久随时刷新'refresh_ttl' => env('JWT_REFRESH_TTL', 20160)
首先,您需要在用户模型上实现 Tymon\JWTAuth\Contracts\JWTSubject
契约,它要求您实现两个方法 getJWTIdentifier()
和 getJWTCustomClaims()
。
下面的示例应该能让您了解这可能是什么样子的。显然,您应该根据需要进行任何更改,以满足自己的需要。
getKey(); } /** * 返回一个键值数组,其中包含要添加到JWT的任何自定义声明. * * @return array */ public function getJWTCustomClaims() { return []; } }
修改 config/auth.php
文件以使用jwt保护来为接口身份验证提供支持。
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ]
修改dingo配置文件 config/api.php
文件中的身份验证提供者
'auth' => [ 'jwt' => \Dingo\Api\Auth\Provider\JWT::class, ],
路由定义
$api = app(\Dingo\Api\Routing\Router::class); $api->version('v1', ['namespace' => 'App\Http\Controllers\Api',], function ($api) { $api->post('login', 'AuthController@login'); $api->get('logout', 'AuthController@logout'); $api->get('me', 'AuthController@me'); });
控制器定义
class AuthController extends Controller{ public function __construct() { // 除login外都需要验证 $this->middleware('auth:api', ['except' => ['login']]); } //登录获取token public function login() { $credentials = request(['email', 'password']); if (!$token = auth('api')->attempt($credentials)) { return $this->response->errorUnauthorized('帐号或密码错误'); } return $this->respondWithToken($token); } //获取用户资料 public function me() { return response()->json(auth('api')->user()); } //销毁token public function logout() { auth('api')->logout(); return response()->json(['message' => 'Successfully logged out']); } //刷新token public function refresh() { return $this->respondWithToken(auth('api')->refresh()); } //响应token protected function respondWithToken($token) { return response()->json([ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth('api')->factory()->getTTL() * 60, ]); } }
当请求需要验证的api时必须带有token,下面是使用header头携带令牌数据
Authorization: Bearer 令牌数据
在postman 工具中可以使用以下方式简化操作