696 lines
18 KiB
JavaScript
696 lines
18 KiB
JavaScript
|
|
// 全局错误处理管理器 - 微信小程序专用
|
|||
|
|
// 统一处理应用中的各种错误,提供错误恢复和降级方案
|
|||
|
|
|
|||
|
|
const performanceMonitor = require('./performance-monitor.js');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 全局错误处理管理器
|
|||
|
|
* 功能:
|
|||
|
|
* 1. 全局错误捕获和处理
|
|||
|
|
* 2. 错误分类和分析
|
|||
|
|
* 3. 自动重试机制
|
|||
|
|
* 4. 降级方案
|
|||
|
|
* 5. 错误恢复策略
|
|||
|
|
* 6. 用户友好的错误提示
|
|||
|
|
*/
|
|||
|
|
class ErrorHandler {
|
|||
|
|
constructor() {
|
|||
|
|
this.isInitialized = false;
|
|||
|
|
|
|||
|
|
// 错误处理配置
|
|||
|
|
this.config = {
|
|||
|
|
// 错误处理开关
|
|||
|
|
enabled: true,
|
|||
|
|
|
|||
|
|
// 自动重试配置
|
|||
|
|
retry: {
|
|||
|
|
enabled: true,
|
|||
|
|
maxAttempts: 3,
|
|||
|
|
baseDelay: 1000, // 基础延迟 (ms)
|
|||
|
|
maxDelay: 10000, // 最大延迟 (ms)
|
|||
|
|
backoffFactor: 2 // 退避因子
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 降级配置
|
|||
|
|
fallback: {
|
|||
|
|
enabled: true,
|
|||
|
|
cacheTimeout: 300000, // 缓存超时 (5分钟)
|
|||
|
|
offlineMode: true // 离线模式
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 用户提示配置
|
|||
|
|
userNotification: {
|
|||
|
|
enabled: true,
|
|||
|
|
showDetails: false, // 是否显示错误详情
|
|||
|
|
autoHide: true, // 自动隐藏
|
|||
|
|
hideDelay: 3000 // 隐藏延迟 (ms)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 错误类型定义
|
|||
|
|
this.errorTypes = {
|
|||
|
|
NETWORK_ERROR: 'network_error',
|
|||
|
|
API_ERROR: 'api_error',
|
|||
|
|
PARSE_ERROR: 'parse_error',
|
|||
|
|
STORAGE_ERROR: 'storage_error',
|
|||
|
|
PERMISSION_ERROR: 'permission_error',
|
|||
|
|
VALIDATION_ERROR: 'validation_error',
|
|||
|
|
UNKNOWN_ERROR: 'unknown_error'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 错误统计
|
|||
|
|
this.errorStats = {
|
|||
|
|
totalErrors: 0,
|
|||
|
|
errorsByType: new Map(),
|
|||
|
|
errorsByPage: new Map(),
|
|||
|
|
recentErrors: []
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 重试队列
|
|||
|
|
this.retryQueue = new Map();
|
|||
|
|
|
|||
|
|
// 降级缓存
|
|||
|
|
this.fallbackCache = new Map();
|
|||
|
|
|
|||
|
|
this.init();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化错误处理器
|
|||
|
|
init() {
|
|||
|
|
if (this.isInitialized || !this.config.enabled) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 设置全局错误监听
|
|||
|
|
this.setupGlobalErrorHandlers();
|
|||
|
|
|
|||
|
|
// 设置网络错误监听
|
|||
|
|
this.setupNetworkErrorHandlers();
|
|||
|
|
|
|||
|
|
// 设置Promise错误监听
|
|||
|
|
this.setupPromiseErrorHandlers();
|
|||
|
|
|
|||
|
|
this.isInitialized = true;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 全局错误处理器初始化失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🚨 ===== 错误捕获和处理 =====
|
|||
|
|
|
|||
|
|
// 处理错误
|
|||
|
|
handleError(error, context = {}) {
|
|||
|
|
if (!this.config.enabled) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 错误分类
|
|||
|
|
const errorType = this.classifyError(error);
|
|||
|
|
|
|||
|
|
// 创建错误信息
|
|||
|
|
const errorInfo = this.createErrorInfo(error, errorType, context);
|
|||
|
|
|
|||
|
|
// 更新错误统计
|
|||
|
|
this.updateErrorStats(errorInfo);
|
|||
|
|
|
|||
|
|
// 记录错误到性能监控
|
|||
|
|
performanceMonitor.recordError(error, context);
|
|||
|
|
|
|||
|
|
// 处理特定类型的错误
|
|||
|
|
this.handleSpecificError(errorInfo);
|
|||
|
|
|
|||
|
|
// 显示用户提示
|
|||
|
|
this.showUserNotification(errorInfo);
|
|||
|
|
|
|||
|
|
console.error('🚨 错误处理:', errorInfo);
|
|||
|
|
|
|||
|
|
return errorInfo;
|
|||
|
|
|
|||
|
|
} catch (handlerError) {
|
|||
|
|
console.error('❌ 错误处理器本身出错:', handlerError);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 错误分类
|
|||
|
|
classifyError(error) {
|
|||
|
|
if (!error) return this.errorTypes.UNKNOWN_ERROR;
|
|||
|
|
|
|||
|
|
const message = error.message || error.toString();
|
|||
|
|
const stack = error.stack || '';
|
|||
|
|
|
|||
|
|
// 网络错误
|
|||
|
|
if (message.includes('network') || message.includes('timeout') ||
|
|||
|
|
message.includes('连接') || error.code === 'NETWORK_ERROR') {
|
|||
|
|
return this.errorTypes.NETWORK_ERROR;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// API错误
|
|||
|
|
if (message.includes('API') || message.includes('request') ||
|
|||
|
|
message.includes('response') || error.statusCode) {
|
|||
|
|
return this.errorTypes.API_ERROR;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 解析错误
|
|||
|
|
if (message.includes('JSON') || message.includes('parse') ||
|
|||
|
|
message.includes('Unexpected token')) {
|
|||
|
|
return this.errorTypes.PARSE_ERROR;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 存储错误
|
|||
|
|
if (message.includes('storage') || message.includes('setStorage') ||
|
|||
|
|
message.includes('getStorage')) {
|
|||
|
|
return this.errorTypes.STORAGE_ERROR;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 权限错误
|
|||
|
|
if (message.includes('permission') || message.includes('unauthorized') ||
|
|||
|
|
message.includes('权限') || error.code === 'PERMISSION_DENIED') {
|
|||
|
|
return this.errorTypes.PERMISSION_ERROR;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证错误
|
|||
|
|
if (message.includes('validation') || message.includes('invalid') ||
|
|||
|
|
message.includes('验证')) {
|
|||
|
|
return this.errorTypes.VALIDATION_ERROR;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.errorTypes.UNKNOWN_ERROR;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建错误信息
|
|||
|
|
createErrorInfo(error, errorType, context) {
|
|||
|
|
return {
|
|||
|
|
id: this.generateErrorId(),
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
type: errorType,
|
|||
|
|
message: error.message || error.toString(),
|
|||
|
|
stack: error.stack || null,
|
|||
|
|
code: error.code || null,
|
|||
|
|
statusCode: error.statusCode || null,
|
|||
|
|
context: context,
|
|||
|
|
pagePath: this.getCurrentPagePath(),
|
|||
|
|
userAgent: this.getUserAgent(),
|
|||
|
|
canRetry: this.canRetry(errorType),
|
|||
|
|
canFallback: this.canFallback(errorType),
|
|||
|
|
severity: this.getErrorSeverity(errorType)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔄 ===== 自动重试机制 =====
|
|||
|
|
|
|||
|
|
// 自动重试
|
|||
|
|
async autoRetry(operation, context = {}) {
|
|||
|
|
if (!this.config.retry.enabled) {
|
|||
|
|
return await operation();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const retryId = this.generateRetryId();
|
|||
|
|
let lastError = null;
|
|||
|
|
|
|||
|
|
for (let attempt = 1; attempt <= this.config.retry.maxAttempts; attempt++) {
|
|||
|
|
try {
|
|||
|
|
// 记录重试尝试
|
|||
|
|
if (attempt > 1) {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const result = await operation();
|
|||
|
|
|
|||
|
|
// 成功,清除重试记录
|
|||
|
|
this.retryQueue.delete(retryId);
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
lastError = error;
|
|||
|
|
|
|||
|
|
// 检查是否可以重试
|
|||
|
|
if (!this.canRetry(this.classifyError(error)) || attempt >= this.config.retry.maxAttempts) {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算延迟时间
|
|||
|
|
const delay = this.calculateRetryDelay(attempt);
|
|||
|
|
|
|||
|
|
// 记录重试信息
|
|||
|
|
this.retryQueue.set(retryId, {
|
|||
|
|
attempt: attempt,
|
|||
|
|
nextRetry: Date.now() + delay,
|
|||
|
|
context: context,
|
|||
|
|
error: error
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 等待重试
|
|||
|
|
await this.sleep(delay);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 所有重试都失败了
|
|||
|
|
this.retryQueue.delete(retryId);
|
|||
|
|
throw lastError;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算重试延迟
|
|||
|
|
calculateRetryDelay(attempt) {
|
|||
|
|
const delay = this.config.retry.baseDelay * Math.pow(this.config.retry.backoffFactor, attempt - 1);
|
|||
|
|
return Math.min(delay, this.config.retry.maxDelay);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否可以重试
|
|||
|
|
canRetry(errorType) {
|
|||
|
|
const retryableErrors = [
|
|||
|
|
this.errorTypes.NETWORK_ERROR,
|
|||
|
|
this.errorTypes.API_ERROR
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return retryableErrors.includes(errorType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔄 ===== 降级方案 =====
|
|||
|
|
|
|||
|
|
// 降级处理
|
|||
|
|
async fallbackHandler(operation, fallbackKey, fallbackData = null) {
|
|||
|
|
if (!this.config.fallback.enabled) {
|
|||
|
|
return await operation();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const result = await operation();
|
|||
|
|
|
|||
|
|
// 成功时更新缓存
|
|||
|
|
this.updateFallbackCache(fallbackKey, result);
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('🔄 操作失败,尝试降级方案:', error.message);
|
|||
|
|
|
|||
|
|
// 尝试从缓存获取数据
|
|||
|
|
const cachedData = this.getFallbackCache(fallbackKey);
|
|||
|
|
if (cachedData) {
|
|||
|
|
|
|||
|
|
return cachedData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用提供的降级数据
|
|||
|
|
if (fallbackData !== null) {
|
|||
|
|
|
|||
|
|
return fallbackData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 没有降级方案,重新抛出错误
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新降级缓存
|
|||
|
|
updateFallbackCache(key, data) {
|
|||
|
|
this.fallbackCache.set(key, {
|
|||
|
|
data: data,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取降级缓存
|
|||
|
|
getFallbackCache(key) {
|
|||
|
|
const cached = this.fallbackCache.get(key);
|
|||
|
|
|
|||
|
|
if (!cached) return null;
|
|||
|
|
|
|||
|
|
// 检查缓存是否过期
|
|||
|
|
if (Date.now() - cached.timestamp > this.config.fallback.cacheTimeout) {
|
|||
|
|
this.fallbackCache.delete(key);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return cached.data;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否可以降级
|
|||
|
|
canFallback(errorType) {
|
|||
|
|
const fallbackableErrors = [
|
|||
|
|
this.errorTypes.NETWORK_ERROR,
|
|||
|
|
this.errorTypes.API_ERROR
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return fallbackableErrors.includes(errorType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 📱 ===== 用户提示 =====
|
|||
|
|
|
|||
|
|
// 显示用户提示
|
|||
|
|
showUserNotification(errorInfo) {
|
|||
|
|
if (!this.config.userNotification.enabled) return;
|
|||
|
|
|
|||
|
|
// 只对已登录用户显示弹窗
|
|||
|
|
const app = getApp();
|
|||
|
|
const isLoggedIn = app?.globalData?.isLoggedIn || false;
|
|||
|
|
if (!isLoggedIn) return;
|
|||
|
|
|
|||
|
|
const userMessage = this.getUserFriendlyMessage(errorInfo);
|
|||
|
|
|
|||
|
|
// 根据错误严重程度选择提示方式
|
|||
|
|
switch (errorInfo.severity) {
|
|||
|
|
case 'low':
|
|||
|
|
// 低严重程度,不显示提示或显示简单提示
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'medium':
|
|||
|
|
wx.showToast({
|
|||
|
|
title: userMessage,
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: this.config.userNotification.hideDelay
|
|||
|
|
});
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'high':
|
|||
|
|
wx.showModal({
|
|||
|
|
title: '操作失败',
|
|||
|
|
content: userMessage,
|
|||
|
|
showCancel: false,
|
|||
|
|
confirmText: '确定'
|
|||
|
|
});
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'critical':
|
|||
|
|
wx.showModal({
|
|||
|
|
title: '严重错误',
|
|||
|
|
content: userMessage + '\n\n建议重启应用或联系客服。',
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '稍后处理',
|
|||
|
|
confirmText: '重启应用',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
wx.reLaunch({
|
|||
|
|
url: '/pages/splash/splash'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取用户友好的错误消息
|
|||
|
|
getUserFriendlyMessage(errorInfo) {
|
|||
|
|
const messageMap = {
|
|||
|
|
[this.errorTypes.NETWORK_ERROR]: '网络连接异常,请检查网络设置',
|
|||
|
|
[this.errorTypes.API_ERROR]: '服务暂时不可用,请稍后重试',
|
|||
|
|
[this.errorTypes.PARSE_ERROR]: '数据格式错误,请稍后重试',
|
|||
|
|
[this.errorTypes.STORAGE_ERROR]: '存储空间不足或存储异常',
|
|||
|
|
[this.errorTypes.PERMISSION_ERROR]: '权限不足,请检查相关权限设置',
|
|||
|
|
[this.errorTypes.VALIDATION_ERROR]: '输入信息有误,请检查后重试',
|
|||
|
|
[this.errorTypes.UNKNOWN_ERROR]: '操作失败,请稍后重试'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let message = messageMap[errorInfo.type] || messageMap[this.errorTypes.UNKNOWN_ERROR];
|
|||
|
|
|
|||
|
|
// 如果配置显示详情,添加错误详情
|
|||
|
|
if (this.config.userNotification.showDetails && errorInfo.message) {
|
|||
|
|
message += `\n\n详情: ${errorInfo.message}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return message;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取错误严重程度
|
|||
|
|
getErrorSeverity(errorType) {
|
|||
|
|
const severityMap = {
|
|||
|
|
[this.errorTypes.NETWORK_ERROR]: 'medium',
|
|||
|
|
[this.errorTypes.API_ERROR]: 'medium',
|
|||
|
|
[this.errorTypes.PARSE_ERROR]: 'high',
|
|||
|
|
[this.errorTypes.STORAGE_ERROR]: 'high',
|
|||
|
|
[this.errorTypes.PERMISSION_ERROR]: 'medium',
|
|||
|
|
[this.errorTypes.VALIDATION_ERROR]: 'low',
|
|||
|
|
[this.errorTypes.UNKNOWN_ERROR]: 'high'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return severityMap[errorType] || 'medium';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔧 ===== 特定错误处理 =====
|
|||
|
|
|
|||
|
|
// 处理特定类型的错误
|
|||
|
|
handleSpecificError(errorInfo) {
|
|||
|
|
switch (errorInfo.type) {
|
|||
|
|
case this.errorTypes.NETWORK_ERROR:
|
|||
|
|
this.handleNetworkError(errorInfo);
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case this.errorTypes.API_ERROR:
|
|||
|
|
this.handleApiError(errorInfo);
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case this.errorTypes.STORAGE_ERROR:
|
|||
|
|
this.handleStorageError(errorInfo);
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case this.errorTypes.PERMISSION_ERROR:
|
|||
|
|
this.handlePermissionError(errorInfo);
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
this.handleGenericError(errorInfo);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理网络错误
|
|||
|
|
handleNetworkError(errorInfo) {
|
|||
|
|
|
|||
|
|
// 检查网络状态
|
|||
|
|
wx.getNetworkType({
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.networkType === 'none') {
|
|||
|
|
// 无网络连接
|
|||
|
|
this.handleOfflineMode();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理API错误
|
|||
|
|
handleApiError(errorInfo) {
|
|||
|
|
|
|||
|
|
// 根据状态码进行特殊处理
|
|||
|
|
if (errorInfo.statusCode === 401) {
|
|||
|
|
// 未授权,可能需要重新登录
|
|||
|
|
this.handleUnauthorizedError();
|
|||
|
|
} else if (errorInfo.statusCode >= 500) {
|
|||
|
|
// 服务器错误,可以尝试重试
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理存储错误
|
|||
|
|
handleStorageError(errorInfo) {
|
|||
|
|
|
|||
|
|
// 尝试清理存储空间
|
|||
|
|
this.cleanupStorage();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理权限错误
|
|||
|
|
handlePermissionError(errorInfo) {
|
|||
|
|
|
|||
|
|
// 可以引导用户去设置页面
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理通用错误
|
|||
|
|
handleGenericError(errorInfo) {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理离线模式
|
|||
|
|
handleOfflineMode() {
|
|||
|
|
if (!this.config.fallback.offlineMode) return;
|
|||
|
|
|
|||
|
|
// 可以设置全局离线状态
|
|||
|
|
// app.globalData.isOffline = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理未授权错误
|
|||
|
|
handleUnauthorizedError() {
|
|||
|
|
|
|||
|
|
// 清除本地token
|
|||
|
|
wx.removeStorageSync('token');
|
|||
|
|
|
|||
|
|
// 跳转到登录页面
|
|||
|
|
wx.reLaunch({
|
|||
|
|
url: '/pages/login/login'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理存储空间
|
|||
|
|
cleanupStorage() {
|
|||
|
|
try {
|
|||
|
|
// 获取存储信息
|
|||
|
|
const storageInfo = wx.getStorageInfoSync();
|
|||
|
|
|
|||
|
|
if (storageInfo.currentSize > storageInfo.limitSize * 0.8) {
|
|||
|
|
// 存储空间使用超过80%,进行清理
|
|||
|
|
|
|||
|
|
// 清理缓存数据
|
|||
|
|
const keysToClean = ['cache_', 'temp_', 'old_'];
|
|||
|
|
|
|||
|
|
storageInfo.keys.forEach(key => {
|
|||
|
|
if (keysToClean.some(prefix => key.startsWith(prefix))) {
|
|||
|
|
wx.removeStorageSync(key);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 清理存储空间失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 📊 ===== 错误统计 =====
|
|||
|
|
|
|||
|
|
// 更新错误统计
|
|||
|
|
updateErrorStats(errorInfo) {
|
|||
|
|
this.errorStats.totalErrors++;
|
|||
|
|
|
|||
|
|
// 按类型统计
|
|||
|
|
const typeCount = this.errorStats.errorsByType.get(errorInfo.type) || 0;
|
|||
|
|
this.errorStats.errorsByType.set(errorInfo.type, typeCount + 1);
|
|||
|
|
|
|||
|
|
// 按页面统计
|
|||
|
|
const pageCount = this.errorStats.errorsByPage.get(errorInfo.pagePath) || 0;
|
|||
|
|
this.errorStats.errorsByPage.set(errorInfo.pagePath, pageCount + 1);
|
|||
|
|
|
|||
|
|
// 记录最近错误
|
|||
|
|
this.errorStats.recentErrors.push(errorInfo);
|
|||
|
|
|
|||
|
|
// 保持最近50条错误记录
|
|||
|
|
if (this.errorStats.recentErrors.length > 50) {
|
|||
|
|
this.errorStats.recentErrors.shift();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取错误统计
|
|||
|
|
getErrorStats() {
|
|||
|
|
return {
|
|||
|
|
totalErrors: this.errorStats.totalErrors,
|
|||
|
|
errorsByType: Object.fromEntries(this.errorStats.errorsByType),
|
|||
|
|
errorsByPage: Object.fromEntries(this.errorStats.errorsByPage),
|
|||
|
|
recentErrors: this.errorStats.recentErrors.slice(-10), // 最近10条
|
|||
|
|
errorRate: this.calculateErrorRate()
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算错误率
|
|||
|
|
calculateErrorRate() {
|
|||
|
|
const sessionDuration = Date.now() - (performanceMonitor.monitoringState?.startTime || Date.now());
|
|||
|
|
const hours = sessionDuration / (1000 * 60 * 60);
|
|||
|
|
|
|||
|
|
return hours > 0 ? this.errorStats.totalErrors / hours : 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔧 ===== 工具方法 =====
|
|||
|
|
|
|||
|
|
// 设置全局错误监听
|
|||
|
|
setupGlobalErrorHandlers() {
|
|||
|
|
// 微信小程序的错误监听
|
|||
|
|
wx.onError((error) => {
|
|||
|
|
this.handleError(new Error(error), { source: 'global' });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听未处理的Promise拒绝
|
|||
|
|
wx.onUnhandledRejection((res) => {
|
|||
|
|
this.handleError(res.reason, { source: 'unhandled_promise' });
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置网络错误监听
|
|||
|
|
setupNetworkErrorHandlers() {
|
|||
|
|
// 监听网络状态变化
|
|||
|
|
wx.onNetworkStatusChange((res) => {
|
|||
|
|
if (!res.isConnected) {
|
|||
|
|
this.handleError(new Error('Network disconnected'), {
|
|||
|
|
source: 'network_change',
|
|||
|
|
networkType: res.networkType
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置Promise错误监听
|
|||
|
|
setupPromiseErrorHandlers() {
|
|||
|
|
// 这个在微信小程序中通过wx.onUnhandledRejection已经处理
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成错误ID
|
|||
|
|
generateErrorId() {
|
|||
|
|
return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成重试ID
|
|||
|
|
generateRetryId() {
|
|||
|
|
return `retry_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前页面路径
|
|||
|
|
getCurrentPagePath() {
|
|||
|
|
try {
|
|||
|
|
const pages = getCurrentPages();
|
|||
|
|
if (!pages || pages.length === 0) {
|
|||
|
|
console.warn('没有找到当前页面信息');
|
|||
|
|
return 'unknown';
|
|||
|
|
}
|
|||
|
|
const currentPage = pages[pages.length - 1];
|
|||
|
|
if (!currentPage || !currentPage.route) {
|
|||
|
|
console.warn('当前页面或路由信息不存在');
|
|||
|
|
return 'unknown';
|
|||
|
|
}
|
|||
|
|
return currentPage.route;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取当前页面路径失败:', error);
|
|||
|
|
return 'unknown';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取用户代理
|
|||
|
|
getUserAgent() {
|
|||
|
|
try {
|
|||
|
|
// 使用新的API替代已弃用的wx.getSystemInfoSync
|
|||
|
|
const deviceInfo = wx.getDeviceInfo();
|
|||
|
|
const appBaseInfo = wx.getAppBaseInfo();
|
|||
|
|
return `${deviceInfo.platform} ${deviceInfo.system} WeChat/${appBaseInfo.version}`;
|
|||
|
|
} catch (error) {
|
|||
|
|
// 兜底方案
|
|||
|
|
try {
|
|||
|
|
const systemInfo = wx.getSystemInfoSync();
|
|||
|
|
return `${systemInfo.platform} ${systemInfo.system} WeChat/${systemInfo.version}`;
|
|||
|
|
} catch (fallbackError) {
|
|||
|
|
return 'unknown';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 睡眠函数
|
|||
|
|
sleep(ms) {
|
|||
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 销毁错误处理器
|
|||
|
|
destroy() {
|
|||
|
|
this.errorStats = {
|
|||
|
|
totalErrors: 0,
|
|||
|
|
errorsByType: new Map(),
|
|||
|
|
errorsByPage: new Map(),
|
|||
|
|
recentErrors: []
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.retryQueue.clear();
|
|||
|
|
this.fallbackCache.clear();
|
|||
|
|
|
|||
|
|
this.isInitialized = false;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建全局实例
|
|||
|
|
const errorHandler = new ErrorHandler();
|
|||
|
|
|
|||
|
|
module.exports = errorHandler;
|
|||
|
|
|