Parcourir la source

添加多个扩展方法和新类以增强功能

在 `AssemblyExtension.cs` 中添加了 `RemoveDuplicateAssemblies` 和 `RemoveTheAssemblyNamespecified` 方法。
在 `LinqExtension.cs` 中添加了 `DistinctItemBy` 方法。
在 `RedisExtension.cs` 中添加了 `Check` 方法。
在 `SqlExtension.cs` 中添加了多个锁相关方法。
添加了 `TypeExtension.cs` 文件,包含多个类型转换和扩展方法。
在 `TypeConversionHelper.cs` 中添加了 `TypeConversionHelper` 类。
在 `RedisClientList.cs` 中添加了 `RedisClientList` 类。
在 `RedisHub.cs` 中添加了 `RedisHub` 类。
在 `ServiceCenter.cs` 中添加了 `ServiceHub` 静态类。
在 `ServiceCenter.csproj` 中添加了项目文件。
在 `SqlSugarHelper.cs` 中添加了 `SqlSugarHelper` 类。
在 `PlcData.cs` 中添加了 `PlcData` 和 `PLCData` 类。
在 `APICaller.cs` 中添加了 `APICaller` 类。
在 `Attributes.cs` 中添加了 `BelongToAttribute` 和 `OrderAttribute` 类。
在 `Configs.cs` 中添加了 `Configs` 静态类。
在 `Data.cs` 中添加了 `PLCType` 枚举和多个结构体。
在 `DataBlock.cs` 中添加了 `DataBlock` 类。
在 `Device.cs` 中添加了 `EntityEx<T>` 类和多个 `Device` 类的泛型版本。
在 `Extentions.cs` 中添加了 `Extentions` 和 `BitExtension` 类。
在 `Flags.cs` 中添加了 `DeviceFlags` 枚举。
在 `Generator.cs` 中添加了 `Generator<T, TBase>` 类。
在 `IProtocolProxy.cs` 中添加了 `IProtocolProxy` 接口。
在 `Ltc.cs` 中添加了 `Ltc` 类。
在 `PLC.cs` 中添加了 `PLC` 类。
在 `Path.cs` 中添加了 `DevicePath` 类。
在 `PlcItem.cs` 中添加了 `PlcItem` 类。
在 `ProtocolProxyBase.cs` 中添加了 `ProtocolProxyBase` 类。
在 `System.cs` 中添加了 `SystemBase` 类及其泛型版本。
在 `VitrualRedisPLC.cs` 中添加了 `VitrualRedisPLC` 类。
在 `WCS - Backup.Core.csproj` 和 `WCS.Core.csproj` 中添加了对 `FreeRedis` 包的引用。
在 `World.cs` 中添加了 `World` 类。
在 `DevRunInfo.cs` 中添加了 `GetStartAddress` 和 `GetTargetAddress` 方法。
在 `QuestDBBaseEntity.cs` 中添加了三个新的字段。
在 `IRobot521.cs` 中添加了 `using SqlSugar;` 语句。
在 `StationEnum.cs` 中修改了 `LineStatusEnum` 枚举。
在 `WCS_Station222.cs` 中添加了 `using System;` 语句和两个新的字段。
在 `WCS.WorkEngineering.csproj` 中修改了项目引用路径。
在 `WorkStart.cs` 中添加了对 `IStation222` 协议的支持。
林豪 左 il y a 10 mois
Parent
commit
682d16c7aa
39 fichiers modifiés avec 5252 ajouts et 8 suppressions
  1. 45 0
      ServiceCenter/Extensions/AssemblyExtension.cs
  2. 26 0
      ServiceCenter/Extensions/LinqExtension.cs
  3. 46 0
      ServiceCenter/Extensions/RedisExtension.cs
  4. 118 0
      ServiceCenter/Extensions/SqlExtension.cs
  5. 726 0
      ServiceCenter/Extensions/TypeExtension.cs
  6. 155 0
      ServiceCenter/Helpers/TypeConversionHelper.cs
  7. 38 0
      ServiceCenter/Redis/RedisClientList.cs
  8. 204 0
      ServiceCenter/Redis/RedisHub.cs
  9. 160 0
      ServiceCenter/ServiceCenter.cs
  10. 34 0
      ServiceCenter/ServiceCenter.csproj
  11. 197 0
      ServiceCenter/SqlSugars/SqlSugarHelper.cs
  12. 177 0
      ServiceCenter/Virtual_PLC/PlcData.cs
  13. 126 0
      ServiceCenter/WebApi/APICaller.cs
  14. 29 0
      WCS.Core/Attributes.cs
  15. 22 0
      WCS.Core/Configs.cs
  16. 34 0
      WCS.Core/Data.cs
  17. 417 0
      WCS.Core/DataBlock.cs
  18. 308 0
      WCS.Core/Device.cs
  19. 483 0
      WCS.Core/Extentions.cs
  20. 24 0
      WCS.Core/Flags.cs
  21. 343 0
      WCS.Core/IL/Generator.cs
  22. 11 0
      WCS.Core/IL/IProtocolProxy.cs
  23. 239 0
      WCS.Core/Ltc.cs
  24. 48 0
      WCS.Core/PLC.cs
  25. 55 0
      WCS.Core/Path.cs
  26. 129 0
      WCS.Core/PlcItem.cs
  27. 152 0
      WCS.Core/ProtocolProxyBase.cs
  28. 261 0
      WCS.Core/System.cs
  29. 32 0
      WCS.Core/VitrualRedisPLC.cs
  30. 13 0
      WCS.Core/WCS - Backup.Core.csproj
  31. 21 0
      WCS.Core/WCS.Core.csproj
  32. 484 0
      WCS.Core/World.cs
  33. 41 1
      WCS.Entity.Protocol/HUB/DevRunInfo.cs
  34. 6 0
      WCS.Entity.Protocol/QuestDBBaseEntity.cs
  35. 1 0
      WCS.Entity.Protocol/Robot/IRobot521.cs
  36. 2 2
      WCS.Entity.Protocol/Station/StationEnum.cs
  37. 10 1
      WCS.Entity.Protocol/Station/WCS_Station222.cs
  38. 1 1
      WCS.WorkEngineering/WCS.WorkEngineering.csproj
  39. 34 3
      WCS.WorkEngineering/WorkStart.cs

+ 45 - 0
ServiceCenter/Extensions/AssemblyExtension.cs

@@ -0,0 +1,45 @@
+using System.Reflection;
+
+namespace ServiceCenter.Extensions
+{
+    /// <summary>
+    /// 针对程序集操作的扩展
+    /// </summary>
+    public static class AssemblyExtension
+    {
+        /// <summary>
+        /// 删除重复的程序集
+        /// </summary>
+        /// <param name="assemblies"></param>
+        public static IEnumerable<Assembly> RemoveDuplicateAssemblies(this IEnumerable<Assembly> assemblies)
+        {
+            return assemblies.DistinctBy(p => p.FullName);
+        }
+
+        /// <summary>
+        /// 删除指定名称的程序集
+        /// </summary>
+        /// <param name="assemblyNames"></param>
+        /// <returns></returns>
+        public static IEnumerable<AssemblyName> RemoveTheAssemblyNamespecified(this IEnumerable<AssemblyName> assemblyNames)
+        {
+            return assemblyNames.Where(p => p.Name != null && !p.Name.StartsWith("System."))
+                .Where(p => !p.Name.StartsWith("mscorlib."))
+                .Where(p => !p.Name.StartsWith("netstandard."))
+                .Where(p => !p.Name.StartsWith("Senparc."))
+                .Where(p => !p.Name.StartsWith("Newtonsoft."))
+                .Where(p => !p.Name.StartsWith("UnityEngine."))
+                .Where(p => !p.Name.StartsWith("ZKWeb."))
+                .Where(p => !p.Name.StartsWith("NPOI."))
+                .Where(p => !p.Name.StartsWith("ICSharpCode."))
+                .Where(p => !p.Name.StartsWith("NLog"))
+                .Where(p => !p.Name.StartsWith("e_sqlite"))
+                .Where(p => !p.Name.StartsWith("MsgPack."))
+                .Where(p => !p.Name.StartsWith("netstandard"))
+                .Where(p => !p.Name.StartsWith("Jiguang"))
+                .Where(p => !p.Name.StartsWith("StackExchange"))
+                .Where(p => !p.Name.StartsWith("Microsoft."))
+                .Where(p => !p.Name.StartsWith("TIBCO."));
+        }
+    }
+}

+ 26 - 0
ServiceCenter/Extensions/LinqExtension.cs

@@ -0,0 +1,26 @@
+namespace ServiceCenter.Extensions
+{
+    /// <summary>
+    /// Linq 扩展
+    /// </summary>
+    public static class LinqExtension
+    {
+        /// <summary>
+        /// 根据指定的属性返回集合中的非重复元素
+        /// </summary>
+        /// <typeparam name="TSource">数据源的类型</typeparam>
+        /// <typeparam name="TKey">数据源需要进行筛选的属性</typeparam>
+        /// <param name="source">数据源</param>
+        /// <param name="keySelector">指定属性的函数表达式</param>
+        /// <returns></returns>
+        public static IEnumerable<TSource> DistinctItemBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
+        {
+            var seenKeys = new HashSet<TKey>();
+            foreach (var source1 in source)
+            {
+                if (seenKeys.Add(keySelector(source1)))
+                    yield return source1;
+            }
+        }
+    }
+}

+ 46 - 0
ServiceCenter/Extensions/RedisExtension.cs

@@ -0,0 +1,46 @@
+using FreeRedis;
+
+namespace ServiceCenter.Extensions
+{
+    public static class RedisExtension
+    {
+        /// <summary>
+        /// 检查Redis中是否有对应key且value不为空
+        /// </summary>
+        /// <param name="redisClient">redis连接</param>
+        /// <param name="key">要检查的Key</param>
+        /// <returns>
+        /// 1.key不存在,创建这个key value默认为三个空格符。返回null
+        /// 2.key存在单value为空 返回null
+        /// 3.key存在value不为空 返回获取到的值
+        /// </returns>
+        public static string? Check(this RedisClient redisClient, string key)
+        {
+            var result = redisClient.Get(key);
+            if (!string.IsNullOrEmpty(result)) return result;
+            redisClient.Set(key, "   ");
+            Console.WriteLine($"无{key},创建并写入默认值:   ");
+            return null;
+        }
+
+        /// <summary>
+        /// 检查Redis中是否有对应key且value不为空
+        /// </summary>
+        /// <param name="redisClient">redis连接</param>
+        /// <param name="key">要检查的Key</param>
+        /// <param name="defaults">创建key是写入的默认值</param>
+        /// <returns>
+        /// 1.key不存在,创建这个key 并写入传入的默认值。返回默认值
+        /// 2.key存在单value为空 返回null
+        /// 3.key存在value不为空 返回获取到的值
+        /// </returns>
+        public static string? Check(this RedisClient redisClient, string key, string defaults)
+        {
+            var result = redisClient.Get(key);
+            if (!string.IsNullOrEmpty(result)) return result;
+            redisClient.Set(key, defaults);
+            Console.WriteLine($"无{key},创建并写入默认值:{defaults}");
+            return defaults;
+        }
+    }
+}

+ 118 - 0
ServiceCenter/Extensions/SqlExtension.cs

@@ -0,0 +1,118 @@
+using SqlSugar;
+
+namespace ServiceCenter.Extensions
+{
+    public static class SqlExtension
+    {
+        /// <summary>
+        ///  不添加共享锁和排它锁,当这个选项生效后,可能读到未提交读的数据或“脏数据”,这个选项仅仅应用于SELECT语句
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static ISugarQueryable<T> NoLock<T>(this ISugarQueryable<T> obj)
+        {
+            return obj.With(SqlWith.NoLock);
+        }
+
+        /// <summary>
+        ///  跳过已经加锁的数据行,这个选项将使事务读取数据时跳过那些已经被其他事务锁定的数据行,而不是阻塞直到其他事务释放锁,ReadPast仅仅应用于READ COMMITTED隔离性级别下事务操作中的SELECT语句操作。
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static ISugarQueryable<T> ReadPast<T>(this ISugarQueryable<T> obj)
+        {
+            return obj.With(SqlWith.ReadPast);
+        }
+
+        /// <summary>
+        ///  指定在读表中数据时设置更新锁(update lock)而不是设置共享锁,该锁一直保持到这个语句或整个事务结束,使用UpdLock的作用是允许用户先读取数据(而且不阻塞其他用户读数据),并且保证在后来再更新数据时,这一段时间内这些数据没有被其他用户修改。
+        ///  默认持有行级更新锁
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static ISugarQueryable<T> UpdLock<T>(this ISugarQueryable<T> obj)
+        {
+            return obj.With("WITH(ROWLOCK)");
+        }
+
+        /// <summary>
+        ///  跳过加锁数据行并指定在读表中数据时设置更新锁(update lock)而不是设置共享锁,该锁一直保持到这个语句或整个事务结束,使用UpdLock的作用是允许用户先读取数据(而且不阻塞其他用户读数据),并且保证在后来再更新数据时,这一段时间内这些数据没有被其他用户修改。
+        ///  默认持有行级更新锁
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static ISugarQueryable<T> ReadPastUpdLock<T>(this ISugarQueryable<T> obj)
+        {
+            return obj.With("WITH(READPAST,ROWLOCK)");
+        }
+
+        /// <summary>
+        ///  使用行级锁,而不使用粒度更粗的页级锁和表级锁。
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static ISugarQueryable<T> RowLock<T>(this ISugarQueryable<T> obj)
+        {
+            return obj.With(SqlWith.RowLock);
+        }
+
+        /// <summary>
+        ///  更新时使用行级锁
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="scope"></param>
+        /// <param name="updateObj"></param>
+        /// <returns></returns>
+        public static IUpdateable<T> UpdateableRowLock<T>(this SqlSugarScopeProvider scope, T updateObj) where T : class, new() => scope.ScopedContext.Updateable<T>(updateObj).With(SqlWith.RowLock);
+
+        /// <summary>
+        ///  更新时使用行级锁
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="scope"></param>
+        /// <param name="updateObj"></param>
+        /// <returns></returns>
+        public static IUpdateable<T> UpdateableRowLock<T>(this SqlSugarScopeProvider scope, List<T> updateObj) where T : class, new() => scope.ScopedContext.Updateable<T>(updateObj).With(SqlWith.RowLock);
+
+        /// <summary>
+        ///  插入时使用行级锁
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="scope"></param>
+        /// <param name="updateObj"></param>
+        /// <returns></returns>
+        public static IInsertable<T> InsertableRowLock<T>(this SqlSugarScopeProvider scope, T insertObj) where T : class, new() => scope.ScopedContext.Insertable<T>(insertObj).With(SqlWith.RowLock);
+
+        /// <summary>
+        ///  插入时使用行级锁
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="scope"></param>
+        /// <param name="updateObj"></param>
+        /// <returns></returns>
+        public static IInsertable<T> InsertableRowLock<T>(this SqlSugarScopeProvider scope, List<T> insertObj) where T : class, new() => scope.ScopedContext.Insertable<T>(insertObj).With(SqlWith.RowLock);
+
+        /// <summary>
+        ///  删除时使用行级锁
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="scope"></param>
+        /// <param name="deleteObj"></param>
+        /// <returns></returns>
+        public static IDeleteable<T> DeleteableRowLock<T>(this SqlSugarScopeProvider scope, T deleteObj) where T : class, new() => scope.ScopedContext.Deleteable<T>(deleteObj).With(SqlWith.RowLock);
+
+        /// <summary>
+        ///  删除时使用行级锁
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="scope"></param>
+        /// <param name="deleteObj"></param>
+        /// <returns></returns>
+        public static IDeleteable<T> DeleteableRowLock<T>(this SqlSugarScopeProvider scope, List<T> deleteObj) where T : class, new() => scope.ScopedContext.Deleteable<T>(deleteObj).With(SqlWith.RowLock);
+    }
+}

+ 726 - 0
ServiceCenter/Extensions/TypeExtension.cs

