PHP开发Laravel开发实践PHP经验分享

laravel5.6全后台搭建(包括Api接口)

2020-04-01  本文已影响0人  ONEDAYLOG
项目说明

因为近期要为一个投票类APP做后台支持,所以特此准备持续更新文档。搭建后台无非就是数据库数据的管理和与APP交互接口的搭建两大模块,本人主要使用了以下几个主要扩展。

Laravel-admin:用于后台数据管理 github 中文文档
Dingo:用于后台接口 github 中文文档
JWT:用于JWT认证 github 英文文档
Apidoc:用于生成接口文档 github 英文文档

1. 使用Laravel-admin 搭建后台管理

这个中文文档已经写的很详细了,建议查看文档按步骤搭建。

1.创建公告表
php artisan make:migration create_notices_table
2.字段大致如下
Schema::create('notices', function (Blueprint $table) {
            $table->increments('id');
            $table->string('notice_name')->nullable();
            $table->string('notice_content')->nullable();
            $table->string('notice_img')->nullable();
            $table->string('thumbnail')->nullable();
            $table->boolean('is_push')->default(false);
            $table->boolean('is_top')->default(false);
            $table->timestamps();
        });
3.迁移到数据库
php artisan migrate
4.创建Model
php artisan make:model Notice
5.创建Controller对应指定Model这样创建比较方便
php artisan admin:make NoticeController --model=App\Notice

具体如下

<?php
namespace App\Admin\Controllers;
use App\Model\Notice;
use Encore\Admin\Controllers\AdminController;
use Encore\Admin\Form;
use Encore\Admin\Grid;
use Encore\Admin\Show;
class NoticeController extends AdminController
{

    protected $title = '公告管理';

    protected function grid()
    {
        $grid = new Grid(new Notice());
        $grid->actions(function ($actions) {
            $actions->disableView();
        });
        $grid->filter(function($filter){
            $filter->disableIdFilter();
            $filter->like('notice_name', __('Notice name'));
        });
        $grid->column('id', __('Id'));
        $grid->column('notice_name', __('Notice name'));
        $grid->column('notice_img', __('Notice img'))->image(config('filesystems.disks.admin.url'), 80, 60);
        $grid->column('thumbnail',__('Thumbnail'))->image(config('filesystems.disks.admin.url'), 60, 40);
        $grid->column('is_push', __('Is push'))->bool();
        $grid->column('is_top', __('Is top'))->bool();
        $grid->column('created_at', __('Created at'));
        $grid->column('updated_at', __('Updated at'));
        return $grid;
    }

    protected function detail($id)
    {
        $show = new Show(Notice::findOrFail($id));
        $show->field('id', __('Id'));
        $show->field('notice_name', __('Notice name'));
        $show->field('notice_content', __('Notice content'));
        $show->field('notice_img', __('Notice img'));
        $show->field('thumbnail', __('Thumbnail'));
        $show->field('is_push', __('Is push'));
        $show->field('is_top', __('Is top'));
        $show->field('created_at', __('Created at'));
        $show->field('updated_at', __('Updated at'));
        return $show;
    }

    protected function form()
    {
        $form = new Form(new Notice());
        $form->text('notice_name', __('Notice name'));
        $form->editor('notice_content', __('Notice content'));
        $form->image('notice_img',__('Notice img'))->help("建议尺寸800px*600px")
            ->move('/images/'.date('Y', time()).'/'.date('md', time()));
        $form->image('thumbnail', __('Thumbnail'))->help("建议尺寸600px*400px")
            ->move('/images/'.date('Y', time()).'/'.date('md', time()));
        $form->switch('is_push', __('Is push'));
        $form->switch('is_top', __('Is top'));
        return $form;
    }
}

这里需要配置对应中文json和wang-editor编辑器,不做展开描述了

bandicam.gif

2. 配置Dingo接口

这个中文文档也已经写的很详细了,建议查看文档按步骤搭建。

1.配置env
API_DEBUG=true
API_VERSION=v1
API_PREFIX=api
API_SUBTYPE=app
API_DOMAIN=api.test.com
API_STRICT=false
API_NAME="Sites API"
API_STANDARDS_TREE=vnd
API_DEFAULT_FORMAT=json
API_CONDITIONAL_REQUEST=false
2.写了 BaseController 统一返回接口
<?php
namespace App\Api\Controllers;
use Dingo\Api\Routing\Helpers;
use Illuminate\Routing\Controller;
class BaseController extends Controller
{
    use Helpers;
    //成功返回
    public function success($msg="ok",$data=null){
        $this->parseNull($data);
        $result = [
            "code"=>0,
            "msg"=>$msg,
            "data"=>$data,
        ];
        return response()->json($result,200);
    }
    //失败返回
    public function error($code="422",$data="",$msg="fail"){
        $result = [
            "code"=>$code,
            "msg"=>$msg,
            "data"=>$data
        ];
        return response()->json($result,200);
    }
    //如果返回的数据中有 null 则那其值修改为空 (安卓和IOS 对null型的数据不友好,会报错)
    private function parseNull(&$data){
        if(is_array($data)){
            foreach($data as &$v){
                $this->parseNull($v);
            }
        }else{
            if(is_null($data)){
                $data = "";
            }
        }
    }
}
3.写了两个接口,一个用来获取公告列表,一个获取指定公告
<?php

