1. 项目背景与需求解析
作为一名长期奋战在企业级应用开发一线的.NET工程师,最近接手了一个颇具挑战性的文件管理系统开发需求。客户是一家大型制造企业的IT部门,他们现有的文档管理系统已经服役超过10年,面临着诸多痛点:
- 文件上传限制在2GB以内,无法满足设计部门大型3D模型文件(单个文件常达10-20GB)的传输需求
- 文件夹上传后层级结构丢失,导致技术文档、项目资料的管理混乱
- 公司部分老旧电脑仍在使用Windows 7+IE8环境,系统必须兼容这些"古董级"配置
- 敏感技术文档需要加密传输和存储,同时要求支持断点续传功能
经过深入沟通,我们最终确定了以下核心需求规格:
1.1 核心功能指标
-
大文件处理能力:
- 支持单个文件最大20GB的上传下载
- 文件夹上传保留完整目录结构(如
/研发中心/2024新项目/3D模型/发动机总成.stp) - 支持同时上传1000+个文件的任务队列
-
兼容性要求:
- 在IE8浏览器上通过Flash实现基础功能
- 现代浏览器使用HTML5 API获得更好体验
- 支持Windows 7及以上操作系统
-
安全与可靠性:
- 传输过程使用AES-256加密
- 支持服务端存储加密(可选SM4国密算法)
- 断点续传功能需在浏览器关闭/系统重启后仍能恢复
-
性能优化:
- 分片大小智能调整(2MB-10MB可配置)
- 并行上传分片(现代浏览器支持6个并发)
- 流式下载避免内存溢出
2. 技术选型与架构设计
2.1 前端技术栈
经过多轮技术验证,最终选定以下方案:
核心组件:
- WebUploader:百度开源的上传组件,支持Flash和HTML5双模式
- Flash模式用于IE8兼容
- HTML5模式提供更好的现代浏览器体验
- 内置分片上传、MD5校验、进度显示等核心功能
辅助库:
- SparkMD5:计算文件指纹用于断点续传标识
- CryptoJS:前端加密实现(AES/SM4)
- Vue3:构建管理界面(兼容性构建版本)
javascript复制// 前端加密配置示例
const encryptFile = (fileChunk, key) => {
const wordArray = CryptoJS.lib.WordArray.create(fileChunk);
const encrypted = CryptoJS.AES.encrypt(wordArray, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
};
2.2 后端技术栈
考虑到客户现有系统基于.NET Framework,我们选择:
核心框架:
- ASP.NET WebForm:与客户现有系统架构一致
- .NET Framework 4.7.2:平衡新特性和兼容性
关键技术点:
- 分片接收与合并
- 加密/解密服务
- 文件流式处理
- 数据库记录管理
csharp复制// 分片合并逻辑示例
public void MergeChunks(string fileMd5, string fileName)
{
var chunkDir = Path.Combine(ChunkTempPath, fileMd5);
var finalPath = Path.Combine(UploadRootPath, fileName);
// 确保目录存在
Directory.CreateDirectory(Path.GetDirectoryName(finalPath));
// 按序号合并分片
using (var fs = new FileStream(finalPath, FileMode.Create))
{
foreach (var chunk in GetSortedChunks(chunkDir))
{
var chunkData = File.ReadAllBytes(chunk);
var decrypted = AesDecrypt(chunkData, _encryptKey);
fs.Write(decrypted, 0, decrypted.Length);
}
}
// 清理临时分片
Directory.Delete(chunkDir, true);
}
2.3 数据库设计
使用SQL Server作为元数据存储,关键表结构如下:
文件记录表(FileRecord):
sql复制CREATE TABLE [dbo].[FileRecord](
[Id] [uniqueidentifier] PRIMARY KEY,
[FileName] [nvarchar](500) NOT NULL, -- 包含完整路径
[PhysicalPath] [nvarchar](500) NOT NULL,
[FileSize] [bigint] NOT NULL,
[Md5] [varchar](32) NOT NULL,
[EncryptType] [tinyint] NOT NULL, -- 1=AES, 2=SM4
[EncryptKey] [varchar](500) NOT NULL, -- 加密后的密钥
[CreateTime] [datetime] NOT NULL
);
分片记录表(FileChunk):
sql复制CREATE TABLE [dbo].[FileChunk](
[Id] [uniqueidentifier] PRIMARY KEY,
[FileMd5] [varchar](32) NOT NULL,
[ChunkNumber] [int] NOT NULL,
[ChunkSize] [int] NOT NULL,
[Status] [tinyint] NOT NULL, -- 0=等待,1=上传中,2=完成
[CreateTime] [datetime] NOT NULL,
FOREIGN KEY ([FileMd5]) REFERENCES [FileRecord]([Md5])
);
3. 核心功能实现细节
3.1 大文件分片上传
前端实现流程:
- 文件选择后计算MD5(WebWorker避免界面冻结)
- 向服务端查询已上传分片
- 按2MB分片大小切割文件
- 每个分片单独加密后上传
- 实时更新进度信息到localStorage
javascript复制// 分片上传核心逻辑
uploadChunk: function(file, chunkIndex) {
const blob = file.slice(chunkIndex * this.chunkSize,
(chunkIndex + 1) * this.chunkSize);
const reader = new FileReader();
reader.onload = (e) => {
const encrypted = this.encryptData(e.target.result, this.encryptKey);
const formData = new FormData();
formData.append('file', new Blob([encrypted]));
formData.append('chunk', chunkIndex);
formData.append('md5', this.fileMd5);
axios.post('/UploadHandler.ashx?action=chunk', formData).then(() => {
this.saveProgress(chunkIndex);
});
};
reader.readAsArrayBuffer(blob);
}
后端处理要点:
- 使用
HttpPostedFile接收分片 - 临时分片存储在专用目录(按MD5分类)
- 记录分片状态到数据库
- 合并时按顺序读取分片并解密
重要提示:IIS默认限制上传文件大小为30MB,需在web.config中调整:
xml复制<system.web> <httpRuntime maxRequestLength="2147483647" /> </system.web> <system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="4294967295" /> </requestFiltering> </security> </system.webServer>
3.2 文件夹结构保持
现代浏览器方案:
- 使用
<input type="file" webkitdirectory>获取文件夹 - 遍历
FileEntry对象获取完整路径 - 上传时携带相对路径信息
IE8兼容方案:
- 提供路径输入框让用户手动输入
code复制例如:研发部/2024项目/规格说明书/ - 前端模拟目录结构创建
- 后端根据路径字符串创建对应目录
csharp复制// 服务端路径处理
string relativePath = Request["path"]; // "部门/项目/文件.txt"
string physicalPath = Path.Combine(UploadRootPath, relativePath);
// 确保目录存在
Directory.CreateDirectory(Path.GetDirectoryName(physicalPath));
3.3 断点续传实现
关键技术点:
- 唯一标识:使用文件MD5作为标识(部分文件修改后需重新计算)
- 进度存储:
- 前端:localStorage记录已上传分片
- 后端:数据库记录分片状态
- 恢复流程:
- 初始化时查询已上传分片
- 跳过已上传部分
- 从第一个缺失分片继续
javascript复制// 恢复上传逻辑
resumeUpload: function(file) {
this.calculateMd5(file).then(md5 => {
this.fileMd5 = md5;
return axios.get('/UploadHandler.ashx', {
params: {
action: 'init',
md5: md5
}
});
}).then(response => {
const uploaded = response.data.uploadedChunks || [];
this.uploadedChunks = uploaded;
this.startUpload(file);
});
}
3.4 加密传输方案
双端加密流程:
- 前端生成随机密钥(或使用用户输入密码)
- 对每个分片进行AES-256加密
- 密钥通过RSA加密后传输到服务端
- 服务端存储加密后的密钥
- 下载时反向解密
csharp复制// 服务端加密存储
public string EncryptKey(string plainKey)
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
rsa.FromXmlString(ServerPublicKey);
byte[] encrypted = rsa.Encrypt(Encoding.UTF8.GetBytes(plainKey), true);
return Convert.ToBase64String(encrypted);
}
}
4. 兼容性处理方案
4.1 IE8特殊处理
必须条件:
- 部署
crossdomain.xml允许Flash跨域xml复制<cross-domain-policy> <allow-access-from domain="*" /> </cross-domain-policy> - 设置正确的MIME类型
code复制.swf -> application/x-shockwave-flash - 调整Flash安全设置(通过
allowscriptaccess参数)
上传限制:
- 分片大小建议2MB(过大会导致Flash崩溃)
- 禁用并行上传(IE8只支持单连接)
- 提供更频繁的进度反馈
4.2 现代浏览器优化
性能增强:
- 动态分片大小(根据网络质量调整)
javascript复制// 网络测速后调整分片大小 if (speed > 10 * 1024 * 1024) { // 10MB/s以上 this.chunkSize = 10 * 1024 * 1024; // 10MB } - 并行上传(最多6个并发)
- 使用Web Workers计算MD5不阻塞UI
5. 部署与运维指南
5.1 服务器配置
IIS优化建议:
- 调整上传超时时间
xml复制<system.web> <httpRuntime executionTimeout="3600" /> </system.web> - 启用静态内容压缩
- 增加应用池内存限制
存储规划:
- 临时分片目录与最终存储分开
- 建议使用独立磁盘存放上传文件
- 设置定期清理未完成上传任务(超过7天的临时文件)
5.2 高可用方案
负载均衡场景:
- 共享存储(如SAN或分布式文件系统)
- 数据库集中管理上传状态
- 配置Redis缓存分片信息减少数据库压力
csharp复制// 分布式环境下的分片锁定
public bool LockChunk(string fileMd5, int chunkNumber)
{
string cacheKey = $"upload:{fileMd5}:{chunkNumber}";
return RedisDb.StringSet(cacheKey, "locked",
TimeSpan.FromMinutes(30), When.NotExists);
}
6. 实测性能数据
在以下环境进行压力测试:
- 客户端:Windows 10 + Chrome 120
- 服务器:Azure D4s v3 (4核16GB)
- 网络:公司内网千兆环境
测试结果:
| 文件大小 | 文件数量 | 分片大小 | 上传时间 | 平均速度 |
|---|---|---|---|---|
| 1GB | 1 | 5MB | 25s | 40MB/s |
| 10GB | 1 | 5MB | 4m18s | 39.5MB/s |
| 20GB | 1 | 10MB | 8m52s | 38.2MB/s |
| 1GB | 100 | 2MB | 2m45s | 6.1MB/s |
注意:IE8环境下性能下降明显,20GB文件上传需约35分钟
7. 常见问题排查
7.1 上传中断问题
可能原因:
- 网络不稳定导致分片失败
- 服务器超时设置过短
- 客户端存储空间不足
解决方案:
mermaid复制graph TD
A[上传失败] --> B{错误类型?}
B -->|网络错误| C[重试3次]
B -->|服务器错误| D[检查IIS日志]
B -->|客户端错误| E[清理localStorage]
C --> F[仍失败?]
F -->|是| G[减小分片大小]
F -->|否| H[继续上传]
7.2 IE8无法上传
检查清单:
- Flash插件是否已安装并启用
- 是否已部署
crossdomain.xml - 控制台是否有安全策略错误
- 是否使用了HTTPS(IE8对混合内容限制严格)
7.3 大文件合并失败
内存优化技巧:
- 使用文件流而非内存流
- 分块读取和写入(每次处理100MB)
- 禁用病毒实时扫描对上传目录的检查
csharp复制// 低内存占用的合并方法
public void SafeMerge(string outputPath, List<string> chunkPaths)
{
using (var output = File.Create(outputPath))
{
foreach (var chunk in chunkPaths)
{
using (var input = File.OpenRead(chunk))
{
var buffer = new byte[1024 * 1024]; // 1MB缓冲区
int bytesRead;
while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, bytesRead);
}
}
}
}
}
8. 安全增强建议
8.1 密钥管理方案
企业级建议:
- 使用KMS(密钥管理服务)管理主密钥
- 每个文件使用独立的数据密钥
- 密钥轮换策略(每季度更新一次)
csharp复制// 集成Azure Key Vault示例
public async Task<string> GetEncryptionKeyAsync()
{
var secret = await keyVaultClient.GetSecretAsync(
"https://your-vault.vault.azure.net/",
"FileEncryptionKey");
return secret.Value;
}
8.2 防病毒处理
上传安全措施:
- 上传目录禁用脚本执行
xml复制<configuration> <system.webServer> <security> <requestFiltering> <fileExtensions> <add fileExtension=".exe" allowed="false" /> <add fileExtension=".dll" allowed="false" /> </fileExtensions> </requestFiltering> </security> </system.webServer> </configuration> - 集成病毒扫描引擎(如ClamAV)
- 文件类型白名单控制
9. 项目优化方向
9.1 性能提升
待优化点:
- 现代浏览器支持WebRTC实现P2P传输
- 服务端签名后支持直传OSS
- 增量同步(只上传修改部分)
9.2 功能扩展
企业需求:
- 与AD集成实现权限控制
- 文件预览服务(Office/PDF/图片)
- 自动化工作流集成
经过三个月的开发和测试,该系统已在客户生产环境稳定运行,成功处理了超过200TB的设计文件传输。期间最大的收获是:在兼容老旧系统与采用新技术之间找到平衡点,既满足了企业现有环境限制,又为未来升级预留了空间。