Laravel5.5+passport 放弃 dingo 开发 API 实战,让 API 开发更省心

Laravel5.5更新,通过Laravel5.5开发Api更加顺畅了,在这里就分享一下Laravel开发Api的经验吧

1.封装返回的统一消息

返回的自定义消息,和错误消息,我自己封装了一个Trait,用来做基本的返回,Trait的封装如下

namespace App\Api\Helpers\Api;
use Symfony\Component\HttpFoundation\Response as FoundationResponse;
use Response;

trait ApiResponse
{
    /**
     * @var int
     */
    protected $statusCode = FoundationResponse::HTTP_OK;

    /**
     * @return mixed
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }

    /**
     * @param $statusCode
     * @return $this
     */
    public function setStatusCode($statusCode)
    {

        $this->statusCode = $statusCode;
        return $this;
    }

    /**
     * @param $data
     * @param array $header
     * @return mixed
     */
    public function respond($data, $header = [])
    {

        return Response::json($data,$this->getStatusCode(),$header);
    }

    /**
     * @param $status
     * @param array $data
     * @param null $code
     * @return mixed
     */
    public function status($status, array $data, $code = null){

        if ($code){
            $this->setStatusCode($code);
        }

        $status = [
            'status' => $status,
            'code' => $this->statusCode
        ];

        $data = array_merge($status,$data);
        return $this->respond($data);

    }

    /**
     * @param $message
     * @param int $code
     * @param string $status
     * @return mixed
     */
    public function failed($message, $code = FoundationResponse::HTTP_BAD_REQUEST, $status = 'error'){

        return $this->setStatusCode($code)->message($message,$status);
    }

    /**
     * @param $message
     * @param string $status
     * @return mixed
     */
    public function message($message, $status = "success"){

        return $this->status($status,[
            'message' => $message
        ]);
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function internalError($message = "Internal Error!"){

        return $this->failed($message,FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function created($message = "created")
    {
        return $this->setStatusCode(FoundationResponse::HTTP_CREATED)
            ->message($message);

    }

    /**
     * @param $data
     * @param string $status
     * @return mixed
     */
    public function success($data, $status = "success"){

        return $this->status($status,compact('data'));
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function notFond($message = 'Not Fond!')
    {
        return $this->failed($message,Foundationresponse::HTTP_NOT_FOUND);
    }

}

然后创建一个ApiController,通过所有的Api控制器继承该控制器,实现简洁的Api返回

<?php

namespace App\Http\Controllers\Api;

use App\Api\Helpers\Api\ApiResponse;
use App\Http\Controllers\Controller;

class ApiController extends Controller
{

    use ApiResponse;

    // 其他通用的Api帮助函数

}

然后,Api控制器就可以简洁的返回

<?php

namespace App\Http\Controllers\Api;

class IndexController extends ApiController
{
    public function index(){

        return $this->message('请求成功');
    }
}

2.资源类型的返回

资源返回通过5.5的新特性,API资源实现,具体参见
https://learnku.com/docs/laravel/5.5/eloquent-resources
使用方面比之前的Transformer方式更好用更优雅
比如返回用户的分页数据,只需要这样
文档已经很详细了,这里不再做概述

<?php

namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Http\Resources\User as UserCollection;
use Illuminate\Support\Facades\Input;

class IndexController extends ApiController
{
    public function index(){

        return UserCollection::collection(User::paginate(Input::get('limit') ?: 20));

    }
}

3. Api授权模块

这里直接抛弃之前的jwt转向passport的认证方式,之前论坛已经有相关认证的帖子了
这里通过重写AuthenticatesUsers通过password的的授权模式模式进行实现

<?php
namespace App\Http\Controllers\Api;

use Carbon\Carbon;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use Laravel\Passport\Client;
use Socialite;

use App\Models\User;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Validator;

class AuthenticateController extends ApiController
{

    use AuthenticatesUsers;

    public function __construct()
    {
        $this->middleware('auth:api')->only([
            'logout'
        ]);
    }

    public function username()
    {
        return 'phone';
    }

    // 登录
    public function login(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'phone'    => 'required|exists:user',
            'password' => 'required|between:5,32',
        ]);

        if ($validator->fails()) {
            $request->request->add([
                'errors' => $validator->errors()->toArray(),
                'code' => 401,
            ]);
            return $this->sendFailedLoginResponse($request);
        }

        $credentials = $this->credentials($request);

        if ($this->guard('api')->attempt($credentials, $request->has('remember'))) {
            return $this->sendLoginResponse($request);
        }

        return $this->setStatusCode(401)->failed('登录失败');
    }

    // 退出登录
    public function logout(Request $request)
    {

        if (Auth::guard('api')->check()){

            Auth::guard('api')->user()->token()->revoke();

        }

        return $this->message('退出登录成功');

    }

    // 第三方登录
    public function redirectToProvider($driver) {

        if (!in_array($driver,['qq','wechat'])){

            throw new NotFoundHttpException;
        }

        return Socialite::driver($driver)->redirect();
    }

    // 第三方登录回调
    public function handleProviderCallback($driver) {

        $user = Socialite::driver($driver)->user();

        $openId = $user->id;

     // 第三方认证
        $db_user = User::where('xxx',$openId)->first();

        if (empty($db_user)){

            $db_user = User::forceCreate([
                'phone' => '',
                'xxUnionId' => $openId,
                'nickname' => $user->nickname,
                'head' => $user->avatar,
            ]);

        }

        // 直接创建token

        $token = $db_user->createToken($openId)->accessToken;

        return $this->success(compact('token'));

    }

    //调用认证接口获取授权码
    protected function authenticateClient(Request $request)
    {
        $credentials = $this->credentials($request);

            // 个人感觉通过.env配置太复杂,直接从数据库查更方便
        $password_client = Client::query()->where('password_client',1)->latest()->first();

        $request->request->add([
            'grant_type' => 'password',
            'client_id' => $password_client->id,
            'client_secret' => $password_client->secret,
            'username' => $credentials['phone'],
            'password' => $credentials['password'],
            'scope' => ''
        ]);

        $proxy = Request::create(
            'oauth/token',
            'POST'
        );

        $response = \Route::dispatch($proxy);

        return $response;
    }

    protected function authenticated(Request $request)
    {
        return $this->authenticateClient($request);
    }

    protected function sendLoginResponse(Request $request)
    {
        $this->clearLoginAttempts($request);

        return $this->authenticated($request);
    }

    protected function sendFailedLoginResponse(Request $request)
    {
        $msg = $request['errors'];
        $code = $request['code'];
        return $this->setStatusCode($code)->failed($msg);
    }
}

4.自定义返回异常

这里我的做法是直接拦截App\Exceptions\Handlerrender方法,实现自定义返回

<?php

namespace App\Exceptions;

use App\Api\Helpers\Api\ExceptionReport;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    /**
     *  其他代码...
     */

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $exception
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $exception)
    {
        // 将方法拦截到自己的ExceptionReport
        $reporter = ExceptionReport::make($exception);

        if ($reporter->shouldReturn()){
            return $reporter->report();
        }

        return parent::render($request, $exception);
    }
}

