使用 Dingo API 快速构建 RESTful API

# 安装
$ composer require dingo/api:3.0.x@dev

# 1.在 api.php 中编写接口
# 2.在响应构建器中使用转化器构建JSON 响应

# 1.
$api = app(\Dingo\Api\Routing\Router::class);  # 获取对应的 API 路由器实例
$api->version('v1', function ($api) {
    //
}); # 版本分组
# .env中:
API_SUBTYPE=  # 子类型,默认为空
API_PREFIX=dingoapi # 配置路由前缀
API_DOMAIN=api.myapp.com # 子域名,和前缀二选一进行配置
API_VERSION=v1 # 版本
API_NAME= # 名字
API_CONDITIONAL_REQUEST=false # 带条件的请求
API_STRICT=false # Strict 模式
# 完整的 Accept 字段值格式
# Accept: application/API_STANDARDS_TREE.API_SUBTYPE.API_VERSION+json

# 2.
# app\Http\Controller 下创建新的 API 基类控制器
php artisan make:controller ApiController
# 内容:
class ApiController extends Controller
{
    use Helpers;
}
# 定义一个继承自该控制器的子控制器
php artisan make:controller Api/TaskController --resource

# 返回 JSON  响应示例:
$task = Task::findOrFail($id);
        return $this->response->array($task->toArray());
# API  路由示例:
$api->version('v3', function ($api) {
    $api->resource('tasks', \App\Http\Controllers\Api\TaskController::class);
});
# 三种序列化器
    $fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer());
    $fractal->setSerializer(new \League\Fractal\Serializer\DataArraySerializer()); # 默认
    $fractal->setSerializer(new \League\Fractal\Serializer\JsonApiSerializer());
# 转化器
# 新建 app/Transformers/TaskTransformer 转化器,初始化代码:
namespace App\Transformers;
use App\Task;
use League\Fractal\TransformerAbstract;
class TaskTransformer extends TransformerAbstract
{
    public function transform(Task $task)
    {
        return [
            'id' => $task->id,
            'text' => $task->text,
            'completed' => $task->is_completed ? 'yes' : 'no',
            'link' => route('tasks.show', ['id' => $task->id])
        ];
    }
}
# 单个资源:
$task = Task::findOrFail($id);
return $this->response->item($task, new TaskTransformer());
# 资源集合:
$tasks = Task::all();
return $this->response->collection($tasks, new TaskTransformer());

# 添加额外的响应头
return $this->response->item($task, new TaskTransformer)->withHeader('Foo', 'Bar'); 
# 添加 cookie, 必须要是实例
$cookie = new \Symfony\Component\HttpFoundation\Cookie('foo', 'bar');
...->withCookie($cookie);
# 设置响应状态码
...->setStatusCode(200);
# 添加元数据
...->addMeta('foo', 'bar');

API 认证
HTTP基本认证

# 1.注册驱动,在 AuthServiceProvider 的 boot 方法中注册基本认证
# 2.在相应的 API 路由中认证中间件
# 3.访问 (添加Basic Auth / 不添加)
# 这里的认证信息是首页注册时的原邮箱和原密码

# 1.
use Dingo\Api\Auth\Auth;
use Dingo\Api\Auth\Provider\Basic;

public function boot()
{
    // Dingo 认证驱动注册
    $this->app->make(Auth::class)->extend('basic', function ($app) {
        return new Basic($app['auth'], 'email');
    });
}
# 2.
$api->version('v3', ['middleware' => 'api.auth'], function ($api) {
    $api->resource('tasks', \App\Http\Controllers\Api\TaskController::class);
});
# or,在指定路由上应用该中间件
$api->version('v3', function ($api) {
    $api->resource('tasks', \App\Http\Controllers\Api\TaskController::class, [
        'middleware' => 'api.auth'
    ]);
});

JWT认证

# 准备工作:
$ composer require tymon/jwt-auth
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
$ php artisan jwt:secret
# 让用于认证的 User 模型类实现 JWTSubject 接口
class User extends Authenticatable implements JWTSubject {
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }
    public function getJWTCustomClaims()
    {
        return [];
    }
} 

# 1.注册驱动,可以在 config/api.php 中注册对应的认证驱动,也可以和 HTTP 一样
# 2.获取 JWT 令牌
# 3.基于 JWT 认证访问 API

