| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- /**
- * 统一交易所适配器错误处理工具
- */
- export interface AdapterError {
- code: string
- message: string
- exchange: string
- originalError?: any
- retryable: boolean
- severity: 'low' | 'medium' | 'high' | 'critical'
- }
- export class AdapterErrorHandler {
- /**
- * 标准化错误处理
- */
- static handleError(exchange: string, error: any, method: string): AdapterError {
- const originalMessage = this.extractErrorMessage(error)
- const code = this.extractErrorCode(error, exchange)
- // 根据错误码判断是否可重试和严重程度
- const { retryable, severity } = this.analyzeError(code, exchange)
- const adapterError: AdapterError = {
- code,
- message: `[${exchange}:${method}] ${originalMessage}`,
- exchange,
- originalError: error,
- retryable,
- severity,
- }
- // 记录日志
- this.logError(adapterError, method)
- return adapterError
- }
- /**
- * 提取错误信息
- */
- private static extractErrorMessage(error: any): string {
- if (typeof error === 'string') return error
- if (error?.message) return error.message
- if (error?.msg) return error.msg
- if (error?.reason) return error.reason
- return '未知错误'
- }
- /**
- * 提取错误代码
- */
- private static extractErrorCode(error: any, exchange: string): string {
- // Binance 风格错误码
- if (error?.code) return String(error.code)
- // HTTP 状态码
- if (error?.status) return `HTTP_${error.status}`
- if (error?.response?.status) return `HTTP_${error.response.status}`
- // Pacifica 特殊情况
- if (exchange === 'pacifica' && error?.message?.includes('403')) return 'HTTP_403'
- // Aster 特殊情况
- if (exchange === 'aster' && error?.message?.includes('-2019')) return 'INSUFFICIENT_MARGIN'
- return 'UNKNOWN_ERROR'
- }
- /**
- * 分析错误属性
- */
- private static analyzeError(
- code: string,
- exchange: string,
- ): { retryable: boolean; severity: 'low' | 'medium' | 'high' | 'critical' } {
- // 通用 HTTP 错误
- if (code.startsWith('HTTP_')) {
- const statusCode = parseInt(code.replace('HTTP_', ''))
- if (statusCode >= 500) return { retryable: true, severity: 'high' }
- if (statusCode === 429) return { retryable: true, severity: 'medium' }
- if (statusCode === 403) return { retryable: false, severity: 'medium' }
- if (statusCode === 401) return { retryable: false, severity: 'high' }
- return { retryable: false, severity: 'medium' }
- }
- // 交易所特定错误
- if (exchange === 'binance') {
- return this.analyzeBinanceError(code)
- } else if (exchange === 'aster') {
- return this.analyzeAsterError(code)
- } else if (exchange === 'pacifica') {
- return this.analyzePacificaError(code)
- }
- return { retryable: false, severity: 'medium' }
- }
- /**
- * 分析币安错误
- */
- private static analyzeBinanceError(code: string): {
- retryable: boolean
- severity: 'low' | 'medium' | 'high' | 'critical'
- } {
- const retryableCodes = ['-1001', '-1021', '-2013', '-2015'] // 网络、时间同步等
- const criticalCodes = ['-1022', '-2010', '-4028'] // API密钥、余额不足等
- const marginCodes = ['-2019', '-4061'] // 保证金相关
- if (retryableCodes.includes(code)) return { retryable: true, severity: 'medium' }
- if (criticalCodes.includes(code)) return { retryable: false, severity: 'critical' }
- if (marginCodes.includes(code)) return { retryable: false, severity: 'high' }
- return { retryable: false, severity: 'medium' }
- }
- /**
- * 分析Aster错误
- */
- private static analyzeAsterError(code: string): {
- retryable: boolean
- severity: 'low' | 'medium' | 'high' | 'critical'
- } {
- if (code === 'INSUFFICIENT_MARGIN') return { retryable: false, severity: 'high' }
- if (code === 'missing_user_signer_privateKey') return { retryable: false, severity: 'critical' }
- if (code === 'listenKeyExpired') return { retryable: true, severity: 'medium' }
- return { retryable: false, severity: 'medium' }
- }
- /**
- * 分析Pacifica错误
- */
- private static analyzePacificaError(code: string): {
- retryable: boolean
- severity: 'low' | 'medium' | 'high' | 'critical'
- } {
- if (code === 'HTTP_403') return { retryable: false, severity: 'medium' } // CDN/白名单
- if (code === 'orderbook_not_found') return { retryable: true, severity: 'medium' }
- return { retryable: false, severity: 'medium' }
- }
- /**
- * 记录错误日志
- */
- private static logError(error: AdapterError, method: string) {
- const level =
- error.severity === 'critical'
- ? 'error'
- : error.severity === 'high'
- ? 'error'
- : error.severity === 'medium'
- ? 'warn'
- : 'info'
- const logMessage = `${error.message} [代码: ${error.code}, 可重试: ${error.retryable}]`
- // 这里可以集成实际的日志系统
- if (level === 'error') {
- console.error(`❌ ${logMessage}`)
- } else if (level === 'warn') {
- console.warn(`⚠️ ${logMessage}`)
- } else {
- console.info(`ℹ️ ${logMessage}`)
- }
- }
- /**
- * 检查是否应该重试
- */
- static shouldRetry(error: AdapterError, attemptCount: number, maxAttempts = 3): boolean {
- return error.retryable && attemptCount < maxAttempts
- }
- /**
- * 计算重试延迟(指数退避)
- */
- static getRetryDelay(attemptCount: number, baseDelayMs = 1000, maxDelayMs = 10000): number {
- const delay = Math.min(baseDelayMs * Math.pow(2, attemptCount - 1), maxDelayMs)
- // 添加随机抖动避免雷群效应
- return delay + Math.random() * 1000
- }
- }
- /**
- * 装饰器:为适配器方法添加错误处理和重试逻辑
- */
- export function withErrorHandling(exchange: string, maxRetries = 2) {
- return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
- const method = descriptor.value
- descriptor.value = async function (...args: any[]) {
- let lastError: AdapterError | null = null
- for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
- try {
- return await method.apply(this, args)
- } catch (error) {
- const adapterError = AdapterErrorHandler.handleError(exchange, error, propertyName)
- lastError = adapterError
- if (!AdapterErrorHandler.shouldRetry(adapterError, attempt, maxRetries + 1)) {
- throw adapterError
- }
- const delay = AdapterErrorHandler.getRetryDelay(attempt)
- console.info(`🔄 [${exchange}:${propertyName}] 重试第 ${attempt} 次,${delay}ms 后重试`)
- await new Promise(resolve => setTimeout(resolve, delay))
- }
- }
- throw lastError
- }
- }
- }
|