ThinkPHP根据用户ID生成不重复的6位邀请码(使用Hashids实现,带解码方法)

什么是hashids?

Hashids是一个能利用整数生成出短小、唯一、非连续标识符的类库,它支持包含php在内的多种语言。Hashids支持通过生成出来的标识符进行解码为原数字,还支持加盐加密,不会因为大家都用这个类库就被猜到真实ID。

hashids的原理:

数字经过一个加盐(salted)算法产生一个哈希(hash)字符串。这样算法就是通过混淆使结果具有不可预测性,而唯一性依然由数字本身来达成,从而得到(类似 youtube 里的)足够短,不可预测且唯一的 ID。

第一步、创建Hashids.class.php类文件,在App\Home\Common目录下:

Hashids.class.php的代码如下:

<?php
namespace Home\Common;//自定义命名空间,要求模块名\文件夹名字方式。(需要放在第一行)
/*

    Hashids
    http://hashids.org/php
    (c) 2013 Ivan Akimov

    https://github.com/ivanakimov/hashids.php
    hashids may be freely distributed under the MIT license.

*/

/**
 * HashGenerator is a contract for generating hashes
 */
interface HashGenerator {

    /**
     * Encodes a variable number of parameters to generate a hash
     *
     * @param mixed ...
     *
     * @return string the generated hash
     */
    public function encode();

    /**
     * Decodes a hash to the original parameter values
     *
     * @param string $hash the hash to decode
     *
     * @return array
     */
    public function decode($hash);

    /**
     * Encodes hexadecimal values to generate a hash
     *
     * @param string $str hexadecimal string
     *
     * @return string the generated hash
     */
    public function encode_hex($str);

    /**
     * Decodes hexadecimal hash
     *
     * @param string $hash
     *
     * @return string hexadecimal string
     */
    public function decode_hex($hash);

}

class Hashids implements HashGenerator {

    const VERSION = '1.0.5';

    /* internal settings */

    const MIN_ALPHABET_LENGTH = 16;
    const SEP_DIV = 3.5;
    const GUARD_DIV = 12;

    /* error messages */

    const E_ALPHABET_LENGTH = 'alphabet must contain at least %d unique characters';
    const E_ALPHABET_SPACE = 'alphabet cannot contain spaces';

    /* set at constructor */

    private $_alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
    private $_seps = 'cfhistuCFHISTU';
    private $_min_hash_length = 0;
    private $_math_functions = array();
    private $_max_int_value = 1000000000;

    const hashids_length = 6;           // 加密字符串长度
    const hashids_salt = 'test';        // 加密钥匙
    const hashids_alphabet = '';        // 字符仓库,不填写默认为扩展里的字符仓库

    private static $instance; //单例

    /**
     * 初始化
     * @param array $options
     * @return static
     */
    public static function instance($length = null, $salt = null, $alphabet = null)
    {
        if (is_null(self::$instance)) {
            if ($length === null) $length = self::hashids_length;
            if ($salt === null) $salt = self::hashids_salt;
            if ($alphabet === null) self::hashids_alphabet;

            self::$instance = new static($salt, $length, $alphabet);
        }
        return self::$instance;
    }

