Files
it/frontend/src/utils/errorHandler.js
XCool f25b0307db
Some checks failed
CI/CD Pipeline / 测试 (18.x) (push) Has been cancelled
CI/CD Pipeline / 测试 (20.x) (push) Has been cancelled
CI/CD Pipeline / 安全检查 (push) Has been cancelled
CI/CD Pipeline / 部署 (push) Has been cancelled
CI/CD Pipeline / 通知 (push) Has been cancelled
初始化
2025-11-03 19:47:36 +08:00

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)