c 做的網(wǎng)站怎么上傳佳城建站 網(wǎng)站
鶴壁市浩天電氣有限公司
2026/01/24 13:58:21
c 做的網(wǎng)站怎么上傳,佳城建站 網(wǎng)站,織夢(mèng)網(wǎng)站如何做seo,網(wǎng)站源碼下載有什么用大文件上傳方案設(shè)計(jì)與實(shí)現(xiàn)#xff08;政府信創(chuàng)環(huán)境兼容#xff09;
方案背景
作為北京某軟件公司的開發(fā)人員#xff0c;我負(fù)責(zé)為政府客戶實(shí)現(xiàn)一個(gè)兼容主流瀏覽器和信創(chuàng)國(guó)產(chǎn)化環(huán)境的大文件上傳系統(tǒng)。當(dāng)前需求是支持4GB左右文件的上傳#xff0c;后端使用PHP#xff0c;前…大文件上傳方案設(shè)計(jì)與實(shí)現(xiàn)政府信創(chuàng)環(huán)境兼容方案背景作為北京某軟件公司的開發(fā)人員我負(fù)責(zé)為政府客戶實(shí)現(xiàn)一個(gè)兼容主流瀏覽器和信創(chuàng)國(guó)產(chǎn)化環(huán)境的大文件上傳系統(tǒng)。當(dāng)前需求是支持4GB左右文件的上傳后端使用PHP前端使用Vue.js框架。之前嘗試的百度WebUploader在國(guó)產(chǎn)化環(huán)境中存在兼容性問題因此需要重新設(shè)計(jì)解決方案。技術(shù)選型分析方案考慮因素國(guó)產(chǎn)化兼容需支持信創(chuàng)環(huán)境如麒麟、UOS等操作系統(tǒng)飛騰、鯤鵬等CPU架構(gòu)瀏覽器兼容需支持Chrome、Firefox及國(guó)產(chǎn)瀏覽器如360安全瀏覽器、紅芯等開源合規(guī)必須提供完整源代碼供審查穩(wěn)定性大文件上傳的可靠性和斷點(diǎn)續(xù)傳能力性能4GB文件上傳的效率和資源占用最終方案采用基于分片上傳斷點(diǎn)續(xù)傳的自定義實(shí)現(xiàn)結(jié)合以下技術(shù)前端Vue.js 原生HTML5 File API Axios后端PHP原生或Laravel框架分片算法固定大小分片 MD5校驗(yàn)進(jìn)度管理Web Worker處理哈希計(jì)算前端實(shí)現(xiàn)Vue組件示例1. 安裝必要依賴npminstallspark-md5 axios2. 大文件上傳組件 (FileUploader.vue)import SparkMD5 from spark-md5 import axios from axios export default { name: FileUploader, data() { return { file: null, chunkSize: 5 * 1024 * 1024, // 5MB每片 uploadProgress: 0, isUploading: false, isPaused: false, fileHash: , worker: null, currentChunk: 0, totalChunks: 0, uploadId: , abortController: null } }, methods: { triggerFileInput() { this.$refs.fileInput.click() }, handleFileChange(e) { const files e.target.files if (files.length 0) return this.file files[0] this.uploadProgress 0 this.calculateFileHash() }, // 使用Web Worker計(jì)算文件哈希避免主線程阻塞 calculateFileHash() { this.$emit(hash-progress, 0) this.worker new Worker(/hash-worker.js) this.worker.postMessage({ file: this.file, chunkSize: this.chunkSize }) this.worker.onmessage (e) { const { type, data } e.data if (type progress) { this.$emit(hash-progress, data) } else if (type result) { this.fileHash data this.totalChunks Math.ceil(this.file.size / this.chunkSize) this.worker.terminate() } } }, async startUpload() { if (!this.file || !this.fileHash) return this.isUploading true this.isPaused false this.currentChunk 0 // 1. 初始化上傳獲取uploadId try { const initRes await this.request({ url: /api/upload/init, method: post, data: { fileName: this.file.name, fileSize: this.file.size, fileHash: this.fileHash, chunkSize: this.chunkSize } }) this.uploadId initRes.data.uploadId // 2. 開始分片上傳 await this.uploadChunks() // 3. 合并文件 await this.mergeChunks() this.$emit(upload-success, initRes.data) } catch (error) { console.error(上傳失敗:, error) this.$emit(upload-error, error) } finally { this.isUploading false } }, async uploadChunks() { return new Promise((resolve, reject) { const uploadNextChunk async () { if (this.currentChunk this.totalChunks) { return resolve() } if (this.isPaused) return const start this.currentChunk * this.chunkSize const end Math.min(start this.chunkSize, this.file.size) const chunk this.file.slice(start, end) const formData new FormData() formData.append(file, chunk) formData.append(chunkNumber, this.currentChunk) formData.append(totalChunks, this.totalChunks) formData.append(uploadId, this.uploadId) formData.append(fileHash, this.fileHash) try { await this.request({ url: /api/upload/chunk, method: post, data: formData, onUploadProgress: (progressEvent) { // 計(jì)算整體進(jìn)度 const chunkProgress Math.round( (progressEvent.loaded * 100) / progressEvent.total ) const totalProgress Math.round( ((this.currentChunk * 100) chunkProgress) / this.totalChunks ) this.uploadProgress totalProgress } }) this.currentChunk this.$emit(chunk-uploaded, this.currentChunk) // 使用setTimeout避免堆棧溢出 setTimeout(uploadNextChunk, 0) } catch (error) { reject(error) } } uploadNextChunk() }) }, async mergeChunks() { await this.request({ url: /api/upload/merge, method: post, data: { uploadId: this.uploadId, fileHash: this.fileHash, fileName: this.file.name, chunkSize: this.chunkSize } }) }, pauseUpload() { this.isPaused true if (this.abortController) { this.abortController.abort() } }, resumeUpload() { this.isPaused false this.uploadChunks() }, request(config) { // 創(chuàng)建新的AbortController用于取消請(qǐng)求 this.abortController new AbortController() return axios({ ...config, signal: this.abortController.signal, headers: { ...config.headers, Authorization: Bearer localStorage.getItem(token) } }).finally(() { this.abortController null }) }, formatFileSize(bytes) { if (bytes 0) return 0 Bytes const k 1024 const sizes [Bytes, KB, MB, GB, TB] const i Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) sizes[i] } }, beforeDestroy() { if (this.worker) { this.worker.terminate() } if (this.abortController) { this.abortController.abort() } } } .progress-container { margin-top: 10px; width: 100%; } progress { width: 80%; height: 20px; }3. Web Worker腳本 (public/hash-worker.js)// 使用SparkMD5計(jì)算文件哈希在Web Worker中運(yùn)行self.importScripts(https://cdn.jsdelivr.net/npm/spark-md53.0.2/spark-md5.min.js)self.onmessagefunction(e){const{file,chunkSize}e.dataconstchunksMath.ceil(file.size/chunkSize)constsparknewSparkMD5.ArrayBuffer()constfileReadernewFileReader()letcurrentChunk0fileReader.onloadfunction(e){spark.append(e.target.result)currentChunk// 報(bào)告進(jìn)度self.postMessage({type:progress,data:Math.floor((currentChunk/chunks)*100)})if(currentChunkchunks){loadNextChunk()}else{consthashspark.end()self.postMessage({type:result,data:hash})}}functionloadNextChunk(){conststartcurrentChunk*chunkSizeconstendMath.min(startchunkSize,file.size)fileReader.readAsArrayBuffer(file.slice(start,end))}loadNextChunk()}后端PHP實(shí)現(xiàn)1. 初始化上傳接口// api/upload/initpublicfunctioninitUpload(Request$request){$data$request-only([fileName,fileSize,fileHash,chunkSize]);// 驗(yàn)證參數(shù)$validatorValidator::make($data,[fileNamerequired|string,fileSizerequired|integer,fileHashrequired|string,chunkSizerequired|integer]);if($validator-fails()){returnresponse()-json([code400,msg參數(shù)錯(cuò)誤]);}// 生成唯一uploadId$uploadIdmd5(uniqid());// 創(chuàng)建臨時(shí)目錄$tempDirstorage_path(app/uploads/temp/{$uploadId});if(!file_exists($tempDir)){mkdir($tempDir,0755,true);}// 保存上傳信息實(shí)際項(xiàng)目中應(yīng)該存入數(shù)據(jù)庫$uploadInfo[upload_id$uploadId,file_name$data[fileName],file_size$data[fileSize],file_hash$data[fileHash],chunk_size$data[chunkSize],total_chunksceil($data[fileSize]/$data[chunkSize]),uploaded_chunks[],created_atnow()];file_put_contents({$tempDir}/upload_info.json,json_encode($uploadInfo));returnresponse()-json([code200,msgsuccess,data[uploadId$uploadId,tempDir$tempDir]]);}2. 分片上傳接口// api/upload/chunkpublicfunctionuploadChunk(Request$request){$uploadId$request-input(uploadId);$chunkNumber$request-input(chunkNumber);$fileHash$request-input(fileHash);if(!$request-hasFile(file)||!$uploadId||$chunkNumbernull){returnresponse()-json([code400,msg參數(shù)錯(cuò)誤]);}$tempDirstorage_path(app/uploads/temp/{$uploadId});if(!file_exists($tempDir)){returnresponse()-json([code404,msg上傳會(huì)話不存在]);}// 讀取上傳信息$uploadInfojson_decode(file_get_contents({$tempDir}/upload_info.json),true);// 驗(yàn)證文件哈希if($uploadInfo[file_hash]!$fileHash){returnresponse()-json([code400,msg文件哈希不匹配]);}// 保存分片$chunkFile$request-file(file);$chunkPath{$tempDir}/{$chunkNumber}.part;$chunkFile-move(dirname($chunkPath),basename($chunkPath));// 記錄已上傳的分片$uploadInfo[uploaded_chunks][]$chunkNumber;file_put_contents({$tempDir}/upload_info.json,json_encode($uploadInfo));returnresponse()-json([code200,msg分片上傳成功]);}3. 合并分片接口// api/upload/mergepublicfunctionmergeChunks(Request$request){$data$request-only([uploadId,fileHash,fileName]);$validatorValidator::make($data,[uploadIdrequired|string,fileHashrequired|string,fileNamerequired|string]);if($validator-fails()){returnresponse()-json([code400,msg參數(shù)錯(cuò)誤]);}$tempDirstorage_path(app/uploads/temp/{$data[uploadId]});if(!file_exists($tempDir)){returnresponse()-json([code404,msg上傳會(huì)話不存在]);}// 讀取上傳信息$uploadInfojson_decode(file_get_contents({$tempDir}/upload_info.json),true);// 驗(yàn)證文件哈希if($uploadInfo[file_hash]!$data[fileHash]){returnresponse()-json([code400,msg文件哈希不匹配]);}// 檢查是否所有分片都已上傳$totalChunks$uploadInfo[total_chunks];$uploadedChunks$uploadInfo[uploaded_chunks];if(count($uploadedChunks)!$totalChunks){returnresponse()-json([code400,msg還有分片未上傳完成]);}// 創(chuàng)建最終文件$finalDirstorage_path(app/uploads/final);if(!file_exists($finalDir)){mkdir($finalDir,0755,true);}$finalPath{$finalDir}/{$data[fileHash]}_{$data[fileName]};$fpfopen($finalPath,wb);// 按順序合并分片for($i0;$i$totalChunks;$i){$chunkPath{$tempDir}/{$i}.part;$chunkContentfile_get_contents($chunkPath);fwrite($fp,$chunkContent);unlink($chunkPath);// 刪除分片文件}fclose($fp);// 清理臨時(shí)目錄rmdir($tempDir);returnresponse()-json([code200,msg文件合并成功,data[filePath$finalPath,fileUrlasset(storage/uploads/final/.basename($finalPath))]]);}國(guó)產(chǎn)化環(huán)境適配說明瀏覽器兼容使用原生HTML5 File API兼容所有現(xiàn)代瀏覽器對(duì)于不支持的瀏覽器如舊版IE可添加降級(jí)提示信創(chuàng)環(huán)境適配前端代碼不依賴任何特定瀏覽器API后端PHP使用原生文件操作函數(shù)無系統(tǒng)相關(guān)調(diào)用測(cè)試通過麒麟V10、UOS等國(guó)產(chǎn)操作系統(tǒng)飛騰/鯤鵬CPU環(huán)境安全考慮文件哈希驗(yàn)證防止篡改分片上傳避免內(nèi)存溢出臨時(shí)文件及時(shí)清理部署注意事項(xiàng)PHP配置確保upload_max_filesize和post_max_size大于分片大小調(diào)整max_execution_time避免超時(shí)Nginx配置如使用client_max_body_size 100M; client_body_timeout 300s;存儲(chǔ)路徑權(quán)限確保storage/app/uploads目錄有寫入權(quán)限總結(jié)本方案通過分片上傳和斷點(diǎn)續(xù)傳技術(shù)解決了大文件上傳的穩(wěn)定性問題同時(shí)完全滿足政府客戶的國(guó)產(chǎn)化兼容和源代碼審查要求。前端采用Vue.js原生API實(shí)現(xiàn)后端使用純PHP處理不依賴任何閉源組件確保了代碼的完全可控性。將組件復(fù)制到項(xiàng)目中示例中已經(jīng)包含此目錄引入組件配置接口地址接口地址分別對(duì)應(yīng)文件初始化文件數(shù)據(jù)上傳文件進(jìn)度文件上傳完畢文件刪除文件夾初始化文件夾刪除文件列表參考http://www.ncmem.com/doc/view.aspx?ide1f49f3e1d4742e19135e00bd41fa3de處理事件啟動(dòng)測(cè)試啟動(dòng)成功效果數(shù)據(jù)庫下載示例點(diǎn)擊下載完整示例