374 lines
9.5 KiB
JavaScript
374 lines
9.5 KiB
JavaScript
/**
|
|
* 全局错误处理工具
|
|
* 用于统一处理应用中的各种错误
|
|
*/
|
|
|
|
import { ElMessage, ElNotification } from 'element-plus'
|
|
import router from '@/router'
|
|
|
|
// 错误类型枚举
|
|
export const ErrorTypes = {
|
|
NETWORK: 'network',
|
|
API: 'api',
|
|
VALIDATION: 'validation',
|
|
PERMISSION: 'permission',
|
|
BUSINESS: 'business',
|
|
UNKNOWN: 'unknown'
|
|
}
|
|
|
|
// 错误级别枚举
|
|
export const ErrorLevels = {
|
|
INFO: 'info',
|
|
WARNING: 'warning',
|
|
ERROR: 'error',
|
|
CRITICAL: 'critical'
|
|
}
|
|
|
|
// 错误处理配置
|
|
const errorHandlingConfig = {
|
|
// 是否显示错误通知
|
|
showNotification: true,
|
|
// 是否记录错误日志
|
|
logError: true,
|
|
// 是否上报错误
|
|
reportError: true,
|
|
// 错误上报URL
|
|
reportUrl: '/api/errors/report',
|
|
// 最大重试次数
|
|
maxRetries: 3,
|
|
// 重试延迟(毫秒)
|
|
retryDelay: 1000
|
|
}
|
|
|
|
/**
|
|
* 错误处理器类
|
|
*/
|
|
class ErrorHandler {
|
|
constructor() {
|
|
this.errorQueue = []
|
|
this.retryCount = new Map()
|
|
this.initGlobalErrorHandlers()
|
|
}
|
|
|
|
/**
|
|
* 初始化全局错误处理器
|
|
*/
|
|
initGlobalErrorHandlers() {
|
|
// 监听未捕获的Promise错误
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
this.handleError(event.reason, {
|
|
type: ErrorTypes.UNKNOWN,
|
|
level: ErrorLevels.ERROR,
|
|
context: 'unhandledrejection',
|
|
promise: event.promise
|
|
})
|
|
})
|
|
|
|
// 监听全局JavaScript错误
|
|
window.addEventListener('error', (event) => {
|
|
this.handleError(event.error || new Error(event.message), {
|
|
type: ErrorTypes.UNKNOWN,
|
|
level: ErrorLevels.ERROR,
|
|
context: 'javascript',
|
|
filename: event.filename,
|
|
lineno: event.lineno,
|
|
colno: event.colno
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 处理错误
|
|
* @param {Error} error - 错误对象
|
|
* @param {Object} options - 错误处理选项
|
|
*/
|
|
handleError(error, options = {}) {
|
|
const {
|
|
type = ErrorTypes.UNKNOWN,
|
|
level = ErrorLevels.ERROR,
|
|
context = '',
|
|
showMessage = true,
|
|
customMessage = '',
|
|
retryCallback = null,
|
|
...otherOptions
|
|
} = options
|
|
|
|
// 构建错误信息
|
|
const errorInfo = {
|
|
message: error?.message || '未知错误',
|
|
stack: error?.stack || '',
|
|
type,
|
|
level,
|
|
context,
|
|
url: window.location.href,
|
|
userAgent: navigator.userAgent,
|
|
timestamp: new Date().toISOString(),
|
|
...otherOptions
|
|
}
|
|
|
|
// 记录错误
|
|
if (errorHandlingConfig.logError) {
|
|
console.error(`[ErrorHandler] ${level.toUpperCase()}:`, error, errorInfo)
|
|
}
|
|
|
|
// 上报错误
|
|
if (errorHandlingConfig.reportError) {
|
|
this.reportError(errorInfo)
|
|
}
|
|
|
|
// 显示错误消息
|
|
if (showMessage) {
|
|
this.showErrorMessage(errorInfo, customMessage)
|
|
}
|
|
|
|
// 执行重试回调
|
|
if (retryCallback && typeof retryCallback === 'function') {
|
|
this.executeRetry(retryCallback, errorInfo)
|
|
}
|
|
|
|
// 将错误添加到队列
|
|
this.errorQueue.push(errorInfo)
|
|
|
|
// 限制错误队列大小
|
|
if (this.errorQueue.length > 100) {
|
|
this.errorQueue.shift()
|
|
}
|
|
|
|
return errorInfo
|
|
}
|
|
|
|
/**
|
|
* 处理网络错误
|
|
* @param {Error} error - 错误对象
|
|
* @param {Object} options - 错误处理选项
|
|
*/
|
|
handleNetworkError(error, options = {}) {
|
|
return this.handleError(error, {
|
|
type: ErrorTypes.NETWORK,
|
|
level: ErrorLevels.WARNING,
|
|
context: 'network',
|
|
customMessage: '网络连接失败,请检查网络设置',
|
|
...options
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 处理API错误
|
|
* @param {Object} response - API响应对象
|
|
* @param {Object} options - 错误处理选项
|
|
*/
|
|
handleApiError(response, options = {}) {
|
|
const { status, data } = response
|
|
let message = '服务器错误'
|
|
let level = ErrorLevels.ERROR
|
|
|
|
// 根据状态码设置错误消息和级别
|
|
switch (status) {
|
|
case 400:
|
|
message = data?.message || '请求参数错误'
|
|
level = ErrorLevels.WARNING
|
|
break
|
|
case 401:
|
|
message = '未授权,请重新登录'
|
|
level = ErrorLevels.WARNING
|
|
// 跳转到登录页
|
|
router.push('/login')
|
|
break
|
|
case 403:
|
|
message = '没有权限访问该资源'
|
|
level = ErrorLevels.WARNING
|
|
break
|
|
case 404:
|
|
message = '请求的资源不存在'
|
|
level = ErrorLevels.WARNING
|
|
break
|
|
case 500:
|
|
message = '服务器内部错误'
|
|
level = ErrorLevels.ERROR
|
|
break
|
|
case 502:
|
|
case 503:
|
|
case 504:
|
|
message = '服务暂时不可用,请稍后重试'
|
|
level = ErrorLevels.ERROR
|
|
break
|
|
default:
|
|
message = data?.message || `服务器错误 (${status})`
|
|
level = ErrorLevels.ERROR
|
|
}
|
|
|
|
return this.handleError(new Error(message), {
|
|
type: ErrorTypes.API,
|
|
level,
|
|
context: 'api',
|
|
status,
|
|
responseData: data,
|
|
...options
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 处理验证错误
|
|
* @param {Object} errors - 验证错误对象
|
|
* @param {Object} options - 错误处理选项
|
|
*/
|
|
handleValidationErrors(errors, options = {}) {
|
|
let message = '输入验证失败'
|
|
|
|
// 如果是数组,取第一个错误
|
|
if (Array.isArray(errors) && errors.length > 0) {
|
|
message = errors[0].message || message
|
|
}
|
|
// 如果是对象,取第一个错误
|
|
else if (typeof errors === 'object' && errors !== null) {
|
|
const firstKey = Object.keys(errors)[0]
|
|
if (firstKey && errors[firstKey]) {
|
|
message = Array.isArray(errors[firstKey])
|
|
? errors[firstKey][0]
|
|
: errors[firstKey].message || message
|
|
}
|
|
}
|
|
|
|
return this.handleError(new Error(message), {
|
|
type: ErrorTypes.VALIDATION,
|
|
level: ErrorLevels.WARNING,
|
|
context: 'validation',
|
|
validationErrors: errors,
|
|
...options
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 显示错误消息
|
|
* @param {Object} errorInfo - 错误信息
|
|
* @param {string} customMessage - 自定义消息
|
|
*/
|
|
showErrorMessage(errorInfo, customMessage = '') {
|
|
const message = customMessage || errorInfo.message
|
|
const { level } = errorInfo
|
|
|
|
if (!errorHandlingConfig.showNotification) {
|
|
return
|
|
}
|
|
|
|
// 根据错误级别选择不同的显示方式
|
|
switch (level) {
|
|
case ErrorLevels.INFO:
|
|
ElMessage.info(message)
|
|
break
|
|
case ErrorLevels.WARNING:
|
|
ElMessage.warning(message)
|
|
break
|
|
case ErrorLevels.ERROR:
|
|
ElMessage.error(message)
|
|
break
|
|
case ErrorLevels.CRITICAL:
|
|
ElNotification({
|
|
title: '严重错误',
|
|
message,
|
|
type: 'error',
|
|
duration: 0, // 不自动关闭
|
|
showClose: true
|
|
})
|
|
break
|
|
default:
|
|
ElMessage.error(message)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 上报错误
|
|
* @param {Object} errorInfo - 错误信息
|
|
*/
|
|
reportError(errorInfo) {
|
|
try {
|
|
// 使用navigator.sendBeacon进行非阻塞上报
|
|
if (navigator.sendBeacon) {
|
|
const data = new Blob([JSON.stringify(errorInfo)], {
|
|
type: 'application/json'
|
|
})
|
|
navigator.sendBeacon(errorHandlingConfig.reportUrl, data)
|
|
} else {
|
|
// 降级使用fetch
|
|
fetch(errorHandlingConfig.reportUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(errorInfo),
|
|
keepalive: true // 尝试保持连接
|
|
}).catch(err => {
|
|
console.error('[ErrorHandler] 上报错误失败:', err)
|
|
})
|
|
}
|
|
} catch (err) {
|
|
console.error('[ErrorHandler] 上报错误异常:', err)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 执行重试
|
|
* @param {Function} callback - 重试回调
|
|
* @param {Object} errorInfo - 错误信息
|
|
*/
|
|
executeRetry(callback, errorInfo) {
|
|
const errorKey = `${errorInfo.type}_${errorInfo.context}`
|
|
const currentRetryCount = this.retryCount.get(errorKey) || 0
|
|
|
|
if (currentRetryCount < errorHandlingConfig.maxRetries) {
|
|
this.retryCount.set(errorKey, currentRetryCount + 1)
|
|
|
|
ElMessage.info(`正在重试 (${currentRetryCount + 1}/${errorHandlingConfig.maxRetries})...`)
|
|
|
|
setTimeout(() => {
|
|
try {
|
|
callback()
|
|
} catch (err) {
|
|
this.handleError(err, {
|
|
type: errorInfo.type,
|
|
level: errorInfo.level,
|
|
context: `${errorInfo.context}_retry`,
|
|
showMessage: false
|
|
})
|
|
}
|
|
}, errorHandlingConfig.retryDelay)
|
|
} else {
|
|
ElMessage.error('已达到最大重试次数,请稍后再试')
|
|
this.retryCount.delete(errorKey)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取错误队列
|
|
*/
|
|
getErrorQueue() {
|
|
return [...this.errorQueue]
|
|
}
|
|
|
|
/**
|
|
* 清空错误队列
|
|
*/
|
|
clearErrorQueue() {
|
|
this.errorQueue = []
|
|
}
|
|
|
|
/**
|
|
* 配置错误处理
|
|
* @param {Object} config - 配置选项
|
|
*/
|
|
configure(config) {
|
|
Object.assign(errorHandlingConfig, config)
|
|
}
|
|
}
|
|
|
|
// 创建全局错误处理器实例
|
|
const globalErrorHandler = new ErrorHandler()
|
|
|
|
export default globalErrorHandler
|
|
|
|
// 导出便捷方法
|
|
export const handleError = (error, options) => globalErrorHandler.handleError(error, options)
|
|
export const handleNetworkError = (error, options) => globalErrorHandler.handleNetworkError(error, options)
|
|
export const handleApiError = (response, options) => globalErrorHandler.handleApiError(response, options)
|
|
export const handleValidationErrors = (errors, options) => globalErrorHandler.handleValidationErrors(errors, options)
|
|
export const configureErrorHandler = (config) => globalErrorHandler.configure(config) |