17370845950

如何在 PHP 中上传文件并重命名(避免路径错误)

本文详解 PHP 文件上传时自定义文件名的正确方法,重点解决因 MIME 类型误用导致的 `move_uploaded_file()` 路径错误问题,并提供安全、可复用的重命名与上传实践。

在 PHP 中为上传文件指定新名称是常见需求,但若处理不当(如直接将完整 MIME 类型 text/plain 作为文件名一部分),极易引发 failed to open stream: No such file or directory 错误——这并非权限或目录问题,而是文件路径非法所致。原代码中:

$file_type = $_FILES['$_FILES']['type']; // 例如:'text/plain'
$new_filename = "$userid-".$file_type.date('d-m-y').".".$file_ext;
// → 生成类似 "123-text/plain09-02-22.txt" 的文件名

该字符串含非法字符 /,操作系统无法将其识别为有效文件名,move_uploaded_file() 因此找不到目标路径而失败。

✅ 正确做法是仅提取 MIME 类型的主类型(如 text)或完全避免使用 type 字段(因其由客户端提供,不可信且格式不统一)。推荐方案如下:

✅ 安全重命名示例(修正版)

$path = 'temp/';
if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) { // 注意:键名应为 'file',非 '$_FILES'
    $file = $_FILES['file'];
    $file_name = $file['name'];
    $file_tmp = $file['tmp_name'];
    $file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));

    // ✅ 方案1:仅取 MIME 主类型(更语义化)
    $file_type = explode('/', $file['type']);
    $main_type = $file_type[0] ?? 'unknown';
    $new_filename = "{$userid}-{$main_type}-".date('d-m-y').".{$file_ext}";

    // ✅ 方案2(更推荐):彻底弃用 type,用扩展名+时间戳保证唯一性
    // $new_filename = "{$userid}-".date('YmdHis')."-".uniqid().".{$file_ext}";

    $upload_path = $path . $new_filename;

    // ? 确保上传目录存在且可写
    if (!is_dir($path)) {
        mkdir($path, 0755, true);
    }

    if (move_uploaded_file($file_tmp, $upload_path)) {
        echo "Upload success: {$new_filename}";
        http_response_code(200);
    } else {
        error_log("Failed to move uploaded file to {$upload_path}");
        echo "Upload failed";
        http_response_code(400);
    }
} else {
    echo "No file uploaded or upload error occurred.";
    http_response_code(400);
}

⚠️ 关键注意事项:

  • 表单字段名一致性:HTML 中 对应 $_FILES['file'],而非 $_FILES['$_FILES'](原代码存在严重逻辑错误);
  • 验证上传状态:始终检查 $_FILES['file']['error'] === UPLOAD_ERR_OK,而非仅依赖 isset() 或 !empty();
  • 拒绝危险扩展名:生产环境应校验 $file_ext(如白名单 ['pdf', 'jpg', 'png']),防止 Webshell 上传;
  • 禁用 MIME 类型依赖:$_FILES['type'] 可被客户端伪造,绝不用于安全判断或文件存储逻辑
  • 路径安全:$path 拼接前需 realpath() 或 basename() 过滤,防止路径遍历(如 ../../../etc/passwd)。

通过以上修正,即可稳定实现带业务标识(如用户 ID)、时间戳和合法扩展名的文件重命名上传,兼顾功能性与安全性。