全自动短地址hash映射(shortUrl)

/**
 * CREATE TABLE `shortUrl` (
 * `id` int(11) NOT NULL AUTO_INCREMENT,
 * `key` int(11) unsigned NOT NULL,
 * `suffix` int(11) unsigned NOT NULL,
 * `url` text NOT NULL,
 * `num` int(11) NOT NULL,
 * `timeout` int(11) NOT NULL,
 * `createTime` int(11) NOT NULL,
 * `uri` char(16) GENERATED ALWAYS AS (concat(lower(hex(`key`)),hex(`suffix`))) VIRTUAL NOT NULL,
 * PRIMARY KEY (`id`),
 * UNIQUE KEY `uniq` (`key`,`suffix`) USING BTREE
 * ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='short url redirect';
 */
class ShortUrl
{
    private $pdo;

    private $tableName;

    private $strict = false;

    /**
     *
     * @param \PDO $pdo
     * @param string $tableName
     */
    function __construct($pdo, $tableName)
    {
        $this->pdo = $pdo;
        $this->tableName = $tableName;
    }

    /**
     *
     * @param boolean $flag
     */
    function setStrict($flag)
    {
        $this->strict = $flag;
    }

    /**
     *
     * @return boolean
     */
    function getStrict()
    {
        return $this->strict;
    }

    /**
     *
     * @param string $url
     * @param number $timeout
     * @return string
     */
    function add($url, $timeout = 0)
    {
        $query = parse_url($url, PHP_URL_QUERY);
        if (! empty($query)) {
            parse_str($query, $query);
            if (! $this->strict) {
                ksort($query);
            }
            $query = http_build_query($query);
            $classUrl = '\Http\Url';
            if (function_exists('http_build_url')) {
                $parse = parse_url($url);
                $parse['query'] = $query;
                $url = http_build_query($parse);
            } elseif (class_exists($classUrl)) {
                $parse = new $classUrl($url);
                $parse->query = $query;
                $url = $parse->toString();
            } else {
                $url = preg_replace('/\?(.+)(#|$)/', '?' . $query, $url);
            }
        }
        $data = array();
        $key = crc32($url);
        $key = sprintf("%u", $key);
        $sql = 'select uri from ' . $this->tableName . ' where `key`=' . $key;
        $sql .= ' and url=?';
        $stat = $this->pdo->prepare($sql);
        $stat->execute(array(
            $url
        ));
        $uri = $stat->fetchColumn();
        if (! empty($uri)) {
            return $uri;
        }
        $suffix = 0;
        // Find min usable suffix regardless exists values.Very powerful algorithm.
        $batch = 1000;
        $loop = 0;
        $this->pdo->exec('lock tables ' . $this->tableName . ' write');
        while (true) {
            $sql = 'select suffix from ' . $this->tableName;
            $sql .= ' where `key`=' . $key;
            $sql .= ' and suffix>=' . $suffix;
            $sql .= ' order by suffix limit ' . $batch;
            $stat = $this->pdo->query($sql);
            $list = $stat->fetchAll(\PDO::FETCH_NUM);
            if (empty($list)) {
                break;
            }
            foreach ($list as $k => $v) {
                $suffix = ($k + $batch * $loop);
                if ($suffix != $v[0]) {
                    break 2;
                }
            }
            $suffix ++;
            if ($k + 1 < $batch) {
                break;
            }
            $loop ++;
        }
        $data['key'] = $key;
        $data['suffix'] = $suffix;
        $data['url'] = $url;
        $data['num'] = 0;
        $data['timeout'] = $timeout;
        $data['createTime'] = time();
        $sql = 'insert into ' . $this->tableName;
        $sql .= '(`key`,suffix,url,num,timeout,createTime)';
        $sql .= 'values(:key,:suffix,:url,:num,:timeout,:createTime)';
        $stat = $this->pdo->prepare($sql);
        $res = $stat->execute($data);
        $this->pdo->exec('unlock tables');
        if ($res) {
            return dechex($key) . dechex($suffix);
        }
    }

    /**
     *
     * @return boolean
     */
    function clean()
    {
        $time = time();
        $stat = $this->pdo->prepare(
            'delete from ' . $this->tableName .
                 ' where timeout>0 && createTime+timeout<?');
        return $stat->execute(array(
            $time
        ));
    }

    /**
     *
     * @param string $key
     * @return null|integer 1:timeout or unavailable
     */
    function redirect($key)
    {
        $suffix = substr($key, 8);
        $key = substr($key, 0, 8);
        $key = sprintf("%u", hexdec($key));
        $sql = 'select * from ' . $this->tableName;
        $sql .= ' where `key`=? and suffix=?';
        $stat = $this->pdo->prepare($sql);
        $stat->execute(array(
            $key,
            $suffix
        ));
        $row = $stat->fetchObject();
        if (false == $row) {
            return 1;
        }
        if ($row->timeout > 0) {
            $expire = $row->createTime + $row->timeout;
            if ($expire < time()) {
                return 1;
            }
        }
        $sql = 'update ' . $this->tableName . ' set num=num+1 where `key`=?';
        $sql .= ' and suffix=?';
        $stat = $this->pdo->prepare($sql);
        $stat->execute(array(
            $key,
            $suffix
        ));
        header('Location: ' . $row->url);
    }
}

下载:ShortUrl.php

发表评论

电子邮件地址不会被公开。

*