namespace App\Api\Controllers;

use App\Model\Fruit;
use App\Model\Notice;
use App\Transformers\CustomSerializer;
use App\Transformers\FruitsTransformer;
use App\Transformers\NoticeTransformer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class DingoApiController extends BaseController
{
    /**
     * 获取公告列表
     *
     * 查询公告列表 5 条
     *
     * @response {
     * "code": 0,
     * "msg": "查询成功",
     * "data": {
     * "id": 1,
     * "notice_name": "测试公告映前广告独家代理",
     * "notice_content": "映前广告独家代理,联系电话:05758-5221611",
     * "notice_img": "http://vote.test.com/upload/images/2020/0331/dfa.png",
     * "thumbnail": "http://vote.test.com/upload/images/2020/0331/600400.png",
     * "dt": "2020-03-30 07:18:26",
     * "is_top": true,
     * "is_push": false
     * }
     * }
     */

    public function get_notice_list()
    {
        $notice = Notice::all();
        $col = $this->response()->collection($notice, new NoticeTransformer(), function ($resource, $fractal) {
            $fractal->setSerializer(new CustomSerializer);
        });
        return $col;
    }

    /**
     * 读取指定公告
     *
     *
     *
     * @queryParam nid required 公告ID.
     * @response {
     * "code": 0,
     * "msg": "查询成功",
     * "data": {
     * "id": 1,
     * "notice_name": "测试公告映前广告独家代理",
     * "notice_content": "映前广告独家代理,联系电话:05758-5221611",
     * "notice_img": "http://vote.test.com/upload/images/2020/0331/dfa.png",
     * "thumbnail": "http://vote.test.com/upload/images/2020/0331/600400.png",
     * "dt": "2020-03-30 07:18:26",
     * "is_top": true,
     * "is_push": false
     * }
     * }
     */
    public function get_notice(Request $request)
    {

        $validator = Validator::make($request->all(), [
            'nid' => 'required|integer'
        ]);

        if ($validator->fails()) {
            return $this->error(401, null, $validator->messages());
        }

        $notice = Notice::where('id', $request->nid)->first();

        $col = $this->response()->item($notice, new NoticeTransformer(), function ($resource, $fractal) {
            $fractal->setSerializer(new CustomSerializer);
        });
        return $col;
    }

}

4.写了一个转换器,将数据加载成我们想要的格式
<?php
namespace App\Transformers;
use App\Model\Notice;
use League\Fractal\TransformerAbstract;
class NoticeTransformer extends TransformerAbstract
{
    public function transform(Notice $notice)
    {

        return [
            'id'                => (int) $notice->id,
            'notice_name'       => ucfirst($notice->notice_name),
            'notice_content'    => ucfirst($notice->notice_content),
            'notice_img'        => config('filesystems.disks.admin.url').$notice->notice_img,
            'thumbnail'         => config('filesystems.disks.admin.url').$notice->thumbnail,
            'dt'                => $notice->created_at->format('Y-m-d H:i:s'),
            'is_top'            => (bool) $notice->is_top,
            'is_push'           => (bool) $notice->is_push,
        ];
    }
}

5.转换器是后加载的,需要自定义序列给前端返回,序列号的数据
<?php
namespace App\Transformers;

use League\Fractal\Serializer\ArraySerializer;

class CustomSerializer extends ArraySerializer
{
    public function collection($resourceKey, array $data)
    {
        return [ "code"=>0,"msg"=>"查询成功",'data' => $data];
    }

    public function item($resourceKey, array $data)
    {
        return [ "code"=>0,"msg"=>"查询成功",'data' => $data];
    }
}

6.当然要写路由routes/api.php
$api = app('Dingo\Api\Routing\Router');

$api->version('v1', function ($api) {
    $api->group(['namespace' => 'App\Api\Controllers'], function ($api) {
        $api->match(['get', 'post'], 'get_notice', 'DingoApiController@get_notice');
        $api->match(['get', 'post'], 'get_notice_list', 'DingoApiController@get_notice_list');
    });

});

好了,这样所有的人都能访问接口


image.png

3. 配置JWT认证

1.安装的话一句话带过,去看英文文档
2.开始配置kernel.php
//这个是自己写的中间件
        'jwt.verify' => \App\Http\Middleware\JwtMiddleware::class,

        'jwt.auth' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class,
        'jwt.refresh' => \Tymon\JWTAuth\Http\Middleware\RefreshToken::class,

