php文件和文件夹操作类

文件和文件夹操作

移动 | 复制 | 删除 | 重命名 | 下载

<?php

namespace Framework\Tools;
use PharData;

class FileManager
{
    // 提示信息配置
    private $messages = array(
        'file_list_success' => '获取成功。',
        'file_list_failure' => '获取失败。',
        'not_exist' => '文件夹不存在。',
        'duplicate_name' => '文件或文件夹名称已存在。',
        'not_duplicate_name' => '文件或文件夹名称不存在。',
        'file_name' => '文件名称已存在.',
        'folder_name' => '文件夹名称已存在。',
        'copy_success' => '复制成功。',
        'copy_failure' => '复制失败。',
        'move_success' => '移动成功。',
        'move_failure' => '移动失败。',
        'rename_success' => '重命名成功。',
        'rename_failure' => '重命名失败。',
        'recycle_duplicate_name' => '回收站中已存在当前文件或文件夹。',
        'delete_to_recycle_success' => '成功移动到回收站。',
        'delete_to_recycle_failure' => '移动到回收站失败。'
    );

    // 存储最近一次操作的消息
    private $lastMessage = '';

    /**
     * 设置提示信息
     *
     * @param array $messages 自定义提示信息数组,键为消息类型,值为具体消息内容
     * @return void
     */
    public function setMessages($messages)
    {
        $this->messages = array_merge($this->messages, $messages);
    }

    /**
     * 获取最近一次操作的消息
     *
     * @return string 最近一次操作的提示消息
     */
    public function getLastMessage()
    {
        return $this->lastMessage;
    }


    /**
     * 获取当前文件夹下的所有文件夹及子文件夹的树形结构
     *
     * @param string $dir 目录路径
     * @return array
     */
    public function getDirectoryTree($dir, $path = '')
    {
        $result = [];
        if (!is_dir($dir)) {
            $this->lastMessage = $this->messages['not_exist'];
            return $result;
        }
        $cdir = scandir($dir);
        foreach ($cdir as $key => $value) {
            if (!in_array($value, [".", ".."])) {
                $fullPath = $dir . DIRECTORY_SEPARATOR . $value;
                if (is_dir($fullPath)) {
                    $result[] = [
                        'label' => $value, // 文件夹名称
                        'path' => $path ? str_replace($path, '', $fullPath) : $fullPath,
                        'children' => $this->getDirectoryTree($fullPath, $path),
                    ];
                }
            }
        }
        $this->lastMessage = $this->messages['file_list_success'];
        return $result;
    }

    /**
     * 获取指定文件夹下的所有文件(可配置后缀名过滤)
     *
     * @param string $dir 目录路径
     * @param array $allowedExtensions 允许的文件后缀名(可选)
     * @return array
     */
    public function getFilesInDirectory($dir, $allowedExtensions = [])
    {
        $files = [];

        if (!file_exists($dir) && !is_dir($dir)) {
            $this->lastMessage = $this->messages['not_exist'];
            return $files;
        }

        $cdir = scandir($dir);
        foreach ($cdir as $key => $value) {
            if (!in_array($value, [".", ".."]) && is_file($dir . DIRECTORY_SEPARATOR . $value)) {
                // 如果没有设置后缀名过滤,或者文件后缀名在允许的列表中
                if (empty($allowedExtensions) || $this->isFileExtensionAllowed($value, $allowedExtensions)) {
                    $files[] = $value;
                }
            }
        }
        $this->lastMessage = $this->messages['file_list_success'];
        return $files;
    }


    /**
     * 检查文件后缀名是否在允许的列表中
     *
     * @param string $fileName 文件名
     * @param array $allowedExtensions 允许的后缀名列表
     * @return bool
     */
    public function isFileExtensionAllowed($fileName, $allowedExtensions)
    {
        $extension = pathinfo($fileName, PATHINFO_EXTENSION);
        return in_array(strtolower($extension), array_map('strtolower', $allowedExtensions));
    }


