PHP7中使用cURL上传文件的完整指南

一、基础文件上传实现

1.1 单文件上传实现

<?php
// 目标上传URL
$uploadUrl = 'https://example.com/upload.php';

// 准备上传文件(必须使用绝对路径)
$filePath = realpath('test_file.txt');

// 创建CURLFile对象
$postData = [
    'userfile' => new CURLFile($filePath, 'text/plain', 'custom_filename.txt'),
    'additional_field' => 'extra_data'
];

// 初始化cURL会话
$ch = curl_init();

// 设置cURL选项
curl_setopt_array($ch, [
    CURLOPT_URL => $uploadUrl,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $postData,
    CURLOPT_HTTPHEADER => [
        'User-Agent: PHP cURL File Upload'
    ]
]);

// 执行请求并获取响应
$response = curl_exec($ch);

// 检查错误
if (curl_errno($ch)) {
    throw new RuntimeException('cURL Error: ' . curl_error($ch));
}

// 关闭会话
curl_close($ch);

// 处理响应
echo $response;

关键点说明

  1. CURLFile类替代了旧的@语法(PHP5.5+)
  2. realpath()确保使用绝对路径
  3. 可自定义MIME类型和上传后的文件名

二、高级上传功能

2.1 多文件同时上传

$postData = [
    'avatar' => new CURLFile(realpath('user.jpg'), 'image/jpeg'),
    'documents' => [
        new CURLFile(realpath('doc1.pdf'), 'application/pdf'),
        new CURLFile(realpath('doc2.pdf'), 'application/pdf')
    ],
    'user_id' => 12345
];

2.2 上传进度监控

// 进度回调函数
function progressCallback($resource, $downloadSize, $downloaded, $uploadSize, $uploaded) {
    if ($uploadSize > 0) {
        $progress = round(($uploaded / $uploadSize) * 100);
        echo "上传进度: {$progress}%\n";
    }
}

// 设置进度回调
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, 'progressCallback');

三、服务器端接收处理

3.1 基础接收脚本

<?php
header('Content-Type: application/json');

$response = [
    'status' => 'success',
    'files' => $_FILES,
    'post_data' => $_POST
];

// 文件保存处理
foreach ($_FILES as $field => $file) {
    if ($file['error'] === UPLOAD_ERR_OK) {
        $targetPath = 'uploads/' . basename($file['name']);
        move_uploaded_file($file['tmp_name'], $targetPath);
    }
}

echo json_encode($response, JSON_PRETTY_PRINT);

3.2 安全增强版

<?php
header('Content-Type: application/json');

function sanitizeFilename($filename) {
    return preg_replace('/[^a-zA-Z0-9\-\._]/', '', $filename);
}

$response = ['status' => 'error'];
$allowedTypes = ['image/jpeg', 'application/pdf'];

try {
    if (empty($_FILES)) {
        throw new RuntimeException('没有文件被上传');
    }
    
    foreach ($_FILES as $file) {
        if (!in_array($file['type'], $allowedTypes)) {
            throw new RuntimeException('不允许的文件类型: ' . $file['type']);
        }
        
        $safeName = sanitizeFilename($file['name']);
        $targetPath = 'secure_uploads/' . uniqid() . '_' . $safeName;
        
        if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
            throw new RuntimeException('文件保存失败');
        }
    }
    
    $response['status'] = 'success';
    $response['saved_files'] = glob('secure_uploads/*');
    
} catch (RuntimeException $e) {
    $response['message'] = $e->getMessage();
}

echo json_encode($response);

四、错误处理与调试

4.1 完整错误处理方案

$ch = curl_init();
// ... 设置其他选项 ...

try {
    $response = curl_exec($ch);
    
    if ($response === false) {
        throw new RuntimeException('cURL错误: ' . curl_error($ch));
    }
    
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($httpCode >= 400) {
        throw new RuntimeException("HTTP错误: {$httpCode}");
    }
    
    $data = json_decode($response, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new RuntimeException('JSON解析错误: ' . json_last_error_msg());
    }
    
    // 处理成功响应
    print_r($data);
    
} catch (RuntimeException $e) {
    error_log('文件上传失败: ' . $e->getMessage());
    echo '上传失败,请稍后重试';
} finally {
    curl_close($ch);
}

4.2 调试技巧

// 记录完整请求信息
file_put_contents('curl_debug.log', print_r([
    'url' => $uploadUrl,
    'headers' => get_headers($uploadUrl),
    'post_data' => $postData,
    'response' => $response,
    'curl_info' => curl_getinfo($ch)
], true), FILE_APPEND);

五、性能优化建议

  1. 启用HTTP/2

    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
    
  2. 连接复用

    curl_setopt($ch, CURLOPT_FORBID_REUSE, false);
    curl_setopt($ch, CURLOPT_FRESH_CONNECT, false);
    
  3. 超时设置

    curl_setopt_array($ch, [
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 30
    ]);
    
  4. 批量上传优化

    • 使用curl_multi_init()处理并行上传
    • 压缩大文件后再上传

六、安全注意事项

  1. SSL验证

    curl_setopt_array($ch, [
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_CAINFO => '/path/to/cacert.pem'
    ]);
    
  2. 文件类型检查

    function isAllowedFile($tmpPath) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime = finfo_file($finfo, $tmpPath);
        finfo_close($finfo);
    
        $allowed = ['image/jpeg', 'image/png'];
        return in_array($mime, $allowed);
    }
    
  3. 敏感数据保护

    • 不要在上传内容中包含敏感信息
    • 使用临时生成的访问令牌

七、完整封装类示例

class FileUploader {
    private $ch;
    private $options = [
        'timeout' => 30,
        'verify_ssl' => true
    ];
    
    public function __construct(array $options = []) {
        $this->options = array_merge($this->options, $options);
        $this->ch = curl_init();
    }
    
    public function upload($url, $files, $postFields = []) {
        $postData = [];
        
        // 准备文件字段
        foreach ($files as $field => $file) {
            if (is_array($file['tmp_name'])) {
                // 处理多文件上传
                foreach ($file['tmp_name'] as $i => $tmpName) {
                    $postData[$field . '[' . $i . ']'] = new CURLFile(
                        $tmpName,
                        $file['type'][$i],
                        $file['name'][$i]
                    );
                }
            } else {
                $postData[$field] = new CURLFile(
                    $file['tmp_name'],
                    $file['type'],
                    $file['name']
                );
            }
        }
        
        // 合并普通POST字段
        $postData = array_merge($postData, $postFields);
        
        // 配置cURL
        curl_setopt_array($this->ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $postData,
            CURLOPT_TIMEOUT => $this->options['timeout'],
            CURLOPT_SSL_VERIFYPEER => $this->options['verify_ssl']
        ]);
        
        $response = curl_exec($this->ch);
        
        if ($response === false) {
            throw new RuntimeException('Upload failed: ' . curl_error($this->ch));
        }
        
        return $response;
    }
    
    public function __destruct() {
        curl_close($this->ch);
    }
}

// 使用示例
$uploader = new FileUploader();
$response = $uploader->upload(
    'https://example.com/upload',
    [
        'document' => $_FILES['document']
    ],
    [
        'user_id' => 123,
        'api_key' => 'abc123'
    ]
);

标签: PHP, 文件存储, 文件上传, cURL上传, CURLFile类

添加新评论