# 工业自动化生产线管控系统 — 详细设计方案 ## 文档信息 - **版本**:V0.6(全配置化流程引擎) - **日期**:2026-08-10 - **状态**:与代码同步 - **说明**:新增全配置化流程引擎(`internal/engine/`),引擎作为纯执行器,所有业务逻辑通过数据库配置驱动。详细设计见 `流程文档.md`。 --- ## 目录 1. [系统架构总览](#1-系统架构总览) 2. [核心组件职责](#2-核心组件职责) 3. [启动流程](#3-启动流程) 4. [Actor 模型](#4-actor-模型) 5. [工艺配方系统](#5-工艺配方系统) 6. [信号路由系统](#6-信号路由系统) 7. [事件系统](#7-事件系统) 8. [调度器](#8-调度器) 9. [数据流与写入路径](#9-数据流与写入路径) 10. [断电恢复](#10-断电恢复) 11. [数据库设计](#11-数据库设计) 12. [PLC 信号映射](#12-plc-信号映射) 13. [关键技术选型](#13-关键技术选型) --- ## 1. 系统架构总览 本系统是一个**单体应用**,内部按职责分层模块化,打包为单个 Go 可执行文件。 ``` ┌──────────────────────────────────────────────────────────┐ │ 人工终端 (React) │ └──────────────────────┬───────────────────────────────────┘ │ SSE / HTTP ┌──────────────────────▼───────────────────────────────────┐ │ go-zero HTTP Server (API层) │ │ ┌──────────┐ ┌───────────┐ ┌──────────────────────┐ │ │ │ 工单管理 │ │ 看板/监控 │ │ 人工交互(暂停/恢复)│ │ │ └────┬─────┘ └─────┬─────┘ └──────────┬───────────┘ │ │ │ │ │ │ │ ┌────▼──────────────▼───────────────────▼────────────┐ │ │ │ 业务逻辑层 │ │ │ │ ┌────────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ │ │ 工单处理器 │ │ 事件循环 │ │ 调度器 │ │ │ │ │ │ (processor) │ │(eventloop)│ │ (scheduler) │ │ │ │ │ └────────────┘ └───────────┘ └──────────────┘ │ │ │ └────────────────────────┬──────────────────────────┘ │ │ │ │ │ ┌────────────────────────▼──────────────────────────┐ │ │ │ 基础设施层 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ │ │ │ │ 事件总线 │ │ PLC 管理 │ │ 机器人管理 │ │ │ │ │ │(eventbus)│ │(plc) │ │(robot) │ │ │ │ │ └──────────┘ └──────────┘ └────────────────┘ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ │ │ │ │ 设备代理 │ │ 信号路由 │ │ SSE 推送 │ │ │ │ │ │ (actor) │ │(signal) │ │(sse) │ │ │ │ │ └──────────┘ └──────────┘ └────────────────┘ │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ PostgreSQL (持久化) │ └──────────────────────────────────────────────────────────┘ ``` ### 分层职责 | 层 | 职责 | 关键约束 | |----|------|---------| | **API 层** | HTTP 入口,请求校验,响应组装 | 不写业务逻辑 | | **业务逻辑层** | 工单管理、调度决策、事件处理 | 核心状态机,所有决策在此 | | **基础设施层** | 事件总线、PLC 通信、机器人控制 | 可替换实现,不包含业务逻辑 | | **设备代理层** | Actor 管理槽位状态,处理设备信号 | 不写数据库,不调度 | --- ## 2. 核心组件职责 ### 2.1 事件循环 (EventLoop) — 产线大脑 **文件位置:** `internal/processor/eventloop/` **职责:** - 处理所有生产事件:PLC 信号、调度结果、前端指令 - 驱动工件状态迁移(Job 状态机) - 协调调度器下发任务 - 统一写入数据库(唯一写 DB 的入口) **启动方式:** 独立 goroutine,`go eventLoop.Run(ctx)` **关键行为:** - 接收 PLC 信号 → 更新对应 Actor 槽位状态 - 触发调度器 → 决策下一步动作 - 下发动作指令 → PlcWorker 执行 - 处理执行结果 → 更新 Job 状态 ### 2.2 工单处理器 (JobProcessor) — 订单管家 **文件位置:** `internal/processor/` **职责:** - 工单创建(校验料位、生成 Job) - 工单暂停/恢复 - 暂存台补料策略触发 - 工单完成统计 ### 2.3 调度器 (Scheduler) — 决策者 **文件位置:** `internal/processor/scheduler/` **职责:** - 从就绪工件中选择下一个要处理的 - 选择目标设备(约束过滤 + 策略排序) - 生成搬运任务(取→放) **三层架构:** 1. **任务生成层** — 根据工件当前步骤生成候选动作 2. **约束过滤层** — 排除不可用设备、暂停工单、冲突等 3. **策略选择层** — 按优先级排序 ### 2.4 Actor — 设备代理 **文件位置:** `internal/processor/actor/` **职责:** - 管理设备槽位状态(占用/空闲) - 处理 PLC 发来的设备信号 - 不写数据库,不调度 ### 2.5 信号路由 (SignalRouter) — 信号分拣 **文件位置:** `internal/processor/actor/` **职责:** - 每秒轮询 PLC 输入寄存器 - 检测信号变化(上升沿/下降沿) - 根据信号找到对应 Actor,分发处理 ### 2.6 PLC 管理器 (PLC Manager) — 硬件通信 **文件位置:** `internal/plc/` **职责:** - 建立与西门子 S1200 的通信连接 - 读写 PLC 寄存器(Modbus TCP / OPC-UA) - 发送动作指令(取料、放料、换料等) ### 2.7 事件总线 (EventBus) — 消息通道 **文件位置:** `internal/eventbus/` **职责:** - 组件间解耦通信 - 当前实现:`LocalBus`(内存 channel) ### 2.8 机器人管理器 (Robot Manager) — 搬运执行 **文件位置:** `internal/robot/` **职责:** - 控制地轨机器人执行取/放动作 - 管理手持工具(扫码枪、激光打标机) - 任务执行结果回传 --- ## 3. 启动流程 启动顺序分三个阶段,按依赖关系排列: ### 阶段一:基础设施初始化 ``` 1. PostgreSQL 连接 → ent ORM + 原生 SQL 可用 2. 预加载 → 信号地址表 + 产品类型加载到内存 3. 事件总线创建 → 组件间通信通道(此时未启动,防止事件丢失) 4. 告警服务注册 → 订阅异常事件 6. PLC 管理器创建 → 硬件通信通道建立 7. 机器人管理器创建 → 上下料能力就绪 ``` ### 阶段二:业务组件组装 ``` 8. 工艺配方加载 → 各工件型号的加工步骤序列可用 9. 调度器创建 → 决策策略就绪 10. 产线 Actor 构建 → 每个设备对应一个 Actor 管理槽位 11. 工单处理器创建 → 可管理工单全生命周期 12. PLC 动作执行器创建 → 可将动作指令转为 PLC 信号 13. 事件循环创建 → 产线状态机核心(此时未运行) 14. 信号路由绑定事件循环 → PLC 信号可直接驱动状态机 15. 信号轮询启动(goroutine)→ 每秒读取 PLC 信号 ``` ### 阶段三:启动与恢复 ``` 16. 事件总线启动 → 之前注册的订阅开始生效 17. 事件日志写入器启动 → 所有事件异步写入数据库 18. 总线全量订阅 → 日志写入 → 审计追溯能力就绪 19. SSE 桥接器创建 → 事件实时推送到前端 20. 断电恢复 → 从数据库读取未完成工单 ├─ L1 自动恢复 → CREATED/ON_BUFFER 直接恢复 ├─ L2 半自动恢复 → PROCESSING 且 PLC 确认完成 └─ L3 人工确认 → 发布事件通知前端 21. Actor 槽位同步 → 内存状态与 DB 一致 22. 事件循环启动(goroutine)→ 产线状态机开始运行 ``` --- ## 4. Actor 模型 ### 4.1 什么是 Actor 每个 Actor 对应一台物理设备,管理该设备的所有槽位。Actor 是**统一实现**(`MachineActor`),通过 `Type` 字段适配不同设备类型。Actor 不调度、不写数据库,只做两件事: - 维护槽位状态(占用/空闲/哪个工件在上面) - 处理 PLC 发来的信号(加工完成 → 把槽位状态改为 DONE) Actor 是 mutex 保护的同步状态结构体,由 EventLoop 同步调用,不启动独立 goroutine。 ### 4.2 设备类型与槽位配置 | 设备类型 | 槽位数 | 支持 Exchange | 说明 | |----------|--------|-------------|------| | GRINDER | 1 | 是 | 单槽位,支持换料(取成品+放毛坯一次完成) | | TURN_TABLE | 1 | 是 | 单槽位,支持换料,用于工件换向 | | CACHE | 1 | 否 | 单槽位暂存缓冲,仅由槽位占用状态表达语义 | | FULL_CHECK | 2 | 否 | 双槽位检测台,OK/NG 双结果信号 | | AIR_BLOW | 1 | 否 | 单槽位,气吹清洁 | | DOCK | 84 | 否 | 不建模为 MachineActor,由 PlcExecutor 直接驱动 | ### 4.3 Actor 槽位状态机 每个 Actor 的槽位经历以下状态: ``` EMPTY → OCCUPIED → DONE → EMPTY ``` - `EMPTY`:空闲,可放工件 - `OCCUPIED`:有工件,设备正在加工/处理中 - `DONE`:处理完成,等待机器人取走 ### 4.4 Actor 与 PlcExecutor 的关系 Actor 只管槽位状态(纯内存),PlcExecutor 管 PLC 信号握手。两者分离: - SignalRouter 检测到 PLC 完成信号 → 投递 SignalEvent 到 EventLoop - EventLoop.handleMachineSignal → Actor.MarkSlotDone → 更新槽位状态 - 调度器决定动作 → PlcWorker 直接调用 PlcExecutor 方法执行 PLC 握手 --- ## 5. 工艺配方系统 ### 5.1 配方结构 每种工件型号(Product Type)对应一份 Recipe(工艺配方),Recipe 包含一组有序的 Step(工序步骤)。 **Recipe 表:** ``` recipe { id // 配方编号,如 "R-A6VM107" name // 名称 version // 版本号 } ``` **RecipeStep 表:** ``` recipe_step { step_id // 步骤编号,如 "10", "20" recipe_id // 所属配方 step_name // 步骤名称 step_index // 步骤序号 step_type // 工序类型(见下表) resource_type // 设备资源类型 next_step_default // 默认下一步 desc // 步骤描述 } ``` ### 5.2 工序类型 工序类型(`step_type`)由 `agent业务.md` 定义,不同项目可不同。框架支持以下通用工序类型: | 工序类型 | 含义 | 需要什么资源 | |----------|------|-------------| | LOAD | 上料并扫码 | 搬运设备 + 接驳台 | | BUFFER_STAGE | 暂存台等待 | 暂存台槽位 | | MACHINING | 加工 | 加工设备 | | INSPECTION | 检测 | 检测设备 | | PLACE | 下料 | 搬运设备 + 接驳台 | 具体工序类型和步骤内容见 `agent业务.md` 和 `doc/data.sql`。 ### 5.3 配方加载时机 系统启动时从数据库加载所有 Recipe 到内存(`RecipeLoader`),运行期间不查库。新增配方需要重启或手动刷新。 --- ## 6. 信号路由系统 ### 6.1 信号流向 ``` PLC 输入寄存器 │ ▼ SignalRouter.Run() ─── 每秒轮询 │ ▼ 检测信号变化(上升沿/下降沿) │ ▼ 根据信号找到对应 Actor │ ▼ Actor.HandleSignal() ─── 更新槽位状态,发布事件 │ ▼ EventLoop 收到事件 ─── 触发调度器 ``` ### 6.2 信号类型 **设备完成信号(PLC → 系统):** 每种设备类型可定义自己的完成信号,由 `done_signal_name` 字段指定。信号按设备类型分组: | 设备类型 | 完成信号 | 处理方 | 触发效果 | |----------|----------|--------|---------| | GRINDER | GrinderMachiningDone | MachineActor | 槽位状态 PROCESSING → DONE | | TURN_TABLE | TurnTableExchangeDone | MachineActor | 槽位状态 PROCESSING → DONE | | AIR_BLOW | WashCompleteDone | MachineActor | 槽位状态 PROCESSING → DONE | | FULL_CHECK | FullCheckOK / FullCheckNG | MachineActor | 检测结果分支(OK→继续 / NG→报废) | **请求信号(系统 → PLC):** 系统通过 `PlcExecutor` 向 PLC 发送请求信号,信号命名规范为 `{设备}{动作}Req`,等待对应的 `{设备}{动作}Done` 完成信号。 ### 6.3 信号 vs 状态 **重要原则:PLC 信号是输入,不是状态源。** - 系统以数据库中的状态为准 - PLC 信号只用来触发状态迁移 - 信号可能丢失或重复,系统通过幂等设计保证正确性 --- ## 7. 事件系统 ### 7.1 事件总线 当前使用 `LocalBus`(内存实现),基于 Go channel 的发布/订阅模式。 **事件结构:** ```go type Event struct { ID string // UUID Type string // 事件类型 Source string // 来源 Timestamp time.Time Payload map[string]interface{} } ``` ### 7.2 事件类型 | 事件类型 | 发布者 | 订阅者 | 作用 | |----------|--------|--------|------| | 设备信号事件 | SignalRouter | EventLoop | 驱动状态机 | | 工单创建事件 | 前端 API | JobProcessor | 开始生产 | | 槽位状态变更 | Actor | EventLoop | 触发调度 | | 调度任务下发 | EventLoop | PlcWorker | 执行动作 | | 告警事件 | 各组件 | AlarmService | 通知 | | 人工确认事件 | RecoveryCoordinator | 前端 | 断电恢复 | ### 7.3 事件订阅者 当前所有订阅者: - **EventLoop** — 订阅 PLC 信号和调度结果 - **AlarmService** — 订阅告警事件 - **EventLogWriter** — 订阅所有事件,写入数据库审计 - **SSEBridge** — 订阅所有事件,推送到前端看板 ### 7.4 启动顺序保证 事件总线先创建、后启动: 1. 创建总线(`NewLocalBus`)— 此时可以订阅,但不分发事件 2. 各组件注册订阅 3. 启动总线(`bus.Start`)— 开始分发事件 防止"事件发了但没人处理"的竞态条件。 --- ## 8. 调度器 ### 8.1 调度触发 事件循环收到"槽位状态变化"事件后,触发调度器: - 工件变为 `ON_BUFFER`(暂存台上就绪) - 工件变为 `WAITING_UNLOAD`(设备加工完成待取走) ### 8.2 决策过程 ``` 输入:就绪工件列表 │ ▼ 任务生成层 ─── 根据工件当前步骤,生成候选动作 │ ▼ 约束过滤层 ─── 排除不可用的设备和冲突 │ ▼ 策略选择层 ─── 按优先级排序,选择最优方案 │ ▼ 输出:搬运任务(从哪取,放到哪) ``` ### 8.3 优先级策略 1. **紧急转移** — WAITING_UNLOAD 的工件必须尽快取走,否则堵住设备 2. **成组装卸** — 清洗机需要凑够 2 件才关门 3. **设备上料** — 给空闲设备上料 4. **批量补料** — 暂存台空位 > 4 时触发 5. **手持作业** — 扫码、打标 6. **普通搬运** — 常规的暂存台 ↔ 设备搬运 --- ## 9. 数据流与写入路径 ### 9.1 写入路径 ``` 事件源(PLC 信号 / 前端指令 / 调度结果) │ ▼ EventLoop 处理事件 │ ├──→ 更新 Actor 内存状态 │ ├──→ 触发调度器 │ └──→ 写入数据库(唯一写 DB 入口) ``` **关键约束:** EventLoop 是唯一写数据库的入口。Actor 不能直接操作数据库。 ### 9.2 数据一致性 ``` EventLoop 处理事件 │ ├──1. 更新 PostgreSQL(持久化) │ └──2. 发布事件到总线(通知其他组件) ``` 先写数据库,后发事件。如果写库失败,事件不发布。 ### 9.3 事件日志 所有总线事件通过 `EventLogWriter` 异步写入 `event_log` 表: - 用于审计追溯 - 用于断电恢复重放 --- ## 10. 断电恢复 ### 10.1 恢复分级 | 级别 | 条件 | 处理方式 | |------|------|---------| | **L1 自动** | 工件在接驳台(CREATED)、暂存台(ON_BUFFER)、等待资源 | 直接恢复,无需人工 | | **L2 半自动** | 设备加工中(PROCESSING),PLC 确认完成 | 转为 WAITING_UNLOAD,自动恢复 | | **L3 人工确认** | 机器人操作中(IN_HANDLING)、关门中、位置不明 | 发布人工确认事件,前端通知操作员 | ### 10.2 恢复流程 ``` 1. 从数据库读取所有未完成的 Job 和 Task 2. 按分级分类 3. L1 → 直接恢复状态 4. L2 → 查询 PLC 确认设备状态,恢复 5. L3 → 发布 ManualActionCreatedEvent 到总线 6. 前端收到 → 弹出人工确认对话框 7. 操作员确认后 → 恢复 Job ``` ### 10.3 事件重放 从 `event_log` 表按顺序重放事件,版本号和幂等性保证正确性: - 版本号校验:事件版本必须等于当前实体版本 - 幂等:重复事件(相同 `last_event_id`)直接跳过 --- ## 11. 数据库设计 ### 11.1 主数据表 | 表名 | 用途 | 备注 | |------|------|------| | `product_type` | 产品类型定义 | 关联 Recipe | | `recipe` | 工艺配方 | 静态配置 | | `recipe_step` | 配方步骤 | 步骤类型、资源类型、默认下一步 | | `equipment` | 设备定义 | 来自 PLC 配置 | | `equipment_slot` | 设备槽位(运行时) | 唯一槽位真相源,包含 `dock_no` 区分左右托盘 | ### 11.2 业务数据表 | 表名 | 用途 | 备注 | |------|------|------| | `work_order` | 工单 | 关联产品类型、数量、状态 | | `job` | 工件实例 | 当前步骤、状态、位置、版本 | | `job_step_instance` | 工序执行记录 | 每次工序的执行记录 | | `task` | 搬运任务 | 机器人执行的任务记录 | | `event_log` | 事件日志 | 审计和恢复用 | ### 11.3 关键设计 - `equipment_slot` 是槽位状态的**唯一真相源**,包含 `status`、`current_job_id`、`occupied_at` 等字段 - `job` 表使用 `version` 字段实现乐观锁,防止并发冲突 - `job` 表使用 `last_event_id` 字段实现幂等 --- ## 12. PLC 信号映射 ### 12.1 信号模型 PLC 信号分为两类,全部定义在 `doc/signals.sql` 中: **状态信号(PLC → 系统):** PLC 向系统上报设备状态变化,由 `SignalRouter` 轮询检测上升沿后投递给 EventLoop。 **指令信号(系统 → PLC):** 系统向 PLC 下发动作指令,由 `PlcExecutor` 按握手协议执行(写 Req → 等 Done → 复位 Req)。 具体信号地址和映射关系见 `doc/signals.sql`,不同项目替换此文件即可。 ### 12.2 信号握手协议 所有 PLC 动作遵循统一的四步握手协议: ``` 1. 写参数字节(如位置号、托盘号),非必须 2. 置位 Req 信号 = true,触发 PLC 执行动作 3. 轮询等待 Done 信号 = true(上升沿检测,间隔 100ms,超时 30s) 4. 复位 Req 信号 = false 5. 等待 Done 信号 = false(PLC 看到 Req 为 false 后复位 Done,握手完成) ``` 信号命名规范:`{设备名}{动作}Req` / `{设备名}{动作}Done` 成对出现。 --- ## 13. 关键技术选型 | 组件 | 技术 | 选型理由 | |------|------|---------| | 后端语言 | Go 1.23.4 | 并发性能好,编译快,单二进制部署 | | 框架 | go-zero v1.3.8 | 轻量级,支持 HTTP/API 代码生成 | | ORM | ent v0.14.5 | 类型安全,代码生成 | | 数据库 | PostgreSQL | 稳定可靠,支持 JSONB | | Token 存储 | 内存(InMemoryTokenStore) | 单进程部署,无需外部依赖 | | PLC 通信 | Snap7(西门子 S7 协议) | 工业标准协议 | | 前端 | React 19 + Semi Design | 企业级 UI 组件库 --- ## 14. 已知问题与待办 ### 14.1 GrinderMachiningDone 信号初始化 `doc/signals.sql` 中已定义该信号,但需执行 `go run data.go signals` 导入到数据库。 ### 14.2 RecipeLoader 调用方 `RecipeLoader` 提供按产品类型加载工艺路线的缓存能力,目前仅用于 `cmd/mockrun` 调试工具。核心业务路径(EventLoop / DBState)直接通过 `entClient.RecipeStep.Query()` 查库。 --- ## 15. 安全防护架构 ### 15.1 宕机风险防护 事件循环是产线的核心状态机,一旦崩溃将导致生产中断。系统采用多层次防护机制: ``` ┌──────────────────────────────────────────────────────┐ │ 应用层防护 │ │ ├── panic recover(EventLoop.Run + goroutine) │ │ ├── workerBusy 超时保护(5分钟自动重置) │ │ └── 通道背压保护(非阻塞发送 + 告警) │ ├──────────────────────────────────────────────────────┤ │ 业务层防护 │ │ ├── WorkerResult 幂等性(taskID 去重) │ │ ├── Exchange LoadJob 失败同步处理 │ │ ├── 槽位分配 fail-fast(不 fallback 到槽位1) │ │ └── 全检台 RequestInspect 去重 │ ├──────────────────────────────────────────────────────┤ │ 数据层防护 │ │ ├── 数据库查询超时(context.WithTimeout) │ │ ├── DB 写操作 error 必须处理 │ │ ├── Exchange 原子化操作 │ │ └── 断电恢复数据一致性校验 │ └──────────────────────────────────────────────────────┘ ``` ### 15.2 并发安全设计 | 场景 | 风险 | 防护措施 | |------|------|---------| | goroutine 访问共享 map | `concurrent map read/write` | 启动前提取局部变量传入 | | 类型断言失败 | panic | 带 ok 检查的类型断言 | | JSON 反序列化类型不匹配 | panic | `intFromPayload` 兼容 int/int64/float64 | | map key 不存在 | 零值静默使用 | `v, ok := m[key]` 模式 | ### 15.3 待修复的安全问题 | 优先级 | 问题 | 位置 | 修复方案 | |--------|------|------|---------| | P0 | EventLoop.Run() 缺少 panic recover | `loop.go:274-300` | 添加 defer recover | | P0 | dispatchWorker goroutine 缺少 recover | `worker_dispatch.go:223` | 添加 defer recover | | P0 | workerBusy 缺少超时保护 | `loop.go:64` | 添加 workerStartedAt + 超时检查 | | P0 | snap 空指针访问 | `worker_dispatch.go:79` | 添加 nil 检查 | | P1 | DB 查询缺少超时 | `scheduler_bridge.go:47` | 添加 context.WithTimeout | | P1 | handleWorkerResult 缺少幂等性 | `worker_dispatch.go:189-220` | 添加 taskID 去重 | | P1 | Exchange LoadJob 失败未处理 | `worker_dispatch.go:428-462` | 检查 LoadJobID > 0 | | P1 | 槽位分配失败 fallback 到槽位1 | `worker_dispatch.go:319-321` | 改为 fail-fast | | P2 | 全检台 RequestInspect 重复触发 | `worker_dispatch.go:471-507` | 添加原子标志去重 | | P2 | 全检台容量硬编码为6 | `worker_dispatch.go:483` | 从 Actor SlotCount 获取 | | P2 | msgCh 通道满时阻塞 | `loop.go:199` | 改为 select + default | --- ## 16. 代码结构优化建议 ### 16.1 函数拆分 | 函数 | 当前行数 | 问题 | 拆分建议 | |------|----------|------|---------| | `dispatchWorker` | ~130 行 | 超过 80 行约束 | 拆分为 fillActionSlots、executeAndSyncActor、buildWorkerResultMessage | | `trySchedule` | ~200 行 | 承担 5 个职责 | 拆分为 cleanupExpiredSnapshots、refreshRuntimeSnapshots、buildJobViews、buildSystemState、dispatchCandidates | | `buildSystemState` | ~150 行 | 承担 6 个子逻辑 | 拆分为 buildMachineBusyState、buildMachineSlotState、buildCacheSlotState、buildDockSlotState、buildOrderState、buildExchangePairState | | `handleMachineDone` | ~70 行 | 嵌套过深 | 拆分为 handleInspectionResult、recordCNCTime、advanceJobStep | ### 16.2 代码质量改进 | 问题 | 位置 | 改进方案 | |------|------|---------| | Actor 查找重复 5 次 | `worker_dispatch.go:102-144` | 分支前一次取出 Actor | | handleWorkerSuccess 分支条件风格不一致 | `worker_dispatch.go:242-264` | 统一为 switch payload.Action | | tryRequestFullCheckInspect 嵌套过深 | `worker_dispatch.go:486-497` | 拆为 isFullCheckFull、isLastPieceOfOrder | | handleExchangeLoadJob 混合两条路径 | `worker_dispatch.go:389-426` | 拆为 handleExchangeLoadJobForOperation、handleExchangeLoadJobForAdvance | | tryRequestFullCheckInspect 文件归属不当 | `worker_dispatch.go:471-507` | 移到 candidate_handlers.go 或新建 fullcheck_handler.go | | success 判断逻辑分散 | `worker_dispatch.go:203` | 在 types.go 增加 func (t MessageType) IsSuccess() bool | | UpdateOneID 影响行数被忽略 | 多处 | 检查 n == 0 时 slog.Warn | --- ## 17. BuildProductionLine 详细流程 `BuildProductionLine` 是产线初始化的核心入口,位于 `internal/processor/factory.go`。 ### 执行步骤 ``` 1. buildSignalMaps(ctx, entClient) ├── 查询 equipment 表,读取 done_signal_name 字段 ├── 调用 preload.GetMAddress(signalName) 查找 PLC 地址 ├── 成功 → 写入 doneSignals[equipmentID] = PLC地址 └── 失败 → slog.Warn 2. BuildActors(ctx, entClient) ├── 查询 equipment 表,遍历每台设备 ├── NewMachineActor(cfg) → 创建带锁状态结构体 └── machineActors[eq.ID] = actor 3. NewSignalRouter(plc) → 创建信号路由器 ├── 遍历 equipment 表,对每台设备注册完成信号 └── signalRouter.Watch(eq.ID, doneSignals[eq.ID]) 4. 返回 ProductionLine{Actors, SignalRouter} ``` ### 关键设计决策 | 决策 | 说明 | |------|------| | Actor 不启动 goroutine | Actor 是纯状态结构体,由 EventLoop 同步调用 | | 设备 PLC 握手由 PlcExecutor 统一管理 | 不同设备类型的 PLC 操作通过 PlcExecutor 方法实现,PlcWorker 直接调用,无中间 Station 抽象 | | SignalRouter 在 Build 时不绑定 sink | EventLoop 尚未创建,由 `service_context.go` 后续绑定 | | 不在此处写 DB | 只读 equipment 表,所有写入由 EventLoop 统一完成 | --- *本方案与代码保持同步,修改代码前请先更新此文档。*