更新项目配置,新增设备同步模块,优化WebSocket和Swagger配置,增强SCADA系统的免登录接口,支持数据字典项和登录日志的免登录查询与记录。调整Java编译设置,确保更好的开发体验。

This commit is contained in:
geht
2026-04-28 10:23:58 +08:00
parent bbe46dcf2d
commit 142a0bdaba
1013 changed files with 41858 additions and 28 deletions

View File

@@ -0,0 +1,78 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Client;
using NewLife.Caching.Services;
using NewLife.Caching;
using NewLife.Log;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YY.Admin.Core;
using YY.Admin.Core.Option;
using NewLife.Configuration;
public static class CacheExtensions
{
/// <summary>
/// 注册缓存服务
/// </summary>
public static void AddNewLifeCache(this IContainerRegistry containerRegistry,IConfiguration configuration)
{
// 读取配置
var BaseCacheOptions = configuration.GetSection("Cache").Get<BaseCacheOptions>();
// 注册配置选项
containerRegistry.RegisterInstance(BaseCacheOptions);
// 注册缓存提供器
if (BaseCacheOptions.CacheType == CacheTypeEnum.Redis.ToString())
{
containerRegistry.RegisterSingleton<ICacheProvider>(() =>
{
return CreateRedisCacheProvider(BaseCacheOptions);
});
}
else
{
// 默认使用内存缓存
containerRegistry.RegisterSingleton<ICacheProvider, CacheProvider>();
}
// 注册缓存服务
containerRegistry.RegisterSingleton<ISysCacheService, SysCacheService>();
}
private static ICacheProvider CreateRedisCacheProvider(BaseCacheOptions options)
{
var redis = new FullRedis
{
Name = "RedisCache",
Tracer = null, // 禁用跟踪器
Log = XTrace.Log // 使用NewLife日志
};
// 初始化Redis
redis.Init(options.Redis.Configuration!);
// 设置前缀
if (!string.IsNullOrEmpty(options.Redis.Prefix))
{
redis.Prefix = options.Redis.Prefix;
}
// 设置最大消息大小
if (options.Redis.MaxMessageSize > 0)
{
redis.MaxMessageSize = options.Redis.MaxMessageSize;
}
//// 测试连接
//if (!redis.Ping())
//{
// throw new Exception("Redis连接失败请检查配置");
//}
return new RedisCacheProvider { Cache = redis };
}
}

View File

@@ -0,0 +1,27 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Client;
using NewLife.Caching.Services;
using NewLife.Caching;
using NewLife.Log;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YY.Admin.Core;
using YY.Admin.Core.Option;
using NewLife.Configuration;
using YY.Admin.Core.SqlSugar;
public static class DbExtensions
{
/// <summary>
/// 注册缓存服务
/// </summary>
public static void AddDbContext(this IContainerRegistry containerRegistry,IConfiguration configuration)
{
containerRegistry.AddSqlSugar(configuration);
}
}

View File

@@ -0,0 +1,41 @@
using System.Net.Http;
namespace YY.Admin.Module
{
public static class HttpClientExtensions
{
public static void AddHttpClient(this IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<HttpClient>(() =>
{
var handler = new SocketsHttpHandler
{
// 连接池大小
MaxConnectionsPerServer = 50,
// 最大存活时间(绝对时间)
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
// 空闲连接超时时间,超时后自动回收
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
// 是否自动重试
AutomaticDecompression = System.Net.DecompressionMethods.GZip |
System.Net.DecompressionMethods.Deflate
};
var client = new HttpClient(handler)
{
// 默认超时时间 30秒
Timeout = TimeSpan.FromSeconds(30)
};
client.DefaultRequestHeaders.UserAgent.ParseAdd("YY.Admin");
return client;
});
}
}
}

View File

@@ -0,0 +1,29 @@
using YY.Admin.Core;
namespace YY.Admin.Module
{
/// <summary>
/// SideBar选项
/// </summary>
public class NavItem : TabSource
{
/// <summary>
/// 是否放到底部
/// </summary>
public bool AlignBottom { get; set; } = false;
/// <summary>
/// 是否支持高亮选中
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// 图标类型
/// </summary>
public override IconTypeEnum IconType { get => base.IconType; set => base.IconType = value; }
public NavItem()
{
IconType = IconTypeEnum.MaterialDesign;
}
}
}

View File

