Files
bj_power/bj_power_mes/docs/superpowers/plans/2026-05-01-system-design-alignment-phase1.md
T

19 KiB

系统设计方案第一阶段对齐重构 Implementation Plan

For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 让工站域、编排域和数据库 schema 全部对齐 系统设计方案.md 的第一阶段语义,并保持现有执行内核可继续运行。

Architecture: 先统一领域常量和 ent schema,再用适配层收口 stationprocessor 的公开语义,最后通过迁移脚本和回归测试完成旧字段到新模型的平滑切换。第一阶段不接入 Redis SSOT、不实现事件重放,也不完整落地三层调度器,只为这些能力预埋模型与边界。

Tech Stack: Go 1.23.4, go-zero, ent v0.14.5, PostgreSQL, testify


文件结构

必改文件

  • Modify: constants/constants.go — 统一第一阶段使用的 StationStatusPositionType、必要的 TaskStatus 与补充步骤类型。
  • Modify: schema/job.gojob 主模型对齐文档,新增 versionlastEventId,降级旧位置字段为兼容字段。
  • Modify: schema/recipe_step.gorecipe_step 从基于 index 的流转改为以 step_id 为主。
  • Modify: schema/gen.go — 仅确认生成入口,无语义改动。
  • Modify: internal/station/interface.go — 公共 Station 契约切到文档语义。
  • Modify: internal/station/base.go — 保留槽位内部实现,但不再作为上层公共 API 依赖。
  • Modify: internal/station/registry.go — Registry 改为 string ID / 新接口语义。
  • Modify: internal/station/cnc.go
  • Modify: internal/station/washer.go
  • Modify: internal/station/inspection.go
  • Modify: internal/station/deburr.go
  • Modify: internal/station/cleaning.go
  • Modify: internal/station/sampling.go
  • Modify: internal/station/dock.go — 具体站点适配新接口。
  • Modify: internal/processor/interface.go — 顶层编排接口输出新领域状态。
  • Modify: internal/processor/task.go — 拆分领域 Task 与执行对象。
  • Modify: internal/processor/job_runtime.go — 运行时状态与 DB 状态统一到文档语义。
  • Modify: internal/processor/job_processor.go — 改造成第一阶段编排门面。
  • Modify: internal/processor/dispatcher.go — 消费新的 TaskExecution 结构。
  • Modify: internal/processor/recipe_loader.go — 读取新 recipe_step 字段与 step_id 语义。
  • Modify: internal/svc/service_context.go — 注入与恢复逻辑适配新接口。

新建文件

  • Create: schema/task.go — Task 领域持久化模型。
  • Create: schema/job_step_instance.go — Job 步骤尝试履历模型。
  • Create: internal/station/adapter.go — 旧槽位站点到新 Station 语义的适配层。
  • Create: internal/processor/task_model.go — 领域 Task、位置值对象、TaskStatus。
  • Create: internal/processor/job_runtime_status.goJobRuntime 内部状态与文档状态映射收口。
  • Create: internal/processor/repositories.go — 新旧字段映射与 repository 收口点。
  • Create: internal/station/interface_test.go — 新接口语义测试。
  • Create: internal/station/registry_test.go — string ID registry 测试。
  • Create: internal/processor/task_test.go — 任务状态机与领域/执行对象分离测试。

现有测试要改

  • Modify: internal/processor/job_runtime_test.go
  • Modify: internal/processor/temp_slot_allocator_test.go — 仅保留与“终态释放槽位”一致的断言。
  • Modify: internal/processor/replenisher_test.go — 如受位置语义影响则同步更新。

Chunk 1: 常量与 Schema 对齐

Task 1: 统一领域常量与状态枚举

Files:

  • Modify: constants/constants.go
  • Test: internal/processor/job_runtime_test.go
  • Test: internal/station/interface_test.go

Depends on:

  • Step 1: 写失败测试,锁定新状态语义
