本文目录导读:

我将为您设计一个PHP文件上传共享存储系统,这个系统支持多用户文件上传、浏览和下载功能。
数据库结构
-- 创建文件表
CREATE TABLE IF NOT EXISTS files (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
original_name VARCHAR(255) NOT NULL,
stored_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size INT NOT NULL,
mime_type VARCHAR(100),
category VARCHAR(50),
description TEXT,
downloads_count INT DEFAULT 0,
is_public TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_category (category),
INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户表(简化版,实际项目中通常已有)
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
上传配置文件
<?php
// config.php
define('UPLOAD_DIR', __DIR__ . '/uploads/'); // 上传目录
define('ALLOWED_EXTENSIONS', ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar']);
define('MAX_FILE_SIZE', 50 * 1024 * 1024); // 50MB
define('MAX_FILES_PER_UPLOAD', 10);
define('DATE_FORMAT', 'Y-m-d H:i:s');
// 创建上传目录(如果不存在)
if (!file_exists(UPLOAD_DIR)) {
mkdir(UPLOAD_DIR, 0755, true);
mkdir(UPLOAD_DIR . 'images/', 0755, true);
mkdir(UPLOAD_DIR . 'documents/', 0755, true);
mkdir(UPLOAD_DIR . 'archives/', 0755, true);
}
文件上传类
<?php
// FileUploader.php
class FileUploader {
private $uploadDir;
private $allowedExts;
private $maxSize;
public function __construct() {
$this->uploadDir = UPLOAD_DIR;
$this->allowedExts = ALLOWED_EXTENSIONS;
$this->maxSize = MAX_FILE_SIZE;
}
/**
* 上传单个文件
*/
public function uploadFile($file, $userId, $category = 'general', $isPublic = true) {
$result = [
'success' => false,
'message' => '',
'file' => null
];
if ($file['error'] !== UPLOAD_ERR_OK) {
$result['message'] = '上传失败,错误码: ' . $file['error'];
return $result;
}
// 检查文件大小
if ($file['size'] > $this->maxSize) {
$result['message'] = '文件大小超过限制';
return $result;
}
// 检查文件类型
$fileInfo = pathinfo($file['name']);
$extension = strtolower($fileInfo['extension']);
if (!in_array($extension, $this->allowedExts)) {
$result['message'] = '不支持的文件类型';
return $result;
}
// 生成存储文件名
$storedName = $this->generateStoredName($fileInfo['filename'], $extension);
// 根据文件类型分配到子目录
$subDir = $this->getSubDirectory($extension);
$targetPath = $this->uploadDir . $subDir;
// 确保目录存在
if (!file_exists($targetPath)) {
mkdir($targetPath, 0755, true);
}
$targetFile = $targetPath . '/' . $storedName;
// 移动文件
if (move_uploaded_file($file['tmp_name'], $targetFile)) {
// 保存文件信息到数据库
$fileData = [
'user_id' => $userId,
'original_name' => $file['name'],
'stored_name' => $storedName,
'file_path' => $subDir . '/' . $storedName,
'file_size' => $file['size'],
'mime_type' => $file['type'],
'category' => $category,
'is_public' => $isPublic ? 1 : 0
];
$fileId = $this->saveFileInfo($fileData);
if ($fileId) {
$fileData['id'] = $fileId;
$result['success'] = true;
$result['file'] = $fileData;
$result['message'] = '文件上传成功';
} else {
unlink($targetFile); // 删除已上传的文件
$result['message'] = '保存文件信息失败';
}
} else {
$result['message'] = '文件移动失败';
}
return $result;
}
/**
* 批量上传文件
*/
public function uploadMultipleFiles($files, $userId, $category = 'general') {
$results = [];
$count = count($files['name']);
for ($i = 0; $i < $count; $i++) {
$file = [
'name' => $files['name'][$i],
'type' => $files['type'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i]
];
$results[] = $this->uploadFile($file, $userId, $category);
}
return $results;
}
/**
* 生成存储文件名
*/
private function generateStoredName($originalName, $extension) {
$unique = uniqid() . '_' . substr(md5(uniqid(mt_rand(), true)), 0, 8);
return $unique . '.' . $extension;
}
/**
* 根据文件类型返回子目录
*/
private function getSubDirectory($extension) {
$imageExts = ['jpg', 'jpeg', 'png', 'gif'];
$docExts = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
$archiveExts = ['zip', 'rar'];
if (in_array($extension, $imageExts)) {
return 'images';
} elseif (in_array($extension, $docExts)) {
return 'documents';
} elseif (in_array($extension, $archiveExts)) {
return 'archives';
} else {
return 'others';
}
}
/**
* 保存文件信息到数据库
*/
private function saveFileInfo($fileData) {
$db = Database::getInstance()->getConnection();
$sql = "INSERT INTO files (user_id, original_name, stored_name, file_path, file_size, mime_type, category, is_public)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $db->prepare($sql);
$stmt->bind_param("isssissi",
$fileData['user_id'],
$fileData['original_name'],
$fileData['stored_name'],
$fileData['file_path'],
$fileData['file_size'],
$fileData['mime_type'],
$fileData['category'],
$fileData['is_public']
);
if ($stmt->execute()) {
return $db->insert_id;
}
return false;
}
}
数据库连接类
<?php
// Database.php
class Database {
private static $instance = null;
private $connection;
private $host = 'localhost';
private $username = 'root';
private $password = '';
private $database = 'file_sharing';
private function __construct() {
$this->connection = new mysqli($this->host, $this->username, $this->password, $this->database);
if ($this->connection->connect_error) {
die("连接失败: " . $this->connection->connect_error);
}
$this->connection->set_charset("utf8mb4");
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Database();
}
return self::$instance;
}
public function getConnection() {
return $this->connection;
}
}
文件管理类
<?php
// FileManager.php
class FileManager {
private $db;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
}
/**
* 获取文件列表
*/
public function getFiles($userId = null, $category = null, $search = '', $page = 1, $perPage = 20) {
$offset = ($page - 1) * $perPage;
$where = [];
$params = [];
$types = "";
if ($userId !== null) {
$where[] = "user_id = ?";
$params[] = $userId;
$types .= "i";
}
if ($category && $category !== 'all') {
$where[] = "category = ?";
$params[] = $category;
$types .= "s";
}
if (!empty($search)) {
$where[] = "original_name LIKE ?";
$params[] = "%$search%";
$types .= "s";
}
// 只显示公开文件或自己的文件
$where[] = "(is_public = 1 OR user_id = ?)";
$params[] = $_SESSION['user_id'] ?? 0;
$types .= "i";
$whereSQL = $where ? "WHERE " . implode(" AND ", $where) : "";
$sql = "SELECT * FROM files $whereSQL ORDER BY created_at DESC LIMIT ? OFFSET ?";
$params[] = $perPage;
$params[] = $offset;
$types .= "ii";
$stmt = $this->db->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
$files = $result->fetch_all(MYSQLI_ASSOC);
// 获取总数
$countSQL = "SELECT COUNT(*) as total FROM files $whereSQL";
$stmt = $this->db->prepare($countSQL);
$countParams = array_slice($params, 0, count($params) - 2);
$countTypes = substr($types, 0, -2);
if ($countTypes) {
$stmt->bind_param($countTypes, ...$countParams);
}
$stmt->execute();
$countResult = $stmt->get_result()->fetch_assoc();
return [
'files' => $files,
'total' => $countResult['total'],
'page' => $page,
'perPage' => $perPage,
'totalPages' => ceil($countResult['total'] / $perPage)
];
}
/**
* 获取单个文件信息
*/
public function getFileById($fileId) {
$sql = "SELECT * FROM files WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->bind_param("i", $fileId);
$stmt->execute();
return $stmt->get_result()->fetch_assoc();
}
/**
* 下载文件
*/
public function downloadFile($fileId) {
$file = $this->getFileById($fileId);
if (!$file) {
return ['success' => false, 'message' => '文件不存在'];
}
$filePath = UPLOAD_DIR . $file['file_path'];
if (!file_exists($filePath)) {
return ['success' => false, 'message' => '文件物理文件不存在'];
}
// 更新下载次数
$this->incrementDownloadCount($fileId);
// 配置下载响应
header('Content-Description: File Transfer');
header('Content-Type: ' . $file['mime_type']);
header('Content-Disposition: attachment; filename="' . $file['original_name'] . '"');
header('Content-Length: ' . filesize($filePath));
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Expires: 0');
readfile($filePath);
return ['success' => true];
}
/**
* 删除文件
*/
public function deleteFile($fileId, $userId) {
$file = $this->getFileById($fileId);
if (!$file) {
return ['success' => false, 'message' => '文件不存在'];
}
// 检查权限(只有文件所有者可以删除)
if ($file['user_id'] != $userId) {
return ['success' => false, 'message' => '没有删除权限'];
}
// 删除物理文件
$filePath = UPLOAD_DIR . $file['file_path'];
if (file_exists($filePath)) {
unlink($filePath);
}
// 删除数据库记录
$sql = "DELETE FROM files WHERE id = ? AND user_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->bind_param("ii", $fileId, $userId);
if ($stmt->execute()) {
return ['success' => true, 'message' => '文件删除成功'];
}
return ['success' => false, 'message' => '删除失败'];
}
/**
* 更新文件信息
*/
public function updateFileInfo($fileId, $userId, $data) {
$allowedFields = ['category', 'description', 'is_public'];
$updates = [];
$params = [];
$types = "";
foreach ($data as $key => $value) {
if (in_array($key, $allowedFields)) {
$updates[] = "$key = ?";
$params[] = $value;
$types .= "s";
}
}
if (empty($updates)) {
return ['success' => false, 'message' => '没有要更新的字段'];
}
$params[] = $fileId;
$params[] = $userId;
$types .= "ii";
$sql = "UPDATE files SET " . implode(", ", $updates) . " WHERE id = ? AND user_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->bind_param($types, ...$params);
if ($stmt->execute()) {
return ['success' => true, 'message' => '更新成功'];
}
return ['success' => false, 'message' => '更新失败'];
}
/**
* 增加下载次数
*/
private function incrementDownloadCount($fileId) {
$sql = "UPDATE files SET downloads_count = downloads_count + 1 WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->bind_param("i", $fileId);
$stmt->execute();
}
/**
* 获取文件分类统计
*/
public function getCategoryStats() {
$sql = "SELECT category, COUNT(*) as count FROM files GROUP BY category";
$result = $this->db->query($sql);
return $result->fetch_all(MYSQLI_ASSOC);
}
}
上传页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">文件上传中心</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.upload-form {
background: #f9f9f9;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.btn {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn:hover {
background-color: #45a049;
}
.file-list {
margin-top: 20px;
}
.file-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
margin-top: 20px;
}
.file-card {
background-color: white;
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
}
.file-card h4 {
margin: 0 0 10px 0;
color: #333;
}
.file-card .meta {
color: #666;
font-size: 12px;
}
.file-card .actions {
margin-top: 10px;
}
.file-card .actions a {
margin-right: 10px;
text-decoration: none;
color: #2196F3;
}
.file-card .actions a:hover {
text-decoration: underline;
}
.success {
background-color: #d4edda;
color: #155724;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.error {
background-color: #f8d7da;
color: #721c24;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>文件上传共享系统</h1>
<?php if (!isset($_SESSION['user_id'])): ?>
<div class="error">请先登录</div>
<?php else: ?>
<div class="upload-form">
<h2>上传文件</h2>
<form action="upload_handler.php" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="files">选择文件(可多选)</label>
<input type="file" id="files" name="files[]" multiple required>
<input type="hidden" name="MAX_FILE_SIZE" value="<?php echo MAX_FILE_SIZE; ?>">
</div>
<div class="form-group">
<label for="category">文件分类</label>
<select id="category" name="category">
<option value="general">综合</option>
<option value="images">图片</option>
<option value="documents">文档</option>
<option value="archives">压缩包</option>
<option value="videos">视频</option>
<option value="audio">音频</option>
<option value="other">其他</option>
</select>
</div>
<div class="form-group">
<label for="description">文件描述</label>
<textarea id="description" name="description" rows="3"></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_public" value="1" checked>
公开共享
</label>
</div>
<button type="submit" class="btn">上传文件</button>
</form>
</div>
<div class="file-list">
<h2>文件列表</h2>
<div class="file-grid">
<?php
$fileManager = new FileManager();
$files = $fileManager->getFiles(null, null, '', 1, 100);
foreach ($files['files'] as $file):
?>
<div class="file-card">
<h4><?php echo htmlspecialchars($file['original_name']); ?></h4>
<div class="meta">
<p>大小: <?php echo formatFileSize($file['file_size']); ?></p>
<p>类型: <?php echo htmlspecialchars($file['mime_type']); ?></p>
<p>分类: <?php echo htmlspecialchars($file['category']); ?></p>
<p>上传时间: <?php echo date('Y-m-d H:i', strtotime($file['created_at'])); ?></p>
<p>下载次数: <?php echo $file['downloads_count']; ?></p>
</div>
<div class="actions">
<a href="download.php?id=<?php echo $file['id']; ?>">下载</a>
<?php if ($_SESSION['user_id'] == $file['user_id']): ?>
<a href="delete.php?id=<?php echo $file['id']; ?>" onclick="return confirm('确定要删除这个文件吗?')">删除</a>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</body>
</html>
上传处理脚本
<?php
// upload_handler.php
session_start();
require_once 'config.php';
require_once 'Database.php';
require_once 'FileUploader.php';
// 检查用户是否登录
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
// 验证上传请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['files'])) {
$uploader = new FileUploader();
$category = $_POST['category'] ?? 'general';
$isPublic = isset($_POST['is_public']) ? true : false;
$results = $uploader->uploadMultipleFiles(
$_FILES['files'],
$_SESSION['user_id'],
$category,
$isPublic
);
// 处理结果
$successCount = 0;
$errorMessages = [];
foreach ($results as $result) {
if ($result['success']) {
$successCount++;
} else {
$errorMessages[] = $result['message'];
}
}
// 保存上传结果到会话
$_SESSION['upload_messages'] = [
'success_count' => $successCount,
'errors' => $errorMessages
];
header('Location: index.php');
exit;
}
下载处理脚本
<?php
// download.php
session_start();
require_once 'config.php';
require_once 'Database.php';
require_once 'FileManager.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$fileId = $_GET['id'] ?? null;
if ($fileId) {
$fileManager = new FileManager();
$result = $fileManager->downloadFile($fileId);
// 如果返回了结果,说明文件不存在
if (!$result['success']) {
$_SESSION['error_message'] = $result['message'];
header('Location: index.php');
exit;
}
} else {
header('Location: index.php');
exit;
}
辅助函数
<?php
// helpers.php
/**
* 格式化文件大小
*/
function formatFileSize($size) {
if ($size >= 1073741824) {
return round($size / 1073741824, 2) . ' GB';
} elseif ($size >= 1048576) {
return round($size / 1048576, 2) . ' MB';
} elseif ($size >= 1024) {
return round($size / 1024, 2) . ' KB';
} else {
return $size . ' bytes';
}
}
/**
* 检查文件路径安全性
*/
function isSafePath($path) {
$realPath = realpath(UPLOAD_DIR . $path);
if (!$realPath) {
return false;
}
$uploadDir = realpath(UPLOAD_DIR);
return strpos($realPath, $uploadDir) === 0;
}
/**
* 生成安全的文件名
*/
function generateSafeFileName($name) {
// 移除危险字符
$name = preg_replace('/[^\w\.\-]/', '', $name);
// 限制长度
return substr($name, 0, 255);
}
页面提示功能
<?php
// 在index.php的合适位置添加
if (isset($_SESSION['upload_messages'])) {
$messages = $_SESSION['upload_messages'];
if ($messages['success_count'] > 0) {
echo '<div class="success">';
echo $messages['success_count'] . ' 个文件上传成功';
echo '</div>';
}
foreach ($messages['errors'] as $error) {
echo '<div class="error">' . htmlspecialchars($error) . '</div>';
}
unset($_SESSION['upload_messages']);
}
if (isset($_SESSION['error_message'])) {
echo '<div class="error">' . htmlspecialchars($_SESSION['error_message']) . '</div>';
unset($_SESSION['error_message']);
}
安全注意事项
-
文件类型验证:始终验证文件的实际MIME类型,而不仅仅是文件扩展名。
-
文件名安全:不要直接使用用户提供的文件名,生成唯一的新文件名。
-
路径安全:确保文件路径不包含../等路径穿越字符。
-
访问控制:确保只有文件所有者或公开文件可以被访问。
-
文件大小限制:在服务器端PHP配置和应用程序层面都设置上传大小限制。
-
使用HTTPS:在生产环境中使用HTTPS保护文件传输。
这个系统提供了一个完整的文件上传共享解决方案,支持多文件上传、分类管理、搜索、下载统计等功能,您可以根据实际需求进行扩展和修改。