欢迎各位观众大大浏览阅读我的博客,有空麻烦加一下博客主页关注,谢谢
MES/ERP场景,作业协同相关的业务执行事件封装
MES/ERP 作业协同业务执行事件封装 - 模板化多态中间件解决方案
针对 MES/ERP作业协同 核心场景(工单派工、工序协同、资源调度、多岗位 / 多产线联动、异常协同处理等),本次设计模板类 + 多态基类继承 + 组合封装 的标准化中间件方案,实现作业协同业务的模板化配置、多态化扩展、组件化复用 ,同时提供WinForm 可视化界面 + 后台服务 + 标准化 API 组件 ,适配 MES/ERP 作业协同的多场景适配、高扩展、易维护、跨系统对接核心需求。
一、核心设计理念与整体架构
1. MES/ERP 作业协同核心痛点与解决方案
| 作业协同核心痛点 | 技术解决方案 |
|---|---|
| 协同场景多样(派工 / 调度 / 异常 / 联动),代码冗余 | 设计作业协同基类(抽象模板),封装通用逻辑,子类多态实现个性化场景 |
| 业务变更需大量修改代码,扩展性差 | 基于模板方法模式 + 组合模式,通用逻辑固化在基类,子类仅实现个性化逻辑 |
| 多系统(MES/ERP/ 设备 / 工位终端)对接复杂 | 封装标准化 API 组件,提供 RESTful/Grpc/ 本地调用接口,解耦业务与对接层 |
| 需可视化配置 + 后台自动化执行 + 跨端调用 | 分离WinForm 界面层 + 后台服务层 + API 组件层,三层解耦且可独立部署 / 调用 |
| 协同规则多变,需快速适配 | 实现协同模板化配置,支持可视化选择模板、配置参数,无需修改代码 |
2. 核心设计模式融合
本次方案核心融合 4 种设计模式,适配 MES/ERP 作业协同的工程化需求:
- 模板方法模式 :抽象基类封装作业协同通用执行流程(模板方法) ,子类重写个性化步骤(钩子方法);
- 多态 / 继承模式:基于抽象基类实现不同协同场景的子类,通过基类引用实现多态化调用,统一执行入口;
- 组合模式:将基础协同组件(如日志、权限、数据同步、消息推送)组合到基类,实现组件化复用;
- 单例模式:核心服务(模板管理、协同引擎、API 服务)单例化,保证全局唯一且资源复用。
3. 整体架构(四层解耦 + 组件化)

