组轴式执行引擎 ✅ 多线程调度组装组件

整体设计方案

核心设计准则

  1. 实体分层清晰、简洁
    • 分层:DB实体(仅数据持久层)→业务DTO(组件之间传输)
    • 数据库实体仅在本地数据库组件内部使用,向外输出统一业务 DTO,避免全局到处引用数据库实体;杜绝大而全的实体,字段按需精简。
  2. 业务目标(3C 电子组装产线:SMT 贴片 AX01、回流焊 AX02、整机组装 AX03、功能检测 AX04)
    • 组轴执行引擎(组支轴模式)
    • 多线程调度 + 多任务执行结果统一汇总组装
    • 固定 Excel 模板导入导出(物料、多级 BOM,3C 物料组别 15/16/17 校验)
    • 原生 GDI + 自定义图表控件(产量柱状图、设备负载仪表盘报表)
    • ERP 物料 + BOM 主子分层树形协同场景
    • SQLite 本地文件数据库可视化管理组件
  3. 架构规范
    • 所有业务封装独立组件,消除窗体零散 Click 业务代码;
    • WinForm 窗体只负责 UI 交互展示,不编写业务逻辑;
    • 组件通过全局事件总线解耦通信,减少硬依赖;
  4. 技术栈:.NET Framework4.8 WinForm、SqlSugar、NPOI、原生 GDI+、Task 异步并发

项目目录结构

plaintext

复制代码
3C_MES_WinStationClient
├─ App.config
├─ Program.cs
├─ DataTransfer            # 【业务DTO,组件通用传输对象,轻量化】
│    AxisDto.cs
│    MaterialDto.cs
│    BomNodeDto.cs
│    ScheduleResultDto.cs
│    ChartStatDto.cs
├─ Components
│  ├─ LocalDbComponent          # 本地SQLite文件数据库模块
│  │     DatabaseEntity/        # 数据库实体,仅限本组件内部访问
│  │        AxisDbEntity.cs
│  │        MaterialDbEntity.cs
│  │        BomDbEntity.cs
│  │        ScheduleLogDbEntity.cs
│  │     SqliteDataProvider.cs  # 数据提供层,DbEntity→DTO转换
│  │     DbManageForm.cs        # 数据库可视化管理窗体
│  ├─ AxisEngineComponent       # 组轴执行引擎
│  │     AxisExecuteEngine.cs
│  ├─ MultiScheduleComponent    # 多线程调度+结果组装
│  │     ScheduleTaskScheduler.cs
│  ├─ BomCooperComponent        # ERP物料BOM树形协同
│  │     BomTreeBuilder.cs
│  ├─ ExcelTemplateComponent   # 固定Excel模板导入导出
│  │     FixedExcelHelper.cs
│  ├─ GdiChartComponent         # GDI自定义绘图控件
│  │     BaseGdiRender.cs
│  │     OutputBarChart.cs
│  │     DeviceLoadGauge.cs
│  ├─ ApiClientComponent        # 对接 IFactoryMom5.Api-dev
│  ├─ IotMqttComponent          # 对接iot-master物联网采集
│  └─ GlobalEventBus
│       GlobalEventBus.cs
├─ Forms                       # UI窗体,只处理界面交互
│    MainFrm
│    AxisScheduleFrm
│    BomManageFrm
│    StatReportFrm
├─ Utils
└─ Resources 内置Excel标准模板

一、通用传输 DTO(DataTransfer 文件夹)

DataTransfer/AxisDto.cs

csharp

运行

复制代码
namespace DataTransfer
{
    /// <summary>产线组轴 业务传输DTO</summary>
    public class AxisDto
    {
        public string AxisCode { get; set; }
        public string AxisName { get; set; }
        public string LinkedGroup { get; set; }
        public string RunState { get; set; }
        public string BindProcess { get; set; }
    }
}

DataTransfer/MaterialDto.cs

csharp

运行

