refactor(platform): extract blurActiveElement to infrastructure module
- Centralize duplicated Platform.OS === 'web' blur logic across 16 components - Add blurActiveElement utility to src/infrastructure/platform module - Optimize MessageBubble and ImageSegment with React.memo custom comparators - Add useMemo for computed values in MessageSegmentsRenderer - Update DTO types with is_system_notice and notice_content fields - Fix keyboard dismissal order in useChatScreen handleDismiss - Simplify userStore follow/unfollow state updates - Remove unnecessary TypeScript type assertions throughout codebase
This commit is contained in:
196
src/services/errorHandler.ts
Normal file
196
src/services/errorHandler.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* 统一错误处理服务
|
||||
*
|
||||
* 提供标准化的错误处理流程:
|
||||
* 1. 错误分类(网络错误、认证错误、业务错误等)
|
||||
* 2. 日志记录
|
||||
* 3. 用户提示
|
||||
* 4. 错误上报
|
||||
*
|
||||
* 替代散落在代码中的 console.error + 手动 Alert 模式
|
||||
*/
|
||||
|
||||
import { showPrompt } from './promptService';
|
||||
|
||||
export enum AppErrorCode {
|
||||
NETWORK_ERROR = 'NETWORK_ERROR',
|
||||
AUTH_ERROR = 'AUTH_ERROR',
|
||||
FORBIDDEN = 'FORBIDDEN',
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
||||
SERVER_ERROR = 'SERVER_ERROR',
|
||||
TIMEOUT = 'TIMEOUT',
|
||||
UNKNOWN = 'UNKNOWN',
|
||||
}
|
||||
|
||||
export interface AppError {
|
||||
code: AppErrorCode;
|
||||
message: string;
|
||||
originalError?: unknown;
|
||||
context?: string;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
const ERROR_MESSAGES: Record<AppErrorCode, string> = {
|
||||
[AppErrorCode.NETWORK_ERROR]: '网络连接失败,请检查网络设置',
|
||||
[AppErrorCode.AUTH_ERROR]: '登录已过期,请重新登录',
|
||||
[AppErrorCode.FORBIDDEN]: '没有权限执行此操作',
|
||||
[AppErrorCode.NOT_FOUND]: '请求的资源不存在',
|
||||
[AppErrorCode.VALIDATION_ERROR]: '输入数据有误,请检查后重试',
|
||||
[AppErrorCode.SERVER_ERROR]: '服务器错误,请稍后重试',
|
||||
[AppErrorCode.TIMEOUT]: '请求超时,请稍后重试',
|
||||
[AppErrorCode.UNKNOWN]: '操作失败,请稍后重试',
|
||||
};
|
||||
|
||||
function classifyError(error: unknown): AppErrorCode {
|
||||
if (!error) return AppErrorCode.UNKNOWN;
|
||||
|
||||
if (error instanceof Error) {
|
||||
const message = error.message.toLowerCase();
|
||||
|
||||
if (message.includes('network') || message.includes('fetch') || message.includes('econnrefused')) {
|
||||
return AppErrorCode.NETWORK_ERROR;
|
||||
}
|
||||
if (message.includes('timeout') || message.includes('timed out')) {
|
||||
return AppErrorCode.TIMEOUT;
|
||||
}
|
||||
if (message.includes('unauthorized') || message.includes('401') || message.includes('token')) {
|
||||
return AppErrorCode.AUTH_ERROR;
|
||||
}
|
||||
if (message.includes('forbidden') || message.includes('403')) {
|
||||
return AppErrorCode.FORBIDDEN;
|
||||
}
|
||||
if (message.includes('not found') || message.includes('404')) {
|
||||
return AppErrorCode.NOT_FOUND;
|
||||
}
|
||||
if (message.includes('validation') || message.includes('400') || message.includes('422')) {
|
||||
return AppErrorCode.VALIDATION_ERROR;
|
||||
}
|
||||
if (message.includes('500') || message.includes('502') || message.includes('503')) {
|
||||
return AppErrorCode.SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
const err = error as Record<string, unknown>;
|
||||
const status = err.status || err.statusCode || err.code;
|
||||
|
||||
if (status === 401) return AppErrorCode.AUTH_ERROR;
|
||||
if (status === 403) return AppErrorCode.FORBIDDEN;
|
||||
if (status === 404) return AppErrorCode.NOT_FOUND;
|
||||
if (status === 400 || status === 422) return AppErrorCode.VALIDATION_ERROR;
|
||||
if (typeof status === 'number' && status >= 500) return AppErrorCode.SERVER_ERROR;
|
||||
}
|
||||
|
||||
return AppErrorCode.UNKNOWN;
|
||||
}
|
||||
|
||||
export function createAppError(
|
||||
error: unknown,
|
||||
context?: string,
|
||||
options?: { silent?: boolean; userMessage?: string }
|
||||
): AppError {
|
||||
const code = classifyError(error);
|
||||
return {
|
||||
code,
|
||||
message: options?.userMessage || ERROR_MESSAGES[code],
|
||||
originalError: error,
|
||||
context,
|
||||
silent: options?.silent,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ErrorHandlerOptions {
|
||||
context?: string;
|
||||
userMessage?: string;
|
||||
silent?: boolean;
|
||||
showErrorPrompt?: boolean;
|
||||
logToConsole?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一错误处理函数
|
||||
*
|
||||
* 替代散落的 console.error + Alert 模式
|
||||
*
|
||||
* @example
|
||||
* // 之前
|
||||
* } catch (error) {
|
||||
* console.error('加载帖子详情失败:', error);
|
||||
* }
|
||||
*
|
||||
* // 之后
|
||||
* } catch (error) {
|
||||
* handleError(error, { context: '加载帖子详情' });
|
||||
* }
|
||||
*/
|
||||
export function handleError(error: unknown, options: ErrorHandlerOptions = {}): AppError {
|
||||
const {
|
||||
context,
|
||||
userMessage,
|
||||
silent = false,
|
||||
showErrorPrompt = false,
|
||||
logToConsole = true,
|
||||
} = options;
|
||||
|
||||
const appError = createAppError(error, context, { silent, userMessage });
|
||||
|
||||
if (logToConsole) {
|
||||
const prefix = context ? `[${context}]` : '';
|
||||
console.error(`${prefix} ${appError.message}`, error);
|
||||
}
|
||||
|
||||
if (showErrorPrompt && !silent) {
|
||||
showPrompt({
|
||||
type: 'error',
|
||||
title: context || '错误',
|
||||
message: appError.message,
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
return appError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带上下文的错误处理器
|
||||
*
|
||||
* @example
|
||||
* const postErrorHandler = createErrorHandler('帖子');
|
||||
*
|
||||
* } catch (error) {
|
||||
* postErrorHandler(error, { showErrorPrompt: true });
|
||||
* }
|
||||
*/
|
||||
export function createErrorHandler(defaultContext: string) {
|
||||
return (error: unknown, options: Omit<ErrorHandlerOptions, 'context'> = {}) => {
|
||||
return handleError(error, { ...options, context: defaultContext });
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全执行异步操作
|
||||
*
|
||||
* @example
|
||||
* const result = await safeAsync(
|
||||
* () => postService.getPost(id),
|
||||
* { context: '加载帖子' }
|
||||
* );
|
||||
* if (result.error) {
|
||||
* // 处理错误
|
||||
* } else {
|
||||
* // 使用 result.data
|
||||
* }
|
||||
*/
|
||||
export async function safeAsync<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: ErrorHandlerOptions = {}
|
||||
): Promise<{ data: T | null; error: AppError | null }> {
|
||||
try {
|
||||
const data = await fn();
|
||||
return { data, error: null };
|
||||
} catch (error) {
|
||||
const appError = handleError(error, options);
|
||||
return { data: null, error: appError };
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,11 @@ export {
|
||||
export { showPrompt } from './promptService';
|
||||
export { showConfirm } from './dialogService';
|
||||
|
||||
// 统一错误处理服务
|
||||
export { handleError, createErrorHandler, safeAsync, createAppError } from './errorHandler';
|
||||
export type { AppError, ErrorHandlerOptions } from './errorHandler';
|
||||
export { AppErrorCode } from './errorHandler';
|
||||
|
||||
// APK 更新检查服务
|
||||
export { apkUpdateService, checkForAPKUpdate, manualCheckForUpdate } from './apkUpdateService';
|
||||
export type { APKVersionInfo, VersionCheckResult } from './apkUpdateService';
|
||||
|
||||
Reference in New Issue
Block a user