老楊說他的使用者資訊存在 Redis 裡面

qufo發表於2017-04-27

去找老楊,他說要請我吃飯,結果到那一看,他正咬著筆目光呆滯,“怎麼了?”我問他。
“你來得正好,幫我看下這個登入怎麼弄。“
”一個登入,還能怎麼弄,php artisan make:auth ,然後 php artisan migrate 建好表結構不就行了,登入註冊的頁面都好了,連路由都好了。“
”不一樣,這個使用者的資訊不是放在關係型資料庫裡,放在 Redis 裡。“
”幹嘛放 Redis 裡?“
”說是放 Redis 裡速度快,連 Redis 快,查也快。登入頁我都套好了,接下來怎麼搞?“
”不就加個自定義使用者提供者嘛,至於把你折騰成那樣?“
”you can you up !“
”滾“
拉過鍵盤,開啟命令列,熟悉地進行專案目錄,先來個

php artisan make:auth

以建立相應的框架,接下來正想 php artisan migrate 發現不對勁。要用 Redis 哦。
沒事,我們建立一個新的使用者提供者,就叫 RedisUserProvider 放在 app/Providers/ 下面。要實現 UserProvider 。
空的長這樣

<?php
namespace App\Providers;
use Illuminate\Contracts\Auth\UserProvider;
class RedisUserProvider implements UserProvider
{
}

要實現下列方法

    public function retrieveById($identifier);    // 根據 $identifier 一般是 ID 來取回使用者資訊
    public function retrieveByToken($identifier, $token);    // 根據 $identifier 和 $token 來取回使用者資訊
    public function updateRememberToken(Authenticatable $user, $token);    // 登入後儲存使用者的 token
    public function retrieveByCredentials(array $credentials);    // 根據 $credentials 就象使用者名稱密碼之類的,取回使用者資訊
    public function validateCredentials(Authenticatable $user, array $credentials);    // 根據 $credentials 就象使用者名稱密碼之類的,檢查使用者名稱密碼是否正確。

要返回的使用者資訊可以是一個陣列,給 Illuminate\Auth\GenericUser 轉一下就可以用了。

所以,先寫第一個方法,陣列資訊轉 user .

    /**
     * Get the generic user.
     *
     * @param  mixed  $user
     * @return \Illuminate\Auth\GenericUser|null
     */
    protected function getGenericUser($user)
    {
        if (! is_null($user)) {
            return new GenericUser((array) $user);
        }
    }

然後依次實現各個方法,首先,既然要求速度,我們就直接用 Redis 的 PHP 擴充套件,而不只是使用 predis 之流,當然不是說他不好。
定義一個 conn 並連線上。順便把快取的 prefix 取出來。

class RedisUserProvider implements UserProvider
{

    protected $conn = null;
    protected $prefix = '';

    public function __construct($config)
    {
        $this->prefix = config('cache.prefix');
        $this->conn = new \Redis();
        $this->conn->connect(config('database.redis.default.host','127.0.0.1'),config('database.redis.default.port',6379));
        $this->conn->auth(config('database.redis.default.password',null));

    }

使用者名稱列到底叫什麼名字呢? name ? username ? user_name ? account ? 算了,定義一個配置項,順便密碼也定義個算了。從 $config 傳過來。於是。

class RedisUserProvider implements UserProvider
{

    protected $conn = null;
    protected $prefix = '';

    protected $username_field='';
    protected $password_field='';

    public function __construct($config)
    {

        $this->username_field = $config['username_field']??'username';
        $this->password_field = $config['password_field']??'password';
        $this->prefix = config('cache.prefix');

        $this->conn = new \Redis();
        $this->conn->connect(config('database.redis.default.host','127.0.0.1'),config('database.redis.default.port',6379));
        $this->conn->auth(config('database.redis.default.password',null));

    }

我們把使用者的資訊呀,token呀之類的存在Redis,相應 key 怎麼定義呢?也定義幾個吧。從 $config 傳過來。於是。

class RedisUserProvider implements UserProvider
{

    protected $conn = null;
    protected $prefix = '';

    protected $username_field='';
    protected $password_field='';
    protected $key_user_id  = '';
    protected $key_user_name= '';
    protected $key_user_token   = '';

    public function __construct($config)
    {

        $this->username_field   = $config['username_field']??'username';
        $this->password_field   = $config['password_field']??'password';
        $this->prefix           = config('cache.prefix');
        $this->key_user_id      = $this->prefix.':'.($config['key_user_id']??'USER_ID').':';
        $this->key_user_name    = $this->prefix.':'.($config['key_user_name']??'USER_NAME').':';
        $this->key_user_token   = $this->prefix.':'.($config['key_user_token']??'USER_TOKEN').':';

        $this->conn = new \Redis();
        $this->conn->connect(config('database.redis.default.host','127.0.0.1'),config('database.redis.default.port',6379));
        $this->conn->auth(config('database.redis.default.password',null));
    }

接下來可以開心地實現幾個方法了。全文如下:

<?php
/**
 * User: ufo
 * Date: 17/4/26
 * Time: 下午8:44
 */

namespace App\Providers;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Auth\GenericUser;

class RedisUserProvider implements UserProvider
{

    protected $conn = null;
    protected $prefix = '';

