顯示具有 laravel 標籤的文章。 顯示所有文章
顯示具有 laravel 標籤的文章。 顯示所有文章

2020年10月28日 星期三

laravel 6 Mix前端指南

環境

框架:laravel 6.6

安裝 laravel/ui 

$ composer require laravel/ui:^1.0 --dev
如果報錯:

In PackageManifest.php line 122:

  Undefined index: name


Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1

Installation failed, reverting ./composer.json and ./composer.lock to their original content.
原因:composer 2.0的問題,把composer 版本回退之前版本
# composer self-update
Updating to version 2.0.2 (stable channel).
   Downloading (100%)
Use composer self-update --rollback to return to version 1.8.4
# composer self-update --rollback
Rolling back to version 2019-02-11_10-52-10-1.8.4.
ps.  laravel 6必須安裝 laravel/ui:^1.0 --dev 的版本


生成vue基本腳手架

$ php artisan ui vue
Vue scaffolding installed successfully.
Please run "npm install && npm run dev" to compile your fresh scaffolding.
這個操作新增了:
resources/js/components/ExampleComponent.vue
resources/sass/_variables.scss
resources/sass/app.scss
修改了:
package.json
resources/js/app.js
resources/js/bootstrap.js
註冊vue和vue組件
+++ b/resources/js/app.js
+window.Vue = require('vue');
...
+Vue.component('example-component', require('./components/ExampleComponent.vue').default);
...
+const app = new Vue({
+    el: '#app',
+});

$ npm install
$ npm run dev
...
 ERROR  Failed to compile with 2 errors

 error  in ./resources/sass/app.scss

Module build failed (from ./node_modules/css-loader/index.js):
ModuleBuildError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
ValidationError: Invalid options object. Sass Loader has been initialized using an options object that does not match the API schema.
 - options has an unknown property 'outputStyle'. These properties are valid:
   object { implementation?, sassOptions?, prependData?, sourceMap?, webpackImporter? }
    at validate (/var/www/html/project-z/coolapp/node_modules/schema-utils/dist/validate.js:98:11)
    at Object.loader (/var/www/html/project-z/coolapp/node_modules/sass-loader/dist/index.js:36:28)
    at /var/www/html/project-z/coolapp/node_modules/webpack/lib/NormalModule.js:316:20
    at /var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:367:11
    at /var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:233:18
    at runSyncOrAsync (/var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:143:3)
    at iterateNormalLoaders (/var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:232:2)
    at /var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:205:4
    at /var/www/html/project-z/coolapp/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:85:15
    at processTicksAndRejections (internal/process/task_queues.js:79:11)

 error  in ./resources/sass/app.scss

Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
ValidationError: Invalid options object. Sass Loader has been initialized using an options object that does not match the API schema.
 - options has an unknown property 'outputStyle'. These properties are valid:
   object { implementation?, sassOptions?, prependData?, sourceMap?, webpackImporter? }
    at validate (/var/www/html/project-z/coolapp/node_modules/schema-utils/dist/validate.js:98:11)
    at Object.loader (/var/www/html/project-z/coolapp/node_modules/sass-loader/dist/index.js:36:28)

 @ ./resources/sass/app.scss 2:14-253

原因:

 php artisan ui vue  改了 package.json ,使用了 "sass-loader": "^8.0.0", ,使得下一個操作 npm install 安裝了sass-loader 8.0.0

解法:

移除sass-loader 8.0.0 改裝7.1.0
$ npm uninstall --save-dev sass-loader
$ npm install --save-dev sass-loader@7.1.0

再執行
$ npm run dev

生成 登錄/註冊 腳手架

$ php artisan ui vue --auth
這個操作新增了:
app/Http/Controllers/HomeController.php
resources/views/auth/login.blade.php
resources/views/auth/passwords/confirm.blade.php
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
resources/views/auth/register.blade.php
resources/views/auth/verify.blade.php
resources/views/home.blade.php
resources/views/layouts/app.blade.php
修改了:
routes/web.php

登錄頁

http://app.test:5566/login  使用users 表的 email 和password 登錄(passport API也是這個方式登錄的)

歡迎頁

http://app.test:5566/  resources/views/welcome.blade.php:68 上方新增了Login和Register按鈕

編寫Vue 組件

diff --git a/resources/js/components/ExampleComponent.vue b/resources/js/components/ExampleComponent.vue
index 3fb9f9aa..944c6973 100644
--- a/resources/js/components/ExampleComponent.vue
+++ b/resources/js/components/ExampleComponent.vue
@@ -3,7 +3,7 @@
         <div class="row justify-content-center">
             <div class="col-md-8">
                 <div class="card">
-                    <div class="card-header">Example Component</div>
+                    <div class="card-header">Example Component Bear</div>

                     <div class="card-body">
                         I'm an example component.
diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php
index 05dfca92..73075388 100644
--- a/resources/views/home.blade.php
+++ b/resources/views/home.blade.php
@@ -1,6 +1,7 @@
 @extends('layouts.app')

 @section('content')
+<example-component></example-component>
 <div class="container">
     <div class="row justify-content-center">
         <div class="col-md-8">

監視和自動重編譯發生變化的組件

在hyper-v 上執行,不要在windows 10 上執行,PHPStorm Deployment 要忽略 node_modules 目錄上傳
$ npm run watch-poll 

新增組件後,需要重新編譯 npm run dev , 或重新執行 npm run watch-poll  


參考資料

https://stackoverflow.com/a/61197256  Laravel PackageManifest.php: Undefined index: name
https://stackoverflow.com/a/60513876  Sass Loader Error: Invalid options object that does not match the API schema







2019年12月13日 星期五

laravel 6 心得

環境

laradock php7.3
laravel 6.6.0
mysql 8.0.16 ( on Windows 10 )

Passport

參考資料