复制代码
namespace DataTransfer
{
    /// <summary>物料 DTO,ERP物料同步、Excel导入共用</summary>
    public class MaterialDto
    {
        public string MatCode { get; set; }
        public string MatName { get; set; }
        public string Spec { get; set; }
        public string Unit { get; set; }
        public decimal StockQty { get; set; }
        public int GroupNo { get; set; } //15原材料/16半成品/17成品
        public string MatType { get; set; }
    }
}

DataTransfer/BomNodeDto.cs

csharp

运行

复制代码
using System.Collections.Generic;
namespace DataTransfer
{
    /// <summary>BOM树形节点DTO,主子分层结构</summary>
    public class BomNodeDto
    {
        public long Id { get; set; }
        public string BomNo { get; set; }
        public string MaterialCode { get; set; }
        public long ParentNodeId { get; set; }
        public int BomLevel { get; set; }
        public decimal UseQty { get; set; }
        public string BindAxisCode { get; set; }
        public List<BomNodeDto> ChildNodes { get; set; } = new List<BomNodeDto>();
    }
}

DataTransfer/ScheduleResultDto.cs

csharp

运行

复制代码
using System;
namespace DataTransfer
{
    /// <summary>多线程调度完成后组装汇总结果DTO</summary>
    public class ScheduleResultDto
    {
        public string ScheduleMode { get; set; }
        public string TargetInfo { get; set; }
        public string Operator { get; set; }
        public string FullMergeLog { get; set; }
        public DateTime OperTime { get; set; }
    }
}

DataTransfer/ChartStatDto.cs

csharp

运行

复制代码
namespace DataTransfer
{
    /// <summary>GDI图表统计数据DTO</summary>
    public class ChartStatDto
    {
        public string WorkNo { get; set; }
        public decimal PlanQty { get; set; }
        public decimal ActualQty { get; set; }
    }
}

二、组件 1:LocalDbComponent 本地 SQLite 数据库模块

Components/LocalDbComponent/DatabaseEntity/AxisDbEntity.cs

csharp

运行

复制代码
namespace Components.LocalDbComponent.DatabaseEntity
{
    /// <summary>数据库实体【仅本组件内部使用】</summary>
    internal class AxisDbEntity
    {
        public long Id { get; set; }
        public string AxisCode { get; set; }
        public string AxisName { get; set; }
        public string LinkedGroup { get; set; }
        public string RunState { get; set; }
        public string BindProcess { get; set; }
    }
}

Components/LocalDbComponent/DatabaseEntity/MaterialDbEntity.cs

csharp

运行

复制代码
using System;
namespace Components.LocalDbComponent.DatabaseEntity
{
    internal class MaterialDbEntity
    {
        public long Id { get; set; }
        public string MatCode { get; set; }
        public string MatName { get; set; }
        public string Spec { get; set; }
        public string Unit { get; set; }
        public decimal StockQty { get; set; }
        public int GroupNo { get; set; }
        public string MatType { get; set; }
        public DateTime CreateTime { get; set; }
    }
}

Components/LocalDbComponent/DatabaseEntity/BomDbEntity.cs

csharp

运行

复制代码
namespace Components.LocalDbComponent.DatabaseEntity
{
    internal class BomDbEntity
    {
        public long Id { get; set; }
        public string BomNo { get; set; }
        public string MaterialCode { get; set; }
        public long ParentNodeId { get; set; }
        public int BomLevel { get; set; }
        public decimal UseQty { get; set; }
        public string BindAxisCode { get; set; }
        public int SortIndex { get; set; }
    }
}

Components/LocalDbComponent/DatabaseEntity/ScheduleLogDbEntity.cs

csharp

运行

复制代码
using System;
namespace Components.LocalDbComponent.DatabaseEntity
{
    internal class ScheduleLogDbEntity
    {
        public long Id { get; set; }
        public string ScheduleMode { get; set; }
        public string TargetInfo { get; set; }
        public string Operator { get; set; }
        public string FullMergeLog { get; set; }
        public DateTime OperTime { get; set; }
    }
}

