使用PHP与Apache实现服务器端文件管理

引言

作为前端开发者,你可能经常需要与服务器文件系统交互。本文将详细介绍如何通过PHP配合Apache实现服务器端文件管理功能。即使你没有任何PHP经验,也能按照本教程实现完整解决方案!

系统准备

PHP下载与安装

  1. 访问PHP官网下载页面

  2. 选择与Apache匹配的版本:

    • Apache版本:2.4.x

    • PHP版本:8.4.x TS (Thread Safe)(对应64位Apache的文件名应该类似:VS17 x64 Thread Safe,对应32位Apache的文件名应该类似:VS17 x86 Thread Safe)

    • 架构:x64(64位系统)或x86(32位系统)

  3. 解压到目录(如D:\php),目录结构应包含:

    bash 复制代码
    php.exe
    php8apache2_4.dll
    php.ini-development
    ext/ (扩展目录)

Apache配置PHP

编辑conf/httpd.conf文件:

bash 复制代码
# 加载PHP模块
LoadModule php_module "D:/php/php8apache2_4.dll"

# 指定php.ini目录
PHPIniDir "D:/php"

# 将.php文件交给PHP处理
AddHandler application/x-httpd-php .php

# 关联文件扩展名
<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

验证配置

bash 复制代码
cd D:\Apache24\bin
httpd -t

看到"Syntax OK"表示配置正确,重启Apache服务:

bash 复制代码
httpd -k restart

文件管理API实现

创建file_manager.php文件:

php 复制代码
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *'); // 测试用,生产环境应移除

// 安全配置
$BASE_DIR = realpath(__DIR__ . '/Resources');
$ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt', 'docx'];

// 路径验证函数
function validatePath($path) {
    global $BASE_DIR;
    $realPath = realpath($BASE_DIR . '/' . $path);
    
    return ($realPath && strpos($realPath, $BASE_DIR) === 0) 
        ? $realPath : false;
}

