diff --git a/design/ota-upgrade-and-log-monitoring/lora_refactoring_plan.md b/design/ota-upgrade-and-log-monitoring/lora_refactoring_plan.md new file mode 100644 index 0000000..d1ef16f --- /dev/null +++ b/design/ota-upgrade-and-log-monitoring/lora_refactoring_plan.md @@ -0,0 +1,199 @@ +# LoRa 通信层统一重构方案 + +## 1. 目标 + +统一项目当前并存的两种 LoRa 通信模式(基于 ChirpStack API 和基于串口透传),使其在架构层面遵循相同的接口和设计模式。最终实现: + +- **业务逻辑统一**:所有上行业务处理逻辑集中在一个地方,与具体的通信方式无关。 +- **发送接口统一**:上层服务(如 `DeviceService`)使用同一个接口发送下行指令,无需关心底层实现。 +- **架构清晰**:明确划分基础设施层(负责传输)和应用层(负责业务)的职责。 +- **高扩展性**:未来支持新的通信方式时,只需添加新的“适配器”,而无需改动核心业务代码。 + +## 2. 背景与问题分析 + +### 2.1. 当前存在两种 LoRa 通信模式 + +1. **ChirpStack 模式**: 通过 `internal/infra/transport/lora/chirp_stack.go` 实现发送,通过 `internal/app/listener/chirp_stack/chirp_stack.go` 监听并处理 ChirpStack Webhook 推送的数据。 +2. **串口透传模式**: 通过 `internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` 实现发送和接收处理。 + +### 2.2. 核心差异 + +| 特性 | ChirpStack 模式 | 串口透传模式 | +| :--- | :--- | :--- | +| **通信模型** | 双向、有状态、异步API调用 | 单向、无状态、直接串口读写 | +| **接收机制** | Webhook (HTTP POST) 推送 | 主动从串口读取字节流 | +| **数据格式** | JSON 包装 + Base64 编码 | 自定义二进制物理帧 | +| **寻址方式**| `DevEui` | 自定义 16 位网络地址 | +| **核心职责** | LNS,管理会话、ACK、队列 | 纯粹的“无线串口” | + +### 2.3. 问题 + +- **业务逻辑分散**:处理 `CollectResult` 的业务逻辑在 `chirp_stack.go` 和 `lora_mesh_uart_passthrough_transport.go` 中都存在,造成代码重复和维护困难。 +- **职责不清**:`lora_mesh_uart_passthrough_transport.go` 同时承担了基础设施(串口读写、分片重组)和应用(处理采集结果、写数据库)两种职责,违反了分层架构原则。 +- **抽象缺失**:两种模式没有统一的接口,导致上层代码如果需要切换模式,将产生大量修改。 + +## 3. 统一架构设计方案 + +### 3.1. 核心思想 + +采用 **适配器模式 (Adapter Pattern)** 和 **依赖倒置原则 (Dependency Inversion Principle)**。 + +- 将所有上行业务逻辑抽离到**统一的业务处理器**中。 +- 将 `chirp_stack.go` 和 `lora_mesh_uart_passthrough_transport.go` 的接收部分重构为**适配器**。它们的唯一职责是将各自协议的数据,适配并转发给统一的业务处理器。 +- 上层依赖抽象接口,而不是具体实现。 + +### 3.2. 统一接口定义 + +#### 3.2.1. 发送接口 (已存在,无需修改) + +此接口设计良好,上层业务通过它下发指令,无需关心底层实现。 + +```go +// file: internal/infra/transport/transport.go +package transport + +type Communicator interface { + Send(ctx context.Context, address string, payload []byte) (*SendResult, error) +} +``` + +#### 3.2.2. 接收处理接口 (新定义) + +此接口是应用层对所有上行数据处理能力的抽象。 + +```go +// file: internal/app/listener/handler.go +package listener + +import ( + "context" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/proto" +) + +// UpstreamHandler 定义了处理所有来源的上行数据的统一协约。 +type UpstreamHandler interface { + // HandleInstruction 处理来自设备的、已解析为Instruction的业务指令。 + HandleInstruction(ctx context.Context, sourceAddr string, instruction *proto.Instruction) + + // HandleStatus 处理非业务指令的设备状态更新,例如信号强度、电量等。 + HandleStatus(ctx context.Context, sourceAddr string, status map[string]interface{}) +} +``` + +### 3.3. 组件职责划分 (重构后) + +#### 3.3.1. 统一业务处理器 (应用层核心) + +- **文件**: `internal/app/listener/lora_listener.go` (新) +- **职责**: + - 实现 `listener.UpstreamHandler` 接口。 + - 包含所有处理业务所需的数据库仓库依赖。 + - 实现 `HandleInstruction` 方法,通过 `switch-case` 处理 `CollectResult`, `OtaUpgradeStatus`, `LogUploadRequest` 等所有核心业务。 + - 实现 `HandleStatus` 方法,处理 ChirpStack 上报的电量、信号等旁路信息。 + - **这是项目中唯一处理 LoRa 上行业务的地方。** + +#### 3.3.2. 适配器层 (连接 Infra 与 App) + +- **文件 1**: `internal/app/listener/chirp_stack/chirp_stack.go` (重构) + - **职责**: 纯粹的 Webhook 适配器。 + - 移除所有业务逻辑和数据库依赖。 + - 依赖 `listener.UpstreamHandler` 接口。 + - 功能:接收 Webhook -> 解析 JSON -> 调用 `handler.HandleInstruction` 或 `handler.HandleStatus`。 + +- **文件 2**: `internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` (重构) + - **职责**: 纯粹的串口适配器。 + - 移除所有业务逻辑和数据库依赖。 + - 依赖 `listener.UpstreamHandler` 接口。 + - 功能:管理串口 -> 读字节流 -> 重组分片 -> 解析 `proto.Instruction` -> 调用 `handler.HandleInstruction`。 + +#### 3.3.3. 发送器 (基础设施层) + +- **文件 1**: `internal/infra/transport/lora/chirp_stack.go` (或拆分出的新文件) + - 实现 `transport.Communicator` 接口,通过调用 ChirpStack API 完成发送。 +- **文件 2**: `internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` + - 实现 `transport.Communicator` 接口,通过向串口写入数据完成发送。 + +### 3.4. 架构图 (重构后) + +``` ++--------------------------------+ +| Upper-Level Services | +| (e.g., DeviceService) | ++--------------------------------+ + | + v (uses) ++--------------------------------+ +| transport.Communicator (I) | ++--------------------------------+ + ^ ^ + | | (implements) ++------------------+------------------+ +| ChirpStackTransport | UartPassthroughTransport | (Infra Layer - Senders) ++------------------+------------------+ + + ++--------------------------------+ +| listener.LoRaListener | +| (Unified Business Logic) | ++--------------------------------+ + ^ ^ + | | (calls) ++------------------+------------------+ +| ChirpStackAdapter| UartPassthroughAdapter | (Adapter Layer) ++------------------+------------------+ + ^ ^ + | | (receives from) ++------------------+------------------+ +| HTTP Webhook | Serial Port | (Physical/Network Layer) ++------------------+------------------+ +``` + +### 3.5. 依赖注入与组装示例 + +```go +// file: internal/core/component_initializers.go + +// 1. 创建统一的业务处理器 (单例) +loraListener := listener.NewLoRaListener(dbRepo1, dbRepo2, ...) + +// 2. 初始化 ChirpStack 模式 +// 2a. 创建 ChirpStack 的发送器 (infra) +chirpStackCommunicator := chirp_stack.NewChirpStackTransport(...) +// 2b. 创建 ChirpStack 的监听适配器 (app),并注入统一的业务处理器 +chirpStackListenerAdapter := chirp_stack.NewChirpStackListener(loraListener) +// 2c. 注册 Webhook 路由 +api.RegisterWebhook("/chirpstack", chirpStackListenerAdapter.Handler()) + +// 3. 初始化串口透传模式 +// 3a. 创建串口的发送器/监听器 (infra),并注入统一的业务处理器 +uartTransport := lora.NewLoRaMeshUartPassthroughTransport(port, loraListener) +// 3b. 启动串口监听 +uartTransport.Listen() + +// 4. 向上层业务提供统一的发送器 +var finalCommunicator transport.Communicator +if config.UseChirpStack { + finalCommunicator = chirpStackCommunicator +} else { + finalCommunicator = uartTransport +} +// 将 finalCommunicator 注入到需要发送指令的服务中... +``` + +## 4. 实施步骤 + +1. **定义接口**: 在 `internal/app/listener/` 下创建 `handler.go` 并定义 `UpstreamHandler` 接口。 +2. **创建统一处理器**: 创建 `internal/app/listener/lora_listener.go`,定义 `LoRaListener` 结构体,并实现 `UpstreamHandler` 接口的空方法。 +3. **迁移业务逻辑**: 将 `chirp_stack.go` 和 `lora_mesh_uart_passthrough_transport.go` 中的业务逻辑(查库、存数据等)逐步迁移到 `lora_listener.go` 的对应方法中。 +4. **重构适配器**: + - 清理 `chirp_stack.go`,移除 Repo 依赖,改为依赖 `UpstreamHandler` 接口,并调用其方法。 + - 清理 `lora_mesh_uart_passthrough_transport.go`,做同样的操作。 +5. **更新依赖注入**: 修改 `component_initializers.go`,按照 `3.5` 中的示例完成组件的创建和注入。 +6. **测试与验证**: 对两种模式分别进行完整的上下行通信测试。 + +## 5. 收益 + +- **消除代码重复**:业务逻辑仅存在于一处。 +- **职责清晰**:基础设施层只管传输,应用层只管业务。 +- **可维护性**:修改业务逻辑只需改一个文件,修改传输细节不影响业务。 +- **可测试性**:可以轻松地对 `LoRaListener` 进行单元测试,无需真实的硬件或网络。 diff --git a/project_structure.txt b/project_structure.txt index 634d32f..8eb8a4a 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -38,6 +38,7 @@ design/archive/2025-11-06-health-check-routing/index.md design/archive/2025-11-06-system-plan-continuously-triggered/index.md design/archive/2025-11-10-exceeding-threshold-alarm/index.md design/archive/2025-11-29-recipe-management/index.md +design/ota-upgrade-and-log-monitoring/index.md docs/docs.go docs/swagger.json docs/swagger.yaml @@ -84,6 +85,10 @@ internal/app/dto/pig_farm_dto.go internal/app/dto/plan_converter.go internal/app/dto/plan_dto.go internal/app/dto/user_dto.go +internal/app/listener/chirp_stack/chirp_stack.go +internal/app/listener/chirp_stack/chirp_stack_types.go +internal/app/listener/chirp_stack/placeholder_listener.go +internal/app/listener/transport.go internal/app/middleware/audit.go internal/app/middleware/auth.go internal/app/service/audit_service.go @@ -102,10 +107,6 @@ internal/app/service/raw_material_service.go internal/app/service/recipe_service.go internal/app/service/threshold_alarm_service.go internal/app/service/user_service.go -internal/app/webhook/chirp_stack.go -internal/app/webhook/chirp_stack_types.go -internal/app/webhook/placeholder_listener.go -internal/app/webhook/transport.go internal/core/application.go internal/core/component_initializers.go internal/core/data_initializer.go