前言
在项目开发实施的过程中,我们经常遇到服务器需要存储大量文件的情况,而且这些文件中存在大量内容相同的文件。这篇文章的目标是使用少量代码。提供一个简单的解决方法
解决思路
- 建立一个表,用来保存用户上传的的文件记录.
- 当有新文件上传的时候,添加一条记录.并设置引用计数=1
- 当有记录删除或者修改的时候更新其对应的引用计数.
- 尔后,你就只需要定期清理引用计数为0 的文件就好.
数据表设计
文件引用计数器表
CREATE TABLE `tp_files_ref` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`path` varchar(100) NOT NULL COMMENT '文件路径 使用MD5作为文件名.',
`ref_count` mediumint(9) NOT NULL COMMENT '引用计数',
PRIMARY KEY (`id`),
UNIQUE KEY `path` (`path`) USING HASH,
KEY `ref_count` (`ref_count`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
假定我们有一个相册表,其中cover 便是用户要上传的相册封面照片.
CREATE TABLE `tp_category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pid` int(11) DEFAULT '0',
`path` varchar(35) DEFAULT NULL COMMENT '分类路径 数字 以 | 结尾',
`name` varchar(60) DEFAULT NULL COMMENT '相册标题',
`desc` varchar(120) DEFAULT NULL COMMENT '相册描述',
`cover` varchar(100) DEFAULT NULL COMMENT '相册封面',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
我们针对相册封面表编写触发器,分别是针对 插入、更新、删除的操作.
# 在记录插入的时候, 自动设置标记文件引用
CREATE TRIGGER `category_after_insert` AFTER INSERT ON `tp_category` FOR EACH ROW BEGIN
IF FALSE = ISNULL(OLD.cover) THEN
INSERT INTO `tp_files_ref`(`path`,`ref_count`)VALUES(NEW.cover, 1) ON DUPLICATE KEY UPDATE `ref_count`=`ref_count`+1;
END IF;
END;
# 在记录更新的时候,如果改变了封面字段, 自动调整引用计数表.
CREATE TRIGGER `category_after_update` AFTER UPDATE ON `tp_category` FOR EACH ROW BEGIN
IF NEW.cover != OLD.cover THEN
IF FALSE = ISNULL(OLD.cover) THEN
UPDATE `tp_files_ref` SET `ref_count`=`ref_count`-1 WHERE `path`=OLD.cover;
END IF;
IF FALSE = ISNULL(NEW.cover) THEN
INSERT INTO `tp_files_ref`(`path`,`ref_count`)VALUES(NEW.cover, 1) ON DUPLICATE KEY UPDATE `ref_count`=`ref_count`+1;
END IF;
END IF;
END;
# 在删除的时候 引用计数 - 1
CREATE TRIGGER `category_after_delete` AFTER DELETE ON `tp_category` FOR EACH ROW BEGIN
IF FALSE = ISNULL(OLD.cover) THEN
UPDATE `tp_files_ref` SET `ref_count`=`ref_count`-1 WHERE `path`=OLD.cover;
END IF;
END;
开发文件清理功能.
以下代码基于PHP ThinkPHP5.1 开发.
<?php
namespace app\common\model;
use think\Model;
/*
文件引用计数表. 用户标记 清理不必要的文件.
当 ref_count =0 的时候,则表示该文件可以被删除.
*/
class FilesRef extends Model {
private function is_empty_dir($path){
if(is_dir($path) && ($handle = opendir($path))!==false){
while(($file=readdir($handle))!==false){ // 遍历文件夹
if($file!='.' && $file!='..'){
closedir($handle);
return false;
}
}
return true;
}
return false;
}
private function ClearEmptyDir()
{
$dir = dirname($this->path);
$times = 2; //目录层数.
while(Self::is_empty_dir($dir) && $times > 0) {
rmdir($dir);
$dir = dirname($dir);
$times--;
}
}
/*
清理不需要的文件.
*/
static public function Clear()
{
$rows = Self::where('ref_count','EQ', 0)->select();
foreach($rows as $item){
$file = WWW_SITE_ROOT.$item->path;
@\unlink($file);
$item->ClearEmptyDir();
}
Self::where('ref_count', 'EQ', 0)->delete();
}
}