    public function __construct($salt = '', $min_hash_length = 8, $alphabet = '') {

        /* if either math precision library is present, raise $this->_max_int_value */

        if (function_exists('gmp_add')) {
            $this->_math_functions['add'] = 'gmp_add';
            $this->_math_functions['div'] = 'gmp_div';
            $this->_math_functions['str'] = 'gmp_strval';
        } else if (function_exists('bcadd')) {
            $this->_math_functions['add'] = 'bcadd';
            $this->_math_functions['div'] = 'bcdiv';
            $this->_math_functions['str'] = 'strval';
        }

        $this->_lower_max_int_value = $this->_max_int_value;
        if ($this->_math_functions) {
            $this->_max_int_value = PHP_INT_MAX;
        }

        /* handle parameters */

        $this->_salt = $salt;

        if ((int)$min_hash_length > 0) {
            $this->_min_hash_length = (int)$min_hash_length;
        }

        if ($alphabet) {
            $this->_alphabet = implode('', array_unique(str_split($alphabet)));
        }

        if (strlen($this->_alphabet) < self::MIN_ALPHABET_LENGTH) {
            throw new \Exception(sprintf(self::E_ALPHABET_LENGTH, self::MIN_ALPHABET_LENGTH));
        }

        if (is_int(strpos($this->_alphabet, ' '))) {
            throw new \Exception(self::E_ALPHABET_SPACE);
        }

        $alphabet_array = str_split($this->_alphabet);
        $seps_array = str_split($this->_seps);

        $this->_seps = implode('', array_intersect($alphabet_array, $seps_array));
        $this->_alphabet = implode('', array_diff($alphabet_array, $seps_array));
        $this->_seps = $this->_consistent_shuffle($this->_seps, $this->_salt);

        if (!$this->_seps || (strlen($this->_alphabet) / strlen($this->_seps)) > self::SEP_DIV) {

            $seps_length = (int)ceil(strlen($this->_alphabet) / self::SEP_DIV);

            if ($seps_length == 1) {
                $seps_length++;
            }

            if ($seps_length > strlen($this->_seps)) {

                $diff = $seps_length - strlen($this->_seps);
                $this->_seps .= substr($this->_alphabet, 0, $diff);
                $this->_alphabet = substr($this->_alphabet, $diff);

            } else {
                $this->_seps = substr($this->_seps, 0, $seps_length);
            }

        }

        $this->_alphabet = $this->_consistent_shuffle($this->_alphabet, $this->_salt);
        $guard_count = (int)ceil(strlen($this->_alphabet) / self::GUARD_DIV);

        if (strlen($this->_alphabet) < 3) {
            $this->_guards = substr($this->_seps, 0, $guard_count);
            $this->_seps = substr($this->_seps, $guard_count);
        } else {
            $this->_guards = substr($this->_alphabet, 0, $guard_count);
            $this->_alphabet = substr($this->_alphabet, $guard_count);
        }

    }

    public function encode() {

        $ret = '';
        $numbers = func_get_args();

        if (func_num_args() == 1 && is_array(func_get_arg(0))) {
            $numbers = $numbers[0];
        }

        if (!$numbers) {
            return $ret;
        }

        foreach ($numbers as $number) {

            $is_number = ctype_digit((string)$number);

            if (!$is_number || $number < 0 || $number > $this->_max_int_value) {
                return $ret;
            }

        }

        return $this->_encode($numbers);

    }

    public function decode($hash) {

        $ret = array();

        if (!$hash || !is_string($hash) || !trim($hash)) {
            return $ret;
        }

        return $this->_decode(trim($hash), $this->_alphabet);

    }

    public function encode_hex($str) {

        if (!ctype_xdigit((string)$str)) {
            return '';
        }

        $numbers = trim(chunk_split($str, 12, ' '));
        $numbers = explode(' ', $numbers);

        foreach ($numbers as $i => $number) {
            $numbers[$i] = hexdec('1' . $number);
        }

        return call_user_func_array(array($this, 'encode'), $numbers);

    }

    public function decode_hex($hash) {

        $ret = "";
        $numbers = $this->decode($hash);

        foreach ($numbers as $i => $number) {
            $ret .= substr(dechex($number), 1);
        }

        return $ret;

    }

    public function get_max_int_value() {
        return $this->_max_int_value;
    }

    private function _encode(array $numbers) {

        $alphabet = $this->_alphabet;
        $numbers_size = sizeof($numbers);
        $numbers_hash_int = 0;

        foreach ($numbers as $i => $number) {
            $numbers_hash_int += ($number % ($i + 100));
        }

        $lottery = $ret = $alphabet[$numbers_hash_int % strlen($alphabet)];
        foreach ($numbers as $i => $number) {

            $alphabet = $this->_consistent_shuffle($alphabet, substr($lottery . $this->_salt . $alphabet, 0, strlen($alphabet)));
            $ret .= $last = $this->_hash($number, $alphabet);

            if ($i + 1 < $numbers_size) {
                $number %= (ord($last) + $i);
                $seps_index = $number % strlen($this->_seps);
                $ret .= $this->_seps[$seps_index];
            }

        }

        if (strlen($ret) < $this->_min_hash_length) {

            $guard_index = ($numbers_hash_int + ord($ret[0])) % strlen($this->_guards);

            $guard = $this->_guards[$guard_index];
            $ret = $guard . $ret;

            if (strlen($ret) < $this->_min_hash_length) {

                $guard_index = ($numbers_hash_int + ord($ret[2])) % strlen($this->_guards);
                $guard = $this->_guards[$guard_index];

                $ret .= $guard;

            }

        }

        $half_length = (int)(strlen($alphabet) / 2);
        while (strlen($ret) < $this->_min_hash_length) {

            $alphabet = $this->_consistent_shuffle($alphabet, $alphabet);
            $ret = substr($alphabet, $half_length) . $ret . substr($alphabet, 0, $half_length);

            $excess = strlen($ret) - $this->_min_hash_length;
            if ($excess > 0) {
                $ret = substr($ret, $excess / 2, $this->_min_hash_length);
            }

        }

        return $ret;

    }