https://learnku.com/docs/laravel/6.x/api-authentication/5429  API 认证
官方強烈建議使用 Laravel Passport 來實現提供API身份驗證
https://learnku.com/laravel/t/22586  使用 Laravel Passport 处理 API 认证
https://medium.com/techcompose/create-rest-api-in-laravel-with-authentication-using-passport-133a1678a876  Create REST API in Laravel with authentication using Passport(原文)

安裝

$ composer require laravel/passport

跑Migration

設定MySQL server用戶權限(以Navicat為例)

使用者 => 新增使用者
在MySQL 8.0,php7.3 插件必須選擇 mysql_native_password ,否則跑migrate時會報錯:
SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client
https://stackoverflow.com/a/50027581  php mysqli_connect: authentication method unknown to the client [caching_sha2_password]
https://mysqlserverteam.com/upgrading-to-mysql-8-0-default-authentication-plugin-considerations/  Upgrading to MySQL 8.0 : Default Authentication Plugin Considerations
因為當下的 php mysqli extension 不支持新的 caching_sha2 authentication 功能,除非升級php 7.4
伺服器權限:全部授予 => 儲存

建立資料庫、設置.env MySQL連線

略

運行數據庫遷移

$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (1.05 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (1.2 seconds)
Migrating: 2016_06_01_000001_create_oauth_auth_codes_table
Migrated:  2016_06_01_000001_create_oauth_auth_codes_table (1.83 seconds)
Migrating: 2016_06_01_000002_create_oauth_access_tokens_table
Migrated:  2016_06_01_000002_create_oauth_access_tokens_table (1.59 seconds)
Migrating: 2016_06_01_000003_create_oauth_refresh_tokens_table
Migrated:  2016_06_01_000003_create_oauth_refresh_tokens_table (1.37 seconds)
Migrating: 2016_06_01_000004_create_oauth_clients_table
Migrated:  2016_06_01_000004_create_oauth_clients_table (1.15 seconds)
Migrating: 2016_06_01_000005_create_oauth_personal_access_clients_table
Migrated:  2016_06_01_000005_create_oauth_personal_access_clients_table (0.69 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.55 seconds)

新增了以下資料表:
failed_jobs
migrations
oauth_access_tokens
oauth_auth_codes
oauth_clients
oauth_personal_access_clients
oauth_refresh_tokens
password_resets
users

生成密鑰

$ php artisan passport:install
Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client secret: xLFj4jeEdfNJtxcwPUqHojrU0d6F6D0kO1x2yyxx
Password grant client created successfully.
Client ID: 2
Client secret: KUwHMd5XxmrikxqxBFzJDZnNTDcW9kXZY1SKK2T7

密鑰保存在 oauth_clients 表的 Laravel Personal Access Client 和 Laravel Password Grant Client 中

修改代碼


diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php
index 30490683..90ce09b8 100644
--- a/app/Providers/AuthServiceProvider.php
+++ b/app/Providers/AuthServiceProvider.php
@@ -4,6 +4,7 @@ namespace App\Providers;

 use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
 use Illuminate\Support\Facades\Gate;
+use Laravel\Passport\Passport;

 class AuthServiceProvider extends ServiceProvider
 {
@@ -25,6 +26,6 @@ class AuthServiceProvider extends ServiceProvider
     {
         $this->registerPolicies();

-        //
+        Passport::routes();
     }
 }
diff --git a/app/User.php b/app/User.php
index e79dab7f..45db5e3e 100644
--- a/app/User.php
+++ b/app/User.php
@@ -5,10 +5,11 @@ namespace App;
 use Illuminate\Contracts\Auth\MustVerifyEmail;
 use Illuminate\Foundation\Auth\User as Authenticatable;
 use Illuminate\Notifications\Notifiable;
+use Laravel\Passport\HasApiTokens;

 class User extends Authenticatable
 {
-    use Notifiable;
+    use Notifiable, HasApiTokens;

     /**
      * The attributes that are mass assignable.
diff --git a/config/auth.php b/config/auth.php
index aaf982bc..04c6eec2 100644
--- a/config/auth.php
+++ b/config/auth.php
@@ -42,7 +42,7 @@ return [
         ],

         'api' => [
-            'driver' => 'token',
+            'driver' => 'passport',
             'provider' => 'users',
             'hash' => false,
         ],
diff --git a/routes/api.php b/routes/api.php
index c641ca5e..254958fe 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -16,3 +16,17 @@ use Illuminate\Http\Request;
 Route::middleware('auth:api')->get('/user', function (Request $request) {
     return $request->user();
 });
+
+Route::group([
+    'prefix' => 'auth'
+], function () {
+    Route::post('login', 'AuthController@login');
+    Route::post('signup', 'AuthController@signup');
+
+    Route::group([
+      'middleware' => 'auth:api'
+    ], function() {
+        Route::get('logout', 'AuthController@logout');
+        Route::get('user', 'AuthController@user');
+    });
+});
s

  1. 將 Laravel\Passport\HasApiTokens 加入 App\User 模型
  2. AuthServiceProvider boot()中調用Passport::routes()
  3. config/auth.php guards.api.driver 設為 passport
  4. routes/api.php 中添加API路由

創建控制器

$ php artisan make:controller AuthController
app/Http/Controllers/AuthController.php 加入
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\User;

class AuthController extends Controller
{
    /**
     * Create user
     *
     * @param  [string] name
     * @param  [string] email
     * @param  [string] password
     * @param  [string] password_confirmation
     * @return [string] message
     */
    public function signup(Request $request)
    {
        $request->validate([
            'name' => 'required|string',
            'email' => 'required|string|email|unique:users',
            'password' => 'required|string|confirmed'
        ]);

        $user = new User([
            'name' => $request->name,
            'email' => $request->email,
            'password' => bcrypt($request->password)
        ]);

        $user->save();

        return response()->json([
            'message' => 'Successfully created user!'
        ], 201);
    }

    /**
     * Login user and create token
     *
     * @param  [string] email
     * @param  [string] password
     * @param  [boolean] remember_me
     * @return [string] access_token
     * @return [string] token_type
     * @return [string] expires_at
     */
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string',
            'remember_me' => 'boolean'
        ]);

        $credentials = request(['email', 'password']);

        if(!Auth::attempt($credentials))
            return response()->json([
                'message' => 'Unauthorized'
            ], 401);

        $user = $request->user();

        $tokenResult = $user->createToken('Personal Access Token');
        $token = $tokenResult->token;

        if ($request->remember_me)
            $token->expires_at = Carbon::now()->addWeeks(1);

        $token->save();

        return response()->json([
            'access_token' => $tokenResult->accessToken,
            'token_type' => 'Bearer',
            'expires_at' => Carbon::parse(
                $tokenResult->token->expires_at
            )->toDateTimeString()
        ]);
    }

    /**
     * Logout user (Revoke the token)
     *
     * @return [string] message
     */
    public function logout(Request $request)
    {
        $request->user()->token()->revoke();

        return response()->json([
            'message' => 'Successfully logged out'
        ]);
    }

    /**
     * Get the authenticated User
     *
     * @return [json] user object
     */
    public function user(Request $request)
    {
        return response()->json($request->user());
    }
}