// 获取目录结构
function listDirectory($path) {
    $realPath = validatePath($path);
    if (!$realPath || !is_dir($realPath)) {
        return ['error' => '无效目录路径'];
    }

    $result = [];
    $items = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($realPath, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($items as $item) {
        $relativePath = substr($item->getPathname(), strlen($realPath)) ?: '/';
        $relativePath = ltrim(str_replace('\\', '/', $relativePath), '/');
        
        $result[] = [
            'name' => $item->getFilename(),
            'path' => $relativePath,
            'type' => $item->isDir() ? 'directory' : 'file',
            'size' => $item->isFile() ? $item->getSize() : 0,
            'modified' => date('Y-m-d H:i:s', $item->getMTime())
        ];
    }

    return $result;
}

// 主请求处理
$response = ['status' => 'error', 'message' => '无效请求'];
$request = json_decode(file_get_contents('php://input'), true) ?? $_REQUEST;

try {
    if (!isset($request['action'])) {
        throw new Exception('未指定操作');
    }

    $action = $request['action'];
    
    switch ($action) {
        case 'list':
            $path = $request['path'] ?? '';
            $data = listDirectory($path);
            $response = ['status' => 'success', 'data' => $data];
            break;
            
        case 'create-folder':
            $path = $request['path'] ?? '';
            $name = $request['name'] ?? '';
            
            if (empty($name)) throw new Exception('文件夹名称不能为空');
            
            $realPath = validatePath($path);
            if (!$realPath) throw new Exception('无效路径');
            
            // 清理文件夹名称
            $cleanName = preg_replace('/[^a-zA-Z0-9_-]/', '', $name);
            $newFolder = $realPath . DIRECTORY_SEPARATOR . $cleanName;
            
            if (file_exists($newFolder)) {
                throw new Exception('文件夹已存在');
            }
            
            if (!mkdir($newFolder, 0755)) {
                throw new Exception('创建文件夹失败');
            }
            
            $response = ['status' => 'success', 'message' => '文件夹创建成功'];
            break;
            
        case 'delete-folder':
            $path = $request['path'] ?? '';
            $realPath = validatePath($path);
            
            if (!$realPath || !is_dir($realPath)) {
                throw new Exception('无效目录路径');
            }
            
            // 安全措施:防止删除根目录
            if ($realPath === $BASE_DIR) {
                throw new Exception('不能删除根目录');
            }
            
            // 递归删除
            $files = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($realPath, FilesystemIterator::SKIP_DOTS),
                RecursiveIteratorIterator::CHILD_FIRST
            );
            
            foreach ($files as $file) {
                if ($file->isDir()) {
                    rmdir($file->getRealPath());
                } else {
                    unlink($file->getRealPath());
                }
            }
            
            if (!rmdir($realPath)) {
                throw new Exception('删除文件夹失败');
            }
            
            $response = ['status' => 'success', 'message' => '文件夹已删除'];
            break;
            
        case 'rename':
            $type = $request['type'] ?? 'file';
            $path = $request['path'] ?? '';
            $newName = $request['newName'] ?? '';
            
            if (empty($newName)) throw new Exception('新名称不能为空');
            
            $realPath = validatePath($path);
            if (!$realPath) throw new Exception('无效路径');
            
            // 清理新名称
            $cleanName = preg_replace('/[^a-zA-Z0-9_.-]/', '', $newName);
            $newPath = dirname($realPath) . DIRECTORY_SEPARATOR . $cleanName;
            
            if (file_exists($newPath)) {
                throw new Exception('目标名称已存在');
            }
            
            if (!rename($realPath, $newPath)) {
                throw new Exception('重命名失败');
            }
            
            $response = ['status' => 'success', 'message' => '重命名成功'];
            break;
            
        case 'upload-file':
            $targetPath = $request['path'] ?? '';
            $realPath = validatePath($targetPath);
            
            if (!$realPath || !is_dir($realPath)) {
                throw new Exception('无效目标目录');
            }
            
            if (empty($_FILES['file'])) {
                throw new Exception('未选择上传文件');
            }
            
            $file = $_FILES['file'];
            $filename = preg_replace('/[^a-zA-Z0-9_.-]/', '', basename($file['name']));
            $targetFile = $realPath . DIRECTORY_SEPARATOR . $filename;
            $ext = strtolower(pathinfo($targetFile, PATHINFO_EXTENSION));
            
            // 文件类型验证
            if (!in_array($ext, $ALLOWED_EXTENSIONS)) {
                throw new Exception('不允许的文件类型: ' . $ext);
            }
            
            // 防止文件覆盖
            if (file_exists($targetFile)) {
                $filename = time() . '_' . $filename;
                $targetFile = $realPath . DIRECTORY_SEPARATOR . $filename;
            }
            
            if (!move_uploaded_file($file['tmp_name'], $targetFile)) {
                throw new Exception('文件上传失败');
            }
            
            $response = ['status' => 'success', 'filename' => $filename, 'message' => '文件上传成功'];
            break;
            
        case 'delete-file':
            $path = $request['path'] ?? '';
            $realPath = validatePath($path);
            
            if (!$realPath || !is_file($realPath)) {
                throw new Exception('无效文件路径');
            }
            
            if (!unlink($realPath)) {
                throw new Exception('文件删除失败');
            }
            
            $response = ['status' => 'success', 'message' => '文件已删除'];
            break;
            
        default:
            throw new Exception('未知操作: ' . $action);
    }
    
} catch (Exception $e) {
    $response = ['status' => 'error', 'message' => $e->getMessage()];
}

echo json_encode($response);
?>

测试页面