    private function _decode($hash, $alphabet) {

        $ret = array();

        $hash_breakdown = str_replace(str_split($this->_guards), ' ', $hash);
        $hash_array = explode(' ', $hash_breakdown);

        $i = 0;
        if (sizeof($hash_array) == 3 || sizeof($hash_array) == 2) {
            $i = 1;
        }

        $hash_breakdown = $hash_array[$i];
        if (isset($hash_breakdown[0])) {

            $lottery = $hash_breakdown[0];
            $hash_breakdown = substr($hash_breakdown, 1);

            $hash_breakdown = str_replace(str_split($this->_seps), ' ', $hash_breakdown);
            $hash_array = explode(' ', $hash_breakdown);

            foreach ($hash_array as $sub_hash) {
                $alphabet = $this->_consistent_shuffle($alphabet, substr($lottery . $this->_salt . $alphabet, 0, strlen($alphabet)));
                $ret[] = (int)$this->_unhash($sub_hash, $alphabet);
            }

            if ($this->_encode($ret) != $hash) {
                $ret = array();
            }

        }

        //修改为直接返回字符串
        if(isset($ret[0])){
            return $ret[0];
        }else{
            return false;
        }


    }

    private function _consistent_shuffle($alphabet, $salt) {

        if (!strlen($salt)) {
            return $alphabet;
        }

        for ($i = strlen($alphabet) - 1, $v = 0, $p = 0; $i > 0; $i--, $v++) {

            $v %= strlen($salt);
            $p += $int = ord($salt[$v]);
            $j = ($int + $v + $p) % $i;

            $temp = $alphabet[$j];
            $alphabet[$j] = $alphabet[$i];
            $alphabet[$i] = $temp;

        }

        return $alphabet;

    }

    private function _hash($input, $alphabet) {

        $hash = '';
        $alphabet_length = strlen($alphabet);

        do {

            $hash = $alphabet[$input % $alphabet_length] . $hash;
            if ($input > $this->_lower_max_int_value && $this->_math_functions) {
                $input = $this->_math_functions['str']($this->_math_functions['div']($input, $alphabet_length));
            } else {
                $input = (int)($input / $alphabet_length);
            }

        } while ($input);

        return $hash;

    }

    private function _unhash($input, $alphabet) {

        $number = 0;
        if (strlen($input) && $alphabet) {

            $alphabet_length = strlen($alphabet);
            $input_chars = str_split($input);

            foreach ($input_chars as $i => $char) {

                $pos = strpos($alphabet, $char);
                if ($this->_math_functions) {
                    $number = $this->_math_functions['str']($this->_math_functions['add']($number, $pos * pow($alphabet_length, (strlen($input) - $i - 1))));
                } else {
                    $number += $pos * pow($alphabet_length, (strlen($input) - $i - 1));
                }

            }

        }

        return $number;

    }

}

 第二步、在Controller控制器中引入Hashids类,并使用Hashids的类方法实现加解密操作。

1、引入Hashids类

2、实例化Hashids类,加解密

/*-----------------根据用户ID生成邀请码 start-----------------------------*/
//1.实例化Hashids类
$hashids=Hashids::instance(6,'mackie');//6为邀请码的长度,mackie为加盐密钥
//2.根据用户Id加密
$yqm=$hashids->encode(session('USER_KEY_ID'));
//3.反向解密
$id=$hashids->decode($yqm);
//4.输出到前台页面
$this->assign('yqm',$yqm);
$this->assign('id',$id);
$this->display();
/*-----------------根据用户ID生成邀请码 end------------------------------*/

3、最后我们看一下加解密的效果

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值