Laravel 开发 API 心得

前言

虽然开发了几年,但一直没有使用 Laravel 作为项目框架,在 Laravel 里,属于不折不扣的新人,正好新项目用了 Laravel,在这分享一下 Laravel 开发 API 心得,希望能给想使用 Laravel 开发的人带来帮助。

通过实现「用户注册」接口,来介绍每个功能的使用场景。
PS:「用户注册」接口纯粹是为了方便介绍,所以没有使用自带的 Auth 模块.

大纲

开发环境

Docker - PHP7.4 + MySQL5.7 + Nginx1.19
IDE:PhpStorm

项目搭建

一、安装 Laravel

$ composer create-project --prefer-dist laravel/laravel:^6.2 test

二、Laravel - User 相关文件处理
1) 删除以下文件

app/Http/Controllers/Auth 目录
database/factories/UserFactory.php
database/migrations/2014_10_12_000000_create_users_table.php
database/migrations/2014_10_12_100000_create_password_resets_table.php
resources/views/welcome.blade.php

2) 移动 app/User.phpapp/Models/User/User.php

三、配置
1) .env - 配置数据库

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=123456

2) config/app.php - 配置时区

'timezone' => 'Asia/Shanghai'

四、中间件 AcceptHeader

Accept 决定了响应返回的格式,设置为 application/json, 遇到的所有报错 Laravel 会默认处理为 JSON 格式

1) 新建 app/Http/Middleware/AcceptHeader.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class AcceptHeader
{
    /**
     * Handle an incoming request.
     *
     * @param Request $request
     * @param Closure $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $request->headers->set('Accept', 'application/json');
        return $next($request);
    }
}

2) app/Http/Kernel.php

...
protected $middlewareGroups = [
    ...
    'api' => [
        \App\Http\Middleware\AcceptHeader::class,
        ...
    ],
];
...

五、创建 users
1) 创建迁移

php artisan make:migration create_users_table --create=users

2) 编辑 database/migrations/2021_04_29_070350_create_users_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;

class CreateUsersTable extends Migration
{
    private const TABLE = 'users';

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create(self::TABLE, function (Blueprint $table) {
            $table->integerIncrements('id');
            $table->string('account', 32)->comment('账号');
            $table->string('password')->comment('密码');
            $table->string('register_address')->comment('注册地址');
            $table->string('last_location')->default('')->comment('最后登录位置');
            $table->unsignedInteger('last_ip')->comment('最后登录IP');
            $table->timestamps();
        });
        DB::statement('ALTER TABLE ' . self::TABLE . ' COMMENT "用户表"');
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists(self::TABLE);
    }
}

3) 执行迁移

$ php artisan migrate

六、安装扩展包

1) laravel-ide-helper - IDE 智能提示插件

$ composer require "barryvdh/laravel-ide-helper:2.8.*" --dev
$ php artisan ide-helper:generate
$ php artisan ide-helper:modles

2) laravel-telescope - 调试工具

$ composer require "laravel/telescope:^3.0" --dev
$ php artisan telescope:install
$ php artisan migrate --path=./vendor/laravel/telescope/src/Storage/migrations/

# 一般也不需要配置 `laravel-telescope`, 删除以下文件
config/telescope.php
config/app.php -> TelescopeServiceProvider 服务注册
app/Providers/TelescopeServiceProvider.php

七、取消扩展包自动发现,并在 AppServiceProvider 注册

1) composer.json

...
"extra": {
    "laravel": {
        "dont-discover": [
            "barryvdh/laravel-ide-helper",
            "laravel/telescope"
        ]
    }
}
...

2) app/Providers/AppServiceProvider.php - 仅本地环境注册服务

...
public function register()
{
    if ($this->app->isLocal()) {
        $this->app->register('Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider');
        $this->app->register('Laravel\Telescope\TelescopeServiceProvider');
}
...

目录结构

  • app 目录
    Console - 自定义的 Artisan 命令目录
    Enums - 枚举目录
    Exceptions - 异常处理目录
    Http - 包含控制器、中间件以及表单请求等目录
    Jobs - 队列任务目录
    Libraries - 第三方包目录(包含扩展包封装类)
    Listeners - 事件监听目录
    ModelFilters - 模型过滤器目录
    Models - 模型目录
    Observers - 模型观察者目录
    Providers - 服务提供者目录
    Services - 业务服务类目录
  • route 目录
    api - 接口目录

路由加载

1) 删除 routes/api.php & routes/web.php 路由文件

2) 编辑 app/Providers/RouteServiceProvider.php, 加载 routes/api 目录下的路由文件

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * This namespace is applied to your controller routes.
     *
     * In addition, it is set as the URL generator's root namespace.
     *
     * @var string
     */
    protected $namespace = 'App\Http\Controllers\V1';

    /**
     * The path to the "home" route for your application.
     *
     * @var string
     */
    public const HOME = '/';

    /**
     * Define your route model bindings, pattern filters, etc.
     *
     * [@return](https://learnku.com/users/31554) void
     */
    public function boot()
    {
        parent::boot();
    }

    /**
     * Define the routes for the application.
     *
     * [@return](https://learnku.com/users/31554) void
     */
    public function map()
    {
        $this->mapApiRoutes();
    }

    /**
     * Define the "api" routes for the application.
     *
     * These routes are typically stateless.
     *
     * [@return](https://learnku.com/users/31554) void
     */
    protected function mapApiRoutes()
    {
        Route::prefix('v1')
            ->middleware('api')
            ->namespace($this->namespace)
            ->name('v1.')
            ->group(function () {
                foreach (glob(base_path('routes') . '/api/*.php') as $file) {
                    require $file;
                }
            });
    }
}

