使用 Laravel 开发 API,参考了网上一些资料,laravel 默认已经支持了 API 开发,但是因为都没有了解过,打算尝试使用 Dingo API
github 地址:
https://github.com/dingo/api
laravel-china 中文文档:
https://laravel-china.org/docs/dingo-api/2.0.0
文档写的非常详细,翻译的也非常好,但是需要仔细品读,另外 Dingo API 中使用到了其他两个技术,也需要了解下。之前已经写过笔记了,可参考:
fractal - https://blog.csdn.net/beyond__devil/article/details/83870176
jwt-auth - https://blog.csdn.net/beyond__devil/article/details/83827075
这里简单挑几个重点写下:
1.安装
composer require dingo/api
发布配置
php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"
Facades
Dingo\Api\Facade\API
Dingo\Api\Facade\Route
2.配置信息
3.创建端点(在 API 中,路由一般叫做端点)
1)引用 Dingo API
$api = app('Dingo\Api\Routing\Router');
2)设置版本分组
// 单个分组
$api->version('v1', function($api){
})
// 多个分组
$api->version(['v1', 'v2'], function($api){
})
// 第二个参数,还可以传递一个 『属性数组』
$api->version(['v1', 'v2'], ['middleware' => 'foo'], function($api){
})
注意:
属性数组,是旧版的 Laravel,5.7 版本有些可能不支持
// 其他的和 laravel 的路由没有太大的区分(旧版,新版链式调用不支持)
$api->version('v1', function($api){
$api->group(['middleware' => 'bar'], function($api){
...
});
})
3)创建端点(之前大概看了点源码,忘记了,下面是之前笔记的)
group() - 路由分组
domain() - 子域名路由
namespace() - 命名空间
prefix() - 前缀
name() - 命名前缀,是 as 的别名
as() - 命名前缀
middle() - 中间件
where() - 参数的正则约束
/*
5.7 版本,可以使用这些方法,之前的版本,没有这些方法,使用的是:
group(['prefix' => '', 'namespace' => '', ...], function(){
});
*/
4)命名路由 & 生成路由
$api->get('users/{id}', ['as' => 'users.index', 'uses' => 'Api\V1\UserController@show']);
app('Dingo\Api\Routing\UrlGenerator')->version('v1')->route('users.index');
5)命令行中查看路由
php artisan api::routes
注意:
Laravel 5.7版本,routes/api.php 默认就具备中间件,我们需考虑使用 'Dingo API' 还是 'Laravel 系统默认'
app/Providers/RouteServiceProvider.php
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
注意:
如果采用 Ding API,我们可能得取消这里默认的 prefix、middleware、namespace
4.响应
响应生成器,需要使用 'Dingo\Api\Routing\Helpers' trait。我们的 API 控制器基本都要使用它,所以创建一个 『基类控制器』
php artisan make:controller Api/Controller
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Dingo\Api\Routing\Helpers;
use App\Http\Controllers\Controller as BaseController;
class Controller extends BaseController
{
use Helpers;
}
关于响应生成器,文档写的很详细,上次可能看了下源码,总结了下,基本和文档一致。
参考源码:Dingo\Api\Http\Response\Factory
1)响应数组
$user = User::findOrFail($id);
$this->response->array($user->toArray()); // 估计只能是数组
// 查看源码:
// array 时,会响应 $parameters[0]
// 『new Response($parameters[0])』
2)响应一个元素
$user = User::findOrFail($id);
$this->response->item($user, new UserTransformer)
3)响应一个元素集合
$users = User::all();
$this->response->collection($user, new UserTransformer)
4)分页响应
$users = User::paginate(25);
$this->response->paginator($user, new UserTransformer)
5)无内容响应
$this->response->noContent();
6)创建了资源的响应
$this->response->created($location = null, $content = null);
location - 会在 header 添加 Location 头部
content - 响应内容
7)accepted() - 同 created() 一样,created() 返回 201,accepted() 返回 202
8)自定义错误内容和状态码
error($message, $statusCode)
9)其他调用 error() 的便捷方法
errorNotFound() - 404
errorBadRequest() - 400
errorForbidden() - 403
errorInternal() - 500
errorUnauthorized() - 401
errorMethodNotAllowed() - 405
10)添加额外的头信息
->withHeader('X-Foo', 'Bar')
11)添加 Meta 信息
->addMeta('foo', 'bar')->addMeta(xx, yy)
->setMeta($meta)
12)设置响应状态码
->setStatusCode(200)
事件:
返回响应之前,会转换响应,如果响应的变化有更多的控制,可以使用以下2个事件
ResponseWasMorphed
ResponseIsMorphing
5.异常处理
API 的开发过程中,对于异常,我们采用直接 throw,然后统一由 Dingo API 来捕获,统一处理
Symfony 内置的异常
Dingo API 内置的资源异常
自定义异常
继承 Symfony\Component\HttpKernel\Exception\HttpException
或
实现 Symfony\Component\HttpKernel\Exception\HttpExceptionInterface
自定义异常响应
app('Dingo\Api\Exception\Handler')->register(function (Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException $exception) {
return Response::make(['error' => 'Hey, 你这是要干嘛!?'], 401);
});
表单请求
定义基础的 API 表单请求类,验证失败,将抛出:
Dingo\Api\Exception\ValidationHttpException
Dingo API 默认的错误格式
'errorFormat' => [
'message' => ':message',
'errors' => ':errors',
'code' => ':code',
'status_code' => ':status_code',
'debug' => ':debug',
],
6.Transformers
使用 transformers 有 2 种方法:
1>为一个指定的类注册一个 transformer,直接从路由中返回这个类,并且通过 transformer 自动运行
app('Dingo\Api\Transformer\Factory')->register('User', 'UserTransformer');
2>使用响应生成器
Fractal
Fractal 是 Dingo API 使用的默认转换层
自动关系预加载
更多配置
定义嵌入关系时,要自定义 'include' 关键词和 '分隔符',在 'service provider' 或 '启动文件' 中,手动实例化 'Dingo\Api\Transformer\Adapter\Fractal'
$this->app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
// 这里是 'include' 和 ','
return new Dingo\Api\Transformer\Adapter\Fractal(new League\Fractal\Manager, 'include', ',');
});
响应生成器的高级用法
响应生成器的 item()、collection() 和 paginator() 方法,接受额外参数,进一步自定义 Fractal
第三个参数 - 资源键
return $this->item($user, new UserTransformer, ['key' => 'user']);
第四个参数 - 回调函数
return $this->collection($users, new UserTransformer, [], function ($resource, $fractal) {
$resource->setCursor($cursor);
});
回调函数有 2 个参数:
第一个参数:
League\Fractal\Resource\Item 或 League\Fractal\Resource\Collection 实例
第二个参数:
League\Fractal\Manager 实例
不使用 '第三个参数',也可以省略,直接传递 '第四个参数' 回调函数
自定义转换层
7.API 认证
配置身份验证提供者
内置 3 个身份验证提供者:
HTTP Basic - Dingo\Api\Auth\Provider\Basic
JSON Web Tokens - Dingo\Api\Auth\Provider\JWT
OAuth 2.0 - Dingo\Api\Auth\Provider\OAuth2
默认仅在配置文件中,启用 HTTP 基本身份验证
HTTP 基本验证
服务提供者使用 laravel 内置的默认基本身份验证。需要在 '配置文件' 或 '引导文件' 配置此服务提供者
app('Dingo\Api\Auth\Auth')->extend('basic', function ($app) {
return new Dingo\Api\Auth\Provider\Basic($app['auth'], 'email');
});
JSON Web Tokens(JWT)
使用 tymon/jwt-auth 包
在 '配置文件' 或 '引导文件' 中配置该服务提供者
config.php
'auth' => [
'jwt' => 'Dingo\Api\Auth\Provider\JWT',
],
app('Dingo\Api\Auth\Auth')->extend('jwt', function ($app) {
return new Dingo\Api\Auth\Provider\JWT($app['Tymon\JWTAuth\JWTAuth']);
});
OAuth 2.0
使用 league/oauth2-server 或 lucadegasperi/oauth2-server-laravel 包
需要配置 'provider server - 服务提供者' 或 'bootstrap - 引导文件'(包没有给我们提供 服务提供者,而 basic 和 jwt 都有默认的服务提供者,应该是这样...)
同时,应该还需要配置 config.php 的 auth
自定义身份验证
保护级别
可以使用 'api.auth' 中间件来验证
// 某个版本路由
$api->version(['middleware' => 'api.auth'], function(){});
// 路由组
$api->group( ['middleware' => 'api.auth'], function(){});
// 单个路由
$api->get('user', ['middleware' => 'api.auth'], function(){});
控制器中
$this->middleware('api.auth', ['only' => ['index']]);
只允许特定的身份验证提供者
$api->version(['middleware' => 'api.auth', 'providers' => ['basic', 'oauth']], function(){});
检索经过身份验证的用户
$user = app('Dingo\Api\Auth\Auth')->user();
如果控制器中,使用了 'Dingo\Api\Routing\Helpers',可以使用 $this->auth 属性
use Dingo\Api\Routing\Helpers; // 引入 trait
class UserController extends Controller
{
use Helpers; // 使用 trait
public function index()
{
$user = $this->auth->user();
return $user;
}
}
不通过中间件来验证,需要手动在需要的地方进行验证
if( !app('Dingo\Api\Auth\Auth')->user()){
return '用户未验证!';
}
8.访问节流限制(rate limiting)
改变节流限速的 key
默认是 "客户端IP",通过 $request->getClientIp() 获取。
我们可以自定义:
app('Dingo\Api\Http\RateLimit\Handler')->setRateLimiter(function ($app, $request) {
return $app['example']->getRateLimiterKey();
});
回调函数的 2 个参数是:
$app - Ioc 容器
$request - 请求实例
启用节流限速
使用 'api.throttle' 中间件
支持,额外的 2 个选项:
'middleware' => 'api.throttle', 'limit' => 100, 'expires' => 5
limit - 限制次数
expires - 过期时间
自定义阀门(throttle)
继承 Dingo\Api\Contract\Http\RateLimit\Throttle,定义一个 match() 方法,返回 true | false
use Illuminate\Container\Container;
use Dingo\Api\Http\RateLimit\Throttle\Throttle;
class CustomThrottle extends Throttle
{
public function match(Container $app)
{
// 在这里执行一些逻辑并根据是否返回 true 或 false
// 你的条件与阀门匹配。
}
}
注册阀门
api.php
'throttling' => [
'custom' => new CustomThrottle(['limit' => 200, 'expires' => 10])
]
9.内部调用
API 都是外部调用的,但是如果内部某个方法内,也想调用 API 的结果,这样可复用代码
// 构建一个分发器
$dispatcher = app('Dingo\Api\Dispatcher');
// 发起内部请求
$users = $dispatcher->get('api/users');
控制器中,也可使用 'Dingo\Api\Routing\Helpers' trait
use Dingo\Api\Routing\Helpers; // 引用 trait
class HomeController extends Controller
{
use Helpers; // 使用 trait
public function index()
{
$users = $this->api->get('users'); // 调用内部请求
return view('index')->with('users', $users);
}
}
发送数据
$dispatcher->with(['name' => 'Jason', 'location' => 'Australia'])->post('users');
或
$dispatcher->post('users', ['name' => 'Jason', 'location' => 'Australia']);
指定版本的 API
$dispatcher->version('v2')->get('users');
指定的域名
$dispatcher->on('api.example.com')->get('users');
上传附件
1>Symfony\Component\HttpFoundation\File\UploadedFile 实例数组
$dispatcher->attach(Input::files())->post('photos');
2>文件路径数组,数组 key 是文件的下标
$dispatcher->attach(['photo' => 'photos/me.jpg'])->post('photos');
3>文件路径相关的元数据数组(上传时,不会再计算文件 mime 类型和文件大小)
dispatcher->attach([
'photo' => [
'path' => 'photos/me.jpg',
'mime' => 'image/jpeg',
'size' => '49430'
]
])->post('photos');
发送 JSON 数据
$data = ['name' => 'bill', 'password' => 12345];
$dispatcher->json($data)->post('users');
注意:
如果 $data 是一个数组,将会自动被转换为 json。
这个请求的 Content-Type 被设置为 application/json
模拟认证用户
当我们请求的端点(路由),需要用户认证通过后,才可进行处理,模拟用户
任何后续请求都将视为同一用户
$dispatcher->be(auth()->user())->get('posts');
只为给定的用户模拟一次请求
$dispatcher->be(auth()->user())->once()->get('posts');
获取原始返回对象
// 默认的 API 请求,返回的数据是经过预先转换、格式化
$response = $dispatcher->get('users');
// 要想获取原始查询的数据,添加 raw()
$response = $dispatcher->raw()->get('users');
异常处理
内部调用的 API,会抛出异常,我们如果想处理异常,需要自己捕获。
捕获的异常类需要和抛出的异常类一致
10.OAuth 2.0 认证
Scopes
11.请求你的 API
使用 Postman
使用 CURL
curl -v -H "Accept: application/vnd.YOUR_SUBTYPE.v1+json" http://example.app/users
12.API 文档
资源
/**
* 用户资源标识
*
* @Resource("Users", uri="/users")
*/
行为
@Get、@Post、@Put、@Patch、@Delete
@Versions
@Request
@Response
@Transaction
@Parameters
13.命令行工具
1>生成 API 路由列表
php artisan api:routes
php artisan api:routes --versions v1
php artisan api:routes --scopes read_user_data --scopes write_user_data
2>缓存 API 路由,同时缓存主路由。
php artisan api:cache
切记:
不要执行 laravel 的 route:cache,这样只会缓存主路由,不会缓存 Dingo API 的路由
3>根据标准注释,生成 API 文档
php artisan api:docs
php artisan api:docs --name Example --use-version v2 --output-file /path/to/documentation.md
php artisan api:docs --name Example --use-version v2 > /path/to/documentation.md // 或使用 '>' 标准输出重定向