本文環境:php7.3.4 CI3.0框架
一、大概步驟:
(1)利用百度的webuploader插件,將大文件分片上傳的自己的服務器
(2)利用騰訊云接口從本服務器上傳到騰訊云
二、詳細代碼:
1、進入前端頁面的控制器
upload.php 從packageupload函數進入Html頁面
class Upload {// 進入上傳母包頁面function packageupload(){$data['appid']=$this->input->get('appid', TRUE);// $data['appid']='iostt';if (!empty($data['appid'])) {$data['game_info'] = $this->mapp->getApp($data['appid']);$this->load->view("bapp/upload_form",$data);}else{echo '<script>alert("參數錯誤");</script>';}}// 前端分片上傳包體 組合完成再統一上傳(服務端到CDN傳送文件)function upload_appid_package() {// 禁用輸出緩沖區,確保響應立即發送if (ob_get_level()) {ob_end_clean();}header('Content-Type: application/json'); // 在函數開頭設置 JSON 頭部$appid = $this->input->post('appid');if (empty($appid)) {echo json_encode(['code' => 400, 'error' => 'App ID is required.']);exit;}$file = $_FILES['userfile'];$chunk = $this->input->post('chunk', TRUE); // 當前分片索引$chunks = $this->input->post('chunks', TRUE); // 總分片數$fileName = $this->input->post('name', TRUE); // 原始文件名$uniqueId = $this->input->post('uniqueId', TRUE); // 文件唯一標識if (!$fileName || !$uniqueId) {echo json_encode(['code' => 400, 'error' => 'Invalid file or unique ID.']);exit;}//臨時存放的服務器文件夾 確保有存放權限$tmpDir = FCPATH . 'uploads/tmp/' . $uniqueId . '/';if (!is_dir($tmpDir)) {mkdir($tmpDir, 0777, TRUE);}$chunkPath = $tmpDir . $chunk;if (!move_uploaded_file($file['tmp_name'], $chunkPath)) {echo json_encode(['code' => 400, 'error' => 'Failed to save chunk.']);exit;}// 為每個分片返回成功響應// echo json_encode(['code' => 200, 'msg' => '分片上傳成功', 'chunk' => $chunk]);// 處理最后一個分片并合并if ($chunk == $chunks - 1) {$finalPath = FCPATH . 'uploads/' . uniqid() . '_' . $fileName;$out = @fopen($finalPath, 'wb');if (!$out) {echo json_encode(['code' => 400, 'error' => '無法創建最終文件']);exit;}// 合并分片for ($i = 0; $i < $chunks; $i++) {$chunkFile = $tmpDir . $i;$in = @fopen($chunkFile, 'rb');if ($in) {while ($buff = fread($in, 8192)) {fwrite($out, $buff);}fclose($in);}}fclose($out);$game_info = $this->mapp->getApp($appid);if (empty($game_info)) {echo json_encode(['code' => 400, 'data' => null, 'msg' => '游戲不存在']);exit;}$extension = pathinfo($fileName, PATHINFO_EXTENSION);$version = !empty($game_info['version']) ? number_format($game_info['version'] + 0.1, 1) : '1.0';$cpid=$game_info['cpid'];$cos_path = 'test/' . $cpid . '/' . $cpid . '_' . $version . '.' . $extension;if ($game_info['plat'] == 1) { $cos_path = 'package/ios/' . $cpid . '/' . $cpid . '_' . $version . '.' . $extension;}// 騰訊云上傳$ten=new TencentCosUploader();$result = $ten->uploadFileFromPath($finalPath, $cos_path);log_message('error', json_encode($result, JSON_UNESCAPED_UNICODE));if (!empty($result['status'])) {//處理成功之后的邏輯// 清理臨時目錄$this->cleanDirectory();echo json_encode(['code' => 0, 'data' => 'success', 'msg' => '上傳成功']);exit;} else {// 刪除合并后的文件if (file_exists($finalPath)) {unlink($finalPath);}// 清理臨時目錄$this->cleanDirectory();echo json_encode(['code' => 400, 'data' => $result['error'], 'msg' => '上傳失敗']);exit;}}}// 清空臨時文件夾public function cleanDirectory() {// 使用默認路徑如果未指定$dir = rtrim($dir ?: FCPATH . 'uploads/tmp', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;// 記錄開始清理log_message('info', '開始清空臨時目錄: ' . $dir);// 檢查目錄是否存在if (!is_dir($dir)) {log_message('error', '臨時目錄不存在: ' . $dir);return true;}try {// 打開目錄$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),RecursiveIteratorIterator::CHILD_FIRST);$success = true;// 遍歷目錄foreach ($iterator as $item) {$path = $item->getPathname();if ($item->isDir()) {// 刪除空目錄if (@rmdir($path) === false) {log_message('error', '無法刪除目錄: ' . $path);$success = false;}} else {// 刪除文件if (@unlink($path) === false) {log_message('error', '無法刪除文件: ' . $path);$success = false;}}}log_message('info', '臨時目錄清理完成,狀態: ' . ($success ? '成功' : '部分失敗'));return $success;} catch (Exception $e) {log_message('error', '清理臨時目錄時發生異常: ' . $e->getMessage());return false;} }//獲取appid信息,比對version確定是否更新成功function getappidinfo(){$appid = $this->input->get("appid", true);$version = $this->input->get("version", true);$data = $this->mapp->getApp($appid);$res=['code'=>0,'data'=>$data['version'],'msg'=>'success'];if($data['version'] == $version){$res['code'] = 1;}echo json_encode($res);}
}
2、前端代碼:upload_form.html
請先下載webuploader相關插件:地址:https://github.com/fex-team/webuploader
這個兄弟也有源碼:GitCode - 全球開發者的開源社區,開源代碼托管平臺
如果都嫌麻煩,也可以通過這個鏈接下載:https://download.csdn.net/download/weixin_45143733/91008568
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>File Upload to Tencent COS</title><!-- Bootstrap CSS --><link href="/statics/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><!-- WebUploader CSS --><link rel="stylesheet" type="text/css" href="/statics/js/plugins/ueditor/third-party/webuploader/webuploader.css" /><!-- jQuery --><script src="/statics/js/jquery-2.1.1.js"></script><script src="/assets/libs/fastadmin-layer/dist/layer.js"></script><style>body {background-color: #f8f9fa;}.upload-container {min-height: 100vh;display: flex;align-items: center;justify-content: center;}.upload-form {background-color: white;padding: 2rem;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);width: 100%;max-width: 500px;}.btn-upload {padding: 10px 20px;font-size: 1.1rem;}#picker {display: inline-block;}.progress-container {display: none;margin-top: 1rem;}.error-message {display: none;}</style>
</head>
<body><div class="upload-container"><div class="upload-form"><h3 class="text-center mb-4">上傳母包</h3><div class="alert alert-danger error-message"></div><form id="uploadForm"><div class="mb-3"><label for="appid" class="form-label">Appid</label><input type="text" id="appid" name="appid" class="form-control" readonly value="<?php echo htmlspecialchars($game_info['appid']); ?>"></div> <div class="mb-3"><label for="game_name" class="form-label">游戲名</label><input type="text" class="form-control" readonly value="<?php echo htmlspecialchars($game_info['name']); ?>"></div> <div class="mb-3"><label for="userfile" class="form-label">現有版本號</label><input type="text" id="version" class="form-control" readonly value="<?php echo $game_info['version']?:'1.0'?>"></div> <div class="mb-3" style="margin: 1rem;height: 5rem;"><div id="picker">選擇文件上傳</div></div><div class="d-flex align-items-center"><button type="button" id="uploadButton" class="btn btn-primary btn-upload" disabled>開始上傳</button></div><div class="progress-container"><div class="progress"><div class="progress-bar" role="progressbar" style="width: 0%;" id="progressBar">0%</div></div></div></form></div></div><!-- Bootstrap JS --><script src="/statics/js/bootstrap.bundle.min.js"></script><!-- WebUploader JS --><script src="/statics/js/plugins/ueditor/third-party/webuploader/webuploader.js"></script><script>$(document).ready(function() {try {//console.log('Initializing WebUploader...');var uploader = WebUploader.create({auto: false,swf: '/statics/js/plugins/ueditor/third-party/webuploader/Uploader.swf',//處理分片合并的后端函數server: '<?php echo site_url('bapp/upload_appid_package'); ?>',pick: '#picker',chunked: true,chunkSize: 5 * 1024 * 1024, // 5MB 分片threads: 8,fileVal: 'userfile',formData: {appid: $('#appid').val()}});// 存儲服務端返回數據// let serverResponses = [];//console.log('WebUploader initialized successfully');// 更新 appid$('#appid').on('change', function() {//console.log('Updating appid:', $(this).val());uploader.option('formData', {appid: $(this).val()});});// 文件選擇前的校驗uploader.on('beforeFileQueued', function(file) {var appid = $('#appid').val().toLowerCase();var isIos = appid.indexOf('ios') !== -1;var ext = file.ext.toLowerCase();if (isIos && ext !== 'ipa') {layer.msg('蘋果包,請選擇 .ipa 文件!', {icon: 5, shade: [0.3, '#393D49'], time: 1500});return false;} else if (!isIos && ext !== 'apk') {layer.msg('安卓包:請選擇 .apk 文件!', {icon: 5, shade: [0.3, '#393D49'], time: 1500});return false;}return true;});// 文件選擇后uploader.on('fileQueued', function(file) {console.log('File queued:', file.name);$('#uploadButton').prop('disabled', false);$('.webuploader-pick').text('已選擇文件');// 更新 formData 以包含 uniqueId 和 nameuploader.option('formData', {appid: $('#appid').val(),uniqueId: file.uniqueId,name: file.name});});// 分片上傳前,添加唯一標識uploader.on('beforeFileQueued', function(file) {//console.log('Generating unique ID for file:', file.name);file.uniqueId = WebUploader.Base.guid();});// 每個分片上傳前,確保 formData 包含必要參數uploader.on('uploadBeforeSend', function(block, data) {//console.log('Sending chunk:', block.chunk, 'of', block.chunks);data.appid = $('#appid').val();data.uniqueId = block.file.uniqueId;data.name = block.file.name;});// 上傳進度uploader.on('uploadProgress', function(file, percentage) {var percent = Math.round(percentage * 100);// //console.log('Upload progress:', percent + '%');$('#progressBar').css('width', percent + '%').text(percent + '%');$('.progress-container').show();});// 上傳成功uploader.on('uploadSuccess', function(file, response) { if (response && typeof response === 'object') {if (response.code === 0) {//這里的成功捕捉不太準確,建議在uploadComplete處理} else if (response.code === 200) {// 單個分片上傳成功console.log('分片 ' + response.chunk + ' 上傳成功');} else if (response.code ===400) {// 錯誤響應$('#uploadButton').prop('disabled', false).text('上傳失敗:'+response.msg);$('.progress-container').hide();$('.error-message').text('上傳失敗,重試中···').show();resetUploader();}} else {// 無效響應$('#uploadButton').prop('disabled', false).text('上傳失敗');$('.progress-container').hide();$('.error-message').text('服務器響應無效').show();resetUploader();}});// 上傳錯誤uploader.on('uploadError', function(file, reason) {layer.msg('上傳失敗', {icon: 5, shade: [0.3, '#393D49'], time: 1500});$('.error-message').text('Upload failed: ' + reason).show();resetUploader();});// 上傳完成uploader.on('uploadComplete', function(file, response) { $.getJSON('<?php echo site_url('bapp/getappidinfo'); ?>', {appid: $('#appid').val(), version: $('#version').val()}, function(data) {if(data.code==0){var newversion='成功升級為:'+data.data;$('#version').val(newversion);// 上傳成功layer.msg('上傳成功', { icon: 6, shade: [0.3, '#393D49'], time: 2000 });$('.webuploader-pick').css('display', 'none');$('.error-message').css('display', 'none');// 最終上傳成功$('#uploadButton').prop('disabled', true).text('上傳成功');}else{// 錯誤響應$('#uploadButton').prop('disabled', false).text('上傳失敗');$('.progress-container').hide();$('.error-message').text('上傳失敗').show();}}) $('#uploadButton').prop('disabled', true).text('上傳完成');});// 開始上傳$('#uploadButton').on('click', function() {//console.log('Upload button clicked');if (!uploader.getFiles().length) {$('.error-message').text('請選擇文件.').show();console.warn('No file selected');return;}if (!$('#appid').val()) {$('.error-message').text('appid 未填寫.').show();console.warn('No App ID provided');return;}$(this).prop('disabled', true).html('<span style="color:red">處理中,請勿關閉頁面</span>');$('.error-message').hide();uploader.upload();});function resetUploader() {//console.log('Resetting uploader');$('#progressBar').css('width', '0%').text('');$('.progress-container').hide();uploader.reset();}} catch (e) {console.error('JavaScript error:', e.message, e.stack);$('.error-message').text('Initialization failed: ' + e.message).show();}});</script>
</body>
</html>
3、引用Teo接口上傳前,請先安裝騰訊云的插件
composer命令:composer require qcloud/cos-sdk-v5:^2.6
里面的參數請到騰訊云的管理后臺去拿
Tencent.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');require_once APPPATH . 'libraries/third_party/vendor/autoload.php';use Qcloud\Cos\Client;class TencentCosUploader {private $CI;private $cosClient;private $config;public function __construct() {$this->CI =& get_instance(); $this->initializeCosClient();}/*** 統一配置騰訊云 COS 參數* @return array 配置數組*/private function getCosConfig() {return ['region' => '', // 存儲桶地域,如 ap-guangzhou'schema' => 'http', // 協議頭,http 或 https'credentials' => ['secretId' => '', // 騰訊云 Secret ID'secretKey' => '' // 騰訊云 Secret Key],'bucket' => '', // 存儲桶名稱,格式:BucketName-APPID'timeout' => 600, // 請求超時時間(秒)'connect_timeout' => 60 // 連接超時時間(秒)];}/*** 初始化 COS 客戶端*/private function initializeCosClient() {$this->config = $this->getCosConfig();$this->cosClient = new Client(['region' => $this->config['region'],'schema' => $this->config['schema'],'credentials' => $this->config['credentials'],'timeout' => $this->config['timeout'],'connect_timeout' => $this->config['connect_timeout']]);}/*** 從本地路徑上傳文件到騰訊云 COS* @param string $local_file_path 本地文件路徑* @param string $cos_path 文件在 COS 上的存儲路徑* @return array 上傳結果*/public function uploadFileFromPath($local_file_path, $cos_path) {if (!file_exists($local_file_path)) {return ['status' => FALSE,'error' => 'Local file does not exist.'];}// 獲取文件信息$file_name = basename($local_file_path);$extension = pathinfo($file_name, PATHINFO_EXTENSION);$extension = $extension ? strtolower($extension) : '';// 檢查文件大小(3GB 限制)$file_size = filesize($local_file_path);if ($file_size > 3*1024 * 1024 * 1024) {return ['status' => FALSE,'error' => 'File size exceeds 1GB limit.'];}// 確保文件可寫if (!is_writable($local_file_path)) {chmod($local_file_path, 0666);}try {// 根據文件大小選擇上傳方式$file_handle = fopen($local_file_path, 'rb');if ($file_size > 50 * 1024 * 1024) {$result = $this->cosClient->upload($this->config['bucket'],$cos_path,$file_handle,['PartSize' => 10 * 1024 * 1024]);} else {$result = $this->cosClient->putObject(['Bucket' => $this->config['bucket'],'Key' => $cos_path,'Body' => $file_handle]);}// 顯式關閉文件句柄if (is_resource($file_handle)) {fclose($file_handle);}// 刪除本地臨時文件if (!unlink($local_file_path)) {log_message('error', 'Failed to delete local file: ' . $local_file_path . ' | Error: ' . error_get_last()['message']);}return ['status' => TRUE,'data' => ['file_name' => $file_name,'cos_path' => $cos_path,'url' => $result['Location'],'extension' => $extension,]];} catch (\Exception $e) {// 顯式關閉文件句柄if (is_resource($file_handle)) {fclose($file_handle);}return ['status' => FALSE,'error' => 'COS Upload Failed: ' . $e->getMessage()];}}/*** 上傳文件到騰訊云 COS* @param string $field_name 表單文件字段名* @param string $cos_path 文件在 COS 上的存儲路徑* @return array 上傳結果*/public function uploadFile($field_name, $cos_path) {$upload_path = FCPATH . 'uploads/';// 確保上傳目錄存在if (!is_dir($upload_path)) {mkdir($upload_path, 0777, TRUE);}// 驗證文件是否存在if (!isset($_FILES[$field_name]) || $_FILES[$field_name]['error'] === UPLOAD_ERR_NO_FILE) {return ['status' => FALSE,'error' => 'No file uploaded.'];}// 檢查上傳錯誤if ($_FILES[$field_name]['error'] !== UPLOAD_ERR_OK) {$errors = [UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize.',UPLOAD_ERR_FORM_SIZE => 'File exceeds form size limit.',UPLOAD_ERR_PARTIAL => 'File only partially uploaded.',UPLOAD_ERR_NO_TMP_DIR => 'Temporary directory missing.',UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload.'];return ['status' => FALSE,'error' => isset($errors[$_FILES[$field_name]['error']]) ? $errors[$_FILES[$field_name]['error']] : 'Unknown upload error.'];}// 檢查文件大小(3GB 限制)if ($_FILES[$field_name]['size'] > 3*1024 * 1024 * 1024) {return ['status' => FALSE,'error' => 'File size exceeds 1GB limit.'];}// 獲取文件信息$original_name = $_FILES[$field_name]['name'];$tmp_path = $_FILES[$field_name]['tmp_name'];$extension = pathinfo($original_name, PATHINFO_EXTENSION);$extension = $extension ? strtolower($extension) : '';// 生成本地存儲路徑$local_file_name = uniqid('upload_', true) . ($extension ? '.' . $extension : '');$local_file_path = $upload_path . $local_file_name;// 移動文件到本地目錄if (!move_uploaded_file($tmp_path, $local_file_path)) {return ['status' => FALSE,'error' => 'Failed to move uploaded file.'];}// 確保文件可寫if (!is_writable($local_file_path)) {chmod($local_file_path, 0666);}try {// 根據文件大小選擇上傳方式$file_size = filesize($local_file_path);$file_handle = fopen($local_file_path, 'rb');if ($file_size > 50 * 1024 * 1024) { // 大于 50MB 使用分片上傳$result = $this->cosClient->upload($this->config['bucket'],$cos_path,$file_handle,['PartSize' => 10 * 1024 * 1024]);} else {$result = $this->cosClient->putObject(['Bucket' => $this->config['bucket'],'Key' => $cos_path,'Body' => $file_handle]);}// 顯式關閉文件句柄if (is_resource($file_handle)) {fclose($file_handle);}// 刪除本地臨時文件if (!unlink($local_file_path)) {log_message('error', 'Failed to delete local file: ' . $local_file_path . ' | Error: ' . error_get_last()['message']);}return ['status' => TRUE,'data' => ['file_name' => $original_name,'cos_path' => $cos_path,'url' => $result['Location'],'extension' => $extension]];} catch (\Exception $e) {// 顯式關閉文件句柄if (is_resource($file_handle)) {fclose($file_handle);}// 刪除本地臨時文件if (file_exists($local_file_path) && !unlink($local_file_path)) {log_message('error', 'Failed to delete local file: ' . $local_file_path . ' | Error: ' . error_get_last()['message']);}return ['status' => FALSE,'error' => 'COS Upload Failed: ' . $e->getMessage()];}}
}