'目录名', 'filename' => '文件名', 'realpath' => '完整路径', 'path' => '相对网址', 'extension' => '文件后缀', 'fstat' => '资源状态', //同php方法fstat: https://www.php.net/manual/en/function.fstat.php 'content' => 'MD文件内容', 'shortcut' => 'URL快捷方式', 'description' => '描述', 'keywords' => '关键词', 'snapshot' => '快照图片', ]; private $rootDir; //当前扫描的根目录 private $scanningDirLevel = 0; //当前扫描的目录深度 private $scanResults = []; //目录扫描结果 protected $supportFileExtensions = [ //支持的文件类型 'txt', //纯文本 'md', //纯文本 'url', //快捷方式 'jpg', //图片 'png', //图片 'gif', //图片 'ico', //图标 'mp4', //视频 'ts', //视频 'm3u8', //视频 ]; protected $maxReadFilesize = [ //默认每种文件读取内容最大大小 'txt' => 100*1024, //纯文本 'md' => 5*1024*1024, //纯文本 'url' => 20*1024, //快捷方式 'jpg' => 500*1024, //图片 'png' => 500*1024, //图片 'gif' => 500*1024, //图片 'ico' => 50*1024, //图标 'mp4' => 100*1024*1024, //视频 'ts' => 10*1024*1024, //视频 'm3u8' => 10*1024*1024, //视频 ]; protected $securedFileExtensions = [ //开启Nginx防盗链的文件类型 'jpg', //图片 'png', //图片 'gif', //图片 'ico', //图标 'mp4', //视频 'ts', //视频 'm3u8', //视频 ]; public $scanTimeCost = 0; //上一次目录扫描耗时,单位:微秒 //解析描述文件内容 private function parseDescriptionFiles($realpath) { $field = preg_replace('/^.+_([a-z0-9]+)\.txt$/i', "$1", $realpath); if ($field == $realpath) {return false;} $content = file_get_contents($realpath); $data = []; $data[$field] = $content; return $data; } //解析快捷方式文件内容 private function parseShortCuts($realpath, $filename) { $content = file_get_contents($realpath); if (empty($content) || !preg_match('/\[InternetShortcut\]/i', $content)) {return false;} preg_match('/URL=(\S+)/i', $content, $matches); if (empty($matches) || empty($matches[1])) { return false; } return [ 'name' => $filename, 'url' => $matches[1], ]; } //根据文件路径生成唯一编号 private function getId($realpath) { return !empty($realpath) ? md5($realpath) : ''; } //判断Nginx防盗链MD5加密方式字符串是否合格 private function isNginxSecureLinkMd5PatternValid($pattern) { $valid = true; $fieldsNeeded = [ '{secure_link_expires}', '{uri}', '{remote_addr}', '{secret}', ]; foreach($fieldsNeeded as $needle) { if (strstr($pattern, $needle) === false) { $valid = false; break; } } return $valid; } //根据路径生成目录数组 private function getDirData($realpath, $files) { $id = $this->getId($realpath); return [ 'id' => $id, 'directory' => basename($realpath), 'realpath' => $realpath, 'path' => $this->getDirPath($realpath), 'files' => $files, ]; } //根据路径生成文件数组,兼容URL文件 private function getFileData($realpath, $dirLevel) { $id = $this->getId($realpath); $fp = fopen($realpath, 'r'); $fstat = fstat($fp); fclose($fp); $pathinfo = pathinfo($realpath); $extension = strtolower($pathinfo['extension']); return [ 'id' => $id, 'filename' => $pathinfo['filename'], 'extension' => $extension, 'fstat' => [ 'size' => $fstat['size'], 'atime' => $fstat['atime'], 'mtime' => $fstat['mtime'], 'ctime' => $fstat['ctime'], ], 'realpath' => $realpath, 'path' => $this->getFilePath( $this->getDirectoryName($pathinfo['dirname'], $dirLevel), $pathinfo['filename'], $extension ), ]; } //根据路径和根目录获取当前扫描的目录深度 private function getScanningLevel($rootDir, $dirname) { $level = 0; if ($dirname == $rootDir) { $level = 1; }else { $dirname = preg_replace('/\/$/', '', $dirname); $dirs = explode('/', str_replace($rootDir, '', $dirname)); $level = count($dirs); } return $level; } //根据路径和当前扫描深度获取目录名 private function getDirectoryName($dirname, $dirLevel) { if ($dirLevel <= 0) {return basename($dirname);} $dirname = preg_replace('/\/$/', '', $dirname); $dirname = preg_replace('/^\//', '', $dirname); $names = explode('/', $dirname); if ($dirLevel >= count($names)) {return $dirname;} return implode('/', array_splice($names, -$dirLevel)); } //根据文件生成防盗链网址 //参考:https://nginx.org/en/docs/http/ngx_http_secure_link_module.html#secure_link //防盗链参数名:md5, expires protected function getSecureLink($path) { $expires = time() + $this->nginxSecureTimeout; $originStr = str_replace([ '{secure_link_expires}', '{uri}', '{remote_addr}', '{secret}', ], [ $expires, $path, $this->userIp, $this->nginxSecret, ], $this->nginxSecureLinkMd5Pattern); $md5 = base64_encode( md5($originStr, true) ); $md5 = strtr($md5, '+/', '-_'); $md5 = str_replace('=', '', $md5); return "{$path}?md5={$md5}&expires={$expires}"; } //根据文件生成相对路径 protected function getFilePath($directory, $filename, $extension) { if (empty($directory)) { $directory = '/'; } if (!preg_match('/\/$/', $directory)) { $directory .= '/'; } if (!preg_match('/^\//', $directory)) { $directory = "/{$directory}"; } $extensionPathMap = [ //默认每种文件读取内容最大大小 'txt' => '', 'md' => '/view/', 'url' => '/link/', 'm3u8' => '/m3u8/', 'jpg' => "{$directory}{$filename}.{$extension}", 'png' => "{$directory}{$filename}.{$extension}", 'gif' => "{$directory}{$filename}.{$extension}", 'ico' => "{$directory}{$filename}.{$extension}", 'mp4' => "{$directory}{$filename}.{$extension}", 'ts' => "{$directory}{$filename}.{$extension}", ]; $path = isset($extensionPathMap[$extension]) ? $extensionPathMap[$extension] : ''; if (!empty($path) && in_array($extension, ['md', 'url', 'm3u8'])) { if ($this->nginxSecureOn && $extension == 'm3u8') { $path = $this->getSecureLink($path); $path = "{$path}&file=" . urlencode($filename); }else { $path = "{$path}?file=" . urlencode($filename); } }else if (!empty($path) && $this->nginxSecureOn) { $path = $this->getSecureLink($path); } return $path; } //根据目录生成相对路径 protected function getDirPath($realpath) { return '/list/?dir=' . urlencode(basename($realpath)); } //设置Nginx防盗链开启或关闭,以及密钥、加密方式、超时时长 public function setNginxSecure($secureOn, $secret = '', $userIp = '', $pattern = '', $timeout = 0) { $status = false; if (is_string($secureOn) && strtolower($secureOn) == 'on') { $status = true; }else if (is_string($secureOn) && strtolower($secureOn) == 'off') { $status = false; }else if ((bool)$secureOn == true) { $status = true; } $this->nginxSecureOn = $status; if (!empty($secret) && is_string($secret)) { $this->nginxSecret = $secret; } if (!empty($userIp) && is_string($userIp)) { $this->userIp = $userIp; } if (!empty($pattern) && is_string($pattern)) { if ($this->isNginxSecureLinkMd5PatternValid($pattern) == false) { throw new Exception("Invalid Nginx secure link md5 pattern: {$pattern}", 500); } $this->nginxSecureLinkMd5Pattern = $pattern; } if ((int)$timeout > 0) { $this->nginxSecureTimeout = (int)$timeout; } } //设置Nginx防盗链密钥 public function setNginxSecret($secret) { if (!empty($secret) && is_string($secret)) { $this->nginxSecret = $secret; } } //获取Nginx防盗链密钥 public function getNginxSecret() { return $this->nginxSecret; } //设置Nginx防盗链密钥 public function setUserIp($userIp) { if (!empty($userIp) && is_string($userIp)) { $this->userIp = $userIp; } } //获取Nginx防盗链密钥 public function getUserIp() { return $this->userIp; } //设置Nginx防盗链MD5加密方式 /** * Nginx防盗链MD5加密方式参考下面网址中的示例, * 将Nginx的变量替换$符号为英文大括号; * * 示例: * ``` * {secure_link_expires}{uri}{remote_addr} {secret} * ``` * Nginx文档参考:http://nginx.org/en/docs/http/ngx_http_secure_link_module.html#secure_link_md5 */ public function setNginxSecureLinkMd5Pattern($pattern) { if (!empty($pattern) && is_string($pattern)) { if ($this->isNginxSecureLinkMd5PatternValid($pattern) == false) { throw new Exception("Invalid Nginx secure link md5 pattern: {$pattern}", 500); } $this->nginxSecureLinkMd5Pattern = $pattern; } } //获取Nginx防盗链MD5加密方式 public function getNginxSecureLinkMd5Pattern() { return $this->nginxSecureLinkMd5Pattern; } //设置Nginx防盗链超时时长,单位:秒 public function setNginxSecureTimeout($timeout) { if ((int)$timeout > 0) { $this->nginxSecureTimeout = (int)$timeout; } } //获取Nginx防盗链超时时长,单位:秒 public function getNginxSecureTimeout() { return $this->nginxSecureTimeout; } //获取是否开启防盗链 public function isSecureOn() { return $this->nginxSecureOn; } //扫描目录获取目录和文件列表,支持指定目录扫描深度(目录级数) public function scan($dir, $levels = 3) { $tree = array(); $ignore_files = array('.', '..'); if (is_dir($dir)) { if (!preg_match('/\/$/', $dir)) {$dir .= '/';} if (empty($this->rootDir)) { $this->rootDir = $dir; } $this->scanningDirLevel = $this->getScanningLevel($this->rootDir, $dir); $nextLevels = $levels - $this->scanningDirLevel; $files = scandir($dir); foreach($files as $file) { if (in_array($file, $ignore_files)) {continue;} $realpath = realpath("{$dir}{$file}"); if (is_dir($realpath)) { $files = []; if ($nextLevels > 0) { $files = $this->scan($realpath, $nextLevels); } $tree[] = $this->getDirData($realpath, $files); }else { $tree[] = $this->getFileData($realpath, $this->scanningDirLevel); } } } return $tree; } }