mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-03 00:00:49 +08:00
重写了Web Api的逻辑,用Emit构造委托加速API处理
This commit is contained in:
@@ -500,7 +500,7 @@ namespace Serein.Library.Api
|
||||
/// 清理加载的DLL(待更改)
|
||||
/// </summary>
|
||||
void ClearAll();
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开始运行
|
||||
@@ -549,6 +549,11 @@ namespace Serein.Library.Api
|
||||
/// <param name="nodeGuid">待移除的节点Guid</param>
|
||||
void RemoteNode(string nodeGuid);
|
||||
|
||||
// 启动触发器
|
||||
void ActivateFlipflopNode(string nodeGuid);
|
||||
void TerminateFlipflopNode(string nodeGuid);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置节点中断级别
|
||||
/// </summary>
|
||||
@@ -612,7 +617,6 @@ namespace Serein.Library.Api
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region UI视觉
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,6 +8,9 @@ using static Serein.Library.Utils.EmitHelper;
|
||||
|
||||
namespace Serein.Library.Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 委托描述
|
||||
/// </summary>
|
||||
public class DelegateDetails
|
||||
{
|
||||
public DelegateDetails(EmitMethodType EmitMethodType, Delegate EmitDelegate)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Serein.Library.Enums
|
||||
Exit,
|
||||
|
||||
/// <summary>
|
||||
/// 触发器
|
||||
/// 触发器(建议手动设置返回类型,方便视图直观)
|
||||
/// </summary>
|
||||
Flipflop,
|
||||
/// <summary>
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Serein.Library.Web
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class WebApiAttribute : Attribute
|
||||
{
|
||||
public API Http; // HTTP 请求类型
|
||||
public ApiType ApiType; // HTTP 请求类型
|
||||
public string Url; // URL 路径
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
@@ -68,15 +68,15 @@ namespace Serein.Library.Web
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="url"></param>
|
||||
public WebApiAttribute(API http = API.POST, bool isUrl = true, string url = "")
|
||||
public WebApiAttribute(ApiType http = ApiType.POST, bool isUrl = true, string url = "")
|
||||
{
|
||||
Http = http;
|
||||
ApiType = http;
|
||||
Url = url;
|
||||
IsUrl = isUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public enum API
|
||||
public enum ApiType
|
||||
{
|
||||
POST,
|
||||
GET,
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Attributes;
|
||||
using Serein.Library.Entity;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Security.AccessControl;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
@@ -20,10 +24,199 @@ namespace Serein.Library.Web
|
||||
{
|
||||
public interface IRouter
|
||||
{
|
||||
bool RegisterController(Type controllerType);
|
||||
void AddHandle(Type controllerType);
|
||||
Task<bool> ProcessingAsync(HttpListenerContext context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class ApiHandleConfig
|
||||
{
|
||||
private readonly Delegate EmitDelegate;
|
||||
private readonly EmitHelper.EmitMethodType EmitMethodType;
|
||||
|
||||
public enum PostArgType
|
||||
{
|
||||
None,
|
||||
IsUrlData,
|
||||
IsBobyData,
|
||||
}
|
||||
public ApiHandleConfig(MethodInfo methodInfo)
|
||||
{
|
||||
EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out EmitDelegate);
|
||||
var parameterInfos = methodInfo.GetParameters();
|
||||
ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray();
|
||||
ParameterName = parameterInfos.Select(t => t.Name.ToLower()).ToArray();
|
||||
|
||||
PostArgTypes = parameterInfos.Select(p =>
|
||||
{
|
||||
bool isUrlData = p.GetCustomAttribute(typeof(UrlAttribute)) != null;
|
||||
bool isBobyData = p.GetCustomAttribute(typeof(BobyAttribute)) != null;
|
||||
if (isBobyData)
|
||||
{
|
||||
return PostArgType.IsBobyData;
|
||||
}
|
||||
else if (isUrlData)
|
||||
{
|
||||
return PostArgType.IsUrlData;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PostArgType.None;
|
||||
}
|
||||
}).ToArray();
|
||||
|
||||
|
||||
|
||||
}
|
||||
private readonly PostArgType[] PostArgTypes;
|
||||
private readonly string[] ParameterName;
|
||||
private readonly Type[] ParameterType;
|
||||
|
||||
|
||||
public async Task<object> HandleGet(object instance, Dictionary<string, string> routeData)
|
||||
{
|
||||
object[] args = new object[ParameterType.Length];
|
||||
for (int i = 0; i < ParameterType.Length; i++)
|
||||
{
|
||||
var type = ParameterType[i];
|
||||
var argName = ParameterName[i];
|
||||
if (routeData.TryGetValue(argName, out var argValue))
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
args[i] = argValue;
|
||||
}
|
||||
else // if (type.IsValueType)
|
||||
{
|
||||
args[i] = JsonConvert.DeserializeObject(argValue, type);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object result;
|
||||
try
|
||||
{
|
||||
if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
{
|
||||
result = await hasResultTask(instance, args);
|
||||
}
|
||||
else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
|
||||
{
|
||||
await task.Invoke(instance, args);
|
||||
result = null;
|
||||
}
|
||||
else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
|
||||
{
|
||||
result = func.Invoke(instance, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = null;
|
||||
await Console.Out.WriteLineAsync(ex.Message);
|
||||
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<object> HandlePost(object instance, JObject jsonObject, Dictionary<string, string> routeData)
|
||||
{
|
||||
object[] args = new object[ParameterType.Length];
|
||||
for (int i = 0; i < ParameterType.Length; i++)
|
||||
{
|
||||
var type = ParameterType[i];
|
||||
var argName = ParameterName[i];
|
||||
if (PostArgTypes[i] == PostArgType.IsUrlData)
|
||||
{
|
||||
if (routeData.TryGetValue(argName, out var argValue))
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
args[i] = argValue;
|
||||
}
|
||||
else // if (type.IsValueType)
|
||||
{
|
||||
args[i] = JsonConvert.DeserializeObject(argValue, type);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
}
|
||||
else if (PostArgTypes[i] == PostArgType.IsBobyData)
|
||||
{
|
||||
args[i] = jsonObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
var jsonValue = jsonObject.GetValue(argName);
|
||||
if (jsonValue is null)
|
||||
{
|
||||
// 值类型返回默认值,引用类型返回null
|
||||
args[i] = type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
args[i] = jsonValue.ToObject(type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
object result;
|
||||
try
|
||||
{
|
||||
if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
{
|
||||
result = await hasResultTask(instance, args);
|
||||
}
|
||||
else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
|
||||
{
|
||||
await task.Invoke(instance, args);
|
||||
result = null;
|
||||
}
|
||||
else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
|
||||
{
|
||||
result = func.Invoke(instance, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = null;
|
||||
await Console.Out.WriteLineAsync(ex.Message);
|
||||
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 路由注册与解析
|
||||
/// </summary>
|
||||
@@ -35,30 +228,30 @@ namespace Serein.Library.Web
|
||||
/// <summary>
|
||||
/// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType]
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Type> _controllerTypes; // 存储控制器类型
|
||||
private readonly ConcurrentDictionary<string, Type> _controllerTypes = new ConcurrentDictionary<string, Type>();
|
||||
|
||||
/// <summary>
|
||||
/// 用于存储路由信息,[GET|POST - [Url - Method]]
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>> _routes;
|
||||
//private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>> _routes = new ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>>();
|
||||
|
||||
|
||||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, ApiHandleConfig>> HandleModels = new ConcurrentDictionary<string, ConcurrentDictionary<string, ApiHandleConfig>>();
|
||||
|
||||
|
||||
// private readonly ILoggerService loggerService; // 用于存储路由信息
|
||||
|
||||
//private Type PostRequest;
|
||||
|
||||
public Router(ISereinIOC SereinIOC)
|
||||
{
|
||||
this.SereinIOC = SereinIOC;
|
||||
_routes = new ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>>(); // 初始化路由字典
|
||||
_controllerTypes = new ConcurrentDictionary<string, Type>(); // 初始化控制器实例对象字典
|
||||
foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值
|
||||
foreach (ApiType method in Enum.GetValues(typeof(ApiType))) // 遍历 HTTP 枚举类型的所有值
|
||||
{
|
||||
_routes.TryAdd(method.ToString(), new ConcurrentDictionary<string, MethodInfo>()); // 初始化每种 HTTP 方法对应的路由字典
|
||||
HandleModels.TryAdd(method.ToString(), new ConcurrentDictionary<string, ApiHandleConfig>()); // 初始化每种 HTTP 方法对应的路由字典
|
||||
}
|
||||
#if false
|
||||
Type baseAttribute = typeof(AutoHostingAttribute);
|
||||
Type baseController = typeof(ControllerBase);
|
||||
|
||||
|
||||
// 获取当前程序集
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
// 获取包含“Controller”名称的类型
|
||||
@@ -69,9 +262,44 @@ namespace Serein.Library.Web
|
||||
foreach (var controllerType in controllerTypes)
|
||||
{
|
||||
RegisterController(controllerType);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void AddHandle(Type controllerType)
|
||||
{
|
||||
if (!controllerType.IsClass || controllerType.IsAbstract) return; // 如果不是类或者是抽象类,则直接返回
|
||||
|
||||
var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
|
||||
var methods = controllerType.GetMethods().Where(m => m.GetCustomAttribute<WebApiAttribute>() != null).ToArray();
|
||||
|
||||
|
||||
foreach (var method in methods) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
if (routeAttribute is null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method);
|
||||
if (url is null) continue;
|
||||
|
||||
Console.WriteLine(url);
|
||||
var apiType = routeAttribute.ApiType.ToString();
|
||||
|
||||
var config = new ApiHandleConfig(method);
|
||||
if(!HandleModels.TryGetValue(apiType, out var configs))
|
||||
{
|
||||
configs = new ConcurrentDictionary<string, ApiHandleConfig>();
|
||||
HandleModels[apiType] = configs;
|
||||
}
|
||||
configs.TryAdd(url, config);
|
||||
_controllerTypes.TryAdd(url,controllerType);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 在外部调用API后,解析路由,调用对应的方法
|
||||
/// </summary>
|
||||
@@ -84,14 +312,14 @@ namespace Serein.Library.Web
|
||||
var url = request.Url; // 获取请求的 URL
|
||||
var httpMethod = request.HttpMethod; // 获取请求的 HTTP 方法
|
||||
var template = request.Url.AbsolutePath.ToLower();
|
||||
if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method))
|
||||
if (!_controllerTypes.TryGetValue(template, out Type controllerType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var routeValues = GetUrlData(url); // 解析 URL 获取路由参数
|
||||
|
||||
ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例
|
||||
ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(controllerType);
|
||||
|
||||
if (controllerInstance is null)
|
||||
{
|
||||
@@ -99,195 +327,51 @@ namespace Serein.Library.Web
|
||||
}
|
||||
|
||||
controllerInstance.Url = url.AbsolutePath;
|
||||
|
||||
|
||||
if (!HandleModels.TryGetValue(httpMethod, out var modules))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!modules.TryGetValue(template,out var config))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
dynamic invokeResult;
|
||||
switch (httpMethod)
|
||||
{
|
||||
case "GET":
|
||||
invokeResult = config.HandleGet(controllerInstance, routeValues);
|
||||
break;
|
||||
case "POST":
|
||||
var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
|
||||
controllerInstance.BobyData = requestBody;
|
||||
var requestJObject = JObject.Parse(requestBody);
|
||||
invokeResult = config.HandlePost(controllerInstance, requestJObject, routeValues);
|
||||
break;
|
||||
default:
|
||||
invokeResult = null;
|
||||
break;
|
||||
}
|
||||
object result;
|
||||
try
|
||||
{
|
||||
object result;
|
||||
switch (httpMethod) // 根据请求的 HTTP 方法执行不同的操作
|
||||
{
|
||||
case "GET": // 如果是 GET 请求,传入方法、控制器、url参数
|
||||
// loggerService.Information(GetLog(template));
|
||||
result = InvokeControllerMethodWithRouteValues(method, controllerInstance, routeValues);
|
||||
break;
|
||||
case "POST": // POST 请求传入方法、控制器、请求体内容,url参数
|
||||
var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
|
||||
controllerInstance.BobyData = requestBody;
|
||||
var requestJObject = JObject.Parse(requestBody); //requestBody.FromJSON<JObject>();
|
||||
|
||||
// loggerService.Information(GetLog(template, requestBody));
|
||||
result = InvokeControllerMethod(method, controllerInstance, requestJObject, routeValues);
|
||||
break;
|
||||
default:
|
||||
result = null;
|
||||
break;
|
||||
}
|
||||
Return(response, result); // 返回结果
|
||||
return true;
|
||||
result = invokeResult.Result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误
|
||||
Return(response, ex.Message); // 返回结果
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动注册并实例化控制器类型
|
||||
/// </summary>
|
||||
/// <param name="controllerType"></param>
|
||||
public bool RegisterController(Type controllerType) // 方法声明,用于注册并实例化控制器类型
|
||||
{
|
||||
if (!controllerType.IsClass || controllerType.IsAbstract) return false; // 如果不是类或者是抽象类,则直接返回
|
||||
|
||||
var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
|
||||
var methods = controllerType.GetMethods().Where(m => m.GetCustomAttribute<WebApiAttribute>() != null).ToArray();
|
||||
|
||||
foreach (var method in methods) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method);
|
||||
Console.WriteLine(url);
|
||||
if (url is null) continue;
|
||||
_controllerTypes[url] = controllerType;
|
||||
}
|
||||
result = invokeResult;
|
||||
}
|
||||
Return(response, result); // 返回结果
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
#region 调用Get Post对应的方法
|
||||
|
||||
/// <summary>
|
||||
/// GET请求的控制器方法
|
||||
/// </summary>
|
||||
private object InvokeControllerMethodWithRouteValues(MethodInfo method, object controllerInstance, Dictionary<string, string> routeValues)
|
||||
{
|
||||
object[] parameters = GetMethodParameters(method, routeValues);
|
||||
return InvokeMethod(method, controllerInstance, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GET请求调用控制器方法传入参数
|
||||
/// </summary>
|
||||
/// <param name="method">方法</param>
|
||||
/// <param name="controllerInstance">控制器实例</param>
|
||||
/// <param name="methodParameters">参数列表</param>
|
||||
/// <returns></returns>
|
||||
private object InvokeMethod(MethodInfo method, object controllerInstance, object[] methodParameters)
|
||||
{
|
||||
object result = null;
|
||||
try
|
||||
{
|
||||
result = method?.Invoke(controllerInstance, methodParameters);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
string targetType = ExtractTargetTypeFromExceptionMessage(ex.Message);
|
||||
|
||||
// 如果方法调用失败
|
||||
result = new
|
||||
{
|
||||
error = $"函数签名类型[{targetType}]不符合",
|
||||
};
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
|
||||
// 查找类型信息开始的索引
|
||||
int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length;
|
||||
// 查找类型信息结束的索引
|
||||
int endIndex = ex.Message.IndexOf("'", startIndex);
|
||||
// 提取类型信息
|
||||
string typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
return result; // 调用方法并返回结果
|
||||
}
|
||||
|
||||
private readonly Dictionary<MethodInfo, ParameterInfo[]> methodParameterCache = new Dictionary<MethodInfo, ParameterInfo[]>();
|
||||
/// <summary>
|
||||
/// POST请求调用控制器方法传入参数
|
||||
/// </summary>
|
||||
public object InvokeControllerMethod(MethodInfo method, object controllerInstance, JObject requestData, Dictionary<string, string> routeValues)
|
||||
{
|
||||
if (requestData is null) return null;
|
||||
ParameterInfo[] parameters;
|
||||
object[] cachedMethodParameters;
|
||||
if (!methodParameterCache.TryGetValue(method, out parameters))
|
||||
{
|
||||
parameters = method.GetParameters();
|
||||
}
|
||||
cachedMethodParameters = new object[parameters.Length];
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
string paramName = parameters[i].Name;
|
||||
bool isUrlData = parameters[i].GetCustomAttribute(typeof(UrlAttribute)) != null;
|
||||
bool isBobyData = parameters[i].GetCustomAttribute(typeof(BobyAttribute)) != null;
|
||||
|
||||
if (isUrlData)
|
||||
{
|
||||
if (routeValues.ContainsKey(paramName))
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(routeValues[paramName], parameters[i].ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = null;
|
||||
}
|
||||
}
|
||||
else if (isBobyData)
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(requestData.ToString(), parameters[i].ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requestData.ContainsKey(paramName))
|
||||
{
|
||||
var rd = requestData[paramName];
|
||||
if (parameters[i].ParameterType == typeof(string))
|
||||
{
|
||||
cachedMethodParameters[i] = rd.ToString();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(bool))
|
||||
{
|
||||
cachedMethodParameters[i] = rd.ToBool();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(int))
|
||||
{
|
||||
cachedMethodParameters[i] = rd.ToInt();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(double))
|
||||
{
|
||||
cachedMethodParameters[i] = rd.ToDouble();
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(rd, parameters[i].ParameterType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存方法和参数的映射
|
||||
//methodParameterCache[method] = cachedMethodParameters;
|
||||
|
||||
|
||||
// 调用方法
|
||||
return method.Invoke(controllerInstance, cachedMethodParameters);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region 工具方法
|
||||
/// <summary>
|
||||
/// 读取Body中的消息
|
||||
/// </summary>
|
||||
@@ -304,72 +388,6 @@ namespace Serein.Library.Web
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查方法入参参数类型,返回对应的入参数组
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="routeValues"></param>
|
||||
/// <returns></returns>
|
||||
private object[] GetMethodParameters(MethodInfo method, Dictionary<string, string> routeValues)
|
||||
{
|
||||
ParameterInfo[] methodParameters = method.GetParameters();
|
||||
object[] parameters = new object[methodParameters.Length];
|
||||
|
||||
for (int i = 0; i < methodParameters.Length; i++)
|
||||
{
|
||||
string paramName = methodParameters[i].Name;
|
||||
if (routeValues.ContainsKey(paramName))
|
||||
{
|
||||
parameters[i] = ConvertValue(routeValues[paramName], methodParameters[i].ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转为对应的类型
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <returns></returns>
|
||||
private object ConvertValue(object value, Type targetType)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (targetType == typeof(string))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return JsonConvert.DeserializeObject(value.ToString(), targetType);
|
||||
}
|
||||
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return value;
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
// 如果无法转为对应的JSON对象
|
||||
int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引
|
||||
int endIndex = ex.Message.IndexOf("'", startIndex); // 查找类型信息结束的索引
|
||||
var typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex); // 提取出错类型信息,该怎么传出去?
|
||||
return null;
|
||||
}
|
||||
catch // (Exception ex)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回响应消息
|
||||
/// </summary>
|
||||
@@ -449,7 +467,7 @@ namespace Serein.Library.Web
|
||||
controllerName = autoHostingAttribute.Url;
|
||||
}
|
||||
|
||||
var httpMethod = webAttribute.Http; // 获取 HTTP 方法
|
||||
var httpMethod = webAttribute.ApiType; // 获取 HTTP 方法
|
||||
var customUrl = webAttribute.Url; // 获取自定义 URL
|
||||
|
||||
string url;
|
||||
@@ -466,7 +484,7 @@ namespace Serein.Library.Web
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
//HandleModels[httpMethod.ToString()].TryAdd(url ); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -479,7 +497,7 @@ namespace Serein.Library.Web
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/{controllerName}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
// _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
|
||||
return url;
|
||||
@@ -539,7 +557,7 @@ namespace Serein.Library.Web
|
||||
foreach (var kvp in parsedQuery)
|
||||
{
|
||||
//Console.WriteLine($"{kvp.Key}: {kvp.Value}");
|
||||
routeValues[kvp.Key] = kvp.Value; // 将键值对添加到路由参数字典中
|
||||
routeValues[kvp.Key.ToLower()] = kvp.Value; // 将键值对添加到路由参数字典中
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +585,6 @@ namespace Serein.Library.Web
|
||||
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,9 +10,18 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class AutoSocketHandleAttribute : Attribute
|
||||
{
|
||||
public string ThemeValue;
|
||||
public string ThemeValue = string.Empty;
|
||||
public bool IsReturnValue = true;
|
||||
//public Type DataType;
|
||||
}
|
||||
|
||||
public class SocketHandleModel
|
||||
{
|
||||
public string ThemeValue { get; set; } = string.Empty;
|
||||
public bool IsReturnValue { get; set; } = true;
|
||||
}
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class AutoSocketModuleAttribute : Attribute
|
||||
{
|
||||
|
||||
@@ -20,17 +20,24 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
private readonly Delegate EmitDelegate;
|
||||
private readonly EmitHelper.EmitMethodType EmitMethodType;
|
||||
|
||||
public MyHandleConfig(ISocketControlBase instance, MethodInfo methodInfo)
|
||||
private Action<Exception, Action<object>> OnExceptionTracking;
|
||||
|
||||
public MyHandleConfig(SocketHandleModel model,ISocketControlBase instance, MethodInfo methodInfo, Action<Exception, Action<object>> onExceptionTracking)
|
||||
{
|
||||
EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo,out EmitDelegate);
|
||||
|
||||
this.Model = model;
|
||||
Instance = instance;
|
||||
var parameterInfos = methodInfo.GetParameters();
|
||||
ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray();
|
||||
ParameterName = parameterInfos.Select(t => t.Name).ToArray();
|
||||
this.HandleGuid = instance.HandleGuid;
|
||||
this.OnExceptionTracking = onExceptionTracking;
|
||||
|
||||
}
|
||||
public ISocketControlBase Instance { get; private set; }
|
||||
|
||||
private SocketHandleModel Model;
|
||||
private ISocketControlBase Instance;
|
||||
public Guid HandleGuid { get; }
|
||||
private string[] ParameterName;
|
||||
private Type[] ParameterType;
|
||||
|
||||
@@ -92,30 +99,44 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
|
||||
|
||||
}
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
//Stopwatch sw = new Stopwatch();
|
||||
//sw.Start();
|
||||
object result;
|
||||
if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
try
|
||||
{
|
||||
result = await hasResultTask(Instance, args);
|
||||
if (EmitMethodType == EmitHelper.EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
{
|
||||
result = await hasResultTask(Instance, args);
|
||||
}
|
||||
else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
|
||||
{
|
||||
await task.Invoke(Instance, args);
|
||||
result = null;
|
||||
}
|
||||
else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
|
||||
{
|
||||
result = func.Invoke(Instance, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
else if (EmitMethodType == EmitHelper.EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
|
||||
catch (Exception ex)
|
||||
{
|
||||
await task.Invoke(Instance, args);
|
||||
result = null;
|
||||
}
|
||||
else if (EmitMethodType == EmitHelper.EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
|
||||
{
|
||||
result = func.Invoke(Instance, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("构造委托无法正确调用");
|
||||
}
|
||||
sw.Stop();
|
||||
Console.WriteLine($"Emit Invoke:{sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
|
||||
await Console.Out.WriteLineAsync(ex.Message);
|
||||
this.OnExceptionTracking.Invoke(ex, (async data =>
|
||||
{
|
||||
|
||||
if(result != null && result.GetType().IsClass)
|
||||
var jsonText = JsonConvert.SerializeObject(data);
|
||||
await RecoverAsync.Invoke(jsonText);
|
||||
}));
|
||||
}
|
||||
//sw.Stop();
|
||||
//Console.WriteLine($"Emit Invoke:{sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
|
||||
|
||||
if(Model.IsReturnValue && result != null && result.GetType().IsClass)
|
||||
{
|
||||
var reusltJsonText = JsonConvert.SerializeObject(result);
|
||||
_ = RecoverAsync.Invoke($"{reusltJsonText}");
|
||||
|
||||
@@ -18,25 +18,30 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
public string ThemeJsonKey { get; }
|
||||
public string DataJsonKey { get; }
|
||||
|
||||
|
||||
|
||||
|
||||
public ConcurrentDictionary<string, MyHandleConfig> MyHandleConfigs = new ConcurrentDictionary<string, MyHandleConfig>();
|
||||
public void AddHandleConfigs(string themeValue, ISocketControlBase instance, MethodInfo methodInfo)
|
||||
public void AddHandleConfigs(SocketHandleModel model, ISocketControlBase instance, MethodInfo methodInfo
|
||||
, Action<Exception, Action<object>> onExceptionTracking)
|
||||
{
|
||||
if (!MyHandleConfigs.ContainsKey(themeValue))
|
||||
if (!MyHandleConfigs.ContainsKey(model.ThemeValue))
|
||||
{
|
||||
var myHandleConfig = new MyHandleConfig(instance, methodInfo);
|
||||
MyHandleConfigs[themeValue] = myHandleConfig;
|
||||
var myHandleConfig = new MyHandleConfig(model,instance, methodInfo, onExceptionTracking);
|
||||
MyHandleConfigs[model.ThemeValue] = myHandleConfig;
|
||||
}
|
||||
}
|
||||
public void ResetConfig(ISocketControlBase socketControlBase)
|
||||
public bool ResetConfig(ISocketControlBase socketControlBase)
|
||||
{
|
||||
foreach (var kv in MyHandleConfigs.ToArray())
|
||||
{
|
||||
var config = kv.Value;
|
||||
if (config.Instance.HandleGuid.Equals(socketControlBase.HandleGuid))
|
||||
if (config.HandleGuid.Equals(socketControlBase.HandleGuid))
|
||||
{
|
||||
MyHandleConfigs.TryRemove(kv.Key, out _);
|
||||
}
|
||||
}
|
||||
return MyHandleConfigs.Count == 0;
|
||||
}
|
||||
|
||||
public void ResetConfig()
|
||||
|
||||
@@ -28,7 +28,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
public ConcurrentDictionary<(string, string), MyHandleModule> MyHandleModuleDict
|
||||
= new ConcurrentDictionary<(string, string), MyHandleModule>();
|
||||
|
||||
|
||||
private Action<Exception, Action<object>> _onExceptionTracking;
|
||||
/// <summary>
|
||||
/// 异常跟踪
|
||||
/// </summary>
|
||||
public event Action<Exception, Action<object>> OnExceptionTracking;
|
||||
|
||||
private MyHandleModule AddMyHandleModule(string themeKeyName, string dataKeyName)
|
||||
{
|
||||
@@ -52,13 +56,14 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
var themeKeyName = moduleAttribute.JsonThemeField;
|
||||
var dataKeyName = moduleAttribute.JsonDataField;
|
||||
var key = (themeKeyName, dataKeyName);
|
||||
if (MyHandleModuleDict.TryRemove(key, out var myHandleModules))
|
||||
if (MyHandleModuleDict.TryGetValue(key, out var myHandleModules))
|
||||
{
|
||||
myHandleModules.ResetConfig(socketControlBase);
|
||||
var isRemote = myHandleModules.ResetConfig(socketControlBase);
|
||||
if (isRemote) MyHandleModuleDict.TryGetValue(key, out _);
|
||||
}
|
||||
|
||||
}
|
||||
public void AddModule(ISocketControlBase socketControlBase)
|
||||
public void AddModule(ISocketControlBase socketControlBase, Action<Exception, Action<object>> onExceptionTracking)
|
||||
{
|
||||
var type = socketControlBase.GetType();
|
||||
var moduleAttribute = type.GetCustomAttribute<AutoSocketModuleAttribute>();
|
||||
@@ -67,10 +72,9 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加处理模块
|
||||
var themeKey = moduleAttribute.JsonThemeField;
|
||||
var dataKey = moduleAttribute.JsonDataField;
|
||||
|
||||
|
||||
var handlemodule = AddMyHandleModule(themeKey, dataKey);
|
||||
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Select(method =>
|
||||
@@ -78,23 +82,35 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
var methodsAttribute = method.GetCustomAttribute<AutoSocketHandleAttribute>();
|
||||
if (methodsAttribute is null)
|
||||
{
|
||||
return (string.Empty, null);
|
||||
return (null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(methodsAttribute.ThemeValue))
|
||||
{
|
||||
methodsAttribute.ThemeValue = method.Name;
|
||||
}
|
||||
var model = new SocketHandleModel
|
||||
{
|
||||
IsReturnValue = methodsAttribute.IsReturnValue,
|
||||
ThemeValue = methodsAttribute.ThemeValue,
|
||||
};
|
||||
var value = methodsAttribute.ThemeValue;
|
||||
return (value, method);
|
||||
return (model, method);
|
||||
}
|
||||
})
|
||||
.Where(x => !string.IsNullOrEmpty(x.value)).ToList();
|
||||
.Where(x => !(x.model is null)).ToList();
|
||||
if (methods.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ((var value, var method) in methods)
|
||||
Console.WriteLine($"add websocket handle model :");
|
||||
Console.WriteLine($"theme key, data key : {themeKey}, {dataKey}");
|
||||
foreach ((var model, var method) in methods)
|
||||
{
|
||||
handlemodule.AddHandleConfigs(value, socketControlBase, method);
|
||||
Console.WriteLine($"theme value : {model.ThemeValue}");
|
||||
handlemodule.AddHandleConfigs(model, socketControlBase, method, onExceptionTracking);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -106,7 +122,9 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
{
|
||||
foreach (var module in MyHandleModuleDict.Values)
|
||||
{
|
||||
|
||||
module.HandleSocketMsg(RecoverAsync, json);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -14,15 +14,8 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Network.WebSocketCommunication
|
||||
{
|
||||
[AutoRegister]
|
||||
public class WebSocketServer
|
||||
{
|
||||
public WebSocketServer()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public SocketMsgHandleHelper MsgHandleHelper { get; } = new SocketMsgHandleHelper();
|
||||
|
||||
HttpListener listener;
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Serein.Library.Attributes
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 建议触发器手动设置返回类型
|
||||
/// 生成的节点类型
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class NodeActionAttribute : Attribute
|
||||
|
||||
@@ -8,7 +8,6 @@ using Serein.Library.Attributes;
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library.Ex;
|
||||
using Serein.Library.Framework.NodeFlow;
|
||||
using Serein.Library.Network.WebSocketCommunication;
|
||||
using Serein.Library.NodeFlow.Tool;
|
||||
using Serein.Library.Web;
|
||||
using System;
|
||||
@@ -16,64 +15,33 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Net462DllTest.LogicControl
|
||||
{
|
||||
|
||||
[AutoRegister]
|
||||
[DynamicFlow("[SiemensPlc]")]
|
||||
public class PlcLogicControl
|
||||
[DynamicFlow("[SiemensPlc]")]
|
||||
public class PlcLogicControl
|
||||
{
|
||||
public Guid HandleGuid { get; } = new Guid();
|
||||
|
||||
private readonly SiemensPlcDevice MyPlc;
|
||||
private readonly PlcVarModelDataProxy plcVarModelDataProxy;
|
||||
|
||||
public PlcLogicControl(SiemensPlcDevice MyPlc,
|
||||
PlcVarModelDataProxy plcVarModelDataProxy)
|
||||
PlcVarModelDataProxy plcVarModelDataProxy)
|
||||
{
|
||||
this.MyPlc = MyPlc;
|
||||
this.plcVarModelDataProxy = plcVarModelDataProxy;
|
||||
}
|
||||
|
||||
#region 初始化、初始化完成以及退出的事件
|
||||
[NodeAction(NodeType.Init)]
|
||||
public void Init(IDynamicContext context)
|
||||
{
|
||||
context.Env.IOC.Register<WebSocketServer>();
|
||||
context.Env.IOC.Register<WebSocketClient>();
|
||||
|
||||
context.Env.IOC.Register<IRouter, Router>();
|
||||
context.Env.IOC.Register<WebApiServer>();
|
||||
|
||||
|
||||
}
|
||||
|
||||
#region 初始化
|
||||
[NodeAction(NodeType.Loading)] // Loading 初始化完成已注入依赖项,可以开始逻辑上的操作
|
||||
public void Loading(IDynamicContext context)
|
||||
{
|
||||
// 注册控制器
|
||||
context.Env.IOC.Run<IRouter, WebApiServer>((router, apiServer) => {
|
||||
router.RegisterController(typeof(FlowController));
|
||||
apiServer.Start("http://*:8089/"); // 开启 Web Api 服务
|
||||
});
|
||||
|
||||
|
||||
context.Env.IOC.Run<WebSocketServer>(async (socketServer) => {
|
||||
// socketServer.RegisterModuleInstance(userService);
|
||||
await socketServer.StartAsync("http://localhost:5005/"); // 开启 Web Socket 监听
|
||||
});
|
||||
context.Env.IOC.Run<WebSocketClient>(async client => {
|
||||
await client.ConnectAsync("ws://localhost:5005/"); // 连接到服务器
|
||||
});
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Exit)] // 流程结束时自动执行
|
||||
public void Exit(IDynamicContext context)
|
||||
{
|
||||
context.Env.IOC.Run<WebApiServer>((apiServer) =>
|
||||
{
|
||||
apiServer?.Stop(); // 关闭 Web 服务
|
||||
|
||||
});
|
||||
context.Env.IOC.Run<WebSocketServer>((socketServer) =>
|
||||
{
|
||||
socketServer?.Stop(); // 关闭 Web 服务
|
||||
});
|
||||
MyPlc.Close();
|
||||
MyPlc.CancelAllTasks();
|
||||
}
|
||||
@@ -120,13 +88,14 @@ namespace Net462DllTest.LogicControl
|
||||
|
||||
}
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "PLC初始化")]
|
||||
public SiemensPlcDevice PlcInit(SiemensVersion version = SiemensVersion.None,
|
||||
string ip = "192.168.10.100",
|
||||
int port = 102)
|
||||
{
|
||||
//MyPlc.Model.Set(PlcVarName.DoorVar,1);
|
||||
//MyPlc.Model.Value.SpaceNum = 1;
|
||||
MyPlc.Model.Set(PlcVarName.DoorVar,(Int16)1);
|
||||
MyPlc.Model.Get(PlcVarName.DoorVar);
|
||||
if (MyPlc.Client is null)
|
||||
{
|
||||
try
|
||||
@@ -155,6 +124,7 @@ namespace Net462DllTest.LogicControl
|
||||
return MyPlc;
|
||||
}
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "PLC获取变量")]
|
||||
public object ReadVar(PlcVarName varName)
|
||||
{
|
||||
@@ -163,6 +133,7 @@ namespace Net462DllTest.LogicControl
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "PLC写入变量")]
|
||||
public SiemensPlcDevice WriteVar(object value, PlcVarName varName)
|
||||
{
|
||||
@@ -171,10 +142,13 @@ namespace Net462DllTest.LogicControl
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Action, "批量读取")]
|
||||
public void BatchReadVar()
|
||||
public PlcVarModelDataProxy BatchReadVar()
|
||||
{
|
||||
MyPlc.BatchRefresh();
|
||||
return plcVarModelDataProxy;
|
||||
}
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "开启定时刷新")]
|
||||
public void OpenTimedRefresh()
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Net462DllTest.LogicControl
|
||||
#region 触发器节点
|
||||
|
||||
[NodeAction(NodeType.Flipflop, "等待视图命令", ReturnType = typeof(int))]
|
||||
public async Task<IFlipflopContext> WaitTask(CommandSignal command = CommandSignal.Command_1)
|
||||
public async Task<IFlipflopContext> WaitTask(CommandSignal command)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<DependentUpon>TestFormView.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Web\FlowController.cs" />
|
||||
<Compile Include="Web\UserService.cs" />
|
||||
<Compile Include="Web\PlcSocketService.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\DynamicControl\SereinFlow\Library.Framework\Serein.Library.Framework.csproj">
|
||||
|
||||
@@ -17,6 +17,7 @@ using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar;
|
||||
using System.Linq;
|
||||
using Serein.Library.Network.WebSocketCommunication;
|
||||
|
||||
namespace Net462DllTest.Trigger
|
||||
{
|
||||
|
||||
@@ -11,11 +11,13 @@ namespace Net462DllTest.Utils
|
||||
{
|
||||
public interface IGSModel<TKey, TModel>
|
||||
{
|
||||
TModel Value { get; set; }
|
||||
//TModel Value { get; set; }
|
||||
void Set(TKey tEnum, object value);
|
||||
object Get(TKey tEnum);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通过 Emit 创建 set/get 委托
|
||||
/// </summary>
|
||||
@@ -23,7 +25,8 @@ namespace Net462DllTest.Utils
|
||||
where TKey : struct, Enum
|
||||
where TModel : class
|
||||
{
|
||||
public TModel Value { get; set; }
|
||||
private TModel Value;
|
||||
|
||||
public GSModel(TModel Model)
|
||||
{
|
||||
this.Value = Model;
|
||||
@@ -32,55 +35,6 @@ namespace Net462DllTest.Utils
|
||||
private readonly Dictionary<TKey, Action<TModel, object>> _setterCache = new Dictionary<TKey, Action<TModel, object>>();
|
||||
private readonly Dictionary<TKey, Func<TModel, object>> _getterCache = new Dictionary<TKey, Func<TModel, object>>();
|
||||
|
||||
// 动态创建调用Setter方法
|
||||
private Action<TModel, object> CreateSetter(PropertyInfo property)
|
||||
{
|
||||
var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(TModel), typeof(object) }, true);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue)
|
||||
il.Emit(OpCodes.Ldarg_1); // 加载值(object)
|
||||
|
||||
if (property.PropertyType.IsValueType)
|
||||
{
|
||||
il.Emit(OpCodes.Unbox_Any, property.PropertyType); // 解箱并转换为值类型
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Castclass, property.PropertyType); // 引用类型转换
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Callvirt, property.GetSetMethod()); // 调用属性的Setter方法
|
||||
il.Emit(OpCodes.Ret); // 返回
|
||||
|
||||
|
||||
|
||||
return (Action<TModel, object>)method.CreateDelegate(typeof(Action<TModel, object>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态创建调用Getter方法
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
private Func<TModel, object> CreateGetter(PropertyInfo property)
|
||||
{
|
||||
var method = new DynamicMethod("Get" + property.Name, typeof(object), new[] { typeof(TModel) }, true);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue)
|
||||
il.Emit(OpCodes.Callvirt, property.GetGetMethod()); // 调用属性的Getter方法
|
||||
|
||||
if (property.PropertyType.IsValueType)
|
||||
{
|
||||
il.Emit(OpCodes.Box, property.PropertyType); // 值类型需要装箱
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret); // 返回
|
||||
|
||||
return (Func<TModel, object>)method.CreateDelegate(typeof(Func<TModel, object>));
|
||||
}
|
||||
|
||||
public void Set(TKey tEnum, object value)
|
||||
{
|
||||
if (!_setterCache.TryGetValue(tEnum, out var setter))
|
||||
@@ -89,7 +43,6 @@ namespace Net462DllTest.Utils
|
||||
if (property == null)
|
||||
{
|
||||
_setterCache[tEnum] = (s, o) => throw new ArgumentException($"没有对应的Model属性{{{tEnum}");
|
||||
//throw new ArgumentException($"Property not found for {plcVarEnum}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -141,5 +94,67 @@ namespace Net462DllTest.Utils
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 动态创建调用Setter方法
|
||||
private Action<TModel, object> CreateSetter(PropertyInfo property)
|
||||
{
|
||||
var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(TModel), typeof(object) }, true);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue)
|
||||
il.Emit(OpCodes.Ldarg_1); // 加载值(object)
|
||||
|
||||
if (property.PropertyType.IsValueType)
|
||||
{
|
||||
il.Emit(OpCodes.Unbox_Any, property.PropertyType); // 解箱并转换为值类型
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Castclass, property.PropertyType); // 引用类型转换
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Callvirt, property.GetSetMethod()); // 调用属性的Setter方法
|
||||
il.Emit(OpCodes.Ret); // 返回
|
||||
|
||||
|
||||
|
||||
return (Action<TModel, object>)method.CreateDelegate(typeof(Action<TModel, object>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态创建调用Getter方法
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
private Func<TModel, object> CreateGetter(PropertyInfo property)
|
||||
{
|
||||
var method = new DynamicMethod("Get" + property.Name, typeof(object), new[] { typeof(TModel) }, true);
|
||||
var il = method.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0); // 加载实例(PlcVarValue)
|
||||
il.Emit(OpCodes.Callvirt, property.GetGetMethod()); // 调用属性的Getter方法
|
||||
|
||||
if (property.PropertyType.IsValueType)
|
||||
{
|
||||
il.Emit(OpCodes.Box, property.PropertyType); // 值类型需要装箱
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret); // 返回
|
||||
|
||||
return (Func<TModel, object>)method.CreateDelegate(typeof(Func<TModel, object>));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Net462DllTest
|
||||
|
||||
private void FromWorkBenchView_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
ViewModel.CommandCloseForm.Execute();
|
||||
ViewModel.CommandCloseForm?.Execute();
|
||||
}
|
||||
|
||||
private void button2_Click(object sender, EventArgs e)
|
||||
|
||||
@@ -30,10 +30,8 @@ namespace Net462DllTest.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
[AutoSocketModule(JsonThemeField = "theme", JsonDataField = "data")]
|
||||
public class FromWorkBenchViewModel : INotifyPropertyChanged, ISocketControlBase
|
||||
public class FromWorkBenchViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public Guid HandleGuid { get; } = new Guid();
|
||||
|
||||
private readonly SiemensPlcDevice Device;
|
||||
private readonly ViewManagement viewManagement;
|
||||
@@ -51,81 +49,6 @@ namespace Net462DllTest.ViewModel
|
||||
this.plcVarModelDataProxy = plcVarModelDataProxy;
|
||||
|
||||
InitCommand(); // 初始化指令
|
||||
|
||||
webSocketServer.MsgHandleHelper.AddModule(this); // 用于关闭窗体时移除监听
|
||||
CommandCloseForm = new RelayCommand((p) =>
|
||||
{
|
||||
webSocketServer.MsgHandleHelper.RemoteModule(this); // 用于关闭窗体时移除监听
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//Console.WriteLine($"Emit: {sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
|
||||
|
||||
|
||||
//var msgHandl = new WsMsgHandl<LibSpace>()
|
||||
//{
|
||||
// DataJsonKey = "data", // 数据键
|
||||
// ThemeJsonKey = "theme", // 主题键
|
||||
//};
|
||||
//msgHandl.AddHandle<LibSpace>(nameof(GetSpace), GetSpace); // theme:AddUser
|
||||
//msgHandl.AddHandle<LibSpace>(nameof(UpData), UpData); // theme:DeleteUser
|
||||
//webSocketServer.AddModule(msgHandl);
|
||||
//CommandCloseForm = new RelayCommand((p) =>
|
||||
//{
|
||||
// webSocketServer.RemoteModule(msgHandl); // 用于关闭窗体时移除监听
|
||||
//});
|
||||
}
|
||||
Action<string> Recover;
|
||||
[AutoSocketHandle(ThemeValue = "SavaRecover")]
|
||||
public async Task SavaRecover(int waitTime , Action<string> Recover)
|
||||
{
|
||||
if(waitTime <=0 || waitTime>= 10000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Task.Delay(waitTime);
|
||||
Recover("haha~" );
|
||||
this.Recover = Recover;
|
||||
return;
|
||||
}
|
||||
[AutoSocketHandle(ThemeValue = "Invoke")]
|
||||
public void Invoke(string a)
|
||||
{
|
||||
if (Recover is null)
|
||||
return;
|
||||
Recover("haha~"+a);
|
||||
|
||||
}
|
||||
|
||||
|
||||
[AutoSocketHandle(ThemeValue = "PlcModel")]
|
||||
public async Task<PlcVarModelDataProxy> GetModelData(Action<object> Recover)
|
||||
{
|
||||
Recover("等待5秒");
|
||||
await Task.Delay(5000);
|
||||
//Recover.Invoke(plcVarModelDataProxy);
|
||||
return plcVarModelDataProxy;
|
||||
}
|
||||
|
||||
[AutoSocketHandle(ThemeValue = "Up")]
|
||||
public void UpData(LibSpace libSpace, Action<dynamic> Recover)
|
||||
{
|
||||
Recover("收到");
|
||||
return;
|
||||
}
|
||||
|
||||
[AutoSocketHandle(ThemeValue = "Bind")]
|
||||
public LibSpace SpaceBind(string spaceNum, string plateNumber, Action<string> Recover)
|
||||
{
|
||||
return new LibSpace
|
||||
{
|
||||
PlateNumber = plateNumber,
|
||||
SpaceNum = spaceNum,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Net462DllTest.Web
|
||||
* "value":0,
|
||||
* }
|
||||
*/
|
||||
[WebApi(API.POST)]
|
||||
[WebApi(ApiType.POST)]
|
||||
public dynamic PlcOp([Url] string var, int value)
|
||||
{
|
||||
if (EnumHelper.TryConvertEnum<PlcVarName>(var,out var signal))
|
||||
@@ -61,7 +61,7 @@ namespace Net462DllTest.Web
|
||||
* "value":0,
|
||||
* }
|
||||
*/
|
||||
[WebApi(API.POST)]
|
||||
[WebApi(ApiType.POST)]
|
||||
public dynamic Trigger([Url] string command, int value)
|
||||
{
|
||||
if (EnumHelper.TryConvertEnum<CommandSignal>(command, out var signal))
|
||||
|
||||
170
Net462DllTest/Web/PlcSocketService.cs
Normal file
170
Net462DllTest/Web/PlcSocketService.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using IoTClient.Common.Enums;
|
||||
using Net462DllTest.Enums;
|
||||
using Net462DllTest.Model;
|
||||
using Net462DllTest.Trigger;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Attributes;
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library.Ex;
|
||||
using Serein.Library.Framework.NodeFlow;
|
||||
using Serein.Library.Network.WebSocketCommunication;
|
||||
using Serein.Library.NodeFlow.Tool;
|
||||
using Serein.Library.Web;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Net462DllTest.Web
|
||||
{
|
||||
|
||||
[DynamicFlow("[PlcSocketService]")]
|
||||
[AutoRegister]
|
||||
[AutoSocketModule(JsonThemeField = "theme", JsonDataField = "data")]
|
||||
public class PlcSocketService : ISocketControlBase
|
||||
{
|
||||
public Guid HandleGuid { get; } = new Guid();
|
||||
|
||||
private readonly SiemensPlcDevice MyPlc;
|
||||
private readonly PlcVarModelDataProxy plcVarModelDataProxy;
|
||||
|
||||
public PlcSocketService(SiemensPlcDevice MyPlc,
|
||||
PlcVarModelDataProxy plcVarModelDataProxy)
|
||||
{
|
||||
this.MyPlc = MyPlc;
|
||||
this.plcVarModelDataProxy = plcVarModelDataProxy;
|
||||
}
|
||||
|
||||
#region 初始化、初始化完成以及退出的事件
|
||||
[NodeAction(NodeType.Init)]
|
||||
public void Init(IDynamicContext context)
|
||||
{
|
||||
context.Env.IOC.Register<WebSocketServer>();
|
||||
context.Env.IOC.Register<WebSocketClient>();
|
||||
|
||||
context.Env.IOC.Register<IRouter, Router>();
|
||||
context.Env.IOC.Register<WebApiServer>();
|
||||
|
||||
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Loading)] // Loading 初始化完成已注入依赖项,可以开始逻辑上的操作
|
||||
public void Loading(IDynamicContext context)
|
||||
{
|
||||
// 注册控制器
|
||||
context.Env.IOC.Run<IRouter, WebApiServer>((router, apiServer) => {
|
||||
router.AddHandle(typeof(FlowController));
|
||||
apiServer.Start("http://*:8089/"); // 开启 Web Api 服务
|
||||
});
|
||||
|
||||
context.Env.IOC.Run<WebSocketServer>(async (socketServer) => {
|
||||
socketServer.MsgHandleHelper.AddModule(this, (ex, recover) =>
|
||||
{
|
||||
recover(new
|
||||
{
|
||||
ex = ex.Message,
|
||||
storehouseInfo = ex.StackTrace
|
||||
});
|
||||
|
||||
});
|
||||
await socketServer.StartAsync("http://localhost:5005/"); // 开启 Web Socket 监听
|
||||
});
|
||||
context.Env.IOC.Run<WebSocketClient>(async client => {
|
||||
await client.ConnectAsync("ws://localhost:5005/"); // 连接到服务器
|
||||
});
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Exit)] // 流程结束时自动执行
|
||||
public void Exit(IDynamicContext context)
|
||||
{
|
||||
context.Env.IOC.Run<WebApiServer>((apiServer) =>
|
||||
{
|
||||
apiServer?.Stop(); // 关闭 Web 服务
|
||||
|
||||
});
|
||||
context.Env.IOC.Run<WebSocketServer>((socketServer) =>
|
||||
{
|
||||
socketServer?.Stop(); // 关闭 Web 服务
|
||||
});
|
||||
MyPlc.Close();
|
||||
MyPlc.CancelAllTasks();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "等待")]
|
||||
public async Task Delay(int ms = 5000)
|
||||
{
|
||||
await Console.Out.WriteLineAsync("开始等待");
|
||||
await Task.Delay(ms);
|
||||
await Console.Out.WriteLineAsync("不再等待");
|
||||
|
||||
}
|
||||
|
||||
[AutoSocketHandle(IsReturnValue = false)]
|
||||
public SiemensPlcDevice PlcInit(SiemensVersion version = SiemensVersion.None,
|
||||
string ip = "192.168.10.100",
|
||||
int port = 102)
|
||||
{
|
||||
MyPlc.Model.Set(PlcVarName.DoorVar, (Int16)1);
|
||||
MyPlc.Model.Get(PlcVarName.DoorVar);
|
||||
if (MyPlc.Client is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
MyPlc.Init(version, ip, port);
|
||||
Console.WriteLine($"西门子PLC初始化成功[{version},{ip}:{port}]");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"西门子PLC[{version},{ip}:{port}]初始化异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"西门子PLC已经初始化[{version},{ip}:{port}]");
|
||||
}
|
||||
return MyPlc;
|
||||
}
|
||||
|
||||
[AutoSocketHandle(IsReturnValue = false)]
|
||||
public SiemensPlcDevice SetState(PlcState state = PlcState.PowerOff)
|
||||
{
|
||||
var oldState = MyPlc.State;
|
||||
MyPlc.State = state;
|
||||
Console.WriteLine($"PLC状态从[{oldState}]转为[{state}]");
|
||||
return MyPlc;
|
||||
}
|
||||
|
||||
[AutoSocketHandle]
|
||||
public object ReadVar(PlcVarName varName)
|
||||
{
|
||||
var result = MyPlc.Read(varName);
|
||||
Console.WriteLine($"获取变量成功:({varName})\t result = {result}");
|
||||
return result;
|
||||
}
|
||||
|
||||
[AutoSocketHandle(IsReturnValue = false)]
|
||||
public SiemensPlcDevice WriteVar(object value, PlcVarName varName)
|
||||
{
|
||||
MyPlc.Write(varName, value); // 新数据
|
||||
return MyPlc;
|
||||
}
|
||||
|
||||
public PlcVarModelDataProxy BatchReadVar()
|
||||
{
|
||||
MyPlc.BatchRefresh();
|
||||
return plcVarModelDataProxy;
|
||||
}
|
||||
|
||||
public void OpenTimedRefresh()
|
||||
{
|
||||
Task.Run(async () => await MyPlc.OpenTimedRefreshAsync());
|
||||
}
|
||||
|
||||
public void CloseTimedRefresh()
|
||||
{
|
||||
MyPlc.CloseTimedRefresh();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Net462DllTest.ViewModel;
|
||||
using Serein.Library.Attributes;
|
||||
using Serein.Library.Network.WebSocketCommunication;
|
||||
using Serein.Library.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Net462DllTest.Web
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
@@ -395,37 +395,66 @@ namespace Serein.NodeFlow.Base
|
||||
|
||||
try
|
||||
{
|
||||
string? valueStr = inputParameter?.ToString();
|
||||
parameters[i] = ed.DataType switch
|
||||
|
||||
if (ed.DataType.IsValueType)
|
||||
{
|
||||
Type t when t == typeof(IDynamicContext) => context, // 上下文
|
||||
Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举
|
||||
Type t when t == typeof(string) => inputParameter?.ToString(),
|
||||
Type t when t == typeof(char) && !string.IsNullOrEmpty(valueStr) => char.Parse(valueStr),
|
||||
Type t when t == typeof(bool) && !string.IsNullOrEmpty(valueStr) => inputParameter is not null && bool.Parse(valueStr),
|
||||
Type t when t == typeof(float) && !string.IsNullOrEmpty(valueStr) => float.Parse(valueStr),
|
||||
Type t when t == typeof(decimal) && !string.IsNullOrEmpty(valueStr) => decimal.Parse(valueStr),
|
||||
Type t when t == typeof(double) && !string.IsNullOrEmpty(valueStr) => double.Parse(valueStr),
|
||||
Type t when t == typeof(sbyte) && !string.IsNullOrEmpty(valueStr) => sbyte.Parse(valueStr),
|
||||
Type t when t == typeof(byte) && !string.IsNullOrEmpty(valueStr) => byte.Parse(valueStr),
|
||||
Type t when t == typeof(short) && !string.IsNullOrEmpty(valueStr) => short.Parse(valueStr),
|
||||
Type t when t == typeof(ushort) && !string.IsNullOrEmpty(valueStr) => ushort.Parse(valueStr),
|
||||
Type t when t == typeof(int) && !string.IsNullOrEmpty(valueStr) => int.Parse(valueStr),
|
||||
Type t when t == typeof(uint) && !string.IsNullOrEmpty(valueStr) => uint.Parse(valueStr),
|
||||
Type t when t == typeof(long) && !string.IsNullOrEmpty(valueStr) => long.Parse(valueStr),
|
||||
Type t when t == typeof(ulong) && !string.IsNullOrEmpty(valueStr) => ulong.Parse(valueStr),
|
||||
Type t when t == typeof(nint) && !string.IsNullOrEmpty(valueStr) => nint.Parse(valueStr),
|
||||
Type t when t == typeof(nuint) && !string.IsNullOrEmpty(valueStr) => nuint.Parse(valueStr),
|
||||
//Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? 0 : DateTime.Parse(valueStr),
|
||||
if (inputParameter is null)
|
||||
{
|
||||
parameters[i] = Activator.CreateInstance(ed.DataType);
|
||||
}
|
||||
else
|
||||
{
|
||||
string? valueStr = inputParameter?.ToString();
|
||||
if (string.IsNullOrEmpty(valueStr))
|
||||
{
|
||||
parameters[i] = Activator.CreateInstance(ed.DataType);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters[i] = ed.DataType switch
|
||||
{
|
||||
Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举
|
||||
Type t when t == typeof(char) => char.Parse(valueStr),
|
||||
Type t when t == typeof(bool) => bool.Parse(valueStr),
|
||||
Type t when t == typeof(float) => float.Parse(valueStr),
|
||||
Type t when t == typeof(decimal) => decimal.Parse(valueStr),
|
||||
Type t when t == typeof(double) => double.Parse(valueStr),
|
||||
Type t when t == typeof(sbyte) => sbyte.Parse(valueStr),
|
||||
Type t when t == typeof(byte) => byte.Parse(valueStr),
|
||||
Type t when t == typeof(short) => short.Parse(valueStr),
|
||||
Type t when t == typeof(ushort) => ushort.Parse(valueStr),
|
||||
Type t when t == typeof(int) => int.Parse(valueStr),
|
||||
Type t when t == typeof(uint) => uint.Parse(valueStr),
|
||||
Type t when t == typeof(long) => long.Parse(valueStr),
|
||||
Type t when t == typeof(ulong) => ulong.Parse(valueStr),
|
||||
Type t when t == typeof(nint) => nint.Parse(valueStr),
|
||||
Type t when t == typeof(nuint) => nuint.Parse(valueStr),
|
||||
_ => throw new Exception($"调用节点对应方法[{nodeModel.MethodDetails.MethodName}]时,遇到了未在预期内的值类型入参:{ed.DataType.FullName}"),
|
||||
// Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters[i] = ed.DataType switch
|
||||
{
|
||||
Type t when t == typeof(IDynamicContext) => context, // 上下文
|
||||
Type t when t == typeof(string) => inputParameter?.ToString(),
|
||||
|
||||
Type t when t == typeof(MethodDetails) => md, // 节点方法描述
|
||||
Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类
|
||||
//Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? 0 : DateTime.Parse(valueStr),
|
||||
|
||||
Type t when t.IsArray => (inputParameter as Array)?.Cast<object>().ToList(),
|
||||
Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter,
|
||||
_ => inputParameter,
|
||||
// Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)),
|
||||
};
|
||||
Type t when t == typeof(MethodDetails) => md, // 节点方法描述
|
||||
Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类
|
||||
|
||||
Type t when t.IsArray => (inputParameter as Array)?.Cast<object>().ToList(),
|
||||
Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter,
|
||||
_ => inputParameter,
|
||||
// Type t when Nullable.GetUnderlyingType(t) != null => inputParameter is null ? null : Convert.ChangeType(inputParameter, Nullable.GetUnderlyingType(t)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ namespace Serein.NodeFlow
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 运行时创建节点
|
||||
/// 流程正在运行时创建节点
|
||||
/// </summary>
|
||||
/// <param name="nodeControlType"></param>
|
||||
/// <param name="position"></param>
|
||||
@@ -536,12 +536,12 @@ namespace Serein.NodeFlow
|
||||
var nodeModel = CreateNode(nodeControlType, methodDetails);
|
||||
TryAddNode(nodeModel);
|
||||
|
||||
if (flowStarter?.FlowState != RunState.Completion
|
||||
&& nodeControlType == NodeControlType.Flipflop
|
||||
&& nodeModel is SingleFlipflopNode flipflopNode)
|
||||
{
|
||||
_ = flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 当前添加节点属于触发器,且当前正在运行,则加载到运行环境中
|
||||
}
|
||||
//if (flowStarter?.FlowState != RunState.Completion
|
||||
// && nodeControlType == NodeControlType.Flipflop
|
||||
// && nodeModel is SingleFlipflopNode flipflopNode)
|
||||
//{
|
||||
// _ = flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 当前添加节点属于触发器,且当前正在运行,则加载到运行环境中
|
||||
//}
|
||||
|
||||
// 通知UI更改
|
||||
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position));
|
||||
@@ -642,33 +642,7 @@ namespace Serein.NodeFlow
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除连接关系
|
||||
/// </summary>
|
||||
/// <param name="fromNodeGuid">起始节点Model</param>
|
||||
/// <param name="toNodeGuid">目标节点Model</param>
|
||||
/// <param name="connectionType">连接关系</param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void RemoteConnect(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType)
|
||||
{
|
||||
fromNode.SuccessorNodes[connectionType].Remove(toNode);
|
||||
toNode.PreviousNodes[connectionType].Remove(fromNode);
|
||||
|
||||
if (toNode is SingleFlipflopNode flipflopNode) // 子节点为触发器
|
||||
{
|
||||
if (flowStarter?.FlowState != RunState.Completion
|
||||
&& flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点
|
||||
{
|
||||
flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中
|
||||
}
|
||||
}
|
||||
|
||||
// 通知UI
|
||||
OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid,
|
||||
toNode.Guid,
|
||||
connectionType,
|
||||
NodeConnectChangeEventArgs.ConnectChangeType.Remote));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取方法描述
|
||||
@@ -709,6 +683,8 @@ namespace Serein.NodeFlow
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置起点控件
|
||||
/// </summary>
|
||||
@@ -781,55 +757,6 @@ namespace Serein.NodeFlow
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//public bool AddInterruptExpression(string nodeGuid, string expression)
|
||||
//{
|
||||
// var nodeModel = GuidToModel(nodeGuid);
|
||||
// if (nodeModel is null) return false;
|
||||
// if (string.IsNullOrEmpty(expression))
|
||||
// {
|
||||
// nodeModel.DebugSetting.InterruptExpressions.Clear(); // 传入空表达式时清空
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// if (nodeModel.DebugSetting.InterruptExpressions.Contains(expression))
|
||||
// {
|
||||
// Console.WriteLine("表达式已存在");
|
||||
// return false;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// nodeModel.DebugSetting.InterruptExpressions.Clear();// 暂时删除,等UI做好了
|
||||
// nodeModel.DebugSetting.InterruptExpressions.Add(expression);
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 监视节点的数据(暂时注释)
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid">需要监视的节点Guid</param>
|
||||
//public void SetMonitorObjState(string nodeGuid, bool isMonitor)
|
||||
//{
|
||||
// var nodeModel = GuidToModel(nodeGuid);
|
||||
// if (nodeModel is null) return;
|
||||
// nodeModel.DebugSetting.IsMonitorFlowData = isMonitor;
|
||||
|
||||
// if (isMonitor)
|
||||
// {
|
||||
// var obj = nodeModel.GetFlowData();
|
||||
// if(obj is not null)
|
||||
// {
|
||||
// FlowDataNotification(nodeGuid, obj);
|
||||
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // 不再监视的节点清空表达式
|
||||
// nodeModel.DebugSetting.InterruptExpressions.Clear();
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 要监视的对象,以及与其关联的表达式
|
||||
@@ -894,6 +821,36 @@ namespace Serein.NodeFlow
|
||||
OnInterruptTrigger?.Invoke(new InterruptTriggerEventArgs(nodeGuid, expression, type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 激活全局触发器
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
public void ActivateFlipflopNode(string nodeGuid)
|
||||
{
|
||||
var nodeModel = GuidToModel(nodeGuid);
|
||||
if (nodeModel is null) return;
|
||||
if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器
|
||||
{
|
||||
if (flowStarter.FlowState != RunState.Completion
|
||||
&& flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点
|
||||
{
|
||||
_ = flowStarter.RunGlobalFlipflopAsync(this, flipflopNode);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中
|
||||
|
||||
}
|
||||
}
|
||||
} /// <summary>
|
||||
/// 关闭全局触发器
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
public void TerminateFlipflopNode(string nodeGuid)
|
||||
{
|
||||
var nodeModel = GuidToModel(nodeGuid);
|
||||
if (nodeModel is null) return;
|
||||
if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器
|
||||
{
|
||||
flowStarter.TerminateGlobalFlipflopRuning(flipflopNode);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<CancelType> GetOrCreateGlobalInterruptAsync()
|
||||
{
|
||||
@@ -902,7 +859,6 @@ namespace Serein.NodeFlow
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Guid 转 NodeModel
|
||||
/// </summary>
|
||||
@@ -955,7 +911,24 @@ namespace Serein.NodeFlow
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 移除连接关系
|
||||
/// </summary>
|
||||
/// <param name="fromNodeGuid">起始节点Model</param>
|
||||
/// <param name="toNodeGuid">目标节点Model</param>
|
||||
/// <param name="connectionType">连接关系</param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void RemoteConnect(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType)
|
||||
{
|
||||
fromNode.SuccessorNodes[connectionType].Remove(toNode);
|
||||
toNode.PreviousNodes[connectionType].Remove(fromNode);
|
||||
|
||||
// 通知UI
|
||||
OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid,
|
||||
toNode.Guid,
|
||||
connectionType,
|
||||
NodeConnectChangeEventArgs.ConnectChangeType.Remote));
|
||||
}
|
||||
|
||||
private (NodeLibrary?, Dictionary<RegisterSequence, List<Type>>, List<MethodDetails>) LoadAssembly(string dllPath)
|
||||
{
|
||||
|
||||
@@ -304,7 +304,7 @@ namespace Serein.NodeFlow
|
||||
// 使用 TaskCompletionSource 创建未启动的触发器任务
|
||||
var tasks = flipflopNodes.Select(async node =>
|
||||
{
|
||||
await RunGlobalFlipflopAsync(env,node);
|
||||
await RunGlobalFlipflopAsync(env,node); // 启动流程时启动全局触发器
|
||||
}).ToArray();
|
||||
_ = Task.WhenAll(tasks);
|
||||
}
|
||||
@@ -362,7 +362,7 @@ namespace Serein.NodeFlow
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 总结所有全局触发器
|
||||
/// 终结所有全局触发器
|
||||
/// </summary>
|
||||
private void TerminateAllGlobalFlipflop()
|
||||
{
|
||||
|
||||
@@ -882,16 +882,40 @@ namespace Serein.WorkBench
|
||||
{
|
||||
|
||||
var contextMenu = new ContextMenu();
|
||||
var nodeGuid = nodeControl.ViewModel?.Node?.Guid;
|
||||
#region 触发器节点
|
||||
|
||||
if(nodeControl.ViewModel?.Node.ControlType == NodeControlType.Flipflop)
|
||||
{
|
||||
contextMenu.Items.Add(CreateMenuItem("启动触发器", (s, e) =>
|
||||
{
|
||||
if (s is MenuItem menuItem)
|
||||
{
|
||||
if (menuItem.Header.ToString() == "启动触发器")
|
||||
{
|
||||
FlowEnvironment.ActivateFlipflopNode(nodeGuid);
|
||||
|
||||
|
||||
if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
|
||||
menuItem.Header = "终结触发器";
|
||||
}
|
||||
else
|
||||
{
|
||||
FlowEnvironment.TerminateFlipflopNode(nodeGuid);
|
||||
menuItem.Header = "启动触发器";
|
||||
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (nodeControl?.ViewModel?.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
|
||||
{
|
||||
contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
|
||||
{
|
||||
DisplayReturnTypeTreeViewer(returnType);
|
||||
}));
|
||||
}
|
||||
var nodeGuid = nodeControl.ViewModel?.Node?.Guid;
|
||||
|
||||
#region 右键菜单功能 - 中断
|
||||
|
||||
@@ -926,7 +950,7 @@ namespace Serein.WorkBench
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream)));
|
||||
|
||||
|
||||
|
||||
|
||||
#region 右键菜单功能 - 控件对齐
|
||||
|
||||
var AvoidMenu = new MenuItem();
|
||||
|
||||
Reference in New Issue
Block a user