func TestStationStatusValuesMatchPhase1Design(t *testing.T) {
    assert.Equal(t, []string{"IDLE", "BUSY", "WAITING", "FAULT"}, constants.StationStatus("").Values())
}

func TestPositionTypeValuesIncludeDockAndSamplingCompatibility(t *testing.T) {
    values := constants.PositionType("").Values()
    assert.Contains(t, values, "ON_EQUIPMENT")
    assert.Contains(t, values, "ON_BUFFER")
    assert.Contains(t, values, "IN_HAND")
}
  • Step 2: 运行测试确认失败

Run: rtk go test ./internal/processor/... ./internal/station/... Expected: FAIL,提示 StationStatus 仍为 PROCESSING/DONE/ERROR 或缺少新枚举。

  • Step 3: 最小化修改常量定义

    • StationStatus 改为 IDLE/BUSY/WAITING/FAULT
    • 新增 TaskStatus 枚举
    • 如需要,补充 PositionType_OnDockPositionType_OnSampling 作为兼容值
    • 不修改与本阶段无关的常量
  • Step 4: 运行测试确认通过

Run: rtk go test ./internal/processor/... ./internal/station/... Expected: PASS 或仅剩 schema/编译相关失败。

  • Step 5: 记录检查点
    • 非 git 仓库,跳过 commit
    • 在执行日志中记录“常量层已切到文档语义”

Task 2: 重构 jobrecipe_step 并新增 task / job_step_instance

Files:

  • Modify: schema/job.go
  • Modify: schema/recipe_step.go
  • Create: schema/task.go
  • Create: schema/job_step_instance.go
  • Modify: schema/gen.go

Depends on: Task 1

  • Step 1: 先写 schema 层断言测试或编译断言
func TestJobStatusDefaultsMatchPhase1Design(t *testing.T) {
    assert.Equal(t, string(constants.JobStatus_Created), string(constants.JobStatus_Created))
}

说明:ent schema 当前缺少直接单测入口,这一步以最小编译断言 + 生成后编译为主。

  • Step 2: 修改 schema/job.go

    • 保留:workOrderId/productTypeId/recipeId/currentStepId/status/positionType/positionRefId/context/priority/suspendedReason/createdTime/lastUpdated
    • 新增:versionlastEventId
    • 旧字段 currentStepIndex/tempSlotNo/onMachineId/onSlot/dockNo/slotNo 保留但标注兼容用途
  • Step 3: 修改 schema/recipe_step.go

    • 引入 stepId 作为业务步骤标识
    • nextStepDefaultnextStepBranches 改为表达 step id
    • 新增 stepTimeout
    • 保留 stepIndex 仅用于排序
  • Step 4: 新增 schema/task.goschema/job_step_instance.go

    • task 包含 id/jobId/type/status/fromPos/toPos/assignedRobot/startedTime/completedTime/result
    • job_step_instance 包含 jobId/stepId/attemptNo/status/equipmentId/startTime/endTime/result
  • Step 5: 生成 ent 代码

Run: cd schema && rtk go generate Expected: ent code generated successfully!

  • Step 6: 编译确认新 schema 可生成

Run: rtk go test ./schema/... Expected: PASS 或输出“[no test files]”,但不能有 ent 生成/编译错误。

  • Step 7: 记录检查点
    • 标记“schema 已能表达第一阶段语义”

Task 3: 收口旧字段到新字段的迁移入口

Files:

  • Create: internal/processor/repositories.go
  • Modify: internal/processor/recipe_loader.go
  • Test: internal/processor/task_test.go

Depends on: Task 2

  • Step 1: 写失败测试,锁定旧字段到新模型的映射
func TestBuildPositionFromLegacyFieldsPrefersNewFields(t *testing.T) {
    job := legacyJobFixture()
    pos := buildPosition(job)
    assert.Equal(t, "ON_BUFFER", pos.Type)
}
  • Step 2: 运行单测确认失败

