<?php
header('Content-Type: text/html; charset=UTF-8');
/**
* 使用原生HTTP请求上传文件到OSS
* (大文件分片上传)
*/
class AliyunOSSUploader {
private $accessKeyId;
private $accessKeySecret;
private $bucket;
private $endpoint;
private $partSize = 5 * 1024 * 1024; // 5MB 分片大小
private $maxRetries = 3;
public function __construct() {
$this->accessKeyId = 'AccessKey ID';
$this->accessKeySecret = 'AccessKey Secret';
$this->bucket = 'Bucket名称';
$this->endpoint = 'OSS Endpoint';
}
/**
* 生成签名
*/
private function sign($stringToSign) {
return base64_encode(hash_hmac('sha1', $stringToSign, $this->accessKeySecret, true));
}
/**
* 生成请求头
*/
private function generateHeaders($method, $resource, $headers = []) {
$date = gmdate('D, d M Y H:i:s \G\M\T');
$contentType = isset($headers['Content-Type']) ? $headers['Content-Type'] : '';
$stringToSign = $method . "\n"
. (isset($headers['Content-MD5']) ? $headers['Content-MD5'] : '') . "\n"
. $contentType . "\n"
. $date . "\n"
. $resource;
$signature = $this->sign($stringToSign);
$defaultHeaders = [
'Date' => $date,
'Authorization' => 'OSS ' . $this->accessKeyId . ':' . $signature,
'Content-Type' => $contentType
];
return array_merge($defaultHeaders, $headers);
}
/**
* 初始化分片上传
*/
public function initiateMultipartUpload($objectKey) {
$resource = '/' . $this->bucket . '/' . $objectKey . '?uploads';
$url = 'http://' . $this->bucket . '.' . $this->endpoint . '/' . $objectKey . '?uploads';
$headers = $this->generateHeaders('POST', $resource, [
'Content-Type' => 'application/octet-stream'
]);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $this->formatHeaders($headers),
CURLOPT_HEADER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
$xml = simplexml_load_string(substr($response, strpos($response, '<?xml')));
return (string)$xml->UploadId;
}
throw new Exception('初始化分片上传失败: ' . $response);
}
/**
* 上传分片
*/
public function uploadPart($objectKey, $uploadId, $partNumber, $filePath, $offset, $partSize) {
$resource = '/' . $this->bucket . '/' . $objectKey . '?partNumber=' . $partNumber . '&uploadId=' . $uploadId;
$url = 'http://' . $this->bucket . '.' . $this->endpoint . '/' . $objectKey . '?partNumber=' . $partNumber . '&uploadId=' . $uploadId;
// 读取文件分片
$fileHandle = fopen($filePath, 'rb');
fseek($fileHandle, $offset);
$partData = fread($fileHandle, $partSize);
fclose($fileHandle);
// 计算 MD5
$contentMD5 = base64_encode(md5($partData, true));
$headers = $this->generateHeaders('PUT', $resource, [
'Content-Type' => 'application/octet-stream',
'Content-MD5' => $contentMD5,
'Content-Length' => strlen($partData)
]);
// 重试机制
for ($retry = 0; $retry < $this->maxRetries; $retry++) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_PUT => true,
CURLOPT_HTTPHEADER => $this->formatHeaders($headers),
CURLOPT_HEADER => true,
CURLOPT_INFILE => fopen('data://application/octet-stream;base64,' . base64_encode($partData), 'r'),
CURLOPT_INFILESIZE => strlen($partData)
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
if ($httpCode == 200) {
$headers = substr($response, 0, $headerSize);
preg_match('/ETag: "([^"]+)"/', $headers, $matches);
return isset($matches[1]) ? $matches[1] : '';
}
// 失败后等待重试
if ($retry < $this->maxRetries - 1) {
usleep(pow(2, $retry) * 100000); // 指数退避
}
}
}
/**
* 格式化请求头
*/
private function formatHeaders($headers) {
$formatted = [];
foreach ($headers as $key => $value) {
$formatted[] = $key . ': ' . $value;
}
return $formatted;
}
/**
* 完成分片上传
*/
public function completeMultipartUpload($objectKey, $uploadId, $parts) {
ksort($parts);
$resource = '/' . $this->bucket . '/' . $objectKey . '?uploadId=' . $uploadId;
$url = 'http://' . $this->bucket . '.' . $this->endpoint . '/' . $objectKey . '?uploadId=' . $uploadId;
// 构建 XML 请求体
$xml = '<?xml version="1.0" encoding="UTF-8"?>';
$xml .= '<CompleteMultipartUpload>';
foreach ($parts as $partNumber => $etag) {
$xml .= '<Part>';
$xml .= '<PartNumber>' . $partNumber . '</PartNumber>';
$xml .= '<ETag>' . $etag . '</ETag>';
$xml .= '</Part>';
}
$xml .= '</CompleteMultipartUpload>';
$headers = $this->generateHeaders('POST', $resource, [
'Content-Type' => 'application/xml',
'Content-Length' => strlen($xml)
]);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $this->formatHeaders($headers),
CURLOPT_POSTFIELDS => $xml,
CURLOPT_HEADER => false
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
return true;
}
throw new Exception('完成分片上传失败: ' . $response);
}
/**
* 执行完整的分片上传
*/
public function uploadLargeFile($filePath, $objectKey) {
try {
// 2. 计算分片数量
$fileSize = filesize($filePath);
$partCount = ceil($fileSize / $this->partSize);
$parts = [];
$resumeFile = 'oss_upload_' . md5($filePath . $objectKey) . '.json';
$uploadState = [];
if (file_exists($resumeFile)) {
$uploadState = json_decode(file_get_contents($resumeFile), true);
$uploadId = $uploadState['uploadId'];
$completedParts = $uploadState['completedParts'];
} else {
// 初始化上传
$uploadId = $this->initiateMultipartUpload($objectKey);
$completedParts = [];
$uploadState = [
'uploadId' => $uploadId,
'objectKey' => $objectKey,
'filePath' => $filePath,
'completedParts' => $completedParts
];
file_put_contents($resumeFile, json_encode($uploadState));
}
// 3. 逐个上传分片
for ($i = 1; $i <= $partCount; $i++) {
if (isset($completedParts[$i])) {
$parts[$i] = $completedParts[$i];
continue; // 跳过已上传的分片
}
$offset = ($i - 1) * $this->partSize;
$currentPartSize = ($i == $partCount) ? ($fileSize - $offset) : $this->partSize;
$etag = $this->uploadPart($objectKey, $uploadId, $i, $filePath, $offset, $currentPartSize);
$parts[$i] = $etag;
$uploadState['completedParts'][$i] = $etag;
file_put_contents($resumeFile, json_encode($uploadState));
echo "上传分片 {$i}/{$partCount} 完成\n";
}
// 4. 完成分片上传
$this->completeMultipartUpload($objectKey, $uploadId, $parts);
// 清理进度文件
if (file_exists($resumeFile)) {
unlink($resumeFile);
}
return true;
} catch (Exception $e) {
echo "上传失败: " . $e->getMessage() . "\n";
return false;
}
}
/**
* 列出已上传的分片
*/
public function listParts($objectKey, $uploadId) {
$resource = '/' . $this->bucket . '/' . $objectKey.'?uploadId='.$uploadId;
$url = "http://{$this->bucket}.{$this->endpoint}/{$objectKey}?uploadId={$uploadId}";
$headers = $this->generateHeaders('GET', $resource,['Host' => $this->bucket . '.' . $this->endpoint]);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $this->formatHeaders($headers)
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
$xml = simplexml_load_string($response);
$parts = [];
foreach ($xml->Part as $part) {
$parts[(int)$part->PartNumber] = (string)$part->ETag;
}
return $parts;
}
return [];
}
/**
* 上传小文件
*/
public function uploadFile($file_path,$file_type, $oss_path) {
set_time_limit(0);
$resource = '/' . $this->bucket . '/' . $oss_path;
$url = 'http://' . $this->bucket . '.' . $this->endpoint . '/' . $oss_path;
$file_size = filesize($file_path);
// 构建请求头
$headers = $this->generateHeaders('PUT', $resource, [
'Host' => $this->bucket . '.' . $this->endpoint,
'Content-Type' => $file_type,
'Content-Disposition' => 'inline',
'Content-Length' => $file_size
]);
// 发送PUT请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_PUT, true);
curl_setopt($ch, CURLOPT_HTTPHEADER,$this->formatHeaders($headers));
curl_setopt($ch, CURLOPT_INFILE, fopen($file_path, 'r'));
curl_setopt($ch, CURLOPT_INFILESIZE, $file_size);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return array('code'=>$http_code,'url'=>$url,'response'=>$response);
}
/**
* 删除文件
*/
public function deleteOssObject($objectKey) {
$resource = '/' . $this->bucket . '/' . $objectKey;
$url = "https://{$this->bucket}.{$this->endpoint}/" . ($objectKey);
$headers = $this->generateHeaders('DELETE', $resource);
// 初始化cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->formatHeaders($headers));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 执行请求
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 检查响应
if ($httpCode == 204) {
return true; // 删除成功
} else {
return "删除失败,HTTP状态码: {$httpCode},响应: {$response}";
}
}
}
$uploader = new AliyunOSSUploader();
// 上传小文件
if($_FILES && $_FILES['video_file'] && $_FILES['video_file']['size']<100*1024*1024){
$upload_file = $_FILES['video_file'];
$oss_filename = 'videos/' . date('Ymd') . '/' . uniqid() . '.' .
pathinfo($upload_file['name'], PATHINFO_EXTENSION);
$res = $uploader->uploadFile($upload_file['tmp_name'],$upload_file['type'], $oss_filename);
print_r('<pre>');
print_r($res);
print_r('</pre>');
exit();
}
// 上传大文件
if($_FILES && $_FILES['video_file'] && $_FILES['video_file']['size']>100*1024*1024){
$upload_file = $_FILES['video_file'];
$oss_filename = 'videos/' . date('Ymd') . '/' . uniqid() . '.' .
pathinfo($upload_file['name'], PATHINFO_EXTENSION);
print_r('<pre>');
print_r($upload_file['tmp_name']);
print_r('</pre>');
print_r('<pre>');
print_r($oss_filename);
print_r('</pre>');
exit();
$result = $uploader->uploadLargeFile($upload_file['tmp_name'], $oss_filename); // 5MB 分片
if ($result){
echo "文件上传成功!\n";
echo "文件地址: http://" . $config['bucket'] . '.' . $config['endpoint'] . '/' . $objectKey . "\n";
} else{
echo "文件上传失败!\n";
}
}
//删除文件
//$res = $uploader->deleteOssObject('videos/20251129/692a87ab21581.mp4');
// 创建上传实例
$uploader = new AliyunOSSUploader();
//列出已上传的分片
/*$res = $uploader->listParts('uploads/DE-GS99864.zip','F198E9334004453FA2E92E2C073ABB62');
print_r('<pre>');
print_r($res);
print_r('</pre>');
exit();*/
// 上传大文件
$filePath = 'DE-GS99864.zip';
$objectKey = 'uploads/DE-GS99864.zip'; // OSS 中的文件路径
$result = $uploader->uploadLargeFile($filePath, $objectKey); // 5MB 分片
if ($result) {
echo "文件上传成功!\n";
echo "文件地址: http://" . $config['bucket'] . '.' . $config['endpoint'] . '/' . $objectKey . "\n";
} else {
echo "文件上传失败!\n";
}
exit();
?>
<form method="post" enctype="multipart/form-data">
<input type="file" name="video_file" accept="video/*">
<input type="submit" value="上传视频">
</form>