3) 新建「路由」 routes/api/user.php

<?php

Route::prefix('user')
    ->namespace('User')
    ->name('user.')
    ->group(function () {
        Route::post('register', 'UserController@register')->name('user.register');
    });

4) 新建「控制器」 app/Http/Controllers/V1/User/UserController.php

<?php

namespace App\Http\Controllers\V1\User;

use App\Http\Controllers\Controller;

class UserController extends Controller
{
    public function register()
    {
    }
}

API规范

参考 Jiannei/laravel-response
由于个人并不太喜欢用 RESTful 规范的 HTTP 状态码以及 Enum 的定义, 就整了个新包 sevming/laravel-response, 默认 HTTP 返回状态码为 200

1) 安装

$ composer require sevming/laravel-response:^1.0

2) 编辑 app/Exceptions/Handler.php

class Handler extends ExceptionHandler
{
    use \Sevming\LaravelResponse\Support\Traits\ExceptionTrait;
}

Enum枚举

1) 新建 app/Enums/ResponseEnum.php

<?php

namespace App\Enums;

class ResponseEnum
{
    // sevming/laravel-response 默认以 '|' 作为分割错误码与错误信息的字符串
    public const INVALID_REQUEST = '无效请求|21001';
}

2) 编辑 app/Http/Controllers/V1/User/UserController.php

...
use Sevming\LaravelResponse\Support\Facades\Response;
use App\Enums\ResponseEnum;
...
public function register()
{
    Response::fail(ResponseEnum::INVALID_REQUEST);
}
...

3) POST 请求 /v1/user/register

{
    "status": "fail",
    "code": "21001",
    "message": "无效请求",
    "data": {},
    "errors": {}
}

表单场景验证

1) 新建「表单验证基类」 app/Http/Requests/FormRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;

class FormRequest extends BaseFormRequest
{
    use SceneValidator;

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }
}

2) 新建 app/Http/Requests/SceneValidator.php

<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\{Factory, Validator};

trait SceneValidator
{
    protected $scene = null;

    protected $onlyRule = [];

    protected $autoValidate = true;

    /**
     * Validate.
     *
     * @param string|array $scene
     */
    public function validate($scene = '')
    {
        if (!$this->autoValidate) {
            if (is_array($scene)) {
                $this->onlyRule = $scene;
            } else {
                $this->scene = $scene;
            }

            $this->handleValidate();
        }
    }

    /**
     * 覆盖 ValidatesWhenResolvedTrait->validateResolved
     */
    public function validateResolved()
    {
        if (method_exists($this, 'autoValidate')) {
            $this->autoValidate = $this->container->call([$this, 'autoValidate']);
        }

        if ($this->autoValidate) {
            $this->handleValidate();
        }
    }

