🚨 错误处理
深入了解 Zhin.js 适配器中的错误处理机制。
🎯 错误处理概述
错误处理是适配器稳定性的关键,需要处理网络错误、平台API错误、消息解析错误等各种异常情况。
🔄 错误处理流程
错误处理流程图
mermaid
graph TD
A[错误发生] --> B[错误分类]
B --> C[错误处理策略]
C --> D[错误恢复]
D --> E[错误记录]
E --> F[错误通知]
G[网络错误] --> H[重连机制]
H --> I[指数退避]
I --> J[最大重试次数]
K[API错误] --> L[错误码映射]
L --> M[用户友好提示]
M --> N[降级处理]🚨 错误分类
错误类型定义
typescript
enum ErrorType {
NETWORK = 'network',
API = 'api',
AUTHENTICATION = 'authentication',
RATE_LIMIT = 'rate_limit',
PERMISSION = 'permission',
MESSAGE_PARSE = 'message_parse',
MESSAGE_SEND = 'message_send',
CONNECTION = 'connection',
UNKNOWN = 'unknown'
}
interface BotError extends Error {
type: ErrorType
code?: string | number
retryable: boolean
context?: any
timestamp: number
}错误分类器
typescript
class ErrorClassifier {
static classify(error: any): BotError {
const botError: BotError = {
name: error.name || 'BotError',
message: error.message || 'Unknown error',
type: ErrorType.UNKNOWN,
retryable: false,
timestamp: Date.now(),
stack: error.stack
}
// 网络错误
if (this.isNetworkError(error)) {
botError.type = ErrorType.NETWORK
botError.retryable = true
}
// API错误
else if (this.isApiError(error)) {
botError.type = ErrorType.API
botError.code = error.code || error.status
botError.retryable = this.isRetryableApiError(error)
}
// 认证错误
else if (this.isAuthError(error)) {
botError.type = ErrorType.AUTHENTICATION
botError.retryable = false
}
// 限流错误
else if (this.isRateLimitError(error)) {
botError.type = ErrorType.RATE_LIMIT
botError.retryable = true
}
// 权限错误
else if (this.isPermissionError(error)) {
botError.type = ErrorType.PERMISSION
botError.retryable = false
}
return botError
}
private static isNetworkError(error: any): boolean {
return error.code === 'ECONNRESET' ||
error.code === 'ENOTFOUND' ||
error.code === 'ETIMEDOUT' ||
error.message.includes('timeout') ||
error.message.includes('network')
}
private static isApiError(error: any): boolean {
return error.status >= 400 && error.status < 600
}
private static isAuthError(error: any): boolean {
return error.status === 401 ||
error.code === 'UNAUTHORIZED' ||
error.message.includes('token') ||
error.message.includes('auth')
}
private static isRateLimitError(error: any): boolean {
return error.status === 429 ||
error.code === 'RATE_LIMITED' ||
error.message.includes('rate limit')
}
private static isPermissionError(error: any): boolean {
return error.status === 403 ||
error.code === 'FORBIDDEN' ||
error.message.includes('permission')
}
private static isRetryableApiError(error: any): boolean {
return error.status >= 500 ||
error.status === 429 ||
error.code === 'TIMEOUT'
}
}🔄 重试机制
指数退避重试
typescript
class RetryManager {
private maxRetries = 5
private baseDelay = 1000
private maxDelay = 30000
async executeWithRetry<T>(
operation: () => Promise<T>,
context?: string
): Promise<T> {
let lastError: BotError
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
return await operation()
} catch (error) {
lastError = ErrorClassifier.classify(error)
if (!lastError.retryable || attempt === this.maxRetries) {
throw lastError
}
const delay = this.calculateDelay(attempt)
this.plugin.logger.warn(
`操作失败,${delay}ms后重试 (${attempt + 1}/${this.maxRetries}):`,
lastError.message
)
await this.sleep(delay)
}
}
throw lastError!
}
private calculateDelay(attempt: number): number {
const delay = this.baseDelay * Math.pow(2, attempt)
return Math.min(delay, this.maxDelay)
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
}连接重试
typescript
class MyBot implements Bot {
private retryManager = new RetryManager()
private reconnectAttempts = 0
private maxReconnectAttempts = 10
async connect() {
try {
await this.retryManager.executeWithRetry(
() => this.establishConnection(),
'Bot连接'
)
this.setConnected(true)
this.reconnectAttempts = 0
this.plugin.logger.info('Bot 连接成功')
} catch (error) {
this.plugin.logger.error('Bot 连接失败:', error)
throw error
}
}
private async establishConnection() {
// 实现具体的连接逻辑
await this.client.connect()
}
private async handleConnectionError(error: any) {
const botError = ErrorClassifier.classify(error)
if (botError.retryable && this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++
const delay = this.calculateReconnectDelay()
this.plugin.logger.warn(
`连接错误,${delay}ms后尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts}):`,
botError.message
)
setTimeout(() => {
this.connect()
}, delay)
} else {
this.plugin.logger.error('重连次数超限,停止重连')
this.plugin.emit('bot.connection.failed', botError)
}
}
private calculateReconnectDelay(): number {
const baseDelay = 5000
const maxDelay = 300000 // 5分钟
const delay = baseDelay * Math.pow(2, this.reconnectAttempts - 1)
return Math.min(delay, maxDelay)
}
}📝 错误记录
错误日志记录
typescript
class ErrorLogger {
private errorCounts = new Map<string, number>()
private errorHistory: BotError[] = []
private maxHistorySize = 1000
logError(error: BotError, context?: any) {
// 记录错误计数
const key = `${error.type}:${error.code || 'unknown'}`
const count = this.errorCounts.get(key) || 0
this.errorCounts.set(key, count + 1)
// 记录错误历史
this.errorHistory.push({
...error,
context
})
// 限制历史记录大小
if (this.errorHistory.length > this.maxHistorySize) {
this.errorHistory.shift()
}
// 根据错误类型选择日志级别
switch (error.type) {
case ErrorType.NETWORK:
this.plugin.logger.warn('网络错误:', error.message, context)
break
case ErrorType.API:
this.plugin.logger.error('API错误:', error.message, context)
break
case ErrorType.AUTHENTICATION:
this.plugin.logger.error('认证错误:', error.message, context)
break
case ErrorType.RATE_LIMIT:
this.plugin.logger.warn('限流错误:', error.message, context)
break
case ErrorType.PERMISSION:
this.plugin.logger.error('权限错误:', error.message, context)
break
default:
this.plugin.logger.error('未知错误:', error.message, context)
}
}
getErrorStats() {
return {
counts: Object.fromEntries(this.errorCounts),
recent: this.errorHistory.slice(-10),
total: this.errorHistory.length
}
}
}错误监控
typescript
class ErrorMonitor {
private errorLogger = new ErrorLogger()
private alertThresholds = {
errorRate: 0.1, // 10%错误率
consecutiveErrors: 5,
criticalErrors: 3
}
private recentErrors: BotError[] = []
private consecutiveErrorCount = 0
handleError(error: BotError, context?: any) {
this.errorLogger.logError(error, context)
this.updateErrorStats(error)
this.checkAlerts(error)
}
private updateErrorStats(error: BotError) {
this.recentErrors.push(error)
// 只保留最近100个错误
if (this.recentErrors.length > 100) {
this.recentErrors.shift()
}
if (error.type === ErrorType.API || error.type === ErrorType.NETWORK) {
this.consecutiveErrorCount++
} else {
this.consecutiveErrorCount = 0
}
}
private checkAlerts(error: BotError) {
// 检查连续错误
if (this.consecutiveErrorCount >= this.alertThresholds.consecutiveErrors) {
this.triggerAlert('consecutive_errors', {
count: this.consecutiveErrorCount,
lastError: error
})
}
// 检查错误率
const errorRate = this.calculateErrorRate()
if (errorRate >= this.alertThresholds.errorRate) {
this.triggerAlert('high_error_rate', {
rate: errorRate,
recentErrors: this.recentErrors.slice(-10)
})
}
// 检查关键错误
if (error.type === ErrorType.AUTHENTICATION || error.type === ErrorType.PERMISSION) {
this.triggerAlert('critical_error', {
type: error.type,
error: error
})
}
}
private calculateErrorRate(): number {
const now = Date.now()
const recentWindow = 5 * 60 * 1000 // 5分钟
const recentErrors = this.recentErrors.filter(
error => now - error.timestamp < recentWindow
)
// 这里需要知道总操作数,简化处理
return recentErrors.length / 100 // 假设最近有100次操作
}
private triggerAlert(type: string, data: any) {
this.plugin.emit('bot.alert', { type, data })
this.plugin.logger.error(`Bot 告警 [${type}]:`, data)
}
}🔧 错误恢复
自动恢复策略
typescript
class ErrorRecovery {
private recoveryStrategies = new Map<ErrorType, (error: BotError) => Promise<void>>()
constructor(private bot: MyBot) {
this.setupRecoveryStrategies()
}
private setupRecoveryStrategies() {
this.recoveryStrategies.set(ErrorType.NETWORK, this.handleNetworkError.bind(this))
this.recoveryStrategies.set(ErrorType.RATE_LIMIT, this.handleRateLimitError.bind(this))
this.recoveryStrategies.set(ErrorType.AUTHENTICATION, this.handleAuthError.bind(this))
this.recoveryStrategies.set(ErrorType.CONNECTION, this.handleConnectionError.bind(this))
}
async recover(error: BotError) {
const strategy = this.recoveryStrategies.get(error.type)
if (strategy) {
try {
await strategy(error)
this.bot.plugin.logger.info(`错误恢复成功: ${error.type}`)
} catch (recoveryError) {
this.bot.plugin.logger.error(`错误恢复失败: ${error.type}`, recoveryError)
}
}
}
private async handleNetworkError(error: BotError) {
// 网络错误:等待一段时间后重连
await this.sleep(5000)
await this.bot.connect()
}
private async handleRateLimitError(error: BotError) {
// 限流错误:等待更长时间
const delay = this.extractRetryAfter(error) || 60000
await this.sleep(delay)
}
private async handleAuthError(error: BotError) {
// 认证错误:尝试重新认证
await this.bot.authenticate()
}
private async handleConnectionError(error: BotError) {
// 连接错误:重新建立连接
await this.bot.disconnect()
await this.sleep(2000)
await this.bot.connect()
}
private extractRetryAfter(error: BotError): number | null {
// 从错误中提取重试时间
const retryAfter = error.context?.retryAfter || error.context?.retry_after
return retryAfter ? parseInt(retryAfter) * 1000 : null
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
}