然后在该方法实现自定义返回

<?php

namespace App\Api\Helpers\Api;

use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;

class ExceptionReport
{
    use ApiResponse;

    /**
     * @var Exception
     */
    public $exception;
    /**
     * @var Request
     */
    public $request;

    /**
     * @var
     */
    protected $report;

    /**
     * ExceptionReport constructor.
     * @param Request $request
     * @param Exception $exception
     */
    function __construct(Request $request, Exception $exception)
    {
        $this->request = $request;
        $this->exception = $exception;
    }

    /**
     * @var array
     */
    public $doReport = [
        AuthenticationException::class => ['未授权',401],
        ModelNotFoundException::class => ['改模型未找到',404]
    ];

    /**
     * @return bool
     */
    public function shouldReturn(){

        if (! ($this->request->wantsJson() || $this->request->ajax())){
            return false;
        }

        foreach (array_keys($this->doReport) as $report){

            if ($this->exception instanceof $report){

                $this->report = $report;
                return true;
            }
        }

        return false;

    }

    /**
     * @param Exception $e
     * @return static
     */
    public static function make(Exception $e){

        return new static(\request(),$e);
    }

    /**
     * @return mixed
     */
    public function report(){

        $message = $this->doReport[$this->report];

        return $this->failed($message[0],$message[1]);

    }

}

好啦,所有的基础模块都构建完了,现在就可以开发你需要的业务逻辑啦~

个人博客地址:https://www.timenotes.me/articles/code/11
欢迎大佬参观~~~

本帖由 Summer 于 1年前 加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 147

赞。最近也在考虑放弃 Dingo,做了些定制后维护起来太麻烦了。
原生的一套兼容等各方面还是比较好。

1年前

@HyanCat 是呀,之前的项目就是用Dingo开发的,后台5.5的Api资源的确让人眼前一亮,新开的项目就直接用5.5重构了

1年前

请问一下,passport使用密码模式登录,在登录验证的时候报这个错误是怎么回事啊:

BadMethodCallException
Method attempt does not exist.

控制器代码是这样的:

if (!Auth::guard('api')->attempt(['account' => $account, 'password' => $password])) {
    return response(['message' => '用户名或者密码错误'], 403);
}

auth.php配置是这样的:

'api' => [
        'driver' => 'passport',
        'provider' => 'users',
 ],
'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
 ],

模型是这样的:

namespace App\Models;

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

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
1年前

@justinstar Auth::guard('api')拿到的是RequestGuard对象,没有attempt方法呀,试试Auth::attempt(xxx)

1年前

@王举 这样怎么来指定auth.php里面的api呢,

'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'admin_users',
    ],

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],
1年前

@justinstar 做法是use AuthenticatesUsers;使用这个Trait,然后通过$this->guard('api')->attempt([])的方式,这个会获取SessionGuard的实例,调用里面的attempt方法,passport中的RequestGuard默认没有实现attempt方法,或者你重写一个Guard都可以

1年前

@王举 好了,非常感谢:smile:

1年前

想问一下,@王举 api 参数校验这一块,你是如何封装并的呢?laravel提供的校验类好像都是针对form表单提交过来的数据校验。

1年前