    /**
     * 递归删除目录及其内容
     *
     * @param string $dir 目录路径
     * @return bool 是否删除成功
     */
    private function deleteDir($dir)
    {
        if (!is_dir($dir)) return false;
        foreach (scandir($dir) as $item) {
            if ($item == '.' || $item == '..') continue;
            $fullPath = "$dir/$item";
            is_dir($fullPath) ? $this->deleteDir($fullPath) : unlink($fullPath);
        }
        return rmdir($dir);
    }


    /**
     * 删除文件夹
     * emptyTheRecycleBin  是否清空回收站
     * @param string $dir
     * @return bool
     */
    public function deleteDirectory($dir)
    {
        if (!file_exists($dir)) {
            return true;
        }

        if (!is_dir($dir)) {
            return unlink($dir);
        }

        foreach (scandir($dir) as $item) {
            if ($item == '.' || $item == '..') {
                continue;
            }

            if (!$this->deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
                return false;
            }
        }

        return rmdir($dir);
    }

    /**
     * 复制单个或多个文件或文件夹
     *
     * @param string|array $sources 要复制的源文件或文件夹路径,可以是单个路径字符串或路径数组
     * @param string $destination 目标文件夹路径
     * @return bool 操作成功返回 true,失败返回 false
     */
    public function copy($sources, $destination)
    {

        if (!$this->validateSources($sources, $destination)) {
            return false;
        }
        if (!is_array($sources)) {
            $sources = array($sources);
        }
        foreach ($sources as $source) {
            if (is_file($source)) {
                if (!copy($source, $destination . '/' . basename($source))) {
                    $this->lastMessage = $this->messages['copy_failure'];
                    return false;
                }
            } elseif (is_dir($source)) {
                if (!$this->recursiveCopy($source, $destination . '/' . basename($source))) {
                    $this->lastMessage = $this->messages['copy_failure'];
                    return false;
                }
            }
        }
        $this->lastMessage = $this->messages['copy_success'];
        return true;
    }

    /**
     * 递归复制文件夹
     *
     * @param string $source 源文件夹路径
     * @param string $destination 目标文件夹路径
     * @return bool 复制成功返回 true
     */
    private function recursiveCopy($source, $destination)
    {
        if (!is_dir($destination)) {
            mkdir($destination);
        }
        $dir = opendir($source);
        while (($file = readdir($dir)) !== false) {
            if ($file != '.' && $file != '..') {
                if (is_dir($source . '/' . $file)) {
                    $this->recursiveCopy($source . '/' . $file, $destination . '/' . $file);
                } else {
                    copy($source . '/' . $file, $destination . '/' . $file);
                }
            }
        }
        closedir($dir);
        return true;
    }

    /**
     * 移动单个或多个文件或文件夹
     *
     * @param string|array $sources 要移动的源文件或文件夹路径,可以是单个路径字符串或路径数组
     * @param string $destination 目标文件夹路径
     * @return bool 操作成功返回 true,失败返回 false
     */
    public function move($sources, $destination)
    {
        if (!$this->validateSources($sources, $destination)) {
            return false;
        }
        if (!is_array($sources)) {
            $sources = array($sources);
        }
        foreach ($sources as $source) {
            if (!rename($source, $destination . '/' . basename($source))) {
                $this->lastMessage = $this->messages['move_failure'];
                return false;
            }
        }
        $this->lastMessage = $this->messages['move_success'];
        return true;
    }

    /**
     * 重命名文件或文件夹
     *
     * @param string $path 要重命名的文件或文件夹的当前路径
     * @param string $newName 新的名称
     * @return bool 重命名成功返回 true,失败返回 false
     */
    public function rename($path, $newName)
    {
        $dir = dirname($path);
        if (file_exists($dir . '/' . $newName)) {
            $this->lastMessage = $this->messages['duplicate_name'];
            return false;
        }
        if (!rename($path, $dir . '/' . $newName)) {
            $this->lastMessage = $this->messages['rename_failure'];
            return false;
        }
        $this->lastMessage = $this->messages['rename_success'];
        return true;
    }