Components/LocalDbComponent/SqliteDataProvider.cs

核心:内部 DbEntity ↔ 对外 DTO 转换,上层业务看不到数据库实体

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using SqlSugar;
using Components.LocalDbComponent.DatabaseEntity;
using DataTransfer;
using Components.GlobalEventBus;

namespace Components.LocalDbComponent
{
    public static class SqliteDataProvider
    {
        private static SqlSugarClient _db;
        private static readonly string _dbFileName;

        static SqliteDataProvider()
        {
            _dbFileName = ConfigurationManager.AppSettings["SqliteDbFileName"];
            string fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbFileName);
            string connStr = ConfigurationManager.ConnectionStrings["SqliteConn"]
                .Replace("{DbPath}", fullPath);

            _db = new SqlSugarClient(new ConnectionConfig
            {
                ConnectionString = connStr,
                DbType = DbType.Sqlite,
                IsAutoCloseConnection = true
            });
            InitDatabaseTables();
        }

        private static void InitDatabaseTables()
        {
            //自动创建数据表(内部实体)
            _db.CodeFirst.InitTables(
                typeof(AxisDbEntity),
                typeof(MaterialDbEntity),
                typeof(BomDbEntity),
                typeof(ScheduleLogDbEntity));
            InitBaseAxisData();
        }

        //初始化3C标准4条产线组轴
        private static void InitBaseAxisData()
        {
            var axisList = new List<AxisDbEntity>()
            {
                new AxisDbEntity{AxisCode="AX01",AxisName="SMT贴片",LinkedGroup="LINK01",RunState="Idle",BindProcess="锡膏印刷、元件贴片"},
                new AxisDbEntity{AxisCode="AX02",AxisName="回流焊",LinkedGroup="LINK01",RunState="Idle"},
                new AxisDbEntity{AxisCode="AX03",AxisName="整机组装",LinkedGroup="LINK02",RunState="Idle"},
                new AxisDbEntity{AxisCode="AX04",AxisName="功能检测",LinkedGroup="LINK02",RunState="Idle"}
            };
            _db.Insertable(axisList).ExecuteCommand();
        }

        #region 对外公开接口【返回DTO】
        public static List<AxisDto> QueryAllAxis()
        {
            var dbList = _db.Queryable<AxisDbEntity>().ToList();
            List<AxisDto> result = new List<AxisDto>();
            foreach (var item in dbList)
            {
                result.Add(new AxisDto()
                {
                    AxisCode = item.AxisCode,
                    AxisName = item.AxisName,
                    LinkedGroup = item.LinkedGroup,
                    RunState = item.RunState,
                    BindProcess = item.BindProcess
                });
            }
            return result;
        }

        public static bool UpdateAxisState(AxisDto dto)
        {
            var entity = _db.Queryable<AxisDbEntity>()
                .Where(x => x.AxisCode == dto.AxisCode).First();
            if (entity == null) return false;
            entity.RunState = dto.RunState;
            return _db.Updateable(entity).ExecuteCommand() > 0;
        }

        public static List<MaterialDto> QueryAllMaterial()
        {
            var dbList = _db.Queryable<MaterialDbEntity>().ToList();
            List<MaterialDto> dtoList = new List<MaterialDto>();
            foreach (var m in dbList)
            {
                dtoList.Add(new MaterialDto()
                {
                    MatCode = m.MatCode,
                    MatName = m.MatName,
                    Spec = m.Spec,
                    Unit = m.Unit,
                    StockQty = m.StockQty,
                    GroupNo = m.GroupNo,
                    MatType = m.MatType
                });
            }
            return dtoList;
        }

