670 lines
16 KiB
JavaScript
670 lines
16 KiB
JavaScript
// 网络优化管理器 - 微信小程序专用
|
||
// 提供网络请求优化、缓存管理、离线支持等功能
|
||
|
||
const errorHandler = require('./error-handler.js');
|
||
const performanceMonitor = require('./performance-monitor.js');
|
||
|
||
/**
|
||
* 网络优化管理器
|
||
* 功能:
|
||
* 1. 请求缓存和优化
|
||
* 2. 离线数据支持
|
||
* 3. 网络状态监控
|
||
* 4. 请求队列管理
|
||
* 5. 断网重连机制
|
||
* 6. 数据压缩和优化
|
||
*/
|
||
class NetworkOptimizer {
|
||
constructor() {
|
||
this.isInitialized = false;
|
||
|
||
// 网络优化配置
|
||
this.config = {
|
||
// 缓存配置
|
||
cache: {
|
||
enabled: true,
|
||
maxSize: 50 * 1024 * 1024, // 50MB
|
||
defaultTTL: 300000, // 5分钟
|
||
maxAge: 24 * 60 * 60 * 1000, // 24小时
|
||
compressionEnabled: true
|
||
},
|
||
|
||
// 离线配置
|
||
offline: {
|
||
enabled: true,
|
||
maxOfflineData: 10 * 1024 * 1024, // 10MB
|
||
syncOnReconnect: true
|
||
},
|
||
|
||
// 请求配置
|
||
request: {
|
||
timeout: 10000, // 10秒超时
|
||
maxConcurrent: 6, // 最大并发请求数
|
||
retryAttempts: 3, // 重试次数
|
||
retryDelay: 1000, // 重试延迟
|
||
enableCompression: true, // 启用压缩
|
||
enableKeepAlive: true // 启用长连接
|
||
},
|
||
|
||
// 预加载配置
|
||
preload: {
|
||
enabled: true,
|
||
maxPreloadSize: 5 * 1024 * 1024, // 5MB
|
||
preloadDelay: 2000 // 预加载延迟
|
||
}
|
||
};
|
||
|
||
// 网络状态
|
||
this.networkState = {
|
||
isOnline: true,
|
||
networkType: 'unknown',
|
||
lastOnlineTime: Date.now(),
|
||
connectionQuality: 'good'
|
||
};
|
||
|
||
// 缓存存储
|
||
this.cache = new Map();
|
||
this.cacheMetadata = new Map();
|
||
|
||
// 离线数据队列
|
||
this.offlineQueue = [];
|
||
|
||
// 请求队列
|
||
this.requestQueue = [];
|
||
this.activeRequests = new Set();
|
||
|
||
// 预加载队列
|
||
this.preloadQueue = [];
|
||
|
||
// 统计数据
|
||
this.stats = {
|
||
totalRequests: 0,
|
||
cachedRequests: 0,
|
||
failedRequests: 0,
|
||
offlineRequests: 0,
|
||
averageResponseTime: 0,
|
||
cacheHitRate: 0
|
||
};
|
||
|
||
this.init();
|
||
}
|
||
|
||
// 初始化网络优化器
|
||
async init() {
|
||
if (this.isInitialized || !this.config.cache.enabled) return;
|
||
|
||
try {
|
||
// 获取网络状态
|
||
await this.updateNetworkState();
|
||
|
||
// 加载缓存数据
|
||
await this.loadCacheFromStorage();
|
||
|
||
// 设置网络监听
|
||
this.setupNetworkListeners();
|
||
|
||
// 启动请求队列处理
|
||
this.startRequestQueueProcessor();
|
||
|
||
// 启动缓存清理
|
||
this.startCacheCleanup();
|
||
|
||
// 启动预加载处理
|
||
this.startPreloadProcessor();
|
||
|
||
this.isInitialized = true;
|
||
|
||
} catch (error) {
|
||
console.error('❌ 网络优化器初始化失败:', error);
|
||
}
|
||
}
|
||
|
||
// 🌐 ===== 网络状态管理 =====
|
||
|
||
// 更新网络状态
|
||
async updateNetworkState() {
|
||
try {
|
||
const networkInfo = await new Promise((resolve, reject) => {
|
||
wx.getNetworkType({
|
||
success: resolve,
|
||
fail: reject
|
||
});
|
||
});
|
||
|
||
const wasOnline = this.networkState.isOnline;
|
||
this.networkState.isOnline = networkInfo.networkType !== 'none';
|
||
this.networkState.networkType = networkInfo.networkType;
|
||
|
||
if (this.networkState.isOnline) {
|
||
this.networkState.lastOnlineTime = Date.now();
|
||
|
||
// 如果从离线恢复到在线,处理离线队列
|
||
if (!wasOnline && this.config.offline.syncOnReconnect) {
|
||
this.processOfflineQueue();
|
||
}
|
||
}
|
||
|
||
// 更新连接质量
|
||
this.updateConnectionQuality();
|
||
|
||
} catch (error) {
|
||
console.error('❌ 更新网络状态失败:', error);
|
||
}
|
||
}
|
||
|
||
// 更新连接质量
|
||
updateConnectionQuality() {
|
||
const networkType = this.networkState.networkType;
|
||
|
||
if (networkType === 'wifi') {
|
||
this.networkState.connectionQuality = 'excellent';
|
||
} else if (networkType === '4g') {
|
||
this.networkState.connectionQuality = 'good';
|
||
} else if (networkType === '3g') {
|
||
this.networkState.connectionQuality = 'fair';
|
||
} else if (networkType === '2g') {
|
||
this.networkState.connectionQuality = 'poor';
|
||
} else {
|
||
this.networkState.connectionQuality = 'unknown';
|
||
}
|
||
}
|
||
|
||
// 设置网络监听
|
||
setupNetworkListeners() {
|
||
wx.onNetworkStatusChange((res) => {
|
||
|
||
this.networkState.isOnline = res.isConnected;
|
||
this.networkState.networkType = res.networkType;
|
||
|
||
if (res.isConnected) {
|
||
this.networkState.lastOnlineTime = Date.now();
|
||
|
||
// 网络恢复,处理离线队列
|
||
if (this.config.offline.syncOnReconnect) {
|
||
this.processOfflineQueue();
|
||
}
|
||
}
|
||
|
||
this.updateConnectionQuality();
|
||
});
|
||
}
|
||
|
||
// 📦 ===== 缓存管理 =====
|
||
|
||
// 获取缓存
|
||
getCache(key) {
|
||
if (!this.config.cache.enabled) return null;
|
||
|
||
const cached = this.cache.get(key);
|
||
const metadata = this.cacheMetadata.get(key);
|
||
|
||
if (!cached || !metadata) return null;
|
||
|
||
// 检查缓存是否过期
|
||
if (Date.now() - metadata.timestamp > metadata.ttl) {
|
||
this.cache.delete(key);
|
||
this.cacheMetadata.delete(key);
|
||
return null;
|
||
}
|
||
|
||
// 更新访问时间
|
||
metadata.lastAccess = Date.now();
|
||
metadata.accessCount++;
|
||
|
||
this.stats.cachedRequests++;
|
||
|
||
return cached;
|
||
}
|
||
|
||
// 设置缓存
|
||
setCache(key, data, ttl = this.config.cache.defaultTTL) {
|
||
if (!this.config.cache.enabled) return;
|
||
|
||
try {
|
||
// 检查缓存大小
|
||
if (this.getCacheSize() > this.config.cache.maxSize) {
|
||
this.cleanupCache();
|
||
}
|
||
|
||
// 压缩数据(如果启用)
|
||
const compressedData = this.config.cache.compressionEnabled ?
|
||
this.compressData(data) : data;
|
||
|
||
this.cache.set(key, compressedData);
|
||
this.cacheMetadata.set(key, {
|
||
timestamp: Date.now(),
|
||
ttl: ttl,
|
||
size: this.getDataSize(compressedData),
|
||
lastAccess: Date.now(),
|
||
accessCount: 1,
|
||
compressed: this.config.cache.compressionEnabled
|
||
});
|
||
|
||
// 异步保存到本地存储
|
||
this.saveCacheToStorage();
|
||
|
||
} catch (error) {
|
||
console.error('❌ 设置缓存失败:', error);
|
||
}
|
||
}
|
||
|
||
// 删除缓存
|
||
deleteCache(key) {
|
||
this.cache.delete(key);
|
||
this.cacheMetadata.delete(key);
|
||
this.saveCacheToStorage();
|
||
}
|
||
|
||
// 清空缓存
|
||
clearCache() {
|
||
this.cache.clear();
|
||
this.cacheMetadata.clear();
|
||
wx.removeStorageSync('network_cache');
|
||
wx.removeStorageSync('cache_metadata');
|
||
|
||
}
|
||
|
||
// 获取缓存大小
|
||
getCacheSize() {
|
||
let totalSize = 0;
|
||
for (const metadata of this.cacheMetadata.values()) {
|
||
totalSize += metadata.size;
|
||
}
|
||
return totalSize;
|
||
}
|
||
|
||
// 清理过期缓存
|
||
cleanupCache() {
|
||
const now = Date.now();
|
||
const keysToDelete = [];
|
||
|
||
for (const [key, metadata] of this.cacheMetadata.entries()) {
|
||
// 删除过期的缓存
|
||
if (now - metadata.timestamp > metadata.ttl ||
|
||
now - metadata.timestamp > this.config.cache.maxAge) {
|
||
keysToDelete.push(key);
|
||
}
|
||
}
|
||
|
||
// 如果还是太大,删除最少使用的缓存
|
||
if (this.getCacheSize() > this.config.cache.maxSize) {
|
||
const sortedEntries = Array.from(this.cacheMetadata.entries())
|
||
.sort((a, b) => a[1].lastAccess - b[1].lastAccess);
|
||
|
||
const halfSize = Math.floor(sortedEntries.length / 2);
|
||
for (let i = 0; i < halfSize; i++) {
|
||
keysToDelete.push(sortedEntries[i][0]);
|
||
}
|
||
}
|
||
|
||
// 删除缓存
|
||
keysToDelete.forEach(key => {
|
||
this.cache.delete(key);
|
||
this.cacheMetadata.delete(key);
|
||
});
|
||
|
||
if (keysToDelete.length > 0) {
|
||
|
||
this.saveCacheToStorage();
|
||
}
|
||
}
|
||
|
||
// 启动缓存清理定时器
|
||
startCacheCleanup() {
|
||
setInterval(() => {
|
||
this.cleanupCache();
|
||
}, 5 * 60 * 1000); // 每5分钟清理一次
|
||
}
|
||
|
||
// 📱 ===== 离线支持 =====
|
||
|
||
// 添加到离线队列
|
||
addToOfflineQueue(request) {
|
||
if (!this.config.offline.enabled) return;
|
||
|
||
// 检查离线数据大小
|
||
const currentSize = this.getOfflineQueueSize();
|
||
if (currentSize > this.config.offline.maxOfflineData) {
|
||
// 删除最旧的请求
|
||
this.offlineQueue.shift();
|
||
}
|
||
|
||
this.offlineQueue.push({
|
||
...request,
|
||
timestamp: Date.now(),
|
||
retryCount: 0
|
||
});
|
||
|
||
this.stats.offlineRequests++;
|
||
}
|
||
|
||
// 处理离线队列
|
||
async processOfflineQueue() {
|
||
if (!this.networkState.isOnline || this.offlineQueue.length === 0) return;
|
||
|
||
const queue = [...this.offlineQueue];
|
||
this.offlineQueue = [];
|
||
|
||
for (const request of queue) {
|
||
try {
|
||
await this.makeRequest(request);
|
||
|
||
} catch (error) {
|
||
console.error('❌ 离线请求同步失败:', request.url, error);
|
||
|
||
// 重试次数未达到上限,重新加入队列
|
||
if (request.retryCount < this.config.request.retryAttempts) {
|
||
request.retryCount++;
|
||
this.offlineQueue.push(request);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取离线队列大小
|
||
getOfflineQueueSize() {
|
||
return this.offlineQueue.reduce((size, request) => {
|
||
return size + this.getDataSize(request);
|
||
}, 0);
|
||
}
|
||
|
||
// 🚀 ===== 请求优化 =====
|
||
|
||
// 优化的请求方法
|
||
async optimizedRequest(options) {
|
||
const requestId = performanceMonitor.startApiMonitoring(options.url, options.method);
|
||
|
||
try {
|
||
this.stats.totalRequests++;
|
||
|
||
// 生成缓存键
|
||
const cacheKey = this.generateCacheKey(options);
|
||
|
||
// 检查缓存
|
||
if (options.method === 'GET' || options.useCache) {
|
||
const cached = this.getCache(cacheKey);
|
||
if (cached) {
|
||
performanceMonitor.endApiMonitoring(requestId, {
|
||
success: true,
|
||
statusCode: 200,
|
||
fromCache: true
|
||
});
|
||
|
||
return this.config.cache.compressionEnabled ?
|
||
this.decompressData(cached) : cached;
|
||
}
|
||
}
|
||
|
||
// 检查网络状态
|
||
if (!this.networkState.isOnline) {
|
||
// 离线状态,添加到离线队列
|
||
if (options.method !== 'GET') {
|
||
this.addToOfflineQueue(options);
|
||
}
|
||
|
||
throw new Error('网络不可用,请求已加入离线队列');
|
||
}
|
||
|
||
// 检查并发限制
|
||
if (this.activeRequests.size >= this.config.request.maxConcurrent) {
|
||
await this.waitForRequestSlot();
|
||
}
|
||
|
||
// 发起请求
|
||
const result = await this.makeRequest(options);
|
||
|
||
// 缓存GET请求结果
|
||
if (options.method === 'GET' || options.useCache) {
|
||
this.setCache(cacheKey, result, options.cacheTTL);
|
||
}
|
||
|
||
performanceMonitor.endApiMonitoring(requestId, {
|
||
success: true,
|
||
statusCode: result.statusCode || 200
|
||
});
|
||
|
||
return result;
|
||
|
||
} catch (error) {
|
||
this.stats.failedRequests++;
|
||
|
||
performanceMonitor.endApiMonitoring(requestId, {
|
||
success: false,
|
||
errorMessage: error.message
|
||
});
|
||
|
||
// 使用错误处理器处理错误
|
||
throw await errorHandler.handleError(error, {
|
||
url: options.url,
|
||
method: options.method
|
||
});
|
||
}
|
||
}
|
||
|
||
// 发起实际请求
|
||
async makeRequest(options) {
|
||
const requestPromise = new Promise((resolve, reject) => {
|
||
const requestOptions = {
|
||
url: options.url,
|
||
method: options.method || 'GET',
|
||
data: options.data,
|
||
header: {
|
||
'Content-Type': 'application/json',
|
||
...options.header
|
||
},
|
||
timeout: options.timeout || this.config.request.timeout,
|
||
success: (res) => {
|
||
this.activeRequests.delete(requestPromise);
|
||
resolve(res.data);
|
||
},
|
||
fail: (error) => {
|
||
this.activeRequests.delete(requestPromise);
|
||
reject(new Error(error.errMsg || '请求失败'));
|
||
}
|
||
};
|
||
|
||
// 添加到活跃请求集合
|
||
this.activeRequests.add(requestPromise);
|
||
|
||
wx.request(requestOptions);
|
||
});
|
||
|
||
return requestPromise;
|
||
}
|
||
|
||
// 等待请求槽位
|
||
async waitForRequestSlot() {
|
||
while (this.activeRequests.size >= this.config.request.maxConcurrent) {
|
||
await new Promise(resolve => setTimeout(resolve, 100));
|
||
}
|
||
}
|
||
|
||
// 启动请求队列处理器
|
||
startRequestQueueProcessor() {
|
||
setInterval(() => {
|
||
this.processRequestQueue();
|
||
}, 1000);
|
||
}
|
||
|
||
// 处理请求队列
|
||
processRequestQueue() {
|
||
while (this.requestQueue.length > 0 &&
|
||
this.activeRequests.size < this.config.request.maxConcurrent) {
|
||
const request = this.requestQueue.shift();
|
||
this.optimizedRequest(request.options)
|
||
.then(request.resolve)
|
||
.catch(request.reject);
|
||
}
|
||
}
|
||
|
||
// 🚀 ===== 预加载 =====
|
||
|
||
// 添加预加载请求
|
||
addPreloadRequest(url, options = {}) {
|
||
if (!this.config.preload.enabled) return;
|
||
|
||
this.preloadQueue.push({
|
||
url: url,
|
||
options: options,
|
||
priority: options.priority || 'normal',
|
||
timestamp: Date.now()
|
||
});
|
||
|
||
}
|
||
|
||
// 启动预加载处理器
|
||
startPreloadProcessor() {
|
||
setInterval(() => {
|
||
this.processPreloadQueue();
|
||
}, this.config.preload.preloadDelay);
|
||
}
|
||
|
||
// 处理预加载队列
|
||
async processPreloadQueue() {
|
||
if (!this.networkState.isOnline || this.preloadQueue.length === 0) return;
|
||
|
||
// 按优先级排序
|
||
this.preloadQueue.sort((a, b) => {
|
||
const priorityOrder = { high: 3, normal: 2, low: 1 };
|
||
return priorityOrder[b.priority] - priorityOrder[a.priority];
|
||
});
|
||
|
||
// 处理高优先级的预加载请求
|
||
const request = this.preloadQueue.shift();
|
||
|
||
try {
|
||
await this.optimizedRequest({
|
||
url: request.url,
|
||
method: 'GET',
|
||
useCache: true,
|
||
...request.options
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('❌ 预加载失败:', request.url, error);
|
||
}
|
||
}
|
||
|
||
// 🔧 ===== 工具方法 =====
|
||
|
||
// 生成缓存键
|
||
generateCacheKey(options) {
|
||
const keyData = {
|
||
url: options.url,
|
||
method: options.method || 'GET',
|
||
data: options.data || {}
|
||
};
|
||
|
||
return `cache_${this.hashCode(JSON.stringify(keyData))}`;
|
||
}
|
||
|
||
// 简单哈希函数
|
||
hashCode(str) {
|
||
let hash = 0;
|
||
for (let i = 0; i < str.length; i++) {
|
||
const char = str.charCodeAt(i);
|
||
hash = ((hash << 5) - hash) + char;
|
||
hash = hash & hash; // 转换为32位整数
|
||
}
|
||
return Math.abs(hash).toString(36);
|
||
}
|
||
|
||
// 压缩数据
|
||
compressData(data) {
|
||
try {
|
||
// 简单的JSON压缩(移除空格)
|
||
return JSON.stringify(data);
|
||
} catch (error) {
|
||
return data;
|
||
}
|
||
}
|
||
|
||
// 解压数据
|
||
decompressData(data) {
|
||
try {
|
||
return typeof data === 'string' ? JSON.parse(data) : data;
|
||
} catch (error) {
|
||
return data;
|
||
}
|
||
}
|
||
|
||
// 获取数据大小
|
||
getDataSize(data) {
|
||
try {
|
||
return new Blob([JSON.stringify(data)]).size;
|
||
} catch (error) {
|
||
return JSON.stringify(data).length * 2; // 估算
|
||
}
|
||
}
|
||
|
||
// 保存缓存到本地存储
|
||
async saveCacheToStorage() {
|
||
try {
|
||
// 将Map转换为对象进行存储
|
||
const cacheData = Object.fromEntries(this.cache);
|
||
const metadataData = Object.fromEntries(this.cacheMetadata);
|
||
|
||
wx.setStorageSync('network_cache', cacheData);
|
||
wx.setStorageSync('cache_metadata', metadataData);
|
||
|
||
} catch (error) {
|
||
console.error('❌ 保存缓存到本地存储失败:', error);
|
||
}
|
||
}
|
||
|
||
// 从本地存储加载缓存
|
||
async loadCacheFromStorage() {
|
||
try {
|
||
const cacheData = wx.getStorageSync('network_cache');
|
||
const metadataData = wx.getStorageSync('cache_metadata');
|
||
|
||
if (cacheData) {
|
||
this.cache = new Map(Object.entries(cacheData));
|
||
}
|
||
|
||
if (metadataData) {
|
||
this.cacheMetadata = new Map(Object.entries(metadataData));
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ 从本地存储加载缓存失败:', error);
|
||
}
|
||
}
|
||
|
||
// 获取网络统计
|
||
getNetworkStats() {
|
||
this.stats.cacheHitRate = this.stats.totalRequests > 0 ?
|
||
this.stats.cachedRequests / this.stats.totalRequests : 0;
|
||
|
||
return {
|
||
...this.stats,
|
||
networkState: this.networkState,
|
||
cacheSize: this.getCacheSize(),
|
||
offlineQueueSize: this.offlineQueue.length,
|
||
activeRequests: this.activeRequests.size
|
||
};
|
||
}
|
||
|
||
// 销毁网络优化器
|
||
destroy() {
|
||
// 保存缓存
|
||
this.saveCacheToStorage();
|
||
|
||
// 清理数据
|
||
this.cache.clear();
|
||
this.cacheMetadata.clear();
|
||
this.offlineQueue = [];
|
||
this.requestQueue = [];
|
||
this.activeRequests.clear();
|
||
this.preloadQueue = [];
|
||
|
||
this.isInitialized = false;
|
||
|
||
}
|
||
}
|
||
|
||
// 创建全局实例
|
||
const networkOptimizer = new NetworkOptimizer();
|
||
|
||
module.exports = networkOptimizer;
|
||
|