setWebRoot(FSC::$app['config']['content_directory']); $rootDir = __DIR__ . '/../../../www/' . FSC::$app['config']['content_directory']; $scanner->setRootDir($rootDir); //优先从缓存读取数据 $defaultCateId = $scanner->getId(preg_replace("/\/$/", '', realpath($rootDir))); $maxScanDeep = 0; //最大扫描目录级数 $cacheKey = $this->getCacheKey($defaultCateId, 'tree', $maxScanDeep); $cachedData = Common::getCacheFromFile($cacheKey); if (!empty($cachedData)) { $dirTree = $cachedData; $scanner->setTreeData($cachedData); }else { $dirTree = $scanner->scan(__DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'], $maxScanDeep); Common::saveCacheToFile($cacheKey, $dirTree); } //优先从缓存读取数据 $cacheKey = $cacheDataId = $this->getCacheKey($defaultCateId, 'data', $maxScanDeep); $cachedData = Common::getCacheFromFile($cacheKey); if (!empty($cachedData)) { $scanResults = $cachedData; $scanner->setScanResults($cachedData); }else { $scanResults = $scanner->getScanResults(); Common::saveCacheToFile($cacheKey, $scanResults); } if (!empty($scanResults) && !empty($scanResults[$defaultCateId])) { //TODO: 获取根目录下的txt说明文件内容 } //优先从缓存获取目录数据 $cacheKey = $this->getCacheKey('all', 'menu', $maxScanDeep); $menus = Common::getCacheFromFile($cacheKey); if (empty($menus) && !empty($scanResults)) { //获取目录 $menus = $scanner->getMenus(); //在path网址中追加cid缓存key参数 if (!empty($menus)) { foreach ($menus as $index => $menu) { $menus[$index]['cid'] = $cacheDataId; $menus[$index]['path'] .= "&cid={$cacheDataId}"; } } $readmeFile = $scanner->getDefaultReadme(); if (!empty($readmeFile)) { if (!empty($readmeFile['sort'])) { $menus_sorted = explode("\n", $readmeFile['sort']); } } //排序 $sortedTree = $this->sortMenusAndDirTree($menus_sorted, $menus, $dirTree); if (!empty($sortedTree)) { $menus = $sortedTree['menus']; $dirTree = $sortedTree['dirTree']; } Common::saveCacheToFile($cacheKey, $menus); //保存目录数据 } //获取联系方式 $titles = array(); $cacheKey = $this->getCacheKey('root', 'readme', $maxScanDeep); $cachedData = Common::getCacheFromFile($cacheKey); if (empty($cachedData)) { $readmeFile = $scanner->getDefaultReadme(); if (!empty($readmeFile)) { $titles = $scanner->getMDTitles($readmeFile['id']); $Parsedown = new Parsedown(); $content = file_get_contents($readmeFile['realpath']); $htmlReadme = $Parsedown->text($content); $htmlReadme = $scanner->fixMDUrls($readmeFile['realpath'], $htmlReadme); $title = !empty($readmeFile['title']) ? $readmeFile['title'] : ''; $copyright = !empty($readmeFile['copyright']) ? $readmeFile['copyright'] : ''; Common::saveCacheToFile($cacheKey, array( 'htmlReadme' => $htmlReadme, 'titles' => $titles, 'title' => $title, 'copyright' => $copyright, )); } }else { $readmeFile = $cachedData; $htmlReadme = $cachedData['htmlReadme']; $titles = $cachedData['titles']; } //优先从缓存获取默认mp3文件 $cacheKey = $this->getCacheKey('root', 'mp3', $maxScanDeep); $mp3File = Common::getCacheFromFile($cacheKey); if (empty($mp3File)) { $mp3File = $scanner->getDefaultFile('mp3'); if (!empty($mp3File)) { Common::saveCacheToFile($cacheKey, $mp3File); } } //提示信息支持 $alertWarning = $this->get('err', ''); //翻页支持 $page = $this->get('page', 1); $pageSize = $this->get('limit', 24); $page = (int)$page; $pageSize = (int)$pageSize; $pageTitle = !empty($titles) ? $titles[0]['name'] : "FileSite.io"; if (!empty($readmeFile['title'])) { $pageTitle = $readmeFile['title']; } if (!empty($subcate)) { $pageTitle = "{$subcate['directory']}照片,来自{$pageTitle}"; } //底部版权申明配置支持 $copyright = ''; if (!empty($readmeFile['copyright'])) { $copyright = $readmeFile['copyright']; } //图片、视频类型筛选支持 $imgExts = !empty(FSC::$app['config']['supportedImageExts']) ? FSC::$app['config']['supportedImageExts'] : array('jpg', 'jpeg', 'png', 'webp', 'gif'); $videoExts = !empty(FSC::$app['config']['supportedVideoExts']) ? FSC::$app['config']['supportedVideoExts'] : array('mp4', 'mov', 'm3u8'); $showType = $this->get('show', 'all'); if ($showType == 'image') { $scanResults = array_filter($scanResults, function($item) { $imgExts = !empty(FSC::$app['config']['supportedImageExts']) ? FSC::$app['config']['supportedImageExts'] : array('jpg', 'jpeg', 'png', 'webp', 'gif'); return !empty($item['extension']) && in_array($item['extension'], $imgExts); }); }else if ($showType == 'video') { $scanResults = array_filter($scanResults, function($item) { $videoExts = !empty(FSC::$app['config']['supportedVideoExts']) ? FSC::$app['config']['supportedVideoExts'] : array('mp4', 'mov', 'm3u8'); return !empty($item['extension']) && in_array($item['extension'], $videoExts); }); } //dataType支持:[image, video] $dataType = $this->get('dataType', 'html'); if ($dataType == 'image') { $imgs = array(); $pageStartIndex = ($page-1) * $pageSize; $index = 0; foreach ($scanResults as $id => $item) { //翻页支持 if ($index < $pageStartIndex) { $index ++; continue; }else if ($index >= $pageStartIndex + $pageSize) { break; } //增加caption:图片、视频显示文件修改日期 $title = Common::getDateFromString($item['filename']); if (empty($title) && !empty($item['fstat']['mtime']) && !empty($item['fstat']['ctime'])) { $title = date('Y-m-d', min($item['fstat']['mtime'], $item['fstat']['ctime'])); } $item['caption'] = "{$title} - {$item['filename']}"; if (!empty($item['extension']) && in_array($item['extension'], $imgExts)) { array_push($imgs, $item); $index ++; } } return $this->renderJson(compact('page', 'pageSize', 'imgs')); }else if ($dataType == 'video') { $videos = array(); $pageStartIndex = ($page-1) * $pageSize; $index = 0; foreach ($scanResults as $id => $item) { //翻页支持 if ($index < $pageStartIndex) { $index ++; continue; }else if ($index >= $pageStartIndex + $pageSize) { break; } if (!empty($item['extension']) && in_array($item['extension'], $videoExts)) { array_push($videos, $item); $index ++; } } return $this->renderJson(compact('page', 'pageSize', 'videos')); } $isAdminIp = Common::isAdminIp($this->getUserIp()); //判断是否拥有管理权限 $viewName = 'index'; $params = compact( 'page', 'pageSize', 'cacheDataId', 'showType', 'dirTree', 'scanResults', 'menus', 'htmlReadme', 'htmlCateReadme', 'mp3File', 'copyright', 'alertWarning', 'isAdminIp' ); return $this->render($viewName, $params, $pageTitle); } //清空所有缓存 public function actionCleancache() { $code = 1; $msg = 'OK'; try { if (Common::isAdminIp($this->getUserIp()) == false) { $code = 0; $msg = '403 Forbidden,禁止访问'; }else { $cacheDir = __DIR__ . '/../../../runtime/cache/'; $files = scandir($cacheDir); foreach($files as $file) { if (!preg_match('/\.json$/i', $file)) {continue;} unlink("{$cacheDir}{$file}"); } } }catch(Exception $e) { $code = 0; $msg = '缓存清空失败:' . $e->getMessage(); } return $this->renderJson(compact('code', 'msg')); } //根据目录id,获取第一张图网址作为封面图返回 public function actionDirsnap() { $code = 1; $msg = 'OK'; $url = ''; $img_id = ''; $size = 'orignal'; $cacheId = $this->post('cid', ''); $cateId = $this->post('id', ''); if (empty($cacheId) || empty($cateId)) { $code = 0; $msg = '参数不能为空'; }else { //优先从缓存获取 $cacheKey = $this->getCacheKey($cateId, 'snap'); $expireSeconds = FSC::$app['config']['screenshot_expire_seconds']; //有效期3650天 $cacheSubDir = 'dir'; $cachedData = Common::getCacheFromFile($cacheKey, $expireSeconds, $cacheSubDir); //如果关闭缩略图 if (empty(FSC::$app['config']['enableSmallImage']) || FSC::$app['config']['enableSmallImage'] === 'false') { if (!empty($cachedData) && !empty($cachedData['size']) && $cachedData['size'] == 'small') { $cachedData = null; } } //弃用老版本数据格式,抛弃没有size属性的 if (!empty($cachedData) && empty($cachedData['size'])) { $cachedData = null; } if (empty($cachedData)) { //从缓存数据中获取目录的realpath $cachedData = Common::getCacheFromFile($cacheId, $expireSeconds); 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'); $imgFile = $scanner->getSnapshotImage($realpath, $imgExts); //支持视频目录 if (empty($imgFile)) { $videoExts = !empty(FSC::$app['config']['supportedVideoExts']) ? FSC::$app['config']['supportedVideoExts'] : array('mp4', 'mov', 'm3u8'); $firstVideo = $scanner->getSnapshotImage($realpath, $videoExts); if (!empty($firstVideo)) { $url = '/img/beauty/video_dir.png'; //尝试从缓存数据中获取封面图 $cacheKey_snap = $this->getCacheKey($firstVideo['id'], 'vmeta');; $expireSeconds = FSC::$app['config']['screenshot_expire_seconds']; //有效期3650天 $cacheSubDir = 'video'; $cachedData = Common::getCacheFromFile($cacheKey_snap, $expireSeconds, $cacheSubDir); if (!empty($cachedData)) { $url = $cachedData['snapshot']; $cacheSubDir = 'dir'; $size = 'vm'; Common::saveCacheToFile($cacheKey, compact('url', 'size'), $cacheSubDir); } } }else { $url = $imgFile['path']; $img_id = $imgFile['id']; $size = 'orignal'; //小尺寸图片支持 if (!empty(FSC::$app['config']['enableSmallImage']) && FSC::$app['config']['enableSmallImage'] !== 'false') { $cacheKey_smimg = $this->getCacheKey($imgFile['id'], 'imgsm'); $expireSeconds = FSC::$app['config']['screenshot_expire_seconds']; //有效期3650天 $cacheSubDir = 'image'; $cachedData = Common::getCacheFromFile($cacheKey_smimg, $expireSeconds, $cacheSubDir); if (!empty($cachedData)) { //已经有缩略图 $url = $cachedData; $size = 'small'; //当前目录有缩略图的时候才缓存 $cacheSubDir = 'dir'; Common::saveCacheToFile($cacheKey, compact('url', 'size'), $cacheSubDir); }else { //实时生成缩略图 $img_filepath = $imgFile['realpath']; $img_data = $this->createSmallJpg($img_filepath); if (!empty($img_data)) { //保存到缓存文件 $cacheKey_smimg = $this->getCacheKey($imgFile['id'], 'imgsm'); $cacheSubDir = 'image'; $base64_img = base64_encode($img_data); Common::saveCacheToFile($cacheKey_smimg, "data:image/jpeg;base64,{$base64_img}", $cacheSubDir); $url = "data:image/jpeg;base64,{$base64_img}"; $size = 'small'; //缓存目录封面图 $cacheSubDir = 'dir'; Common::saveCacheToFile($cacheKey, compact('url', 'size'), $cacheSubDir); } } }else if (empty(FSC::$app['config']['enableSmallImage']) || FSC::$app['config']['enableSmallImage'] === 'false') { //如果关闭了缩略图功能则缓存原图 $cacheSubDir = 'dir'; Common::saveCacheToFile($cacheKey, compact('url', 'size'), $cacheSubDir); } } }else { $code = 0; $msg = '缓存数据已失效,请刷新网页'; } }else { $url = $cachedData['url']; } } return $this->renderJson(compact('code', 'msg', 'url')); } //保存目录封面图到缓存 public function actionSavedirsnap() { $code = 0; $msg = 'OK'; $cateId = $this->post('id', ''); //目录id $url = $this->post('url', ''); //base64格式的图片数据或者图片网址 if (empty($cateId) || empty($url)) { $code = 0; $msg = '参数不能为空'; }else if (Common::isAdminIp($this->getUserIp()) == false) { $code = 0; $msg = '403 Forbidden,禁止访问'; }else { $cacheKey = $this->getCacheKey($cateId, 'snap'); $img_id = ''; //为保持数据格式一致,图片id传空 $cacheSubDir = 'dir'; $size = 'orignal'; if (!empty(FSC::$app['config']['enableSmallImage']) && FSC::$app['config']['enableSmallImage'] !== 'false') { $size = 'small'; } $saved = Common::saveCacheToFile($cacheKey, compact('url', 'img_id', 'size'), $cacheSubDir); if ($saved !== false) { $code = 1; } } return $this->renderJson(compact('code', 'msg')); } //借助gd库,获取图片类型、尺寸,并实时生成缩略图 protected function createSmallJpg($img_filepath, $min_width = 198, $min_height = 219, $max_width = 600, $max_height = 500) { //如果服务器端生成缩略图关闭 if (!empty(FSC::$app['config']['disableGenerateSmallImageInServer']) && FSC::$app['config']['disableGenerateSmallImageInServer'] !== 'false') { return false; } $img_data = null; try { list($naturalWidth, $naturalHeight, $imgTypeIndex, $style) = getimagesize($img_filepath); $imgType = image_type_to_extension($imgTypeIndex); //小图片则保持原图尺寸 if ($naturalWidth <= $max_width || $naturalHeight <= $max_height) { return false; } //生成同比例缩略图尺寸 $zoomRate = FSC::$app['config']['small_image_zoom_rate']; //缩略图在最小尺寸基础上放大比例,为确保清晰度 $width = $min_width; $height = $min_height; $aspect = $naturalHeight / $naturalWidth; if ($naturalWidth <= $naturalHeight) { if ($width * $zoomRate >= $naturalWidth) {return false;} //避免把小图片放大 $width = $width * $zoomRate <= $max_width ? (int)($width * $zoomRate) : $max_width; $height = (int)($width * $aspect); }else { if ($height * $zoomRate >= $naturalHeight) {return false;} //避免把小图片放大 $height = $height * $zoomRate <= $max_height ? (int)($height * $zoomRate) : $max_height; $width = (int)($height / $aspect); } $imgSource = null; switch ($imgType) { case '.jpeg': $imgSource = imagecreatefromjpeg($img_filepath); break; case '.png': $imgSource = imagecreatefrompng($img_filepath); break; case '.gif': $imgSource = imagecreatefromgif($img_filepath); break; case '.webp': if (function_exists('imagecreatefromwebp')) { $imgSource = imagecreatefromwebp($img_filepath); } break; case '.bmp': if (function_exists('imagecreatefrombmp')) { $imgSource = imagecreatefrombmp($img_filepath); } break; } //保存base64格式的缩略图到缓存文件 if (!empty($imgSource)) { $dst_img = imagecreatetruecolor($width, $height); $copy_done = imagecopyresized($dst_img, $imgSource, 0, 0, 0, 0, $width, $height, $naturalWidth, $naturalHeight); if ($copy_done) { ob_start(); imagejpeg($dst_img); $img_data = ob_get_clean(); ob_end_clean(); } imagedestroy($dst_img); } }catch(Exception $e) { $this->logError('创建缩略图失败:' . $e->getMessage()); } return $img_data; } //优先从缓存获取小尺寸的图片 //增加父目录封面图缓存更新 public function actionSmallimg() { $imgId = $this->get('id', ''); $imgUrl = $this->get('url', ''); if (empty($imgId) || empty($imgUrl)) { return $this->redirect('/img/beauty/lazy.svg'); } $cacheKey = $this->getCacheKey($imgId, 'imgsm'); $expireSeconds = FSC::$app['config']['screenshot_expire_seconds']; //有效期3650天 $cacheSubDir = 'image'; $cachedData = Common::getCacheFromFile($cacheKey, $expireSeconds, $cacheSubDir); //无缓存,则实时生成缩略图 if (empty($cachedData)) { $tmpUrl = parse_url($imgUrl); $img_filepath = __DIR__ . '/../../../www' . $tmpUrl['path']; $img_data = $this->createSmallJpg($img_filepath); if (!empty($img_data)) { //保存到缓存文件 $cacheKey = $this->getCacheKey($imgId, 'imgsm'); $cacheSubDir = 'image'; $base64_img = base64_encode($img_data); Common::saveCacheToFile($cacheKey, "data:image/jpeg;base64,{$base64_img}", $cacheSubDir); //返回图片数据 header("Content-Type: image/jpeg"); header('Cache-Control: max-age=3600'); //缓存 1 小时 header("Etag: " . md5($img_data)); echo $img_data; exit; } }else { //有缓存,则返回缓存数据 $imgType = preg_replace('/^data:(image\/.+);base64,.+$/i', "$1", $cachedData); $base64_img = preg_replace('/^data:image\/.+;base64,/i', '', $cachedData); $img_data = base64_decode($base64_img); header("Content-Type: {$imgType}"); header('Cache-Control: max-age=3600'); //缓存 1 小时 header("Etag: " . md5($img_data)); echo $img_data; exit; } return $this->redirect($imgUrl); } //保存小尺寸图片数据到缓存 public function actionSavesmallimg() { $code = 0; $msg = 'OK'; $cateId = $this->post('pid', ''); $imgId = $this->post('id', ''); $imgData = $this->post('data', ''); //base64格式的图片数据 if (empty($imgId) || empty($imgData)) { $code = 0; $msg = '参数不能为空'; }else { //如果是目录封面图生成缩略图,则更新目录封面图缓存数据 if (!empty($cateId)) { $cacheKey = $this->getCacheKey($cateId, 'snap'); $img_id = ''; //为保持数据格式一致,图片id传空 $cacheSubDir = 'dir'; $size = 'small'; Common::saveCacheToFile($cacheKey, array('url' => $imgData, 'img_id' => $img_id, 'size' => $size), $cacheSubDir); } $cacheKey = $this->getCacheKey($imgId, 'imgsm'); $cacheSubDir = 'image'; $saved = Common::saveCacheToFile($cacheKey, $imgData, $cacheSubDir); if ($saved !== false) { $code = 1; } } return $this->renderJson(compact('code', 'msg')); } //TODO: 增加mp3播放器,以及mp3时长获取 public function actionPlayer() { $videoUrl = $this->get('url', ''); $videoId = $this->get('id', ''); $cateId = $this->get('pid', ''); $cacheParentDataId = $this->get('cid', ''); $page = $this->get('page', 1); $pageSize = $this->get('limit', 24); if (empty($videoUrl) || empty($videoId) || empty($cateId) || empty($cacheParentDataId)) { throw new Exception("缺少参数!", 403); } $arr = parse_url($videoUrl); $videoFilename = basename($arr['path']); //增加文件后缀格式检查,区分:mp4, mov, m3u8 $videoExtension = pathinfo($arr['path'], PATHINFO_EXTENSION); //支持m3u8地址:/m3u8/?id=xxx if ($videoFilename == 'm3u8') { $videoExtension = 'm3u8'; } $videoSourceType = Html::getMediaSourceType($videoExtension); //获取联系方式 $maxScanDeep = 0; //最大扫描目录级数 $cacheKey = $this->getCacheKey('root', 'readme', $maxScanDeep); $readmeFile = Common::getCacheFromFile($cacheKey); //底部版权申明配置支持 $copyright = ''; if (!empty($readmeFile['copyright'])) { $copyright = $readmeFile['copyright']; } $isAdminIp = Common::isAdminIp($this->getUserIp()); //判断是否拥有管理权限 $pageTitle = "正在播放:{$videoFilename}"; $this->layout = 'player'; $viewName = 'player'; $params = compact( 'videoUrl', 'videoId', 'videoFilename', 'cateId', 'cacheParentDataId', 'page', 'pageSize', 'copyright', 'isAdminIp', 'videoExtension', 'videoSourceType' ); return $this->render($viewName, $params, $pageTitle); } //根据视频文件id返回缓存数据: //{duration: 单位秒的时长, snapshot: base64格式的jpg封面图} public function actionVideometa() { $code = 1; $msg = 'OK'; $meta = array(); $videoId = $this->get('id', ''); if (empty($videoId)) { $code = 0; $msg = '参数不能为空'; }else { $cacheKey = $this->getCacheKey($videoId, 'vmeta'); $expireSeconds = FSC::$app['config']['screenshot_expire_seconds']; //有效期3650天 $cacheSubDir = 'video'; $cachedData = Common::getCacheFromFile($cacheKey, $expireSeconds, $cacheSubDir); if (!empty($cachedData)) { $meta = $cachedData; }else { $code = 0; $msg = '此视频无缓存或缓存已过期'; } } return $this->renderJson(compact('code', 'msg', 'meta')); } //保存视频meta数据到缓存,支持手动生成 public function actionSavevideometa() { $code = 0; $msg = 'OK'; $videoId = $this->post('id', ''); $metaData = $this->post('meta', ''); $manual = $this->post('manual', 0); if (empty($videoId) || empty($metaData)) { $code = 0; $msg = '参数不能为空'; }else if (Common::isAdminIp($this->getUserIp()) == false) { $code = 0; $msg = '403 Forbidden,禁止访问'; }else { $cacheKey = $this->getCacheKey($videoId, 'vmeta'); $cacheSubDir = 'video'; $saved = true; if (!empty($manual)) { $metaData['manual'] = 1; $saved = Common::saveCacheToFile($cacheKey, $metaData, $cacheSubDir); }else { $expireSeconds = FSC::$app['config']['screenshot_expire_seconds']; //有效期3650天 $cachedData = Common::getCacheFromFile($cacheKey, $expireSeconds, $cacheSubDir); if (empty($cachedData) || empty($cachedData['manual'])) { $saved = Common::saveCacheToFile($cacheKey, $metaData, $cacheSubDir); } } if ($saved !== false) { $code = 1; } } return $this->renderJson(compact('code', 'msg')); } //密码授权 public function actionPwdauth() { $checkDir = $this->get('dir', ''); $goBackUrl = $this->get('back', ''); $password = ''; if (empty($checkDir) || empty($goBackUrl)) { throw new Exception("缺少参数!", 403); } $errorMsg = ''; $post = $this->post(); if (!empty($post)) { $password = $this->post('password', ''); $authed = Common::pwdAuthToDir($checkDir, $password); if ($authed == false) { $errorMsg = '密码错误,请仔细检查后重试。'; }else { return $this->redirect($goBackUrl); } } $maxScanDeep = 0; //获取根目录下的readme $htmlReadme = ''; $cacheKey = $this->getCacheKey('root', 'readme', $maxScanDeep); $expireSeconds = 86400; $readmeFile = Common::getCacheFromFile($cacheKey, $expireSeconds); if (!empty($readmeFile)) { $htmlReadme = $readmeFile['htmlReadme']; } $copyright = ''; if (!empty($readmeFile['copyright'])) { $copyright = $readmeFile['copyright']; } $pageTitle = '密码授权'; $viewName = 'pwdauth'; $params = compact( 'htmlReadme', 'copyright', 'checkDir', 'goBackUrl', 'password', 'errorMsg' ); return $this->render($viewName, $params, $pageTitle); } }