chore: init bj_power monorepo (dashboard/mes/wms/wms_client/workstation), clear subproject git metadata and align naming to folder names

This commit is contained in:
SunYF
2026-08-27 10:09:54 +08:00
commit 1f0ee844a4
408 changed files with 102201 additions and 0 deletions
+705
View File
@@ -0,0 +1,705 @@
# 工业自动化生产线管控系统 — 详细设计方案
## 文档信息
- **版本**: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 信号 = falsePLC 看到 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 recoverEventLoop.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 统一完成 |
---
*本方案与代码保持同步,修改代码前请先更新此文档。*