# 1. config/api/php:
'auth' => [
    'jwt' => \Dingo\Api\Auth\Provider\JWT::class,
],
# 2.
$api->version('v3', function ($api) {
    $api->post('user/auth', function () {
        $credentials = app('request')->only('email', 'password');
        try {
            if (! $token = \Tymon\JWTAuth\Facades\JWTAuth::attempt($credentials)) {
                throw new \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException('Invalid credentials');
            }
        } catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
            throw new \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException('Create token failed');
        }
        return compact('token');
    });
}); # 访问即可获取到 Token 值
# 3.代码保持和 HTTP 基本认证一样,设置认证类型为 Bearer Token,写入获取到的 Token 值

OAuth 2.0 认证
1.安装配置 Passport

$ composer require laravel/passport # 安装
# php artisan migrate # 生成用于存放客户端和访问令牌的数据表
$ php artisan passport:install # 创建生成安全访问令牌(token)所需的加密键
# 添加 Laravel\Passport\HasApiTokens trait 到 App\User 模型
namespace App;

use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}
# 在 AuthServiceProvider 的 boot 方法中调用 Passport::routes 方法
class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    public function boot()
    {
        $this->registerPolicies();
        Passport::routes();
    }
}
# 修改配置文件 config/auth.php
 'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],

2.设置认证中间件

# Api\TaskController.php:
public function __construct()
{
    $this->middleware('auth:api');
}

3.通过密码授权令牌访问认证 API

# 1.获取CLIENT_ID和CLIENT_SECRET,配置到 .env 中
# 2.在 routes/api.php 中定义一个用于获取授权令牌的路由
# 3.将获取到的access_token(认证 API 时的令牌)设置到类型为 Bearer 的 Authorization 字段,请求 tasks.index 路由

# 1.
$ php artisan passport:client --password
# 2.
$api->version('v3', function ($api) {
    $api->post('user/token', function () {
        app('request')->validate([
            'email' => 'required|string',
            'password' => 'required|string',
        ]);

        $http = new \GuzzleHttp\Client();
        // 发送相关字段到后端应用获取授权令牌
        $response = $http->post(route('passport.token'), [
            'form_params' => [
                'grant_type' => 'password',
                'client_id' => env('CLIENT_ID'),
                'client_secret' => env('CLIENT_SECRET'),
                'username' => app('request')->input('email'),  // 这里传递的是邮箱
                'password' => app('request')->input('password'), // 传递密码信息
                'scope' => '*'
            ],
        ]);

        return response()->json($response->getBody()->getContents());
    });
    $api->resource('tasks', \App\Http\Controllers\Api\TaskController::class);
});

php 内置的 http server 是 block 的,一次只能处理一个请求,这里将项目部署到wamp中。

访问频率限制
通过 Laravel 自带的节流中间件 - throttle

'throttle:60,1', # 默认1 分钟内可以对应用该中间件的路由发起 60 次请求

$api->post('user/token', ['middleware' => 'throttle:3,1', function () {
    //
}]); # 通过传递参数到该中间件来手动设置时间范围和请求次数

# 响应头中通过 X-RateLimit-* 字段获取频率限制相关数值:
X-RateLimit-Limit: 3
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1557801344

通过 Dingo 实现的节流中间件

$api->post('user/token', ['middleware' => 'api.throttle', 'limit' => 3, 'expires' => 1, function () {
    //
}

自定义节流器

# 创建自定义节流器 app/Throttles/CustomThrottle.php:
namespace App\Throttles;

use Dingo\Api\Contract\Http\RateLimit\HasRateLimiter;
use Dingo\Api\Http\RateLimit\Throttle\Throttle;
use Dingo\Api\Http\Request;
use Illuminate\Container\Container;

class CustomThrottle extends Throttle implements HasRateLimiter
{
    protected $options = ['limit' => 5, 'expires' => 1];

  
    public function match(Container $container)
    {
        return ! $container['api.auth']->check();
    }

    // 通过域名+IP识别客户端
    public function getRateLimiter(Container $app, Request $request)
    {
        return $request->route()->getDomain() . '|' . $request->getClientIp();
    }
}
# 在 routes/api.php 中通过 throttle 参数指定 user/token 路由使用该自定义节流器
$api->post('user/token', ['middleware' => 'api.throttle', 'throttle' => 'App\Throttles\CustomThrottle', function () {
    ...
}]);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344