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_tokenrefresh_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年11月28日 星期四

安裝SSR


安裝SSR

# wget https://raw.githubusercontent.com/ToyoDAdoubi/doubi/master/ssr.sh
# chmod +x ssr.sh && bash ssr.sh
(中間會有交互:設定密碼、端口、加密方式。如果忘記了可以去 /etc/shadowsocksr/user-config.json 查)

停止SSR

# /etc/init.d/ssr stop

啟動SSR

# /etc/init.d/ssr start
ps. 因為腳本放在  /etc/init.d/ 下,所以重開機會自動執行。這就是為什麼主機重啟後ss、openserv 連不上,只有SSR連得上

重啟SSR

# /etc/init.d/ssr restart
[信息] ShadowsocksR 停止成功 !
[信息] ShadowsocksR 启动成功 !

日誌

/usr/local/shadowsocksr/shadowsocks/ssserver.log

多賬戶

https://ssr.tools/194  SSR 添加多用户多端口教程(ShadowsocksR多用户)
配置文件(/etc/shadowsocksr/user-config.json)刪除server_port和password。改成
"port_password":{
    "12345":"password1",
    "12346":"password2"
},
12345 端口的密碼password1。12346 端口的密碼password2
多開端口後記得防火墻要放行該端口


Windows客戶端

https://github.com/shadowsocksrr/shadowsocksr-csharp/releases
搭配chrome SwitchyOmega
代理協議:SOCKS5
本地端口:1080

客戶端讓區域網路機器走代理

hyper-v centos 機器使用proxychains4 走windows代理,你需要這樣做:
1. 開啟windows防火墻1080端口

2. 在windows SSR 客戶端上【選項設置】


開啟【允許來自區域網路的連接】


Git Bash走SSR代理

https://stackoverflow.com/a/16756248  Using a socks proxy with git for the http transport

問題

如果你的IP拉不動代碼
$ git clone https://gitee.com/asurplus/google-auth.git
Cloning into 'google-auth'...
fatal: unable to access 'https://gitee.com/asurplus/google-auth.git/': OpenSSL SSL_connect: Connection was reset in connection to gitee.com:443 

Windows打開Shadowsocks客戶端

設定

$ git config --global http.proxy 'socks5://127.0.0.1:1080'

檢查

$ cat ~/.gitconfig 
...
[http]
        proxy = socks5://127.0.0.1:1080

IP沒變(只有git走代理)

$ curl ip.sb
xxx.xxx.xxx.xxx

$ git clone https://gitee.com/asurplus/google-auth.git
Cloning into 'google-auth'...
remote: Enumerating objects: 1032, done.
Receiving objects:  96% (991/1032), 6.75 MiB | 1.02 MiB/sused 1032 eceiving objects:  95% (981/1032), 6.75 MiB | 1.02 MiB/s
Receiving objects: 100% (1032/1032), 7.52 MiB | 558.00 KiB/s, done.
Resolving deltas: 100% (240/240), done.

取消

$ git config --global --unset http.proxy

$ cat ~/.gitconfig 
[http]
        proxy = socks5://127.0.0.1:1080




參考資料:

https://boke.wsfnk.com/archives/601.html  Centos7搭建SSR(兼容ss客户端)仅用于学习(連結已失效)



2019年10月7日 星期一

docker 心得2019

系統:CentOS 7.6

安裝docker CE

由於 Docker 後來分為 Docker EE(Enterprise Edition)與 Docker CE(Community Edition)兩種版本,新的套件名稱與舊的不同,所以在安裝 Docker 之前,要先移除 CentOS Linux 系統上舊版的 Docker 套件:(新系統可略)
# yum remove docker docker-common container-selinux docker-selinux docker-engine docker-engine-selinux

安裝一些必要套件:
# yum install -y yum-utils device-mapper-persistent-data lvm2

新增 Docker 官方的 stable 套件庫(repository):
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

安裝 Docker CE 版:
# yum install docker-ce

將你的用戶加入docker group (實測該用戶還是不能執行docker  重啟機器打開新的screen才生效)
$ sudo usermod -aG docker $(whoami)
執行後可檢查 /etc/gshadow 和 /etc/group

docker 開機自動啟動
# systemctl enable docker.service

開啟docker 服務
# systemctl start docker.service

測試
# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:b8ba256769a0ac28dd126d584e0a2011cd2877f3f76e093a7ae560f2a5301c00
Status: Downloaded newer image for hello-world:latest
之後docker images 會多一個 hello-world:latest 的鏡像

安裝Docker Compose

安裝 epel
# yum install epel-release

查裝什麼package才能用pip
# yum provides */pip

安裝pip
# yum install python34-pip

安裝docker compose
# pip3.4 install docker-compose
...
You are using pip version 8.1.2, however version 19.2.3 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

升級pip
# pip3.4 install --upgrade pip
...
Successfully installed pip-19.2.3


檢測pip版本(升級後pip可以直接用了)
# pip --version
pip 19.2.3 from /usr/lib/python3.4/site-packages/pip (python 3.4)


升級Python packages,讓docker-compose 正確執行(我沒做)
# yum upgrade python*

查 docker-compose 版本
# docker-compose version
docker-compose version 1.24.1, build 4667896
docker-py version: 3.7.3
CPython version: 3.4.10

