Files
pig-farm-controller/design/ota-upgrade-and-log-monitoring/lora_refactoring_plan.md
2025-12-01 14:32:50 +08:00

9.3 KiB
Raw Blame History

LoRa 通信层统一重构方案

1. 目标

统一项目当前并存的两种 LoRa 通信模式(基于 ChirpStack API 和基于串口透传),使其在架构层面遵循相同的接口和设计模式。最终实现:

  • 业务逻辑统一:所有上行业务处理逻辑集中在一个地方,与具体的通信方式无关。
  • 发送接口统一:上层服务使用同一个接口发送下行指令,无需关心底层实现。
  • 架构清晰:明确划分基础设施层(负责传输)和应用层(负责业务)的职责,并确保正确的依赖方向 (app -> infra)。
  • 高扩展性:未来支持新的通信方式时,只需添加新的“适配器”,而无需改动核心业务代码。

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.golora_mesh_uart_passthrough_transport.go 中都存在,造成代码重复和维护困难。
  • 职责不清lora_mesh_uart_passthrough_transport.go 同时承担了基础设施(串口读写)和应用(处理业务)两种职责。
  • 依赖关系混乱:为了让 infra 层的串口模块能调用业务逻辑,可能会导致 infra 层反向依赖 app 层,破坏了项目的核心架构原则。

3. 统一架构设计方案

3.1. 核心思想

采用 端口与适配器模式 (Ports and Adapters Pattern),严格遵守 依赖倒置原则

  • 端口 (Port):在 infra 层定义一个 UpstreamHandler 接口。这个接口是 infra 层向上层暴露的“端口”,它规定了上行业务处理器必须满足的协约。
  • 适配器 (Adapter):在 app 层创建一个 LoRaListener 作为“适配器”,它实现 infra 层定义的 UpstreamHandler 接口,并封装所有核心业务处理逻辑。
  • 依赖注入:在系统启动时,将 app 层的 LoRaListener 实例注入到需要它的 infra 层组件中。

3.2. 统一接口定义

3.2.1. 发送接口 (已存在,无需修改)

// file: internal/infra/transport/transport.go
package transport

type Communicator interface {
    Send(ctx context.Context, address string, payload []byte) (*SendResult, error)
}

3.2.2. 接收处理接口 (端口定义)

此接口定义了 infra 层对上行业务处理器的期望,是 infra 层向上层暴露的“端口”。

// file: internal/infra/transport/transport.go
package transport

import (
    "context"
    "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/proto"
)

// UpstreamHandler 定义了处理所有来源的上行数据的统一协约。
// 任何实现了上行消息监听的基础设施如串口、MQTT客户端都应该在收到消息后调用此接口的实现者。
// 这样,基础设施层只负责“接收和解析”,而将“业务处理”的控制权交给了上层。
type UpstreamHandler interface {
    // HandleInstruction 处理来自设备的、已解析为Instruction的业务指令。
    HandleInstruction(ctx context.Context, sourceAddr string, instruction *proto.Instruction) error

    // HandleStatus 处理非业务指令的设备状态更新,例如信号强度、电量等。
    HandleStatus(ctx context.Context, sourceAddr string, status map[string]interface{}) error
}

3.3. 组件职责划分 (重构后)

3.3.1. 统一业务处理器 (应用层适配器)

  • 文件: internal/app/listener/lora_listener.go (新)
  • 职责:
    • 实现 transport.UpstreamHandler 接口。
    • 包含所有处理业务所需的依赖(如领域服务、仓储等)。
    • 实现 HandleInstruction 方法,通过 switch-case 编排所有核心业务。
    • 实现 HandleStatus 方法,处理设备状态更新。
    • 这是项目中唯一处理 LoRa 上行业务的地方。

