# MQ 推送事件处理:命令响应模式 + Channel 实践

部署运行你感兴趣的模型镜像
# MQ 推送事件处理:命令响应模式 + Channel 实践指南

## 📋 目录

1. [设计概述](#设计概述)
2. [核心概念](#核心概念)
3. [架构设计](#架构设计)
4. [实现方案](#实现方案)
5. [使用示例](#使用示例)
6. [最佳实践](#最佳实践)
7. [设计原则](#设计原则)
8. [常见问题](#常见问题)
9. [与其他模式对比](#与其他模式对比)

---

## 🎯 设计概述

### 为什么使用命令响应模式?

MQ 推送事件处理的特点:
- ✅ **外部触发**:MQTT/其他消息队列推送
- ✅ **需要操作结果**:处理成功/失败需要明确反馈
- ✅ **顺序保证**:同一记录的操作需要按顺序处理
- ✅ **可能并发**:多个推送消息可能同时到达
- ✅ **操作追踪**:需要知道每个操作的状态和结果

### 命令响应模式的优势

相比传统的观察者模式:
- ✅ **明确的请求-响应**:每个推送事件有明确的处理结果
- ✅ **顺序保证**:通过 Channel 队列保证操作顺序
- ✅ **操作追踪**:每个命令有唯一ID,可以追踪状态
- ✅ **错误处理**:完善的错误处理和反馈机制
- ✅ **易于扩展**:新增命令类型只需添加新的命令类

### Channel 的优势

- ✅ **队列语义**:天然支持命令队列
- ✅ **顺序保证**:单消费者模式保证顺序处理
- ✅ **背压处理**:支持缓冲区配置
- ✅ **协程集成**:与 Kotlin 协程完美集成

---

## 💡 核心概念

### 1. 命令模式(Command Pattern)

```
┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│  推送事件    │  ──→    │   命令封装    │  ──→    │   命令队列    │
│  (MQTT)     │         │  (Command)   │         │  (Channel)   │
└─────────────┘         └──────────────┘         └─────────────┘
                              ↓
                         ┌──────────────┐
                         │   命令处理器   │
                         │  (Processor) │
                         └──────────────┘
                              ↓
                         ┌──────────────┐
                         │   操作结果    │
                         │  (Result)    │
                         └──────────────┘
```

### 2. 请求-响应模式(Request-Response Pattern)

```
Producer (推送事件)
    ↓
Command (命令封装)
    ↓
Channel (命令队列)
    ↓
Processor (命令处理)
    ↓
Result (操作结果)
    ↓
Response Channel (响应通道)
    ↓
Producer (收到响应)
```

### 3. Channel 队列模式

```kotlin
// ✅ 单消费者模式(保证顺序)
val commandChannel = Channel<Command>(Channel.UNLIMITED)

actorScope.launch {
    for (command in commandChannel) {
        // 串行处理,保证顺序
        processCommand(command)
    }
}
```

---

## 🏗️ 架构设计

### 整体架构

```
┌─────────────────────────────────────────────────────────┐
│              MQ 推送事件处理系统                          │
├─────────────────────────────────────────────────────────┤
│                                                           │
│  1. 消息接收层:                                          │
│  ├─ MQTT Handler                                         │
│  ├─ Message Parser                                       │
│  └─ Event Validator                                      │
│                                                           │
│  2. 命令管理层:                                          │
│  ├─ Command Factory(命令工厂)                          │
│  ├─ Command Channel(命令队列)                          │
│  └─ Command Router(命令路由)                          │
│                                                           │
│  3. 命令处理层:                                          │
│  ├─ AddRecordHandler                                     │
│  ├─ UpdateRecordHandler                                  │
│  ├─ DeleteRecordHandler                                  │
│  └─ GenericHandler                                       │
│                                                           │
│  4. 结果反馈层:                                          │
│  ├─ Result Channel(结果通道)                           │
│  ├─ Result Notifier(结果通知)                         │
│  └─ Error Handler(错误处理)                            │
│                                                           │
└─────────────────────────────────────────────────────────┘
```

### 数据流向

```
1. MQTT 推送消息到达
   ↓
2. 解析推送事件(Event Parser)
   ↓
3. 创建命令对象(Command Factory)
   ↓
4. 发送到命令队列(Command Channel,按记录ID分组)
   ↓
5. 命令处理器串行处理(Command Processor)
   ↓
6. 返回操作结果(Result Channel)
   ↓
7. 结果通知(Result Notifier)
```

### 顺序保证机制

```
记录ID = "123"
├─ 命令1: AddRecord(id="123") → Channel[123] → 处理中...
├─ 命令2: UpdateRecord(id="123") → Channel[123] → 等待中...
└─ 命令3: DeleteRecord(id="123") → Channel[123] → 等待中...

✅ 每个记录ID有独立的 Channel,保证同一记录的操作按顺序处理
✅ 不同记录ID的操作可以并行处理
```

---

## 🔧 实现方案

### 方案1:完整命令响应模式实现

#### 1. 命令定义(Command)

```kotlin
/**
 * ✅ 推送事件命令(密封类)
 */
sealed class PushEventCommand : Comparable<PushEventCommand> {
    abstract val commandId: String
    abstract val recordId: String
    abstract val timestamp: Long
    abstract val context: PushEventContext
    abstract val responseChannel: Channel<CommandResult>
    
    // ✅ 按时间戳排序(同记录按顺序)
    override fun compareTo(other: PushEventCommand): Int {
        if (recordId != other.recordId) {
            return recordId.compareTo(other.recordId)
        }
        return timestamp.compareTo(other.timestamp)
    }
}

/**
 * ✅ 添加记录命令
 */
data class AddRecordCommand(
    override val commandId: String,
    override val recordId: String,
    override val timestamp: Long,
    override val context: PushEventContext,
    override val responseChannel: Channel<CommandResult>,
    val eventData: CloudSyncEventData
) : PushEventCommand()

/**
 * ✅ 更新记录命令
 */
data class UpdateRecordCommand(
    override val commandId: String,
    override val recordId: String,
    override val timestamp: Long,
    override val context: PushEventContext,
    override val responseChannel: Channel<CommandResult>,
    val eventData: CloudSyncEventData
) : PushEventCommand()

/**
 * ✅ 删除记录命令
 */
data class DeleteRecordCommand(
    override val commandId: String,
    override val recordId: String,
    override val timestamp: Long,
    override val context: PushEventContext,
    override val responseChannel: Channel<CommandResult>,
    val eventData: CloudSyncEventData
) : PushEventCommand()
```

#### 2. 命令上下文(Command Context)

```kotlin
/**
 * ✅ 推送事件上下文
 */
data class PushEventContext(
    val eventId: String,              // 事件唯一ID
    val source: String,               // 事件来源(MQTT/HTTP等)
    val timestamp: Long,              // 事件时间戳
    val messageId: String? = null,    // MQTT 消息ID
    val userId: String? = null,       // 用户ID
    val homeId: String? = null,       // 家庭ID
    val metadata: Map<String, Any> = emptyMap()  // 扩展元数据
)
```

#### 3. 命令管理器(Command Manager)

```kotlin
/**
 * ✅ 推送事件命令管理器
 * 
 * 职责:
 * 1. 接收推送事件
 * 2. 封装为命令
 * 3. 发送到命令队列(按记录ID分组)
 * 4. 返回操作结果
 */
class PushEventCommandManager {
    private const val TAG = "PushEventCommandManager"
    
    private val actorScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    
    // ✅ 命令队列(按记录ID分组)
    private val commandChannels = mutableMapOf<String, Channel<PushEventCommand>>()
    private val channelMutex = Mutex()
    
    // ✅ 命令处理器
    private val commandProcessor = CommandProcessor()
    
    // ✅ 命令工厂
    private val commandFactory = CommandFactory()
    
    init {
        Logger.i(TAG, "PushEventCommandManager initialized")
    }
    
    /**
     * ✅ 处理 MQTT 推送事件(主入口)
     */
    suspend fun handlePushEvent(
        mqttMessage: MqttMessageBean
    ): PushEventResult {
        return try {
            // 1. 解析事件
            val eventData = CloudSyncEventParser.parseCloudSyncEvent(mqttMessage)
                ?: return PushEventResult.Failed(
                    message = "Failed to parse MQTT message",
                    recordId = "",
                    error = IllegalArgumentException("Invalid message format")
                )
            
            Logger.i(TAG, "handlePushEvent: eventType=${eventData.eventType}, recordId=${eventData.recordId}, operateType=${eventData.operateType}")
            
            // 2. 创建命令
            val command = commandFactory.createCommand(eventData)
            
            // 3. 发送到队列并等待结果
            val result = sendCommandAndWait(command)
            
            // 4. 转换为 PushEventResult
            when (result) {
                is CommandResult.Success -> {
                    PushEventResult.Success(
                        message = result.message,
                        recordId = command.recordId,
                        skipped = result.skipped
                    )
                }
                is CommandResult.Failed -> {
                    PushEventResult.Failed(
                        message = result.message,
                        recordId = command.recordId,
                        error = result.error
                    )
                }
                is CommandResult.Rejected -> {
                    PushEventResult.Rejected(
                        reason = result.reason,
                        recordId = command.recordId
                    )
                }
            }
        } catch (e: Exception) {
            Logger.e(TAG, "handlePushEvent exception: ${e.message}")
            PushEventResult.Failed(
                message = "Unexpected error: ${e.message}",
                recordId = "",
                error = e
            )
        }
    }
    
    /**
     * ✅ 发送命令到队列并等待结果
     */
    private suspend fun sendCommandAndWait(
        command: PushEventCommand
    ): CommandResult {
        // ✅ 获取或创建命令队列(按记录ID分组)
        val channel = getOrCreateChannel(command.recordId)
        
        // ✅ 发送命令到队列
        channel.send(command)
        
        // ✅ 等待响应结果(带超时)
        return withTimeoutOrNull(30000) {  // 30秒超时
            command.responseChannel.receive()
        } ?: CommandResult.Failed(
            error = TimeoutException("Command processing timeout"),
            message = "Command processing timeout after 30 seconds"
        )
    }
    
    /**
     * ✅ 获取或创建命令队列(按记录ID分组,保证顺序)
     */
    private suspend fun getOrCreateChannel(
        recordId: String
    ): Channel<PushEventCommand> {
        return channelMutex.withLock {
            commandChannels.getOrPut(recordId) {
                val channel = Channel<PushEventCommand>(Channel.UNLIMITED)
                
                // ✅ 启动命令处理器(每个记录ID一个处理器,保证顺序)
                actorScope.launch {
                    processCommandsForRecord(recordId, channel)
                }
                
                Logger.d(TAG, "Created command channel for recordId=$recordId")
                channel
            }
        }
    }
    
    /**
     * ✅ 处理指定记录的命令(串行处理,保证顺序)
     */
    private suspend fun processCommandsForRecord(
        recordId: String,
        channel: Channel<PushEventCommand>
    ) {
        Logger.d(TAG, "Started command processor for recordId=$recordId")
        
        try {
            for (command in channel) {
                try {
                    // ✅ 处理命令
                    val result = commandProcessor.processCommand(command)
                    
                    // ✅ 发送结果
                    command.responseChannel.send(result)
                    command.responseChannel.close()
                    
                    Logger.d(TAG, "Command processed: commandId=${command.commandId}, result=${result::class.simpleName}")
                } catch (e: Exception) {
                    Logger.e(TAG, "Error processing command: commandId=${command.commandId}, error=${e.message}")
                    
                    // ✅ 发送错误结果
                    command.responseChannel.send(
                        CommandResult.Failed(
                            error = e,
                            message = e.message ?: "Unknown error"
                        )
                    )
                    command.responseChannel.close()
                }
            }
        } catch (e: Exception) {
            Logger.e(TAG, "Command processor error for recordId=$recordId: ${e.message}")
        } finally {
            // ✅ 清理资源
            channelMutex.withLock {
                commandChannels.remove(recordId)
            }
            Logger.d(TAG, "Command processor stopped for recordId=$recordId")
        }
    }
    
    /**
     * ✅ 清理资源(登出时调用)
     */
    fun clear() {
        channelMutex.withLock {
            // 关闭所有 Channel
            commandChannels.values.forEach { it.close() }
            commandChannels.clear()
        }
        actorScope.cancel()
        Logger.i(TAG, "PushEventCommandManager cleared")
    }
}
```

#### 4. 命令工厂(Command Factory)

```kotlin
/**
 * ✅ 命令工厂
 * 
 * 职责:
 * 1. 根据事件类型创建对应的命令
 * 2. 设置命令上下文
 * 3. 创建响应 Channel
 */
class CommandFactory {
    private const val TAG = "CommandFactory"
    
    /**
     * ✅ 创建命令(根据事件数据)
     */
    fun createCommand(
        eventData: CloudSyncEventData
    ): PushEventCommand {
        // ✅ 创建上下文
        val context = PushEventContext(
            eventId = UUID.randomUUID().toString(),
            source = "MQTT",
            timestamp = System.currentTimeMillis(),
            messageId = eventData.key,
            homeId = eventData.homeId,
            metadata = mapOf(
                "eventType" to eventData.eventType,
                "bizType" to eventData.bizType,
                "modifyTime" to eventData.modifyTime
            )
        )
        
        // ✅ 创建响应 Channel
        val responseChannel = Channel<CommandResult>(Channel.RENDEZVOUS)
        
        // ✅ 根据操作类型创建命令
        return when (eventData.operateType) {
            RecordOperateDef.OPERATE_TYPE_ADD -> {
                AddRecordCommand(
                    commandId = context.eventId,
                    recordId = eventData.recordId,
                    timestamp = context.timestamp,
                    context = context,
                    responseChannel = responseChannel,
                    eventData = eventData
                )
            }
            RecordOperateDef.OPERATE_TYPE_UPDATE -> {
                UpdateRecordCommand(
                    commandId = context.eventId,
                    recordId = eventData.recordId,
                    timestamp = context.timestamp,
                    context = context,
                    responseChannel = responseChannel,
                    eventData = eventData
                )
            }
            RecordOperateDef.OPERATE_TYPE_DELETE -> {
                DeleteRecordCommand(
                    commandId = context.eventId,
                    recordId = eventData.recordId,
                    timestamp = context.timestamp,
                    context = context,
                    responseChannel = responseChannel,
                    eventData = eventData
                )
            }
            else -> {
                Logger.w(TAG, "Unknown operateType=${eventData.operateType}, creating GenericCommand")
                GenericCommand(
                    commandId = context.eventId,
                    recordId = eventData.recordId,
                    timestamp = context.timestamp,
                    context = context,
                    responseChannel = responseChannel,
                    eventData = eventData
                )
            }
        }
    }
}
```

#### 5. 命令处理器(Command Processor)

```kotlin
/**
 * ✅ 命令处理器
 * 
 * 职责:
 * 1. 执行命令
 * 2. 返回操作结果
 * 3. 错误处理
 */
class CommandProcessor {
    private const val TAG = "CommandProcessor"
    
    private val dbManger = RecordDbManger.getInstance(MicroContext.getApplication())
    private val recordSyncService = MicroContext.findServiceByInterface<RecordDataSyncService>(
        RecordDataSyncService::class.java.name
    )
    
    /**
     * ✅ 处理命令
     */
    suspend fun processCommand(
        command: PushEventCommand
    ): CommandResult {
        Logger.i(TAG, "Processing command: commandId=${command.commandId}, recordId=${command.recordId}, type=${command::class.simpleName}")
        
        return when (command) {
            is AddRecordCommand -> handleAddCommand(command)
            is UpdateRecordCommand -> handleUpdateCommand(command)
            is DeleteRecordCommand -> handleDeleteCommand(command)
            is GenericCommand -> handleGenericCommand(command)
        }
    }
    
    /**
     * ✅ 处理添加命令
     */
    private suspend fun handleAddCommand(
        command: AddRecordCommand
    ): CommandResult {
        return try {
            // 1. 检查本地是否存在
            val localRecord = dbManger.getFileByRecordId(command.recordId)
            
            if (localRecord != null) {
                Logger.i(TAG, "handleAddCommand: record already exists, skipped: recordId=${command.recordId}")
                return CommandResult.Success(
                    message = "Record already exists, skipped",
                    data = localRecord,
                    skipped = true
                )
            }
            
            // 2. 从云端同步记录
            Logger.d(TAG, "handleAddCommand: fetching record from cloud: recordId=${command.recordId}")
            val cloudRecord = recordSyncService?.getCloudRecordById(command.recordId)
                ?: return CommandResult.Failed(
                    error = Exception("Record not found in cloud"),
                    message = "Record not found in cloud: recordId=${command.recordId}"
                )
            
            // 3. 添加到本地数据库
            dbManger.insertRecord(cloudRecord)
            
            Logger.i(TAG, "handleAddCommand: record added successfully: recordId=${command.recordId}")
            
            CommandResult.Success(
                message = "Record added successfully",
                data = cloudRecord,
                skipped = false
            )
        } catch (e: Exception) {
            Logger.e(TAG, "handleAddCommand exception: recordId=${command.recordId}, error=${e.message}")
            CommandResult.Failed(
                error = e,
                message = "Failed to add record: ${e.message}"
            )
        }
    }
    
    /**
     * ✅ 处理更新命令
     */
    private suspend fun handleUpdateCommand(
        command: UpdateRecordCommand
    ): CommandResult {
        return try {
            // 1. 检查本地是否存在
            val localRecord = dbManger.getFileByRecordId(command.recordId)
            
            if (localRecord == null) {
                Logger.i(TAG, "handleUpdateCommand: local record not found, skipped: recordId=${command.recordId}")
                return CommandResult.Success(
                    message = "Local record not found, skipped",
                    data = null,
                    skipped = true
                )
            }
            
            // 2. 时间戳比较(只处理更新的数据)
            val modifyTime = command.eventData.modifyTime.toLongOrNull() ?: 0L
            val localModifyTime = localRecord.updateAt ?: 0L
            
            if (modifyTime <= localModifyTime) {
                Logger.d(TAG, "handleUpdateCommand: older timestamp, skipped: recordId=${command.recordId}, local=$localModifyTime, new=$modifyTime")
                return CommandResult.Success(
                    message = "Older timestamp, skipped",
                    data = localRecord,
                    skipped = true
                )
            }
            
            // 3. 从云端同步最新记录
            Logger.d(TAG, "handleUpdateCommand: fetching latest record from cloud: recordId=${command.recordId}")
            val cloudRecord = recordSyncService?.getCloudRecordById(command.recordId)
                ?: return CommandResult.Failed(
                    error = Exception("Record not found in cloud"),
                    message = "Record not found in cloud: recordId=${command.recordId}"
                )
            
            // 4. 更新本地记录
            dbManger.updateRecord(cloudRecord)
            
            Logger.i(TAG, "handleUpdateCommand: record updated successfully: recordId=${command.recordId}")
            
            CommandResult.Success(
                message = "Record updated successfully",
                data = cloudRecord,
                skipped = false
            )
        } catch (e: Exception) {
            Logger.e(TAG, "handleUpdateCommand exception: recordId=${command.recordId}, error=${e.message}")
            CommandResult.Failed(
                error = e,
                message = "Failed to update record: ${e.message}"
            )
        }
    }
    
    /**
     * ✅ 处理删除命令
     */
    private suspend fun handleDeleteCommand(
        command: DeleteRecordCommand
    ): CommandResult {
        return try {
            // 1. 检查本地是否存在
            val localRecord = dbManger.getFileByRecordId(command.recordId)
            
            if (localRecord == null) {
                Logger.i(TAG, "handleDeleteCommand: local record not found, skipped: recordId=${command.recordId}")
                return CommandResult.Success(
                    message = "Local record not found, skipped",
                    data = null,
                    skipped = true
                )
            }
            
            // 2. 删除本地记录(包括相关文件)
            RecordFileDeleteManager.deleteFilesAsync(command.recordId)
            dbManger.deleteRecord(command.recordId)
            
            Logger.i(TAG, "handleDeleteCommand: record deleted successfully: recordId=${command.recordId}")
            
            CommandResult.Success(
                message = "Record deleted successfully",
                data = null,
                skipped = false
            )
        } catch (e: Exception) {
            Logger.e(TAG, "handleDeleteCommand exception: recordId=${command.recordId}, error=${e.message}")
            CommandResult.Failed(
                error = e,
                message = "Failed to delete record: ${e.message}"
            )
        }
    }
    
    /**
     * ✅ 处理通用命令(未知类型)
     */
    private suspend fun handleGenericCommand(
        command: GenericCommand
    ): CommandResult {
        Logger.w(TAG, "handleGenericCommand: unknown command type, recordId=${command.recordId}")
        return CommandResult.Rejected(
            reason = "Unknown command type: ${command.eventData.operateType}"
        )
    }
}
```

#### 6. 操作结果(Command Result)

```kotlin
/**
 * ✅ 命令结果(密封类)
 */
sealed class CommandResult {
    /**
     * ✅ 操作成功
     */
    data class Success(
        val message: String,
        val data: Any? = null,
        val skipped: Boolean = false,  // 是否被跳过(已存在/不存在/时间戳更小)
        val timestamp: Long = System.currentTimeMillis()
    ) : CommandResult()
    
    /**
     * ❌ 操作失败
     */
    data class Failed(
        val error: Throwable,
        val message: String,
        val timestamp: Long = System.currentTimeMillis()
    ) : CommandResult()
    
    /**
     * ⚠️ 操作被拒绝
     */
    data class Rejected(
        val reason: String,
        val timestamp: Long = System.currentTimeMillis()
    ) : CommandResult()
}

/**
 * ✅ 推送事件结果(封装 CommandResult,对外接口)
 */
sealed class PushEventResult {
    data class Success(
        val message: String,
        val recordId: String,
        val skipped: Boolean = false
    ) : PushEventResult()
    
    data class Failed(
        val message: String,
        val recordId: String,
        val error: Throwable? = null
    ) : PushEventResult()
    
    data class Rejected(
        val reason: String,
        val recordId: String
    ) : PushEventResult()
}
```

#### 7. 完整集成示例

```kotlin
/**
 * ✅ 云同步事件管理器(使用命令响应模式)
 */
class CloudSyncEventManager {
    private val commandManager = PushEventCommandManager()
    
    /**
     * ✅ 处理 MQTT 消息(统一入口)
     */
    fun handleMqttMessage(messageBean: MqttMessageBean) {
        if (messageBean.protocol != MQTT_PROTOCOL_CLOUD_SYNC) {
            Logger.w(TAG, "handleMqttMessage: unsupported protocol=${messageBean.protocol}")
            return
        }
        
        // ✅ 发送到命令管理器处理
        ioScope.launch {
            val result = commandManager.handlePushEvent(messageBean)
            
            // ✅ 根据结果处理
            handlePushEventResult(result)
        }
    }
    
    /**
     * ✅ 处理推送事件结果
     */
    private fun handlePushEventResult(result: PushEventResult) {
        when (result) {
            is PushEventResult.Success -> {
                Logger.i(TAG, "Push event handled successfully: recordId=${result.recordId}, message=${result.message}, skipped=${result.skipped}")
                
                // 通知监听器
                if (!result.skipped) {
                    notifyRecordUpdated(result.recordId)
                    updateSyncTime(result.recordId)
                }
            }
            is PushEventResult.Failed -> {
                Logger.e(TAG, "Push event failed: recordId=${result.recordId}, message=${result.message}")
                
                // 错误处理
                handlePushEventError(result)
            }
            is PushEventResult.Rejected -> {
                Logger.w(TAG, "Push event rejected: recordId=${result.recordId}, reason=${result.reason}")
                
                // 被拒绝处理
            }
        }
    }
    
    /**
     * ✅ 清理资源
     */
    fun clear() {
        commandManager.clear()
    }
}
```

---

## 📝 使用示例

### 示例1:基本使用

```kotlin
/**
 * ✅ 基本使用:处理 MQTT 推送事件
 */
class MqttPushHandler {
    private val commandManager = PushEventCommandManager()
    
    suspend fun handlePushMessage(message: MqttMessageBean) {
        // ✅ 发送到命令管理器处理
        val result = commandManager.handlePushEvent(message)
        
        // ✅ 根据结果处理
        when (result) {
            is PushEventResult.Success -> {
                if (!result.skipped) {
                    // 操作成功,通知 UI
                    notifyUI(result.recordId)
                }
            }
            is PushEventResult.Failed -> {
                // 操作失败,记录错误
                logError(result)
            }
            is PushEventResult.Rejected -> {
                // 操作被拒绝,记录原因
                logRejection(result)
            }
        }
    }
}
```

### 示例2:带回调的使用

```kotlin
/**
 * ✅ 带回调的使用:处理完推送事件后执行回调
 */
class PushEventWithCallback {
    private val commandManager = PushEventCommandManager()
    
    suspend fun handlePushMessage(
        message: MqttMessageBean,
        callback: (PushEventResult) -> Unit
    ) {
        val result = commandManager.handlePushEvent(message)
        
        // ✅ 执行回调
        callback(result)
        
        // ✅ 根据结果处理
        when (result) {
            is PushEventResult.Success -> handleSuccess(result)
            is PushEventResult.Failed -> handleFailed(result)
            is PushEventResult.Rejected -> handleRejected(result)
        }
    }
}
```

### 示例3:批量处理

```kotlin
/**
 * ✅ 批量处理:处理多个推送事件
 */
class BatchPushHandler {
    private val commandManager = PushEventCommandManager()
    
    suspend fun handleBatchMessages(
        messages: List<MqttMessageBean>
    ): List<PushEventResult> {
        // ✅ 并发处理多个消息
        return messages.map { message ->
            async {
                commandManager.handlePushEvent(message)
            }
        }.awaitAll()
    }
}
```

### 示例4:结果监听

```kotlin
/**
 * ✅ 结果监听:监听推送事件处理结果
 */
class PushEventResultListener {
    private val commandManager = PushEventCommandManager()
    
    // ✅ 结果 Flow(用于监听所有处理结果)
    private val resultFlow = MutableSharedFlow<PushEventResult>(
        replay = 0,
        extraBufferCapacity = 100
    )
    
    fun startListening() {
        lifecycleScope.launch {
            resultFlow
                .filter { it is PushEventResult.Success && !it.skipped }
                .collect { result ->
                    // ✅ 只处理成功的、未跳过的结果
                    handleSuccessfulResult(result as PushEventResult.Success)
                }
        }
    }
}
```

---

## 🎯 最佳实践

### 1. 命令队列管理

```kotlin
// ✅ 推荐:按记录ID分组,保证顺序
private val commandChannels = mutableMapOf<String, Channel<PushEventCommand>>()

// ✅ 推荐:使用 UNLIMITED 容量(或根据实际需求设置)
val channel = Channel<PushEventCommand>(Channel.UNLIMITED)

// ✅ 推荐:每个记录ID一个处理器
actorScope.launch {
    for (command in channel) {
        processCommand(command)  // 串行处理
    }
}
```

### 2. 超时处理

```kotlin
// ✅ 推荐:设置超时时间
val result = withTimeoutOrNull(30000) {  // 30秒超时
    command.responseChannel.receive()
} ?: CommandResult.Failed(
    error = TimeoutException("Command processing timeout"),
    message = "Command processing timeout"
)
```

### 3. 错误处理

```kotlin
// ✅ 推荐:统一的错误处理
try {
    val result = commandProcessor.processCommand(command)
    command.responseChannel.send(result)
} catch (e: Exception) {
    Logger.e(TAG, "Error processing command: ${e.message}")
    command.responseChannel.send(
        CommandResult.Failed(
            error = e,
            message = e.message ?: "Unknown error"
        )
    )
} finally {
    command.responseChannel.close()
}
```

### 4. 资源清理

```kotlin
// ✅ 推荐:清理时关闭所有 Channel
fun clear() {
    commandChannels.values.forEach { it.close() }
    commandChannels.clear()
    actorScope.cancel()
}
```

---

## 📐 设计原则

### 1. 单一职责原则

```kotlin
// ✅ CommandFactory:只负责创建命令
class CommandFactory {
    fun createCommand(eventData: CloudSyncEventData): PushEventCommand
}

// ✅ CommandProcessor:只负责处理命令
class CommandProcessor {
    suspend fun processCommand(command: PushEventCommand): CommandResult
}

// ✅ CommandManager:只负责命令队列管理
class PushEventCommandManager {
    suspend fun handlePushEvent(message: MqttMessageBean): PushEventResult
}
```

### 2. 开闭原则

```kotlin
// ✅ 对扩展开放:新增命令类型只需添加新的命令类
sealed class PushEventCommand {
    // 新增命令类型
    data class RestoreRecordCommand(...) : PushEventCommand()
}

// ✅ 对修改封闭:CommandProcessor 使用 when 处理,不需要修改核心逻辑
```

### 3. 依赖倒置原则

```kotlin
// ✅ 依赖抽象接口,不依赖具体实现
interface RecordSyncService {
    suspend fun getCloudRecordById(recordId: String): CloudRecord?
}

class CommandProcessor {
    private val recordSyncService: RecordSyncService  // ← 依赖接口
}
```

### 4. 接口隔离原则

```kotlin
// ✅ 每个接口职责单一
interface CommandFactory {
    fun createCommand(eventData: CloudSyncEventData): PushEventCommand
}

interface CommandProcessor {
    suspend fun processCommand(command: PushEventCommand): CommandResult
}
```

---

## ❓ 常见问题

### Q1: 如何处理大量并发推送?

**A:** 
```kotlin
// ✅ 方案1:按记录ID分组,并行处理不同记录
private val commandChannels = mutableMapOf<String, Channel<PushEventCommand>>()

// ✅ 方案2:使用 SupervisorJob,单个命令失败不影响其他命令
private val actorScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
```

### Q2: 如何处理命令处理超时?

**A:** 
```kotlin
// ✅ 使用 withTimeoutOrNull
val result = withTimeoutOrNull(30000) {
    command.responseChannel.receive()
} ?: CommandResult.Failed(
    error = TimeoutException("Timeout"),
    message = "Command processing timeout"
)
```

### Q3: 如何保证同一记录的操作顺序?

**A:** 
```kotlin
// ✅ 按记录ID分组,每个记录ID一个 Channel,串行处理
private fun getOrCreateChannel(recordId: String): Channel<PushEventCommand> {
    return commandChannels.getOrPut(recordId) {
        val channel = Channel<PushEventCommand>(Channel.UNLIMITED)
        
        // ✅ 启动串行处理器
        actorScope.launch {
            for (command in channel) {
                processCommand(command)  // 串行处理
            }
        }
        
        channel
    }
}
```

### Q4: 如何处理命令处理失败?

**A:** 
```kotlin
// ✅ 统一的错误处理机制
try {
    val result = commandProcessor.processCommand(command)
    command.responseChannel.send(result)
} catch (e: Exception) {
    // ✅ 发送错误结果
    command.responseChannel.send(
        CommandResult.Failed(
            error = e,
            message = e.message ?: "Unknown error"
        )
    )
} finally {
    command.responseChannel.close()
}
```

### Q5: 如何扩展新的命令类型?

**A:** 
```kotlin
// ✅ 1. 添加新的命令类
data class RestoreRecordCommand(...) : PushEventCommand()

// ✅ 2. 在 CommandFactory 中添加创建逻辑
when (eventData.operateType) {
    RecordOperateDef.OPERATE_TYPE_RESTORE -> {
        RestoreRecordCommand(...)
    }
}

// ✅ 3. 在 CommandProcessor 中添加处理逻辑
when (command) {
    is RestoreRecordCommand -> handleRestoreCommand(command)
}
```

---

## 🔄 与其他模式对比

### 命令响应模式 vs 观察者模式

| 特性 | 命令响应模式 | 观察者模式 |
|------|------------|-----------|
| **操作结果** | ✅ 明确的请求-响应 | ❌ 被动接收状态变化 |
| **顺序保证** | ✅ Channel 队列保证 | ❌ 无法保证顺序 |
| **操作追踪** | ✅ 每个命令有ID | ❌ 无法追踪操作 |
| **适用场景** | ✅ 需要操作结果的场景 | ✅ 只需要状态通知的场景 |

### 命令响应模式 vs Actor 模式

| 特性 | 命令响应模式 | Actor 模式 |
|------|------------|-----------|
| **实现方式** | 命令 + Channel + 处理器 | Channel + 单线程处理 |
| **结果反馈** | ✅ 明确的响应 Channel | ⚠️ 需要额外设计 |
| **命令封装** | ✅ 密封类命令 | ⚠️ 简单的数据结构 |
| **复杂度** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **推荐度** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |

---

## 📚 总结

### 核心要点

1. **命令模式**:每个推送事件封装为一个命令对象
2. **请求-响应**:每个命令有明确的处理结果
3. **Channel 队列**:保证命令按顺序处理
4. **分组处理**:按记录ID分组,保证同一记录的操作顺序
5. **错误处理**:完善的错误处理和反馈机制

### 设计优势

- ✅ **明确的请求-响应**:每个推送事件有明确的处理结果
- ✅ **顺序保证**:通过 Channel 队列保证操作顺序
- ✅ **操作追踪**:每个命令有唯一ID,可以追踪状态
- ✅ **易于扩展**:新增命令类型只需添加新的命令类
- ✅ **错误处理完善**:统一的错误处理和反馈机制

### 适用场景

- ✅ **MQ 推送事件处理**:MQTT、Kafka 等消息队列推送
- ✅ **需要操作结果**:更新/删除操作需要明确的成功/失败结果
- ✅ **需要顺序保证**:同一记录的操作需要按顺序处理
- ✅ **需要操作追踪**:需要追踪每个操作的状态和结果

### 不适用场景

- ❌ **只需要状态通知**:如果只需要通知状态变化,使用观察者模式更合适
- ❌ **不需要操作结果**:如果不需要知道操作是否成功,使用观察者模式更简单

---

## 🔄 后续探索方向

1. **命令优先级**:如何实现命令的优先级处理
2. **命令重试机制**:如何处理失败命令的重试
3. **命令持久化**:如何持久化命令,保证可靠性
4. **命令监控**:如何监控命令处理的性能指标
5. **分布式场景**:如何在分布式环境中使用命令模式

---

**文档版本**: v1.0  
**最后更新**: 2024-01-XX  
**作者**: AI Assistant

您可能感兴趣的与本文相关的镜像

Llama Factory

Llama Factory

模型微调
LLama-Factory

LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上百种预训练模型的微调

### 如何将死信队列中的消息重新推送到正常队列 在消息队列系统中,死信队列(Dead Letter Queue, DLQ)用于存储那些因某种原因未能成功处理的消息。然而,在某些场景下,可能希望将这些死信队列中的消息重新推送回正常的业务队列以便再次尝试处理。 以下是实现这一目标的具体方法: #### 方法一:手动消费并重新发布 可以通过编写一个专门的程序来定期轮询死信队列,并将其中的消息重新发送到原始队列或其他指定的目标队列。具体步骤如下: 1. **连接到 MQ 客户端** 使用支持 RabbitMQ 的客户端库(如 Pika 对于 Python),建立与 Broker 的连接。 2. **订阅死信队列** 订阅死信队列以获取未处理成功的消息[^1]。 3. **解析消息内容** 获取每一条消息的内容及其元数据(例如路由键、头部信息等)。这一步非常重要,因为它决定了如何正确地将消息重新发布到目标队列。 4. **重新发布消息** 将接收到的消息重新发布到原始队列或者另一个适合的队列中。注意保持原有的消息属性不变,比如 TTL 和优先级等参数[^2]。 ```python import pika def callback(ch, method, properties, body): # 假设我们有一个函数 publish_message 来负责重新发布消息 publish_message(body, exchange=&#39;main_exchange&#39;, routing_key=method.routing_key) def publish_message(message_body, exchange, routing_key): connection = pika.BlockingConnection(pika.ConnectionParameters(&#39;localhost&#39;)) channel = connection.channel() channel.basic_publish(exchange=exchange, routing_key=routing_key, body=message_body) print(f"Re-published message to {exchange} with key {routing_key}") connection.close() connection = pika.BlockingConnection(pika.ConnectionParameters(&#39;localhost&#39;)) channel = connection.channel() # 绑定死信队列 channel.queue_declare(queue=&#39;dlq&#39;) channel.basic_consume(queue=&#39;dlq&#39;, on_message_callback=callback, auto_ack=True) print(&#39;Waiting for messages from dead letter queue...&#39;) channel.start_consuming() ``` #### 方法二:利用重试机制自动返回 另一种更优雅的方式是借助 RabbitMQ 自带的功能——即通过配置 `x-message-ttl` 和 `x-dead-letter-routing-key` 属性让失败后的消息经过几次尝试后再回到原队列或特定的重试队列中去等待进一步处理[^3]。 - 配置主队列时增加额外参数表明当达到一定条件后应把该条目转移到哪里继续流转; - 设置合理的超时时间以及最大重试次数限制防止无限循环的发生; 这种方法无需额外开发单独的应用逻辑即可完成基本功能需求,但灵活性相对较低一些。 --- ### 注意事项 无论采用哪种方式都需要考虑以下几点因素: - 数据一致性问题:确保在整个过程中不会丢失任何重要信息。 - 性能影响评估:频繁操作可能会带来资源消耗上升的风险。 - 错误监控报警体系构建:及时发现潜在隐患从而快速响应解决。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值