    /**
     * Handle validate.
     */
    protected function handleValidate()
    {
        parent::validateResolved();
    }

    /**
     * 定义 FormRequest->getValidatorInstance 下 validator 验证器
     *
     * @param Factory $factory
     *
     * @return Validator
     */
    public function validator(Factory $factory)
    {
        $validationData = $this->isMethod('GET') ? $this->query() : $this->post();
        return $factory->make($validationData, $this->getRules(), $this->messages(), $this->attributes());
    }

    /**
     * Get rules.
     *
     * @return array
     */
    protected function getRules()
    {
        return $this->handleScene($this->container->call([$this, 'rules']));
    }

    /**
     * Handle scene.
     *
     * @param array $rules
     *
     * @return array
     */
    protected function handleScene(array $rules)
    {
        if ($this->onlyRule) {
            return $this->handleRules($this->onlyRule, $rules);
        }

        if (!empty($this->scene) && method_exists($this, 'scene')) {
            $scene = $this->container->call([$this, 'scene']);
            if (array_key_exists($this->scene, $scene)) {
                return $this->handleRules($scene[$this->scene], $rules);
            }
        }

        return $rules;
    }

    /**
     * Handle rules.
     *
     * @param array $sceneRules
     * @param array $rules
     *
     * @return array
     */
    protected function handleRules(array $sceneRules, array $rules)
    {
        $result = [];
        foreach ($sceneRules as $key => $value) {
            if (is_numeric($key) && array_key_exists($value, $rules)) {
                $result[$value] = $rules[$value];
            } else {
                $result[$key] = $value;
            }
        }

        return $result;
    }
}

3) 新建「请求类」 app/Http/Requests/User/UserRequest.php

<?php

namespace App\Http\Requests\User;

use App\Http\Requests\FormRequest;

class UserRequest extends FormRequest
{
    protected $autoValidate = false;

    public function rules()
    {
        return [
            'account' => 'required|string',
            'password' => 'required|string|min:6',
        ];
    }

    public function scene()
    {
        return [
            'register' => [
                'account',
                'password',
            ],
        ];
    }
}

4) 编辑 app/Http/Controllers/V1/User/UserController.php

...
use App\Http\Requests\User\UserRequest;
...
public function register(UserRequest $request)
{
    $params = $request->post();
    $request->validate('register');
    dd($params);
}
...

5) POST 请求 /v1/user/register

# 请求数据
account: test
password: 12345

# 返回数据 - 错误提示为英文
{
    "status": "fail",
    "code": "20002",
    "message": "Unprocessable Entity",
    "data": {},
    "errors": {
        "password": [
            "The password must be at least 6 characters."
        ]
    }
}

6) 安装 overtrue/laravel-lang - 语言包

composer require "overtrue/laravel-lang:~3.0"

# 1. 配置 config/app.php
Illuminate\Translation\TranslationServiceProvider::class
替换
Overtrue\LaravelLang\TranslationServiceProvider::class

# 2. 配置 config/app.php
'locale' => 'zh-CN'

7) 重新请求接口,返回数据如下:

{
    "status": "fail",
    "code": "20002",
    "message": "Unprocessable Entity",
    "data": {},
    "errors": {
        "password": [
            "密码 至少为 6 个字符。"
        ]
    }
}

事件监听

QueryListener - SQL 日志记录

1) 新建 app/Listeners/QueryListener.php

<?php

namespace App\Listeners;

use Illuminate\Database\Events\QueryExecuted;

class QueryListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
    }

    /**
     * Handle the event.
     *
     * @param  QueryExecuted  $event
     * @return void
     */
    public function handle(QueryExecuted $event)
    {
        if (true === config('app.debug')) {
            foreach ($event->bindings as $key => $value) {
                if ($value instanceof \DateTimeInterface) {
                    $event->bindings[$key] = $value->format('Y-m-d H:i:s');
                } elseif (is_bool($value)) {
                    $event->bindings[$key] = (int)$value;
                }
            }

            $sql = str_replace('?', "'%s'", $event->sql);
            logger("[{$event->time}ms] " . vsprintf($sql, $event->bindings));
        }
    }
}

