n5321 | 2025年12月15日 00:18

Tags: AutoEM


这是一个非常典型的桌面应用程序架构演进问题。目前的架构中,数据在 数据库 (SQLite)UI 控件状态DataService (内存) 三者之间流转,确实存在“多头管理”和“同步不及时”的风险。

以下我将深入分析你当前的 数据流现状,并给出基于 DataService 作为唯一可信源 (Single Source of Truth) 的重构建议。


一、 当前数据流现状分析 (Current Data Flow)

目前的流向是混合的,主要痛点在于:数据是谁的?

1. 加载流程 (Loading)

  • 路径: Database -> ProductController -> CustomerRequirementsPage (UI) -> FieldWidget

  • 问题: 数据直接填入了 UI 控件。虽然 BLDCPage 里也顺手更新了一下 DataService,但主流程是依赖 UI 控件存储数据的。

2. 修改流程 (Editing)

  • 路径 A (Design Target Page): 用户打字 -> on_text_changed -> 直接调用 DataService.update...。

  • 路径 B (Input Panel): 用户打字 -> _on_text_changed -> 直接调用 DataService.update_maxwell_parameters。

  • 问题: UI 直接操作 Service,耦合度高。而且如果代码里有 setText 操作,会触发信号,导致不必要的递归更新。

3. 仿真启动流程 (Simulation Start)

  • 路径: InputPanel 点击开始 -> 从 DataService.customer_requirements 和 DataService.maxwell_parameters 拼凑参数 -> 发送给 Controller -> Worker。

  • 问题: maxwell_parameters 和 customer_requirements 之间界限模糊。有些参数(如额定电压)既是客户需求,又是 Maxwell 输入,目前是简单粗暴的 update() 覆盖,容易弄丢数据来源。

4. 结果回填流程 (Result Backfill)

  • 路径: Worker -> MaxwellAutomation -> DataService.update_result_detail (写入) -> Controller -> View.on_simulation_success -> View 读取 DataService 或直接使用传参。

  • 问题: 这是一个比较好的部分,已经通过 Service 中转了,但 Worker 内部隐式实例化 DataService 有点依赖全局单例的副作用。


二、 改进建议:DataService 中枢化重构

目标:UI 只是数据的展示层,Database 是持久化层,DataService 是运行时的唯一核心。

1. 重新定义变量含义

首先理清 DataService 中那几个字典的具体职责:

变量名当前定义建议改进定义
product_info产品基础信息(ID, Model)身份信息:当前选中产品的 ID、型号、系列ID。
customer_requirements客户需求(电压、扭矩等)设计目标(Target):只读或通过"设计目标页"修改。代表“我想要达到的指标”。
maxwell_parameters发送给 Maxwell 的参数仿真输入(Input):实际发给 Maxwell 的几何/物理参数。默认从 Target 继承,但允许在 InputPanel 覆盖修改。
maxwell_para_ranges优化范围优化配置:AI 算法的上下限配置。
result_detail仿真结果仿真输出(Output):Maxwell 算完回填的标量数据。

2. 架构重构:引入信号驱动机制

修改 DataService,使其继承自 QObject,并在数据变更时发送信号。

修改后的 services/data_service.py 结构建议:

codePython

from PyQt6.QtCore import QObject, pyqtSignal, QMutex, QMutexLocker

class DataService(QObject):
  _instance = None
  _create_mutex = QMutex()

  # === 定义信号,通知 UI 刷新 ===
  # 这样 UI 不需要知道是谁改了数据,只要数据变了,UI 就自动刷新
  data_changed = pyqtSignal(str, dict) # (category, new_data)

  def __init__(self):
      super().__init__()
      if not hasattr(self, '_initialized'):
          self._data_mutex = QMutex()
           
          # 数据容器
          self._data = {
              "product_info": {},
              "project_info": {},
              "customer_requirements": {}, # 对应设计目标
              "maxwell_parameters": {},     # 对应仿真输入
              "process_limits": {},
              "maxwell_para_ranges": {},
              "result_detail": {}
          }
          self._initialized = True

  # === 通用更新方法 ===
  def _update_category(self, category: str, data: dict):
      with QMutexLocker(self._data_mutex):
          self._data[category].update(data)
          # 拿到更新后的完整副本
          full_data = self._data[category].copy()
       
      # 发射信号 (在锁外部,防止死锁)
      self.data_changed.emit(category, full_data)

  # === 具体业务方法 ===
  def set_active_product(self, product_dict: dict):
      """加载新产品时调用,清空旧状态,填入新状态"""
      with QMutexLocker(self._data_mutex):
          self._data["product_info"] = product_dict
          self._data["customer_requirements"] = product_dict # 初始时,需求即参数
          # 关键:仿真参数初始值 = 客户需求
          self._data["maxwell_parameters"] = product_dict.copy()
          self._data["result_detail"] = {} # 清空旧结果
       
      # 通知所有监听者:产品变了,请刷新
      self.data_changed.emit("all", self._data)

  def update_maxwell_parameters(self, params: dict):
      self._update_category("maxwell_parameters", params)

  def update_result_detail(self, results: dict):
      self._update_category("result_detail", results)

  # ... get 方法保持不变 (返回 copy)