@@ -0,0 +1,726 @@
+using Newtonsoft.Json;
+using SqlSugar;
+using System.Collections.Concurrent;
+using System.ComponentModel;
+using System.Data;
+using System.Globalization;
+using System.Numerics;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace ServiceCenter.Extensions
+{
+    /// <summary>
+    /// 类型转换扩展
+    /// </summary>
+    public static class TypeExtension
+    {
+        /// <summary>
+        /// 将字符串转换为short
+        /// </summary>
+        /// <param name="value">需要转换的字符串</param>
+        /// <returns></returns>
+        public static short ToShort(this string value)
+        {
+            return Convert.ToInt16(value);
+        }
+
+        /// <summary>
+        /// 将int转换为short
+        /// </summary>
+        /// <param name="value">需要转换的字符串</param>
+        /// <returns></returns>
+        public static short ToShort(this int value)
+        {
+            return Convert.ToInt16(value);
+        }
+
+        /// <summary>
+        /// 将decimal转换为short
+        /// </summary>
+        /// <param name="value">需要转换的字符串</param>
+        /// <returns></returns>
+        public static short ToShort(this decimal value)
+        {
+            return Convert.ToInt16(value);
+        }
+
+        /// <summary>
+        /// 将字符串转换为int
+        /// </summary>
+        /// <param name="value">需要转换的字符串</param>
+        /// <returns></returns>
+        public static int ToInt(this string value)
+        {
+            return Convert.ToInt32(value);
+        }
+
+        /// <summary>
+        ///  获取最后一位数字
+        /// </summary>
+        /// <param name="value">字符串</param>
+        /// <returns></returns>
+        public static int GetLastDigit(this string value)
+        {
+            return Convert.ToInt32(Regex.Match(value, @"\d+$").Value);
+        }
+
+        /// <summary>
+        /// 判断值为奇数/偶数
+        /// </summary>
+        /// <param name="value">需要判断的值</param>
+        /// <returns> true:是奇数   false:是偶数</returns>
+        public static bool OddNumberOrEven(this short value)
+        {
+            return value % 2 != 0;
+        }
+
+        /// <summary>
+        /// 获取short类型Code,只限设备组
+        /// </summary>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static short GetShortCode(this string value)
+        {
+            return value.Replace("G", "").ToShort();
+        }
+
+        /// <summary>
+        /// 扩展方法,获得枚举的Description
+        /// </summary>
+        /// <param name="value">枚举值</param>
+        /// <param name="nameInstead">当枚举值没有定义DescriptionAttribute,是否使用枚举名代替,默认是使用</param>
+        /// <returns>枚举的Description</returns>
+        public static string GetDescription(this Enum value, Boolean nameInstead = true)
+        {
+            Type type = value.GetType();
+            string name = Enum.GetName(type, value);
+            if (name == null)
+            {
+                return null;
+            }
+
+            FieldInfo field = type.GetField(name);
+            DescriptionAttribute attribute = System.Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
+
+            if (attribute == null && nameInstead == true)
+            {
+                return name;
+            }
+            return attribute?.Description;
+        }
+
+        /// <summary>
+        /// 获取属性的描述信息
+        /// </summary>
+        public static string GetDescription(this Type type, string proName)
+        {
+            PropertyInfo pro = type.GetProperty(proName);
+            string des = proName;
+            if (pro != null)
+            {
+                des = pro.GetDescription();
+            }
+            return des;
+        }
+
+        public static string JsonToString(this object value)
+        {
+            return JsonConvert.SerializeObject(value);
+        }
+
+        /// <summary>
+        /// 获取属性的描述信息
+        /// </summary>
+        public static string GetDescription(this MemberInfo info)
+        {
+            var attrs = (DescriptionAttribute[])info.GetCustomAttributes(typeof(DescriptionAttribute), false);
+            string des = info.Name;
+            foreach (DescriptionAttribute attr in attrs)
+            {
+                des = attr.Description;
+            }
+            return des;
+        }
+
+        /// <summary>
+        /// 数据映射
+        /// </summary>
+        /// <typeparam name="D">映射目标实体Type</typeparam>
+        /// <typeparam name="S"></typeparam>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public static D Mapper<D, S>(this S s)
+        {
+            var d = Activator.CreateInstance<D>();
+
+            var sType = s?.GetType();
+            var dType = typeof(D);
+
+            foreach (var sP in sType.GetProperties())
+            {
+                foreach (var dP in dType.GetProperties())
+                {
+                    if (dP.Name == sP.Name)
+                    {
+                        dP.SetValue(d, sP.GetValue(s));
+                        break;
+                    }
+                }
+            }
+
+            return d;
+        }
+
+
+        public static object Copy(this object obj, object type)
+        {
+            foreach (var p in type.GetType().GetProperties())
+            {
+                var p2 = obj.GetType().GetProperty(p.Name);
+                if (p2 != null && p2.PropertyType == p.PropertyType)
+                {
+                    var a = p2.GetValue(obj);
+                    p.SetValue(type, p2.GetValue(obj));
+                }
+            }
+            return type;
+        }
+
+        public static object Copy(this object obj, Type type)
+        {
+            var res = Activator.CreateInstance(type);
+            foreach (var p in type.GetProperties())
+            {
+                var p2 = obj.GetType().GetProperty(p.Name);
+                if (p2 != null)
+                {
+                    var value = p2.GetValue(obj);
+                    p.SetValue(res, value);
+                }
+            }
+            return res;
+        }
+
+
+        //public static object Copy(this object source, Type t)
+        //{
+        //    var obj = Activator.CreateInstance(t);
+        //    foreach (var p in t.GetProperties())
+        //    {
+        //        var p2 = source.GetType().GetProperty(p.Name);
+        //        var value = p2.GetValue(source);
+        //        p.SetValue(obj, value);
+        //    }
+        //    return obj;
+        //}
+
+        /// <summary>
+        /// 获取字典
+        /// </summary>
+        /// <typeparam name="T1"></typeparam>
+        /// <typeparam name="T2"></typeparam>
+        /// <typeparam name="T3"></typeparam>
+        /// <param name="t3"></param>
+        /// <returns></returns>
+        public static Dictionary<string, object> EntityClassToDictionary<T>(T t)
+        {
+            var type = typeof(SugarColumn);
+            Dictionary<string, object> d = new Dictionary<string, object>();
+
+            var sType = t.GetType();
+            foreach (var sP in sType.GetProperties())
+            {
+                if (sP.CustomAttributes.Any(v => v.AttributeType == type) && sP.Name != "VER" && sP.Name != "ID")
+                {
+                    d.Add(sP.Name, sP.GetValue(t));
+                }
+            }
+
+            return d;
+        }
+
+        /// <summary>
+        /// 获取MD5字符串
+        /// </summary>
+        /// <param name="myString"></param>
+        /// <returns></returns>
+        public static string GetMD5(this string myString)
+        {
+            var md5 = MD5.Create();
+            var fromData = Encoding.Unicode.GetBytes(myString);
+            var targetData = md5.ComputeHash(fromData);
+            string byte2String = null;
+
+            for (var i = 0; i < targetData.Length; i++)
+            {
+                byte2String += targetData[i].ToString("x");
+            }
+
+            return byte2String;
+        }
+
+        /// <summary>
+        /// DataTable转换成实体类
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="dt"></param>
+        /// <returns></returns>
+        public static List<object> TableToEntity(this DataTable dt, string typeName)
+        {
+            var list = new List<object>();
+            try
+            {
+                foreach (DataRow row in dt.Rows)
+                {
+                    var entity = Type.GetType(typeName);
+                    PropertyInfo[] pArray = entity.GetType().GetProperties();
+
+                    foreach (var p in pArray)
+                    {
+                        if (dt.Columns.Contains(p.Name))
+                        {
+                            if (!p.CanWrite) continue;
+                            var value = row[p.Name];
+                            if (value != DBNull.Value)
+                            {
+                                var targetType = p.PropertyType;
+                                var convertType = targetType;
+                                if (targetType.IsGenericType && targetType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
+                                {
+                                    //可空类型
+                                    var nullableConverter = new NullableConverter(targetType);
+                                    convertType = nullableConverter.UnderlyingType;
+                                }
+                                if (!string.IsNullOrEmpty(convertType.FullName) && !string.IsNullOrEmpty(value.ToString()))
+                                {
+                                    value = Convert.ChangeType(value, convertType);
+                                }
+                                switch (convertType.FullName)
+                                {
+                                    case "System.Decimal":
+                                        p.SetValue(entity, Convert.ToDecimal(value), null);
+                                        break;
+
+                                    case "System.String":
+                                        p.SetValue(entity, Convert.ToString(value), null);
+                                        break;
+
+                                    case "System.Int32":
+                                        p.SetValue(entity, Convert.ToInt32(value), null);
+                                        break;
+
+                                    case "System.Int64":
+                                        p.SetValue(entity, Convert.ToInt64(value), null);
+                                        break;
+
+                                    case "System.Int16":
+                                        p.SetValue(entity, Convert.ToInt16(value), null);
+                                        break;
+
+                                    case "System.Double":
+                                        p.SetValue(entity, Convert.ToDouble(value), null);
+                                        break;
+
+                                    case "System.Single":
+                                        p.SetValue(entity, Convert.ToSingle(value), null);
+                                        break;
+
+                                    case "System.DateTime":
+                                        p.SetValue(entity, Convert.ToDateTime(value), null);
+                                        break;
+
+                                    default:
+                                        p.SetValue(entity, value, null);
+                                        break;
+                                }
+                            }
+                        }
+                    }
+                    list.Add(entity);
+                }
+            }
+            catch (Exception ex)
+            {
+            }
+            return list;
+        }
+
+        /// <summary>
+        /// 转换到目标类型
+        /// </summary>
+        /// <param name="value"></param>
+        /// <typeparam name="T"></typeparam>
+        /// <returns></returns>
+        internal static T ConvertTo<T>(this object value) => (T)typeof(T).FromObject(value);
+
+        public static string yyyy(this DateTime time)
+        {
+            return time.GetFormat(GetFormatterEnum.yyyy);
+        }
+
+        public static string yyyyMM(this DateTime time)
+        {
+            return time.GetFormat(GetFormatterEnum.yyyyMM);
+        }
+
+        public static string yyyyMMdd(this DateTime time)
+        {
+            return time.GetFormat(GetFormatterEnum.yyyyMMdd);
+        }
+
+        public static string yyyyMMddhh(this DateTime time)
+        {
+            return time.GetFormat(GetFormatterEnum.yyyyMMddhh);
+        }
+
+        public static string yyyyMMddhhmm(this DateTime time)
+        {
+            return time.GetFormat(GetFormatterEnum.yyyyMMddhhmm);
+        }
+
+        public static string yyyyMMddhhmmss(this DateTime time)
+        {
+            return time.GetFormat(GetFormatterEnum.yyyyMMddhhmmss);
+        }
+
+        public static string yyyyMMddhhmmssf(this DateTime time)
+        {
+            return time.GetFormat(GetFormatterEnum.yyyyMMddhhmmssfffffff);
+        }
+
+        /// <summary>
+        ///  获取指定格式时间的字符串
+        /// </summary>
+        /// <param name="time">时间</param>
+        /// <param name="formatterEnum">类型</param>
+        /// <returns></returns>
+        public static string GetFormat(this DateTime time, GetFormatterEnum formatterEnum)
+        {
+            switch (formatterEnum)
+            {
+                case GetFormatterEnum.Default:
+                    return time.ToString();
+
+                case GetFormatterEnum.yyyy:
+                    return time.ToString("yyyy");
+
+                case GetFormatterEnum.yyyyMM:
+                    return time.ToString("yyyy-MM");
+
+                case GetFormatterEnum.yyyyMMdd:
+                    return time.ToString("yyyy-MM-dd");
+
+                case GetFormatterEnum.yyyyMMddhh:
+                    return time.ToString("yyyy-MM-dd HH");
+
+                case GetFormatterEnum.yyyyMMddhhmm:
+                    return time.ToString("yyyy-MM-dd HH:mm");
+
+                case GetFormatterEnum.yyyyMMddhhmmss:
+                    return time.ToString("yyyy-MM-dd HH:mm:ss");
+
+                case GetFormatterEnum.yyyyMMddhhmmssfffffff:
+                    return time.ToString("yyyy-MM-dd HH:mm:ss:fffffff");
+
+                case GetFormatterEnum.only:
+                    return time.ToString("yyyyMMddHHmmssfffffff");
+
+                default:
+                    return time.ToString();
+            }
+        }
+
+        private static ConcurrentDictionary<Type, Func<string, object>> _dicFromObject = new ConcurrentDictionary<Type, Func<string, object>>();
+
+        public static object FromObject(this Type targetType, object value, Encoding encoding = null)
+        {
+            if (targetType == typeof(object)) return value;
+            if (encoding == null) encoding = Encoding.UTF8;
+            var valueIsNull = value == null;
+            var valueType = valueIsNull ? typeof(string) : value.GetType();
+            if (valueType == targetType) return value;
+            if (valueType == typeof(byte[])) //byte[] -> guid
+            {
+                if (targetType == typeof(Guid))
+                {
+                    var bytes = value as byte[];
+                    return Guid.TryParse(BitConverter.ToString(bytes, 0, Math.Min(bytes.Length, 36)).Replace("-", ""), out var tryguid) ? tryguid : Guid.Empty;
+                }
+                if (targetType == typeof(Guid?))
+                {
+                    var bytes = value as byte[];
+                    return Guid.TryParse(BitConverter.ToString(bytes, 0, Math.Min(bytes.Length, 36)).Replace("-", ""), out var tryguid) ? (Guid?)tryguid : null;
+                }
+            }
+            if (targetType == typeof(byte[])) //guid -> byte[]
+            {
+                if (valueIsNull) return null;
+                if (valueType == typeof(Guid) || valueType == typeof(Guid?))
+                {
+                    var bytes = new byte[16];
+                    var guidN = ((Guid)value).ToString("N");
+                    for (var a = 0; a < guidN.Length; a += 2)
+                        bytes[a / 2] = byte.Parse($"{guidN[a]}{guidN[a + 1]}", NumberStyles.HexNumber);
+                    return bytes;
+                }
+                return encoding.GetBytes(value.ToInvariantCultureToString());
+            }
+            else if (targetType.IsArray)
+            {
+                if (value is Array valueArr)
+                {
+                    var targetElementType = targetType.GetElementType();
+                    var sourceArrLen = valueArr.Length;
+                    var target = Array.CreateInstance(targetElementType, sourceArrLen);
+                    for (var a = 0; a < sourceArrLen; a++) target.SetValue(targetElementType.FromObject(valueArr.GetValue(a), encoding), a);
+                    return target;
+                }
+                //if (value is IList valueList)
+                //{
+                //    var targetElementType = targetType.GetElementType();
+                //    var sourceArrLen = valueList.Count;
+                //    var target = Array.CreateInstance(targetElementType, sourceArrLen);
+                //    for (var a = 0; a < sourceArrLen; a++) target.SetValue(targetElementType.FromObject(valueList[a], encoding), a);
+                //    return target;
+                //}
+            }
+            var func = _dicFromObject.GetOrAdd(targetType, tt =>
+            {
+                if (tt == typeof(object)) return vs => vs;
+                if (tt == typeof(string)) return vs => vs;
+                if (tt == typeof(char[])) return vs => vs == null ? null : vs.ToCharArray();
+                if (tt == typeof(char)) return vs => vs == null ? default(char) : vs.ToCharArray(0, 1).FirstOrDefault();
+                if (tt == typeof(bool)) return vs =>
+                {
+                    if (vs == null) return false;
+                    switch (vs.ToLower())
+                    {
+                        case "true":
+                        case "1":
+                            return true;
+                    }
+                    return false;
+                };
+                if (tt == typeof(bool?)) return vs =>
+                {
+                    if (vs == null) return false;
+                    switch (vs.ToLower())
+                    {
+                        case "true":
+                        case "1":
+                            return true;
+
+                        case "false":
+                        case "0":
+                            return false;
+                    }
+                    return null;
+                };
+                if (tt == typeof(byte)) return vs => vs == null ? 0 : byte.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(byte?)) return vs => vs == null ? null : byte.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (byte?)tryval : null;
+                if (tt == typeof(decimal)) return vs => vs == null ? 0 : decimal.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(decimal?)) return vs => vs == null ? null : decimal.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (decimal?)tryval : null;
+                if (tt == typeof(double)) return vs => vs == null ? 0 : double.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(double?)) return vs => vs == null ? null : double.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (double?)tryval : null;
+                if (tt == typeof(float)) return vs => vs == null ? 0 : float.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(float?)) return vs => vs == null ? null : float.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (float?)tryval : null;
+                if (tt == typeof(int)) return vs => vs == null ? 0 : int.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(int?)) return vs => vs == null ? null : int.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (int?)tryval : null;
+                if (tt == typeof(long)) return vs => vs == null ? 0 : long.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(long?)) return vs => vs == null ? null : long.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (long?)tryval : null;
+                if (tt == typeof(sbyte)) return vs => vs == null ? 0 : sbyte.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(sbyte?)) return vs => vs == null ? null : sbyte.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (sbyte?)tryval : null;
+                if (tt == typeof(short)) return vs => vs == null ? 0 : short.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(short?)) return vs => vs == null ? null : short.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (short?)tryval : null;
+                if (tt == typeof(uint)) return vs => vs == null ? 0 : uint.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(uint?)) return vs => vs == null ? null : uint.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (uint?)tryval : null;
+                if (tt == typeof(ulong)) return vs => vs == null ? 0 : ulong.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(ulong?)) return vs => vs == null ? null : ulong.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (ulong?)tryval : null;
+                if (tt == typeof(ushort)) return vs => vs == null ? 0 : ushort.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(ushort?)) return vs => vs == null ? null : ushort.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (ushort?)tryval : null;
+                if (tt == typeof(DateTime)) return vs => vs == null ? DateTime.MinValue : DateTime.TryParse(vs, out var tryval) ? tryval : DateTime.MinValue;
+                if (tt == typeof(DateTime?)) return vs => vs == null ? null : DateTime.TryParse(vs, out var tryval) ? (DateTime?)tryval : null;
+                if (tt == typeof(DateTimeOffset)) return vs => vs == null ? DateTimeOffset.MinValue : DateTimeOffset.TryParse(vs, out var tryval) ? tryval : DateTimeOffset.MinValue;
+                if (tt == typeof(DateTimeOffset?)) return vs => vs == null ? null : DateTimeOffset.TryParse(vs, out var tryval) ? (DateTimeOffset?)tryval : null;
+                if (tt == typeof(TimeSpan)) return vs => vs == null ? TimeSpan.Zero : TimeSpan.TryParse(vs, out var tryval) ? tryval : TimeSpan.Zero;
+                if (tt == typeof(TimeSpan?)) return vs => vs == null ? null : TimeSpan.TryParse(vs, out var tryval) ? (TimeSpan?)tryval : null;
+                if (tt == typeof(Guid)) return vs => vs == null ? Guid.Empty : Guid.TryParse(vs, out var tryval) ? tryval : Guid.Empty;
+                if (tt == typeof(Guid?)) return vs => vs == null ? null : Guid.TryParse(vs, out var tryval) ? (Guid?)tryval : null;
+                if (tt == typeof(BigInteger)) return vs => vs == null ? 0 : BigInteger.TryParse(vs, NumberStyles.Any, null, out var tryval) ? tryval : 0;
+                if (tt == typeof(BigInteger?)) return vs => vs == null ? null : BigInteger.TryParse(vs, NumberStyles.Any, null, out var tryval) ? (BigInteger?)tryval : null;
+                if (tt.NullableTypeOrThis().IsEnum)
+                {
+                    var tttype = tt.NullableTypeOrThis();
+                    var ttdefval = tt.CreateInstanceGetDefaultValue();
+                    return vs =>
+                    {
+                        if (string.IsNullOrWhiteSpace(vs)) return ttdefval;
+                        return Enum.Parse(tttype, vs, true);
+                    };
+                }
+                var localTargetType = targetType;
+                var localValueType = valueType;
+                return vs =>
+                {
+                    if (vs == null) return null;
+                    throw new NotSupportedException($"convert failed {localValueType.DisplayCsharp()} -> {localTargetType.DisplayCsharp()}");
+                };
+            });
+            var valueStr = valueIsNull ? null : valueType == typeof(byte[]) ? encoding.GetString(value as byte[]) : value.ToInvariantCultureToString();
+            return func(valueStr);
+        }
+
+        internal static string ToInvariantCultureToString(this object obj) => obj is string objstr ? objstr : string.Format(CultureInfo.InvariantCulture, @"{0}", obj);
+
+        private static bool IsNullableType(this Type that) => that.IsArray == false && that?.FullName.StartsWith("System.Nullable`1[") == true;
+
+        private static Type NullableTypeOrThis(this Type that) => that?.IsNullableType() == true ? that.GetGenericArguments().First() : that;
+
+        internal static object CreateInstanceGetDefaultValue(this Type that)
+        {
+            if (that == null) return null;
+            if (that == typeof(string)) return default(string);
+            if (that == typeof(Guid)) return default(Guid);
+            if (that == typeof(byte[])) return default(byte[]);
+            if (that.IsArray) return Array.CreateInstance(that.GetElementType(), 0);
+            if (that.IsInterface || that.IsAbstract) return null;
+            var ctorParms = that.InternalGetTypeConstructor0OrFirst(false)?.GetParameters();
+            if (ctorParms == null || ctorParms.Any() == false) return Activator.CreateInstance(that, true);
+            return Activator.CreateInstance(that, ctorParms.Select(a => a.ParameterType.IsInterface ||
+                                                                        a.ParameterType.IsAbstract ||
+                                                                        a.ParameterType == typeof(string) ||
+                                                                        a.ParameterType.IsArray ? null : Activator.CreateInstance(a.ParameterType, null)).ToArray());
+        }
+
+        private static ConcurrentDictionary<Type, ConstructorInfo> _dicInternalGetTypeConstructor0OrFirst = new ConcurrentDictionary<Type, ConstructorInfo>();
+
+        private static ConstructorInfo InternalGetTypeConstructor0OrFirst(this Type that, bool isThrow = true)
+        {
+            var ret = _dicInternalGetTypeConstructor0OrFirst.GetOrAdd(that, tp =>
+                tp.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null) ??
+                tp.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault());
+            if (ret == null && isThrow) throw new ArgumentException($"{that.FullName} has no method to access constructor");
+            return ret;
+        }
+
+        private static string DisplayCsharp(this Type type, bool isNameSpace = true)
+        {
+            if (type == null) return null;
+            if (type == typeof(void)) return "void";
+            if (type.IsGenericParameter) return type.Name;
+            if (type.IsArray) return $"{DisplayCsharp(type.GetElementType())}[]";
+            var sb = new StringBuilder();
+            var nestedType = type;
+            while (nestedType.IsNested)
+            {
+                sb.Insert(0, ".").Insert(0, DisplayCsharp(nestedType.DeclaringType, false));
+                nestedType = nestedType.DeclaringType;
+            }
+            if (isNameSpace && string.IsNullOrWhiteSpace(nestedType.Namespace) == false)
+                sb.Insert(0, ".").Insert(0, nestedType.Namespace);
+
+            if (type.IsGenericType == false)
+                return sb.Append(type.Name).ToString();
+
+            var genericParameters = type.GetGenericArguments();
+            if (type.IsNested && type.DeclaringType.IsGenericType)
+            {
+                var dic = genericParameters.ToDictionary(a => a.Name);
+                foreach (var nestedGenericParameter in type.DeclaringType.GetGenericArguments())
+                    if (dic.ContainsKey(nestedGenericParameter.Name))
+                        dic.Remove(nestedGenericParameter.Name);
+                genericParameters = dic.Values.ToArray();
+            }
+            if (genericParameters.Any() == false)
+                return sb.Append(type.Name).ToString();
+
+            sb.Append(type.Name.Remove(type.Name.IndexOf('`'))).Append("<");
+            var genericTypeIndex = 0;
+            foreach (var genericType in genericParameters)
+            {
+                if (genericTypeIndex++ > 0) sb.Append(", ");
+                sb.Append(DisplayCsharp(genericType, true));
+            }
+            return sb.Append(">").ToString();
+        }
+
+        /// <summary>
+        ///  对象转换为字典
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static Dictionary<string, string> ToDic<T>(this T obj) where T : class
+        {
+            Dictionary<string, string> map = new Dictionary<string, string>();
+            var objSte = JsonConvert.SerializeObject(obj);
+            map = JsonConvert.DeserializeObject<Dictionary<string, string>>(objSte);
+            return map;
+        }
+
+        /// <summary>
+        ///  字典转为实体
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public static T ToClass<T>(this Dictionary<string, string> obj)
+        {
+            var objSte = JsonConvert.SerializeObject(obj);
+            var map = JsonConvert.DeserializeObject<T>(objSte);
+            return map;
+        }
+    }
+
+    public enum GetFormatterEnum
+    {
+        /// <summary>
+        ///  默认类型
+        /// </summary>
+        Default = 0,
+
+        /// <summary>
+        ///  yyyy
+        /// </summary>
+        yyyy = 4,
+
+        /// <summary>
+        ///  yyyy-MM
+        /// </summary>
+        yyyyMM = 5,
+
+        /// <summary>
+        ///  yyyy-MM-dd
+        /// </summary>
+        yyyyMMdd = 6,
+
+        /// <summary>
+        ///  yyyy-MM-dd hh
+        /// </summary>
+        yyyyMMddhh = 7,
+
+        /// <summary>
+        ///  yyyy-MM-dd hh:mm
+        /// </summary>
+        yyyyMMddhhmm = 8,
+
+        /// <summary>
+        ///  yyyy-MM-dd hh:mm:ss
+        /// </summary>
+        yyyyMMddhhmmss = 9,
+
+        /// <summary>
+        ///  yyyy-MM-dd hh:mm:ss:fffffff
+        /// </summary>
+        yyyyMMddhhmmssfffffff = 10,
+
+        /// <summary>
+        /// 用时间组成唯一值
+        ///  yyyyMMddhhmmssfffffff
+        /// </summary>
+        only = 11,
+    }
+}

+ 155 - 0
ServiceCenter/Helpers/TypeConversionHelper.cs