2) 编辑 app/Providers/EventServiceProvider.php

<?php
...
protected $listen = [
    \Illuminate\Database\Events\QueryExecuted::class => [
        \App\Listeners\QueryListener::class
    ],
];
...

用户注册

1) 新建「腾讯地图类」 app/Libraries/TencentMapLibrary.php

<?php

namespace App\Libraries;

use \Exception;

class TencentMapLibrary
{
    /**
     * 通过IP获取用户位置信息
     *
     * @param string $ip
     *
     * @return string
     * @throws Exception
     */
    public static function getLocationByIp(string $ip)
    {
        return '福建省厦门市';
    }
}

2) 新建「服务类」 app/Services/User/UserService.php

<?php

namespace App\Services\User;

use \Exception;
use App\Libraries\TencentMapLibrary;
use App\Models\User\User;

class UserService
{
    /**
     * @param array     $params
     *
     * @return User
     * @throws Exception
     */
    public function createUser(array $params)
    {
        $userData = [
            'account' => $params['account'],
            'password' => bcrypt($params['password']),
            'last_ip' => request()->ip(),
        ];
        $location = TencentMapLibrary::getLocationByIp($userData['last_ip']);
        if (!empty($location)) {
            $userData['register_address'] = $location;
        }

        return User::create($userData);
    }
}

3) 编辑 app/Models/User/User.php

<?php

namespace App\Models\User;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    protected $guarded = [];

    public static function findByAccount(string $account)
    {
        return static::query()
            ->where('account', $account)
            ->first();
    }

    public function setLastIpAttribute($value)
    {
        $this->attributes['last_ip'] = ip2long($value);
    }

    public function getLastIpAttribute($value)
    {
        return long2ip($value);
    }
}

4) 编辑 app/Enums/ResponseEnum.php

...
public const USER_ACCOUNT_REGISTERED = '账号已注册|23001';
...

5) 编辑 app/Http/Controllers/V1/User/UserController.php

<?php

namespace App\Http\Controllers\V1\User;

use \Exception;
use Illuminate\Http\JsonResponse;
use Sevming\LaravelResponse\Support\Facades\Response;
use App\Enums\ResponseEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserRequest;
use App\Services\User\UserService;
use App\Models\User\User;

class UserController extends Controller
{
    /**
     * @var UserService
     */
    protected $service;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->service = new UserService();
    }

    /**
     * @param UserRequest $request
     *
     * @return JsonResponse
     * @throws Exception
     */
    public function register(UserRequest $request)
    {
        $params = $request->post();
        $request->validate('register');
        $user = User::findByAccount($params['account']);
        if (!empty($user)) {
            Response::fail(ResponseEnum::USER_ACCOUNT_REGISTERED);
        }

        $user = $this->service->createUser($params);

        return Response::success([
            'id' => $user->id
        ]);
    }
}

Eloquent条件查询

1) 安装

$ composer require tucker-eric/eloquentfilter:^2.4

2) 新建 app/Models/ModelTrait.php

<?php

namespace App\Models;

trait ModelTrait
{
    protected function modelFilter()
    {
        return config('eloquentfilter.namespace', 'App\\ModelFilters\\')
            . str_replace(__NAMESPACE__ . '\\', '', get_class($this)) . 'Filter';
    }
}

3) 新建「模型过滤类」 app/ModelFilters/User/UserFilter.php

<?php

namespace App\ModelFilters\User;

use EloquentFilter\ModelFilter;

class UserFilter extends ModelFilter
{
    public function account($account)
    {
        return $this->where('account', $account);
    }
}

4) 编辑 app/Models/User/User.php

...
use EloquentFilter\Filterable;
use App\Models\ModelTrait;

class User extends Authenticatable
{
  use Filterable, ModelTrait;
  ...  
  public static function findByAccount(string $account)
  {
      return static::filter([
          'account' => $account
      ])
          ->first();
   }   
   ...
}

模型事件

优化用户注册时的地址获取

1) 新建「观察者类」 app/Observers/User/UserObserver.php

<?php

namespace App\Observers\User;