Run: rtk go test ./internal/processor/... Expected: FAIL,提示缺少 buildPosition 或仍直接依赖旧字段。

  • Step 3: 新建 repositories.go

    • 提供从 ent.Job 到新领域 JobSnapshot / PositionRef / TaskRecord 的转换
    • currentStepIndex -> currentStepId、旧位置字段 -> positionType + positionRefId 的兼容逻辑集中放这里
    • recipe_loader.go 改为优先使用 stepId
  • Step 4: 运行 processor 测试

Run: rtk go test ./internal/processor/... Expected: 旧失败减少,映射逻辑测试通过。

  • Step 5: 记录检查点
    • 标记“新旧字段映射已集中,不允许继续散落在业务代码里”

Chunk 2: 工站接口对齐

Task 4: 切换 Station 公开接口到文档语义

Files:

  • Modify: internal/station/interface.go
  • Create: internal/station/adapter.go
  • Create: internal/station/interface_test.go

Depends on: Chunk 1 完成

  • Step 1: 写失败测试,锁定新接口签名与行为
func TestStationAdapterExposesDocumentInterface(t *testing.T) {
    st := newFakeStationAdapter()
    assert.Equal(t, "CNC_1", st.ID())
    assert.Equal(t, constants.StationStatus_Idle, st.GetStatus())
    assert.True(t, st.CanAccept("A6VM160"))
}
  • Step 2: 运行 station 测试确认失败

Run: rtk go test ./internal/station/... Expected: FAIL,提示 ID() 返回 int、GetStatus() 签名不匹配或 CanAccept 缺参。

  • Step 3: 修改 interface.go 并新增 adapter.go

    • Station 改为 ID() stringGetStatus() constants.StationStatusCanAccept(jobType string) boolExecute(cmd StationCommand) (string, error)
    • 保留 StationCommand / StationEvent,但字段值语义切到文档语言
    • 用适配层包住旧 BaseStation 的槽位实现
  • Step 4: 运行 station 测试确认通过

Run: rtk go test ./internal/station/... Expected: PASS 或仅剩 registry/具体站点适配失败。

  • Step 5: 记录检查点
    • 标记“公开工站语义已与文档一致,槽位只留在内部实现”

Task 5: 调整 Registry 与具体站点实现

Files:

  • Modify: internal/station/registry.go
  • Modify: internal/station/base.go
  • Modify: internal/station/cnc.go
  • Modify: internal/station/washer.go
  • Modify: internal/station/inspection.go
  • Modify: internal/station/deburr.go
  • Modify: internal/station/cleaning.go
  • Modify: internal/station/sampling.go
  • Modify: internal/station/dock.go
  • Create: internal/station/registry_test.go

Depends on: Task 4

  • Step 1: 写失败测试,锁定 string ID registry 行为
func TestRegistryGetByStringID(t *testing.T) {
    reg := NewStationRegistry()
    reg.Register(newFakeStation("CNC_1", "CNC"))
    st, ok := reg.Get("CNC_1")
    assert.True(t, ok)
    assert.Equal(t, "CNC", st.Type())
}
  • Step 2: 运行测试确认失败

Run: rtk go test ./internal/station/... Expected: FAIL,提示 registry 仍使用 map[int]Station

  • Step 3: 改造 registry 和具体站点

    • StationRegistry 改为 map[string]Station
    • BuildProductionLine 统一产出 CNC_1CNC_2 等 string 标识
    • BaseStation 内部继续维护槽位数组,但对外状态映射为 IDLE/BUSY/WAITING/FAULT
    • 具体站点的 Execute 返回 string 型位点引用
  • Step 4: 运行 station 测试

Run: rtk go test ./internal/station/... Expected: PASS。

  • Step 5: 运行依赖 station 的基础编译测试

Run: rtk go test ./internal/svc/... ./internal/processor/... Expected: 可能有 processor 适配失败,但 station 引起的签名错误应明显减少。


Chunk 3: 编排模型与执行内核适配

Task 6: 引入领域 Task 模型并拆分执行对象

Files:

  • Create: internal/processor/task_model.go
  • Modify: internal/processor/task.go
  • Create: internal/processor/task_test.go
  • Modify: internal/processor/dispatcher.go