    /**
     * 生成唯一的文件名
     *
     * @param string $baseName 基础文件名
     * @param string $recycleBin 回收站目录
     * @return string 唯一的文件名
     */
    private function getUniqueFileName($baseName, $recycleBin)
    {
        $nameInfo = pathinfo($baseName);
        $name = $nameInfo['filename'];
        $ext = isset($nameInfo['extension']) ? '.' . $nameInfo['extension'] : '';
        $counter = 1;
        $newName = $baseName;
        while (file_exists($recycleBin . '/' . $newName)) {
            $newName = $name . " ($counter)" . $ext;
            $counter++;
        }
        return $newName;
    }

    /**
     * 删除文件或文件夹到回收站
     *
     * @param string|array $sources 要删除到回收站的文件或文件夹路径,可以是单个路径字符串或路径数组
     * @param string $recycleBin 回收站目录
     * @return bool 操作成功返回 true,失败返回 false
     */
    public function deleteToRecycle($sources, $recycleBin)
    {
        if (!is_array($sources)) {
            $sources = array($sources);
        }
        foreach ($sources as $source) {
            $baseName = basename($source);
            if (!file_exists($source)) {
                $this->lastMessage = $this->messages['not_duplicate_name'];
                return false;
            }
            $uniqueName = $this->getUniqueFileName($baseName, $recycleBin);
            $destination = $recycleBin . '/' . $uniqueName;
            if (!rename($source, $destination)) {
                $this->lastMessage = $this->messages['delete_to_recycle_failure'];
                return false;
            }
        }
        $this->lastMessage = $this->messages['delete_to_recycle_success'];
        return true;
    }

    /**
     * 验证源文件或文件夹是否可以操作
     *
     * @param string|array $sources 要验证的源文件或文件夹路径,可以是单个路径字符串或路径数组
     * @param string $destination 目标文件夹路径
     * @return bool 验证通过返回 true,存在重名情况返回 false
     */
    private function validateSources($sources, $destination)
    {
        if (!is_array($sources)) {
            $sources = array($sources);
        }

        if (!file_exists($destination) && !is_dir($destination)) {
            $this->lastMessage = $this->messages['not_exist'];
            return false;
        }

        foreach ($sources as $source) {
            if (file_exists($destination . '/' . basename($source))) {
                if (is_file($destination . '/' . basename($source))) {
                    $this->lastMessage = $this->messages['file_name'];
                } elseif (is_dir($destination . '/' . basename($source))) {
                    $this->lastMessage = $this->messages['folder_name'];
                } else {
                    $this->lastMessage = $this->messages['duplicate_name'];
                }
                return false;
            }
        }
        return true;
    }

    /**
     * 获取文件详细信息
     *
     * @param string $filePath
     * @return array
     */
    public function getFileDetails($filePath)
    {
        return [
            'size' => filesize($filePath),
            'last_modified' => date("Y m d H:i:s.", filemtime($filePath)),
            'type' => filetype($filePath),
            'extension' => pathinfo($filePath, PATHINFO_EXTENSION),
        ];
    }

    /**
     * 获取文件夹详细信息
     *
     * @param string $dir
     * @return array
     */
    public function getDirectoryDetails($dir)
    {
        return [
            'size' => $this->getDirectorySize($dir),
            'last_modified' => date("Y m d H:i:s.", filemtime($dir)),
            'type' => filetype($dir),
        ];
    }

    /**
     * 计算文件夹大小
     *
     * @param string $dir
     * @return int
     */
    private function getDirectorySize($dir)
    {
        $size = 0;
        foreach (scandir($dir) as $item) {
            if ($item == '.' || $item == '..') {
                continue;
            }

            $path = $dir . DIRECTORY_SEPARATOR . $item;
            if (is_file($path)) {
                $size += filesize($path);
            } else {
                $size += $this->getDirectorySize($path);
            }
        }
        return $size;
    }

