结论:要做干净室、插件化的遥测工具重构,落点是把「宿主」压缩到最小、把能力全部下沉到可独立加载与卸载的插件。本文的取舍可以概括为三条:宿主只负责插件生命周期、工作区、命令路由与能力发现;所有数据模型与契约位于中立的 Abstractions 层,不依赖任何 UI 框架;单个插件加载失败只进入失败清单,不阻止无依赖插件运行。清单的 schemaVersion 当前为 1,插件 ID 使用反向域名风格,通道名使用稳定点分命名(如 speed.kmh)。
七条设计原则
架构文档给出的七条原则,决定了后续所有工程取舍:
| 原则 | 含义 |
|---|---|
| 宿主最小化 | 宿主只负责插件生命周期、工作区、命令路由和能力发现 |
| 数据中立 | 会话、采样、圈、分段、视频引用与弯角定义位于 Abstractions,不依赖 UI 或第三方库 |
| 多实现并存 | 同一契约可注册多个实现,例如 CSV、GeoJSON 与报告都实现同一个导出契约 |
| 依赖显式 | 依赖写在 plugin.json,加载器做拓扑排序并检测循环 |
| 故障隔离 | 单个插件加载失败进入失败清单,不阻止无依赖插件运行 |
| UI 是适配器 | CLI、桌面与 Web 都是宿主外壳,不能持有核心业务算法 |
| 安全默认值 | 更新源只接受 HTTPS;插件加载目录必须由宿主明确指定 |
第四条与第五条合起来,是插件架构能否长期维护的关键:依赖必须显式声明,才能在加载阶段做拓扑排序、定位失败插件并隔离它。
插件生命周期与加载隔离
加载流程是固定的一段顺序,可按下面步骤核对:
- 发现
plugin.json; - 验证
schemaVersion; - 定位
entryAssembly; - 依赖拓扑排序;
- 创建独立 AssemblyLoadContext;
- 实例化插件接口;
- 注册服务与命令;
- 启动(StartAsync);
- 正常运行;
- 停止(StopAsync);
- 卸载 AssemblyLoadContext。
这里有一条容易踩坑的规则:共享的 Abstractions 程序集始终由默认加载上下文提供,插件加载上下文不会加载自己的契约副本。这么做是为了避免 CLR 类型身份不一致——如果每个插件各自加载一份契约,同一接口在不同上下文里会被当成不同类型,注册与调用都会失败。
能力契约与功能拆分映射
宿主通过一组能力契约暴露功能,插件只实现自己关心的契约:
| 契约 | 用途 |
|---|---|
| 数据导入契约 | 文件到遥测会话 |
| 会话处理契约 | 导入后的派生通道与标准化处理 |
| 数据导出契约 | 会话到外部文件 |
| 弯角库服务 | 赛道弯角库读取 |
| 圈速服务 | 门线、圈速、分段 |
| 视频引用服务 | 视频关联和路径解析 |
| 图表序列服务 | 图表序列和抽样 |
| 地图路线服务 | 地图轨迹和简化 |
| 洞察分析器 | 弯角分析 |
| 配置存储 | 配置持久化 |
| 更新源服务 | 安全更新元数据 |
| 赛道库服务 | 赛道、门线和位置查询 |
| 文件传输服务 | 会话文件分组和复制 |
| 插件命令 | 可脚本化的用户命令 |
原软件里集中的「服务」被拆成独立边界。功能域的映射关系如下:
| 原功能域 | 对应插件 |
|---|---|
| 文件读写与数据服务 | 数据导入插件 |
| 弯角数据与赛道路径 | 弯角插件 |
| 起终点数据库 | 赛道数据库插件 |
| 开放赛道几何 | 开放赛道数据插件 |
| 数学通道 | 数学插件 |
| 圈与分段 | 圈速插件 |
| 视频服务 | 视频插件(播放后端作为后续适配器) |
| 图表、地图、导出、报告、洞察 | 各自独立的图表 / 地图 / 导出 / 报告 / 洞察插件 |
| 设置、自动更新、文件传输 | 设置 / 更新 / 文件传输插件 |
桌面层延续同样原则:外壳只提供停靠区、菜单、状态栏与工作区选择;图表插件贡献图表面板、不直接引用导入插件;地图插件只读标准位置通道;视频插件可接入可替换的播放后端;PDF、打印、HTML 报告分别实现同一个报告渲染契约;在线地图、遥测上传与 AI 建议必须是显式安装、可禁用的网络插件。
干净室边界:能用什么、不能带进什么
干净室开发的核心是划一条清晰的输入边界,只允许来自可观察行为与公开资料的证据:
| 可以使用 | 不进入新工程 |
|---|---|
| 用户可观察的输入、输出和交互行为 | 原程序的反编译源码或轻微改名的等价复制 |
| 公开文档、公开文件格式和独立测试数据 | 保护区域中恢复出的专有方法体 |
| 为互操作性而确认的字段、单位和二进制布局 | 厂商图标、图片、文案、赛道数据库等受保护资源 |
| 自行设计的数据模型、算法、插件接口和用户界面 | 厂商 DLL、许可组件、私有密钥或未授权在线服务 |
| — | 绕过许可、签名验证或访问控制的逻辑 |
工程上还有几条落地措施:源码、插件与测试目录不引用反编译目录;测试用的采集文件为人工合成数据;文件格式解析器只实现互操作,测试时从用户本机安装读取、不把数据库复制进仓库;可分发的赛道与弯角数据包只从明确许可的开放数据源生成,并在每个快照中保留来源、查询、署名和文件哈希;更新插件拒绝 HTTP 源且不自动下载或安装包。
新增一个插件的五步
- 新建一个引用 Abstractions 的目标框架类库;
- 实现插件接口;
- 通过注册器注册服务或命令;
- 在项目根目录放置
plugin.json,声明入口程序集、入口类型、依赖和能力; - 把项目加入解决方案并构建,宿主会从输出目录自动定位程序集。
兼容性规则同时要求:插件不得引用反编译目录或厂商二进制;破坏性契约变化要发布新的 Abstractions 主版本。把这两条与前面的「依赖显式」放在一起看,就能理解为什么插件清单必须声明入口程序集、入口类型、依赖与能力——加载器据此排序、隔离并在失败时给出清单,而不是静默跳过。
常见问题
单个插件加载失败会不会拖垮整个工具?
不会。架构原则要求故障隔离:单个插件加载失败进入失败清单,不阻止无依赖的插件运行。前提是依赖显式写在清单里,加载器才能做拓扑排序并隔离失败项。
为什么每个插件各自加载一份契约会出问题?
会造成 CLR 类型身份不一致:同一接口在不同加载上下文里被当成不同类型,注册与调用都会失败。因此共享的契约程序集始终由默认加载上下文提供,插件加载上下文不加载自己的契约副本。
哪些数据可以来自原软件?
只有用户可观察的输入、输出与交互行为,以及公开文档、公开文件格式和独立测试数据。反编译源码、保护区域恢复的专有方法体、厂商图标文案、厂商二进制与私有密钥都不进入新工程。
网络、在线地图和 AI 能力怎么处理?
它们必须是显式安装、可禁用的独立网络插件。更新源只接受 HTTPS,且不自动下载或安装包;这类能力应保持默认关闭、可审计。
延伸阅读
- OBD AI 插件化平台:端侧解析为什么必须插件化 —— 从端侧解析角度看插件边界。
- EdgeForge:嵌入式侧端框架的自编译方案 —— 另一种把能力下沉到可替换部件的工程路径。
- Ed25519 签名加 96 字节事务记录:让 MCU 升级不怕中途断电 —— 承接「更新只接受 HTTPS / 校验哈希」的安全基线。
- 大型 ADAS 代码库怎么读:四步阅读顺序与三类边界 —— 复杂代码库的边界划分方法。
参考资料
- 项目 03 ——
nixiang-tracklab/PLUGIN_ARCHITECTURE.md(取用七条设计原则、插件生命周期、能力契约、功能拆分映射与兼容性规则)。 - 项目 03 ——
nixiang-tracklab/CLEAN_ROOM.md(取用干净室的可使用 / 不进入边界与工程措施)。 - 项目 03 ——
nixiang-tracklab/README.md(取用当前插件状态、构建运行方式与新增插件五步)。
说明:本文为对整洁室重构文档与固定源码快照的静态结构整理,未运行完整测试套件、未连接任何厂商数据库或车辆、未验证各插件在实际数据上的输出。文中所有原则、契约与步骤均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复