OK,上一篇我们一起将wp_identicon剥离出来作为自己的随机头像引擎。

源码托管在github:https://github.com/sevensth/DWRA.git

此篇我们要将其改造成一个直接返回图片数据的单独服务,再为其增加限制缓存数量的函数,并为其增加静态解析的功能,像这样:

 

实际上我采取的静态解析非常简单(仅适合访问量不大的情况):

  1. 将传入的字符串先进行hash(或者假设传入的字符串已经hash过)。
  2. 先从静态解析目录中找到hash所对应的图片文件。
  3. 如果找到这样的文件,直接返回此文件的内容。
  4. 如果找不到,则正常调用wp_identicon进行随机生成。

但是重点根本就不是这个简单的要死的逻辑,而是怎么修改wp_identicon的代码对吧?

让我们来修改wp_identicon的函数:

// init random seed
if ($random) $id=substr(sha1($seed),0,10);
else $id=$seed;
$filename=substr(sha1($id.substr(get_option('admin_email'),0,5)),0,15).'.png';
if ($outsize=='') $outsize=$this->identicon_options['size'];
if($displaysize=='') $displaysize=$outsize;
if (!file_exists(WP_IDENTICON_DIR_INTERNAL.$filename)){

额,这个代码风格真是糟心。

不过我们可以看到,$random参数打开时,传入的数据$seed会被先hash然后截取前10个字符,为何要截取?我尝试了一下去除这个截取的函数,发现wp_identicon运作都不正常了,我猜测是wp_identicon的算法并不支持太长的hash字符串。

然后我们发现它为随机头像缓存图片取名时,使用admin email地址的前5个字符拼接id,然后再hash一次,最后取前15个字母作为文件名,我们不能这么做,因为我们已经脱离wordpress环境,没有什么admin email这种东西。

不管怎么样,我们要修改它:

// init random seed
$id=substr($seed, 0, 10);
$filename=md5($id) . '.png';
if ($outsize=='') $outsize=$this->identicon_options['size'];
if($displaysize=='') $displaysize=$outsize;
$fileLocalPath = WP_IDENTICON_DIR_INTERNAL.DIRECTORY_SEPARATOR.$filename;
if (!file_exists($fileLocalPath)){

OK,现在我把它改为【假定传入的seed已经被hash过】,但是要保持截取10个字符的语句,否则wp_identicon会运作不正常。还有取名的方法,直接md5一下完事,实际上直接使用$id也是可以的。

好了,build函数修改好,接下来我们增强调用处:

//get static
$avatarList = glob(WP_IDENTICON_ROOT_PATH . DIRECTORY_SEPARATOR . DW_IDENTICON_STATIC_DIR . DIRECTORY_SEPARATOR . $seed . '.*', GLOB_NOSORT);
$avatar = $avatarList[0];
if (!$avatar)
{
    //generate avatar
    include 'wp_identicon.php';
    $idc = new identicon();
    //function identicon_build($seed='',$altImgText='',$img=true,$outsize='',$write=true,$random=true,$displaysize='',$gravataron=true)
    $avatar = $idc->identicon_build($seed, '', false, $size, true, true, $size, $gravatar);
}

首先我们从指定的静态目录中获取名为$seed但是扩展名任意的文件们,这里使用*配合glob函数,可以匹配任意扩展名的同名文件,这样做的好处是可以兼容不同的图片类型。

然后glob可能返回0个1个或者多个文件名,我们直接取第一个也就是下标为0的,如果未取得,说明不存在静态解析文件,那么直接进入wp_identicon的流程即可。

这里我已经假设build函数返回的是文件的本地路径($img参数传false),所以我们再修改一下wp_identicon:

$filename=get_option('siteurl').WP_IDENTICON_DIR.$filename;
if($this->identicon_options['gravatar']&&$gravataron)
$filename = "http://www.gravatar.com/avatar.php?gravatar_id=".md5($seed)."&size=$outsize&default=$filename";
if ($img){
    $filename='<img class="identicon" src="'.$filename.'" alt="'.str_replace('" width="'.$displaysize.'" height="'.$displaysize.'" />';
    }
return $filename;

原代码返回的是文件的网址或者img标签,我们需要把它改为返回文件本地路径或者img标签:

$processedFileName=WP_IDENTICON_DIR.DIRECTORY_SEPARATOR.$filename;
if($this->identicon_options['gravatar']&&$gravataron)
    $processedFileName = "http://www.gravatar.com/avatar.php?gravatar_id=".$seed."&size=$outsize&default=$processedFileName";
if ($img){
    $processedFileName='<img class="identicon" src="'.$processedFileName.'" alt="'.str_replace('" width="'.$displaysize.'" height="'.$displaysize.'" />';
}
else
{
    $processedFileName = WP_IDENTICON_DIR_INTERNAL.DIRECTORY_SEPARATOR.$filename;
}
return $processedFileName;

OK,当$img参数false时,返回的是WP_IDENTICON_DIR_INTERNAL拼接出的路径,还记得上一篇中讲的吗,WP_IDENTICON_DIR_INTERNAL是用来拼接出本地路径

至于DIRECTORY_SEPARATOR这个东西,还要看前方的WP_IDENTICON_DIR_INTERNAL是否有结尾斜杠,我的没有,所以要手动加入DIRECTORY_SEPARATOR。

最后我们要创建一个静态解析的目录,并在里面防止要静态解析的图片,比如我的:

c78fccb17b567e263d0bd863e776d77a.jpeg

那么到此,静态解析功能模块算完成了,我们接下来要将其改为直接输出图片数据到客户端:

header('Content-type: image/png');

使用这样的header可以告诉网页浏览器,此地址的输出内容是个png图片,但是我们可能输出的不是png图片,那么只要动态支持一下:

//output image
$filename = basename($avatar);
$file_extension = strtolower(substr(strrchr($filename,"."),1));
$contentType = 'application/octet-stream';
switch($file_extension)
{
    case "gif": $contentType="image/gif"; break;
    case "png": $contentType="image/png"; break;
    case "jpeg":
    case "jpg": $contentType="image/jpeg"; break;
    default:
}
header('Content-type: ' . $contentType);

OK,这样我们就支持了gif/png/jpg/jpeg这四种,要支持其它的只要依葫芦画瓢即可,不过也没什么必要,你总不会输出bmp给客户端吧,输出tiff,svg这种东西也没什么必要。

最后,我们要输出我们的图片数据:

header('Content-Length: ' . filesize($avatar));
@readfile($avatar);

readfile这个函数会读取文件内容并直接输出到标准输出,这样就到达了输出文件内容到网页浏览器的目的,@前缀是用来抑制错误消息。

So far so good~

近乎完美,哈哈,就差一个缓存控制函数了,我们要在build函数里进行再改造:

	imagedestroy($out);
    $this->limitCache();
}
else
{
    touch($fileLocalPath);
}

当没有找到缓存时,生成新随机图片,然后调用缓存控制函数;当找到缓存图片时,我们直接touch它,更新它的修改时间,这样缓存控制函数会认为它是一个比较新的图片。

接下来请出我们的缓存控制函数:

function limitCache($maxCount = 512, $extensions = ['png', 'jpg', 'jpeg', 'bmp', 'gif'])
{
    if (DEBUG)
    {
        $maxCount = 7;
    }
    $glob = WP_IDENTICON_DIR_INTERNAL.DIRECTORY_SEPARATOR.'*.{' . implode(',', $extensions) . '}';
    $files = glob($glob, GLOB_BRACE|GLOB_NOSORT);
    array_multisort(array_map('filemtime', $files), SORT_NUMERIC, SORT_ASC, $files);
    $fileCount = count($files);
    if ($fileCount > $maxCount)
    {
        $deleteFiles = array_slice($files, 0, $fileCount - $maxCount);
        array_map('unlink', $deleteFiles);
    }
    return $deleteFiles;
}

我们还是故技重施,使用glob函数来匹配所有想要的文件,不过这次我们使用了GLOB_BRACE这个参数来告诉glob函数,我们使用{gif,png}这样的方式来进行匹配。接下来我们使用修改时间(filemtime函数)来排序(array_multisort函数)。最后我们找到超出maxCount的数量,并使用unlink函数删除之。

array_map函数是用来对数组中的每个元素调用第一个参数指定的函数。

注意,这个函数并不适用于大量图片,比如10万个,因为它会首先将图片文件列表读入内存。

ok,这样我们只要修改maxCount值就可以了~

大功告成,我们还差一件事:

防止别人偷偷调用我们的随机头像服务,下一篇我们要构建一个简单的验证机制 :)

评论模块尚未加载