mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
1. 重新设计了 JSON门户类的实现
2. Script脚本添加了原始字符串的实现 3. 修复了Script中无法对 \" 双引号转义的问题 4. 新增了对于集合嵌套取值的支持(目前仅是集合取值) 5. 重新设计了FlowWorkManagement任务启动的逻辑,修复了触发器无法正常运行的问题 6. 在ScriptBaseFunc中新增了 json() 本地函数,支持将字符串转为IJsonToken进行取值。 7. EmitHelper对于集合取值时,反射获取“get_item”委托时存在看你多个MethodInfo,现在可以传入子项类型,帮助匹配目标重载方法
This commit is contained in:
@@ -1002,7 +1002,7 @@ namespace Serein.Library.Api
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="type">输出类型</param>
|
||||
/// <param name="class">输出级别</param>
|
||||
void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial);
|
||||
void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Debug);
|
||||
/// <summary>
|
||||
/// <para>提供设置UI上下文的能力</para>
|
||||
/// <para>提供设置UI上下文的能力,在WinForm/WPF项目中,在UI线程外对UI元素的修改将会导致异常</para>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -9,8 +10,22 @@ namespace Serein.Library.Api
|
||||
/// <summary>
|
||||
/// JSON数据交互的Token接口,允许使用不同的JSON库进行数据处理。
|
||||
/// </summary>
|
||||
public interface IJsonToken
|
||||
public interface IJsonToken
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 Token
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
IJsonToken this[object name] { get; }
|
||||
|
||||
/* /// <summary>
|
||||
/// 获取 Token 数组的元素,允许通过索引访问数组中的元素。
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
IJsonToken this[int index] { get; }*/
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定名称的属性,如果存在则返回true,并通过out参数返回对应的IJsonToken对象。
|
||||
/// </summary>
|
||||
@@ -83,6 +98,7 @@ namespace Serein.Library.Api
|
||||
/// </summary>
|
||||
public interface IJsonProvider
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// JSON文本转为指定类型
|
||||
/// </summary>
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace Serein.Library
|
||||
public enum InfoClass
|
||||
{
|
||||
/// <summary>
|
||||
/// 琐碎的
|
||||
/// 调试
|
||||
/// </summary>
|
||||
Trivial,
|
||||
Debug,
|
||||
/// <summary>
|
||||
/// 一般的
|
||||
/// </summary>
|
||||
|
||||
@@ -37,15 +37,13 @@ namespace Serein.Library
|
||||
UI,
|
||||
|
||||
/// <summary>
|
||||
/// <para>触发器节点,必须为标记在可异步等待的方法,建议与继承了 FlowTriggerk<TEnum> 的实例对象搭配使用</para>
|
||||
/// <para>触发器节点,必须为标记在可异步等待的方法</para>
|
||||
/// <para>方法返回值必须为Task<IFlipflopContext<TResult>>,若为其它返回值,将不会创建节点。</para>
|
||||
/// <para>触发器根据在分支中的位置,分为两种类型:流程分支中的触发器、全局触发器</para>
|
||||
/// <para>一般的触发器:存在于分支某处,也可能是分支的终点,但一定不是流程的起点与分支的起点。</para>
|
||||
/// <para>一般的触发器行为:在当前分支中执行一次之后不再执行,一般用于等待某个操作的响应。</para>
|
||||
/// <para>一般的触发器入参:如果使用了 FlowTriggerk<TEnum> ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。</para>
|
||||
/// <para>全局触发器:没有上游分支、同时并非流程的起始节点。</para>
|
||||
/// <para>全局触发器行为:全局触发器会循环执行,直到流程结束。</para>
|
||||
/// <para>一般的触发器入参:如果使用了 FlowTriggerk<TEnum> ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。</para>
|
||||
/// </summary>
|
||||
Flipflop,
|
||||
/// <summary>
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
/// <param name="type">类型信息</param>
|
||||
/// <param name="emitType">操作类型</param>
|
||||
public DelegateDetails(Type type, EmitType emitType)
|
||||
public DelegateDetails(Type type, EmitType emitType, Type? itemType = null)
|
||||
{
|
||||
if (emitType == EmitType.CollectionSetter)
|
||||
{
|
||||
@@ -170,7 +170,7 @@ namespace Serein.Library
|
||||
else if (emitType == EmitType.CollectionGetter)
|
||||
{
|
||||
this.emitType = EmitType.CollectionGetter;
|
||||
collectionGetter = EmitHelper.CreateCollectionGetter(type);
|
||||
collectionGetter = EmitHelper.CreateCollectionGetter(type, itemType);
|
||||
}
|
||||
else if (emitType == EmitType.ArrayCreate)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library
|
||||
@@ -82,33 +83,92 @@ namespace Serein.Library
|
||||
/// <summary>
|
||||
/// 触发类型
|
||||
/// </summary>
|
||||
|
||||
public TriggerDescription Type { get; set; }
|
||||
/// <summary>
|
||||
/// 触发时传递的数据
|
||||
/// </summary>
|
||||
public TResult Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发器上下文构造函数
|
||||
/// </summary>
|
||||
/// <param name="ffState"></param>
|
||||
public FlipflopContext(FlipflopStateType ffState)
|
||||
public FlipflopContext()
|
||||
{
|
||||
State = ffState;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发器上下文构造函数,传入状态和数据值
|
||||
/// 成功触发器上下文,表示触发器执行成功并返回结果
|
||||
/// </summary>
|
||||
/// <param name="ffState"></param>
|
||||
/// <param name="value"></param>
|
||||
public FlipflopContext(FlipflopStateType ffState, TResult value)
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Ok<TResult>(TResult result)
|
||||
{
|
||||
State = ffState;
|
||||
Value = value;
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = FlipflopStateType.Succeed,
|
||||
Type = TriggerDescription.External,
|
||||
Value = result,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示触发器执行失败
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Fail()
|
||||
{
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = FlipflopStateType.Fail,
|
||||
Type = TriggerDescription.External,
|
||||
Value = default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示触发器执行过程中发生了错误
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Error()
|
||||
{
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = FlipflopStateType.Error,
|
||||
Type = TriggerDescription.External,
|
||||
Value = default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消触发器上下文,表示触发器被外部取消
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Cancel()
|
||||
{
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = FlipflopStateType.Cancel,
|
||||
Type = TriggerDescription.External,
|
||||
Value = default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 超时触发器上下文,表示触发器在指定时间内未完成
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Overtime(FlipflopStateType state = FlipflopStateType.Fail)
|
||||
{
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = state,
|
||||
Type = TriggerDescription.Overtime,
|
||||
Value = default,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Serein.Library
|
||||
/// 起始节点
|
||||
/// </summary>
|
||||
[DataInfo]
|
||||
private IFlowNode _startNode;
|
||||
private IFlowNode? _startNode;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Serein.Library
|
||||
{
|
||||
|
||||
private readonly SortedDictionary<string, CallNode> _callNodes = new SortedDictionary<string,CallNode>();
|
||||
//private readonly Dictionary<string, CallNode> _callNodes = new Dictionary<string,CallNode>();
|
||||
|
||||
/// <summary>
|
||||
/// 索引器,允许通过字符串索引访问CallNode
|
||||
@@ -700,7 +699,7 @@ namespace Serein.Library
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial)
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Debug)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -134,6 +135,28 @@ namespace Serein.Library
|
||||
return obj?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
|
||||
#region JSON挂载方法
|
||||
|
||||
/// <summary>
|
||||
/// 转为JSON对象
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
public static IJsonToken json(string content)
|
||||
{
|
||||
return JsonHelper.Parse(content);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取全局数据
|
||||
/// </summary>
|
||||
@@ -153,11 +176,11 @@ namespace Serein.Library
|
||||
{
|
||||
return type.GetType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录日志信息
|
||||
/// 输出内容
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
|
||||
public static void log(object value)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, value?.ToString());
|
||||
@@ -168,18 +191,9 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task sleep(object value)
|
||||
public static async Task sleep(int value)
|
||||
{
|
||||
if (value is int @int)
|
||||
{
|
||||
Console.WriteLine($"等待{@int}ms");
|
||||
await Task.Delay(@int);
|
||||
}
|
||||
else if (value is TimeSpan timeSpan)
|
||||
{
|
||||
Console.WriteLine($"等待{timeSpan}");
|
||||
await Task.Delay(timeSpan);
|
||||
}
|
||||
await Task.Delay(value);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
@@ -504,58 +505,97 @@ namespace Serein.Library.Utils
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构建集合获取委托:Func<object, object, object>
|
||||
/// 构建集合取值委托:(object collection, object index) => object value
|
||||
/// 支持数组、泛型集合、IDictionary 等类型
|
||||
/// </summary>
|
||||
/// <param name="collectionType"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
public static Func<object, object, object> CreateCollectionGetter(Type collectionType)
|
||||
public static Func<object, object, object> CreateCollectionGetter(Type collectionType, Type? itemType = null)
|
||||
{
|
||||
DynamicMethod dm = new DynamicMethod(
|
||||
"GetCollectionValue",
|
||||
typeof(object),
|
||||
new[] { typeof(object), typeof(object) },
|
||||
typeof(EmitHelper).Module,
|
||||
true);
|
||||
skipVisibility: true);
|
||||
|
||||
ILGenerator il = dm.GetILGenerator();
|
||||
|
||||
// 数组类型处理
|
||||
if (collectionType.IsArray)
|
||||
{
|
||||
// (object array, object index) => ((T[])array)[(int)index]
|
||||
var elementType = collectionType.GetElementType()!;
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, collectionType); // 转为真实数组类型
|
||||
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Unbox_Any, typeof(int)); // index
|
||||
|
||||
il.Emit(OpCodes.Unbox_Any, typeof(int)); // 转为int索引
|
||||
il.Emit(OpCodes.Ldelem, elementType); // 取值
|
||||
|
||||
if (elementType.IsValueType)
|
||||
il.Emit(OpCodes.Box, elementType); // 装箱
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
else
|
||||
// 非泛型 IDictionary 类型(如 Hashtable、JObject)
|
||||
else if (IsGenericDictionaryType(collectionType, out var keyType, out var valueType))
|
||||
{
|
||||
// 调用 get_Item 方法
|
||||
MethodInfo? getItem = collectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public);
|
||||
var getItem = collectionType.GetMethod("get_Item", new[] { keyType });
|
||||
if (getItem == null)
|
||||
throw new NotSupportedException($"类型 {collectionType} 不支持 get_Item。");
|
||||
throw new NotSupportedException($"{collectionType} 未实现 get_Item({keyType})");
|
||||
|
||||
var parameters = getItem.GetParameters();
|
||||
var indexType = parameters[0].ParameterType;
|
||||
var returnType = getItem.ReturnType;
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, collectionType);
|
||||
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
if (indexType.IsValueType)
|
||||
il.Emit(OpCodes.Unbox_Any, indexType);
|
||||
if (keyType.IsValueType)
|
||||
il.Emit(OpCodes.Unbox_Any, keyType);
|
||||
else
|
||||
il.Emit(OpCodes.Castclass, indexType);
|
||||
il.Emit(OpCodes.Castclass, keyType);
|
||||
|
||||
il.Emit(OpCodes.Callvirt, getItem);
|
||||
|
||||
if (returnType.IsValueType)
|
||||
il.Emit(OpCodes.Box, returnType);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
// 实现 get_Item 方法的类型(如 List<T>, Dictionary<TKey, TValue> 等)
|
||||
else
|
||||
{
|
||||
/*var methodInfos = collectionType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
|
||||
MethodInfo? getItem;
|
||||
|
||||
if (methodInfos.Length > 1)
|
||||
{
|
||||
getItem = methodInfos.Where(m => m.Name.Equals("get_Item")).Where(m =>
|
||||
{
|
||||
var ps = m.GetParameters().ToArray();
|
||||
if (ps.Length > 1) return false;
|
||||
if (ps[0].ParameterType == typeof(object)) return false;
|
||||
return true;
|
||||
}).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
//getItem = collectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public);
|
||||
getItem = methodInfos.First(m => m.Name.Equals("get_Item"));
|
||||
}
|
||||
*/
|
||||
// GetMethod(name, bindingAttr, binder: null, types, modifiers: null);
|
||||
MethodInfo? getItem = collectionType.GetMethod("get_Item", bindingAttr: BindingFlags.Instance | BindingFlags.Public, binder: null, types: [itemType], modifiers: null);
|
||||
if (getItem == null)
|
||||
throw new NotSupportedException($"类型 {collectionType} 不支持 get_Item。");
|
||||
|
||||
var indexParamType = getItem.GetParameters()[0].ParameterType;
|
||||
var returnType = getItem.ReturnType;
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, collectionType);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
if (indexParamType.IsValueType)
|
||||
il.Emit(OpCodes.Unbox_Any, indexParamType);
|
||||
else
|
||||
il.Emit(OpCodes.Castclass, indexParamType);
|
||||
|
||||
il.Emit(OpCodes.Callvirt, getItem);
|
||||
|
||||
@@ -568,6 +608,29 @@ namespace Serein.Library.Utils
|
||||
return (Func<object, object, object>)dm.CreateDelegate(typeof(Func<object, object, object>));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static bool IsGenericDictionaryType(Type type, out Type keyType, out Type valueType)
|
||||
{
|
||||
keyType = null!;
|
||||
valueType = null!;
|
||||
|
||||
var dictInterface = type
|
||||
.GetInterfaces()
|
||||
.FirstOrDefault(t =>
|
||||
t.IsGenericType &&
|
||||
t.GetGenericTypeDefinition() == typeof(IDictionary<,>));
|
||||
|
||||
if (dictInterface != null)
|
||||
{
|
||||
var args = dictInterface.GetGenericArguments();
|
||||
keyType = args[0];
|
||||
valueType = args[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,16 @@ namespace Serein.Library.Utils
|
||||
/// <returns>对应的 Channel</returns>
|
||||
private Channel<TriggerResult<object>> GetOrCreateChannel(TSignal signal)
|
||||
{
|
||||
return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded<TriggerResult<object>>());
|
||||
if(_channels.TryGetValue(signal, out var channel))
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
else
|
||||
{
|
||||
channel = Channel.CreateUnbounded<TriggerResult<object>>();
|
||||
_channels.AddOrUpdate(signal, _ => channel, (s, r) => channel = r);
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace Serein.Library
|
||||
/// <param name="class"></param>
|
||||
public static void WriteLine(Exception ex, InfoClass @class = InfoClass.General)
|
||||
{
|
||||
if(@class == InfoClass.Trivial)
|
||||
if(@class == InfoClass.Debug)
|
||||
{
|
||||
|
||||
SereinEnv.environment.WriteLine(InfoType.ERROR, ex.ToString(), @class);
|
||||
|
||||
Reference in New Issue
Block a user