🆔使用Hashids将数字ID生成类似YouTube的ID或B站BVID

v0.0.1 版本
2023-08-01 版本更新时间
248 安装
4 star

webman-hashids

  • Webman 中使用 Hashids 用于将数字ID生成类似YouTube的ID。当您不想向用户公开数据库数字ID时使用
  • 支持B站的ID生成模式,生成B站/video/BV1fx411v7eo这种ID

Minimum PHP Version Minimum Webman Version Stable Version Total Downloads License

安装

composer require isszz/webman-hashids

配置

在 config/plugin/isszz/webman-hashids/app.php 中更改

return [
    'enable'  => true,

    // 默认连接名称
    'default' => 'main', // 支持bilibili的BV模式

    // Hashids modes
    'modes' => [
        'main' => [
            'salt' => '',
            'length' => 0,
            'alphabet' => 'oqyei4pYnjDLXuPOw6c9IvzlWUmBs1Z0rdAkFCKM8hgHb2QV7NJ35TfaxRtESGArray'
        ],
        'other' => [
            'salt' => 'salt',
            'length' => 0,
            'alphabet' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
        ],
        'bilibili' => [
            // 此模式无需添加其他的配置
            // 前缀超过2位英文字母忽略
            'prefix' => '', // B站BV模式前缀类似: BV1fx411v7eo = 12345678
        ],
    ],
];

用法

依赖注入方式

use isszz\hashids\Hashids;

class Index
{
    public function index(Hashids $hashids)
    {
        // B站BV模式, B站模式支持第二个参数增加前缀,可以设置例如: prefix = 'BV'
        $_hashids = $hashids->mode(name: 'bilibili');
        $_hashids->encode(12345678); // 1fx411v7eo
        $_hashids->decode('1fx411v7eo'); // 12345678

        // other模式
        $hashids->mode('other')->encode(12345678); // gpyAoR
        $hashids->mode('other')->decode('gpyAoR'); // 12345678

        // 默认
        $hashids->encode(12345678); // 1rQ2go
        $hashids->decode('1rQ2go'); // 12345678

        // 其他传输ID的方式,返回为数组,对应传参
        $hashID = $hashids->encode(12, 34, 56, 78); // nyILSjosbR
        $hashID2 = $hashids->encode([12, 34, 56, 78]); // nyILSjosbR

        $result = $hashids->decode($hashID);
        // 返回数组
        /*
        $result = [
            '0' => 12
            '1' => 34
            '2' => 56
            '3' => 78
        ];
        */ 
    }
}

facade方式引入

use isszz\hashids\facade\Hashids;

class Index
{
    public function index()
    {
        // B站BV模式
        Hashids::mode('bilibili')->encode(12345678); // 1fx411v7eo
        Hashids::mode('bilibili')->decode('1fx411v7eo'); // 12345678

        // other模式
        Hashids::mode('other')->encode(12345678); // gpyAoR
        Hashids::mode('other')->decode('gpyAoR'); // 12345678

        // 默认
        Hashids::encode(12345678); // 1rQ2go
        Hashids::decode('1rQ2go'); // 12345678
    }
}

助手函数

class Index
{
    public function index()
    {
        // 加密
        id_encode(12345678); // 1rQ2go
        id_encode(12, 34, 56, 78, 'other'); // nyILSjosbR
        id_encode([12, 34, 56, 78], mode: 'other'); // nyILSjosbR

        // 解密
        id_decode('1rQ2go'); // 12345678
        id_decode('gpyAoR', 'other'); // 12345678

        // 切换模式
        id_mode('other')->encode(12345678); // gpyAoR
        id_mode('other')->decode('gpyAoR'); // 12345678

        // 助手函数还有一个获取字母表的函数
        // 拿到可以用来设置`config/plugin/isszz/webman-hashids/app.php `配置中的alphabet字段
        $alphabet = id_build_alphabet();
    }
}

使用ThinkORM获取器对ID进行加密

public function getIdAttr($value)
{
    return id_encode($value);
}

