分为3张表,
1.file_dir(包括文件夹和文件),
2.file_userl(用户表,用于同一个文件夹【第二次文件夹,比如项目】的多人协作)
3.file(存储的是文件真实地址的url)
设计的表结构需要解决如下几个方面的性能问题
1.解决批量插入文件夹/文件( id是不能用的)
处理手段:用如下规则生成当前文件夹/文件的唯一值:unique_hash = md5(文件路径(md5_path)+项目创建人ID(salt)+文件类型(文件/文件夹))
使用如上生成方式的确可以解决文件夹/文件的唯一性问题,但有一个致命的问题,是因为文件名/文件路径参与了唯一值计算,当在文件系统中修改文件,会导致当前目录的hash和子目录/子文件的hash都需要变更,这是一个灾难性的,更新性能会很差
那如何设计一个良好的表结构,满足功能(批量插入、批量更新【不用更新当前的唯一hash】),以解决如下问题:
2.批量插入(第一条已经解决没问题)
3.更新文件夹名称(不更新hash的化,性能就很高了,只需要更新当前的文件夹名称和子目录的path,)
解决方式:
a.设计冗余的文件夹名称和冗余的文件夹路径,文件信息的展示都是展示冗余的文件名和冗余的路径,之前的文件名和路径只是为了生成唯一的hash
b.如果复制过来的文件夹和当前的文件夹重名,修改待插入的文件夹名称(加时间戳),生成规则按照最新的文件夹+路径生成即可
c.如果目标目录之前的文件夹从a改成了b,另一个目录下的a现在需要拷贝到和当前b目录同级别的,就会导致问题(目标目录的文件夹名字虽然变了,但对应的hash值没有变,copy源a文件夹过来会导致生成的hash和目标b的hash值一样,这里解决方案是直接判断重名的有两种【文件夹重名,hash重名】,给重名的hash加一个时间戳生成就没问题了)
通过以上处理方式可以实现批量插入数据,批量更新数据,不用考虑唯一值hash的问题,为什么我对于这块有这些认识,因为本人当前就是面临着几千万条数据量的问题,之前只设计到第一种手段的程度,没有解决批量更新的性能问题,所以接下来新项目设计考虑到了这个问题。
调用生成的代码:
$hashes = generateFileHash(['path' => $file_config['file_root_path'], 'name' => $project_sn, 'salt' => $params['uid'], 'type' => 'd']);
/**
* 根据一些参数生成文件表需要的hash字段值
*
* @param array
*
* @return array
**/
function generateFileHash($params)
{
//该生成hash只适合以下场景:1.工作流返回数据,初始化项目文件夹,为什么不适合其他情况:如果文件目录修改了,对应的hash不会修改,如果使用此方法生成的parent_hash就无法匹配的上父级的unique_hash
//unique_hash的生成是可以用来插入数据的
try {
$verify = new \Custom\Org\Verify;
//验证必填, path:路径, name:文件/文件夹名字, salt:如果当前是为了生成项目的则传项目创建者ID,如果是属于项目文件夹下的,也是传项目创建者ID, type:d(文件夹),f(文件)
$verify->required($params, ['path', 'name', 'salt', 'type']);
if ($params['path'][strlen($params['path'])-1] == '/') {
$params['path'] = substr($params['path'], 0, strlen($params['path'])-1);
}
$count_path = explode('/', $params['path']);
if ($count_path == 2) {
return array(
'unique_hash' => md5($params['path'].'/'.$params['name'].'/'.$params['salt'].'/'.$params['type']), //当前行的唯一标识
'privilege_hash' => md5($params['name'].'/'.$params['salt']), //用于第二层级 level:2的权限层级,一方面用于共享项目文件的查看,一方面是
'parent_hash' => md5($params['path'].'/'.$params['salt'].'/d'), //父级一定是一个文件夹
);
}
//不是第二层目录的,只返回unique_hash,因为存在目录的变化性,且另两个都是从父目录得到的,所以不提供生产,防止错误使用
return array(
'unique_hash' => md5($params['path'].'/'.$params['name'].'/'.$params['salt'].'/'.$params['type'])
);
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
相关的数据表结构:
添加、修改 文件夹代码:
/**
* 添加目录
*
* @param string
*
* @return array
**/
public function addDir($params)
{
\Db::startTrans();
try {
if (empty($params)) {
throw new \Exception('参数有误');
}
//权限验证,这块后面还需要添加
$verify = new \Custom\Org\Verify;
//验证必填
$verify->required($params, ['dir_name', 'dir_note', 'parent_hash']);
$dir_info = $this->getFileDirInfoByUniqueHash($params['parent_hash']);
if (empty($dir_info)) {
throw new \Exception('没有对应的父类文件夹');
}
$repeat_dir_info = $this->getFileDirInfoByName($params['dir_name'], ['parent_hash' => $params['parent_hash'], 'privilege_hash' => $dir_info['privilege_hash'], 'type' => 'd']);
if (!empty($repeat_dir_info)) {
throw new \Exception('文件夹已存在');
}
$hashes = generateFileHash(['path' => $dir_info['path'], 'name' => $params['dir_name'], 'salt' => $dir_info['salt'], 'type' => 'd']);
$repeat_dir_info = $this->getFileDirInfoByUniqueHash($hashes['unique_hash']);
if (!empty($repeat_dir_info)) {
//如果存在重复的unique_hash,给生成hash的因子加随机数
$hashes = generateFileHash(['path' => generateUniqidSn().$dir_info['path'], 'name' => $params['dir_name'], 'salt' => $dir_info['salt'], 'type' => 'd']);
}
$time = time();
$file_dir_params = array(
'name' => $params['dir_name'],
'privilege_hash' => $dir_info['privilege_hash'],
'unique_hash' => $hashes['unique_hash'],
'system_dir' => 'o',
'type' => 'd',
'parent_hash' => $dir_info['unique_hash'],
'salt' => $dir_info['salt'],
'path' => $dir_info['path'].$dir_info['name'].'/',
'level' => $dir_info['level'] +1,
'time' => $time,
'file_size' => '',
'note' => strval($params['dir_note']),
);
$this->addFileDirInfo($file_dir_params);
\Db::commit();
} catch (\Exception $e) {
\Db::rollback();
throw new \Exception($e->getMessage());
}
return true;
}
/**
* 修改目录
*
* @param string
*
* @return array
**/
public function editDirByUniqueHash($unique_hash, $params)
{
\Db::startTrans();
try {
if (empty($params)) {
throw new \Exception('参数有误');
}
//权限验证,这块后面还需要添加
$verify = new \Custom\Org\Verify;
//验证必填
$verify->required($params, ['dir_name', 'dir_note', 'unique_hash']);
$dir_info = $this->getFileDirInfoByUniqueHash($unique_hash);
if (empty($dir_info)) {
throw new \Exception('当前文件夹不存在');
}
if ($dir_info['level'] == '2') {
throw new \Exception('根文件夹不允许修改');
}
$repeat_dir_info = $this->getFileDirInfoByName($params['dir_name'], ['parent_hash' => $params['parent_hash'], 'privilege_hash' => $dir_info['privilege_hash'], 'type' => 'd']);
if (!empty($repeat_dir_info) && $repeat_dir_info['unique_hash'] != $unique_hash) {
throw new \Exception('文件夹已存在');
}
$time = time();
$file_dir_params = array(
'name' => $params['dir_name'],
'note' => strval($params['dir_note']),
);
$this->updateFileDirInfoByUniqueHash($unique_hash, $file_dir_params);
// 如果修改了文件夹名字,需要更新它的子路径
if ($params['dir_name'] != $dir_info['name'] && $dir_info['type'] == 'd') {
$this->updateFileDirInfosByPath($dir_info['path'].$dir_info['name'].'/', ['path' => $dir_info['path'].$file_dir_params['name'].'/'], ['privilege_hash' => $dir_info['privilege_hash']]);
}
\Db::commit();
} catch (\Exception $e) {
\Db::rollback();
throw new \Exception($e->getMessage());
}
return true;
}