    protected $username_field='';
    protected $password_field='';
    protected $key_user_id  = '';
    protected $key_user_name= '';
    protected $key_user_token   = '';

    public function __construct($config)
    {

        $this->username_field   = $config['username_field']??'username';
        $this->password_field   = $config['password_field']??'password';
        $this->prefix           = config('cache.prefix');
        $this->key_user_id      = $this->prefix.':'.($config['key_user_id']??'USER_ID').':';
        $this->key_user_name    = $this->prefix.':'.($config['key_user_name']??'USER_NAME').':';
        $this->key_user_token   = $this->prefix.':'.($config['key_user_token']??'USER_TOKEN').':';

        $this->conn = new \Redis();
        $this->conn->connect(config('database.redis.default.host','127.0.0.1'),config('database.redis.default.port',6379));
        $this->conn->auth(config('database.redis.default.password',null));

    }

    /**
     * 根據 identifier 取回使用者資訊
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {
        return $this->getGenericUser($this->conn->hGetAll($this->key_user_id.$identifier));
    }

    /**
     * 根據 identifier 和 rember token 取回使用者資訊
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed $identifier
     * @param  string $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token)
    {
        $key = $this->key_user_token.$identifier;
        if ($this->conn->exists($key) && $this->conn->get($key) == $token) {
            return $this->retrieveById($identifier);
        } else {
            return null;
        }
    }

    /**
     * 為使用者更新 remember token ,為空則刪除之
     * Update the "remember me" token for the given user in storage.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable $user
     * @param  string $token
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token)
    {
        $identifier = $user->getAuthIdentifier();
        $key = $this->key_user_token.$identifier;
        if (!$token) {
            $this->conn->delete($key);
        } else {
            $this->conn->set($key,$token);
        }
        return true;
    }

    /**
     * 按照給定的憑據(一般是使用者名稱密碼)檢索使用者
     *
     * Retrieve a user by the given credentials.
     *
     * @param  array $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials)
    {
        $key = $this->key_user_name.($credentials[$this->username_field]);
        if ($this->conn->exists($key)) {
            $user = $this->conn->hGetAll($key);
            return $this->getGenericUser($user);
        } else {
            return null;
        }
    }

    /**
     * 按給定的憑據(一般是使用者名稱密碼)檢驗使用者是否可以登入
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable $user
     * @param  array $credentials
     * @return bool
     */
    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        return password_verify($credentials[$this->password_field],$user->getAuthPassword());
    }

    /**
     *
     * Get the generic user.
     *
     * @param  mixed  $user
     * @return \Illuminate\Auth\GenericUser|null
     */
    protected function getGenericUser($user)
    {
        if (! is_null($user)) {
            return new GenericUser((array) $user);
        }
    }
}

東西有了,要怎麼用呢?在 app/Providers/AuthServiceProvider.php 註冊下

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //

        Auth::provider('redis',function($app,array $config){
            return new RedisUserProvider($config);
        });
    }
}

記得我們剛才一堆的配置嗎?
到 config/auth.php 裡配置,在 providers 節裡,新增一個 redis 節點。
配置成如下:

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        'redis' => [
            'driver'    => 'redis',
            'model'     => '',
            'username_field'  => 'email',
            'password_field'  => 'password',
            'key_user_id'     => env('KEY_USER_ID','USER_ID'),
            'key_user_name'   => env('KEY_USER_NAME','USER_NAME'),
            'key_user_token'  => env('KEY_USER_TOKEN','USER_TOKEN'),
        ],
    ],

現在好了,可以用了。

老楊興奮地搶過鍵盤,熟練地開啟瀏覽器,切到登入頁,填好使用者名稱和密碼。只是他遲遲沒有點下登入,而是點了一根菸。
“我們剛連的 Redis 是個空庫,裡面什麼東西都沒有!“
”沒有?那還登入個P呀。“
”使用者資訊由另一個使用者API服務提供,我這只是快取,快取明白不?“
“有點餓了,我弄個帳號讓你能先登入吧。“
接過鍵盤,開啟 redis-cli ,然後pia pia 輸入以下指令

HSET "Laravel:USER_NAME:fakename_qufo@163.com" "id" "1"
HSET "Laravel:USER_NAME:fakename_qufo@163.com" "email" "fakename_qufo@163.com"
HSET "Laravel:USER_NAME:fakename_qufo@163.com" "password" "$2y$10$4Gu6Q/wa3Gx35yl7bqbeKuPnK9fxbxtGZNG.GNPfas8mUXYjVWNEy"
HSET "Laravel:USER_NAME:fakename_qufo@163.com" "name" "qufo"
HSET "Laravel:USER_NAME:fakename_qufo@163.com" "remember_token" ""

“好了,使用者名稱 fakename_qufo@163.com 密碼 123456 ,登入試試。“
“哎,那個 remember_token 是什麼,為什麼是空的?”
“那個呀,存 remember_token 的。“
”別騙我,不是有另一個,你還定義成 key_user_token 的,存 token 麼?“
“先別管,關公戰秦瓊聽過沒,叫你定來你就定,你若是不定,他不管飯。”
”不就是一頓飯嘛,至於麼,對了,你聽說過沙縣麼?“
。。。

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

相關文章