3.3.2. 基础设施层 (Infra Layer)

  • 文件 1: internal/app/listener/chirp_stack/chirp_stack.go (重构)

    • 职责: 纯粹的 Webhook 适配器。
    • 移除所有业务逻辑和数据库依赖。
    • 依赖 transport.UpstreamHandler 接口。
    • 功能:接收 Webhook -> 解析 JSON -> 调用 handler.HandleInstructionhandler.HandleStatus
  • 文件 2: internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go (重构)

    • 职责: 纯粹的串口传输工具。
    • 移除所有业务逻辑和数据库依赖。
    • 依赖 transport.UpstreamHandler 接口。
    • 功能:管理串口 -> 读字节流 -> 重组分片 -> 解析 proto.Instruction -> 调用 handler.HandleInstruction

3.4. 架构图 (重构后)

+--------------------------------+
|      Upper-Level Services      |
| (e.g., DeviceService)          |
+--------------------------------+
             |
             v (uses)
+--------------------------------+
|   transport.Communicator (I)   |  <-- Infra Layer (Send Port)
+--------------------------------+
      ^           ^
      |           | (implements)
+------------------+------------------+
| ChirpStackSender |   UartSender     |  <-- Infra Layer (Senders)
+------------------+------------------+


+--------------------------------+
|      listener.LoRaListener     |  <-- App Layer (Adapter)
| (Implements UpstreamHandler)   |
+--------------------------------+
             ^
             | (dependency, via interface)
+--------------------------------+
|  transport.UpstreamHandler (I) |  <-- Infra Layer (Receive Port)
+--------------------------------+
      ^           ^
      |           | (calls)
+------------------+------------------+
| ChirpStackWebhook| UartPassthrough  |  <-- Infra Layer (Receivers)
+------------------+------------------+
      ^           ^
      |           | (receives from)
+------------------+------------------+
|   HTTP Webhook   |   Serial Port    |
+------------------+------------------+

3.5. 依赖注入与组装示例

// file: internal/core/component_initializers.go

// 1. 创建统一的业务处理器 (App层适配器)
// 它实现了 infra 层的 transport.UpstreamHandler 接口
loraListener := listener.NewLoRaListener(logger, dbRepo1, dbRepo2)

// 2. 初始化 ChirpStack 模式
// 2a. 创建 ChirpStack 的发送器 (infra)
chirpStackCommunicator := chirp_stack.NewChirpStackTransport(...)
// 2b. 创建 ChirpStack 的监听器 (infra),并注入 App 层的业务处理器
chirpStackListener := chirp_stack.NewChirpStackListener(loraListener)
// 2c. 注册 Webhook 路由
api.RegisterWebhook("/chirpstack", chirpStackListener.Handler())

// 3. 初始化串口透传模式
// 3a. 创建串口的传输工具 (infra),并注入 App 层的业务处理器
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/infra/transport/transport.go 中定义 UpstreamHandler 接口。
  2. 创建适配器: 创建 internal/app/listener/lora_listener.go,定义 LoRaListener 结构体,并实现 transport.UpstreamHandler 接口。
  3. 迁移业务逻辑: 将 chirp_stack.golora_mesh_uart_passthrough_transport.go 中的业务逻辑(查库、存数据等)逐步迁移到 lora_listener.go 的对应方法中。
  4. 重构基础设施:
    • 清理 chirp_stack.go,移除 Repo 依赖,改为依赖 transport.UpstreamHandler 接口,并调用其方法。
    • 清理 lora_mesh_uart_passthrough_transport.go,做同样的操作。
  5. 更新依赖注入: 修改 component_initializers.go,按照 3.5 中的示例完成组件的创建和注入。
  6. 测试与验证: 对两种模式分别进行完整的上下行通信测试。

5. 收益

  • 消除代码重复:业务逻辑仅存在于一处。
  • 职责清晰:基础设施层只管传输,应用层只管业务。
  • 正确的依赖关系:确保了 app -> infra 的单向依赖,核心架构更加稳固。
  • 可维护性:修改业务逻辑只需改一个文件,修改传输细节不影响业务。
  • 可测试性:可以轻松地对 LoRaListener 进行单元测试,无需真实的硬件或网络。