@@ -0,0 +1,155 @@
+using ServiceCenter.Extensions;
+using System.Text;
+
+namespace ServiceCenter.Helpers
+{
+    public class TypeConversionHelper
+    {
+        #region 序列化写入,反序列化
+
+        public static Func<object, object> Serialize;
+        public static Func<string, Type, object> Deserialize;
+        public static Func<byte[], Type, object> DeserializeRaw;
+
+        public static object SerializeRedisValue(object value)
+        {
+            switch (value)
+            {
+                case null: return null;
+                case string _:
+                case byte[] _: return value;
+
+                case bool b: return b ? "1" : "0";
+                case char c: return value;
+                case decimal _:
+                case double _:
+                case float _:
+                case int _:
+                case long _:
+                case sbyte _:
+                case short _:
+                case uint _:
+                case ulong _:
+                case ushort _: return value.ToInvariantCultureToString();
+
+                case DateTime time: return time.ToString("yyyy-MM-ddTHH:mm:sszzzz", System.Globalization.DateTimeFormatInfo.InvariantInfo);
+                case DateTimeOffset _: return value.ToString();
+                case TimeSpan span: return span.Ticks;
+                case Guid _: return value.ToString();
+                default:
+                    if (Serialize != null) return Serialize(value);
+                    return value.ConvertTo<string>();
+            }
+        }
+
+        public static T DeserializeRedisValue<T>(byte[] valueRaw, Encoding encoding)
+        {
+            if (valueRaw == null) return default(T);
+            var type = typeof(T);
+            var typename = type.ToString().TrimEnd(']');
+            if (typename == "System.Byte[") return (T)Convert.ChangeType(valueRaw, type);
+            if (typename == "System.String") return (T)Convert.ChangeType(encoding.GetString(valueRaw), type);
+            if (typename == "System.Boolean[") return (T)Convert.ChangeType(valueRaw.Select(a => a == 49).ToArray(), type);
+            if (valueRaw.Length == 0) return default(T);
+
+            string valueStr = null;
+            if (type.IsValueType)
+            {
+                valueStr = encoding.GetString(valueRaw);
+                bool isNullable = typename.StartsWith("System.Nullable`1[");
+                var basename = isNullable ? typename.Substring(18) : typename;
+
+                bool isElse = false;
+                object obj = null;
+                switch (basename)
+                {
+                    case "System.Boolean":
+                        if (valueStr == "1") obj = true;
+                        else if (valueStr == "0") obj = false;
+                        break;
+
+                    case "System.Byte":
+                        if (byte.TryParse(valueStr, out var trybyte)) obj = trybyte;
+                        break;
+
+                    case "System.Char":
+                        if (valueStr.Length > 0) obj = valueStr[0];
+                        break;
+
+                    case "System.Decimal":
+                        if (Decimal.TryParse(valueStr, out var trydec)) obj = trydec;
+                        break;
+
+                    case "System.Double":
+                        if (Double.TryParse(valueStr, out var trydb)) obj = trydb;
+                        break;
+
+                    case "System.Single":
+                        if (Single.TryParse(valueStr, out var trysg)) obj = trysg;
+                        break;
+
+                    case "System.Int32":
+                        if (Int32.TryParse(valueStr, out var tryint32)) obj = tryint32;
+                        break;
+
+                    case "System.Int64":
+                        if (Int64.TryParse(valueStr, out var tryint64)) obj = tryint64;
+                        break;
+
+                    case "System.SByte":
+                        if (SByte.TryParse(valueStr, out var trysb)) obj = trysb;
+                        break;
+
+                    case "System.Int16":
+                        if (Int16.TryParse(valueStr, out var tryint16)) obj = tryint16;
+                        break;
+
+                    case "System.UInt32":
+                        if (UInt32.TryParse(valueStr, out var tryuint32)) obj = tryuint32;
+                        break;
+
+                    case "System.UInt64":
+                        if (UInt64.TryParse(valueStr, out var tryuint64)) obj = tryuint64;
+                        break;
+
+                    case "System.UInt16":
+                        if (UInt16.TryParse(valueStr, out var tryuint16)) obj = tryuint16;
+                        break;
+
+                    case "System.DateTime":
+                        if (DateTime.TryParse(valueStr, out var trydt)) obj = trydt;
+                        break;
+
+                    case "System.DateTimeOffset":
+                        if (DateTimeOffset.TryParse(valueStr, out var trydtos)) obj = trydtos;
+                        break;
+
+                    case "System.TimeSpan":
+                        if (Int64.TryParse(valueStr, out tryint64)) obj = new TimeSpan(tryint64);
+                        break;
+
+                    case "System.Guid":
+                        if (Guid.TryParse(valueStr, out var tryguid)) obj = tryguid;
+                        break;
+
+                    default:
+                        isElse = true;
+                        break;
+                }
+
+                if (isElse == false)
+                {
+                    if (obj == null) return default(T);
+                    return (T)obj;
+                }
+            }
+            if (DeserializeRaw != null) return (T)DeserializeRaw(valueRaw, typeof(T));
+
+            if (valueStr == null) valueStr = encoding.GetString(valueRaw);
+            if (Deserialize != null) return (T)Deserialize(valueStr, typeof(T));
+            return valueStr.ConvertTo<T>();
+        }
+
+        #endregion 序列化写入,反序列化
+    }
+}

+ 38 - 0
ServiceCenter/Redis/RedisClientList.cs

@@ -0,0 +1,38 @@
+using FreeRedis;
+
+namespace ServiceCenter.Redis
+{
+    /// <summary>
+    /// 链接集合
+    /// </summary>
+    public class RedisClientList
+    {
+        /// <summary>
+        /// 连接集合
+        /// </summary>
+        /// <param name="key">连接标识</param>
+        /// <param name="client">连接</param>
+        /// <param name="connectionConfig">连接字符串</param>
+        public RedisClientList(string key, RedisClient client, string connectionConfig)
+        {
+            Key = key;
+            Client = client;
+            ConnectionConfig = connectionConfig;
+        }
+
+        /// <summary>
+        /// 连接标识
+        /// </summary>
+        public string Key { get; set; }
+
+        /// <summary>
+        /// 连接
+        /// </summary>
+        public RedisClient Client { get; set; }
+
+        /// <summary>
+        /// 连接字符串
+        /// </summary>
+        public string ConnectionConfig { get; set; }
+    }
+}

+ 204 - 0
ServiceCenter/Redis/RedisHub.cs

