基于laravel使用dingo/api + jwt开发api接口扫盲篇

/ 0

这里就不去叙说dingo/api的牛逼之处了,要是不知道你也不会看这篇文章。

集成dingo/api

1.安装扩展包

命令行cd到laravel项目安装目录,执行下面命令安装dingo/api扩展包:

composer require dingo/api:1.0.x@dev

还可以直接修改composer.json文件中的require字段,增加下面键值对:

"dingo/api": "1.0.*@dev"

并在laravel项目安装目录下执行下面命令:

composer update

2.注册服务提供者

config/app.php注册到 providers 数组:

'providers' => [
    //...
    Dingo\Api\Provider\LaravelServiceProvider::class,
]

3.生成微调配置文件

如果需要对dingo/api配置进行微调,可以执行下面命令,生成配置文件到config/api.php

php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"

4.配置

.env配置文件中,设置dingo/api相关配置:

API_STANDARDS_TREE=vnd // 环境
API_SUBTYPE=myapp // 子类型
API_PREFIX=api // 前缀
API_DOMAIN=api.myapp.com //子域名  (前缀和子域名只能存在一个)
API_VERSION=v1 // 版本
API_NAME=My API // 名字(使用API Blueprint命令才会用到)
API_CONDITIONAL_REQUEST=false // 带条件的请求
API_STRICT=false // Strict模式
API_DEFAULT_FORMAT=json // 响应格式
API_DEBUG=true // 调试模式

至此,dingo/api集成就完成了,dingo/api的使用,将在后面慢慢列出。

集成jwt

jwt即JSON Web Token的缩写,是一种api身份认证的方式。相比session,session是基于cookie的,而app则不方便处理cookie。

1.安装扩展包

命令行cd到laravel项目安装目录,执行下面命令安装jwt扩展包:

composer require tymon/jwt-auth 0.5.x

还可以直接修改composer.json文件中的require字段,增加下面键值对:

"tymon/jwt-auth": "0.5.*"

并在laravel项目安装目录下执行下面命令:

composer update

2.注册服务提供者

和dingo/api一样,在config/app.php注册到 providers 数组:

'providers' => [
    //...
    Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
]

3.注册门面

config/app.php中注册门面,也是也就是取别名而已:

'aliases' => [
    // ...
    'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
	
    'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
]

4.生成微调配置文件

和dingo/api一样,如果需要对jwt配置进行微调,可以执行下面命令,生成配置文件到config/jwt.php

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

5.生成秘钥

执行下面命令,会在jwt.php文件中数组的secret键成对应的值:

php artisan jwt:generate

6.配置

jwt.php配置详解如下:

User —— providers.user:基于sub获取用户的实现

JWT —— providers.jwt:加密/解密token

Authentication —— providers.auth:通过证书/ID获取认证用户

Storage —— providers.storage:存储token直到它们失效

至此,jwt集成就完成了。

dingo/api路由配置

路由在dingo/api中被称为Endpoint,便于区分web路由和api路由,也方便api版本和其他访问限制管理。

开发接口和开发web也一样,首先在app/Http/routes.php中配置Endpoint,下面来看一段简单的Endpoint配置:

// 接管路由
$api = app('Dingo\Api\Routing\Router');

// 配置api版本和路由
$api->version('v1', ['namespace' => 'App\Http\Api\V1\Controllers'], function ($api) {

    // 授权组
    $api->group(['prefix' => 'auth'], function ($api) {
        $api->post('register', 'AuthenticateController@register')->name('auth.register');
    });
});

和web路由组配置类似,除了可以在version中配置版本号以外,也可以在后面一个参数中配置路由组的命名空间、前缀、中间件等,并且支持resources路由、嵌套路由,和路由别名。

根据上面的配置,命名空间在App\Http\Api\V1\Controllers下,这样做是为了和web逻辑分离,并且便于api版本管理。

dingo/api响应

下面来看一下auth.register路由对应控制器的方法实现:

public function register(Request $request)
{
    $rules = [
        'name' => ['required'],
        'phone' => ['required', 'min:11', 'max:11', 'unique:users'],
        'password' => ['required', 'min:6', 'max:16'],
        'key' => ['required', 'min:6'], // 手机验证码
    ];

    $payload = $request->only('name', 'phone', 'password', 'key');
    $validator = Validator::make($payload, $rules);

    // 验证手机验证码
    if (Cache::has($payload['phone'])) {
        $key = Cache::get($payload['key']);
        if ($key != $payload['key']) {
            return $this->response->array(['error' => '验证码错误']);
        }
    } else {
        return $this->response->array(['error' => '验证码错误']);
    }
    
    // 验证格式
    if ($validator->fails()) {
        return $this->response->array(['error' => $validator->errors()]);
    }

    // 创建用户
    $result = Users::create([
        'name' => $payload['name'],
        'phone' => $payload['phone'],
        'password' => bcrypt($payload['password']),
    ]);

    if ($result) {
        return $this->response->array(['success' => '创建用户成功']);
    } else {
        return $this->response->array(['error' => '创建用户失败']);
    }

}