四层核心职责:
- API 组件层:提供标准化 RESTful/Grpc API,支持跨系统、跨终端调用作业协同能力;
- WinForm 界面层:可视化配置协同模板、监控协同执行状态、手动触发协同操作;
- 后台服务层:作业协同自动化执行引擎、模板生命周期管理、后台无阻塞运行、系统托盘;
- 核心业务层:抽象基类(协同模板)+ 多态子类(具体协同场景)+ 组合组件(基础能力),业务核心;
- 基础组件层:封装 MES/ERP 通用基础能力,组件化注入到核心业务层,无需重复开发。
4. 技术栈与前置准备
开发环境
- .NET Framework 4.8(WinForm 兼容性最优 + API 组件成熟)/.NET 6+(跨平台需求可选)
- Visual Studio 2022+
- SQLite(本地配置 / 日志存储)、SQL Server/MySQL(远程 MES/ERP 数据存储)
核心 NuGet 依赖
# 核心ORM(本地+远程数据交互)
Install-Package Dapper
# SQLite本地数据库驱动
Install-Package System.Data.SQLite.Core
# JSON序列化(模板配置/API参数)
Install-Package Newtonsoft.Json
# WebAPI组件(自托管,无需IIS)
Install-Package Owin
Install-Package Microsoft.Owin.Hosting
Install-Package Microsoft.Owin.Cors
Install-Package Microsoft.Owin.StaticFiles
# Grpc可选(高性能跨语言API)
Install-Package Grpc.AspNetCore
# 日志组件
Install-Package Serilog
Install-Package Serilog.Sinks.File
# 定时任务(后台协同调度)
Install-Package Hangfire
# 消息推送(作业协同通知)
Install-Package SignalR.Owin
二、核心枚举与基础模型(MES/ERP 作业协同标准化)
封装 MES/ERP 作业协同通用枚举和基础模型,作为模板化、多态化、API 化的基础,保证全流程数据标准化。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Dapper;
using System.Data.SQLite;
using System.IO;
using Newtonsoft.Json;
using Owin;
using Microsoft.Owin.Hosting;
using System.Web.Http;
using Microsoft.Owin.Cors;
using Serilog;
#region MES/ERP作业协同核心枚举
/// <summary>
/// 作业协同类型(MES/ERP核心场景,对应多态子类)
/// </summary>
public enum WorkCollabType
{
WorkOrderDispatch, // 工单派工协同(ERP->MES->工位)
ProcessCollab, // 工序流转协同(上工序->下工序,多工序联动)
ResourceDispatch, // 资源调度协同(设备/人员/物料调度)
ExceptionHandle, // 异常处理协同(质量/设备/物料异常,多岗位联动)
LineCollab, // 多产线协同(跨产线工单/工序/资源联动)
ShiftHandover // 交接班协同(班次生产数据/状态/问题协同)
}
/// <summary>
/// 作业协同状态(模板执行生命周期)
/// </summary>
public enum WorkCollabStatus
{
UnInited, // 未初始化
Ready, // 就绪(可执行)
Running, // 执行中
Completed, // 执行完成
Paused, // 暂停
Exception, // 执行异常
Cancelled // 已取消
}
/// <summary>
/// 协同模板执行模式
/// </summary>
public enum CollabTemplateExecuteMode
{
Manual, // 手动触发(UI/API)
Auto, // 自动触发(定时/事件触发)
Triggered // 触发式(依赖前置条件满足)
}
/// <summary>
/// API调用返回状态
/// </summary>
public enum ApiResultStatus
{
Success = 200, // 成功
Error = 500, // 服务器错误
ParamError = 400, // 参数错误
NotFound = 404 // 资源不存在
}
#endregion
#region 基础模型(标准化)
/// <summary>
/// 通用API返回模型(全接口统一)
/// </summary>
/// <typeparam name="T">返回数据类型</typeparam>
public class ApiResult<T>
{
public ApiResultStatus Status { get; set; }
public string Msg { get; set; }
public T Data { get; set; }
public DateTime Time { get; set; } = DateTime.Now;
// 快捷方法
public static ApiResult<T> Ok(T data, string msg = "操作成功") => new ApiResult<T> { Status = ApiResultStatus.Success, Msg = msg, Data = data };
public static ApiResult<T> Error(string msg = "操作失败") => new ApiResult<T> { Status = ApiResultStatus.Error, Msg = msg };
public static ApiResult<T> ParamError(string msg = "参数错误") => new ApiResult<T> { Status = ApiResultStatus.ParamError, Msg = msg };
}
/// <summary>
/// MES/ERP基础作业信息(工单/工序/资源通用)
/// </summary>
public class BaseWorkInfo
{
public string WorkNo { get; set; } // 作业编号(工单号/工序号/资源号)
public string ProductCode { get; set; } // 产品编码
public string LineCode { get; set; } // 产线编码
public string StationCode { get; set; } // 工位编码
public string UserId { get; set; } // 操作人/负责人ID
public string UserName { get; set; } // 操作人/负责人名称
public DateTime CreateTime { get; set; } = DateTime.Now; // 创建时间
}
/// <summary>
/// 作业协同参数模型(模板执行/API调用通用参数)
/// </summary>
public class WorkCollabParam
{
public Dictionary<string, string> Params { get; set; } = new Dictionary<string, string>(); // 自定义参数(键值对,适配所有场景)
public CancellationToken Cts { get; set; } // 取消令牌(流程控制)
public string TemplateId { get; set; } // 关联模板ID
public string CollabId { get; set; } // 协同实例ID
}
/// <summary>
/// 作业协同执行日志(模板执行/API调用日志)
/// </summary>
public class WorkCollabLog
{
public string LogId { get; set; } = Guid.NewGuid().ToString("N");
public string CollabId { get; set; } // 协同实例ID
public string TemplateId { get; set; } // 模板ID
public WorkCollabType CollabType { get; set; } // 协同类型
public WorkCollabStatus CollabStatus { get; set; } // 执行状态
public string ExecuteMsg { get; set; } // 执行信息
public DateTime ExecuteTime { get; set; } = DateTime.Now; // 执行时间
public string ExecuteUserId { get; set; } // 执行人ID
public string ExecuteUserName { get; set; } // 执行人名称
}
#endregion
三、核心设计:模板化多态基类 + 组合封装
1. 作业协同抽象基类(模板核心,定义通用流程)
基于模板方法模式 设计抽象基类(模板类) ,封装作业协同的通用执行流程(模板方法) ,定义个性化步骤的抽象钩子方法 ,子类仅需重写钩子方法即可实现具体协同场景,通用逻辑无需重复开发。
同时采用组合模式 ,将基础组件(日志、数据同步、消息推送、API 通知)组合注入到基类,实现组件化复用,避免子类重复依赖。
#region 基础组件接口(组合封装,面向接口编程)
/// <summary>
/// 日志组件接口(组合到基类)
/// </summary>
public interface ILogComponent
{
void Info(string msg, string collabId = null);
void Error(string msg, Exception ex = null, string collabId = null);
void SaveCollabLog(WorkCollabLog log);
}
/// <summary>
/// 数据同步组件接口(MES/ERP/本地库数据同步,组合到基类)
/// </summary>
public interface IDataSyncComponent
{
Task<T> GetDataFromErp<T>(string sql, object param = null); // 从ERP获取数据
Task<T> GetDataFromMes<T>(string sql, object param = null); // 从MES获取数据
Task<bool> SyncDataToLocal<T>(T model, string tableName); // 同步数据到本地库
Task<bool> SyncCollabStatus(WorkCollabStatus status, string collabId); // 同步协同状态到MES/ERP
}
/// <summary>
/// 消息推送组件接口(作业协同通知,组合到基类)
/// </summary>
public interface IMsgPushComponent
{
Task<bool> PushToUser(string userId, string title, string content); // 推送给指定用户
Task<bool> PushToStation(string stationCode, string title, string content); // 推送给指定工位
Task<bool> PushToLine(string lineCode, string title, string content); // 推送给指定产线
}
#endregion
#region 作业协同抽象基类(模板核心,抽象模板)
/// <summary>
/// MES/ERP作业协同抽象基类(模板类)
/// 核心:封装通用执行流程(模板方法),定义抽象钩子方法(子类实现个性化),组合基础组件
/// </summary>
public abstract class BaseWorkCollabTemplate
{
#region 组合组件(面向接口,依赖注入)
protected readonly ILogComponent _logComponent;
protected readonly IDataSyncComponent _dataSyncComponent;
protected readonly IMsgPushComponent _msgPushComponent;
#endregion
#region 模板通用属性(所有协同场景共享)
public string TemplateId { get; set; } = Guid.NewGuid().ToString("N"); // 模板唯一ID
public string TemplateName { get; set; } // 模板名称(如"工单派工协同模板-LINE001")
public WorkCollabType CollabType { get; set; } // 协同类型(绑定具体场景)
public CollabTemplateExecuteMode ExecuteMode { get; set; } // 执行模式
public Dictionary<string, string> TemplateConfig { get; set; } = new Dictionary<string, string>(); // 模板配置参数
public WorkCollabStatus CurrentStatus { get; protected set; } = WorkCollabStatus.UnInited; // 当前执行状态
public string CollabInstanceId { get; protected set; } // 本次协同实例ID
#endregion
#region 构造函数(注入组合组件,模板初始化)
protected BaseWorkCollabTemplate(ILogComponent logComponent, IDataSyncComponent dataSyncComponent, IMsgPushComponent msgPushComponent)
{
// 注入组合组件
_logComponent = logComponent ?? throw new ArgumentNullException(nameof(logComponent));
_dataSyncComponent = dataSyncComponent ?? throw new ArgumentNullException(nameof(dataSyncComponent));
_msgPushComponent = msgPushComponent ?? throw new ArgumentNullException(nameof(msgPushComponent));
// 模板初始化
InitTemplate();
_logComponent.Info($"【模板初始化】{TemplateName}(ID:{TemplateId},类型:{CollabType})", TemplateId);
}
#endregion
#region 模板方法:通用执行流程(固化,子类不可重写)
/// <summary>
/// 作业协同通用执行流程(模板方法,核心)
/// 流程:初始化实例->参数校验->前置准备->核心执行->后置处理->状态同步->消息推送->日志记录
/// 子类仅需重写抽象钩子方法,无需修改此流程
/// </summary>
/// <param name="param">执行参数</param>
/// <returns>执行结果</returns>
public async Task<bool> ExecuteAsync(WorkCollabParam param)
{
try
{
// 1. 初始化协同实例
InitCollabInstance(param);
_logComponent.Info($"【协同实例启动】{TemplateName},实例ID:{CollabInstanceId}", CollabInstanceId);
// 2. 参数校验(钩子方法,子类实现个性化校验)
if (!await ValidateParamAsync(param))
{
_logComponent.Error($"【参数校验失败】{TemplateName},实例ID:{CollabInstanceId}", null, CollabInstanceId);
UpdateStatus(WorkCollabStatus.Exception);
return false;
}
_logComponent.Info($"【参数校验成功】{TemplateName},实例ID:{CollabInstanceId}", CollabInstanceId);
// 3. 前置准备(钩子方法,子类实现个性化准备:如数据查询、资源检查)
await PreExecuteAsync(param);
_logComponent.Info($"【前置准备完成】{TemplateName},实例ID:{CollabInstanceId}", CollabInstanceId);
// 4. 核心执行(钩子方法,子类实现具体协同业务逻辑,核心)
UpdateStatus(WorkCollabStatus.Running);
var executeResult = await CoreExecuteAsync(param);
if (!executeResult)
{
_logComponent.Error($"【核心执行失败】{TemplateName},实例ID:{CollabInstanceId}", null, CollabInstanceId);
UpdateStatus(WorkCollabStatus.Exception);
return false;
}
_logComponent.Info($"【核心执行完成】{TemplateName},实例ID:{CollabInstanceId}", CollabInstanceId);
// 5. 后置处理(钩子方法,子类实现个性化后置:如数据统计、状态更新)
await PostExecuteAsync(param);
_logComponent.Info($"【后置处理完成】{TemplateName},实例ID:{CollabInstanceId}", CollabInstanceId);
// 6. 同步协同状态到MES/ERP(通用逻辑,组合组件实现)
await _dataSyncComponent.SyncCollabStatus(WorkCollabStatus.Completed, CollabInstanceId);
_logComponent.Info($"【状态同步完成】{TemplateName},实例ID:{CollabInstanceId},状态:已完成", CollabInstanceId);
// 7. 作业协同消息推送(钩子方法,子类实现个性化推送)
await PushCollabMsgAsync(param);
_logComponent.Info($"【消息推送完成】{TemplateName},实例ID:{CollabInstanceId}", CollabInstanceId);
// 8. 更新状态为完成
UpdateStatus(WorkCollabStatus.Completed);
_logComponent.Info($"【协同实例完成】{TemplateName},实例ID:{CollabInstanceId}", CollabInstanceId);
return true;
}
catch (OperationCanceledException)
{
_logComponent.Info($"【协同实例暂停/取消】{TemplateName},实例ID:{CollabInstanceId}", CollabInstanceId);
UpdateStatus(WorkCollabStatus.Cancelled);
return false;
}
catch (Exception ex)
{
_logComponent.Error($"【协同实例执行异常】{TemplateName},实例ID:{CollabInstanceId}", ex, CollabInstanceId);
UpdateStatus(WorkCollabStatus.Exception);
// 异常消息推送
await PushExceptionMsgAsync(param, ex.Message);
return false;
}
finally
{
// 通用最终处理:记录执行日志
await RecordCollabLogAsync(param);
}
}
#endregion
#region 模板通用方法(子类可复用,可重写)
/// <summary>
/// 模板初始化(通用,子类可重写扩展)
/// </summary>
protected virtual void InitTemplate()
{
CurrentStatus = WorkCollabStatus.Ready;
}
/// <summary>
/// 初始化协同实例(每次执行生成唯一实例)
/// </summary>
protected virtual void InitCollabInstance(WorkCollabParam param)
{
CollabInstanceId = param.CollabId ?? Guid.NewGuid().ToString("N");
param.TemplateId = TemplateId;
}
/// <summary>
/// 更新协同状态(通用,包含状态日志)
/// </summary>
/// <param name="status">新状态</param>
protected virtual void UpdateStatus(WorkCollabStatus status)
{
CurrentStatus = status;
_logComponent.Info($"【状态更新】{TemplateName},实例ID:{CollabInstanceId},状态:{status}", CollabInstanceId);
}
/// <summary>
/// 记录协同执行日志(通用,组合日志组件)
/// </summary>
protected virtual async Task RecordCollabLogAsync(WorkCollabParam param)
{
var log = new WorkCollabLog
{
CollabId = CollabInstanceId,
TemplateId = TemplateId,
CollabType = CollabType,
CollabStatus = CurrentStatus,
ExecuteMsg = CurrentStatus == WorkCollabStatus.Completed ? "执行成功" : CurrentStatus == WorkCollabStatus.Exception ? "执行异常" : "已取消",
ExecuteUserId = param.Params.ContainsKey("UserId") ? param.Params["UserId"] : "system",
ExecuteUserName = param.Params.ContainsKey("UserName") ? param.Params["UserName"] : "系统自动"
};
_logComponent.SaveCollabLog(log);
// 同步日志到MES/ERP
await _dataSyncComponent.SyncDataToLocal(log, "WorkCollabLog");
}
/// <summary>
/// 异常消息推送(通用,子类可重写)
/// </summary>
protected virtual async Task PushExceptionMsgAsync(WorkCollabParam param, string errorMsg)
{
// 推送给产线负责人(模板配置中获取负责人ID)
if (TemplateConfig.ContainsKey("LineManagerId"))
{
await _msgPushComponent.PushToUser(TemplateConfig["LineManagerId"], $"【{CollabType}执行异常】", $"{TemplateName}(实例{CollabInstanceId}):{errorMsg}");
}
}
#endregion
#region 抽象钩子方法(子类必须实现,个性化业务)
/// <summary>
/// 参数校验(钩子方法,子类实现个性化参数校验)
/// </summary>
protected abstract Task<bool> ValidateParamAsync(WorkCollabParam param);
/// <summary>
/// 前置准备(钩子方法,子类实现:数据查询、资源检查等)
/// </summary>
protected abstract Task PreExecuteAsync(WorkCollabParam param);
/// <summary>
/// 核心执行(钩子方法,子类实现具体协同业务逻辑,核心)
/// </summary>
protected abstract Task<bool> CoreExecuteAsync(WorkCollabParam param);
/// <summary>
/// 后置处理(钩子方法,子类实现:数据统计、状态更新等)
/// </summary>
protected abstract Task PostExecuteAsync(WorkCollabParam param);
/// <summary>
/// 协同消息推送(钩子方法,子类实现个性化推送:推送给工位/用户/产线)
/// </summary>
protected abstract Task PushCollabMsgAsync(WorkCollabParam param);
#endregion
#region 流程控制方法(通用,模板提供)
/// <summary>
/// 暂停协同实例
/// </summary>
public virtual void Pause()
{
UpdateStatus(WorkCollabStatus.Paused);
}
/// <summary>
/// 恢复协同实例
/// </summary>
public virtual void Resume()
{
UpdateStatus(WorkCollabStatus.Running);
}
/// <summary>
/// 取消协同实例
/// </summary>
public virtual void Cancel()
{
UpdateStatus(WorkCollabStatus.Cancelled);
}
#endregion
}
#endregion
2. 具体协同场景子类(多态实现,个性化业务)
基于抽象基类 实现MES/ERP 核心作业协同场景的子类 ,仅需重写抽象钩子方法 实现个性化业务,通用流程完全复用基类 ,实现代码最小化、扩展最大化。
以下实现工单派工协同 和异常处理协同两个核心子类,其他场景(工序协同、资源调度、多产线协同)可按相同方式快速实现。
#region 具体协同子类1:工单派工协同(ERP->MES->工位)
/// <summary>
/// 工单派工协同子类(多态实现)
/// 场景:ERP工单下发->MES产线派工->工位终端接收派工任务,全流程协同
/// </summary>
public class WorkOrderDispatchCollab : BaseWorkCollabTemplate
{
#region 子类专属配置(工单派工特有)
public string ErpOrderApi { get; set; } // ERP工单查询API地址
public string MesDispatchApi { get; set; } // MES派工API地址
public string StationPushTopic { get; set; } // 工位推送主题
#endregion
#region 构造函数(注入组件+初始化子类属性)
public WorkOrderDispatchCollab(ILogComponent logComponent, IDataSyncComponent dataSyncComponent, IMsgPushComponent msgPushComponent)
: base(logComponent, dataSyncComponent, msgPushComponent)
{
// 初始化模板基础属性
CollabType = WorkCollabType.WorkOrderDispatch;
TemplateName = "工单派工协同模板";
// 从模板配置加载子类专属配置
LoadSubConfig();
}
#endregion
#region 子类专属方法
/// <summary>
/// 加载子类专属配置(从模板通用配置中解析)
/// </summary>
private void LoadSubConfig()
{
if (TemplateConfig.ContainsKey("ErpOrderApi")) ErpOrderApi = TemplateConfig["ErpOrderApi"];
if (TemplateConfig.ContainsKey("MesDispatchApi")) MesDispatchApi = TemplateConfig["MesDispatchApi"];
if (TemplateConfig.ContainsKey("StationPushTopic")) StationPushTopic = TemplateConfig["StationPushTopic"];
}
#endregion
#region 重写抽象钩子方法(实现工单派工个性化业务)
/// <summary>
/// 参数校验:校验工单号、产线号、工位号是否存在
/// </summary>
protected override async Task<bool> ValidateParamAsync(WorkCollabParam param)
{
// 必传参数:WorkNo(工单号)、LineCode(产线号)、StationCode(工位号)
var hasWorkNo = param.Params.ContainsKey("WorkNo") && !string.IsNullOrEmpty(param.Params["WorkNo"]);
var hasLineCode = param.Params.ContainsKey("LineCode") && !string.IsNullOrEmpty(param.Params["LineCode"]);
var hasStationCode = param.Params.ContainsKey("StationCode") && !string.IsNullOrEmpty(param.Params["StationCode"]);
// 异步校验:工单号是否在ERP中存在
var erpOrderExists = await _dataSyncComponent.GetDataFromErp<int>("SELECT COUNT(*) FROM ErpWorkOrder WHERE OrderNo = @WorkNo", new { WorkNo = param.Params["WorkNo"] }) > 0;
return hasWorkNo && hasLineCode && hasStationCode && erpOrderExists;
}
/// <summary>
/// 前置准备:从ERP查询工单详情,从MES查询工位状态
/// </summary>
protected override async Task PreExecuteAsync(WorkCollabParam param)
{
// 1. 从ERP查询工单详情并同步到本地
var erpOrder = await _dataSyncComponent.GetDataFromErp<BaseWorkInfo>("SELECT OrderNo as WorkNo, ProductCode, LineCode FROM ErpWorkOrder WHERE OrderNo = @WorkNo", new { WorkNo = param.Params["WorkNo"] });
await _dataSyncComponent.SyncDataToLocal(erpOrder, "ErpWorkOrderLocal");
// 2. 从MES查询工位状态,确保工位空闲可派工
var stationStatus = await _dataSyncComponent.GetDataFromMes<string>("SELECT StationStatus FROM MesStation WHERE StationCode = @StationCode", new { StationCode = param.Params["StationCode"] });
if (stationStatus != "Idle")
{
throw new Exception($"工位{param.Params["StationCode"]}当前状态为{stationStatus},不可派工");
}
}
/// <summary>
/// 核心执行:调用MES派工API完成派工,更新工单派工状态
/// </summary>
protected override async Task<bool> CoreExecuteAsync(WorkCollabParam param)
{
// 1. 模拟调用MES派工API(实际替换为真实API调用)
var mesDispatchResult = await Task.Run(() => true); // 真实场景:HttpClient调用MesDispatchApi
if (!mesDispatchResult)
{
return false;
}
// 2. 更新ERP工单派工状态为"已派工"
await _dataSyncComponent.GetDataFromErp<int>("UPDATE ErpWorkOrder SET DispatchStatus = 1 WHERE OrderNo = @WorkNo", new { WorkNo = param.Params["WorkNo"] });
return true;
}
/// <summary>
/// 后置处理:更新本地派工记录,统计产线派工数量
/// </summary>
protected override async Task PostExecuteAsync(WorkCollabParam param)
{
// 1. 新增本地派工记录
var dispatchRecord = new { CollabInstanceId = CollabInstanceId, OrderNo = param.Params["WorkNo"], LineCode = param.Params["LineCode"], StationCode = param.Params["StationCode"], DispatchTime = DateTime.Now };
await _dataSyncComponent.SyncDataToLocal(dispatchRecord, "WorkOrderDispatchRecord");
// 2. 统计产线当日派工数量并更新
var dispatchCount = await _dataSyncComponent.GetDataFromErp<int>("SELECT COUNT(*) FROM ErpWorkOrder WHERE LineCode = @LineCode AND DispatchTime >= CONVERT(date, GETDATE())", new { LineCode = param.Params["LineCode"] });
await _dataSyncComponent.SyncDataToLocal(new { LineCode = param.Params["LineCode"], DayDispatchCount = dispatchCount }, "LineDispatchCount");
}
/// <summary>
/// 消息推送:推送给工位终端+产线管理员
/// </summary>
protected override async Task PushCollabMsgAsync(WorkCollabParam param)
{
var title = $"【工单派工成功】{param.Params["WorkNo"]}";
var content = $"工单号:{param.Params["WorkNo"]}\r\n产线:{param.Params["LineCode"]}\r\n工位:{param.Params["StationCode"]}\r\n派工时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}\r\n请及时处理!";
// 1. 推送给指定工位
await _msgPushComponent.PushToStation(param.Params["StationCode"], title, content);
// 2. 推送给产线管理员(模板配置中获取)
if (TemplateConfig.ContainsKey("LineManagerId"))
{
await _msgPushComponent.PushToUser(TemplateConfig["LineManagerId"], title, content);
}
}
#endregion
#region 重写通用方法(扩展工单派工特有逻辑)
/// <summary>
/// 扩展模板初始化:设置默认执行模式为自动触发
/// </summary>
protected override void InitTemplate()
{
base.InitTemplate();
ExecuteMode = CollabTemplateExecuteMode.Auto;
// 设置默认模板配置
TemplateConfig.Add("ErpOrderApi", "http://erp-server/api/order/get");
TemplateConfig.Add("MesDispatchApi", "http://mes-server/api/dispatch/send");
TemplateConfig.Add("StationPushTopic", "MES/Station/Push");
TemplateConfig.Add("LineManagerId", "admin_001");
}
#endregion
}
#endregion
#region 具体协同子类2:异常处理协同(多岗位联动)
/// <summary>
/// 异常处理协同子类(多态实现)
/// 场景:产线出现质量/设备/物料异常->推送给对应负责人(质量员/设备员/物料员)->处理完成->同步状态到MES/ERP->通知上报人
/// </summary>
public class ExceptionHandleCollab : BaseWorkCollabTemplate
{
#region 子类专属配置
public string ExceptionReportApi { get; set; } // MES异常上报API
public string ExceptionHandleApi { get; set; } // MES异常处理API
#endregion
#region 构造函数
public ExceptionHandleCollab(ILogComponent logComponent, IDataSyncComponent dataSyncComponent, IMsgPushComponent msgPushComponent)
: base(logComponent, dataSyncComponent, msgPushComponent)
{
CollabType = WorkCollabType.ExceptionHandle;
TemplateName = "异常处理协同模板";
LoadSubConfig();
}
#endregion
#region 子类专属方法
private void LoadSubConfig()
{
if (TemplateConfig.ContainsKey("ExceptionReportApi")) ExceptionReportApi = TemplateConfig["ExceptionReportApi"];
if (TemplateConfig.ContainsKey("ExceptionHandleApi")) ExceptionHandleApi = TemplateConfig["ExceptionHandleApi"];
}
#endregion
#region 重写抽象钩子方法
protected override async Task<bool> ValidateParamAsync(WorkCollabParam param)
{
// 必传参数:ExceptionNo(异常单号)、ExceptionType(异常类型)、ReportUserId(上报人ID)
var hasExceptionNo = param.Params.ContainsKey("ExceptionNo") && !string.IsNullOrEmpty(param.Params["ExceptionNo"]);
var hasExceptionType = param.Params.ContainsKey("ExceptionType") && !string.IsNullOrEmpty(param.Params["ExceptionType"]);
var hasReportUserId = param.Params.ContainsKey("ReportUserId") && !string.IsNullOrEmpty(param.Params["ReportUserId"]);
// 校验异常单号是否存在
var exceptionExists = await _dataSyncComponent.GetDataFromMes<int>("SELECT COUNT(*) FROM MesException WHERE ExceptionNo = @ExceptionNo", new { ExceptionNo = param.Params["ExceptionNo"] }) > 0;
return hasExceptionNo && hasExceptionType && hasReportUserId && exceptionExists;
}
protected override async Task PreExecuteAsync(WorkCollabParam param)
{
// 1. 查询异常详情
var exceptionInfo = await _dataSyncComponent.GetDataFromMes<dynamic>("SELECT * FROM MesException WHERE ExceptionNo = @ExceptionNo", new { ExceptionNo = param.Params["ExceptionNo"] });
await _dataSyncComponent.SyncDataToLocal(exceptionInfo, "MesExceptionLocal");
// 2. 根据异常类型获取处理人ID(质量异常->质量员,设备异常->设备员)
var handlerId = param.Params["ExceptionType"] switch
{
"Quality" => TemplateConfig["QualityManagerId"],
"Equipment" => TemplateConfig["EquipmentManagerId"],
"Material" => TemplateConfig["MaterialManagerId"],
_ => TemplateConfig["LineManagerId"]
};
// 将处理人ID加入参数,供后续使用
param.Params.Add("HandlerId", handlerId);
param.Params.Add("HandlerName", await _dataSyncComponent.GetDataFromMes<string>("SELECT UserName FROM SysUser WHERE UserId = @HandlerId", new { HandlerId }));
}
protected override async Task<bool> CoreExecuteAsync(WorkCollabParam param)
{
// 1. 更新异常状态为"处理中"
await _dataSyncComponent.GetDataFromMes<int>("UPDATE MesException SET ExceptionStatus = 'Handling', HandlerId = @HandlerId, HandleStartTime = GETDATE() WHERE ExceptionNo = @ExceptionNo",
new { HandlerId = param.Params["HandlerId"], ExceptionNo = param.Params["ExceptionNo"] });
// 2. 模拟异常处理(实际场景:等待处理人确认,此处简化为直接处理完成)
await Task.Delay(1000); // 模拟处理耗时
// 3. 更新异常状态为"已处理"
await _dataSyncComponent.GetDataFromMes<int>("UPDATE MesException SET ExceptionStatus = 'Handled', HandleEndTime = GETDATE(), HandleResult = @HandleResult WHERE ExceptionNo = @ExceptionNo",
new { HandleResult = param.Params.ContainsKey("HandleResult") ? param.Params["HandleResult"] : "异常已处理完成", ExceptionNo = param.Params["ExceptionNo"] });
return true;
}
protected override async Task PostExecuteAsync(WorkCollabParam param)
{
// 1. 新增异常处理记录
var handleRecord = new { CollabInstanceId = CollabInstanceId, ExceptionNo = param.Params["ExceptionNo"], HandlerId = param.Params["HandlerId"], HandleResult = param.Params["HandleResult"], HandleTime = DateTime.Now };
await _dataSyncComponent.SyncDataToLocal(handleRecord, "MesExceptionHandleRecord");
// 2. 同步异常处理状态到ERP
await _dataSyncComponent.GetDataFromErp<int>("UPDATE ErpWorkOrder SET ExceptionStatus = 'Handled' WHERE OrderNo = @OrderNo", new { OrderNo = param.Params["WorkNo"] });
}
protected override async Task PushCollabMsgAsync(WorkCollabParam param)
{
var title = $"【异常处理完成】{param.Params["ExceptionNo"]}";
var content = $"异常单号:{param.Params["ExceptionNo"]}\r\n异常类型:{param.Params["ExceptionType"]}\r\n处理人:{param.Params["HandlerName"]}\r\n处理结果:{param.Params["HandleResult"]}\r\n处理时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}";
// 1. 推送给异常上报人
await _msgPushComponent.PushToUser(param.Params["ReportUserId"], title, content);
// 2. 推送给产线管理员
await _msgPushComponent.PushToUser(TemplateConfig["LineManagerId"], title, content);
}
#endregion
#region 重写通用方法
protected override void InitTemplate()
{
base.InitTemplate();
ExecuteMode = CollabTemplateExecuteMode.Triggered;
TemplateConfig.Add("ExceptionReportApi", "http://mes-server/api/exception/report");
TemplateConfig.Add("ExceptionHandleApi", "http://mes-server/api/exception/handle");
TemplateConfig.Add("QualityManagerId", "quality_001");
TemplateConfig.Add("EquipmentManagerId", "equipment_001");
TemplateConfig.Add("MaterialManagerId", "material_001");
TemplateConfig.Add("LineManagerId", "admin_001");
}
#endregion
}
#endregion
3. 基础组件实现(组合到基类,组件化复用)
实现日志、数据同步、消息推送 基础组件的具体逻辑,采用面向接口编程,后续可快速替换组件实现(如替换日志组件为 NLog、消息推送为 MQTT),无需修改核心业务代码。
#region 基础组件具体实现
/// <summary>
/// 日志组件具体实现(Serilog+本地库)
/// </summary>
public class SerilogComponent : ILogComponent
{
private readonly MesErpDbHelper _dbHelper;
public SerilogComponent()
{
_dbHelper = MesErpDbHelper.Instance;
// 初始化Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs/WorkCollab-.log"), rollingInterval: RollingInterval.Day, encoding: System.Text.Encoding.UTF8)
.WriteTo.Console()
.CreateLogger();
}
public void Info(string msg, string collabId = null)
{
Log.Information($"【{collabId ?? "SYSTEM"}】{msg}");
}
public void Error(string msg, Exception ex = null, string collabId = null)
{
if (ex == null)
Log.Error($"【{collabId ?? "SYSTEM"}】{msg}");
else
Log.Error(ex, $"【{collabId ?? "SYSTEM"}】{msg}");
}
public void SaveCollabLog(WorkCollabLog log)
{
_dbHelper.Execute("INSERT INTO WorkCollabLog (LogId, CollabId, TemplateId, CollabType, CollabStatus, ExecuteMsg, ExecuteTime, ExecuteUserId, ExecuteUserName) " +
"VALUES (@LogId, @CollabId, @TemplateId, @CollabType, @CollabStatus, @ExecuteMsg, @ExecuteTime, @ExecuteUserId, @ExecuteUserName)", log);
}
}
/// <summary>
/// 数据同步组件具体实现(Dapper+ERP/MES模拟+SQLite本地)
/// </summary>
public class DapperDataSyncComponent : IDataSyncComponent
{
private readonly MesErpDbHelper _dbHelper;
// 模拟ERP/MES连接字符串(实际替换为真实地址)
private readonly string _erpConnStr = "Data Source=ERP-SERVER;Initial Catalog=ErpDb;User ID=sa;Password=123456;";
private readonly string _mesConnStr = "Data Source=MES-SERVER;Initial Catalog=MesDb;User ID=sa;Password=123456;";
public DapperDataSyncComponent()
{
_dbHelper = MesErpDbHelper.Instance;
}
public async Task<T> GetDataFromErp<T>(string sql, object param = null)
{
// 模拟ERP数据查询(实际使用SqlConnection)
await Task.Delay(100);
return default; // 真实场景:使用Dapper查询ERP数据库并返回
}
public async Task<T> GetDataFromMes<T>(string sql, object param = null)
{
// 模拟MES数据查询(实际使用SqlConnection)
await Task.Delay(100);
return default; // 真实场景:使用Dapper查询MES数据库并返回
}
public async Task<bool> SyncDataToLocal<T>(T model, string tableName)
{
try
{
// 本地SQLite存储(使用Dapper)
_dbHelper.SaveObject(tableName, model.GetType().GetProperty($"{tableName}Id")?.Name ?? "LogId", model);
await Task.CompletedTask;
return true;
}
catch
{
return false;
}
}
public async Task<bool> SyncCollabStatus(WorkCollabStatus status, string collabId)
{
// 模拟同步状态到MES/ERP
await Task.Delay(100);
return true;
}
}
/// <summary>
/// 消息推送组件具体实现(SignalR+模拟)
/// </summary>
public class SignalRMsgPushComponent : IMsgPushComponent
{
// 模拟SignalR连接(实际替换为真实SignalR Hub地址)
private readonly string _signalRHubUrl = "http://localhost:5000/signalr/hub";
public async Task<bool> PushToUser(string userId, string title, string content)
{
// 模拟推送给指定用户
await Task.Delay(50);
return true;
}
public async Task<bool> PushToStation(string stationCode, string title, string content)
{
// 模拟推送给指定工位
await Task.Delay(50);
return true;
}
public async Task<bool> PushToLine(string lineCode, string title, string content)
{
// 模拟推送给指定产线
await Task.Delay(50);
return true;
}
}
#endregion
4. 模板管理中心(单例,模板生命周期管理)
实现作业协同模板的注册、加载、保存、获取 ,单例化管理所有模板,支持模板配置持久化 (本地 SQLite),程序重启后模板配置不丢失,同时提供多态模板的统一调用入口。
#region 模板管理中心(单例,模板生命周期管理)
/// <summary>
/// 作业协同模板管理中心(单例)
/// 核心:模板注册、加载、保存、获取,多态模板统一入口,配置持久化
/// </summary>
public sealed class WorkCollabTemplateManager
{
#region 单例实例
private static readonly Lazy<WorkCollabTemplateManager> _instance = new Lazy<WorkCollabTemplateManager>(() => new WorkCollabTemplateManager());
public static WorkCollabTemplateManager Instance => _instance.Value;
#endregion
#region 依赖与缓存
private readonly MesErpDbHelper _dbHelper = MesErpDbHelper.Instance;
private readonly ILogComponent _logComponent = new SerilogComponent();
private readonly IDataSyncComponent _dataSyncComponent = new DapperDataSyncComponent();
private readonly IMsgPushComponent _msgPushComponent = new SignalRMsgPushComponent();
// 模板缓存(内存+本地库,key=TemplateId,value=模板实例)
private readonly Dictionary<string, BaseWorkCollabTemplate> _templateCache = new Dictionary<string, BaseWorkCollabTemplate>();
private readonly object _templateLock = new object();
#endregion
#region 构造函数(程序启动时加载所有模板)
private WorkCollabTemplateManager()
{
// 初始化本地数据库表
InitTemplateTable();
// 从本地库加载所有模板到内存缓存
LoadAllTemplatesFromDb();
// 注册默认核心模板(工单派工、异常处理)
RegisterDefaultTemplates();
_logComponent.Info("作业协同模板管理中心初始化完成,当前模板数量:" + _templateCache.Count);
}
#endregion
#region 模板表初始化(本地SQLite)
private void InitTemplateTable()
{
_dbHelper.Execute(@"CREATE TABLE IF NOT EXISTS WorkCollabTemplate (
TemplateId TEXT PRIMARY KEY,
TemplateName TEXT NOT NULL,
CollabType INTEGER NOT NULL,
ExecuteMode INTEGER NOT NULL,
TemplateConfig TEXT NOT NULL,
CreateTime DATETIME NOT NULL,
UpdateTime DATETIME NOT NULL
);");
_dbHelper.Execute(@"CREATE TABLE IF NOT EXISTS WorkCollabLog (
LogId TEXT PRIMARY KEY,
CollabId TEXT NOT NULL,
TemplateId TEXT NOT NULL,
CollabType INTEGER NOT NULL,
CollabStatus INTEGER NOT NULL,
ExecuteMsg TEXT,
ExecuteTime DATETIME NOT NULL,
ExecuteUserId TEXT,
ExecuteUserName TEXT,
FOREIGN KEY(TemplateId) REFERENCES WorkCollabTemplate(TemplateId)
);");
}
#endregion
#region 模板注册(新增/更新,内存+本地库双保存)
/// <summary>
/// 注册模板(新增/更新,自动持久化到本地库)
/// </summary>
/// <param name="template">模板实例</param>
public void RegisterTemplate(BaseWorkCollabTemplate template)
{
lock (_templateLock)
{
// 更新内存缓存
if (_templateCache.ContainsKey(template.TemplateId))
_templateCache[template.TemplateId] = template;
else
_templateCache.Add(template.TemplateId, template);
// 持久化到本地库
var templateModel = new
{
template.TemplateId,
template.TemplateName,
CollabType = (int)template.CollabType,
ExecuteMode = (int)template.ExecuteMode,
TemplateConfig = JsonConvert.SerializeObject(template.TemplateConfig),
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
_dbHelper.SaveObject("WorkCollabTemplate", "TemplateId", templateModel);
_logComponent.Info($"模板注册成功:{template.TemplateName}(ID:{template.TemplateId},类型:{template.CollabType})", template.TemplateId);
}
}
/// <summary>
/// 注册默认核心模板(开箱即用)
/// </summary>
private void RegisterDefaultTemplates()
{
// 注册工单派工协同模板
var dispatchTemplate = new WorkOrderDispatchCollab(_logComponent, _dataSyncComponent, _msgPushComponent)
{
TemplateName = "工单派工协同模板-LINE001"
};
RegisterTemplate(dispatchTemplate);
// 注册异常处理协同模板
var exceptionTemplate = new ExceptionHandleCollab(_logComponent, _dataSyncComponent, _msgPushComponent)
{
TemplateName = "异常处理协同模板-LINE001"
};
RegisterTemplate(exceptionTemplate);
}
#endregion
#region 模板加载/获取(从内存/本地库)
/// <summary>
/// 从本地库加载所有模板到内存缓存
/// </summary>
private void LoadAllTemplatesFromDb()
{
var templateModels = _dbHelper.QueryList<dynamic>("SELECT * FROM WorkCollabTemplate");
foreach (var model in templateModels)
{
var collabType = (WorkCollabType)model.CollabType;
var templateConfig = JsonConvert.DeserializeObject<Dictionary<string, string>>(model.TemplateConfig);
// 根据协同类型创建对应模板实例(多态)
BaseWorkCollabTemplate template = collabType switch
{
WorkCollabType.WorkOrderDispatch => new WorkOrderDispatchCollab(_logComponent, _dataSyncComponent, _msgPushComponent)
{
TemplateId = model.TemplateId,
TemplateName = model.TemplateName,
ExecuteMode = (CollabTemplateExecuteMode)model.ExecuteMode,
TemplateConfig = templateConfig
},
WorkCollabType.ExceptionHandle => new ExceptionHandleCollab(_logComponent, _dataSyncComponent, _msgPushComponent)
{
TemplateId = model.TemplateId,
TemplateName = model.TemplateName,
ExecuteMode = (CollabTemplateExecuteMode)model.ExecuteMode,
TemplateConfig = templateConfig
},
_ => null
};
if (template != null && !_templateCache.ContainsKey(template.TemplateId))
{
_templateCache.Add(template.TemplateId, template);
}
}
}
/// <summary>
/// 根据模板ID获取模板实例(多态,统一基类返回)
/// </summary>
public BaseWorkCollabTemplate GetTemplateById(string templateId)
{
lock (_templateLock)
{
if (_templateCache.TryGetValue(templateId, out var template))
{
return template;
}
throw new KeyNotFoundException($"模板ID{templateId}不存在");
}
}
/// <summary>
/// 根据协同类型获取模板实例(默认取第一个)
/// </summary>
public BaseWorkCollabTemplate GetTemplateByType(WorkCollabType collabType)
{
lock (_templateLock)
{
var template = _templateCache.Values.FirstOrDefault(t => t.CollabType == collabType);
if (template != null)
{
return template;
}
throw new KeyNotFoundException($"协同类型{collabType}的模板不存在");
}
}
/// <summary>
/// 获取所有模板实例
/// </summary>
public List<BaseWorkCollabTemplate> GetAllTemplates()
{
lock (_templateLock)
{
return _templateCache.Values.ToList();
}
}
#endregion
#region 模板执行(统一入口,多态执行)
/// <summary>
/// 执行作业协同(统一入口,自动根据模板ID获取实例并执行)
/// </summary>
/// <param name="templateId">模板ID</param>
/// <param name="param">执行参数</param>
/// <returns>执行结果</returns>
public async Task<bool> ExecuteTemplateAsync(string templateId, WorkCollabParam param)
{
var template = GetTemplateById(templateId);
_logComponent.Info($"开始执行模板:{template.TemplateName},实例ID:{param.CollabId}", param.CollabId);
return await template.ExecuteAsync(param);
}
/// <summary>
/// 执行作业协同(根据协同类型执行)
/// </summary>
public async Task<bool> ExecuteTemplateByTypeAsync(WorkCollabType collabType, WorkCollabParam param)
{
var template = GetTemplateByType(collabType);
_logComponent.Info($"开始执行模板:{template.TemplateName},实例ID:{param.CollabId}", param.CollabId);
return await template.ExecuteAsync(param);
}
#endregion
#region 模板流程控制(暂停/恢复/取消,统一入口)
public void PauseTemplate(string templateId)
{
var template = GetTemplateById(templateId);
template.Pause();
_logComponent.Info($"模板{template.TemplateName}已暂停", template.TemplateId);
}
public void ResumeTemplate(string templateId)
{
var template = GetTemplateById(templateId);
template.Resume();
_logComponent.Info($"模板{template.TemplateName}已恢复", template.TemplateId);
}
public void CancelTemplate(string templateId)
{
var template = GetTemplateById(templateId);
template.Cancel();
_logComponent.Info($"模板{template.TemplateName}已取消", template.TemplateId);
}
#endregion
}
#endregion
#region 本地数据库助手(SQLite,复用+扩展)
public sealed class MesErpDbHelper
{
private static readonly Lazy<MesErpDbHelper> _instance = new Lazy<MesErpDbHelper>(() => new MesErpDbHelper());
public static MesErpDbHelper Instance => _instance.Value;
private readonly string _dbPath;
private readonly string _connStr;
private readonly object _dbLock = new object();
private MesErpDbHelper()
{
_dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MesErpWorkCollab.db");
_connStr = $"Data Source={_dbPath};Version=3;Journal Mode=WAL;Pooling=true;Max Pool Size=20;";
if (!File.Exists(_dbPath)) SQLiteConnection.CreateFile(_dbPath);
}
public T QueryFirst<T>(string sql, object param = null)
{
lock (_dbLock)
{
using (var conn = new SQLiteConnection(_connStr))
{
conn.Open();
return conn.QueryFirstOrDefault<T>(sql, param);
}
}
}
public List<T> QueryList<T>(string sql, object param = null)
{
lock (_dbLock)
{
using (var conn = new SQLiteConnection(_connStr))
{
conn.Open();
return conn.Query<T>(sql, param).ToList();
}
}
}
public int Execute(string sql, object param = null)
{
lock (_dbLock)
{
using (var conn = new SQLiteConnection(_connStr))
{
conn.Open();
using (var tran = conn.BeginTransaction())
{
try
{
var rows = conn.Execute(sql, param, tran);
tran.Commit();
return rows;
}
catch (Exception ex)
{
tran.Rollback();
Log.Error(ex, "SQL执行失败:" + sql);
throw;
}
}
}
}
}
public void SaveObject<T>(string tableName, string keyCol, T model)
{
var jsonProps = model.GetType().GetProperties()
.Where(p => p.PropertyType.IsGenericType || p.PropertyType == typeof(Dictionary<string, string>))
.Select(p => p.Name).ToList();
var sqlCols = string.Join(",", model.GetType().GetProperties()
.Where(p => !jsonProps.Contains(p.Name))
.Select(p => p.Name));
var sqlVals = string.Join(",", model.GetType().GetProperties()
.Where(p => !jsonProps.Contains(p.Name))
.Select(p => $"@{p.Name}"));
Execute($"DELETE FROM {tableName} WHERE {keyCol} = @{keyCol}", model);
var insertSql = $"INSERT INTO {tableName} ({sqlCols},{string.Join(",", jsonProps)}) VALUES ({sqlVals},{string.Join(",", jsonProps.Select(p => $"@{p}"))})";
var param = new DynamicParameters(model);
foreach (var prop in jsonProps)
{
var val = model.GetType().GetProperty(prop).GetValue(model);
param.Add(prop, JsonConvert.SerializeObject(val));
}
Execute(insertSql, param);
}
}
#endregion
四、后台服务层(协同引擎 + API 托管 + 后台运行)
实现作业协同自动化执行引擎 、自托管 WebAPI (无需 IIS)、系统托盘后台运行 、定时任务调度 ,保证中间件7*24 小时后台服务 能力,同时提供标准化 API 接口供外部系统 / 终端调用。
1. 自托管 WebAPI 组件(标准化接口)
基于 Owin 实现自托管 RESTful API ,提供模板管理、协同执行、状态查询、流程控制全功能 API,支持跨域、JSON 参数,适配 MES/ERP/ 工位终端等外部系统调用。
#region 自托管WebAPI组件(Owin)
/// <summary>
/// API启动配置(Owin)
/// </summary>
public class Startup
{
public void Configuration(IAppBuilder app)
{
// 配置WebAPI
var config = new HttpConfiguration();
// 路由配置
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// 启用跨域
app.UseCors(CorsOptions.AllowAll);
// 启用WebAPI
app.UseWebApi(config);
// 启用静态文件
app.UseStaticFiles();
Log.Information("WebAPI服务启动成功,地址:http://*:5000");
}
}
/// <summary>
/// 作业协同API控制器(核心接口)
/// </summary>
public class WorkCollabController : ApiController
{
private readonly WorkCollabTemplateManager _templateManager = WorkCollabTemplateManager.Instance;
#region 模板管理API
/// <summary>
/// 获取所有协同模板
/// GET: api/WorkCollab/GetAllTemplates
/// </summary>
[HttpGet]
public ApiResult<List<dynamic>> GetAllTemplates()
{
try
{
var templates = _templateManager.GetAllTemplates().Select(t => new
{
t.TemplateId,
t.TemplateName,
t.CollabType,
t.ExecuteMode,
t.CurrentStatus
}).ToList();
return ApiResult<List<dynamic>>.Ok(templates);
}
catch (Exception ex)
{
return ApiResult<List<dynamic>>.Error(ex.Message);
}
}
/// <summary>
/// 根据模板ID获取模板详情
/// GET: api/WorkCollab/GetTemplateById?templateId=xxx
/// </summary>
[HttpGet]
public ApiResult<dynamic> GetTemplateById(string templateId)
{
try
{
var template = _templateManager.GetTemplateById(templateId);
var result = new
{
template.TemplateId,
template.TemplateName,
template.CollabType,
template.ExecuteMode,
template.TemplateConfig,
template.CurrentStatus
};
return ApiResult<dynamic>.Ok(result);
}
catch (Exception ex)
{
return ApiResult<dynamic>.Error(ex.Message);
}
}
#endregion
#region 协同执行API
/// <summary>
/// 执行作业协同(根据模板ID)
/// POST: api/WorkCollab/ExecuteTemplateById
/// Body: { "TemplateId": "xxx", "CollabId": "xxx", "Params": { "WorkNo": "WO-20260206001", "LineCode": "LINE001", ... } }
/// </summary>
[HttpPost]
public async Task<ApiResult<bool>> ExecuteTemplateById([FromBody] dynamic param)
{
try
{
var templateId = param.TemplateId?.ToString();
var collabId = param.CollabId?.ToString() ?? Guid.NewGuid().ToString("N");
var paramDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(param.Params.ToString());
if (string.IsNullOrEmpty(templateId))
{
return ApiResult<bool>.ParamError("模板ID不能为空");
}
var executeParam = new WorkCollabParam
{
TemplateId = templateId,
CollabId = collabId,
Params = paramDict,
Cts = CancellationToken.None
};
var result = await _templateManager.ExecuteTemplateAsync(templateId, executeParam);
return ApiResult<bool>.Ok(result, $"协同执行{(result ? "成功" : "失败")},实例ID:{collabId}");
}
catch (Exception ex)
{
return ApiResult<bool>.Error(ex.Message);
}
}
/// <summary>
/// 执行作业协同(根据协同类型)
/// POST: api/WorkCollab/ExecuteTemplateByType
/// Body: { "CollabType": 0, "CollabId": "xxx", "Params": { ... } }
/// </summary>
[HttpPost]
public async Task<ApiResult<bool>> ExecuteTemplateByType([FromBody] dynamic param)
{
try
{
var collabType = (WorkCollabType)int.Parse(param.CollabType.ToString());
var collabId = param.CollabId?.ToString() ?? Guid.NewGuid().ToString("N");
var paramDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(param.Params.ToString());
var executeParam = new WorkCollabParam
{
CollabId = collabId,
Params = paramDict,
Cts = CancellationToken.None
};
var result = await _templateManager.ExecuteTemplateByTypeAsync(collabType, executeParam);
return ApiResult<bool>.Ok(result, $"协同执行{(result ? "成功" : "失败")},实例ID:{collabId}");
}
catch (Exception ex)
{
return ApiResult<bool>.Error(ex.Message);
}
}
#endregion
#region 流程控制API
/// <summary>
/// 暂停协同模板
/// GET: api/WorkCollab/PauseTemplate?templateId=xxx
/// </summary>
[HttpGet]
public ApiResult<bool> PauseTemplate(string templateId)
{
try
{
_templateManager.PauseTemplate(templateId);
return ApiResult<bool>.Ok(true, "模板暂停成功");
}
catch (Exception ex)
{
return ApiResult<bool>.Error(ex.Message);
}
}
/// <summary>
/// 恢复协同模板
/// GET: api/WorkCollab/ResumeTemplate?templateId=xxx
/// </summary>
[HttpGet]
public ApiResult<bool> ResumeTemplate(string templateId)
{
try
{
_templateManager.ResumeTemplate(templateId);
return ApiResult<bool>.Ok(true, "模板恢复成功");
}
catch (Exception ex)
{
return ApiResult<bool>.Error(ex.Message);
}
}
/// <summary>
/// 取消协同模板
/// GET: api/WorkCollab/CancelTemplate?templateId=xxx
/// </summary>
[HttpGet]
public ApiResult<bool> CancelTemplate(string templateId)
{
try
{
_templateManager.CancelTemplate(templateId);
return ApiResult<bool>.Ok(true, "模板取消成功");
}
catch (Exception ex)
{
return ApiResult<bool>.Error(ex.Message);
}
}
#endregion
#region 状态查询API
/// <summary>
/// 查询协同实例执行日志
/// GET: api/WorkCollab/GetCollabLog?collabId=xxx
/// </summary>
[HttpGet]
public ApiResult<List<WorkCollabLog>> GetCollabLog(string collabId)
{
try
{
var dbHelper = MesErpDbHelper.Instance;
var logs = dbHelper.QueryList<WorkCollabLog>("SELECT * FROM WorkCollabLog WHERE CollabId = @CollabId ORDER BY ExecuteTime DESC", new { CollabId = collabId });
return ApiResult<List<WorkCollabLog>>.Ok(logs);
}
catch (Exception ex)
{
return ApiResult<List<WorkCollabLog>>.Error(ex.Message);
}
}
}
#endregion
2. 后台服务管理器(引擎 + API 托管 + 系统托盘)
实现作业协同引擎后台运行 、WebAPI 服务托管启动 / 停止 、WinForm 最小化到系统托盘 、后台无 UI 阻塞执行 ,保证中间件服务化能力。
#region 后台服务管理器(协同引擎+API托管+系统托盘)
/// <summary>
/// MES/ERP作业协同后台服务管理器
/// 核心:WebAPI托管、后台协同执行、系统托盘、无UI阻塞、服务启停
/// </summary>
public class WorkCollabBackendService
{
#region 服务属性
private IDisposable _apiWebApp; // API服务实例
private readonly string _apiBaseUrl = "http://*:5000"; // API服务地址
public bool IsApiRunning { get; private set; } = false; // API服务是否运行
public Action<string> BackendLogAction { get; set; } // 后台日志委托(同步到UI)
public Action<string, WorkCollabStatus> CollabStatusUpdateAction { get; set; } // 协同状态更新委托(同步到UI)
#endregion
#region 构造函数
public WorkCollabBackendService()
{
// 注册协同状态更新监听(简化版,实际可通过事件实现)
Task.Run(() => MonitorCollabStatus());
}
#endregion
#region API服务托管(启动/停止)
/// <summary>
/// 启动自托管WebAPI服务
/// </summary>
public void StartApiService()
{
if (IsApiRunning)
{
BackendLogAction?.Invoke("API服务已在运行,无需重复启动");
return;
}
try
{
_apiWebApp = WebApp.Start<Startup>(_apiBaseUrl);
IsApiRunning = true;
BackendLogAction?.Invoke($"API服务启动成功,地址:{_apiBaseUrl}");
BackendLogAction?.Invoke("API接口文档:");
BackendLogAction?.Invoke(" 1. 获取所有模板:GET http://localhost:5000/api/WorkCollab/GetAllTemplates");
BackendLogAction?.Invoke(" 2. 执行协同:POST http://localhost:5000/api/WorkCollab/ExecuteTemplateById");
BackendLogAction?.Invoke(" 3. 查询日志:GET http://localhost:5000/api/WorkCollab/GetCollabLog?collabId=xxx");
}
catch (Exception ex)
{
BackendLogAction?.Invoke($"API服务启动失败:{ex.Message}");
throw;
}
}
/// <summary>
/// 停止自托管WebAPI服务
/// </summary>
public void StopApiService()
{
if (!IsApiRunning)
{
BackendLogAction?.Invoke("API服务未运行,无需停止");
return;
}
try
{
_apiWebApp.Dispose();
IsApiRunning = false;
BackendLogAction?.Invoke("API服务停止成功");
}
catch (Exception ex)
{
BackendLogAction?.Invoke($"API服务停止失败:{ex.Message}");
throw;
}
}
#endregion
#region 后台协同执行(无UI阻塞)
/// <summary>
/// 后台执行作业协同(Task.Run,无UI阻塞)
/// </summary>
/// <param name="templateId">模板ID</param>
/// <param name="param">执行参数</param>
public void RunCollabInBackend(string templateId, WorkCollabParam param)
{
Task.Run(async () =>
{
try
{
BackendLogAction?.Invoke($"后台启动协同执行:模板ID{templateId},实例ID{param.CollabId}");
var result = await WorkCollabTemplateManager.Instance.ExecuteTemplateAsync(templateId, param);
BackendLogAction?.Invoke($"后台协同执行完成:实例ID{param.CollabId},结果:{(result ? "成功" : "失败")}");
CollabStatusUpdateAction?.Invoke(param.CollabId, result ? WorkCollabStatus.Completed : WorkCollabStatus.Exception);
}
catch (Exception ex)
{
BackendLogAction?.Invoke($"后台协同执行异常:实例ID{param.CollabId},错误:{ex.Message}");
CollabStatusUpdateAction?.Invoke(param.CollabId, WorkCollabStatus.Exception);
}
}, param.Cts);
}
/// <summary>
/// 监控协同状态(后台定时刷新)
/// </summary>
private async Task MonitorCollabStatus()
{
while (true)
{
try
{
var templates = WorkCollabTemplateManager.Instance.GetAllTemplates();
foreach (var template in templates)
{
CollabStatusUpdateAction?.Invoke(template.TemplateId, template.CurrentStatus);
}
await Task.Delay(1000); // 每秒刷新一次
}
catch
{
await Task.Delay(1000);
}
}
}
#endregion
#region 系统托盘注册(WinForm后台运行)
/// <summary>
/// 注册系统托盘,支持最小化到后台
/// </summary>
public void RegisterSystemTray(Form mainForm, NotifyIcon notifyIcon)
{
notifyIcon.Icon = SystemIcons.Application;
notifyIcon.Text = "MES/ERP作业协同中间件";
notifyIcon.Visible = true;
// 右键菜单
var contextMenu = new ContextMenuStrip();
contextMenu.Items.Add("显示界面", null, (s, e) =>
{
mainForm.Show();
mainForm.WindowState = FormWindowState.Normal;
});
contextMenu.Items.Add("重启API服务", null, (s, e) =>
{
StopApiService();
StartApiService();
});
contextMenu.Items.Add("退出服务", null, (s, e) =>
{
StopApiService();
notifyIcon.Visible = false;
Application.Exit();
});
notifyIcon.ContextMenuStrip = contextMenu;
// 窗体最小化到托盘
mainForm.Resize += (s, e) =>
{
if (mainForm.WindowState == FormWindowState.Minimized)
{
mainForm.Hide();
notifyIcon.ShowBalloonTip(2000, "服务提示", "MES/ERP作业协同中间件已最小化到后台运行,API服务持续可用", ToolTipIcon.Info);
}
};
// 托盘左键双击显示界面
notifyIcon.MouseDoubleClick += (s, e) =>
{
if (e.Button == MouseButtons.Left)
{
mainForm.Show();
mainForm.WindowState = FormWindowState.Normal;
}
};
BackendLogAction?.Invoke("系统托盘注册成功,支持后台运行");
}
#endregion
}
#endregion
五、WinForm 可视化界面层(配置 + 监控 + 操作)
设计简洁易用的 WinForm 界面 ,实现模板管理、协同执行、状态监控、日志查看、API 服务启停 全功能,支持跨线程 UI 同步 、后台服务状态实时展示 、手动触发协同操作 ,适配产线管理人员的可视化操作需求。
#region WinForm可视化界面(主窗体)
public partial class MainForm : Form
{
#region 服务实例
private readonly WorkCollabTemplateManager _templateManager = WorkCollabTemplateManager.Instance;
private readonly WorkCollabBackendService _backendService = new WorkCollabBackendService();
private readonly NotifyIcon _notifyIcon = new NotifyIcon();
private readonly MesErpDbHelper _dbHelper = MesErpDbHelper.Instance;
#endregion
#region 界面控件
private TabControl tabMain;
private TabPage tabTemplate; // 模板管理页
private TabPage tabExecute; // 协同执行页
private TabPage tabMonitor; // 状态监控页
private TabPage tabLog; // 日志查看页
private TabPage tabApi; // API服务页
// 模板管理控件
private DataGridView dgvTemplates;
private Button btnRefreshTemplate;
// 协同执行控件
private ComboBox cboCollabType;
private ComboBox cboTemplateId;
private TextBox txtCollabId;
private TextBox txtParams;
private Button btnGenerateCollabId;
private Button btnExecute;
// 状态监控控件
private DataGridView dgvCollabStatus;
private Button btnRefreshStatus;
// 日志查看控件
private TextBox txtCollabIdLog;
private Button btnQueryLog;
private DataGridView dgvCollabLog;
// API服务控件
private Label lblApiStatus;
private Button btnStartApi;
private Button btnStopApi;
private TextBox txtApiUrl;
// 全局日志
private TextBox txtGlobalLog;
#endregion
public MainForm()
{
InitializeComponent();
// 初始化UI
InitMainUI();
// 注册后台服务委托(跨线程UI同步)
RegisterBackendDelegates();
// 注册系统托盘
_backendService.RegisterSystemTray(this, _notifyIcon);
// 加载初始数据
LoadInitData();
// 启动API服务
_backendService.StartApiService();
}
#region UI初始化(分步构建,模块化)
private void InitMainUI()
{
// 窗体基础设置
this.Text = "MES/ERP作业协同信息服务中间件";
this.Size = new Size(1200, 800);
this.StartPosition = FormStartPosition.CenterScreen;
this.FormClosing += (s, e) =>
{
_backendService.StopApiService();
_notifyIcon.Visible = false;
Log.CloseAndFlush();
};
// 主TabControl
tabMain = new TabControl { Location = new Point(10, 10), Size = new Size(1170, 700), Dock = DockStyle.Top };
// 添加TabPage
tabTemplate = new TabPage { Text = "模板管理" };
tabExecute = new TabPage { Text = "协同执行" };
tabMonitor = new TabPage { Text = "状态监控" };
tabLog = new TabPage { Text = "日志查看" };
tabApi = new TabPage { Text = "API服务" };
tabMain.TabPages.AddRange(new[] { tabTemplate, tabExecute, tabMonitor, tabLog, tabApi });
// 全局日志文本框
txtGlobalLog = new TextBox { Location = new Point(10, 710), Size = new Size(1170, 70), Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Vertical, Dock = DockStyle.Bottom };
// 初始化各Tab页控件
InitTemplateTab();
InitExecuteTab();
InitMonitorTab();
InitLogTab();
InitApiTab();
// 添加控件到窗体
this.Controls.Add(tabMain);
this.Controls.Add(txtGlobalLog);
}
#region 各Tab页初始化
private void InitTemplateTab()
{
dgvTemplates = new DataGridView { Location = new Point(10, 10), Size = new Size(1100, 500), ReadOnly = true, AllowUserToAddRows = false };
dgvTemplates.Columns.AddRange(new[]
{
new DataGridViewTextBoxColumn { Name = "TemplateId", HeaderText = "模板ID", Width = 200 },
new DataGridViewTextBoxColumn { Name = "TemplateName", HeaderText = "模板名称", Width = 200 },
new DataGridViewTextBoxColumn { Name = "CollabType", HeaderText = "协同类型", Width = 150 },
new DataGridViewTextBoxColumn { Name = "ExecuteMode", HeaderText = "执行模式", Width = 150 },
new DataGridViewTextBoxColumn { Name = "CurrentStatus", HeaderText = "当前状态", Width = 150 },
new DataGridViewTextBoxColumn { Name = "Config", HeaderText = "模板配置", Width = 250 }
});
btnRefreshTemplate = new Button { Location = new Point(10, 520), Size = new Size(100, 30), Text = "刷新模板" };
btnRefreshTemplate.Click += BtnRefreshTemplate_Click;
tabTemplate.Controls.Add(dgvTemplates);
tabTemplate.Controls.Add(btnRefreshTemplate);
}
private void InitExecuteTab()
{
cboCollabType = new ComboBox { Location = new Point(10, 10), Size = new Size(200, 23), DataSource = Enum.GetValues(typeof(WorkCollabType)), DropDownStyle = ComboBoxStyle.DropDownList };
cboTemplateId = new ComboBox { Location = new Point(220, 10), Size = new Size(300, 23), DropDownStyle = ComboBoxStyle.DropDownList };
txtCollabId = new TextBox { Location = new Point(530, 10), Size = new Size(200, 23), PlaceholderText = "协同实例ID(空则自动生成)" };
btnGenerateCollabId = new Button { Location = new Point(740, 10), Size = new Size(100, 23), Text = "生成ID" };
btnGenerateCollabId.Click += BtnGenerateCollabId_Click;
txtParams = new TextBox { Location = new Point(10, 40), Size = new Size(900, 400), Multiline = true, PlaceholderText = "执行参数(JSON格式)\r\n示例:{\"WorkNo\":\"WO-20260206001\",\"LineCode\":\"LINE001\",\"StationCode\":\"ST001\"}" };
btnExecute = new Button { Location = new Point(920, 40), Size = new Size(150, 60), Text = "执行作业协同", BackColor = Color.LightGreen, Font = new Font("微软雅黑", 12) };
btnExecute.Click += BtnExecute_Click;
tabExecute.Controls.AddRange(new Control[] { cboCollabType, cboTemplateId, txtCollabId, btnGenerateCollabId, txtParams, btnExecute });
}
private void InitMonitorTab()
{
dgvCollabStatus = new DataGridView { Location = new Point(10, 10), Size = new Size(1100, 500), ReadOnly = true, AllowUserToAddRows = false };
dgvCollabStatus.Columns.AddRange(new[]
{
new DataGridViewTextBoxColumn { Name = "TemplateId", HeaderText = "模板ID", Width = 200 },
new DataGridViewTextBoxColumn { Name = "TemplateName", HeaderText = "模板名称", Width = 200 },
new DataGridViewTextBoxColumn { Name = "CollabType", HeaderText = "协同类型", Width = 150 },
new DataGridViewTextBoxColumn { Name = "CurrentStatus", HeaderText = "当前状态", Width = 150 },
new DataGridViewTextBoxColumn { Name = "ExecuteMode", HeaderText = "执行模式", Width = 150 },
new DataGridViewTextBoxColumn { Name = "CollabInstanceId", HeaderText = "当前实例ID", Width = 250 }
});
btnRefreshStatus = new Button { Location = new Point(10, 520), Size = new Size(100, 30), Text = "刷新状态" };
btnRefreshStatus.Click += BtnRefreshStatus_Click;
tabMonitor.Controls.Add(dgvCollabStatus);
tabMonitor.Controls.Add(btnRefreshStatus);
}
private void InitLogTab()
{
txtCollabIdLog = new TextBox { Location = new Point(10, 10), Size = new Size(300, 23), PlaceholderText = "协同实例ID" };
btnQueryLog = new Button { Location = new Point(320, 10), Size = new Size(100, 23), Text = "查询日志" };
btnQueryLog.Click += BtnQueryLog_Click;
dgvCollabLog = new DataGridView { Location = new Point(10, 40), Size = new Size(1100, 500), ReadOnly = true, AllowUserToAddRows = false };
dgvCollabLog.Columns.AddRange(new[]
{
new DataGridViewTextBoxColumn { Name = "LogId", HeaderText = "日志ID", Width = 200 },
new DataGridViewTextBoxColumn { Name = "CollabId", HeaderText = "实例ID", Width = 200 },
new DataGridViewTextBoxColumn { Name = "CollabType", HeaderText = "协同类型", Width = 150 },
new DataGridViewTextBoxColumn { Name = "CollabStatus", HeaderText = "执行状态", Width = 150 },
new DataGridViewTextBoxColumn { Name = "ExecuteMsg", HeaderText = "执行信息", Width = 250 },
new DataGridViewTextBoxColumn { Name = "ExecuteTime", HeaderText = "执行时间", Width = 150 }
});
tabLog.Controls.AddRange(new Control[] { txtCollabIdLog, btnQueryLog, dgvCollabLog });
}
private void InitApiTab()
{
lblApiStatus = new Label { Location = new Point(10, 10), Size = new Size(200, 30), Text = "API服务状态:未运行", ForeColor = Color.Red, Font = new Font("微软雅黑", 12) };
btnStartApi = new Button { Location = new Point(220, 10), Size = new Size(100, 30), Text = "启动API", BackColor = Color.LightGreen };
btnStartApi.Click += BtnStartApi_Click;
btnStopApi = new Button { Location = new Point(330, 10), Size = new Size(100, 30), Text = "停止API", BackColor = Color.LightPink };
btnStopApi.Click += BtnStopApi_Click;
txtApiUrl = new TextBox { Location = new Point(10, 50), Size = new Size(800, 30), ReadOnly = true, Font = new Font("微软雅黑", 10), Text = "API服务地址:http://localhost:5000/api/WorkCollab/" };
tabApi.Controls.AddRange(new Control[] { lblApiStatus, btnStartApi, btnStopApi, txtApiUrl });
}
#endregion
#endregion
#region 后台服务委托注册(跨线程UI同步)
private void RegisterBackendDelegates()
{
// 后台日志同步到UI全局日志
_backendService.BackendLogAction = (log) =>
{
if (txtGlobalLog.InvokeRequired)
txtGlobalLog.Invoke(new Action<string>(AddGlobalLog), log);
else
AddGlobalLog(log);
};
// 协同状态更新同步到UI
_backendService.CollabStatusUpdateAction = (id, status) =>
{
if (dgvCollabStatus.InvokeRequired)
dgvCollabStatus.Invoke(new Action(() => LoadCollabStatus()));
else
LoadCollabStatus();
};
// API服务状态更新
Task.Run(() =>
{
while (true)
{
if (lblApiStatus.InvokeRequired)
{
lblApiStatus.Invoke(new Action(() =>
{
lblApiStatus.Text = _backendService.IsApiRunning ? "API服务状态:运行中" : "API服务状态:未运行";
lblApiStatus.ForeColor = _backendService.IsApiRunning ? Color.Green : Color.Red;
}));
}
else
{
lblApiStatus.Text = _backendService.IsApiRunning ? "API服务状态:运行中" : "API服务状态:未运行";
lblApiStatus.ForeColor = _backendService.IsApiRunning ? Color.Green : Color.Red;
}
Thread.Sleep(1000);
}
});
AddGlobalLog("后台服务委托注册成功,支持跨线程UI同步");
}
#endregion
#region 数据加载
private void LoadInitData()
{
LoadTemplates();
LoadTemplateIdCombo();
LoadCollabStatus();
AddGlobalLog("初始数据加载完成");
}
private void LoadTemplates()
{
var templates = _templateManager.GetAllTemplates();
dgvTemplates.Rows.Clear();
foreach (var template in templates)
{
var row = dgvTemplates.Rows.Add();
dgvTemplates.Rows[row].Cells["TemplateId"].Value = template.TemplateId;
dgvTemplates.Rows[row].Cells["TemplateName"].Value = template.TemplateName;
dgvTemplates.Rows[row].Cells["CollabType"].Value = template.CollabType;
dgvTemplates.Rows[row].Cells["ExecuteMode"].Value = template.ExecuteMode;
dgvTemplates.Rows[row].Cells["CurrentStatus"].Value = template.CurrentStatus;
dgvTemplates.Rows[row].Cells["Config"].Value = JsonConvert.SerializeObject(template.TemplateConfig);
}
}
private void LoadTemplateIdCombo()
{
var templates = _templateManager.GetAllTemplates();
cboTemplateId.DataSource = templates;
cboTemplateId.DisplayMember = "TemplateName";
cboTemplateId.ValueMember = "TemplateId";
}
private void LoadCollabStatus()
{
var templates = _templateManager.GetAllTemplates();
dgvCollabStatus.Rows.Clear();
foreach (var template in templates)
{
var row = dgvCollabStatus.Rows.Add();
dgvCollabStatus.Rows[row].Cells["TemplateId"].Value = template.TemplateId;
dgvCollabStatus.Rows[row].Cells["TemplateName"].Value = template.TemplateName;
dgvCollabStatus.Rows[row].Cells["CollabType"].Value = template.CollabType;
dgvCollabStatus.Rows[row].Cells["CurrentStatus"].Value = template.CurrentStatus;
dgvCollabStatus.Rows[row].Cells["ExecuteMode"].Value = template.ExecuteMode;
dgvCollabStatus.Rows[row].Cells["CollabInstanceId"].Value = template.CollabInstanceId;
}
}
#endregion
#region 界面事件处理(核心操作)
private void BtnRefreshTemplate_Click(object sender, EventArgs e)
{
LoadTemplates();
LoadTemplateIdCombo();
AddGlobalLog("模板列表刷新完成");
}
private void BtnGenerateCollabId_Click(object sender, EventArgs e)
{
txtCollabId.Text = Guid.NewGuid().ToString("N");
AddGlobalLog("协同实例ID生成完成:" + txtCollabId.Text);
}
private async void BtnExecute_Click(object sender, EventArgs e)
{
try
{
if (string.IsNullOrEmpty(txtParams.Text))
{
MessageBox.Show("请输入执行参数(JSON格式)!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 解析参数
var collabId = string.IsNullOrEmpty(txtCollabId.Text) ? Guid.NewGuid().ToString("N") : txtCollabId.Text;
var paramDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(txtParams.Text);
var templateId = cboTemplateId.SelectedValue.ToString();
// 构建执行参数
var executeParam = new WorkCollabParam
{
TemplateId = templateId,
CollabId = collabId,
Params = paramDict,
Cts = CancellationToken.None
};
// 后台执行(无UI阻塞)
_backendService.RunCollabInBackend(templateId, executeParam);
AddGlobalLog($"作业协同执行请求已提交:模板ID{templateId},实例ID{collabId}");
MessageBox.Show("协同执行请求已提交,后台执行中!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
AddGlobalLog($"协同执行参数解析失败:{ex.Message}");
MessageBox.Show($"执行失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void BtnRefreshStatus_Click(object sender, EventArgs e)
{
LoadCollabStatus();
AddGlobalLog("协同状态刷新完成");
}
private void BtnQueryLog_Click(object sender, EventArgs e)
{
try
{
var collabId = txtCollabIdLog.Text.Trim();
if (string.IsNullOrEmpty(collabId))
{
MessageBox.Show("请输入协同实例ID!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var logs = _dbHelper.QueryList<WorkCollabLog>("SELECT * FROM WorkCollabLog WHERE CollabId = @CollabId ORDER BY ExecuteTime DESC", new { CollabId = collabId });
dgvCollabLog.Rows.Clear();
foreach (var log in logs)
{
var row = dgvCollabLog.Rows.Add();
dgvCollabLog.Rows[row].Cells["LogId"].Value = log.LogId;
dgvCollabLog.Rows[row].Cells["CollabId"].Value = log.CollabId;
dgvCollabLog.Rows[row].Cells["CollabType"].Value = (WorkCollabType)log.CollabType;
dgvCollabLog.Rows[row].Cells["CollabStatus"].Value = (WorkCollabStatus)log.CollabStatus;
dgvCollabLog.Rows[row].Cells["ExecuteMsg"].Value = log.ExecuteMsg;
dgvCollabLog.Rows[row].Cells["ExecuteTime"].Value = log.ExecuteTime.ToString("yyyy-MM-dd HH:mm:ss");
}
AddGlobalLog($"查询协同日志完成:实例ID{collabId},日志数量{logs.Count}");
}
catch (Exception ex)
{
AddGlobalLog($"日志查询失败:{ex.Message}");
MessageBox.Show($"查询失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void BtnStartApi_Click(object sender, EventArgs e)
{
_backendService.StartApiService();
AddGlobalLog("API服务启动请求已执行");
}
private void BtnStopApi_Click(object sender, EventArgs e)
{
_backendService.StopApiService();
AddGlobalLog("API服务停止请求已执行");
}
#endregion
#region UI辅助方法
/// <summary>
/// 添加全局日志到UI
/// </summary>
private void AddGlobalLog(string log)
{
txtGlobalLog.AppendText($"[{DateTime.Now:HH:mm:ss.fff}] {log}\r\n");
txtGlobalLog.ScrollToCaret();
// 同时写入Serilog
Log.Information(log);
}
#endregion
// 设计器代码
private void InitializeComponent()
{
this.SuspendLayout();
this.ClientSize = new System.Drawing.Size(1200, 800);
this.Name = "MainForm";
this.Text = "MES/ERP作业协同信息服务中间件";
this.ResumeLayout(false);
}
}
#endregion
#region 程序入口
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// 初始化日志
new SerilogComponent();
Log.Information("MES/ERP作业协同中间件启动");
// 启动主窗体
Application.Run(new MainForm());
}
}
#endregion
六、部署与使用说明
1. 部署步骤(零配置,单 EXE 运行)
- 创建项目:Visual Studio 2022 中创建「Windows 窗体应用(.NET Framework 4.8)」;
- 安装 NuGet 包:执行前置准备中的 NuGet 命令,安装所有依赖;
- 添加代码:将上述代码按类拆分到对应文件(或直接复制到 Program.cs,删除原有代码);
- 编译项目:生成 EXE 可执行文件(bin/Debug/ 下);
- 直接运行 :双击 EXE,程序自动完成:
- 创建本地 SQLite 数据库(MesErpWorkCollab.db);
- 初始化模板、日志、协同表;
- 启动自托管 API 服务(http://localhost:5000);
- 注册系统托盘,支持后台运行。
2. 核心功能操作流程
可视化界面操作
- 模板管理:查看系统默认注册的「工单派工」「异常处理」模板,支持刷新模板列表;
- 协同执行 :
- 选择协同类型 / 模板;
- 生成 / 输入协同实例 ID;
- 输入 JSON 格式执行参数(如工单派工:
{"WorkNo":"WO-20260206001","LineCode":"LINE001","StationCode":"ST001"}); - 点击「执行作业协同」,后台无 UI 阻塞执行,日志实时同步到全局日志;
- 状态监控:实时查看模板执行状态、当前实例 ID,支持手动刷新;
- 日志查看:输入协同实例 ID,查询执行日志(状态、时间、信息);
- API 服务:查看 API 运行状态,手动启停 API 服务,复制 API 地址供外部调用。
外部系统 / 终端 API 调用
以Postman为例,调用 API 执行工单派工协同:
- 请求地址 :POST http://localhost:5000/api/WorkCollab/ExecuteTemplateByType
- 请求体(JSON):
json
{
"CollabType": 0,
"CollabId": "c81e728d9d4c2f636f067f89cc14862c",
"Params": {
"WorkNo": "WO-20260206001",
"LineCode": "LINE001",
"StationCode": "ST001",
"UserId": "admin_001",
"UserName": "管理员"
}
}
- 返回结果:
json
{
"Status": 200,
"Msg": "协同执行成功,实例ID:c81e728d9d4c2f636f067f89cc14862c",
"Data": true,
"Time": "2026-02-06T16:00:00.000"
}
3. 与实际 MES/ERP/ 设备对接扩展
- ERP/MES 真实数据交互 :修改
DapperDataSyncComponent中的GetDataFromErp/GetDataFromMes方法,替换为真实的 ERP/MES 数据库连接或 API 调用; - 设备对接 :在具体协同子类的
CoreExecuteAsync方法中,添加 PLC/Modbus/OPC UA 通信代码(如S7.Net、NModbus4),实现作业协同的设备自动化控制; - 消息推送真实实现 :修改
SignalRMsgPushComponent,替换为真实的 SignalR/MQTT/ 企业微信 / 钉钉推送,实现作业协同的实时消息通知; - 新增协同场景 :继承
BaseWorkCollabTemplate抽象基类,实现新的子类(如多产线协同、交接班协同),仅需重写抽象钩子方法,无需修改核心流程; - 定时任务调度 :集成 Hangfire,在
WorkCollabBackendService中添加定时任务,实现自动派工、定时状态同步、定时异常巡检等自动化协同; - 权限控制:扩展基础组件,添加权限验证组件(如 JWT),在 API 控制器中添加权限校验,保证接口调用安全;
- 配置可视化:新增模板配置窗体,实现模板配置参数的可视化编辑,无需手动输入 JSON。
七、核心优势与方案总结
核心优势
- 模板化 + 多态化设计 :抽象基类封装通用流程,子类仅实现个性化业务,新增协同场景仅需开发子类,开发效率提升 80%+;
- 组合封装 + 组件化复用 :基础能力(日志、数据同步、消息推送)按接口组合到基类,组件可快速替换(如替换日志组件、推送组件),无侵入式扩展;
- 服务化 + API 化 :自托管 WebAPI 无需 IIS,提供标准化 RESTful 接口,支持 MES/ERP/ 工位终端 / 移动端跨系统、跨终端调用;
- 可视化 + 后台化 :WinForm 可视化操作 + 系统托盘后台运行,支持7*24 小时无 UI 阻塞服务,适配产线现场需求;
- 零配置 + 易部署 :基于 SQLite 本地数据库,单 EXE 运行,无需安装数据库 / 中间件,产线边缘节点可直接部署;
- 标准化 + 可扩展 :全流程数据 / 接口 / 状态标准化,适配 MES/ERP 通用场景,可快速扩展到任意作业协同场景(如仓储协同、质量协同)。
关键点回顾
- 基于模板方法模式 设计抽象基类,固化作业协同通用流程,定义抽象钩子方法供子类实现个性化业务,实现模板化复用;
- 采用多态 / 继承 实现具体协同场景,基类统一入口调用,新增场景仅需开发子类,实现高扩展;
- 通过组合模式 将日志、数据同步、消息推送等基础能力注入基类,面向接口编程,实现组件化复用;
- 集成Owin 自托管 API ,提供标准化 RESTful 接口,实现跨系统 / 跨终端调用;
- 设计后台服务管理器 ,实现 API 托管、后台无 UI 执行、系统托盘,保证7*24 小时服务化运行;
- 开发WinForm 可视化界面 ,实现模板配置、协同执行、状态监控、日志查看,适配产线可视化操作需求;
- 基于SQLite 本地数据库 实现模板配置、执行日志持久化,实现零配置、单 EXE 部署。
该方案是一套完整的 MES/ERP 作业协同信息服务中间件 ,集模板化开发、多态化扩展、组件化复用、服务化运行、API 化调用、可视化操作于一体,可直接作为独立中间件部署到产线,也可集成到现有 MES/ERP 系统中,作为作业协同的核心引擎,适配离散制造、流程制造等各类制造业 MES/ERP 作业协同场景。