安裝Docker Compose - 方法2

如果pip安裝遇到 RuntimeError: Python 3.5 or later is required 錯誤,可使用這個方法。更無腦
https://phoenixnap.com/kb/install-docker-compose-centos-7  How To Install Docker Compose On CentOS 7
# curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose

如果報錯:
curl: (35) Peer reports incompatible or unsupported protocol version.
https://blog.csdn.net/feinifi/article/details/79629904  git clone 报错:Peer reports incompatible or unsupported protocol version解决办法
原因是curl,nss的版本低

解法:

# yum update nss curl



docker-compose 自動完成

# curl -L https://raw.githubusercontent.com/docker/compose/1.25.4/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose
讓安裝前的tty  docker-compose可以自動完成
# . /etc/bash_completion.d/docker-compose

開啟ipv4 ip_forward

啟動時如果報錯
# docker run -d --name my-running-app -p 5656:80 -v /root/php5.6-apache/src:/var/www/html my-php-app:latest
WARNING: IPv4 forwarding is disabled. Networking will not work.
b44f4990a94025f7d03329dfb32c64b2677dc595d937a309582150b409c0404a

https://stackoverflow.com/questions/41453263/docker-networking-disabled-warning-ipv4-forwarding-is-disabled-networking-wil Docker Networking Disabled: WARNING: IPv4 forwarding is disabled. Networking will not work
https://blog.csdn.net/weiguang1017/article/details/76212203 centos 7 Docker容器启动报WARNING: IPv4 forwarding is disabled. Networking will not work
https://carlislebear.blogspot.com/2014/11/arch-linux-internet-sharing.html  Arch Internet sharing(透過另一台電腦上網)
Windows是無法訪問容器的web服務的(hyper-v上可以),要開啟以下設定
/etc/sysctl.conf
net.ipv4.ip_forward=1
重啟網路
# systemctl restart network
這樣windows才能訪問容器的web服務
如果打不開,要注意有沒有因為被XDebug斷住了所以打不開

laradock

重啟Exited 的 laradock container

# docker ps -a
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                      PORTS                                        NAMES
cc866de06b61        laradock_nginx       "/bin/bash /opt/star…"   2 months ago        Exited (255) 11 days ago    0.0.0.0:443->443/tcp, 0.0.0.0:5566->80/tcp   laradock_ngin
x_1
ce974cde40bf        laradock_php-fpm     "docker-php-entrypoi…"   3 months ago        Exited (255) 11 days ago    9000/tcp                                     laradock_php-
fpm_1
cb36fbf53996        laradock_workspace   "/sbin/my_init"          3 months ago        Exited (255) 11 days ago    0.0.0.0:2222->22/tcp                         laradock_work
space_1
d475c0f6d18f        docker:dind          "dockerd-entrypoint.…"   3 months ago        Exited (255) 11 days ago    2375-2376/tcp                                laradock_dock
er-in-docker_1
64c1a5ed1659        laradock_mysql       "docker-entrypoint.s…"   3 months ago        Exited (255) 2 months ago   0.0.0.0:3306->3306/tcp, 33060/tcp            laradock_mysq
l_1

啟動單一容器

# docker start laradock_workspace_1
laradock_workspace_1

但因為laradock 是很多容器組合而成的

正確的重啟laradock

# docker-compose up -d workspace nginx
laradock_docker-in-docker_1 is up-to-date
laradock_workspace_1 is up-to-date
Starting laradock_php-fpm_1 ... done
Starting laradock_nginx_1   ... done

如果報錯:
Creating network "laradock_frontend" with driver "bridge"
ERROR: Failed to Setup IP tables: Unable to enable SKIP DNAT rule:  (iptables failed: iptables --wait -t nat -I DOCKER -i br-7d0b53dc35e0 -j RETURN: iptables: No chain/target/match by that name.
 (exit status 1))
原因:
應該是改過防火墻iptables造成的
https://stackoverflow.com/a/53766860  iptables: No chain/target/match error (with docker network create)
https://github.com/wodby/docker4drupal/issues/211#issuecomment-351016274  Unable to enable SKIP DNAT rule (未使用)
解法:
重啟docker
# systemctl restart docker
再重啟laradock
# docker-compose up -d workspace nginx

進入workspace容器

# docker exec -it laradock_workspace_1 bash
上面是以root身份進入,跑composer 會有警告。如何進入容器跑composer不報警告?
以laradock身份進入容器
# docker-compose exec --user=laradock workspace bash
laradock@daa4f318e534:/var/www$ composer --version
Composer version 1.9.0 2019-08-02 20:55:32


關閉laradock

# docker-compose down

多人開發

配置.env

#   APP_CODE_PATH_HOST=../  
# APP_CODE_PATH_HOST=../project-z/coolapp/   # 使用 laradock/nginx/sites/default.conf 不需特別設定
APP_CODE_PATH_HOST=../project-z/  # 多個專案,複製 laradock/nginx/sites/app.conf.example 成 coolapp.conf 和 coolapp2.conf
#   WORKSPACE_INSTALL_XDEBUG=false
WORKSPACE_INSTALL_XDEBUG=true
#   PHP_FPM_INSTALL_XDEBUG=false
PHP_FPM_INSTALL_XDEBUG=true  # workspace 和 php-fpm容器都裝XDebug
#   NGINX_HOST_HTTP_PORT=80
NGINX_HOST_HTTP_PORT=5566


