高效掌握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 工具中可以使用以下方式简化操作