3. 修改 UI 逻辑:变为被动视图 (Passive View)

UI 不再直接持有数据,而是作为 DataService 的观察者。

在 InputPanel (示例) 中的改动:

codePython

class InputPanel(QWidget):
  def __init__(self, data_service, parent=None):
      super().__init__(parent)
      self.data_service = data_service
      self._init_ui()
       
      # 1. 连接信号:只有 Service 说变了,我才变
      self.data_service.data_changed.connect(self.on_data_changed)

  def on_data_changed(self, category, data):
      """响应数据变化"""
      if category == "maxwell_parameters" or category == "all":
          self.block_signals(True) # 防止死循环:UI改->Service改->UI改
          self.fill_input_fields(data)
          self.block_signals(False)
       
      if category == "result_detail" or category == "all":
          self.update_result_labels(data)

  def _on_text_changed(self, key, text):
      """用户手动修改时,只负责提交给 Service"""
      # ... 解析 val ...
      # 只要提交,不要在这里直接 setText,让 on_data_changed 回调来做
      self.data_service.update_maxwell_parameters({key: val})

  def block_signals(self, block: bool):
      # 辅助函数,批量阻塞所有输入框信号
      for widgets in self.fields_map.values():
          if 'value' in widgets: widgets['value'].blockSignals(block)

三、 梳理后的数据流向图

采用上述重构后,数据流将变得非常清晰:

1. 选择产品 (Select Product)

  1. Controller: 从 DB 读取 Product 对象。

  2. Controller: 调用 data_service.set_active_product(product_dict)。

  3. DataService: 更新内部状态,发射 data_changed("all", ...) 信号。

  4. UI (TargetPage, InputPanel, DetailPage): 收到信号 -> 自动刷新界面显示。

2. 用户修改参数 (User Edit)

  1. UI (InputPanel): 用户修改“线径”。

  2. UI: 触发 textChanged -> 调用 data_service.update_maxwell_parameters({'wire_size': 0.5})。

  3. DataService: 更新内存字典,发射 data_changed("maxwell_parameters", ...)。

  4. UI: 收到信号 (如果是其他联动UI则更新,如果是自己则被 blockSignals 忽略)。

3. 执行仿真 (Run Simulation)

  1. UI: 点击“开始”。

  2. Controller: 调用 data_service.get_maxwell_parameters() 获取最新、最全的参数。

  3. Controller: 将参数包传给 Worker。

  4. Worker: 执行 Maxwell 脚本。

4. 仿真结束 (Simulation Finish)

  1. Worker: 解析出结果字典。

  2. Worker: 调用 data_service.update_result_detail(result_dict)。

  3. DataService: 发射 data_changed("result_detail", ...)。

  4. UI (ResultPanel, ChartPanel): 收到信号 -> 自动刷新结果显示 (对比值、图表)。

  5. Controller: (可选) 调用 DB Service 将 DataService 中的 maxwell_parameters (输入) 和 result_detail (输出) 存入 SimulationResult 表,形成历史记录。

四、 具体的代码修改 Checklist

  1. services/data_service.py:

    • 继承 QObject。

    • 添加 pyqtSignal。

    • 在 update 方法中发射信号。

    • 添加 set_active_product 方法来统一初始化一次仿真的上下文。

  2. ui/pages/bldc_page/widgets/input_panel.py:

    • 删除 fill_input_fields 的直接调用逻辑(由信号触发)。

    • 添加 on_data_changed 槽函数。

    • 在 _on_text_changed 中确保只是“提交数据”,而不是“修改界面”。

  3. controllers/product_controller.py:

    • on_product_selected 中,除了更新 UI,务必调用 self.service.data_service.set_active_product(...)。目前你好像把 data service 混在了 ProductService 或者直接在 View 里调用,建议统一在 Controller 里分发。

  4. utils/property_change.py:

    • 确保它只负责翻译数据,不持有数据。它现在的逻辑是 fields_map (UI控件引用),这不好。

    • 改进: 让它接收 dict (来自 DataService) 而不是 fields_map (UI控件)。这样后台 Worker 可以直接用它,不需要依赖 UI 线程。

通过这种方式,你的应用将遵循 MVVM (Model-View-ViewModel) 的变体模式,DataService 充当 ViewModel,负责持有状态和通知 View 更新,彻底解决数据流混乱的问题。