use Illuminate\Support\Facades\DB;
use App\Libraries\TencentMapLibrary;
use App\Models\User\User;

class UserObserver
{
    public function saved(User $user)
    {
        rescue(function () use ($user) {
            $wasRecentlyCreated = $user->wasRecentlyCreated;
            if ($wasRecentlyCreated || $user->wasChanged('last_ip')) {
                $location = TencentMapLibrary::getLocationByIp($user->last_ip);
                if (!empty($location)) {
                    $data = ['last_location' => $location];
                    $wasRecentlyCreated && ($data['register_address'] = $data['last_location']);
                    DB::table('users')->where('id', $user->id)->update($data);
                }
            }
        });
    }
}

2) 编辑 app/Models/User/User.php

...
use App\Observers\User\UserObserver;

class User extends Authenticatable
{
    ...
    public static function boot()
    {
        parent::boot();
        static::observe(UserObserver::class);
    }
    ...
}

3) 编辑 app/Services/User/UserService.php

...
public function createUser(array $params)
{
    $userData = [
        'account' => $params['account'],
        'password' => bcrypt($params['password']),
        'register_address' => '',
        'last_ip' => request()->ip(),
    ];
    return User::create($userData);
}
...

队列

用户注册地址获取修改为队列方式

1) .env 队列配置

QUEUE_CONNECTION=redis

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=123456
REDIS_PORT=6379

2) 新建「任务类」 app/Jobs/User/UserLocation.php

<?php

namespace App\Jobs\User;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\{InteractsWithQueue, SerializesModels};
use App\Libraries\TencentMapLibrary;
use Illuminate\Support\Facades\DB;
use App\Models\User\User;

class UserLocation implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;

    protected $wasRecentlyCreated;

    public function __construct(User $user, bool $wasRecentlyCreated)
    {
        $this->user = $user;
        $this->wasRecentlyCreated = $wasRecentlyCreated;
    }

    public function handle()
    {
        $location = TencentMapLibrary::getLocationByIp($this->user->last_ip);
        if (!empty($location)) {
            $data = ['last_location' => $location];
            $this->wasRecentlyCreated && ($data['register_address'] = $data['last_location']);
            DB::table('users')->where('id', $this->user->id)->update($data);
        }
    }
}

3) 编辑 app/Observers/User/UserObserver.php

<?php

namespace App\Observers\User;

use App\Jobs\User\UserLocation;
use App\Models\User\User;

class UserObserver
{
    public function saved(User $user)
    {
        rescue(function () use ($user) {
            $wasRecentlyCreated = $user->wasRecentlyCreated;
            if ($wasRecentlyCreated || $user->wasChanged('last_ip')) {
                dispatch(new UserLocation($user, $wasRecentlyCreated));
            }
        });
    }
}

4) 开启队列监听

$ php artisan queue:listen

Passport认证

实现用户注册后返回 Token, 以及删除无效 Token

1) 安装 & 配置

$ composer require laravel/passport:^9.4.0
$ php artisan migrate --path=./vendor/laravel/passport/database/migrations/
$ php artisan passport:keys
$ php artisan passport:client --personal
$ php artisan vendor:publish --tag=passport-config

# 安装报错
laravel/passport[v9.4.0, ..., 9.x-dev] require phpseclib/phpseclib ^2.0 -> found phpseclib/phpseclib
# 删除 laravel/telescope 包后再安装, 之后再重新安装 laravel/telescope
$ composer remove laravel/telescope --dev

2) .env 配置, 保存刚刚生成的「个人客户端」CLIENT_ID & CLIENT_SECRET

PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=jToNhXPoQVxVkBrdljCwk5oWzO7hroozHkqQ9Kja

3) 配置 app/Providers/AuthServiceProvider.php, 设置 TOKEN 有效期一周

...
use Laravel\Passport\Passport;
...
public function boot()
{
    ...
    Passport::personalAccessClientId(config('passport.personal_access_client.id'));
    Passport::personalAccessTokensExpireIn(now()->addWeek());
}
...

4) 编辑 app/Providers/EventServiceProvider.php 监听 Token 创建事件

...
protected $listen = [
    ...
    \Laravel\Passport\Events\AccessTokenCreated::class => [
        \App\Listeners\PassportAccessTokenCreated::class
    ]
];
...

