如何用PHP+MySQL同时上传视频图片到服务器并实现下载去重
问题根源
代码存在4个核心错误,导致无法同时上传两个文件:
- 前后端字段名不匹配:前端表单文件控件name是
videofile、picturefile,后端$_FILES取的键是vfile、pfile,根本拿不到上传的文件信息 move_uploaded_file()用法完全错误:该函数第一个参数必须是PHP生成的上传临时文件路径($_FILES['xxx']['tmp_name']),第二个参数是服务器存储的目标路径,你写的参数顺序颠倒、漏写变量$符号、没取临时路径,自然无法正确保存文件- 数据库语法错误:建表语句缺字段名、表名前后不一致(建表用
userstable,插入数据写的usertable)、语句没闭合,根本无法正常写入数据 - 缺少必要校验逻辑:没有文件类型/大小校验、没有重复文件判定、存在SQL注入风险,也没实现下载功能
完整可运行实现方案
第一步:修正数据库建表语句
提前在phpMyAdmin中创建对应数据库(比如命名为upload_demo),执行以下SQL建表,新增哈希字段用于重复文件判定:
CREATE TABLE `userstable`( `id` INT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY, `file_hash` CHAR(32) NOT NULL COMMENT '文件组合哈希,用于判重', `videofile` VARCHAR(200) NOT NULL COMMENT '视频存储文件名', `picturefile` VARCHAR(200) NOT NULL COMMENT '图片存储文件名', `upload_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `idx_file_hash` (`file_hash`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
第二步:数据库连接文件(connect.php)
XAMPP默认MySQL账号为root,密码为空,按自己实际环境调整配置:
<?php $db_host = 'localhost'; $db_user = 'root'; $db_pwd = ''; $db_name = 'upload_demo'; $conn = mysqli_connect($db_host, $db_user, $db_pwd, $db_name); if (!$conn) { die("数据库连接失败:" . mysqli_connect_error()); } mysqli_set_charset($conn, 'utf8mb4'); ?>
第三步:上传表单页面(index.html)
增加文件类型限制、必填校验,嵌入文件列表展示模块:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>视频图片上传</title> </head> <body> <h3>文件上传</h3> <form action="post.php" method="POST" enctype="multipart/form-data"> <p>选择视频文件:<input type="file" name="videofile" accept="video/*" required></p> <p>选择图片文件:<input type="file" name="picturefile" accept="image/*" required></p> <p><input type="submit" name="submit" value="开始上传"></p> </form> <h3>已上传文件列表</h3> <?php include 'file_list.php'; ?> </body> </html>
第四步:上传处理逻辑(post.php)
实现文件校验、重复判定、双文件存储、数据库写入全流程逻辑:
<?php include "connect.php"; if(isset($_POST['submit'])){ // 提前在同级目录创建uploads文件夹,设置读写权限 $upload_dir = __DIR__ . "/uploads/"; // 允许的文件后缀 $allow_video_ext = ['mp4', 'avi', 'mov', 'wmv']; $allow_img_ext = ['jpg', 'jpeg', 'png', 'gif', 'webp']; // 单文件大小限制:视频最大100M,图片最大10M $max_video_size = 100 * 1024 * 1024; $max_img_size = 10 * 1024 * 1024; // 校验文件上传状态 if ( !isset($_FILES['videofile']['error']) || $_FILES['videofile']['error'] != UPLOAD_ERR_OK || !isset($_FILES['picturefile']['error']) || $_FILES['picturefile']['error'] != UPLOAD_ERR_OK ) { die("文件上传出错,请检查两个文件是否都选择、大小是否超出限制"); } // 校验视频格式、大小 $video_tmp = $_FILES['videofile']['tmp_name']; $video_name = $_FILES['videofile']['name']; $video_size = $_FILES['videofile']['size']; $video_ext = strtolower(pathinfo($video_name, PATHINFO_EXTENSION)); if (!in_array($video_ext, $allow_video_ext)) die("仅支持上传mp4/avi/mov/wmv格式视频"); if ($video_size > $max_video_size) die("视频大小不能超过100M"); // 校验图片格式、大小 $img_tmp = $_FILES['picturefile']['tmp_name']; $img_name = $_FILES['picturefile']['name']; $img_size = $_FILES['picturefile']['size']; $img_ext = strtolower(pathinfo($img_name, PATHINFO_EXTENSION)); if (!in_array($img_ext, $allow_img_ext)) die("仅支持上传jpg/jpeg/png/gif/webp格式图片"); if ($img_size > $max_img_size) die("图片大小不能超过10M"); // 计算文件内容哈希,相同内容的文件就算改了文件名也会被判定为重复 $video_hash = md5_file($video_tmp); $img_hash = md5_file($img_tmp); $total_hash = md5($video_hash . $img_hash); $check_dup = mysqli_query($conn, "SELECT id FROM userstable WHERE file_hash = '{$total_hash}' LIMIT 1"); if (mysqli_num_rows($check_dup) > 0) { die("请勿重复提交相同的文件"); } // 重命名文件避免同名覆盖 $video_save_name = time() . '_' . uniqid() . '.' . $video_ext; $img_save_name = time() . '_' . uniqid() . '.' . $img_ext; $video_target = $upload_dir . $video_save_name; $img_target = $upload_dir . $img_save_name; // 分别移动两个文件到存储目录 $move_video = move_uploaded_file($video_tmp, $video_target); $move_img = move_uploaded_file($img_tmp, $img_target); if (!$move_video || !$move_img) { // 移动失败时清理已上传的残留文件 if (file_exists($video_target)) unlink($video_target); if (file_exists($img_target)) unlink($img_target); die("文件保存到服务器失败,请检查uploads目录权限"); } // 用预处理语句写入数据库,避免SQL注入 $stmt = mysqli_prepare($conn, "INSERT INTO userstable (file_hash, videofile, picturefile) VALUES (?, ?, ?)"); mysqli_stmt_bind_param($stmt, "sss", $total_hash, $video_save_name, $img_save_name); $insert_res = mysqli_stmt_execute($stmt); if (!$insert_res) { // 入库失败时清理已上传文件 unlink($video_target); unlink($img_target); die("数据写入失败:" . mysqli_error($conn)); } echo "文件上传成功!<a href='index.html'>返回上传页</a>"; } ?>
第五步:文件列表与下载功能
文件列表模块(file_list.php)
展示所有上传记录,提供下载入口:
<?php include "connect.php"; $res = mysqli_query($conn, "SELECT id, videofile, picturefile, upload_time FROM userstable ORDER BY id DESC"); if (mysqli_num_rows($res) == 0) { echo "<p>暂无上传记录</p>"; } else { echo "<ul>"; while ($row = mysqli_fetch_assoc($res)) { echo "<li>"; echo "上传时间:" . $row['upload_time'] . " | "; echo "<a href='download.php?id=".$row['id']."&type=video'>下载视频</a> | "; echo "<a href='download.php?id=".$row['id']."&type=img'>下载图片</a>"; echo "</li>"; } echo "</ul>"; } ?>
下载处理模块(download.php)
实现文件流式下载到本地:
<?php include "connect.php"; $upload_dir = __DIR__ . "/uploads/"; // 校验请求参数 $id = intval($_GET['id'] ?? 0); $type = $_GET['type'] ?? ''; if ($id <=0 || !in_array($type, ['video', 'img'])) { die("下载参数错误"); } // 查询对应文件记录 $res = mysqli_query($conn, "SELECT videofile, picturefile FROM userstable WHERE id = {$id} LIMIT 1"); if (mysqli_num_rows($res) == 0) { die("文件不存在"); } $file_info = mysqli_fetch_assoc($res); $filename = $type == 'video' ? $file_info['videofile'] : $file_info['picturefile']; $file_path = $upload_dir . $filename; if (!file_exists($file_path)) { die("服务器上的文件已丢失"); } // 输出下载响应头 header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.basename($file_path).'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($file_path)); readfile($file_path); exit; ?>
部署注意事项
- 所有代码放到XAMPP安装目录下
htdocs的upload子文件夹中,启动Apache和MySQL后,访问http://localhost/upload即可使用 - 必须手动在代码同级目录创建
uploads文件夹,Windows环境下XAMPP默认无需额外设置权限,Linux/macOS环境需给该文件夹设置755读写权限 - 若需要上传更大的文件,除了修改代码里的大小限制值,还要同步修改php.ini中的三个配置:upload_max_filesize、post_max_size、max_execution_time,修改完成后重启Apache服务生效
- 重复文件判定基于文件内容哈希实现,即使用户修改文件名,只要上传的视频、图片内容和历史提交完全一致,就会被拦截,不会重复存储占用服务器空间
内容的提问来源于stack exchange,提问作者Jeff
相关产品推荐
相关产品推荐

