咱就是说,作为一个福州信息安全专业的大三狗,最近被毕业设计折腾得头发都快薅成“地中海”了——老师拍板要做一个文件管理系统,美其名曰“兼顾实用性和技术深度”,结果我翻遍全网找大文件上传的代码,要么是残缺的“demo片段”,要么是“需要付费授权”的商业代码,连个能完整跑通的项目都找不到!最气的是,遇到问题想找作者问问,要么微信拉黑,要么QQ装死,群里喊破喉咙都没人理……(拍桌)
不过咱是谁?信息安全专业的“卷王”(自封的)!既然网上没现成的,咱就自己造轮子!刚好老师说“作品能直接答辩演示”,咱必须整出个“能打”的系统——大文件上传(10G+)、断点续传(关浏览器重启都不怕)、文件夹上传(保留层级)、加密传输+加密存储,还要兼容IE8这种“古董浏览器”(学校机房的老机器哭晕在厕所)。
先唠唠踩过的坑(血泪史版)
- 大文件上传:用WebUploader吧,文档不全;自己用H5 File API?分片、合并、进度保存全得自己写,头秃。
- 断点续传:想存进度?localStorage容量不够(10G文件的分片信息得存到后端);关浏览器就丢?那得用IndexedDB或者后端数据库存进度。
- 文件夹上传:webkitdirectory属性IE不认,IE8连File API都没有,只能让用户一个个选文件,再手动拼层级(用户骂骂咧咧但能凑合用)。
- 加密:传输要HTTPS,存储要AES加密,密钥得存后端(不然被偷库就完犊子)。
- 兼容IE8:放弃H5新特性,用iframe模拟异步上传,表单提交……(体验差但能跑)。
甩干货:核心代码(附调试说明)
咱直接上“能跑通”的代码,毕业答辩绝对稳!(PS:部分细节需要根据实际环境调整,群里喊我帮你调~)
前端:Vue3 + 原生JS(大文件分片上传+断点续传)
(兼容IE9+,IE8用iframe fallback,代码里标了注释)
export default { data() { return { uploading: false, progress: 0, chunkSize: 2 * 1024 * 1024, // 分片大小2MB(10G文件分5000片) file: null, fileHash: '', // 文件哈希(用于断点续传标识) uploadedChunks: [] // 已上传的分片序号 }; }, methods: { async handleFileSelect(e) { const files = e.target.files; if (!files.length) return; this.file = files[0]; // 计算文件哈希(用于断点续传,用SparkMD5库) this.fileHash = await this.calculateFileHash(this.file); // 检查后端是否已有部分分片(断点续传关键!) const { data } = await this.$http.get(`/api/checkChunks?hash=${this.fileHash}`); this.uploadedChunks = data.uploadedChunks || []; // 开始上传 this.uploadFile(); }, // 计算文件哈希(用Web Worker防卡顿) calculateFileHash(file) { return new Promise((resolve) => { const spark = new SparkMD5.ArrayBuffer(); const reader = new FileReader(); const chunkSize = this.chunkSize; const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; reader.onload = (e) => { spark.append(e.target.result); currentChunk++; if (currentChunk < chunks) { loadNext(); } else { resolve(spark.end()); } }; const loadNext = () => { const start = currentChunk * chunkSize; const end = Math.min(start + chunkSize, file.size); reader.readAsArrayBuffer(file.slice(start, end)); }; loadNext(); }); }, // 上传文件(分片+断点续传) async uploadFile() { this.uploading = true; const totalChunks = Math.ceil(this.file.size / this.chunkSize); for (let i = 0; i < totalChunks; i++) { // 跳过已上传的分片 if (this.uploadedChunks.includes(i)) { this.progress = (i / totalChunks) * 100; continue; } const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, this.file.size); const chunk = this.file.slice(start, end); // 加密分片(AES加密,密钥从后端获取) const encryptedChunk = await this.encryptChunk(chunk); // 构造FormData(包含分片、文件哈希、当前分片序号) const formData = new FormData(); formData.append('file', encryptedChunk, `${this.fileHash}_${i}`); formData.append('hash', this.fileHash); formData.append('chunkIndex', i); formData.append('totalChunks', totalChunks); try { await this.$http.post('/api/uploadChunk', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); this.uploadedChunks.push(i); this.progress = (i / totalChunks) * 100; } catch (err) { console.error('上传失败:', err); break; } } // 所有分片上传完成,通知后端合并 if (this.uploadedChunks.length === totalChunks) { await this.$http.post('/api/mergeChunks', { hash: this.fileHash, fileName: this.file.name, totalChunks: totalChunks }); alert('上传成功!'); } this.uploading = false; }, // AES加密分片(密钥从后端获取,这里用伪代码) async encryptChunk(chunk) { const key = await this.$http.get('/api/getEncryptKey'); // 后端返回AES密钥 const iv = crypto.getRandomValues(new Uint8Array(16)); // 随机IV const cipher = new CryptoJS.AES.encrypt( chunk, CryptoJS.enc.Hex.parse(key), { iv: CryptoJS.enc.Hex.parse(iv.toString()) } ); return new Blob([cipher.toString()], { type: 'application/octet-stream' }); } } };后端:PHP(分片接收+合并+加密存储)
(兼容IE8的表单上传 fallback,用$_FILES接收)
prepare("INSERT INTO upload_progress (hash, chunk_index) VALUES (?, ?) ON DUPLICATE KEY UPDATE chunk_index=?");$stmt->execute([$hash,$chunkIndex,$chunkIndex]);echojson_encode(['code'=>200,'msg'=>'分片上传成功']);exit;}// 合并分片接口:/api/mergeChunksif($_SERVER['REQUEST_METHOD']==='POST'&&isset($_POST['hash'])){$hash=$_POST['hash'];$fileName=$_POST['fileName'];$totalChunks=(int)$_POST['totalChunks'];$tempDir=__DIR__."/uploads/temp/{$hash}";$finalPath=__DIR__."/uploads/files/{$fileName}";// 按顺序合并分片(加密后上传到OSS)$fp=fopen($finalPath,'wb');for($i=0;$i<$totalChunks;$i++){$chunkContent=file_get_contents("{$tempDir}/{$i}");fwrite($fp,aesEncrypt($chunkContent,getEncryptKey()));// 加密存储unlink("{$tempDir}/{$i}");// 删除临时分片}fclose($fp);rmdir($tempDir);// 删除临时目录// 清理数据库记录$pdo=newPDO("mysql:host=".DB_HOST.";dbname=".DB_NAME,DB_USER,DB_PASS);$pdo->exec("DELETE FROM upload_progress WHERE hash='{$hash}'");echojson_encode(['code'=>200,'msg'=>'文件合并成功']);exit;}// 检查已上传分片接口:/api/checkChunksif($_SERVER['REQUEST_METHOD']==='GET'&&isset($_GET['hash'])){$hash=$_GET['hash'];$pdo=newPDO("mysql:host=".DB_HOST.";dbname=".DB_NAME,DB_USER,DB_PASS);$stmt=$pdo->prepare("SELECT chunk_index FROM upload_progress WHERE hash=?");$stmt->execute([$hash]);$uploadedChunks=$stmt->fetchAll(PDO::FETCH_COLUMN,0);echojson_encode(['code'=>200,'uploadedChunks'=>$uploadedChunks]);exit;}// AES加密函数(密钥从环境变量获取,别硬编码!)functionaesEncrypt($data,$key){$iv=openssl_random_pseudo_bytes(16);$encrypted=openssl_encrypt($data,'AES-256-CBC',$key,0,$iv);returnbase64_encode($iv.$encrypted);// 存IV+密文}functionaesDecrypt($data,$key){$decoded=base64_decode($data);$iv=substr($decoded,0,16);$encrypted=substr($decoded,16);returnopenssl_decrypt($encrypted,'AES-256-CBC',$key,0,$iv);}functiongetEncryptKey(){// 从阿里云KMS或环境变量获取密钥(别直接写死!)returngetenv('AES_ENCRYPT_KEY');}?>兼容IE8的“土味方案”
IE8不支持File API和FormData,只能用传统的iframe模拟异步上传:
// IE8检测 if (/*@cc_on!@*/false) { document.getElementById('fileInput').style.display = 'none'; document.getElementById('ie8FileInput').addEventListener('change', function(e) { document.getElementById('ie8UploadForm').submit(); }); }吐槽+安利:群里见!
咱最近建了个QQ群(374992201),专门拉拢“被毕业设计逼疯”的兄弟姐妹们~群里福利拉满:
- 新人加群送1~99元红包(手慢无!);
- 推荐客户成交拿20%提成(2万项目提4千,比外卖自由香多了!);
- 大神在线指导代码(不会写?直接@我,远程帮你调!);
- 内推工作(学长学姐在互联网大厂蹲坑,直接甩岗位链接!)。
咱这毕业设计要是成了,答辩老师看了都得夸“这学生有点东西”~ 赶紧加群,一起卷死同学,顺便搞钱!(PS:群里还有人分享“如何用AI写论文”的玄学技巧,亲测能过查重!)
最后,求师哥师姐推荐工作!福州信息安全行业的岗位,求内推!(鞠躬)
安装环境
PHP:7.2.14
调整块大小
NOSQL
NOSQL不需要任何配置,可以直接访问测试
SQL
创建数据库
您可以直接复制脚本进行创建
配置数据库连接
安装依赖
访问页面进行测试
数据表中的数据
效果预览
文件上传
文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
免费下载示例
点击下载完整示例