本文由 资源共享网 – ziyuan 发布,转载请注明出处,如有问题请联系我们![免费]简易文件管理器 —— 一个纯 PHP文件 实现 ZIP 打包下载

收藏
<?php
/**
 * 简易文件管理器 —— 纯 PHP 实现 ZIP 打包下载
 * 不依赖任何扩展 / 插件 / 组件
 */

define('ROOT_DIR', realpath(__DIR__));
$currentPath = '';

// 安全检查
if (isset($_GET['path'])) {
    $requested = $_GET['path'];
    $realPath = realpath(ROOT_DIR . '/' . $requested);
    if ($realPath === false || strpos($realPath, ROOT_DIR) !== 0) {
        exit('Access denied.');
    }
    $currentPath = trim($requested, '/');
}
$currentDir = ROOT_DIR . ($currentPath ? '/' . $currentPath : '');

// 允许编辑的文本类型
$editableExts = [
    'txt','php','html','htm','css','js','json','xml','md','ini',
    'cfg','log','csv','sql','yaml','yml','sh','bat','py','rb',
    'java','c','cpp','h','conf','env'
];

// ============ 动作处理 ============
if (isset($_GET['action'])) {
    // 1. 打包下载(纯 PHP ZIP)
    if ($_GET['action'] === 'download_zip') {
        $zipFileName = 'backup_' . date('YmdHis') . '.zip';

        // 创建临时文件
        $tmp = tmpfile();
        if ($tmp === false) {
            $tmpPath = tempnam(sys_get_temp_dir(), 'zip_');
            $tmp = fopen($tmpPath, 'w+b');
        } else {
            $tmpPath = null; // tmpfile 自动删除
        }

        // 生成 ZIP 并写入临时文件
        buildZipStream($currentDir, $tmp);

        // 获取大小
        fseek($tmp, 0, SEEK_END);
        $size = ftell($tmp);
        rewind($tmp);

        // 输出头
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="' . $zipFileName . '"');
        header('Content-Length: ' . $size);
        fpassthru($tmp);

        // 清理
        fclose($tmp);
        if ($tmpPath) @unlink($tmpPath);
        exit;
    }

    // 2. 编辑文件界面
    if ($_GET['action'] === 'edit' && !empty($_GET['file'])) {
        $fileName = basename($_GET['file']);
        $filePath = realpath($currentDir . '/' . $fileName);
        if ($filePath === false || strpos($filePath, ROOT_DIR) !== 0 || !is_file($filePath)) {
            exit('File not found.');
        }
        if (!in_array(strtolower(pathinfo($filePath, PATHINFO_EXTENSION)), $editableExts)) {
            exit('Editing this file type is not allowed.');
        }
        $content = file_get_contents($filePath);
        ?>
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>编辑:<?php echo htmlspecialchars(basename($filePath)); ?></title>
            <style>
                body { font-family: system-ui; margin: 30px; background: #f4f6f9; color: #333; }
                h2 { color: #007bff; }
                textarea { width: 100%; height: 60vh; font-family: monospace; font-size: 14px; padding: 10px; border: 1px solid #ccc; border-radius: 6px; }
                .btn { padding: 10px 22px; border: none; border-radius: 6px; background: #007bff; color: white; cursor: pointer; margin-right: 10px; text-decoration: none; display: inline-block; }
                .btn:hover { background: #0056b3; }
                .cancel { background: #6c757d; } .cancel:hover { background: #5a6268; }
            </style>
        </head>
        <body>
            <h2>✏️ 编辑:<?php echo htmlspecialchars(basename($filePath)); ?></h2>
            <form method="POST" action="?action=save&file=<?php echo urlencode($fileName); ?>&path=<?php echo urlencode($currentPath); ?>">
                <textarea name="content"><?php echo htmlspecialchars($content); ?></textarea><br><br>
                <button type="submit">? 保存</button>
                <a href="?path=<?php echo urlencode($currentPath); ?>" class="btn cancel">取消</a>
            </form>
        </body>
        </html>
        <?php
        exit;
    }

    // 3. 保存编辑
    if ($_GET['action'] === 'save' && $_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_GET['file'])) {
        $fileName = basename($_GET['file']);
        $filePath = realpath($currentDir . '/' . $fileName);
        if ($filePath === false || strpos($filePath, ROOT_DIR) !== 0 || !is_file($filePath)) exit('File not found.');
        if (!in_array(strtolower(pathinfo($filePath, PATHINFO_EXTENSION)), $editableExts)) exit('Not allowed.');
        file_put_contents($filePath, $_POST['content'] ?? '');
        header('Location: ?path=' . urlencode($currentPath));
        exit;
    }
}

// ============ ZIP 构建函数(纯 PHP,无扩展)============
function buildZipStream($sourceDir, $outputHandle) {
    $entries = [];
    $basePath = rtrim(realpath($sourceDir), '/') . '/';

    // 递归收集文件/目录
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($iterator as $item) {
        $realPath = $item->getRealPath();
        $relative = substr($realPath, strlen($basePath));
        if ($relative === '') continue; // 根目录自身跳过

        if ($item->isDir()) {
            $entries[] = [
                'type' => 'dir',
                'path' => $relative . '/',
                'real' => null,
                'size' => 0,
                'mtime' => $item->getMTime()
            ];
        } else {
            $entries[] = [
                'type' => 'file',
                'path' => $relative,
                'real' => $realPath,
                'size' => $item->getSize(),
                'mtime' => $item->getMTime()
            ];
        }
    }

    // 收集中央目录信息
    $centralEntries = [];
    $offset = 0;

    foreach ($entries as $entry) {
        $name = $entry['path'];
        $nameBytes = $name; // 字节串
        $mtime = $entry['mtime'];
        $date = date('Y-m-d H:i:s', $mtime);
        $dostime = dosTime($mtime);

        if ($entry['type'] === 'dir') {
            $data = '';
            $crc = 0;
            $compSize = 0;
            $uncompSize = 0;
        } else {
            $data = file_get_contents($entry['real']);
            $uncompSize = strlen($data);
            $compSize = $uncompSize; // STORE 模式
            $crc = crc32($data);
        }

        // 本地文件头
        $localHeader = pack('V', 0x04034b50); // 签名
        $localHeader .= pack('v', 20);        // 解压版本 2.0
        $localHeader .= pack('v', 0);         // 通用标志位
        $localHeader .= pack('v', 0);         // 压缩方法 0=STORE
        $localHeader .= pack('V', $dostime);  // 修改时间/日期
        $localHeader .= pack('V', $crc);      // CRC32
        $localHeader .= pack('V', $compSize); // 压缩大小
        $localHeader .= pack('V', $uncompSize);// 未压缩大小
        $localHeader .= pack('v', strlen($nameBytes)); // 文件名长度
        $localHeader .= pack('v', 0);         // 额外字段长度
        $localHeader .= $nameBytes;

        fwrite($outputHandle, $localHeader);
        fwrite($outputHandle, $data);

        // 记录中央目录信息
        $centralEntries[] = [
            'name'       => $nameBytes,
            'crc'        => $crc,
            'compSize'   => $compSize,
            'uncompSize' => $uncompSize,
            'dostime'    => $dostime,
            'offset'     => $offset
        ];

        $offset += strlen($localHeader) + strlen($data);
    }

    // 中央目录起始位置
    $centralStart = $offset;

    foreach ($centralEntries as $e) {
        $centralHeader = pack('V', 0x02014b50); // 中央目录签名
        $centralHeader .= pack('v', 20);        // 创建版本
        $centralHeader .= pack('v', 20);        // 解压版本
        $centralHeader .= pack('v', 0);         // 标志位
        $centralHeader .= pack('v', 0);         // 压缩方法
        $centralHeader .= pack('V', $e['dostime']);
        $centralHeader .= pack('V', $e['crc']);
        $centralHeader .= pack('V', $e['compSize']);
        $centralHeader .= pack('V', $e['uncompSize']);
        $centralHeader .= pack('v', strlen($e['name'])); // 文件名长
        $centralHeader .= pack('v', 0);  // 额外字段
        $centralHeader .= pack('v', 0);  // 注释长度
        $centralHeader .= pack('v', 0);  // 磁盘起始
        $centralHeader .= pack('v', 0);  // 内部属性
        $centralHeader .= pack('V', 0);  // 外部属性
        $centralHeader .= pack('V', $e['offset']);
        $centralHeader .= $e['name'];

        fwrite($outputHandle, $centralHeader);
        $offset += strlen($centralHeader);
    }

    // 结束记录
    $endRecord = pack('V', 0x06054b50);
    $endRecord .= pack('v', 0);       // 磁盘编号
    $endRecord .= pack('v', 0);       // 起始磁盘
    $endRecord .= pack('v', count($centralEntries)); // 磁盘条目数
    $endRecord .= pack('v', count($centralEntries)); // 总条目数
    $endRecord .= pack('V', $offset - $centralStart); // 中央目录大小
    $endRecord .= pack('V', $centralStart); // 中央目录偏移
    $endRecord .= pack('v', 0);       // 注释长度
    fwrite($outputHandle, $endRecord);
}

function dosTime($timestamp) {
    $time = localtime($timestamp, true);
    $dosTime  = ($time['tm_sec'] >> 1) & 0x1F;          // 秒/2
    $dosTime |= ($time['tm_min'] & 0x3F) << 5;
    $dosTime |= ($time['tm_hour'] & 0x1F) << 11;
    $dosDate  = ($time['tm_mday'] & 0x1F);
    $dosDate |= (($time['tm_mon'] + 1) & 0x0F) << 5;
    $dosDate |= (($time['tm_year'] + 1900 - 1980) & 0x7F) << 9;
    return ($dosDate << 16) | $dosTime;
}

// ============ 目录列表 ============
$items = @scandir($currentDir);
if ($items === false) exit('Cannot read directory.');

$dirs = [];
$files = [];
foreach ($items as $item) {
    if ($item === '.') continue;
    if ($item === '..') {
        if ($currentPath !== '') {
            $parentPath = dirname($currentPath);
            if ($parentPath === '.') $parentPath = '';
            $dirs[] = ['name' => '..', 'path' => $parentPath, 'isDir' => true];
        }
        continue;
    }
    $full = $currentDir . '/' . $item;
    if (is_dir($full)) {
        $dirs[] = ['name' => $item, 'path' => ($currentPath ? $currentPath . '/' : '') . $item, 'isDir' => true];
    } else {
        $files[] = [
            'name' => $item,
            'path' => ($currentPath ? $currentPath . '/' : '') . $item,
            'isDir' => false,
            'size' => filesize($full),
            'ext'  => pathinfo($item, PATHINFO_EXTENSION)
        ];
    }
}

usort($dirs, function($a,$b){ return strcasecmp($a['name'],$b['name']); });
usort($files, function($a,$b){ return strcasecmp($a['name'],$b['name']); });
$allItems = array_merge($dirs, $files);

// 面包屑
$crumbs = [['label' => '? 根目录', 'path' => '']];
$parts = $currentPath ? explode('/', $currentPath) : [];
$build = '';
foreach ($parts as $part) {
    if ($part === '') continue;
    $build .= ($build ? '/' : '') . $part;
    $crumbs[] = ['label' => $part, 'path' => $build];
}

function formatBytes($b) {
    if ($b >= 1073741824) return number_format($b/1073741824,2).' GB';
    if ($b >= 1048576)    return number_format($b/1048576,2).' MB';
    if ($b >= 1024)        return number_format($b/1024,2).' KB';
    return $b.' B';
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>简易文件管理器</title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body { font-family: system-ui, sans-serif; background: #f0f2f5; color: #222; padding: 30px; }
        .container { max-width: 960px; margin: auto; background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); padding: 25px 30px; }
        h1 { font-size: 24px; color: #007bff; margin-bottom: 15px; }
        .breadcrumb { margin-bottom: 20px; color: #555; }
        .breadcrumb a { color: #007bff; text-decoration: none; }
        .breadcrumb a:hover { text-decoration: underline; }
        .breadcrumb span { margin: 0 6px; }
        .toolbar { margin-bottom: 18px; }
        .btn { display: inline-block; padding: 8px 18px; border-radius: 6px; background: #007bff; color: #fff; text-decoration: none; border: none; cursor: pointer; }
        .btn:hover { background: #0056b3; }
        table { width: 100%; border-collapse: collapse; }
        th, td { text-align: left; padding: 12px 15px; border-bottom: 1px solid #e9ecef; }
        th { background: #f8f9fa; color: #495057; }
        tr:hover td { background: #f1f3f5; }
        .icon { margin-right: 6px; }
        .actions a { color: #007bff; text-decoration: none; margin-right: 10px; }
        .actions a:hover { text-decoration: underline; }
        .size { color: #6c757d; }
        .empty { text-align: center; color: #6c757d; padding: 30px; }
    </style>
</head>
<body>
<div>
    <h1>? 文件管理器</h1>
    <div>
        <?php foreach ($crumbs as $i => $c): ?>
            <?php if ($i > 0): ?><span>/</span><?php endif; ?>
            <?php if ($i === count($crumbs)-1): ?>
                <strong><?php echo htmlspecialchars($c['label']); ?></strong>
            <?php else: ?>
                <a href="?path=<?php echo urlencode($c['path']); ?>"><?php echo htmlspecialchars($c['label']); ?></a>
            <?php endif; ?>
        <?php endforeach; ?>
    </div>

    <div>
        <a href="?action=download_zip&path=<?php echo urlencode($currentPath); ?>">⬇️ 打包下载此目录</a>
    </div>

    <table>
        <thead><tr><th>名称</th><th>大小</th><th>操作</th></tr></thead>
        <tbody>
            <?php if (empty($allItems)): ?>
                <tr><td colspan="3">此目录为空</td></tr>
            <?php else: ?>
                <?php foreach ($allItems as $item): ?>
                <tr>
                    <td>
                        <?php if ($item['isDir']): ?>
                            <span>?</span>
                            <a href="?path=<?php echo urlencode($item['path']); ?>"><?php echo htmlspecialchars($item['name']); ?></a>
                        <?php else: ?>
                            <span>?</span>
                            <?php echo htmlspecialchars($item['name']); ?>
                        <?php endif; ?>
                    </td>
                    <td><?php echo $item['isDir'] ? '—' : formatBytes($item['size']); ?></td>
                    <td>
                        <?php if (!$item['isDir'] && in_array(strtolower($item['ext']), $editableExts)): ?>
                            <a href="?action=edit&file=<?php echo urlencode($item['name']); ?>&path=<?php echo urlencode($currentPath); ?>">✏️ 编辑</a>
                        <?php endif; ?>
                    </td>
                </tr>
                <?php endforeach; ?>
            <?php endif; ?>
        </tbody>
    </table>
</div>
</body>
</html>


评论(0条)

请登录后评论
ziyuan

ziyuan Rank: 16

0

0

0

( 此人很懒并没有留下什么~~ )

首页

栏目

搜索

会员