使用PHP與Apache實(shí)現(xiàn)服務(wù)器端文件管理的完整方案
引言
作為前端開發(fā)者,你可能經(jīng)常需要與服務(wù)器文件系統(tǒng)交互。本文將詳細(xì)介紹如何通過(guò)PHP配合Apache實(shí)現(xiàn)服務(wù)器端文件管理功能。即使你沒(méi)有任何PHP經(jīng)驗(yàn),也能按照本教程實(shí)現(xiàn)完整解決方案!
系統(tǒng)準(zhǔn)備
PHP下載與安裝
訪問(wèn)PHP官網(wǎng)下載頁(yè)面
選擇與Apache匹配的版本:
- Apache版本:2.4.x
- PHP版本:8.4.x TS (Thread Safe)(對(duì)應(yīng)64位Apache的文件名應(yīng)該類似:VS17 x64 Thread Safe,對(duì)應(yīng)32位Apache的文件名應(yīng)該類似:VS17 x86 Thread Safe)
- 架構(gòu):x64(64位系統(tǒng))或x86(32位系統(tǒng))
解壓到目錄(如D:\php),目錄結(jié)構(gòu)應(yīng)包含:
php.exe php8apache2_4.dll php.ini-development ext/ (擴(kuò)展目錄)
Apache配置PHP
編輯conf/httpd.conf文件:
# 加載PHP模塊
LoadModule php_module "D:/php/php8apache2_4.dll"
# 指定php.ini目錄
PHPIniDir "D:/php"
# 將.php文件交給PHP處理
AddHandler application/x-httpd-php .php
# 關(guān)聯(lián)文件擴(kuò)展名
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>驗(yàn)證配置
cd D:\Apache24\bin httpd -t
看到"Syntax OK"表示配置正確,重啟Apache服務(wù):
httpd -k restart
文件管理API實(shí)現(xiàn)
創(chuàng)建file_manager.php文件:
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *'); // 測(cè)試用,生產(chǎn)環(huán)境應(yīng)移除
// 安全配置
$BASE_DIR = realpath(__DIR__ . '/Resources');
$ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt', 'docx'];
// 路徑驗(yàn)證函數(shù)
function validatePath($path) {
global $BASE_DIR;
$realPath = realpath($BASE_DIR . '/' . $path);
return ($realPath && strpos($realPath, $BASE_DIR) === 0)
? $realPath : false;
}
// 獲取目錄結(jié)構(gòu)
function listDirectory($path) {
$realPath = validatePath($path);
if (!$realPath || !is_dir($realPath)) {
return ['error' => '無(wú)效目錄路徑'];
}
$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;
}
// 主請(qǐng)求處理
$response = ['status' => 'error', 'message' => '無(wú)效請(qǐng)求'];
$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('無(wú)效路徑');
// 清理文件夾名稱
$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('創(chuàng)建文件夾失敗');
}
$response = ['status' => 'success', 'message' => '文件夾創(chuàng)建成功'];
break;
case 'delete-folder':
$path = $request['path'] ?? '';
$realPath = validatePath($path);
if (!$realPath || !is_dir($realPath)) {
throw new Exception('無(wú)效目錄路徑');
}
// 安全措施:防止刪除根目錄
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('無(wú)效路徑');
// 清理新名稱
$cleanName = preg_replace('/[^a-zA-Z0-9_.-]/', '', $newName);
$newPath = dirname($realPath) . DIRECTORY_SEPARATOR . $cleanName;
if (file_exists($newPath)) {
throw new Exception('目標(biāo)名稱已存在');
}
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('無(wú)效目標(biāo)目錄');
}
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));
// 文件類型驗(yàn)證
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('無(wú)效文件路徑');
}
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);
?>測(cè)試頁(yè)面
創(chuàng)建file_manager_test.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件管理器測(cè)試</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>服務(wù)器文件管理器測(cè)試</h1>
<p class="subtitle">通過(guò)PHP與Apache實(shí)現(xiàn)安全的服務(wù)器端文件操作</p>
</header>
<!-- 列出目錄內(nèi)容 -->
<div class="section">
<h2><i class="fas fa-folder-open"></i> 列出目錄內(nè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">目錄內(nèi)容將顯示在這里...</pre>
</div>
</div>
<!-- 創(chuàng)建文件夾 -->
<div class="section">
<h2><i class="fas fa-folder-plus"></i> 創(chuàng)建文件夾</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()">創(chuàng)建文件夾</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> 重命名項(xiàng)目</h2>
<div class="form-group">
<label for="renamePath">當(dāng)前路徑:</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">目標(biāo)目錄:</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 圖標(biāo) -->
<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';
// 顯示響應(yīng)消息
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);
}
// 調(diào)用API
async function callApi(data, files) {
const formData = new FormData();
// 添加表單數(shù)據(jù)
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: '網(wǎng)絡(luò)錯(cuò)誤: ' + error.message
};
}
}
// 列出目錄內(nèi)容
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 = '錯(cuò)誤: ' + response.message;
}
}
// 創(chuàng)建文件夾
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
: '? 錯(cuò)誤: ' + 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(`確定要?jiǎng)h除文件夾 "${path}" 及其所有內(nèi)容嗎?`)) {
return;
}
const response = await callApi({
action: 'delete-folder',
path: path
});
showResponse(
responseEl,
response.status === 'success'
? '? ' + response.message
: '? 錯(cuò)誤: ' + response.message,
response.status === 'success'
);
}
// 重命名項(xiàng)目
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
: '? 錯(cuò)誤: ' + 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, '請(qǐng)選擇要上傳的文件', false);
return;
}
const response = await callApi({
action: 'upload-file',
path: path
}, [fileInput.files[0]]);
showResponse(
responseEl,
response.status === 'success'
? `? ${response.message} - 文件名: ${response.filename}`
: '? 錯(cuò)誤: ' + 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(`確定要?jiǎng)h除文件 "${path}" 嗎?`)) {
return;
}
const response = await callApi({
action: 'delete-file',
path: path
});
showResponse(
responseEl,
response.status === 'success'
? '? ' + response.message
: '? 錯(cuò)誤: ' + response.message,
response.status === 'success'
);
}
</script>
</body>
</html>關(guān)鍵注意事項(xiàng)
安全配置
- 生產(chǎn)環(huán)境中移除
Access-Control-Allow-Origin: * - 添加身份驗(yàn)證(如Basic Auth或API密鑰)
- 限制允許的文件擴(kuò)展名
- 設(shè)置文件上傳大小限制(在php.ini中配置)
權(quán)限設(shè)置
- Windows:為
Resources目錄添加Apache用戶(如SYSTEM)的完全控制權(quán)限