和web端开发比较可以很容易发现有一个不同点,return的不再是一个view,而是使用$this->response属性的array方法来创建的一个响应结果。也就是接收到一个客户端的请求并返回响应给客户端。

这里的response属性,其实是dingo/api提供的,需要在控制器里引入Dingo\Api\Routing\Helpers命名空间。为了让所有控制器都可以使用,我们可以自定义一个控制器继承系统的Controller控制器,并在我们自定义的控制器里引入命名空间,我们后续创建的控制器都继承自我们自定义的这一个基控制器:

namespace App\Http\Api\V1\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Dingo\Api\Routing\Helpers;
use App\Http\Requests;

class BaseController extends Controller
{
    use Helpers;
}

这样,BaseController的所有子控制器都能使用了。

上面的register例子中,都是响应数组,其实也就是直接响应了json。

return $this->response->array([]);

除了能响应数组,dingo/api还提供了其他便捷的响应方式。比如单个item响应、集合响应、分页响应、无内容响应、创建响应和错误响应,并且可以给响应添加元数据、状态码,这些功能具体请看dingo/api官方文档。

dingo/api转化器

有些时候我们需要返回用户数据,在web端开发我们可以直接传递一个对象,但在api开发中则是需要把对象转化为标准的json格式响应给客户端。其实我们也可以自己根据对象的属性拼接一个数组,并响应给客户端,但是dingo/api给我们提供了更便捷的方式,也就是转化器(Transformer)。

自定义转化器需要继承TransformerAbstract类,并至少实现transform方法。例如下面是一个User模型的转化:

namespace App\Http\Api\V1\Transformers;

use App\Http\Api\V1\Model\User;
use Illuminate\Http\Request;
use App\Http\Requests;
use League\Fractal\TransformerAbstract;

class UserTransformer extends TransformerAbstract
{
    public function transform(User $user)
    {
        return [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
            'phone' => $user->phone,
        ];
    }
}

transform将User对象转成了一个数组返回。

再来看看如何使用自定义的转化器:

namespace App\Http\Api\V1\Controllers;

use App\Http\Api\V1\Model\User;
use App\Http\Api\V1\Transformers\UserTransformer;
use Illuminate\Http\Request;
use App\Http\Requests;

class UserController extends BaseController
{
    public function getUserInfo($id)
    {
        $user = User::findOrFail($id);
        return $this->response->item($user, new UserTransformer);
    }
}

这里是响应单个item,传入需要转化的模型和转化器对象即可。

然后我们配置如下路由:

// 接管路由
$api = app('Dingo\Api\Routing\Router');

// 配置api版本和路由
$api->version('v1', ['namespace' => 'App\Http\Api\V1\Controllers'], function ($api) {
    $api->get('users/{id}', 'UserController@getUserInfo')->name('getUserInfo');
});

然后请求下面的接口:

yourdomains/api/users/1

得到响应结果:

{
    "data": {
        "id": 1,
        "name": "admin",
        "email": "admin@6ag.cn",
        "phone": "13666655555"
    }
}

jwt的简单使用

jwt在api的应用一般是用于验证用户的登录有效性,比如在登录的时候,返回给用户一个token值,这个token值包含在有效期内用户才可以进行操作,token失效后,登录也就失效。

下面例子中就是在登录成功后生成一个token,关于token的有效期和一些其他配置,在config/jwt.php中可以进行设置。

public function authenticate(Request $request)
{
    $payload = $request->only('phone', 'password');

    try {
        if (! $token = JWTAuth::attempt($payload)) {
            return $this->response->array(['error' => 'token已经失效']);
        } else {
            return $this->response->array(['token' => $token]);
        }
    } catch (JWTException $e) {
        return $this->response->array(['error' => '不能创建token']);
    }

}

刷新token

public function updateToken()
{
    $token = JWTAuth::refresh();
    return $this->response->array(['token' => $token]);
}

一般我的写法是在登录验证成功后生成token字符串、token有效期和用户信息一起返回给客户端,这个具体看自己的心情吧。至此,dingo/api + jwt 开发api简单流程也就介绍完了,我本人接触laravel也不久,如有不对的地方,还望大神指正。