Depends on: Chunk 2 完成

  • Step 1: 写失败测试,锁定 Task 状态机
func TestTaskStatusFlow(t *testing.T) {
    task := NewTaskRecord("task-1", 1001)
    task.MarkDispatched()
    task.MarkRunning()
    task.MarkSuccess(map[string]any{"slot": "BUFFER_1"})
    assert.Equal(t, TaskStatusSuccess, task.Status)
}
  • Step 2: 运行测试确认失败

Run: rtk go test ./internal/processor/... Expected: FAIL,提示缺少领域 Task 结构或 RobotTask 混合职责。

  • Step 3: 新建 task_model.go 并改 task.go

    • task_model.go 定义 TaskRecordPositionRefTaskStatus
    • task.go 保留 RobotTask / TaskExecution,只持有执行闭包与运行时引用
    • dispatcher.go 改为消费执行对象,同时在回调中推进领域 Task 状态
  • Step 4: 运行 processor 测试确认通过

Run: rtk go test ./internal/processor/... Expected: Task 状态机测试通过。

  • Step 5: 记录检查点
    • 标记“数据库/接口看 Task,dispatcher 看执行对象”

Task 7: 统一 JobRuntime 的领域状态与 DB 状态

Files:

  • Create: internal/processor/job_runtime_status.go
  • Modify: internal/processor/job_runtime.go
  • Modify: internal/processor/job_runtime_test.go

Depends on: Task 6

  • Step 1: 改写失败测试为文档状态
func TestRuntimeStateToDomainStatus(t *testing.T) {
    assert.Equal(t, constants.JobStatus_OnBuffer, runtimeStateToDomainStatus(RuntimeStateOnBuffer))
    assert.Equal(t, constants.JobStatus_WaitingUnload, runtimeStateToDomainStatus(RuntimeStateWaitingUnload))
}
  • Step 2: 运行测试确认失败

Run: rtk go test ./internal/processor/... Expected: FAIL,提示仍存在 Running/Paused/Error 这套旧状态映射。

  • Step 3: 新建 job_runtime_status.go 并修改 job_runtime.go

    • 明确定义运行时内部状态与文档领域状态映射
    • Pause/Resume/Suspend/Cancel 全部改为写文档状态
    • 暂存位释放规则仅在 COMPLETED/SCRAPPED 时触发
  • Step 4: 运行 processor 测试

Run: rtk go test ./internal/processor/... Expected: 现有 job_runtime_test.go 与新状态测试通过。

  • Step 5: 记录检查点
    • 标记“JobRuntime 已降级为执行会话对象,不再自带公开业务世界观”

Task 8: 将 JobProcessor 改为第一阶段编排门面

Files:

  • Modify: internal/processor/interface.go
  • Modify: internal/processor/job_processor.go
  • Modify: internal/svc/service_context.go

Depends on: Task 7

  • Step 1: 写失败测试或编译断言,锁定新编排输出语义
func TestOrderProcessorUsesDocumentStatusVocabulary(t *testing.T) {
    // 编译期约束:接口返回值与监控结构只能使用文档状态
}
  • Step 2: 运行 processor / svc 测试确认失败

Run: rtk go test ./internal/processor/... ./internal/svc/... Expected: FAIL,提示 StationRegistry.Get、恢复逻辑、状态输出仍依赖旧接口。

  • Step 3: 修改 interface.gojob_processor.goservice_context.go

    • OrderProcessorInterface 对外只暴露文档语义
    • JobProcessor 内部用 repository、recipe resolver、runtime factory 的方式收口职责
    • restoreActiveOrders() 保持现有入口,但恢复读取新 currentStepId/position 模型
  • Step 4: 运行核心测试

Run: rtk go test ./internal/processor/... ./internal/svc/... Expected: PASS 或仅剩数据迁移/fixture 不一致问题。


Chunk 4: 数据迁移、回归验证与收尾

Task 9: 实现一次性数据回填与兼容读取验证

