C#标准Mes接口框架(持续更新)
前言
由于近期我做了好几个客户的接入工厂Mes系统的需求。但是每个客户的Mes都有不同程度的定制需求,原有的代码复用难度其实很大。所以打算将整个接入Mes系统的框架单独拿出来作为一个项目使用,同时因为不同的设备接入同一个Mes系统,所以代码的迁移规范同样非常重要。
1.需求分析
这部分的需求分析,主要来自于我在接入不同客户Mes系统时发现的一些问题和解决方案,同时也了解过工厂Mes系统供应商的朋友们。列举了一些比较实际的功能(主要是代码方面的)。
- 需要有同一个的接入方式,方便接入不同客户的Mes系统。
- 需要有全局的参数列表。
- 只允许存在一个Mes系统的入口。
- 保证数据统一性,在多线程访问,设备不同轨道运行时,数据需要做区分。
- 尽可能的减少后续的代码修改。
- UI上,不同的客户的选项卡要做区分,但是只显示一个选项卡。
- 所有关于Mes的操作需要独立于设备内容,方便在不同设备软件上迁移,设备软件只做传入数据的功能。
- 需要考虑由Mes控制设备的功能
2.设计项目内容
- 首先会有一个MesApp的入口,用来访问整个Mes系统,但是这个入口只能有一个,所有MesApp应当使用单例模式。
- 不同的客户需要继承于同一个接口,根据客户的名称信息去访问指定的客户类,所以MesApp应当具有工厂模式,通过工厂生产客户类。
- 根据全局参数列表,需要一个可通过MesApp访问的全局静态类Const,和枚举类
- 保证数据做好区分,但是又保证数据统一性,所以Mes需要全部公用一个数据类对象,并且可以在外部写入参数,并作为类对象进行传参
- UI上面仍然使用MVVM框架去实现,同时使用Visibility的binding的形式来控制在选项卡中显示UI(根据实际情况可以选择公用选项卡或者,多选项卡的形式)
- 考虑数据的通用性,所以数据应到以类对象的形式存在(类型为String的Name,类型为Object的Value)
- 需要独立的数据存储部分,通讯方式需要独立,
- 在MesApp中需要由一个队列跟软件的框架进行通讯,用来控制软件执行某些内容
3.代码内容
3.1MesApp入口
首先MesApp是整个项目的入口,除了数据结构类以外,全部数据都应该从MesApp的端口中进入。所以MesApp有一个单例的入口。其中包含,config配置文件,const在软件运行时的不用存储的变量,IMesSend接口与Mes交互的主要代码,Enum需要使用的枚举值。同时在进入Mes前,需要根据不同的客户去选择我们需要的使用的Mes内容,所以有一个创建mes的函数。同时还有保存配置和获取配置的部分
namespace Mes
{public class MesApp{#region 单例部分private static MesApp _instance = null;private static readonly object Lock = new object();private MesConst _Const = new MesConst();private IMesSend _Mes = null;private MesEnum _MesEnum = new MesEnum();private MyMesConfig _MesConfig = new MyMesConfig();private string EnvironmentAddress = Path.Combine(Environment.CurrentDirectory, "Config");private string ConfigPath = Path.Combine(Environment.CurrentDirectory, "Config\\MesConfig.txt");//mes接收控制软件的队列public Queue<MesProcessData> MesQueueAccept = new Queue<MesProcessData>();//mes接收控制软件结束后的反馈队列public Queue<MesProcessData> MesQueueSend = new Queue<MesProcessData>();public MyMesConfig MyMesConfig{get => _MesConfig;set => _MesConfig = value;}public MesEnum MesEnum{get => _MesEnum;}public MesConst Const{get => _Const;set => _Const = value;}public IMesSend Mes{get => _Mes;set => _Mes = value;}public static MesApp Instance{get{if (_instance == null){lock (Lock){if (_instance == null){_instance = new MesApp();}}}return _instance;}}#endregion/// <summary>/// 创建Mes对象/// </summary>public bool CreatMes(){try{MesEnum.MesCustomer customer = new MesEnum.MesCustomer();customer = MesEnum.GetEnumValueFromDescription<MesEnum.MesCustomer>(MesApp.Instance.MyMesConfig.SelectCustomer);if (!MesApp.Instance.MyMesConfig.IsEnableMes){customer = MesEnum.MesCustomer.None;}switch (customer){case MesEnum.MesCustomer.CustomerA:Mes = new CustomerA();break;default:Mes = new DefaultMes();break;}return true;}catch (Exception){return false;}}public bool SaveMesConfig(){// 检查 config 文件夹是否存在if (!Directory.Exists(EnvironmentAddress)){try{// 创建 config 文件夹Directory.CreateDirectory(EnvironmentAddress);string json = JsonConvert.SerializeObject(MesApp.Instance.MyMesConfig, Formatting.Indented);File.WriteAllText(ConfigPath, json);return true;}catch (Exception ex){MesLog.Error("配置参数序列化失败:" + ex.ToString());return false;}}else{string json = JsonConvert.SerializeObject(MesApp.Instance.MyMesConfig, Formatting.Indented);File.WriteAllText(ConfigPath, json);return true;}}public bool GetMesConfig(){if (Directory.Exists(EnvironmentAddress)){try{string json = File.ReadAllText(ConfigPath);MesApp.Instance.MyMesConfig = MesJson.DeserializeObject<MyMesConfig>(json);}catch (Exception ex){MesLog.Error("配置参数反序列化失败:" + ex.ToString());}}else{return false;}return true;}}
}
3.2IMesSend接口
IMesSend接口是项目主要的内容,在创建Mes时,会使用工厂模式,通过IMesSend接口去生产指定的客户类,客户类通常包含我们自有设备通常需要上传的函数方法。同时包含一个动态接口,因为在某些客户需要定制一些独特的功能,但是大部分客户都是没有的,可以使用这个Task Dynamic(MesDynamic dynamic);的接口。
using System.Threading.Tasks;namespace Mes
{public interface IMesSend{/// <summary>/// 用户登录/// </summary>/// <returns></returns>Task<MesProcessData> MesLogin(MesDynamic dynamic);/// <summary>/// 上报拿板情况/// </summary>Task<bool> RemovePCB(MesDynamic data);/// <summary>/// 上传工艺参数/// </summary>void ProcessParameters();/// <summary>/// 上传整板测试结果/// </summary>Task<bool> Result(MesDynamic dynamic);/// <summary>/// Mes启用/// </summary>/// <returns></returns>bool MesEnable();/// <summary>/// 上传报警信息,/// </summary>/// <param name="message"></param>/// <param name="Level">级别:1为警告黄灯,2为红灯报警</param>Task<bool> AlarmInformation(string message, int Level);/// <summary>/// 消除报警信息/// </summary>/// <param name="message"></param>Task<bool> CancelAlarmInformation(string message);/// <summary>/// 发送设备状态/// </summary>Task<bool> ProcessStop(MesEnum.MachineState on);/// <summary>/// 切换程序/// </summary>void SwitchPrograms();/// <summary>/// 过站检测/// </summary>/// <param name="BoardCode"></param>/// <returns></returns>Task<bool> CheckBoard(MesDynamic dynamic);/// <summary>/// 设备出板/// </summary>/// <returns></returns>Task<bool> OutBoard(MesDynamic dynamic);/// <summary>/// 关闭Mes/// </summary>void CloseMes();/// <summary>/// 动态接口,用于在特殊情况下调用的接口/// </summary>/// <param name="dynamic"></param>/// <returns></returns>Task<MesProcessData> Dynamic(MesDynamic dynamic);}
}
3.3通讯类
通讯类所需要做的内容并不是很多,需要有创建通讯的步骤,发送数据,监听端口,关闭通讯就可以了。
using JOJO.Mes.Log;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace JOJO.Mes.CommModel
{internal class MesHttp{public string MesUrlAddress { get; set; } = @"http:\\Send";public string AccessInterface { get; set; } = "";public string MesUrlAcceptAddress { get; set; } = @"http:\\Accept";public int MesUrlTimeOut { get; set; } = 5000;public bool UseToken { get; set; } = false;public string Token { get; set; } = "";private string url { get; set; } = "";public TimeSpan CtsTimeOut = TimeSpan.FromSeconds(10);HttpClient Client;HttpListener Listener;public Queue<string> GetHttpQueue = new Queue<string>();public Queue<string> ResponseHttpQueue = new Queue<string>();public bool CreatHttpClient(){Client = new HttpClient();Client.Timeout = TimeSpan.FromSeconds(MesUrlTimeOut);Listener = new HttpListener();Listener.Prefixes.Add(MesUrlAcceptAddress); // 监听的 URL 前缀Listener.Start();return true;}public async Task<string> MesUrlSendAndAccept(string obj){try{string dataOut = "";url = MesUrlAddress + "/" + AccessInterface;MesLog.Info("当前访问的URL地址:" + url);// 创建 HTTP 客户端实例if (UseToken){Client.DefaultRequestHeaders.Add("token", Token);}// 构造要发送的内容var content = new StringContent(obj, Encoding.UTF8, "application/json");MesLog.Info("MesUrl数据上传:" + obj);// 发送POST请求var response = await Client.PostAsync(url, content);// 确保响应成功if (response.IsSuccessStatusCode){dataOut = await response.Content.ReadAsStringAsync().ConfigureAwait(false);MesLog.Info("Mes数据接受:" + dataOut);}content = null;response = null;return dataOut;}catch (Exception ex){MesLog.Error("发送Http数据失败:" + ex.ToString());return null;}}public async void AcceptHttp(){while (true){HttpListenerContext context = await Listener.GetContextAsync();HttpListenerRequest request = context.Request;HttpListenerResponse response = context.Response;if (request.HttpMethod == "POST"){using (StreamReader reader = new StreamReader(request.InputStream, request.ContentEncoding)){string requestContent = await reader.ReadToEndAsync();GetHttpQueue.Enqueue(requestContent);string responseString = "";//等待内容响应try{Task ResponseTask = Task.Run(async () =>{while (true){if (ResponseHttpQueue.Count > 0){responseString = ResponseHttpQueue.Dequeue();break;}await Task.Delay(10);}}, new CancellationTokenSource(CtsTimeOut).Token);}catch (Exception ex){MesLog.Error("Http接收响应失败:" + ex.ToString());MesApp.Instance.Const.SetMachineLog("Http接收响应失败");return;}byte[] buffer = Encoding.UTF8.GetBytes(responseString);response.ContentLength64 = buffer.Length;using (Stream output = response.OutputStream){await output.WriteAsync(buffer, 0, buffer.Length);}}}else{response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;response.Close();}}}public void Close(){Client.Dispose();}}
}
using JOJO.Mes.Log;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace JOJO.Mes.CommModel
{internal class MesSocket{private readonly byte[] buffer = new byte[1024 * 1024 * 100];public Socket listener;public Socket handler;public int Point = 8888;public string Address = "192.168.8.88";public Queue<string> SocketQueue = new Queue<string>();public bool CreatSocket(){listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);long address = new long();try{IPAddress ipAddress = IPAddress.Parse(Address);address = BitConverter.ToInt32(ipAddress.GetAddressBytes(), 0);IPEndPoint localEndPoint = new IPEndPoint(address, Point);listener.Bind(localEndPoint);listener.Listen(10);}catch (FormatException){return false;}MesLog.Info("Socket,等待客户端连接...");// 开始异步接受客户端连接listener.BeginAccept(AcceptCallback, listener);return true;}private void AcceptCallback(IAsyncResult ar){Socket listener = (Socket)ar.AsyncState;// 完成接受客户端连接handler = listener.EndAccept(ar);MesLog.Info($"连接Socket成功:" + handler.AddressFamily);// 开始异步接收数据handler.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, handler); 继续监听新的连接listener.BeginAccept(AcceptCallback, listener);}private void ReceiveCallback(IAsyncResult ar){Socket handler = (Socket)ar.AsyncState;try{int bytesRead = handler.EndReceive(ar);if (bytesRead > 0){byte[] data = new byte[bytesRead];Array.Copy(buffer, data, bytesRead);string message = System.Text.Encoding.UTF8.GetString(data);MesLog.Info($"接收Socket数据: {message}");SocketQueue.Enqueue(message);handler.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, handler);}}catch (SocketException e){MesLog.Warn($"接收Socket数据出错: {e.Message}");}finally{handler.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, handler);}}public async void SendObject(string SendString){try{if (!handler.Connected){MesApp.Instance.Const.SetMachineLog("Mes所在的Socket端口未连接,无法发送数据");return;}byte[] SendBytes = Encoding.UTF8.GetBytes(SendString);await Task.Run(() =>{// 通过Socket发送数据handler.Send(SendBytes, 0, SendBytes.Length, SocketFlags.None);});}catch (Exception ex){MesLog.Error("发送不带反馈的Socket数据失败:" + ex.ToString());}}public void Close(){try{handler.Shutdown(SocketShutdown.Both);//listener.Shutdown(SocketShutdown.Both);handler.Close();listener.Close();}catch (Exception){}}}
}
3.4日志类
using System;
using System.IO;
using System.Threading.Tasks;namespace Mes.Log
{internal static class MesLog{public enum LogLevel{Trace,Debug,Info,Warn,Error,Fatal}private static string logBasePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log", "Meslog");private static long maxFileSize = 5 * 1024 * 1024; // 5MBprivate static LogLevel minimumLevel = LogLevel.Trace;static MesLog(){if (!Directory.Exists(logBasePath)){Directory.CreateDirectory(logBasePath);}}private static string GetLogFilePath(){string date = DateTime.Now.ToString("yyyy-MM-dd");return Path.Combine(logBasePath, $"{date}.txt");}private static async Task AppendTextAsync(string text, string filePath){var fileOptions = FileOptions.Asynchronous;using (var fileStream = new FileStream(filePath,FileMode.Append, // 使用FileMode.Append以追加模式打开文件FileAccess.Write,FileShare.ReadWrite,bufferSize: 4096 * 10,fileOptions)){using (var streamWriter = new StreamWriter(fileStream)){// 异步写入文本到文件await streamWriter.WriteAsync(text);}}}public static async void Write(LogLevel level, string message){if (level < minimumLevel){return;}string timestamp = DateTime.Now.ToString("yyyy - MM - dd HH:mm:ss");string logMessage = $"{timestamp} [{level}]: {message}{Environment.NewLine}";string filePath = GetLogFilePath();if (File.Exists(filePath) && new FileInfo(filePath).Length >= maxFileSize){filePath = Path.Combine(logBasePath, $"{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt");}await AppendTextAsync(logMessage, filePath);}public static void Trace(string message){Write(LogLevel.Trace, message);}public static void Debug(string message){
#if DEBUGWrite(LogLevel.Debug, message);
#endif}public static void Info(string message){Write(LogLevel.Info, message);}public static void Warn(string message){Write(LogLevel.Warn, message);}public static void Error(string message){Write(LogLevel.Error, message);}public static void Fatal(string message){Write(LogLevel.Fatal, message);}}
}
3.5:配置参数类(使用Json格式)
using System;
using System.Windows;namespace Mes.Config
{[Serializable]public class MyMesConfig{/// <summary>/// 是否需要Mes控制软件,不需要情况下,减少线程开辟/// </summary>public bool IsMesControMachine { get; set; } = false;/// <summary>/// 是否显示选择客户页面/// </summary>public string IsShowSelectCustomer { get; set; } = Visibility.Visible.ToString();/// <summary>/// 是否开启Mes/// </summary>public bool IsEnableMes { get; set; } = false;/// <summary>/// Mes超时时间/// </summary>public int MesTimeOut { get; set; } = 5000;public string EquipmentID { get; set; } = "SMT01";public string MesAddress { get; set; } = "192.168.1.1";/// <summary>/// 选择客户选项卡的ID/// </summary>public int SelectedTabIndex { get; set; } = 0;/// <summary>/// 是否显示客户页面/// </summary>public string IsShowCustomer { get; set; } = Visibility.Collapsed.ToString();/// <summary>/// 选择的客户名称/// </summary>public string SelectCustomer { get; set; } = "选择Mes客户";public CustomerConfig.CustomerA CustomerA { get; set; } = new CustomerConfig.CustomerA();public CustomerConfig.CustomerB CustomerB { get; set; } = new CustomerConfig.CustomerB();}
}
3.6,实际使用的用户类参考
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;namespace Mes.Client
{/// <summary>/// 客户A/// </summary>public class CustomerA : IMesSend{private readonly byte[] buffer = new byte[1024 * 1024 * 100];private MesSocket socket = new MesSocket();private static readonly object _lockObject = new object();private Dictionary<string, CustomerARec> _recDic = new Dictionary<string, CustomerARec>();private Dictionary<string, CustomerARec> RecDic{get => _recDic;set{lock (_lockObject){_recDic = value;}}}DateTime HeartTime = DateTime.Now;private bool CustomerAIsConnect = false;private bool IsAlarm = false;private string Heade { get; } = "Header";public CustomerA(){socket.Address = MesApp.Instance.MyMesConfig.MesAddress;socket.Point = MesApp.Instance.MyMesConfig.CustomerA.Port;socket.CreatSocket();Receive();HeartTimeAndIsConnect();}private object lockObj = new object();private void Receive(){Task MesContralMachine = Task.Run(async () =>{while (true){lock (lockObj){if (socket.SocketQueue.Count > 0){string message = socket.SocketQueue.Dequeue();MesLog.Info($"接收客户AMes数据: {message}");MesProcessData ProcessData = MesXml.DeserializeXml(message);string MesInterface = ProcessData.FindValueByPath(new string[] { "Header", "MESSAGENAME" }, 0).ToString();switch (MesInterface){case "EAP_LinkTest_Request":EAP_LinkTest_Request_Accept(ProcessData);break;case "DATE_TIME_CALIBREATION_COMMAND":ChangeTime(ProcessData);break;case "ALARM_REPORT_R":case "EQP_STATUS_REPORT_R":case "JOB_RECEIVE_REPORT_R":case "JOB_SEND_REPORT_R":case "EDC_REPORT_R":case "JOB_REMOVE_RECOVERY_REPORT_R":CheckSend(ProcessData);break;case "123"://预留Mes控制设备部分MesApp.Instance.MesQueueAccept.Enqueue(ProcessData); break;default:break;}}}// 等待一段时间,避免忙等待await Task.Delay(10);}});}public bool MesEnable(){return MesApp.Instance.MyMesConfig.IsEnableMes && socket.handler.Connected;}public async Task<bool> AlarmInformation(string message, int Level){List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "AlarmStatus", MesValue = 0 });datas.Add(new MesProcessData { MesName = "AlarmLevel", MesValue = 0 });datas.Add(new MesProcessData { MesName = "AlarmCode", MesValue = "00" });datas.Add(new MesProcessData { MesName = "AlarmText", MesValue = message });MesDynamic dynamic = new MesDynamic();dynamic.AddressInterface = "ALARM_REPORT";IsAlarm = true;return await MesSend(datas, dynamic);}public void ProcessParameters(){return;}/// <summary>/// 过站检查/// </summary>/// <param name="BoardCode"></param>/// <returns></returns>public async Task<bool> CheckBoard(MesDynamic dynamic){List<MesProcessData> datas = new List<MesProcessData>();if (dynamic.BoardCode == "" || dynamic.BoardCode == null){dynamic.BoardCode = MesApp.Instance.Const.NowTimeF;}datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "JobID", MesValue = dynamic.BoardCode });dynamic.AddressInterface = "JOB_RECEIVE_REPORT";bool result = await MesSend(datas, dynamic);return result;}/// <summary>/// 发送设备状态/// </summary>/// <param name="on"></param>public async Task<bool> ProcessStop(MesEnum.MachineState on){if (!MesApp.Instance.Mes.MesEnable()){return true;}int EquipmentStatus = -1;switch (on){case MesEnum.MachineState.stop:EquipmentStatus = 3;break;case MesEnum.MachineState.start:EquipmentStatus = 1;break;case MesEnum.MachineState.RedLight:EquipmentStatus = 2;break;case MesEnum.MachineState.AwaitEnterBoard:EquipmentStatus = 4;break;case MesEnum.MachineState.AwaitOutBoard:EquipmentStatus = 5;break;default:EquipmentStatus = 2;break;}List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });List<MesProcessData> StationInfoList = new List<MesProcessData>();List<MesProcessData> StationInfo = new List<MesProcessData>();StationInfo.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName });StationInfo.Add(new MesProcessData { MesName = "EquipmentStatus", MesValue = EquipmentStatus });StationInfoList.Add(new MesProcessData { MesName = "StationInfo", MesValue = StationInfo.ToArray() });datas.Add(new MesProcessData { MesName = "StationInfoList", MesValue = StationInfoList.ToArray() });MesDynamic dynamic = new MesDynamic();dynamic.AddressInterface = "EQP_STATUS_REPORT";return await MesSend(datas, dynamic);}public async Task<bool> RemovePCB(MesDynamic data){if (!MesApp.Instance.Mes.MesEnable()){return true;}List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "JobID", MesValue = data.BoardCode });datas.Add(new MesProcessData { MesName = "RemoveFlag", MesValue = 0 });data.AddressInterface = "JOB_REMOVE_RECOVERY_REPORT";return await MesSend(datas, data);}public async Task<bool> Result(MesDynamic data){if (!MesApp.Instance.Mes.MesEnable()){return true;}List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID.ToString() });datas.Add(new MesProcessData { MesName = "JobID", MesValue = data.BoardCode.ToString() });datas.Add(new MesProcessData { MesName = "ProcessTime", MesValue = data.TimeCost.ToString() });datas.Add(new MesProcessData { MesName = "ProcessStartTime", MesValue = data.ProcessStartTime.ToString() });datas.Add(new MesProcessData { MesName = "ProcessEndTime", MesValue = data.ProcessEndTime.ToString() });List<MesProcessData> MesProcessDataList = new List<MesProcessData>();List<MesProcessData> ProcessData1 = new List<MesProcessData>();ProcessData1.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData1.Add(new MesProcessData { MesName = "Name", MesValue = "TotalResult" });ProcessData1.Add(new MesProcessData { MesName = "value", MesValue = int.Parse(data.VerifiedBoardResult) == 0 ? "NG" : "OK" });List<MesProcessData> ProcessData2 = new List<MesProcessData>();ProcessData2.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData2.Add(new MesProcessData { MesName = "Name", MesValue = "ProductCode" });ProcessData2.Add(new MesProcessData { MesName = "value", MesValue = data.BoardCode.ToString() });List<MesProcessData> ProcessData3 = new List<MesProcessData>();ProcessData3.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData3.Add(new MesProcessData { MesName = "Name", MesValue = "ProcessName" });ProcessData3.Add(new MesProcessData { MesName = "value", MesValue = data.ProduceName.ToString() });List<MesProcessData> ProcessData4 = new List<MesProcessData>();ProcessData4.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData4.Add(new MesProcessData { MesName = "Name", MesValue = "PartNum" });ProcessData4.Add(new MesProcessData { MesName = "value", MesValue = data.TPNumber.ToString() });List<MesProcessData> ProcessData5 = new List<MesProcessData>();ProcessData5.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData5.Add(new MesProcessData { MesName = "Name", MesValue = "NGNum" });ProcessData5.Add(new MesProcessData { MesName = "value", MesValue = data.NGTPNumber.ToString() });JArray Detailes = new JArray();foreach (var item in data.TPNGs){string codeName = string.Join(",", item.NGCodeName);JObject Detailedata = new JObject{{"Code", item.SubBoardCode},{"Results", "NG"},{"TEST_ITEM", item.TagNumber},{"Result", codeName},{"PartName", item.PartNumber}};Detailes.Add(Detailedata);}List<MesProcessData> ProcessData6 = new List<MesProcessData>();ProcessData6.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName });ProcessData6.Add(new MesProcessData { MesName = "Name", MesValue = "Details" });ProcessData6.Add(new MesProcessData { MesName = "value", MesValue = Detailes.ToString() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData1.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData2.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData3.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData4.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData5.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData6.ToArray() });datas.Add(new MesProcessData { MesName = "ProcessDataList", MesValue = MesProcessDataList.ToArray() });MesDynamic dynamic = new MesDynamic{AddressInterface = "EDC_REPORT"};return await MesSend(datas, dynamic, true); ;}public void SwitchPrograms(){return;}private static DateTime _lastCallTime = DateTime.MinValue;/// <summary>/// 发送客户A数据/// </summary>/// <param name="datas">数据源</param>/// <param name="dynamic">接口</param>/// <returns></returns>public async Task<bool> MesSend(List<MesProcessData> datas, MesDynamic dynamic, bool IsUseFix = false){Random random = new Random();int number = GetUniqueFiveDigitNumber();List<MesProcessData> Message = new List<MesProcessData>();List<MesProcessData> Head = new List<MesProcessData>();Head.Add(new MesProcessData { MesName = "MESSAGENAME", MesValue = dynamic.AddressInterface });Head.Add(new MesProcessData { MesName = "TRANSACTIONID", MesValue = MesApp.Instance.Const.NowTime.ToString("yyyyMMddHHmmssffff") + number });Head.Add(new MesProcessData { MesName = "MESSAGEID", MesValue = number.ToString() });Head.Add(new MesProcessData { MesName = "REPLYSUBJECTNAME", MesValue = MesApp.Instance.MyMesConfig.MesAddress + ":" + MesApp.Instance.MyMesConfig.CustomerA.Port });Message.Add(new MesProcessData { MesName = "Header", MesValue = Head.ToArray() });Message.Add(new MesProcessData { MesName = "Body", MesValue = datas.ToArray() });List<MesProcessData> MessageReturn = new List<MesProcessData>();MessageReturn.Add(new MesProcessData { MesName = "ReturnCode", MesValue = "" });MessageReturn.Add(new MesProcessData { MesName = "ReturnMessage", MesValue = "" });Message.Add(new MesProcessData { MesName = "Return", MesValue = MessageReturn.ToArray() });MesProcessData Top = new MesProcessData();Top.MesName = "Message";Top.MesValue = Message.ToArray();if ((DateTime.Now - _lastCallTime).TotalMilliseconds >= 500){_lastCallTime = DateTime.Now;MesSend_Accept(Top);}return await AwaitReceive(Top);}/// <summary>/// 发送数据后,将参数存储到字典中,等待超时和反馈信号/// </summary>/// <param name="mesData"></param>/// <returns></returns>private async Task<bool> AwaitReceive(MesProcessData Data){CancellationTokenSource cts1 = new CancellationTokenSource();CustomerARec meiDiRec = new CustomerARec();string Time = DateTime.Now.ToString();meiDiRec.Cts = cts1;string MESSAGEID = Data.FindValueByPath(new string[] { Heade, "MESSAGEID" }).ToString();RecDic.Add(MESSAGEID, meiDiRec);bool Result = false;Task task1 = Task.Run(async () =>{int timeoutCount = 0;while (true){try{Task.Delay(TimeSpan.FromMilliseconds(MesApp.Instance.MyMesConfig.MesTimeOut), cts1.Token).Wait(cts1.Token);}catch (OperationCanceledException ex){if (ex.CancellationToken == cts1.Token){if (RecDic[MESSAGEID].Result){RecDic.Remove(MESSAGEID);MesLog.Info(MESSAGEID + "return true");Result = true;return;}RecDic.Remove(MESSAGEID);Result = false;return;}timeoutCount++;if (timeoutCount >= MesApp.Instance.MyMesConfig.CustomerA.CustomerAReNumber){RecDic.Remove(MESSAGEID);MesLog.Error("Mes重新发送3次失败。接口为:" + Data.FindValueByPath(new string[] { Heade, "MESSAGENAME" }).ToString() + " 时间为:" + Time +"随机ID为:" + MESSAGEID);CustomerAIsConnect = false;MesApp.Instance.Const.SetMachinAlarm();break;}else{MesSend_Accept(Data);continue;}}await Task.Delay(5000 * 100);}Result = false;return;}, cts1.Token);await task1;return Result;}private void CheckSend(MesProcessData data){string MESSAGEID = data.FindValueByPath(new string[] { "Header", "MESSAGEID" }).ToString();if (RecDic.ContainsKey(MESSAGEID)){if (!data.FindValueByPath(new string[] { "Return", "ReturnCode" }).ToString().Contains("1")){MesLog.Error("Mes执行失败。 接口为:" + data.FindValueByPath(new string[] { "Header", "MESSAGENAME" }).ToString());}RecDic[MESSAGEID].Result = true;RecDic[MESSAGEID].Cts.Cancel();}else{MesLog.Error("Mes没有此发送数据");}}public void MesSend_Accept(MesProcessData Data){string data = MesXml.SerializeToXml(Data);data = Regex.Replace(data, @"<\?.*?\?>", "");//整理XMl的缩进XmlDocument xmlDoc = new XmlDocument();xmlDoc.LoadXml(data);StringBuilder sb = new StringBuilder();XmlWriterSettings settings = new XmlWriterSettings();settings.Indent = true; // 设置缩进为 truesettings.IndentChars = " "; // 设置缩进字符,这里使用两个空格using (XmlWriter writer = XmlWriter.Create(sb, settings)){xmlDoc.Save(writer);}data = sb.ToString();if (!socket.handler.Connected){MesApp.Instance.Const.SetMachineLog("客户AMes 没有连接");MesApp.Instance.Const.SetMachinAlarm();return;}socket.SendObject(data);}/// <summary>/// 接收心跳,反馈接收的心跳/// </summary>public void EAP_LinkTest_Request_Accept(MesProcessData Data){HeartTime = DateTime.Now;Data = Data.ModifyValueByPath(new string[] { "Header", "MESSAGENAME" }, "EAP_LinkTest_Request_R");Data = Data.ModifyValueByPath(new string[] { "Header", "REPLYSUBJECTNAME" }, MesApp.Instance.MyMesConfig.MesAddress + ":" + MesApp.Instance.MyMesConfig.CustomerA.Port);Data = Data.ModifyValueByPath(new string[] { "Return", "ReturnCode" }, "01");Data = Data.ModifyValueByPath(new string[] { "Return", "ReturnMessage" }, "指令执行成功");MesSend_Accept(Data);}public bool AwaitHeartTime(TimeSpan time){if (HeartTime + time < DateTime.Now){return false;}return true;}public async Task<bool> OutBoard(MesDynamic dynamic){List<MesProcessData> datas = new List<MesProcessData>();if (dynamic.BoardCode == "" || dynamic.BoardCode == null){dynamic.BoardCode = MesApp.Instance.Const.NowTimeF;}datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "JobID", MesValue = dynamic.BoardCode });dynamic.AddressInterface = "JOB_SEND_REPORT";bool result = await MesSend(datas, dynamic);return result;}/// <summary>/// Mes校准系统时间/// </summary>/// <param name="data"></param>public void ChangeTime(MesProcessData data){data = data.ModifyValueByPath(new string[] { "Header", "MESSAGENAME" }, "DATE_TIME_CALIBREATION_COMMAND_R");data = data.ModifyValueByPath(new string[] { "Header", "REPLYSUBJECTNAME" }, MesApp.Instance.MyMesConfig.MesAddress + ":" + MesApp.Instance.MyMesConfig.CustomerA.Port);try{string input = data.FindValueByPath(new string[] { "Body", "DateTime" }).ToString();string format = "yyyyMMddHHmmss";DateTime result = DateTime.ParseExact(input, format, null);DateTime dt = result;bool r = UpdateTime.SetDate(dt);if (r){data = data.ModifyValueByPath(new string[] { "Return", "ReturnCode" }, "01");data = data.ModifyValueByPath(new string[] { "Return", "ReturnMessage" }, "指令执行成功");}else{data = data.ModifyValueByPath(new string[] { "Return", "ReturnCode" }, "02");data = data.ModifyValueByPath(new string[] { "Return", "ReturnMessage" }, "指令执行失败");}}catch (Exception ex){data = data.ModifyValueByPath(new string[] { "Return", "ReturnCode" }, "02");data = data.ModifyValueByPath(new string[] { "Return", "ReturnMessage" }, "指令执行失败");MesLog.Error("Mes矫正时间失败:" + ex.Message);}MesSend_Accept(data);}private static Random _random = new Random();private static HashSet<int> _uniqueNumbers = new HashSet<int>();/// <summary>/// 获取五位的随机数/// </summary>/// <returns></returns>static int GetUniqueFiveDigitNumber(){int fiveDigitNumber;do{fiveDigitNumber = _random.Next(10000, 100000);} while (!_uniqueNumbers.Add(fiveDigitNumber));return fiveDigitNumber;}public async Task<bool> CancelAlarmInformation(string message){if (!IsAlarm){return true;}IsAlarm = false;message += ",报警已消除";List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "AlarmStatus", MesValue = 1 });datas.Add(new MesProcessData { MesName = "AlarmLevel", MesValue = 0 });datas.Add(new MesProcessData { MesName = "AlarmCode", MesValue = "10" });datas.Add(new MesProcessData { MesName = "AlarmText", MesValue = message });MesDynamic dynamic = new MesDynamic();dynamic.AddressInterface = "ALARM_REPORT";return await MesSend(datas, dynamic);}/// <summary>/// 监听心跳/// </summary>private void HeartTimeAndIsConnect(){Task task1 = Task.Run(() =>{while (true){if (!AwaitHeartTime(MesApp.Instance.MyMesConfig.CustomerA.HeartTime)){MesLog.Error("Mes心跳异常");MesApp.Instance.Const.SetMachinAlarm();CloseMes();break;}}});}public void CloseMes(){socket.Close();}public async Task<MesProcessData> MesLogin(MesDynamic dynamic){List<MesProcessData> resultList1 = new List<MesProcessData>();resultList1.Add(new MesProcessData { MesName = "IsEnable", MesValue = true });return new MesProcessData { MesValue = resultList1.ToArray() };}public Task<MesProcessData> Dynamic(MesDynamic dynamic){throw new NotImplementedException();}public class CustomerARec{public CancellationTokenSource Cts { get; set; } = new CancellationTokenSource();public bool Result { get; set; } = false;}}
}
3.7,框架整体解析
如下图所示:整体框架包含7个部分。
1:Client,客户类。由于每个客户都有独特的定制需求,所以所有的客户定制的内容都存放再Client中,便于管理
2:CommModel,通讯类。存放每种不同通讯方式的方法,通常最常使用的是Http和Socket,如果有另外特殊的通讯模式还可以单独编写。
3:Config,配置文件类。推荐使用可读的Json格式。一开始编写Mes时候就有一个客户是在登陆工程师级别账户时要求Mes通讯同意,但是由于客户Mes在升级无法通讯,所有软件登陆不了工程师级别,无法关闭Mes,造成需要软件重新配置参数。所以需要在特殊情况下,可以手动配置Mes参数。不同的客户Mes配置也是单独做出区分即可,在打开软件时将json反序列化到MesApp下面的Config字段中,即可全局使用。
4.Const,参数类。MesConst,用于存放软件中需要使用但是不需要保存的配置参数。MesData,用于存放自定义的数据结构。MesDynamic,一个动态数据结构类,这个类存在的意义在于,所有的接口传入传出都可以使用这个类对象,所有的数据都可以在这个类中建立新的字段,这个是考虑在,某些定制的客户中,一些框架满足不了的需求,可以在这里做新增内容。MesEnum,枚举类,用来存入Mes的枚举,例如客户枚举,设备状态代码等。其中包含,访问Description参数的方法。
5.Log,日志类,将mes的日志与原有的软件日志做区分防止单日的日志过多。
6.Model,方法类,这里存放着Json和Xml的序列化和反序列化的方法类,就暂时来说,客户的序列化都是Json或者Xml的形式。
7.接口类和MesApp的单例入口
3.8,举例部分方法使用方式
报警和取消报警的调用
public async void Alarm(bool enable, int emgLight = -1, string message = "Alarm")
{ChangeEMGLight(enable ? MachineConsts.EMG_ERROR : emgLight == -1 ? MachineConsts.EMG_RUNNING : emgLight);if (MesApp.Instance.Mes.MesEnable() && !enable){MesApp.Instance.Const.ProcessState = MesEnum.MachineState.GreenLight;if (!await MesApp.Instance.Mes.CancelAlarmInformation(message)){LogController.Instance.Error("上传Mes取消报警信息失败");}}if (MesApp.Instance.Mes.MesEnable() && enable){MesApp.Instance.Const.ProcessState = MesEnum.MachineState.RedLight;if (!await MesApp.Instance.Mes.AlarmInformation(message, 2)){LogController.Instance.Error("上传Mes报警信息失败");}}_machine.EnableBuzzer(enable);
}
3.9,UI部分
由于我使用的WPF框架,对winform,QT的框架并不是很熟悉,所以这里只使用WPF框架的内容作为参考
1.选择厂商的UI。WPF的UI其实编写很简单,核心在于Visibility=“{Binding }”>的使用,例如,在选择厂商前,需要将选择厂商的选项卡显示,厂商选项卡隐藏,那么将选择厂商选项卡的binding设置为显示,其他全部厂商选项卡设置为隐藏。同理选择完厂商后就将指定厂商的选项卡显示即可。
2.参数的Binding,如果是需要保存在配置文件中的,可以使用Binding的Value指向Config的类即可。
3.由于UI部分难度不高,并且大家UI都是不一样的,所以这里仅说一下我是怎么使用UI的
<TabItemWidth="200"Height="24"FontSize="15"Header="请选择Mes厂商"Style="{StaticResource OverrideMaterialDesignNavigationRailTabItem}"Visibility="{Binding IsShowSelectCustomer}"><Grid IsEnabled="{Binding Source={x:Static service:ApplicationStateService.Instance}, Path=LoginUser.UserGroupKey, Converter={StaticResource LoginUserAuthorityToIsEnableConverter}, ConverterParameter={x:Static enums:AuthorityKeys.SoftwareOptions}}"><Grid.RowDefinitions><RowDefinition Height="auto" /><RowDefinition Height="auto" /></Grid.RowDefinitions><StackPanel><Grid Margin="8"><Grid.ColumnDefinitions><ColumnDefinition Width="200" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><TextBlockHorizontalAlignment="Right"VerticalAlignment="Center"Text="选择需要使用的Mes系统:" /><ComboBoxName="客户名称"Grid.Column="1"Height="30"Margin="0,0,8,0"Cursor="IBeam"ItemsSource="{Binding Path=CustomerName}"Style="{DynamicResource MaterialDesignComboBox}"Text="{Binding SelectCustomer, UpdateSourceTrigger=PropertyChanged}" /></Grid><ButtonMargin="5"HorizontalAlignment="Center"VerticalAlignment="Center"Command="{Binding OpenMesUI}"CommandParameter="ALM"Content="开启Mes配置界面" /></StackPanel></Grid>
</TabItem>
4.总结
整个标准Mes框架并不是很难。难点只有一个是如果将数据灵活应用,在常见编写Mes时,由于每个客户的数据结构都是不一样的,每个客户都需要单独开多个数据结构类去序列化和反序列化,当然这个并没有错,只是这样子会导致代码冗杂量非常大,而且维护难度大,代码命名混乱的问题。所以标准框架主要还是提供一个如何解决数据灵活性的思路而已。除去这个以外,其他内容并不是很难,剩下的就是如何规范后续客户的扩展性,和如何高效的实现解耦和代码迁移。
最后,如果有什么想法可以持续交流。有时间的话,这个标准接口框架还会随着我接入Mes的次数而优化。
相关文章:
C#标准Mes接口框架(持续更新)
前言 由于近期我做了好几个客户的接入工厂Mes系统的需求。但是每个客户的Mes都有不同程度的定制需求,原有的代码复用难度其实很大。所以打算将整个接入Mes系统的框架单独拿出来作为一个项目使用,同时因为不同的设备接入同一个Mes系统,所以代…...
22_设计方案(4.1.7)
4.1.7 数据组织存储实例 全区所有数据库信息根据业务使用范围存储在互联网区、政务外网区、自然资源业务网区的服务器。互联网区的服务器主要存储互联网数据库。政务外网区的服务器主要存储全区所有市、县(区)不动产登记业务库、档案库、后台管理库、工作流库,全区共享查询…...
Ansys Thermal Desktop 概述
介绍 Thermal Desktop 是一种用于热分析和流体分析的通用工具。它可用于组件或系统级分析。 来源:CRTech 历史 Thermal Desktop 由 C&R Technologies (CR Tech) 开发。它采用了 SINDA/FLUINT 求解器。SINDA/FLUINT 最初由 CR Tech 的创始人为 NASA 的约翰逊航…...
PageView组件的功能和用法
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了如何屏蔽事件关的内容,本章回中将介绍PageView Widget.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的PageView是指左右滑动或者上下滑动显示不同的页面,Flutter把它…...
自动化实现的思路变化
阶段一: 1、成功调用。第一步,一般是用现用的工具,或者脚本成功调用接口 2、解决关联接口的参数传递。有的接口直接,存在参数的传递,一般的思路,就是将这个参数设置为变量。 3、简化代码。总会有些东西是重…...
微信小程序启动小程序APP Page Component创建顺序
之前之后的打印 都是在()之外...
从 UTC 日期时间字符串获取 Unix 时间戳:C 和 C++ 中的挑战与解决方案
在编程世界里,从 UTC 日期时间字符串获取 Unix 时间戳,看似简单,实则暗藏玄机。你以为输入一个像 “Fri, 17 Jan 2025 06:07:07” 这样的 UTC 时间,然后轻松得到 1737094027(从 1970 年 1 月 1 日 00:00:00 UTC 开始经…...
[Spring] Gateway详解
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...
计数排序算法
基本思想 先确定待排序数组的最大值(Max)和最小值(Min),随后创建Max - Min 1个长度的数组称为计数数组,计数数组的索引对应着待排序数组中元素的值,数组的值表示该元素的出现次数。通过从前往…...
Spring--基于注解的声明式事务
基于注解的声明式事务 1.选择一个合适的事务管理器实现加入到ioc容器 2.使用注解指定哪些方法需要添加事务即可 1.事务属性:只读 // readOnly true把当前事务设置为只读 默认是false! Transactional(readOnly true)Transactional注解放在类上 生效原则 如果一…...
SQL-leetcode—1164. 指定日期的产品价格
1164. 指定日期的产品价格 产品数据表: Products ---------------------- | Column Name | Type | ---------------------- | product_id | int | | new_price | int | | change_date | date | ---------------------- (product_id, change_date) 是此表的主键(具…...
微服务搭建----springboot接入Nacos2.x
springboot接入Nacos2.x nacos之前用的版本是1.0的,现在重新搭建一个2.0版本的,学如逆水行舟,不进则退,废话不多说,开搞 1、 nacos2.x搭建 1,首先第一步查询下项目之间的版本对照,不然后期会…...
JavaEE:多线程进阶
JavaEE:多线程进阶 一、对比不同锁策略之间的应用场景及其区别1. 悲观锁 和 乐观锁1.1 定义和原理1.2 应用场景1.3 示例代码 2. 重量级锁 和 轻量级锁2.1 定义和原理2.2 应用场景2.3 示例代码 3. 挂起等待锁 和 自旋锁3.1 定义和原理3.2 应用场景3.3 示例代码 4. 几…...
软件测试 —— jmeter(2)
软件测试 —— jmeter(2) HTTP默认请求头(元件)元件作用域和取样器作用域HTTP Cookie管理器同步定时器jmeter插件梯度压测线程组(Stepping Thread Group)参数解析总结 Response Times over TimeActive Thre…...
[java] 面向对象进阶篇1--黑马程序员
目录 static 静态变量及其访问 实例变量及其访问 静态方法及其访问 实例方法及其访问 总结 继承 作用 定义格式 示例 总结 子类不能继承的内容 继承后的特点 成员变量 成员变量不重名 成员变量重名 super访问父类成员变量 成员方法 成员方法不重名 成员方法…...
openstack单机安装
openstack单机安装 网卡配置安装依赖开启虚拟环境修改配置文件 部署openstack部署openstack客户端访问可视化界面Horizon补充 本篇主要讲述Ubuntu2204单机安装openstackstable/2024.2。其他版本的Linux系统或者openstack版本,请参考openstack官网。 网卡配置 需要配…...
OFD、PDF 电子签章系统处理流程
在C#中实现电子签章系统的处理流程,可以参考以下步骤和技术实现: 1. 电子签章系统的基本流程 电子签章系统的核心流程包括以下几个步骤: 密钥生成:生成公钥和私钥对,私钥由签章人保管,公钥用于验证签名。…...
「 机器人 」系统辨识实验浅谈
前言 系统辨识实验是一种通过实验和数据分析的方法,用于建立物理系统的数学模型的技术。系统辨识是控制工程和系统科学中的重要环节,尤其是在模型未知或复杂的情况下。以下是系统辨识实验的详细介绍: 1. 系统辨识实验的目的 1.1 建模 为动态系统(如机械系统、电气系统或生…...
15.7k!DISM++一款快捷的系统优化工具
软件介绍 链接 软件介绍 dism是一款由初雨团队开发的win系统优化工具,可当作是微软系统命令行工具dism的GUI版本。可用作系统垃圾清除、启动项管理、程序卸载、驱动管理、系统优化等 该工具个人感觉最重要的就是系统优化选项,它将一些实用、无用或者没…...
Windows10安装MySQL找不到MSVCR120.dll和MSVCP120.dll问题解决
个人博客地址:Windows10安装MySQL找不到MSVCR120.dll和MSVCP120.dll问题解决 | 一张假钞的真实世界 msvcp120.dll、msvcr120.dll、vcomp120.dll属于VC2013版中的动态链接库,如果丢失重新安装VC2013即可。下载地址:https://www.microsoft.com…...
Vue 3 30天精进之旅:Day 03 - Vue实例
引言 在前两天的学习中,我们成功搭建了Vue.js的开发环境,并创建了我们的第一个Vue项目。今天,我们将深入了解Vue的核心概念之一——Vue实例。通过学习Vue实例,你将理解Vue的基础架构,掌握数据绑定、模板语法和指令的使…...
被遮挡QT窗口置顶
问题描述 开发环境:windows QT 需求: 单击托盘将桌面窗口在被遮挡的情况下置顶解决方案 方案1 资料链接 代码实现 Qt::WindowFlags flags windowFlags(); this->setWindowFlags((flags | Qt::WindowStaysOnTopHint)); this->showMaximized();…...
Apache Flink 概述学习笔记
一、引言 在大数据处理领域,Apache Flink 是一个极具影响力的开源流批一体化计算框架,它以其独特的架构和强大的功能,为大规模数据处理提供了高效、灵活的解决方案。 二、基本概念 Flink 是什么:Flink 是一个分布式流批处理框架…...
系统思考—复杂问题的根源分析
在企业中,许多问题看似简单,背后却潜藏着复杂的因果关系。传统的思维方式往往只能看到表面,而无法深入挖掘问题的真正根源。我们常常通过“表面解决”来应对眼前的症状,但这往往只是治标不治本。 比如,销量下降时&…...
Python 之 Excel 表格常用操作
示例文件 test.xlsx 将各个表单拆分成单独的 Excel 文件 import os.pathimport openpyxl import pandasdef handle_excel(file_path):dirname os.path.dirname(file_path)basename os.path.basename(file_path).split(".")[0]wb openpyxl.load_workbook(file_pat…...
《用DOTS解决实际需求》集锦
去年作者发布了一篇《DOTS-ECS系列课程》,深受同学们的好评!前期课程是基于0.51版本录制的,DOTS升级至1.0版本后,同学们纷纷希望能使用DOTS 1.0版本录制实战课程。 今年作者带着DOTS 1.0版本的实战课程回来啦!&#x…...
【MySQL】存储引擎有哪些?区别是什么?
频率难度60%⭐⭐⭐⭐ 这个问题其实难度并不是很大,只是涉及到的相关知识比较繁杂,比如事务、锁机制等等,都和存储引擎有关系。有时还会根据场景选择不同的存储引擎。 下面笔者将会根据几个部分尽可能地讲清楚 MySQL 中的存储引擎࿰…...
ios打包:uuid与udid
ios的uuid与udid混乱的网上信息 新人开发ios,发现uuid和udid在网上有很多帖子里是混淆的,比如百度下,就会说: 在iOS中使用UUID(通用唯一识别码)作为永久签名,通常是指生成一个唯一标识…...
Jadx动态调试安卓逆向
adb shell su ls 找到default.prop cat default.prop ro.debuggable0(代表没有调试权限) adb shell getprop ro.debuggable # 检查设备是否可调试(1可调试) adb shell getprop ro.product.cpu.abi # 获取设备 CPU 架构(如 arm64-v…...
在Ubuntu上使用Apache+MariaDB安装部署Nextcloud并修改默认存储路径
一、前言 Nextcloud 是一款开源的私有云存储解决方案,允许用户轻松搭建自己的云服务。它不仅支持文件存储和共享,还提供了日历、联系人、任务管理、笔记等丰富的功能。本文将详细介绍如何在 Ubuntu 22.04 LTS 上使用 Apache 和 MariaDB 安装部署 Nextcl…...
FPGA实现任意角度视频旋转(二)视频90度/270度无裁剪旋转
本文主要介绍如何基于FPGA实现视频的90度/270度无裁剪旋转,关于视频180度实时旋转,请见本专栏前面的文章,旋转效果示意图如下: 为了实时对比旋转效果,采用分屏显示进行处理,左边代表旋转前的视频在屏幕中…...
六、深入了解DI
依赖注入是⼀个过程,是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源,⽽资源指的就是对象. 在上⾯程序案例中,我们使⽤了 Autowired 这个注解,完成了依赖注⼊的操作. 简单来说,就是把对象取出来放到某个类的属性中。 关于依赖注…...
kotlin内联函数——let,run,apply,also,with的区别
一、概述 为了帮助您根据使用场景选择合适的作用域函数(scope function),我们将对它们进行详细描述并提供使用建议。从技术上讲,许多情况下范围函数是可以互换使用的,因此示例中展示了使用它们的约定俗成的做法。 1.…...
火语言RPA—超级鹰打码
🚩【组件功能】:通过传入图像返回图像中的文字或结果信息 针对不同类型图片形式的验证码,提交至平台api,以字符串形式返回图片识别结果。 配置预览 配置说明 文件路径 支持T或# 默认FLOW输入项 待识别本地图片的完整路径。 用…...
C#新语法
目录 顶级语句(C#9.0) using 全局using指令(C#10.0) using资源管理问题 using声明(C#8.0) using声明陷阱 错误写法 正确写法 文件范围的命名空间声明(C#10.0) 可空引用类型…...
Cloudflare通过代理服务器绕过 CORS 限制:原理、实现场景解析
第一部分:问题背景 1.1 错误现象复现 // 浏览器控制台报错示例 Access to fetch at https://chat.qwenlm.ai/api/v1/files/ from origin https://ocr.doublefenzhuan.me has been blocked by CORS policy: Response to preflight request doesnt pass access con…...
lightgbm做分类
python import pandas as pd#导入csv文件的库 import numpy as np#进行矩阵运算的库 import json#用于读取和写入json数据格式#model lgb分类模型,日志评估,早停防止过拟合 from lightgbm import LGBMClassifier,log_evaluation,early_stopping #metric from sklearn.metrics …...
下载Visual Studio Community 2019
官方链接如下:Visual Studio Community 2019下载链接 https://learn.microsoft.com/zh-cn/visualstudio/releases/2019/system-requirements#download 目前官方仅建议2022版,已经关闭vs2019等旧版本,哪天开放了,记得踢我一下。 …...
深入理解MySQL事务(万字详)
文章目录 什么是事务为什么会出现事务事务的版本支持事务的提交方式事务常见操作方式正常演示 - 证明事务的开始与回滚非正常演示1 - 证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)非正常演示2 - 证明commit了…...
FFPlay命令全集合
FFPlay是以FFmpeg框架为基础,外加渲染音视频的库libSDL构建的媒体文件播放器。 ffplay工具下载并播放视频,可以辅助卡看流信息。 官网下载地址:http://ffmpeg.org/download.html#build-windows 下载build好的exe程序: 此处下载…...
AI编程工具使用技巧:在Visual Studio Code中高效利用阿里云通义灵码
AI编程工具使用技巧:在Visual Studio Code中高效利用阿里云通义灵码 前言一、通义灵码介绍1.1 通义灵码简介1.2 主要功能1.3 版本选择1.4 支持环境 二、Visual Studio Code介绍1.1 VS Code简介1.2 主要特点 三、安装VsCode3.1下载VsCode3.2.安装VsCode3.3 打开VsCod…...
开源的Text-to-SQL工具WrenAI
WrenAI是一个开源的Text-to-SQL工具,旨在通过自然语言交互界面,帮助用户更便捷地查询数据库。以下是对WrenAI的详细介绍: 一、主要功能 自然语言交互:用户可以通过对话方式提出问题,WrenAI能够理解和解析复杂的查询需…...
python创建一个httpServer网页上传文件到httpServer
一、代码 1.server.py import os from http.server import SimpleHTTPRequestHandler, HTTPServer import cgi # 自定义请求处理类 class MyRequestHandler(SimpleHTTPRequestHandler):# 处理GET请求def do_GET(self):if self.path /:# 响应200状态码self.send_response(2…...
Linux中page、buffer_head、bio的关系
在Linux中,page、buffer_head、bio这三个概念紧密相关,共同构成了块设备I/O和内存管理的重要部分,它们的联系主要体现在以下方面: page与buffer_head 基于page构建:buffer_head通常是基于page来构建的,一…...
C++11新特性之decltype
1.decltype的作用 decltype是C11新增的一个关键字,与auto的功能一样,都是在编译期间推导变量类型的。不了解auto的可以转到——C11新特性之auto。 为什么引入decltype?看过上边那篇博客的读者应该知道auto在有些场景中并不适用,所以引入declt…...
对神经网络基础的理解
目录 一、《python神经网络编程》 二、一些粗浅的认识 1) 神经网络也是一种拟合 2)神经网络不是真的大脑 3)网络构建需要反复迭代 三、数字图像识别的实现思路 1)建立一个神经网络类 2)权重更新的具体实现 3&am…...
后端开发Web
Maven Maven是apache旗下的一个开源项目,是一款用于管理和构建java项目的工具 Maven的作用 依赖管理 方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题 统一项目结构 提供标准、统一的项目结构 项目构建 标准跨平台(…...
QT 通过ODBC连接数据库的好方法:
效果图: PWD使用自己的,我的这是自己的,所以你用不了。 以下是格式。 // 1. 设置数据库连接 QSqlDatabase db QSqlDatabase::addDatabase("QODBC");// 建立和QMYSQL数据库的连接 // 设置数据库连接名称(DSN&am…...
【Feature Scaling】:加速梯度下降法的利器
目录 特征缩放的目的常见的特征缩放方法1. 最小-最大缩放(Min-Max Scaling)2. 标准化(Standardization 或 Z-Score Normalization)3. 最大绝对值缩放(Max Abs Scaling) Rescale的使用场景结论 在机器学习中…...
QT:控件属性及常用控件(3)-----输入类控件(正则表达式)
输入类控件既可以进行显示,也能让用户输入一些内容! 文章目录 1.Line Edit1.1 用户输入个人信息1.2 基于正则表达式的文本限制1.3 验证两次输入的密码是否一致1.4 让输入的密码可以被查看 2.Text Edit2.1 输入和显示同步2.1 其他信号出发情况 3.ComboBox…...