开发环境
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.php 到 app/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
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
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 目录下的路由文件
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
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
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
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
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
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
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
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
... | |
protected $listen = [ | |
\Illuminate\Database\Events\QueryExecuted::class => [ | |
\App\Listeners\QueryListener::class | |
], | |
]; | |
... |
用户注册
1) 新建「腾讯地图类」 app/Libraries/TencentMapLibrary.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
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
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
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
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
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
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
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
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
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
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) 关于 PhpStorm 中 Laravel 的路由跳转 (Windows 下 Ctrl + 鼠标左键 点击路由跳转至对应控制器),Demo 中无法跳转问题
编辑 routes/api/user.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 不需要添加,框架自带