Files:

  • Modify: internal/processor/repositories.go
  • Modify: internal/processor/recipe_loader.go
  • Test: internal/processor/task_test.go
  • Test: internal/processor/replenisher_test.go

Depends on: Chunk 3 完成

  • Step 1: 写失败测试,覆盖旧数据兼容读取
func TestLegacyJobFieldsBackfillToCurrentStepID(t *testing.T) {
    snap := buildJobSnapshot(legacyJobFixture())
    assert.Equal(t, "30", snap.CurrentStepID)
}
  • Step 2: 运行测试确认失败

Run: rtk go test ./internal/processor/... Expected: FAIL,提示仍依赖 currentStepIndex 或旧位置字段。

  • Step 3: 实现回填与兼容读取

    • 优先读取 currentStepId/positionType/positionRefId
    • 仅当新字段为空时回退旧字段
    • 将回填逻辑收口到 repository / loader,不让上层分散判断
  • Step 4: 运行 processor 测试

Run: rtk go test ./internal/processor/... Expected: PASS。

Task 10: 完成端到端回归验证

Files:

  • Modify: internal/processor/job_runtime_test.go
  • Modify: internal/processor/replenisher_test.go
  • Modify: internal/processor/temp_slot_allocator_test.go
  • Test: internal/station/interface_test.go
  • Test: internal/station/registry_test.go

Depends on: Task 9

  • Step 1: 补充关键路径测试

    • 工单启动进入首个文档状态
    • 扫码成功进入 ON_BUFFER
    • 扫码失败进入 SUSPENDED
    • 加工完成进入 WAITING_UNLOAD
    • COMPLETED/SCRAPPED 释放暂存位
    • Task 生命周期完整走通
  • Step 2: 运行定向测试

Run: rtk go test ./internal/station/... ./internal/processor/... Expected: PASS。

  • Step 3: 运行更大范围回归

Run: rtk go test ./... Expected: PASS;如果现有无关模块失败,需记录为已有问题并隔离确认不由本计划引入。

  • Step 4: 运行覆盖率检查

Run: rtk go test -cover ./internal/processor/... ./internal/station/... Expected: 输出覆盖率,新增逻辑路径有明确测试覆盖。

  • Step 5: 人工验收清单
    • Station 对外不再暴露槽位操作 API
    • Job/Task/RecipeStep 术语与文档一致
    • JobProcessor 仍可作为运行入口
    • 没有引入 Redis SSOT、事件重放或三层调度的半成品实现

风险点

  1. StationRegistry 从 int ID 切到 string ID 会波及 processor 与恢复逻辑
    • 处理:先落适配层,再统一替换调用点。
  2. currentStepIndexcurrentStepId 的切换容易引入 recipe 跳转错误
    • 处理:在 recipe_loader 中集中转换,先保留 stepIndex 做排序。
  3. RobotTask 与领域 Task 拆分后可能出现状态不同步
    • 处理:所有状态推进统一经由 TaskRecord 方法,不允许 dispatcher 直接写裸字段。
  4. 终态释放暂存位的规则可能和现有补料测试冲突
    • 处理:先改测试,确保只有 COMPLETED/SCRAPPED 释放。

验证点

  • internal/station/... 全量测试通过
  • internal/processor/... 全量测试通过
  • cd schema && rtk go generate 成功
  • rtk go test ./... 无新增回归
  • 关键状态词只出现文档语义:IDLE/BUSY/WAITING/FAULTCREATED/IN_HANDLING/...

执行说明

  • 当前目录不是 git 仓库,跳过“频繁 commit”步骤,改为每个任务结束记录检查点。
  • 当前环境无法使用基于 worktree 的审阅代理;执行时如需审阅,采用当前会话内人工检查或普通子代理替代。
  • 严格遵守项目规则:只改 schema/*.go 后再生成 ent;不要手改 ent/

Plan complete and saved to docs/superpowers/plans/2026-05-01-system-design-alignment-phase1.md. Ready to execute?