    /**
     * 下载文件或文件夹
     *
     * @param array|string $paths 文件或文件夹路径(可以是单个路径或路径数组)
     * @param string $archiveName 打包下载的文件名(可选)
     * @return bool
     */
    public function download($paths, $archiveName = 'download.tar.gz')
    {
        if (empty($paths)) {
            return $this->returnError("没有文件可供下载");
        }

        // 如果是单个文件,直接下载
        if (is_string($paths)) {
            if (!file_exists($paths)) {
                return $this->returnError("文件不存在");
            }
            $this->downloadFile($paths);
            return true;
        }

        // 如果是多个文件或文件夹,检查是否存在
        foreach ($paths as $path) {
            if (!file_exists($path)) {
                return $this->returnError("文件或文件夹不存在");
            }
        }

        // 打包下载
        $this->downloadAsTarGz($paths, $archiveName);
        return true;
    }


    /**
     * 下载文件或图片的方法
     *
     * @param string $filePath 文件的完整路径
     * @param string $fileName 下载时显示的文件名,默认为空
     * @return void
     */
    private function downloadFile($filePath, $fileName = '')
    {
        // 检查文件是否存在
        if (!file_exists($filePath)) {
            http_response_code(404);
            die("文件不存在");
        }

        // 如果没有提供文件名,则使用文件的原始名称
        $fileName = $fileName ?: basename($filePath);

        // 获取文件的 MIME 类型
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $filePath);
        finfo_close($finfo);

        // 设置响应头
        header('Content-Description: File Transfer');
        header('Content-Type: ' . $mimeType);
        header('Content-Disposition: attachment; filename="' . $fileName . '"');
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('Pragma: public');
        header('Content-Length: ' . filesize($filePath));

        // 开启输出缓冲
        ob_clean();
        flush();

        // 读取并输出文件内容
        readfile($filePath);

        // 终止脚本执行
        exit;
    }

    /**
     * 打包下载多个文件或文件夹为 tar.gz 格式
     *
     * @param array $paths 文件或文件夹路径数组
     * @param string $archiveName 打包下载的文件名
     */
    private function downloadAsTarGz($paths, $archiveName = 'download.tar.gz')
    {
        // 创建临时文件
        $archivePath = sys_get_temp_dir() . '/' . $archiveName;

        // 使用 Linux 命令打包
        if ($this->isLinux()) {
            $fileList = implode(' ', array_map('escapeshellarg', $paths));
            exec("tar -czf '{$archivePath}' -C " . dirname($paths[0]) . " " . $fileList);
            if (file_exists($archivePath)) {
                $this->downloadFile($archivePath, $archiveName);
                unlink($archivePath); // 删除临时文件
                exit;
            } else {
                die("打包文件失败");
            }
        }

        // 使用 PHP 打包
        $phar = new PharData($archivePath);
        foreach ($paths as $path) {
            if (is_dir($path)) {
                $phar->buildFromDirectory($path);
            } else {
                $phar->addFile($path, basename($path));
            }
        }

        if (file_exists($archivePath)) {
            $this->downloadFile($archivePath, $archiveName);
            unlink($archivePath); // 删除临时文件
            exit;
        } else {
            die("打包文件失败");
        }
    }

    /**
     * 判断是否是 Linux 系统
     *
     * @return bool
     */
    private function isLinux()
    {
        return strtoupper(substr(PHP_OS, 0, 3)) === 'LIN';
    }

    /**
     * 返回错误信息
     *
     * @param string $message 错误信息
     * @return bool
     */
    private function returnError($message)
    {
        echo $message;
        return false;
    }


}

posted @ 2025-03-11 17:06  xingduo  阅读(30)  评论(0)    收藏  举报