- Linux:
chown -R www-data:www-data Resources && chmod -R 755 Resources
路徑說(shuō)明
- 列出Resources根目錄:輸入框留空
- 列出子目錄:直接輸入子目錄名(如
uploads) - 不要使用前導(dǎo)斜杠(如
/uploads)
測(cè)試流程
創(chuàng)建Resources目錄并設(shè)置權(quán)限
上傳file_manager.php和file_manager_test.html到Apache文檔根目錄
訪問(wèn)http://localhost/file_manager_test.html
測(cè)試各項(xiàng)功能:
- 列出目錄(留空)
- 創(chuàng)建測(cè)試文件夾
- 上傳文件
- 重命名/刪除操作
補(bǔ)充:
由于前面的php提供的目錄樹形式過(guò)于扁平,這里提供一個(gè)返回樹形結(jié)構(gòu)的目錄的json的php文件。如果要基于這個(gè)php文件進(jìn)行測(cè)試,在測(cè)試文件file-manager-test.html里面 action:'list'的地方改成action:'get-tree'就可以了。
改進(jìn)后的file_manager.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');
// 配置安全選項(xiàng)
$BASE_DIR = realpath(__DIR__ . '/Resources');
$ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'glb', 'gltf', 'fbx', 'obj', 'txt', 'md'];
// 遞歸獲取目錄結(jié)構(gòu)
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;
}
// 處理請(qǐng)求
$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));
// 驗(yàn)證文件類型
if (!in_array($ext, $ALLOWED_EXTENSIONS)) {
throw new Exception('File type not allowed');
}
// 防止覆蓋現(xiàn)有文件
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);
?>以上就是使用PHP與Apache實(shí)現(xiàn)服務(wù)器端文件管理的完整方案的詳細(xì)內(nèi)容,更多關(guān)于PHP Apache服務(wù)器端文件管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PHP中全局變量global和$GLOBALS[]的區(qū)別分析
$GLOBALS['var']是外部的全局變量本身,global $var是外部$var的同名引用或者指針2012-08-08
PHP的array_diff()函數(shù)在處理大數(shù)組時(shí)的效率問(wèn)題
PHP 5.2.6 以上版本的 array_diff() 函數(shù)在處理大數(shù)組時(shí),需要花費(fèi)超長(zhǎng)時(shí)間,這個(gè) bug 已經(jīng)被官方確認(rèn);在這個(gè)問(wèn)題被修復(fù)之前或者在我們不能控制 PHP 版本的時(shí)候,可以使用本文提供的方法2011-11-11
PHP結(jié)合JQueryJcrop實(shí)現(xiàn)圖片裁切實(shí)例詳解
這篇文章主要介紹了PHP結(jié)合JQueryJcrop實(shí)現(xiàn)圖片裁切實(shí)例,非常實(shí)用的一個(gè)功能,需要的朋友可以參考下2014-07-07
深入講解PHP Session及如何保持其不過(guò)期的方法
這篇文章主要介紹了深入講解PHP Session及如何保持其不過(guò)期的方法,包括對(duì)Session回收機(jī)制的講解以及SessionId保持不變的問(wèn)題的解決,需要的朋友可以參考下2015-08-08
php驗(yàn)證碼的制作思路和實(shí)現(xiàn)方法
這篇文章主要介紹了php驗(yàn)證碼的制作思路和實(shí)現(xiàn)方法,我們不能盲目的去實(shí)現(xiàn)php生成驗(yàn)證碼,更應(yīng)該了解php驗(yàn)證碼的基本原理,真正的掌握php驗(yàn)證碼的實(shí)現(xiàn)方法,需要的朋友可以參考下2015-11-11
深入理解PHP中的static和yield關(guān)鍵字
這篇文章主要給大家介紹了關(guān)于PHP中static和yield關(guān)鍵字的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用php具有一定的參考學(xué)習(xí)價(jià)值,文章需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
在PHP中使用curl_init函數(shù)的說(shuō)明
在這篇文章中主要講解php_curl庫(kù)的知識(shí),并教你如何更好的使用php_curl。2010-11-11
PHP獲取表單數(shù)據(jù)與HTML嵌入PHP腳本的實(shí)現(xiàn)