@Thank 前面已经提过可以拦截请求过程的Exception,(4.自定义返回异常)[https://learnku.com/articles/6035/laravel55-developing-api-combat#4自定义返回异常],其实表单验证也是可以拦截的呀
比如,在Api控制器我们用表单验证

public function index(Request $request){

        $this->validate($request,[

            'test1' => 'required',
            'test2' => 'min:5'

        ]);
    }

然后在我们的拦截器里面拦截Illuminate\Validation\ValidationException异常

/**
     * @var array
     */
    public $doReport = [
        AuthenticationException::class => ['未授权',401],
        ModelNotFoundException::class => ['改模型未找到',404],
        ValidationException::class => []
    ];

然后就可以直接拦截到之后就可以自定义返回了

/**
     * @return mixed
     */
    public function report(){

        if ($this->exception instanceof ValidationException){

            return $this->failed($this->exception->errors());

        }
        $message = $this->doReport[$this->report];

        return $this->failed($message[0],$message[1]);

    }

效果:
file

1年前

学习了

1年前

@王举 好,我先按你的思路进行测试。谢谢。

1年前

ApiResponse 里的failed 方法有问题,调用这个方法如果传递 status code 参数的话,status 这个方法就会用400 覆盖掉 setStatusCode 设置的状态吗!

比如 notFound 方法 返回的 status code 是400

另外个人觉得 status 这个方法有悖于 RESTfull API 的设计规范,进行无意义的包装……我之前也是这么做的,但是用 axios 调用接口时,要 response.data.data,这样比较郁闷!

1年前

$user->createToken();这个方法创建的是Personal Token 有效期一年,有没有什么方法创建 普通的 AccessToken

1年前

@Jinrenjie
多谢指点,已更新。
我觉得status的包装是有必要的,在js你可能觉得这样麻烦,但是api不仅仅用在web,IOS安卓都是要使用的。特别是移动端要根据状态去判断请求的结果。或者拿JSON转换成相应的对象去展示。这样里面包装的data就很有必要了。而且response.data.data的两个data又不冲突,第二个data是可能有可能没的,要根据请求状态判断
Personal Token的有效期可以设置的,具体可以参考这个 https://github.com/laravel/passport/issues/162
感觉这样的话应该能满足你的需求

1年前

@王举 移动端我没接触过,难道不能根据 HTTP 状态码判断吗?
还有个问题,就是关于 POST、PUT和 PATCH 者三种操作如果成功的话都是返回 201状态码吗?有人说是 PUT和 PATCH 是200,也有人说是201 但是规范上好像说201 是创建新资源对象才返回的状态码!

感谢分享,从你的这篇文章里学到了不少东西:smile: :smile: :smile:

1年前
franktrue

请教个问题,在passport密码授权模式下使用手机号密码登陆获取access_token。当接入APP端第三方登陆。这时候如何生成access_token呢?
因为使用第三方授权登陆的时候是没有手机号password等信息的。access_token好像只能请求/oauth/token获取,没有像jwt那样获取$user,然后调用fromUser($user)就可以生成token.

1年前

@fnf1993 这个只能创建 Personal Token,根据手机号码获取用户模型,$user->createToken('name_string');

1年前

@fnf1993 第三方授权的话我选用这个
然后这样就可以创建Token了

$token = $db_user->createToken($openId)->accessToken;

这种个人令牌默认有效期是一年
你可以进行修改,参考:https://github.com/laravel/passport/issues/162
使用文档地址:
https://learnku.com/docs/laravel/5.5/passport#管理个人访问令牌

1年前
franktrue

@Jinrenjie thx
我去试试~

1年前
franktrue

@王举 thx
我去看看,非常感谢

1年前
franktrue

@王举 还有个问题
把第三方登陆的标识信息(openid微信、uid微博等存在下图name的位置),那如何区分不同平台呢?
file

我现在的想法是加个前缀eg.

  1. 微信
$db_user->createToken('wechat'.$openId)->accessToken;

2.

1年前
franktrue

@王举

2.微博

$db_user->createToken('weibo'.$uid)->accessToken;

这样可以嘛?

1年前

@fnf1993 可以的,创建token的名字随意

1年前
medz

用了半年的 dingo,控制器,依赖注入都被废了。最后放弃了。

1年前

@medz 歇了好久没看,到底 passport 和 dingo/api 有啥区别啊,我还是5.2的时候用过dingo

1年前
medz

@kylesean 你理解错 Passport 和 dingo/api 了,Passport 是 Laravel 官方提供的 oAuth 2.0 实现包。dingo/api 是一个标准 REST ful API 开发拓展框架。

1年前

@medz 哦哦,3Q,可是 dingo/api 不也封装了 oauth 吗? passport 更有优势么?

1年前
medz

@kylesean passport 只是单纯封装 oauth。

1年前
class Controller extends BaseController {
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    //json格式输出
    public function outPutJson($data, $code = 200, $message = NULL) {
        $message = $message ?? config('code')[$code];
        return json_encode(['code' => $code, 'message' => $message, 'data' => $data], JSON_UNESCAPED_UNICODE);
    }
}

我直接这样的,在config/code.php里面写所有的错误码和对应的信息。然后调用

return $this->outPutJson($merge_data);
1年前

@欧阳逸 也是可以的,这个自己感觉怎么省事怎么来

1年前

@王举 大哥,我按照你的方法来,请求登录接口的时候

(1/1) BadMethodCallException

Method attempt does not exist.
引用的和你一样的,只是我继承的是Controller,我的Controller代码如下:


<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController {
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

//json格式输出
public function outPutJson($data, $code = 200, $message = NULL) {
    $message = $message ?? config('code')[$code];
    return json_encode(['code' => $code, 'message' => $message, 'data' => $data], JSON_UNESCAPED_UNICODE);
}

}

我的D:\phpStudy\WWW\api.douxiaoli.com\app\Http\Controllers\AuthenticateController.php登录代码如下
// 登录
public function login(Request $request) {
    $validator = Validator::make($request->all(), [
        'name' => 'required|string|max:255',
        'password' => 'required|string|min:6',
    ]);

    if ($validator->fails()) {
        return $this->outPutJson('', 402, $validator->errors()->all());
    }

    $credentials = $this->credentials($request);

    if ($this->guard('api')->attempt($credentials, $request->has('remember'))) {
        return $this->sendLoginResponse($request);
    }

    return $this->outPutJson('', 401);
}

请问为什么会这样呢?
1年前

@王举 login的时候$this->guard('api')->attempt($credentials, $request->has('remember')),我这个拿到的也是RequestGard,我没看到你在AuthenticatesUsers这里面写attempt方法啊,我在这里卡住了,能够指导下吗?多谢了

1年前

@王举 试过Auth::attempt(),也不行啊。

1年前

@欧阳逸 如果你用的是tymon/jwt-auth的包的话,升级到1.0.0-rc.1还要在User 模型里重写一个方法,具体的我也忘记了,你可以参照源码,因为这些在官方文档里都没有得到更新……

1年前

@Jinrenjie 我直接用的官方自带的passport,没用第三方包

1年前

@欧阳逸 Passport 没有 实现Guard的类,他的$this->guard('api')不是passport 的验证方式……

1年前

@王举 移动端android ios什么的用http状态码一点问题都没有,实践过多次,如果不是为了RPC接口没必要,而且更方便。 一直没有包装http code在json里,完全没必要!最多就是有一些业务细节的code代码

1年前

@ThinkWorld 其实不用也是完全可以的,这个是参考其他api的返回,比如dingo的错误也会返回如下

{
    "message": "User was updated prior to your request.",
    "status_code": 409
}

说不上用不用的好坏,主要是和前端人员沟通好才是最重要的,ios是可以判断,但是打印的json结果如果不带code的话默认是看不到状态码的,要单独在response中拿code,个人感觉还是带了方便一些。

1年前

@王举 那就有备无患吧。

1年前

有个bug,当我使用$this->success(new UserCollection(User::paginate()))返回分页资源时,返回的json数据中没有 meta 和 links 键包含的分页状态信息

1年前

@ganlanshu0211 resources资源直接返回

return new UserCollection(User::find(1));

或者自定义

return $this->success([
 'name' => 'zhangsan',
 'say' => 'hello'
]);
1年前

@王举 这样不就不能再json数据里面返回status了,想全部做成一样的格式的

1年前

@ganlanshu0211 给你个思路,就是重写路由的Api Response 返回
目前没有发现很简单的方法,思路是重写Json\Resourcewarp方法
直接贴代码了,你自己研究一下

<?php

namespace App\Http\Responses;

use Illuminate\Http\Resources\Json\Resource as BaseResource;

class Resource extends BaseResource
{

    public static function collection($resource)
    {

        $resource = new class($resource, get_called_class()) extends ResourceCollection {
            /**
             * @var string
             */
            public $collects;

            /**
             * Create a new anonymous resource collection.
             *
             * @param  mixed  $resource
             * @param  string  $collects
             * @return void
             */
            public function __construct($resource, $collects)
            {
                $this->collects = $collects;

                parent::__construct($resource);
            }
        };

        return $resource;

    }

    public function toResponse($request)
    {
        return (new ResourceResponse($this))->toResponse($request);
    }

}

App\Http\Responses\ResourceCollection

<?php

namespace App\Http\Responses;

use IteratorAggregate;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Http\Resources\CollectsResources;

class ResourceCollection extends Resource implements IteratorAggregate
{
    use CollectsResources;

    /**
     * The resource that this resource collects.
     *
     * @var string
     */
    public $collects;

    /**
     * The mapped collection instance.
     *
     * @var \Illuminate\Support\Collection
     */
    public $collection;

    /**
     * Create a new resource instance.
     *
     * @param  mixed  $resource
     * @return void
     */
    public function __construct($resource)
    {

        parent::__construct($resource);

        $this->resource = $this->collectResource($resource);
    }

    /**
     * Transform the resource into a JSON array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return $this->collection->map->toArray($request)->all();
    }

    /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function toResponse($request)
    {
        return $this->resource instanceof AbstractPaginator
            ? (new PaginatedResourceResponse($this))->toResponse($request)
            : parent::toResponse($request);
    }

}

App\Http\Responses\PaginatedResourceResponse

<?php

namespace App\Http\Responses;
use Illuminate\Http\Resources\Json\PaginatedResourceResponse as BasePaginatedResourceResponse;

class PaginatedResourceResponse extends BasePaginatedResourceResponse
{

    protected function wrap($data, $with = [], $additional = [])
    {

        $meta = [
            "status"=> "success",
            "code" => $this->calculateStatus(),
        ];
        $parent = parent::wrap($data, $with, $additional);
        return array_merge_recursive($meta,$parent);
    }
}

App\Http\Responses\ResourceResponse

<?php
namespace App\Http\Responses;
use Illuminate\Http\Resources\Json\ResourceResponse as BaseResourceResponse;

class ResourceResponse extends BaseResourceResponse
{
    protected function wrap($data, $with = [], $additional = [])
    {
        $meta = [
            "status"=> "success",
            "code" => $this->calculateStatus(),
        ];
        $parent = parent::wrap($data, $with, $additional);
        return array_merge_recursive($meta,$parent);

    }

}

然后所有的Resource都继承自己的App\Http\Responses\Resource
当然,meta是我随便定义的,你可以自由定义
example

<?php

namespace App\Http\Resources;

//use Illuminate\Http\Resources\Json\Resource;

use App\Http\Responses\Resource;

class User extends Resource
{

    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request
     * @return array
     */
    public function toArray($request)
    {

        return [
            'nickname' => $this->nickname,
            'id' => $this->id,
            'head' => $this->head,
            'signature' => $this->signature,

        ];

    }
}

然后所有的资源既可以带你加的meta了

1年前

ios工程师初学laravel,特别感谢!

1年前
Ali

用了资源类型的返回,就不知道封装的这个Trait,怎么使用了.

1年前
Ali

@Ali 是不是错误的返回类型,可以使用封装的这个

1年前

@Ali 错误的消息返回,以及特殊的返回格式,用户提示等

1年前
Ali

@Thank 问您一下,你实验成功了吗.我一直不成功,不走验证这一步.

1年前

@Ali 嗯,可能你代码还有问题吧

1年前
Ali

@王举 也可能是我不知道 怎么调用封装的这些方法吧.

1年前

为什么这个 第三方回调方法里你 return 了东西。。。。你返回给谁了。懵逼

1年前

@王举 我的理解是,这个是第三方比如qq重定向过来的吧。如果你在这个回调方法return success方法, 浏览器页面显示是你返回的 json?

1年前

@geekzwb 授权成功会会掉到这里的,这些你都可以参考第三方授权的接口文档,说的很清楚

1年前

@王举 我不明白的是,这个返回是发送到 前端的? 对应前端哪个请求? 可是这个回调并不是前端发起的呀,而是qq

1年前

@geekzwb 前端请求你的地址 -> 你跳转到第三方授权的地址 -> 授权成功回调你设置的回调地址 -> 处理登录信息

1年前

@王举 前端请求服务端地址,这里的请求是指的跳转还是ajax请求?

1年前
dc

我创建了一个login的表单请求,

1年前
dc

我创建了一个login的表单请求后,就突然开始提示 unsupported_grant_type,感觉八竿子打不着的俩东西,有点懵逼,能帮我解下惑吗?

1年前

@Ali 成功了,但没整理成文档。

1年前

我想在lumen使用jwt 按照文档进行总是各种报错
这是其中的一处错误
lumen版本5.5 jwt0.5
(1/1) ErrorException
Non-static method Tymon\JWTAuth\JWTAuth::attempt() should not be called statically
有没有相关的安装文档 非常感谢

1年前

眼前一亮

1年前

@王举 $status = "success" 返回参数中的"status"起到什么作用?,成功失败不是根据code来的吗?code=200表示成功?

1年前

@torome 这个用来表示用户的请求状态是否成功,觉得多余可以直接去掉的,没啥关系

1年前

大佬 我想问下 如果是单页面应用 在passport密码授权模式下 获取到token 保存在哪里 ?如果别人获取到这个token 是不是对用户的安全方面有一些问题

1年前

@墨韵留香 spa的话,直接将token用js保存到localStorge就好了,在 App.vue created的时候拿到它,判断用户的登录状态,如果用户特意拿的话可能会请求到你的相应的接口,如果考虑token安全问题的话,可以考虑token的失效周期或者用户的地理位置检测方面考虑

1年前

@王举 嗯嗯 好的 谢谢 我试试用ip 进行判断下

1年前

@ganlanshu0211
@王举
关于分页的我想了个办法解决,在ApiResponse中增加一个方法来组合数据

/**
     * 含有分页的资源返回方法
     *
     * @param AnonymousResourceCollection $collection
     * @param string $status
     * @param null $code
     * @return mixed
     */
    public function respondForPaginate(AnonymousResourceCollection $collection, $status = 'success', $code = null)
    {
        if ($code) {
            $this->setStatusCode($code);
        }

        $status = [
            'status' => $status,
            'code' => $this->statusCode
        ];

        return $collection->additional($status);
    }
1年前

mark 顺便赞一下

1年前

我很无解释~还是没有弄懂怎么设置个人令牌的时间
已经新建了ServiceProvider.php
之后不知道怎么应用....

1年前

按你的代都贴去了,对于我这个新手来说,实在不知如何入手啊,能不能给个完整的测试实例啊,

1年前
'guards' => [
    'admin'=>[
        'driver'=>'session',
        'provider'=>'admin_users',
    ],
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'passport',// 'driver' => 'token',
        'provider' => 'api_users',// 'provider' => 'users',
    ],
], 