s

測試(使用Postman)

Header加入
Content-Type:application/json
X-Requested-With:XMLHttpRequest

註冊

http://app.test:5566/api/auth/signup

登錄

http://app.test:5566/api/auth/login

用戶

將登錄拿到的 access_token 放到 Bearer Token
http://app.test:5566/api/auth/user

登出

http://app.test:5566/api/auth/logout


底層如何運作?

登錄

Auth::attempt($credentials);

$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
SessionGuard.php:349, Illuminate\Auth\SessionGuard->attempt()
return $query->first();
EloquentUserProvider.php:131, Illuminate\Auth\EloquentUserProvider->retrieveByCredentials()
=> 沒驗證密碼,直接取users對象
if ($this->hasValidCredentials($user, $credentials)) {
SessionGuard.php:354, Illuminate\Auth\SessionGuard->attempt()
return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
SessionGuard.php:377, Illuminate\Auth\SessionGuard->hasValidCredentials()
return $this->hasher->check($plain, $user->getAuthPassword());
EloquentUserProvider.php:145, Illuminate\Auth\EloquentUserProvider->validateCredentials()
=> 檢查密碼
$this->login($user, $remember);
SessionGuard.php:355, Illuminate\Auth\SessionGuard->attempt()
$this->setUser($user);
SessionGuard.php:423, Illuminate\Auth\SessionGuard->login()
=>設置用戶

$user = $request->user();

return $this->user;
SessionGuard.php:123, Illuminate\Auth\SessionGuard->user()

$tokenResult = $user->createToken('Personal Access Token');

return Container::getInstance()->make(PersonalAccessTokenFactory::class)->make(
    $this->getKey(), $name, $scopes
);
HasApiTokens.php:67, App\User->createToken()
$this->createRequest($this->clients->personalAccessClient(), $userId, $scopes)
PersonalAccessTokenFactory.php:71, Laravel\Passport\PersonalAccessTokenFactory->make()
return json_decode($this->server->respondToAccessTokenRequest(
    $request, new Response
)->getBody()->__toString(), true);
PersonalAccessTokenFactory.php:114, Laravel\Passport\PersonalAccessTokenFactory->dispatchRequestToAuthorizationServer()
$tokenResponse = $grantType->respondToAccessTokenRequest(
    $request,
    $this->getResponseType(),
    $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()]
);
AuthorizationServer.php:198, League\OAuth2\Server\AuthorizationServer->respondToAccessTokenRequest()
// Issue and persist access token
$accessToken = $this->issueAccessToken(
    $accessTokenTTL, $client,
    $this->getRequestParameter('user_id', $request), $scopes
);
PersonalAccessGrant.php:29, Laravel\Passport\Bridge\PersonalAccessGrant->respondToAccessTokenRequest()
$accessToken->setIdentifier($this->generateUniqueIdentifier());
AbstractGrant.php:440, Laravel\Passport\Bridge\PersonalAccessGrant->issueAccessToken()
return bin2hex(random_bytes($length));
AbstractGrant.php:550, Laravel\Passport\Bridge\PersonalAccessGrant->generateUniqueIdentifier()
=> 將存入 oauth_access_tokens.id 
$this->accessTokenRepository->persistNewAccessToken($accessToken);
AbstractGrant.php:442, Laravel\Passport\Bridge\PersonalAccessGrant->issueAccessToken()
$this->tokenRepository->create([
    'id' => $accessTokenEntity->getIdentifier(),
    'user_id' => $accessTokenEntity->getUserIdentifier(),
    'client_id' => $accessTokenEntity->getClient()->getIdentifier(),
    'scopes' => $this->scopesToArray($accessTokenEntity->getScopes()),
    'revoked' => false,
    'created_at' => new DateTime,
    'updated_at' => new DateTime,
    'expires_at' => $accessTokenEntity->getExpiryDateTime(),
]);
AccessTokenRepository.php:57, Laravel\Passport\Bridge\AccessTokenRepository->persistNewAccessToken()
=> 存入 oauth_access_tokens 表
$token = tap($this->findAccessToken($response), function ($token) use ($userId, $name) {
    $this->tokens->save($token->forceFill([
        'user_id' => $userId,
        'name' => $name,
    ]));
});
PersonalAccessTokenFactory.php:74, Laravel\Passport\PersonalAccessTokenFactory->make()
$response
‌array (
  'token_type' => 'Bearer',
  'expires_in' => 31535998,
  'access_token' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiZmE0OGEyNzYxN2FkNjlhZDlkMTBlZDkzZmExMzFkZTc4MDU2ODBjYzQ0YjYwMDEyNjIwM2E1MTE1ZmExYTFhZTM5ZmEyMDBhZjIzZTNlNTgiLCJpYXQiOjE2MDM3NjQxMDEsIm5iZiI6MTYwMzc2NDEwMSwiZXhwIjoxNjM1MzAwMDk5LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.fSBAzUlHwfwctJBjnM_kiOn48Imp7gcqrMkfJeVdXtE8tFmcNGiFeT3DvtK_0zYCF7qWJM5ZUsOcOEjBhttVgsMPEOjv9Cb7kVdipE2GUX4d4x6Bhw1xCrmYKbNFw4rY3G9J2tySuLZR85ZmIYGf0LvKNkwWvdeS3SZJeh00FAhN3g9ssVgw5NjxzV9T65G5_X-9yDg6GzRQhWNU_GoX1qYhet8dUM1SQTtaTorpjVF5Xk0bEFXyjqkQfiRHiQxBb5YKIuRZu_653Gw8gs1HEv2252CWh7YsGY2iM-5kH3PPz9DpE9GYelC0ULWcWWA7uYknJ7IjXfDhgQKjV_RYpmOIC-EpidazuFd4x1T4ZPIn-ZTjh0wEsPdbReoXagValXByCmykMqAEP9-qPQVG05w_qWoHW_OmV8spnOLz-xZnwmVirughgQiJihN3z1fmnYZTrrsuMZ5pzeG3LNeKDDx1KrYV6m-42JREcHeXVWfl0RgMyCh4d4CkjksNAVB_t756aA4eAzgldGIVtVMuAtkLroS6rtkMQqVVkSfzhjix9fsRN45-7ZKbLCbSi665rl0Bs_XZ0MDs-9LJXkQqSe5DEvvlVoSmLjxUaGkXWfnbkLck2NIAhR3OOYcDq2TEiS749qzaaPEf_nEL17M4mHuDdI4o24NsFBt-gZ5CcHM',
)
return $this->tokens->find(
    $this->jwt->parse($response['access_token'])->getClaim('jti')
);
PersonalAccessTokenFactory.php:126, Laravel\Passport\PersonalAccessTokenFactory->findAccessToken()
$this->tokens Type:
\Laravel\Passport\TokenRepository
$this->jwt Type:
\Lcobucci\JWT\Parser
=> 由此方法可以用\Laravel\Passport\TokenRepository和 \Lcobucci\JWT\Parser 從 access_token 找回 oauth_access_tokens 

