# 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、三层调度和事件重放,而不需要再先做一轮术语翻译