1. 重新设计了 JSON门户类的实现

2. Script脚本添加了原始字符串的实现
3. 修复了Script中无法对  \" 双引号转义的问题
4. 新增了对于集合嵌套取值的支持(目前仅是集合取值)
5. 重新设计了FlowWorkManagement任务启动的逻辑,修复了触发器无法正常运行的问题
6. 在ScriptBaseFunc中新增了 json() 本地函数,支持将字符串转为IJsonToken进行取值。
7. EmitHelper对于集合取值时,反射获取“get_item”委托时存在看你多个MethodInfo,现在可以传入子项类型,帮助匹配目标重载方法
This commit is contained in:
fengjiayi
2025-07-31 23:59:31 +08:00
parent 5f6a58168a
commit 1bccccc835
36 changed files with 948 additions and 335 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -12,9 +12,9 @@ namespace Serein.Library
public enum InfoClass
{
/// <summary>
/// 琐碎的
/// 调试
/// </summary>
Trivial,
Debug,
/// <summary>
/// 一般的
/// </summary>

View File

@@ -37,15 +37,13 @@ namespace Serein.Library
UI,
/// <summary>
/// <para>触发器节点,必须为标记在可异步等待的方法,建议与继承了 FlowTriggerk&lt;TEnum&gt; 的实例对象搭配使用</para>
/// <para>触发器节点,必须为标记在可异步等待的方法</para>
/// <para>方法返回值必须为Task&lt;IFlipflopContext&lt;TResult&gt;&gt;,若为其它返回值,将不会创建节点。</para>
/// <para>触发器根据在分支中的位置,分为两种类型:流程分支中的触发器、全局触发器</para>
/// <para>一般的触发器:存在于分支某处,也可能是分支的终点,但一定不是流程的起点与分支的起点。</para>
/// <para>一般的触发器行为:在当前分支中执行一次之后不再执行,一般用于等待某个操作的响应。</para>
/// <para>一般的触发器入参:如果使用了 FlowTriggerk&lt;TEnum&gt; ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。</para>
/// <para>全局触发器:没有上游分支、同时并非流程的起始节点。</para>
/// <para>全局触发器行为:全局触发器会循环执行,直到流程结束。</para>
/// <para>一般的触发器入参:如果使用了 FlowTriggerk&lt;TEnum&gt; ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。</para>
/// </summary>
Flipflop,
/// <summary>

View File

@@ -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)
{

View File

@@ -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,
};
}
}

View File

@@ -98,7 +98,7 @@ namespace Serein.Library
/// 起始节点
/// </summary>
[DataInfo]
private IFlowNode _startNode;
private IFlowNode? _startNode;
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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&lt;object, object, object&gt;
/// 构建集合取委托:(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;
}
}
}

View File

@@ -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>

View File

@@ -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);