Laravel 中使用 Redis 生成自增主键

Laravel 中使用 Redis 生成自增主键

终于,开始使用 Laravel 开发正式项目,虽然看过很多文章,做过一些练习,但是真搞起来,仍然是个丈二。

考虑到国情,项目一开始就考虑分库分表的事情,数据库管控的自增主键值,不利于将来搞大事,但是又不想使用 uuid,所以要对所有表主键值进行统一管理,于是应用 Redis 生成.

主键值的填充可以在 Model 事件 creating 的时候进行,这里有两种方式,详情请见下文。

1. 创建主键值生成器

独立出来,在某些情况可以单独调用。

<?php

namespace App\Support\Database;

use Illuminate\Support\Facades\Redis;
use Illuminate\Database\Eloquent\Model;

class PrimaryKeyValueGenerator
{
    /**
     * 自增值
     *
     * @param string $key
     * @return integer
     * @author Chuoke
     */
    public function incrementId($key = 'model-primary-key')
    {
        return Redis::incr($this->formatKey($key));
    }

    /**
     * 给模型实例生成主键值
     *
     * @param Model $model
     * @return void
     * @author Chuoke
     */
    public function incrementIdOfModel(Model $model)
    {
        $key = $this->buildKeyOfModel($model);

        return self::incrementId($key);
    }

    /**
     * 给 model 实例生成一个主键 key
     *
     * @param Model $model
     * @return string
     * @author Chuoke
     */
    public function buildKeyOfModel(Model $model)
    {
        return implode('_', ['table', $model->getTable(), 'id']);
    }

    /**
     * 格式化 key
     *
     * @param string $key
     * @return string
     * @author Chuoke
     */
    public function formatKey($key)
    {
        return strtoupper($key);
    }
}

2. 主键填充

方便起见,把填充方法封成 trait.


<?php

namespace App\Models\Traits;

use App\Support\Facades\PrimaryKeyValueGenerator;

trait MustFillPrimaryKey
{
    /**
     * 启动填充主键值事件
     *
     * @return void
     * @author Chuoke
     */
    public static function bootMustFillPrimaryKeyEvent()
    {
        static::creating(function ($model) {
            $model->fillPrimaryKey();
        });
    }

    /**
     * 填充主键值
     *
     * @param \App\Modells\Model $model
     * @return void
     * @author Chuoke
     */
    public function fillPrimaryKey()
    {
        if ($this->needFillPrimaryKey()) {
            $id = PrimaryKeyValueGenerator::incrementIdOfModel($this);
            $this->setAttribute($this->getKeyName(), $id);
        }
    }

    /**
     * 判断是否需要填充主键值
     *
     * @return boolean
     * @author Chuoke
     */
    public function needFillPrimaryKey()
    {
        return empty($this->getKey())
            // && $this->getIncrementing()
            && $this->getKeyName() !== false;
    }
}

设置 incrementing = false `` 避免执行 insertAndSetId() 导致插入后主键值为 0 `

3. 事件监听

Model 中的 trait 可以做一个和 trait 名称一样的 boot 方法,如:bootbootMustFillPrimaryKey, 这样在启动的时候会自动启动,所以上面有一个这个类似的方法,只是个备份,如果是个别的需要这样的操作,使用这种方式很方便。

创建一个事件处理


<?php

namespace App\Events;

use Illuminate\Queue\SerializesModels;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;

class FillModelPrimaryKey
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $model;

    public function handle($e, $payload)
    {
        if (is_array($payload)) {
            foreach ($payload as $model) {
                $this->handle($e, $model);
            }
        } else if ($payload instanceof Model && \method_exists($payload, 'fillPrimaryKey')) {
            $payload->fillPrimaryKey();
        }
    }

}

然后在 App\Providers\EventServiceProvider 中注册监听所有的模型创建事件.


    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'eloquent.creating: *' => [
            FillModelPrimaryKey::class,
        ],
    ];

这里有个小坑,调了半天,回眸了若干次源码才发现,那个 * 前面居然有个空格 ?

4. 无侵入式方案

下面这种方式是全局性的,更灵活,不需要引入 trait,合理的利用了框架提供的能力,第三方扩展也会使用该方式生成主键,如果某些表有特殊的自定义情况,可以使用上面那种方式,当然也可以结合使用。

<?php

namespace App\Events;

use Illuminate\Queue\SerializesModels;
use Illuminate\Database\Eloquent\Model;
use App\Support\Facades\PrimaryKeyValueGenerator;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;

class FillModelPrimaryKey
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    public function handle($e, $payload)
    {
        if (is_array($payload)) {
            foreach ($payload as $model) {
                $this->handle($e, $model);
            }
        } else if ($payload instanceof Model) {
            if ($payload->getIncrementing() && empty($payload->getKey())) {
                // 关闭主键自增,以避免内部获取自增主键值逻辑(insertAndSetId())导致主键被置空
                $payload->setIncrementing(false); 
                $payload->setAttribute($payload->getKeyName(), $this->generatePk($payload));
            }
        }
    }

    /**
     * 生成主键值
     *
     * @return integer
     * @author Chuoke
     */
    protected function generatePk($model)
    {
        return PrimaryKeyValueGenerator::getIdOfModel($model);
    }
}

总结

无论是自增主键还是普通主键,只要有主键就行。统一生成有利于分库分表,还可以方便做其他特殊化的事情,这只是一个思路,虽然能够运用 Laravel 进行开发,但是还不够了解,有很多需要优化的地方,自定义框架的功能可以对框架进行深入的了解,慢慢进步吧。

初出茅庐,一知半解,望有识之士多多指教 ?

本作品采用《CC 协议》,转载必须注明作者和本文链接
? 我的导航网站已经可以公开使用啦:Cootab
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 2

redis 是内存数据库 关系型数据库的主键增长是比较稳定的 uuid 也是可以满足分布式系统的需求

4年前 评论

@hrz3424 redis 可以持久化存储,他已经不只是能做做缓存的那个 redis 了,uuid 太占空间是不想用的原因之一

4年前 评论

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