'providers' => [
'admin_users'=>[
'driver'=>'eloquent',
'model' =>App\Models\AdminUser::class,
],
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'api_users' => [
'driver' => 'eloquent',
'model' => App\Models\ApiUser::class,
],

    // 'users' => [
    //     'driver' => 'database',
    //     'table' => 'users',
    // ],
],
1年前

//if ($this->guard('api')->attempt($credentials,$request->has('remember'))) {
// return $this->sendLoginResponse($request);
//}
这个代码 总是出错

1年前

原来只支持默认 User模型

1年前

大神 想请教一下 ,在你的第三方登录的两个方法中 redirectToProviderhandleProviderCallback 这两个方法的路由是写在 api.php 中 还是 web.php 中呢?

1年前

@dptms 属于web的

1年前

@王举 那如果让 laravel 纯粹写 api 第三方登录有什么好的实践办法吗?

1年前

@dptms 文档是最好的实践方法啊

1年前

看了你的帖子,心里很纠结究竟要不要用dingo,个人也觉得dingo提供的一些我用到的功能,实现方式并不复杂,用上dingo反而显得累赘,担心后期维护起来会很麻烦.顺便提个问题,如果不使用dingo,如何实现api接口的版本控制呢?

1年前

@山海王子 个人建议彻底抛弃dingo,后端项目复杂的时候真的用的难受,抛弃Fractal的Transformer,Api资源无论灵活性还是封装都是完全超过Fractal的,以后用的难受了再迁移,为啥不提前直接用新特性呢,dingo自带了版本控制,直接看dingo的文档就知道啦