创建file_manager_test.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件管理器测试</title>
    <style>
        :root {
            --primary: #3498db;
            --success: #2ecc71;
            --danger: #e74c3c;
            --dark: #34495e;
            --light: #f8f9fa;
        }
        
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background-color: #f5f7fa;
            color: #333;
            line-height: 1.6;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 10px;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
            padding: 30px;
        }
        
        header {
            text-align: center;
            margin-bottom: 30px;
            padding-bottom: 20px;
            border-bottom: 1px solid #eee;
        }
        
        h1 {
            color: var(--dark);
            margin-bottom: 10px;
        }
        
        .subtitle {
            color: #7f8c8d;
            font-weight: 400;
        }
        
        .section {
            margin-bottom: 30px;
            padding: 25px;
            border: 1px solid #e1e4e8;
            border-radius: 8px;
            background: var(--light);
            transition: all 0.3s ease;
        }
        
        .section:hover {
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
            border-color: #d1d9e0;
        }
        
        .section h2 {
            color: var(--dark);
            margin-top: 0;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 1px dashed #ddd;
            display: flex;
            align-items: center;
        }
        
        .section h2 i {
            margin-right: 10px;
            color: var(--primary);
        }
        
        .form-group {
            margin-bottom: 20px;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
        }
        
        label {
            display: inline-block;
            width: 150px;
            font-weight: 600;
            color: #555;
        }
        
        input[type="text"],
        input[type="file"] {
            width: calc(100% - 160px);
            padding: 12px 15px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
            transition: border 0.3s;
        }
        
        input[type="text"]:focus,
        input[type="file"]:focus {
            border-color: var(--primary);
            outline: none;
            box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
        }
        
        button {
            padding: 12px 25px;
            background: var(--primary);
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            font-weight: 600;
            transition: all 0.3s;
            margin-top: 10px;
        }
        
        button:hover {
            background: #2980b9;
            transform: translateY(-2px);
        }
        
        .delete-btn {
            background: var(--danger);
        }
        
        .delete-btn:hover {
            background: #c0392b;
        }
        
        .radio-group {
            display: flex;
            gap: 20px;
            margin-left: 150px;
            width: calc(100% - 150px);
        }
        
        .radio-group label {
            width: auto;
            display: flex;
            align-items: center;
            gap: 5px;
            font-weight: normal;
        }
        
        .response {
            margin-top: 20px;
            padding: 15px;
            border-radius: 4px;
            display: none;
        }
        
        .success {
            background-color: rgba(46, 204, 113, 0.1);
            border: 1px solid var(--success);
            color: #27ae60;
            display: block;
        }
        
        .error {
            background-color: rgba(231, 76, 60, 0.1);
            border: 1px solid var(--danger);
            color: #c0392b;
            display: block;
        }
        
        pre {
            background: #2d2d2d;
            color: #f8f8f2;
            padding: 15px;
            border-radius: 4px;
            max-height: 400px;
            overflow: auto;
            margin-top: 15px;
            font-family: 'Consolas', monospace;
        }
        
        @media (max-width: 768px) {
            .form-group {
                flex-direction: column;
                align-items: flex-start;
            }
            
            label {
                width: 100%;
                margin-bottom: 8px;
            }
            
            input[type="text"],
            input[type="file"] {
                width: 100%;
            }
            
            .radio-group {
                margin-left: 0;
                width: 100%;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>服务器文件管理器测试</h1>
            <p class="subtitle">通过PHP与Apache实现安全的服务器端文件操作</p>
        </header>
        
        <!-- 列出目录内容 -->
        <div class="section">
            <h2><i class="fas fa-folder-open"></i> 列出目录内容</h2>
            <div class="form-group">
                <label for="listPath">目录路径:</label>
                <input type="text" id="listPath" placeholder="例如: docs/images (留空显示根目录)">
            </div>
            <button onclick="listDirectory()">列出目录</button>
            <div class="response">
                <pre id="listResult">目录内容将显示在这里...</pre>
            </div>
        </div>
        
        <!-- 创建文件夹 -->
        <div class="section">
            <h2><i class="fas fa-folder-plus"></i> 创建文件夹</h2>
            <div class="form-group">
                <label for="createPath">父目录路径:</label>
                <input type="text" id="createPath" placeholder="例如: docs">
            </div>
            <div class="form-group">
                <label for="folderName">文件夹名称:</label>
                <input type="text" id="folderName" placeholder="例如: new_folder">
            </div>
            <button onclick="createFolder()">创建文件夹</button>
            <div class="response" id="createResponse"></div>
        </div>
        
        <!-- 删除文件夹 -->
        <div class="section">
            <h2><i class="fas fa-trash-alt"></i> 删除文件夹</h2>
            <div class="form-group">
                <label for="deleteFolderPath">文件夹路径:</label>
                <input type="text" id="deleteFolderPath" placeholder="例如: docs/old_folder">
            </div>
            <button class="delete-btn" onclick="deleteFolder()">删除文件夹</button>
            <div class="response" id="deleteFolderResponse"></div>
        </div>
        
        <!-- 重命名文件/文件夹 -->
        <div class="section">
            <h2><i class="fas fa-i-cursor"></i> 重命名项目</h2>
            <div class="form-group">
                <label for="renamePath">当前路径:</label>
                <input type="text" id="renamePath" placeholder="例如: docs/image.jpg">
            </div>
            <div class="form-group">
                <label for="newName">新名称:</label>
                <input type="text" id="newName" placeholder="例如: new_image.jpg">
            </div>
            <div class="form-group">
                <label>类型:</label>
                <div class="radio-group">
                    <label><input type="radio" name="renameType" value="file" checked> 文件</label>
                    <label><input type="radio" name="renameType" value="folder"> 文件夹</label>
                </div>
            </div>
            <button onclick="renameItem()">重命名</button>
            <div class="response" id="renameResponse"></div>
        </div>
        
        <!-- 上传文件 -->
        <div class="section">
            <h2><i class="fas fa-upload"></i> 上传文件</h2>
            <div class="form-group">
                <label for="uploadPath">目标目录:</label>
                <input type="text" id="uploadPath" placeholder="例如: docs/uploads">
            </div>
            <div class="form-group">
                <label for="fileInput">选择文件:</label>
                <input type="file" id="fileInput">
            </div>
            <button onclick="uploadFile()">上传文件</button>
            <div class="response" id="uploadResponse"></div>
        </div>
        
        <!-- 删除文件 -->
        <div class="section">
            <h2><i class="fas fa-trash-alt"></i> 删除文件</h2>
            <div class="form-group">
                <label for="deleteFilePath">文件路径:</label>
                <input type="text" id="deleteFilePath" placeholder="例如: docs/file.txt">
            </div>
            <button class="delete-btn" onclick="deleteFile()">删除文件</button>
            <div class="response" id="deleteFileResponse"></div>
        </div>
    </div>

    <!-- Font Awesome 图标 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/js/all.min.js"></script>
    
    <script>
        const API_URL = 'file_manager.php';
        
        // 显示响应消息
        function showResponse(element, message, isSuccess = true) {
            element.textContent = message;
            element.className = 'response ' + (isSuccess ? 'success' : 'error');
            element.style.display = 'block';
            
            // 3秒后淡出
            setTimeout(() => {
                element.style.opacity = '1';
                let opacity = 1;
                const fadeOut = setInterval(() => {
                    opacity -= 0.05;
                    element.style.opacity = opacity;
                    if (opacity <= 0) {
                        clearInterval(fadeOut);
                        element.style.display = 'none';
                    }
                }, 50);
            }, 3000);
        }
        
        // 调用API
        async function callApi(data, files) {
            const formData = new FormData();
            
            // 添加表单数据
            for (const key in data) {
                formData.append(key, data[key]);
            }
            
            // 添加文件
            if (files) {
                for (const file of files) {
                    formData.append('file', file);
                }
            }
            
            try {
                const response = await fetch(API_URL, {
                    method: 'POST',
                    body: formData
                });
                
                return await response.json();
            } catch (error) {
                return {
                    status: 'error',
                    message: '网络错误: ' + error.message
                };
            }
        }
        
        // 列出目录内容
        async function listDirectory() {
            const path = document.getElementById('listPath').value || '';
            const result = document.getElementById('listResult');
            
            result.textContent = '加载中...';
            
            const response = await callApi({
                action: 'list',
                path: path
            });
            
            if (response.status === 'success') {
                result.textContent = JSON.stringify(response.data, null, 2);
            } else {
                result.textContent = '错误: ' + response.message;
            }
        }
        
        // 创建文件夹
        async function createFolder() {
            const path = document.getElementById('createPath').value || '';
            const name = document.getElementById('folderName').value;
            const responseEl = document.getElementById('createResponse');
            
            if (!name) {
                showResponse(responseEl, '文件夹名称不能为空', false);
                return;
            }
            
            const response = await callApi({
                action: 'create-folder',
                path: path,
                name: name
            });
            
            showResponse(
                responseEl, 
                response.status === 'success' 
                    ? '✅ ' + response.message 
                    : '❌ 错误: ' + response.message,
                response.status === 'success'
            );
        }
        
        // 删除文件夹
        async function deleteFolder() {
            const path = document.getElementById('deleteFolderPath').value;
            const responseEl = document.getElementById('deleteFolderResponse');
            
            if (!path) {
                showResponse(responseEl, '文件夹路径不能为空', false);
                return;
            }
            
            if (!confirm(`确定要删除文件夹 "${path}" 及其所有内容吗?`)) {
                return;
            }
            
            const response = await callApi({
                action: 'delete-folder',
                path: path
            });
            
            showResponse(
                responseEl, 
                response.status === 'success' 
                    ? '✅ ' + response.message 
                    : '❌ 错误: ' + response.message,
                response.status === 'success'
            );
        }
        
        // 重命名项目
        async function renameItem() {
            const path = document.getElementById('renamePath').value;
            const newName = document.getElementById('newName').value;
            const type = document.querySelector('input[name="renameType"]:checked').value;
            const responseEl = document.getElementById('renameResponse');
            
            if (!path || !newName) {
                showResponse(responseEl, '路径和新名称不能为空', false);
                return;
            }
            
            const response = await callApi({
                action: 'rename',
                path: path,
                newName: newName,
                type: type
            });
            
            showResponse(
                responseEl, 
                response.status === 'success' 
                    ? '✅ ' + response.message 
                    : '❌ 错误: ' + response.message,
                response.status === 'success'
            );
        }
        
        // 上传文件
        async function uploadFile() {
            const path = document.getElementById('uploadPath').value || '';
            const fileInput = document.getElementById('fileInput');
            const responseEl = document.getElementById('uploadResponse');
            
            if (!fileInput.files || fileInput.files.length === 0) {
                showResponse(responseEl, '请选择要上传的文件', false);
                return;
            }
            
            const response = await callApi({
                action: 'upload-file',
                path: path
            }, [fileInput.files[0]]);
            
            showResponse(
                responseEl, 
                response.status === 'success' 
                    ? `✅ ${response.message} - 文件名: ${response.filename}` 
                    : '❌ 错误: ' + response.message,
                response.status === 'success'
            );
        }
        
        // 删除文件
        async function deleteFile() {
            const path = document.getElementById('deleteFilePath').value;
            const responseEl = document.getElementById('deleteFileResponse');
            
            if (!path) {
                showResponse(responseEl, '文件路径不能为空', false);
                return;
            }
            
            if (!confirm(`确定要删除文件 "${path}" 吗?`)) {
                return;
            }
            
            const response = await callApi({
                action: 'delete-file',
                path: path
            });
            
            showResponse(
                responseEl, 
                response.status === 'success' 
                    ? '✅ ' + response.message 
                    : '❌ 错误: ' + response.message,
                response.status === 'success'
            );
        }
    </script>
</body>
</html>

关键注意事项

  1. 安全配置

    • 生产环境中移除Access-Control-Allow-Origin: *

    • 添加身份验证(如Basic Auth或API密钥)

    • 限制允许的文件扩展名

    • 设置文件上传大小限制(在php.ini中配置)

  2. 权限设置

    • Windows:为Resources目录添加Apache用户(如SYSTEM)的完全控制权限

    • Linux:chown -R www-data:www-data Resources && chmod -R 755 Resources

  3. 路径说明

    • 列出Resources根目录:输入框留空

    • 列出子目录:直接输入子目录名(如uploads

    • 不要使用前导斜杠(如/uploads

测试流程

  1. 创建Resources目录并设置权限

  2. 上传file_manager.phpfile_manager_test.html到Apache文档根目录

  3. 访问http://localhost/file_manager_test.html

  4. 测试各项功能:

    • 列出目录(留空)

    • 创建测试文件夹

    • 上传文件

    • 重命名/删除操作

补充:

由于前面的php提供的目录树形式过于扁平,这里提供一个返回树形结构的目录的json的php文件。如果要基于这个php文件进行测试,在测试文件file-manager-test.html里面 action:'list'的地方改成action:'get-tree'就可以了。

改进后的file_manager.php:

php 复制代码
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');

// 配置安全选项
$BASE_DIR = realpath(__DIR__ . '/Resources');
$ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'glb', 'gltf', 'fbx', 'obj', 'txt', 'md'];

// 递归获取目录结构
function getDirectoryTree($path) {
    $realPath = realpath($path);
    if (!$realPath || !is_dir($realPath)) {
        return null;
    }

    $result = [
        'name' => basename($realPath),
        'path' => str_replace($GLOBALS['BASE_DIR'], '', $realPath) ?: '/',
        'type' => 'directory',
        'children' => []
    ];

    $items = scandir($realPath);
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        
        $itemPath = $realPath . DIRECTORY_SEPARATOR . $item;
        if (is_dir($itemPath)) {
            $result['children'][] = getDirectoryTree($itemPath);
        } else {
            $ext = strtolower(pathinfo($itemPath, PATHINFO_EXTENSION));
            $result['children'][] = [
                'name' => $item,
                'path' => str_replace($GLOBALS['BASE_DIR'], '', $itemPath),
                'type' => 'file',
                'size' => filesize($itemPath),
                'modified' => filemtime($itemPath),
                'extension' => $ext
            ];
        }
    }

    return $result;
}

// 处理请求
$response = ['status' => 'error', 'message' => 'Invalid request'];
$request = json_decode(file_get_contents('php://input'), true) ?? $_REQUEST;

try {
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
        exit(0);
    }

    if (!isset($request['action'])) {
        throw new Exception('No action specified');
    }

    $action = $request['action'];
    
    switch ($action) {
        case 'get-tree':
            $tree = getDirectoryTree($BASE_DIR);
            if ($tree) {
                $response = ['status' => 'success', 'data' => $tree];
            } else {
                throw new Exception('Failed to load directory tree');
            }
            break;
            
        case 'get-files':
            $path = $request['path'] ?? '/';
            $realPath = realpath($BASE_DIR . $path);
            
            if (!$realPath || !is_dir($realPath)) {
                throw new Exception('Invalid directory path');
            }
            
            $files = [];
            $items = scandir($realPath);
            foreach ($items as $item) {
                if ($item === '.' || $item === '..') continue;
                
                $itemPath = $realPath . DIRECTORY_SEPARATOR . $item;
                if (!is_dir($itemPath)) {
                    $ext = strtolower(pathinfo($itemPath, PATHINFO_EXTENSION));
                    $files[] = [
                        'name' => $item,
                        'path' => $path . '/' . $item,
                        'type' => 'file',
                        'size' => filesize($itemPath),
                        'modified' => filemtime($itemPath),
                        'extension' => $ext
                    ];
                }
            }
            
            $response = ['status' => 'success', 'files' => $files];
            break;
            
        case 'create-folder':
            $path = $request['path'] ?? '';
            $name = $request['name'] ?? '';
            
            if (empty($name)) throw new Exception('Folder name is required');
            
            $realPath = validatePath($path);
            if (!$realPath) throw new Exception('Invalid path');
            
            $newFolder = $realPath . DIRECTORY_SEPARATOR . preg_replace('/[^a-zA-Z0-9_-]/', '', $name);
            
            if (file_exists($newFolder)) {
                throw new Exception('Folder already exists');
            }
            
            if (!mkdir($newFolder)) {
                throw new Exception('Failed to create folder');
            }
            
            $response = ['status' => 'success', 'message' => 'Folder created'];
            break;
            
        case 'delete-folder':
            $path = $request['path'] ?? '';
            $realPath = validatePath($path);
            
            if (!$realPath || !is_dir($realPath)) {
                throw new Exception('Invalid directory path');
            }
            
            // 安全措施:防止删除根目录
            if ($realPath === $BASE_DIR) {
                throw new Exception('Cannot delete base directory');
            }
            
            // 递归删除目录
            $files = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($realPath, FilesystemIterator::SKIP_DOTS),
                RecursiveIteratorIterator::CHILD_FIRST
            );
            
            foreach ($files as $file) {
                if ($file->isDir()) {
                    rmdir($file->getRealPath());
                } else {
                    unlink($file->getRealPath());
                }
            }
            
            if (!rmdir($realPath)) {
                throw new Exception('Failed to delete folder');
            }
            
            $response = ['status' => 'success', 'message' => 'Folder deleted'];
            break;
            
        case 'rename':
            $type = $request['type'] ?? ''; // 'file' or 'folder'
            $path = $request['path'] ?? '';
            $newName = $request['newName'] ?? '';
            
            if (empty($newName)) throw new Exception('New name is required');
            
            $realPath = validatePath($path);
            if (!$realPath) throw new Exception('Invalid path');
            
            $newPath = dirname($realPath) . DIRECTORY_SEPARATOR . preg_replace('/[^a-zA-Z0-9_.-]/', '', $newName);
            
            if (file_exists($newPath)) {
                throw new Exception('Target name already exists');
            }
            
            if (!rename($realPath, $newPath)) {
                throw new Exception('Rename failed');
            }
            
            $response = ['status' => 'success', 'message' => 'Renamed successfully'];
            break;
            
        case 'upload-file':
            $targetPath = $request['path'] ?? '';
            $realPath = validatePath($targetPath);
            
            if (!$realPath || !is_dir($realPath)) {
                throw new Exception('Invalid target directory');
            }
            
            if (empty($_FILES['file'])) {
                throw new Exception('No file uploaded');
            }
            
            $file = $_FILES['file'];
            $filename = preg_replace('/[^a-zA-Z0-9_.-]/', '', basename($file['name']));
            $targetFile = $realPath . DIRECTORY_SEPARATOR . $filename;
            $ext = strtolower(pathinfo($targetFile, PATHINFO_EXTENSION));
            
            // 验证文件类型
            if (!in_array($ext, $ALLOWED_EXTENSIONS)) {
                throw new Exception('File type not allowed');
            }
            
            // 防止覆盖现有文件
            if (file_exists($targetFile)) {
                $filename = time() . '_' . $filename;
                $targetFile = $realPath . DIRECTORY_SEPARATOR . $filename;
            }
            
            if (!move_uploaded_file($file['tmp_name'], $targetFile)) {
                throw new Exception('File upload failed');
            }
            
            $response = ['status' => 'success', 'filename' => $filename, 'message' => 'File uploaded'];
            break;
            
        case 'delete-file':
            $path = $request['path'] ?? '';
            $realPath = validatePath($path);
            
            if (!$realPath || !is_file($realPath)) {
                throw new Exception('Invalid file path');
            }
            
            if (!unlink($realPath)) {
                throw new Exception('File deletion failed');
            }
            
            $response = ['status' => 'success', 'message' => 'File deleted'];
            break;
            
        default:
            throw new Exception('Unknown action');
    }
    
} catch (Exception $e) {
    $response = ['status' => 'error', 'message' => $e->getMessage()];
}

echo json_encode($response);
?>