我在使用jwt.auth的时候遇到了问题authenticate(),因为服务器环境问题,这里会报错要用getToken方法,所以我自己重写中间件

            if (! $this->auth->parseToken()->authenticate()) {
//            if (! $this->auth->getToken()) {
3.中间件
<?php

namespace App\Http\Middleware;

use Closure;
use JWTAuth;
use Exception;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class JwtMiddleware extends BaseMiddleware
{

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        try {
            $user = JWTAuth::getToken();
            if (! $user) {
                throw new UnauthorizedHttpException('jwt-auth', 'User not found');
            }
        } catch (Exception $e) {
            return response()->json(["msg"=>"Token Error","code"=>401,  "data"=>null]);
        }
        return $next($request);
    }
}

4.写Controller
<?php
namespace App\Api\Controllers;
use App\Model\User;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;
use Illuminate\Support\Facades\Validator;

/**
 * @group 用户功能
 *
 * APIs for managing users
 */
class JwtAuthController extends BaseController
{

    /**
     *
     * 测试功能
     *
     * @return string
     */
    public function test()
    {
        return $this->success("访问测试成功");
    }

    /**
     * 用户注册
     *
     *
     * @queryParam username required 用户名.
     * @queryParam password required 密码.
     * @response {
     * "code": 0,
     * "msg": "注册成功",
     * "data": {
     * "username": "15968426220",
     * "updated_at": "2020-03-31 08:01:01",
     * "created_at": "2020-03-31 08:01:01",
     * "id": 6
     * }
     * }
     */
    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'username' => 'required|string',
            'password' => 'required|string|min:8|max:255',
        ]);
        if ($validator->fails()) {
            return $this->error(401, null, $validator->messages());
        }
        $user = new User();
        $user->fill($request->all());
        $user->password = bcrypt($request->password);
        $user->save();
        return $this->success("注册成功", $user);
    }


    /**
     * 用户登录
     *
     *
     * @queryParam username required 用户名.
     * @queryParam password required 密码.
     * @response {
     * "code": 0,
     * "msg": "授权成功",
     * "data": {
     * "access_token": "",
     * "token_type": "bearer",
     * "expires_in": 3600
     * }
     * }
     */
    public function login(Request $request)
    {
        $credentials = $request->only('username', 'password');
        $token = JWTAuth::attempt($credentials);
        if (!$token) {
            return $this->error("401", "未经许可");
        }
        return $this->respondWithToken($token);
    }


    /**
     * 用户退出
     *
     *
     * @queryParam token required 用户名.
     * @response {
     * "code": 0,
     * "msg": "退出登录",
     * "data": {
     * }
     * }
     */
    public function logout()
    {
        JWTAuth::parseToken()->invalidate();
        return $this->success("退出登录");
    }
    /**
     * 用户刷新token
     *
     *
     * @queryParam token required 用户名.
     * @response {
     * "code": 0,
     * "msg": "授权成功",
     * "data": {
     * "access_token": "",
     * "token_type": "bearer",
     * "expires_in": 3600
     * }
     * }
     */
    public function refresh()
    {
        return $this->respondWithToken(JWTAuth::parseToken()->refresh());
    }

    protected function respondWithToken($token)
    {
        $data = [
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => JWTAuth::factory()->getTTL() * 60
        ];
        return $this->success("授权成功", $data);
    }
}

6.改写User的Model
<?php

namespace App\Model;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
    //
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'username','nickname', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        // TODO: Implement getJWTIdentifier() method.
        return $this-> getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        // TODO: Implement getJWTCustomClaims() method.
        return [];
    }
}

7.改写路由api.php

refresh logout test get_notice get_notice_list需要登录后才能访问

$api = app('Dingo\Api\Routing\Router');

$api->version('v1', function ($api) {
    $api->group(['namespace' => 'App\Api\Controllers'], function ($api) {
        $api->post('register', 'JwtAuthController@register');
        $api->post('login', 'JwtAuthController@login');
    });

});

$api->version('v1', function ($api) {
    $api->group(['namespace' => 'App\Api\Controllers','middleware'=>'jwt.verify'], function ($api) {
        $api->post('refresh', 'JwtAuthController@refresh');
        $api->post('logout', 'JwtAuthController@logout');
        $api->get('test', 'JwtAuthController@test');
        $api->match(['get', 'post'], 'get_notice', 'DingoApiController@get_notice');
        $api->match(['get', 'post'], 'get_notice_list', 'DingoApiController@get_notice_list');
    });
});
8.测试一下

没有登录的时候获取公告列表,显示错误


image.png

用户名密码登录一下获取token(没有用户的记得先注册)


image.png

传回token,就可以调用该接口

image.png

3. 配置laravel-apidoc-generator用来自动生成api网页

Dingo虽然可以生成API的md,但是不便于阅读,所以另外用了laravel-apidoc-generator

1.怎么安装就跳过吧,直接看英文文档
2.配置apidoc.php
    'output' => 'public/docs',
//记得配置成Dingo,不然会生成乱七八糟的一堆别的接口
    'router' => 'Dingo',
3.生成api
php artisan api:generate
4.访问apidoc网页http://vote.test.com/docs/
image.png

到这里就差不多都完成了,项目后续还要加入阿里云短信 阿里云推送 支付等等,有空再更新吧

上一篇 下一篇

猜你喜欢

热点阅读