@@ -0,0 +1,204 @@
+using FreeRedis;
+using ServiceCenter.Logs;
+
+namespace ServiceCenter.Redis
+{
+    /// <summary>
+    /// Redis
+    /// </summary>
+    public class RedisHub
+    {
+        /// <summary>
+        /// 连接集合
+        /// </summary>
+        private static List<RedisClientList> RedisClients = new List<RedisClientList>();
+
+        /// <summary>
+        /// 默认上下文类类型
+        /// </summary>
+        public static string DefaultContextType { get; private set; } = null!;
+
+        /// <summary>
+        /// 默认监控上下文类类型
+        /// </summary>
+        public static string MonitorContextType { get; private set; } = null!;
+
+        /// <summary>
+        /// 默认调试上下文类类型
+        /// </summary>
+        public static string DebugContextType { get; private set; } = null!;
+
+        /// <summary>
+        /// 默认WMS上下文类类型
+        /// </summary>
+        public static string WMSContextType { get; private set; } = null!;
+
+        public static object redisLock = new object();
+
+        /// <summary>
+        /// 设置默认链接
+        /// </summary>
+        /// <param name="key"></param>
+        public static void SetDefaultContextType(string key)
+        {
+            DefaultContextType = key;
+        }
+
+        /// <summary>
+        /// 设置监控链接
+        /// </summary>
+        /// <param name="key"></param>
+        public static void SetMonitorContextType(string key)
+        {
+            MonitorContextType = key;
+        }
+
+        /// <summary>
+        /// 设置调试链接
+        /// </summary>
+        /// <param name="key"></param>
+        public static void SetDebugContextType(string key)
+        {
+            DebugContextType = key;
+        }
+
+        /// <summary>
+        /// 设置WMS链接
+        /// </summary>
+        /// <param name="key"></param>
+        public static void SetWMSContextType(string key)
+        {
+            WMSContextType = key;
+        }
+
+        /// <summary>
+        /// 默认链接
+        /// </summary>
+        public static RedisClient Default
+        {
+            get
+            {
+                {
+                    if (DefaultContextType == null)
+                        throw new Exception("请先设置默认RedisDB库,调用静态方法SetDefaultDbContextType()");
+                    return Context(DefaultContextType);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 监控连接
+        /// </summary>
+        public static RedisClient Monitor
+        {
+            get
+            {
+                {
+                    if (MonitorContextType == null) throw new Exception("请先设置监控RedisDB库,调用静态方法SetMonitorContextType()");
+                    return Context(MonitorContextType);
+                }
+            }
+        }
+
+        /// <summary>
+        ///调试连接
+        /// </summary>
+        public static RedisClient Debug
+        {
+            get
+            {
+                {
+                    if (MonitorContextType == null)
+                        throw new Exception("请先设置DebugRedisDB库,调用静态方法SetDebugContextType()");
+                    return Context(MonitorContextType);
+                }
+            }
+        }
+
+        /// <summary>
+        ///调试连接
+        /// </summary>
+        public static RedisClient WMS
+        {
+            get
+            {
+                {
+                    if (WMSContextType == null) throw new Exception("请先设置WMSRedisDB库,调用静态方法SetWMSContextTypee()");
+                    return Context(WMSContextType);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 创建一个连接
+        /// </summary>
+        /// <param name="connectionString"> 连接字符串</param>
+        /// <param name="key">标识</param>
+        /// <param name="defaultClient"> 是否为默认连接,默认为false</param>
+        /// <returns></returns>
+        public static RedisClient CreateContext(string connectionString, string key, bool defaultClient = false)
+        {
+            var ctx = RedisClients.FirstOrDefault(v => v.Key == key);
+            if (ctx != null) return ctx.Client;
+            ctx = new RedisClientList(key, new RedisClient(connectionString), connectionString);
+            RedisClients.Add(ctx);
+            if (defaultClient)
+            {
+                SetDefaultContextType(key);
+            }
+            return ctx.Client;
+        }
+
+        /// <summary>
+        /// 获取指定的连接
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        /// <exception cref="Exception"></exception>
+        public static RedisClient Context(string key)
+        {
+            var ctx = RedisClients.FirstOrDefault(v => v.Key == key);
+            if (ctx == null) throw new Exception("没有对应的链接,请先调用创建");
+            return ctx.Client;
+        }
+
+        /// <summary>
+        ///  执行并发锁
+        ///  使用默认连接
+        /// </summary>
+        /// <param name="lockKey">并发锁key</param>
+        /// <param name="act">执行内容</param>
+        /// <exception cref="Exception"></exception>
+        public static void Do(string lockKey, Action<RedisClient> act) => Do(DefaultContextType, lockKey, act);
+
+        /// <summary>
+        ///  执行并发锁
+        ///  指定redis
+        /// </summary>
+        /// <param name="key">连接key</param>
+        /// <param name="lockKey">并发锁key</param>
+        /// <param name="act">执行内容</param>
+        /// <exception cref="Exception"></exception>
+        public static void Do(string key, string lockKey, Action<RedisClient> act)
+        {
+            if (!RedisClients.Any()) throw new Exception($"请调用{nameof(CreateContext)}方法设置设置数据库连接");
+            var redis = Context(key);
+            lockKey = "ConcurrentLock:" + lockKey;
+            try
+            {
+                if (redis.Exists(lockKey)) throw new Exception($"重复调用:{lockKey}");
+                redis.Set(lockKey, lockKey);
+                redis.Expire(lockKey, 60);
+                act(redis);
+            }
+            catch (Exception ex)
+            {
+                throw new KnownException($"{ex.Message}", LogLevelEnum.Mid);
+            }
+            finally
+            {
+                redis.Del(lockKey);
+            }
+        }
+    }
+}

+ 160 - 0
ServiceCenter/ServiceCenter.cs

@@ -0,0 +1,160 @@
+using SqlSugar;
+
+namespace ServiceCenter
+{
+    /// <summary>
+    ///  服务中心
+    /// </summary>
+    public static class ServiceHub
+    {
+        #region 配置中心
+
+        /// <summary>
+        /// 数据库连接字符串集合
+        /// </summary>
+        public static List<DataBaseConnectionString>? DbConnectionStrings { get; set; } = null!;
+
+        /// <summary>
+        /// Redis连接字符串集合
+        /// </summary>
+        public static List<DataBaseConnectionString>? RedisConnectionStrings { get; set; } = null!;
+
+        /// <summary>
+        /// 设备IP集合
+        /// </summary>
+        public static List<string> DeviceIPList { get; set; } = null!;
+
+        /// <summary>
+        /// 仓库名称
+        /// </summary>
+        private static string _WarehouseName { get; set; } = null!;
+
+        /// <summary>
+        ///  Http请求超时时间
+        /// </summary>
+        private static int _HttpTimeout { get; set; }
+
+        /// <summary>
+        ///  Http请求超时时间
+        /// </summary>
+        public static int HttpTimeout
+        {
+            get
+            {
+                return _HttpTimeout;
+            }
+        }
+
+        /// <summary>
+        /// 仓库名称
+        /// </summary>
+        public static string WarehouseName
+        {
+            get
+            {
+                return _WarehouseName;
+            }
+        }
+
+        /// <summary>
+        /// 设置仓库名称
+        /// </summary>
+        /// <param name="warehouseName"></param>
+        public static void SetWarehouseName(string warehouseName)
+        {
+            _WarehouseName = warehouseName;
+        }
+
+        /// <summary>
+        ///  设置Http请求超时时间
+        /// </summary>
+        /// <param name="timeout"></param>
+        public static void SetHttpTimeout(int timeout)
+        {
+            _HttpTimeout = timeout;
+        }
+
+        #endregion 配置中心
+
+        #region 系统运行模式
+
+        /// <summary>
+        /// 系统运行模式
+        /// </summary>
+        private static List<SystemMode> SystemModes { get; set; } = new List<SystemMode>();
+
+        /// <summary>
+        /// 添加一种模式
+        /// </summary>
+        /// <param name="mode">系统模式</param>
+        public static void AddSystemMode(SystemMode mode)
+        {
+            if (SystemModes.Contains(mode)) return;
+            SystemModes.Add(mode);
+        }
+
+        /// <summary>
+        /// 是否包含传入模式
+        /// </summary>
+        /// <param name="mode">系统模式</param>
+        /// <returns></returns>
+        public static bool Any(SystemMode mode)
+        {
+            return SystemModes.Contains(mode);
+        }
+
+        #endregion 系统运行模式
+    }
+
+    /// <summary>
+    /// 系统模式
+    /// </summary>
+    public enum SystemMode
+    {
+        /// <summary>
+        /// 虚拟plc,启用该模式后,将在Redis中建立一个虚拟PLC用于流程测试
+        /// </summary>
+        虚拟plc = 1,
+    }
+
+    /// <summary>
+    /// 数据库连接
+    /// </summary>
+    public class DataBaseConnectionString
+    {
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="key">连接对应的Key</param>
+        /// <param name="connectionString">连接字符串</param>
+        /// <param name="dbType">数据库类型</param>
+        /// <param name="isDefault">是否为默认数据库连接</param>
+        public DataBaseConnectionString(string key, string connectionString, DbType dbType, bool isDefault)
+        {
+            Key = key;
+            ConnectionString = connectionString;
+            DbType = dbType;
+            IsDefault = isDefault;
+        }
+
+        /// <summary>
+        /// 连接对应的Key
+        /// </summary>
+        public string Key { get; set; }
+
+        /// <summary>
+        /// 连接字符串
+        /// </summary>
+        public string ConnectionString { get; set; }
+
+        /// <summary>
+        /// 数据库类型
+        /// </summary>
+        public DbType DbType { get; set; }
+
+        /// <summary>
+        /// 是否为默认数据库连接
+        /// </summary>
+        public bool IsDefault { get; set; }
+    }
+}

+ 34 - 0
ServiceCenter/ServiceCenter.csproj

@@ -0,0 +1,34 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <NoWarn>1701;1702;8602;8616;8618;8625;8600;8603;8714;1591;1712;8619;8625;0168;8061;8604</NoWarn>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <NoWarn>1701;1702;8602;8616;8618;8625;8600;8603;8714;1591;1712;8619;8625;0168;8061;8604</NoWarn>
+  </PropertyGroup>
+  
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.15" />
+    <PackageReference Include="PlcSiemens" Version="1.0.1" />
+    <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
+    <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
+  </ItemGroup>
+  
+  <ItemGroup>
+    <ProjectReference Include="..\..\ZTGT-FJZX\WCS.Entity\WCS.Entity.csproj" />
+    <ProjectReference Include="..\WCS.Core\WCS.Core.csproj" />
+  </ItemGroup>
+  
+  <ItemGroup>
+    <Folder Include="Attributes\" />
+  </ItemGroup>
+
+</Project>

+ 197 - 0
ServiceCenter/SqlSugars/SqlSugarHelper.cs

@@ -0,0 +1,197 @@
+using Microsoft.Data.SqlClient;
+using Newtonsoft.Json.Linq;
+using SqlSugar;
+using System.Data;
+
+namespace ServiceCenter.SqlSugars
+{
+    /// <summary>
+    ///  ORM
+    /// </summary>
+    public class SqlSugarHelper
+    {
+        /// <summary>
+        /// 数据库连接
+        /// </summary>
+        private static SqlSugarScope? _Db { get; set; } = null;
+
+        /// <summary>
+        /// 默认数据库连接Key
+        /// </summary>
+        private static string _Default { get; set; } = "";
+
+        /// <summary>
+        /// PLC据库连接Key
+        /// </summary>
+        private static string _PLCEX { get; set; } = "";
+
+        /// <summary>
+        /// PLC据库连接Key
+        /// </summary>
+        public static string _PLC { get; set; } = "";
+
+        /// <summary>
+        /// Dlog数据库连接Key
+        /// </summary>
+        private static string _Dlog { get; set; } = "";
+
+        /// <summary>
+        ///  设置数据库连接Key
+        /// </summary>
+        /// <param name="configId">默认多租户ID</param>
+        public static void SetDefault(string configId)
+        {
+            _Default = configId;
+        }
+
+        /// <summary>
+        ///  设置 Dlog数据库连接Key
+        /// </summary>
+        /// <param name="configId">多租户Dlog ID</param>
+        public static void SetDlog(string configId)
+        {
+            _Dlog = configId;
+        }
+
+        /// <summary>
+        ///  设置 plc数据库连接Key
+        /// </summary>
+        /// <param name="configId">多租户Dlog ID</param>
+        public static void SetPLC(string configId)
+        {
+            _PLC = configId;
+        }
+
+        /// <summary>
+        ///  设置 plc数据库连接Key
+        /// </summary>
+        /// <param name="configId">多租户Dlog ID</param>
+        public static void SetPLCEX(string configId)
+        {
+            _PLCEX = configId;
+        }
+
+        /// <summary>
+        /// 默认数据库连接Key
+        /// </summary>
+        public SqlSugarScopeProvider Default
+        {
+            get
+            {
+                if (_Default == "") throw new Exception("请调用[SqlSugarHelper.SetDefault]方法设置默认数据库连接");
+                if (_Db == null) throw new Exception("请调用[SqlSugarHelper.SetDb]方法设置设置数据库连接");
+                return _Db.GetConnectionScope(_Default);
+            }
+        }
+
+        /// <summary>
+        /// Dlog数据库连接Key
+        /// </summary>
+        public SqlSugarScopeProvider Dlog
+        {
+            get
+            {
+                if (_Dlog == "") throw new Exception("请调用[SqlSugarHelper.SetDlog]方法设置默认数据库连接");
+                if (_Db == null) throw new Exception("请调用[SqlSugarHelper.SetDb]方法设置设置数据库连接");
+                return _Db.GetConnectionScope(_Dlog);
+            }
+        }
+
+        /// <summary>
+        /// plc数据库连接Key
+        /// </summary>
+        public SqlSugarScopeProvider PLC
+        {
+            get
+            {
+                if (_PLC == "") throw new Exception("请调用[SqlSugarHelper.SetPLC]方法设置默认数据库连接");
+                if (_Db == null) throw new Exception("请调用[SqlSugarHelper.SetDb]方法设置设置数据库连接");
+                return _Db.GetConnectionScope(_PLC);
+            }
+        }
+
+        /// <summary>
+        /// plc数据库连接Key
+        /// </summary>
+        public SqlSugarScopeProvider PLCEX
+        {
+            get
+            {
+                if (_PLCEX == "") throw new Exception("请调用[SqlSugarHelper.SetPLC]方法设置默认数据库连接");
+                if (_Db == null) throw new Exception("请调用[SqlSugarHelper.SetDb]方法设置设置数据库连接");
+                return _Db.GetConnectionScope(_PLCEX);
+            }
+        }
+
+        /// <summary>
+        ///  设置数据库连接
+        /// </summary>
+        /// <param name="sqlSugarScope"></param>
+        public static void SetDb(SqlSugarScope sqlSugarScope)
+        {
+            _Db = sqlSugarScope;
+        }
+
+        /// <summary>
+        /// 数据库连接
+        /// 注意需要
+        /// </summary>
+        public SqlSugarScope Connect
+        {
+            get
+            {
+                return _Db ?? throw new Exception("请调用[SqlSugarHelper.SetDb]方法设置设置数据库连接");
+            }
+        }
+
+        /// <summary>
+        ///  执行事务
+        /// </summary>
+        /// <param name="act"></param>
+        /// <exception cref="Exception"></exception>
+        public static void Do(Action<SqlSugarHelper> act)
+        {
+            if (_Db == null) throw new Exception("请调用[SqlSugarHelper.SetDb]方法设置设置数据库连接");
+            var db = new SqlSugarHelper();
+            try
+            {
+                db.Connect.BeginTran();//开始事务
+                act(db);//执行委托
+                db.Connect.CommitTran();//提交事务
+            }
+            catch (Exception ex)
+            {
+                db.Connect.RollbackTran();//回滚事务
+                if (ex.Message.Contains("SqlTransaction")) throw new Exception($"{ex.Message}:{ex.StackTrace}");
+                throw new Exception(ex.Message);
+            }
+        }
+
+        /// <summary>
+        ///  直接返回查询结果
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="act"></param>
+        /// <returns></returns>
+        /// <exception cref="Exception"></exception>
+        public static T Do<T>(Func<SqlSugarHelper, T> act)
+        {
+            if (_Db == null) throw new Exception("请调用[SqlSugarHelper.SetDb]方法设置设置数据库连接");
+            var db = new SqlSugarHelper();
+            try
+            {
+                db.Connect.BeginTran();//开始事务
+                db.Connect.Ado.CommandTimeOut = 10;
+                var res = act(db);//执行委托
+                db.Connect.CommitTran();//提交事务
+                return res;
+            }
+            catch (Exception ex)
+            {
+                db.Connect.RollbackTran();//回滚事务
+                if (ex.Message.Contains("SqlTransaction")) throw new Exception($"{ex.Message}:{ex.StackTrace}");
+                throw new Exception(ex.Message);
+            }
+        }
+    }
+}

+ 177 - 0
ServiceCenter/Virtual_PLC/PlcData.cs

@@ -0,0 +1,177 @@
+using FreeRedis;
+using Newtonsoft.Json;
+using ServiceCenter.Redis;
+
+namespace ServiceCenter.Virtual_PLC
+{
+    /// <summary>
+    /// plc数据
+    /// </summary>
+    public class PlcData
+    {
+        private static string RedisKey = "Virtual_PLC";
+        private static RedisClient Redis;
+
+        /// <summary>
+        /// 数据结构缓存
+        /// </summary>
+        private static List<PLCData> PLCDatas { get; set; } = new List<PLCData>();
+
+        /// <summary>
+        /// redis 链接字符串
+        /// </summary>
+        /// <param name="redisClient">Redis链接字符串</param>
+        public PlcData(string redisClient)
+        {
+            Redis = RedisHub.CreateContext(redisClient, RedisKey);
+        }
+
+        /// <summary>
+        /// redis 链接字符串
+        /// </summary>
+        /// <param name="redisClient"></param>
+        /// <returns></returns>
+        public static PlcData Init(string redisClient)
+        {
+            return new PlcData(redisClient);
+        }
+
+        /// <summary>
+        /// 初始化PLC数据
+        /// </summary>
+        /// <param name="pLCData">一个PLC</param>
+        private void InitPlcData(PLCData pLCData)
+        {
+            if (!PLCDatas.Contains(pLCData))
+            {
+                PLCDatas.Add(pLCData);
+            }
+            //用总长度除以数据长度,再以每断数据的起始位置、IP、DB组成Key
+            var mun = pLCData.Length / pLCData.DataLength;
+            int addstart = 0;
+
+            for (int i = 0; i < mun; i++)
+            {
+                var key = $"{pLCData.IP}:{pLCData.DB}:{addstart}";
+                if (Redis.Exists(key)) continue;
+                Redis.Set(key, new byte[pLCData.DataLength]);
+                addstart = addstart + pLCData.DataLength;
+            }
+        }
+
+        /// <summary>
+        /// 初始化PLC数据
+        /// </summary>
+        /// <param name="pLCDatas">多个PLC</param>
+        public void InitPlcData(List<PLCData> pLCDatas)
+        {
+            string key = "PLCDataList";
+            if (Redis.Exists(key))
+            {
+                //判断值是否相等,值相等代表已经有过数据生成了,并且没有任何变化
+                var RedisPlcData = Redis.Get(key);
+                if (JsonConvert.SerializeObject(pLCDatas) == RedisPlcData)
+                {
+                    PLCDatas = pLCDatas;
+                    return;
+                }
+            }
+
+            pLCDatas.ForEach(v =>
+            {
+                InitPlcData(v);
+            });
+            Redis.Set(key, JsonConvert.SerializeObject(pLCDatas));
+        }
+
+        /// <summary>
+        /// 按照DB读取
+        /// </summary>
+        /// <param name="pLCData"></param>
+        /// <returns></returns>
+        public static byte[] Read(PLCData pLCData)
+        {
+            byte[] data = new byte[pLCData.Length];
+            //用总长度除以数据长度,再以每断数据的起始位置、IP、DB组成Key
+            var mun = pLCData.Length / pLCData.DataLength;
+            int addstart = 0;
+
+            for (int i = 0; i < mun; i++)
+            {
+                var a = Redis.Get<byte[]>($"{pLCData.IP}:{pLCData.DB}:{addstart}");
+                a.CopyTo(data, addstart);
+                addstart = addstart + pLCData.DataLength;
+            }
+            return data;
+        }
+
+        /// <summary>
+        /// 按照长度读取
+        /// </summary>
+        /// <param name="pLCData"></param>
+        /// <param name="startLength">起始长度</param>
+        /// <returns></returns>
+        public static byte[] Read(PLCData pLCData, int startLength)
+        {
+            return System.Text.Encoding.Default.GetBytes(Redis.Get($"{pLCData.IP}:{pLCData.DB}:{startLength}"));
+        }
+
+        /// <summary>
+        /// 写入数据
+        /// </summary>
+        /// <param name="pLCData"></param>
+        /// <param name="startLength"></param>
+        /// <param name="value"></param>
+        public static void Write(PLCData pLCData, int startLength, byte[] value)
+        {
+            var data = PLCDatas.Find(v => v.IP == pLCData.IP && v.DB == pLCData.DB);
+            int start = 0;
+            if (data.DataLength == data.Length) //数据长度与总长度相等时计算
+            {
+                start = startLength < data.DataLength ? 0 : data.DataLength - startLength + startLength;
+            }
+            else //不等
+            {
+                int addstart = 0;
+                var mun = data.Length / data.DataLength;
+                var a = new List<int>();
+                for (int i = 0; i < mun; i++)
+                {
+                    a.Add(addstart);
+                    addstart = addstart + data.DataLength;
+                }
+                start = a.Where(v => v <= startLength).OrderByDescending(v => v).FirstOrDefault();
+                startLength = startLength - start;
+            }
+            var bytes = System.Text.Encoding.Default.GetBytes(Redis.Get($"{pLCData.IP}:{pLCData.DB}:{start}")); //获取原有数据
+            value.CopyTo(bytes, startLength); //将变更的数据,更新到redis字节组中
+            Redis.Set($"{pLCData.IP}:{pLCData.DB}:{start}", bytes);
+        }
+    }
+
+    /// <summary>
+    /// PLC数据结构
+    /// </summary>
+    public class PLCData
+    {
+        /// <summary>
+        /// IP
+        /// </summary>
+        public string IP { get; set; }
+
+        /// <summary>
+        /// DB
+        /// </summary>
+        public int DB { get; set; }
+
+        /// <summary>
+        /// 总长度
+        /// </summary>
+        public int Length { get; set; }
+
+        /// <summary>
+        /// 数据长度
+        /// </summary>
+        public int DataLength { get; set; }
+    }
+}

+ 126 - 0
ServiceCenter/WebApi/APICaller.cs

@@ -0,0 +1,126 @@
+using Newtonsoft.Json;
+using ServiceCenter.Logs;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Net;
+using System.Text;
+
+namespace ServiceCenter.WebApi
+{
+    public class APICaller
+    {
+        private class Result
+        {
+            public bool Succsess;
+            public string Exception;
+            public object Data;
+        }
+
+        private static ConcurrentDictionary<string, Result> Results = new ConcurrentDictionary<string, Result>();
+
+        public static T CallApi2<T>(string url, object parameter, string type = "POST")
+        {
+            var content = JsonConvert.SerializeObject(parameter);
+            var result = HttpApi(url, content, type);
+            return JsonConvert.DeserializeObject<T>(result);
+        }
+
+        public static T CallApi1<T>(string url, string parameter, string type = "GET")
+        {
+            var result = HttpApi(url + parameter, "", type);
+            return JsonConvert.DeserializeObject<T>(result);
+        }
+
+        public static T CallApi<T>(string url, object parameter, string type = "Post")
+        {
+            var content = JsonConvert.SerializeObject(parameter);
+            var key = url + content;
+            if (Results.ContainsKey(key))
+            {
+                var res = Results[key];
+                if (res == null) throw new Exception("接口调用中");
+
+                try
+                {
+                    if (!res.Succsess) throw new Exception(res.Exception);
+
+                    return (T)res.Data;
+                }
+                finally
+                {
+                    Results.Remove(key, out res);
+                }
+            }
+            else
+            {
+                Results[key] = null;
+                var task = Task.Run(() =>
+                 {
+                     var res = new Result();
+                     try
+                     {
+                         var result = HttpApi(url, content, type);
+                         res.Data = JsonConvert.DeserializeObject<T>(result);
+                         res.Succsess = true;
+                     }
+                     catch (Exception ex)
+                     {
+                         res.Exception = ex.Message;
+                     }
+                     finally
+                     {
+                         Results[key] = res;
+                     }
+                 });
+                task.Wait(50);
+                if (task.IsCompleted)
+                {
+                    return CallApi<T>(url, parameter, type);
+                }
+                else
+                {
+                    throw new Exception("接口调用中");
+                }
+            }
+        }
+
+        private static string HttpApi(string url, string jsonstr, string type)
+        {
+            var name = url.Split("/").Last();
+            LogHub.InterfacePublish(name, $"[{Thread.CurrentThread.ManagedThreadId}]--接口{name}传参---------{JsonConvert.SerializeObject(jsonstr)}");
+            try
+            {
+                var sw = new Stopwatch();
+                sw.Start();
+                Encoding encoding = Encoding.UTF8;
+                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);//webrequest请求api地址
+                request.Timeout = 60000;//连接超时
+                request.ReadWriteTimeout = 3600000;//读写超时
+                request.Accept = "text/html,application/xhtml+xml,*/*";
+
+                request.ContentType = "application/json";
+                request.Method = type.ToUpper().ToString();//get或者post
+                byte[] buffer = encoding.GetBytes(jsonstr);
+                request.ContentLength = buffer.Length;
+                if (type != "GET")
+                {
+                    request.GetRequestStream().Write(buffer, 0, buffer.Length);
+                }
+
+                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+                using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
+                {
+                    var res = reader.ReadToEnd();
+                    sw.Stop();
+                    LogHub.InterfacePublish(name, $"[{Thread.CurrentThread.ManagedThreadId}]--接口{name}调用耗时:{sw.ElapsedMilliseconds}---------{JsonConvert.SerializeObject(res)}");
+                    return res;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHub.InterfacePublish(name, $"[{Thread.CurrentThread.ManagedThreadId}]--接口{name}调用错误:{ex.StackTrace}");
+                throw new Exception(ex.Message);
+            }
+        }
+    }
+}

+ 29 - 0
WCS.Core/Attributes.cs

@@ -0,0 +1,29 @@
+namespace WCS.Core;
+
+public class BelongToAttribute : Attribute
+{
+    public BelongToAttribute(Type worldType)
+    {
+        WorldType = worldType;
+    }
+
+    public Type WorldType { get; set; }
+}
+
+public class OrderAttribute : Attribute
+{
+    public OrderAttribute(OrderType order, Type sysType)
+    {
+        Order = order;
+        SystemType = sysType;
+    }
+
+    public OrderType Order { get; set; }
+    public Type SystemType { get; set; }
+}
+
+public enum OrderType
+{
+    Before = -1,
+    After = 1
+}

+ 22 - 0
WCS.Core/Configs.cs

@@ -0,0 +1,22 @@
+using System.Text;
+
+namespace WCS.Core;
+
+public static class Configs
+{
+    /// <summary>
+    ///     设置协议通讯代理类的基类,继承至DataObject,IBaseType
+    /// </summary>
+    public static Type? ProtocolProxyBaseType { get; set; }
+
+    public static Encoding StringEncoding { get; set; }
+
+    public static string DebugRedisUrl { get; set; }
+
+    public static IPLCAccessorCreater? PLCAccessorCreater { get; set; }
+
+    public static string QdbConnString { get; set; }
+
+    public static string wmsConnString { get; set; }
+    public static string wcsConnString { get; set; }
+}

+ 34 - 0
WCS.Core/Data.cs

@@ -0,0 +1,34 @@
+using System.ComponentModel;
+
+namespace WCS.Core;
+
+public enum PLCType
+{
+    [Description("西门子")] Siemens = 1,
+
+    [Description("三菱")] Mitsubishi = 2,
+
+    [Description("欧姆龙")] Omron = 3,
+    [Description("虚拟PLC")] VitrualRedisPLC = 4
+}
+
+public struct PLCInfo
+{
+    public string IP { get; set; }
+    public int Port { get; set; }
+    public int Slot { get; set; }
+    public int Rack { get; set; }
+    public PLCType Type { get; set; }
+}
+
+public struct DBInfo
+{
+    public PLCInfo PLCInfo { get; set; }
+    public ushort No { get; set; }
+}
+
+public struct ProtocolInfo
+{
+    public DBInfo DBInfo { get; set; }
+    public int Position { get; set; }
+}

+ 417 - 0
WCS.Core/DataBlock.cs

@@ -0,0 +1,417 @@
+using System.Collections;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace WCS.Core;
+
+public class DataBlock : EntityEx<DBInfo>
+{
+    public static List<DataBlock> DBList = new();
+    private byte[] Data = new byte[0];
+
+    private DateTime faildTime = DateTime.MinValue;
+    private readonly int id;
+
+    private readonly List<ProtocolProxyBase> ProxyList = new();
+
+    public DataBlock(DBInfo ent, World world) : base(ent)
+    {
+        World = world;
+        DBList.Add(this);
+        id = DBList.Count;
+    }
+
+    public ushort Start { get; private set; }
+    public ushort Length { get; private set; }
+    public World World { get; }
+    public bool Failed { get; private set; } = true;
+    public event Action<byte[]> DbChanged;
+
+    public override string ToString()
+    {
+        return id.ToString();
+    }
+
+    public void RefreshData()
+    {
+        try
+        {
+            var plc = Entity.PLCInfo.Ex(World);
+            if (Entity.PLCInfo.Type != PLCType.VitrualRedisPLC)
+                if (Failed)
+                    if (!plc.Ping)
+                    {
+                        if (Entity.PLCInfo.IP == "1") throw new Exception($"{Entity.PLCInfo.IP}无法访问");
+                        Console.ForegroundColor = ConsoleColor.Red;
+                        Console.WriteLine($"{Entity.PLCInfo.IP}无法访问");
+                        Console.ResetColor();
+                        throw new Exception($"{Entity.PLCInfo.IP}无法访问");
+                    }
+
+            Start = (ushort)ProxyList.Min(v => v.Info.Position);
+            var last = ProxyList.OrderBy(v => v.Info.Position).Last();
+            Length = (ushort)(last.Info.Position + last.BytesCount);
+            var data = new byte[0];
+            
+            lock (plc)
+            {
+                
+                data = plc.Accessor.ReadBytes(Entity.No, Start, (ushort)(Length - Start));
+                
+            }
+
+            
+            if (!Data.SequenceEqual(data))
+            {
+                Data = data;
+                DbChanged?.Invoke(Data);
+            }
+
+            Failed = false;
+        }
+        catch (Exception ex)
+        {
+            Failed = true;
+
+            throw;
+        }
+    }
+
+    public PlcItem<T> Regist<T>(ProtocolProxyBase proxy, string objid, string name, int start, byte arrLen = 1,
+        byte strLen = 0)
+    {
+        if (!ProxyList.Contains(proxy))
+            ProxyList.Add(proxy);
+        var t = typeof(T);
+        var pv = new PlcItem<T>(objid, name, this, start, arrLen, strLen);
+        //Values.Add(pv);
+        return pv;
+    }
+
+    private ushort GetByteStart(int bitStart)
+    {
+        var res = bitStart / 8;
+        return (ushort)res;
+    }
+
+    private ushort GetBitIndex(int bitStart)
+    {
+        return (ushort)(bitStart % 8);
+    }
+
+    #region 读取
+
+    public T Read<T>(int bitStart, int strLength, int arrLength)
+    {
+        bitStart -= Start * 8;
+        return (T)Read(typeof(T), ref bitStart, strLength, arrLength);
+    }
+
+    private object Read(Type type, ref int bitStart, int strLength, int arrLength)
+    {
+        if (Failed) throw new Exception(Entity.No + "连接失败");
+        if (type.IsArray)
+        {
+            var t = type.GetElementType();
+            if (t.IsArray) throw new Exception("只支持一维数组");
+            var arr = Array.CreateInstance(t, arrLength);
+
+            for (var i = 0; i < arr.Length; i++)
+            {
+                var value = Read(t, ref bitStart, strLength, 0);
+                arr.SetValue(value, i);
+            }
+
+            return arr;
+        }
+
+        if (typeof(IList).IsAssignableFrom(type))
+        {
+            var t = type.GetGenericArguments().First();
+            var res = Activator.CreateInstance(type) as IList;
+            while (bitStart < Length * 8)
+            {
+                var value = Read(t, ref bitStart, strLength, 0);
+                res.Add(value);
+            }
+
+            return res;
+        }
+
+        if (type == typeof(bool)) return ReadBit(ref bitStart);
+
+        if (type == typeof(string)) return ReadString(ref bitStart, strLength);
+
+        if (type.IsEnum) return ReadEnum(type, ref bitStart);
+
+        if (type.IsPrimitive)
+        {
+            var revers = Entity.PLCInfo.Type == PLCType.Siemens;
+
+            return ReadPrimitive(type, ref bitStart, revers);
+
+            //var revers = Entity.PLC.TYPE == PLCType.西门子;
+
+            //return ReadPrimitive(type, ref bitStart, false);
+        }
+
+        if (type.IsValueType)
+        {
+            //ushort size = (ushort)Marshal.SizeOf(type);
+            //var s = bitStart;
+            //var bytes = ReadBytes(ref s, size).Reverse().ToArray();
+            //var o = ByteToStruct(type, bytes);
+
+            var res = Activator.CreateInstance(type);
+            foreach (var p in type.GetProperties())
+            {
+                var value = Read(p.PropertyType, ref bitStart, strLength, arrLength);
+                p.SetValue(res, value);
+            }
+
+            return res;
+        }
+
+        throw new Exception("类型不支持");
+    }
+
+    public static object ByteToStruct(Type type, byte[] dataBuffer)
+    {
+        var size = Marshal.SizeOf(type);
+        var allocIntPtr = Marshal.AllocHGlobal(size);
+        try
+        {
+            Marshal.Copy(dataBuffer, 0, allocIntPtr, size);
+            return Marshal.PtrToStructure(allocIntPtr, type);
+        }
+        finally
+        {
+            Marshal.FreeHGlobal(allocIntPtr);
+        }
+    }
+
+    private string ReadString(ref int bitStart, int strLen)
+    {
+        if (Entity.PLCInfo.Type == PLCType.Siemens)
+        {
+            var data = ReadBytes(ref bitStart, (ushort)strLen);
+
+            //var len = data[1];
+            //data = data.Skip(2).Take(len).ToArray();
+            var str = Configs.StringEncoding.GetString(data);
+            return str;
+        }
+        else
+        {
+            var data = ReadBytes(ref bitStart, (ushort)strLen);
+            var str = Configs.StringEncoding.GetString(data);
+            return str;
+        }
+    }
+
+    private object ReadEnum(Type enumType, ref int bitStart)
+    {
+        var type = enumType.GetEnumUnderlyingType();
+
+        var reverse = false;
+
+        if (enumType.GetCustomAttribute<FlagsAttribute>() == null) reverse = Entity.PLCInfo.Type == PLCType.Siemens;
+
+        var value = ReadPrimitive(type, ref bitStart, reverse);
+        var res = Enum.Parse(enumType, value.ToString());
+        return res;
+    }
+
+    private object ReadPrimitive(Type type, ref int bitStart, bool reverse)
+    {
+        var size = (ushort)Marshal.SizeOf(type);
+        if (type == typeof(bool))
+            size = 1;
+        var data = ReadBytes(ref bitStart, size);
+        if (reverse)
+            data = data.Reverse().ToArray();
+        if (type == typeof(byte))
+            return data.First();
+        if (type == typeof(bool))
+            return BitConverter.ToBoolean(data, 0);
+        if (type == typeof(char))
+            return BitConverter.ToChar(data, 0);
+        if (type == typeof(short))
+            return BitConverter.ToInt16(data, 0);
+        if (type == typeof(ushort))
+            return BitConverter.ToUInt16(data, 0);
+        if (type == typeof(int))
+            return BitConverter.ToInt32(data, 0);
+        if (type == typeof(uint))
+            return BitConverter.ToUInt32(data, 0);
+        if (type == typeof(long))
+            return BitConverter.ToInt64(data, 0);
+        if (type == typeof(ulong))
+            return BitConverter.ToUInt64(data, 0);
+        if (type == typeof(float))
+            return BitConverter.ToSingle(data, 0);
+        if (type == typeof(double))
+            return BitConverter.ToDouble(data, 0);
+        throw new Exception("类型不支持");
+    }
+
+    private byte[] ReadBytes(ref int bitStart, ushort length)
+    {
+        var start = GetByteStart(bitStart);
+        if (Data.Length < start + length)
+        {
+            var data1 = "";
+            foreach (var item in Data.ToList()) data1 = data1 + $"[{item}]";
+            var msg = "读取数据的位置超出数据总长度:";
+            msg = msg + $"Data--{Data.Length};";
+            msg = msg + $"bitStart--{bitStart};";
+            msg = msg + $"start--{start};";
+            msg = msg + $"length--{length};";
+            msg = msg + $"读取PLC传入参数{Entity.No},{Start},{(ushort)(Length - Start)};";
+            msg = msg + $"Data内容--{data1}";
+            throw new Exception(msg);
+        }
+
+        var data = Data.Skip(start).Take(length).ToArray();
+        bitStart += length * 8;
+        return data;
+    }
+
+    private bool ReadBit(ref int bitStart)
+    {
+        var start = GetByteStart(bitStart);
+        var b = Data.Skip(start).First();
+        var bitIndex = GetBitIndex(bitStart);
+        bitStart++;
+        return b.GetBit(bitIndex);
+    }
+
+    #endregion 读取
+
+    #region 写入
+
+    public void Write<T>(int bitStart, T value, int strLength, int arrLength)
+    {
+        Write(typeof(T), ref bitStart, value, strLength, arrLength);
+    }
+
+    private void Write(Type type, ref int bitStart, object value, int strLength, int arrLength)
+    {
+        if (type.IsArray)
+        {
+            var t = type.GetElementType();
+            if (t.IsArray) throw new Exception("只支持一维数组");
+            var arr = value as Array;
+            if (arr.Length > arrLength)
+                throw new Exception("数组长度超出");
+            foreach (var obj in arr) Write(obj.GetType(), ref bitStart, obj, strLength, arrLength);
+        }
+        else if (type == typeof(bool))
+        {
+            WriteBit(ref bitStart, (bool)value);
+        }
+        else if (type == typeof(string))
+        {
+            WriteString(ref bitStart, value.ToString(), strLength);
+        }
+        else if (type.IsEnum)
+        {
+            WriteEnum(ref bitStart, value);
+        }
+        else if (type.IsPrimitive)
+        {
+            var revers = Entity.PLCInfo.Type == PLCType.Siemens;
+            WritePrimitive(ref bitStart, value, revers);
+        }
+        else
+        {
+            throw new Exception("类型不支持");
+        }
+    }
+
+    private void WriteString(ref int bitStart, string value, int strLen)
+    {
+        var data = Configs.StringEncoding.GetBytes(value);
+        if (data.Length > strLen)
+            throw new Exception("字符串长度超出");
+        //if (Entity.PLCInfo.Type == PLCType.Siemens)
+        //{
+        //    var a = Encoding.UTF8.GetString(data);
+        //    var title = new byte[] { (byte)strLen, (byte)data.Length };
+        //    data = title.Concat(data).ToArray();
+        //    var b = Encoding.UTF8.GetString(data);
+
+        //    var c = 0;
+        //}
+        WriteBytes(ref bitStart, data);
+    }
+
+    private void WriteEnum(ref int bitStart, object value)
+    {
+        var etype = value.GetType();
+        var type = etype.GetEnumUnderlyingType();
+        var obj = Convert.ChangeType(value, type);
+        var reverse = false;
+        if (etype.GetCustomAttribute<FlagsAttribute>() == null) reverse = Entity.PLCInfo.Type == PLCType.Siemens;
+        WritePrimitive(ref bitStart, obj, reverse);
+    }
+
+    private void WritePrimitive(ref int bitStart, object value, bool reverse)
+    {
+        var type = value.GetType();
+        byte[] data;
+        if (type == typeof(char))
+            data = BitConverter.GetBytes((char)value);
+        else if (type == typeof(byte))
+            data = new[] { (byte)value };
+        else if (type == typeof(short))
+            data = BitConverter.GetBytes((short)value);
+        else if (type == typeof(ushort))
+            data = BitConverter.GetBytes((ushort)value);
+        else if (type == typeof(int))
+            data = BitConverter.GetBytes((int)value);
+        else if (type == typeof(uint))
+            data = BitConverter.GetBytes((uint)value);
+        else if (type == typeof(long))
+            data = BitConverter.GetBytes((long)value);
+        else if (type == typeof(ulong))
+            data = BitConverter.GetBytes((ulong)value);
+        else
+            throw new Exception("类型不支持");
+        if (reverse)
+            data = data.Reverse().ToArray();
+        WriteBytes(ref bitStart, data);
+    }
+
+    private void WriteBytes(ref int bitStart, byte[] data)
+    {
+        lock (Entity.PLCInfo.Ex(World))
+        {
+            var start = GetByteStart(bitStart);
+
+            Entity.PLCInfo.Ex(World).Accessor.WriteBytes(Entity.No, start, data);
+            data.CopyTo(Data, start - Start);
+            bitStart += data.Length * 8;
+        }
+    }
+
+    private void WriteBit(ref int bitStart, bool flag)
+    {
+        lock (Entity.PLCInfo.Ex(World))
+        {
+            var start = GetByteStart(bitStart);
+            var bitIndex = GetBitIndex(bitStart);
+
+            var b = Data.Skip(start).First();
+            b = b.SetBit(bitIndex, flag);
+            var data = new[] { b };
+
+            Entity.PLCInfo.Ex(World).Accessor.WriteBytes(Entity.No, start, data);
+            data.CopyTo(Data, start - Start);
+
+            bitStart += 1;
+        }
+    }
+
+    #endregion 写入
+}

+ 308 - 0
WCS.Core/Device.cs

@@ -0,0 +1,308 @@
+using System.Collections.Concurrent;
+using System.Reflection;
+using System.Text.Json.Serialization;
+
+namespace WCS.Core;
+
+public class EntityEx<T>
+{
+    public EntityEx(T ent)
+    {
+        Entity = ent;
+    }
+
+    public T Entity { get; }
+
+    public override string ToString()
+    {
+        return Entity.ToString();
+    }
+}
+
+public class Device
+{
+    private static readonly ConcurrentDictionary<string, Device> all = new();
+
+    private readonly ConcurrentDictionary<World, ConcurrentDictionary<Type, object>> ProtocolObjsOfWorld = new();
+
+    public Device(string code)
+    {
+        if (all.ContainsKey(code))
+            throw new Exception($"{code}设备号重复");
+        Code = code;
+        all[code] = this;
+    }
+
+    public static IEnumerable<Device> All => all.Values;
+
+    public string Code { get; set; } = "";
+
+    public Dictionary<string, ProtocolInfo> Protocols { get; } = new();
+
+    [JsonIgnore]
+    public IEnumerable<Device> Targets
+    {
+        get { return GetFlags("Target").Select(v => all[v]).ToList(); }
+    }
+
+    [JsonIgnore]
+    public IEnumerable<Device> Sources
+    {
+        get { return All.Where(v => v.GetFlags("Target").Contains(Code)).ToArray(); }
+    }
+
+    /// <summary>
+    ///     设备组
+    /// </summary>
+    [JsonIgnore]
+    public IEnumerable<Device> DeviceGroup
+    {
+        get { return GetFlags("DeviceGroup").Select(v => all[v]).ToList(); }
+    }
+
+    [JsonIgnore]
+    public Device? Parent
+    {
+        get
+        {
+            var code = GetFlag("Parent");
+            if (string.IsNullOrEmpty(code))
+                return null;
+            return Find(code);
+        }
+        set => SetFlag("Parent", value.Code);
+    }
+
+    public Dictionary<string, List<string>> Flags { get; } = new();
+
+    public static Device Find(string code)
+    {
+        if (all.TryGetValue(code, out var dev))
+            return dev;
+        throw new Exception($"未找到设备{code}");
+    }
+
+    public ConcurrentDictionary<Type, object> ProtocolObjs(World world)
+    {
+        if (!ProtocolObjsOfWorld.TryGetValue(world, out var pobjs))
+        {
+            pobjs = new ConcurrentDictionary<Type, object>();
+            ProtocolObjsOfWorld[world] = pobjs;
+        }
+
+        return pobjs;
+    }
+
+    public void AddProtocol(Type type, ProtocolInfo info)
+    {
+        if (!Protocols.TryAdd(type.AssemblyQualifiedName, info))
+            throw new Exception($"{Code}重复添加协议{type.Name}");
+    }
+
+    public void AddProtocol<T>(ProtocolInfo info)
+    {
+        AddProtocol(typeof(T), info);
+    }
+
+    public bool HasProtocol(Type type)
+    {
+        return Protocols.ContainsKey(type.AssemblyQualifiedName);
+    }
+
+    public bool HasProtocol<T>()
+    {
+        return HasProtocol(typeof(T));
+    }
+
+    public T Protocol<T>(World world)
+    {
+        return (T)Protocol(typeof(T), world);
+    }
+
+    public object Protocol(Type protocolType, World world)
+    {
+        if (!HasProtocol(protocolType))
+            throw new Exception($"{Code}不包含协议:{protocolType.Name}");
+
+        if (!ProtocolObjs(world).ContainsKey(protocolType))
+        {
+            var type = typeof(Generator<,>);
+            type = type.MakeGenericType(protocolType, Configs.ProtocolProxyBaseType);
+            var m = type.GetMethod("Create", BindingFlags.Public | BindingFlags.Static);
+
+            var info = Protocols[protocolType.AssemblyQualifiedName];
+            var obj = m.Invoke(null, new object[] { new object[] { this, info, protocolType, world } });
+            ProtocolObjs(world)[protocolType] = obj;
+        }
+
+        return ProtocolObjs(world)[protocolType];
+    }
+
+    public override string ToString()
+    {
+        return Code;
+    }
+
+    /// <summary>
+    ///     添加设备组信息
+    /// </summary>
+    /// <param name="codes"></param>
+    /// <exception cref="Exception"></exception>
+    public void AddDeviceGroup(params string[] codes)
+    {
+        foreach (var code in codes)
+        {
+            if (HasFlag("DeviceGroup", code))
+                throw new Exception($"{Code}子设备:{code}");
+            AddFlag("DeviceGroup", code);
+        }
+    }
+
+    public void AddTarget(params string[] codes)
+    {
+        foreach (var code in codes)
+        {
+            if (HasFlag("Target", code))
+                throw new Exception($"{Code}已经存在目的地:{code}");
+            AddFlag("Target", code);
+        }
+    }
+
+    public Device AddFlag(string key, string flag)
+    {
+        if (!Flags.ContainsKey(key))
+            Flags[key] = new List<string>();
+        Flags[key].Add(flag);
+        return this;
+    }
+
+    public void SetFlag(string flag)
+    {
+        SetFlag("Globals", flag);
+    }
+
+    public void SetFlag(string key, string flag)
+    {
+        var list = new List<string>();
+        list.Add(flag);
+        Flags[key] = list;
+    }
+
+    public IEnumerable<string> GetFlags(string key)
+    {
+        if (!Flags.ContainsKey(key))
+            return new List<string>();
+        return Flags[key];
+    }
+
+    public string? GetFlag(string key)
+    {
+        return GetFlags(key).FirstOrDefault();
+    }
+
+    public bool HasFlag(string key, string flag)
+    {
+        if (!Flags.ContainsKey(key))
+            return false;
+        return Flags[key].Contains(flag);
+    }
+
+    public bool HasFlag(string flag)
+    {
+        return HasFlag("Globals", flag);
+    }
+
+    public Device AddFlag<T>(T flag) where T : struct, Enum
+    {
+        var key = typeof(T).AssemblyQualifiedName;
+        if (Flags.ContainsKey(key))
+        {
+            dynamic value = Enum.Parse<T>(Flags[key].First());
+            var str = (value | flag).ToString();
+            SetFlag(key, str);
+        }
+        else
+        {
+            var str = flag.ToString();
+            SetFlag(key, str);
+        }
+
+        return this;
+    }
+
+    public bool HasFlag<T>(T flag) where T : struct, Enum
+    {
+        var key = typeof(T).AssemblyQualifiedName;
+        if (!Flags.ContainsKey(key))
+            return false;
+        dynamic value = Enum.Parse<T>(Flags[key].First());
+        return value.HasFlag(flag);
+    }
+}
+
+public class Device<T> : EntityEx<Device>
+{
+    public Device(Device device, World world) : base(device)
+    {
+        World = world;
+        Data = Entity.Protocol<T>(world);
+    }
+
+    public World World { get; private set; }
+    public T Data { get; set; }
+
+    public override string ToString()
+    {
+        return Entity.Code;
+    }
+}
+
+public class Device<T, T2> : Device<T>
+{
+    public Device(Device device, World world) : base(device, world)
+    {
+        Data2 = Entity.Protocol<T2>(world);
+    }
+
+    public T2 Data2 { get; set; }
+}
+
+public class Device<T, T2, T3> : Device<T, T2>
+{
+    public Device(Device device, World world) : base(device, world)
+    {
+        Data3 = Entity.Protocol<T3>(world);
+    }
+
+    public T3 Data3 { get; set; }
+}
+
+public class Device<T, T2, T3, T4> : Device<T, T2, T3>
+{
+    public Device(Device device, World world) : base(device, world)
+    {
+        Data4 = Entity.Protocol<T4>(world);
+    }
+
+    public T4 Data4 { get; set; }
+}
+
+public class Device<T, T2, T3, T4, T5> : Device<T, T2, T3, T4>
+{
+    public Device(Device device, World world) : base(device, world)
+    {
+        Data5 = Entity.Protocol<T5>(world);
+    }
+
+    public T5 Data5 { get; set; }
+}
+
+public class Device<T, T2, T3, T4, T5, T6> : Device<T, T2, T3, T4, T5>
+{
+    public Device(Device device, World world) : base(device, world)
+    {
+        Data6 = Entity.Protocol<T6>(world);
+    }
+
+    public T6 Data6 { get; set; }
+}

+ 483 - 0
WCS.Core/Extentions.cs

@@ -0,0 +1,483 @@
+using System.Collections.Concurrent;
+using System.ComponentModel;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace WCS.Core;
+
+public static class Extentions
+{
+    private static readonly ConcurrentDictionary<object, object> ExObjs = new();
+
+    private static readonly ConcurrentDictionary<World, ConcurrentDictionary<object, object>> ExObjsOfWorld = new();
+
+    public static T GetEx<T>(object entity)
+    {
+        lock (ExObjs)
+        {
+            if (!ExObjs.ContainsKey(entity))
+            {
+                var obj = Activator.CreateInstance(typeof(T), entity);
+                ExObjs[entity] = obj;
+            }
+
+            return (T)ExObjs[entity];
+        }
+    }
+
+    public static PLC Ex(this PLCInfo source, World world)
+    {
+        return GetExOfWorld<PLC>(source, world);
+    }
+
+    private static T GetExOfWorld<T>(this object source, World world)
+    {
+        if (!ExObjsOfWorld.TryGetValue(world, out var objs))
+        {
+            ExObjsOfWorld.TryAdd(world, new ConcurrentDictionary<object, object>());
+            objs = ExObjsOfWorld[world];
+        }
+
+        if (!objs.TryGetValue(source, out var obj))
+            lock (objs)
+            {
+                if (!objs.ContainsKey(source))
+                {
+                    obj = Activator.CreateInstance(typeof(T), source, world);
+                    objs.TryAdd(source, obj);
+                }
+
+                obj = objs[source];
+            }
+
+        return (T)obj;
+    }
+
+    public static DataBlock Ex(this DBInfo source, World world)
+    {
+        return GetExOfWorld<DataBlock>(source, world);
+    }
+
+    public static DataBlock[] GetDataBlocks(this World source)
+    {
+        ExObjsOfWorld.TryGetValue(source, out var dic);
+        if (dic == null)
+            return new DataBlock[0];
+        var res = dic.Values.OfType<DataBlock>().ToArray();
+        return res;
+    }
+
+    public static WorldEx Ex(this World source)
+    {
+        return GetEx<WorldEx>(source);
+    }
+
+
+    public static void AddSafe<T>(this List<T> source, T item)
+    {
+        lock (source)
+        {
+            source.Add(item);
+        }
+    }
+
+    public static void AddSafe<T>(this List<T> source, IEnumerable<T> item)
+    {
+        lock (source)
+        {
+            source.AddRange(item);
+        }
+    }
+
+    public static string Description<T>(this T source) where T : struct, Enum
+    {
+        var name = Enum.GetName(source);
+        var f = source.GetType().GetField(name);
+        var attr = f.GetCustomAttribute<DescriptionAttribute>();
+        if (attr == null)
+            return source.ToString();
+        return attr.Description;
+    }
+
+    public static object Copy(this object source, Type t)
+    {
+        var obj = Activator.CreateInstance(t);
+        foreach (var p in t.GetProperties())
+        {
+            var p2 = source.GetType().GetProperty(p.Name);
+            var value = p2.GetValue(source);
+            p.SetValue(obj, value);
+        }
+
+        return obj;
+    }
+
+    public static object Copy(this object source, Type t, DateTime farme)
+    {
+        var obj = Activator.CreateInstance(t);
+        foreach (var p in t.GetProperties())
+        {
+            var p2 = source.GetType().GetProperty(p.Name);
+            if (p2 == null) continue;
+            var value = p2.GetValue(source);
+            //判断一下P2的类型是否为字符串
+            if (p2.PropertyType == typeof(string))
+            {
+                var sValue = (string)value;
+                value = sValue.Trim('\0', '\a', '\b', '\f', '\n', '\r', '\t', '\v').Trim();
+            }
+
+            p.SetValue(obj, value);
+        }
+
+        t.GetProperty("Frame").SetValue(obj, farme);
+        return obj;
+    }
+
+    public static T Copy<T>(this object source)
+    {
+        return (T)source.Copy(typeof(T), DateTime.Now);
+    }
+}
+
+public static class BitExtension
+{
+    #region ushort
+
+    /// <summary>
+    ///     设置指定位置的位值的值
+    /// </summary>
+    /// <param name="value">ushort对象</param>
+    /// <param name="position">指定位置</param>
+    /// <param name="flag">值</param>
+    /// <returns></returns>
+    public static ushort SetBit(this ushort value, int position, bool flag)
+    {
+        return SetBits(value, position, 1, flag ? (byte)1 : (byte)0);
+    }
+
+    /// <summary>
+    ///     批量设置指定位置的位值的值
+    /// </summary>
+    /// <param name="value">ushort对象</param>
+    /// <param name="position">开始位置</param>
+    /// <param name="length">长度</param>
+    /// <param name="bits">值</param>
+    /// <returns></returns>
+    public static ushort SetBits(this ushort value, int position, int length, ushort bits)
+    {
+        if (length <= 0 || position >= 16) return value;
+
+        var mask = (2 << (length - 1)) - 1;
+
+        value &= (ushort)~(mask << position);
+        value |= (ushort)((bits & mask) << position);
+
+        return value;
+    }
+
+    /// <summary>
+    ///     获取指定位置的值
+    /// </summary>
+    /// <param name="value">ushort对象</param>
+    /// <param name="position">指定位置</param>
+    /// <returns></returns>
+    public static bool GetBit(this ushort value, int position)
+    {
+        return GetBits(value, position, 1) == 1;
+    }
+
+    /// <summary>
+    ///     批量获取指定位置的值
+    /// </summary>
+    /// <param name="value">ushort对象</param>
+    /// <param name="position">开始位值</param>
+    /// <param name="length">长度</param>
+    /// <returns></returns>
+    public static ushort GetBits(this ushort value, int position, int length)
+    {
+        if (length <= 0 || position >= 16) return 0;
+
+        var mask = (2 << (length - 1)) - 1;
+
+        return (ushort)((value >> position) & mask);
+    }
+
+    #endregion ushort
+
+    #region byte
+
+    /// <summary>
+    ///     设置指定位置的值
+    /// </summary>
+    /// <param name="value">byte对象</param>
+    /// <param name="position">指定位置</param>
+    /// <param name="flag">设置值</param>
+    /// <returns></returns>
+    public static byte SetBit(this byte value, int position, bool flag)
+    {
+        if (position >= 8) return value;
+
+        var mask = (2 << (1 - 1)) - 1;
+
+        value &= (byte)~(mask << position);
+        value |= (byte)(((flag ? 1 : 0) & mask) << position);
+
+        return value;
+    }
+
+    /// <summary>
+    ///     获取指定位置的值
+    /// </summary>
+    /// <param name="value">byte对象</param>
+    /// <param name="position">指定位置</param>
+    /// <returns></returns>
+    public static bool GetBit(this byte value, int position)
+    {
+        if (position >= 8) return false;
+
+        var mask = (2 << (1 - 1)) - 1;
+
+        return (byte)((value >> position) & mask) == 1;
+    }
+
+    #endregion byte
+
+    #region uint
+
+    /// <summary>
+    ///     设置指定位置的位值的值
+    /// </summary>
+    /// <param name="value">uint对象</param>
+    /// <param name="position">指定位置</param>
+    /// <param name="flag">值</param>
+    /// <returns></returns>
+    public static uint SetBit(this uint value, int position, bool flag)
+    {
+        return SetBits(value, position, 1, flag ? (byte)1 : (byte)0);
+    }
+
+    /// <summary>
+    ///     批量设置指定位置的位值的值
+    /// </summary>
+    /// <param name="value">uint对象</param>
+    /// <param name="position">开始位置</param>
+    /// <param name="length">长度</param>
+    /// <param name="bits">值</param>
+    /// <returns></returns>
+    public static uint SetBits(this uint value, int position, int length, uint bits)
+    {
+        if (length <= 0 || position >= 32) return value;
+
+        var mask = (2 << (length - 1)) - 1;
+
+        value &= (uint)~(mask << position);
+        value |= (uint)((bits & mask) << position);
+
+        return value;
+    }
+
+    /// <summary>
+    ///     获取指定位置的值
+    /// </summary>
+    /// <param name="value">uint对象</param>
+    /// <param name="position">指定位置</param>
+    /// <returns></returns>
+    public static bool GetBit(this uint value, int position)
+    {
+        return GetBits(value, position, 1) == 1;
+    }
+
+    /// <summary>
+    ///     批量获取指定位置的值
+    /// </summary>
+    /// <param name="value">uint对象</param>
+    /// <param name="position">开始位值</param>
+    /// <param name="length">长度</param>
+    /// <returns></returns>
+    public static uint GetBits(this uint value, int position, int length)
+    {
+        if (length <= 0 || position >= 32) return 0;
+
+        var mask = (2 << (length - 1)) - 1;
+
+        return (uint)((value >> position) & mask);
+    }
+
+    #endregion uint
+
+    #region ulong
+
+    /// <summary>
+    ///     设置指定位置的位值的值
+    /// </summary>
+    /// <param name="value">ulong对象</param>
+    /// <param name="position">指定位置</param>
+    /// <param name="flag">值</param>
+    /// <returns></returns>
+    public static ulong SetBit(this ulong value, int position, bool flag)
+    {
+        return SetBits(value, position, 1, flag ? (byte)1 : (byte)0);
+    }
+
+    /// <summary>
+    ///     批量设置指定位置的位值的值
+    /// </summary>
+    /// <param name="value">ulong对象</param>
+    /// <param name="position">开始位置</param>
+    /// <param name="length">长度</param>
+    /// <param name="bits">值</param>
+    /// <returns></returns>
+    public static ulong SetBits(this ulong value, int position, int length, ulong bits)
+    {
+        if (length <= 0 || position >= 64) return value;
+
+        var mask = (ulong)(2 << (length - 1)) - 1;
+
+        value &= ~(mask << position);
+        value |= (bits & mask) << position;
+
+        return value;
+    }
+
+    /// <summary>
+    ///     获取指定位置的值
+    /// </summary>
+    /// <param name="value">ulong对象</param>
+    /// <param name="position">指定位置</param>
+    /// <returns></returns>
+    public static bool GetBit(this ulong value, int position)
+    {
+        return GetBits(value, position, 1) == 1;
+    }
+
+    /// <summary>
+    ///     批量获取指定位置的值
+    /// </summary>
+    /// <param name="value">ulong对象</param>
+    /// <param name="position">开始位值</param>
+    /// <param name="length">长度</param>
+    /// <returns></returns>
+    public static ulong GetBits(this ulong value, int position, int length)
+    {
+        if (length <= 0 || position >= 64) return 0;
+
+        var mask = (ulong)(2 << (length - 1)) - 1;
+
+        return (value >> position) & mask;
+    }
+
+    #endregion ulong
+}
+
+public static class ExpressionExtensions
+{
+    public static string ExpToString(this LambdaExpression source)
+    {
+        var str = "";
+        try
+        {
+            str = LambdaToString(source);
+        }
+        catch
+        {
+            str = source.ToString();
+        }
+
+        var arr = str.Split("Convert(");
+        arr = arr.Select(v =>
+        {
+            if (!v.Contains(','))
+                return v;
+            var index = v.IndexOf(',');
+            var index2 = v.IndexOf(')', index);
+            var sub = v.Substring(index, index2 - index + 1);
+            var str = v.Replace(sub, "");
+            return str;
+        }).ToArray();
+        var res = string.Join("", arr);
+        return res;
+    }
+
+    public static string LambdaToString(LambdaExpression expression)
+    {
+        var replacements = new Dictionary<string, string>();
+        WalkExpression(replacements, expression);
+        var body = expression.ToString();
+        foreach (var parm in expression.Parameters)
+        {
+            var parmName = parm.Name;
+            var parmTypeName = parm.Type.Name;
+            body = body.Replace(parmName + " =>", "(" + parmTypeName + " v) =>");
+        }
+
+        foreach (var replacement in replacements) body = body.Replace(replacement.Key, replacement.Value);
+        return body;
+    }
+
+    private static void WalkExpression(Dictionary<string, string> replacements, Expression expression)
+    {
+        switch (expression.NodeType)
+        {
+            case ExpressionType.MemberAccess:
+                var replacementExpression = expression.ToString();
+                if (replacementExpression.Contains("value("))
+                {
+                    var replacementValue = Expression.Lambda(expression).Compile().DynamicInvoke().ToString();
+                    if (!replacements.ContainsKey(replacementExpression))
+                        replacements.Add(replacementExpression, replacementValue);
+                }
+
+                break;
+            case ExpressionType.GreaterThan:
+            case ExpressionType.GreaterThanOrEqual:
+            case ExpressionType.LessThan:
+            case ExpressionType.LessThanOrEqual:
+            case ExpressionType.OrElse:
+            case ExpressionType.AndAlso:
+            case ExpressionType.Equal:
+            case ExpressionType.NotEqual:
+                var bexp = expression as BinaryExpression;
+                WalkExpression(replacements, bexp.Left);
+                WalkExpression(replacements, bexp.Right);
+                break;
+            case ExpressionType.Call:
+                var mcexp = expression as MethodCallExpression;
+                foreach (var argument in mcexp.Arguments) WalkExpression(replacements, argument);
+                break;
+            case ExpressionType.Lambda:
+                var lexp = expression as LambdaExpression;
+                WalkExpression(replacements, lexp.Body);
+                break;
+            case ExpressionType.Constant:
+                //do nothing
+                break;
+            case ExpressionType.Convert:
+                var exp = expression as UnaryExpression;
+                WalkExpression(replacements, exp.Operand);
+                break;
+        }
+    }
+
+
+    private static bool When<T>(this T source, Expression<Func<T, bool>> exp) where T : class
+    {
+        var str = exp.ExpToString();
+        try
+        {
+            var res = exp.Compile().Invoke(source);
+            str += res ? " 成立" : " 不成立";
+            return res;
+        }
+        catch (Exception ex)
+        {
+            str += ex.GetBaseException().Message;
+            throw;
+        }
+        finally
+        {
+            Console.WriteLine(str);
+            Console.WriteLine("------------------------------------");
+        }
+    }
+}

+ 24 - 0
WCS.Core/Flags.cs

@@ -0,0 +1,24 @@
+namespace WCS.Core;
+
+[Flags]
+public enum DeviceFlags
+{
+    扫码 = 1 << 0,
+    称重 = 1 << 1,
+    外检 = 1 << 2,
+
+    顶升 = 1 << 3,
+    移栽 = 1 << 4,
+    旋转 = 1 << 5,
+
+    入库 = 1 << 6,
+    出库 = 1 << 7,
+
+    巷道口 = 1 << 8,
+    RGV口 = 1 << 9,
+    AGV口 = 1 << 10,
+
+    直轨 = 1 << 11,
+    弯轨 = 1 << 12,
+    环轨 = 1 << 13
+}

+ 343 - 0
WCS.Core/IL/Generator.cs

@@ -0,0 +1,343 @@
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace WCS.Core;
+
+/// <summary>
+///     动态创建接口实例
+/// </summary>
+/// <typeparam name="T">接口</typeparam>
+/// <typeparam name="TBase">基类</typeparam>
+public static class Generator<T, TBase> where TBase : IProtocolProxy
+{
+    private const MethodAttributes METHOD_ATTRIBUTES =
+        MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final |
+        MethodAttributes.HideBySig;
+
+    private static readonly TypeBuilder tb;
+    private static readonly Type Type;
+
+    static Generator()
+    {
+        if (!typeof(T).IsInterface)
+            throw new Exception("T必须是interface");
+        if (!typeof(TBase).IsClass)
+            throw new Exception("TBase必须是class");
+
+        var myAssemblyName = new AssemblyName();
+        myAssemblyName.Name = typeof(T).Name + "_Generate";
+        var myAssembly = AssemblyBuilder.DefineDynamicAssembly(myAssemblyName, AssemblyBuilderAccess.RunAndCollect);
+        //AssemblyBuilder myAssembly = AssemblyBuilder.DefineDynamicAssembly(myAssemblyName, AssemblyBuilderAccess.RunAndSave);
+
+
+        var myModule = myAssembly.DefineDynamicModule("Main");
+        tb = myModule.DefineType(typeof(T).Name + "_Generate", TypeAttributes.Public, typeof(TBase),
+            new[] { typeof(T) });
+        InitConstructors();
+        InitMethods();
+        InitProperties();
+        InitEvents();
+        try
+        {
+            Type = tb.CreateTypeInfo().AsType(); //.CreateType();
+        }
+        catch (Exception ex)
+        {
+        }
+        //myAssembly.Save(typeof(T).Name + ".dll");
+    }
+
+    private static void InitConstructors()
+    {
+        foreach (var cons in typeof(TBase).GetConstructors())
+        {
+            var pTypes = cons.GetParameters().Select(v => v.ParameterType).ToArray();
+            var build = tb.DefineConstructor(cons.Attributes, CallingConventions.Standard, pTypes);
+            for (var i = 0; i < pTypes.Length; i++) build.DefineParameter(i + 1, ParameterAttributes.In, "p" + i);
+            var il = build.GetILGenerator();
+            il.Emit(OpCodes.Ldarg_0);
+            for (var i = 0; i < pTypes.Length; i++) il.Emit(OpCodes.Ldarg, i + 1);
+            il.Emit(OpCodes.Call, cons);
+            //il.Emit(OpCodes.Pop);
+            il.Emit(OpCodes.Ret);
+        }
+    }
+
+    private static void InitMethods()
+    {
+        var ms = GetInterfaces().SelectMany(v => v.GetMethods()).ToArray();
+        ms = ms.Where(v => !v.IsSpecialName).ToArray();
+        foreach (var m in ms) GenerateMethod(m);
+    }
+
+    private static void InitProperties()
+    {
+        var ps = GetInterfaces().SelectMany(v => v.GetProperties());
+        ps = GetMembers().OfType<PropertyInfo>();
+        foreach (var p in ps)
+        {
+            if (typeof(TBase).GetProperty(p.Name,
+                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null) continue;
+            var pTypes = p.GetIndexParameters().Select(v => v.ParameterType).ToArray();
+            var pb = tb.DefineProperty(p.Name, p.Attributes, p.PropertyType, pTypes);
+            if (p.CanRead)
+            {
+                if (pTypes.Length > 0)
+                {
+                    //索引器
+                    var mb = GenerateIndexer(p.GetGetMethod());
+                    pb.SetGetMethod(mb);
+                }
+                else
+                {
+                    var mb = GenerateProperty(p.GetGetMethod());
+                    pb.SetGetMethod(mb);
+                }
+            }
+
+            if (p.CanWrite)
+            {
+                if (pTypes.Length > 0)
+                {
+                    var mb = GenerateIndexer(p.GetSetMethod());
+                    pb.SetSetMethod(mb);
+                }
+                else
+                {
+                    var mb = GenerateProperty(p.GetSetMethod());
+                    pb.SetSetMethod(mb);
+                }
+            }
+        }
+    }
+
+    private static void InitEvents()
+    {
+        var evts = GetInterfaces().SelectMany(v => v.GetEvents());
+
+        foreach (var evt in evts)
+        {
+            var eb = tb.DefineEvent(evt.Name, evt.Attributes, evt.EventHandlerType);
+            var add = GenerateEventEnabled(evt, evt.GetAddMethod());
+            eb.SetAddOnMethod(add);
+            var remove = GenerateEventEnabled(evt, evt.GetRemoveMethod());
+            eb.SetRemoveOnMethod(remove);
+        }
+    }
+
+    private static MethodBuilder GenerateMethod(MethodInfo m)
+    {
+        var pTypes = m.GetParameters().Select(v => v.ParameterType).ToArray();
+        var mb = tb.DefineMethod(m.Name, METHOD_ATTRIBUTES, CallingConventions.Standard, m.ReturnType, pTypes);
+        var ilGen = mb.GetILGenerator();
+
+
+        ilGen.DeclareLocal(typeof(object[])); //声明object数组变量  
+
+        #region 参数准备
+
+        ilGen.Emit(OpCodes.Ldarg_0); //this
+        ilGen.Emit(OpCodes.Ldstr, m.Name); //string name  
+
+
+        ilGen.Emit(OpCodes.Ldc_I4_S, pTypes.Length); //数组长度
+        ilGen.Emit(OpCodes.Newarr, typeof(object)); //数组
+        ilGen.Emit(OpCodes.Stloc_0);
+
+
+        for (var i = 0; i < pTypes.Length; i++)
+        {
+            ilGen.Emit(OpCodes.Ldloc_0);
+            ilGen.Emit(OpCodes.Ldc_I4_S, i);
+            ilGen.Emit(OpCodes.Ldarg_S, 1 + i);
+            ilGen.Emit(OpCodes.Box, pTypes[i]);
+            ilGen.Emit(OpCodes.Stelem_Ref);
+        }
+
+        #endregion
+
+        ilGen.Emit(OpCodes.Ldloc_0);
+
+        if (m.ReturnType != typeof(void))
+        {
+            var mi = typeof(TBase).GetMethod("CallReturn",
+                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+            mi = mi.MakeGenericMethod(m.ReturnType);
+            ilGen.Emit(OpCodes.Call, mi);
+            var local = ilGen.DeclareLocal(m.ReturnType);
+            ilGen.Emit(OpCodes.Stloc_1, local);
+            ilGen.Emit(OpCodes.Ldloc_1, local);
+        }
+        else
+        {
+            var mi = typeof(TBase).GetMethod("CallVoid",
+                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+            ilGen.Emit(OpCodes.Call, mi);
+        }
+
+        ilGen.Emit(OpCodes.Ret);
+        return mb;
+    }
+
+    private static MethodBuilder GenerateProperty(MethodInfo m)
+    {
+        var pTypes = m.GetParameters().Select(v => v.ParameterType).ToArray();
+        var mb = tb.DefineMethod(m.Name, METHOD_ATTRIBUTES, CallingConventions.Standard, m.ReturnType, pTypes);
+        var ilGen = mb.GetILGenerator();
+
+
+        #region 参数准备
+
+        ilGen.Emit(OpCodes.Ldarg_0); //this
+        ilGen.Emit(OpCodes.Ldstr, m.Name.Replace("set_", "").Replace("get_", "")); //string name   
+
+        #endregion
+
+        if (m.Name.StartsWith("set_"))
+        {
+            ilGen.Emit(OpCodes.Ldarg_1); //value  准备参数 
+            var mi = typeof(TBase).GetMethod("Set",
+                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+            mi = mi.MakeGenericMethod(pTypes);
+            ilGen.Emit(OpCodes.Call, mi); //调用基类方法  
+            //ilGen.Emit(OpCodes.Pop);
+        }
+        else
+        {
+            var mi = typeof(TBase).GetMethod("Get",
+                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+            mi = mi.MakeGenericMethod(m.ReturnType);
+            ilGen.Emit(OpCodes.Call, mi); //调用方法
+
+            #region 返回值
+
+            var local = ilGen.DeclareLocal(m.ReturnType);
+            ilGen.Emit(OpCodes.Stloc_0, local);
+            ilGen.Emit(OpCodes.Ldloc_0, local);
+
+            #endregion
+        }
+
+        ilGen.Emit(OpCodes.Ret); //return
+        return mb;
+    }
+
+    private static MethodBuilder GenerateIndexer(MethodInfo m)
+    {
+        var pTypes = m.GetParameters().Select(v => v.ParameterType).ToArray();
+        var mb = tb.DefineMethod(m.Name, METHOD_ATTRIBUTES, CallingConventions.Standard, m.ReturnType, pTypes);
+        var ilGen = mb.GetILGenerator();
+
+
+        #region 参数准备
+
+        ilGen.DeclareLocal(typeof(object[])); //声明object数组变量  
+
+        ilGen.Emit(OpCodes.Ldarg_0); //this 
+
+        ilGen.Emit(OpCodes.Ldc_I4_S, pTypes.Length); //数组长度
+        ilGen.Emit(OpCodes.Newarr, typeof(object)); //数组
+        ilGen.Emit(OpCodes.Stloc_0);
+
+
+        var i = 0;
+        for (i = 0; i < pTypes.Length; i++)
+        {
+            ilGen.Emit(OpCodes.Ldloc_0);
+            ilGen.Emit(OpCodes.Ldc_I4_S, i);
+            ilGen.Emit(OpCodes.Ldarg_S, 1 + i);
+            ilGen.Emit(OpCodes.Box, pTypes[i]);
+            ilGen.Emit(OpCodes.Stelem_Ref);
+        }
+
+        #endregion
+
+        ilGen.Emit(OpCodes.Ldloc_0);
+
+        if (m.Name.StartsWith("set"))
+        {
+            //ilGen.Emit(OpCodes.Ldarg_S, 1 + i);//value  准备参数 
+            var mi = typeof(TBase).GetMethod("SetItem",
+                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+            ilGen.Emit(OpCodes.Call, mi); //调用基类方法  
+            //ilGen.Emit(OpCodes.Pop);
+        }
+        else
+        {
+            var mi = typeof(TBase).GetMethod("GetItem",
+                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+            mi = mi.MakeGenericMethod(m.ReturnType);
+            ilGen.Emit(OpCodes.Call, mi); //调用方法
+
+            #region 返回值
+
+            var local = ilGen.DeclareLocal(m.ReturnType);
+            ilGen.Emit(OpCodes.Stloc_1, local);
+            ilGen.Emit(OpCodes.Ldloc_1, local);
+
+            #endregion
+        }
+
+        ilGen.Emit(OpCodes.Ret); //return
+        return mb;
+    }
+
+    private static MethodBuilder GenerateEventEnabled(EventInfo evt, MethodInfo m)
+    {
+        var add = m.Name.StartsWith("add_");
+        var n = m.DeclaringType.Name;
+        var pTypes = m.GetParameters().Select(v => v.ParameterType).ToArray();
+        var mb = tb.DefineMethod(m.Name, METHOD_ATTRIBUTES, CallingConventions.Standard, m.ReturnType, pTypes);
+        var ilGen = mb.GetILGenerator();
+
+
+        //ilGen.DeclareLocal(typeof(EventInfo));//声明字符串变量
+        //ilGen.DeclareLocal(typeof(Delegate)); //value Delegate
+
+        //ilGen.Emit(OpCodes.Ldstr, evt.Name);//string evtName 字符串值
+        //ilGen.Emit(OpCodes.Stloc_0);//保存至变量
+        //ilGen.Emit(OpCodes.Ldarg_1);//Delegate dlg
+        //ilGen.Emit(OpCodes.Stloc_1);//保存至变量
+
+
+        #region 参数准备
+
+        ilGen.Emit(OpCodes.Ldarg_0);
+        ilGen.Emit(OpCodes.Ldstr, evt.Name);
+        ilGen.Emit(OpCodes.Ldarg_1);
+
+        #endregion
+
+        var mname = add ? "AddEvent" : "RemoveEvent";
+        ilGen.Emit(OpCodes.Call,
+            typeof(TBase).GetMethod(mname, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
+
+        ilGen.Emit(OpCodes.Ret);
+        return mb;
+    }
+
+
+    private static Type[] GetInterfaces()
+    {
+        var tInterface = typeof(T);
+        var ts = tInterface.GetInterfaces().ToList();
+        ts.Add(tInterface);
+        return ts.ToArray();
+    }
+
+    private static MemberInfo[] GetMembers()
+    {
+        var members = GetInterfaces().SelectMany(v => v.GetMembers()).Distinct().ToArray();
+        return members;
+    }
+
+    public static T Create(params object[] args)
+    {
+        return (T)Activator.CreateInstance(Type, args);
+    }
+
+    public static Type GetGenerateType()
+    {
+        return Type;
+    }
+}

+ 11 - 0
WCS.Core/IL/IProtocolProxy.cs

@@ -0,0 +1,11 @@
+namespace WCS.Core;
+
+public interface IProtocolProxy
+{
+    void Set<T>(string propertyName, T value);
+    T Get<T>(string propertyName);
+    T CallReturn<T>(string methodName, params object[] args);
+    void CallVoid(string methodName, params object[] args);
+    void AddEvent(string eventName, Delegate delgate);
+    void RemoveEvent(string eventName, Delegate delgate);
+}

+ 239 - 0
WCS.Core/Ltc.cs

@@ -0,0 +1,239 @@
+using System.Collections.Concurrent;
+
+namespace WCS.Core;
+
+public static class Ltc
+{
+    private static readonly ConcurrentDictionary<Thread, Channel> Channels = new();
+
+    public static void SetChannel(Channel channel)
+    {
+        Channels[Thread.CurrentThread] = channel;
+        //ClearChannel();
+    }
+
+    public static Channel GetChannel()
+    {
+        if (Channels.ContainsKey(Thread.CurrentThread))
+            return Channels[Thread.CurrentThread];
+        return null;
+    }
+
+    //static ConcurrentDictionary<Channel, List<LogInfo>> Msgs = new ConcurrentDictionary<Channel, List<LogInfo>>();
+
+
+    //static void ClearChannel()
+    //{
+    //    if (Msgs.TryGetValue(GetChannel(), out var list))
+    //        list.Clear();
+    //}
+
+    //public static string GetLogStr()
+    //{
+    //    var channel = GetChannel();
+    //    if (Msgs.TryGetValue(channel, out var list))
+    //    {
+    //        var msg = "-------------------"+channel + "--------------------\n" + string.Join('\n', list.Select(v => $"{string.Join('\n', v)}"));
+    //        return msg;
+    //    }
+    //    else
+    //    {
+    //        return "";
+    //    } 
+    //}
+
+    //public static List<LogInfo> GetLogInfo()
+    //{
+    //    var channel = GetChannel();
+    //    if (Msgs.TryGetValue(channel, out var list))
+    //    {
+    //        return list;
+    //    }
+    //    return new List<LogInfo>();
+    //}
+
+    //public static void Log(string msg,LogLevel level,ErrorType type)
+    //{ 
+    //    var channel = GetChannel();
+
+    //    if (!Msgs.TryGetValue(channel, out var list))
+    //    {
+    //        list = new List<LogInfo>();
+    //        Msgs[channel] = list;
+    //    }
+    //    list.Add(new LogInfo { Message = msg, Level = level, Type = type, Channel = channel });
+    //}
+
+    //public static void Publish(World world)
+    //{ 
+    //    var channel = GetChannel();
+    //    if (Msgs.TryGetValue(channel, out var list))
+    //    {
+    //        var msg = string.Join('\n', list); 
+    //        world.Ex().Publish(channel, msg); 
+    //    } 
+    //} 
+
+    //private static string ResultString<T>(T obj)
+    //{
+    //    if (obj == null)
+    //    {
+    //        return "null";
+    //    }
+    //    else if (obj is bool)
+    //    {
+    //        var b = obj as Boolean?;
+    //        return b.Value ? "成立" : "不成立";
+    //    }
+    //    else if (obj is System.Collections.ICollection)
+    //    {
+    //        var coll = obj as System.Collections.ICollection;
+    //        return coll.Count.ToString() + "元素";
+    //    }
+
+    //    return obj.ToString();
+    //}
+
+    //public static T Do<T>(Expression<Func<T>> exp)
+    //{
+    //    var msg = exp.ExpToString();
+    //    msg += "  结果:";
+    //    try
+    //    {
+    //        var res = exp.Compile().Invoke();
+    //        msg += res;
+    //        return res;
+    //    }
+    //    catch (Exception ex)
+    //    { 
+    //        throw;
+    //    }
+    //    finally
+    //    {
+    //        Log(msg, LogLevel.Low, ErrorType.Kown);
+    //    }
+    //}
+
+    //public static T Do<T1, T>(T1 obj, Expression<Func<T1, T>> exp)
+    //{
+    //    var msg = "";
+    //    try
+    //    {
+    //        try
+    //        {
+    //            msg = exp.ExpToString();
+    //        }
+    //        catch (Exception ex2)
+    //        {
+    //        }
+    //        msg += "  结果:";
+    //        var res = exp.Compile().Invoke(obj);
+    //        msg += ResultString(res);
+    //        return res;
+    //    }
+    //    catch (Exception ex)
+    //    {  
+    //        throw;
+    //    }
+    //    finally
+    //    {
+    //        Log(msg, LogLevel.Low, ErrorType.Kown);
+    //    }
+    //}
+
+    //public static T Do<T1, T2, T>(T1 obj, T2 obj2, Expression<Func<T1, T2, T>> exp)
+    //{
+    //    var msg = exp.ExpToString();
+    //    msg += "  结果:";
+    //    try
+    //    {
+    //        var res = exp.Compile().Invoke(obj, obj2);
+    //        msg += ResultString(res);
+    //        return res;
+    //    }
+    //    catch (Exception ex)
+    //    { 
+    //        throw;
+    //    }
+    //    finally
+    //    {
+    //        Log(msg, LogLevel.Low, ErrorType.Kown);
+    //    }
+    //}
+
+    //public static T Do<T1, T2, T3, T>(T1 obj, T2 obj2, T3 obj3, Expression<Func<T1, T2, T3, T>> exp)
+    //{
+    //    var msg = exp.ExpToString();
+    //    msg += "  结果:";
+    //    try
+    //    {
+    //        var res = exp.Compile().Invoke(obj, obj2, obj3);
+    //        msg += ResultString(res);
+    //        return res;
+    //    }
+    //    catch (Exception ex)
+    //    { 
+    //        throw;
+    //    }
+    //    finally
+    //    {
+    //        Log(msg, LogLevel.Low, ErrorType.Kown);
+    //    }
+    //}
+}
+
+//public class LogInfo
+//{
+//    public ErrorType Type { get; set; }
+//    public LogLevel Level { get; set; }
+//    public Channel Channel { get; set; }
+//    public string Message { get; set; }
+
+//    public override string ToString()
+//    {
+//        //var a = ErrorType.Unkown;
+//        return $"类型:{Type.Description()},级别:{Level.Description()},内容:{Message}";
+//    }
+//}
+
+public class Channel
+{
+    public string Item = "";
+    public string Stage = "";
+    public string System = "";
+    public string World = "";
+
+
+    public override string ToString()
+    {
+        return $"{World}-{Stage}-{System}-{Item}";
+    }
+}
+
+//public enum ErrorType
+//{
+//    [Description("未知")]
+//    Unkown = 0,
+//    [Description("已知")]
+//    Kown = 1
+//}
+
+//public enum LogLevel
+//{
+//    [Description("低")]
+//    Low = 0,
+//    [Description("中")]
+//    Mid = 1,
+//    [Description("高")]
+//    High = 2
+//}
+
+//public class KnownException : Exception
+//{ 
+//    public LogLevel Level { get; set; }
+
+//    public KnownException(string msg, LogLevel level) : base(msg)
+//    {
+//        this.Level = level;
+//    }
+//}

+ 48 - 0
WCS.Core/PLC.cs

@@ -0,0 +1,48 @@
+using System.Net.NetworkInformation;
+
+namespace WCS.Core;
+
+public class PLC : EntityEx<PLCInfo>
+{
+    public PLC(PLCInfo ent, World world) : base(ent)
+    {
+        if (Configs.PLCAccessorCreater != null)
+            Accessor = Configs.PLCAccessorCreater.Create(ent);
+        else
+            throw new Exception("Configs.PLCAccessorCreater未赋值");
+
+        Task.Run(() =>
+        {
+            while (true)
+            {
+                Ping = ping();
+                Task.Delay(1000).Wait();
+            }
+            // ReSharper disable once FunctionNeverReturns
+        });
+    }
+
+    public bool Ping { get; private set; }
+
+    public IPLCAccessor Accessor { get; private set; }
+
+    private bool ping(int timeout = 300)
+    {
+        var p = new Ping();
+        if (Entity.IP == "1") return false;
+        var res = p.Send(Entity.IP, timeout);
+        return res.Status == IPStatus.Success;
+    }
+}
+
+public interface IPLCAccessorCreater
+{
+    IPLCAccessor Create(PLCInfo data);
+}
+
+public interface IPLCAccessor
+{
+    void WriteBytes(ushort db, ushort address, byte[] data);
+
+    byte[] ReadBytes(ushort db, ushort address, ushort length);
+}

+ 55 - 0
WCS.Core/Path.cs

@@ -0,0 +1,55 @@
+namespace WCS.Core;
+
+public class DevicePath
+{
+    private List<string> points = new();
+    public string From { get; set; } = "";
+    public string To { get; set; } = "";
+
+    public List<Device> Points
+    {
+        get
+        {
+            var list = points.Select(v => Device.Find(v)).ToList();
+            return list;
+        }
+    }
+
+    public static DevicePath[] GetPaths(string from, string to)
+    {
+        var all = Device.All.ToDictionary(v => v.Code, v => v.Targets.Select(d => d.Code).ToList());
+
+        var pathList = new List<List<string>>();
+        recursion(from, to, all, pathList, new List<string>());
+        pathList.ForEach(path => path.Remove(from));
+
+        var arr = pathList.Select(v => new DevicePath { From = from, To = to, points = v }).ToArray();
+        return arr;
+    }
+
+    public static DevicePath? GetPath(string from, string to)
+    {
+        return GetPaths(from, to).OrderBy(v => v.Points.Count).FirstOrDefault();
+    }
+
+    private static void recursion(string from, string to, Dictionary<string, List<string>> all,
+        List<List<string>> pathList, List<string> points)
+    {
+        if (points.Count > 100)
+            return;
+        points.Add(from);
+        if (from.Contains(to))
+        {
+            pathList.Add(points);
+            return;
+        }
+
+        if (!all.ContainsKey(from)) return;
+        foreach (var code in all[from])
+        {
+            if (points.Contains(code))
+                continue;
+            recursion(code, to, all, pathList, new List<string>(points));
+        }
+    }
+}

+ 129 - 0
WCS.Core/PlcItem.cs

@@ -0,0 +1,129 @@
+using System.Collections;
+using System.Runtime.InteropServices;
+
+namespace WCS.Core;
+
+public delegate void ValueChangedHandler<T>(PlcItem<T> sender, T value);
+
+public delegate void ValueChangedHandler(PlcItem sender, object value);
+
+public abstract class PlcItem
+{
+    public byte ArrayLength, StringLength;
+    protected DataBlock Db;
+    public ushort DB;
+
+    public string Id;
+    public string IP;
+
+    private object lastValue;
+    public string Name;
+    public int Start; //按位计算 如,第2个字节,Start=8
+
+    public PlcItem(string id, string name, DataBlock db, Type type, int start, byte arrLen = 1, byte strLength = 0)
+    {
+        Id = id;
+        Name = name;
+        Db = db;
+        DataType = type;
+        //this.Db.DBChanged += Db_DBChanged;
+        Start = start;
+        ArrayLength = arrLen;
+        StringLength = strLength;
+
+        DataSize = GetTypeLen(DataType);
+
+        DataSizeOfBits = _getBitLen(DataType);
+    }
+
+    public Type DataType { get; }
+
+    /// <summary>
+    ///     数据类型所占的字节数
+    /// </summary>
+    public int DataSize { get; private set; }
+
+    public int DataSizeOfBits { get; private set; }
+
+    public object Value
+    {
+        get => getValue();
+        set => setValue(value);
+    }
+
+    private int GetTypeLen(Type type)
+    {
+        var bitLen = _getBitLen(type);
+        var mod = bitLen % 8;
+        if (mod > 0)
+            bitLen += 8 - mod;
+        return bitLen / 8;
+    }
+
+    private int _getBitLen(Type type)
+    {
+        if (type.IsArray)
+            return _getBitLen(type.GetElementType()) * ArrayLength;
+        if (type == typeof(bool)) return 1;
+        if (type.IsEnum) return Marshal.SizeOf(type.GetEnumUnderlyingType()) * 8;
+        if (type == typeof(string)) return StringLength * 8;
+        if (typeof(IList).IsAssignableFrom(type)) return 0;
+        return Marshal.SizeOf(type) * 8;
+    }
+
+    protected abstract void setValue(object value);
+
+    protected abstract object getValue();
+
+    private bool ValueEquals(object obj1, object obj2)
+    {
+        if (obj1.GetType().IsArray)
+        {
+            var arr1 = obj1 as Array;
+            if (obj2 is Array arr2)
+                return !arr1.Cast<object>().Where((t, i) => !arr1.GetValue(i).Equals(arr2.GetValue(2))).Any();
+
+            return true;
+        }
+
+        return obj1.Equals(obj2);
+    }
+}
+
+public class PlcItem<T> : PlcItem
+{
+    public PlcItem(string id, string name, DataBlock db, int start, byte arrLen = 1, byte strLen = 0) : base(id, name,
+        db, typeof(T), start, arrLen, strLen)
+    {
+    }
+
+    public new T Value
+    {
+        get => (T)base.Value;
+        set => base.Value = value;
+    }
+
+    protected override void setValue(object value)
+    {
+        var i = 0;
+        while (true)
+            try
+            {
+                Db.Write<T>(Start, (T)value, StringLength, ArrayLength);
+                return;
+            }
+            catch (Exception ex)
+            {
+                if (i >= 3)
+                    throw new Exception(
+                        $"写入出错{ex.Message}--{Start}--{(T)value}--{StringLength}--{ArrayLength}--{Db.Entity.No}");
+                i++;
+                Thread.Sleep(100);
+            }
+    }
+
+    protected override object getValue()
+    {
+        return Db.Read<T>(Start, StringLength, ArrayLength);
+    }
+}

+ 152 - 0
WCS.Core/ProtocolProxyBase.cs

@@ -0,0 +1,152 @@
+using System.ComponentModel.DataAnnotations;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace WCS.Core;
+
+public abstract class ProtocolProxyBase : IProtocolProxy
+{
+    public DateTime ChangedTime;
+
+    private byte[] Data = new byte[0];
+    public DateTime Frame;
+    private readonly string Id = Guid.NewGuid().ToString();
+    public Dictionary<string, PlcItem> Items = new();
+    public Type ProtocolType, ProtocolDataType;
+
+    public ProtocolProxyBase(Device dev, ProtocolInfo info, Type protocolType, World world)
+    {
+        World = world;
+        Frame = world.Frame;
+        ChangedTime = world.Frame;
+        Device = dev;
+        Info = info;
+        Db = info.DBInfo.Ex(world);
+        Db.DbChanged += Db_DbChanged;
+        ProtocolType = protocolType;
+
+        var bitStart = info.Position * 8; //偏移量,按位算
+
+        //this.Start = start;
+        //PlcDB db = new PlcDB(null, 50, 100);
+        var lst = GetType().GetInterfaces().ToList();
+
+        var ps = lst.SelectMany(v => v.GetProperties()).ToArray();
+        foreach (var p in ps)
+        {
+            if (p.GetIndexParameters().Length > 0) continue;
+            if (typeof(ProtocolProxyBase).GetProperty(p.Name,
+                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null) continue;
+
+            #region 计算偏移量
+
+            var modeNum = 0;
+
+            var t = p.PropertyType;
+            if (t.IsEnum)
+                t = t.GetEnumUnderlyingType();
+            if (t == typeof(bool)) //bit类型,db块中无须补齐16位
+                modeNum = 1;
+            else if (t.IsPrimitive && Marshal.SizeOf(t) == 1) //byte char 等单字节类型,db块中无须补齐16位
+                modeNum = 8;
+            else //其他类型,db块中必须补齐16位
+                modeNum = 16;
+            var mod = bitStart % modeNum;
+            if (mod > 0) bitStart += modeNum - mod;
+
+            #endregion 计算偏移量
+
+            byte arrlen = 0;
+            byte strLen = 0;
+            if (t.IsArray)
+            {
+                var attr =
+                    p.GetCustomAttributes(typeof(MaxLengthAttribute), true).FirstOrDefault() as MaxLengthAttribute;
+                if (attr != null)
+                    arrlen = (byte)attr.Length;
+            }
+            else if (p.PropertyType == typeof(string))
+            {
+                var attr =
+                    p.GetCustomAttributes(typeof(StringLengthAttribute), true)
+                        .FirstOrDefault() as StringLengthAttribute;
+                strLen = (byte)attr.MaximumLength;
+            }
+
+            var m = typeof(DataBlock).GetMethod("Regist");
+            m = m.MakeGenericMethod(p.PropertyType);
+            var item = m.Invoke(Db, new object[] { this, Id, p.Name, bitStart, arrlen, strLen }) as PlcItem;
+            Items.Add(p.Name, item);
+            bitStart += item.DataSizeOfBits;
+            BytesCount += item.DataSize;
+        }
+
+        ProtocolDataType = ProtocolType.Assembly.GetTypes().Where(v => v.IsClass || v.IsValueType)
+            .First(v => v.GetInterface(ProtocolType.Name) != null && v != GetType());
+    }
+
+    public ProtocolInfo Info { get; }
+    public int BytesCount { get; }
+    public Device Device { get; }
+    public World World { get; }
+    public DataBlock Db { get; }
+
+    public string Code => Device.Code;
+
+    private void Db_DbChanged(byte[] data)
+    {
+        var pos = Info.Position - Db.Start;
+        var bytes = data.Skip(pos).Take(BytesCount).ToArray();
+        if (Data.SequenceEqual(bytes))
+            return;
+        Frame = World.Frame;
+        ChangedTime = World.Frame;
+        Data = bytes;
+        DataChanged();
+    }
+
+    protected abstract void DataChanged();
+
+    #region
+
+    public void AddEvent(string eventName, Delegate delgate)
+    {
+        throw new NotImplementedException();
+    }
+
+    public void RemoveEvent(string eventName, Delegate delgate)
+    {
+        throw new NotImplementedException();
+    }
+
+    public T CallReturn<T>(string methodName, params object[] args)
+    {
+        throw new NotImplementedException();
+    }
+
+    public void CallVoid(string methodName, params object[] args)
+    {
+        throw new NotImplementedException();
+    }
+
+    public T Get<T>(string propertyName)
+    {
+        var item = Items[propertyName] as PlcItem<T>;
+        var res = item.Value;
+        var channel = Ltc.GetChannel();
+        return res;
+    }
+
+    public void Set<T>(string propertyName, T value)
+    {
+        var item = Items[propertyName] as PlcItem<T>;
+        item.Value = value;
+        var channel = Ltc.GetChannel();
+        if (channel != null)World.OnInternalLog(channel, $"设置值:设置{Device.Code}.{ProtocolType.Name}.{propertyName} 值:{value}");
+        Frame = World.Frame;
+        ChangedTime = World.Frame;
+        DataChanged();
+    }
+
+    #endregion
+}

+ 261 - 0
WCS.Core/System.cs

@@ -0,0 +1,261 @@
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Reflection;
+
+namespace WCS.Core;
+
+public abstract class SystemBase : DescriptionClass
+{
+    public SystemBase()
+    {
+        var attr = GetType().GetCustomAttribute<BelongToAttribute>();
+        if (attr == null) return;
+        var wt = attr.WorldType;
+        World = World.Worlds.First(v => v.GetType() == wt);
+    }
+
+    public World World { get; }
+
+    public abstract List<object> GetObjects();
+
+    public abstract void Update(List<WorkTimes> list);
+}
+
+public abstract class SystemBase<T> : SystemBase
+{
+    public SystemBase()
+    {
+        Objects = Create(); //.Select(v=>Activator.CreateInstance(typeof(T),v)).OfType<T>().ToList();
+    }
+
+    public List<T> Objects { get; set; }
+
+    /// <summary>
+    ///     对所有Objects并行循环执行Do
+    /// </summary>
+    protected abstract bool ParallelDo { get; }
+
+    public override void Update(List<WorkTimes> list)
+    {
+        if (ParallelDo)
+            Parallel.ForEach(Objects, new ParallelOptions { MaxDegreeOfParallelism = 256 }, obj =>
+            {
+                var sw = new Stopwatch();
+                sw.Start();
+                InvokeDo(obj);
+                sw.Stop();
+                list.AddSafe(new WorkTimes { Key = $"{obj?.ToString()}", Total = sw.ElapsedMilliseconds });
+            });
+        else
+            foreach (var obj in Objects)
+            {
+                var sw = new Stopwatch();
+                sw.Start();
+                InvokeDo(obj);
+                sw.Stop();
+                list.AddSafe(new WorkTimes { Key = $"{obj?.ToString()}", Total = sw.ElapsedMilliseconds });
+            }
+    }
+
+    private void InvokeDo(T obj)
+    {
+        var channel = new Channel
+        {
+            World = World.Description,
+            Stage = "DoLogics",
+            System = Description,
+            Item = obj.ToString()
+        };
+        try
+        {
+            if (channel.Item == "1")
+            {
+                var a = 1;
+            }
+
+            Ltc.SetChannel(channel);
+            World.OnInternalLog(channel, "开始");
+            Do(obj);
+        }
+        catch (Exception ex)
+        {
+            World.OnError(channel, ex);
+        }
+        finally
+        {
+            World.OnInternalLog(channel, "结束");
+            World.Publish();
+        }
+    }
+
+    public abstract List<T> Create();
+
+    public abstract void Do(T obj);
+
+    public override List<object> GetObjects()
+    {
+        return Objects.OfType<object>().ToList();
+    }
+}
+
+public abstract class DeviceSystem<T> : SystemBase<T> where T : EntityEx<Device>
+{
+    public override List<T> Create()
+    {
+        var t = typeof(T);
+        while (true)
+        {
+            if (t.IsGenericType) break;
+            t = t.BaseType;
+        }
+
+        if (t == typeof(EntityEx<Device>))
+        {
+            var list = Device.All
+                .Where(Select)
+                .Select(v => Activator.CreateInstance(typeof(T), v)).OfType<T>().ToList(); //此时才实例化Protocol 
+            return list;
+        }
+        else
+        {
+            var types = t.GetGenericArguments();
+            var list = Device.All.Where(v => types.All(v.HasProtocol))
+                .Where(Select)
+                .Select(v => Activator.CreateInstance(typeof(T), v, World)).OfType<T>().ToList(); //此时才实例化Protocol
+            if (list.Count == 0)
+            {
+                //throw new Exception($"{this.GetType().Name}未匹配到任何Device");
+            }
+
+            return list;
+        }
+    }
+
+    /// <summary>
+    ///     筛选出需要实例化Protocol的Device
+    /// </summary>
+    /// <param name="dev"></param>
+    /// <returns></returns>
+    public abstract bool Select(Device dev);
+
+    //public abstract List<Device> CreateDevices();
+}
+
+public abstract class ServiceSystem<T, TR> : SystemBase
+{
+    private readonly ConcurrentQueue<Action<List<WorkTimes>>> Actions = new();
+
+    public TR Invoke(T obj)
+    {
+        var flag = false;
+        var result = default(TR);
+        Actions.Enqueue(list =>
+        {
+            var sw = new Stopwatch();
+            sw.Start();
+            try
+            {
+                result = InvokeDo(obj);
+            }
+            finally
+            {
+                sw.Stop();
+                list.AddSafe(new WorkTimes { Key = $"{obj?.ToString()}", Total = sw.ElapsedMilliseconds });
+                flag = true;
+            }
+        });
+        SpinWait.SpinUntil(() => flag);
+        return result;
+    }
+
+    protected abstract TR Do(T obj);
+
+    public override List<object> GetObjects()
+    {
+        return new List<object>();
+    }
+
+    public override void Update(List<WorkTimes> list)
+    {
+        while (Actions.TryDequeue(out var act)) act(list);
+    }
+
+    private TR InvokeDo(T obj)
+    {
+        var channel = new Channel
+        {
+            World = World.Description,
+            Stage = "DoLogics",
+            System = Description,
+            Item = obj.ToString()
+        };
+        try
+        {
+            Ltc.SetChannel(channel);
+            World.OnInternalLog(channel, "开始");
+            return Do(obj);
+        }
+        finally
+        {
+            World.OnInternalLog(channel, "结束");
+            World.Publish();
+        }
+    }
+}
+
+public abstract class ServiceSystem<T> : SystemBase
+{
+    private readonly ConcurrentQueue<Action<List<WorkTimes>>> Actions = new();
+
+    public void Invoke(T obj)
+    {
+        Actions.Enqueue(list =>
+        {
+            var sw = new Stopwatch();
+            sw.Start();
+            try
+            {
+                InvokeDo(obj);
+            }
+            finally
+            {
+                sw.Stop();
+                list.AddSafe(new WorkTimes { Key = $"{obj?.ToString()}", Total = sw.ElapsedMilliseconds });
+            }
+        });
+    }
+
+    protected abstract void Do(T obj);
+
+    private void InvokeDo(T obj)
+    {
+        var channel = new Channel
+        {
+            World = World.Description,
+            Stage = "DoLogics",
+            System = Description,
+            Item = obj.ToString()
+        };
+        try
+        {
+            Ltc.SetChannel(channel);
+            World.OnInternalLog(channel, "开始");
+            Do(obj);
+        }
+        finally
+        {
+            World.OnInternalLog(channel, "结束");
+            World.Publish();
+        }
+    }
+
+    public override List<object> GetObjects()
+    {
+        return new List<object>();
+    }
+
+    public override void Update(List<WorkTimes> list)
+    {
+        while (Actions.TryDequeue(out var act)) act(list);
+    }
+}

+ 32 - 0
WCS.Core/VitrualRedisPLC.cs

@@ -0,0 +1,32 @@
+using FreeRedis;
+
+namespace WCS.Core;
+
+public class VitrualRedisPLC : IPLCAccessor
+{
+    private readonly int dbLen = 50000;
+    private readonly PLCInfo plcInfo;
+    private readonly RedisClient redis;
+
+    public VitrualRedisPLC(PLCInfo plcInfo, string redisConnStr)
+    {
+        this.plcInfo = plcInfo;
+        redis = new RedisClient(redisConnStr);
+    }
+
+    public byte[] ReadBytes(ushort db, ushort address, ushort length)
+    {
+        var key = $"{plcInfo.IP}-{db}";
+        if (!redis.Exists(key)) redis.SetRange(key, 0, new byte[dbLen]);
+
+        var res = redis.GetRange<byte[]>(key, address, address + length - 1);
+        return res;
+    }
+
+    public void WriteBytes(ushort db, ushort address, byte[] data)
+    {
+        var key = $"{plcInfo.IP}-{db}";
+        if (!redis.Exists(key)) redis.SetRange(key, 0, new byte[dbLen]);
+        redis.SetRange(key, address, data);
+    }
+}

+ 13 - 0
WCS.Core/WCS - Backup.Core.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="FreeRedis" Version="1.1.1" />
+  </ItemGroup>
+
+</Project>

+ 21 - 0
WCS.Core/WCS.Core.csproj

@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <NoWarn>1701;1702;8602;8616;8618;8625;8600;8603;8714;1591</NoWarn>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <NoWarn>1701;1702;8602;8616;8618;8625;8600;8603;8714;1591</NoWarn>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="FreeRedis" Version="1.2.13" />
+  </ItemGroup>
+
+</Project>

+ 484 - 0
WCS.Core/World.cs

@@ -0,0 +1,484 @@
+using System.Collections.Concurrent;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+using FreeRedis;
+
+namespace WCS.Core;
+
+/// <summary>
+///     世界用来管理下属System的执行周期,此为默认世界。也可以通过继承此类创建多个不同世界,不同世界的执行周期相互独立,不受其它世界延迟干扰。
+/// </summary>
+[Description("默认世界")]
+public abstract class World : DescriptionClass
+{
+    public SystemBase[] Systems
+    {
+        get { return SystemGroups.SelectMany(v => v.Value).ToArray(); }
+    }
+
+    [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
+    public static extern uint MM_BeginPeriod(uint uMilliseconds);
+
+    [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
+    public static extern uint MM_EndPeriod(uint uMilliseconds);
+
+    public T GetSystem<T>() where T : SystemBase
+    {
+        var sys = Systems.FirstOrDefault(v => v.GetType() == typeof(T)) as T;
+        if (sys == null) throw new Exception($"世界{GetType().Name}中不存在系统{typeof(T).Name}");
+        return sys;
+    }
+    static ConcurrentDictionary<World, WorkTimes> WorldsInfo = new ConcurrentDictionary<World, WorkTimes>();
+    protected virtual void FrameInfo(WorkTimes wt)
+    {
+        WorldsInfo[this] = wt;
+        //if (wt.Total > Interval) Console.ForegroundColor = ConsoleColor.Red;
+        //Console.WriteLine(wt.GetInfo());
+        //Console.ResetColor();
+    }
+    static string LastInfo = "";
+    static void Print()
+    {
+        var info = "\n" + string.Join("\n", WorldsInfo.Values.Select(v => v.GetInfo().PadRight(200))).PadRight(1000);
+
+        if (info != LastInfo)
+        {
+            Console.CursorVisible = false;
+            Console.WriteLine(info);
+            Console.SetCursorPosition(0, 0);
+            LastInfo = info;
+        }
+
+    }
+    public void Log<T>(T log) where T : ILog
+    {
+        var channel = Ltc.GetChannel();
+        if (channel != null)
+            OnLog(Ltc.GetChannel(), log);
+    }
+
+    protected internal abstract void OnError(Channel channel, Exception exception);
+
+    protected internal abstract void OnInternalLog(Channel channel, string msg);
+
+    protected abstract void OnLog(Channel channel, object logObj);
+
+    protected abstract IEnumerable<string> GetChannelMsg(Channel channel);
+
+    internal void Publish()
+    {
+        var channel = Ltc.GetChannel();
+        if (channel != null)
+        {
+            var msgs = GetChannelMsg(channel);
+            var msg = string.Join("\n", msgs);
+            this.Ex().Publish(channel, msg);
+        }
+    }
+
+    #region Static
+
+    /// <summary>
+    ///     世界是否启用成功
+    /// </summary>
+    public static bool IsStart;
+
+    private static List<World> _Worlds;
+
+    internal static List<World> Worlds
+    {
+        get
+        {
+            if (_Worlds == null)
+            {
+                _Worlds = new List<World>();
+                //_Worlds.Add(new World());//默认世界
+                var arr = AppDomain.CurrentDomain.GetAssemblies().Select(v => v.GetTypes()).SelectMany(v => v)
+                    .Where(v => typeof(World).IsAssignableFrom(v) && v != typeof(World))
+                    .Select(v => Activator.CreateInstance(v)).OfType<World>().ToArray(); //自定义世界
+                _Worlds.AddRange(arr);
+            }
+
+            return _Worlds;
+        }
+    }
+
+    public static T GetWorldInstance<T>() where T : World
+    {
+        return (T)Worlds.First(v => v.GetType() == typeof(T));
+    }
+
+    public static T GetSystemInstance<T>() where T : SystemBase
+    {
+        try
+        {
+            return (T)Worlds.SelectMany(v => v.Systems).First(v => v.GetType() == typeof(T));
+        }
+        catch (Exception ex)
+        {
+            throw new Exception($"系统:{typeof(T).Name}未设置BelongToAttribute");
+        }
+    }
+
+    public static void StartAll()
+    {
+        MM_BeginPeriod(1);
+        Worlds.ForEach(w => w.Init());
+        foreach (var w in Worlds)
+        {
+            if (w.SystemTypes.Length == 0) continue;
+            w.Start();
+        }
+
+        var arr = Worlds.SelectMany(v => v.SystemGroups).SelectMany(v => v.Value)
+            .SelectMany(v => v.GetObjects().OfType<EntityEx<Device>>().Select(d => new { Sys = v, Obj = d }))
+            .GroupBy(v => v.Obj.Entity.Code)
+            .Select(v => new
+            {
+                Code = v.Key, Systems = v.Select(d => d.Sys.GetType().Name).ToArray(),
+                Worlds = v.Select(d => d.Sys.World.GetType().Name).Distinct().ToArray()
+            })
+            .Where(v => v.Systems.Length > 1).ToArray();
+        if (arr.Length > 0)
+        {
+            var msgs = arr.Select(v =>
+                    $"设备{v.Code}同时存在于{v.Systems.Length}个系统:({string.Join(',', v.Systems)}),{v.Worlds.Length}个世界:({string.Join(',', v.Worlds)})中")
+                .ToArray();
+            var str = string.Join('\n', msgs);
+            Console.ForegroundColor = ConsoleColor.DarkYellow;
+            Console.WriteLine(str);
+            Console.ResetColor();
+        }
+
+        IsStart = true;
+
+        Task.Run(() =>
+        {
+
+            var i = 0;
+            while (IsStart)
+            {
+                i++;
+                if (i % 30 == 1)
+                {
+                    Console.Clear();
+                }
+                Print();
+                Task.Delay(100).Wait();
+            }
+        });
+    }
+
+    public static void StopAll()
+    {
+        MM_EndPeriod(1);
+        foreach (var w in Worlds) w.Stop();
+    }
+
+    #endregion Static
+
+    #region Dynamic
+
+    public bool Stoped;
+    private readonly Dictionary<Type, int> TypeOrder = new();
+    private readonly Dictionary<int, List<SystemBase>> SystemGroups = new();
+    protected Type[] SystemTypes;
+
+    /// <summary>
+    ///     周期最小间隔时间(毫秒)
+    /// </summary>
+    protected abstract int Interval { get; }
+
+    public DateTime Frame { get; private set; }
+
+    public World()
+    {
+        SystemTypes = GetSystemTypes();
+    }
+
+    protected virtual Type[] GetSystemTypes()
+    {
+        var sysTypes = AppDomain.CurrentDomain.GetAssemblies().Select(v => v.GetTypes()).SelectMany(v => v)
+            .Where(v => !v.IsAbstract)
+            .Where(v => typeof(SystemBase).IsAssignableFrom(v))
+            .Where(v =>
+            {
+                var attr = v.GetCustomAttribute<BelongToAttribute>();
+                if (attr == null) return GetType() == typeof(World);
+                if (attr.WorldType == GetType()) return true;
+                return false;
+            }).ToArray();
+        return sysTypes;
+    }
+
+    //public List<Device> Devices;
+
+    /// <summary>
+    ///     初始化,实例化世界下的所有系统
+    /// </summary>
+    public virtual void Init()
+    {
+        //Devices = Protocols.Generate(this);
+        foreach (var type in SystemTypes)
+        {
+            var sysDesc = type.GetCustomAttribute<DescriptionAttribute>()?.Description;
+            Ltc.SetChannel(new Channel
+                { World = Description, Stage = "Init", System = sysDesc ?? type.Name, Item = "排序" });
+            Set(type, 0);
+        }
+
+        var arr = TypeOrder.OrderBy(v => v.Value).Select(v => v.Key).ToArray();
+        var gs = TypeOrder.GroupBy(v => v.Value).OrderBy(v => v.Key).ToArray();
+        for (var i = 0; i < gs.Length; i++)
+        {
+            var g = gs[i];
+            var list = new List<SystemBase>();
+            var sysArr = g.Select(v =>
+            {
+                var sysDesc = v.Key.GetCustomAttribute<DescriptionAttribute>()?.Description;
+                Ltc.SetChannel(new Channel
+                    { World = Description, Stage = "Init", System = sysDesc ?? v.Key.Name, Item = "构造" });
+                return Activator.CreateInstance(v.Key);
+            }).OfType<SystemBase>().ToArray();
+            list.AddRange(sysArr);
+            SystemGroups.Add(i, list);
+        }
+    }
+
+    private int Set(Type type, int level)
+    {
+        if (!SystemTypes.Contains(type))
+            throw new Exception($"OrderAttribute设置错误,与目标不属于同一世界。类型:{type}。");
+        if (level > 10) throw new Exception($"OrderAttribute设置错误,导致死循环。类型:{type}。");
+        var attr = type.GetCustomAttribute<OrderAttribute>();
+        if (attr != null)
+        {
+            level++;
+            var num = Set(attr.SystemType, level);
+            TypeOrder[type] = num + (int)attr.Order;
+        }
+        else
+        {
+            TypeOrder[type] = 0;
+        }
+
+        return TypeOrder[type];
+    }
+
+    /// <summary>
+    ///     开启世界主循环
+    /// </summary>
+    public void Start()
+    {
+        Stoped = false;
+        Task.Run(Loop); //不要使用Thread,可以使用ThreadPool
+    }
+
+    private void Loop()
+    {
+        var sw = new Stopwatch();
+        while (!Stoped)
+        {
+            Frame = DateTime.Now;
+            var wt = new WorkTimes();
+            wt.Key = $"{Description} 周期:{Interval}";
+            sw.Restart();
+            try
+            {
+                BeforeUpdate(wt.Items);
+                Update(wt.Items);
+                AfterUpdate(wt.Items);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex);
+            }
+
+            sw.Stop();
+            var workTimes = (int)sw.ElapsedMilliseconds;
+            var ms = Interval - workTimes;
+            //sw.Start();
+            if (ms > 0) Thread.Sleep(ms); //不要使用Task.Delay().Wait()
+            //sw.Stop();
+            //var total = sw.ElapsedMilliseconds;
+            wt.Total = workTimes;
+            FrameInfo(wt);
+        }
+    }
+
+    public void Stop()
+    {
+        Stoped = true;
+    }
+
+    private void Update(List<WorkTimes> list)
+    {
+        var wt = new WorkTimes();
+        wt.Key = "读取PLC数据";
+        var sw = new Stopwatch();
+        sw.Start();
+        LoadPlcData(wt.Items);
+        sw.Stop();
+        wt.Total = sw.ElapsedMilliseconds;
+        list.AddSafe(wt);
+
+        wt = new WorkTimes();
+        wt.Key = "系统业务";
+        sw.Restart();
+        DoLogics(wt.Items);
+        sw.Stop();
+        wt.Total = sw.ElapsedMilliseconds;
+        list.AddSafe(wt);
+    }
+
+    private void LoadPlcData(List<WorkTimes> list)
+    {
+        Parallel.ForEach(this.GetDataBlocks(), db =>
+        {
+            var channel = new Channel
+            {
+                World = GetType().Name,
+                Stage = "LoadPlcData",
+                System = "",
+                Item = $"{db.Entity.PLCInfo.IP}_{db.Entity.No}"
+            };
+            var sw = new Stopwatch();
+            sw.Start();
+            try
+            {
+                db.RefreshData();
+            }
+            catch (Exception ex)
+            {
+                this.Ex().Publish(channel, ex.GetBaseException().Message);
+            }
+
+            sw.Stop();
+            list.AddSafe(new WorkTimes
+                { Key = $"{db.Entity.PLCInfo.IP}/{db.Entity.No}", Total = sw.ElapsedMilliseconds });
+        });
+    }
+
+    private void DoLogics(List<WorkTimes> list)
+    {
+        foreach (var group in SystemGroups)
+        {
+            var wt = new WorkTimes();
+            wt.Key = $"组{group.Key}";
+            var sw = new Stopwatch();
+            sw.Restart();
+            Parallel.ForEach(group.Value, sys =>
+            {
+                var wt2 = new WorkTimes();
+                wt2.Key = sys.Description;
+                var sw2 = new Stopwatch();
+                sw2.Start();
+                try
+                {
+                    sys.Update(wt2.Items);
+                }
+                catch (Exception ex)
+                {
+                    Console.ForegroundColor = ConsoleColor.Red;
+                    Console.WriteLine(ex.GetBaseException().Message);
+                    Console.ResetColor();
+                }
+
+                sw2.Stop();
+                wt2.Total = sw2.ElapsedMilliseconds;
+                list.AddSafe(wt2);
+            });
+            sw.Stop();
+            wt.Total = sw.ElapsedMilliseconds;
+            //list.AddSafe(wt);
+        }
+    }
+
+    protected virtual void BeforeUpdate(List<WorkTimes> list)
+    {
+    }
+
+    protected virtual void AfterUpdate(List<WorkTimes> list)
+    {
+    }
+
+    #endregion Dynamic
+}
+
+public interface ILog
+{
+}
+
+public class WorldEx : EntityEx<World>
+{
+    private readonly ConcurrentQueue<string> ChannelList = new();
+    private readonly RedisClient Redis = new(Configs.DebugRedisUrl);
+
+    private DateTime SubTime = DateTime.Now;
+
+    public WorldEx(World ent) : base(ent)
+    {
+        return;
+        Redis.Subscribe("Login", (channel, msg) =>
+        {
+            ChannelList.Clear();
+            foreach (var m in msg.ToString().Split(',')) ChannelList.Enqueue(m);
+
+            SubTime = DateTime.Now;
+            Console.WriteLine($"调试工具正在使用中,已订阅:{msg}");
+        });
+    }
+
+    public void Publish(Channel channel, string msg)
+    {
+        if ((DateTime.Now - SubTime).TotalSeconds > 20)
+            return;
+        var flag = false;
+
+        flag = ChannelList.Any(v =>
+        {
+            var b = Regex.Match(channel.ToString(), $"^{v.Replace("*", ".*")}$");
+            return b.Success;
+        });
+
+        if (flag)
+            Redis.Publish(channel.ToString(), msg);
+    }
+}
+
+public class WorkTimes
+{
+    public string Key { get; set; } = "";
+    public long Total { get; set; }
+    public List<WorkTimes> Items { get; set; } = new();
+
+    public override string ToString()
+    {
+        return $"{Key},明细:{Items.Count},耗时:{Total}";
+    }
+
+    public string GetInfo()
+    {
+        var str = $"[{ToString()}]";
+        if (Items.Count > 0) str += $" > {Items.MaxBy(v => v.Total)?.GetInfo()}";
+        return str;
+    }
+}
+
+public abstract class AttrClass<T> where T : Attribute
+{
+    public AttrClass()
+    {
+        Attr = GetType().GetCustomAttribute<T>();
+    }
+
+    public T? Attr { get; }
+}
+
+public abstract class DescriptionClass : AttrClass<DescriptionAttribute>
+{
+    public string Description => Attr != null ? Attr.Description : GetType().Name;
+}

+ 41 - 1
WCS.Entity.Protocol/HUB/DevRunInfo.cs

@@ -72,6 +72,36 @@ namespace WCS.Entity.Protocol.HUB
         /// </summary>
         [SugarColumn(ColumnDataType = "symbol")]
         public string Type { get; set; }
+
+        /// <summary>
+        ///  获取起始地址
+        /// </summary>
+        /// <param name="workstation"></param>
+        /// <returns></returns>
+        public string GetStartAddress(int workstation)
+        {
+            return workstation switch
+            {
+                1 => ExtraField1.Split("|")[0],
+                2 => ExtraField2.Split("|")[0],
+                _ => throw new Exception("请选择工位")
+            };
+        }
+
+        /// <summary>
+        ///  获取目标地址
+        /// </summary>
+        /// <param name="workstation"></param>
+        /// <returns></returns>
+        public string GetTargetAddress(int workstation)
+        {
+            return workstation switch
+            {
+                1 => ExtraField1.Split("|")[1],
+                2 => ExtraField2.Split("|")[1],
+                _ => throw new Exception("请选择工位")
+            };
+        }
     }
 
     public enum DevType
@@ -119,6 +149,16 @@ namespace WCS.Entity.Protocol.HUB
         /// <summary>
         ///     来料统计
         /// </summary>
-        FeedRate = 9
+        FeedRate = 9,
+
+        /// <summary>
+        ///  状态统计
+        /// </summary>
+        RunStatus = 10,
+
+        /// <summary>
+        ///  侧推任务统计
+        /// </summary>
+        线体状态统计 = 11
     }
 }

+ 6 - 0
WCS.Entity.Protocol/QuestDBBaseEntity.cs

@@ -16,5 +16,11 @@ namespace WCS.Entity.Protocol
         [DataMember(Order = 1)]
         [SugarColumn(ColumnDataType = "symbol")]
         public string Code { get; set; }
+
+        public string ExtraField1 { get; set; }
+
+        public string ExtraField2 { get; set; }
+
+        public string ExtraField3 { get; set; }
     }
 }

+ 1 - 0
WCS.Entity.Protocol/Robot/IRobot521.cs

@@ -1,4 +1,5 @@
 using System.ComponentModel;
+using SqlSugar;
 using WCS.Entity.Protocol.Protocol.Robot;
 
 namespace WCS.Entity.Protocol.Robot

+ 2 - 2
WCS.Entity.Protocol/Station/StationEnum.cs

@@ -390,9 +390,9 @@ namespace WCS.Entity.Protocol.Station
     public enum LineStatusEnum : short
     {
         /// <summary>
-        ///     空闲
+        ///     自动
         /// </summary>
-        [Description("空闲")] Idle = 1,
+        [Description("自动")] Automatic= 1,
 
         /// <summary>
         ///     报警

+ 10 - 1
WCS.Entity.Protocol/Station/WCS_Station222.cs

@@ -1,4 +1,5 @@
-using System.Runtime.Serialization;
+using System;
+using System.Runtime.Serialization;
 using SqlSugar;
 
 namespace WCS.Entity.Protocol.Station
@@ -13,5 +14,13 @@ namespace WCS.Entity.Protocol.Station
         [DataMember(Order = 0)] public LineStatusEnum LineStatus { get; set; }
 
         [DataMember(Order = 1)] public FwdOrRevEnum FwdOrRev { get; set; }
+
+        [DataMember(Order = 2)]
+        [TimeDbSplitField(DateType.Day)]
+        public DateTime Frame { get; set; }
+
+        [DataMember(Order = 3)]
+        [SugarColumn(ColumnDataType = "symbol")]
+        public string Code { get; set; }
     }
 }

+ 1 - 1
WCS.WorkEngineering/WCS.WorkEngineering.csproj

@@ -22,7 +22,7 @@
     <PackageReference Include="Rezero.Api" Version="1.7.12" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\ZTGT-FJZX\ServiceCenter\ServiceCenter.csproj" />
+    <ProjectReference Include="..\ServiceCenter\ServiceCenter.csproj" />
     <ProjectReference Include="..\WCS.Entity.Protocol\WCS.Entity.Protocol.csproj" />
   </ItemGroup>
   <ItemGroup>

+ 34 - 3
WCS.WorkEngineering/WorkStart.cs

@@ -120,7 +120,33 @@ public static class WorkStart
                 new(401, 599),
                 new(801, 999),
                 new(1201, 1399)
-            }),
+            })
+        };
+
+        foreach (var item in mLtuples523)
+        {
+            var db523 = 0;
+            var db524 = 0;
+            var db222 = 0;
+            foreach (var item1 in item.Item2)
+            {
+                for (var i = item1.Item1; i <= item1.Item2; i++)
+                {
+                   var conv = new Device(i.ToString());
+                    conv.AddFlag(DeviceFlags.输送机);
+                   
+                    conv.AddProtocol<IStation523>(db523, 523, item.Item1);
+                    conv.AddProtocol<IStation524>(db524, 524, item.Item1);
+                    conv.AddProtocol<IStation222>(db222, 7, item.Item1);
+                    db523 += 12;
+                    db524 += 16;
+                    db222 += 4;
+                }
+            }
+        }
+
+        var mLtuples5231 = new List<Tuple<string, List<Tuple<int, int>>>>
+        {
             new("10.30.37.198", new List<Tuple<int, int>>() //南侧满轮主线
             {
                 new(101, 210),
@@ -131,21 +157,26 @@ public static class WorkStart
             })
         };
 
-        foreach (var item in mLtuples523)
+        foreach (var item in mLtuples5231)
         {
             var db523 = 0;
             var db524 = 0;
+            var db222 = 0;
             foreach (var item1 in item.Item2)
+            {
                 for (var i = item1.Item1; i <= item1.Item2; i++)
                 {
                     var conv = new Device(i.ToString());
                     conv.AddFlag(DeviceFlags.输送机);
+                   
                     conv.AddProtocol<IStation523>(db523, 523, item.Item1);
                     conv.AddProtocol<IStation524>(db524, 524, item.Item1);
-
+                    conv.AddProtocol<IStation222>(db222, 7, item.Item1);
                     db523 += 12;
                     db524 += 16;
+                    db222 += 4;
                 }
+            }
         }
 
         #endregion DB523,所有线体都会有DB523