# V-Hono 大文件分片上传系统 ## 概述 V-Hono 大文件分片上传系统是一个基于 V 语言和 Hono 框架构建的高性能文件上传解决方案。支持大文件分片上传、断点续传、秒传、自动合并等功能。 ## 主要特性 - ✅ **分片上传**:支持大文件分片上传,可配置分片大小 - ✅ **断点续传**:支持上传中断后继续上传 - ✅ **秒传功能**:基于文件哈希的秒传检测 - ✅ **自动合并**:所有分片上传完成后自动合并文件 - ✅ **文件去重**:基于文件哈希的去重机制 - ✅ **数据库存储**:使用 SQLite 存储文件元数据 - ✅ **RESTful API**:完整的 REST API 接口 - ✅ **Web 界面**:提供友好的 Web 上传界面 - ✅ **可配置参数**:支持自定义分片大小、文件大小限制等 - ✅ **双请求池系统**:活跃请求池 + 待请求池,智能并发控制 - ✅ **实时状态监控**:实时显示上传进度和请求池状态 ## 系统架构 ``` 前端 (Web/移动端) ↓ 双请求池系统 (活跃池 + 待请求池) ↓ V-Hono 服务器 ↓ 分片存储 (./uploads/chunks/) ↓ 文件合并 (./uploads/files/) ↓ 数据库 (SQLite) ``` ### 双请求池系统架构 ``` 请求池管理器 ├── 活跃请求池 (Map) │ ├── 请求1 (正在执行) │ ├── 请求2 (正在执行) │ └── ... (最多6个并发) └── 待请求池 (Array) ├── 请求7 (等待执行) ├── 请求8 (等待执行) └── ... (无限制) ``` **工作原理:** 1. 新请求到达时,检查活跃池是否已满 2. 如果未满,直接加入活跃池并执行 3. 如果已满,加入待请求池等待 4. 活跃池中的请求完成后,自动从待请求池取出下一个请求执行 5. 实时更新状态显示,提供完整的监控信息 ## 安装和运行 ### 1. 环境要求 - V 语言 0.4.x 或更高版本 - Windows/Linux/macOS ### 2. 启动服务器 ```bash # 克隆项目 git clone cd v-hono # 启动分片上传服务器 v run chunk_upload_example.v ``` 服务器将在 `http://localhost:8080` 启动。 ### 3. 访问 Web 界面 打开浏览器访问 `http://localhost:8080` 即可使用 Web 上传界面。 ## API 接口文档 ### 1. 分片上传接口 **接口地址:** `POST /upload/chunk` **请求参数:** - `file_hash` (string): 文件哈希值 - `chunk_index` (int): 分片索引,从 0 开始 - `filename` (string): 原始文件名 - `file_size` (int): 文件总大小(字节) - `chunk_size` (int): 分片大小(字节) - `chunk_hash` (string): 分片哈希值 - `chunk` (file): 分片文件数据 **响应格式:** 上传完成(自动合并): ```json { "success": true, "all_chunk_uploaded": true, "file_path": ".\\uploads\\files\\xxx.zip", "file_uuid": "xxx-xxx-xxx", "message": "File merged successfully" } ``` 上传中: ```json { "success": true, "chunk_index": 1, "all_chunk_uploaded": false, "message": "Chunk uploaded successfully" } ``` ### 2. 分片存在检查接口 **接口地址:** `GET /upload/chunk_exists` **请求参数:** - `file_hash` (string): 文件哈希值 - `chunk_index` (int): 分片索引 - `chunk_hash` (string): 分片哈希值 - `file_size` (int): 文件总大小 - `trunk_size` (int): 分片大小 **响应格式:** ```json { "exists": true, "all_chunk_uploaded": false } ``` ### 3. 上传状态查询接口 **接口地址:** `GET /upload/status` **请求参数:** - `file_hash` (string): 文件哈希值 **响应格式:** ```json { "file_hash": "xxx", "filename": "example.zip", "total_chunks": 0, "file_size": 1048576, "chunk_size": 2097152, "uploaded_chunks": [0, 1, 2], "status": "uploading", "created_at": 1640995200, "updated_at": 1640995300 } ``` ### 4. 已上传分片查询接口 **接口地址:** `GET /upload/chunks` **请求参数:** - `file_hash` (string): 文件哈希值 **响应格式:** ```json { "uploaded_chunks": [0, 1, 2], "total_chunks": 5, "completed": false } ``` ### 5. 文件管理接口 #### 获取所有文件 **接口地址:** `GET /api/files` #### 根据 UUID 获取文件 **接口地址:** `GET /api/files/{uuid}` #### 根据哈希获取文件 **接口地址:** `GET /api/files/hash/{hash}` #### 删除文件 **接口地址:** `DELETE /api/files/{uuid}` ## 配置说明 ### ChunkUploadConfig 配置项 ```v pub struct ChunkUploadConfig { chunk_size: int = 1024 * 1024 // 默认分片大小 1MB max_file_size: int = 1024 * 1024 * 1024 // 最大文件大小 1GB max_chunk_size: int = 10 * 1024 * 1024 // 最大分片大小 10MB temp_dir: string = './uploads/chunks' // 临时分片目录 upload_dir: string = './uploads/files' // 最终文件目录 cleanup_delay: int = 3600 // 清理延迟时间(秒) clear_chunks_on_complete: bool = false // 完成后是否清理分片 db_path: string = './uploads/files.db' // 数据库文件路径 } ``` ## 文件存储结构 ``` uploads/ ├── chunks/ # 分片文件目录 │ └── {file_hash}/ # 按文件哈希分组 │ └── {chunk_size}/ # 按分片大小分组 │ ├── chunk_0.part # 分片文件 │ ├── chunk_1.part │ └── ... ├── files/ # 最终文件目录 │ ├── {file_hash}.{ext} # 合并后的文件 │ └── ... └── files.db # SQLite 数据库文件 ``` ## 配置示例 ### 1. 使用默认配置 ```v // 使用默认配置创建分片上传管理器 mut upload_manager := hono.new_chunk_upload_manager(hono.ChunkUploadConfig{}) ``` ### 2. 使用自定义配置 ```v // 创建自定义配置 custom_config := hono.ChunkUploadConfig{ chunk_size: 5 * 1024 * 1024 // 5MB 默认分片大小 max_file_size: 5 * 1024 * 1024 * 1024 // 5GB 最大文件大小 max_chunk_size: 20 * 1024 * 1024 // 20MB 最大分片大小 temp_dir: './uploads/chunks' upload_dir: './uploads/files' cleanup_delay: 7200 // 2小时后清理临时文件 clear_chunks_on_complete: true // 上传完成后清理分片 db_path: './uploads/files.db' } // 使用自定义配置创建分片上传管理器 mut upload_manager := hono.new_chunk_upload_manager(custom_config) ``` ### 3. 获取配置信息 前端可以通过 `/api/upload-config` 接口获取后端配置: ```javascript // 获取后端配置信息 async function getUploadConfig() { const response = await fetch('/api/upload-config'); const config = await response.json(); console.log('最大分片大小:', config.max_chunk_size); return config; } ``` ## 双请求池系统详解 ### 概述 双请求池系统是 V-Hono 分片上传系统的核心性能优化组件,通过智能的并发控制机制,确保大文件上传的高效性和稳定性。 ### 核心组件 #### 1. 活跃请求池 (Active Pool) - **数据结构**:`Map` - **功能**:存储正在执行的请求 - **容量**:可配置,默认最大6个并发 - **特点**:实时执行,状态可追踪 #### 2. 待请求池 (Pending Pool) - **数据结构**:`Array` - **功能**:存储等待执行的请求 - **容量**:无限制,按需扩展 - **特点**:FIFO队列,自动调度 #### 3. 请求管理器 - **请求ID生成**:自动递增的唯一标识 - **智能调度**:根据活跃池状态自动分配 - **状态监控**:实时提供池状态信息 - **错误处理**:请求级别的错误隔离 ### 配置参数 ```javascript // 创建请求池实例 const requestPool = new RequestPool(6); // 最大并发6个 // 可配置参数 const config = { maxConcurrent: 6, // 最大并发数 enableLogging: true, // 启用日志 autoRetry: true, // 自动重试 retryCount: 3 // 重试次数 }; ``` ### 状态监控 ```javascript // 获取实时状态 const status = requestPool.getStatus(); console.log({ activeCount: status.activeCount, // 活跃请求数 pendingCount: status.pendingCount, // 等待请求数 maxConcurrent: status.maxConcurrent, // 最大并发数 utilization: (status.activeCount / status.maxConcurrent * 100).toFixed(1) + '%' // 利用率 }); ``` ### 性能优势 1. **并发控制**:避免服务器过载,保护系统稳定性 2. **资源优化**:合理利用网络带宽和服务器资源 3. **用户体验**:提供实时进度反馈和状态显示 4. **错误隔离**:单个请求失败不影响其他请求 5. **自动恢复**:支持断点续传和失败重试 ## 使用示例 ### 1. 前端 JavaScript 示例 ```javascript // 请求池管理类 class RequestPool { constructor(maxConcurrent = 6) { this.maxConcurrent = maxConcurrent; this.activePool = new Map(); // 活跃请求池 {requestId: Promise} this.pendingPool = []; // 待请求池 [{requestId, task, resolve, reject}] this.requestIdCounter = 0; } // 生成请求ID generateRequestId() { return ++this.requestIdCounter; } // 添加请求到池中 async addRequest(task) { const requestId = this.generateRequestId(); return new Promise((resolve, reject) => { const request = { requestId, task, resolve, reject }; // 如果活跃池未满,直接执行 if (this.activePool.size < this.maxConcurrent) { this.executeRequest(request); } else { // 否则加入待请求池 this.pendingPool.push(request); console.log(`请求 ${requestId} 加入待请求池,当前待请求数: ${this.pendingPool.length}`); } }); } // 执行请求 async executeRequest(request) { const { requestId, task, resolve, reject } = request; console.log(`开始执行请求 ${requestId},当前活跃请求数: ${this.activePool.size + 1}`); // 将请求添加到活跃池 const promise = task() .then(result => { console.log(`请求 ${requestId} 执行成功`); resolve(result); return result; }) .catch(error => { console.log(`请求 ${requestId} 执行失败:`, error); reject(error); throw error; }) .finally(() => { // 请求完成后从活跃池移除 this.activePool.delete(requestId); console.log(`请求 ${requestId} 完成,从活跃池移除,当前活跃请求数: ${this.activePool.size}`); // 检查待请求池,如果有待请求则执行 this.processPendingRequests(); }); this.activePool.set(requestId, promise); } // 处理待请求池中的请求 processPendingRequests() { while (this.pendingPool.length > 0 && this.activePool.size < this.maxConcurrent) { const request = this.pendingPool.shift(); this.executeRequest(request); console.log(`从待请求池取出请求 ${request.requestId} 执行,剩余待请求数: ${this.pendingPool.length}`); } } // 获取池状态 getStatus() { return { activeCount: this.activePool.size, pendingCount: this.pendingPool.length, maxConcurrent: this.maxConcurrent }; } // 清空所有池 clear() { this.activePool.clear(); this.pendingPool = []; console.log('请求池已清空'); } } // 计算文件哈希 async function calculateFileHash(file) { return new Promise(resolve => { const spark = new SparkMD5.ArrayBuffer(); const reader = new FileReader(); reader.onload = e => { spark.append(e.target.result); resolve(spark.end()); }; reader.readAsArrayBuffer(file); }); } // 上传分片 async function uploadChunk(file, chunk, { fileHash, chunkIndex, chunkHash }) { const form = new FormData(); form.append('file_hash', fileHash); form.append('chunk_index', chunkIndex); form.append('filename', file.name); form.append('file_size', file.size); form.append('chunk_size', CHUNK_SIZE); form.append('chunk_hash', chunkHash); form.append('chunk', chunk); const response = await fetch('/upload/chunk', { method: 'POST', body: form }); const result = await response.json(); if (result.all_chunk_uploaded) { console.log('上传完成,文件已合并:', result.file_path); return true; // 上传完成 } return false; // 继续上传 } // 使用请求池的分片上传主函数 async function uploadFileWithPool(file) { const fileHash = await calculateFileHash(file); const chunkSize = 2 * 1024 * 1024; // 2MB const totalChunks = Math.ceil(file.size / chunkSize); // 创建请求池实例 const requestPool = new RequestPool(6); // 创建所有分片的上传任务并添加到请求池 const uploadPromises = []; for (let i = 0; i < totalChunks; i++) { const chunkIndex = i; const promise = requestPool.addRequest(async () => { const chunk = file.slice(chunkIndex * chunkSize, (chunkIndex + 1) * chunkSize); const chunkHash = await calculateFileHash(chunk); return await uploadChunk(file, chunk, { fileHash, chunkIndex, chunkHash }); }); uploadPromises.push(promise); } // 等待所有上传任务完成 const results = await Promise.all(uploadPromises); // 清空请求池 requestPool.clear(); return results; } // 实时监控请求池状态 function monitorRequestPool(requestPool) { setInterval(() => { const status = requestPool.getStatus(); console.log(`请求池状态: ${status.activeCount}/${status.maxConcurrent} 活跃, ${status.pendingCount} 等待中`); // 更新UI显示 updatePoolStatusDisplay(status); }, 1000); } // 更新UI显示 function updatePoolStatusDisplay(status) { const statusElement = document.getElementById('poolStatus'); if (statusElement) { if (status.activeCount === 0 && status.pendingCount === 0) { statusElement.textContent = '请求池状态: 所有请求已完成'; } else { statusElement.textContent = `请求池状态: ${status.activeCount}/${status.maxConcurrent} 活跃, ${status.pendingCount} 等待中`; } } } ``` ### 2. cURL 示例 ```bash # 上传分片 curl -X POST http://localhost:8080/upload/chunk \ -F "file_hash=abc123" \ -F "chunk_index=0" \ -F "filename=large_file.zip" \ -F "file_size=10485760" \ -F "chunk_size=2097152" \ -F "chunk_hash=def456" \ -F "chunk=@chunk_0.part" # 检查分片是否存在 curl "http://localhost:8080/upload/chunk_exists?file_hash=abc123&chunk_index=0&chunk_hash=def456&file_size=10485760&trunk_size=2097152" ``` ## 请求池最佳实践 ### 1. 并发数配置建议 ```javascript // 根据网络环境调整并发数 const networkConfig = { 'fast': 8, // 高速网络:8个并发 'normal': 6, // 普通网络:6个并发 'slow': 4, // 慢速网络:4个并发 'mobile': 3 // 移动网络:3个并发 }; // 根据文件大小调整并发数 const fileSizeConfig = { 'small': 8, // 小文件(<10MB):8个并发 'medium': 6, // 中等文件(10MB-100MB):6个并发 'large': 4, // 大文件(>100MB):4个并发 }; ``` ### 2. 错误处理和重试机制 ```javascript class RobustRequestPool extends RequestPool { constructor(maxConcurrent = 6, maxRetries = 3) { super(maxConcurrent); this.maxRetries = maxRetries; } async addRequestWithRetry(task) { return this.addRequest(async () => { let lastError; for (let attempt = 1; attempt <= this.maxRetries; attempt++) { try { return await task(); } catch (error) { lastError = error; console.log(`请求失败,第${attempt}次重试:`, error.message); if (attempt < this.maxRetries) { // 指数退避 await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); } } } throw lastError; }); } } ``` ### 3. 性能监控和优化 ```javascript class MonitoredRequestPool extends RequestPool { constructor(maxConcurrent = 6) { super(maxConcurrent); this.metrics = { totalRequests: 0, successfulRequests: 0, failedRequests: 0, averageResponseTime: 0, startTime: Date.now() }; } async executeRequest(request) { const startTime = Date.now(); this.metrics.totalRequests++; try { const result = await super.executeRequest(request); this.metrics.successfulRequests++; this.updateMetrics(Date.now() - startTime); return result; } catch (error) { this.metrics.failedRequests++; throw error; } } updateMetrics(responseTime) { const { totalRequests, averageResponseTime } = this.metrics; this.metrics.averageResponseTime = (averageResponseTime * (totalRequests - 1) + responseTime) / totalRequests; } getMetrics() { const uptime = Date.now() - this.metrics.startTime; return { ...this.metrics, uptime, successRate: (this.metrics.successfulRequests / this.metrics.totalRequests * 100).toFixed(2) + '%', requestsPerSecond: (this.metrics.totalRequests / (uptime / 1000)).toFixed(2) }; } } ``` ### 4. 内存管理和清理 ```javascript class ManagedRequestPool extends RequestPool { constructor(maxConcurrent = 6, maxMemoryMB = 100) { super(maxConcurrent); this.maxMemory = maxMemoryMB * 1024 * 1024; // 转换为字节 this.memoryUsage = 0; } async addRequest(task) { // 检查内存使用情况 if (this.memoryUsage > this.maxMemory) { console.warn('内存使用过高,等待清理...'); await this.waitForMemory(); } return super.addRequest(task); } async waitForMemory() { return new Promise(resolve => { const checkMemory = () => { if (this.memoryUsage < this.maxMemory * 0.8) { resolve(); } else { setTimeout(checkMemory, 1000); } }; checkMemory(); }); } // 定期清理 startCleanup() { setInterval(() => { this.cleanup(); }, 30000); // 每30秒清理一次 } cleanup() { // 清理过期的请求记录 const now = Date.now(); // 实现清理逻辑... } } ``` ## 核心算法 ### 1. 分片合并判断 系统使用以下逻辑判断是否所有分片都已上传: ```v // 计算理论上需要的分片数量 expected_chunks := (expected_file_size + u64(chunk_size) - 1) / u64(chunk_size) // 只有当分片数量达到预期且总大小 >= 文件大小时,才认为上传完成 if chunk_count >= int(expected_chunks) && total_chunk_size >= expected_file_size { all_chunk_uploaded = true } ``` ### 2. 文件去重机制 - 基于文件哈希进行去重 - 相同哈希的文件只存储一份 - 支持同一文件的不同文件名 ### 3. 秒传检测 - 前端计算文件哈希 - 后端检查文件是否已存在 - 如果存在则直接返回文件信息,无需上传 ## 性能优化 ### 1. 内存管理 - 分片文件存储在磁盘,不占用大量内存 - 上传状态使用内存缓存,提高查询速度 - 支持配置清理策略,自动清理临时文件 ### 2. 并发处理 - 支持多用户同时上传 - 分片级别的并发控制 - 文件级别的锁机制 ### 3. 错误处理 - 网络异常自动重试 - 分片损坏自动重新上传 - 完整的错误日志记录 ## 监控和日志 ### 1. 调试日志 系统提供详细的调试日志,包括: - 分片上传状态 - 文件合并过程 - 错误信息追踪 ### 2. 性能监控 - 上传速度统计 - 分片成功率 - 系统资源使用情况 ## 故障排除 ### 1. 常见问题 **Q: 上传大文件时出现内存不足** A: 检查分片大小配置,建议设置为 1-5MB **Q: 分片上传后文件合并失败** A: 检查磁盘空间和文件权限 **Q: 秒传功能不工作** A: 确认前端哈希算法与后端一致 ### 2. 日志分析 查看服务器日志获取详细错误信息: ```bash # 启动时查看详细日志 v run chunk_upload_example.v ``` ## 扩展功能 ### 1. 云存储集成 可以扩展支持: - AWS S3 - 阿里云 OSS - 腾讯云 COS ### 2. 文件处理 可以添加: - 图片压缩 - 视频转码 - 文档预览 ### 3. 权限控制 可以集成: - 用户认证 - 文件权限 - 访问控制 ## 贡献指南 欢迎提交 Issue 和 Pull Request 来改进这个项目。 ## 许可证 本项目采用 MIT 许可证。