// mockrun: 使用 Mock PLC + Mock Marking 完整模拟订单执行流程。 // 用法: go run cmd/mockrun/main.go -jobs 4 -docks 1,2 -product 2 package main import ( "context" "flag" "fmt" "log/slog" "os" "strconv" "strings" "time" xlog "bj_power_mes/common/logx" "bj_power_mes/constants" "bj_power_mes/ent/job" "bj_power_mes/ent/recipestep" "bj_power_mes/internal/config" "bj_power_mes/internal/svc" "github.com/zeromicro/go-zero/core/conf" ) var ( flagJobs = flag.Int("jobs", 12, "工件数量") flagProduct = flag.Int("product", 1, "产品类型 ID") flagStep = flag.Int("step", 0, "初始步骤索引(0=从第一步开始,4=从清洗机上料开始)") flagTimeout = flag.Duration("timeout", 120*time.Minute, "超时时间") ) func main() { flag.Parse() slog.Info("=== Mock 订单模拟启动 ===", "jobs", *flagJobs, "product", *flagProduct) // 1. 加载配置 var c config.Config conf.MustLoad("etc/bj_power_mes-api.yaml", &c) logger := xlog.NewLogger(c.Logger) xlog.SetDefault(logger) //if !c.Mock.Enable { // fmt.Println("错误: Mock.Enable 必须为 true") // os.Exit(1) //} c.Mock.MockRun = true // 2. 创建 ServiceContext(Mock 模式下已自动清空旧数据) fmt.Println("正在初始化 ServiceContext...") svcCtx := svc.NewServiceContext(c) time.Sleep(2 * time.Second) ctx := context.Background() // 3. 加载工艺路线 recipe, err := svcCtx.RecipeLoader.Load(ctx, *flagProduct) if err != nil { slog.Error("加载工艺路线失败", "error", err) os.Exit(1) } fmt.Printf("工艺路线: %s (%d步)\n", recipe.RecipeCode, recipe.TotalSteps) firstStepIdx := recipe.Steps[0].StepIndex // 4. 创建工单 + 工件 order, err := svcCtx.EntClient.WorkOrder.Create(). SetProductTypeId(*flagProduct). SetQuantity(*flagJobs). SetStatus(constants.WorkOrderStatus_Created). Save(ctx) if err != nil { slog.Error("创建工单失败", "error", err) os.Exit(1) } fmt.Printf("工单已创建: id=%d\n", order.ID) // 确定初始步骤索引 initialStepIdx := firstStepIdx if *flagStep > 0 { initialStepIdx = *flagStep } initialStepID := "" if step, err := svcCtx.EntClient.RecipeStep.Query(). Where(recipestep.RecipeIdEQ(recipe.RecipeID), recipestep.StepIndexEQ(initialStepIdx)). First(ctx); err == nil { initialStepID = step.StepId } for i := 1; i <= *flagJobs; i++ { dockNo := (i-1)/9 + 1 slotIdx := (i-1)%9 + 1 jobCreate := svcCtx.EntClient.Job.Create(). SetWorkOrderId(order.ID). SetProductTypeId(*flagProduct). SetRecipeID(recipe.RecipeID). SetCurrentStepId(initialStepID) if *flagStep > 0 && i <= 8 { // 跳过前置步骤:工件在暂存台,状态为 Processing jobCreate = jobCreate. SetPositionType(constants.PositionType_OnBuffer). SetPositionRefId(fmt.Sprintf("%d", i)). SetTempSlotNo(i). SetStatus(constants.JobStatus_Processing) } else { // 正常流程:工件在接驳台 jobCreate = jobCreate. SetPositionType(constants.PositionType_OnDock). SetPositionRefId(fmt.Sprintf("%d:%d", dockNo, slotIdx)). SetDockNo(dockNo). SetDockSlotNo(slotIdx). SetStatus(constants.JobStatus_Created) } _, err = jobCreate.Save(ctx) if err != nil { slog.Error("创建工件失败", "id", i, "error", err) os.Exit(1) } } if *flagStep > 0 { fmt.Printf("已创建 %d 个工件(跳过前置步骤,从 step %d 开始,暂存台位置)\n", *flagJobs, *flagStep) } else { fmt.Printf("已创建 %d 个工件 \n", *flagJobs) } // 6. 启动工单 if err := svcCtx.OrderProcessor.StartOrder(ctx, order.ID); err != nil { slog.Error("启动工单失败", "error", err) os.Exit(1) } slog.Info("工单已启动", "orderId", order.ID) // 7. 轮询直到全部完成 deadline := time.Now().Add(*flagTimeout) ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() done := false for !done && time.Now().Before(deadline) { <-ticker.C jobs, err := svcCtx.EntClient.Job.Query(). Where(job.WorkOrderIdEQ(order.ID)). All(ctx) if err != nil { continue } allDone := true for _, j := range jobs { if string(j.Status) != string(constants.JobStatus_Completed) && string(j.Status) != string(constants.JobStatus_Scrapped) { allDone = false } } if allDone { done = true } } if !done { fmt.Println("超时!") } else { fmt.Println("\n===== 全部完成 =====") } fmt.Printf("共运行 %d 个工件\n", *flagJobs) } // parseIntList 解析逗号分隔的整数列表 func parseIntList(s string) []int { parts := strings.Split(s, ",") var result []int for _, p := range parts { p = strings.TrimSpace(p) if n, err := strconv.Atoi(p); err == nil && n > 0 { result = append(result, n) } } return result }