@vlian/framework 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/dist/core/config/ConfigLoader.cjs +90 -8
- package/dist/core/config/ConfigLoader.cjs.map +1 -1
- package/dist/core/config/ConfigLoader.d.ts +20 -4
- package/dist/core/config/ConfigLoader.d.ts.map +1 -1
- package/dist/core/config/ConfigLoader.js +90 -8
- package/dist/core/config/ConfigLoader.js.map +1 -1
- package/dist/core/error/ErrorBoundary.cjs +23 -9
- package/dist/core/error/ErrorBoundary.cjs.map +1 -1
- package/dist/core/error/ErrorBoundary.d.ts.map +1 -1
- package/dist/core/error/ErrorBoundary.js +23 -9
- package/dist/core/error/ErrorBoundary.js.map +1 -1
- package/dist/core/error/ErrorHandler.cjs +277 -0
- package/dist/core/error/ErrorHandler.cjs.map +1 -0
- package/dist/core/error/ErrorHandler.d.ts +172 -0
- package/dist/core/error/ErrorHandler.d.ts.map +1 -0
- package/dist/core/error/ErrorHandler.js +261 -0
- package/dist/core/error/ErrorHandler.js.map +1 -0
- package/dist/core/error/index.cjs +10 -0
- package/dist/core/error/index.cjs.map +1 -1
- package/dist/core/error/index.d.ts +2 -0
- package/dist/core/error/index.d.ts.map +1 -1
- package/dist/core/error/index.js +1 -0
- package/dist/core/error/index.js.map +1 -1
- package/dist/core/startup/initializeServices.cjs +69 -24
- package/dist/core/startup/initializeServices.cjs.map +1 -1
- package/dist/core/startup/initializeServices.d.ts +9 -2
- package/dist/core/startup/initializeServices.d.ts.map +1 -1
- package/dist/core/startup/initializeServices.js +73 -26
- package/dist/core/startup/initializeServices.js.map +1 -1
- package/dist/core/startup/renderApp.cjs +100 -61
- package/dist/core/startup/renderApp.cjs.map +1 -1
- package/dist/core/startup/renderApp.d.ts +2 -2
- package/dist/core/startup/renderApp.d.ts.map +1 -1
- package/dist/core/startup/renderApp.js +101 -62
- package/dist/core/startup/renderApp.js.map +1 -1
- package/dist/core/startup/startApp.cjs +7 -0
- package/dist/core/startup/startApp.cjs.map +1 -1
- package/dist/core/startup/startApp.d.ts.map +1 -1
- package/dist/core/startup/startApp.js +7 -0
- package/dist/core/startup/startApp.js.map +1 -1
- package/dist/index.umd.js +32434 -39098
- package/dist/index.umd.js.map +1 -1
- package/dist/request/core/RequestClient.cjs +20 -1
- package/dist/request/core/RequestClient.cjs.map +1 -1
- package/dist/request/core/RequestClient.d.ts +8 -0
- package/dist/request/core/RequestClient.d.ts.map +1 -1
- package/dist/request/core/RequestClient.js +20 -1
- package/dist/request/core/RequestClient.js.map +1 -1
- package/dist/request/index.cjs +3 -0
- package/dist/request/index.cjs.map +1 -1
- package/dist/request/index.d.ts +2 -2
- package/dist/request/index.d.ts.map +1 -1
- package/dist/request/index.js +1 -1
- package/dist/request/index.js.map +1 -1
- package/dist/request/plugin/index.cjs +4 -0
- package/dist/request/plugin/index.cjs.map +1 -1
- package/dist/request/plugin/index.d.ts +1 -0
- package/dist/request/plugin/index.d.ts.map +1 -1
- package/dist/request/plugin/index.js +1 -0
- package/dist/request/plugin/index.js.map +1 -1
- package/dist/request/plugin/queue.cjs +140 -0
- package/dist/request/plugin/queue.cjs.map +1 -0
- package/dist/request/plugin/queue.d.ts +92 -0
- package/dist/request/plugin/queue.d.ts.map +1 -0
- package/dist/request/plugin/queue.js +156 -0
- package/dist/request/plugin/queue.js.map +1 -0
- package/dist/request/utils/RequestQueueManager.cjs +168 -0
- package/dist/request/utils/RequestQueueManager.cjs.map +1 -0
- package/dist/request/utils/RequestQueueManager.d.ts +75 -0
- package/dist/request/utils/RequestQueueManager.d.ts.map +1 -0
- package/dist/request/utils/RequestQueueManager.js +160 -0
- package/dist/request/utils/RequestQueueManager.js.map +1 -0
- package/dist/request/utils/index.cjs +4 -0
- package/dist/request/utils/index.cjs.map +1 -1
- package/dist/request/utils/index.d.ts +1 -0
- package/dist/request/utils/index.d.ts.map +1 -1
- package/dist/request/utils/index.js +1 -0
- package/dist/request/utils/index.js.map +1 -1
- package/package.json +40 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/error/ErrorBoundary.tsx"],"sourcesContent":["import React, { type ReactNode, type ErrorInfo, useState, useRef, useEffect, useCallback, useMemo } from 'react';\nimport { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';\nimport { Result, Button } from 'antd';\nimport { logger } from '../../utils';\nimport { errorUtils } from '../../utils/errors';\nimport type { FrameworkError } from '../../utils/errors';\nimport { initializationErrorState } from '../initialization';\n\n/**\n * 错误边界组件属性\n */\nexport interface ErrorBoundaryProps {\n /**\n * 子组件\n */\n children: ReactNode;\n /**\n * 错误回退 UI(函数形式)\n * \n * 如果不提供,将使用默认的错误 UI(基于 antd 的 Result 组件,居中显示)。\n * 如果提供,将使用自定义的错误 UI。\n * \n * @param error - 标准化后的框架错误对象\n * @param resetError - 重置错误边界的函数,调用后可以尝试重新渲染子组件\n * @returns 错误 UI 的 React 节点\n * \n * @example\n * ```tsx\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <Result\n * status=\"error\"\n * title=\"自定义错误标题\"\n * subTitle={error.message}\n * extra={<Button onClick={reset}>重试</Button>}\n * />\n * )}\n * >\n * <App />\n * </ErrorBoundary>\n * ```\n */\n fallback?: (error: FrameworkError, resetError: () => void) => ReactNode;\n /**\n * 错误回调\n */\n onError?: (error: FrameworkError, errorInfo: ErrorInfo) => void;\n /**\n * 是否在控制台显示错误\n */\n showInConsole?: boolean;\n /**\n * 重置键数组,当这些值变化时自动重置错误边界\n */\n resetKeys?: Array<string | number>;\n /**\n * 重置错误时的回调\n */\n onReset?: () => void;\n /**\n * 重置键变化时的回调\n */\n onResetKeysChange?: (prevKeys: Array<string | number> | undefined, nextKeys: Array<string | number> | undefined) => void;\n}\n\n/**\n * 错误边界组件\n * \n * 基于 react-error-boundary 实现,集成了框架的错误处理系统\n * 用于捕获 React 组件树中的错误,防止整个应用崩溃\n * \n * 默认使用 antd 的 Result 组件显示错误 UI,居中显示,美观易用。\n * 可以通过 fallback 属性自定义错误 UI。\n * \n * @example\n * ```tsx\n * // 使用默认错误 UI(推荐)\n * <ErrorBoundary>\n * <App />\n * </ErrorBoundary>\n * \n * // 自定义错误 UI\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <div>\n * <h1>出错了</h1>\n * <p>{error.message}</p>\n * <button onClick={reset}>重试</button>\n * </div>\n * )}\n * >\n * <App />\n * </ErrorBoundary>\n * ```\n */\nexport function ErrorBoundary({\n children,\n fallback,\n onError,\n showInConsole = true,\n resetKeys,\n onReset,\n onResetKeysChange,\n}: ErrorBoundaryProps): ReactNode {\n // 使用 ref 存储重试计数,在多次错误之间保持状态\n const retryCountRef = useRef<number>(0);\n\n // 处理错误,将标准 Error 转换为 FrameworkError\n const handleError = (error: Error, errorInfo: { componentStack?: string | null }): void => {\n const normalizedError = errorUtils.normalizeError(error);\n\n // 将 react-error-boundary 的 errorInfo 转换为 React 的 ErrorInfo 格式\n const reactErrorInfo: ErrorInfo = {\n componentStack: errorInfo.componentStack || '',\n };\n\n // 记录错误\n logger.error('错误边界捕获到错误:', {\n error: normalizedError.toJSON(),\n errorInfo: {\n componentStack: reactErrorInfo.componentStack,\n },\n });\n\n // 调用错误回调\n if (onError) {\n onError(normalizedError, reactErrorInfo);\n }\n\n // 在控制台显示错误\n if (showInConsole) {\n console.error('错误边界捕获到错误:', normalizedError);\n console.error('错误信息:', reactErrorInfo);\n }\n };\n\n // 处理重置\n const handleReset = (): void => {\n // 清除初始化错误状态\n initializationErrorState.clearError();\n // 调用用户提供的重置回调\n if (onReset) {\n onReset();\n }\n };\n\n // 默认 fallback UI(使用 antd 组件)\n // 使用内部组件来管理重试计数状态,使用 React.memo 优化性能\n const DefaultFallbackComponent = React.memo(({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n const normalizedError = errorUtils.normalizeError(error);\n const [retryCount, setRetryCount] = useState<number>(retryCountRef.current);\n const [showDetails, setShowDetails] = useState(false);\n\n // 同步 ref 的值到 state,确保组件重新创建时能读取到最新的重试计数\n useEffect(() => {\n setRetryCount(retryCountRef.current);\n }, []);\n\n const handleRetry = useCallback((): void => {\n // 增加重试计数\n const newCount = retryCount + 1;\n retryCountRef.current = newCount;\n setRetryCount(newCount);\n \n // 执行重置\n handleReset();\n resetErrorBoundary();\n }, [retryCount, resetErrorBoundary]);\n\n const handleReload = useCallback((): void => {\n // 刷新页面\n window.location.reload();\n }, []);\n\n // 构建用户友好的错误提示\n const userFriendlyMessage = useMemo(() => {\n // 根据错误类型提供不同的提示\n if (normalizedError.code === 'NETWORK_ERROR') {\n return '网络连接失败,请检查您的网络设置后重试';\n }\n if (normalizedError.code === 'TIMEOUT_ERROR') {\n return '请求超时,请稍后重试';\n }\n if (normalizedError.code === 'VALIDATION_ERROR') {\n return '数据验证失败,请检查输入内容';\n }\n // 默认提示\n return '应用运行时发生了错误,请稍后重试';\n }, [normalizedError.code]);\n\n // 构建按钮数组\n const extraButtons = useMemo(() => {\n const buttons = [\n <Button\n type=\"primary\"\n key=\"retry\"\n onClick={handleRetry}\n aria-label=\"重试加载应用\"\n >\n 重试\n </Button>,\n <Button\n key=\"details\"\n onClick={() => setShowDetails(!showDetails)}\n aria-label={showDetails ? '隐藏错误详情' : '显示错误详情'}\n >\n {showDetails ? '隐藏详情' : '查看详情'}\n </Button>,\n ];\n\n // 如果重试次数 >= 1,添加刷新页面按钮(提前显示)\n if (retryCount >= 1) {\n buttons.push(\n <Button\n key=\"reload\"\n onClick={handleReload}\n aria-label=\"刷新页面\"\n >\n 刷新页面\n </Button>\n );\n }\n\n return buttons;\n }, [retryCount, handleRetry, handleReload, showDetails]);\n\n return (\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: '100vh',\n padding: '24px',\n }}\n >\n <Result\n status=\"error\"\n title=\"出错了\"\n subTitle={userFriendlyMessage}\n extra={extraButtons}\n />\n {showDetails && (\n <div\n style={{\n marginTop: 24,\n padding: 16,\n backgroundColor: '#f5f5f5',\n borderRadius: 4,\n maxWidth: 800,\n width: '100%',\n maxHeight: 400,\n overflow: 'auto',\n }}\n >\n <div style={{ marginBottom: 8, fontWeight: 'bold' }}>错误详情:</div>\n <div style={{ fontFamily: 'monospace', fontSize: 12 }}>\n <div><strong>错误类型:</strong>{normalizedError.name}</div>\n <div><strong>错误代码:</strong>{normalizedError.code}</div>\n <div><strong>错误信息:</strong>{normalizedError.message}</div>\n {normalizedError.stack && (\n <div style={{ marginTop: 8 }}>\n <strong>堆栈信息:</strong>\n <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>\n {normalizedError.stack}\n </pre>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n );\n });\n \n DefaultFallbackComponent.displayName = 'DefaultFallbackComponent';\n\n const defaultFallback = ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n return <DefaultFallbackComponent error={error} resetErrorBoundary={resetErrorBoundary} />;\n };\n\n // 如果提供了自定义 fallback,使用自定义的\n const fallbackRender = fallback\n ? ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n const normalizedError = errorUtils.normalizeError(error);\n return fallback(normalizedError, () => {\n handleReset();\n resetErrorBoundary();\n });\n }\n : defaultFallback;\n\n // 处理重置时清除重试计数\n const handleResetWithCount = (): void => {\n retryCountRef.current = 0;\n handleReset();\n };\n\n // 处理 resetKeys 变化时清除重试计数\n const handleResetKeysChange = (\n prevKeys: Array<string | number> | undefined,\n nextKeys: Array<string | number> | undefined\n ): void => {\n retryCountRef.current = 0;\n if (onResetKeysChange) {\n onResetKeysChange(prevKeys, nextKeys);\n }\n };\n\n // 构建 react-error-boundary 的 props\n const errorBoundaryProps = {\n fallbackRender,\n onError: handleError,\n onReset: handleResetWithCount,\n resetKeys,\n onResetKeysChange: handleResetKeysChange,\n } as any;\n\n return (\n <ReactErrorBoundary {...errorBoundaryProps}>\n {children}\n </ReactErrorBoundary>\n );\n}\n\n/**\n * 带错误边界的 HOC\n */\nexport function withErrorBoundary<P extends object>(\n Component: React.ComponentType<P>,\n errorBoundaryProps?: Omit<ErrorBoundaryProps, 'children'>\n): React.ComponentType<P> {\n const WrappedComponent = (props: P) => {\n return (\n <ErrorBoundary {...errorBoundaryProps}>\n <Component {...props} />\n </ErrorBoundary>\n );\n };\n\n WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name || 'Component'})`;\n\n return WrappedComponent;\n}\n\n/**\n * 使用错误处理器的 Hook\n * \n * 用于在函数组件中处理异步错误\n * \n * 注意:这个 Hook 需要配合 ErrorBoundary 使用。当调用返回的错误处理函数时,\n * 错误会被设置到初始化错误状态,然后通过 InitializationErrorThrower 组件抛出。\n * \n * @example\n * ```tsx\n * import { useErrorHandler } from '@vlian/framework';\n * \n * function MyComponent() {\n * const handleError = useErrorHandler();\n * \n * useEffect(() => {\n * async function fetchData() {\n * try {\n * await someAsyncOperation();\n * } catch (error) {\n * handleError(error);\n * }\n * }\n * fetchData();\n * }, [handleError]);\n * }\n * ```\n */\nexport function useErrorHandler(): (error: unknown) => void {\n return (error: unknown) => {\n // 将错误设置到初始化错误状态\n // InitializationErrorThrower 组件会检测到错误并抛出,让 ErrorBoundary 捕获\n initializationErrorState.setError(error);\n };\n}\n"],"names":["ErrorBoundary","useErrorHandler","withErrorBoundary","children","fallback","onError","showInConsole","resetKeys","onReset","onResetKeysChange","retryCountRef","useRef","handleError","error","errorInfo","normalizedError","errorUtils","normalizeError","reactErrorInfo","componentStack","logger","toJSON","console","handleReset","initializationErrorState","clearError","DefaultFallbackComponent","React","memo","resetErrorBoundary","retryCount","setRetryCount","useState","current","showDetails","setShowDetails","useEffect","handleRetry","useCallback","newCount","handleReload","window","location","reload","userFriendlyMessage","useMemo","code","extraButtons","buttons","Button","type","onClick","aria-label","push","div","role","aria-live","aria-atomic","style","display","flexDirection","alignItems","justifyContent","minHeight","padding","Result","status","title","subTitle","extra","marginTop","backgroundColor","borderRadius","maxWidth","width","maxHeight","overflow","marginBottom","fontWeight","fontFamily","fontSize","strong","name","message","stack","pre","whiteSpace","wordBreak","displayName","defaultFallback","fallbackRender","handleResetWithCount","handleResetKeysChange","prevKeys","nextKeys","errorBoundaryProps","ReactErrorBoundary","Component","WrappedComponent","props","setError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/core/error/ErrorBoundary.tsx"],"sourcesContent":["import React, { type ReactNode, type ErrorInfo, useState, useRef, useEffect, useCallback, useMemo } from 'react';\nimport { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';\nimport { Result, Button } from 'antd';\nimport { logger } from '../../utils';\nimport { errorUtils } from '../../utils/errors';\nimport type { FrameworkError } from '../../utils/errors';\nimport { initializationErrorState } from '../initialization';\nimport { getDefaultErrorHandler } from './ErrorHandler';\n\n/**\n * 错误边界组件属性\n */\nexport interface ErrorBoundaryProps {\n /**\n * 子组件\n */\n children: ReactNode;\n /**\n * 错误回退 UI(函数形式)\n * \n * 如果不提供,将使用默认的错误 UI(基于 antd 的 Result 组件,居中显示)。\n * 如果提供,将使用自定义的错误 UI。\n * \n * @param error - 标准化后的框架错误对象\n * @param resetError - 重置错误边界的函数,调用后可以尝试重新渲染子组件\n * @returns 错误 UI 的 React 节点\n * \n * @example\n * ```tsx\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <Result\n * status=\"error\"\n * title=\"自定义错误标题\"\n * subTitle={error.message}\n * extra={<Button onClick={reset}>重试</Button>}\n * />\n * )}\n * >\n * <App />\n * </ErrorBoundary>\n * ```\n */\n fallback?: (error: FrameworkError, resetError: () => void) => ReactNode;\n /**\n * 错误回调\n */\n onError?: (error: FrameworkError, errorInfo: ErrorInfo) => void;\n /**\n * 是否在控制台显示错误\n */\n showInConsole?: boolean;\n /**\n * 重置键数组,当这些值变化时自动重置错误边界\n */\n resetKeys?: Array<string | number>;\n /**\n * 重置错误时的回调\n */\n onReset?: () => void;\n /**\n * 重置键变化时的回调\n */\n onResetKeysChange?: (prevKeys: Array<string | number> | undefined, nextKeys: Array<string | number> | undefined) => void;\n}\n\n/**\n * 错误边界组件\n * \n * 基于 react-error-boundary 实现,集成了框架的错误处理系统\n * 用于捕获 React 组件树中的错误,防止整个应用崩溃\n * \n * 默认使用 antd 的 Result 组件显示错误 UI,居中显示,美观易用。\n * 可以通过 fallback 属性自定义错误 UI。\n * \n * @example\n * ```tsx\n * // 使用默认错误 UI(推荐)\n * <ErrorBoundary>\n * <App />\n * </ErrorBoundary>\n * \n * // 自定义错误 UI\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <div>\n * <h1>出错了</h1>\n * <p>{error.message}</p>\n * <button onClick={reset}>重试</button>\n * </div>\n * )}\n * >\n * <App />\n * </ErrorBoundary>\n * ```\n */\nexport function ErrorBoundary({\n children,\n fallback,\n onError,\n showInConsole = true,\n resetKeys,\n onReset,\n onResetKeysChange,\n}: ErrorBoundaryProps): ReactNode {\n // 使用 ref 存储重试计数,在多次错误之间保持状态\n const retryCountRef = useRef<number>(0);\n\n // 优化:使用统一的错误处理器\n const errorHandler = useMemo(() => getDefaultErrorHandler(), []);\n\n // 处理错误,将标准 Error 转换为 FrameworkError\n const handleError = useCallback(async (error: Error, errorInfo: { componentStack?: string | null }): Promise<void> => {\n const normalizedError = errorUtils.normalizeError(error);\n\n // 将 react-error-boundary 的 errorInfo 转换为 React 的 ErrorInfo 格式\n const reactErrorInfo: ErrorInfo = {\n componentStack: errorInfo.componentStack || '',\n };\n\n // 使用统一的错误处理器处理错误\n const handleResult = await errorHandler.handleError(normalizedError, {\n componentStack: reactErrorInfo.componentStack,\n source: 'ErrorBoundary',\n });\n\n // 如果错误处理器没有处理错误,使用默认处理\n if (!handleResult.handled) {\n logger.error('错误边界捕获到错误:', {\n error: normalizedError.toJSON(),\n errorInfo: {\n componentStack: reactErrorInfo.componentStack,\n },\n });\n }\n\n // 调用错误回调\n if (onError) {\n onError(normalizedError, reactErrorInfo);\n }\n\n // 在控制台显示错误(如果配置了)\n if (showInConsole) {\n console.error('错误边界捕获到错误:', normalizedError);\n console.error('错误信息:', reactErrorInfo);\n }\n }, [errorHandler, onError, showInConsole]);\n\n // 处理重置\n const handleReset = (): void => {\n // 清除初始化错误状态\n initializationErrorState.clearError();\n // 调用用户提供的重置回调\n if (onReset) {\n onReset();\n }\n };\n\n // 默认 fallback UI(使用 antd 组件)\n // 使用内部组件来管理重试计数状态,使用 React.memo 优化性能\n const DefaultFallbackComponent = React.memo(({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n const normalizedError = errorUtils.normalizeError(error);\n const [retryCount, setRetryCount] = useState<number>(retryCountRef.current);\n const [showDetails, setShowDetails] = useState(false);\n\n // 同步 ref 的值到 state,确保组件重新创建时能读取到最新的重试计数\n useEffect(() => {\n setRetryCount(retryCountRef.current);\n }, []);\n\n const handleRetry = useCallback((): void => {\n // 增加重试计数\n const newCount = retryCount + 1;\n retryCountRef.current = newCount;\n setRetryCount(newCount);\n \n // 执行重置\n handleReset();\n resetErrorBoundary();\n }, [retryCount, resetErrorBoundary]);\n\n const handleReload = useCallback((): void => {\n // 刷新页面\n window.location.reload();\n }, []);\n\n // 构建用户友好的错误提示\n const userFriendlyMessage = useMemo(() => {\n // 根据错误类型提供不同的提示\n if (normalizedError.code === 'NETWORK_ERROR') {\n return '网络连接失败,请检查您的网络设置后重试';\n }\n if (normalizedError.code === 'TIMEOUT_ERROR') {\n return '请求超时,请稍后重试';\n }\n if (normalizedError.code === 'VALIDATION_ERROR') {\n return '数据验证失败,请检查输入内容';\n }\n // 默认提示\n return '应用运行时发生了错误,请稍后重试';\n }, [normalizedError.code]);\n\n // 构建按钮数组\n const extraButtons = useMemo(() => {\n const buttons = [\n <Button\n type=\"primary\"\n key=\"retry\"\n onClick={handleRetry}\n aria-label=\"重试加载应用\"\n >\n 重试\n </Button>,\n <Button\n key=\"details\"\n onClick={() => setShowDetails(!showDetails)}\n aria-label={showDetails ? '隐藏错误详情' : '显示错误详情'}\n >\n {showDetails ? '隐藏详情' : '查看详情'}\n </Button>,\n ];\n\n // 如果重试次数 >= 1,添加刷新页面按钮(提前显示)\n if (retryCount >= 1) {\n buttons.push(\n <Button\n key=\"reload\"\n onClick={handleReload}\n aria-label=\"刷新页面\"\n >\n 刷新页面\n </Button>\n );\n }\n\n return buttons;\n }, [retryCount, handleRetry, handleReload, showDetails]);\n\n return (\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: '100vh',\n padding: '24px',\n }}\n >\n <Result\n status=\"error\"\n title=\"出错了\"\n subTitle={userFriendlyMessage}\n extra={extraButtons}\n />\n {showDetails && (\n <div\n style={{\n marginTop: 24,\n padding: 16,\n backgroundColor: '#f5f5f5',\n borderRadius: 4,\n maxWidth: 800,\n width: '100%',\n maxHeight: 400,\n overflow: 'auto',\n }}\n >\n <div style={{ marginBottom: 8, fontWeight: 'bold' }}>错误详情:</div>\n <div style={{ fontFamily: 'monospace', fontSize: 12 }}>\n <div><strong>错误类型:</strong>{normalizedError.name}</div>\n <div><strong>错误代码:</strong>{normalizedError.code}</div>\n <div><strong>错误信息:</strong>{normalizedError.message}</div>\n {normalizedError.stack && (\n <div style={{ marginTop: 8 }}>\n <strong>堆栈信息:</strong>\n <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>\n {normalizedError.stack}\n </pre>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n );\n });\n \n DefaultFallbackComponent.displayName = 'DefaultFallbackComponent';\n\n const defaultFallback = ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n return <DefaultFallbackComponent error={error} resetErrorBoundary={resetErrorBoundary} />;\n };\n\n // 如果提供了自定义 fallback,使用自定义的\n const fallbackRender = fallback\n ? ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n const normalizedError = errorUtils.normalizeError(error);\n return fallback(normalizedError, () => {\n handleReset();\n resetErrorBoundary();\n });\n }\n : defaultFallback;\n\n // 处理重置时清除重试计数\n const handleResetWithCount = (): void => {\n retryCountRef.current = 0;\n handleReset();\n };\n\n // 处理 resetKeys 变化时清除重试计数\n const handleResetKeysChange = (\n prevKeys: Array<string | number> | undefined,\n nextKeys: Array<string | number> | undefined\n ): void => {\n retryCountRef.current = 0;\n if (onResetKeysChange) {\n onResetKeysChange(prevKeys, nextKeys);\n }\n };\n\n // 构建 react-error-boundary 的 props\n const errorBoundaryProps = {\n fallbackRender,\n onError: handleError,\n onReset: handleResetWithCount,\n resetKeys,\n onResetKeysChange: handleResetKeysChange,\n } as any;\n\n return (\n <ReactErrorBoundary {...errorBoundaryProps}>\n {children}\n </ReactErrorBoundary>\n );\n}\n\n/**\n * 带错误边界的 HOC\n */\nexport function withErrorBoundary<P extends object>(\n Component: React.ComponentType<P>,\n errorBoundaryProps?: Omit<ErrorBoundaryProps, 'children'>\n): React.ComponentType<P> {\n const WrappedComponent = (props: P) => {\n return (\n <ErrorBoundary {...errorBoundaryProps}>\n <Component {...props} />\n </ErrorBoundary>\n );\n };\n\n WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name || 'Component'})`;\n\n return WrappedComponent;\n}\n\n/**\n * 使用错误处理器的 Hook\n * \n * 用于在函数组件中处理异步错误\n * \n * 注意:这个 Hook 需要配合 ErrorBoundary 使用。当调用返回的错误处理函数时,\n * 错误会被设置到初始化错误状态,然后通过 InitializationErrorThrower 组件抛出。\n * \n * @example\n * ```tsx\n * import { useErrorHandler } from '@vlian/framework';\n * \n * function MyComponent() {\n * const handleError = useErrorHandler();\n * \n * useEffect(() => {\n * async function fetchData() {\n * try {\n * await someAsyncOperation();\n * } catch (error) {\n * handleError(error);\n * }\n * }\n * fetchData();\n * }, [handleError]);\n * }\n * ```\n */\nexport function useErrorHandler(): (error: unknown) => void {\n return (error: unknown) => {\n // 将错误设置到初始化错误状态\n // InitializationErrorThrower 组件会检测到错误并抛出,让 ErrorBoundary 捕获\n initializationErrorState.setError(error);\n };\n}\n"],"names":["ErrorBoundary","useErrorHandler","withErrorBoundary","children","fallback","onError","showInConsole","resetKeys","onReset","onResetKeysChange","retryCountRef","useRef","errorHandler","useMemo","getDefaultErrorHandler","handleError","useCallback","error","errorInfo","normalizedError","errorUtils","normalizeError","reactErrorInfo","componentStack","handleResult","source","handled","logger","toJSON","console","handleReset","initializationErrorState","clearError","DefaultFallbackComponent","React","memo","resetErrorBoundary","retryCount","setRetryCount","useState","current","showDetails","setShowDetails","useEffect","handleRetry","newCount","handleReload","window","location","reload","userFriendlyMessage","code","extraButtons","buttons","Button","type","onClick","aria-label","push","div","role","aria-live","aria-atomic","style","display","flexDirection","alignItems","justifyContent","minHeight","padding","Result","status","title","subTitle","extra","marginTop","backgroundColor","borderRadius","maxWidth","width","maxHeight","overflow","marginBottom","fontWeight","fontFamily","fontSize","strong","name","message","stack","pre","whiteSpace","wordBreak","displayName","defaultFallback","fallbackRender","handleResetWithCount","handleResetKeysChange","prevKeys","nextKeys","errorBoundaryProps","ReactErrorBoundary","Component","WrappedComponent","props","setError"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/core/error/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAqD,MAAM,OAAO,CAAC;AAKjH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/core/error/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAqD,MAAM,OAAO,CAAC;AAKjH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIzD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IACxE;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;IAChE;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACnC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,KAAK,IAAI,CAAC;CAC1H;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,aAAoB,EACpB,SAAS,EACT,OAAO,EACP,iBAAiB,GAClB,EAAE,kBAAkB,GAAG,SAAS,CA2OhC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAChD,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EACjC,kBAAkB,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,GACxD,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAYxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,eAAe,IAAI,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAM1D"}
|
|
@@ -5,6 +5,7 @@ import { Result, Button } from "antd";
|
|
|
5
5
|
import { logger } from "../../utils";
|
|
6
6
|
import { errorUtils } from "../../utils/errors";
|
|
7
7
|
import { initializationErrorState } from "../initialization";
|
|
8
|
+
import { getDefaultErrorHandler } from "./ErrorHandler";
|
|
8
9
|
/**
|
|
9
10
|
* 错误边界组件
|
|
10
11
|
*
|
|
@@ -37,30 +38,43 @@ import { initializationErrorState } from "../initialization";
|
|
|
37
38
|
*/ export function ErrorBoundary({ children, fallback, onError, showInConsole = true, resetKeys, onReset, onResetKeysChange }) {
|
|
38
39
|
// 使用 ref 存储重试计数,在多次错误之间保持状态
|
|
39
40
|
const retryCountRef = useRef(0);
|
|
41
|
+
// 优化:使用统一的错误处理器
|
|
42
|
+
const errorHandler = useMemo(()=>getDefaultErrorHandler(), []);
|
|
40
43
|
// 处理错误,将标准 Error 转换为 FrameworkError
|
|
41
|
-
const handleError = (error, errorInfo)=>{
|
|
44
|
+
const handleError = useCallback(async (error, errorInfo)=>{
|
|
42
45
|
const normalizedError = errorUtils.normalizeError(error);
|
|
43
46
|
// 将 react-error-boundary 的 errorInfo 转换为 React 的 ErrorInfo 格式
|
|
44
47
|
const reactErrorInfo = {
|
|
45
48
|
componentStack: errorInfo.componentStack || ''
|
|
46
49
|
};
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
componentStack: reactErrorInfo.componentStack
|
|
52
|
-
}
|
|
50
|
+
// 使用统一的错误处理器处理错误
|
|
51
|
+
const handleResult = await errorHandler.handleError(normalizedError, {
|
|
52
|
+
componentStack: reactErrorInfo.componentStack,
|
|
53
|
+
source: 'ErrorBoundary'
|
|
53
54
|
});
|
|
55
|
+
// 如果错误处理器没有处理错误,使用默认处理
|
|
56
|
+
if (!handleResult.handled) {
|
|
57
|
+
logger.error('错误边界捕获到错误:', {
|
|
58
|
+
error: normalizedError.toJSON(),
|
|
59
|
+
errorInfo: {
|
|
60
|
+
componentStack: reactErrorInfo.componentStack
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
54
64
|
// 调用错误回调
|
|
55
65
|
if (onError) {
|
|
56
66
|
onError(normalizedError, reactErrorInfo);
|
|
57
67
|
}
|
|
58
|
-
//
|
|
68
|
+
// 在控制台显示错误(如果配置了)
|
|
59
69
|
if (showInConsole) {
|
|
60
70
|
console.error('错误边界捕获到错误:', normalizedError);
|
|
61
71
|
console.error('错误信息:', reactErrorInfo);
|
|
62
72
|
}
|
|
63
|
-
}
|
|
73
|
+
}, [
|
|
74
|
+
errorHandler,
|
|
75
|
+
onError,
|
|
76
|
+
showInConsole
|
|
77
|
+
]);
|
|
64
78
|
// 处理重置
|
|
65
79
|
const handleReset = ()=>{
|
|
66
80
|
// 清除初始化错误状态
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/error/ErrorBoundary.tsx"],"sourcesContent":["import React, { type ReactNode, type ErrorInfo, useState, useRef, useEffect, useCallback, useMemo } from 'react';\nimport { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';\nimport { Result, Button } from 'antd';\nimport { logger } from '../../utils';\nimport { errorUtils } from '../../utils/errors';\nimport type { FrameworkError } from '../../utils/errors';\nimport { initializationErrorState } from '../initialization';\n\n/**\n * 错误边界组件属性\n */\nexport interface ErrorBoundaryProps {\n /**\n * 子组件\n */\n children: ReactNode;\n /**\n * 错误回退 UI(函数形式)\n * \n * 如果不提供,将使用默认的错误 UI(基于 antd 的 Result 组件,居中显示)。\n * 如果提供,将使用自定义的错误 UI。\n * \n * @param error - 标准化后的框架错误对象\n * @param resetError - 重置错误边界的函数,调用后可以尝试重新渲染子组件\n * @returns 错误 UI 的 React 节点\n * \n * @example\n * ```tsx\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <Result\n * status=\"error\"\n * title=\"自定义错误标题\"\n * subTitle={error.message}\n * extra={<Button onClick={reset}>重试</Button>}\n * />\n * )}\n * >\n * <App />\n * </ErrorBoundary>\n * ```\n */\n fallback?: (error: FrameworkError, resetError: () => void) => ReactNode;\n /**\n * 错误回调\n */\n onError?: (error: FrameworkError, errorInfo: ErrorInfo) => void;\n /**\n * 是否在控制台显示错误\n */\n showInConsole?: boolean;\n /**\n * 重置键数组,当这些值变化时自动重置错误边界\n */\n resetKeys?: Array<string | number>;\n /**\n * 重置错误时的回调\n */\n onReset?: () => void;\n /**\n * 重置键变化时的回调\n */\n onResetKeysChange?: (prevKeys: Array<string | number> | undefined, nextKeys: Array<string | number> | undefined) => void;\n}\n\n/**\n * 错误边界组件\n * \n * 基于 react-error-boundary 实现,集成了框架的错误处理系统\n * 用于捕获 React 组件树中的错误,防止整个应用崩溃\n * \n * 默认使用 antd 的 Result 组件显示错误 UI,居中显示,美观易用。\n * 可以通过 fallback 属性自定义错误 UI。\n * \n * @example\n * ```tsx\n * // 使用默认错误 UI(推荐)\n * <ErrorBoundary>\n * <App />\n * </ErrorBoundary>\n * \n * // 自定义错误 UI\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <div>\n * <h1>出错了</h1>\n * <p>{error.message}</p>\n * <button onClick={reset}>重试</button>\n * </div>\n * )}\n * >\n * <App />\n * </ErrorBoundary>\n * ```\n */\nexport function ErrorBoundary({\n children,\n fallback,\n onError,\n showInConsole = true,\n resetKeys,\n onReset,\n onResetKeysChange,\n}: ErrorBoundaryProps): ReactNode {\n // 使用 ref 存储重试计数,在多次错误之间保持状态\n const retryCountRef = useRef<number>(0);\n\n // 处理错误,将标准 Error 转换为 FrameworkError\n const handleError = (error: Error, errorInfo: { componentStack?: string | null }): void => {\n const normalizedError = errorUtils.normalizeError(error);\n\n // 将 react-error-boundary 的 errorInfo 转换为 React 的 ErrorInfo 格式\n const reactErrorInfo: ErrorInfo = {\n componentStack: errorInfo.componentStack || '',\n };\n\n // 记录错误\n logger.error('错误边界捕获到错误:', {\n error: normalizedError.toJSON(),\n errorInfo: {\n componentStack: reactErrorInfo.componentStack,\n },\n });\n\n // 调用错误回调\n if (onError) {\n onError(normalizedError, reactErrorInfo);\n }\n\n // 在控制台显示错误\n if (showInConsole) {\n console.error('错误边界捕获到错误:', normalizedError);\n console.error('错误信息:', reactErrorInfo);\n }\n };\n\n // 处理重置\n const handleReset = (): void => {\n // 清除初始化错误状态\n initializationErrorState.clearError();\n // 调用用户提供的重置回调\n if (onReset) {\n onReset();\n }\n };\n\n // 默认 fallback UI(使用 antd 组件)\n // 使用内部组件来管理重试计数状态,使用 React.memo 优化性能\n const DefaultFallbackComponent = React.memo(({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n const normalizedError = errorUtils.normalizeError(error);\n const [retryCount, setRetryCount] = useState<number>(retryCountRef.current);\n const [showDetails, setShowDetails] = useState(false);\n\n // 同步 ref 的值到 state,确保组件重新创建时能读取到最新的重试计数\n useEffect(() => {\n setRetryCount(retryCountRef.current);\n }, []);\n\n const handleRetry = useCallback((): void => {\n // 增加重试计数\n const newCount = retryCount + 1;\n retryCountRef.current = newCount;\n setRetryCount(newCount);\n \n // 执行重置\n handleReset();\n resetErrorBoundary();\n }, [retryCount, resetErrorBoundary]);\n\n const handleReload = useCallback((): void => {\n // 刷新页面\n window.location.reload();\n }, []);\n\n // 构建用户友好的错误提示\n const userFriendlyMessage = useMemo(() => {\n // 根据错误类型提供不同的提示\n if (normalizedError.code === 'NETWORK_ERROR') {\n return '网络连接失败,请检查您的网络设置后重试';\n }\n if (normalizedError.code === 'TIMEOUT_ERROR') {\n return '请求超时,请稍后重试';\n }\n if (normalizedError.code === 'VALIDATION_ERROR') {\n return '数据验证失败,请检查输入内容';\n }\n // 默认提示\n return '应用运行时发生了错误,请稍后重试';\n }, [normalizedError.code]);\n\n // 构建按钮数组\n const extraButtons = useMemo(() => {\n const buttons = [\n <Button\n type=\"primary\"\n key=\"retry\"\n onClick={handleRetry}\n aria-label=\"重试加载应用\"\n >\n 重试\n </Button>,\n <Button\n key=\"details\"\n onClick={() => setShowDetails(!showDetails)}\n aria-label={showDetails ? '隐藏错误详情' : '显示错误详情'}\n >\n {showDetails ? '隐藏详情' : '查看详情'}\n </Button>,\n ];\n\n // 如果重试次数 >= 1,添加刷新页面按钮(提前显示)\n if (retryCount >= 1) {\n buttons.push(\n <Button\n key=\"reload\"\n onClick={handleReload}\n aria-label=\"刷新页面\"\n >\n 刷新页面\n </Button>\n );\n }\n\n return buttons;\n }, [retryCount, handleRetry, handleReload, showDetails]);\n\n return (\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: '100vh',\n padding: '24px',\n }}\n >\n <Result\n status=\"error\"\n title=\"出错了\"\n subTitle={userFriendlyMessage}\n extra={extraButtons}\n />\n {showDetails && (\n <div\n style={{\n marginTop: 24,\n padding: 16,\n backgroundColor: '#f5f5f5',\n borderRadius: 4,\n maxWidth: 800,\n width: '100%',\n maxHeight: 400,\n overflow: 'auto',\n }}\n >\n <div style={{ marginBottom: 8, fontWeight: 'bold' }}>错误详情:</div>\n <div style={{ fontFamily: 'monospace', fontSize: 12 }}>\n <div><strong>错误类型:</strong>{normalizedError.name}</div>\n <div><strong>错误代码:</strong>{normalizedError.code}</div>\n <div><strong>错误信息:</strong>{normalizedError.message}</div>\n {normalizedError.stack && (\n <div style={{ marginTop: 8 }}>\n <strong>堆栈信息:</strong>\n <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>\n {normalizedError.stack}\n </pre>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n );\n });\n \n DefaultFallbackComponent.displayName = 'DefaultFallbackComponent';\n\n const defaultFallback = ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n return <DefaultFallbackComponent error={error} resetErrorBoundary={resetErrorBoundary} />;\n };\n\n // 如果提供了自定义 fallback,使用自定义的\n const fallbackRender = fallback\n ? ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n const normalizedError = errorUtils.normalizeError(error);\n return fallback(normalizedError, () => {\n handleReset();\n resetErrorBoundary();\n });\n }\n : defaultFallback;\n\n // 处理重置时清除重试计数\n const handleResetWithCount = (): void => {\n retryCountRef.current = 0;\n handleReset();\n };\n\n // 处理 resetKeys 变化时清除重试计数\n const handleResetKeysChange = (\n prevKeys: Array<string | number> | undefined,\n nextKeys: Array<string | number> | undefined\n ): void => {\n retryCountRef.current = 0;\n if (onResetKeysChange) {\n onResetKeysChange(prevKeys, nextKeys);\n }\n };\n\n // 构建 react-error-boundary 的 props\n const errorBoundaryProps = {\n fallbackRender,\n onError: handleError,\n onReset: handleResetWithCount,\n resetKeys,\n onResetKeysChange: handleResetKeysChange,\n } as any;\n\n return (\n <ReactErrorBoundary {...errorBoundaryProps}>\n {children}\n </ReactErrorBoundary>\n );\n}\n\n/**\n * 带错误边界的 HOC\n */\nexport function withErrorBoundary<P extends object>(\n Component: React.ComponentType<P>,\n errorBoundaryProps?: Omit<ErrorBoundaryProps, 'children'>\n): React.ComponentType<P> {\n const WrappedComponent = (props: P) => {\n return (\n <ErrorBoundary {...errorBoundaryProps}>\n <Component {...props} />\n </ErrorBoundary>\n );\n };\n\n WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name || 'Component'})`;\n\n return WrappedComponent;\n}\n\n/**\n * 使用错误处理器的 Hook\n * \n * 用于在函数组件中处理异步错误\n * \n * 注意:这个 Hook 需要配合 ErrorBoundary 使用。当调用返回的错误处理函数时,\n * 错误会被设置到初始化错误状态,然后通过 InitializationErrorThrower 组件抛出。\n * \n * @example\n * ```tsx\n * import { useErrorHandler } from '@vlian/framework';\n * \n * function MyComponent() {\n * const handleError = useErrorHandler();\n * \n * useEffect(() => {\n * async function fetchData() {\n * try {\n * await someAsyncOperation();\n * } catch (error) {\n * handleError(error);\n * }\n * }\n * fetchData();\n * }, [handleError]);\n * }\n * ```\n */\nexport function useErrorHandler(): (error: unknown) => void {\n return (error: unknown) => {\n // 将错误设置到初始化错误状态\n // InitializationErrorThrower 组件会检测到错误并抛出,让 ErrorBoundary 捕获\n initializationErrorState.setError(error);\n };\n}\n"],"names":["React","useState","useRef","useEffect","useCallback","useMemo","ErrorBoundary","ReactErrorBoundary","Result","Button","logger","errorUtils","initializationErrorState","children","fallback","onError","showInConsole","resetKeys","onReset","onResetKeysChange","retryCountRef","handleError","error","errorInfo","normalizedError","normalizeError","reactErrorInfo","componentStack","toJSON","console","handleReset","clearError","DefaultFallbackComponent","memo","resetErrorBoundary","retryCount","setRetryCount","current","showDetails","setShowDetails","handleRetry","newCount","handleReload","window","location","reload","userFriendlyMessage","code","extraButtons","buttons","type","onClick","aria-label","push","div","role","aria-live","aria-atomic","style","display","flexDirection","alignItems","justifyContent","minHeight","padding","status","title","subTitle","extra","marginTop","backgroundColor","borderRadius","maxWidth","width","maxHeight","overflow","marginBottom","fontWeight","fontFamily","fontSize","strong","name","message","stack","pre","whiteSpace","wordBreak","displayName","defaultFallback","fallbackRender","handleResetWithCount","handleResetKeysChange","prevKeys","nextKeys","errorBoundaryProps","withErrorBoundary","Component","WrappedComponent","props","useErrorHandler","setError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/core/error/ErrorBoundary.tsx"],"sourcesContent":["import React, { type ReactNode, type ErrorInfo, useState, useRef, useEffect, useCallback, useMemo } from 'react';\nimport { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';\nimport { Result, Button } from 'antd';\nimport { logger } from '../../utils';\nimport { errorUtils } from '../../utils/errors';\nimport type { FrameworkError } from '../../utils/errors';\nimport { initializationErrorState } from '../initialization';\nimport { getDefaultErrorHandler } from './ErrorHandler';\n\n/**\n * 错误边界组件属性\n */\nexport interface ErrorBoundaryProps {\n /**\n * 子组件\n */\n children: ReactNode;\n /**\n * 错误回退 UI(函数形式)\n * \n * 如果不提供,将使用默认的错误 UI(基于 antd 的 Result 组件,居中显示)。\n * 如果提供,将使用自定义的错误 UI。\n * \n * @param error - 标准化后的框架错误对象\n * @param resetError - 重置错误边界的函数,调用后可以尝试重新渲染子组件\n * @returns 错误 UI 的 React 节点\n * \n * @example\n * ```tsx\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <Result\n * status=\"error\"\n * title=\"自定义错误标题\"\n * subTitle={error.message}\n * extra={<Button onClick={reset}>重试</Button>}\n * />\n * )}\n * >\n * <App />\n * </ErrorBoundary>\n * ```\n */\n fallback?: (error: FrameworkError, resetError: () => void) => ReactNode;\n /**\n * 错误回调\n */\n onError?: (error: FrameworkError, errorInfo: ErrorInfo) => void;\n /**\n * 是否在控制台显示错误\n */\n showInConsole?: boolean;\n /**\n * 重置键数组,当这些值变化时自动重置错误边界\n */\n resetKeys?: Array<string | number>;\n /**\n * 重置错误时的回调\n */\n onReset?: () => void;\n /**\n * 重置键变化时的回调\n */\n onResetKeysChange?: (prevKeys: Array<string | number> | undefined, nextKeys: Array<string | number> | undefined) => void;\n}\n\n/**\n * 错误边界组件\n * \n * 基于 react-error-boundary 实现,集成了框架的错误处理系统\n * 用于捕获 React 组件树中的错误,防止整个应用崩溃\n * \n * 默认使用 antd 的 Result 组件显示错误 UI,居中显示,美观易用。\n * 可以通过 fallback 属性自定义错误 UI。\n * \n * @example\n * ```tsx\n * // 使用默认错误 UI(推荐)\n * <ErrorBoundary>\n * <App />\n * </ErrorBoundary>\n * \n * // 自定义错误 UI\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <div>\n * <h1>出错了</h1>\n * <p>{error.message}</p>\n * <button onClick={reset}>重试</button>\n * </div>\n * )}\n * >\n * <App />\n * </ErrorBoundary>\n * ```\n */\nexport function ErrorBoundary({\n children,\n fallback,\n onError,\n showInConsole = true,\n resetKeys,\n onReset,\n onResetKeysChange,\n}: ErrorBoundaryProps): ReactNode {\n // 使用 ref 存储重试计数,在多次错误之间保持状态\n const retryCountRef = useRef<number>(0);\n\n // 优化:使用统一的错误处理器\n const errorHandler = useMemo(() => getDefaultErrorHandler(), []);\n\n // 处理错误,将标准 Error 转换为 FrameworkError\n const handleError = useCallback(async (error: Error, errorInfo: { componentStack?: string | null }): Promise<void> => {\n const normalizedError = errorUtils.normalizeError(error);\n\n // 将 react-error-boundary 的 errorInfo 转换为 React 的 ErrorInfo 格式\n const reactErrorInfo: ErrorInfo = {\n componentStack: errorInfo.componentStack || '',\n };\n\n // 使用统一的错误处理器处理错误\n const handleResult = await errorHandler.handleError(normalizedError, {\n componentStack: reactErrorInfo.componentStack,\n source: 'ErrorBoundary',\n });\n\n // 如果错误处理器没有处理错误,使用默认处理\n if (!handleResult.handled) {\n logger.error('错误边界捕获到错误:', {\n error: normalizedError.toJSON(),\n errorInfo: {\n componentStack: reactErrorInfo.componentStack,\n },\n });\n }\n\n // 调用错误回调\n if (onError) {\n onError(normalizedError, reactErrorInfo);\n }\n\n // 在控制台显示错误(如果配置了)\n if (showInConsole) {\n console.error('错误边界捕获到错误:', normalizedError);\n console.error('错误信息:', reactErrorInfo);\n }\n }, [errorHandler, onError, showInConsole]);\n\n // 处理重置\n const handleReset = (): void => {\n // 清除初始化错误状态\n initializationErrorState.clearError();\n // 调用用户提供的重置回调\n if (onReset) {\n onReset();\n }\n };\n\n // 默认 fallback UI(使用 antd 组件)\n // 使用内部组件来管理重试计数状态,使用 React.memo 优化性能\n const DefaultFallbackComponent = React.memo(({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n const normalizedError = errorUtils.normalizeError(error);\n const [retryCount, setRetryCount] = useState<number>(retryCountRef.current);\n const [showDetails, setShowDetails] = useState(false);\n\n // 同步 ref 的值到 state,确保组件重新创建时能读取到最新的重试计数\n useEffect(() => {\n setRetryCount(retryCountRef.current);\n }, []);\n\n const handleRetry = useCallback((): void => {\n // 增加重试计数\n const newCount = retryCount + 1;\n retryCountRef.current = newCount;\n setRetryCount(newCount);\n \n // 执行重置\n handleReset();\n resetErrorBoundary();\n }, [retryCount, resetErrorBoundary]);\n\n const handleReload = useCallback((): void => {\n // 刷新页面\n window.location.reload();\n }, []);\n\n // 构建用户友好的错误提示\n const userFriendlyMessage = useMemo(() => {\n // 根据错误类型提供不同的提示\n if (normalizedError.code === 'NETWORK_ERROR') {\n return '网络连接失败,请检查您的网络设置后重试';\n }\n if (normalizedError.code === 'TIMEOUT_ERROR') {\n return '请求超时,请稍后重试';\n }\n if (normalizedError.code === 'VALIDATION_ERROR') {\n return '数据验证失败,请检查输入内容';\n }\n // 默认提示\n return '应用运行时发生了错误,请稍后重试';\n }, [normalizedError.code]);\n\n // 构建按钮数组\n const extraButtons = useMemo(() => {\n const buttons = [\n <Button\n type=\"primary\"\n key=\"retry\"\n onClick={handleRetry}\n aria-label=\"重试加载应用\"\n >\n 重试\n </Button>,\n <Button\n key=\"details\"\n onClick={() => setShowDetails(!showDetails)}\n aria-label={showDetails ? '隐藏错误详情' : '显示错误详情'}\n >\n {showDetails ? '隐藏详情' : '查看详情'}\n </Button>,\n ];\n\n // 如果重试次数 >= 1,添加刷新页面按钮(提前显示)\n if (retryCount >= 1) {\n buttons.push(\n <Button\n key=\"reload\"\n onClick={handleReload}\n aria-label=\"刷新页面\"\n >\n 刷新页面\n </Button>\n );\n }\n\n return buttons;\n }, [retryCount, handleRetry, handleReload, showDetails]);\n\n return (\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: '100vh',\n padding: '24px',\n }}\n >\n <Result\n status=\"error\"\n title=\"出错了\"\n subTitle={userFriendlyMessage}\n extra={extraButtons}\n />\n {showDetails && (\n <div\n style={{\n marginTop: 24,\n padding: 16,\n backgroundColor: '#f5f5f5',\n borderRadius: 4,\n maxWidth: 800,\n width: '100%',\n maxHeight: 400,\n overflow: 'auto',\n }}\n >\n <div style={{ marginBottom: 8, fontWeight: 'bold' }}>错误详情:</div>\n <div style={{ fontFamily: 'monospace', fontSize: 12 }}>\n <div><strong>错误类型:</strong>{normalizedError.name}</div>\n <div><strong>错误代码:</strong>{normalizedError.code}</div>\n <div><strong>错误信息:</strong>{normalizedError.message}</div>\n {normalizedError.stack && (\n <div style={{ marginTop: 8 }}>\n <strong>堆栈信息:</strong>\n <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>\n {normalizedError.stack}\n </pre>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n );\n });\n \n DefaultFallbackComponent.displayName = 'DefaultFallbackComponent';\n\n const defaultFallback = ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n return <DefaultFallbackComponent error={error} resetErrorBoundary={resetErrorBoundary} />;\n };\n\n // 如果提供了自定义 fallback,使用自定义的\n const fallbackRender = fallback\n ? ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }): ReactNode => {\n const normalizedError = errorUtils.normalizeError(error);\n return fallback(normalizedError, () => {\n handleReset();\n resetErrorBoundary();\n });\n }\n : defaultFallback;\n\n // 处理重置时清除重试计数\n const handleResetWithCount = (): void => {\n retryCountRef.current = 0;\n handleReset();\n };\n\n // 处理 resetKeys 变化时清除重试计数\n const handleResetKeysChange = (\n prevKeys: Array<string | number> | undefined,\n nextKeys: Array<string | number> | undefined\n ): void => {\n retryCountRef.current = 0;\n if (onResetKeysChange) {\n onResetKeysChange(prevKeys, nextKeys);\n }\n };\n\n // 构建 react-error-boundary 的 props\n const errorBoundaryProps = {\n fallbackRender,\n onError: handleError,\n onReset: handleResetWithCount,\n resetKeys,\n onResetKeysChange: handleResetKeysChange,\n } as any;\n\n return (\n <ReactErrorBoundary {...errorBoundaryProps}>\n {children}\n </ReactErrorBoundary>\n );\n}\n\n/**\n * 带错误边界的 HOC\n */\nexport function withErrorBoundary<P extends object>(\n Component: React.ComponentType<P>,\n errorBoundaryProps?: Omit<ErrorBoundaryProps, 'children'>\n): React.ComponentType<P> {\n const WrappedComponent = (props: P) => {\n return (\n <ErrorBoundary {...errorBoundaryProps}>\n <Component {...props} />\n </ErrorBoundary>\n );\n };\n\n WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name || 'Component'})`;\n\n return WrappedComponent;\n}\n\n/**\n * 使用错误处理器的 Hook\n * \n * 用于在函数组件中处理异步错误\n * \n * 注意:这个 Hook 需要配合 ErrorBoundary 使用。当调用返回的错误处理函数时,\n * 错误会被设置到初始化错误状态,然后通过 InitializationErrorThrower 组件抛出。\n * \n * @example\n * ```tsx\n * import { useErrorHandler } from '@vlian/framework';\n * \n * function MyComponent() {\n * const handleError = useErrorHandler();\n * \n * useEffect(() => {\n * async function fetchData() {\n * try {\n * await someAsyncOperation();\n * } catch (error) {\n * handleError(error);\n * }\n * }\n * fetchData();\n * }, [handleError]);\n * }\n * ```\n */\nexport function useErrorHandler(): (error: unknown) => void {\n return (error: unknown) => {\n // 将错误设置到初始化错误状态\n // InitializationErrorThrower 组件会检测到错误并抛出,让 ErrorBoundary 捕获\n initializationErrorState.setError(error);\n };\n}\n"],"names":["React","useState","useRef","useEffect","useCallback","useMemo","ErrorBoundary","ReactErrorBoundary","Result","Button","logger","errorUtils","initializationErrorState","getDefaultErrorHandler","children","fallback","onError","showInConsole","resetKeys","onReset","onResetKeysChange","retryCountRef","errorHandler","handleError","error","errorInfo","normalizedError","normalizeError","reactErrorInfo","componentStack","handleResult","source","handled","toJSON","console","handleReset","clearError","DefaultFallbackComponent","memo","resetErrorBoundary","retryCount","setRetryCount","current","showDetails","setShowDetails","handleRetry","newCount","handleReload","window","location","reload","userFriendlyMessage","code","extraButtons","buttons","type","onClick","aria-label","push","div","role","aria-live","aria-atomic","style","display","flexDirection","alignItems","justifyContent","minHeight","padding","status","title","subTitle","extra","marginTop","backgroundColor","borderRadius","maxWidth","width","maxHeight","overflow","marginBottom","fontWeight","fontFamily","fontSize","strong","name","message","stack","pre","whiteSpace","wordBreak","displayName","defaultFallback","fallbackRender","handleResetWithCount","handleResetKeysChange","prevKeys","nextKeys","errorBoundaryProps","withErrorBoundary","Component","WrappedComponent","props","useErrorHandler","setError"],"mappings":"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"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一错误处理器
|
|
3
|
+
*
|
|
4
|
+
* 优化:
|
|
5
|
+
* 1. 统一错误处理策略:记录、上报、恢复、降级
|
|
6
|
+
* 2. 错误分类:致命、可恢复、警告
|
|
7
|
+
* 3. 错误恢复机制:自动重试、降级方案
|
|
8
|
+
*/ "use strict";
|
|
9
|
+
Object.defineProperty(exports, "__esModule", {
|
|
10
|
+
value: true
|
|
11
|
+
});
|
|
12
|
+
function _export(target, all) {
|
|
13
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
_export(exports, {
|
|
19
|
+
get ErrorHandler () {
|
|
20
|
+
return ErrorHandler;
|
|
21
|
+
},
|
|
22
|
+
get ErrorHandlingStrategy () {
|
|
23
|
+
return ErrorHandlingStrategy;
|
|
24
|
+
},
|
|
25
|
+
get getDefaultErrorHandler () {
|
|
26
|
+
return getDefaultErrorHandler;
|
|
27
|
+
},
|
|
28
|
+
get setDefaultErrorHandler () {
|
|
29
|
+
return setDefaultErrorHandler;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const _utils = require("../../utils");
|
|
33
|
+
const _errors = require("../../utils/errors");
|
|
34
|
+
function _define_property(obj, key, value) {
|
|
35
|
+
if (key in obj) {
|
|
36
|
+
Object.defineProperty(obj, key, {
|
|
37
|
+
value: value,
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
obj[key] = value;
|
|
44
|
+
}
|
|
45
|
+
return obj;
|
|
46
|
+
}
|
|
47
|
+
var ErrorHandlingStrategy = /*#__PURE__*/ function(ErrorHandlingStrategy) {
|
|
48
|
+
/**
|
|
49
|
+
* 记录错误(日志)
|
|
50
|
+
*/ ErrorHandlingStrategy["LOG"] = "LOG";
|
|
51
|
+
/**
|
|
52
|
+
* 上报错误(监控服务)
|
|
53
|
+
*/ ErrorHandlingStrategy["REPORT"] = "REPORT";
|
|
54
|
+
/**
|
|
55
|
+
* 尝试恢复错误
|
|
56
|
+
*/ ErrorHandlingStrategy["RECOVER"] = "RECOVER";
|
|
57
|
+
/**
|
|
58
|
+
* 降级处理
|
|
59
|
+
*/ ErrorHandlingStrategy["FALLBACK"] = "FALLBACK";
|
|
60
|
+
/**
|
|
61
|
+
* 忽略错误
|
|
62
|
+
*/ ErrorHandlingStrategy["IGNORE"] = "IGNORE";
|
|
63
|
+
return ErrorHandlingStrategy;
|
|
64
|
+
}({});
|
|
65
|
+
let ErrorHandler = class ErrorHandler {
|
|
66
|
+
/**
|
|
67
|
+
* 设置监控服务
|
|
68
|
+
*/ setMonitoring(monitoring) {
|
|
69
|
+
this.monitoring = monitoring;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 处理错误
|
|
73
|
+
*
|
|
74
|
+
* @param error - 错误对象
|
|
75
|
+
* @param context - 错误上下文
|
|
76
|
+
* @returns 错误处理结果
|
|
77
|
+
*/ async handleError(error, context) {
|
|
78
|
+
// 标准化错误对象
|
|
79
|
+
const normalizedError = _errors.errorUtils.normalizeError(error);
|
|
80
|
+
// 创建包含上下文信息的错误对象(由于 context 是只读的,需要创建新对象)
|
|
81
|
+
const errorWithContext = context ? new _errors.FrameworkError(normalizedError.message, normalizedError.type, normalizedError.severity, {
|
|
82
|
+
code: normalizedError.code,
|
|
83
|
+
originalError: normalizedError.originalError,
|
|
84
|
+
context: {
|
|
85
|
+
...normalizedError.context,
|
|
86
|
+
...context
|
|
87
|
+
},
|
|
88
|
+
recoverable: normalizedError.recoverable
|
|
89
|
+
}) : normalizedError;
|
|
90
|
+
// 确定处理策略
|
|
91
|
+
const strategies = this.determineStrategies(errorWithContext);
|
|
92
|
+
// 执行处理策略
|
|
93
|
+
const handled = await this.executeStrategies(errorWithContext, strategies);
|
|
94
|
+
// 尝试恢复错误
|
|
95
|
+
let recoveredValue;
|
|
96
|
+
let recoverable = false;
|
|
97
|
+
if (handled && errorWithContext.recoverable && this.config.recoveryOptions) {
|
|
98
|
+
const recoveryResult = await this.attemptRecovery(errorWithContext);
|
|
99
|
+
recoverable = recoveryResult.recoverable;
|
|
100
|
+
recoveredValue = recoveryResult.value;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
handled,
|
|
104
|
+
recoverable,
|
|
105
|
+
recoveredValue,
|
|
106
|
+
strategies,
|
|
107
|
+
error: errorWithContext
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 确定错误处理策略
|
|
112
|
+
*/ determineStrategies(error) {
|
|
113
|
+
// 1. 检查错误类型对应的策略
|
|
114
|
+
if (this.config.typeStrategies[error.type]) {
|
|
115
|
+
return this.config.typeStrategies[error.type];
|
|
116
|
+
}
|
|
117
|
+
// 2. 检查错误严重程度对应的策略
|
|
118
|
+
if (this.config.severityStrategies[error.severity]) {
|
|
119
|
+
return this.config.severityStrategies[error.severity];
|
|
120
|
+
}
|
|
121
|
+
// 3. 使用默认策略
|
|
122
|
+
return this.config.defaultStrategies;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 执行处理策略
|
|
126
|
+
*/ async executeStrategies(error, strategies) {
|
|
127
|
+
let handled = false;
|
|
128
|
+
for (const strategy of strategies){
|
|
129
|
+
try {
|
|
130
|
+
switch(strategy){
|
|
131
|
+
case "LOG":
|
|
132
|
+
this.logError(error);
|
|
133
|
+
handled = true;
|
|
134
|
+
break;
|
|
135
|
+
case "REPORT":
|
|
136
|
+
await this.reportError(error);
|
|
137
|
+
handled = true;
|
|
138
|
+
break;
|
|
139
|
+
case "RECOVER":
|
|
140
|
+
// 恢复策略在 handleError 中统一处理
|
|
141
|
+
handled = true;
|
|
142
|
+
break;
|
|
143
|
+
case "FALLBACK":
|
|
144
|
+
// 降级策略在 attemptRecovery 中处理
|
|
145
|
+
handled = true;
|
|
146
|
+
break;
|
|
147
|
+
case "IGNORE":
|
|
148
|
+
// 忽略错误,不做任何处理
|
|
149
|
+
handled = true;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
} catch (strategyError) {
|
|
153
|
+
_utils.logger.warn(`错误处理策略 ${strategy} 执行失败:`, strategyError);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return handled;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 记录错误
|
|
160
|
+
*/ logError(error) {
|
|
161
|
+
const errorInfo = _errors.errorUtils.extractErrorInfo(error);
|
|
162
|
+
// 根据错误严重程度选择日志级别
|
|
163
|
+
switch(error.severity){
|
|
164
|
+
case _errors.ErrorSeverity.CRITICAL:
|
|
165
|
+
case _errors.ErrorSeverity.HIGH:
|
|
166
|
+
_utils.logger.error('错误:', errorInfo);
|
|
167
|
+
break;
|
|
168
|
+
case _errors.ErrorSeverity.MEDIUM:
|
|
169
|
+
_utils.logger.warn('警告:', errorInfo);
|
|
170
|
+
break;
|
|
171
|
+
case _errors.ErrorSeverity.LOW:
|
|
172
|
+
_utils.logger.info('信息:', errorInfo);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
// 开发环境显示详细错误信息
|
|
176
|
+
if (this.config.showDetailedErrorsInDev && process.env.NODE_ENV === 'development') {
|
|
177
|
+
const detailedError = _errors.errorUtils.formatErrorForDev(error);
|
|
178
|
+
console.error(detailedError);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 上报错误
|
|
183
|
+
*/ async reportError(error) {
|
|
184
|
+
if (!this.monitoring) {
|
|
185
|
+
_utils.logger.debug('监控服务未配置,跳过错误上报');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
this.monitoring.captureError(error, {
|
|
190
|
+
severity: error.severity,
|
|
191
|
+
type: error.type,
|
|
192
|
+
code: error.code,
|
|
193
|
+
context: error.context
|
|
194
|
+
});
|
|
195
|
+
} catch (reportError) {
|
|
196
|
+
_utils.logger.warn('错误上报失败:', reportError);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 尝试恢复错误
|
|
201
|
+
*/ async attemptRecovery(error) {
|
|
202
|
+
const options = this.config.recoveryOptions;
|
|
203
|
+
if (!options) {
|
|
204
|
+
return {
|
|
205
|
+
recoverable: false
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
209
|
+
const retryDelay = options.retryDelay ?? 1000;
|
|
210
|
+
const exponentialBackoff = options.exponentialBackoff ?? true;
|
|
211
|
+
const shouldRetry = options.shouldRetry ?? (()=>true);
|
|
212
|
+
// 如果有降级方案,先尝试降级
|
|
213
|
+
if (options.fallback) {
|
|
214
|
+
try {
|
|
215
|
+
const fallbackValue = await Promise.resolve(options.fallback());
|
|
216
|
+
_utils.logger.info('错误已通过降级方案恢复');
|
|
217
|
+
return {
|
|
218
|
+
recoverable: true,
|
|
219
|
+
value: fallbackValue
|
|
220
|
+
};
|
|
221
|
+
} catch (fallbackError) {
|
|
222
|
+
_utils.logger.warn('降级方案执行失败:', fallbackError);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// 尝试重试(如果错误可恢复且满足重试条件)
|
|
226
|
+
if (error.recoverable && shouldRetry(error, 0)) {
|
|
227
|
+
for(let attempt = 0; attempt < maxRetries; attempt++){
|
|
228
|
+
if (!shouldRetry(error, attempt)) {
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
const delay = exponentialBackoff ? retryDelay * Math.pow(2, attempt) : retryDelay;
|
|
232
|
+
_utils.logger.debug(`错误恢复重试 ${attempt + 1}/${maxRetries},${delay}ms 后重试...`);
|
|
233
|
+
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
234
|
+
// 这里应该调用原始操作的重试逻辑
|
|
235
|
+
// 由于错误处理器不知道原始操作,这里只返回可恢复状态
|
|
236
|
+
// 实际的重试应该在调用方实现
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
recoverable: error.recoverable
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 创建错误处理器的便捷方法
|
|
245
|
+
*/ static create(config) {
|
|
246
|
+
return new ErrorHandler(config);
|
|
247
|
+
}
|
|
248
|
+
constructor(config = {}){
|
|
249
|
+
_define_property(this, "config", void 0);
|
|
250
|
+
_define_property(this, "monitoring", void 0);
|
|
251
|
+
this.monitoring = config.monitoring;
|
|
252
|
+
this.config = {
|
|
253
|
+
defaultStrategies: config.defaultStrategies ?? [
|
|
254
|
+
"LOG",
|
|
255
|
+
"REPORT"
|
|
256
|
+
],
|
|
257
|
+
severityStrategies: config.severityStrategies ?? {},
|
|
258
|
+
typeStrategies: config.typeStrategies ?? {},
|
|
259
|
+
recoveryOptions: config.recoveryOptions ?? {},
|
|
260
|
+
showDetailedErrorsInDev: config.showDetailedErrorsInDev ?? true
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* 默认错误处理器实例(单例)
|
|
266
|
+
*/ let defaultErrorHandler = null;
|
|
267
|
+
function getDefaultErrorHandler() {
|
|
268
|
+
if (!defaultErrorHandler) {
|
|
269
|
+
defaultErrorHandler = new ErrorHandler();
|
|
270
|
+
}
|
|
271
|
+
return defaultErrorHandler;
|
|
272
|
+
}
|
|
273
|
+
function setDefaultErrorHandler(handler) {
|
|
274
|
+
defaultErrorHandler = handler;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
//# sourceMappingURL=ErrorHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/error/ErrorHandler.ts"],"sourcesContent":["/**\n * 统一错误处理器\n * \n * 优化:\n * 1. 统一错误处理策略:记录、上报、恢复、降级\n * 2. 错误分类:致命、可恢复、警告\n * 3. 错误恢复机制:自动重试、降级方案\n */\n\nimport { logger } from '../../utils';\nimport { errorUtils, ErrorSeverity, ErrorType, FrameworkError } from '../../utils/errors';\nimport type { MonitoringService } from '../../utils/monitoring';\n\n/**\n * 错误处理策略\n */\nexport enum ErrorHandlingStrategy {\n /**\n * 记录错误(日志)\n */\n LOG = 'LOG',\n /**\n * 上报错误(监控服务)\n */\n REPORT = 'REPORT',\n /**\n * 尝试恢复错误\n */\n RECOVER = 'RECOVER',\n /**\n * 降级处理\n */\n FALLBACK = 'FALLBACK',\n /**\n * 忽略错误\n */\n IGNORE = 'IGNORE',\n}\n\n/**\n * 错误恢复选项\n */\nexport interface ErrorRecoveryOptions {\n /**\n * 最大重试次数\n * @default 3\n */\n maxRetries?: number;\n /**\n * 重试延迟(毫秒)\n * @default 1000\n */\n retryDelay?: number;\n /**\n * 是否启用指数退避\n * @default true\n */\n exponentialBackoff?: boolean;\n /**\n * 重试条件函数\n */\n shouldRetry?: (error: unknown, attempt: number) => boolean;\n /**\n * 降级方案函数\n */\n fallback?: () => Promise<unknown> | unknown;\n}\n\n/**\n * 错误处理配置\n */\nexport interface ErrorHandlerConfig {\n /**\n * 监控服务实例(可选)\n */\n monitoring?: MonitoringService;\n /**\n * 默认错误处理策略\n * @default [ErrorHandlingStrategy.LOG, ErrorHandlingStrategy.REPORT]\n */\n defaultStrategies?: ErrorHandlingStrategy[];\n /**\n * 错误严重程度对应的处理策略\n */\n severityStrategies?: Partial<Record<ErrorSeverity, ErrorHandlingStrategy[]>>;\n /**\n * 错误类型对应的处理策略\n */\n typeStrategies?: Partial<Record<ErrorType, ErrorHandlingStrategy[]>>;\n /**\n * 错误恢复选项\n */\n recoveryOptions?: ErrorRecoveryOptions;\n /**\n * 是否在开发环境显示详细错误信息\n * @default true\n */\n showDetailedErrorsInDev?: boolean;\n}\n\n/**\n * 错误处理结果\n */\nexport interface ErrorHandleResult {\n /**\n * 是否已处理\n */\n handled: boolean;\n /**\n * 是否可恢复\n */\n recoverable: boolean;\n /**\n * 恢复后的结果(如果已恢复)\n */\n recoveredValue?: unknown;\n /**\n * 使用的处理策略\n */\n strategies: ErrorHandlingStrategy[];\n /**\n * 错误信息\n */\n error: FrameworkError;\n}\n\n/**\n * 统一错误处理器\n */\nexport class ErrorHandler {\n private config: Omit<Required<ErrorHandlerConfig>, 'monitoring'>;\n private monitoring?: MonitoringService;\n\n constructor(config: ErrorHandlerConfig = {}) {\n this.monitoring = config.monitoring;\n this.config = {\n defaultStrategies: config.defaultStrategies ?? [\n ErrorHandlingStrategy.LOG,\n ErrorHandlingStrategy.REPORT,\n ],\n severityStrategies: config.severityStrategies ?? {},\n typeStrategies: config.typeStrategies ?? {},\n recoveryOptions: config.recoveryOptions ?? {},\n showDetailedErrorsInDev: config.showDetailedErrorsInDev ?? true,\n };\n }\n\n /**\n * 设置监控服务\n */\n setMonitoring(monitoring: MonitoringService): void {\n this.monitoring = monitoring;\n }\n\n /**\n * 处理错误\n * \n * @param error - 错误对象\n * @param context - 错误上下文\n * @returns 错误处理结果\n */\n async handleError(\n error: unknown,\n context?: Record<string, unknown>\n ): Promise<ErrorHandleResult> {\n // 标准化错误对象\n const normalizedError = errorUtils.normalizeError(error);\n \n // 创建包含上下文信息的错误对象(由于 context 是只读的,需要创建新对象)\n const errorWithContext: FrameworkError = context\n ? new FrameworkError(\n normalizedError.message,\n normalizedError.type,\n normalizedError.severity,\n {\n code: normalizedError.code,\n originalError: normalizedError.originalError,\n context: {\n ...normalizedError.context,\n ...context,\n },\n recoverable: normalizedError.recoverable,\n }\n )\n : normalizedError;\n\n // 确定处理策略\n const strategies = this.determineStrategies(errorWithContext);\n \n // 执行处理策略\n const handled = await this.executeStrategies(errorWithContext, strategies);\n\n // 尝试恢复错误\n let recoveredValue: unknown | undefined;\n let recoverable = false;\n if (handled && errorWithContext.recoverable && this.config.recoveryOptions) {\n const recoveryResult = await this.attemptRecovery(errorWithContext);\n recoverable = recoveryResult.recoverable;\n recoveredValue = recoveryResult.value;\n }\n\n return {\n handled,\n recoverable,\n recoveredValue,\n strategies,\n error: errorWithContext,\n };\n }\n\n /**\n * 确定错误处理策略\n */\n private determineStrategies(error: FrameworkError): ErrorHandlingStrategy[] {\n // 1. 检查错误类型对应的策略\n if (this.config.typeStrategies[error.type]) {\n return this.config.typeStrategies[error.type]!;\n }\n\n // 2. 检查错误严重程度对应的策略\n if (this.config.severityStrategies[error.severity]) {\n return this.config.severityStrategies[error.severity]!;\n }\n\n // 3. 使用默认策略\n return this.config.defaultStrategies;\n }\n\n /**\n * 执行处理策略\n */\n private async executeStrategies(\n error: FrameworkError,\n strategies: ErrorHandlingStrategy[]\n ): Promise<boolean> {\n let handled = false;\n\n for (const strategy of strategies) {\n try {\n switch (strategy) {\n case ErrorHandlingStrategy.LOG:\n this.logError(error);\n handled = true;\n break;\n\n case ErrorHandlingStrategy.REPORT:\n await this.reportError(error);\n handled = true;\n break;\n\n case ErrorHandlingStrategy.RECOVER:\n // 恢复策略在 handleError 中统一处理\n handled = true;\n break;\n\n case ErrorHandlingStrategy.FALLBACK:\n // 降级策略在 attemptRecovery 中处理\n handled = true;\n break;\n\n case ErrorHandlingStrategy.IGNORE:\n // 忽略错误,不做任何处理\n handled = true;\n break;\n }\n } catch (strategyError) {\n logger.warn(`错误处理策略 ${strategy} 执行失败:`, strategyError);\n }\n }\n\n return handled;\n }\n\n /**\n * 记录错误\n */\n private logError(error: FrameworkError): void {\n const errorInfo = errorUtils.extractErrorInfo(error);\n \n // 根据错误严重程度选择日志级别\n switch (error.severity) {\n case ErrorSeverity.CRITICAL:\n case ErrorSeverity.HIGH:\n logger.error('错误:', errorInfo);\n break;\n case ErrorSeverity.MEDIUM:\n logger.warn('警告:', errorInfo);\n break;\n case ErrorSeverity.LOW:\n logger.info('信息:', errorInfo);\n break;\n }\n\n // 开发环境显示详细错误信息\n if (this.config.showDetailedErrorsInDev && process.env.NODE_ENV === 'development') {\n const detailedError = errorUtils.formatErrorForDev(error);\n console.error(detailedError);\n }\n }\n\n /**\n * 上报错误\n */\n private async reportError(error: FrameworkError): Promise<void> {\n if (!this.monitoring) {\n logger.debug('监控服务未配置,跳过错误上报');\n return;\n }\n\n try {\n this.monitoring.captureError(error, {\n severity: error.severity,\n type: error.type,\n code: error.code,\n context: error.context,\n });\n } catch (reportError) {\n logger.warn('错误上报失败:', reportError);\n }\n }\n\n /**\n * 尝试恢复错误\n */\n private async attemptRecovery(\n error: FrameworkError\n ): Promise<{ recoverable: boolean; value?: unknown }> {\n const options = this.config.recoveryOptions;\n if (!options) {\n return { recoverable: false };\n }\n\n const maxRetries = options.maxRetries ?? 3;\n const retryDelay = options.retryDelay ?? 1000;\n const exponentialBackoff = options.exponentialBackoff ?? true;\n const shouldRetry = options.shouldRetry ?? (() => true);\n\n // 如果有降级方案,先尝试降级\n if (options.fallback) {\n try {\n const fallbackValue = await Promise.resolve(options.fallback());\n logger.info('错误已通过降级方案恢复');\n return { recoverable: true, value: fallbackValue };\n } catch (fallbackError) {\n logger.warn('降级方案执行失败:', fallbackError);\n }\n }\n\n // 尝试重试(如果错误可恢复且满足重试条件)\n if (error.recoverable && shouldRetry(error, 0)) {\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n if (!shouldRetry(error, attempt)) {\n break;\n }\n\n const delay = exponentialBackoff\n ? retryDelay * Math.pow(2, attempt)\n : retryDelay;\n\n logger.debug(`错误恢复重试 ${attempt + 1}/${maxRetries},${delay}ms 后重试...`);\n\n await new Promise(resolve => setTimeout(resolve, delay));\n\n // 这里应该调用原始操作的重试逻辑\n // 由于错误处理器不知道原始操作,这里只返回可恢复状态\n // 实际的重试应该在调用方实现\n }\n }\n\n return { recoverable: error.recoverable };\n }\n\n /**\n * 创建错误处理器的便捷方法\n */\n static create(config?: ErrorHandlerConfig): ErrorHandler {\n return new ErrorHandler(config);\n }\n}\n\n/**\n * 默认错误处理器实例(单例)\n */\nlet defaultErrorHandler: ErrorHandler | null = null;\n\n/**\n * 获取默认错误处理器\n */\nexport function getDefaultErrorHandler(): ErrorHandler {\n if (!defaultErrorHandler) {\n defaultErrorHandler = new ErrorHandler();\n }\n return defaultErrorHandler;\n}\n\n/**\n * 设置默认错误处理器\n */\nexport function setDefaultErrorHandler(handler: ErrorHandler): void {\n defaultErrorHandler = handler;\n}\n"],"names":["ErrorHandler","ErrorHandlingStrategy","getDefaultErrorHandler","setDefaultErrorHandler","setMonitoring","monitoring","handleError","error","context","normalizedError","errorUtils","normalizeError","errorWithContext","FrameworkError","message","type","severity","code","originalError","recoverable","strategies","determineStrategies","handled","executeStrategies","recoveredValue","config","recoveryOptions","recoveryResult","attemptRecovery","value","typeStrategies","severityStrategies","defaultStrategies","strategy","logError","reportError","strategyError","logger","warn","errorInfo","extractErrorInfo","ErrorSeverity","CRITICAL","HIGH","MEDIUM","LOW","info","showDetailedErrorsInDev","process","env","NODE_ENV","detailedError","formatErrorForDev","console","debug","captureError","options","maxRetries","retryDelay","exponentialBackoff","shouldRetry","fallback","fallbackValue","Promise","resolve","fallbackError","attempt","delay","Math","pow","setTimeout","create","defaultErrorHandler","handler"],"mappings":"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"}
|