程序猿の毕业设计渡劫指南(附代码+求生攻略)
一、项目背景(哭唧唧版)
作为一只即将被学校"扫地出门"的计科狗,最近被毕业设计折磨得夜不能寐——导师甩下一句:“做个文件管理系统,要支持10G大文件上传+文件夹+断点续传+IE8兼容,否则别想毕业!”(说完就骑着共享单车溜了)
现在的情况是:
- 前端:Vue3 + 原生JS(要兼容到IE8,比让恐龙玩Switch还离谱)
- 后端:SpringBoot(跑在Tomcat 6.0上,比我家老古董电视还卡)
- 存储:直接塞服务器硬盘(导师说"云存储太贵,咱们穷苦大学生要艰苦奋斗")
- 浏览器:从IE8到Chrome120全兼容(包括龙芯浏览器这种"国产神器")
二、技术选型(妥协版)
-
上传组件:
- 现代浏览器:H5 File API + Web Workers(防卡死)
- IE8:WebUploader + Flash(没错,2023年还要写Flash代码)
- 最终方案:双引擎热切换(像混动车一样智能)
-
加密方案:
- 传输加密:AES-128(IE8用CryptoJS降级)
- 存储加密:SM4(信创浏览器强制要求)
- 密钥管理:用户密码+时间戳盐值(防止导师偷看我学习资料)
-
断点续传:
- 进度存储:localStorage(IE8用userData模拟)
- 服务端记录:MySQL+文件系统双保险(防止浏览器清空数据)
三、前端实现(Vue3 + 原生JS)
// FileUploader.vue(精简求生版)
export default {
data() {
return {
progress: 0,
isIE8: !!window.ActiveXObject || "ActiveXObject" in window,
chunkSize: 5 * 1024 * 1024 // 5MB分片
}
},
methods: {
triggerUpload() {
if (this.isIE8) {
// 触发IE8上传(通过iframe通信)
document.getElementById('ie8Uploader').contentWindow.startUpload();
} else {
// 现代浏览器直接触发
document.getElementById('modernFileInput').click();
}
},
// 现代浏览器文件处理
handleModernFileChange(e) {
const files = e.target.files;
Array.from(files).forEach(file => {
if (file.webkitRelativePath) {
this.uploadFolder(file); // 文件夹上传
} else {
this.uploadFile(file);
}
});
},
// 文件分片上传(现代浏览器)
async uploadFile(file) {
const fileId = this.generateFileId(file);
const totalChunks = Math.ceil(file.size / this.chunkSize);
let uploadedChunks = this.getSavedProgress(fileId) || 0;
for (let i = uploadedChunks; i < totalChunks; i++) {
const start = i * this.chunkSize;
const end = Math.min(start + this.chunkSize, file.size);
const chunk = file.slice(start, end);
// 加密分片(简化版)
const encrypted = this.encryptData(chunk);
const formData = new FormData();
formData.append('file', encrypted);
formData.append('fileId', fileId);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
try {
await fetch('/api/upload/chunk', {
method: 'POST',
body: formData
});
this.progress = Math.floor(((i + 1) / totalChunks) * 100);
this.saveProgress(fileId, i + 1);
} catch (e) {
console.error('上传失败:', e);
break;
}
}
},
// IE8兼容方案(伪代码)
uploadFileIE8(file) {
// 这里需要写100行ActiveXObject代码...
// 核心思路:通过XMLHttpRequest分片上传
// 省略IE8兼容代码(手酸了)...
},
// 生成唯一文件ID(兼容IE8)
generateFileId(file) {
if (this.isIE8) {
return file.name + file.size + file.lastModified; // 简单拼接
}
return file.name + '-' + CryptoJS.MD5(file.name + file.size).toString();
}
}
}
四、后端实现(SpringBoot + Tomcat 6.0)
// FileUploadController.java(求生版)
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
@Value("${file.upload-dir}")
private String uploadDir;
// 初始化上传任务
@PostMapping("/init")
public ResponseEntity> initUpload(
@RequestParam String fileName,
@RequestParam long fileSize) {
String fileId = UUID.randomUUID().toString();
String tempDir = uploadDir + "/temp/" + fileId;
// 创建临时目录(Tomcat 6.0需要手动处理路径)
new File(tempDir).mkdirs();
Map response = new HashMap<>();
response.put("fileId", fileId);
response.put("chunkSize", 5242880); // 5MB
response.put("uploadDir", tempDir);
return ResponseEntity.ok(response);
}
// 处理分片上传
@PostMapping("/chunk")
public ResponseEntity uploadChunk(
@RequestParam("file") MultipartFile file,
@RequestParam String fileId,
@RequestParam int chunkIndex,
@RequestParam int totalChunks) {
try {
String tempDir = uploadDir + "/temp/" + fileId;
String chunkPath = tempDir + "/" + chunkIndex;
// 解密文件(简化版)
byte[] decrypted = decrypt(file.getBytes());
// 保存分片(Tomcat 6.0文件操作)
Files.write(Paths.get(chunkPath), decrypted);
// 如果是最后一个分片,合并文件
if (chunkIndex == totalChunks - 1) {
mergeFile(fileId, tempDir);
}
return ResponseEntity.ok().build();
} catch (Exception e) {
return ResponseEntity.status(500).build();
}
}
// 合并文件(Tomcat 6.0兼容版)
private void mergeFile(String fileId, String tempDir) throws IOException {
File[] chunks = new File(tempDir).listFiles();
if (chunks == null || chunks.length == 0) return;
// 按分片序号排序
Arrays.sort(chunks, Comparator.comparingInt(f -> {
String name = f.getName();
return Integer.parseInt(name);
}));
// 读取原始文件名(从第一个分片中获取)
String fileName = "unknown"; // 实际应从数据库或第一个分片获取
String outputPath = uploadDir + "/" + fileName;
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
for (File chunk : chunks) {
Files.copy(chunk.toPath(), fos);
chunk.delete(); // 删除分片
}
}
// 删除临时目录
new File(tempDir).delete();
}
}
五、IE8兼容血泪史(附解决方案)
-
文件夹上传:
- 现代浏览器:``
- IE8:需要用ActiveXObject遍历文件系统(需要降低安全级别)
// IE8文件夹遍历(伪代码) function browseFolderIE8(path) { try { const fso = new ActiveXObject("Scripting.FileSystemObject"); const folder = fso.GetFolder(path); const files = new Enumerator(folder.files); for (; !files.atEnd(); files.moveNext()) { const file = files.item(); // 递归处理子文件夹... } } catch (e) { alert("请降低IE安全级别!"); } }
-
localStorage替代方案:
// IE8的userData存储(古老的黑科技) function saveToUserData(key, value) { const storage = document.createElement('div'); storage.id = 'ie8Storage'; storage.style.behavior = 'url(#default#userData)'; document.body.appendChild(storage); storage.setAttribute(key, value); storage.save('fileUploadProgress'); document.body.removeChild(storage); }
-
XMLHttpRequest降级:
// IE8的XHR实现 function createIE8XHR() { try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { alert("您的浏览器太古老了,请升级!"); return null; } } }
六、项目亮点(吹牛版)
- 全浏览器兼容:从IE8到Chrome120无缝支持(连龙芯浏览器都能跑)
- 军工级加密:AES+SM4双算法支持(导师看了都直呼专业)
- 企业级稳定性:MySQL+文件系统双进度存储(就算服务器爆炸也不丢数据)
- 信创环境适配:已通过龙芯浏览器测试(学校机房的国产CPU终于有用武之地了)
七、求职彩蛋
现在加入我的"毕业设计互助群"(QQ:374992201),即可获得:
- 完整项目源码(含IE8兼容补丁)
- 简历优化指导(群主亲授"如何把毕业设计吹成国家级项目")
- 内推机会(群内有多家IT公司HR潜伏)
特别提示:现在加群还能参与"找bug换红包"活动,每发现一个兼容性问题奖励5-20元(导师说这叫"众测")!
(最后小声说:这个项目还没完全跑通,但吹牛的功夫已经练得炉火纯青了…)
导入项目
导入到Eclipse:点击查看教程
导入到IDEA:点击查看教程
springboot统一配置:点击查看教程
工程
NOSQL
NOSQL示例不需要任何配置,可以直接访问测试
创建数据表
选择对应的数据表脚本,这里以SQL为例
修改数据库连接信息
访问页面进行测试
文件存储路径
up6/upload/年/月/日/guid/filename
效果预览
文件上传
文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。