用戶

如何用access_token身份驗證?

routes/api.php
Route::group([
  'middleware' => 'auth:api'
], function() {
    Route::get('user', 'AuthController@user');
});
app/Http/Kernel.php
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
];
Stack:
$this->authenticate($request, $guards);
Authenticate.php:41, App\Http\Middleware\Authenticate->handle()
$this->auth->guard($guard)->check()
Authenticate.php:62, App\Http\Middleware\Authenticate->authenticate()
return ! is_null($this->user());
GuardHelpers.php:60, Illuminate\Auth\RequestGuard->check()
return $this->user = call_user_func(
    $this->callback, $this->request, $this->getProvider()
);
RequestGuard.php:58, Illuminate\Auth\RequestGuard->user()
return (new TokenGuard(
    $this->app->make(ResourceServer::class),
    Auth::createUserProvider($config['provider']),
    $this->app->make(TokenRepository::class),
    $this->app->make(ClientRepository::class),
    $this->app->make('encrypter')
))->user($request);
PassportServiceProvider.php:283, Laravel\Passport\PassportServiceProvider->Laravel\Passport\{closure:/var/www/coolapp/vendor/laravel/passport/src/PassportServiceProvider.php:276-284}()
return $this->authenticateViaBearerToken($request);
TokenGuard.php:94, Laravel\Passport\Guards\TokenGuard->user()
if (! $psr = $this->getPsrRequestViaBearerToken($request)) {
TokenGuard.php:131, Laravel\Passport\Guards\TokenGuard->authenticateViaBearerToken()
return $this->server->validateAuthenticatedRequest($psr);
TokenGuard.php:184, Laravel\Passport\Guards\TokenGuard->getPsrRequestViaBearerToken()
return $this->getAuthorizationValidator()->validateAuthorization($request);
ResourceServer.php:84, League\OAuth2\Server\ResourceServer->validateAuthenticatedRequest()
if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) {
BearerTokenValidator.php:72, League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator->validateAuthorization()
$this->publicKey->getKeyPath()
‌file:///var/www/coolapp/storage/oauth-public.key
=> 使用 storage/oauth-public.key 驗證 access_token 是否合法
// Ensure access token hasn't expired
$data = new ValidationData();
$data->setCurrentTime(time());
if ($token->validate($data) === false) {
BearerTokenValidator.php:83, League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator->validateAuthorization()
=> 檢查token有無過期
// Check if token has been revoked
if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) {
BearerTokenValidator.php:88, League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator->validateAuthorization()
=> 檢查token是否被revoked

密碼授權令牌(grant_type=password)

curl --location --request POST 'http://app.test:5566/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=2' \
--data-urlencode 'client_secret=client_secret' \
--data-urlencode 'username=username@github.com' \
--data-urlencode 'password=password' \
--data-urlencode 'scope='

返回錯誤:

Symfony\Component\Debug\Exception\FatalThrowableError: Class 'Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory' not found in file /var/www/coolapp/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php on line 131

原因

https://github.com/sfelix-martins/passport-multiauth/issues/124#issuecomment-596418813  Class 'Symfony\\Bridge\\PsrHttpMessage\\Factory\\DiactorosFactory' not found
這個問題將在 laravel 6.18+ / 7.x 修正

解法:

更新laravel
$ composer update



刷新令牌(grant_type=refresh_token)

https://stackoverflow.com/a/59334090   How do I get a refresh token in Laravel Passport?

使用【密碼授權令牌】獲取 access_token 和 refresh_token 

{
    "token_type": "Bearer",
    "expires_in": 31536000,
    "access_token": "access_token.xxx.xxx",
    "refresh_token": "refresh_token_xxx"
}

使用 refresh_token 獲取【刷新令牌】

curl --location --request POST 'http://app.test:5566/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=the-refresh-token' \
--data-urlencode 'client_id=2' \
--data-urlencode 'client_secret=client_secret' \
--data-urlencode 'scope='
(原本的會失效,oauth_access_tokens.revoked = 1)

將授權碼轉換為訪問令牌

準備兩個專案

http://app.test:5566/  => laravel 6 ,有安裝passport
http://app3.test:5566/  => laravel 8,無安裝passport

前端快速上手

$ php artisan vendor:publish --tag=passport-components

註冊組件

resources/js/app.js
Vue.component(
    'passport-clients',
    require('./components/passport/Clients.vue').default
);

Vue.component(
    'passport-authorized-clients',
    require('./components/passport/AuthorizedClients.vue').default
);

Vue.component(
    'passport-personal-access-tokens',
    require('./components/passport/PersonalAccessTokens.vue').default
);
在view  resources/views/home.blade.php 中使用
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>

在app.test新增用戶的oauth_clients

ps. 綠色那塊是 <passport-clients></passport-clients>  




原理

http://app3.test:5566/ 透過 http://app.test:5566/ 登錄後的授權,在 http://app3.test:5566/ 上使用用戶在app.test生成的oauth_clients(Client ID = 3)獲取 http://app.test:5566/  的token,進而獲取 http://app.test:5566/ 上的資料


在laradock的 workspace和php-fpm 容器新增hosts


diff --git a/docker-compose.yml b/docker-compose.yml
index bc737c0..62152fb 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -165,6 +165,7 @@ services:
         - ./php-worker/supervisord.d:/etc/supervisord.d
       extra_hosts:
         - "dockerhost:${DOCKER_HOST_IP}"
+        - "app.test:192.168.1.9"
       ports:
         - "${WORKSPACE_SSH_PORT}:22"
         - "${WORKSPACE_BROWSERSYNC_HOST_PORT}:3000"
@@ -264,6 +265,7 @@ services:
         - "9000"
       extra_hosts:
         - "dockerhost:${DOCKER_HOST_IP}"
+        - "app.test:192.168.1.9"
       environment:
         - PHP_IDE_CONFIG=${PHP_IDE_CONFIG}
         - DOCKER_HOST=tcp://docker-in-docker:2376
app.test:192.168.1.9 => hyper-v的IP:192.168.1.9,非填laradock容器IP

實作

https://github.com/laravel/passport/issues/221#issuecomment-269828055  API Authentication Error: {"error":"invalid_client","message":"Client authentication failed"}
http://app3.test:5566/ (要改在laravel 8專案,非安裝passport的 http://app.test:5566/) 的 routes/web.php 新增 

  
Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => '3',
        'redirect_uri' => 'http://app3.test:5566/callback',
        'response_type' => 'code',
        'scope' => '',
    ]);

    return redirect('http://app.test:5566/oauth/authorize?'.$query);
});

