Browse Source

improve dir scan performance

master
filesite 5 months ago
parent
commit
2948a223e8
  1. 13
      controller/Controller.php
  2. 31
      lib/DirScanner.php
  3. 49
      themes/beauty/controller/ListController.php
  4. 65
      themes/beauty/controller/SiteController.php
  5. 38
      themes/beauty/views/site/index.php
  6. 24
      www/js/beauty.js

13
controller/Controller.php

@ -311,4 +311,17 @@ Class Controller {
return "{$prefix}_{$dataType}_{$maxScanDeep}_{$cateId}"; return "{$prefix}_{$dataType}_{$maxScanDeep}_{$cateId}";
} }
protected function getCurrentWebroot($realpath) {
$realpath = preg_replace('/\/[^\/]+\.[^\/]+$/', '', $realpath); //删除文件名,只保留目录
if (empty($realpath)) {return '/';}
$webroot = FSC::$app['config']['content_directory'];
$arr = explode($webroot, $realpath);
if (count($arr) < 2) {
return $webroot;
}
return "{$webroot}{$arr[1]}/";
}
} }

31
lib/DirScanner.php

@ -144,7 +144,9 @@ Class DirScanner {
//根据文件路径生成唯一编号 //根据文件路径生成唯一编号
public function getId($realpath) { public function getId($realpath) {
return !empty($realpath) ? md5($realpath) : ''; if ($realpath == '/') {return md5($realpath);}
return !empty($realpath) ? md5(preg_replace('/\/$/', '', $realpath)) : '';
} }
//判断Nginx防盗链MD5加密方式字符串是否合格 //判断Nginx防盗链MD5加密方式字符串是否合格
@ -772,7 +774,7 @@ Class DirScanner {
} }
} }
} }
}else if (!empty($dirid) && !empty($this->scanResults)) { }else if (!empty($dirid) && !empty($this->scanResults) && !empty($this->scanResults[$dirid])) {
$directory = $this->scanResults[$dirid]; $directory = $this->scanResults[$dirid];
if (!empty($directory) && !empty($directory['files'])) { if (!empty($directory) && !empty($directory['files'])) {
foreach($directory['files'] as $id => $file) { foreach($directory['files'] as $id => $file) {
@ -794,4 +796,29 @@ Class DirScanner {
return $readme; return $readme;
} }
//获取目录下第一个图片作为封面图返回
public function getSnapshotImage($realpath, $imgExts = array('jpg', 'jpeg', 'png', 'webp', 'gif')) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($realpath, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
$imgUrl = '';
foreach ($iterator as $item) {
if ($item->isFile()) {
$extension = $item->getExtension();
if (in_array(strtolower($extension), $imgExts)) {
$imgPath = $item->getPath() . '/' . $item->getFilename();
$imgData = $this->getFileData($imgPath);
if (!empty($imgData['path'])) {
$imgUrl = $imgData['path'];
}
break;
}
}
}
return $imgUrl;
}
} }

49
themes/beauty/controller/ListController.php

@ -36,11 +36,11 @@ Class ListController extends Controller {
throw new Exception("缓存数据中找不到当前目录,请返回上一页重新进入!", 404); throw new Exception("缓存数据中找不到当前目录,请返回上一页重新进入!", 404);
} }
$scanner->setWebRoot($this->getCurrentWebroot($currentDir)); $scanner->setWebRoot($this->getCurrentWebroot($currentDir['realpath']));
$scanner->setRootDir($currentDir['realpath']); $scanner->setRootDir($currentDir['realpath']);
//优先从缓存读取数据 //优先从缓存读取数据
$maxScanDeep = 2; //最大扫描目录级数 $maxScanDeep = 0; //最大扫描目录级数
$cacheKey = $this->getCacheKey($cateId, 'tree', $maxScanDeep); $cacheKey = $this->getCacheKey($cateId, 'tree', $maxScanDeep);
$cachedData = Common::getCacheFromFile($cacheKey); $cachedData = Common::getCacheFromFile($cacheKey);
if (!empty($cachedData)) { if (!empty($cachedData)) {
@ -63,20 +63,31 @@ Class ListController extends Controller {
} }
//按照scanResults格式把当前目录扫描结果中的目录数据拼接到当前目录数据里: currentDir //按照scanResults格式把当前目录扫描结果中的目录数据拼接到当前目录数据里: currentDir
if (!empty($currentDir['directories'])) { if (!empty($scanResults)) {
foreach ($currentDir['directories'] as $id => $item) { $dirs = array();
if (empty($scanResults[$id]['directories'])) { $files = array();
continue; foreach ($scanResults as $id => $item) {
if (!empty($item['directory'])) {
array_push($dirs, $item);
}else {
array_push($files, $item);
}
} }
$currentDir['directories'][$id] = $scanResults[$id]; if (!empty($dirs)) {
$currentDir['directories'] = $dirs;
} }
if (!empty($files)) {
$currentDir['files'] = $files;
} }
$scanResults = array($cateId => $currentDir); //重新组装数据 $scanResults = array($cateId => $currentDir); //重新组装数据
}
//非首页统一从缓存获取目录数据,有效期 1 小时 //非首页统一从缓存获取目录数据,有效期 1 小时
$cacheKey = $this->getCacheKey('all', 'menu', 2); $cacheKey = $this->getCacheKey('all', 'menu', $maxScanDeep);
$menus = Common::getCacheFromFile($cacheKey, 3600); $menus = Common::getCacheFromFile($cacheKey, 3600);
@ -94,11 +105,10 @@ Class ListController extends Controller {
} }
//获取默认mp3文件 //获取默认mp3文件
$rootCateId = $this->get('id', ''); //优先从缓存获取默认mp3文件
$mp3File = $scanner->getDefaultFile('mp3', $rootCateId); $cacheKey = $this->getCacheKey('root', 'mp3', $maxScanDeep);
if (empty($mp3File)) { $mp3File = Common::getCacheFromFile($cacheKey);
$mp3File = $scanner->getDefaultFile('mp3');
}
//翻页支持 //翻页支持
$page = $this->get('page', 1); $page = $this->get('page', 1);
@ -152,16 +162,6 @@ Class ListController extends Controller {
return $parent; return $parent;
} }
protected function getCurrentWebroot($currentDir) {
$webroot = FSC::$app['config']['content_directory'];
$arr = explode($webroot, $currentDir['realpath']);
if (count($arr) < 2) {
return $webroot;
}
return "{$webroot}{$arr[1]}/";
}
//根据目录结构以及当前目录获取面包屑 //根据目录结构以及当前目录获取面包屑
//缓存key统一生成,方便按规则获取上一级目录的缓存cid //缓存key统一生成,方便按规则获取上一级目录的缓存cid
protected function getBreadcrumbs($currentDir, $scanResults, $scanner) { protected function getBreadcrumbs($currentDir, $scanResults, $scanner) {
@ -183,7 +183,8 @@ Class ListController extends Controller {
//下一级子目录的id //下一级子目录的id
$parentCateId = $scanner->getId($parentCate); $parentCateId = $scanner->getId($parentCate);
$cacheKey = $this->getCacheKey($parentCateId, 'data'); $maxScanDeep = 0; //所有页面扫描深度都为 1
$cacheKey = $this->getCacheKey($parentCateId, 'data', $maxScanDeep);
array_push($breads, [ array_push($breads, [
'id' => $cateId, 'id' => $cateId,

65
themes/beauty/controller/SiteController.php

@ -23,7 +23,7 @@ Class SiteController extends Controller {
//优先从缓存读取数据 //优先从缓存读取数据
$defaultCateId = $scanner->getId(preg_replace("/\/$/", '', realpath($rootDir))); $defaultCateId = $scanner->getId(preg_replace("/\/$/", '', realpath($rootDir)));
$maxScanDeep = 2; //最大扫描目录级数 $maxScanDeep = 0; //最大扫描目录级数
$cacheKey = $this->getCacheKey($defaultCateId, 'tree', $maxScanDeep); $cacheKey = $this->getCacheKey($defaultCateId, 'tree', $maxScanDeep);
$cachedData = Common::getCacheFromFile($cacheKey); $cachedData = Common::getCacheFromFile($cacheKey);
if (!empty($cachedData)) { if (!empty($cachedData)) {
@ -45,15 +45,22 @@ Class SiteController extends Controller {
Common::saveCacheToFile($cacheKey, $scanResults); Common::saveCacheToFile($cacheKey, $scanResults);
} }
//优先从缓存获取目录数据 //优先从缓存获取目录数据
$cacheKey = $this->getCacheKey('all', 'menu', $maxScanDeep); $cacheKey = $this->getCacheKey('all', 'menu', $maxScanDeep);
$menus = Common::getCacheFromFile($cacheKey); $menus = Common::getCacheFromFile($cacheKey);
if (empty($menus)) { if (empty($menus) && !empty($scanResults)) {
//获取目录 //获取目录
$menus = $scanner->getMenus(); $menus = $scanner->getMenus();
//在path网址中追加cid缓存key参数
if (!empty($menus)) {
foreach ($menus as $index => $menu) {
$menus[$index]['cid'] = $cacheDataId;
$menus[$index]['path'] .= "&cid={$cacheDataId}";
}
}
$titles = array(); $titles = array();
$readmeFile = $scanner->getDefaultReadme(); $readmeFile = $scanner->getDefaultReadme();
if (!empty($readmeFile)) { if (!empty($readmeFile)) {
@ -80,12 +87,8 @@ Class SiteController extends Controller {
} }
//当前目录数据
$cateId = $this->get('id', $menus[0]['id']);
$subcate = $scanResults[$cateId];
//获取当前目录下的readme //获取当前目录下的readme
$cateReadmeFile = $scanner->getDefaultReadme($cateId); $cateReadmeFile = $scanner->getDefaultReadme($defaultCateId);
if (!empty($cateReadmeFile)) { if (!empty($cateReadmeFile)) {
$Parsedown = new Parsedown(); $Parsedown = new Parsedown();
$content = file_get_contents($cateReadmeFile['realpath']); $content = file_get_contents($cateReadmeFile['realpath']);
@ -93,11 +96,14 @@ Class SiteController extends Controller {
$htmlCateReadme = $scanner->fixMDUrls($cateReadmeFile['realpath'], $htmlCateReadme); $htmlCateReadme = $scanner->fixMDUrls($cateReadmeFile['realpath'], $htmlCateReadme);
} }
//获取默认mp3文件 //优先从缓存获取默认mp3文件
$rootCateId = $this->get('id', ''); $cacheKey = $this->getCacheKey('root', 'mp3', $maxScanDeep);
$mp3File = $scanner->getDefaultFile('mp3', $rootCateId); $mp3File = Common::getCacheFromFile($cacheKey);
if (empty($mp3File)) { if (empty($mp3File)) {
$mp3File = $scanner->getDefaultFile('mp3'); $mp3File = $scanner->getDefaultFile('mp3', $defaultCateId);
if (!empty($mp3File)) {
Common::saveCacheToFile($cacheKey, $mp3File);
}
} }
@ -114,13 +120,13 @@ Class SiteController extends Controller {
} }
$viewName = 'index'; $viewName = 'index';
$params = compact( $params = compact(
'cateId', 'page', 'pageSize', 'cacheDataId', 'page', 'pageSize', 'cacheDataId',
'dirTree', 'scanResults', 'menus', 'htmlReadme', 'htmlCateReadme', 'mp3File' 'dirTree', 'scanResults', 'menus', 'htmlReadme', 'htmlCateReadme', 'mp3File'
); );
return $this->render($viewName, $params, $pageTitle); return $this->render($viewName, $params, $pageTitle);
} }
//清空缓存 //TODO: 清空所有缓存
public function actionCleancache() { public function actionCleancache() {
$prefix = FSC::$app['config']['theme']; $prefix = FSC::$app['config']['theme'];
$cacheKey = "{$prefix}_allFilesTree"; $cacheKey = "{$prefix}_allFilesTree";
@ -134,4 +140,35 @@ Class SiteController extends Controller {
return $this->renderJson(compact('code', 'msg')); return $this->renderJson(compact('code', 'msg'));
} }
//根据目录id,获取第一张图网址作为封面图返回
public function actionDirsnap() {
$code = 1;
$msg = 'OK';
$url = '';
$cacheId = $this->post('cid', '');
$cateId = $this->post('id', '');
if (empty($cacheId) || empty($cateId)) {
$code = 0;
$msg = '参数不能为空';
}else {
//从缓存数据中获取目录的realpath
$cachedData = Common::getCacheFromFile($cacheId);
if (!empty($cachedData)) {
$realpath = $cachedData[$cateId]['realpath'];
$scanner = new DirScanner();
$scanner->setWebRoot($this->getCurrentWebroot($realpath));
$scanner->setRootDir($realpath);
$imgExts = !empty(FSC::$app['config']['supportedImageExts']) ? FSC::$app['config']['supportedImageExts'] : array('jpg', 'jpeg', 'png', 'webp', 'gif');
$url = $scanner->getSnapshotImage($realpath, $imgExts);
}else {
$code = 0;
$msg = '缓存数据已失效,请刷新网页';
}
}
return $this->renderJson(compact('code', 'msg', 'url'));
}
} }

38
themes/beauty/views/site/index.php

@ -34,13 +34,13 @@
<ul class="nav navbar-fixed-left"> <ul class="nav navbar-fixed-left">
<?php <?php
$selectedId = $viewData['cateId']; $selectedId = !empty($viewData['cateId']) ? $viewData['cateId'] : '';
$breadcrumbs = !empty($viewData['breadcrumbs']) ? $viewData['breadcrumbs'] : []; $breadcrumbs = !empty($viewData['breadcrumbs']) ? $viewData['breadcrumbs'] : [];
if (!empty($viewData['menus'])) { //只显示第一级目录 if (!empty($viewData['menus'])) { //只显示第一级目录
foreach ($viewData['menus'] as $index => $item) { foreach ($viewData['menus'] as $index => $item) {
$selected = $item['id'] == $selectedId || (!empty($breadcrumbs) && $item['id'] == $breadcrumbs[0]['id']) ? 'active' : ''; $selected = $item['id'] == $selectedId || (!empty($breadcrumbs) && $item['id'] == $breadcrumbs[0]['id']) ? 'active' : '';
echo <<<eof echo <<<eof
<li class="{$selected}"><a href="/?id={$item['id']}">{$item['directory']}</a></li> <li class="{$selected}"><a href="{$item['path']}">{$item['directory']}</a></li>
eof; eof;
} }
} }
@ -84,6 +84,29 @@ eof;
$category = !empty($viewData['scanResults'][$selectedId]) ? $viewData['scanResults'][$selectedId] : []; $category = !empty($viewData['scanResults'][$selectedId]) ? $viewData['scanResults'][$selectedId] : [];
$total = 0; //翻页支持 $total = 0; //翻页支持
//如果没有选中任何目录,则把所有目录显示出来
if (empty($category) && !empty($viewData['menus'])) {
foreach ($viewData['menus'] as $index => $dir) {
echo <<<eof
<div class="im_item bor_radius col-xs-6 col-sm-4 col-md-3 col-lg-2">
<a href="{$dir['path']}" class="bor_radius dir_item" data-id="{$dir['id']}" data-cid="{$dir['cid']}">
eof;
$title = !empty($dir['title']) ? $dir['title'] : $dir['directory'];
echo <<<eof
<div class="im_img_title">
<span>
<img src="/img/beauty/folder.svg" alt="folder" width="20">
{$title}
</span>
</div>
</a>
</div>
eof;
}
}
//当前目录的描述介绍 //当前目录的描述介绍
if (!empty($category['description'])) { if (!empty($category['description'])) {
echo <<<eof echo <<<eof
@ -99,12 +122,11 @@ eof;
} }
if (!empty($category['directories'])) { //两级目录支持 if (!empty($category['directories'])) { //两级目录支持
$total = count($category['directories']); //翻页支持
$index = 0; $index = 0;
foreach ($category['directories'] as $dir) { foreach ($category['directories'] as $dir) {
echo <<<eof echo <<<eof
<div class="im_item bor_radius col-xs-6 col-sm-4 col-md-3 col-lg-2"> <div class="im_item bor_radius col-xs-6 col-sm-4 col-md-3 col-lg-2">
<a href="{$dir['path']}&cid={$viewData['cacheDataId']}" class="bor_radius"> <a href="{$dir['path']}&cid={$viewData['cacheDataId']}" class="bor_radius dir_item" data-id="{$dir['id']}" data-cid="{$viewData['cacheDataId']}">
eof; eof;
if (!empty($dir['snapshot'])) { if (!empty($dir['snapshot'])) {
@ -173,7 +195,7 @@ eof;
$pageStartIndex = ($viewData['page']-1) * $viewData['pageSize']; $pageStartIndex = ($viewData['page']-1) * $viewData['pageSize'];
$index = 0; $index = 0;
foreach ($category['files'] as $file) { foreach ($category['files'] as $file) {
if (!in_array($file['extension'], $imgExts)) { if (empty($file['extension']) || !in_array($file['extension'], $imgExts)) {
continue; continue;
} }
@ -231,7 +253,9 @@ eof;
<div class="text-center"> <div class="text-center">
<?php <?php
$pagination = Html::getPaginationHtmlCode($viewData['page'], $viewData['pageSize'], $total); if ($total > 0) {
echo $pagination; $pagination = Html::getPaginationHtmlCode($viewData['page'], $viewData['pageSize'], $total);
echo $pagination;
}
?> ?>
</div> </div>

24
www/js/beauty.js

@ -109,6 +109,30 @@ if ($('#image_site').get(0)) {
saveLanpnumToLocalstorage(lanpnum); saveLanpnumToLocalstorage(lanpnum);
}); });
//异步加载目录的封面图
$('.dir_item').each(function(index, el) {
var cid = $(el).attr('data-cid'), id = $(el).attr('data-id');
if ($(el).find('.im_img').length == 0) {
$.ajax({
url: '/site/dirsnap',
method: 'POST',
dataType: 'json',
data: {
cid: cid,
id: id
}
}).done(function(data) {
if (data.code == 1 && data.url) {
$(el).find('.im_img_title').before('<img src="' + data.url + '" class="bor_radius im_img">');
}else {
console.error('目录封面图获取失败:%s', data.msg);
}
}).fail(function(jqXHR, textStatus, errorThrown) {
console.error('获取封面图失败,错误信息:' + errorThrown);
});
}
});
//刷新缓存 //刷新缓存
$('.cleanCacheJS').click(function () { $('.cleanCacheJS').click(function () {
$.ajax({ $.ajax({

Loading…
Cancel
Save