        //批量物料入库(Excel导入调用)
        public static bool BatchInsertMaterial(List<MaterialDto> dtoList)
        {
            return TransactionExecute(() =>
            {
                List<MaterialDbEntity> dbEntityList = new List<MaterialDbEntity>();
                foreach (var dto in dtoList)
                {
                    dbEntityList.Add(new MaterialDbEntity()
                    {
                        MatCode = dto.MatCode,
                        MatName = dto.MatName,
                        Spec = dto.Spec,
                        Unit = dto.Unit,
                        StockQty = dto.StockQty,
                        GroupNo = dto.GroupNo,
                        MatType = dto.MatType,
                        CreateTime = DateTime.Now
                    });
                }
                _db.Insertable(dbEntityList).ExecuteCommand();
            });
        }

        //保存调度汇总日志
        public static bool SaveScheduleLog(ScheduleResultDto dto)
        {
            var logEntity = new ScheduleLogDbEntity()
            {
                ScheduleMode = dto.ScheduleMode,
                TargetInfo = dto.TargetInfo,
                Operator = dto.Operator,
                FullMergeLog = dto.FullMergeLog,
                OperTime = dto.OperTime
            };
            return _db.Insertable(logEntity).ExecuteCommand() > 0;
        }
        #endregion

        #region 事务、数据库运维方法(可视化窗体调用)
        public static bool TransactionExecute(Action action)
        {
            try
            {
                _db.Ado.BeginTran();
                action.Invoke();
                _db.Ado.CommitTran();
                return true;
            }
            catch (Exception ex)
            {
                _db.Ado.RollbackTran();
                GlobalEventBus.PublishLog($"事务执行失败:{ex.Message}");
                return false;
            }
        }

        public static bool BackupDatabase(string savePath)
        {
            try
            {
                string sourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbFileName);
                File.Copy(sourcePath, savePath, true);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public static void ClearScheduleLog()
        {
            _db.Deleteable<ScheduleLogDbEntity>().Where(x => x.Id > 0).ExecuteCommand();
        }

        public static string GetDbFullPath() => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbFileName);
        #endregion
    }
}

Components/LocalDbComponent/DbManageForm.cs(数据库可视化窗口)

csharp

运行

复制代码
using System;
using System.Windows.Forms;

namespace Components.LocalDbComponent
{
    public partial class DbManageForm : Form
    {
        public DbManageForm()
        {
            InitializeComponent();
            lblDbPath.Text = $"数据库路径:{SqliteDataProvider.GetDbFullPath()}";
            RefreshStatistics();
        }

        private void RefreshStatistics()
        {
            var matCount = SqliteDataProvider.QueryAllMaterial().Count;
            var axisCount = SqliteDataProvider.QueryAllAxis().Count;
            txtStat.Text = $"物料总数:{matCount}\r\n产线组轴数量:{axisCount}";
        }

        private void btnBackup_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "*.db";
            sfd.FileName = $"MES数据库备份_{DateTime.Now:yyyyMMdd}.db";
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                bool res = SqliteDataProvider.BackupDatabase(sfd.FileName);
                MessageBox.Show(res ? "备份成功" : "备份失败");
                RefreshStatistics();
            }
        }

        private void btnClearLog_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("确认清空调度日志?数据不可恢复", "警告", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
                return;
            SqliteDataProvider.ClearScheduleLog();
            MessageBox.Show("日志已清空");
            RefreshStatistics();
        }
    }
}

三、组件 2:AxisEngineComponent 组轴执行引擎

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Components.LocalDbComponent;
using Components.GlobalEventBus;
using DataTransfer;
using System.Configuration;

namespace Components.AxisEngineComponent
{
    public class AxisExecuteEngine
    {
        private readonly int _simDelayMs;
        private readonly Dictionary<string, AxisDto> _axisCache = new Dictionary<string, AxisDto>();

        public AxisExecuteEngine()
        {
            _simDelayMs = int.Parse(ConfigurationManager.AppSettings["AxisSimDelayMs"]);
            LoadAxisCache();
        }

        private void LoadAxisCache()
        {
            var list = SqliteDataProvider.QueryAllAxis();
            _axisCache.Clear();
            foreach (var dto in list)
            {
                _axisCache[dto.AxisCode] = dto;
            }
        }