1年前

重要的事要大一点

老兄,你能在写全一点吗?毕竟有些人接触API比较少。看不大懂?

1年前

请问, 如果根据客户端传过来的参数, 动态查找关联模型的数据, 用 Api 资源要怎么实现 ?

1年前

研究了一下, 知道怎么玩了~ :)

1年前

@王举

file

手机号和密码是对的 出现图片的那写 这是什么问题啊 帮帮忙 :disappointed_relieved:

1年前

@木拉地力 缺少username 字段

1年前

@木拉地力 重写AuthenticatesUsers的trait的username 方法为你自己的字段,用passport的话,在user模型实现findForPassport方法

1年前

@王举

file

username字段是我用tel字段为用户名的所有使用用户名的地方我都改为tel了

还是一样

1年前

@木拉地力 你的错误是请求非法,缺少了一个必选参数,你按照报错好好找一下,自己debug很容易找到的

1年前

@王举

file

我刚才是把上面的username改为tel了所以报刚才那个错误
这个username的值是传过来的密码 可是我数据库字段里的字段是phone(我把tel该phone了)这样对吗
现在改回来了报下面这个图的错误

file

1年前

@木拉地力 左边的username的key不能改,修改的不应该是你右边的$credentials['xxx']么,你传的username是tel的话应该就是$credentials['tel'],和左边对应起来