Route::get('/callback', function (\Illuminate\Http\Request $request) {
    $http = new GuzzleHttp\Client;

    $response = $http->post('http://app.test:5566/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => '3',
            'client_secret' => 'rR2P1HBeysX5NO9noSfMc3BNpEIQyANExxx',
            'redirect_uri' => 'http://app3.test:5566/callback',
            'code' => $request->code,
        ],
    ]);

    return json_decode((string) $response->getBody(), true);
});
  
授權後會轉跳到 http://app3.test:5566/callback?code=def50200ff1e6e1d070750700d964b58c208bcfc69ad68e12c16340c0bf80550de3f8c8c68609f0414365e55a2272a69f99d6a23226ccc01b58079cb3f17d8f800f6c4ef27563da3e98082c53d5abc9b4d1f3d4a6802274b4017fc91a553a297f7ce97b3e81d70c0033642328d0e16cf364edeefad089a476e62ca687d545719f72af9222eb0db3ccb825fc58aefc3bd1edff579fde203704975d66c7984d8873203e610b572c02e6254bffa79417897025fed73a890034300fd91e298bea302e3026f2e9af610d8b2c7848b996acb46574d2c0b75af3c1eca107ad50f56fa25a13a87361f0777ec8ae25e67f7a96751f95047eac0142807406c0cc1a2f61dfeb8eee7a246e89fd74c2c742d3e571a1f6cc7dbac5a1327c6dcde8597028dec48f58c67ee5fa1e3ba48b2326a3e9758e8f1561716e2579bca05f78c3ea0a6801aada0f1112cb0b35d86f838dc0ec85d4e88a4679b6d3cda388af5b0dd2547  
以在 http://app3.test:5566/ 上獲取 該用戶(user_id=1,client_id=3) 的token 。
=》oauth_clients會新增client_id=3的token
app3.test 的 /redirect 路由中的 redirect_uri 必須和 app.test 系統上oauth_clients 該token的oauth_clients.redirect 一致,否則會驗證失敗












2019年3月13日 星期三

hyper-v 安裝CentOS 7.6

Windows 10 Pro 內建 Hyper-v
安裝 CentOS 7.6 + PHP 7.3 + nginx + laravel 5.8

安裝Hyper-v 和 WSL( Windows Subsystem for Linux ) - 略
ps. WSL可以在Microsoft Store 搜尋Linux 下載不同版本Linux(Kali、ubuntu、Debian...)等Subsystem
ps. Hyper-v 和安卓的夜神模擬器nox無法同時執行,Windows會報錯SYSTEM_SERVICE_EXCEPTION 藍色畫面
https://www.reddit.com/r/windows/comments/9wdmcc/help_bsod_when_i_start_nox_player_hyperv_enabled/
NOX cannot run when Hyper-V is enabled.
ps. BlueStacks 和 Hyper-v 也無法同時執行


該使用WSL還是Hyper-v?
結論:你不應該使用WSL
1. Microsoft did not design or build WSL for production workloads.
微軟設計WSL不是為了生產環境
2. Running a virtual machine (VM) can be more efficient.
虛擬機更高效
3. 4. 5. 略