        /// <summary>单组轴执行加工工序,返回完整执行日志</summary>
        public string RunSingleAxisProcess(string axisCode)
        {
            if (!_axisCache.ContainsKey(axisCode) || _axisCache[axisCode].RunState != "Idle")
                return $"【{axisCode}】设备当前繁忙,无法启动工序";

            AxisDto targetAxis = _axisCache[axisCode];
            targetAxis.RunState = "Running";
            SqliteDataProvider.UpdateAxisState(targetAxis);
            GlobalEventBus.PublishAxisState(axisCode, "开始执行工序");

            List<string> logBuffer = new List<string>();
            logBuffer.Add($"=====组轴{axisCode}【{targetAxis.AxisName}】工序开始=====");
            string[] processes = targetAxis.BindProcess?.Split('、') ?? new[] { "标准加工工序" };

            foreach (var proc in processes)
            {
                Thread.Sleep(_simDelayMs);
                logBuffer.Add($"工序:{proc} 执行完成");
            }

            targetAxis.RunState = "Idle";
            SqliteDataProvider.UpdateAxisState(targetAxis);
            logBuffer.Add($"=====组轴{axisCode}工序全部执行完毕=====\r\n");
            string fullLog = string.Join("\r\n", logBuffer);
            GlobalEventBus.PublishLog(fullLog);
            return fullLog;
        }

        public void PauseAxis(string axisCode)
        {
            if (_axisCache.TryGetValue(axisCode, out var axis) && axis.RunState == "Running")
            {
                axis.RunState = "Paused";
                SqliteDataProvider.UpdateAxisState(axis);
                GlobalEventBus.PublishLog($"组轴{axisCode}已手动暂停");
            }
        }

        public void EmergencyStopLinkedGroup(string linkGroup)
        {
            var groupAxisList = _axisCache.Values.Where(x => x.LinkedGroup == linkGroup).ToList();
            foreach (var ax in groupAxisList)
            {
                ax.RunState = "Error";
                SqliteDataProvider.UpdateAxisState(ax);
            }
            GlobalEventBus.PublishLog($"联控分组 {linkGroup} 紧急停机");
        }

        public List<string> GetAllAxisCode() => _axisCache.Keys.ToList();
        public List<string> GetAllLinkedGroup() => _axisCache.Values.Select(x => x.LinkedGroup).Distinct().ToList();
    }
}

四、组件 3:MultiScheduleComponent 多线程调度 + 结果组装

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Components.AxisEngineComponent;
using Components.LocalDbComponent;
using Components.GlobalEventBus;
using DataTransfer;

namespace Components.MultiScheduleComponent
{
    public class ScheduleTaskScheduler
    {
        private readonly AxisExecuteEngine _axisEngine;

        public ScheduleTaskScheduler(AxisExecuteEngine engine)
        {
            _axisEngine = engine;
        }

        /// <summary>单轴异步调度</summary>
        public async Task<ScheduleResultDto> RunSingleTask(string axisCode, string operatorName)
        {
            var task = Task.Run(() => _axisEngine.RunSingleAxisProcess(axisCode));
            await task;
            string logText = task.Result;

            var resultDto = new ScheduleResultDto()
            {
                ScheduleMode = "SingleAxis",
                TargetInfo = axisCode,
                Operator = operatorName,
                FullMergeLog = logText,
                OperTime = DateTime.Now
            };
            SqliteDataProvider.SaveScheduleLog(resultDto);
            GlobalEventBus.PublishScheduleComplete(logText);
            return resultDto;
        }

