12 KiB
双专用版与共享核心库架构设计
日期:2026-04-14
1. 目标
在当前 plc_control 仓库基础上,重构为“共享平台核心库 + 两个专用上层应用”的结构,满足以下目标:
- 产出两个独立可执行程序:
- 投煤器布料机专用版
- 运转系统专用版
- 两个程序各自带 Web 界面、独立路由、独立业务逻辑
- 两个程序复用同一套 Rust 平台能力,避免重复维护 OPC UA、数据库、事件、设备点位等基础能力
- 后续保留扩展空间,允许在同一套代码上再组合出“统一版单程序”产物
本次设计优先保证边界清晰、后续可维护,不追求第一阶段的抽象最少代码量。
2. 设计结论
本项目采用以下总体结构:
plc_platform_core:共享平台核心库app_feeder_distributor:投煤器布料机专用版app_operation_system:运转系统专用版
这是推荐的第一阶段目标结构:
plc_control/
Cargo.toml
Cargo.lock
crates/
plc_platform_core/
app_feeder_distributor/
app_operation_system/
未来如需支持“统一单程序版本”,再新增:
crates/
app_unified/
当前不引入“配置切换两套业务模式”的做法,也不把两套业务状态机强行抽成一套通用业务引擎。
3. 方案选择与取舍
本次在三个方案中选择方案 B。
方案 A:单仓库单 crate,多 bin
做法:
- 保留当前工程主结构
- 使用
src/bin/*.rs增加多个入口
优点:
- 改动最小
- 最快得到两个可执行程序
缺点:
- 共享边界不清晰
main.rs装配逻辑会持续膨胀- 两套业务会继续在同一套模块里相互污染
方案 B:workspace + 共享核心库 + 两个业务应用
做法:
- 根目录改为 Cargo workspace
- 下沉平台能力到共享库
- 两套业务各自保留自己的入口、路由、页面和控制逻辑
优点:
- 平台层和业务层职责清晰
- 两套专用系统可以独立发版
- 后续新增第三套业务时仍可沿用同一模式
缺点:
- 第一次重构成本高于方案 A
方案 C:workspace + 平台库 + 业务插件化
做法:
- 在共享库上继续抽象统一业务接口或配置驱动模式
优点:
- 理论抽象度最高
缺点:
- 当前阶段过早抽象
- 容易把两套差异明显的业务规则硬揉在一起
选择结论
采用方案 B。
原因:
- 当前代码已经具备平台层与业务层雏形
- 投煤器布料机与运转系统预计将长期演化为两套不同的业务规则与页面表达
- 平台能力稳定、业务能力高变化,适合分层
4. 编译产物与发布模型
本设计明确支持以下发布模式:
4.1 两个独立应用
发布两个独立 exe:
app_feeder_distributor.exeapp_operation_system.exe
适用场景:
- 两套系统部署隔离
- 发版节奏不同
- 功能和界面差异持续扩大
4.2 单个统一应用
在不改变共享库设计的前提下,未来可新增 app_unified,将两套业务模块装配到同一个程序中。
适用场景:
- 现场更希望只部署一个软件
- 需要统一登录、统一菜单、统一入口
结论:
- 当前阶段以“双独立应用”为主目标
- 架构上保留“可组合出单一应用”的扩展空间
5. 模块边界
5.1 共享平台核心库边界
plc_platform_core 负责平台层能力,不直接绑定投煤器布料机或运转系统的业务语义。
建议纳入共享库的模块:
configdbmodelconnectiontelemetryevent的平台公共能力websocketserviceutilcontrol/commandcontrol/runtimecontrol/validator中的通用校验能力
共享库主要职责:
- 配置加载
- 数据库初始化与访问
- OPC UA 接入与重连
- 点位读写与遥测订阅
- 通用事件发布、持久化、广播
- WebSocket 基础设施
- 通用运行时状态存储
- 通用控制命令封装
5.2 业务应用边界
两个 app crate 各自承载自己的业务能力。
app_feeder_distributor 负责:
- 投煤器/布料机控制状态机
- 投煤器/布料机业务接口
- 业务页面与交互
- 业务事件定义
- 业务运行时状态
app_operation_system 负责:
- 运转系统控制逻辑
- 运转系统业务接口
- 运转系统业务页面
- 业务事件定义
- 业务运行时状态
5.3 不建议先共用的模块
以下模块当前阶段不建议直接抽为统一业务层:
control/engine.rshandler/control.rs- 业务 Web 页面目录
原因:
- 这些位置最容易掺杂业务状态机、联锁规则和页面表达逻辑
- 强行共用会导致大量条件分支
6. 事件体系设计
6.1 总体原则
采用“代码层分层,存储层统一”的方案:
- 共享库定义平台级事件
- 各业务应用定义自己的业务事件
- 所有事件最终落同一张
event表 event_type使用命名空间字符串
6.2 平台事件
平台事件放在 plc_platform_core::event,只承载两个系统都需要的公共语义,例如:
- 数据源连接/断开
- 通用控制命令已发送
- 通用设备基础信息变更
- 通用订阅或连接异常
6.3 业务事件
业务事件分别定义在各自 app 内。
投煤器布料机专用版示例:
feeder.auto_control_startedfeeder.auto_control_stoppedfeeder.fault_lockedfeeder.fault_ackedfeeder.comm_lockedfeeder.unit_state_changedfeeder.rem_localfeeder.rem_recovered
运转系统专用版示例:
ops.unit_startedops.unit_stoppedops.phase_changedops.interlock_triggeredops.interlock_released
6.4 存储设计
事件统一使用现有 event 表存储。
要求:
event_type必须使用命名空间,例如platform.*、feeder.*、ops.*payload继续存结构化 JSON- 事件查询接口支持按命名空间或事件类型前缀过滤
本阶段不拆分为多张事件表。
原因:
- 复用现有表结构和查询能力
- 查询统一时间线更方便
- 降低改造成本
7. 应用上下文设计
7.1 共享平台上下文
共享库定义平台上下文,承载所有公共资源:
pub struct PlatformContext {
pub config: AppConfig,
pub pool: sqlx::PgPool,
pub connection_manager: Arc<ConnectionManager>,
pub event_manager: Arc<EventManager>,
pub ws_manager: Arc<WebSocketManager>,
}
职责:
- 提供公共资源句柄
- 提供平台初始化入口
- 支撑通用 service、事件、WebSocket 能力
7.2 业务应用状态
每个 app 在平台上下文之上增加自己的运行态:
pub struct FeederAppState {
pub platform: Arc<PlatformContext>,
pub runtime: Arc<FeederRuntimeStore>,
}
pub struct OperationAppState {
pub platform: Arc<PlatformContext>,
pub runtime: Arc<OperationRuntimeStore>,
}
设计原则:
- 平台上下文不持有业务特有运行态
- 业务运行态不回流污染共享库
- 每个业务程序保持自己的状态边界
8. 路由与前端拆分
8.1 平台通用 API
以下接口倾向保留为平台公共能力:
sourcepointequipmenttagpagelogs- 文档接口
- 通用事件查询接口
这些能力更偏基础平台,不依赖单一业务系统。
8.2 业务专用 API
投煤器布料机专用版保留自己的业务接口,例如:
- 自动控制启停
- 故障确认
- 运行时状态查询
- 业务详情页接口
运转系统专用版保留自己的业务接口,例如:
- 运转单元控制
- 联锁状态
- 运行阶段与流程查询
8.3 统一版扩展策略
如果未来新增 app_unified,建议在统一程序内按前缀挂业务路由,例如:
/api/feeder/.../api/ops/...
8.4 Web 资源拆分
不继续维持一个共享 web/ 目录承载两套业务页面。
建议:
app_feeder_distributor/webapp_operation_system/web
未来若有统一版,再单独提供:
app_unified/web
理由:
- 页面层是业务表达层,最容易分叉
- 强行共用会导致样式、导航、面板和脚本互相污染
9. 现有文件迁移建议
9.1 优先迁入共享库
建议优先迁入 plc_platform_core 的现有文件:
src/config.rssrc/db.rssrc/model.rssrc/connection.rssrc/telemetry.rssrc/event.rs中的平台公共部分src/websocket.rssrc/service.rssrc/service/src/util.rssrc/util/src/control/command.rssrc/control/runtime.rs
9.2 迁入共享库前需拆分
-
src/control/validator.rs- 保留通用前置校验到共享库
- 业务特殊校验留在各业务应用层
-
src/handler/- 平台通用 handler 可保留为共享路由组件
- 业务控制 handler 留在各自 app
9.3 保留在业务应用
优先保留在业务应用层的模块:
src/main.rssrc/control/engine.rssrc/control/simulate.rssrc/handler/control.rs- 业务页面资源
web/
10. 迁移顺序
阶段 1:整理当前单体边界
目标:
- 功能不变
- 将启动逻辑、平台资源、业务运行态边界梳理清楚
工作:
- 拆解
main.rs启动装配逻辑 - 明确平台资源与业务资源边界
- 收口明显业务相关的控制模块
阶段 2:改造成 workspace
目标:
- 仓库切换为 Cargo workspace
- 当前程序仍能编译运行
工作:
- 根目录改为
[workspace] - 新建
plc_platform_core - 新建第一个 app crate
阶段 3:抽共享平台库
目标:
- 将平台能力逐步迁入共享库
建议迁移顺序:
configdbmodelutilservicetelemetryconnectionwebsocketeventcontrol/runtimecontrol/command
每迁一批都要求:
- 当前 app 能编译
- 行为不回退
- 引用关系清晰
阶段 4:固化第一套专用版
目标:
- 将当前仓库能力收敛为第一套专用应用
根据当前代码现状,优先将当前系统固化为:
app_feeder_distributor
原因:
- 当前控制引擎与文档明显更贴近投煤器/布料机控制业务
阶段 5:新增运转系统专用版
目标:
- 基于共享库新增第二个业务应用
工作:
- 新建
app_operation_system - 接入运转系统自己的控制逻辑、事件、页面和接口
阶段 6:按需增加统一版
目标:
- 如有需要,再增加
app_unified
当前阶段不作为必须交付项。
11. 验收标准
完成本设计后的实施阶段,应至少达到以下结果:
- 仓库成为 workspace 结构
- 共享库可独立被两个 app 依赖
app_feeder_distributor可独立编译为 exeapp_operation_system可独立编译为 exe- 两个 app 各自拥有独立 Web 页面
- 事件体系支持平台事件与业务事件分层
- 事件统一落到同一张
event表并使用命名空间 - 共享库不承载具体业务状态机语义
12. 风险与约束
12.1 当前主要风险
- 当前
main.rs装配逻辑集中,拆分时容易引入依赖循环 - 当前
handler与control的边界仍有部分耦合 - Web 前端目前是单目录,拆分业务页面时需要重新梳理资源结构
12.2 约束
- 第一阶段不重写现有全部业务逻辑
- 第一阶段不做业务插件化抽象
- 第一阶段不把两套业务强行配置化为同一状态机
13. 最终结论
本项目适合演进为:
- 一个共享平台核心库
- 两个独立业务专用应用
共享层只承载通用平台能力,业务层分别承载投煤器布料机和运转系统的控制逻辑、事件和页面。
发布上以“双独立 exe”为第一目标,同时保留未来组合出“统一单程序版”的能力。
这是当前仓库在可维护性、扩展性和实施风险之间最平衡的方案。