phpwork/files.php
2025-03-27 09:57:30 +08:00

189 lines
6.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// upload.php
session_start();
// 配置参数
$config = [
'max_file_size' => 50 * 1024 * 1024, // 50MB
'allowed_types' => [
'image/jpeg',
'image/png',
'application/pdf',
'text/plain'
],
'upload_dir' => __DIR__ . '/uploads/',
'csrf_token' => 'secure_token_here' // 生产环境应使用更安全的生成方式
];
// 处理文件上传
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// CSRF 验证
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $config['csrf_token']) {
$error = '无效的请求令牌';
} else {
try {
// 验证上传文件
$uploadedFile = $_FILES['file'] ?? null;
if (!$uploadedFile || $uploadedFile['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException(handleUploadError($uploadedFile['error']));
}
// 验证文件大小
if ($uploadedFile['size'] > $config['max_file_size']) {
throw new RuntimeException('文件大小超过50MB限制');
}
// 验证文件类型
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($uploadedFile['tmp_name']);
if (!in_array($mimeType, $config['allowed_types'])) {
throw new RuntimeException('不支持的文件类型');
}
// 创建上传目录
if (!is_dir($config['upload_dir']) && !mkdir($config['upload_dir'], 0755, true)) {
throw new RuntimeException('无法创建上传目录');
}
// 生成安全文件名
$extension = pathinfo($uploadedFile['name'], PATHINFO_EXTENSION);
$safeName = sprintf('%s.%s', bin2hex(random_bytes(8)), $extension);
$targetPath = $config['upload_dir'] . $safeName;
// 移动文件
if (!move_uploaded_file($uploadedFile['tmp_name'], $targetPath)) {
throw new RuntimeException('文件保存失败');
}
$success = sprintf('文件上传成功!文件名:%s', htmlspecialchars($safeName));
} catch (RuntimeException $e) {
$error = $e->getMessage();
}
}
}
function handleUploadError($code) {
switch ($code) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
return '文件大小超过服务器限制';
case UPLOAD_ERR_PARTIAL:
return '文件只有部分被上传';
case UPLOAD_ERR_NO_FILE:
return '没有文件被上传';
case UPLOAD_ERR_NO_TMP_DIR:
return '缺少临时文件夹';
case UPLOAD_ERR_CANT_WRITE:
return '写入磁盘失败';
default:
return '未知上传错误';
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>安全文件上传系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.upload-container {
max-width: 800px;
margin: 2rem auto;
padding: 2rem;
background: #f8f9fa;
border-radius: 10px;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
}
.preview-box {
border: 2px dashed #6c757d;
border-radius: 8px;
padding: 1.5rem;
text-align: center;
margin: 1rem 0;
transition: all 0.3s ease;
}
.preview-box:hover {
border-color: #0d6efd;
background: rgba(13, 110, 253, 0.05);
}
#file-input {
opacity: 0;
position: absolute;
z-index: -1;
}
</style>
</head>
<body>
<div class="container">
<div class="upload-container">
<h2 class="mb-4 text-center">安全文件上传系统</h2>
<?php if ($error): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?= $success ?></div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= $config['csrf_token'] ?>">
<div class="preview-box">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-cloud-upload mb-3" viewBox="0 0 16 16">
<path d="M4.406 1.342A5.53 5.53 0 0 1 8 0c2.69 0 4.923 2 5.166 4.579C14.758 4.804 16 6.137 16 7.773 16 9.569 14.502 11 12.687 11H10a.5.5 0 0 1 0-1h2.688C13.979 10 15 8.988 15 7.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 2.825 10.328 1 8 1a4.53 4.53 0 0 0-2.941 1.1c-.757.652-1.153 1.438-1.153 2.055v.448l-.445.049C2.064 4.805 1 5.952 1 7.318 1 8.785 2.23 10 3.781 10H6a.5.5 0 0 1 0 1H3.781C1.708 11 0 9.366 0 7.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383z"/>
<path d="M7.646 4.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 5.707V14.5a.5.5 0 0 1-1 0V5.707L5.354 7.854a.5.5 0 1 1-.708-.708l3-3z"/>
</svg>
<h5>选择要上传的文件</h5>
<p class="text-muted">支持格式JPEG, PNG, PDF, TXT</p>
<label for="file-input" class="btn btn-primary mt-2">
<i class="bi bi-folder2-open"></i> 浏览文件
</label>
<div id="file-name" class="mt-2"></div>
</div>
<input
type="file"
name="file"
id="file-input"
class="form-control"
required
accept=".jpg,.jpeg,.png,.pdf,.txt"
>
<div class="d-grid gap-2 mt-4">
<button type="submit" class="btn btn-success btn-lg">
<i class="bi bi-upload"></i> 开始上传
</button>
</div>
</form>
</div>
</div>
<script>
// 文件选择交互
document.getElementById('file-input').addEventListener('change', function(e) {
const fileName = e.target.files[0]?.name || '未选择文件';
document.getElementById('file-name').textContent = `已选择文件:${fileName}`;
});
// 前端大小验证
document.querySelector('form').addEventListener('submit', function(e) {
const fileInput = document.getElementById('file-input');
if (fileInput.files[0]?.size > <?= $config['max_file_size'] ?>) {
alert('文件大小超过50MB限制');
e.preventDefault();
}
});
</script>
</body>
</html>