Laravel 開發 API 心得

▍默然°發表於2021-04-30

前言

雖然開發了幾年,但一直沒有使用 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:models

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(UserService $userService)
    {
        $this->service = $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) 佇列

補充點

1) 關於 PhpStormLaravel 的路由跳轉 (WindowsCtrl + 滑鼠左鍵 點選路由跳轉至對應控制器),Demo 中無法跳轉問題

  • 編輯 routes/api/user.php
    <?php
        Route::group([
            'prefix' => 'user',
            'namespace' => 'User'
            'as' => 'user.',
        ], function () {
            ...
        });
  • 重新生成 ide-helper.php
    $ php artisan ide-helper:generate

2) 跨域

  • 安裝 fruitcake/laravel-cors
    $ composer require "fruitcake/laravel-cors:^2.0"
    $ php artisan vendor:publish --tag="cors"
  • 編輯 app/Http/Kernel.php
    ...
    protected $middleware = [
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        ...
    ];
    ...
  • 編輯 config/cors.php - 檢視更多配置
    'paths' => ['v1/*']

3) 新增模型 saveQuietly 方法 - 儲存給定的模型而不觸發任何事件 (Laravel 8.x 框架自帶)

  • 編輯 app/Models/ModelTrait.php
    ...
    /**
     * Save the model to the database without raising any events.
     *
     * @param array $options
     *
     * @return bool
     */
    public function saveQuietly(array $options = [])
    {
        return static::withoutEvents(function () use ($options) {
            return $this->save($options);
        });
    }
    ...

升級8.x

直接安裝 8.x 版本, 再按文件搭專案,相關包版本變化如下:

  • laravel/laravel:^6.2 to ^8.4.0

  • barryvdh/laravel-ide-helper:2.8.* to ^2.10

  • laravel/telescope:^3.0 to ^4.4

    # migrations 路徑變更
    php artisan migrate --path=./vendor/laravel/telescope/database/migrations/
  • overtrue/laravel-lang:~3.0 to ~5.0

    # 編輯 config/app.php
    
    # 與 3.0 版本不同, 這個無需替換服務提供者
    Illuminate\Translation\TranslationServiceProvider::class  
    
    # 配置語言
    'locale' => 'zh_CN'
  • tucker-eric/eloquentfilter:^2.4 to ^3.0

  • laravel/passport:^9.4.0 to ^10.1

    # 與 9.x 版本不同, 不需要在 `AuthServiceProvider` 配置 personalAccessClientId(), 且此方法在新版本的包中已移除
  • sevming/laravel-response:^1.0 to ^2.0

  • fruitcake/laravel-cors - 無需安裝,框架自帶

  • app/Models/ModelTrait => saveQuietly 不需要新增,框架自帶

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章