mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-20 00:06:45 +08:00
修改了无法保存项目文件的bug
This commit is contained in:
@@ -14,7 +14,17 @@ using Type = System.Type;
|
||||
|
||||
namespace Serein.Library.Core.Http
|
||||
{
|
||||
/*
|
||||
Router类负责解析请求的url,url参数,boby参数
|
||||
根据url
|
||||
|
||||
web server 监听类,监听外部的请求
|
||||
router 选择对应的控制器
|
||||
agent 负责传入对应的参数,注入依赖
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -22,7 +32,6 @@ namespace Serein.Library.Core.Http
|
||||
/// </summary>
|
||||
public class Router
|
||||
{
|
||||
|
||||
private readonly ConcurrentDictionary<string, bool> _controllerAutoHosting; // 存储是否实例化
|
||||
private readonly ConcurrentDictionary<string, Type> _controllerTypes; // 存储控制器类型
|
||||
private readonly ConcurrentDictionary<string, object> _controllerInstances; // 存储控制器实例对象
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Serein.Library.Core.NodeFlow
|
||||
|
||||
public Task CreateTimingTask(Action action, int time = 100, int count = -1)
|
||||
{
|
||||
NodeRunCts ??= SereinIoc.GetOrInstantiate<NodeRunCts>();
|
||||
NodeRunCts ??= SereinIoc.GetOrRegisterInstantiate<NodeRunCts>();
|
||||
return Task.Factory.StartNew(async () =>
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Serein.Library.Framework.NodeFlow
|
||||
if(NodeRunCts == null)
|
||||
{
|
||||
|
||||
NodeRunCts = SereinIoc.GetOrInstantiate<NodeRunCts>();
|
||||
NodeRunCts = SereinIoc.GetOrRegisterInstantiate<NodeRunCts>();
|
||||
}
|
||||
return Task.Factory.StartNew(async () =>
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<OutputPath>..\.Output\Debug\net8.0-windows7.0\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace Serein.Library.Api
|
||||
public delegate void FlowRunCompleteHandler(FlowEventArgs eventArgs);
|
||||
|
||||
/// <summary>
|
||||
/// 加载项目文件时成功加载了节点
|
||||
/// 项目加载完成
|
||||
/// </summary>
|
||||
public delegate void LoadNodeHandler(LoadNodeEventArgs eventArgs);
|
||||
public delegate void ProjectLoadedHandler(ProjectLoadedEventArgs eventArgs);
|
||||
|
||||
/// <summary>
|
||||
/// 加载项目文件时成功加载了DLL文件
|
||||
@@ -63,23 +63,30 @@ namespace Serein.Library.Api
|
||||
/// </summary>
|
||||
public string ErrorTips { get; protected set; } = string.Empty;
|
||||
}
|
||||
public class LoadNodeEventArgs : FlowEventArgs
|
||||
{
|
||||
public LoadNodeEventArgs(NodeInfo NodeInfo, MethodDetails MethodDetailss)
|
||||
{
|
||||
this.NodeInfo = NodeInfo;
|
||||
this.MethodDetailss = MethodDetailss;
|
||||
}
|
||||
/// <summary>
|
||||
/// 项目文件节点信息参数
|
||||
/// </summary>
|
||||
public NodeInfo NodeInfo { get; protected set; }
|
||||
/// <summary>
|
||||
/// 已加载在环境中的方法描述
|
||||
/// </summary>
|
||||
public MethodDetails MethodDetailss { get; protected set; }
|
||||
}
|
||||
|
||||
//public class LoadNodeEventArgs : FlowEventArgs
|
||||
//{
|
||||
// public LoadNodeEventArgs(NodeInfo NodeInfo, MethodDetails MethodDetailss)
|
||||
// {
|
||||
// this.NodeInfo = NodeInfo;
|
||||
// this.MethodDetailss = MethodDetailss;
|
||||
// }
|
||||
// /// <summary>
|
||||
// /// 项目文件节点信息参数
|
||||
// /// </summary>
|
||||
// public NodeInfo NodeInfo { get; protected set; }
|
||||
// /// <summary>
|
||||
// /// 已加载在环境中的方法描述
|
||||
// /// </summary>
|
||||
// public MethodDetails MethodDetailss { get; protected set; }
|
||||
//}
|
||||
|
||||
public class ProjectLoadedEventArgs : FlowEventArgs
|
||||
{
|
||||
public ProjectLoadedEventArgs()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class LoadDLLEventArgs : FlowEventArgs
|
||||
{
|
||||
@@ -143,14 +150,25 @@ namespace Serein.Library.Api
|
||||
|
||||
public class NodeCreateEventArgs : FlowEventArgs
|
||||
{
|
||||
public NodeCreateEventArgs(object nodeModel)
|
||||
public NodeCreateEventArgs(object nodeModel, Position position)
|
||||
{
|
||||
this.NodeModel = nodeModel;
|
||||
this.Position = position;
|
||||
}
|
||||
public NodeCreateEventArgs(object nodeModel, bool isAddInRegion, string regeionGuid)
|
||||
{
|
||||
this.NodeModel = nodeModel;
|
||||
this.RegeionGuid = regeionGuid;
|
||||
this.IsAddInRegion = isAddInRegion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点Model对象,目前需要手动转换对应的类型
|
||||
/// </summary>
|
||||
public object NodeModel { get; private set; }
|
||||
public Position Position { get; private set; }
|
||||
public bool IsAddInRegion { get; private set; }
|
||||
public string RegeionGuid { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -189,13 +207,14 @@ namespace Serein.Library.Api
|
||||
/// 新的起始节点Guid
|
||||
/// </summary>
|
||||
public string NewNodeGuid { get; private set; }
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public interface IFlowEnvironment
|
||||
{
|
||||
|
||||
event FlowRunCompleteHandler OnFlowRunComplete;
|
||||
event LoadNodeHandler OnLoadNode;
|
||||
event ProjectLoadedHandler OnProjectLoaded;
|
||||
event LoadDLLHandler OnDllLoad;
|
||||
event NodeConnectChangeHandler OnNodeConnectChange;
|
||||
event NodeCreateHandler OnNodeCreate;
|
||||
@@ -256,7 +275,7 @@ namespace Serein.Library.Api
|
||||
/// </summary>
|
||||
/// <param name="nodeBase">节点/区域/基础控件</param>
|
||||
/// <param name="methodDetails">节点绑定的方法说明(</param>
|
||||
void CreateNode(NodeControlType nodeBase, MethodDetails methodDetails = null);
|
||||
void CreateNode(NodeControlType nodeBase, Position position, MethodDetails methodDetails = null);
|
||||
/// <summary>
|
||||
/// 移除两个节点之间的连接关系
|
||||
/// </summary>
|
||||
|
||||
@@ -20,11 +20,11 @@ namespace Serein.Library.Api
|
||||
/// <summary>
|
||||
/// 获取或创建并注入目标类型
|
||||
/// </summary>
|
||||
T GetOrInstantiate<T>();
|
||||
T GetOrRegisterInstantiate<T>();
|
||||
/// <summary>
|
||||
/// 获取或创建并注入目标类型
|
||||
/// </summary>
|
||||
object GetOrInstantiate(Type type);
|
||||
object GetOrRegisterInstantiate(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// 创建目标类型的对象, 并注入依赖项
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Serein.Library.Entity
|
||||
MethodName = MethodName,
|
||||
MethodLockName = MethodLockName,
|
||||
IsNetFramework = IsNetFramework,
|
||||
ExplicitDatas = ExplicitDatas.Select(it => it.Clone()).ToArray(),
|
||||
ExplicitDatas = ExplicitDatas?.Select(it => it.Clone()).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -54,13 +56,13 @@ namespace Serein.Library.Entity
|
||||
/// 画布
|
||||
/// </summary>
|
||||
|
||||
public FlowCanvas canvas { get; set; }
|
||||
public FlowCanvas Canvas { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 版本
|
||||
/// </summary>
|
||||
|
||||
public string versions { get; set; }
|
||||
public string Versions { get; set; }
|
||||
|
||||
// 预览位置
|
||||
|
||||
@@ -74,11 +76,11 @@ namespace Serein.Library.Entity
|
||||
/// <summary>
|
||||
/// 宽度
|
||||
/// </summary>
|
||||
public float width { get; set; }
|
||||
public float Width { get; set; }
|
||||
/// <summary>
|
||||
/// 高度
|
||||
/// </summary>
|
||||
public float lenght { get; set; }
|
||||
public float Lenght { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -156,7 +158,7 @@ namespace Serein.Library.Entity
|
||||
/// <summary>
|
||||
/// 如果是区域控件,则会存在子项。
|
||||
/// </summary>
|
||||
public NodeInfo[] ChildNodes { get; set; }
|
||||
public string[] ChildNodeGuids { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -173,9 +175,9 @@ namespace Serein.Library.Entity
|
||||
|
||||
public class Parameterdata
|
||||
{
|
||||
public bool state { get; set; }
|
||||
public string value { get; set; }
|
||||
public string expression { get; set; }
|
||||
public bool State { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Expression { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@@ -185,8 +187,13 @@ namespace Serein.Library.Entity
|
||||
/// </summary>
|
||||
public class Position
|
||||
{
|
||||
public float X { get; set; }
|
||||
public float Y { get; set; }
|
||||
public Position(double x, double y)
|
||||
{
|
||||
this.X = x; this.Y = y;
|
||||
}
|
||||
|
||||
public double X { get; set; } = 0;
|
||||
public double Y { get; set; } = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
116
Library/Http/Attribute.cs
Normal file
116
Library/Http/Attribute.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
|
||||
namespace Serein.Library.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示参数为url中的数据(Get请求中不需要显式标注)
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class IsUrlDataAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示入参参数为整个boby的数据
|
||||
/// <para>
|
||||
/// 例如:User类型含有int id、string name字段</para>
|
||||
/// <para>
|
||||
/// ① Add(User user)</para>
|
||||
/// <para>请求需要传入的json为
|
||||
/// {"user":{
|
||||
/// "id":2,
|
||||
/// "name":"李志忠"}}</para>
|
||||
/// <para>
|
||||
/// ② Add([Boby]User user)</para>
|
||||
/// <para>请求需要传入的json为
|
||||
/// {"id":2,"name":"李志忠"}</para>
|
||||
///
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class IsBobyDataAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示该控制器会被自动注册(与程序集同一命名空间,暂时不支持运行时自动加载DLL,需要手动注册)
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class AutoHostingAttribute(string url = "") : Attribute
|
||||
{
|
||||
public string Url { get; } = url;
|
||||
}
|
||||
/// <summary>
|
||||
/// 表示该属性为自动注入依赖项
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class AutoInjectionAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 方法的接口类型与附加URL
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 假设UserController.Add()的WebAPI特性中
|
||||
/// http是HTTP.POST
|
||||
/// url被显示标明“temp”
|
||||
/// 那么请求的接口是POST,URL是
|
||||
/// [http://localhost:8080]/user/add/temp
|
||||
/// </remarks>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="url"></param>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
|
||||
public sealed class WebApiAttribute() : Attribute
|
||||
|
||||
{
|
||||
public API Type ;
|
||||
public string Url ;
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl;
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
|
||||
public sealed class ApiPostAttribute() : Attribute
|
||||
|
||||
{
|
||||
public string Url;
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl = true;
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
|
||||
public sealed class ApiGetAttribute() : Attribute
|
||||
|
||||
{
|
||||
public string Url;
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl = true;
|
||||
}
|
||||
|
||||
/*public sealed class WebApiAttribute(API http, bool isUrl = true, string url = "") : Attribute
|
||||
{
|
||||
public API Http { get; } = http;
|
||||
public string Url { get; } = url;
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl { get; } = isUrl;
|
||||
}*/
|
||||
public enum API
|
||||
{
|
||||
POST,
|
||||
GET,
|
||||
//PUT,
|
||||
//DELETE
|
||||
}
|
||||
}
|
||||
19
Library/Http/ControllerBase.cs
Normal file
19
Library/Http/ControllerBase.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Serein.Library.Http
|
||||
{
|
||||
public class ControllerBase
|
||||
{
|
||||
|
||||
public string Url { get; set; }
|
||||
|
||||
public string BobyData { get; set; }
|
||||
|
||||
public string GetLog(Exception ex)
|
||||
{
|
||||
return "Url : " + Url + Environment.NewLine +
|
||||
"Ex : " + ex.Message + Environment.NewLine +
|
||||
"Data : " + BobyData + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
768
Library/Http/Router.cs
Normal file
768
Library/Http/Router.cs
Normal file
@@ -0,0 +1,768 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Enum = System.Enum;
|
||||
using Type = System.Type;
|
||||
|
||||
namespace Serein.Library.Http
|
||||
{
|
||||
/*
|
||||
Router类负责解析请求的url,url参数,boby参数
|
||||
根据url
|
||||
|
||||
web server 监听类,监听外部的请求
|
||||
router 选择对应的控制器
|
||||
agent 负责传入对应的参数,注入依赖
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 路由注册与解析
|
||||
/// </summary>
|
||||
public class Router
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, bool> _controllerAutoHosting; // 存储是否实例化
|
||||
private readonly ConcurrentDictionary<string, Type> _controllerTypes; // 存储控制器类型
|
||||
private readonly ConcurrentDictionary<string, object> _controllerInstances; // 存储控制器实例对象
|
||||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>> _routes; // 用于存储路由信息
|
||||
|
||||
private readonly SereinIOC serviceRegistry; // 用于存储路由信息
|
||||
|
||||
//private Type PostRequest;
|
||||
|
||||
public Router(ISereinIOC serviceRegistry) // 构造函数,初始化 Router 类的新实例
|
||||
{
|
||||
this.serviceRegistry = serviceRegistry;
|
||||
|
||||
_routes = new ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>>(); // 初始化路由字典
|
||||
|
||||
_controllerAutoHosting = new ConcurrentDictionary<string, bool>(); // 初始化控制器实例对象字典
|
||||
_controllerTypes = new ConcurrentDictionary<string, Type>(); // 初始化控制器实例对象字典
|
||||
_controllerInstances = new ConcurrentDictionary<string, object>(); // 初始化控制器实例对象字典
|
||||
|
||||
foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值
|
||||
{
|
||||
_routes.TryAdd(method.ToString(), new ConcurrentDictionary<string, MethodInfo>()); // 初始化每种 HTTP 方法对应的路由字典
|
||||
}
|
||||
|
||||
// 获取当前程序集
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
// 获取包含“Controller”名称的类型
|
||||
var controllerTypes = assembly.GetTypes()
|
||||
.Where(t => t.Name.Contains("Controller"));
|
||||
|
||||
Type baseAttribute = typeof(AutoHostingAttribute);
|
||||
Type baseController = typeof(ControllerBase);
|
||||
foreach (var controllerType in controllerTypes)
|
||||
{
|
||||
if (controllerType.IsSubclassOf(baseController) && controllerType.IsDefined(baseAttribute))
|
||||
{
|
||||
|
||||
// 如果属于控制器,并标记了AutoHosting特性,进行自动注册
|
||||
AutoRegisterAutoController(controllerType);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 自动注册 自动实例化控制器 类型
|
||||
/// </summary>
|
||||
/// <param name="controllerType"></param>
|
||||
public void AutoRegisterAutoController(Type controllerType) // 方法声明,用于注册并实例化控制器类型
|
||||
{
|
||||
if (!controllerType.IsClass || controllerType.IsAbstract) return; // 如果不是类或者是抽象类,则直接返回
|
||||
|
||||
var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
|
||||
if (autoHostingAttribute != null) {
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var apiGetAttribute = method.GetCustomAttribute<ApiGetAttribute>();
|
||||
var apiPostAttribute = method.GetCustomAttribute<ApiPostAttribute>();
|
||||
if( apiGetAttribute == null && apiPostAttribute == null )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
WebApiAttribute webApiAttribute = new WebApiAttribute()
|
||||
{
|
||||
Type = apiGetAttribute != null ? API.GET : API.POST,
|
||||
Url = apiGetAttribute != null ? apiGetAttribute.Url : apiPostAttribute.Url,
|
||||
IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl,
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (apiPostAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
var url = AddRoutesUrl(autoHostingAttribute,
|
||||
webApiAttribute,
|
||||
controllerType, method);
|
||||
Console.WriteLine(url);
|
||||
if (url == null) continue;
|
||||
_controllerAutoHosting[url] = true;
|
||||
_controllerTypes[url] = controllerType;
|
||||
|
||||
_controllerInstances[url] = null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method);
|
||||
Console.WriteLine(url);
|
||||
if (url == null) continue;
|
||||
_controllerAutoHosting[url] = true;
|
||||
_controllerTypes[url] = controllerType;
|
||||
_controllerInstances[url] = null;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 手动注册 自动实例化控制器实例
|
||||
/// </summary>
|
||||
public void RegisterAutoController<T>() // 方法声明,用于动态注册路由
|
||||
{
|
||||
Type controllerType = typeof(T); // 获取控制器实例的类型
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var apiGetAttribute = method.GetCustomAttribute<ApiGetAttribute>();
|
||||
var apiPostAttribute = method.GetCustomAttribute<ApiPostAttribute>();
|
||||
if (apiGetAttribute == null && apiPostAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
WebApiAttribute webApiAttribute = new WebApiAttribute()
|
||||
{
|
||||
Type = apiGetAttribute != null ? API.GET : API.POST,
|
||||
Url = apiGetAttribute != null ? apiGetAttribute.Url : apiPostAttribute.Url,
|
||||
IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl,
|
||||
};
|
||||
|
||||
|
||||
|
||||
var url = AddRoutesUrl(null, webApiAttribute, controllerType, method);
|
||||
|
||||
if (url == null) continue;
|
||||
_controllerAutoHosting[url] = true;
|
||||
_controllerTypes[url] = controllerType;
|
||||
|
||||
_controllerInstances[url] = null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 手动注册 实例持久控制器实例
|
||||
/// </summary>
|
||||
/// <param name="controllerInstance"></param>
|
||||
public void RegisterController<TController>(TController controllerInstance) where TController : ControllerBase // 方法声明,用于动态注册路由
|
||||
{
|
||||
if(controllerInstance == null) return;
|
||||
Type controllerType = controllerInstance.GetType(); // 获取控制器实例的类型
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var apiGetAttribute = method.GetCustomAttribute<ApiGetAttribute>();
|
||||
var apiPostAttribute = method.GetCustomAttribute<ApiPostAttribute>();
|
||||
if (apiGetAttribute == null && apiPostAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
WebApiAttribute webApiAttribute = new WebApiAttribute()
|
||||
{
|
||||
Type = apiGetAttribute != null ? API.GET : API.POST,
|
||||
Url = apiGetAttribute != null ? apiGetAttribute.Url : apiPostAttribute.Url,
|
||||
IsUrl = apiGetAttribute != null ? apiGetAttribute.IsUrl : apiPostAttribute.IsUrl,
|
||||
};
|
||||
|
||||
|
||||
|
||||
var url = AddRoutesUrl(null, webApiAttribute, controllerType, method);
|
||||
|
||||
if (url == null) continue;
|
||||
_controllerInstances[url] = controllerInstance;
|
||||
_controllerAutoHosting[url] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从方法中收集路由信息
|
||||
/// </summary>
|
||||
/// <param name="controllerType"></param>
|
||||
public string AddRoutesUrl(AutoHostingAttribute autoHostingAttribute, WebApiAttribute webAttribute, Type controllerType, MethodInfo method)
|
||||
{
|
||||
string controllerName;
|
||||
if (autoHostingAttribute == null || string.IsNullOrWhiteSpace(autoHostingAttribute.Url))
|
||||
{
|
||||
controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写
|
||||
}
|
||||
else
|
||||
{
|
||||
controllerName = autoHostingAttribute.Url;
|
||||
}
|
||||
|
||||
var httpMethod = webAttribute.Type; // 获取 HTTP 方法
|
||||
var customUrl = webAttribute.Url; // 获取自定义 URL
|
||||
|
||||
string url;
|
||||
|
||||
if (webAttribute.IsUrl)
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
{
|
||||
url = $"/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL
|
||||
}
|
||||
else
|
||||
{
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
{
|
||||
url = $"/{controllerName}".ToLower(); // 构建默认 URL
|
||||
}
|
||||
else
|
||||
{
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/{controllerName}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
|
||||
return url;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 收集路由信息
|
||||
/// </summary>
|
||||
/// <param name="controllerType"></param>
|
||||
public void CollectRoutes(Type controllerType)
|
||||
{
|
||||
string controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
var customUrl = routeAttribute.Url; // 获取自定义 URL
|
||||
string url;
|
||||
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
{
|
||||
url = $"/api/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL
|
||||
}
|
||||
else
|
||||
{
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/api/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
var httpMethod = routeAttribute.Type; // 获取 HTTP 方法
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解析路由,调用对应的方法
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> RouteAsync(HttpListenerContext context)
|
||||
{
|
||||
var request = context.Request; // 获取请求对象
|
||||
var response = context.Response; // 获取响应对象
|
||||
var url = request.Url; // 获取请求的 URL
|
||||
var httpMethod = request.HttpMethod; // 获取请求的 HTTP 方法
|
||||
|
||||
var template = request.Url.AbsolutePath.ToLower();
|
||||
|
||||
|
||||
if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var routeValues = GetUrlData(url); // 解析 URL 获取路由参数
|
||||
|
||||
ControllerBase controllerInstance;
|
||||
if (!_controllerAutoHosting[template])
|
||||
{
|
||||
controllerInstance = (ControllerBase)_controllerInstances[template];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
controllerInstance = (ControllerBase)serviceRegistry.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (controllerInstance == null)
|
||||
{
|
||||
return false; // 未找到控制器实例
|
||||
}
|
||||
|
||||
controllerInstance.Url = url.AbsolutePath;
|
||||
object result;
|
||||
switch (httpMethod) // 根据请求的 HTTP 方法执行不同的操作
|
||||
{
|
||||
case "GET": // 如果是 GET 请求,传入方法、控制器、url参数
|
||||
result = InvokeControllerMethodWithRouteValues(method, controllerInstance, routeValues);
|
||||
break;
|
||||
case "POST": // POST 请求传入方法、控制器、请求体内容,url参数
|
||||
var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
|
||||
controllerInstance.BobyData = requestBody;
|
||||
var requestJObject = requestBody.FromJSON<object>();
|
||||
|
||||
result = InvokeControllerMethod(method, controllerInstance, requestJObject, routeValues);
|
||||
break;
|
||||
default:
|
||||
|
||||
result = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Return(response, result); // 返回结果
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetLog(string Url, string BobyData = "")
|
||||
{
|
||||
return Environment.NewLine +
|
||||
"Url : " + Url + Environment.NewLine +
|
||||
"Data : " + BobyData + Environment.NewLine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GET请求的控制器方法
|
||||
/// </summary>
|
||||
private object InvokeControllerMethodWithRouteValues(MethodInfo method, object controllerInstance, Dictionary<string, string> routeValues)
|
||||
{
|
||||
object[] parameters = GetMethodParameters(method, routeValues);
|
||||
return InvokeMethod(method, controllerInstance, parameters);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<MethodInfo, ParameterInfo[]> methodParameterCache = new Dictionary<MethodInfo, ParameterInfo[]>();
|
||||
/// <summary>
|
||||
/// POST请求的调用控制器方法
|
||||
/// </summary>
|
||||
public object InvokeControllerMethod(MethodInfo method, object controllerInstance, dynamic requestData, Dictionary<string, string> routeValues)
|
||||
{
|
||||
object[] cachedMethodParameters;
|
||||
|
||||
if (!methodParameterCache.TryGetValue(method, out ParameterInfo[] 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(IsUrlDataAttribute)) != null;
|
||||
bool isBobyData = parameters[i].GetCustomAttribute(typeof(IsBobyDataAttribute)) != null;
|
||||
|
||||
if (isUrlData)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(paramName) && routeValues.TryGetValue(paramName, out string value))
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(value, parameters[i].ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = null;
|
||||
}
|
||||
}
|
||||
else if (isBobyData)
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(requestData.ToString(), parameters[i].ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requestData.ContainsKey(paramName))
|
||||
{
|
||||
if (parameters[i].ParameterType == typeof(string))
|
||||
{
|
||||
cachedMethodParameters[i] = requestData[paramName].ToString();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(bool))
|
||||
{
|
||||
cachedMethodParameters[i] = requestData[paramName?.ToLower()].ToBool();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(int))
|
||||
{
|
||||
cachedMethodParameters[i] = requestData[paramName].ToInt();
|
||||
}
|
||||
else if (parameters[i].ParameterType == typeof(double))
|
||||
{
|
||||
cachedMethodParameters[i] = requestData[paramName].ToDouble();
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = ConvertValue(requestData[paramName], parameters[i].ParameterType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedMethodParameters[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存方法和参数的映射
|
||||
//methodParameterCache[method] = cachedMethodParameters;
|
||||
|
||||
|
||||
// 调用方法
|
||||
|
||||
return method.Invoke(controllerInstance, cachedMethodParameters);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <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.TryGetValue(paramName, out string value))
|
||||
{
|
||||
parameters[i] = ConvertValue(value, 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
|
||||
{
|
||||
return JsonConvert.DeserializeObject(value.ToString(), targetType);
|
||||
}
|
||||
catch (JsonReaderException 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>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <returns></returns>
|
||||
private object ConvertValue(string value, Type targetType)
|
||||
{
|
||||
if(targetType == typeof(string))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
return JsonConvert.DeserializeObject(value.ToString(), targetType);
|
||||
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
// 如果无法转为对应的JSON对象
|
||||
int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引
|
||||
int endIndex = ex.Message.IndexOf('\''); // 查找类型信息结束的索引
|
||||
var typeInfo = ex.Message[startIndex..endIndex]; // 提取出错类型信息,该怎么传出去?
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
catch // (Exception ex)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用控制器方法传入参数
|
||||
/// </summary>
|
||||
/// <param name="method">方法</param>
|
||||
/// <param name="controllerInstance">控制器实例</param>
|
||||
/// <param name="methodParameters">参数列表</param>
|
||||
/// <returns></returns>
|
||||
private static 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('\'');
|
||||
// 提取类型信息
|
||||
string typeInfo = ex.Message[startIndex..endIndex];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
|
||||
return result; // 调用方法并返回结果
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 方法声明,用于解析 URL 获取路由参数
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
private static Dictionary<string, string> GetUrlData(Uri uri)
|
||||
{
|
||||
Dictionary<string, string> routeValues = [];
|
||||
|
||||
var pathParts = uri.ToString().Split('?'); // 拆分 URL,获取路径部分
|
||||
|
||||
if (pathParts.Length > 1) // 如果包含查询字符串
|
||||
{
|
||||
var queryParams = HttpUtility.ParseQueryString(pathParts[1]); // 解析查询字符串
|
||||
|
||||
foreach (string key in queryParams) // 遍历查询字符串的键值对
|
||||
{
|
||||
if (key == null) continue;
|
||||
|
||||
routeValues[key] = queryParams[key]; // 将键值对添加到路由参数字典中
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return routeValues; // 返回路由参数字典
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取Body中的消息
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task<string> ReadRequestBodyAsync(HttpListenerRequest request)
|
||||
{
|
||||
using (Stream stream = request.InputStream)
|
||||
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
return await reader.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 返回响应消息
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="msg"></param>
|
||||
private static void Return(HttpListenerResponse response, dynamic msg)
|
||||
{
|
||||
string resultData;
|
||||
if (response != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg is IEnumerable && msg is not string)
|
||||
{
|
||||
// If msg is a collection (e.g., array or list), serialize it as JArray
|
||||
resultData = JArray.FromObject(msg).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, serialize it as JObject
|
||||
resultData = JObject.FromObject(msg).ToString();
|
||||
}
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(resultData);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If serialization fails, use the original message's string representation
|
||||
resultData = msg.ToString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析JSON
|
||||
/// </summary>
|
||||
/// <param name="requestBody"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private static dynamic ParseJson(string requestBody)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(requestBody))
|
||||
{
|
||||
throw new Exception("Invalid JSON format");
|
||||
}
|
||||
return JObject.Parse(requestBody);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new Exception("Invalid JSON format");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修正方法特性中的URL格式
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
private static string CleanUrl(string url)
|
||||
{
|
||||
|
||||
while (url.Length > 0 && url[0] == '/') // 去除开头的斜杠
|
||||
{
|
||||
url = url[1..];
|
||||
}
|
||||
|
||||
while (url.Length > 0 && url[^1] == '/') // 去除末尾的斜杠
|
||||
{
|
||||
url = url[..^1];
|
||||
}
|
||||
|
||||
for (int i = 0; i < url.Length - 1; i++) // 去除连续的斜杠
|
||||
{
|
||||
if (url[i] == '/' && url[i + 1] == '/')
|
||||
{
|
||||
url = url.Remove(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return url; // 返回清理后的 URL
|
||||
}
|
||||
/// <summary>
|
||||
/// 从控制器调用方法的异常中获取出出错类型的信息
|
||||
/// </summary>
|
||||
/// <param name="errorMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static string ExtractTargetTypeFromExceptionMessage(string errorMessage)
|
||||
{
|
||||
string targetText = "为类型“";
|
||||
int startIndex = errorMessage.IndexOf(targetText);
|
||||
if (startIndex != -1)
|
||||
{
|
||||
startIndex += targetText.Length;
|
||||
int endIndex = errorMessage.IndexOf('\'');
|
||||
if (endIndex != -1)
|
||||
{
|
||||
return errorMessage[startIndex..endIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
190
Library/Http/WebAPIAttribute.cs
Normal file
190
Library/Http/WebAPIAttribute.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Http
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// HTTP接口监听类
|
||||
/// </summary>
|
||||
public class WebServer
|
||||
{
|
||||
private readonly HttpListener listener; // HTTP 监听器
|
||||
private Router router; // 路由器
|
||||
private readonly RequestLimiter requestLimiter; //接口防刷
|
||||
|
||||
|
||||
|
||||
public WebServer()
|
||||
|
||||
{
|
||||
listener = new HttpListener();
|
||||
|
||||
requestLimiter = new RequestLimiter(5, 8);
|
||||
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
public WebServer Start(string prefixe, ISereinIOC serviceContainer)
|
||||
{
|
||||
try
|
||||
{
|
||||
router = new Router(serviceContainer);
|
||||
if (listener.IsListening)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!prefixe.Substring(prefixe.Length - 1, 1).Equals(@"/"))
|
||||
{
|
||||
prefixe += @"/";
|
||||
}
|
||||
|
||||
|
||||
listener.Prefixes.Add(prefixe); // 添加监听前缀
|
||||
listener.Start(); // 开始监听
|
||||
|
||||
Console.WriteLine($"开始监听:{prefixe}");
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (listener.IsListening)
|
||||
{
|
||||
var context = await listener.GetContextAsync(); // 获取请求上下文
|
||||
_ = Task.Run(() => ProcessRequestAsync(context)); // 处理请求)
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
catch (HttpListenerException ex) when (ex.ErrorCode == 183)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 处理请求
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
private async Task ProcessRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
// 添加CORS头部
|
||||
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||
context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if (context.Request.HttpMethod == "OPTIONS")
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
context.Response.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
var isPass = await router.RouteAsync(context); // 路由解析
|
||||
if (isPass)
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
context.Response.Close(); // 关闭响应
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
context.Response.Close(); // 关闭响应
|
||||
}
|
||||
|
||||
//var isPass = requestLimiter.AllowRequest(context.Request);
|
||||
//if (isPass)
|
||||
//{
|
||||
// // 如果路由没有匹配,返回 404
|
||||
// router.RouteAsync(context); // 路由解析
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// context.Response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误
|
||||
// context.Response.Close(); // 关闭响应
|
||||
//}
|
||||
|
||||
// var request = context.Request;
|
||||
// 获取远程终结点信息
|
||||
//var remoteEndPoint = context.Request.RemoteEndPoint;
|
||||
//// 获取用户的IP地址和端口
|
||||
//IPAddress ipAddress = remoteEndPoint.Address;
|
||||
//int port = remoteEndPoint.Port;
|
||||
//Console.WriteLine("外部连接:" + ipAddress.ToString() + ":" + port);
|
||||
}
|
||||
|
||||
// 停止服务器
|
||||
public void Stop()
|
||||
{
|
||||
if (listener.IsListening)
|
||||
{
|
||||
listener?.Stop(); // 停止监听
|
||||
listener?.Close(); // 关闭监听器
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterAutoController<T>()
|
||||
{
|
||||
//var instance = Activator.CreateInstance(typeof(T));
|
||||
router.RegisterAutoController<T>();
|
||||
}
|
||||
|
||||
/*public void RegisterRoute<T>(T controllerInstance)
|
||||
{
|
||||
router.RegisterRoute(controllerInstance);
|
||||
}*/
|
||||
}
|
||||
/// <summary>
|
||||
/// 判断访问接口的频次是否正常
|
||||
/// </summary>
|
||||
public class RequestLimiter(int seconds, int maxRequests)
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Queue<DateTime>> requestHistory = new ();
|
||||
private readonly TimeSpan interval = TimeSpan.FromSeconds(seconds);
|
||||
private readonly int maxRequests = maxRequests;
|
||||
|
||||
/// <summary>
|
||||
/// 判断访问接口的频次是否正常
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool AllowRequest(HttpListenerRequest request)
|
||||
{
|
||||
var clientIp = request.RemoteEndPoint.Address.ToString();
|
||||
var clientPort = request.RemoteEndPoint.Port;
|
||||
var clientKey = clientIp + ":" + clientPort;
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 尝试从字典中获取请求队列,不存在则创建新的队列
|
||||
var requests = requestHistory.GetOrAdd(clientKey, new Queue<DateTime>());
|
||||
|
||||
lock (requests)
|
||||
{
|
||||
// 移除超出时间间隔的请求记录
|
||||
while (requests.Count > 0 && now - requests.Peek() > interval)
|
||||
{
|
||||
requests.Dequeue();
|
||||
}
|
||||
|
||||
// 如果请求数超过限制,拒绝请求
|
||||
if (requests.Count >= maxRequests)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加当前请求时间,并允许请求
|
||||
requests.Enqueue(now);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
<BaseOutputPath>D:\Project\C#\DynamicControl\SereinFlow\.Output</BaseOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Http\**" />
|
||||
<Compile Remove="Utils\SerinExpression\**" />
|
||||
<EmbeddedResource Remove="Http\**" />
|
||||
<EmbeddedResource Remove="Utils\SerinExpression\**" />
|
||||
<None Remove="Http\**" />
|
||||
<None Remove="Utils\SerinExpression\**" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Attributes;
|
||||
using Serein.Library.Web;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@@ -14,41 +15,46 @@ namespace Serein.Library.Utils
|
||||
public class SereinIOC : ISereinIOC
|
||||
{
|
||||
/// <summary>
|
||||
/// 实例集合
|
||||
/// 类型集合,暂放待实例化的类型,完成实例化之后移除
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Type> _typeMappings;
|
||||
|
||||
/// <summary>
|
||||
/// 实例集合(包含已完成注入、未完成注入的对象实例,计划在未来的版本中区分:)
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, object> _dependencies;
|
||||
|
||||
/// <summary>
|
||||
/// 未完成注入的实例集合。
|
||||
/// 键:需要的类型名称
|
||||
/// 值:对象实例(存储对象)
|
||||
/// 值:元组(对象实例,对象的属性)
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, List<(object,PropertyInfo)>> _unfinishedDependencies;
|
||||
|
||||
/// <summary>
|
||||
/// 类型集合
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Type> _typeMappings;
|
||||
|
||||
/// <summary>
|
||||
/// 待实例化的类型
|
||||
/// </summary>
|
||||
private readonly List<Type> _waitingForInstantiation;
|
||||
// private readonly List<Type> _waitingForInstantiation;
|
||||
|
||||
public SereinIOC()
|
||||
{
|
||||
// 首先注册自己
|
||||
_dependencies = new ConcurrentDictionary<string, object>
|
||||
{
|
||||
[typeof(ISereinIOC).FullName] = this
|
||||
};
|
||||
_typeMappings = new ConcurrentDictionary<string, Type>
|
||||
{
|
||||
[typeof(ISereinIOC).FullName] = typeof(ISereinIOC)
|
||||
};
|
||||
_dependencies = new ConcurrentDictionary<string, object>();
|
||||
_typeMappings = new ConcurrentDictionary<string, Type>();
|
||||
_unfinishedDependencies = new ConcurrentDictionary<string, List<(object, PropertyInfo)>>();
|
||||
_waitingForInstantiation = new List<Type>();
|
||||
}
|
||||
|
||||
|
||||
public void InitRegister()
|
||||
{
|
||||
_dependencies[typeof(ISereinIOC).FullName] = this;
|
||||
Register<IRouter, Router>();
|
||||
/*foreach (var type in _typeMappings.Values)
|
||||
{
|
||||
Register(type);
|
||||
}
|
||||
Build();*/
|
||||
}
|
||||
|
||||
#region 类型的注册
|
||||
|
||||
@@ -59,7 +65,7 @@ namespace Serein.Library.Utils
|
||||
/// <param name="parameters">参数</param>
|
||||
public ISereinIOC Register(Type type, params object[] parameters)
|
||||
{
|
||||
RegisterType(type.FullName, type);
|
||||
RegisterType(type?.FullName, type);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
@@ -74,7 +80,11 @@ namespace Serein.Library.Utils
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册接口类型
|
||||
/// </summary>
|
||||
/// <param name="type">目标类型</param>
|
||||
/// <param name="parameters">参数</param>
|
||||
public ISereinIOC Register<TService, TImplementation>(params object[] parameters)
|
||||
where TImplementation : TService
|
||||
{
|
||||
@@ -88,7 +98,7 @@ namespace Serein.Library.Utils
|
||||
/// <summary>
|
||||
/// 尝试从容器中获取对象,如果不存在目标类型的对象,则将类型信息登记到容器,并实例化注入依赖项。
|
||||
/// </summary>
|
||||
public object GetOrInstantiate(Type type)
|
||||
public object GetOrRegisterInstantiate(Type type)
|
||||
{
|
||||
// 尝试从容器中获取对象
|
||||
if (!_dependencies.TryGetValue(type.FullName, out object value))
|
||||
@@ -103,7 +113,7 @@ namespace Serein.Library.Utils
|
||||
/// <summary>
|
||||
/// 尝试从容器中获取对象,如果不存在目标类型的对象,则将类型信息登记到容器,并实例化注入依赖项。
|
||||
/// </summary>
|
||||
public T GetOrInstantiate<T>()
|
||||
public T GetOrRegisterInstantiate<T>()
|
||||
{
|
||||
var value = Instantiate(typeof(T));
|
||||
return (T)value;
|
||||
@@ -144,7 +154,7 @@ namespace Serein.Library.Utils
|
||||
_unfinishedDependencies?.Clear();
|
||||
_typeMappings?.Clear();
|
||||
_dependencies?.Clear();
|
||||
_waitingForInstantiation?.Clear();
|
||||
// _waitingForInstantiation?.Clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -154,6 +164,7 @@ namespace Serein.Library.Utils
|
||||
/// <returns></returns>
|
||||
public ISereinIOC Build()
|
||||
{
|
||||
InitRegister();
|
||||
// 遍历已注册类型
|
||||
foreach (var type in _typeMappings.Values.ToArray())
|
||||
{
|
||||
@@ -262,29 +273,29 @@ namespace Serein.Library.Utils
|
||||
/// <summary>
|
||||
/// 再次尝试注入目标实例的依赖项
|
||||
/// </summary>
|
||||
private void TryInstantiateWaitingDependencies()
|
||||
{
|
||||
foreach (var waitingType in _waitingForInstantiation.ToList())
|
||||
{
|
||||
if (_typeMappings.TryGetValue(waitingType.FullName, out var implementationType))
|
||||
{
|
||||
var instance = Instantiate(implementationType);
|
||||
if (instance != null)
|
||||
{
|
||||
//private void TryInstantiateWaitingDependencies()
|
||||
//{
|
||||
// foreach (var waitingType in _waitingForInstantiation.ToList())
|
||||
// {
|
||||
// if (_typeMappings.TryGetValue(waitingType.FullName, out var implementationType))
|
||||
// {
|
||||
// var instance = Instantiate(implementationType);
|
||||
// if (instance != null)
|
||||
// {
|
||||
|
||||
_dependencies[waitingType.FullName] = instance;
|
||||
// _dependencies[waitingType.FullName] = instance;
|
||||
|
||||
_waitingForInstantiation.Remove(waitingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// _waitingForInstantiation.Remove(waitingType);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
#endregion
|
||||
|
||||
#region run()
|
||||
public ISereinIOC Run<T>(Action<T> action)
|
||||
{
|
||||
var service = GetOrInstantiate<T>();
|
||||
var service = GetOrRegisterInstantiate<T>();
|
||||
if (service != null)
|
||||
{
|
||||
action(service);
|
||||
@@ -294,8 +305,8 @@ namespace Serein.Library.Utils
|
||||
|
||||
public ISereinIOC Run<T1, T2>(Action<T1, T2> action)
|
||||
{
|
||||
var service1 = GetOrInstantiate<T1>();
|
||||
var service2 = GetOrInstantiate<T2>();
|
||||
var service1 = GetOrRegisterInstantiate<T1>();
|
||||
var service2 = GetOrRegisterInstantiate<T2>();
|
||||
|
||||
action(service1, service2);
|
||||
return this;
|
||||
@@ -303,69 +314,69 @@ namespace Serein.Library.Utils
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3>(Action<T1, T2, T3> action)
|
||||
{
|
||||
var service1 = GetOrInstantiate<T1>();
|
||||
var service2 = GetOrInstantiate<T2>();
|
||||
var service3 = GetOrInstantiate<T3>();
|
||||
var service1 = GetOrRegisterInstantiate<T1>();
|
||||
var service2 = GetOrRegisterInstantiate<T2>();
|
||||
var service3 = GetOrRegisterInstantiate<T3>();
|
||||
action(service1, service2, service3);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
|
||||
{
|
||||
var service1 = GetOrInstantiate<T1>();
|
||||
var service2 = GetOrInstantiate<T2>();
|
||||
var service3 = GetOrInstantiate<T3>();
|
||||
var service4 = GetOrInstantiate<T4>();
|
||||
var service1 = GetOrRegisterInstantiate<T1>();
|
||||
var service2 = GetOrRegisterInstantiate<T2>();
|
||||
var service3 = GetOrRegisterInstantiate<T3>();
|
||||
var service4 = GetOrRegisterInstantiate<T4>();
|
||||
action(service1, service2, service3, service4);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action)
|
||||
{
|
||||
var service1 = GetOrInstantiate<T1>();
|
||||
var service2 = GetOrInstantiate<T2>();
|
||||
var service3 = GetOrInstantiate<T3>();
|
||||
var service4 = GetOrInstantiate<T4>();
|
||||
var service5 = GetOrInstantiate<T5>();
|
||||
var service1 = GetOrRegisterInstantiate<T1>();
|
||||
var service2 = GetOrRegisterInstantiate<T2>();
|
||||
var service3 = GetOrRegisterInstantiate<T3>();
|
||||
var service4 = GetOrRegisterInstantiate<T4>();
|
||||
var service5 = GetOrRegisterInstantiate<T5>();
|
||||
action(service1, service2, service3, service4, service5);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action)
|
||||
{
|
||||
var service1 = GetOrInstantiate<T1>();
|
||||
var service2 = GetOrInstantiate<T2>();
|
||||
var service3 = GetOrInstantiate<T3>();
|
||||
var service4 = GetOrInstantiate<T4>();
|
||||
var service5 = GetOrInstantiate<T5>();
|
||||
var service6 = GetOrInstantiate<T6>();
|
||||
var service1 = GetOrRegisterInstantiate<T1>();
|
||||
var service2 = GetOrRegisterInstantiate<T2>();
|
||||
var service3 = GetOrRegisterInstantiate<T3>();
|
||||
var service4 = GetOrRegisterInstantiate<T4>();
|
||||
var service5 = GetOrRegisterInstantiate<T5>();
|
||||
var service6 = GetOrRegisterInstantiate<T6>();
|
||||
action(service1, service2, service3, service4, service5, service6);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action)
|
||||
{
|
||||
var service1 = GetOrInstantiate<T1>();
|
||||
var service2 = GetOrInstantiate<T2>();
|
||||
var service3 = GetOrInstantiate<T3>();
|
||||
var service4 = GetOrInstantiate<T4>();
|
||||
var service5 = GetOrInstantiate<T5>();
|
||||
var service6 = GetOrInstantiate<T6>();
|
||||
var service7 = GetOrInstantiate<T7>();
|
||||
var service1 = GetOrRegisterInstantiate<T1>();
|
||||
var service2 = GetOrRegisterInstantiate<T2>();
|
||||
var service3 = GetOrRegisterInstantiate<T3>();
|
||||
var service4 = GetOrRegisterInstantiate<T4>();
|
||||
var service5 = GetOrRegisterInstantiate<T5>();
|
||||
var service6 = GetOrRegisterInstantiate<T6>();
|
||||
var service7 = GetOrRegisterInstantiate<T7>();
|
||||
action(service1, service2, service3, service4, service5, service6, service7);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
|
||||
{
|
||||
var service1 = GetOrInstantiate<T1>();
|
||||
var service2 = GetOrInstantiate<T2>();
|
||||
var service3 = GetOrInstantiate<T3>();
|
||||
var service4 = GetOrInstantiate<T4>();
|
||||
var service5 = GetOrInstantiate<T5>();
|
||||
var service6 = GetOrInstantiate<T6>();
|
||||
var service7 = GetOrInstantiate<T7>();
|
||||
var service8 = GetOrInstantiate<T8>();
|
||||
var service1 = GetOrRegisterInstantiate<T1>();
|
||||
var service2 = GetOrRegisterInstantiate<T2>();
|
||||
var service3 = GetOrRegisterInstantiate<T3>();
|
||||
var service4 = GetOrRegisterInstantiate<T4>();
|
||||
var service5 = GetOrRegisterInstantiate<T5>();
|
||||
var service6 = GetOrRegisterInstantiate<T6>();
|
||||
var service7 = GetOrRegisterInstantiate<T7>();
|
||||
var service8 = GetOrRegisterInstantiate<T8>();
|
||||
action(service1, service2, service3, service4, service5, service6, service7, service8);
|
||||
return this;
|
||||
}
|
||||
|
||||
86
Library/Web/Attribute.cs
Normal file
86
Library/Web/Attribute.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示参数为url中的数据
|
||||
/// </summary>
|
||||
public class UrlAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 表示入参参数为整个boby的数据
|
||||
/// <para>
|
||||
/// 例如:User类型含有int id、string name字段</para>
|
||||
/// <para>
|
||||
/// ① Add(User user)</para>
|
||||
/// <para>请求需要传入的json为
|
||||
/// {"user":{
|
||||
/// "id":2,
|
||||
/// "name":"李志忠"}}</para>
|
||||
/// <para>
|
||||
/// ② Add([Boby]User user)</para>
|
||||
/// <para>请求需要传入的json为
|
||||
/// {"id":2,"name":"李志忠"}</para>
|
||||
///
|
||||
/// </summary>
|
||||
public class BobyAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 自动注册控制器
|
||||
/// </summary>
|
||||
public class AutoHostingAttribute : Attribute
|
||||
{
|
||||
public string Url { get; }
|
||||
public AutoHostingAttribute(string url = "")
|
||||
{
|
||||
this.Url = url;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 方法的接口类型与附加URL
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class WebApiAttribute : Attribute
|
||||
{
|
||||
public API Http; // HTTP 请求类型
|
||||
public string Url; // URL 路径
|
||||
/// <summary>
|
||||
/// 方法名称不作为url的部分
|
||||
/// </summary>
|
||||
public bool IsUrl;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 假设UserController.Add()的WebAPI特性中
|
||||
/// http是HTTP.POST
|
||||
/// url被显示标明“temp”
|
||||
/// 那么请求的接口是POST,URL是
|
||||
/// [http://localhost:8080]/user/add/temp
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="url"></param>
|
||||
public WebApiAttribute(API http = API.POST, bool isUrl = false, string url = "")
|
||||
{
|
||||
Http = http;
|
||||
Url = url;
|
||||
IsUrl = isUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public enum API
|
||||
{
|
||||
POST,
|
||||
GET,
|
||||
//PUT,
|
||||
//DELETE
|
||||
}
|
||||
}
|
||||
19
Library/Web/ControllerBase.cs
Normal file
19
Library/Web/ControllerBase.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Serein.Library.Web
|
||||
{
|
||||
public class ControllerBase
|
||||
{
|
||||
// [AutoInjection]
|
||||
// public ILoggerService loggerService { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string BobyData { get; set; }
|
||||
|
||||
public string GetLog(Exception ex)
|
||||
{
|
||||
return "Url : " + Url + Environment.NewLine +
|
||||
"Ex : " + ex + Environment.NewLine +
|
||||
"Data : " + BobyData + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
650
Library/Web/Router.cs
Normal file
650
Library/Web/Router.cs
Normal file
@@ -0,0 +1,650 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Attributes;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Enum = System.Enum;
|
||||
using Type = System.Type;
|
||||
|
||||
namespace Serein.Library.Web
|
||||
{
|
||||
public interface IRouter
|
||||
{
|
||||
bool RegisterController(Type controllerType);
|
||||
Task ProcessingAsync(HttpListenerContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 路由注册与解析
|
||||
/// </summary>
|
||||
public class Router : IRouter
|
||||
{
|
||||
/// <summary>
|
||||
/// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType]
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Type> _controllerTypes; // 存储控制器类型
|
||||
|
||||
/// <summary>
|
||||
/// 用于存储路由信息,[GET|POST - [Url - Method]]
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>> _routes;
|
||||
|
||||
[AutoInjection]
|
||||
public ISereinIOC SereinIOC { get; set; } // 用于存储路由信息
|
||||
// private readonly ILoggerService loggerService; // 用于存储路由信息
|
||||
|
||||
//private Type PostRequest;
|
||||
|
||||
public Router()
|
||||
{
|
||||
|
||||
_routes = new ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>>(); // 初始化路由字典
|
||||
_controllerTypes = new ConcurrentDictionary<string, Type>(); // 初始化控制器实例对象字典
|
||||
foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值
|
||||
{
|
||||
_routes.TryAdd(method.ToString(), new ConcurrentDictionary<string, MethodInfo>()); // 初始化每种 HTTP 方法对应的路由字典
|
||||
}
|
||||
Type baseAttribute = typeof(AutoHostingAttribute);
|
||||
Type baseController = typeof(ControllerBase);
|
||||
|
||||
// 获取当前程序集
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
// 获取包含“Controller”名称的类型
|
||||
var controllerTypes = assembly.GetTypes().Where(type => type.IsSubclassOf(baseController) // 控制器子类
|
||||
&& type.IsDefined(baseAttribute) // 包含特性
|
||||
&& type.Name.Contains("Controller"));
|
||||
|
||||
foreach (var controllerType in controllerTypes)
|
||||
{
|
||||
RegisterController(controllerType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在外部调用API后,解析路由,调用对应的方法
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task ProcessingAsync(HttpListenerContext context)
|
||||
{
|
||||
var request = context.Request; // 获取请求对象
|
||||
var response = context.Response; // 获取响应对象
|
||||
var url = request.Url; // 获取请求的 URL
|
||||
var httpMethod = request.HttpMethod; // 获取请求的 HTTP 方法
|
||||
var template = request.Url.AbsolutePath.ToLower();
|
||||
if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var routeValues = GetUrlData(url); // 解析 URL 获取路由参数
|
||||
|
||||
ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例
|
||||
|
||||
if (controllerInstance == null)
|
||||
{
|
||||
return; // 未找到控制器实例
|
||||
}
|
||||
|
||||
controllerInstance.Url = url.AbsolutePath;
|
||||
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); // 返回结果
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误
|
||||
Return(response, ex.Message); // 返回结果
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动注册并实例化控制器类型
|
||||
/// </summary>
|
||||
/// <param name="controllerType"></param>
|
||||
public bool RegisterController(Type controllerType) // 方法声明,用于注册并实例化控制器类型
|
||||
{
|
||||
if (!controllerType.IsClass || controllerType.IsAbstract) return false; // 如果不是类或者是抽象类,则直接返回
|
||||
|
||||
var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
|
||||
|
||||
foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
{
|
||||
var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
{
|
||||
var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method);
|
||||
Console.WriteLine(url);
|
||||
if (url == null) continue;
|
||||
_controllerTypes[url] = controllerType;
|
||||
}
|
||||
}
|
||||
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 == 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>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<string> ReadRequestBodyAsync(HttpListenerRequest request)
|
||||
{
|
||||
using (Stream stream = request.InputStream)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
return await reader.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
return JsonConvert.DeserializeObject(value.ToString(), targetType);
|
||||
}
|
||||
catch (JsonReaderException 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>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="msg"></param>
|
||||
private static void Return(HttpListenerResponse response, object msg)
|
||||
{
|
||||
string resultData;
|
||||
if (response != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg is JArray jArray)
|
||||
{
|
||||
resultData = jArray.ToString();
|
||||
}
|
||||
else if (msg is JObject jObject)
|
||||
{
|
||||
resultData = jObject.ToString();
|
||||
}
|
||||
else if (msg is IEnumerable ienumerable)
|
||||
{
|
||||
resultData = JArray.FromObject(ienumerable).ToString();
|
||||
}
|
||||
else if (msg is string tmpmsg)
|
||||
{
|
||||
resultData = tmpmsg;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则,将其序列化为JObject
|
||||
resultData = JObject.FromObject(msg).ToString();
|
||||
}
|
||||
//
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(resultData);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If serialization fails, use the original message's string representation
|
||||
resultData = ex.ToString();
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(resultData);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从方法中收集路由信息,返回方法对应的url
|
||||
/// </summary>
|
||||
/// <param name="autoHostingAttribute">类的特性</param>
|
||||
/// <param name="webAttribute">方法的特性</param>
|
||||
/// <param name="controllerType">控制器类型</param>
|
||||
/// <param name="method">方法信息</param>
|
||||
/// <returns>方法对应的urk</returns>
|
||||
private string AddRoutesUrl(AutoHostingAttribute autoHostingAttribute, WebApiAttribute webAttribute, Type controllerType, MethodInfo method)
|
||||
{
|
||||
string controllerName;
|
||||
if (string.IsNullOrWhiteSpace(autoHostingAttribute.Url))
|
||||
{
|
||||
controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写
|
||||
}
|
||||
else
|
||||
{
|
||||
controllerName = autoHostingAttribute.Url;
|
||||
}
|
||||
|
||||
var httpMethod = webAttribute.Http; // 获取 HTTP 方法
|
||||
var customUrl = webAttribute.Url; // 获取自定义 URL
|
||||
|
||||
string url;
|
||||
|
||||
if (webAttribute.IsUrl)
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
{
|
||||
url = $"/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL
|
||||
}
|
||||
else
|
||||
{
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
{
|
||||
url = $"/{controllerName}".ToLower(); // 构建默认 URL
|
||||
}
|
||||
else
|
||||
{
|
||||
customUrl = CleanUrl(customUrl);
|
||||
url = $"/{controllerName}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
}
|
||||
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
}
|
||||
|
||||
return url;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修正方法特性中的URL格式
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
private static string CleanUrl(string url)
|
||||
{
|
||||
|
||||
while (url.Length > 0 && url[0] == '/') // 去除开头的斜杠
|
||||
{
|
||||
url = url.Substring(1);
|
||||
}
|
||||
|
||||
while (url.Length > 0 && url[url.Length - 1] == '/') // 去除末尾的斜杠
|
||||
{
|
||||
url = url.Substring(0, url.Length - 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < url.Length - 1; i++) // 去除连续的斜杠
|
||||
{
|
||||
if (url[i] == '/' && url[i + 1] == '/')
|
||||
{
|
||||
url = url.Remove(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return url; // 返回清理后的 URL
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 方法声明,用于解析 URL 获取路由参数
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
private Dictionary<string, string> GetUrlData(Uri uri)
|
||||
{
|
||||
Dictionary<string, string> routeValues = new Dictionary<string, string>();
|
||||
|
||||
var pathParts = uri.ToString().Split('?'); // 拆分 URL,获取路径部分
|
||||
|
||||
if (pathParts.Length > 1) // 如果包含查询字符串
|
||||
{
|
||||
var queryParams = HttpUtility.ParseQueryString(pathParts[1]); // 解析查询字符串
|
||||
|
||||
foreach (string key in queryParams) // 遍历查询字符串的键值对
|
||||
{
|
||||
if (key == null) continue;
|
||||
routeValues[key] = queryParams[key]; // 将键值对添加到路由参数字典中
|
||||
}
|
||||
}
|
||||
|
||||
return routeValues; // 返回路由参数字典
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从控制器调用方法的异常中获取出出错类型的信息
|
||||
/// </summary>
|
||||
/// <param name="errorMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static string ExtractTargetTypeFromExceptionMessage(string errorMessage)
|
||||
{
|
||||
string targetText = "为类型“";
|
||||
int startIndex = errorMessage.IndexOf(targetText);
|
||||
if (startIndex != -1)
|
||||
{
|
||||
startIndex += targetText.Length;
|
||||
int endIndex = errorMessage.IndexOf("”", startIndex);
|
||||
if (endIndex != -1)
|
||||
{
|
||||
return errorMessage.Substring(startIndex, endIndex - startIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal static class WebFunc
|
||||
{
|
||||
public static bool ToBool(this JToken token,bool defult = false)
|
||||
{
|
||||
var value = token?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return defult;
|
||||
}
|
||||
if (!bool.TryParse(value, out bool result))
|
||||
{
|
||||
return defult;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
public static int ToInt(this JToken token, int defult = 0)
|
||||
{
|
||||
var value = token?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return defult;
|
||||
}
|
||||
if (!int.TryParse(value, out int result))
|
||||
{
|
||||
return defult;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
public static double ToDouble(this JToken token, double defult = 0)
|
||||
{
|
||||
var value = token?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return defult;
|
||||
}
|
||||
if (!int.TryParse(value, out int result))
|
||||
{
|
||||
return defult;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 已经注释
|
||||
|
||||
// private readonly ConcurrentDictionary<string, bool> _controllerAutoHosting; // 存储是否实例化
|
||||
// private readonly ConcurrentDictionary<string, object> _controllerInstances;
|
||||
|
||||
//public void CollectRoutes(Type controllerType)
|
||||
//{
|
||||
// string controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写
|
||||
// foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
// {
|
||||
// var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
// if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
// {
|
||||
// var customUrl = routeAttribute.Url; // 获取自定义 URL
|
||||
// string url;
|
||||
// if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
|
||||
// {
|
||||
// url = $"/api/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// customUrl = CleanUrl(customUrl);
|
||||
// url = $"/api/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL,并构建新的 URL
|
||||
// }
|
||||
// var httpMethod = routeAttribute.Http; // 获取 HTTP 方法
|
||||
// _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//public void RegisterRoute<T>(T controllerInstance) // 方法声明,用于动态注册路由
|
||||
//{
|
||||
// Type controllerType = controllerInstance.GetType(); // 获取控制器实例的类型
|
||||
// var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
|
||||
// foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
|
||||
// {
|
||||
// var webAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
|
||||
// if (webAttribute != null) // 如果存在 WebAPIAttribute 属性
|
||||
// {
|
||||
// var url = AddRoutesUrl(autoHostingAttribute, webAttribute, controllerType, method);
|
||||
// if (url == null) continue;
|
||||
// _controllerInstances[url] = controllerInstance;
|
||||
// _controllerAutoHosting[url] = false;
|
||||
// }
|
||||
|
||||
// }
|
||||
//}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
154
Library/Web/WebAPIAttribute.cs
Normal file
154
Library/Web/WebAPIAttribute.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Web
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// HTTP接口监听类
|
||||
/// </summary>
|
||||
public class WebServer
|
||||
{
|
||||
[AutoHosting]
|
||||
public IRouter Router { get; set; } // 路由器
|
||||
|
||||
private HttpListener listener; // HTTP 监听器
|
||||
private RequestLimiter requestLimiter; //接口防刷
|
||||
|
||||
public WebServer()
|
||||
{
|
||||
listener = new HttpListener();
|
||||
requestLimiter = new RequestLimiter(5, 8);
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
public WebServer Start(string prefixe)
|
||||
{
|
||||
|
||||
if (!prefixe.Substring(prefixe.Length - 1, 1).Equals(@"/"))
|
||||
{
|
||||
prefixe += @"/";
|
||||
}
|
||||
|
||||
listener.Prefixes.Add(prefixe); // 添加监听前缀
|
||||
|
||||
listener.Start(); // 开始监听
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (listener.IsListening)
|
||||
{
|
||||
var context = await listener.GetContextAsync(); // 获取请求上下文
|
||||
ProcessRequestAsync(context); // 处理请求
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 处理请求
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
private async void ProcessRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
// 添加CORS头部
|
||||
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||
context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if (context.Request.HttpMethod == "OPTIONS")
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
context.Response.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
var isPass = requestLimiter.AllowRequest(context.Request);
|
||||
if (isPass)
|
||||
{
|
||||
// 如果路由没有匹配,会返回 404
|
||||
await Router.ProcessingAsync(context); // 路由解析
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误
|
||||
context.Response.Close(); // 关闭响应
|
||||
}
|
||||
|
||||
// var request = context.Request;
|
||||
// 获取远程终结点信息
|
||||
var remoteEndPoint = context.Request.RemoteEndPoint;
|
||||
// 获取用户的IP地址和端口
|
||||
IPAddress ipAddress = remoteEndPoint.Address;
|
||||
int port = remoteEndPoint.Port;
|
||||
Console.WriteLine("外部连接:" + ipAddress.ToString() + ":" + port);
|
||||
}
|
||||
|
||||
// 停止服务器
|
||||
public void Stop()
|
||||
{
|
||||
listener.Stop(); // 停止监听
|
||||
listener.Close(); // 关闭监听器
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 判断访问接口的频次是否正常
|
||||
/// </summary>
|
||||
public class RequestLimiter
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Queue<DateTime>> requestHistory = new ConcurrentDictionary<string, Queue<DateTime>>();
|
||||
private readonly TimeSpan interval;
|
||||
private readonly int maxRequests;
|
||||
|
||||
public RequestLimiter(int seconds, int maxRequests)
|
||||
{
|
||||
this.interval = TimeSpan.FromSeconds(seconds);
|
||||
this.maxRequests = maxRequests;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断访问接口的频次是否正常
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool AllowRequest(HttpListenerRequest request)
|
||||
{
|
||||
var clientIp = request.RemoteEndPoint.Address.ToString();
|
||||
var clientPort = request.RemoteEndPoint.Port;
|
||||
var clientKey = clientIp + ":" + clientPort;
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 尝试从字典中获取请求队列,不存在则创建新的队列
|
||||
var requests = requestHistory.GetOrAdd(clientKey, new Queue<DateTime>());
|
||||
|
||||
lock (requests)
|
||||
{
|
||||
// 移除超出时间间隔的请求记录
|
||||
while (requests.Count > 0 && now - requests.Peek() > interval)
|
||||
{
|
||||
requests.Dequeue();
|
||||
}
|
||||
|
||||
// 如果请求数超过限制,拒绝请求
|
||||
if (requests.Count >= maxRequests)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加当前请求时间,并允许请求
|
||||
requests.Enqueue(now);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,15 +18,15 @@ namespace Serein.NodeFlow.Base
|
||||
/// </summary>
|
||||
public abstract partial class NodeModelBase : IDynamicFlowNode
|
||||
{
|
||||
public abstract Parameterdata[] GetParameterdatas();
|
||||
public virtual NodeInfo ToInfo()
|
||||
internal abstract Parameterdata[] GetParameterdatas();
|
||||
internal virtual NodeInfo ToInfo()
|
||||
{
|
||||
if (MethodDetails == null) return null;
|
||||
// if (MethodDetails == null) return null;
|
||||
|
||||
var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支
|
||||
var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支
|
||||
var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支
|
||||
var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支
|
||||
var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支
|
||||
var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 上游分支
|
||||
|
||||
// 生成参数列表
|
||||
Parameterdata[] parameterData = GetParameterdatas();
|
||||
@@ -42,9 +42,41 @@ namespace Serein.NodeFlow.Base
|
||||
UpstreamNodes = upstreamNodes.ToArray(),
|
||||
ParameterData = parameterData.ToArray(),
|
||||
ErrorNodes = errorNodes.ToArray(),
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
internal virtual NodeModelBase LoadInfo(NodeInfo nodeInfo)
|
||||
{
|
||||
var node = this;
|
||||
if (node != null)
|
||||
{
|
||||
node.Guid = nodeInfo.Guid;
|
||||
for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
|
||||
{
|
||||
Parameterdata? pd = nodeInfo.ParameterData[i];
|
||||
node.MethodDetails.ExplicitDatas[i].IsExplicitData = pd.State;
|
||||
node.MethodDetails.ExplicitDatas[i].DataValue = pd.Value;
|
||||
}
|
||||
}
|
||||
|
||||
//if (control is ConditionNodeControl conditionNodeControl)
|
||||
//{
|
||||
// conditionNodeControl.ViewModel.IsCustomData = pd.state;
|
||||
// conditionNodeControl.ViewModel.CustomData = pd.value;
|
||||
// conditionNodeControl.ViewModel.Expression = pd.expression;
|
||||
//}
|
||||
//else if (control is ExpOpNodeControl expOpNodeControl)
|
||||
//{
|
||||
// expOpNodeControl.ViewModel.Expression = pd.expression;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// node.MethodDetails.ExplicitDatas[i].IsExplicitData = pd.state;
|
||||
// node.MethodDetails.ExplicitDatas[i].DataValue = pd.value;
|
||||
//}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -55,7 +87,7 @@ namespace Serein.NodeFlow.Base
|
||||
/// <returns></returns>
|
||||
public async Task StartExecution(IDynamicContext context)
|
||||
{
|
||||
var cts = context.SereinIoc.GetOrInstantiate<CancellationTokenSource>();
|
||||
var cts = context.SereinIoc.GetOrRegisterInstantiate<CancellationTokenSource>();
|
||||
|
||||
Stack<NodeModelBase> stack = [];
|
||||
stack.Push(this);
|
||||
@@ -66,12 +98,12 @@ namespace Serein.NodeFlow.Base
|
||||
var currentNode = stack.Pop();
|
||||
|
||||
// 设置方法执行的对象
|
||||
if (currentNode.MethodDetails is not null)
|
||||
if (currentNode.MethodDetails is not null && currentNode.MethodDetails.ActingInstanceType is not null)
|
||||
{
|
||||
// currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrInstantiate(MethodDetails.ActingInstanceType);
|
||||
// currentNode.MethodDetails.ActingInstance = context.SereinIoc.GetOrInstantiate(MethodDetails.ActingInstanceType);
|
||||
|
||||
currentNode.MethodDetails.ActingInstance = context.SereinIoc.GetOrInstantiate(currentNode.MethodDetails.ActingInstanceType);
|
||||
currentNode.MethodDetails.ActingInstance = context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
|
||||
}
|
||||
|
||||
// 获取上游分支,首先执行一次
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Serein.Library.Api;
|
||||
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Attributes;
|
||||
using Serein.Library.Entity;
|
||||
using Serein.Library.Enums;
|
||||
@@ -8,8 +9,10 @@ using Serein.NodeFlow.Model;
|
||||
using Serein.NodeFlow.Tool;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Mime;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using static Serein.NodeFlow.FlowStarter;
|
||||
@@ -44,20 +47,25 @@ namespace Serein.NodeFlow
|
||||
/// </summary>
|
||||
public class FlowEnvironment : IFlowEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点的命名空间
|
||||
/// </summary>
|
||||
public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}";
|
||||
|
||||
/// <summary>
|
||||
/// 加载Dll
|
||||
/// </summary>
|
||||
public event LoadDLLHandler OnDllLoad;
|
||||
/// <summary>
|
||||
/// 加载节点事件
|
||||
/// 项目加载完成
|
||||
/// </summary>
|
||||
public event LoadNodeHandler OnLoadNode;
|
||||
public event ProjectLoadedHandler OnProjectLoaded;
|
||||
/// <summary>
|
||||
/// 节点连接属性改变事件
|
||||
/// </summary>
|
||||
public event NodeConnectChangeHandler OnNodeConnectChange;
|
||||
/// <summary>
|
||||
/// 节点创建时间
|
||||
/// 节点创建事件
|
||||
/// </summary>
|
||||
public event NodeCreateHandler OnNodeCreate;
|
||||
/// <summary>
|
||||
@@ -75,11 +83,6 @@ namespace Serein.NodeFlow
|
||||
|
||||
private FlowStarter? nodeFlowStarter = null;
|
||||
|
||||
/// <summary>
|
||||
/// 节点的命名空间
|
||||
/// </summary>
|
||||
public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}";
|
||||
|
||||
/// <summary>
|
||||
/// 一种轻量的IOC容器
|
||||
/// </summary>
|
||||
@@ -103,7 +106,7 @@ namespace Serein.NodeFlow
|
||||
|
||||
public Dictionary<string, NodeModelBase> Nodes { get; } = [];
|
||||
|
||||
// public List<NodeModelBase> Regions { get; } = [];
|
||||
public List<NodeModelBase> Regions { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 存放触发器节点(运行时全部调用)
|
||||
@@ -149,6 +152,8 @@ namespace Serein.NodeFlow
|
||||
var initMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Init).ToList();
|
||||
var loadingMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Loading).ToList();
|
||||
var exitMethods = MethodDetailss.Where(it => it.MethodDynamicType == NodeType.Exit).ToList();
|
||||
|
||||
|
||||
await nodeFlowStarter.RunAsync(StartNode,
|
||||
this,
|
||||
runMethodDetailess,
|
||||
@@ -180,6 +185,30 @@ namespace Serein.NodeFlow
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行环节加载了项目文件,需要创建节点控件
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <param name="methodDetailss"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private NodeControlType GetNodeControlType(NodeInfo nodeInfo)
|
||||
{
|
||||
// 创建控件实例
|
||||
NodeControlType controlType = nodeInfo.Type switch
|
||||
{
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件
|
||||
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件
|
||||
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件
|
||||
_ => NodeControlType.None,
|
||||
};
|
||||
|
||||
return controlType;
|
||||
}
|
||||
|
||||
#region 对外暴露的接口
|
||||
|
||||
@@ -201,59 +230,120 @@ namespace Serein.NodeFlow
|
||||
(var assembly, var list) = LoadAssembly(dllFilePath);
|
||||
if (assembly is not null && list.Count > 0)
|
||||
{
|
||||
methodDetailss.AddRange(methodDetailss); // 暂存方法描述
|
||||
OnDllLoad?.Invoke(new LoadDLLEventArgs(assembly, methodDetailss)); // 通知UI创建dll面板显示
|
||||
MethodDetailss.AddRange(list); // 暂存方法描述
|
||||
OnDllLoad?.Invoke(new LoadDLLEventArgs(assembly, list)); // 通知UI创建dll面板显示
|
||||
}
|
||||
}
|
||||
// 方法加载完成,缓存到运行环境中。
|
||||
MethodDetailss.AddRange(methodDetailss);
|
||||
methodDetailss.Clear();
|
||||
//MethodDetailss.AddRange(methodDetailss);
|
||||
//methodDetailss.Clear();
|
||||
|
||||
|
||||
List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>();
|
||||
List<(NodeModelBase, Position)> ordinaryNodes = new List<(NodeModelBase, Position)>();
|
||||
// 加载节点
|
||||
foreach (var nodeInfo in projectFile.Nodes)
|
||||
{
|
||||
if (TryGetMethodDetails(nodeInfo.MethodName, out MethodDetails? methodDetails))
|
||||
var controlType = GetNodeControlType(nodeInfo);
|
||||
if(controlType == NodeControlType.None)
|
||||
{
|
||||
OnLoadNode?.Invoke(new LoadNodeEventArgs(nodeInfo, methodDetails));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
TryGetMethodDetails(nodeInfo.MethodName, out MethodDetails? methodDetails); // 加载项目时尝试获取方法信息
|
||||
methodDetails ??= new MethodDetails();
|
||||
var nodeModel = CreateNode(controlType, methodDetails);
|
||||
nodeModel.LoadInfo(nodeInfo); // 创建节点model
|
||||
if (nodeModel is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
TryAddNode(nodeModel);
|
||||
if(nodeInfo.ChildNodeGuids?.Length > 0)
|
||||
{
|
||||
regionChildNodes.Add((nodeModel,nodeInfo.ChildNodeGuids));
|
||||
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position));
|
||||
}
|
||||
else
|
||||
{
|
||||
ordinaryNodes.Add((nodeModel, nodeInfo.Position));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 加载区域的子项
|
||||
foreach((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes)
|
||||
{
|
||||
foreach (var childNodeGuid in item.childNodeGuids)
|
||||
{
|
||||
Nodes.TryGetValue(childNodeGuid, out NodeModelBase? childNode);
|
||||
if (childNode is null)
|
||||
{
|
||||
// 节点尚未加载
|
||||
continue;
|
||||
}
|
||||
// 存在节点
|
||||
OnNodeCreate?.Invoke(new NodeCreateEventArgs(childNode, true, item.region.Guid));
|
||||
}
|
||||
}
|
||||
// 加载节点
|
||||
foreach ((NodeModelBase nodeModel, Position position) item in ordinaryNodes)
|
||||
{
|
||||
bool IsContinue = false;
|
||||
foreach ((NodeModelBase region, string[] childNodeGuids) item2 in regionChildNodes)
|
||||
{
|
||||
foreach (var childNodeGuid in item2.childNodeGuids)
|
||||
{
|
||||
if (item.nodeModel.Guid.Equals(childNodeGuid))
|
||||
{
|
||||
IsContinue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IsContinue) continue;
|
||||
OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 确定节点之间的连接关系
|
||||
foreach (var nodeInfo in projectFile.Nodes)
|
||||
{
|
||||
if (!Nodes.TryGetValue(nodeInfo.Guid, out NodeModelBase fromNode))
|
||||
if (!Nodes.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode))
|
||||
{
|
||||
// 不存在对应的起始节点
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
List<(ConnectionType, string[])> nodeGuids = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes),
|
||||
(ConnectionType.IsFail, nodeInfo.FalseNodes),
|
||||
(ConnectionType.IsError, nodeInfo.ErrorNodes),
|
||||
(ConnectionType.Upstream, nodeInfo.UpstreamNodes)];
|
||||
List<(ConnectionType connectionType, string[] guids)> allToNodes = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes),
|
||||
(ConnectionType.IsFail, nodeInfo.FalseNodes),
|
||||
(ConnectionType.IsError, nodeInfo.ErrorNodes),
|
||||
(ConnectionType.Upstream, nodeInfo.UpstreamNodes)];
|
||||
|
||||
List<(ConnectionType, NodeModelBase[])> nodes = nodeGuids.Where(info => info.Item2.Length > 0)
|
||||
.Select(info => (info.Item1,
|
||||
info.Item2.Select(guid => Nodes[guid])
|
||||
List<(ConnectionType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0)
|
||||
.Select(info => (info.connectionType,
|
||||
info.guids.Select(guid => Nodes[guid])
|
||||
.ToArray()))
|
||||
.ToList();
|
||||
// 遍历每种类型的节点分支(四种)
|
||||
foreach ((ConnectionType connectionType, NodeModelBase[] nodeBases) item in nodes)
|
||||
foreach ((ConnectionType connectionType, NodeModelBase[] toNodes) item in fromNodes)
|
||||
{
|
||||
// 遍历当前类型分支的节点(确认连接关系)
|
||||
foreach (var node in item.nodeBases)
|
||||
foreach (var toNode in item.toNodes)
|
||||
{
|
||||
ConnectNode(fromNode, node, item.connectionType); // 加载时确定节点间的连接关系
|
||||
ConnectNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 保存项目为项目文件
|
||||
/// </summary>
|
||||
@@ -268,6 +358,7 @@ namespace Serein.NodeFlow
|
||||
};
|
||||
return projectData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件路径中加载DLL
|
||||
/// </summary>
|
||||
@@ -281,66 +372,27 @@ namespace Serein.NodeFlow
|
||||
MethodDetailss.AddRange(list);
|
||||
OnDllLoad?.Invoke(new LoadDLLEventArgs(assembly, list));
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 创建节点
|
||||
/// 运行时创建节点
|
||||
/// </summary>
|
||||
/// <param name="nodeBase"></param>
|
||||
public void CreateNode(NodeControlType nodeControlType, MethodDetails? methodDetails = null)
|
||||
public void CreateNode(NodeControlType nodeControlType, Position position, MethodDetails? methodDetails = null)
|
||||
{
|
||||
// 确定创建的节点类型
|
||||
Type? nodeType = nodeControlType switch
|
||||
{
|
||||
NodeControlType.Action => typeof(SingleActionNode),
|
||||
NodeControlType.Flipflop => typeof(SingleFlipflopNode),
|
||||
|
||||
NodeControlType.ExpOp => typeof(SingleExpOpNode),
|
||||
NodeControlType.ExpCondition => typeof(SingleConditionNode),
|
||||
NodeControlType.ConditionRegion => typeof(CompositeConditionNode),
|
||||
_ => null
|
||||
};
|
||||
if (nodeType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 生成实例
|
||||
var nodeObj = Activator.CreateInstance(nodeType);
|
||||
if (nodeObj is not NodeModelBase nodeBase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 配置基础的属性
|
||||
nodeBase.ControlType = nodeControlType;
|
||||
nodeBase.Guid = Guid.NewGuid().ToString();
|
||||
if (methodDetails != null)
|
||||
{
|
||||
var md = methodDetails.Clone();
|
||||
nodeBase.DisplayName = md.MethodTips;
|
||||
nodeBase.MethodDetails = md;
|
||||
}
|
||||
Nodes[nodeBase.Guid] = nodeBase;
|
||||
|
||||
// 如果是触发器,则需要添加到专属集合中
|
||||
if (nodeControlType == NodeControlType.Flipflop && nodeBase is SingleFlipflopNode flipflopNode)
|
||||
{
|
||||
var guid = flipflopNode.Guid;
|
||||
if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid)))
|
||||
{
|
||||
FlipflopNodes.Add(flipflopNode);
|
||||
}
|
||||
}
|
||||
|
||||
var nodeModel = CreateNode(nodeControlType, methodDetails);
|
||||
TryAddNode(nodeModel);
|
||||
// 通知UI更改
|
||||
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeBase));
|
||||
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position));
|
||||
// 因为需要UI先布置了元素,才能通知UI变更特效
|
||||
// 如果不存在流程起始控件,默认设置为流程起始控件
|
||||
if (StartNode is null)
|
||||
{
|
||||
SetStartNode(nodeBase);
|
||||
SetStartNode(nodeModel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 移除节点
|
||||
/// </summary>
|
||||
@@ -452,12 +504,19 @@ namespace Serein.NodeFlow
|
||||
/// </summary>
|
||||
public bool TryGetMethodDetails(string name, out MethodDetails? md)
|
||||
{
|
||||
md = MethodDetailss.FirstOrDefault(it => it.MethodName == name);
|
||||
if (md == null)
|
||||
var isPass = false;
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
md = MethodDetailss.FirstOrDefault(it => it.MethodName == name);
|
||||
return md != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
md = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -531,6 +590,65 @@ namespace Serein.NodeFlow
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点
|
||||
/// </summary>
|
||||
/// <param name="nodeBase"></param>
|
||||
private NodeModelBase CreateNode(NodeControlType nodeControlType,MethodDetails? methodDetails = null)
|
||||
{
|
||||
// 确定创建的节点类型
|
||||
Type? nodeType = nodeControlType switch
|
||||
{
|
||||
NodeControlType.Action => typeof(SingleActionNode),
|
||||
NodeControlType.Flipflop => typeof(SingleFlipflopNode),
|
||||
|
||||
NodeControlType.ExpOp => typeof(SingleExpOpNode),
|
||||
NodeControlType.ExpCondition => typeof(SingleConditionNode),
|
||||
NodeControlType.ConditionRegion => typeof(CompositeConditionNode),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (nodeType == null)
|
||||
{
|
||||
throw new Exception($"节点类型错误[{nodeControlType}]");
|
||||
}
|
||||
// 生成实例
|
||||
var nodeObj = Activator.CreateInstance(nodeType);
|
||||
if (nodeObj is not NodeModelBase nodeBase)
|
||||
{
|
||||
throw new Exception($"无法创建目标节点类型的实例[{nodeControlType}]");
|
||||
}
|
||||
|
||||
// 配置基础的属性
|
||||
nodeBase.ControlType = nodeControlType;
|
||||
if (methodDetails != null)
|
||||
{
|
||||
var md = methodDetails.Clone();
|
||||
nodeBase.DisplayName = md.MethodTips;
|
||||
nodeBase.MethodDetails = md;
|
||||
}
|
||||
|
||||
// 如果是触发器,则需要添加到专属集合中
|
||||
if (nodeControlType == NodeControlType.Flipflop && nodeBase is SingleFlipflopNode flipflopNode)
|
||||
{
|
||||
var guid = flipflopNode.Guid;
|
||||
if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid)))
|
||||
{
|
||||
FlipflopNodes.Add(flipflopNode);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeBase;
|
||||
}
|
||||
|
||||
private bool TryAddNode(NodeModelBase nodeModel)
|
||||
{
|
||||
nodeModel.Guid ??= Guid.NewGuid().ToString();
|
||||
Nodes[nodeModel.Guid] = nodeModel;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接节点
|
||||
/// </summary>
|
||||
@@ -583,7 +701,6 @@ namespace Serein.NodeFlow
|
||||
}
|
||||
|
||||
|
||||
|
||||
fromNode.SuccessorNodes[connectionType].Add(toNode); // 添加到起始节点的子分支
|
||||
toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支
|
||||
OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid,
|
||||
@@ -603,6 +720,9 @@ namespace Serein.NodeFlow
|
||||
StartNode = newStartNode;
|
||||
OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, StartNode.Guid));
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 网络交互
|
||||
@@ -633,6 +753,42 @@ namespace Serein.NodeFlow
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static Type? ControlTypeToModel(this NodeControlType nodeControlType )
|
||||
{
|
||||
// 确定创建的节点类型
|
||||
Type? nodeType = nodeControlType switch
|
||||
{
|
||||
NodeControlType.Action => typeof(SingleActionNode),
|
||||
NodeControlType.Flipflop => typeof(SingleFlipflopNode),
|
||||
|
||||
NodeControlType.ExpOp => typeof(SingleExpOpNode),
|
||||
NodeControlType.ExpCondition => typeof(SingleConditionNode),
|
||||
NodeControlType.ConditionRegion => typeof(CompositeConditionNode),
|
||||
_ => null
|
||||
};
|
||||
return nodeType;
|
||||
}
|
||||
public static NodeControlType ModelToControlType(this NodeControlType nodeControlType)
|
||||
{
|
||||
var type = nodeControlType.GetType();
|
||||
NodeControlType controlType = type switch
|
||||
{
|
||||
Type when type == typeof(SingleActionNode) => NodeControlType.Action,
|
||||
Type when type == typeof(SingleFlipflopNode) => NodeControlType.Flipflop,
|
||||
|
||||
Type when type == typeof(SingleExpOpNode) => NodeControlType.ExpOp,
|
||||
Type when type == typeof(SingleConditionNode) => NodeControlType.ExpCondition,
|
||||
Type when type == typeof(CompositeConditionNode) => NodeControlType.ConditionRegion,
|
||||
_ => NodeControlType.None,
|
||||
};
|
||||
return controlType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static bool NotExitPreviousNode(this SingleFlipflopNode node)
|
||||
{
|
||||
ConnectionType[] ct = [ConnectionType.IsSucceed,
|
||||
|
||||
@@ -70,12 +70,12 @@ namespace Serein.NodeFlow
|
||||
/// </summary>
|
||||
/// <param name="startNode">起始节点</param>
|
||||
/// <param name="env">运行环境</param>
|
||||
/// <param name="runMd">环境中已加载的所有节点方法</param>
|
||||
/// <param name="runNodeMd">环境中已加载的所有节点方法</param>
|
||||
/// <param name="flipflopNodes">触发器节点</param>
|
||||
/// <returns></returns>
|
||||
public async Task RunAsync(NodeModelBase startNode,
|
||||
IFlowEnvironment env,
|
||||
List<MethodDetails> runMd,
|
||||
List<MethodDetails> runNodeMd,
|
||||
List<MethodDetails> initMethods,
|
||||
List<MethodDetails> loadingMethods,
|
||||
List<MethodDetails> exitMethods,
|
||||
@@ -89,6 +89,8 @@ namespace Serein.NodeFlow
|
||||
return;
|
||||
}
|
||||
|
||||
#region 选择运行环境的上下文
|
||||
|
||||
// 判断使用哪一种流程上下文
|
||||
var isNetFramework = true;
|
||||
if (isNetFramework)
|
||||
@@ -99,23 +101,38 @@ namespace Serein.NodeFlow
|
||||
{
|
||||
Context = new Serein.Library.Core.NodeFlow.DynamicContext(SereinIOC, env);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 初始化运行环境的Ioc容器
|
||||
// 清除节点使用的对象
|
||||
foreach (var nodeMd in runMd)
|
||||
var thisRuningMds = new List<MethodDetails>();
|
||||
thisRuningMds.AddRange(runNodeMd);
|
||||
thisRuningMds.AddRange(initMethods);
|
||||
thisRuningMds.AddRange(loadingMethods);
|
||||
thisRuningMds.AddRange(exitMethods);
|
||||
|
||||
// .AddRange(initMethods).AddRange(loadingMethods).a
|
||||
foreach (var nodeMd in thisRuningMds)
|
||||
{
|
||||
nodeMd.ActingInstance = null;
|
||||
}
|
||||
|
||||
SereinIOC.Reset(); // 开始运行时清空ioc中注册的实例
|
||||
// 初始化ioc容器中的类型对象
|
||||
foreach (var md in runMd)
|
||||
foreach (var md in thisRuningMds)
|
||||
{
|
||||
SereinIOC.Register(md.ActingInstanceType);
|
||||
if(md.ActingInstanceType != null)
|
||||
{
|
||||
SereinIOC.Register(md.ActingInstanceType);
|
||||
}
|
||||
}
|
||||
SereinIOC.Build();
|
||||
foreach (var md in runMd)
|
||||
SereinIOC.Build(); // 流程启动前的初始化
|
||||
foreach (var md in thisRuningMds)
|
||||
{
|
||||
md.ActingInstance = SereinIOC.GetOrInstantiate(md.ActingInstanceType);
|
||||
if (md.ActingInstanceType != null)
|
||||
{
|
||||
md.ActingInstance = SereinIOC.GetOrRegisterInstantiate(md.ActingInstanceType);
|
||||
}
|
||||
}
|
||||
|
||||
//foreach (var md in flipflopNodes.Select(it => it.MethodDetails).ToArray())
|
||||
@@ -124,23 +141,36 @@ namespace Serein.NodeFlow
|
||||
//}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 创建Node中初始化、加载时、退出时调用的方法
|
||||
|
||||
#region 检查并修正初始化、加载时、退出时方法作用的对象,保证后续不会报错
|
||||
foreach (var md in initMethods) // 初始化
|
||||
{
|
||||
md.ActingInstance ??= Context.SereinIoc.GetOrInstantiate(md.ActingInstanceType);
|
||||
md.ActingInstance ??= Context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType);
|
||||
}
|
||||
foreach (var md in loadingMethods) // 加载
|
||||
{
|
||||
md.ActingInstance ??= Context.SereinIoc.GetOrInstantiate(md.ActingInstanceType);
|
||||
md.ActingInstance ??= Context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType);
|
||||
}
|
||||
foreach (var md in exitMethods) // 初始化
|
||||
{
|
||||
md.ActingInstance ??= Context.SereinIoc.GetOrInstantiate(md.ActingInstanceType);
|
||||
md.ActingInstance ??= Context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 执行初始化,绑定IOC容器,再执行加载时,设置流程退出时的回调函数
|
||||
|
||||
object?[]? args = [Context];
|
||||
foreach (var md in initMethods) // 初始化
|
||||
{
|
||||
object?[]? data = [md.ActingInstance, args];
|
||||
md.MethodDelegate.DynamicInvoke(data);
|
||||
}
|
||||
Context.SereinIoc.Build(); // 绑定初始化时注册的类型
|
||||
foreach (var md in loadingMethods) // 加载
|
||||
{
|
||||
object?[]? data = [md.ActingInstance, args];
|
||||
md.MethodDelegate.DynamicInvoke(data);
|
||||
}
|
||||
Context.SereinIoc.Build(); // 预防有人在加载时才注册类型,再绑定一次
|
||||
ExitAction = () =>
|
||||
{
|
||||
foreach (MethodDetails? md in exitMethods)
|
||||
@@ -159,34 +189,18 @@ namespace Serein.NodeFlow
|
||||
FlowState = RunState.Completion;
|
||||
FlipFlopState = RunState.Completion;
|
||||
};
|
||||
Context.SereinIoc.Build();
|
||||
#endregion
|
||||
|
||||
#region 执行初始化,然后绑定IOC容器,再执行加载时
|
||||
|
||||
foreach (var md in initMethods) // 初始化 - 调用方法
|
||||
{
|
||||
object?[]? data = [md.ActingInstance, args];
|
||||
md.MethodDelegate.DynamicInvoke(data);
|
||||
}
|
||||
Context.SereinIoc.Build();
|
||||
foreach (var md in loadingMethods) // 加载
|
||||
{
|
||||
object?[]? data = [md.ActingInstance, args];
|
||||
md.MethodDelegate.DynamicInvoke(data);
|
||||
}
|
||||
#endregion
|
||||
#region 开始启动流程
|
||||
|
||||
|
||||
// 节点任务的启动
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
if (flipflopNodes.Count > 0)
|
||||
{
|
||||
FlipFlopState = RunState.Running;
|
||||
// 如果存在需要启动的触发器,则开始启动
|
||||
FlipFlopCts = SereinIOC.GetOrInstantiate<NodeRunCts>();
|
||||
FlipFlopCts = SereinIOC.GetOrRegisterInstantiate<NodeRunCts>();
|
||||
// 使用 TaskCompletionSource 创建未启动的触发器任务
|
||||
var tasks = flipflopNodes.Select(async node =>
|
||||
{
|
||||
@@ -196,7 +210,7 @@ namespace Serein.NodeFlow
|
||||
}
|
||||
await startNode.StartExecution(Context);
|
||||
// 等待结束
|
||||
if(FlipFlopCts != null)
|
||||
if (FlipFlopCts != null)
|
||||
{
|
||||
while (!FlipFlopCts.IsCancellationRequested)
|
||||
{
|
||||
@@ -207,7 +221,8 @@ namespace Serein.NodeFlow
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Console.Out.WriteLineAsync(ex.ToString());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -228,7 +243,7 @@ namespace Serein.NodeFlow
|
||||
object?[]? parameters = singleFlipFlopNode.GetParameters(context, md);
|
||||
// 调用委托并获取结果
|
||||
|
||||
md.ActingInstance = context.SereinIoc.GetOrInstantiate(md.ActingInstanceType);
|
||||
md.ActingInstance = context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType);
|
||||
|
||||
IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters);
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@ namespace Serein.NodeFlow.Model
|
||||
ActionNodes = actionNodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override Parameterdata[] GetParameterdatas()
|
||||
|
||||
internal override Parameterdata[] GetParameterdatas()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
public override NodeInfo ToInfo()
|
||||
internal override NodeInfo ToInfo()
|
||||
{
|
||||
if (MethodDetails == null) return null;
|
||||
|
||||
@@ -35,8 +35,8 @@ namespace Serein.NodeFlow.Model
|
||||
//var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支
|
||||
var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支
|
||||
var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支
|
||||
var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支
|
||||
var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支
|
||||
var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支
|
||||
var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 上游分支
|
||||
// 生成参数列表
|
||||
Parameterdata[] parameterData = GetParameterdatas();
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Serein.NodeFlow.Model
|
||||
UpstreamNodes = upstreamNodes.ToArray(),
|
||||
ParameterData = parameterData.ToArray(),
|
||||
ErrorNodes = errorNodes.ToArray(),
|
||||
ChildNodes = ActionNodes.Select(node => node.ToInfo()).ToArray(),
|
||||
ChildNodeGuids = ActionNodes.Select(node => node.Guid).ToArray(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,14 +61,14 @@ namespace Serein.NodeFlow.Model
|
||||
}
|
||||
}
|
||||
|
||||
public override Parameterdata[] GetParameterdatas()
|
||||
internal override Parameterdata[] GetParameterdatas()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public override NodeInfo ToInfo()
|
||||
internal override NodeInfo ToInfo()
|
||||
{
|
||||
if (MethodDetails == null) return null;
|
||||
//if (MethodDetails == null) return null;
|
||||
|
||||
//var trueNodes = SucceedBranch.Select(item => item.Guid); // 真分支
|
||||
//var falseNodes = FailBranch.Select(item => item.Guid);// 假分支
|
||||
@@ -76,8 +76,8 @@ namespace Serein.NodeFlow.Model
|
||||
//var errorNodes = ErrorBranch.Select(item => item.Guid);// 异常分支
|
||||
var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支
|
||||
var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支
|
||||
var upstreamNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 上游分支
|
||||
var errorNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 异常分支
|
||||
var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支
|
||||
var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 上游分支
|
||||
|
||||
// 生成参数列表
|
||||
Parameterdata[] parameterData = GetParameterdatas();
|
||||
@@ -93,7 +93,7 @@ namespace Serein.NodeFlow.Model
|
||||
UpstreamNodes = upstreamNodes.ToArray(),
|
||||
ParameterData = parameterData.ToArray(),
|
||||
ErrorNodes = errorNodes.ToArray(),
|
||||
ChildNodes = ConditionNodes.Select(node => node.ToInfo()).ToArray(),
|
||||
ChildNodeGuids = ConditionNodes.Select(node => node.Guid).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -64,15 +64,15 @@ namespace Serein.NodeFlow.Model
|
||||
// context.SetFlowData(result);
|
||||
// }
|
||||
//}
|
||||
public override Parameterdata[] GetParameterdatas()
|
||||
internal override Parameterdata[] GetParameterdatas()
|
||||
{
|
||||
if (base.MethodDetails.ExplicitDatas.Length > 0)
|
||||
{
|
||||
return MethodDetails.ExplicitDatas
|
||||
.Select(it => new Parameterdata
|
||||
{
|
||||
state = it.IsExplicitData,
|
||||
value = it.DataValue,
|
||||
State = it.IsExplicitData,
|
||||
Value = it.DataValue,
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
@@ -55,31 +55,43 @@ namespace Serein.NodeFlow.Model
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Parameterdata[] GetParameterdatas()
|
||||
internal override Parameterdata[] GetParameterdatas()
|
||||
{
|
||||
if (base.MethodDetails.ExplicitDatas.Length > 0)
|
||||
var value = CustomData switch
|
||||
{
|
||||
return MethodDetails.ExplicitDatas
|
||||
.Select(it => new Parameterdata
|
||||
{
|
||||
state = IsCustomData,
|
||||
expression = Expression,
|
||||
value = CustomData switch
|
||||
{
|
||||
Type when CustomData.GetType() == typeof(int)
|
||||
&& CustomData.GetType() == typeof(double)
|
||||
&& CustomData.GetType() == typeof(float)
|
||||
=> ((double)CustomData).ToString(),
|
||||
Type when CustomData.GetType() == typeof(bool) => ((bool)CustomData).ToString(),
|
||||
_ => CustomData?.ToString()!,
|
||||
}
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
Type when CustomData.GetType() == typeof(int)
|
||||
&& CustomData.GetType() == typeof(double)
|
||||
&& CustomData.GetType() == typeof(float)
|
||||
=> ((double)CustomData).ToString(),
|
||||
Type when CustomData.GetType() == typeof(bool) => ((bool)CustomData).ToString(),
|
||||
_ => CustomData?.ToString()!,
|
||||
};
|
||||
return [new Parameterdata
|
||||
{
|
||||
return [];
|
||||
State = IsCustomData,
|
||||
Expression = Expression,
|
||||
Value = value,
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal override NodeModelBase LoadInfo(NodeInfo nodeInfo)
|
||||
{
|
||||
var node = this;
|
||||
if (node != null)
|
||||
{
|
||||
node.Guid = nodeInfo.Guid;
|
||||
for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
|
||||
{
|
||||
Parameterdata? pd = nodeInfo.ParameterData[i];
|
||||
node.IsCustomData = pd.State;
|
||||
node.CustomData = pd.Value;
|
||||
node.Expression = pd.Expression;
|
||||
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
//public override void Execute(DynamicContext context)
|
||||
|
||||
@@ -48,23 +48,25 @@ namespace Serein.NodeFlow.Model
|
||||
|
||||
}
|
||||
|
||||
public override Parameterdata[] GetParameterdatas()
|
||||
internal override Parameterdata[] GetParameterdatas()
|
||||
{
|
||||
if (base.MethodDetails.ExplicitDatas.Length > 0)
|
||||
return [new Parameterdata{ Expression = Expression}];
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal override NodeModelBase LoadInfo(NodeInfo nodeInfo)
|
||||
{
|
||||
var node = this;
|
||||
if (node != null)
|
||||
{
|
||||
return MethodDetails.ExplicitDatas
|
||||
.Select(it => new Parameterdata
|
||||
{
|
||||
state = it.IsExplicitData,
|
||||
// value = it.DataValue,
|
||||
expression = Expression,
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
return [];
|
||||
node.Guid = nodeInfo.Guid;
|
||||
for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
|
||||
{
|
||||
node.Expression = nodeInfo.ParameterData[i].Expression;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,15 @@ namespace Serein.NodeFlow.Model
|
||||
return null;
|
||||
}
|
||||
|
||||
public override Parameterdata[] GetParameterdatas()
|
||||
internal override Parameterdata[] GetParameterdatas()
|
||||
{
|
||||
if (base.MethodDetails.ExplicitDatas.Length > 0)
|
||||
{
|
||||
return MethodDetails.ExplicitDatas
|
||||
.Select(it => new Parameterdata
|
||||
{
|
||||
state = it.IsExplicitData,
|
||||
value = it.DataValue
|
||||
State = it.IsExplicitData,
|
||||
Value = it.DataValue
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
16
NodeFlow/NodeStaticConfig.cs
Normal file
16
NodeFlow/NodeStaticConfig.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.NodeFlow
|
||||
{
|
||||
public static class NodeStaticConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点的命名空间
|
||||
/// </summary>
|
||||
public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}";
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ namespace Serein.NodeFlow.Tool.SereinExpression.Resolver
|
||||
public override bool Evaluate(object? obj)
|
||||
{
|
||||
//object? memberValue = GetMemberValue(obj, MemberPath);
|
||||
|
||||
|
||||
if (TargetObj is T typedObj)
|
||||
{
|
||||
return new ValueTypeConditionResolver<T>
|
||||
|
||||
@@ -18,7 +18,16 @@ namespace Serein.NodeFlow.Tool.SereinExpression.Resolver
|
||||
|
||||
public override bool Evaluate(object obj)
|
||||
{
|
||||
object memberValue = GetMemberValue(obj, MemberPath);
|
||||
object memberValue;
|
||||
if (!string.IsNullOrWhiteSpace(MemberPath))
|
||||
{
|
||||
memberValue = GetMemberValue(obj, MemberPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
memberValue = obj;
|
||||
}
|
||||
|
||||
if (memberValue is string strObj)
|
||||
{
|
||||
return new StringConditionResolver
|
||||
|
||||
@@ -12,7 +12,9 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
||||
try
|
||||
{
|
||||
|
||||
return ConditionParse(data, expression).Evaluate(data);
|
||||
var parse = ConditionParse(data, expression);
|
||||
var result = parse.Evaluate(data);
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -24,7 +26,7 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
||||
|
||||
public static SereinConditionResolver ConditionParse(object data, string expression)
|
||||
{
|
||||
if (expression.StartsWith('.')) // 表达式前缀属于从上一个节点数据对象获取成员值
|
||||
if (expression.StartsWith('.') || expression.StartsWith('<')) // 表达式前缀属于从上一个节点数据对象获取成员值
|
||||
{
|
||||
return ParseObjectExpression(data, expression);
|
||||
}
|
||||
@@ -128,9 +130,6 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
||||
operatorStr = parts[0].ToLower(); // 操作类型
|
||||
valueStr = string.Join(' ', parts.Skip(1)); // 表达式值
|
||||
}
|
||||
|
||||
targetObj = GetMemberValue(data, memberPath);// 获取对象成员,作为表达式的目标对象
|
||||
|
||||
Type? tempType = typeStr switch
|
||||
{
|
||||
"int" => typeof(int),
|
||||
@@ -140,6 +139,15 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
||||
_ => Type.GetType(typeStr)
|
||||
};
|
||||
type = tempType ?? throw new ArgumentException("对象表达式无效的类型声明");
|
||||
if (string.IsNullOrWhiteSpace(memberPath))
|
||||
{
|
||||
targetObj = Convert.ChangeType(data, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetObj = GetMemberValue(data, memberPath);// 获取对象成员,作为表达式的目标对象
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region 解析类型 int
|
||||
|
||||
@@ -150,13 +150,13 @@ namespace Serein.WorkBench
|
||||
Shutdown(); // 关闭应用程序
|
||||
}
|
||||
}
|
||||
else if (1 == 11)
|
||||
else if (1 == 1)
|
||||
{
|
||||
string filePath = @"F:\临时\project\U9 project.dnf";
|
||||
string filePath = @"F:\临时\project\new project.dnf";
|
||||
//string filePath = @"D:\Project\C#\DynamicControl\SereinFlow\.Output\Debug\net8.0-windows7.0\U9 project.dnf";
|
||||
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
|
||||
FData = JsonConvert.DeserializeObject<SereinOutputFileData>(content);
|
||||
App.FileDataPath = System.IO.Path.GetDirectoryName(filePath);
|
||||
App.FData = JsonConvert.DeserializeObject<SereinOutputFileData>(content);
|
||||
App.FileDataPath = filePath;//System.IO.Path.GetDirectoryName(filePath)!;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Serein.WorkBench"
|
||||
mc:Ignorable="d"
|
||||
Topmost="False"
|
||||
Title="LogWindow" Height="600" Width="400"
|
||||
Closing="Window_Closing">
|
||||
<Grid>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
Title="Dynamic Node Flow" Height="700" Width="1200"
|
||||
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
|
||||
Loaded="Window_Loaded"
|
||||
ContentRendered="Window_ContentRendered"
|
||||
Closing="Window_Closing">
|
||||
|
||||
<Window.Resources>
|
||||
@@ -136,7 +137,7 @@
|
||||
|
||||
<!-- Bottom-Right Thumb -->
|
||||
<Thumb x:Name="BottomRightThumb"
|
||||
Width="10" Height="10"
|
||||
Width="40" Height="40"
|
||||
DragDelta="Thumb_DragDelta_BottomRight"
|
||||
Cursor="SizeNWSE"
|
||||
Canvas.Left="{Binding ActualWidth, ElementName=FlowChartCanvas, Mode=OneWay, Converter={StaticResource RightThumbPositionConverter}}"
|
||||
|
||||
@@ -21,6 +21,7 @@ using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
using DataObject = System.Windows.DataObject;
|
||||
|
||||
namespace Serein.WorkBench
|
||||
@@ -50,12 +51,6 @@ namespace Serein.WorkBench
|
||||
/// </summary>
|
||||
private readonly LogWindow logWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 节点的命名空间
|
||||
/// </summary>
|
||||
public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 流程运行环境
|
||||
/// </summary>
|
||||
@@ -82,7 +77,7 @@ namespace Serein.WorkBench
|
||||
/// <summary>
|
||||
/// 拖动创建节点控件时的鼠标位置
|
||||
/// </summary>
|
||||
private Point canvasDropPosition;
|
||||
// private Point canvasDropPosition;
|
||||
|
||||
/// <summary>
|
||||
/// 记录拖动开始时的鼠标位置
|
||||
@@ -138,6 +133,7 @@ namespace Serein.WorkBench
|
||||
{
|
||||
ViewModel = new MainWindowViewModel(this);
|
||||
FlowEnvironment = ViewModel.FlowEnvironment;
|
||||
InitFlowEvent();
|
||||
|
||||
InitializeComponent();
|
||||
logWindow = new LogWindow();
|
||||
@@ -145,14 +141,23 @@ namespace Serein.WorkBench
|
||||
// 重定向 Console 输出
|
||||
var logTextWriter = new LogTextWriter(WriteLog);
|
||||
Console.SetOut(logTextWriter);
|
||||
InitFlowEvent();
|
||||
InitUI();
|
||||
|
||||
var project = App.FData;
|
||||
if (project == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Lenght);// 设置画布大小
|
||||
FlowEnvironment.LoadProject(project, App.FileDataPath); // 加载项目
|
||||
}
|
||||
|
||||
private void InitFlowEvent()
|
||||
{
|
||||
FlowEnvironment.OnDllLoad += FlowEnvironment_DllLoadEvent;
|
||||
FlowEnvironment.OnLoadNode += FlowEnvironment_NodeLoadEvent;
|
||||
// FlowEnvironment.OnLoadNode += FlowEnvironment_NodeLoadEvent;
|
||||
FlowEnvironment.OnProjectLoaded += FlowEnvironment_OnProjectLoaded;
|
||||
FlowEnvironment.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent;
|
||||
FlowEnvironment.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt;
|
||||
FlowEnvironment.OnNodeCreate += FlowEnvironment_NodeCreateEvent;
|
||||
@@ -161,6 +166,7 @@ namespace Serein.WorkBench
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void InitUI()
|
||||
{
|
||||
canvasTransformGroup = new TransformGroup();
|
||||
@@ -171,22 +177,41 @@ namespace Serein.WorkBench
|
||||
canvasTransformGroup.Children.Add(translateTransform);
|
||||
|
||||
FlowChartCanvas.RenderTransform = canvasTransformGroup;
|
||||
|
||||
FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||
}
|
||||
|
||||
#region Main窗体加载方法
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
}
|
||||
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
logWindow.Close();
|
||||
System.Windows.Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private void Window_ContentRendered(object sender, EventArgs e)
|
||||
{
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
connection.Refresh();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
public void WriteLog(string message)
|
||||
{
|
||||
logWindow.AppendText(message);
|
||||
}
|
||||
|
||||
#region 运行环境事件
|
||||
private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
|
||||
{
|
||||
//foreach(var connection in Connections)
|
||||
//{
|
||||
// connection.Refresh();
|
||||
//}
|
||||
Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 运行完成
|
||||
/// </summary>
|
||||
@@ -202,27 +227,30 @@ namespace Serein.WorkBench
|
||||
/// </summary>
|
||||
private void FlowEnvironment_DllLoadEvent(LoadDLLEventArgs eventArgs)
|
||||
{
|
||||
Assembly assembly = eventArgs.Assembly;
|
||||
List<MethodDetails> methodDetailss = eventArgs.MethodDetailss;
|
||||
this.Dispatcher.Invoke(() => {
|
||||
Assembly assembly = eventArgs.Assembly;
|
||||
List<MethodDetails> methodDetailss = eventArgs.MethodDetailss;
|
||||
|
||||
var dllControl = new DllControl
|
||||
{
|
||||
Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称
|
||||
};
|
||||
|
||||
foreach (var methodDetails in methodDetailss)
|
||||
{
|
||||
switch (methodDetails.MethodDynamicType)
|
||||
var dllControl = new DllControl
|
||||
{
|
||||
case Library.Enums.NodeType.Action:
|
||||
dllControl.AddAction(methodDetails.Clone()); // 添加动作类型到控件
|
||||
break;
|
||||
case Library.Enums.NodeType.Flipflop:
|
||||
dllControl.AddFlipflop(methodDetails.Clone()); // 添加触发器方法到控件
|
||||
break;
|
||||
Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称
|
||||
};
|
||||
|
||||
foreach (var methodDetails in methodDetailss)
|
||||
{
|
||||
switch (methodDetails.MethodDynamicType)
|
||||
{
|
||||
case Library.Enums.NodeType.Action:
|
||||
dllControl.AddAction(methodDetails.Clone()); // 添加动作类型到控件
|
||||
break;
|
||||
case Library.Enums.NodeType.Flipflop:
|
||||
dllControl.AddFlipflop(methodDetails.Clone()); // 添加触发器方法到控件
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示
|
||||
DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -230,34 +258,35 @@ namespace Serein.WorkBench
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <param name="methodDetailss"></param>
|
||||
private void FlowEnvironment_NodeLoadEvent(LoadNodeEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.IsSucceed)
|
||||
{
|
||||
MessageBox.Show(eventArgs.ErrorTips);
|
||||
return;
|
||||
}
|
||||
NodeInfo nodeInfo = eventArgs.NodeInfo;
|
||||
MethodDetails methodDetailss = eventArgs.MethodDetailss;
|
||||
//private void FlowEnvironment_NodeLoadEvent(LoadNodeEventArgs eventArgs)
|
||||
//{
|
||||
// if (!eventArgs.IsSucceed)
|
||||
// {
|
||||
// MessageBox.Show(eventArgs.ErrorTips);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 创建对应的实例(包含NodeModel,NodeControl,NodeControlViewModel)
|
||||
NodeControlBase? nodeControl = CreateNodeControlOfNodeInfo(nodeInfo, methodDetailss);
|
||||
if (nodeControl == null)
|
||||
{
|
||||
WriteLog($"无法为节点类型创建节点控件: {nodeInfo.MethodName}\r\n");
|
||||
return;
|
||||
// ConfigureNodeControl(nodeInfo, nodeControl, nodeControls, regionControls);
|
||||
}
|
||||
|
||||
// 判断是否属于区域控件,如果是,则加载区域子项
|
||||
if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl)
|
||||
{
|
||||
AddNodeControlInRegeionControl(nodeControl, nodeInfo.ChildNodes);
|
||||
}
|
||||
// NodeInfo nodeInfo = eventArgs.NodeInfo;
|
||||
// MethodDetails methodDetailss = eventArgs.MethodDetailss;
|
||||
|
||||
NodeControls.TryAdd(nodeInfo.Guid, nodeControl); // 存放对应的控件
|
||||
PlaceNodeOnCanvas(nodeControl, nodeInfo.Position.X, nodeInfo.Position.Y); // 配置节点,并放置在画布上
|
||||
}
|
||||
// // 创建对应的实例(包含NodeModel,NodeControl,NodeControlViewModel)
|
||||
// NodeControlBase? nodeControl = CreateNodeControlOfNodeInfo(nodeInfo, methodDetailss);
|
||||
// if (nodeControl == null)
|
||||
// {
|
||||
// WriteLog($"无法为节点类型创建节点控件: {nodeInfo.MethodName}\r\n");
|
||||
// return;
|
||||
// // ConfigureNodeControl(nodeInfo, nodeControl, nodeControls, regionControls);
|
||||
// }
|
||||
|
||||
// // 判断是否属于区域控件,如果是,则加载区域子项
|
||||
// // if (nodeControl is ActionRegionControl || nodeControl is ConditionRegionControl)
|
||||
// // {
|
||||
// // AddNodeControlInRegeionControl(nodeControl, nodeInfo.ChildNodes);
|
||||
// // }
|
||||
|
||||
// NodeControls.TryAdd(nodeInfo.Guid, nodeControl); // 存放对应的控件
|
||||
// PlaceNodeOnCanvas(nodeControl, nodeInfo.Position.X, nodeInfo.Position.Y); // 配置节点,并放置在画布上
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 节点连接关系变更
|
||||
@@ -267,40 +296,48 @@ namespace Serein.WorkBench
|
||||
/// <param name="connectionType"></param>
|
||||
private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs)
|
||||
{
|
||||
string fromNodeGuid = eventArgs.FromNodeGuid;
|
||||
string toNodeGuid = eventArgs.ToNodeGuid;
|
||||
if (!NodeControls.TryGetValue(fromNodeGuid, out var fromNode) || !NodeControls.TryGetValue(toNodeGuid, out var toNode))
|
||||
this.Dispatcher.Invoke(() =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
ConnectionType connectionType = eventArgs.ConnectionType;
|
||||
if(eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create)
|
||||
{
|
||||
// 添加连接
|
||||
var connection = new Connection
|
||||
string fromNodeGuid = eventArgs.FromNodeGuid;
|
||||
string toNodeGuid = eventArgs.ToNodeGuid;
|
||||
if (!NodeControls.TryGetValue(fromNodeGuid, out var fromNode) || !NodeControls.TryGetValue(toNodeGuid, out var toNode))
|
||||
{
|
||||
Start = fromNode,
|
||||
End = toNode,
|
||||
Type = connectionType
|
||||
};
|
||||
|
||||
BsControl.Draw(FlowChartCanvas, connection); // 添加贝塞尔曲线显示
|
||||
ConfigureLineContextMenu(connection); // 设置连接右键事件
|
||||
Connections.Add(connection);
|
||||
EndConnection();
|
||||
}
|
||||
else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote)
|
||||
{
|
||||
// 需要移除连接
|
||||
var removeConnections = Connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(fromNodeGuid)
|
||||
&& c.End.ViewModel.Node.Guid.Equals(toNodeGuid))
|
||||
.ToList();
|
||||
foreach(var connection in removeConnections)
|
||||
{
|
||||
connection.RemoveFromCanvas(FlowChartCanvas);
|
||||
Connections.Remove(connection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ConnectionType connectionType = eventArgs.ConnectionType;
|
||||
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create)
|
||||
{
|
||||
lock (Connections)
|
||||
{
|
||||
// 添加连接
|
||||
var connection = new Connection
|
||||
{
|
||||
Start = fromNode,
|
||||
End = toNode,
|
||||
Type = connectionType
|
||||
};
|
||||
|
||||
BsControl.Draw(FlowChartCanvas, connection); // 添加贝塞尔曲线显示
|
||||
ConfigureLineContextMenu(connection); // 设置连接右键事件
|
||||
Connections.Add(connection);
|
||||
EndConnection();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote)
|
||||
{
|
||||
// 需要移除连接
|
||||
var removeConnections = Connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(fromNodeGuid)
|
||||
&& c.End.ViewModel.Node.Guid.Equals(toNodeGuid))
|
||||
.ToList();
|
||||
foreach (var connection in removeConnections)
|
||||
{
|
||||
connection.RemoveFromCanvas(FlowChartCanvas);
|
||||
Connections.Remove(connection);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -309,14 +346,16 @@ namespace Serein.WorkBench
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs)
|
||||
{
|
||||
var nodeGuid = eventArgs.NodeGuid;
|
||||
if (!NodeControls.TryGetValue(nodeGuid, out var nodeControl))
|
||||
this.Dispatcher.Invoke(() =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
FlowChartCanvas.Children.Remove(nodeControl);
|
||||
NodeControls.Remove(nodeControl.ViewModel.Node.Guid);
|
||||
|
||||
var nodeGuid = eventArgs.NodeGuid;
|
||||
if (!NodeControls.TryGetValue(nodeGuid, out var nodeControl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
FlowChartCanvas.Children.Remove(nodeControl);
|
||||
NodeControls.Remove(nodeControl.ViewModel.Node.Guid);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -326,35 +365,52 @@ namespace Serein.WorkBench
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.NodeModel is not NodeModelBase nodeModelBase)
|
||||
this.Dispatcher.Invoke(() =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (eventArgs.NodeModel is not NodeModelBase nodeModelBase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建对应控件
|
||||
NodeControlBase? nodeControl = nodeModelBase.ControlType switch
|
||||
{
|
||||
NodeControlType.Action => CreateNodeControl<ActionNodeControl, ActionNodeControlViewModel>(nodeModelBase), //typeof(ActionNodeControl),
|
||||
NodeControlType.Flipflop => CreateNodeControl<FlipflopNodeControl, FlipflopNodeControlViewModel>(nodeModelBase),
|
||||
NodeControlType.ExpCondition => CreateNodeControl<ConditionNodeControl, ConditionNodeControlViewModel>(nodeModelBase),
|
||||
NodeControlType.ExpOp => CreateNodeControl<ExpOpNodeControl, ExpOpNodeViewModel>(nodeModelBase),
|
||||
NodeControlType.ConditionRegion => CreateNodeControl<ConditionRegionControl, ConditionRegionNodeControlViewModel>(nodeModelBase),
|
||||
_ => null,
|
||||
};
|
||||
if(nodeControl == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// MethodDetails methodDetailss = eventArgs.MethodDetailss;
|
||||
Position position = eventArgs.Position;
|
||||
|
||||
|
||||
NodeControls.TryAdd(nodeModelBase.Guid, nodeControl);
|
||||
|
||||
if (!TryPlaceNodeInRegion(nodeControl))
|
||||
{
|
||||
PlaceNodeOnCanvas(nodeControl, canvasDropPosition.X, canvasDropPosition.Y);
|
||||
}
|
||||
// 创建对应控件
|
||||
NodeControlBase? nodeControl = nodeModelBase.ControlType switch
|
||||
{
|
||||
NodeControlType.Action => CreateNodeControl<ActionNodeControl, ActionNodeControlViewModel>(nodeModelBase), //typeof(ActionNodeControl),
|
||||
NodeControlType.Flipflop => CreateNodeControl<FlipflopNodeControl, FlipflopNodeControlViewModel>(nodeModelBase),
|
||||
NodeControlType.ExpCondition => CreateNodeControl<ConditionNodeControl, ConditionNodeControlViewModel>(nodeModelBase),
|
||||
NodeControlType.ExpOp => CreateNodeControl<ExpOpNodeControl, ExpOpNodeViewModel>(nodeModelBase),
|
||||
NodeControlType.ConditionRegion => CreateNodeControl<ConditionRegionControl, ConditionRegionNodeControlViewModel>(nodeModelBase),
|
||||
_ => null,
|
||||
};
|
||||
if (nodeControl == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
NodeControls.TryAdd(nodeModelBase.Guid, nodeControl);
|
||||
|
||||
if (eventArgs.IsAddInRegion && NodeControls.TryGetValue(eventArgs.RegeionGuid, out NodeControlBase? regionControl))
|
||||
{
|
||||
if (regionControl is not null)
|
||||
{
|
||||
TryPlaceNodeInRegion(regionControl, nodeControl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TryPlaceNodeInRegion(nodeControl, position))
|
||||
{
|
||||
PlaceNodeOnCanvas(nodeControl, position.X, position.Y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -365,29 +421,32 @@ namespace Serein.WorkBench
|
||||
/// <param name="newNodeGuid"></param>
|
||||
private void FlowEnvironment_StartNodeChangeEvent(StartNodeChangeEventArgs eventArgs)
|
||||
{
|
||||
string oldNodeGuid = eventArgs.OldNodeGuid;
|
||||
string newNodeGuid = eventArgs.NewNodeGuid;
|
||||
if (!NodeControls.TryGetValue(newNodeGuid, out var newStartNodeControl))
|
||||
this.Dispatcher.Invoke(() =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (newStartNodeControl == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(oldNodeGuid))
|
||||
{
|
||||
NodeControls.TryGetValue(oldNodeGuid, out var oldStartNodeControl);
|
||||
if (oldStartNodeControl != null)
|
||||
string oldNodeGuid = eventArgs.OldNodeGuid;
|
||||
string newNodeGuid = eventArgs.NewNodeGuid;
|
||||
if (!NodeControls.TryGetValue(newNodeGuid, out var newStartNodeControl))
|
||||
{
|
||||
oldStartNodeControl.BorderBrush = Brushes.Black;
|
||||
oldStartNodeControl.BorderThickness = new Thickness(0);
|
||||
return;
|
||||
}
|
||||
if (newStartNodeControl == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(oldNodeGuid))
|
||||
{
|
||||
NodeControls.TryGetValue(oldNodeGuid, out var oldStartNodeControl);
|
||||
if (oldStartNodeControl != null)
|
||||
{
|
||||
oldStartNodeControl.BorderBrush = Brushes.Black;
|
||||
oldStartNodeControl.BorderThickness = new Thickness(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
|
||||
newStartNodeControl.BorderThickness = new Thickness(2);
|
||||
newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
|
||||
newStartNodeControl.BorderThickness = new Thickness(2);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -396,31 +455,7 @@ namespace Serein.WorkBench
|
||||
|
||||
|
||||
#region 加载 DynamicNodeFlow 文件
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var project = App.FData;
|
||||
if (project == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeCanvas(project.Basic.canvas.width, project.Basic.canvas.lenght);// 设置画布大小
|
||||
FlowEnvironment.LoadProject(project, App.FileDataPath); // 加载项目
|
||||
|
||||
|
||||
//LoadDll(project); // 加载DLL
|
||||
//LoadNodeControls(project); // 加载节点
|
||||
|
||||
//var startNode = nodeControls.Values.FirstOrDefault(control => control.ViewModel.Node.Guid.Equals(project.StartNode));
|
||||
//var startNodeGuid = nodeControls.Keys.FirstOrDefault(guid => guid.Equals(project.StartNode));
|
||||
//if (!string.IsNullOrEmpty(startNodeGuid))
|
||||
//{
|
||||
// FlowEnvironment.SetStartNode(startNodeGuid);
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 运行环节加载了项目文件,需要创建节点控件
|
||||
/// </summary>
|
||||
@@ -433,17 +468,17 @@ namespace Serein.WorkBench
|
||||
// 创建控件实例
|
||||
NodeControlBase nodeControl = nodeInfo.Type switch
|
||||
{
|
||||
$"{NodeSpaceName}.{nameof(SingleActionNode)}" =>
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" =>
|
||||
CreateNodeControl<SingleActionNode, ActionNodeControl, ActionNodeControlViewModel>(methodDetailss),// 动作节点控件
|
||||
$"{NodeSpaceName}.{nameof(SingleFlipflopNode)}" =>
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" =>
|
||||
CreateNodeControl<SingleFlipflopNode, FlipflopNodeControl, FlipflopNodeControlViewModel>(methodDetailss), // 触发器节点控件
|
||||
|
||||
$"{NodeSpaceName}.{nameof(SingleConditionNode)}" =>
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" =>
|
||||
CreateNodeControl<SingleConditionNode, ConditionNodeControl, ConditionNodeControlViewModel>(), // 条件表达式控件
|
||||
$"{NodeSpaceName}.{nameof(SingleExpOpNode)}" =>
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" =>
|
||||
CreateNodeControl<SingleExpOpNode, ExpOpNodeControl, ExpOpNodeViewModel>(), // 操作表达式控件
|
||||
|
||||
$"{NodeSpaceName}.{nameof(CompositeConditionNode)}" =>
|
||||
$"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" =>
|
||||
CreateNodeControl<CompositeConditionNode, ConditionRegionControl, ConditionRegionNodeControlViewModel>(), // 条件区域控件
|
||||
_ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.Type}"),
|
||||
};
|
||||
@@ -755,13 +790,14 @@ namespace Serein.WorkBench
|
||||
/// <param name="e"></param>
|
||||
private void FlowChartCanvas_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点
|
||||
var canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点
|
||||
Position position = new Position(canvasDropPosition.X, canvasDropPosition.Y);
|
||||
if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas))
|
||||
{
|
||||
if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData)
|
||||
{
|
||||
// 创建DLL文件的节点对象
|
||||
FlowEnvironment.CreateNode(nodeData.NodeControlType, nodeData.MethodDetails);
|
||||
FlowEnvironment.CreateNode(nodeData.NodeControlType, position, nodeData.MethodDetails);
|
||||
}
|
||||
}
|
||||
else if (e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas))
|
||||
@@ -778,7 +814,7 @@ namespace Serein.WorkBench
|
||||
if(nodeControlType != NodeControlType.None)
|
||||
{
|
||||
// 创建基础节点对象
|
||||
FlowEnvironment.CreateNode(nodeControlType);
|
||||
FlowEnvironment.CreateNode(nodeControlType, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -792,9 +828,10 @@ namespace Serein.WorkBench
|
||||
/// <param name="dropPosition"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <returns></returns>
|
||||
private bool TryPlaceNodeInRegion(NodeControlBase nodeControl)
|
||||
private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, Position position)
|
||||
{
|
||||
HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, canvasDropPosition);
|
||||
var point = new Point(position.X, position.Y);
|
||||
HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, point);
|
||||
if (hitTestResult != null && hitTestResult.VisualHit is UIElement hitElement)
|
||||
{
|
||||
// 准备放置条件表达式控件
|
||||
@@ -803,10 +840,12 @@ namespace Serein.WorkBench
|
||||
ConditionRegionControl conditionRegion = GetParentOfType<ConditionRegionControl>(hitElement);
|
||||
if (conditionRegion != null)
|
||||
{
|
||||
// 如果存在条件区域容器
|
||||
conditionRegion.AddCondition(nodeControl);
|
||||
TryPlaceNodeInRegion(conditionRegion, nodeControl);
|
||||
//// 如果存在条件区域容器
|
||||
//conditionRegion.AddCondition(nodeControl);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -831,8 +870,26 @@ namespace Serein.WorkBench
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将节点放在目标区域中
|
||||
/// </summary>
|
||||
/// <param name="regionControl">区域容器</param>
|
||||
/// <param name="nodeControl">节点控件</param>
|
||||
private void TryPlaceNodeInRegion(NodeControlBase regionControl, NodeControlBase nodeControl)
|
||||
{
|
||||
// 准备放置条件表达式控件
|
||||
if (nodeControl.ViewModel.Node.ControlType == NodeControlType.ExpCondition)
|
||||
{
|
||||
ConditionRegionControl conditionRegion = regionControl as ConditionRegionControl;
|
||||
if (conditionRegion != null)
|
||||
{
|
||||
// 如果存在条件区域容器
|
||||
conditionRegion.AddCondition(nodeControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 拖动效果,根据拖放数据是否为指定类型设置拖放效果
|
||||
/// </summary>
|
||||
@@ -1038,6 +1095,7 @@ namespace Serein.WorkBench
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 画布中框选节点控件动作
|
||||
|
||||
/// <summary>
|
||||
@@ -1120,7 +1178,7 @@ namespace Serein.WorkBench
|
||||
/// </summary>
|
||||
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed)
|
||||
if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) // 正在选取节点
|
||||
{
|
||||
// 获取当前鼠标位置
|
||||
Point currentPoint = e.GetPosition(FlowChartCanvas);
|
||||
@@ -1137,7 +1195,8 @@ namespace Serein.WorkBench
|
||||
SelectionRectangle.Height = height;
|
||||
}
|
||||
|
||||
if (IsConnecting)
|
||||
|
||||
if (IsConnecting) // 正在连接节点
|
||||
{
|
||||
Point position = e.GetPosition(FlowChartCanvas);
|
||||
if (currentLine == null || startConnectNodeControl == null)
|
||||
@@ -1149,7 +1208,7 @@ namespace Serein.WorkBench
|
||||
currentLine.X2 = position.X;
|
||||
currentLine.Y2 = position.Y;
|
||||
}
|
||||
if (IsCanvasDragging)
|
||||
if (IsCanvasDragging) // 正在移动画布
|
||||
{
|
||||
Point currentMousePosition = e.GetPosition(this);
|
||||
double deltaX = currentMousePosition.X - startPoint.X;
|
||||
@@ -1160,9 +1219,6 @@ namespace Serein.WorkBench
|
||||
|
||||
startPoint = currentMousePosition;
|
||||
|
||||
// AdjustCanvasSizeAndContent(deltaX, deltaY);
|
||||
|
||||
|
||||
foreach (var line in Connections)
|
||||
{
|
||||
line.Refresh();
|
||||
@@ -1173,6 +1229,7 @@ namespace Serein.WorkBench
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 拖动画布实现缩放平移效果
|
||||
private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
@@ -1197,16 +1254,25 @@ namespace Serein.WorkBench
|
||||
// 单纯缩放画布,不改变画布大小
|
||||
private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
//var w = (int)(FlowChartCanvas.Width * scaleTransform.ScaleX);
|
||||
//var h = (int)(FlowChartCanvas.Height * scaleTransform.ScaleY);
|
||||
|
||||
//var TMP1 = w / FlowChartStackPanel.ActualWidth < 0.9;
|
||||
//var TMP2 = h / FlowChartStackPanel.ActualHeight < 0.9;
|
||||
|
||||
//Console.WriteLine("w"+(w, FlowChartStackPanel.ActualWidth, TMP1));
|
||||
//Console.WriteLine("h"+(h, FlowChartStackPanel.ActualHeight, TMP2));
|
||||
|
||||
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
{
|
||||
if (e.Delta < 0 && scaleTransform.ScaleX < 0.2) return;
|
||||
if (e.Delta > 0 && scaleTransform.ScaleX > 2.0) return;
|
||||
if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return;
|
||||
double scale = e.Delta > 0 ? 0.1 : -0.1;
|
||||
|
||||
|
||||
scaleTransform.ScaleX += scale;
|
||||
scaleTransform.ScaleY += scale;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1215,6 +1281,8 @@ namespace Serein.WorkBench
|
||||
{
|
||||
FlowChartCanvas.Width = width;
|
||||
FlowChartCanvas.Height = height;
|
||||
//FlowChartStackPanel.Width = width;
|
||||
//FlowChartStackPanel.Height = height;
|
||||
}
|
||||
|
||||
|
||||
@@ -1258,19 +1326,49 @@ namespace Serein.WorkBench
|
||||
|
||||
private void Thumb_DragDelta_BottomRight(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
// 从右下角调整大小
|
||||
double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0);
|
||||
double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0);
|
||||
// 获取缩放后的水平和垂直变化
|
||||
double horizontalChange = e.HorizontalChange * scaleTransform.ScaleX;
|
||||
double verticalChange = e.VerticalChange * scaleTransform.ScaleY;
|
||||
|
||||
// 计算新的宽度和高度,确保不会小于400
|
||||
double newWidth = Math.Max(FlowChartCanvas.ActualWidth + horizontalChange, 400);
|
||||
double newHeight = Math.Max(FlowChartCanvas.ActualHeight + verticalChange, 400);
|
||||
|
||||
|
||||
newWidth = newWidth < 400 ? 400 : newWidth;
|
||||
newHeight = newHeight < 400 ? 400 : newHeight;
|
||||
|
||||
// 更新 Canvas 大小
|
||||
FlowChartCanvas.Width = newWidth;
|
||||
FlowChartCanvas.Height = newHeight;
|
||||
|
||||
// 如果宽度和高度超过400,调整TranslateTransform以保持左上角不动
|
||||
if (newWidth > 400 && newHeight > 400)
|
||||
{
|
||||
// 计算平移的变化,保持左上角不动
|
||||
double deltaX = -horizontalChange / 2; // 水平方向的平移
|
||||
double deltaY = -verticalChange / 2; // 垂直方向的平移
|
||||
|
||||
// 调整TranslateTransform以补偿尺寸变化
|
||||
translateTransform.X += deltaX;
|
||||
translateTransform.Y += deltaY;
|
||||
}
|
||||
|
||||
//// 从右下角调整大小
|
||||
//double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0);
|
||||
//double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0);
|
||||
|
||||
//newWidth = newWidth < 400 ? 400 : newWidth;
|
||||
//newHeight = newHeight < 400 ? 400 : newHeight;
|
||||
|
||||
//if (newWidth > 400 && newHeight > 400)
|
||||
//{
|
||||
// FlowChartCanvas.Width = newWidth;
|
||||
// FlowChartCanvas.Height = newHeight;
|
||||
|
||||
// double x = e.HorizontalChange > 0 ? -0.5 : 0.5;
|
||||
// double y = e.VerticalChange > 0 ? -0.5 : 0.5;
|
||||
|
||||
// double deltaX = x * scaleTransform.ScaleX;
|
||||
// double deltaY = y * scaleTransform.ScaleY;
|
||||
// Test(deltaX, deltaY);
|
||||
//}
|
||||
}
|
||||
|
||||
//private void Thumb_DragDelta_Left(object sender, DragDeltaEventArgs e)
|
||||
@@ -1285,30 +1383,113 @@ namespace Serein.WorkBench
|
||||
private void Thumb_DragDelta_Right(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
//从右侧调整大小
|
||||
double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0);
|
||||
newWidth = newWidth < 400 ? 400 : newWidth;
|
||||
//double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0);
|
||||
//newWidth = newWidth < 400 ? 400 : newWidth;
|
||||
//if (newWidth > 400)
|
||||
//{
|
||||
// FlowChartCanvas.Width = newWidth;
|
||||
|
||||
// double x = e.HorizontalChange > 0 ? -0.5 : 0.5;
|
||||
// double y = 0;
|
||||
|
||||
// double deltaX = x * scaleTransform.ScaleX;
|
||||
// double deltaY = y * 0;
|
||||
// Test(deltaX, deltaY);
|
||||
//}
|
||||
|
||||
|
||||
|
||||
// 获取缩放后的水平和垂直变化
|
||||
double horizontalChange = e.HorizontalChange * scaleTransform.ScaleX;
|
||||
//double verticalChange = e.VerticalChange * scaleTransform.ScaleY;
|
||||
|
||||
// 计算新的宽度和高度,确保不会小于400
|
||||
double newWidth = Math.Max(FlowChartCanvas.ActualWidth + horizontalChange, 400);
|
||||
//double newHeight = Math.Max(FlowChartCanvas.ActualHeight + verticalChange, 400);
|
||||
|
||||
// 更新 Canvas 大小
|
||||
FlowChartCanvas.Width = newWidth;
|
||||
//FlowChartCanvas.Height = newHeight;
|
||||
|
||||
// 如果宽度和高度超过400,调整TranslateTransform以保持左上角不动
|
||||
if (newWidth > 400 /*&& newHeight > 400*/)
|
||||
{
|
||||
// 计算平移的变化,保持左上角不动
|
||||
double deltaX = -horizontalChange / 2; // 水平方向的平移
|
||||
//double deltaY = -verticalChange / 2; // 垂直方向的平移
|
||||
|
||||
// 调整TranslateTransform以补偿尺寸变化
|
||||
translateTransform.X += deltaX;
|
||||
//translateTransform.Y += deltaY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//private void Thumb_DragDelta_Top(object sender, DragDeltaEventArgs e)
|
||||
//{
|
||||
// // 从顶部调整大小
|
||||
// double newHeight = Math.Max(FlowChartCanvas.ActualHeight - e.VerticalChange, 0);
|
||||
|
||||
|
||||
// FlowChartCanvas.Height = newHeight;
|
||||
// Canvas.SetTop(FlowChartCanvas, Canvas.GetTop(FlowChartCanvas) + e.VerticalChange);
|
||||
//}
|
||||
|
||||
private void Thumb_DragDelta_Bottom(object sender, DragDeltaEventArgs e)
|
||||
{
|
||||
// 从底部调整大小
|
||||
double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0);
|
||||
newHeight = newHeight < 400 ? 400 : newHeight;
|
||||
//// 从底部调整大小
|
||||
//double oldHeight = FlowChartCanvas.Height;
|
||||
|
||||
//double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0);
|
||||
////newHeight = newHeight < 400 ? 400 : newHeight;
|
||||
//if(newHeight > 400)
|
||||
//{
|
||||
// FlowChartCanvas.Height = newHeight;
|
||||
|
||||
// double x = 0;
|
||||
// double y = e.VerticalChange > 0 ? -0.5 : 0.5 ;
|
||||
|
||||
// double deltaX = x * 0;
|
||||
// double deltaY = y * (scaleTransform.ScaleY);
|
||||
|
||||
// Test(deltaX, deltaY);
|
||||
//}
|
||||
|
||||
|
||||
// 获取缩放后的水平和垂直变化
|
||||
//double horizontalChange = e.HorizontalChange * scaleTransform.ScaleX;
|
||||
double verticalChange = e.VerticalChange * scaleTransform.ScaleY;
|
||||
|
||||
// 计算新的宽度和高度,确保不会小于400
|
||||
//double newWidth = Math.Max(FlowChartCanvas.ActualWidth + horizontalChange, 400);
|
||||
double newHeight = Math.Max(FlowChartCanvas.ActualHeight + verticalChange, 400);
|
||||
|
||||
// 更新 Canvas 大小
|
||||
//FlowChartCanvas.Width = newWidth;
|
||||
FlowChartCanvas.Height = newHeight;
|
||||
|
||||
// 如果宽度和高度超过400,调整TranslateTransform以保持左上角不动
|
||||
if (/*newWidth > 400 &&*/ newHeight > 400)
|
||||
{
|
||||
// 计算平移的变化,保持左上角不动
|
||||
//double deltaX = -horizontalChange / 2; // 水平方向的平移
|
||||
double deltaY = -verticalChange / 2; // 垂直方向的平移
|
||||
|
||||
// 调整TranslateTransform以补偿尺寸变化
|
||||
//translateTransform.X += deltaX;
|
||||
translateTransform.Y += deltaY;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void Test(double deltaX, double deltaY)
|
||||
{
|
||||
translateTransform.X += deltaX;
|
||||
translateTransform.Y += deltaY;
|
||||
//Console.WriteLine((translateTransform.X, translateTransform.Y));
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
@@ -1358,7 +1539,8 @@ namespace Serein.WorkBench
|
||||
/// <param name="e"></param>
|
||||
private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FlowEnvironment.Exit(); // 在运行平台上点击了退出
|
||||
Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
|
||||
FlowEnvironment?.Exit(); // 在运行平台上点击了退出
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1373,26 +1555,24 @@ namespace Serein.WorkBench
|
||||
var projectData = FlowEnvironment.SaveProject();
|
||||
projectData.Basic = new Basic
|
||||
{
|
||||
canvas = new FlowCanvas
|
||||
Canvas = new FlowCanvas
|
||||
{
|
||||
lenght = (float)FlowChartCanvas.Width,
|
||||
width = (float)FlowChartCanvas.Height,
|
||||
Lenght = (float)FlowChartCanvas.Width,
|
||||
Width = (float)FlowChartCanvas.Height,
|
||||
},
|
||||
versions = "1",
|
||||
Versions = "1",
|
||||
};
|
||||
|
||||
foreach(var node in projectData.Nodes)
|
||||
{
|
||||
var control = new ActionNodeControl(null);// GetControl(node.Guid);
|
||||
Point positionRelativeToParent = control.TranslatePoint(new Point(0, 0), FlowChartCanvas);
|
||||
|
||||
node.Position = new Position
|
||||
|
||||
if(NodeControls.TryGetValue(node.Guid,out var nodeControl))
|
||||
{
|
||||
X = (float)positionRelativeToParent.X,
|
||||
Y = (float)positionRelativeToParent.Y,
|
||||
};
|
||||
Point positionRelativeToParent = nodeControl.TranslatePoint(new Point(0, 0), FlowChartCanvas);
|
||||
node.Position = new Position(positionRelativeToParent.X, positionRelativeToParent.Y);
|
||||
}
|
||||
}
|
||||
var projectJsonData = JArray.FromObject(projectData);
|
||||
var projectJsonData = JObject.FromObject(projectData);
|
||||
var savePath = SaveContentToFile(projectJsonData.ToString());
|
||||
savePath = System.IO.Path.GetDirectoryName(savePath);
|
||||
|
||||
@@ -1696,8 +1876,6 @@ namespace Serein.WorkBench
|
||||
return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
#region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线
|
||||
@@ -1725,7 +1903,9 @@ namespace Serein.WorkBench
|
||||
canvas.Children.Add(connection.ArrowPath);
|
||||
}
|
||||
|
||||
|
||||
BezierLineDrawer.UpdateBezierLine(canvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
@@ -1826,7 +2006,8 @@ namespace Serein.WorkBench
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
BsControl.Draw(Canvas, this);
|
||||
// BsControl.Draw(Canvas, this);
|
||||
BezierLineDrawer.UpdateBezierLine(Canvas, Start, End, BezierPath, ArrowPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1925,6 +2106,7 @@ namespace Serein.WorkBench
|
||||
arrowGeometry.Figures.Add(arrowFigure);
|
||||
|
||||
arrowPath.Data = arrowGeometry;
|
||||
|
||||
}
|
||||
// 计算终点落点位置
|
||||
private static Point CalculateEndpointOutsideElement(FrameworkElement element, Canvas canvas, Point startPoint, out Localhost localhost)
|
||||
|
||||
@@ -35,9 +35,9 @@ namespace Serein.WorkBench.Node.ViewModel
|
||||
public ConditionNodeControlViewModel(SingleConditionNode node) : base(node)
|
||||
{
|
||||
this.singleConditionNode = node;
|
||||
IsCustomData = false;
|
||||
CustomData = "";
|
||||
Expression = "PASS";
|
||||
//IsCustomData = false;
|
||||
//CustomData = "";
|
||||
//Expression = "PASS";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user