初始化
Some checks failed
Some checks failed
This commit is contained in:
374
frontend/src/utils/errorHandler.js
Normal file
374
frontend/src/utils/errorHandler.js
Normal file
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* 全局错误处理工具
|
||||
* 用于统一处理应用中的各种错误
|
||||
*/
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user