1年前

@王举 现在对应起来了报这样的错误
{
"error": "invalid_credentials",
"message": "The user credentials were incorrect."
}

1年前
saybye720

@木拉地力 上面说过要自己实现 『在 User 模型实现 findForPassport 方法』

对应你的 User 模型应该是这样的,你试试吧。

    public function findForPassport($username)
    {
        return self::where(’tel', $username)->first();
    }
1年前

异常无法处理 ,跳去登录页了

1年前

@Kerlin 非常感谢你提供的分页思路,真的非常感谢

1年前

@王举 个人觉得 封装的这个ExceptionReport 意思不大呢

在封装用中 $this->exception typeof 异常类型 和 在本身的Handler 中判断是一样的

另外更想知道api resource 用Response::json 返回会丢失 meta,links数据等数据而直接return 就不会的原因,不知你有没有见解

10个月前

@王举 谢谢

10个月前

根据这篇文章,自定义了一个HelperFunction

<?php

use Symfony\Component\HttpFoundation\Response;
use Illuminate\Pagination\LengthAwarePaginator;

if (! function_exists('stored')) {
    /**
     * 创建资源成功后响应
     *
     * Date: 21/03/2018
     * @author George
     * @param $data
     * @param string $message
     * @return \Illuminate\Http\JsonResponse
     */
    function stored($data, $message = '创建成功') {
        return respond($data, $message);
    }
}

if (! function_exists('updated')) {
    /**
     * 更新资源成功后响应
     *
     * Date: 21/03/2018
     * @author George
     * @param $data
     * @param string $message
     * @return \Illuminate\Http\JsonResponse
     */
    function updated($data, $message = '更新成功') {
        return respond($data, $message);
    }
}

if (! function_exists('deleted')) {
    /**
     * 删除资源成功后响应
     *
     * Date: 21/03/2018
     * @author George
     * @param string $message
     * @return \Illuminate\Http\JsonResponse
     */
    function deleted($message = '删除成功') {
        return message($message, Response::HTTP_OK);
    }
}

if (! function_exists('accepted')) {
    /**
     * 请求已被放入任务队列响应
     *
     * Date: 21/03/2018
     * @author George
     * @param string $message
     * @return \Illuminate\Http\JsonResponse
     */
    function accepted($message = '请求已接受,等待处理') {
        return message($message, Response::HTTP_ACCEPTED);
    }
}

if (! function_exists('notFound')) {
    /**
     * 未找到资源响应
     *
     * Date: 21/03/2018
     * @author George
     * @param string $message
     * @return \Illuminate\Http\JsonResponse
     */
    function notFound($message = '您访问的资源不存在') {
        return message($message, Response::HTTP_NOT_FOUND);
    }
}

if (! function_exists('internalError')) {
    /**
     * 服务器端位置错误响应
     *
     * Date: 21/03/2018
     * @author George
     * @param $message
     * @param int $code
     * @return \Illuminate\Http\JsonResponse
     */
    function internalError($message = '未知错误导致请求失败', $code = Response::HTTP_INTERNAL_SERVER_ERROR) {
        return message($message, $code);
    }
}

if (! function_exists('failed')) {
    /**
     * 错误的请求响应
     *
     * Date: 21/03/2018
     * @author George
     * @param $message
     * @param int $code
     * @return \Illuminate\Http\JsonResponse
     */
    function failed($message, $code = Response::HTTP_BAD_REQUEST) {
        return message($message, $code);
    }
}

if (! function_exists('success')) {
    /**
     * 成功响应
     *
     * Date: 21/03/2018
     * @author George
     * @param $date
     * @return \Illuminate\Http\JsonResponse
     */
    function success($date) {
        return respond($date);
    }
}

if (! function_exists('message')) {
    /**
     * 消息响应
     *
     * Date: 21/03/2018
     * @author George
     * @param $message
     * @param int $code
     * @return \Illuminate\Http\JsonResponse
     */
    function message($message, $code = Response::HTTP_OK) {
        return respond([], $message, $code);
    }
}

if (! function_exists('respond')) {
    /**
     * 生成响应体
     *
     * Date: 21/03/2018
     * @author George
     * @param array $data
     * @param string $message
     * @param int $code
     * @param array $header
     * @return \Illuminate\Http\JsonResponse
     */
    function respond($data = [], $message = '请求成功', $code = Response::HTTP_OK, array $header = []) {
        if ($data instanceof LengthAwarePaginator) {
            return response()->json([
                'resultCode' => $code,
                'resultMessage' => $message,
                'data' => $data->items(),
                'current_page' => $data->currentPage(),
                'from' => $data->firstItem(),
                'per_page' => $data->perPage(),
                'to' => $data->lastItem(),
                'total' => $data->total(),
            ], $code, $header);
        }
        return response()->json([
            'resultCode' => $code,
            'resultMessage' => $message,
            'data' => $data ? $data : []
        ], $code, $header);
    }
}

在控制器里:

public function index()
{
    $companies = Company::query()->paginate(15);
    return success($companies);
}
10个月前
984054610

请问这个能不能是实现,手机号码+短信验证码登录认证,可以的话,大概思路是什么?

10个月前

@984054610 这个具体的功能要你自己写,大致就是 根据手机号 发送验证码,然后将手机号和发送的验证码保存到缓存,并设置缓存周期以及重试次数,在注册的时候拿注册的手机号和验证码进行比对,成功则创建(注册)用户。

这个是一年前写的注册;逻辑,你可以参考一下
https://github.com/wowiwj/LifeRecoder-Server/blob/b5a443b4ee51fc4289ad87ed53df3c75b823d6b0/app/Http/Controllers/Api/AuthenticateController.php#L97

10个月前
Johnson16

@王举 我在用这个方法在本地Homestead可以顺利获取到access_token,获取用户也没有问题,部署到服务器上之后,也可以顺利的获取到access_token,然后使用access_token获取用户之后,提示

'message' : 'Unauthorized'

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return \Auth::guard('api')->user();
});

