feat: chunk file upload add quest pool
This commit is contained in:
@@ -21,6 +21,9 @@
|
|||||||
- ⚡ **秒传功能**:基于文件哈希的秒传检测
|
- ⚡ **秒传功能**:基于文件哈希的秒传检测
|
||||||
- 🗂️ **文件去重**:基于文件哈希的去重机制
|
- 🗂️ **文件去重**:基于文件哈希的去重机制
|
||||||
- 💾 **数据库存储**:使用 SQLite 存储文件元数据
|
- 💾 **数据库存储**:使用 SQLite 存储文件元数据
|
||||||
|
- 🚀 **双请求池系统**:活跃请求池 + 待请求池,支持并发控制和智能调度
|
||||||
|
- ⚡ **并发优化**:可配置最大并发数,自动管理请求队列
|
||||||
|
- 📊 **实时状态监控**:实时显示活跃请求数和等待请求数
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
@@ -370,6 +373,10 @@ v-hono/
|
|||||||
|
|
||||||
V-Hono 大文件分片上传系统是一个基于 V 语言和 Hono 框架构建的高性能文件上传解决方案。支持大文件分片上传、断点续传、秒传、自动合并等功能。
|
V-Hono 大文件分片上传系统是一个基于 V 语言和 Hono 框架构建的高性能文件上传解决方案。支持大文件分片上传、断点续传、秒传、自动合并等功能。
|
||||||
|
|
||||||
|
**相关文档:**
|
||||||
|
- [分片上传系统详解](README_CHUNK_UPLOAD.md) - 完整的分片上传功能文档
|
||||||
|
- [双请求池系统详解](README_REQUEST_POOL.md) - 请求池系统的设计和实现
|
||||||
|
|
||||||
### 主要特性
|
### 主要特性
|
||||||
|
|
||||||
- ✅ **分片上传**:支持大文件分片上传,默认分片大小 1MB
|
- ✅ **分片上传**:支持大文件分片上传,默认分片大小 1MB
|
||||||
@@ -380,6 +387,8 @@ V-Hono 大文件分片上传系统是一个基于 V 语言和 Hono 框架构建
|
|||||||
- ✅ **数据库存储**:使用 SQLite 存储文件元数据
|
- ✅ **数据库存储**:使用 SQLite 存储文件元数据
|
||||||
- ✅ **RESTful API**:完整的 REST API 接口
|
- ✅ **RESTful API**:完整的 REST API 接口
|
||||||
- ✅ **Web 界面**:提供友好的 Web 上传界面
|
- ✅ **Web 界面**:提供友好的 Web 上传界面
|
||||||
|
- ✅ **双请求池系统**:活跃请求池 + 待请求池,智能并发控制
|
||||||
|
- ✅ **实时状态监控**:实时显示上传进度和请求池状态
|
||||||
|
|
||||||
### 系统架构
|
### 系统架构
|
||||||
|
|
||||||
@@ -565,6 +574,99 @@ uploads/
|
|||||||
#### 1. 前端 JavaScript 示例
|
#### 1. 前端 JavaScript 示例
|
||||||
|
|
||||||
```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) {
|
async function calculateFileHash(file) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
@@ -604,26 +706,40 @@ async function uploadChunk(file, chunk, { fileHash, chunkIndex, chunkHash }) {
|
|||||||
return false; // 继续上传
|
return false; // 继续上传
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分片上传主函数
|
// 使用请求池的分片上传主函数
|
||||||
async function uploadFile(file) {
|
async function uploadFileWithPool(file) {
|
||||||
const fileHash = await calculateFileHash(file);
|
const fileHash = await calculateFileHash(file);
|
||||||
const chunkSize = 2 * 1024 * 1024; // 2MB
|
const chunkSize = 2 * 1024 * 1024; // 2MB
|
||||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
|
||||||
|
// 创建请求池实例
|
||||||
|
const requestPool = new RequestPool(6);
|
||||||
|
|
||||||
|
// 创建所有分片的上传任务并添加到请求池
|
||||||
|
const uploadPromises = [];
|
||||||
for (let i = 0; i < totalChunks; i++) {
|
for (let i = 0; i < totalChunks; i++) {
|
||||||
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
|
const chunkIndex = i;
|
||||||
const chunkHash = await calculateFileHash(chunk);
|
const promise = requestPool.addRequest(async () => {
|
||||||
|
const chunk = file.slice(chunkIndex * chunkSize, (chunkIndex + 1) * chunkSize);
|
||||||
const completed = await uploadChunk(file, chunk, {
|
const chunkHash = await calculateFileHash(chunk);
|
||||||
fileHash,
|
|
||||||
chunkIndex: i,
|
return await uploadChunk(file, chunk, {
|
||||||
chunkHash
|
fileHash,
|
||||||
|
chunkIndex,
|
||||||
|
chunkHash
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (completed) {
|
uploadPromises.push(promise);
|
||||||
break; // 上传完成
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 等待所有上传任务完成
|
||||||
|
const results = await Promise.all(uploadPromises);
|
||||||
|
|
||||||
|
// 清空请求池
|
||||||
|
requestPool.clear();
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -680,17 +796,49 @@ if chunk_count >= int(expected_chunks) && total_chunk_size >= expected_file_size
|
|||||||
- 上传状态使用内存缓存,提高查询速度
|
- 上传状态使用内存缓存,提高查询速度
|
||||||
- 支持配置清理策略,自动清理临时文件
|
- 支持配置清理策略,自动清理临时文件
|
||||||
|
|
||||||
#### 2. 并发处理
|
#### 2. 双请求池系统
|
||||||
|
|
||||||
|
**架构设计:**
|
||||||
|
- **活跃请求池**:存储正在执行的请求,使用 Map 结构 `{requestId: Promise}`
|
||||||
|
- **待请求池**:存储等待执行的请求,使用数组结构 `[{requestId, task, resolve, reject}]`
|
||||||
|
- **智能调度**:当活跃池未满时直接执行,满时加入待请求池
|
||||||
|
- **自动管理**:请求完成后自动从活跃池移除,并处理待请求池中的请求
|
||||||
|
|
||||||
|
**核心特性:**
|
||||||
|
- **可配置并发数**:默认最大并发数为 6,可根据服务器性能调整
|
||||||
|
- **请求ID管理**:每个请求都有唯一ID,便于追踪和调试
|
||||||
|
- **状态实时更新**:实时显示活跃请求数和等待请求数
|
||||||
|
- **自动清理**:上传完成后自动清空请求池
|
||||||
|
|
||||||
|
**使用示例:**
|
||||||
|
```javascript
|
||||||
|
// 创建请求池实例(最大并发6个)
|
||||||
|
const requestPool = new RequestPool(6);
|
||||||
|
|
||||||
|
// 添加请求到池中
|
||||||
|
const promise = requestPool.addRequest(async () => {
|
||||||
|
// 上传分片的逻辑
|
||||||
|
return await uploadChunk(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取池状态
|
||||||
|
const status = requestPool.getStatus();
|
||||||
|
console.log(`活跃: ${status.activeCount}/${status.maxConcurrent}, 等待: ${status.pendingCount}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 并发处理
|
||||||
|
|
||||||
- 支持多用户同时上传
|
- 支持多用户同时上传
|
||||||
- 分片级别的并发控制
|
- 分片级别的并发控制
|
||||||
- 文件级别的锁机制
|
- 文件级别的锁机制
|
||||||
|
- 基于请求池的智能并发管理
|
||||||
|
|
||||||
#### 3. 错误处理
|
#### 4. 错误处理
|
||||||
|
|
||||||
- 网络异常自动重试
|
- 网络异常自动重试
|
||||||
- 分片损坏自动重新上传
|
- 分片损坏自动重新上传
|
||||||
- 完整的错误日志记录
|
- 完整的错误日志记录
|
||||||
|
- 请求池级别的错误隔离
|
||||||
|
|
||||||
### 监控和日志
|
### 监控和日志
|
||||||
|
|
||||||
@@ -751,6 +899,12 @@ v run chunk_upload_example.v
|
|||||||
- 文件权限
|
- 文件权限
|
||||||
- 访问控制
|
- 访问控制
|
||||||
|
|
||||||
|
#### 4. 双请求池系统
|
||||||
|
|
||||||
|
详细文档请参考:
|
||||||
|
- [双请求池系统详解](README_REQUEST_POOL.md) - 完整的系统设计和实现指南
|
||||||
|
- [分片上传系统](README_CHUNK_UPLOAD.md) - 包含请求池的分片上传完整方案
|
||||||
|
|
||||||
## 性能特性
|
## 性能特性
|
||||||
|
|
||||||
- **混合路由算法**:静态路由 O(1) 查找,动态路由正则匹配
|
- **混合路由算法**:静态路由 O(1) 查找,动态路由正则匹配
|
||||||
|
|||||||
+421
-12
@@ -6,7 +6,7 @@ V-Hono 大文件分片上传系统是一个基于 V 语言和 Hono 框架构建
|
|||||||
|
|
||||||
## 主要特性
|
## 主要特性
|
||||||
|
|
||||||
- ✅ **分片上传**:支持大文件分片上传,默认分片大小 2MB
|
- ✅ **分片上传**:支持大文件分片上传,可配置分片大小
|
||||||
- ✅ **断点续传**:支持上传中断后继续上传
|
- ✅ **断点续传**:支持上传中断后继续上传
|
||||||
- ✅ **秒传功能**:基于文件哈希的秒传检测
|
- ✅ **秒传功能**:基于文件哈希的秒传检测
|
||||||
- ✅ **自动合并**:所有分片上传完成后自动合并文件
|
- ✅ **自动合并**:所有分片上传完成后自动合并文件
|
||||||
@@ -14,12 +14,17 @@ V-Hono 大文件分片上传系统是一个基于 V 语言和 Hono 框架构建
|
|||||||
- ✅ **数据库存储**:使用 SQLite 存储文件元数据
|
- ✅ **数据库存储**:使用 SQLite 存储文件元数据
|
||||||
- ✅ **RESTful API**:完整的 REST API 接口
|
- ✅ **RESTful API**:完整的 REST API 接口
|
||||||
- ✅ **Web 界面**:提供友好的 Web 上传界面
|
- ✅ **Web 界面**:提供友好的 Web 上传界面
|
||||||
|
- ✅ **可配置参数**:支持自定义分片大小、文件大小限制等
|
||||||
|
- ✅ **双请求池系统**:活跃请求池 + 待请求池,智能并发控制
|
||||||
|
- ✅ **实时状态监控**:实时显示上传进度和请求池状态
|
||||||
|
|
||||||
## 系统架构
|
## 系统架构
|
||||||
|
|
||||||
```
|
```
|
||||||
前端 (Web/移动端)
|
前端 (Web/移动端)
|
||||||
↓
|
↓
|
||||||
|
双请求池系统 (活跃池 + 待请求池)
|
||||||
|
↓
|
||||||
V-Hono 服务器
|
V-Hono 服务器
|
||||||
↓
|
↓
|
||||||
分片存储 (./uploads/chunks/)
|
分片存储 (./uploads/chunks/)
|
||||||
@@ -29,6 +34,27 @@ V-Hono 服务器
|
|||||||
数据库 (SQLite)
|
数据库 (SQLite)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 双请求池系统架构
|
||||||
|
|
||||||
|
```
|
||||||
|
请求池管理器
|
||||||
|
├── 活跃请求池 (Map<requestId, Promise>)
|
||||||
|
│ ├── 请求1 (正在执行)
|
||||||
|
│ ├── 请求2 (正在执行)
|
||||||
|
│ └── ... (最多6个并发)
|
||||||
|
└── 待请求池 (Array<Request>)
|
||||||
|
├── 请求7 (等待执行)
|
||||||
|
├── 请求8 (等待执行)
|
||||||
|
└── ... (无限制)
|
||||||
|
```
|
||||||
|
|
||||||
|
**工作原理:**
|
||||||
|
1. 新请求到达时,检查活跃池是否已满
|
||||||
|
2. 如果未满,直接加入活跃池并执行
|
||||||
|
3. 如果已满,加入待请求池等待
|
||||||
|
4. 活跃池中的请求完成后,自动从待请求池取出下一个请求执行
|
||||||
|
5. 实时更新状态显示,提供完整的监控信息
|
||||||
|
|
||||||
## 安装和运行
|
## 安装和运行
|
||||||
|
|
||||||
### 1. 环境要求
|
### 1. 环境要求
|
||||||
@@ -170,6 +196,7 @@ v run chunk_upload_example.v
|
|||||||
pub struct ChunkUploadConfig {
|
pub struct ChunkUploadConfig {
|
||||||
chunk_size: int = 1024 * 1024 // 默认分片大小 1MB
|
chunk_size: int = 1024 * 1024 // 默认分片大小 1MB
|
||||||
max_file_size: int = 1024 * 1024 * 1024 // 最大文件大小 1GB
|
max_file_size: int = 1024 * 1024 * 1024 // 最大文件大小 1GB
|
||||||
|
max_chunk_size: int = 10 * 1024 * 1024 // 最大分片大小 10MB
|
||||||
temp_dir: string = './uploads/chunks' // 临时分片目录
|
temp_dir: string = './uploads/chunks' // 临时分片目录
|
||||||
upload_dir: string = './uploads/files' // 最终文件目录
|
upload_dir: string = './uploads/files' // 最终文件目录
|
||||||
cleanup_delay: int = 3600 // 清理延迟时间(秒)
|
cleanup_delay: int = 3600 // 清理延迟时间(秒)
|
||||||
@@ -194,11 +221,208 @@ uploads/
|
|||||||
└── files.db # SQLite 数据库文件
|
└── 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<requestId, Promise>`
|
||||||
|
- **功能**:存储正在执行的请求
|
||||||
|
- **容量**:可配置,默认最大6个并发
|
||||||
|
- **特点**:实时执行,状态可追踪
|
||||||
|
|
||||||
|
#### 2. 待请求池 (Pending Pool)
|
||||||
|
- **数据结构**:`Array<Request>`
|
||||||
|
- **功能**:存储等待执行的请求
|
||||||
|
- **容量**:无限制,按需扩展
|
||||||
|
- **特点**: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 示例
|
### 1. 前端 JavaScript 示例
|
||||||
|
|
||||||
```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) {
|
async function calculateFileHash(file) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
@@ -238,24 +462,61 @@ async function uploadChunk(file, chunk, { fileHash, chunkIndex, chunkHash }) {
|
|||||||
return false; // 继续上传
|
return false; // 继续上传
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分片上传主函数
|
// 使用请求池的分片上传主函数
|
||||||
async function uploadFile(file) {
|
async function uploadFileWithPool(file) {
|
||||||
const fileHash = await calculateFileHash(file);
|
const fileHash = await calculateFileHash(file);
|
||||||
const chunkSize = 2 * 1024 * 1024; // 2MB
|
const chunkSize = 2 * 1024 * 1024; // 2MB
|
||||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
|
||||||
|
// 创建请求池实例
|
||||||
|
const requestPool = new RequestPool(6);
|
||||||
|
|
||||||
|
// 创建所有分片的上传任务并添加到请求池
|
||||||
|
const uploadPromises = [];
|
||||||
for (let i = 0; i < totalChunks; i++) {
|
for (let i = 0; i < totalChunks; i++) {
|
||||||
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
|
const chunkIndex = i;
|
||||||
const chunkHash = await calculateFileHash(chunk);
|
const promise = requestPool.addRequest(async () => {
|
||||||
|
const chunk = file.slice(chunkIndex * chunkSize, (chunkIndex + 1) * chunkSize);
|
||||||
const completed = await uploadChunk(file, chunk, {
|
const chunkHash = await calculateFileHash(chunk);
|
||||||
fileHash,
|
|
||||||
chunkIndex: i,
|
return await uploadChunk(file, chunk, {
|
||||||
chunkHash
|
fileHash,
|
||||||
|
chunkIndex,
|
||||||
|
chunkHash
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (completed) {
|
uploadPromises.push(promise);
|
||||||
break; // 上传完成
|
}
|
||||||
|
|
||||||
|
// 等待所有上传任务完成
|
||||||
|
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} 等待中`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,6 +539,154 @@ curl -X POST http://localhost:8080/upload/chunk \
|
|||||||
curl "http://localhost:8080/upload/chunk_exists?file_hash=abc123&chunk_index=0&chunk_hash=def456&file_size=10485760&trunk_size=2097152"
|
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. 分片合并判断
|
### 1. 分片合并判断
|
||||||
|
|||||||
@@ -0,0 +1,464 @@
|
|||||||
|
# V-Hono 双请求池系统
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
双请求池系统是 V-Hono 框架中用于管理并发请求的核心组件,特别针对大文件分片上传场景进行了优化。该系统通过智能的并发控制机制,确保系统在高负载下的稳定性和性能。
|
||||||
|
|
||||||
|
## 系统架构
|
||||||
|
|
||||||
|
### 核心组件
|
||||||
|
|
||||||
|
```
|
||||||
|
双请求池系统
|
||||||
|
├── 活跃请求池 (Active Pool)
|
||||||
|
│ ├── 数据结构: Map<requestId, Promise>
|
||||||
|
│ ├── 容量: 可配置 (默认6个并发)
|
||||||
|
│ └── 功能: 存储正在执行的请求
|
||||||
|
└── 待请求池 (Pending Pool)
|
||||||
|
├── 数据结构: Array<Request>
|
||||||
|
├── 容量: 无限制
|
||||||
|
└── 功能: 存储等待执行的请求
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工作流程
|
||||||
|
|
||||||
|
1. **请求到达**:新请求到达时,系统生成唯一请求ID
|
||||||
|
2. **池状态检查**:检查活跃池是否已满
|
||||||
|
3. **智能分配**:
|
||||||
|
- 如果活跃池未满:直接加入活跃池并执行
|
||||||
|
- 如果活跃池已满:加入待请求池等待
|
||||||
|
4. **自动调度**:活跃池中的请求完成后,自动从待请求池取出下一个请求执行
|
||||||
|
5. **状态更新**:实时更新池状态,提供监控信息
|
||||||
|
|
||||||
|
## 核心特性
|
||||||
|
|
||||||
|
### 1. 智能并发控制
|
||||||
|
- **可配置并发数**:根据服务器性能和网络环境调整
|
||||||
|
- **自动负载均衡**:避免服务器过载
|
||||||
|
- **资源优化**:合理利用网络带宽和服务器资源
|
||||||
|
|
||||||
|
### 2. 请求生命周期管理
|
||||||
|
- **唯一标识**:每个请求都有唯一的requestId
|
||||||
|
- **状态追踪**:实时追踪请求的执行状态
|
||||||
|
- **自动清理**:请求完成后自动清理资源
|
||||||
|
|
||||||
|
### 3. 错误处理和隔离
|
||||||
|
- **错误隔离**:单个请求失败不影响其他请求
|
||||||
|
- **自动重试**:支持配置重试机制
|
||||||
|
- **错误传播**:错误信息准确传递给调用方
|
||||||
|
|
||||||
|
### 4. 实时监控
|
||||||
|
- **状态查询**:实时获取池状态信息
|
||||||
|
- **性能指标**:提供详细的性能监控数据
|
||||||
|
- **日志记录**:完整的操作日志记录
|
||||||
|
|
||||||
|
## 实现代码
|
||||||
|
|
||||||
|
### RequestPool 类
|
||||||
|
|
||||||
|
```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('请求池已清空');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 基本使用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 创建请求池实例
|
||||||
|
const requestPool = new RequestPool(6);
|
||||||
|
|
||||||
|
// 添加请求
|
||||||
|
const promise = requestPool.addRequest(async () => {
|
||||||
|
// 执行具体的任务
|
||||||
|
return await someAsyncTask();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取状态
|
||||||
|
const status = requestPool.getStatus();
|
||||||
|
console.log(`活跃: ${status.activeCount}/${status.maxConcurrent}, 等待: ${status.pendingCount}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分片上传场景
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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 calculateChunkHash(chunk);
|
||||||
|
|
||||||
|
return await uploadChunk(file, chunk, {
|
||||||
|
fileHash,
|
||||||
|
chunkIndex,
|
||||||
|
chunkHash
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadPromises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有上传任务完成
|
||||||
|
const results = await Promise.all(uploadPromises);
|
||||||
|
|
||||||
|
// 清空请求池
|
||||||
|
requestPool.clear();
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实时监控
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 实时监控请求池状态
|
||||||
|
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} 等待中`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置建议
|
||||||
|
|
||||||
|
### 并发数配置
|
||||||
|
|
||||||
|
```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个并发
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能优化配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 高性能配置
|
||||||
|
const highPerformanceConfig = {
|
||||||
|
maxConcurrent: 8,
|
||||||
|
enableLogging: false,
|
||||||
|
autoRetry: true,
|
||||||
|
retryCount: 3,
|
||||||
|
retryDelay: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
// 稳定性配置
|
||||||
|
const stabilityConfig = {
|
||||||
|
maxConcurrent: 4,
|
||||||
|
enableLogging: true,
|
||||||
|
autoRetry: true,
|
||||||
|
retryCount: 5,
|
||||||
|
retryDelay: 2000
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 扩展功能
|
||||||
|
|
||||||
|
### 1. 错误重试机制
|
||||||
|
|
||||||
|
```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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 性能监控
|
||||||
|
|
||||||
|
```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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 优先级队列
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class PriorityRequestPool extends RequestPool {
|
||||||
|
constructor(maxConcurrent = 6) {
|
||||||
|
super(maxConcurrent);
|
||||||
|
this.priorityQueue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
addPriorityRequest(task, priority = 0) {
|
||||||
|
const requestId = this.generateRequestId();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = {
|
||||||
|
requestId,
|
||||||
|
task,
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
priority
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.activePool.size < this.maxConcurrent) {
|
||||||
|
this.executeRequest(request);
|
||||||
|
} else {
|
||||||
|
// 按优先级插入待请求池
|
||||||
|
this.insertPriorityRequest(request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
insertPriorityRequest(request) {
|
||||||
|
let insertIndex = this.pendingPool.length;
|
||||||
|
for (let i = 0; i < this.pendingPool.length; i++) {
|
||||||
|
if (request.priority > this.pendingPool[i].priority) {
|
||||||
|
insertIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.pendingPool.splice(insertIndex, 0, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 合理设置并发数
|
||||||
|
- 根据服务器性能调整并发数
|
||||||
|
- 考虑网络带宽限制
|
||||||
|
- 监控系统资源使用情况
|
||||||
|
|
||||||
|
### 2. 错误处理
|
||||||
|
- 实现适当的重试机制
|
||||||
|
- 记录详细的错误日志
|
||||||
|
- 提供用户友好的错误信息
|
||||||
|
|
||||||
|
### 3. 性能监控
|
||||||
|
- 实时监控请求池状态
|
||||||
|
- 收集性能指标数据
|
||||||
|
- 根据监控数据优化配置
|
||||||
|
|
||||||
|
### 4. 资源管理
|
||||||
|
- 及时清理完成的请求
|
||||||
|
- 避免内存泄漏
|
||||||
|
- 定期检查资源使用情况
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **请求堆积**
|
||||||
|
- 检查并发数设置是否合理
|
||||||
|
- 监控网络连接状态
|
||||||
|
- 检查服务器性能
|
||||||
|
|
||||||
|
2. **内存泄漏**
|
||||||
|
- 确保请求完成后正确清理
|
||||||
|
- 检查是否有循环引用
|
||||||
|
- 定期重启服务
|
||||||
|
|
||||||
|
3. **性能下降**
|
||||||
|
- 调整并发数配置
|
||||||
|
- 检查网络带宽
|
||||||
|
- 优化请求处理逻辑
|
||||||
|
|
||||||
|
### 调试技巧
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 启用详细日志
|
||||||
|
const debugPool = new RequestPool(6);
|
||||||
|
debugPool.enableDebugLogging = true;
|
||||||
|
|
||||||
|
// 监控关键指标
|
||||||
|
setInterval(() => {
|
||||||
|
const status = debugPool.getStatus();
|
||||||
|
const metrics = debugPool.getMetrics();
|
||||||
|
console.log('状态:', status);
|
||||||
|
console.log('指标:', metrics);
|
||||||
|
}, 5000);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
双请求池系统是 V-Hono 框架中重要的性能优化组件,通过智能的并发控制和资源管理,为大文件上传等场景提供了稳定、高效的解决方案。通过合理配置和最佳实践,可以充分发挥系统的性能优势。
|
||||||
@@ -232,6 +232,20 @@ fn main() {
|
|||||||
return c.json('{"success": true, "message": "File deleted successfully"}')
|
return c.json('{"success": true, "message": "File deleted successfully"}')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 获取上传配置信息
|
||||||
|
app.get('/api/upload-config', fn [mut upload_manager] (mut c hono.Context) http.Response {
|
||||||
|
config := {
|
||||||
|
'chunk_size': upload_manager.config.chunk_size.str()
|
||||||
|
'max_file_size': upload_manager.config.max_file_size.str()
|
||||||
|
'max_chunk_size': upload_manager.config.max_chunk_size.str()
|
||||||
|
'temp_dir': upload_manager.config.temp_dir
|
||||||
|
'upload_dir': upload_manager.config.upload_dir
|
||||||
|
'cleanup_delay': upload_manager.config.cleanup_delay.str()
|
||||||
|
'clear_chunks_on_complete': upload_manager.config.clear_chunks_on_complete.str()
|
||||||
|
}
|
||||||
|
return c.json(json.encode(config))
|
||||||
|
})
|
||||||
|
|
||||||
// 静态文件服务
|
// 静态文件服务
|
||||||
app.use(hono.serve_static_path('/files', './uploads'))
|
app.use(hono.serve_static_path('/files', './uploads'))
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -277,6 +277,20 @@ fn main() {
|
|||||||
return c.json('{"success": true, "message": "File deleted successfully"}')
|
return c.json('{"success": true, "message": "File deleted successfully"}')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 获取上传配置信息
|
||||||
|
app.get('/api/upload-config', fn [mut upload_manager] (mut c hono.Context) http.Response {
|
||||||
|
config := {
|
||||||
|
'chunk_size': upload_manager.config.chunk_size.str()
|
||||||
|
'max_file_size': upload_manager.config.max_file_size.str()
|
||||||
|
'max_chunk_size': upload_manager.config.max_chunk_size.str()
|
||||||
|
'temp_dir': upload_manager.config.temp_dir
|
||||||
|
'upload_dir': upload_manager.config.upload_dir
|
||||||
|
'cleanup_delay': upload_manager.config.cleanup_delay.str()
|
||||||
|
'clear_chunks_on_complete': upload_manager.config.clear_chunks_on_complete.str()
|
||||||
|
}
|
||||||
|
return c.json(json.encode(config))
|
||||||
|
})
|
||||||
|
|
||||||
// ===== 5. 基础路由 =====
|
// ===== 5. 基础路由 =====
|
||||||
|
|
||||||
// 根路径 - 欢迎页面
|
// 根路径 - 欢迎页面
|
||||||
|
|||||||
+4
-4
@@ -11,6 +11,7 @@ pub struct ChunkUploadConfig {
|
|||||||
pub:
|
pub:
|
||||||
chunk_size int = 1024 * 1024 // 1MB 默认分片大小
|
chunk_size int = 1024 * 1024 // 1MB 默认分片大小
|
||||||
max_file_size int = 1024 * 1024 * 1024 // 1GB 最大文件大小
|
max_file_size int = 1024 * 1024 * 1024 // 1GB 最大文件大小
|
||||||
|
max_chunk_size int = 10 * 1024 * 1024 // 10MB 最大分片大小
|
||||||
temp_dir string = './uploads/chunks' // 临时分片目录(分片保存在 temp_dir/filehash/chunksize/ 下)
|
temp_dir string = './uploads/chunks' // 临时分片目录(分片保存在 temp_dir/filehash/chunksize/ 下)
|
||||||
upload_dir string = './uploads/files' // 最终文件目录
|
upload_dir string = './uploads/files' // 最终文件目录
|
||||||
cleanup_delay int = 3600 // 1小时后清理临时文件
|
cleanup_delay int = 3600 // 1小时后清理临时文件
|
||||||
@@ -115,11 +116,10 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_upload(mut ctx Context) htt
|
|||||||
}
|
}
|
||||||
chunk_size := chunk_size_str.int()
|
chunk_size := chunk_size_str.int()
|
||||||
|
|
||||||
// 验证分片大小(允许更大的分片大小,但不超过10MB)
|
// 验证分片大小(使用配置中的最大分片大小)
|
||||||
max_allowed_chunk_size := 10 * 1024 * 1024 // 10MB
|
if chunk_size > manager.config.max_chunk_size {
|
||||||
if chunk_size > max_allowed_chunk_size {
|
|
||||||
ctx.status(413)
|
ctx.status(413)
|
||||||
return ctx.json('{"error": "Chunk size too large", "max_chunk_size": "$max_allowed_chunk_size"}')
|
return ctx.json('{"error": "Chunk size too large", "max_chunk_size": "${manager.config.max_chunk_size}"}')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件数据
|
// 获取文件数据
|
||||||
|
|||||||
+213
-32
@@ -450,10 +450,11 @@
|
|||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div class="progress-fill" id="progressFill"></div>
|
<div class="progress-fill" id="progressFill"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chunk-status" id="chunkStatus"></div>
|
<div class="chunk-status" id="chunkStatus"></div>
|
||||||
</div>
|
<div class="chunk-status" id="poolStatus" style="font-size: 12px; color: #888; margin-top: 5px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="status" id="uploadStatus"></div>
|
<div class="status" id="uploadStatus"></div>
|
||||||
|
|
||||||
<button class="test-button" id="uploadBtn" onclick="startUpload()" style="display: none;">开始上传</button>
|
<button class="test-button" id="uploadBtn" onclick="startUpload()" style="display: none;">开始上传</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -893,12 +894,122 @@ app.listen(':8080')
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ===== 分块上传功能 =====
|
// ===== 分块上传功能 =====
|
||||||
const CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
|
const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB 固定分片大小
|
||||||
let selectedFile = null;
|
let selectedFile = null;
|
||||||
let fileHash = null;
|
let fileHash = null;
|
||||||
let totalChunks = 0;
|
let totalChunks = 0;
|
||||||
let uploadedChunks = 0;
|
let uploadedChunks = 0;
|
||||||
|
|
||||||
|
// 请求池管理
|
||||||
|
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.triggerStatusUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
// 处理完待请求后也触发状态更新
|
||||||
|
this.triggerStatusUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取池状态
|
||||||
|
getStatus() {
|
||||||
|
return {
|
||||||
|
activeCount: this.activePool.size,
|
||||||
|
pendingCount: this.pendingPool.length,
|
||||||
|
maxConcurrent: this.maxConcurrent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空所有池
|
||||||
|
clear() {
|
||||||
|
this.activePool.clear();
|
||||||
|
this.pendingPool = [];
|
||||||
|
console.log('请求池已清空');
|
||||||
|
this.triggerStatusUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发状态更新
|
||||||
|
triggerStatusUpdate() {
|
||||||
|
// 使用 setTimeout 确保状态更新在下一个事件循环中执行
|
||||||
|
setTimeout(() => {
|
||||||
|
updatePoolStatus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建全局请求池实例
|
||||||
|
const requestPool = new RequestPool(6);
|
||||||
|
|
||||||
// 文件选择处理
|
// 文件选择处理
|
||||||
document.getElementById('fileInput').addEventListener('change', handleFileSelect);
|
document.getElementById('fileInput').addEventListener('change', handleFileSelect);
|
||||||
|
|
||||||
@@ -955,6 +1066,7 @@ app.listen(':8080')
|
|||||||
// 重置进度条
|
// 重置进度条
|
||||||
document.getElementById('progressFill').style.width = '0%';
|
document.getElementById('progressFill').style.width = '0%';
|
||||||
document.getElementById('chunkStatus').textContent = '';
|
document.getElementById('chunkStatus').textContent = '';
|
||||||
|
document.getElementById('poolStatus').textContent = '';
|
||||||
|
|
||||||
// 隐藏进度容器
|
// 隐藏进度容器
|
||||||
document.getElementById('progressContainer').style.display = 'none';
|
document.getElementById('progressContainer').style.display = 'none';
|
||||||
@@ -969,6 +1081,9 @@ app.listen(':8080')
|
|||||||
|
|
||||||
// 重置变量
|
// 重置变量
|
||||||
uploadedChunks = 0;
|
uploadedChunks = 0;
|
||||||
|
|
||||||
|
// 清空请求池
|
||||||
|
requestPool.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startUpload() {
|
async function startUpload() {
|
||||||
@@ -984,48 +1099,97 @@ app.listen(':8080')
|
|||||||
document.getElementById('progressContainer').style.display = 'block';
|
document.getElementById('progressContainer').style.display = 'block';
|
||||||
showUploadStatus('开始上传...', 'success');
|
showUploadStatus('开始上传...', 'success');
|
||||||
|
|
||||||
|
// 清空之前的请求池
|
||||||
|
requestPool.clear();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 直接开始上传分片,每个分片都会检查是否存在
|
let completedChunks = 0;
|
||||||
|
let failedChunks = [];
|
||||||
|
let instantSuccess = false;
|
||||||
|
|
||||||
|
// 创建所有分片的上传任务并添加到请求池
|
||||||
|
const uploadPromises = [];
|
||||||
for (let i = 0; i < totalChunks; i++) {
|
for (let i = 0; i < totalChunks; i++) {
|
||||||
const chunk = selectedFile.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
|
const chunkIndex = i;
|
||||||
const chunkHash = await calculateChunkHash(chunk);
|
const promise = requestPool.addRequest(async () => {
|
||||||
|
try {
|
||||||
|
const chunk = selectedFile.slice(chunkIndex * CHUNK_SIZE, (chunkIndex + 1) * CHUNK_SIZE);
|
||||||
|
const chunkHash = await calculateChunkHash(chunk);
|
||||||
|
|
||||||
// 检查分片是否已存在(包括秒传检测)
|
// 检查分片是否已存在(包括秒传检测)
|
||||||
const checkResult = await checkChunkExists(fileHash, i, chunkHash);
|
const checkResult = await checkChunkExists(fileHash, chunkIndex, chunkHash);
|
||||||
|
|
||||||
// 如果检测到所有分片都已上传完成,立即结束上传
|
// 如果检测到所有分片都已上传完成,立即结束上传
|
||||||
if (checkResult.all_uploaded) {
|
if (checkResult.all_uploaded) {
|
||||||
console.log('检测到秒传,所有分片都已上传完成!');
|
console.log('检测到秒传,所有分片都已上传完成!');
|
||||||
updateProgress(totalChunks, totalChunks); // 更新进度到100%
|
updateProgress(totalChunks, totalChunks); // 更新进度到100%
|
||||||
showUploadStatus('秒传成功!文件已完整上传。', 'success');
|
showUploadStatus('秒传成功!文件已完整上传。', 'success');
|
||||||
uploadBtn.textContent = '上传完成';
|
uploadBtn.textContent = '上传完成';
|
||||||
return;
|
instantSuccess = true;
|
||||||
}
|
return { success: true, chunkIndex, isInstant: true };
|
||||||
|
}
|
||||||
// 如果分片已存在,跳过上传
|
|
||||||
if (checkResult.exists) {
|
// 如果分片已存在,跳过上传
|
||||||
console.log(`分片 ${i} 已存在,跳过上传`);
|
if (checkResult.exists) {
|
||||||
updateProgress(i + 1, totalChunks);
|
console.log(`分片 ${chunkIndex} 已存在,跳过上传`);
|
||||||
continue;
|
completedChunks++;
|
||||||
}
|
updateProgress(completedChunks, totalChunks);
|
||||||
|
return { success: true, chunkIndex, skipped: true };
|
||||||
|
}
|
||||||
|
|
||||||
// 上传分片
|
// 上传分片
|
||||||
await uploadChunk(selectedFile, chunk, {
|
await uploadChunk(selectedFile, chunk, {
|
||||||
fileHash, chunkIndex: i, totalChunks, chunkHash
|
fileHash, chunkIndex, totalChunks, chunkHash
|
||||||
|
});
|
||||||
|
|
||||||
|
completedChunks++;
|
||||||
|
updateProgress(completedChunks, totalChunks);
|
||||||
|
console.log(`分片 ${chunkIndex} 上传完成`);
|
||||||
|
return { success: true, chunkIndex };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`分片 ${chunkIndex} 上传失败:`, error);
|
||||||
|
failedChunks.push({ chunkIndex, error: error.message });
|
||||||
|
return { success: false, chunkIndex, error: error.message };
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
updateProgress(i + 1, totalChunks);
|
uploadPromises.push(promise);
|
||||||
console.log(`分片 ${i} 上传完成`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showUploadStatus('上传完成!文件已自动合并。', 'success');
|
// 等待所有上传任务完成
|
||||||
uploadBtn.textContent = '上传完成';
|
const results = await Promise.all(uploadPromises);
|
||||||
|
|
||||||
|
// 检查是否有秒传成功
|
||||||
|
if (instantSuccess) {
|
||||||
|
return; // 秒传成功,直接返回
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查上传结果
|
||||||
|
const failedResults = results.filter(result => !result.success);
|
||||||
|
if (failedResults.length > 0) {
|
||||||
|
const errorMessage = `部分分片上传失败: ${failedResults.map(f => `分片${f.chunkIndex}(${f.error})`).join(', ')}`;
|
||||||
|
showUploadStatus(errorMessage, 'error');
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
uploadBtn.textContent = '重新上传';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showUploadStatus('上传完成!文件已自动合并。', 'success');
|
||||||
|
uploadBtn.textContent = '上传完成';
|
||||||
|
|
||||||
|
// 确保最终状态显示正确
|
||||||
|
setTimeout(() => {
|
||||||
|
updatePoolStatus();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('上传失败:', error);
|
console.error('上传失败:', error);
|
||||||
showUploadStatus('上传失败: ' + error.message, 'error');
|
showUploadStatus('上传失败: ' + error.message, 'error');
|
||||||
uploadBtn.disabled = false;
|
uploadBtn.disabled = false;
|
||||||
uploadBtn.textContent = '重新上传';
|
uploadBtn.textContent = '重新上传';
|
||||||
|
} finally {
|
||||||
|
// 清空请求池
|
||||||
|
requestPool.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1119,6 +1283,23 @@ app.listen(':8080')
|
|||||||
const percentage = (current / total) * 100;
|
const percentage = (current / total) * 100;
|
||||||
document.getElementById('progressFill').style.width = percentage + '%';
|
document.getElementById('progressFill').style.width = percentage + '%';
|
||||||
document.getElementById('chunkStatus').textContent = current + '/' + total + ' 分片已上传 (' + percentage.toFixed(1) + '%)';
|
document.getElementById('chunkStatus').textContent = current + '/' + total + ' 分片已上传 (' + percentage.toFixed(1) + '%)';
|
||||||
|
|
||||||
|
// 更新请求池状态
|
||||||
|
updatePoolStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新请求池状态显示
|
||||||
|
function updatePoolStatus() {
|
||||||
|
const status = requestPool.getStatus();
|
||||||
|
const poolStatusElement = document.getElementById('poolStatus');
|
||||||
|
if (poolStatusElement) {
|
||||||
|
// 如果所有请求都完成了,显示完成状态
|
||||||
|
if (status.activeCount === 0 && status.pendingCount === 0) {
|
||||||
|
poolStatusElement.textContent = `请求池状态: 所有请求已完成`;
|
||||||
|
} else {
|
||||||
|
poolStatusElement.textContent = `请求池状态: ${status.activeCount}/${status.maxConcurrent} 活跃, ${status.pendingCount} 等待中`;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示上传状态
|
// 显示上传状态
|
||||||
|
|||||||
Reference in New Issue
Block a user