记一次博客的优化 -- 哈希数据ID

October . 09 . 2018

今天对博客的数据ID进行了一次优化, 优化效果如图:

优化前:

blog1.PNG

优化后:

blog2.PNG

效果显而易见, 普通项目中, 一般使用的项目ID通常为数据库的自增ID, 但是自增的ID有一些问题:

  • 数据的ID是暴露的
  • 会被别有用心的人恶意采集
  • 可以很容易的猜测出数据库中的数据个数
  • 随着数据的增长, ID会越来越长, 直接显示在URL中会十分的突兀

开始我的想法是, 使用加密等手段自己生成ID, 将数据库中的新增ID映射字段并统一为自定义生成的ID, 但是这个方案对其他使用者十分不友好, 我需要给每个人解释一遍我数据库中的该字段是做什么的.

于是我开始百度其他人是怎么解决的, 了解到一个名为 vinkla/hashids 的包完美的解决了这个问题, 人生苦短, 既然有现成的轮子, 何必花时间自己写呢, 开工!

vinkla/hashidshashids 在 Laravel 中的封装,hashids 是一个可以通过数字生成简短,唯一,无序字符串的开源库,它不仅能将数字转换为字符串,还能将转换后的结果转换回数字,利用这一点,我们可以很好的解决上述问题。

分析目前的问题:

  • 将所有的文章 ID 由 /topics/1 修改为 hashids 转换后的值 /topics/nNO31rpJx , 所有涉及文章的地方都要更改.
  • 在查询时由于此时获取到的是一个哈希字符串, 需要先将这个字符串转回先前的ID在进行查询操作.
  • 由于我才用的是 路由模型绑定 的方式获取文章, 需要重新实现一下模型路由绑定的中间件, 或者在所有使用模型绑定的地方都用自己的方法实现.
  • 要考虑到代码的封装以及重用以便以后扩展业务, 不违背 DRY(Don't repeat yourself) 原则

即便是个不起眼的小优化, 但涉及的东西还是不少, 处理时还需谨慎


1. 首先安装扩展包:

  在安装扩展包是需要注意, 由于我当前的 laravel 版本是 5.5, 如果我直接使用

$ composer require vinkla/hashids

会报一个vinkla/hashids 5.0.0 requires illuminate/contracts 5.6.*的错, 大概意思是5.0.0版本vinkla/hashids需要laravel 5.6 才能安装, 所以 5.6 以下版本在安装这个包是需要制定一下版本号

$ composer require vinkla/hashids:~3.3


2. 发布配置文件:

$ php artisan vendor:publish --provider="Vinkla\Hashids\HashidsServiceProvider"

更改配置如下

    'default' => 'main',
    'connections' => [

        'main' => [
            'salt' => 'larabbs',
            'length' => '9',
        ],

        'alternative' => [
            'salt' => 'your-salt-string',
            'length' => 'your-length-integer',
        ],
    ]

vinkla/hashids 的思路是将数字转换为字符串,同时又可以将字符串再转换回数字。为了保证字符串不被轻易的破解,我们需要配置盐(salt),也就是给目标数字额外增加一些变量,保证他人在没有相同盐(salt)的情况下,无法将符串再转换回数字,增加了安全性。同时我们还可以指定转换后字符串的最小长度,这样能保证 ID 尽量统一美观。


3. 创建一个 Trait 文件:

$ touch app/Models/Traits/HashIdHelper.php

填入如下内容:

app/Models/Traits/HashIdHelper.php

hash_id 时触发
    public function getHashIdAttribute()
    {
        if (!$this->hashId) {
            $this->hashId = Hashids::encode($this->id);
        }

        return $this->hashId;
    }

    // 先将参数 decode 为模型id,再调用父类的 resolveRouteBinding 方法
    public function resolveRouteBinding($value)
    {
        if (!is_numeric($value)){
          $value = current(Hashids::decode($value));
          if (!$value) {
              return;
          }
        }

        return parent::resolveRouteBinding($value);
    }

    // 使用 hash_id 生成 URL
    public function getRouteKey()
    {
        return $this->hash_id;
    }
}

重写了 resolveRouteBindinggetRouteKey 两个方法。其中 getHashIdAttribute 是自定义的一个访问器,当访问模型的 hash_id 属性时会通过该方法返回数据,这样任何使用了这个 Trait 的 Eloquent 模型就都能直接使用 hashids 了。


4. 最后在Topic模型中使用这个 Trait:

use Traits\HashIdHelper;

大功告成!