        /// <summary>多轴并行调度,多线程并发执行,统一汇总所有线程输出日志</summary>
        public async Task<ScheduleResultDto> RunCombineParallelTask(List<string> axisCodes, string operatorName)
        {
            List<Task<string>> taskList = new List<Task<string>>();
            foreach (var ax in axisCodes)
            {
                taskList.Add(Task.Run(() => _axisEngine.RunSingleAxisProcess(ax)));
            }
            await Task.WhenAll(taskList);

            string mergeLog = "【多轴并行调度汇总】\r\n"
                + string.Join("\r\n----------------\r\n", taskList.Select(t => t.Result));

            var resultDto = new ScheduleResultDto()
            {
                ScheduleMode = "CombineParallel",
                TargetInfo = string.Join("、", axisCodes),
                Operator = operatorName,
                FullMergeLog = mergeLog,
                OperTime = DateTime.Now
            };
            SqliteDataProvider.SaveScheduleLog(resultDto);
            GlobalEventBus.PublishScheduleComplete(mergeLog);
            return resultDto;
        }

        /// <summary>流水线顺序联控调度</summary>
        public async Task<ScheduleResultDto> RunLinkedPipelineTask(string linkGroup, string operatorName)
        {
            var axisList = _axisEngine.GetAllAxisCode()
                .Where(c => _axisEngine.GetAllLinkedGroup().Contains(linkGroup)).ToList();
            string totalLog = $"【流水线联控 {linkGroup}】执行日志\r\n";

            foreach (var ax in axisList)
            {
                string log = await Task.Run(() => _axisEngine.RunSingleAxisProcess(ax));
                totalLog += log;
                if (log.Contains("过载预警"))
                {
                    _axisEngine.EmergencyStopLinkedGroup(linkGroup);
                    totalLog += ">>>> 设备异常,流水线强制停机 <<<<\r\n";
                    break;
                }
            }

            var resultDto = new ScheduleResultDto()
            {
                ScheduleMode = "LinkedPipeline",
                TargetInfo = linkGroup,
                Operator = operatorName,
                FullMergeLog = totalLog,
                OperTime = DateTime.Now
            };
            SqliteDataProvider.SaveScheduleLog(resultDto);
            GlobalEventBus.PublishScheduleComplete(totalLog);
            return resultDto;
        }

        public void StopAllTask()
        {
            foreach (var axCode in _axisEngine.GetAllAxisCode())
            {
                _axisEngine.PauseAxis(axCode);
            }
        }
    }
}

五、组件 4:ExcelTemplateComponent 固定 Excel 模板导入导出

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using Components.LocalDbComponent;
using DataTransfer;

namespace Components.ExcelTemplateComponent
{
    public static class FixedExcelHelper
    {
        private static readonly List<string> MaterialHeader = new List<string>()
        { "物料编码", "物料名称", "规格型号", "单位", "库存数量", "组别(15/16/17)", "物料类型" };

        public static void DownloadMaterialBlankTemplate()
        {
            SaveFileDialog sfd = new SaveFileDialog
            {
                Filter = "Excel文件(*.xlsx)|*.xlsx",
                FileName = "3C电子物料标准模板.xlsx"
            };
            if (sfd.ShowDialog() != DialogResult.OK) return;
            CreateTemplateFile(MaterialHeader, sfd.FileName);
            MessageBox.Show("空白物料模板下载完成");
        }

        private static void CreateTemplateFile(List<string> header, string path)
        {
            using IWorkbook wb = new XSSFWorkbook();
            ISheet sheet = wb.CreateSheet("标准模板");
            IRow headerRow = sheet.CreateRow(0);
            for (int i = 0; i < header.Count; i++)
            {
                headerRow.CreateCell(i).SetCellValue(header[i]);
                sheet.SetColumnWidth(i, 24 * 256);
            }
            using var fs = new FileStream(path, FileMode.Create);
            wb.Write(fs);
        }

