C#在.NET Core中实现大附件上传至服务器文件夹
·
文件管理系统毕业设计:从IE8兼容到百万梦想的奇幻漂流
大家好,我就是那个被IE8折磨到怀疑人生的通讯专业大三狗。本来以为做个文件管理系统毕业设计撑死两周搞定,没想到这项目直接让我体验了一把"全栈工程师"的酸爽人生。
需求分析:我的毕业设计规格比阿里云还高
老师看完需求文档后的表情be like:😏 → 🤔 → 😨 → 🤯
- 10G文件上传:我家NAS都没这么大容量
- IE8兼容:这浏览器比我表弟年龄还大
- 信创全家桶:红莲花浏览器?这名字听着就像要渡我成佛
- 断点续传持久化:要求比我的婚姻观还坚定(虽然我母胎solo)
- 文件夹层级保留:这功能百度网盘SVIP都要加钱吧?
技术选型:从现代到复古的时空穿越
// 前端大文件上传核心逻辑 (原生JS + WebUploader)
class MegaUploader {
constructor() {
this.uploader = null;
this.ieMode = this.detectIE(); // 检测IE版本
this.initUploader();
}
// IE版本检测(比检测新冠病毒还复杂)
detectIE() {
const ua = window.navigator.userAgent;
const msie = ua.indexOf('MSIE ');
if (msie > 0) return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
return false;
}
initUploader() {
if (this.ieMode && this.ieMode <= 9) {
// IE专属Flash方案(仿佛回到2010年)
this.initFlashUploader();
} else {
// 现代浏览器H5方案
this.initH5Uploader();
}
}
// H5上传方案(主推)
initH5Uploader() {
this.uploader = new WebUploader.create({
// 基础配置
dnd: '#filePicker',
disableGlobalDnd: true,
paste: document.body,
swf: '/static/Uploader.swf', // 给IE准备的备胎
server: '/api/upload',
fileSizeLimit: 10 * 1024 * 1024 * 1024, // 10GB
fileSingleSizeLimit: 5 * 1024 * 1024 * 1024, // 5GB单文件限制
// 分片配置
chunked: true,
chunkSize: 2 * 1024 * 1024, // 2MB每片
threads: 3, // 并发数
chunkRetry: 2, // 失败重试
// 文件夹上传配置
duplicate: true,
accept: {
title: 'Files',
extensions: '*',
mimeTypes: '*'
}
});
this.bindEvents();
}
// 绑定事件(比绑螃蟹还复杂)
bindEvents() {
// 文件添加事件
this.uploader.on('fileQueued', file => {
console.log(`文件 ${file.name} 已添加,大小 ${this.formatSize(file.size)}`);
this.saveToIndexedDB(file); // 持久化存储文件信息
});
// 分片上传前加密
this.uploader.on('uploadBeforeSend', (block, data) => {
data.chunk = block.chunk;
data.chunks = block.chunks;
data.fileId = block.file.id;
data.token = this.generateToken(block.file);
});
// 上传进度
this.uploader.on('uploadProgress', (file, percentage) => {
this.saveProgress(file, percentage); // 保存进度到本地
});
}
// 加密函数(假装很安全)
generateToken(file) {
return btoa(`${file.name}:${file.size}:${Date.now()}`);
}
// 更多方法...
}
后端C#处理:老将新传
// UploadHandler.ashx 文件分片处理 (C# WebForm)
public class UploadHandler : IHttpHandler {
public void ProcessRequest(HttpContext context) {
context.Response.ContentType = "application/json";
try {
// 验证Token
if (!ValidateToken(context.Request["token"])) {
context.Response.Write(JsonConvert.SerializeObject(new {
error = "非法请求"
}));
return;
}
var chunk = context.Request["chunk"] != null ? int.Parse(context.Request["chunk"]) : 0;
var chunks = context.Request["chunks"] != null ? int.Parse(context.Request["chunks"]) : 1;
var fileId = context.Request["fileId"];
var fileName = context.Request["name"] ?? "unknown";
// 处理文件分片
HttpPostedFile file = context.Request.Files[0];
string tempPath = Path.Combine(ServerConfig.TempDir, fileId);
// 确保目录存在
Directory.CreateDirectory(tempPath);
// 写入分片文件
string chunkPath = Path.Combine(tempPath, chunk.ToString());
using (var fs = File.OpenWrite(chunkPath)) {
file.InputStream.CopyTo(fs);
}
// 如果是最后一个分片则合并
if (chunk == chunks - 1) {
MergeFiles(tempPath, fileName, chunks);
EncryptAndSaveToOSS(fileName); // 加密后存OSS
}
context.Response.Write(JsonConvert.SerializeObject(new {
success = true,
chunk = chunk
}));
} catch (Exception ex) {
context.Response.Write(JsonConvert.SerializeObject(new {
error = ex.Message
}));
}
}
// 合并分片文件
private void MergeFiles(string tempPath, string fileName, int totalChunks) {
string destPath = Path.Combine(ServerConfig.UploadDir, fileName);
using (var destStream = File.Create(destPath)) {
for (int i = 0; i < totalChunks; i++) {
string chunkPath = Path.Combine(tempPath, i.ToString());
using (var sourceStream = File.OpenRead(chunkPath)) {
sourceStream.CopyTo(destStream);
}
File.Delete(chunkPath);
}
}
Directory.Delete(tempPath);
}
// 更多方法...
}
数据库设计:比相亲简历还详细
-- SQL Server 数据库表设计
CREATE TABLE [dbo].[FileRecords] (
[Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
[FileName] NVARCHAR(255) NOT NULL,
[FileSize] BIGINT NOT NULL,
[FileHash] VARCHAR(64) NOT NULL,
[StoragePath] NVARCHAR(512) NOT NULL,
[EncryptKey] VARCHAR(512) NOT NULL, -- 加密密钥
[UploadTime] DATETIME DEFAULT GETDATE(),
[UserId] INT NOT NULL, -- 关联用户
[FolderId] UNIQUEIDENTIFIER NULL -- 文件夹关联
);
CREATE TABLE [dbo].[UploadProgress] (
[FileId] UNIQUEIDENTIFIER PRIMARY KEY,
[UserId] INT NOT NULL,
[TotalChunks] INT NOT NULL,
[CompletedChunks] INT NOT NULL,
[LastUpdate] DATETIME DEFAULT GETDATE(),
[BrowserInfo] NVARCHAR(MAX) NULL -- 存储浏览器指纹信息
);
遇到的坑:比我的发际线还高
-
IE8兼容地狱:
- Flash需要用户手动点击才能激活
- 控制台输出要用
window.console.log判断存在性 - AJAX请求要特殊处理
-
信创浏览器适配:
- 龙芯浏览器的内核是Mozilla?还是Chromium?不,它是薛定谔的内核!
- 红莲花浏览器居然不支持ES6的箭头函数
-
持久化存储方案:
- IE8只能用userData或Cookie
- 现代浏览器用IndexedDB
- 信创浏览器?看心情选择存储方式
-
文件夹上传:
- WebUploader的文件夹上传在IE下是废的
- 要自己用递归处理FileSystem API
求职宣言:全栈苦力在线接单
通过这个项目我掌握了:
- 考古学(IE8适配)
- 密码学(虽然只是调库)
- 心理学(调试时的自我疏导)
- 金融学(算红包提成)
求工作推荐!要求:
- 不用兼容IE6(这是我的底线)
- 老板不要求"这个需求很简单"
- 最好能报销生发液
P.S. 那个QQ群红包是真的!我已经靠提成实现奶茶自由了!(虽然还没收到钱)
实用代码片段:文件夹上传处理
// 文件夹上传处理 (现代浏览器)
function handleFolderUpload(folder) {
const entries = [];
// 递归读取文件夹内容
function readDirectory(directory, path = '') {
const reader = directory.createReader();
reader.readEntries(entries => {
entries.forEach(entry => {
if (entry.isFile) {
entry.file(file => {
file.fullPath = path + file.name; // 保留路径信息
entries.push(file);
});
} else if (entry.isDirectory) {
readDirectory(entry, path + entry.name + '/');
}
});
});
}
readDirectory(folder);
// 上传所有文件
return Promise.all(entries.map(file => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
formData.append('path', file.fullPath);
fetch('/api/upload', {
method: 'POST',
body: formData
}).then(resolve).catch(reject);
});
}));
}
// IE下的替代方案(使用ActiveX)
function ieHandleFolderUpload() {
// 这个代码太血腥了,就不展示了
alert('请升级浏览器到IE10以上或使用现代浏览器');
}
总结:毕业设计改变人生
这个项目教会我:
- 永远不要在需求评审时说"这个简单"
- 信创浏览器是检验真爱的新标准
- 10G文件上传前记得清理C盘
- QQ群红包可能是新型庞氏骗局(但我还是想要)
(真诚求工作推荐,会修打印机,能装Windows 7那种)
设置框架
安装.NET Framework 4.7.2
https://dotnet.microsoft.com/en-us/download/dotnet-framework/net472
框架选择4.7.2
添加3rd引用

编译项目

NOSQL
NOSQL无需任何配置可直接访问页面进行测试
SQL
使用IIS
大文件上传测试推荐使用IIS以获取更高性能。
使用IIS Express
小文件上传测试可以使用IIS Express
创建数据库

配置数据库连接信息

检查数据库配置

访问页面进行测试

相关参考:
文件保存位置,
效果预览
文件上传

文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
下载完整示例
更多推荐

所有评论(0)