diff --git a/Admin.NET/Admin.NET.Core/EventBus/RedisEventSourceStorer.cs b/Admin.NET/Admin.NET.Core/EventBus/RedisEventSourceStorer.cs index 272b5ba1..475bdf41 100644 --- a/Admin.NET/Admin.NET.Core/EventBus/RedisEventSourceStorer.cs +++ b/Admin.NET/Admin.NET.Core/EventBus/RedisEventSourceStorer.cs @@ -4,6 +4,8 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! +using NewLife.Caching.Queues; +using Newtonsoft.Json; using System.Threading.Channels; namespace Admin.NET.Core; @@ -11,6 +13,12 @@ namespace Admin.NET.Core; /// /// Redis自定义事件源存储器 /// +/// +/// 在集群部署时,一般每一个消息只由一个服务节点消费一次。 +/// 有些特殊情情要通知到服务器群中的每一个节点(比如需要强制加载某些配置、重点服务等), +/// 在这种情况下就要以“broadcast:”开头来定义EventId, +/// 本系统会把“broadcast:”开头的事件视为“广播消息”保证集群中的每一个服务节点都能消费得到这个消息 +/// public sealed class RedisEventSourceStorer : IEventSourceStorer, IDisposable { /// @@ -23,11 +31,9 @@ public sealed class RedisEventSourceStorer : IEventSourceStorer, IDisposable /// private readonly Channel _channel; - ///// - ///// Redis 连接对象 - ///// - //private readonly FullRedis _redis; - private IProducerConsumer _queue; + private IProducerConsumer _queueSingle; + + private RedisStream _queueBroadcast; /// /// 路由键 @@ -54,24 +60,48 @@ public sealed class RedisEventSourceStorer : IEventSourceStorer, IDisposable //_redis = redis as FullRedis; _routeKey = routeKey; - // 创建消息订阅者 - _queue = cacheProvider.GetQueue(routeKey); - _eventConsumer = new EventConsumer(_queue); + // 创建广播消息订阅者,即所有服务器节点都能收到消息(用来发布重启、Reload配置等消息) + FullRedis redis = (FullRedis)cacheProvider.Cache; + var clusterOpt = App.GetConfig("Cluster", true); + _queueBroadcast = redis.GetStream(routeKey + ":broadcast"); + _queueBroadcast.Group = clusterOpt.ServerId;//根据服务器标识分配到不同的分组里 + _queueBroadcast.Expire = TimeSpan.FromSeconds(10);//消息10秒过期() + _queueBroadcast.ConsumeAsync(OnConsumeBroadcast); + + // 创建队列消息订阅者,只要有一个服务节点消费了消息即可 + _queueSingle = redis.GetQueue(routeKey + ":single"); + _eventConsumer = new EventConsumer(_queueSingle); // 订阅消息写入 Channel _eventConsumer.Received += (send, cr) => { - // 反序列化消息 - //var eventSource = JsonConvert.DeserializeObject(cr); - - // 写入内存管道存储器 - _channel.Writer.WriteAsync(cr); + var oriColor = Console.ForegroundColor; + ChannelEventSource ces = (ChannelEventSource)cr; + ConsumeChannelEventSource(ces); }; - - // 启动消费者 _eventConsumer.Start(); } + private Task OnConsumeBroadcast(string source, Message message, CancellationToken token) + { + ChannelEventSource ces = JsonConvert.DeserializeObject(source); + ConsumeChannelEventSource(ces); + return Task.CompletedTask; + } + + private void ConsumeChannelEventSource(ChannelEventSource ces) + { + // 打印测试事件 + if (ces.EventId != null && ces.EventId.IndexOf(":Test") > 0) + { + var oriColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"有消息要处理{ces.EventId},{ces.Payload}"); + Console.ForegroundColor = oriColor; + } + _channel.Writer.WriteAsync(ces); + } + /// /// 将事件源写入存储器 /// @@ -82,29 +112,28 @@ public sealed class RedisEventSourceStorer : IEventSourceStorer, IDisposable { // 空检查 if (eventSource == default) - { throw new ArgumentNullException(nameof(eventSource)); - } // 这里判断是否是 ChannelEventSource 或者 自定义的 EventSource if (eventSource is ChannelEventSource source) { - // 序列化消息 - //var data = JsonSerializer.Serialize(source); - - //// 获取一个订阅对象 - //var queue = _redis.GetQueue(_routeKey); - // 异步发布 await Task.Factory.StartNew(() => { - //queue.Add(source); - _queue.Add(source); + if (source.EventId != null && source.EventId.StartsWith("broadcast:")) + { + string str = JsonConvert.SerializeObject(source); + _queueBroadcast.Add(str); + } + else + { + _queueSingle.Add(source); + } }, cancellationToken, TaskCreationOptions.LongRunning, System.Threading.Tasks.TaskScheduler.Default); } else { - // 这里处理动态订阅问题 + // 处理动态订阅问题 await _channel.Writer.WriteAsync(eventSource, cancellationToken); } } diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs index b621a55a..7d684e28 100644 --- a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs @@ -380,7 +380,7 @@ public static class SqlSugarSetup .Where(u => !u.GetCustomAttributes().Any()) .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) && !u.IsDefined(typeof(SysTableAttribute), false) && !u.IsDefined(typeof(LogTableAttribute), false) && !u.IsDefined(typeof(TenantAttribute), false)).ToList(); - if (!entityTypes.Any()) return; + if (entityTypes.Count == 0) return; foreach (var entityType in entityTypes) { diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm index 83b5d7df..21874350 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm @@ -78,7 +78,7 @@ @: @: @: - @: + @: @: @: diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm index 8eb33055..1877b132 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm @@ -53,7 +53,7 @@ - + @@ -194,7 +194,7 @@ import { Local } from '/@@/utils/storage'; @:import { codeToName, getConstType } from "/@@/utils/constHelper"; } @if(@Model.TableField.Any(x=>x.EffectType == "Select") || @Model.TableField.Any(x=>x.EffectType == "EnumSelector")){ -@:import { getDictDataItem as di, getDictDataList as dl } from '/@@/utils/dict-utils'; +@:import { getDictLabelByVal as di, getDictDataList as dl } from '/@@/utils/dict-utils'; } @if(@Model.TableField.Any(x=>x.EffectType == "EnumSelector")){ @:import { getDictLabelByVal as dv } from '/@@/utils/dict-utils'; @@ -313,7 +313,7 @@ const options = useVxeTable<@(@Model.ClassName)>( // 工具栏配置 toolbarConfig: { export: false }, // 行设置 - rowConfig: { height: 80 }, + // rowConfig: { height: 80 }, } ); diff --git a/Web/src/views/home/widgets/components/schedule.vue b/Web/src/views/home/widgets/components/schedule.vue index f6f2535a..2437c671 100644 --- a/Web/src/views/home/widgets/components/schedule.vue +++ b/Web/src/views/home/widgets/components/schedule.vue @@ -78,7 +78,6 @@ onMounted(async () => { // 查询操作 const handleQuery = async () => { - debugger; state.queryParams.startTime = GetMonthFirstDay(state.calendarValue); state.queryParams.endTime = GetMonthLastDay(state.calendarValue); diff --git a/Web/src/views/home/widgets/components/scheduleEdit.vue b/Web/src/views/home/widgets/components/scheduleEdit.vue index 983c55ef..cee9a032 100644 --- a/Web/src/views/home/widgets/components/scheduleEdit.vue +++ b/Web/src/views/home/widgets/components/scheduleEdit.vue @@ -78,8 +78,6 @@ const cancel = () => { // 提交 const submit = () => { - console.log(JSON.stringify(state.ruleForm)); - ruleFormRef.value.validate(async (valid: boolean) => { if (!valid) return; if (state.ruleForm.id != undefined && state.ruleForm.id > 0) { diff --git a/Web/src/views/system/print/component/hiprint/preview.vue b/Web/src/views/system/print/component/hiprint/preview.vue index e453dd22..0cf5aa0a 100644 --- a/Web/src/views/system/print/component/hiprint/preview.vue +++ b/Web/src/views/system/print/component/hiprint/preview.vue @@ -48,7 +48,7 @@ const showDialog = (hiprintTemplate: any, printData: {}, width = 210, printType const print = () => { state.waitShowPrinter = true; - debugger; + // debugger; // 判断是否已成功连接 if (state.printType == 2) { // 注意:连接是异步的