        /// <summary>导入物料模板,强制校验组别15/16/17</summary>
        public static (bool success, string msg, List<MaterialDto> dataList) ImportMaterialExcel()
        {
            List<MaterialDto> resultList = new List<MaterialDto>();
            OpenFileDialog ofd = new OpenFileDialog { Filter = "*.xlsx" };
            if (ofd.ShowDialog() != DialogResult.OK)
                return (false, "取消文件选择", resultList);

            try
            {
                using var fs = new FileStream(ofd.FileName, FileMode.Open);
                IWorkbook wb = new XSSFWorkbook(fs);
                ISheet sheet = wb.GetSheetAt(0);
                for (int r = 1; r <= sheet.LastRowNum; r++)
                {
                    IRow row = sheet.GetRow(r);
                    if (row == null) continue;

                    int groupNo = int.TryParse(GetCellText(row, 5), out int g) ? g : 0;
                    if (groupNo < 15 || groupNo > 17)
                        return (false, $"第{r + 1}行物料组别必须为15/16/17", resultList);

                    var dto = new MaterialDto()
                    {
                        MatCode = GetCellText(row, 0),
                        MatName = GetCellText(row, 1),
                        Spec = GetCellText(row, 2),
                        Unit = GetCellText(row, 3),
                        StockQty = decimal.TryParse(GetCellText(row, 4), out var q) ? q : 0,
                        GroupNo = groupNo,
                        MatType = GetCellText(row, 6)
                    };
                    resultList.Add(dto);
                }
                bool insertOk = SqliteDataProvider.BatchInsertMaterial(resultList);
                return (insertOk, $"成功读取{resultList.Count}条物料", resultList);
            }
            catch (Exception ex)
            {
                return (false, $"解析异常:{ex.Message}", resultList);
            }
        }

        private static string GetCellText(IRow row, int idx)
        {
            var cell = row.GetCell(idx);
            return cell == null ? "" : cell.ToString().Trim();
        }
    }
}

六、组件 5:GlobalEventBus 全局事件总线(模块解耦)

csharp

运行

复制代码
using System;

namespace Components.GlobalEventBus
{
    public static class GlobalEventBus
    {
        public delegate void LogMessageHandler(string logContent);
        public delegate void AxisStateHandler(string axisCode, string info);
        public delegate void ScheduleCompleteHandler(string fullLog);
        public delegate void UiRefreshHandler();

        public static event LogMessageHandler OnLogOutput;
        public static event AxisStateHandler OnAxisStateChanged;
        public static event ScheduleCompleteHandler OnScheduleFinish;
        public static event UiRefreshHandler OnRequestUiRefresh;

        public static void PublishLog(string text)
        {
            string log = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | {text}";
            OnLogOutput?.Invoke(log);
        }

        public static void PublishAxisState(string axisCode, string msg)
        {
            PublishLog($"【组轴{axisCode}】{msg}");
            OnAxisStateChanged?.Invoke(axisCode, msg);
        }

        public static void PublishScheduleComplete(string fullLog)
        {
            OnScheduleFinish?.Invoke(fullLog);
        }

        public static void PublishUiRefresh()
        {
            OnRequestUiRefresh?.Invoke();
        }
    }
}

七、窗体调用示例(窗体仅做 UI 交互,无业务逻辑)

csharp

运行

复制代码
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Components.AxisEngineComponent;
using Components.MultiScheduleComponent;
using Components.ExcelTemplateComponent;
using Components.GlobalEventBus;

namespace Forms
{
    public partial class AxisScheduleFrm : Form
    {
        private readonly AxisExecuteEngine _axisEngine;
        private readonly ScheduleTaskScheduler _scheduler;

        public AxisScheduleFrm()
        {
            _axisEngine = new AxisExecuteEngine();
            _scheduler = new ScheduleTaskScheduler(_axisEngine);
            InitializeComponent();
            BindEventBus();
            InitUiSource();
        }

        private void BindEventBus()
        {
            GlobalEventBus.OnLogOutput += log => txtLog.AppendText(log + "\r\n");
            GlobalEventBus.OnRequestUiRefresh += RefreshData;
        }

        private void InitUiSource()
        {
            var axisCodes = _axisEngine.GetAllAxisCode();
            cboSingleAxis.Items.AddRange(axisCodes.ToArray());
            clbCombineAxis.Items.AddRange(axisCodes.ToArray());
            cboLinkGroup.Items.AddRange(_axisEngine.GetAllLinkedGroup().ToArray());
        }

