老杨说他的用户信息存在 Redis 里面

去找老杨,他说要请我吃饭,结果到那一看,他正咬着笔目光呆滞,“怎么了?”我问他。
“你来得正好,帮我看下这个登录怎么弄。“
”一个登录,还能怎么弄,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 协议》,转载必须注明作者和本文链接
本帖由 Summer 于 6年前 加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 26

:bowtie: 搬好板凳来学习

6年前 评论
Summer

我靠,我居然看完了。

6年前 评论
superwen

点赞点赞。

6年前 评论

对了,你听说过沙县国际吗?: :) :

6年前 评论

简直及时雨,我刚想着怎么做,教程就来了,哈哈!

6年前 评论
Destiny

很幽默,学习了。

6年前 评论

厉害了

6年前 评论
Bin

这算是沙县被黑的最惨的一次么?

6年前 评论

我跳过代码,把对话看完了。对话很吸引我。一会儿去沙县来个大肉饭!

6年前 评论

方案是否可行?还是黑幽默?我感觉直接mySQL就行了。干嘛用redis缓存啊(^^)

6年前 评论

好有才的对话~赞(((o(゚▽゚)o)))

6年前 评论

?? 还是 ?:

6年前 评论

这个情景好,虽然代码没看懂。

6年前 评论

蛮有意思。

6年前 评论

对话太多,还是秒看代码

6年前 评论

@我是谁
?: 判断左边是否为 TRUE, ?? 判断左边是否为 NULL

6年前 评论
gitxuzan

挺好的,有时间跟着做

6年前 评论

啥意思?

6年前 评论

受益匪浅,感谢分享~!

有个疑惑,根据ID获取的时候 key 在 Redis 里面是不存在的 , 这个是有什么其他处理的代码吗?
我现在的处理方式是把username作为ID。

再次感谢。

5年前 评论

赞!思路清晰,过了两遍。哈!

5年前 评论

沙县给你多少钱,我河间驴肉火烧给你翻倍

5年前 评论

挺有意思的,求后续

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!