Windows 10 啟用hyper-v

https://www.itread01.com/content/1550134651.html  WIN10 Hyper-V 安裝 CentOS 7
https://dotblogs.com.tw/gelis/2010/07/30/16892  開啟Hyper-V的內部虛擬網路

下載Minimal版本

開啟hyper-v 功能

設定 =》 應用程式 =》 應用程式與功能 =》 選用功能 =》 更多Windows功能 =》 開啟hyper-v
(開啟後需重開機)

新增虛擬交換器

hyper-v 管理員 =》 虛擬交換器管理員 =》 新虛擬網路交換器 =》 新增【外部網路】,選擇有線網卡


虛擬機器 =》 設定 =》 網路界面卡 =》 選擇剛剛新增的【外部網路】虛擬交換器

新裝的機器要有網路

開機自動啟動eth0網路
# vi /etc/sysconfig/network-scripts/ifcfg-eth0
ONBOOT=no
ONBOOT=yes


hyper-v 虛擬交換器 使用「外部網路」,(ThinkPad T460)不要使用無線網卡,使用網路線連接區域連線,不然虛擬機和win 10 hosts會同一個內網ip,win 10 SSH連不上虛擬機

Hyper-v 儲存後即使重開Windows 10 ,再重開虛擬機器,工作狀態還在,等於沒有重開虛擬機

安裝完CentOS 7.6後


安裝一些常用package
# yum install vim epel-release screen git bash-completion telnet
裝完bash-completion後讓root可以正常使用
# source /etc/profile.d/bash_completion.sh
查裝什麼package才能用netstat
# yum provides */netstat
安裝net-tools以使用netstat
# yum install net-tools

升級git

因為yum安裝的git版本是1.8.3.1 ,用以下方法安裝2.x 最新版本的git

移除git

# yum -y remove git

新增 End Point 倉庫

# yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.9-1.x86_64.rpm
然後安裝2.x版本的git
# yum install git

檢查

# git --version
git version 2.30.1

End Point 倉庫過期造成yum無法更新

# yum update
...
https://packages.endpoint.com/rhel/7/os/x86_64/repodata/repomd.xml: [Errno 14] curl#6 - "Could not resolve host: packages.endp
oint.com; Unknown error"

解法

# yum-config-manager --disable endpoint

安裝php、php-fpm、nginx

安裝remi
# yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
如果你要最新的php 7.3.x,必須這樣做
# yum --enablerepo=remi,remi-php73 update -y
查remi的版本
# yum info remi-release
開啟remi-php73
# yum-config-manager --enable remi-php73
安裝php7.3
# yum install php73-php
使php能夠呼叫到php 7.3 bash ( 直接加到.bashrc 可以自動enable )
# source /opt/remi/php73/enable
查php版本
# php -v
PHP 7.3.3 (cli) (built: Mar  5 2019 13:50:38) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.3, Copyright (c) 1998-2018 Zend Technologies
安裝 php 7.3的php-fpm
# yum install php73-php-fpm
安裝nginx
# yum install nginx
啟動php-fpm
# systemctl start php73-php-fpm
查php-fpm狀態
# systemctl status php73-php-fpm
啟動nginx
# systemctl start nginx
查nginx 開機是否自動啟動
# systemctl is-enabled nginx
disabled
開機自動啟動nginx、php-fpm
# systemctl enable nginx
# systemctl enable php73-php-fpm.service

訪問網頁不通時,打開80 port
# firewall-cmd --zone=public --add-port=80/tcp --permanent
success
# firewall-cmd --reload
success
檢查配置是否生效
# iptables-save | grep 80
-A IN_public_allow -p tcp -m tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT

關閉端口

https://docs.fedoraproject.org/en-US/quick-docs/firewalld/  Using firewalld
# firewall-cmd --remove-port=port-number/port-type
# firewall-cmd --runtime-to-permanent
如:
# firewall-cmd --remove-port=80/tcp
# firewall-cmd --runtime-to-permanent

查當前zone開放的端口

# firewall-cmd --list-ports


下載composer-steup.php
# php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
# ls
anaconda-ks.cfg  composer-setup.php
# php -r "if (hash_file('sha384', 'composer-setup.php') === '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
Installer verified
安裝composer
# php composer-setup.php
All settings correct for using Composer
Downloading...

Composer (version 1.8.4) successfully installed to: /root/composer.phar
Use it: php composer.phar
# ls
anaconda-ks.cfg  composer.phar  composer-setup.php
刪除 composer-setup.php
# php -r "unlink('composer-setup.php');"
讓composer方便呼叫
# mv composer.phar /usr/local/bin/composer
# which composer
/usr/local/bin/composer

更新composer

$ composer --version
Composer version 1.7.2
$ composer self-update
Updating to version 1.8.5 (stable channel).
   Downloading (100%)
Use composer self-update --rollback to return to version 1.7.2

加速composer

$ composer global require hirak/prestissimo
...
    Failed to download hirak/prestissimo from dist: The zip extension and unzip command are both missing, skipping.
Your command-line PHP is using multiple ini files. Run `php --ini` to show them.
    Now trying to download from source
解決上面黃底的錯誤
# yum install zip unzip php73-php-pecl-zip

安裝 https://laravel.com/docs/5.8/installation  上laravel 5.8 必裝的php extension
檢查php extension有無安裝
# php -m | grep mbstring

安裝laravel 5.8需要的php extension

# yum install php73-php-pdo
# yum install php73-php-mbstring
# yum install php73-php-xml
# yum install php73-php-bcmath

安裝Xdebug

# yum install php73-php-pecl-xdebug

xdebug.ini報 invalid ELF header 錯誤