        private void RefreshData()
        {
            //刷新表格、树形、图表数据源
        }

        private async void btnSingleRun_Click(object sender, EventArgs e)
        {
            if (cboSingleAxis.SelectedItem == null) return;
            await _scheduler.RunSingleTask(cboSingleAxis.Text, "车间操作员");
        }

        private async void btnCombineRun_Click(object sender, EventArgs e)
        {
            List<string> selectAxis = new List<string>();
            for (int i = 0; i < clbCombineAxis.Items.Count; i++)
            {
                if (clbCombineAxis.GetItemChecked(i))
                    selectAxis.Add(clbCombineAxis.Items[i].ToString());
            }
            if (selectAxis.Count == 0)
            {
                MessageBox.Show("请勾选至少一条产线组轴");
                return;
            }
            await _scheduler.RunCombineParallelTask(selectAxis, "车间操作员");
        }

        private void btnDownloadTemplate_Click(object sender, EventArgs e)
        {
            FixedExcelHelper.DownloadMaterialBlankTemplate();
        }

        private void btnImportMaterial_Click(object sender, EventArgs e)
        {
            var res = FixedExcelHelper.ImportMaterialExcel();
            MessageBox.Show(res.msg);
            GlobalEventBus.PublishUiRefresh();
        }

        private void btnOpenDbManager_Click(object sender, EventArgs e)
        {
            new Components.LocalDbComponent.DbManageForm().ShowDialog();
        }
    }
}

架构优势说明

  1. 实体分层清晰简洁
    • 数据库实体 DbEntity 隔离在数据库组件内部,不对外暴露;
    • 上层业务全部使用轻量化 DTO,分层边界明确;避免全局大实体到处引用造成耦合。
  2. 业务全部组件化 消除窗体零散业务代码,组轴引擎、多线程调度、Excel 模板、数据库操作各自独立组件。
  3. 满足核心业务场景 ✅ 组轴式执行引擎 ✅ 多线程调度 + 任务结果统一组装汇总 ✅ 固定 Excel 模板导入导出、3C 物料组别校验 ✅ 预留扩展:BOM 树形协同组件、GDI 自定义图表控件可直接接入 ✅ SQLite 本地文件数据库可视化管理模块
  4. 组件低耦合 使用全局事件总线通信,减少类之间直接实例依赖,方便后续迭代维护。

如果你需要,我可以继续输出:

  1. BOM 树形协同 BomTreeBuilder.cs 完整代码
  2. GDI 柱状图、仪表盘绘图控件完整代码
  3. App.config 配置样板
  4. BOM Excel 导入导出补充代码
相关推荐
码农学院1 小时前
Node.js MongoDB构建户外用品GEO优化内容管理平台
数据库·mongodb·node.js
吴声子夜歌1 小时前
MongoDB 4.x——治理经验
数据库·mongodb
黄华SJ520it2 小时前
门店预约系统开发:提升服务行业数字化转型
前端·数据库·系统开发
万事可爱^3 小时前
Claude 新发布的 Opus 5,系统提示语删了 80%,半价还能逼近 Fable 5
android·服务器·数据库·人工智能·claude
早点睡啊Y3 小时前
深入学LangChain官方文档:Observability 与 Studio——先看清 Agent 到底做了什么
java·数据库·langchain
@insist1233 小时前
信息系统管理工程师-数据层知识点详解:存储、数据库与信息安全
数据库·软考·软件水平考试·信息系统管理工程师·软考信管
她说可以呀3 小时前
Redis集群
数据库·redis·缓存
Lorin 洛林3 小时前
为什么大模型总会“胡说八道“?一文彻底搞懂 RAG 知识库原理!
数据库
其实防守也摸鱼4 小时前
GitHub开源项目破圈方法论:从技术自嗨到生态共赢
服务器·数据库·学习·开源·github·命令行·linux系统