5) 新建 app/Listeners/PassportAccessTokenCreated.php 删除过期 Token

<?php

namespace App\Listeners;

use Laravel\Passport\Events\AccessTokenCreated;
use Laravel\Passport\Token;

class PassportAccessTokenCreated
{
    public function handle(AccessTokenCreated $event)
    {
        Token::query()
            ->where('id', '<>', $event->tokenId)
            ->where('user_id', $event->userId)
            ->where('client_id', $event->clientId)
            ->where('revoked', 0)
            ->where('expires_at', '<=', now()->toDateTimeString())
            ->delete();
    }
}

6) 编辑 app/Models/User/User.php

...
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use Filterable, ModelTrait, HasApiTokens;
    ...
}

7) 编辑 app/Observers/User/UserObserver.php

...
public function saved(User $user)
{
    $user->setAttribute('token', $user->createToken($user->id)->accessToken);
    ...
}
...

8) 编辑 app/Http/Controllers/V1/User/UserController.php

...
public function register(UserRequest $request)
{
    ...
    return Response::success([
        'id' => $user->id,
        'token' => $user->token
    ]);
}
...

使用 Token 作为用户的登录凭证

1) 编辑 routes/api/user.php

<?php

Route::prefix('user')
    ->namespace('User')
    ->name('user.')
    ->group(function () {
        ...
        Route::middleware('auth:api')->group(function () {
            Route::get('mock', 'UserController@mock')->name('user.mock');
        });
    });

2) 编辑 app/Http/Controllers/V1/User/UserController.php

...
public function mock()
{
    dump(auth()->user());
}
...

3) 编辑 config/auth.php

return [
    ...
    'guards' => [
    ...
        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
            'hash' => false,
         ],
     ],
     'providers' => [
         'users' => [
             'driver' => 'eloquent',
             'model' => App\Models\User\User::class,
          ],
     ],
     ...
];

4) Postman 配置 Header

Authorization: Bearer {token}

4) GET 请求接口 /v1/user/mock, 可以看到用户的信息

5) 通过查看 SQL 日志, 发现 Passport 查询语句太多了, 安装 overtrue/laravel-passport-cache-token, 支持配置缓存方式,具体可查看包文档说明

$ composer require overtrue/laravel-passport-cache-token:^2.1

结语

文笔不好,还是直接发代码来的直接。
有不足的地方,欢迎各位大佬指出,谢谢~

资料

1) 底层原理

2) 架构

3) API 规范相关讨论

4) 表单场景验证

5) 模型相关

6) API 资源

7) 队列

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 6天前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 18

不错,时区设置'timezone' => 'PRC'是不是最好设置成Asia/Shanghai,PRC已经不是php的官方建议写法了,仅用于向后兼容

5天前 评论

RPC,应该改成 Asia/Shanghai ,比较科学

5天前 评论

写的很好啊,学习

1天前 评论

高,实在是高!不错,很好

1周前 评论

请教一下,开发时用Telescope,上线之后用的什么监控服务?

2天前 评论

大概瞄了一眼还不错

1周前 评论
Jianne

看来需要全部返回 200 状态码的人还蛮多 👀

Http 状态码和枚举 Enum 都成为可选配置项啦 :relaxed:

配置文件:github.com/Jiannei/laravel-respons...

  • error_code 设置成 200,即使错误和异常情况也可以返回 200 http 状态码;默认是 false。
  • enum 也成了可选项;如果没有多语言要求,可以直接指定 message,或者自行实现简单版 Enum
1周前 评论

@Jianne :joy:要是早一点,我就不用再整个包了。

1周前 评论
Jianne

@▍默然° 哈哈哈 文档没来及更新 :grin:

文档整理得不错,加油加油💪🏻

1周前 评论

@congcong Telescope 本身也可以在生产环境使用。另外,我觉得 Laravel 本身的异常处理已经挺好了,可以通过记录日志来监控,也可以通过日志级别来采用不同的处理方式(邮件、短信之类)

2天前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
文章
1
粉丝
4
喜欢
39
收藏
75
排名:1338
访问:965
私信
所有博文