You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 18:36:24