436 lines
12 KiB
Markdown
436 lines
12 KiB
Markdown
# 2026-05-01 系统设计方案对齐重构(第一阶段)设计
|
||||
|
|
|
|||
|
|
## 背景
|
|||
|
|
|
|||
|
|
本次重构以 `系统设计方案.md` 为准,对当前系统中与文档冲突的实现进行纠偏。目标不是一次完成全部新架构落地,而是在第一阶段优先完成**领域语义统一**,为后续 Redis SSOT、事件重放和三层调度器改造打基础。
|
|||
|
|
|
|||
|
|
用户已确认本次采用**分阶段迁移**,第一阶段范围为:
|
|||
|
|
|
|||
|
|
- 工站域与编排域**并行对齐**
|
|||
|
|
- 允许修改数据库 schema / ent schema
|
|||
|
|
- 当前代码与文档冲突时,**以文档为准**
|
|||
|
|
|
|||
|
|
## 第一阶段目标
|
|||
|
|
|
|||
|
|
第一阶段目标是把系统从“现有实现自定义的一套运行语义”收敛到“系统设计方案.md 定义的语义”,重点完成以下三类一致化:
|
|||
|
|
|
|||
|
|
1. **工站域一致化**:公开 `Station` 契约以文档定义为准,槽位操作下沉为内部实现细节。
|
|||
|
|
2. **编排域一致化**:`Job / Task / RecipeStep / Position / Status` 统一按文档命名和流转。
|
|||
|
|
3. **数据模型一致化**:数据库 schema 和 ent schema 改为表达文档定义的运行语义,而不是继续围绕旧执行内核组织。
|
|||
|
|
|
|||
|
|
第一阶段结束后,系统可以继续使用现有执行内核的兼容外壳运行,但代码、接口和数据库都必须已经“说文档的语言”。
|
|||
|
|
|
|||
|
|
## 第一阶段范围
|
|||
|
|
|
|||
|
|
### 包含
|
|||
|
|
|
|||
|
|
- 统一 `JobStatus / TaskStatus / StationStatus / PositionType / StepType` 等枚举与领域类型
|
|||
|
|
- 重构 `internal/station` 的公开接口
|
|||
|
|
- 重构 `internal/processor` 的公开接口与运行态模型
|
|||
|
|
- 重构 `schema/job.go`、`schema/recipe_step.go`
|
|||
|
|
- 新增 `task`、`job_step_instance` schema
|
|||
|
|
- 让 `JobProcessor / JobRuntime / Dispatcher` 适配新领域模型继续运行
|
|||
|
|
- 做必要的数据迁移与兼容回填
|
|||
|
|
|
|||
|
|
### 不包含
|
|||
|
|
|
|||
|
|
- Redis 作为运行时单一真相源(SSOT)的完整落地
|
|||
|
|
- Lua 原子版本更新与抢占
|
|||
|
|
- 事件重放能力落地
|
|||
|
|
- 三层调度器完整实现
|
|||
|
|
- PLC 协议与机器人底层通信架构重写
|
|||
|
|
|
|||
|
|
## 模块边界
|
|||
|
|
|
|||
|
|
### station
|
|||
|
|
|
|||
|
|
负责设备抽象、站点状态、命令执行、现场事件接入。
|
|||
|
|
|
|||
|
|
第一阶段中:
|
|||
|
|
- `Station` 对上层暴露能力语义,而不是直接暴露槽位增删改查语义
|
|||
|
|
- 槽位数组、批处理完成态、换料内部逻辑保留为站点内部实现
|
|||
|
|
|
|||
|
|
### processor
|
|||
|
|
|
|||
|
|
负责工单/工件编排、步骤推进、任务生成与状态流转。
|
|||
|
|
|
|||
|
|
第一阶段中:
|
|||
|
|
- `JobProcessor` 改造为编排门面(facade)
|
|||
|
|
- `JobRuntime` 继续存在,但降级为“执行会话对象”,不再作为业务语义真相源
|
|||
|
|
- `RobotTask` 拆分为领域 `Task` 和执行对象两个层次
|
|||
|
|
|
|||
|
|
### schema / ent
|
|||
|
|
|
|||
|
|
负责把文档中的运行语义固化为数据库结构,为后续 Redis 和事件模型落地预留字段和引用方式。
|
|||
|
|
|
|||
|
|
## 核心领域模型
|
|||
|
|
|
|||
|
|
### Job
|
|||
|
|
|
|||
|
|
`Job` 是工件运行态核心实体,第一阶段的主字段以文档为准:
|
|||
|
|
|
|||
|
|
- `id`
|
|||
|
|
- `work_order_id`
|
|||
|
|
- `product_type_id`
|
|||
|
|
- `recipe_id`
|
|||
|
|
- `current_step_id`
|
|||
|
|
- `status`
|
|||
|
|
- `position_type`
|
|||
|
|
- `position_ref_id`
|
|||
|
|
- `context`
|
|||
|
|
- `priority`
|
|||
|
|
- `suspended_reason`
|
|||
|
|
- `version`
|
|||
|
|
- `last_event_id`
|
|||
|
|
- `created_time`
|
|||
|
|
- `last_updated`
|
|||
|
|
|
|||
|
|
约束:
|
|||
|
|
- `current_step_id` 取代 `currentStepIndex` 成为主步骤定位字段
|
|||
|
|
- `position_type + position_ref_id` 成为主位置表达
|
|||
|
|
- `version + last_event_id` 在第一阶段先入库,为第二阶段事件一致性铺路
|
|||
|
|
|
|||
|
|
### Job 状态机
|
|||
|
|
|
|||
|
|
对外可见状态与数据库状态统一为:
|
|||
|
|
|
|||
|
|
- `CREATED`
|
|||
|
|
- `IN_HANDLING`
|
|||
|
|
- `PROCESSING`
|
|||
|
|
- `WAITING_UNLOAD`
|
|||
|
|
- `ON_BUFFER`
|
|||
|
|
- `WAITING_DECISION`
|
|||
|
|
- `COMPLETED`
|
|||
|
|
- `SCRAPPED`
|
|||
|
|
- `SUSPENDED`
|
|||
|
|
|
|||
|
|
要求:
|
|||
|
|
- 外部接口、数据库枚举、监控输出只允许出现文档状态
|
|||
|
|
- 旧 `JobRuntime` 内部若存在过渡状态,只能作为会话辅助状态,不能暴露为领域状态
|
|||
|
|
|
|||
|
|
### Position
|
|||
|
|
|
|||
|
|
位置统一由以下字段表达:
|
|||
|
|
|
|||
|
|
- `position_type`
|
|||
|
|
- `ON_EQUIPMENT`
|
|||
|
|
- `ON_BUFFER`
|
|||
|
|
- `IN_HAND`
|
|||
|
|
- 迁移期按业务需要兼容 `ON_DOCK`、`ON_SAMPLING`
|
|||
|
|
- `position_ref_id`
|
|||
|
|
- 指向设备、槽位、接驳台槽位或抽检位等具体位置
|
|||
|
|
|
|||
|
|
约束:
|
|||
|
|
- `tempSlotNo / onMachineId / onSlot / dockNo / slotNo` 可作为迁移兼容字段短期保留
|
|||
|
|
- 新业务判断只能基于 `position_type + position_ref_id`
|
|||
|
|
|
|||
|
|
### Task
|
|||
|
|
|
|||
|
|
Task 作为明确的领域对象存在,至少包含:
|
|||
|
|
|
|||
|
|
- `id`
|
|||
|
|
- `job_id`
|
|||
|
|
- `type`
|
|||
|
|
- `status`
|
|||
|
|
- `from_pos`
|
|||
|
|
- `to_pos`
|
|||
|
|
- `assigned_robot`
|
|||
|
|
- `started_time`
|
|||
|
|
- `completed_time`
|
|||
|
|
- `result`
|
|||
|
|
- 可选增强:`timeout_at`、`retry_count`
|
|||
|
|
|
|||
|
|
Task 状态机统一为:
|
|||
|
|
|
|||
|
|
- `CREATED`
|
|||
|
|
- `DISPATCHED`
|
|||
|
|
- `RUNNING`
|
|||
|
|
- `SUCCESS`
|
|||
|
|
- `FAILED`
|
|||
|
|
- `TIMEOUT`
|
|||
|
|
|
|||
|
|
要求:
|
|||
|
|
- 领域 Task 可持久化、可查询、可审计
|
|||
|
|
- `RobotTask` 仅保留为执行对象,不再直接承担完整领域语义
|
|||
|
|
|
|||
|
|
### RecipeStep
|
|||
|
|
|
|||
|
|
`recipe_step` 以文档模型为准,重点字段包括:
|
|||
|
|
|
|||
|
|
- `step_id`
|
|||
|
|
- `recipe_id`
|
|||
|
|
- `step_name`
|
|||
|
|
- `step_type`
|
|||
|
|
- `resource_type`
|
|||
|
|
- `tool_type`
|
|||
|
|
- `allowed_resources`
|
|||
|
|
- `processing_params`
|
|||
|
|
- `next_step_default`
|
|||
|
|
- `next_step_branches`
|
|||
|
|
- `step_timeout`
|
|||
|
|
- `description`
|
|||
|
|
|
|||
|
|
约束:
|
|||
|
|
- 步骤跳转以 `step_id` 为主,而不是 `stepIndex`
|
|||
|
|
- `stepIndex` 可保留为排序字段,但不再作为主业务引用
|
|||
|
|
|
|||
|
|
### JobStepInstance
|
|||
|
|
|
|||
|
|
新增 `job_step_instance` 用于记录某个 Job 对某个 Step 的一次执行尝试,包括:
|
|||
|
|
|
|||
|
|
- `job_id`
|
|||
|
|
- `step_id`
|
|||
|
|
- `attempt_no`
|
|||
|
|
- `status`
|
|||
|
|
- `equipment_id`
|
|||
|
|
- `start_time`
|
|||
|
|
- `end_time`
|
|||
|
|
- `result`
|
|||
|
|
|
|||
|
|
作用:
|
|||
|
|
- 支撑返工与恢复
|
|||
|
|
- 提供比 `job.context` 更清晰的步骤履历
|
|||
|
|
- 为第二阶段事件重放与审计提供基础
|
|||
|
|
|
|||
|
|
## 工站接口重构
|
|||
|
|
|
|||
|
|
### 目标公开接口
|
|||
|
|
|
|||
|
|
第一阶段公开 `Station` 契约对齐文档语义:
|
|||
|
|
|
|||
|
|
- `ID() string`
|
|||
|
|
- `Type() string`
|
|||
|
|
- `GetStatus() StationStatus`
|
|||
|
|
- `CanAccept(jobType string) bool`
|
|||
|
|
- `Execute(cmd StationCommand) (assignedSlot string, err error)`
|
|||
|
|
- `OnEvent(event StationEvent)`
|
|||
|
|
|
|||
|
|
### 设计原则
|
|||
|
|
|
|||
|
|
1. `ID` 改为 `string`,统一设备编码表达,避免后续 Redis key 和业务标识反复转换。
|
|||
|
|
2. `CanAccept` 接收 `jobType`,为设备能力约束、机床绑定和工艺过滤预留位置。
|
|||
|
|
3. `Execute` 对外返回 string 型位点引用,便于表达数字槽位、缓冲位、接驳台位等统一位置。
|
|||
|
|
4. 槽位管理保留,但属于站点内部实现,不再作为上层统一契约。
|
|||
|
|
|
|||
|
|
### 分层方式
|
|||
|
|
|
|||
|
|
- **公开领域接口层**:供 processor / scheduler 使用,只表达站点能力、状态和命令语义。
|
|||
|
|
- **内部状态实现层**:保留当前 `BaseStation` 槽位数组、状态迁移、批处理完成与换料逻辑。
|
|||
|
|
|
|||
|
|
### 状态模型纠偏
|
|||
|
|
|
|||
|
|
当前站点状态从旧语义映射到文档语义:
|
|||
|
|
|
|||
|
|
- `PROCESSING` → `BUSY`
|
|||
|
|
- `DONE` → `WAITING`
|
|||
|
|
- `ERROR` → `FAULT`
|
|||
|
|
|
|||
|
|
上层只关心是否可继续编排,不直接依赖内部槽位处于 `DONE` 还是 `OCCUPIED`。
|
|||
|
|
|
|||
|
|
### 兼容策略
|
|||
|
|
|
|||
|
|
分两步推进:
|
|||
|
|
|
|||
|
|
1. 保留现有 `BaseStation` 与具体站点实现,新增适配层包装成新 `Station`。
|
|||
|
|
2. 逐步移除 processor 对 `OccupySlot / ReleaseSlot / TransitionSlot / FindSlotByJobID` 等旧接口的直接依赖,统一通过 `Execute / OnEvent / GetStatus / CanAccept` 交互。
|
|||
|
|
|
|||
|
|
## 编排接口与运行时改造
|
|||
|
|
|
|||
|
|
### OrderProcessorInterface
|
|||
|
|
|
|||
|
|
第一阶段保留“工单入口”的结构,但语义改写为文档术语优先:
|
|||
|
|
|
|||
|
|
- 工单控制:`Start / Pause / Resume / Cancel / Restore`
|
|||
|
|
- 单工件控制:`SuspendJob / ResumeJob / ReworkJob`
|
|||
|
|
- 监控查询:输出文档定义的状态和值对象,不暴露旧内部状态机语义
|
|||
|
|
|
|||
|
|
### JobProcessor
|
|||
|
|
|
|||
|
|
`JobProcessor` 从“大而全编排器”收缩为第一阶段编排门面:
|
|||
|
|
|
|||
|
|
- 对外仍作为工单编排入口
|
|||
|
|
- 对内协调以下子职责:
|
|||
|
|
- `JobRepository / TaskRepository`
|
|||
|
|
- `RecipeResolver`
|
|||
|
|
- `RuntimeFactory`
|
|||
|
|
- `TaskDispatcher`
|
|||
|
|
- `BufferManager`
|
|||
|
|
- `RecoveryCoordinator`(先留接口)
|
|||
|
|
|
|||
|
|
目标不是立刻实现所有拆分,而是先明确责任边界,避免继续把新旧语义堆叠在一个类型中。
|
|||
|
|
|
|||
|
|
### JobRuntime
|
|||
|
|
|
|||
|
|
`JobRuntime` 继续存在,但降级为“执行会话对象”:
|
|||
|
|
|
|||
|
|
- 持有运行时上下文
|
|||
|
|
- 根据 `RecipeStep` 推进步骤
|
|||
|
|
- 响应 Task 完成/失败
|
|||
|
|
- 触发状态落库
|
|||
|
|
|
|||
|
|
约束:
|
|||
|
|
- 不是业务状态真相源
|
|||
|
|
- 领域状态必须与文档状态一致
|
|||
|
|
- 会话辅助状态仅用于执行期控制,不能直接外露
|
|||
|
|
|
|||
|
|
### Task 的双层模型
|
|||
|
|
|
|||
|
|
第一阶段明确拆分:
|
|||
|
|
|
|||
|
|
- **Task(领域对象)**
|
|||
|
|
- 可持久化、可审计
|
|||
|
|
- 表达业务任务状态与位置迁移
|
|||
|
|
- **TaskExecution / RobotTask(执行对象)**
|
|||
|
|
- 持有 `ActionFn`
|
|||
|
|
- 持有运行时引用
|
|||
|
|
- 由 dispatcher 串行或并发执行
|
|||
|
|
|
|||
|
|
迁移结果:
|
|||
|
|
- 数据库与接口看 Task
|
|||
|
|
- 调度执行层看 RobotTask 或等价执行对象
|
|||
|
|
|
|||
|
|
### 状态推进硬约束
|
|||
|
|
|
|||
|
|
- Job 状态只能走文档允许的迁移
|
|||
|
|
- Task 状态只能走 `CREATED -> DISPATCHED -> RUNNING -> SUCCESS/FAILED/TIMEOUT`
|
|||
|
|
- `current_step_id` 是 Job 前进依据
|
|||
|
|
- `job_step_instance` 记录每次步骤尝试
|
|||
|
|
- 暂存台槽位的占用释放只由 Job 终态驱动,不由工件暂时离开槽位驱动
|
|||
|
|
|
|||
|
|
## 数据库与迁移策略
|
|||
|
|
|
|||
|
|
### 总体策略
|
|||
|
|
|
|||
|
|
采用 **先扩展、再切换、最后清理** 的方式:
|
|||
|
|
|
|||
|
|
1. 先补齐文档要求的新字段/新表
|
|||
|
|
2. 让代码优先读写新语义字段
|
|||
|
|
3. 旧字段仅作为短期兼容
|
|||
|
|
4. 第一阶段稳定后再评估是否删除旧兼容字段
|
|||
|
|
|
|||
|
|
### job 表
|
|||
|
|
|
|||
|
|
#### 保留并强化
|
|||
|
|
- `workOrderId`
|
|||
|
|
- `productTypeId`
|
|||
|
|
- `recipeId`
|
|||
|
|
- `currentStepId`
|
|||
|
|
- `status`
|
|||
|
|
- `positionType`
|
|||
|
|
- `positionRefId`
|
|||
|
|
- `context`
|
|||
|
|
- `priority`
|
|||
|
|
- `suspendedReason`
|
|||
|
|
- `createdTime`
|
|||
|
|
- `lastUpdated`
|
|||
|
|
|
|||
|
|
#### 新增
|
|||
|
|
- `version`
|
|||
|
|
- `lastEventId`
|
|||
|
|
|
|||
|
|
#### 降级为兼容字段
|
|||
|
|
- `currentStepIndex`
|
|||
|
|
- `tempSlotNo`
|
|||
|
|
- `onMachineId`
|
|||
|
|
- `onSlot`
|
|||
|
|
- `dockNo`
|
|||
|
|
- `slotNo`
|
|||
|
|
|
|||
|
|
规则:
|
|||
|
|
- 新代码只从新主字段读
|
|||
|
|
- 旧字段只允许迁移期回填或兼容查询
|
|||
|
|
|
|||
|
|
### recipe_step 表
|
|||
|
|
|
|||
|
|
第一阶段对齐为文档语义:
|
|||
|
|
|
|||
|
|
- `step_id` 作为业务步骤标识
|
|||
|
|
- `next_step_default`、`next_step_branches` 统一以步骤 ID 表达跳转
|
|||
|
|
- 补齐 `step_timeout`
|
|||
|
|
- 保留 `allowed_resources / processing_params / tool_type / resource_type`
|
|||
|
|
|
|||
|
|
### 新增 task 表
|
|||
|
|
|
|||
|
|
用于持久化 Task 领域对象,支持任务状态查询、超时回退和审计。
|
|||
|
|
|
|||
|
|
### 新增 job_step_instance 表
|
|||
|
|
|
|||
|
|
用于记录 Job 的步骤执行尝试明细,减少上下文字段滥用,支撑返工和恢复。
|
|||
|
|
|
|||
|
|
### 切换步骤
|
|||
|
|
|
|||
|
|
1. 修改 ent schema
|
|||
|
|
2. 重新生成 ent 代码
|
|||
|
|
3. 改 repository / query 层兼容新旧字段
|
|||
|
|
4. 改 processor 使用新字段
|
|||
|
|
5. 编写一次性数据迁移逻辑:
|
|||
|
|
- `currentStepIndex -> currentStepId`
|
|||
|
|
- 旧位置字段 -> `positionType + positionRefId`
|
|||
|
|
6. 稳定后再决定是否移除旧字段
|
|||
|
|
|
|||
|
|
## 实施顺序
|
|||
|
|
|
|||
|
|
建议按以下 6 步推进:
|
|||
|
|
|
|||
|
|
1. **统一常量与领域类型**
|
|||
|
|
- 先统一 `JobStatus / TaskStatus / StationStatus / PositionType / StepType`
|
|||
|
|
2. **改 schema 与 ent**
|
|||
|
|
- 重构 `job`、`recipe_step`
|
|||
|
|
- 新增 `task`、`job_step_instance`
|
|||
|
|
3. **建立过渡层**
|
|||
|
|
- 收口新旧字段映射
|
|||
|
|
- 不让映射逻辑散落在业务代码中
|
|||
|
|
4. **重构 station 公共接口**
|
|||
|
|
- 先引入新契约与适配层
|
|||
|
|
5. **重构 processor 编排入口**
|
|||
|
|
- `JobProcessor` 改为门面
|
|||
|
|
- `JobRuntime` 改为执行会话
|
|||
|
|
- `RobotTask` 拆为领域 Task 与执行对象
|
|||
|
|
6. **做数据迁移与回填**
|
|||
|
|
- 验证历史数据可被新逻辑正确读取
|
|||
|
|
|
|||
|
|
## 回归验证
|
|||
|
|
|
|||
|
|
第一阶段至少验证以下路径:
|
|||
|
|
|
|||
|
|
1. **工单启动**
|
|||
|
|
- 能正确加载 recipe、创建 Job 运行态、进入首个文档定义状态
|
|||
|
|
2. **扫码入暂存台**
|
|||
|
|
- 成功进入 `ON_BUFFER`
|
|||
|
|
- 失败进入 `SUSPENDED`
|
|||
|
|
3. **机加工到下料**
|
|||
|
|
- `PROCESSING -> WAITING_UNLOAD -> IN_HANDLING`
|
|||
|
|
4. **终态释放**
|
|||
|
|
- `COMPLETED / SCRAPPED` 时释放暂存位
|
|||
|
|
5. **任务生命周期**
|
|||
|
|
- `CREATED -> DISPATCHED -> RUNNING -> SUCCESS/FAILED/TIMEOUT`
|
|||
|
|
6. **恢复 / 返工接口语义**
|
|||
|
|
- 即使第二阶段完整恢复体系未落地,接口语义也必须已与文档一致
|
|||
|
|
|
|||
|
|
## 风险控制
|
|||
|
|
|
|||
|
|
### 风险 1:新旧状态并存
|
|||
|
|
|
|||
|
|
- 解决:对外只暴露新状态,旧状态只允许存在于适配层内部
|
|||
|
|
|
|||
|
|
### 风险 2:位置语义混乱
|
|||
|
|
|
|||
|
|
- 解决:业务判断统一只认 `positionType + positionRefId`
|
|||
|
|
|
|||
|
|
### 风险 3:步骤跳转仍依赖 index
|
|||
|
|
|
|||
|
|
- 解决:尽快把主流转切到 `step_id`,`stepIndex` 只保留排序用途
|
|||
|
|
|
|||
|
|
### 风险 4:上层仍直接操纵工站槽位
|
|||
|
|
|
|||
|
|
- 解决:逐步封口,只允许通过 `Execute / OnEvent / GetStatus / CanAccept` 交互
|
|||
|
|
|
|||
|
|
## 第一阶段完成标准
|
|||
|
|
|
|||
|
|
第一阶段完成,不以 Redis 已接入为标准,而以下列结果为准:
|
|||
|
|
|
|||
|
|
- 代码、数据库、接口都使用文档定义的术语
|
|||
|
|
- station 与 processor 不再存在一套公开语义、一套内部语义互相冲突的问题
|
|||
|
|
- 旧执行内核仍可运行,但已运行在新领域模型之上
|
|||
|
|
- 第二阶段可以直接接 Redis SSOT、三层调度和事件重放,而不需要再先做一轮术语翻译
|