$ php --ini | grep xdebug
Failed loading /etc/opt/remi/php73/php.d/15-xdebug.ini:  /etc/opt/remi/php73/php.d/15-xdebug.ini: invalid ELF header
Additional .ini files parsed:      /etc/opt/remi/php73/php.d/15-xdebug.ini,
解法:
重新安裝php73-php-common
# yum remove php73-php-common
# yum install php73-php php73-php-fpm php73-php-pdo php73-php-mbstring php73-php-xml php73-php-bcmath php73-php-pecl-xdebug 
當然 php73-php-fpm 需重啟和重新enable
# systemctl start php73-php-fpm
# systemctl enable php73-php-fpm.service

讓XDebug  CLI 生效

.bashrc 加入
export XDEBUG_CONFIG="remote_host=192.168.1.7 idekey=PHPSTORM"
export PHP_IDE_CONFIG="serverName=localhost"
/etc/opt/remi/php73/php.d/15-xdebug.ini 修改:
xdebug.remote_enable = true


安裝 php-process

如果你要用workerman需要安裝php-process,否則啟動時會報錯
Uncaught Error: Call to undefined function posix_getpid()
# yum install php73-php-process

安裝memcached擴展

# yum install php73-php-pecl-memcached

安裝pdo_mysql擴展

# yum install php73-php-pecl-mysql

yum安裝時404報錯

# yum install php73-php-pecl-memcached
...
http://repo.mongodb.org/yum/redhat/7/mongodb-org/4.2/x86_64/repodata/repomd.xml: [Errno -1] Error importing repomd.xml for MongoDB: Damaged repomd.xml file
Trying other mirror.
http://mirror.pregi.net/centos/7.7.1908/os/x86_64/repodata/repomd.xml: [Errno 14] HTTP Error 404 - Not Found
Trying other mirror.
...

解法

# yum clean all --enablerepo=*
Loaded plugins: fastestmirror
Cleaning repos: C7.0.1406-base C7.0.1406-centosplus C7.0.1406-extras ...
...
Cleaning up list of fastest mirrors
必須清除yum緩存。這邊和IP無關,使用 proxychains4 掛代理IP執行無效

避免出現Memcached::get(): could not unserialize value, no igbinary support的錯誤

memcached有個memcached.serializer,預設採用igbinary。但是,當主機上沒有安裝igbinary時,則會改用php模式(standard PHP serializer)。如果有台主機沒有安裝Igbinary,且該主機的程式由memcache取回資料時為Igbinary格式,因無法使用Igbinary而發生錯誤。所以統一改為使用php模式。

檢查當前PHP CLI的memcached.serializer是什麼值:

# php -r "echo ini_get('memcached.serializer');"
igbinary
或
# php -i | grep memcached.serializer
memcached.serializer => igbinary => igbinary

查配置文件路徑

# php --ini | grep memcached
/etc/opt/remi/php73/php.d/50-memcached.ini,

修改 /etc/opt/remi/php73/php.d/50-memcached.ini 的 memcached.serializer配置為php

memcached.serializer = "php"

檢查修改後的結果(直接生效)

# php -r "echo ini_get('memcached.serializer');"
php
# php -i | grep memcached.serializer
memcached.serializer => php => php


安裝 laravel 5.8

$ composer create-project --prefer-dist laravel/laravel blog

配置nginx

nginx.conf 將server 改成以下
server {
    listen       80;
    server_name  localhost;
    root /var/www/html/blog/public;
    index index.php index.html index.htm;
    location / {
    #   autoindex on; // 如果想要開啟顯示資夾目錄的話加入這條
    #                  // 建議只在debug階段開啟
        try_files $uri $uri/ /index.php?$query_string;
    }
    #error_page  404              /404.html;
    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    location ~ \.php$ {
        root /var/www/html/blog/public;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        include        fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    }
}
如果前一步驟沒配好,訪問網頁會直接下載index.php 文件
https://stackoverflow.com/questions/25591040/nginx-serves-php-files-as-downloads-instead-of-executing-them/41838767#41838767
I had similar problem which was resolved by emptying the browser cache (also worked fine with different browser).
調好後要清空瀏覽器緩存


當storage 為777時,仍然報錯 storage/logs/laravel.log Permission denied
這個是 selinux 的問題
關掉selinux以證明這個問題
# setenforce 0
(可正常訪問)
這解法不好,因為你把被添加的服務器安全,所以把它改回去
# setenforce 1
應使用 SELinux 所允許的寫檔命令
blog ]$ sudo chcon -R -t httpd_sys_rw_content_t storage

laravel連不同(實體)機器DB時報錯

SQLSTATE[HY000] [2002] Permission denied
但是用mysql 命令或Navicat ssh通道卻能連,只有laravel不能連
https://stackoverflow.com/a/56381932  SQLSTATE[HY000] [2002] Permission denied Laravel PDO Driver (credentials working via artisan migrate)
解法:
# setsebool httpd_can_network_connect_db 1

beanstalk也不通 - 
Socket error 13: Permission denied  
https://stackoverflow.com/a/44092808 SQLSTATE[HY000] [2002] Permission denied
# setsebool httpd_can_network_connect 1


安裝workerman/gateway-worker

$ composer require workerman/gateway-worker
https://learnku.com/articles/13151/using-laravel-to-carry-out-socket-communication-in-workerman

新增 Workerman 啟動文件

用命令 php artisan make:command WorkermanCommand 新增 Workerman 啟動文件 app/Console/Commands/WorkermanCommand.php
  namespace App\Console\Commands;

use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use Illuminate\Console\Command;
use Workerman\Worker;

class WorkermanCommand extends Command
{

    protected $signature = 'workman {action} {--d}';

    protected $description = 'Start a Workerman server.';

    public function handle()
    {
        global $argv;
        $action = $this->argument('action');

        $argv[0] = 'wk';
        $argv[1] = $action;
        $argv[2] = $this->option('d') ? '-d' : '';

        $this->start();
    }

    private function start()
    {
        $this->startGateWay();
        $this->startBusinessWorker();
        $this->startRegister();
        Worker::runAll();
    }