coolapp.conf

root /var/www/app;  => root /var/www/coolapp/public;

coolapp2.conf

server_name app.test; => server_name app2.test;
root /var/www/app;  => root /var/www/coolapp2/public;

windows設hosts,重啟laradock(down 和 up),然後 http://app.test:5566/ 訪問coolapp,http://app2.test:5566/ 訪問 coolapp2

更新laradock

git pull 更新laradock後重新build會報錯,因為env-example 已經被改過了,加了一些配置
先備份自己的.env(要記得你改了什麼),然後cp env-example .env 然後再把你原本在.env改的配置改到新的.env。這樣build才會成功

php安裝memcache

修改.env
#   PHP_FPM_INSTALL_MEMCACHED=false
PHP_FPM_INSTALL_MEMCACHED=true

需重新build php-fpm容器出錯

# docker-compose build php-fpm
...
E: Failed to fetch http://deb.debian.org/debian/pool/main/c/cups/libcupsimage2_2.2.10-6+deb10u1_amd64.deb  404  Not Found [IP: 151.101.10.133 80]
E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?
ERROR: Service 'php-fpm' failed to build: The command '/bin/sh -c if [ ${INSTALL_IMAGEMAGICK} = true ]; then     apt-get install -y libmagickwand-dev imagemagick &&     pecl install imagick &&     docker-php-ext-enable imagick ;fi' returned a non-zero code: 100
build 失敗
https://github.com/laradock/laradock/issues/2434
https://blog.51cto.com/beijing0414/1633956 
原來是hyper-v 系統的時間錯誤(當天應為4/8)
[root@localhost laradock]# date
Mon Apr  6 00:06:11 -02 2020
解法:
先安裝ntpdate
# yum install ntpdate
[root@localhost laradock]# date
Mon Apr  6 00:36:28 -02 2020
[root@localhost laradock]# ntpdate cn.pool.ntp.org
 8 Apr 13:07:13 ntpdate[14154]: step time server 119.28.206.193 offset 217834.861195 sec
[root@localhost laradock]# date
Wed Apr  8 13:07:15 -02 2020

重新build php-fpm(不吃緩存,比較慢)
# docker-compose build --no-cache php-fpm
# docker-compose up --build  // 不要不指定service,否則會全部build

這樣phpinfo()和命令行php -m 才有memcached

安裝docker-machine (無使用)

# base=https://github.com/docker/machine/releases/download/v0.16.0 &&
  curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine &&
  sudo mv /tmp/docker-machine /usr/local/bin/docker-machine &&
  chmod +x /usr/local/bin/docker-machine

檢視配置檔案位於哪裡

https://codertw.com/%E4%BC%BA%E6%9C%8D%E5%99%A8/160314/
$ systemctl show --property=FragmentPath docker
FragmentPath=/usr/lib/systemd/system/docker.service

開啟docker API