@@ -0,0 +1,82 @@
using System.Windows;
using System.Windows.Media;
using YY.Admin.ViewModels.Control;
using YY.Admin.ViewModels.Dialogs;
using YY.Admin.Views;
using YY.Admin.Views.Control;
using YY.Admin.Views.Dialogs;
using YY.Admin.Views.SysManage;
namespace YY.Admin
{
public static class NavigationExtensions
{
/// <summary>
/// 注册导航
/// </summary>
public static void AddNavigation(this IContainerRegistry containerRegistry)
{
// 注册对话框
containerRegistry.RegisterDialog<AlertDialogView, AlertDialogViewModel>("AlertDialog");
containerRegistry.RegisterDialog<SuccessDialogView, SuccessDialogViewModel>("SuccessDialog");
containerRegistry.RegisterDialog<ErrorDialogView, ErrorDialogViewModel>("ErrorDialog");
containerRegistry.RegisterDialog<WarningDialogView, WarningDialogViewModel>("WarningDialog");
containerRegistry.RegisterDialog<ConfirmDialogView, ConfirmDialogViewModel>("ConfirmDialog");
containerRegistry.RegisterDialog<ServerSettingsDialogView, ServerSettingsDialogViewModel>("ServerSettingsDialog");
// 设置对话框样式
containerRegistry.RegisterDialogWindow<DialogWindow>();
// 注册导航
containerRegistry.RegisterForNavigation<DashboardView>("DashboardView");
// 404视图
containerRegistry.RegisterForNavigation<NotFoundView>("NotFoundView");
//containerRegistry.RegisterForNavigation<RoleManagementView>("RoleManagementView");
//containerRegistry.RegisterForNavigation<PermissionManagementView>("PermissionManagementView");
//containerRegistry.RegisterForNavigation<OrderManagementView>("OrderManagementView");
//containerRegistry.RegisterForNavigation<ProductManagementView>("ProductManagementView");
//containerRegistry.RegisterForNavigation<ReportView>("ReportView");
//containerRegistry.RegisterForNavigation<MonitorView>("MonitorView");
// 窗口注册
containerRegistry.Register<LoginWindow>();
containerRegistry.Register<MainWindow>();
// 注册视图(页面)
containerRegistry.RegisterForNavigation<MenuTreeView>();
containerRegistry.RegisterForNavigation<UserManagementView>();
containerRegistry.RegisterForNavigation<DataDictionaryManagementView>();
containerRegistry.RegisterForNavigation<RoleManagementView>();
containerRegistry.RegisterForNavigation<TenantManagementView>();
}
}
public class DialogWindow : Window, IDialogWindow
{
public DialogWindow()
{
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
Background = Brushes.Transparent; // 背景透明
WindowStartupLocation = WindowStartupLocation.CenterOwner;
SizeToContent = SizeToContent.WidthAndHeight;
ResizeMode = ResizeMode.NoResize;
}
public IDialogResult? Result { get; set; }
}
//public class DialogWindow : Window, IDialogWindow
//{
// public DialogWindow()
// {
// //InitializeComponent();
// // 去掉最大化最小化
// ResizeMode = ResizeMode.NoResize;
// // 去掉右上角系统按钮
// WindowStyle = WindowStyle.None;
// }
// public IDialogResult Result { get; set; }
//}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using YY.Admin.EventBus;
using static YY.Admin.Core.SysUserEvents;
using System.Reflection;
using YY.Admin.Core;
using Prism.Ioc;
namespace YY.Admin.Module
{
public static class NotificationExtensions
{
/// <summary>
/// 注册通知事件
/// </summary>
public static void AddNotificationEventBus(this IContainerRegistry containerRegistry)
{
// 注册EventAggregator为单例
containerRegistry.RegisterSingleton<IEventAggregator, EventAggregator>();
}
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YY.Admin.Core.Option;
using YY.Admin.Core.SqlSugar;
namespace YY.Admin.Setup
{
public static class ProjectOptions
{
/// <summary>
/// 注册项目配置选项到Prism容器
/// </summary>
public static IContainerRegistry AddProjectOptions(
this IContainerRegistry containerRegistry,
IConfiguration configuration)
{
// 绑定数据库连接配置到对象
var dbOptions = configuration.GetSection("DbConnection").Get<DbConnectionOptions>();
// 注册配置实例
containerRegistry.RegisterInstance(dbOptions);
return containerRegistry;
}
}
}

View File

@@ -0,0 +1,109 @@
using Microsoft.Extensions.Configuration;
using System.Reflection;
using YY.Admin.Core;
using YY.Admin.Services.Service.Auth;
namespace YY.Admin
{
public static class ServiceExtensions
{
/// <summary>
/// 注册服务
/// </summary>
public static void AddService(this IContainerRegistry containerRegistry, IConfiguration configuration)
{
// 注册配置
containerRegistry.RegisterInstance<IConfiguration>(configuration);
//// 注册日志服务为单例
containerRegistry.RegisterSingleton<ILoggerService, SerilogLoggerService>();
// 自动扫描并注册应用层服务
RegisterServicesByAssembly(containerRegistry, typeof(ISysAuthService).Assembly);
}
/// <summary>
/// 自动注册程序集中所有服务
/// </summary>
private static void RegisterServicesByAssembly(IContainerRegistry containerRegistry, Assembly assembly)
{
// 获取所有公开的非抽象类
var serviceTypes = assembly.GetExportedTypes()
.Where(t => t.IsClass && !t.IsAbstract);
foreach (var implementationType in serviceTypes)
{
// 查找服务接口(名称以"I"开头,去掉首字母后匹配)
var serviceInterface = implementationType.GetInterfaces()
.FirstOrDefault(i => i.Name == $"I{implementationType.Name}");
// 如果没有直接匹配的接口,尝试查找其他接口
if (serviceInterface == null)
{
// 备选方案1注册所有实现的接口适合多接口实现
RegisterAllInterfaces(containerRegistry, implementationType);
// 备选方案2只注册主接口
// serviceInterface = implementationType.GetInterface($"I{implementationType.Name}");
}
else
{
// 注册找到的接口
RegisterService(containerRegistry, serviceInterface, implementationType);
}
}
}
/// <summary>
/// 注册服务到容器
/// </summary>
private static void RegisterService(
IContainerRegistry containerRegistry,
Type serviceType,
Type implementationType)
{
// 根据命名约定判断生命周期
bool isSingleton = implementationType.Name.EndsWith("Service") ||
implementationType.Name.EndsWith("Repository");
// 根据基类判断生命周期
bool isSingletonByBase = typeof(ISingletonDependency).IsAssignableFrom(implementationType);
// 根据特性判断生命周期
var attribute = implementationType.GetCustomAttribute<LifecycleAttribute>();
var lifecycle = attribute?.Lifecycle ?? Lifecycle.Transient;
// 确定注册方式
if (isSingleton || isSingletonByBase || lifecycle == Lifecycle.Singleton)
{
containerRegistry.RegisterSingleton(serviceType, implementationType);
Console.WriteLine($"注册单例服务: {serviceType.Name} -> {implementationType.Name}");
}
else
{
containerRegistry.Register(serviceType, implementationType);
Console.WriteLine($"注册瞬时服务: {serviceType.Name} -> {implementationType.Name}");
}
}
/// <summary>
/// 注册所有实现的接口
/// </summary>
private static void RegisterAllInterfaces(IContainerRegistry containerRegistry, Type implementationType)
{
var interfaces = implementationType.GetInterfaces()
.Where(i => i != typeof(IDisposable) &&
!i.Name.StartsWith("_") && // 排除某些特殊接口
i.Assembly == implementationType.Assembly); // 只注册同程序集接口
foreach (var serviceType in interfaces)
{
RegisterService(containerRegistry, serviceType, implementationType);
}
if (!interfaces.Any())
{
// 如果没有接口,直接注册具体类型
containerRegistry.Register(implementationType);
Console.WriteLine($"注册具体类型: {implementationType.Name}");
}
}
}
}

View File

@@ -0,0 +1,69 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Polly;
using Polly.Extensions.Http;
using Prism.Ioc;
using Prism.Modularity;
using System.Net.Http;
using YY.Admin.Core.Services;
using YY.Admin.Infrastructure.Hubs;
using YY.Admin.Infrastructure.Network;
using YY.Admin.Infrastructure.Storage;
using YY.Admin.Infrastructure.Sync;
namespace YY.Admin.Module;
public class SyncModule : IModule
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<TokenStore>();
containerRegistry.RegisterSingleton<HttpSyncClient>();
containerRegistry.RegisterSingleton<OutboxProcessor>();
containerRegistry.RegisterSingleton<IJeecgUserMirrorPullOutbox, JeecgUserMirrorPullOutbox>();
containerRegistry.RegisterSingleton<INetworkMonitor, NetworkMonitor>();
containerRegistry.RegisterSingleton<ISignalRService, StompWebSocketService>();
var serviceCollection = new ServiceCollection();
serviceCollection.AddHttpClient("JeecgApi", (sp, client) =>
{
var config = containerRegistry.GetContainer().Resolve<IConfiguration>();
var baseUrl = config.GetValue<string>("JeecgIntegration:BaseUrl")?.TrimEnd('/');
if (!string.IsNullOrWhiteSpace(baseUrl))
{
client.BaseAddress = new Uri(baseUrl);
}
var tokenStore = containerRegistry.GetContainer().Resolve<TokenStore>();
var token = tokenStore.GetTokenAsync(default).ConfigureAwait(false).GetAwaiter().GetResult();
if (!string.IsNullOrWhiteSpace(token))
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
}).AddPolicyHandler(GetRetryPolicy());
var provider = serviceCollection.BuildServiceProvider();
var httpClientFactory = provider.GetRequiredService<IHttpClientFactory>();
containerRegistry.RegisterInstance(httpClientFactory);
}
public void OnInitialized(IContainerProvider containerProvider)
{
var networkMonitor = containerProvider.Resolve<INetworkMonitor>();
var outboxProcessor = containerProvider.Resolve<OutboxProcessor>();
var signalService = containerProvider.Resolve<ISignalRService>();
_ = networkMonitor.StartAsync(CancellationToken.None);
_ = outboxProcessor.StartConsumerAsync(CancellationToken.None);
// 用户镜像 + 设备指令:统一 STOMP/ws/device免密与设备 Token 模式均启动
_ = Task.Run(() => signalService.ConnectUnifiedDeviceChannelAsync(CancellationToken.None));
}
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
}

View File

@@ -0,0 +1,246 @@
using System.Collections.ObjectModel;
using YY.Admin.Core;
using YY.Admin.Event;
namespace YY.Admin.Module
{
/// <summary>
/// 单个标签的数据模型
/// </summary>
public class TabItemModel : BindableBase
{
public ObservableCollection<TabItemModel>? OpenTabs { get; set; }
public IEventAggregator? EventAggregator { get; set; }
private string? _id;
public string? Id
{
get => _id;
set => SetProperty(ref _id, value);
}
private string? _header;
public string? Header
{
get => _header;
set => SetProperty(ref _header, value);
}
private string? _icon = string.Empty;
public string? Icon
{
get => _icon;
set => SetProperty(ref _icon, value);
}
private IconTypeEnum? _iconType;
public IconTypeEnum IconType {
get {
return _iconType ?? IconTypeEnum.AntDesign;
}
set => SetProperty(ref _iconType, value);
}
private string? _viewName = string.Empty;
public string? ViewName
{
get => _viewName;
set => SetProperty(ref _viewName, value);
}
/// <summary>
/// Tab是否允许关闭
/// </summary>
private bool _isClosable = true;
public bool IsClosable
{
get => _isClosable;
set => SetProperty(ref _isClosable, value);
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
// 添加对Tab源的引用
public TabSource? TabSource { get; set; }
/// <summary>
/// 左侧是否有可关闭Tab
/// </summary>
public bool HasClosableLeft => OpenTabs != null && OpenTabs
.Take(OpenTabs.IndexOf(this))
.Any(t => t.IsClosable);
/// <summary>
/// 右侧是否有可关闭Tab
/// </summary>
public bool HasClosableRight => OpenTabs != null && OpenTabs
.Skip(OpenTabs.IndexOf(this) + 1)
.Any(t => t.IsClosable);
/// <summary>
/// 其他Tab除当前是否有可关闭Tab
/// </summary>
public bool HasClosableOther => OpenTabs != null && OpenTabs
.Where(t => t != this)
.Any(t => t.IsClosable);
/// <summary>
/// 是否有任意可关闭Tab全部
///</summary>
public bool HasClosableAny => OpenTabs != null && OpenTabs
.Any(t => t.IsClosable);
public DelegateCommand<TabItemModel> RefreshTabCommand { get; }
public DelegateCommand<TabItemModel> CloseTabCommand { get; }
public DelegateCommand<TabItemModel> CloseLeftTabsCommand { get; }
public DelegateCommand<TabItemModel> CloseRightTabsCommand { get; }
public DelegateCommand<TabItemModel> CloseOtherTabsCommand { get; }
public DelegateCommand<TabItemModel> CloseAllTabsCommand { get; }
public TabItemModel()
{
Id = $"TabRegion_{Guid.NewGuid():N}"; // 保证唯一
RefreshTabCommand = new DelegateCommand<TabItemModel>(RefreshTab);
CloseTabCommand = new DelegateCommand<TabItemModel>(CloseTab);
CloseLeftTabsCommand = new DelegateCommand<TabItemModel>(CloseLeftTabs);
CloseRightTabsCommand = new DelegateCommand<TabItemModel>(CloseRightTabs);
CloseOtherTabsCommand = new DelegateCommand<TabItemModel>(CloseOtherTabs);
CloseAllTabsCommand = new DelegateCommand<TabItemModel>(CloseAllTabs);
}
/// <summary>
/// 刷新当前Tab
/// </summary>
/// <param name="tabItemModel"></param>
private void RefreshTab(TabItemModel tabItemModel)
{
if (tabItemModel?.TabSource == null)
{
return;
}
// 发布事件
EventAggregator?.GetEvent<TabRefreshEvent>().Publish(tabItemModel);
}
/// <summary>
/// 关闭当前Tab
/// </summary>
/// <param name="tabItemModel"></param>
private void CloseTab(TabItemModel tabItemModel)
{
if (tabItemModel?.IsClosable != true || OpenTabs?.Any() != true)
{
return;
}
// 发布事件
EventAggregator?.GetEvent<TabClosedEvent>().Publish(tabItemModel);
// 从集合中移除标签
OpenTabs.Remove(tabItemModel);
}
/// <summary>
/// 关闭左侧Tab
/// </summary>
private void CloseLeftTabs(TabItemModel tabItemModel)
{
if (OpenTabs?.Any() != true)
{
return;
}
// 找到当前 Tab 的索引
int currentIndex = OpenTabs.IndexOf(tabItemModel);
// 左侧没有 Tab
if (currentIndex <= 0)
{
return;
}
// 找出左侧所有可关闭的 Tab
var leftTabs = OpenTabs
.Take(currentIndex) // 取前面所有 Tab
.Where(t => t.IsClosable) // 只关闭可关闭的
.ToList(); // 先 ToList 避免集合修改时报错
foreach (var tab in leftTabs)
{
CloseTab(tab);
}
}
/// <summary>
/// 关闭右侧Tab
/// </summary>
private void CloseRightTabs(TabItemModel tabItemModel)
{
if (OpenTabs?.Any() != true)
{
return;
}
int currentIndex = OpenTabs.IndexOf(tabItemModel);
if (currentIndex < 0 || currentIndex >= OpenTabs.Count - 1)
{
return;
}
var rightTabs = OpenTabs
.Skip(currentIndex + 1)
.Where(t => t.IsClosable)
.ToList();
foreach (var tab in rightTabs)
{
CloseTab(tab);
}
}
/// <summary>
/// 关闭其他Tab仅保留当前
/// </summary>
private void CloseOtherTabs(TabItemModel tabItemModel)
{
if (OpenTabs?.Any() != true)
{
return;
}
var otherTabs = OpenTabs
.Where(t => t != tabItemModel && t.IsClosable)
.ToList();
foreach (var tab in otherTabs)
{
CloseTab(tab);
}
}
/// <summary>
/// 关闭全部(包括当前)
/// </summary>
private void CloseAllTabs(TabItemModel tabItemModel)
{
if (OpenTabs?.Any() != true)
{
return;
}
var allClosableTabs = OpenTabs
.Where(t => t.IsClosable)
.ToList();
foreach (var tab in allClosableTabs)
{
CloseTab(tab);
}
}
}
}

View File

@@ -0,0 +1,56 @@
using System.Windows.Input;
using YY.Admin.Core;
namespace YY.Admin.Module
{
public class TabSource : BindableBase
{
/// <summary>
/// 名称
/// </summary>
public virtual string? Name { get; set; }
/// <summary>
/// 图标
/// </summary>
public virtual string? Icon { get; set; }
/// <summary>
/// 图标类型
/// </summary>
public virtual IconTypeEnum IconType { get; set; } = IconTypeEnum.AntDesign;
/// <summary>
/// 视图
/// </summary>
public virtual string? ViewName { get; set; }
/// <summary>
/// 视图的导航参数
/// </summary>
public INavigationParameters? NavigationParameter { get; set; }
/// <summary>
/// 是否选中
/// </summary>
private bool _isSelected;
public virtual bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
/// <summary>
/// Tab是否允许关闭
/// </summary>
private bool _isClosable = true;
public bool IsClosable
{
get => _isClosable;
set => SetProperty(ref _isClosable, value);
}
public ICommand? Command { get; set; }
}
}