配置阿里源
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
创建项目
composer create-project --prefer-dist laravel/laravel=6.* haoranapi
ide工具
composer require barryvdh/laravel-ide-helper --dev
# config/app中添加 Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
# terminal运行
php artisan ide-helper:generate
debug
composer require barryvdh/laravel-debugbar --dev
# config/app中添加
Barryvdh\Debugbar\ServiceProvider::class,
解决跨域
composer require barryvdh/laravel-cors
protected $middleware = [
// ...
\Fruitcake\Cors\HandleCors::class,
];
php artisan vendor:publish --tag="cors"
安装dingo
"require": {
"dingo/api": "^2.2"
}
composer update
php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"
安装passport
composer require laravel/passport
数据迁移
php artisan migrate
生成安全访问令牌时所需的加密密钥
php artisan passport:install
# 上面命令执行后,请将 Laravel\Passport\HasApiTokens Trait 添加到 App\User 模型
# 接下来,在 AuthServiceProvider 的 boot 方法中调用 Passport::routes 函数
# 最后,将配置文件 config/auth.php 中授权看守器 guards 的 api 的 driver 选项改为 passport
config/api.php说明
#接口围绕:[x]本地和私有环境 [prs]公司内部app使用 [vnd]公开接口
'standardsTree' => env('API_STANDARDS_TREE', 'x')
#项目名称
'subtype' => env('API_SUBTYPE', 'laradmin')
#Api前缀 通过 www.yiqiesuifeng.cn/api 来访问 API。
'prefix' => env('API_PREFIX', 'api')
#api域名
'domain' => env('API_DOMAIN', 'api.yiqiesuifeng.com'),
#版本号
'version' => env('API_VERSION', 'v1')
#开发时开启DEBUG便于发现错误
'debug' => env('API_DEBUG', false)
### prefix 与 domain 只能二选一,一个需要设置为null
### 响应数据格式高级的格式配置需要发布配置文件到 config 目录或者在服务提供者(Laravel)
Dingo\Api\Http\Response::addFormatter('json', new Dingo\Api\Http\Response\Format\Jsonp);
### 响应转化器Dingo 默认使用 Fractal 作为响应转化器
'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class),
### 你可以在 .env 中配置,但如果要实现更加复杂的配置还是需要在服务提供者(Laravel)或启动文件(Lumen)中操作
$app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
$fractal = new League\Fractal\Manager;
$fractal->setSerializer(new League\Fractal\Serializer\JsonApiSerializer);
return new Dingo\Api\Transformer\Adapter\Fractal($fractal);
});
### 访问频率限制默认访问频率限制是被禁用的,你可以注册自定义的带有频率限制的节流器或者使用已经存在的认证/取消认证节流器。要进行更加复杂的配置,同样需要在服务提供者(Laravel)或启动文件(Lumen)中操作:
$app['Dingo\Api\Http\RateLimit\Handler']->extend(function ($app) {
return new Dingo\Api\Http\RateLimit\Throttle\Authenticated;
});
### 认证提供者Dingo 默认只开启了 basic 认证,如果要对认证进行更复杂的配置,需要在服务提供者(Laravel)或启动文件(Lumen)设置
$app['Dingo\Api\Auth\Auth']->extend('oauth', function ($app) {
return new Dingo\Api\Auth\Provider\JWT($app['Tymon\JWTAuth\JWTAuth']);
});
.env设置示例
#dingoapi
# 标准树 x 表示未注册树,主要用于本地或私有环境 prs 表示个人树,主要用于非商业性质的项目 vnd 表示供应商树,主要用于公开的以及商业性质的项目
API_STANDARDS_TREE=vnd
# 子类型 通常是应用或项目的简称
API_SUBTYPE=haoranapi
# 前缀和子域名
API_PREFIX=api
#API_DOMAIN=api.myapp.com
# 名字 使用 Dingo 提供的 API Blueprint 命令生成文档
API_NAME="docs_api"
# 带条件的请求 由于缓存 API 请求的时候会使用客户端缓存功能,所以默认开启了带条件的请求
API_CONDITIONAL_REQUEST=false
# 版本 API 的默认版本
API_VERSION=v1
# 调试模式
API_DEBUG=true
# Strict 模式 Strict 模式要求客户端发送 Accept 请求头而不是默认在配置文件中指定的版本,这意味着你不能通过 Web 浏览器访问 API
API_STRICT=false
# 响应数据格式
API_DEFAULT_FORMAT=json
使用
$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
#Accept: application/x.laradmin.v2+json
$api->version('v2', function ($api) {
$api->get('version', function () {
return 'v2';
});
});
$api->version('v1', function ($api) {
$api->group(['middleware' => 'foo'], function ($api) {
// Endpoints registered here will have the "foo" middleware applied.
});
});
命名路由 & 生成 URL
$api->get('test', function(){
return $url = app(\Dingo\Api\Routing\UrlGenerator::class)
->version('v1')
->route('login', ['id' => 1]);
});
## http://haoranapi.com/api/login?id=1
User模型 通过给定的username获取用户实例
public function findForPassport($username){
return $this->where('name', $username)->first();
}
创建基类控制器 响应构建器
php artisan make:controller ApiController
# 引入trait
use Helpers;
创建passport控制器
php artisan make:controller Api\PassportController
序列化器
Fractal 默认支持 ArraySerializer、DataArraySerializer、JsonApiSerializer 三种序列化器
创建转化器
# app/Transformers
<?php
namespace App\Transformers;
use App\Models\Housing;
use League\Fractal\TransformerAbstract;
class HousingTransformer extends TransformerAbstract
{
public function transform(Housing $housing)
{
return [
'id' => $housing->id,
'number' => $housing->number,
'acreage' => $housing->acreage,
'rent' => $housing->rent,
'deposit' => $housing->deposit,
'pay_num' => $housing->pay_num,
'pledge_num' => $housing->pledge_num,
'ele_num' => $housing->ele_num,
'water_num' => $housing->water_num,
'remark' => $housing->remark,
'created_at' => $housing->created_at,
'updated_at' => $housing->updated_at,
];
}
//访问关联数据,请求路由http://haoranapi.com/api/zuke?include=renters,housings
//$defaultIncludes 所有此转换器自动加上关联
// protected $defaultIncludes = ['housings','renters'];
protected $availableIncludes = ['housings','renters'];
public function includeHousings(Bill $bill)
{
return $this->item($bill->housings, new HousingTransformer());
}
public function includeRenters(Bill $bill)
{
return $this->item($bill->renters, new RenterTransformer());
}
}
单个资源响应
public function show($id)
{
$detail = Housing::find($id);
return $this->response->item($detail, HousingTransformer::class);
}
资源集合响应
public function index()
{
$housings = Housing::all();
return $this->response->collection($housings, HousingTransformer::class);
}
分页响应
public function index()
{
$housings = Housing::paginate();
return $this->response->paginator($housings, HousingTransformer::class);
}
使用游标
public function index()
{
$current = Request('current');
$previous = Request('previous');
$limit = Request('limit') ? : 10;
if ($current) {
$housings = Housing::where('id', '>', $current)->take($limit)->get();
} else {
$housings = Housing::take($limit)->get();
}
$next = $housings->last()->id;
$cursor = new Cursor($current, $previous, $next, $housings->count());
return $this->response->collection($housings, new HousingTransformer, [], function ($resource, $fractal) use ($cursor) {
$resource->setCursor($cursor);
});
}
添加额外的响应头
return $this->response->item($task, new TaskTransformer)->withHeader('Foo', 'Bar');
return $this->response->item($task, new TaskTransformer())->withHeaders([
'Foo' => 'Bar',
'Hello' => 'World'
]);
# 添加 Cookie
$cookie = new \Symfony\Component\HttpFoundation\Cookie('foo', 'bar');
return $this->response->item($task, new TaskTransformer())->withCookie($cookie);
设置响应状态码
return $this->response->item($task, new TaskTransformer)->setStatusCode(200);
添加元数据
return $this->response->item($task, new TaskTransformer)->addMeta('foo', 'bar');
资源键
return $this->item($task, new TaskTransformer, ['key' => 'task']);
使用回调
// 引入命名空间
use League\Fractal\Serializer\JsonApiSerializer;
$task = Task::findOrFail($id);
return $this->response->item($task, new TaskTransformer(), ['key' => 'task'], function ($resource, $fractal) {
$fractal->setSerializer(new JsonApiSerializer());
});
Morphing 和 Morphed 事件 Dingo 提供的 ResponseIsMorphing(转化前触发)和 ResponseWasMorphed(转化后触发)事件。
# 我们可以在 app/Listeners 目录下为上述事件创建监听器
php artisan make:listener AddPaginationLinksToResponse
# 编辑 app/Listeners/AddPaginationLinksToResponse.php
namespace App\Listeners;
use Dingo\Api\Event\ResponseWasMorphed;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class AddPaginationLinksToResponse
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle(ResponseWasMorphed $event)
{
if (isset($event->content['meta']['pagination'])) {
$links = $event->content['meta']['pagination']['links'];
$next = isset($links['next']) ? $links['next'] : null;
$previous = isset($links['previous']) ? $links['previous'] : null;
$event->response->headers->set(
'link',
sprintf('<%s>; rel="next", <%s>; rel="prev"', $next, $previous)
);
}
}
}
# 然后通过在 EventServiceProvider 中注册事件及其对应监听器之间的映射关系来监听该事件
use App\Listeners\AddPaginationLinksToResponse;
use Dingo\Api\Event\ResponseWasMorphed;
protected $listen = [
...
ResponseWasMorphed::class => [
AddPaginationLinksToResponse::class
]
];
错误及异常处理
异常 状态码
Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException 403
Symfony\Component\HttpKernel\Exception\BadRequestHttpException 400
Symfony\Component\HttpKernel\Exception\ConflictHttpException 409
Symfony\Component\HttpKernel\Exception\GoneHttpException 410
Symfony\Component\HttpKernel\Exception\HttpException 500
Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException 411
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException 405
Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException 406
Symfony\Component\HttpKernel\Exception\NotFoundHttpException 404
Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException 412
Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException 428
Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException 503
Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException 429
Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException 401
Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException 415
# 资源异常
Dingo\Api\Exception\DeleteResourceFailedException
Dingo\Api\Exception\ResourceException
Dingo\Api\Exception\StoreResourceFailedException
Dingo\Api\Exception\UpdateResourceFailedException
# 自定义 HTTP 异常
除了 Dingo 内置支持的异常类之外,你可以创建自定义的 HTTP 异常,前提是它们继承自Symfony\Component\HttpKernel\Exception\HttpException 基类或者实现了Symfony\Component\HttpKernel\Exception\HttpExceptionInterface 接口。
表单请求类
如果你要使用表单请求类,那么需要继承 Dingo API 表单请求基类 Dingo\Api\Http\FormRequest 或者完全自己实现。表单请求类会检查输入请求是否是针对当前 API 的,如果是的话,当验证失败会抛出 Dingo\Api\Exception\ValidationHttpException 异常。这个异常会被 Dingo API 渲染并返回相应的错误响应。
应用内部请求 Dingo API
在 API 路由中请求 API
$api->version('v3', function ($api) {
...
$api->get('task/{id}', function ($id) {
$dispatcher = app(\Dingo\Api\Dispatcher::class);
$task = $dispatcher->version('v1')->get('dingoapi/task/' . $id);
dd($task);
});
});
在 Web 路由中请求 API
$dispatcher = app(\Dingo\Api\Dispatcher::class);
Route::get('/task/{id}', function ($id) use ($dispatcher) {
$task = $dispatcher->version('v3')->get('dingoapi/task/' . $id);
dd($task);
});
在已认证 API 路由中请求
在已认证的 API 路由中请求 Dingo API 的认证接口比较简单,不需要做任何额外的操作,因为已经认证的 API 接口中发起内部请求中会自动带上 Authorization 字段。
在未认证或 Web 路由中请求
在未认证 API 路由或 Web 路由中对 Dingo API 认证接口发起内部请求需要将访问令牌设置到请求头 Authorization 字段中才可以,我们以之前待办任务列表中的 home 路由为例,这是一个 Web 路由,虽然需要认证才能访问,但是 Web 路由认证和 API 路由认证走的是两套体系,之间并不相通,内部请求 的 API 接口需要单独认证,这里我们借助 Passport 私人访问令牌来实现该 API 接口的认证,打开 app/Http/Controllers/HomeController.php,将 index 方法中获取任务列表的逻辑改为基于 Dingo API v3 版本的 tasks.index 接口获取,该接口是需要认证的,我们通过 Passport 私人访问令牌生成访问令牌,然后将其设置到 Authorization 请求头
public function index(Request $request, Dispatcher $dispatcher)
{
$params = [];
if ($request->has('page')) {
$params['page'] = $request->get('page');
}
if ($request->has('limit')) {
$params['limit'] = $request->get('limit');
}
$token = $request->user()->createToken('Internal Request Token')->accessToken;
$tasks = $dispatcher->on('todo.test')->header('Authorization', 'Bearer ' . $token)->version('v3')->get('dingoapi/tasks', $params);
return view('home', ['tasks' => json_encode($tasks->items())]);
}
发起 POST 请求
$dispatcher->with(['name' => '学院君', 'location' => '杭州'])->post('users');
$dispatcher->post('users', ['name' => '学院君', 'location' => '杭州']);
上传文件
$dispatcher->attach(Input::files())->post('photos');
$dispatcher->attach(['photo' => 'photos/me.jpg'])->post('photos');
$dispatcher->attach([
'photo' => [
'path' => 'photos/me.jpg',
'mime' => 'image/jpeg',
'size' => '49430'
]
])->post('photos');
发送 JSON 格式请求数据
$data = ['name' => 'xueyuanjun', 'password' => '12345678'];
$dispatcher->json($data)->post('users');
获取原生响应对象
$response = $dispatcher->raw()->get('users');
异常及错误处理
try {
app(\Dingo\Api\Dispatcher::class)->with($payload)->post('dingoapi/tasks');
} catch (Symfony\Component\HttpKernel\Exception\ConflictHttpException $e) {
// Do something here, like return with an error.
}
生成 API 文档
# Resource
/**
* Task Resource Controller
* @package App\Http\Controllers\Api
* @Resource("Tasks")
*/
@Resource("Tasks", uri="/tasks")
# Action
/**
* Display a listing of the resource.
* @param Request $request
* @return \Illuminate\Http\Response
* @GET("/{?page,limit}"
*/
/**
* Store a newly created resource in storage.
*
* @param CreateTaskRequest $request
* @return \Illuminate\Http\Response
* @POST("/")
*/
# Version
/**
* Display a listing of the resource.
* @param Request $request
* @return \Illuminate\Http\Response
* @GET("/{?page,limit}"
* @Versions({"v3"})
*/
# Parameter
/**
* Display a listing of the resource.
* @param Request $request
* @return \Illuminate\Http\Response
* @GET("/{?page,limit}")
* @Versions({"v3"})
* @Parameters({
* @Parameter("page", description="page number", type="integer", required=false, default=1),
* @Parameter("limit", description="task item number per page", type="integer", required=false, default=10)
* })
*/
# Request
/**
* Store a newly created resource in storage.
*
* @param CreateTaskRequest $request
* @return \Illuminate\Http\Response
* @POST("/")
* @Versions({"v3"})
* @Request("text=test&is_completed=1", contentType="application/x-www-form-urlencoded")
*/
@Request("text={text}&is_completed={is_completed}", contentType="application/x-www-form-urlencoded", attributes={
@Attribute("text", type="string", required=true, description="the body of task", sample="test task"),
@Attribute("is_completed", type="boolean", required=true, description="task is completed or not", sample=1)
})
@Request({"text":"test task", "is_completed":1}, attributes={
@Attribute("text", type="string", required=true, description="the body of task", sample="test task"),
@Attribute("is_completed", type="boolean", required=true, description="task is completed or not", sample=1)
})
@Request({"text":"test task", "is_completed":0}, headers={
"Authorization": "Bearer {API Access Token}"
}, attributes={
@Attribute("text", type="string", required=true, description="the body of task", sample="test task"),
@Attribute("is_completed", type="boolean", required=false, description="task is completed or not", sample=0)
})
# Response
/**
* Store a newly created resource in storage.
*
* @param CreateTaskRequest $request
* @return \Illuminate\Http\Response
* @POST("/")
* @Versions({"v3"})
* @Request({"text":"test task", "is_completed":0}, headers={
* "Authorization": "Bearer {API Access Token}"
* }, attributes={
* @Attribute("text", type="string", required=true, description="the body of task", sample="test task"),
* @Attribute("is_completed", type="boolean", required=true, description="task is completed or not", sample=0)
* })
* @Response(200, body={"data":{"id":4,"text":"Test Task 4","completed":"no","link":"http://todo.test/dingoapi/task/4"}})
*/
@Response(200, body={"data":{"id":1,"text":"Test Task 1","completed":"no","link":"http://todo.test/dingoapi/task/1"}}, attributes={
@Attribute("id", type="integer", description="the id of task", sample=1),
@Attribute("text", type="string", description="the body of task", sample="test task"),
@Attribute("completed", type="string", description="task is completed or not", sample="no"),
@Attribute("link", type="string", description="task link", sample="http://todo.test/dingoapi/task/1")
})
# Transaction
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
* @POST("/{id}")
* @Versions({"v3"})
* @Parameters({
* @Parameter("id", type="integer", description="the ID of the task", required=true)
* })
* @Transaction({
* @Request({"text":"test task", "is_completed":1}, headers={
* "Authorization": "Bearer {API Access Token}"
* }, attributes={
* @Attribute("text", type="string", required=true, description="the body of task", sample="test task"),
* @Attribute("is_completed", type="boolean", required=true, description="task is completed or not", sample=1)
* }),
* @Response(200, body={"data":{"id":1,"text":"Test Task 1","completed":"no","link":"http://todo.test/dingoapi/task/1"}}, attributes={
* @Attribute("id", type="integer", description="the id of task", sample=1),
* @Attribute("text", type="string", description="the body of task", sample="test task"),
* @Attribute("completed", type="string", description="task is completed or not", sample="no"),
* @Attribute("link", type="string", description="task link", sample="http://todo.test/dingoapi/task/1")
* }),
* @Response(404, body={"message":"404 not found", "status_code": 404})
* })
*/
生成 API 文档
php artisan api:docs --name TodoApp --output-file apidocs.md
参考文档
https://xueyuanjun.com/post/19686
https://learnku.com/docs/dingo-api/2.0.0/Errors-And-Error-Responses/1447
https://learnku.com/docs/laravel/6.x/passport/5152#defining-scopes
标签:task,return,dingo,Dingo,api,API,passport,Api
From: https://www.cnblogs.com/fuqian/p/17163968.html