https://gist.github.com/styblope/dc55e0ad2a9848f2cc3307d4819d819f
新增文件:/etc/docker/daemon.json
{"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]}
新增文件:/etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
重新加載 systemd daemon
# systemctl daemon-reload
重啟docker
# systemctl restart docker.service
在機器上測試
$ curl http://localhost:2375
{"message":"page not found"}
但是windows上打不開 => server要打開 2375 端口
# firewall-cmd --zone=public --add-port=2375/tcp --permanent
# firewall-cmd --reload
然後在Windows 瀏覽器上直接打開 http://192.168.1.9:2375/info 顯示json即成功

忽然進不了XDebug斷點

commit ab4e06f270ae83d6e41702b2b652a980b12816ce
Author: Jordy Schreuders <3071062+99linesofcode@users.noreply.github.com>
Date:   Fri Feb 14 15:15:04 2020 +0100

    Default to host.docker.internal

因為更新重新build php-fpm後這個commit把 xdebug.remote_connect_back 改成0了
修改 php-fpm/xdebug.ini 、 workspace/xdebug.ini 將 xdebug.remote_connect_back 改回1,然後重新build
# docker-compose build php-fpm
ps. 重新build 時間比較久,改 xdebug.ini 後沒法像  nginx/sites/default.conf 只需要重啟laradock就能生效,必須build

進不了XDebug斷點-2

新hyper-v虛擬機上裝的laradock,在windows本機不要用同一個PHPStorm 專案改deployment去連,會一直進不了斷點,開一個新project後重新設deployment,請求接口和cli時會自動匹配,PHP CLI無需設docker。不要在同一個PHPStorm projcet上折騰
實體開發機裝了兩個PHP(php 5.5 + 7.3),上面的laradock http能進xdebug,但是cli不行 => php7.3改用hyper-v的laradock去斷點

進不了XDebug斷點-3

如果laradock中 http請求可以斷點,但是CLI不能斷點。別折騰了,直接在hyper-v上面裝PHP去做CLI斷點,不一定要在laradock中斷點。
檢查:
使用-d 自定義INI條目
$ php -dxdebug.remote_enable=1 -dxdebug.remote_mode=req -dxdebug.remote_port=9000 -dxdebug.remote_host=192.168.1.7 -dxdebug.remote_connect_back=0 -dxdebug.idekey=PHPSTORM public/index.php

要注意 xdebug.remote_enable=1 是否開啟

其實是 laradock 的 workspace 容器的xdebug 版本是3.0.0這個原因造成的,workspace和php-fpm容器裝的是不同的xdebug,workspace負責cli,php-fpm負責網頁,所以網頁上的phpinfo()查到的是是php-fpm的xdebug版本,而workspace的xdebug版本要在容器裡cli執行,如:
root@e7aa5f3bd006:/var/www# php -r 'phpinfo();' | grep Xdebug
    with Xdebug v2.5.5, Copyright (c) 2002-2017, by Derick Rethans
需要改 workspace/Dockerfile 的 xDebug 部分,讓workspcace 容器安裝低版本的xdebug

laradock中websocket 端口對外

https://github.com/laradock/laradock/issues/2002#issuecomment-468884705
編輯 docker-compose.yml 加入
services:
  workspace:
    port:
      - 2346:2346
然後重啟laradock



和gitlab端口衝突

目前已知 8080 和3000端口會和gitlab衝突(gitlab已開啟)

解法:

https://github.com/laradock/laradock/issues/2512#issuecomment-589719158  Bind for 0.0.0.0:8080 failed: port is already allocated

換一個端口,修改.env
WORKSPACE_VUE_CLI_SERVE_HOST_PORT=8080 => 8084
...
WORKSPACE_BROWSERSYNC_HOST_PORT=3000 => 3002

安裝特定版本的xdebug

如果你的專案和xdebug最新版本(3.0.0+)有衝突
https://bugs.xdebug.org/view.php?id=1883  0001883: Function xdebug_is_enabled has been removed
XDebug 3.0.0beta1版本把 xdebug_is_enabled() 移除了
可以這樣做
diff --git a/php-fpm/Dockerfile b/php-fpm/Dockerfile
index 1837b45..a64bf63 100644
--- a/php-fpm/Dockerfile
+++ b/php-fpm/Dockerfile
@@ -193,7 +193,7 @@ RUN if [ ${INSTALL_XDEBUG} = true ]; then \
     if [ $(php -r "echo PHP_MINOR_VERSION;") = "0" ]; then \
       pecl install xdebug-2.9.0; \
     else \
-      pecl install xdebug; \
+      pecl install xdebug-2.9.8; \
     fi \
   fi && \
   docker-php-ext-enable xdebug \
ps. PHP_MINOR_VERSION就是PHP的中版本號,ex. php 7.3,PHP_MINOR_VERSION就是3

因為改了Dockerfile,所以要重新build,雖然有緩存,但還是有點久
$ docker-compose build php-fpm
...
Step 29/159 : RUN if [ ${INSTALL_XDEBUG} = true ]; then   if [ $(php -r "echo PHP_MAJOR_VERSION;") = "5" ]; then     pecl install xdebug-2.5.5;   else     if [ $(php -r "echo PHP_MINOR_VERSION;") = "0" ]; then       pecl install xdebug-2.9.0;     else       pecl install xdebug-2.9.8;     fi   fi &&   docker-php-ext-enable xdebug ;fi
 ---> Running in 999878a0db51
downloading xdebug-2.9.8.tgz ...
Starting to download xdebug-2.9.8.tgz (245,293 bytes)
...................................................done: 245,293 bytes
91 source files, building
running: phpize
Configuring for:
PHP Api Version:         20180731
Zend Module Api No:      20180731
Zend Extension Api No:   320180731
building in /tmp/pear/temp/pear-build-defaultusergNFJEA/xdebug-2.9.8
running: /tmp/pear/temp/xdebug/configure --with-php-config=/usr/local/bin/php-config
checking for grep that handles long lines and -e... /bin/grep

自定義workspace中的.bashrc

由 workspace/Dockerfile 
COPY ./aliases.sh /home/laradock/aliases.sh
...
USER laradock
RUN echo "" >> ~/.bashrc && \
    echo "# Load Custom Aliases" >> ~/.bashrc && \
    echo "source ~/aliases.sh" >> ~/.bashrc && \
      echo "" >> ~/.bashrc

修改 workspace/aliases.sh 可以客製化容器中laradock用戶的.bashrc
然後需重新build workspace容器
# docker-compose build workspace

Dockerfile

以安裝 php:5.6-apache 為例
https://hub.docker.com/_/php

新建 Dockerfile 

內容為
FROM php:5.6-apache
COPY src/ /var/www/html/
新增目錄src 和 文件src/index.php

build Docker image

# docker build -t my-php-app .
Step 1/2 : FROM php:5.6-apache
5.6-apache: Pulling from library/php
...
他會從線上把 php:5.6-apache 拉下來,用下面方法查看
# docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
my-php-app              latest              10cc9a5aa0c2        25 seconds ago      355MB
...
php                     5.6-apache          24c791995c1e        14 months ago       355MB

刪除dangling的images(部份<none>TAG的image)

https://stackoverflow.com/a/33913711
因為每次都build同一個image名字時,前一個同名的image的REPOSITORY和TAG會變成<none>
# docker rmi $(docker images --filter "dangling=true" -q --no-trunc)


啟動容器

# docker run -d --name my-running-app -p 5656:80 -v /root/php5.6-apache/src:/var/www/html my-php-app:latest

--name my-running-app => 指定容器的名字
-p 5656:80 => 容器中的80端口對應到host的5656端口,http://192.168.1.9:5656/ 可訪問容器中的web伺服器
-v /root/php5.6-apache/src:/var/www/html => host的/root/php5.6-apache/src對應到容器中的/var/www/html,直接改host目錄的文件容器中文件就會改動

進入容器

# docker exec -it my-running-app bash
用 -d 啟動後再用此目錄進容器,否則容器內的apache不會啟動

停止容器

# docker stop my-running-app

刪除 Exited 的容器

# docker rm my-running-app

進階使用

Dockerfile
FROM php:5.6-apache
COPY src/ /var/www/html/
RUN docker-php-ext-install mysqli \
    && apt-get update \
    && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install gd \
    && pecl install xdebug-2.5.5 && docker-php-ext-enable xdebug \
    && echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" >> /usr/local/etc/php/php.ini  \
    && echo "xdebug.remote_enable=1" >> /usr/local/etc/php/php.ini \
    && echo "xdebug.remote_connect_back=1" >> /usr/local/etc/php/php.ini \
    && echo "xdebug.idekey=PHPSTORM" >> /usr/local/etc/php/php.ini \
    && echo "xdebug.remote_log=/tmp/xdebug.log" >> /usr/local/etc/php/php.ini

註:
安裝mysqli
安裝gd函式庫
安裝xdebug並配置


使用docker安裝beanstalk console

github上推薦用composer安裝,但是如果你的機器沒有php和composer,你可以這樣做。直接拉取github上專案
$ git clone https://github.com/ptrofimov/beanstalk_console.git
$ cd beanstalk_console/
$ docker build --rm -t beanstalk_console .
$ docker run -d -p "8899:80" --name beanstalk_console beanstalk_console

然後瀏覽器打開 http://x.x.x.x:8899  即可顯示 Beanstalk console

在容器內測端口

https://stackoverflow.com/a/2226759  Test if port open and forwarded using PHP
php-fpm容器內可能沒有telnet,可以用PHP去測端口通不通
$host = 'stackoverflow.com';
$ports = array(21, 25, 80, 81, 110, 443, 3306);

foreach ($ports as $port)
{
    $connection = @fsockopen($host, $port);

    if (is_resource($connection))
    {
        echo '<h2>' . $host . ':' . $port . ' ' . '(' . getservbyport($port, 'tcp') . ') is open.</h2>' . "\n";

        fclose($connection);
    }

    else
    {
        echo '<h2>' . $host . ':' . $port . ' is not responding.</h2>' . "\n";
    }
}
輸出:
stackoverflow.com:21 is not responding.
stackoverflow.com:25 is not responding.
stackoverflow.com:80 (http) is open.
stackoverflow.com:81 is not responding.
stackoverflow.com:110 is not responding.
stackoverflow.com:443 is not responding.
stackoverflow.com:3306 is not responding.

如果端口忽然不通可以嘗試重啟docker

參考資料:

https://blog.gtwang.org/linux/centos-linux-7-install-docker-tutorial/  CentOS Linux 7 安裝 Docker 步驟與使用教學
https://github.com/NaturalHistoryMuseum/scratchpads2/wiki/Install-Docker-and-Docker-Compose-(Centos-7)   Install Docker and Docker Compose (Centos 7)





2019年8月24日 星期六

phpstorm心得

起因:

因為sublime 的 Xdebug 插件已經很久沒在更新了
https://github.com/martomo/SublimeTextXdebug
所以在新版的php和xdebug中會有不兼容的情況
https://github.com/martomo/SublimeTextXdebug/issues/172#issuecomment-363770737 
而且workerman 的events.php 裡面因為xdebug版本太低斷不到,而xdebug版本太高sublime又不兼容。所以轉向有在維護更新的phpstorm


熱鍵

Settings => ctrl + alt + s
Settings => Keymap => 選Sublime Text

搜尋某個熱鍵榜定了什麼功能(有無被使用)
Keymap => 點擊搜尋圖示 => 輸入熱鍵。ex. ctrl+shift+p 查出已經榜訂了Find Action
打開Version Control 面板(看git log) => alt + 9
Sublime: ( set_mark + select_to_mark ) => ( Find Action )Toggle Sticky Selection 
Sublime: alignment ( ctrl+alt+a ) => ( Find Action ) Reformat Code
Sublime: Emmet - Go to Matching Pair => ctrl + m ( Move Caret to Matching Brace )
快速加入try / catch: 選擇代碼範圍 => ( Find Action ) Surround With => try / catch ... finally 要手動加
對某段(HTML)代碼縮排 => 選擇後 => ( Find Action ) Auto-Indent Lines - Ctrl+Alt+I

複製純文本(貼上word時不帶樣式)

ctrl  + shift + p (Find Action) => Copy as Plain Text  

找檔案中下一個error

ctrl  + shift + p (Find Action) => Next Highlighted Error    



自定義熱鍵

Resume Program(XDebug Run,跳到下一個breakpoint):F9
https://stackoverflow.com/questions/14223631/in-phpstorm-how-do-you-untab-to-the-previous-tabstop/14746035
Unindent:Shift + Tab

Next Line Bookmark in Editor

原本2021.2.2原本 F2 是在同一個文件中跳到下一個書籤,2022.1.1 是會跨文件跳書籤。如果要維持在同一個文件中跳書籤:
Next Line Bookmark:F2 => Next Line Bookmark in Editor:F2

Toggle Sticky Selection: ctrl+alt+shift+s   
=> 避免使用shift + s,因為中文輸入時按shift + s打不出S

Path with line number 

在某個新版後,在Find in Files(Ctrl+Shift+F)中Copy Reference 不再帶行號
Copy Reference:Ctrl+Alt+Shift+C => Path with line number:Ctrl+Alt+Shift+C 


樣式:

配色方案(color scheme)
使用sublime 預設的Monokai
File => Settings => Editor => Color Scheme
因為弱報錯提示不明顯,所以不使用 Monokai ,如圖:

使用PHPStorm預設的Darcula?

斷點時string和int的顏色區別不明顯,所以不使用Darcula




使用Monokai Pro Theme

Settings => Plugins => Marketplace 下載安裝 Monokai Pro Theme
Appearance & Behavior => Appearance => Theme 改回Darcular
Editor => Color Scheme 選擇 Monokai Pro  (非 Monokai Pro(Classic))




字型大小:

Settings => Editor => Font

字型大小(外觀Appearance):

改變編輯器(Editor)以外的字型和大小
Settings => Appearance & Behavior => Appearance 


改變前

改變後


版本控制(以github 為例)

1. 安裝git bash => PHPStorm 會自動偵測到git路徑,因為git也加Windows PowerShell 的環境變量上了

2. git bash 新增公鑰和私鑰
$ ssh-keygen -C "username@email.com" -t rsa
然後把公鑰 .ssh/id_rsa.pub 上傳到github上
ps. 現在github可以開私人project了

3. 拉取代碼
Find Action.. => Checkout from version control =>  Git => 設定路徑

ps. 效果等同在 git bash上直接 git clone下來


Live Template

如同sublime 的 sinppet,

例:
epc
Applicable in ... => 該template 在什麼語言的什麼地方會提示
echo "<pre>\$VARIABLE$:";
print_r($VARIABLE$);
echo "</pre>";

Editor

General 

https://stackoverflow.com/questions/20486542/selecting-entire-variable-with-double-click  Selecting entire variable with double click
點兩下變數不選$:Settings => Editor => General => Smart Keys => Select variable name without '$' sign on double click (預設沒勾,勾了就不選$)

避免打開太多tab使得PHPstorm把tab關掉

Editor | General | Editor Tabs | Tab closing policy | tab limit 設一個大一點的數

增加剪貼板(clipboard )歷史記錄

https://stackoverflow.com/a/10337122  How to increase PHPStorm clipboard history size?
Settings (Preferences on Mac) | Editor | Maximum number of contents to keep in clipboard.




Code Style

縮排風格,如:將 function的左大括號不換行


註解comment 不要從第一行開始

https://stackoverflow.com/a/48694397  How to set the style of line comment in phpstorm

Preferences > Editor > Code Style > PHP > (tab) Code Generation > Comment Code > (uncheck) Line comment at first column + 勾選"Add a space at comment start"

https://intellij-support.jetbrains.com/hc/en-us/community/posts/206332339-Tabs-and-Indents-are-2-spaces-despite-setting-at-4-spaces?page=1#community_comment_206876035  Tabs and Indents are 2 spaces despite setting at 4 spaces
如果 Reformat Code 不生效,請嘗試
Settings => Editor => Code Style => 取消勾選 "Detect and use existing file indents for editing"

讓 array 的key和value對齊

https://stackoverflow.com/a/29795404  Stop PhpStorm from aligning associative arrays
Settings | Editor | Code Style | PHP | Wrapping and Braces --> Array initializer | Align key-value pairs

讓 class的 const 和value 對齊

Settings | Editor | Code Style | PHP | Wrapping and Braces --> Class field/constant groups | Align constants

Diff

選擇文件與當前文件比較
ctrl + shift + p => compare with  

剪貼簿與當前文件比較
ctrl + shift + p => compare with  Clipboard

underscore.js template

在Settings 中的 Plugins => Marketplace => 搜尋EJS => install 安裝,裝完須重啟IDE
(也可以離線安裝 https://plugins.jetbrains.com/plugin/7296-ejs )


設置特定文件為EJS格式:Settings => Editor => File Types => EJS => 新增文件名
.env 我用 INI Config
.env - Plugins下載 .env files support 

Deployment

Settings => Deployment => Connection
配置SFTP 
Root path: 專案路徑
Encoding for client-server communication: UTF-8
Mappings:
Deployment path: /
Excluded Paths:
本地的 .idea、vendor (PHPStorm 資料夾)不要上傳

Settings => Deployment => Options
Upload changed files automatically to default server: Always
ps. 所有git 操作都在Windows上 PHPStorm的git 或 ConEmu64 + git bash,如果是從Server上Deployment下載下來的話,會下載完又馬上同步上Server(有點坑)
ps2.  Server 分支不用管,全看Windows上 git bash的分支。除非弄亂了再兩邊切到同一個分支的同一個commit再繼續工作
ps3. 如果項目沒git,Upload changed files automatically to default server等先 Deployment  => Download from x.x.x.x 下載完之後再改Always,第一次下載完就不會馬上同步上去

上傳時報錯:Failed to change timestamp of the file 'your.php'

Settings => Deployment => Options
Preserve files timestamps:取消勾選

在Find in Files (Ctrl+Shift+F)中換行

直接點圖Soft-Wrap Preview Editor  

在所選裡面搜尋search in selection

Ctrl+f搜尋逗號(,)=>選擇範圍 => Search In Selection => Select All Occurrences 





XDebug

遠程調試

適用於:本機http調試(xampp、phpstudy)、虛擬機或內網測試機http調試+cli調試
如果忽然不能斷點,檢查本機IP是否變動,更改php.ini的 xdebug.client_host 為本機IP 

Open edit Run/Debug configurations dialog
Add New Configuration -> PHP Remote Debug
IDE key(session id): PHPSTORM   (搭配 chrome 的 Xdebug helper)
Server 不需要配,打開 Start Listening for PHP Debug Connections 和 Xdebug helper 會自動import
如果server 上裝了兩個PHP(5.5 + 7.3 ) 都裝了XDebug,但是只有 5.5的能進斷點,7.3進不了 => 解法:不要裝在同一台,hyper-v 開虛擬機裝PHP 7.3環境。或把php 7.3 + XDebug 跑在laradock上
(可選)Settings => Languages & Framworks => PHP => 配置 PHP language level 和 CLI Interpreter (本機php環境選擇 Local Path to Interpreter...)

Settings => Languages & Framworks => PHP => Debug => External connections => Break at first line in PHP scripts 勿勾選,以免workerman 調試時一直斷在新進程開始處



laradock 中找不到路徑
解法:
在servers中配置laradock的server

對物件斷點

以laravel passport 登錄時
vendor/league/oauth2-server/src/AuthorizationServer.php:192 
為例,$this->enabledGrantTypes跑foreach時,只需要在 personal_access 時斷點
在192行設置斷點,Condition:
strpos("\Laravel\Passport\Bridge\PersonalAccessGrant", get_class($grantType)) !== false

註:
get_class($grantType) => 因為foreach沒指定index,這邊用get_class去取出類命名空間
‌Laravel\Passport\Bridge\PersonalAccessGrant 
strpos => 判斷是否子字串
false => 0也是判斷成功,false才是失敗

\Laravel\Passport\Bridge\PersonalAccessGrant 怎麼快速複製出來?
展開 $this->enabledGrantTypes => 右鍵 => Copy Type
更多:
Jump To Type Source => 打開 vendor/laravel/passport/src/Bridge/PersonalAccessGrant.php
More (Ctrl + Shift + F8) => 顯示所有斷點

本地調試

 適用於:本機cli(腳本、爬蟲),勿使用git bash(斷不到)

Add configuration ...

Add New Configuration  => PHP Script 
設定 Name, File, Interpreter 

設定 CLI  Interpreter 


如果沒設 CLI Interpreter  會報這個錯誤(Debugger extension is not detected )
然後點擊【Debug】
開始快樂斷點


PHPStorm的XDebug也會有bug

https://www.reddit.com/r/phpstorm/comments/un5e97/debug_console_output_only_shows_array_item_count/  Debug console output only shows array item count instead of contents
reddit 上的網友遇到了這個問題,斷點時在console打印array或object只會顯示array的長度,估計是使用最新版本2022.1.1,如圖:
有網友回復他到YouTrack(jetbrains的jira)回報問題

Debug console output only shows array item count instead of contents

官方回復會在 2022.2 修正這個問題

Debug console cannot evaluate arrays or objects

不降版本可以用print_r($a, true) 
https://youtrack.jetbrains.com/issue/WI-66067/Debug-console-cannot-evaluate-arrays-or-objects#focus=Comments-27-6090147.0-0  
PhpStorm 2021.3.2 是最後正常的版本,2021.3.3後就會有這個問題




Docker

.idea

jet brains 系列編輯器會把IDE配置存在項目的.idea 目錄下
如果有人把.idea 加到git 且推到master 分支上,你拉下來用PHPStorm 打開項目時會吃到他的設定。
解法:
關掉PHPStorm ,切到開發分支dev(別的分支 .idea/ 有被gitignore),刪除.idea/ ,然後打開PHPStorm解決

composer

在windows上的PHPStorm直接執行composer而不用 terminal 連上服務器執行

在windows上安裝composer

參考 https://getcomposer.org/download/ 打開 XAMPP for Windows 的終端安裝
# php -v
PHP 7.4.1 (cli) (built: Dec 17 2019 19:24:02) ( ZTS Visual C++ 2017 x64 )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

...
放在 C:\Users\bear\Downloads 下是不行的
bear@BEAR-PC C:\Users\bear\Downloads
# php composer-setup.php
All settings correct for using Composer
The installation directory "C:\Users\bear\Downloads" is not writable

bear@BEAR-PC C:\bear
# php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"

bear@BEAR-PC C:\bear
# php -r "if (hash_file('sha384', 'composer-setup.php') === 'e0012edf3e80b6978849f5eff0d4b4e4c79ff1609dd1e613307e16318854d24ae64f26d17af3ef0bf7cfb710ca74755a') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
Installer verified

bear@BEAR-PC C:\bear
# php composer-setup.php
All settings correct for using Composer
Downloading...

Composer (version 1.10.1) successfully installed to: C:\bear\composer.phar
Use it: php composer.phar

bear@BEAR-PC C:\bear
# php -r "unlink('composer-setup.php');"

在PHPStorm中設定composer

Settings => Tools => Command Line Tool Support => Add(+) => Choose tool: Composer
Path to PHP executable:  C:\xampp\php\php.exe => 即使和server php版本不一樣,用XAMPP 的 PHP 7.4 沒事
Path to composer.phar or composer: C:\bear\composer.phar => 官網上下載
Alias: c => composer

使用方法(會自動 auto-complete):
Run Command: composer dump-autoload
Windows 的 git bash 一樣要設 git config --global core.autocrlf false 避免 composer dump-autoload 後會有 CRLF will be replaced by LF in xxx 錯誤

windows + PHPStorm + git bash 避免CRLF will be replaced by LF in xxx 錯誤

Settings => Editor => Code Style => General => Line Separator: Unix and macOS(\n)
composer install 或 composer require 安裝插件時只在Linux 上執行
就是確保所有新建文件的Line Separator是\n


artisan

Settings => Tools => Command Line Tool Support => Add(+) => Choose tool: Tool based on Symfony Console.
Path to PHP executable: C:\xampp\php\php.exe
Path to script: C:\PhpstormProjects\project\artisan
Alias: php artisan
如果報錯:
Problem
Failed to parse output as xml: ParseError at [row,col]:[2,1]
Message: 前言中不允许有内容。.
Command
d2564ee6-4324-47ee-b9f2-6bb9789fff37 D:\path\laravel\artisan list --format=xml
Output

Warning: require(D:\path\laravel/vendor/autoload.php): Failed to open stream: No such file or directory in D:\path\laravel\artisan on line 18

Fatal error: Uncaught Error: Failed opening required 'D:\path\laravel/vendor/autoload.php' (include_path='.;C:\php\pear') in D:\path\laravel\artisan:18
Stack trace:
#0 {main}
  thrown in D:\path\laravel\artisan on line 18
檢查下vendor目錄,是不是沒有composer install


Plugins

Case conversion

不用這個,改用String Manipulation

.env files support

EJS

Mongo Plugin

Quick File Preview

SQL Formatter

Sublime的 SqlBeautifier 替代品,但還是覺得Navicat查詢的美化SQL做的比較好

String Manipulation

熱鍵:alt+m
https://stackoverflow.com/a/36477843  Auto increment number for multiple lines in PhpStorm
Sublime的Text Pastry替代品,但用起來沒那麼順手,還是開sublime好。也有 Case conversion功能

.ignore

Ideolog

Rainbow Brackets

Php Inspections (EA Extended)

熱鍵:alt+enter - show context action

laravel idea

自動生成Model

ctrl+shift+p => laravel =>Code Generation => Create Model 




自動提示DB字段

新增fruits表
CREATE TABLE `fruits` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
  `class` tinyint(1) NOT NULL DEFAULT '1' COMMENT '等級',
  `quantity` tinyint(2) NOT NULL DEFAULT '0' COMMENT '數量',
  `created_at` datetime NOT NULL COMMENT '創建時間',
  `updated_at` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
s

設定Database
Database(View=>Tool Window) => + => Data Source => MySQL 
第一次打開需要安裝Driver
顯示你專案的資料庫





Helper Code Parameters... 


使用laravel idea Create Model  後(Models/Fruit.php)
先Laravel => Generate Helper Code,然後在 $fillable 中  alt+insert (Generate)

新增字段後。Database先Refresh,然後Generate Helper Code,這樣新增的字段才生效





Languages & Framworks

避免composer.json改變專案的PHP版本

https://stackoverflow.com/a/51222031  How can I set the PHP version in PHPStorm?
PHP => Composer => 取消勾選:Synchronize IDE settings with composer.json


和sublime比較

列模式貼上比sublime強

將剪貼板中的 2 行的內容:
Title1
Title2
取代目標內容的title
- title: Example Title 1
  color: '#03fccf'
  profile: CMD (clink)
  commands:
     - ls
     - cd ..
- title: Example Title 2
  color: '#fc036b'
  profile: 1
- title: Example Title 3
  color: '#302a57'

當剪貼板中的行數(2) 不等於取代目標內容(3)

Sublime


PHPStorm


選擇偶數行

https://stackoverflow.com/a/16273692  How can I select every other line with multiple cursors in Sublime Text?
正則搜索:.*\n.*\n  ,alt+enter 全選, 左鍵 ← ,下鍵 ↓ 






WebStorm

避免切視窗時自動保存文件

不然vue 會自動重新編譯,等手動ctrl+s 保存時再編譯
https://stackoverflow.com/a/15605487  How to Turn off automatic saving on web storm
File | Settings | Appearance & Behavior | System Settings > 取消勾選:
Save files when switching to a different application or a built-in terminal