本地这个方法可以获取到用户,但是部署到服务器上就不可以了,发现这个auth:api没有起作用,整了一个下午,请指教,本地是Homestead,服务器端是Centos+Apache

10个月前

@王举 请问如果跨域怎么处理,我现在是在和你类似的响应封装里添加的头部

 return response()->json($data, $this->getStatusCode())
            ->header('Access-Control-Allow-Origin', '*')// $origin
            ->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN,X-Requested-With')
            ->header('Access-Control-Expose-Headers', 'Authorization, authenticated')
            ->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, OPTIONS,DELETE')
            ->header('Access-Control-Allow-Credentials', 'true'); // 允许浏览器发送 cookie 至服务器

外加 barryvdh/laravel-cors 这个包(不用没法跨域)
单纯用这个包,系统在抛异常时就没法跨域 :smile:
想请教你有没有好的跨域处理方案,网上好多都是用 middleware,但是异常时不走 middleware :smile:

10个月前

@Johnson16 有木有执行passport:install? 部署没问题应该就是这个问题了

10个月前
Johnson16

@王举 非常感谢回答,找到原因了,是因为服务器上的Apache挂了,卸载重新安装了Nginx后,一切顺溜。:pray:

10个月前

@江渚之上 系统的异常可以拦截和自己定制的,在文章自定义异常的位置你可以加api跨域设置,不过线上项目正常是不允许系统异常的,有异常可以自己测试修改呀 :stuck_out_tongue_closed_eyes:

10个月前

@王举 access_token 过期,会抛异常的,这个时候应用就不可以跨域了,在自定义异常处理中拼接跨域响应头 这样真的饿好吗 查了很久 没见过呀 :smile:

10个月前

@江渚之上 我用的5.5,加了一个middleware,没有遇到你这种情况
middleware

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;

class EnableCrossRequestMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        $origin = $request->server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : '';
        $allow_origin = config('manager.sites');

        if (in_array($origin, $allow_origin)) {

            $response->header('Access-Control-Allow-Origin', $origin);
            $response->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN');
            $response->header('Access-Control-Expose-Headers', 'Authorization, authenticated');
            $response->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, OPTIONS');
            $response->header('Access-Control-Allow-Credentials', 'true');
        }
        return $response;
    }
}

kernel

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
        \App\Http\Middleware\EnableCrossRequestMiddleware::class
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            ...
        ],
       ...
10个月前

@王举 我想用我自己新建的用户表,attempt方法这里一直报错,attempt方法只支持默认User模型么 :joy:

10个月前

@Johnson16 请问你用的是laravel自带的User模型么,我用自己新建的模型表,一到$this->guard('api')->attempt($credentials,$request->has('remember'))就报错,求助

10个月前
Johnson16

@xuhui

自定义用户表的模型文件要这样写:

<?php

namespace App;

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

class OtherUser extends Authenticatable  //这里的OtherUser就是其他用户表
{
    use HasApiTokens;  //这一行不能缺
}

多用户表登录参考这篇文章:
https://learnku.com/laravel/t/2777/laravel-53-multi-user-login

然后看app/auth.php文件

 'guards' => [
        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],

        'other_users_api' => [  //这里注意
            'driver' => 'passport',
            'provider' => 'other_users',  //这里注意,就是下方的other_users
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => \App\Models\User::class
        ],

        'other_users' => [
            'driver' => 'eloquent',
            'model' => \App\Models\OtherUser::class  //这里是你其他用户表的模型class
        ],
    ],

然后验证的时候

$this->guard('other_users_api')->attempt($credentials,$request->has('remember'))
10个月前

@Johnson16
这张数据表不是通过migrate创建的,是直接同事从之前的项目拉过来的,我感觉这是唯一的区别吧,别的地方我基本都是照做的呀 :joy:
file
file

10个月前
Johnson16

@xuhui 你的Customer模型文件确定是绑定user表吗?你的具体表名是什么?有没有系统自带的users表?

10个月前

@Johnson16 是不是必须按照多用户的形式搞下面就可以行的通,我现在准备按照你的重新弄一便

10个月前
Johnson16

@xuhui 必须按照多用户表的方式去做,应为你这样操作,原生的users表的验证就会被替换

10个月前

@Johnson16 谢谢指导,我现在重新创建一个项目尝试一下

10个月前

@Johnson16 大神,多用户表登录搞出来了,也可以正常登录,但是为什么$this->guard('api')->attempt($credentials, $request->has('remember'))调用的还是原本User模型来验证,

file

file

原本的表数据完全登录不上去,而且不能使用Auth::guard('api')去调用attempt方法,不然会报错方法不存在 :joy: :joy:

file