// 主键非id时, 比如是tid时
public function getTidAttr($value)
{
    return id_encode($value);
}

使用Laravel Eloquent ORM访问器对ID进行加密

// 10.x版本
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected function id(): Attribute
    {
        return Attribute::make(
            get: fn (int $value) => id_encode($value),
        );
    }
}

// 8.x版本
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function getIdAttribute($value)
    {
        return id_encode($value);
    }
}

Request请求中的使用案例

新建一个路由中间件,在需要的路由引入,不需要解密的路由不建议引入

<?php
namespace app\middleware;

use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;

class Hashid implements MiddlewareInterface
{
    public function process(Request $request, callable $next): Response
    {
        $parameters = [];
        $route = $request->route;

        if ($route) {
            $parameters = $route->param() ?: [];

            foreach ($parameters as $k => $v) {
                $parameters[$k] = $this->decodeParam($v) ?: $v;
            }

            $route->setParams($parameters);
        }

        // POST + GET 用data传值,因官方没有对参数进行二次修改的方法只有这样啦,不过也挺好用的
        $parameters = $request->all();
        if ($parameters && count($parameters) > 0) {
            foreach ($parameters as $k => $v) {
                $parameters[$k] = $this->decodeParam($v) ?: $v;
            }

            $request->data = $parameters;
        }

        return $next($request);
    }

    private function decodeParam($value)
    {
        if (!preg_match("/^[0-9a-zA-Z@]+$/", $value)) {
            return null;
        }

        // 切换模式
        if (str_contains($value, '@')) {
            [$value, $type] = explode('@', $value);
        }

        try {
            return id_decode($value, $type ?? '') ?: null;
        } catch(\Exception $e) {}

        return null;
    }
}

修改support\Request.php增加如下方法

class Request extends \Webman\Http\Request
{
    /**
     * 获取中间件中,以data参数传递的get或者post参数,通常此方法获取的是经过中间件处理后的参数
     * 
     * @param string|array|null $name
     * @param mixed $default
     * @return mixed|null
     */
    public function data(string|array|null $name = null, $default = null)
    {
        $data = $this->data ?: [];

        if(is_null($name)) {
            return $data;
        }

        $result = [];
        if(is_array($name) && count($data) > 0) {
            foreach ($name as $key => $val) {

                if (is_int($key)) {
                    $default = null;
                    $key = $val;
                    if (!key_exists($key, $data)) {
                        continue;
                    }
                } else {
                    $default = $val;
                }

                $result[$key] = $data[$key] ?? $default;
            }

            return $result;
        }

        return $data[$name] ?? $default;
    }

    /**
     * 从路由中获取参数
     * 
     * @param string|array|null $name
     * @param mixed $default
     * @return mixed|null
     */
    public function route(string|array|null $name = null, $default = null)
    {
        $data = $this->route->param() ?: [];

        if(is_null($name)) {
            return $data;
        }

        $result = [];
        if(is_array($name) && count($data) > 0) {
            foreach ($name as $key => $val) {

                if (is_int($key)) {
                    $default = null;
                    $key = $val;
                    if (!key_exists($key, $data)) {
                        continue;
                    }
                } else {
                    $default = $val;
                }

                $result[$key] = $data[$key] ?? $default;
            }

            return $result;
        }

        return $data[$name] ?? $default;
    }
}

控制器中使用例如

class TestController
{
    public function index(Request $request)
    {
        // 正常的参数传递
        // /test?id=1rQ2go&uid=daVBjxW@other
        $request->data(['uid', 'id']);
        $request->data('id');
        $request->data('uid');

        // 使用路由定义的参数
        // /test/{id}/user/{uid}
        // 官方的方法可以这样一次一个参数拿
        $request->route->param('uid');

        // 我们有对request增加一些方法比如,这样可以批量拿
        [$uid, $id] = $request->route(['uid', 'id']);

        // 也可以
        $uid = $request->route('uid', null);

        // 其中参数中以@分割,前面是要解析的参数,后面的是以那种模式解析
        return 'end';

    }
}