    private function startBusinessWorker()
    {
        $worker                  = new BusinessWorker();
        $worker->name            = 'BusinessWorker';
        $worker->count           = 1;
        $worker->registerAddress = '127.0.0.1:1236';
        $worker->eventHandler    = \App\Workerman\Events::class;
    }

    private function startGateWay()
    {
        $gateway = new Gateway("websocket://0.0.0.0:2346");
        $gateway->name                 = 'Gateway';
        $gateway->count                = 1;
        $gateway->lanIp                = '127.0.0.1';
        $gateway->startPort            = 2300;
        $gateway->pingInterval         = 30;
        $gateway->pingNotResponseLimit = 0;
        $gateway->pingData             = '{"type":"@heart@"}';
        $gateway->registerAddress      = '127.0.0.1:1236';
    }

    private function startRegister()
    {
        new Register('text://0.0.0.0:1236');
    }
}

創建事件監聽文件

namespace App\Workerman;

class Events
{

    public static function onWorkerStart($businessWorker)
    {
    }

    public static function onConnect($client_id)
    {
    }

    public static function onWebSocketConnect($client_id, $data)
    {
    }

    public static function onMessage($client_id, $message)
    {
    }

    public static function onClose($client_id)
    {
    }
}
s

啟動Workerman 服務端

$ php artisan workman start --d

檢查是否啟動成功

瀏覽器打開看到這個畫面就是啟動成功
在hyper-v上(不在容器內)竟然也能啟動成功...

設定固定IP

https://www.cyberciti.biz/faq/howto-setting-rhel7-centos-7-static-ip-configuration/

使用 Network Manager 文字GUI,比較無腦
# nmtui edit eth0
IPv4 CONFIGURATION:<Manual>
Addresses:192.168.1.x
Gateway:192.168.1.1
DNS servers:192.168.1.1   (使用host的DNS,沒配置ping 不到域名)

點擊<OK>後即是自動修改了 /etc/sysconfig/network-scripts/ifcfg-eth0
BOOTPROTO="dhcp"
BOOTPROTO=none
IPADDR=192.168.1.x
PREFIX=24
GATEWAY=192.168.1.1

重啟網路服務
# systemctl restart network
機器ip即固定 192.168.1.x ,原本的ssh會斷掉重連 192.168.1.x 即可

如果常常有時候連不上 hyper-v  192.168.1.9 (ping不通,但是瀏覽器打得開的詭異現象),重啟網卡(hyper-v CentOS + Windows) 無效
解法:
Bear-WAN先改【內部網路】再改【外部網路】,再重啟hyper-v 虛擬機

調整pagefile.sys到D碟後ssh無法登陸

http://www.xiaobaixitong.com/win10jiaocheng/31951.html  win10的pagefile.sys是什么文件?文件太大如何移动到其它盘
因為C碟只有100G,使用TreeSizeFreePortable發現pagefile.sys就佔了16G,要把他移動到D碟。
設置完需重啟電腦,移動完發現hyper-v重啟,gitlab更新不了,而且ssh也登陸不上了。以後需要注意此操作

2018年10月20日 星期六

laravel-admin 使用心得

使用laravel-admin快速搭建後台模版
PHP >=7.0.0
Laravel >= 5.5.0

安裝最新版的 laravel 5.7
$ composer create-project --prefer-dist laravel/laravel blog

安裝laravel-admin
$ composer require encore/laravel-admin

列出你裝了哪些packages
$ composer show -i

發佈assets 和 config
$ php artisan vendor:publish --provider="Encore\Admin\AdminServiceProvider"

配置 .env 的db連線
檢查mysql的auto_increment_increment ,避免id兩個一跳造成 php artisan admin:install 報錯 // Menu::find(2)->roles()->save(Role::first()); // add role to menu. 查不到admin_menu.id = 2的資料,因為id跳到3
mysql> SHOW VARIABLES LIKE '%auto_increment_increment%';
如果是2,要改my.cnf改成1

然後你可以在 config/admin.php 配置 laravel-admin的路徑、db連線、table名字
跑以下命令完成安裝
$ php artisan admin:install

預設帳號:admin,密碼:admin

開管理員出現錯誤:
http://laravel.admin/admin/auth/users/create
Disk [admin] not configured, please add a disk config in `config/filesystems.php`
https://github.com/z-song/laravel-admin/issues/1327
http://laravel-admin.org/docs/#/en/model-form-upload?id=upload-to-local
config/filesystems.php 新增
'disks' => [
    ... ,

    'admin' => [
        'driver' => 'local',
        'root' => public_path('uploads'),
        'visibility' => 'public',
        'url' => env('APP_URL').'/uploads', // 要改.env的 APP_URL为正确访问的网址
        'url' => admin_asset('uploads'), // 避免{{ Admin::user()->avatar }}破圖
    ],
],
ps. laravel-admin會破圖的views是放在 vendor/encore/laravel-admin/resources/views/partials/sidebar.blade.php

為什麼 {{ Admin::user()->avatar }} 是取出完整網址,但db只存對應路徑?
vendor/encore/laravel-admin/src/Auth/Database/Administrator.php
Administrator的 getAvatarAttribute() 把它改了
return admin_asset('/vendor/laravel-admin/AdminLTE/dist/img/user2-160x160.jpg'); // 沒上傳頭像返回預設圖,預設圖不破圖

admin_asset() 如何運作?
在vendor/encore/laravel-admin/src/helpers.php 的 admin_asset()這裡

不要在config裡面用helpers
https://github.com/laravel/framework/issues/7671
這樣會造成 composer dumpautoload報錯
$ composer dumpautoload
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover

In UrlGenerator.php line 111:

  Argument 2 passed to Illuminate\Routing\UrlGenerator::__construct() must be
   an instance of Illuminate\Http\Request, null given, called in /var/wwwroot
  /project/vendor/laravel/framework/src/Illuminate/Routing/RoutingServicePro
  vider.php on line 61

Script @php artisan package:discover handling the post-autoload-dump event returned with error code 1


參考資料:
https://github.com/z-song/laravel-admin