文件上传

This commit is contained in:
usami 2025-03-27 09:57:30 +08:00
parent 80ef98ed7e
commit 96fe9915d9

188
files.php Normal file
View File

@ -0,0 +1,188 @@
<?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>