10个月前

目前在做一个前后端分离的网站,用laravel只做接口,现在不太明白的是,前台用户登录坑定要用账号密码获取token值以便访问登陆后的其他页面,那么没登录的时候,对接口的访问是否也是要带上token的,这个token怎么给予客户端?

9个月前

@Alexanderwmc 没登录默认没有token啊,需要授权的接口肯定要禁止访问的呀

9个月前

@王举

file
一个下午了 :sob:

9个月前

请问,中间件验证登录失败后返回的是web登录页面?如何设定返回api提示登录
自问自答一下,
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$accessToken,
],

9个月前

@王举

file

这么写有问题啊。 我一直报我上面的错误 我换成文档的那种写法就好了

file

9个月前

@王举 哥你这个自定义异常好像也有点问题。token过期后并没有拦截 我这边直接去了我后台的登陆页

9个月前

file

这里是把参数验证放在控制器里,如果参数多了,放在控制器就不合适。
新建一个 LoginRequest 也不能返回想要的 json。
请问大家有好的解决方法吗。

9个月前

@王成涛 可以获取的,在app/Exceptions/Handler 的 render 方法中拦截 ValidationException 即可, 获取你可以处理根据之前的
4.自定义返回异常相关代码

这个是我之前的实现

<?php

namespace App\Api\Helpers\Api;

use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class ExceptionReport
{
    use ApiResponse;

    /**
     * @var Exception
     */
    public $exception;
    /**
     * @var Request
     */
    public $request;

    /**
     * @var
     */
    protected $report;

    /**
     * ExceptionReport constructor.
     * @param Request $request
     * @param Exception $exception
     */
    function __construct(Request $request, Exception $exception)
    {
        $this->request = $request;
        $this->exception = $exception;
    }

    /**
     * @var array
     */
    public $doReport = [
        AuthenticationException::class => ['未授权',401],
        ModelNotFoundException::class => ['改模型未找到',404],
        AuthorizationException::class => ['没有此权限',403],
        ValidationException::class => [],

    ];

    public function register($className,callable $callback){

        $this->doReport[$className] = $callback;
    }

    /**
     * @return bool
     */
    public function shouldReturn(){

        if (! ($this->request->wantsJson() || $this->request->ajax())){

            return false;
        }

        foreach (array_keys($this->doReport) as $report){

            if ($this->exception instanceof $report){

                $this->report = $report;
                return true;
            }
        }

        return false;

    }

    /**
     * @param Exception $e
     * @return static
     */
    public static function make(Exception $e){

        return new static(\request(),$e);
    }

    /**
     * @return mixed
     */
    public function report(){

        if ($this->exception instanceof ValidationException){

            $error = array_first($this->exception->errors());

            return $this->failed(array_first($error),$this->exception->status);

        }

        $message = $this->doReport[$this->report];

        return $this->failed($message[0],$message[1]);

    }

}
9个月前
__中国人

感觉没有dingo➕jwt简单啊

8个月前

@等车的猪 dingo 感觉体验不是很好,不过jwt确实用着比passport要简单的多,所以项目选用我一般优先选用jwt,只是jwt-auth的文档还是0.5的,1.0目前还都是release版本。

8个月前
domine

上述的方法中如果未授权返回的结果为什么是如下图,而不是自定义的异常格式

file

8个月前

@王举 password 用的扩展包也是jwt

7个月前
fantasticcat

@王举

我比较纠结一个问题!那个guard方法不接受参数的啊,$this->guard('api') 和$this->guard() 的返回都是sessionGuard啊加个‘api’没意义啊!这样做相当于还是在用 ‘web’这个默认守卫做验证,如果web守卫和api守卫的表名不一致就会出问题

6个月前
fantasticcat

@王举 楼主个人觉得你登录这块有问题。你那个登录验证不应该存在,passport本身就要对用户信息做验证所以没必要自己验证。如果要自定义验证子段上面的朋友已经给出了答案。重写api守卫的模型方法

public function findForPassport($username)
{
        return self::where('phone', $username)->first();
}
6个月前

@王举 大神我的也是报错

file

fileguard这里自定义了一个表。看到你说的这个方法

file我这样写

file发现仍然报错,这样写他查的是user表了

file

5个月前

@Johnson16 按你的写法

file

file

file结果报错,他似乎走的是默认的users表,看守器guard似乎没起作用

file

5个月前
Johnson16

@scenery 整个问题的确有些蹊跷,我抽空帮你看看

5个月前

Laravel passport has made api authentication better and a lot easier. You can now easily create an api in Laravel and use Passport for api authentication quickly.

5个月前
huzhuolei

请问api接口多版本控制怎么处理?

3个月前

@Johnson16 这么耍流氓的么:cry:

2个月前

@huzhuolei 通过不同的请求头或者请求链接划分呗

比如: 链接

/api/v1/comments
/api/v2/comments

或者类型请求头

Accept:application/vnd.{API_SUBTYPE}.v2+json
2个月前
Johnson16

@王举 哈哈,别告诉别人哈。

2个月前

这个地方,需要传一个user_id,关联users表id,如果不绑定user_id,请求获取token会返回:The user credentials were incorrect

file

1个月前
surest

您好,我在使用的过程中,发现,laravel中api接口的话,如果设置的独立验证器之类的话。 我们还是需要去针对性的去定制优化。

例如独立验证器具中,我们使用respose的话,直接return, 还是会使用FormRequest的响应规则

所以我在基础上进行修改了一下,您看看怎么样呢?

https://github.com/surest-sky/example/blob/master/laravel%E7%9A%84%E4%B8%80%E4%BA%9B%E5%86%99%E6%B3%95/ApiResponse.php

2周前

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!