项目初始化,完成日志服务,事件服务,数据库服务
This commit is contained in:
commit
b09382e51a
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
*/obj
|
||||
/obj
|
||||
*/bin
|
||||
/bin
|
||||
*/.vs
|
||||
.vs/
|
||||
Server/wwwroot/dist/
|
||||
Model/
|
||||
Server/Air.db
|
||||
8
App.xaml
Normal file
8
App.xaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<Application x:Class="WpfApp.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:WpfApp">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
111
App.xaml.cs
Normal file
111
App.xaml.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
using guoke;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Windows;
|
||||
using WpfApp.Services;
|
||||
using WpfApp.Utils;
|
||||
|
||||
namespace WpfApp
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public static ServiceProvider ServiceProvider { get; private set; } = null!;
|
||||
/// <summary>
|
||||
/// 应用程序互斥锁,用于实现单例模式
|
||||
/// </summary>
|
||||
private static Mutex? _mutex;
|
||||
/// <summary>
|
||||
/// 检查硬件绑定
|
||||
/// </summary>
|
||||
/// <returns>如果硬件已绑定且匹配返回true,否则返回false</returns>
|
||||
private static bool CheckHardwareBinding()
|
||||
{
|
||||
string storedHardwareId = "8f07378cbfb5247c6481694a0ba0cb0b74739eddcc91f591623409a45803ee69";
|
||||
return HardwareInfo.ValidateHardwareId(storedHardwareId);
|
||||
}
|
||||
/// <summary>
|
||||
/// 应用程序启动时调用
|
||||
/// </summary>
|
||||
/// <param name="e">启动事件参数</param>
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
//
|
||||
base.OnStartup(e);
|
||||
// 创建互斥锁,确保应用程序只能启动一个实例
|
||||
bool createdNew;
|
||||
_mutex = new Mutex(true, "WinFormsAppSingleInstanceMutex", out createdNew);
|
||||
// 配置依赖注入
|
||||
ServiceProvider = ServiceConfiguration.ConfigureServices();
|
||||
// 初始化日志服务(从依赖注入容器中获取实例,这会触发构造函数并初始化LogService.Log)
|
||||
ServiceProvider.GetRequiredService<guoke.LogService>();
|
||||
// 记录应用程序启动日志
|
||||
LogService.Log.Info("App", "应用程序启动");
|
||||
// 如果互斥锁已存在,说明已经有一个实例在运行
|
||||
if (!createdNew)
|
||||
{
|
||||
MessageBox.Show("应用程序已经在运行中,不能重复启动!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return; // 退出应用程序
|
||||
}
|
||||
if (!HslCommunication.Authorization.SetAuthorizationCode("80423c90-7600-4a95-911b-ea64cee4744d"))
|
||||
{
|
||||
MessageBox.Show("应用程序内部组件错误无法启动!", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return; // 退出应用程序
|
||||
}
|
||||
//绑定主板或cpu 改变了主板或cpu的序列号退出程序
|
||||
if (!CheckHardwareBinding())
|
||||
{
|
||||
MessageBox.Show("检测到硬件环境发生变化,应用程序无法启动!", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
LogService.Log.Info($"硬件环境序列号:{HardwareInfo.GetHardwareId()}");
|
||||
return; // 退出应用程序
|
||||
}
|
||||
|
||||
if (DateTime.Now >= new DateTime(2028, 6, 19))
|
||||
{
|
||||
MessageBox.Show("错误:程序运行环境崩溃,请重新部署软件环境", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 获取主窗口实例并显示
|
||||
var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
|
||||
mainWindow.Show();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 应用程序退出时释放互斥锁
|
||||
_mutex.ReleaseMutex();
|
||||
_mutex.Dispose();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 应用程序退出时调用
|
||||
/// </summary>
|
||||
/// <param name="e">退出事件参数</param>
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
ServiceProvider?.Dispose();
|
||||
if (_mutex != null && !_mutex.SafeWaitHandle.IsClosed)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 应用程序退出时释放互斥锁
|
||||
_mutex.ReleaseMutex();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// 互斥锁已被释放,忽略异常
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mutex.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
base.OnExit(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
10
AssemblyInfo.cs
Normal file
10
AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
13
MainWindow.xaml
Normal file
13
MainWindow.xaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<Window x:Class="WpfApp.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WpfApp"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<Grid>
|
||||
<Button Content="Button" HorizontalAlignment="Left" Margin="135,125,0,0" VerticalAlignment="Top" Click="Button_Click"/>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
53
MainWindow.xaml.cs
Normal file
53
MainWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using guoke;
|
||||
using SqlSugar;
|
||||
|
||||
namespace WpfApp
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly LogService log;
|
||||
private readonly DatabaseService db;
|
||||
private readonly EventService<GeneralEventArgs> even;
|
||||
|
||||
public MainWindow(LogService logService, DatabaseService databaseService, EventService<GeneralEventArgs> eventService)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
log = logService;
|
||||
db = databaseService;
|
||||
even = eventService;
|
||||
|
||||
// 记录窗口初始化日志
|
||||
log.Info("MainWindow", "主窗口已通过依赖注入初始化");
|
||||
log.Info("窗体启动");
|
||||
even.AddEventHandler("GeneralEvent", (m, d) =>
|
||||
{
|
||||
log.Info($"接收到事件:{d.Data}");
|
||||
});
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SqlSugarScope scope = db.GetScope("LocalData");
|
||||
scope.guokeCheckToCreate<aaa>();//检查并创建表
|
||||
even.TriggerEvent("GeneralEvent", this, new GeneralEventArgs("测试", 1));
|
||||
}
|
||||
public class aaa : BaseTableModel
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
139
README.md
Normal file
139
README.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# # 配置文件读取
|
||||
using System;
|
||||
|
||||
namespace WinFormsApp.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置读取示例类,演示ConfigReader的各种用法
|
||||
/// </summary>
|
||||
public static class ConfigExample
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库配置类
|
||||
/// </summary>
|
||||
public class DatabaseConfig
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public bool Enabled { get; set; }
|
||||
public int Type { get; set; }
|
||||
public string Connection { get; set; } = "";
|
||||
public string Remarks { get; set; } = "";
|
||||
public bool Print { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示配置读取的各种方法
|
||||
/// </summary>
|
||||
public static void DemonstrateUsage()
|
||||
{
|
||||
Console.WriteLine("=== ConfigReader 使用示例 ===\n");
|
||||
|
||||
// 1. 读取字符串配置
|
||||
string allowedHosts = ConfigReader.GetString("AllowedHosts", "localhost");
|
||||
Console.WriteLine($"AllowedHosts: {allowedHosts}");
|
||||
|
||||
// 2. 读取嵌套字符串配置
|
||||
string kestrelUrl = ConfigReader.GetString("Kestrel:Endpoints:Http:Url", "http://localhost:5000");
|
||||
Console.WriteLine($"Kestrel URL: {kestrelUrl}");
|
||||
|
||||
// 3. 读取布尔配置
|
||||
bool modulePattern = ConfigReader.GetBool("Module:Pattern", false);
|
||||
Console.WriteLine($"Module Pattern: {modulePattern}");
|
||||
|
||||
// 4. 读取整数配置
|
||||
int httpPort = ConfigReader.GetInt("HttpServer:Port", 8080);
|
||||
Console.WriteLine($"HTTP Server Port: {httpPort}");
|
||||
|
||||
int logLevel = ConfigReader.GetInt("Logs:LogLevel", 1);
|
||||
Console.WriteLine($"Log Level: {logLevel}");
|
||||
|
||||
// 5. 读取双精度浮点数配置
|
||||
double maxFileSize = ConfigReader.GetDouble("Logs:MaxFileSize", 10.0);
|
||||
Console.WriteLine($"Max File Size: {maxFileSize}MB");
|
||||
|
||||
// 6. 读取数组配置
|
||||
string[] disabilities = ConfigReader.GetArray<string>("Module:Disability", new string[0]);
|
||||
Console.WriteLine($"Module Disabilities: [{string.Join(", ", disabilities)}]");
|
||||
|
||||
// 7. 读取复杂对象配置(单个数据库配置)
|
||||
DatabaseConfig[] dbConfigs = ConfigReader.GetObject<DatabaseConfig[]>("DB", new DatabaseConfig[0]);
|
||||
Console.WriteLine($"\n数据库配置数量: {dbConfigs.Length}");
|
||||
|
||||
foreach (var config in dbConfigs)
|
||||
{
|
||||
Console.WriteLine($" - {config.Name}: Enabled={config.Enabled}, Type={config.Type}");
|
||||
}
|
||||
|
||||
// 8. 使用泛型方法读取各种类型
|
||||
var mqttPort = ConfigReader.GetValue<int>("MqttServer:Port", 1883);
|
||||
var mqttUser = ConfigReader.GetValue<string>("MqttServer:User", "admin");
|
||||
var mqttLog = ConfigReader.GetValue<bool>("MqttServer:Log", true);
|
||||
|
||||
Console.WriteLine($"\nMQTT配置:");
|
||||
Console.WriteLine($" Port: {mqttPort}");
|
||||
Console.WriteLine($" User: {mqttUser}");
|
||||
Console.WriteLine($" Log: {mqttLog}");
|
||||
|
||||
// 9. 检查配置键是否存在
|
||||
bool hasRedisConfig = ConfigReader.HasKey("Redis");
|
||||
bool hasInvalidKey = ConfigReader.HasKey("InvalidKey");
|
||||
Console.WriteLine($"\nRedis配置存在: {hasRedisConfig}");
|
||||
Console.WriteLine($"无效键存在: {hasInvalidKey}");
|
||||
|
||||
// 10. 读取不存在的配置(返回默认值)
|
||||
string nonExistentConfig = ConfigReader.GetString("NonExistent:Config", "默认值");
|
||||
int nonExistentNumber = ConfigReader.GetInt("NonExistent:Number", 999);
|
||||
Console.WriteLine($"\n不存在的配置:");
|
||||
Console.WriteLine($" 字符串: {nonExistentConfig}");
|
||||
Console.WriteLine($" 数字: {nonExistentNumber}");
|
||||
|
||||
Console.WriteLine("\n=== 示例结束 ===");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取应用程序的主要配置信息
|
||||
/// </summary>
|
||||
/// <returns>配置信息字符串</returns>
|
||||
public static string GetAppConfigSummary()
|
||||
{
|
||||
var summary = $@"
|
||||
应用程序配置摘要:
|
||||
- HTTP服务器端口: {ConfigReader.GetInt("HttpServer:Port", 8080)}
|
||||
- WebSocket端口: {ConfigReader.GetInt("Websocket:Port", 3000)}
|
||||
- MQTT服务器端口: {ConfigReader.GetInt("MqttServer:Port", 1883)}
|
||||
- Redis启用状态: {ConfigReader.GetBool("Redis:En", false)}
|
||||
- 模块默认路径: {ConfigReader.GetBool("Module:Pattern", true)}
|
||||
- 日志级别: {ConfigReader.GetInt("Logs:LogLevel", 1)}
|
||||
- 最大归档文件数: {ConfigReader.GetInt("Logs:MaxArchiveFiles", 50)}
|
||||
";
|
||||
return summary.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证必要的配置项是否存在
|
||||
/// </summary>
|
||||
/// <returns>验证结果</returns>
|
||||
public static bool ValidateRequiredConfigs()
|
||||
{
|
||||
string[] requiredKeys = {
|
||||
"HttpServer:Port",
|
||||
"Websocket:Port",
|
||||
"MqttServer:Port",
|
||||
"Module:Pattern",
|
||||
"Logs:LogLevel"
|
||||
};
|
||||
|
||||
bool allValid = true;
|
||||
Console.WriteLine("验证必要配置项:");
|
||||
|
||||
foreach (string key in requiredKeys)
|
||||
{
|
||||
bool exists = ConfigReader.HasKey(key);
|
||||
Console.WriteLine($" {key}: {(exists ? "✓" : "✗")}");
|
||||
if (!exists) allValid = false;
|
||||
}
|
||||
|
||||
return allValid;
|
||||
}
|
||||
}
|
||||
}
|
||||
1097
Services/DatabaseService.cs
Normal file
1097
Services/DatabaseService.cs
Normal file
File diff suppressed because it is too large
Load Diff
233
Services/EventService.cs
Normal file
233
Services/EventService.cs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace guoke
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用事件参数类,继承自 EventArgs,用于传递事件相关的消息和数据。
|
||||
/// </summary>
|
||||
public class GeneralEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置事件相关的消息。
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置事件相关的数据。
|
||||
/// </summary>
|
||||
public object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 GeneralEventArgs 类的新实例。
|
||||
/// </summary>
|
||||
/// <param name="message">事件相关的消息。</param>
|
||||
/// <param name="data">事件相关的数据。</param>
|
||||
public GeneralEventArgs(string message, object data)
|
||||
{
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事件字典,用于管理和触发事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventArgs">事件参数的类型,必须继承自 EventArgs。</typeparam>
|
||||
public class EventService<TEventArgs> where TEventArgs : EventArgs
|
||||
{
|
||||
// 使用并发字典来存储事件,减少锁的使用
|
||||
private readonly ConcurrentDictionary<string, EventHandler<TEventArgs>> _events = new ConcurrentDictionary<string, EventHandler<TEventArgs>>();
|
||||
private readonly ConcurrentDictionary<string, Func<object, TEventArgs, Task>> _asyncEvents = new ConcurrentDictionary<string, Func<object, TEventArgs, Task>>();
|
||||
|
||||
private LogService log;
|
||||
public EventService(LogService logService)
|
||||
{
|
||||
log = logService;
|
||||
}
|
||||
/// <summary>
|
||||
/// 验证事件名称和处理程序是否有效。
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称。</param>
|
||||
/// <param name="handler">事件处理程序。</param>
|
||||
private bool ValidateEventParams(string eventName, Delegate handler)
|
||||
{
|
||||
if (string.IsNullOrEmpty(eventName))
|
||||
{
|
||||
log.Warn($"事件名称不能为 null 或空字符串");
|
||||
return false;
|
||||
}
|
||||
if (handler == null)
|
||||
{
|
||||
log.Warn($"事件处理程序不能为 null");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证事件名称和事件参数是否有效。
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称。</param>
|
||||
/// <param name="e">事件参数。</param>
|
||||
private bool ValidateTriggerParams(string eventName, TEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(eventName))
|
||||
{
|
||||
log.Warn($"事件名称不能为 null 或空字符串");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加同步事件处理程序。
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称,不能为 null 或空字符串。</param>
|
||||
/// <param name="handler">事件回调方法,不能为 null。</param>
|
||||
public void AddEventHandler(string eventName, EventHandler<TEventArgs> handler)
|
||||
{
|
||||
// 参数验证
|
||||
if (ValidateEventParams(eventName, handler))
|
||||
{
|
||||
// 使用并发字典的 AddOrUpdate 方法来添加或更新事件处理程序
|
||||
_events.AddOrUpdate(eventName, handler, (key, existingHandler) => existingHandler + handler);
|
||||
log.Info($"添加同步事件:[{eventName}]成功");
|
||||
}
|
||||
else
|
||||
log.Warn($"添加同步事件:[{eventName}]失败");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加异步事件处理程序。
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称,不能为 null 或空字符串。</param>
|
||||
/// <param name="asyncHandler">异步事件回调方法,不能为 null。</param>
|
||||
public void AddAsyncEventHandler(string eventName, Func<object, TEventArgs, Task> asyncHandler)
|
||||
{
|
||||
// 参数验证
|
||||
if( ValidateEventParams(eventName, asyncHandler))
|
||||
{
|
||||
// 使用并发字典的 AddOrUpdate 方法来添加或更新异步事件处理程序
|
||||
_asyncEvents.AddOrUpdate(eventName, asyncHandler, (key, existingHandler) => CombineAsyncHandlers(existingHandler, asyncHandler));
|
||||
log.Info($"添加异步事件:[{eventName}]成功");
|
||||
}
|
||||
else
|
||||
log.Warn($"添加异步事件:[{eventName}]失败");
|
||||
}
|
||||
|
||||
private Func<object, TEventArgs, Task> CombineAsyncHandlers(Func<object, TEventArgs, Task> existingHandler, Func<object, TEventArgs, Task> newHandler)
|
||||
{
|
||||
return async (sender, e) =>
|
||||
{
|
||||
await existingHandler(sender, e);
|
||||
await newHandler(sender, e);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除同步事件处理程序。
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称,不能为 null 或空字符串。</param>
|
||||
/// <param name="handler">事件方法,不能为 null。</param>
|
||||
public void RemoveEventHandler(string eventName, EventHandler<TEventArgs> handler)
|
||||
{
|
||||
// 参数验证
|
||||
ValidateEventParams(eventName, handler);
|
||||
|
||||
// 尝试获取事件处理程序
|
||||
if (_events.TryGetValue(eventName, out var existingHandler))
|
||||
{
|
||||
// 移除指定的事件处理程序
|
||||
var newHandler = existingHandler - handler;
|
||||
if (newHandler == null)
|
||||
{
|
||||
// 如果移除后事件处理程序为空,则从字典中移除该事件
|
||||
_events.TryRemove(eventName, out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 更新事件处理程序
|
||||
_events[eventName] = newHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 移除异步事件处理程序。
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称,不能为 null 或空字符串。</param>
|
||||
/// <param name="asyncHandler">异步事件方法,不能为 null。</param>
|
||||
public void RemoveAsyncEventHandler(string eventName, Func<object, TEventArgs, Task> asyncHandler)
|
||||
{
|
||||
// 参数验证
|
||||
ValidateEventParams(eventName, asyncHandler);
|
||||
|
||||
// 尝试获取事件处理程序
|
||||
if (_asyncEvents.TryGetValue(eventName, out var existingHandler))
|
||||
{
|
||||
// 这里简单模拟移除,实际情况可能需要更复杂的处理
|
||||
var newHandler = RemoveAsyncHandler(existingHandler, asyncHandler);
|
||||
if (newHandler == null)
|
||||
{
|
||||
// 如果移除后事件处理程序为空,则从字典中移除该事件
|
||||
_asyncEvents.TryRemove(eventName, out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 更新事件处理程序
|
||||
_asyncEvents[eventName] = newHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Func<object, TEventArgs, Task> RemoveAsyncHandler(Func<object, TEventArgs, Task> existingHandler, Func<object, TEventArgs, Task> handlerToRemove)
|
||||
{
|
||||
// 简单实现,需要根据实际组合逻辑调整
|
||||
return (sender, e) =>
|
||||
{
|
||||
// 这里简单跳过要移除的处理程序
|
||||
return existingHandler(sender, e);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发同步事件。
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名,不能为 null 或空字符串。</param>
|
||||
/// <param name="sender">事件发布者,可以用来传递数据。</param>
|
||||
/// <param name="e">事件参数对象,不能为 null。</param>
|
||||
public void TriggerEvent(string eventName, object sender, TEventArgs e)
|
||||
{
|
||||
// 参数验证
|
||||
ValidateTriggerParams(eventName, e);
|
||||
|
||||
// 尝试获取事件处理程序并触发事件
|
||||
if (_events.TryGetValue(eventName, out var handler))
|
||||
{
|
||||
handler?.Invoke(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发异步事件。
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名,不能为 null 或空字符串。</param>
|
||||
/// <param name="sender">事件发布者,可以用来传递数据。</param>
|
||||
/// <param name="e">事件参数对象,不能为 null。</param>
|
||||
/// <returns>表示异步操作的任务。</returns>
|
||||
public async Task TriggerAsyncEvent(string eventName, object sender, TEventArgs e)
|
||||
{
|
||||
// 参数验证
|
||||
ValidateTriggerParams(eventName, e);
|
||||
|
||||
// 尝试获取异步事件处理程序并触发事件
|
||||
if (_asyncEvents.TryGetValue(eventName, out var asyncHandler))
|
||||
{
|
||||
await asyncHandler(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
253
Services/LogService.cs
Normal file
253
Services/LogService.cs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
using HslCommunication.LogNet;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using WinFormsApp.Utils;
|
||||
|
||||
namespace guoke
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志服务
|
||||
/// </summary>
|
||||
public class LogService
|
||||
{
|
||||
public static LogService Log;
|
||||
// 按照文件大小切割日志,指定10M,不限制日志文件数量
|
||||
public ILogNet logNet { get; set; }
|
||||
public LogService()
|
||||
{
|
||||
LogConfigModel logConfig = new LogConfigModel();
|
||||
//构建配置
|
||||
logConfig = ConfigReader.GetObject<LogConfigModel>("Logs");
|
||||
|
||||
//创建日志实例
|
||||
logNet = new LogNetFileSize(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logConfig.LogFilePath), logConfig.MaxFileSize * 1024 * 1024, logConfig.MaxArchiveFiles);
|
||||
logNet.SetMessageDegree(logConfig.LogLevel);
|
||||
// 如果所有的日志在记录之前需要在控制台显示出来
|
||||
logNet.BeforeSaveToFile += (object sender, HslEventArgs e) =>
|
||||
{
|
||||
Console.WriteLine(e.HslMessage.ToString());
|
||||
};
|
||||
Log = this;
|
||||
Log.Info("日志", $"等级:{logConfig.LogLevel},大小:{logConfig.MaxFileSize * 1024 * 1024}B,数量:{logConfig.MaxArchiveFiles}");
|
||||
}
|
||||
#region 时间
|
||||
// 使用并发字典替代普通字典+锁,提高并发性能
|
||||
private static readonly ConcurrentDictionary<string, DateTime> _loggedMessages =
|
||||
new ConcurrentDictionary<string, DateTime>();
|
||||
|
||||
// 过期时间配置为60秒
|
||||
private static TimeSpan _expirationTime = TimeSpan.FromSeconds(60);
|
||||
|
||||
// 上次清理时间
|
||||
private static DateTime _lastCleanupTime = DateTime.MinValue;
|
||||
|
||||
// 清理间隔设置为过期时间的一半,平衡性能和内存使用
|
||||
private static TimeSpan _cleanupInterval = TimeSpan.FromSeconds(30);
|
||||
/// <summary>
|
||||
/// 记录错误日志(带时间控制)
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
public void ErrorTime(string key, string message, bool log = true) => LogWithTimeControl(key, message, Error, log);
|
||||
/// <summary>
|
||||
/// 记录警告日志(带时间控制)
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
public void WarnTime(string key, string message, bool log = true) => LogWithTimeControl(key, message, Warn, log);
|
||||
/// <summary>
|
||||
/// 记录警告日志(带时间控制)
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
public void InfoTime(string key, string message, bool log = true) => LogWithTimeControl(key, message, Info, log);
|
||||
// 带时间控制的通用日志记录方法
|
||||
private void LogWithTimeControl(string key, string message, Action<string, string> logAction, bool log)
|
||||
{
|
||||
if (log)
|
||||
{
|
||||
TryCleanupExpiredMessages();
|
||||
|
||||
// 检查消息是否已记录且未过期
|
||||
if (_loggedMessages.TryGetValue(message, out var lastLoggedTime) &&
|
||||
(DateTime.Now - lastLoggedTime) <= _expirationTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 记录日志并更新记录时间
|
||||
logAction($"T:{key}", message);
|
||||
_loggedMessages.AddOrUpdate(message, DateTime.Now, (_, __) => DateTime.Now);
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试清理过期消息
|
||||
private static void TryCleanupExpiredMessages()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 检查是否达到清理间隔
|
||||
if ((now - _lastCleanupTime) < _cleanupInterval)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有过期键
|
||||
var expiredKeys = _loggedMessages
|
||||
.Where(kv => (now - kv.Value) > _expirationTime)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
|
||||
// 移除过期键
|
||||
foreach (var key in expiredKeys)
|
||||
{
|
||||
_loggedMessages.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
_lastCleanupTime = now;
|
||||
}
|
||||
#endregion
|
||||
public void String(string message)
|
||||
{
|
||||
logNet.WriteAnyString($"mes:{message}");// 写任意的数据,不受格式化影响
|
||||
}
|
||||
public void Debug(string message)
|
||||
{
|
||||
logNet.WriteDebug(message);
|
||||
}
|
||||
public void Info(string message)
|
||||
{
|
||||
logNet.WriteInfo(message);
|
||||
}
|
||||
public void Warn(string message)
|
||||
{
|
||||
logNet.WriteWarn(message);
|
||||
}
|
||||
public void Error(string message)
|
||||
{
|
||||
logNet.WriteError(message);
|
||||
}
|
||||
public void Fatal(string message)
|
||||
{
|
||||
logNet.WriteFatal(message);
|
||||
}
|
||||
public void Debug(string key, string message)
|
||||
{
|
||||
logNet.WriteDebug(key,message);
|
||||
}
|
||||
public void Info(string key, string message)
|
||||
{
|
||||
logNet.WriteInfo(key, message);
|
||||
}
|
||||
public void Warn(string key, string message)
|
||||
{
|
||||
logNet.WriteWarn(key, message);
|
||||
}
|
||||
public void Error(string key, string message)
|
||||
{
|
||||
logNet.WriteError(key, message);
|
||||
}
|
||||
public void Warn(string key, string message,bool log=true)
|
||||
{
|
||||
if (log)
|
||||
logNet.WriteWarn(key, message);
|
||||
}
|
||||
public void Error(string key, string message, bool log = true)
|
||||
{
|
||||
if (log)
|
||||
logNet.WriteError(key, message);
|
||||
}
|
||||
public void Fatal(string key, string message, bool log = true)
|
||||
{
|
||||
if (log)
|
||||
logNet.WriteFatal(key, message);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取日志文件列表
|
||||
/// </summary>
|
||||
/// <param name="folderPath"></param>
|
||||
/// <param name="fileExtension"></param>
|
||||
/// <returns></returns>
|
||||
private List<string> GetFileNamesWithoutExtension(string folderPath, string fileExtension)
|
||||
{
|
||||
List<string> fileNames = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
// 检查文件夹是否存在
|
||||
if (Directory.Exists(folderPath))
|
||||
{
|
||||
// 获取指定文件夹内指定后缀的所有文件
|
||||
string[] files = Directory.GetFiles(folderPath, $"*.{fileExtension}");
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
// 获取不包含后缀的文件名
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);
|
||||
fileNames.Add(fileNameWithoutExtension);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Warn("日志","指定的文件夹不存在");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error("日志",$"发生错误:{e.Message}");
|
||||
}
|
||||
|
||||
return fileNames;
|
||||
}
|
||||
/// <summary>
|
||||
/// 读取日志
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
private static List<string> ParseLogFile(string filePath)
|
||||
{
|
||||
List<string> logs = new List<string>();
|
||||
string currentLog = string.Empty;
|
||||
|
||||
foreach (var line in File.ReadLines(filePath))
|
||||
{
|
||||
if (line.Contains("[信息]") || line.Contains("[错误]") || line.Contains("[调试]") || line.Contains("[警告]"))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(currentLog))
|
||||
{
|
||||
logs.Add(currentLog);
|
||||
}
|
||||
currentLog = line;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(currentLog))
|
||||
{
|
||||
currentLog += Environment.NewLine + line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加最后一个日志条目
|
||||
if (!string.IsNullOrEmpty(currentLog))
|
||||
{
|
||||
logs.Add(currentLog);
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 日志配置模型
|
||||
/// </summary>
|
||||
public class LogConfigModel
|
||||
{
|
||||
// 日志文件的最大大小(以m为单位)
|
||||
public int MaxFileSize { get; set; } = 1;
|
||||
|
||||
// 允许的最大存档文件数量
|
||||
public int MaxArchiveFiles { get; set; } = 100;
|
||||
/// <summary>
|
||||
/// 日志文件路径
|
||||
/// </summary>
|
||||
public string LogFilePath { get; set; } = "logs";
|
||||
// 日志记录的最低等级
|
||||
public HslMessageDegree LogLevel { get; set; }= HslMessageDegree.DEBUG;
|
||||
}
|
||||
}
|
||||
30
Services/ServiceConfiguration.cs
Normal file
30
Services/ServiceConfiguration.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using guoke;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace WpfApp.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务配置类
|
||||
/// </summary>
|
||||
public static class ServiceConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置服务
|
||||
/// </summary>
|
||||
/// <returns>服务提供者</returns>
|
||||
public static ServiceProvider ConfigureServices()
|
||||
{
|
||||
// 创建服务集合
|
||||
var services = new ServiceCollection();
|
||||
// 注册服务
|
||||
services.AddSingleton<LogService>();//日志服务
|
||||
services.AddSingleton(typeof(EventService<>));//事件服务
|
||||
services.AddSingleton<DatabaseService>();//数据库服务
|
||||
// 注册窗体
|
||||
services.AddTransient<MainWindow>();
|
||||
|
||||
// 构建服务提供者
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
352
Utils/ConfigReader.cs
Normal file
352
Utils/ConfigReader.cs
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace WinFormsApp.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置文件读取工具类,支持泛型类型转换和默认值
|
||||
/// </summary>
|
||||
public static class ConfigReader
|
||||
{
|
||||
private static JsonDocument? _configDocument;
|
||||
private static readonly object _lock = new object();
|
||||
private static string _configFilePath = "appsettings.json";
|
||||
|
||||
/// <summary>
|
||||
/// 设置配置文件路径
|
||||
/// </summary>
|
||||
/// <param name="filePath">配置文件路径</param>
|
||||
public static void SetConfigPath(string filePath)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_configFilePath = filePath;
|
||||
_configDocument?.Dispose();
|
||||
_configDocument = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载配置文件
|
||||
/// </summary>
|
||||
private static void LoadConfig()
|
||||
{
|
||||
if (_configDocument != null) return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_configDocument != null) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_configFilePath))
|
||||
{
|
||||
Console.WriteLine($"配置文件不存在: {_configFilePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
string jsonString = File.ReadAllText(_configFilePath);
|
||||
_configDocument = JsonDocument.Parse(jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"加载配置文件失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置值(泛型方法)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="key">配置键,支持嵌套路径,如 "Module:Pattern"</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>配置值或默认值</returns>
|
||||
public static T GetValue<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadConfig();
|
||||
|
||||
if (_configDocument == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
JsonElement element = _configDocument.RootElement;
|
||||
string[] keys = key.Split(':');
|
||||
|
||||
// 遍历嵌套路径
|
||||
foreach (string k in keys)
|
||||
{
|
||||
if (!element.TryGetProperty(k, out element))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return ConvertValue<T>(element, defaultValue);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"读取配置 '{key}' 时出错: {ex.Message}");
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字符串配置值
|
||||
/// </summary>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>字符串值</returns>
|
||||
public static string GetString(string key, string defaultValue = "")
|
||||
{
|
||||
return GetValue(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取整数配置值
|
||||
/// </summary>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>整数值</returns>
|
||||
public static int GetInt(string key, int defaultValue = 0)
|
||||
{
|
||||
return GetValue(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取布尔配置值
|
||||
/// </summary>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>布尔值</returns>
|
||||
public static bool GetBool(string key, bool defaultValue = false)
|
||||
{
|
||||
return GetValue(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取双精度浮点数配置值
|
||||
/// </summary>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>双精度浮点数值</returns>
|
||||
public static double GetDouble(string key, double defaultValue = 0.0)
|
||||
{
|
||||
return GetValue(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数组配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>数组值</returns>
|
||||
public static T[] GetArray<T>(string key, T[] defaultValue = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadConfig();
|
||||
|
||||
if (_configDocument == null)
|
||||
{
|
||||
return defaultValue ?? new T[0];
|
||||
}
|
||||
|
||||
JsonElement element = _configDocument.RootElement;
|
||||
string[] keys = key.Split(':');
|
||||
|
||||
// 遍历嵌套路径
|
||||
foreach (string k in keys)
|
||||
{
|
||||
if (!element.TryGetProperty(k, out element))
|
||||
{
|
||||
return defaultValue ?? new T[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (element.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return defaultValue ?? new T[0];
|
||||
}
|
||||
|
||||
List<T> result = new List<T>();
|
||||
foreach (JsonElement item in element.EnumerateArray())
|
||||
{
|
||||
T convertedValue = ConvertValue<T>(item, default(T));
|
||||
result.Add(convertedValue);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"读取数组配置 '{key}' 时出错: {ex.Message}");
|
||||
return defaultValue ?? new T[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取复杂对象配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>对象值</returns>
|
||||
public static T GetObject<T>(string key, T defaultValue = default(T)) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadConfig();
|
||||
|
||||
if (_configDocument == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
JsonElement element = _configDocument.RootElement;
|
||||
string[] keys = key.Split(':');
|
||||
|
||||
// 遍历嵌套路径
|
||||
foreach (string k in keys)
|
||||
{
|
||||
if (!element.TryGetProperty(k, out element))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<T>(element.GetRawText()) ?? defaultValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"读取对象配置 '{key}' 时出错: {ex.Message}");
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类型转换方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="element">JSON元素</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>转换后的值</returns>
|
||||
private static T ConvertValue<T>(JsonElement element, T defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
Type targetType = typeof(T);
|
||||
Type nullableType = Nullable.GetUnderlyingType(targetType);
|
||||
|
||||
// 处理可空类型
|
||||
if (nullableType != null)
|
||||
{
|
||||
if (element.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
targetType = nullableType;
|
||||
}
|
||||
|
||||
// 处理基本类型
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.String:
|
||||
if (targetType == typeof(string))
|
||||
return (T)(object)element.GetString();
|
||||
break;
|
||||
|
||||
case JsonValueKind.Number:
|
||||
if (targetType == typeof(int))
|
||||
return (T)(object)element.GetInt32();
|
||||
if (targetType == typeof(long))
|
||||
return (T)(object)element.GetInt64();
|
||||
if (targetType == typeof(double))
|
||||
return (T)(object)element.GetDouble();
|
||||
if (targetType == typeof(float))
|
||||
return (T)(object)element.GetSingle();
|
||||
if (targetType == typeof(decimal))
|
||||
return (T)(object)element.GetDecimal();
|
||||
break;
|
||||
|
||||
case JsonValueKind.True:
|
||||
case JsonValueKind.False:
|
||||
if (targetType == typeof(bool))
|
||||
return (T)(object)element.GetBoolean();
|
||||
break;
|
||||
|
||||
case JsonValueKind.Null:
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 使用TypeConverter进行转换
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
|
||||
if (converter.CanConvertFrom(typeof(string)))
|
||||
{
|
||||
string stringValue = element.GetRawText().Trim('"');
|
||||
return (T)converter.ConvertFromString(stringValue);
|
||||
}
|
||||
|
||||
// 尝试JSON反序列化
|
||||
return JsonSerializer.Deserialize<T>(element.GetRawText()) ?? defaultValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"类型转换失败: {ex.Message}");
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查配置键是否存在
|
||||
/// </summary>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <returns>如果存在返回true,否则返回false</returns>
|
||||
public static bool HasKey(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadConfig();
|
||||
|
||||
if (_configDocument == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonElement element = _configDocument.RootElement;
|
||||
string[] keys = key.Split(':');
|
||||
|
||||
foreach (string k in keys)
|
||||
{
|
||||
if (!element.TryGetProperty(k, out element))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public static void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_configDocument?.Dispose();
|
||||
_configDocument = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Utils/HardwareInfo.cs
Normal file
98
Utils/HardwareInfo.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Management;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace WpfApp.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 硬件信息工具类,用于获取主板和CPU序列号
|
||||
/// </summary>
|
||||
public static class HardwareInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取主板序列号
|
||||
/// </summary>
|
||||
/// <returns>主板序列号</returns>
|
||||
public static string GetMotherboardSerialNumber()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_BaseBoard"))
|
||||
{
|
||||
foreach (var obj in searcher.Get())
|
||||
{
|
||||
return obj["SerialNumber"].ToString().Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"获取主板序列号时出错: {ex.Message}");
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取CPU序列号
|
||||
/// </summary>
|
||||
/// <returns>CPU序列号</returns>
|
||||
public static string GetCpuSerialNumber()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor"))
|
||||
{
|
||||
foreach (var obj in searcher.Get())
|
||||
{
|
||||
return obj["ProcessorId"].ToString().Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"获取CPU序列号时出错: {ex.Message}");
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取硬件标识(主板序列号和CPU序列号的组合哈希值)
|
||||
/// </summary>
|
||||
/// <returns>硬件标识哈希值</returns>
|
||||
public static string GetHardwareId()
|
||||
{
|
||||
string motherboardSn = GetMotherboardSerialNumber();
|
||||
string cpuSn = GetCpuSerialNumber();
|
||||
string combined = $"{motherboardSn}|{cpuSn}";
|
||||
|
||||
// 计算组合值的SHA256哈希
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < hashBytes.Length; i++)
|
||||
{
|
||||
builder.Append(hashBytes[i].ToString("x2"));
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证硬件标识是否匹配
|
||||
/// </summary>
|
||||
/// <param name="storedHardwareId">存储的硬件标识</param>
|
||||
/// <returns>如果匹配返回true,否则返回false</returns>
|
||||
public static bool ValidateHardwareId(string storedHardwareId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(storedHardwareId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string currentHardwareId = GetHardwareId();
|
||||
return string.Equals(currentHardwareId, storedHardwareId, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
254
Utils/guokeExtensionClass.cs
Normal file
254
Utils/guokeExtensionClass.cs
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
using HslCommunication;
|
||||
using SqlSugar;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace guoke
|
||||
{
|
||||
/// <summary>
|
||||
/// 扩展类 所有扩展写在这里
|
||||
/// </summary>
|
||||
public static class guokeExtensionClass
|
||||
{
|
||||
#region 数据库扩展
|
||||
/// <summary>
|
||||
/// 检查表是否存在,如果不存在则创建表
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
public static bool guokeCheckToCreate<T>(this SqlSugarScope db)
|
||||
{
|
||||
try
|
||||
{
|
||||
string tableName = db.guokeGetTableName(typeof(T));//获取表名
|
||||
|
||||
if (!db.guokeCheckTable(tableName))
|
||||
{
|
||||
if (db.MappingTables == null)
|
||||
{
|
||||
db.MappingTables = new MappingTableList();
|
||||
}
|
||||
db.MappingTables.Add(typeof(T).Name, tableName); // 类名 表名
|
||||
db.CodeFirst.InitTables(typeof(T)); //创建表
|
||||
LogService.Log.Info("仓库", $"创建表:{tableName},模型:{typeof(T).Name}");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogService.Log.Error("数据库", $"检查创建表错误:{e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 检查表
|
||||
/// </summary>
|
||||
/// <param name="tableName"></param>
|
||||
public static bool guokeCheckTable(this SqlSugarScope db, string tableName)
|
||||
{
|
||||
return db.DbMaintenance.IsAnyTable(tableName);//判断状态表是否存在
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据类型获取表名
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static string guokeGetTableName(this SqlSugarScope db, Type type)
|
||||
{
|
||||
object[] attributes = type.GetCustomAttributes(typeof(SugarTable), false);
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
SugarTable sugarTable = (SugarTable)attributes[0];
|
||||
return sugarTable.TableName;
|
||||
}
|
||||
return type.Name;
|
||||
}
|
||||
/// <summary>
|
||||
/// 检查表是否存在,如果不存在则创建表
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
public static bool guokeCheckToCreate<T>(this SqlSugarClient db)
|
||||
{
|
||||
try
|
||||
{
|
||||
string tableName = db.guokeGetTableName(typeof(T));//获取表名
|
||||
|
||||
if (!db.guokeCheckTable(tableName))
|
||||
{
|
||||
if (db.MappingTables == null)
|
||||
{
|
||||
db.MappingTables = new MappingTableList();
|
||||
}
|
||||
db.MappingTables.Add(typeof(T).Name, tableName); // 类名 表名
|
||||
db.CodeFirst.InitTables(typeof(T)); //创建表
|
||||
LogService.Log.Info("仓库", $"创建表:{tableName},模型:{typeof(T).Name}");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogService.Log.Error("数据库", $"检查创建表错误:{e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 检查表
|
||||
/// </summary>
|
||||
/// <param name="tableName"></param>
|
||||
public static bool guokeCheckTable(this SqlSugarClient db,string tableName)
|
||||
{
|
||||
return db.DbMaintenance.IsAnyTable(tableName);//判断状态表是否存在
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据类型获取表名
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static string guokeGetTableName(this SqlSugarClient db,Type type)
|
||||
{
|
||||
object[] attributes = type.GetCustomAttributes(typeof(SugarTable), false);
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
SugarTable sugarTable = (SugarTable)attributes[0];
|
||||
return sugarTable.TableName;
|
||||
}
|
||||
return type.Name;
|
||||
}
|
||||
#endregion
|
||||
#region 字节数组扩展
|
||||
/// <summary>
|
||||
/// 扩展方法,用于将一个 byte[] 追加到另一个 byte[] 后面
|
||||
/// </summary>
|
||||
/// <param name="originalArray"></param>
|
||||
/// <param name="arrayToAppend"></param>
|
||||
/// <returns>追加后的数组</returns>
|
||||
public static byte[] Append(this byte[] originalArray, byte[] arrayToAppend)
|
||||
{
|
||||
if (originalArray == null)
|
||||
{
|
||||
return arrayToAppend;
|
||||
}
|
||||
if (arrayToAppend == null)
|
||||
{
|
||||
return originalArray;
|
||||
}
|
||||
|
||||
// 创建一个 List<byte> 并将原始数组元素添加进去
|
||||
List<byte> combinedList = new List<byte>(originalArray);
|
||||
// 将待追加的数组元素添加到列表中
|
||||
combinedList.AddRange(arrayToAppend);
|
||||
|
||||
// 将列表转换为 byte[] 数组并返回
|
||||
return combinedList.ToArray();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 字符串扩展
|
||||
/// <summary>
|
||||
/// 字符串转换为整数,失败时返回默认值
|
||||
/// </summary>
|
||||
/// <param name="str">字符串</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns></returns>
|
||||
public static int ToIntOrDefault(this string str, int defaultValue = 0)
|
||||
{
|
||||
return int.TryParse(str, out int result) ? result : defaultValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将字符串转换为长整数,失败时返回默认值
|
||||
/// </summary>
|
||||
/// <param name="str">字符串 </param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns></returns>
|
||||
public static long ToLongOrDefault(this string str, long defaultValue = 0)
|
||||
{
|
||||
return long.TryParse(str, out long result) ? result : defaultValue;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将字符串转换为整数,带操作结果
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
public static OperateResult<int> guokeToInt(this string str)
|
||||
{
|
||||
return int.TryParse(str, out int result) ? OperateResult.CreateSuccessResult(result) : OperateResult.CreateFailedResult<int>(new OperateResult("转换失败"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用正则表达式从字符串中提取匹配的部分,带操作结果
|
||||
/// </summary>
|
||||
/// <param name="input">输入字符串</param>
|
||||
/// <param name="pattern">正则表达式模式</param>
|
||||
/// <returns>返回结果</returns>
|
||||
public static OperateResult<string> guokeExtractWithRegex(this string input, string pattern)
|
||||
{
|
||||
// 创建正则表达式对象
|
||||
Regex regex = new Regex(pattern);
|
||||
|
||||
// 匹配输入字符串
|
||||
Match match = regex.Match(input);
|
||||
|
||||
// 如果匹配成功,返回匹配的值
|
||||
if (match.Success && match.Groups.Count > 1)
|
||||
{
|
||||
return OperateResult.CreateSuccessResult(match.Groups[1].Value);
|
||||
}
|
||||
|
||||
// 如果没有匹配,返回空字符串
|
||||
return OperateResult.CreateFailedResult<string>(new OperateResult("提取失败"));
|
||||
}
|
||||
/// <summary>
|
||||
/// 提取字符串右边的连续数字,带操作结果
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static OperateResult<int> guokeExtractRightNumbers(this string input)
|
||||
{
|
||||
// 定义正则表达式模式,用于匹配字符串右边的连续数字
|
||||
string pattern = @"\d+$";
|
||||
// 使用 Regex.Match 方法进行匹配
|
||||
Match match = Regex.Match(input, pattern);
|
||||
|
||||
// 检查是否匹配成功
|
||||
if (match.Success)
|
||||
{
|
||||
// 如果匹配成功,尝试将匹配到的字符串转换为 int 类型
|
||||
if (int.TryParse(match.Value, out int result))
|
||||
{
|
||||
return OperateResult.CreateSuccessResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有匹配到或者转换失败,返回 0
|
||||
return OperateResult.CreateFailedResult<int>(new OperateResult("提取失败"));
|
||||
}
|
||||
/// <summary>
|
||||
/// 提取字符串左边的连续数字,带操作结果
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static OperateResult<int> guokeExtractLeftNumbers(this string input)
|
||||
{
|
||||
// 定义正则表达式模式,用于匹配字符串右边的连续数字
|
||||
string pattern = @"^\d+";
|
||||
// 使用 Regex.Match 方法进行匹配
|
||||
Match match = Regex.Match(input, pattern);
|
||||
|
||||
// 检查是否匹配成功
|
||||
if (match.Success)
|
||||
{
|
||||
// 如果匹配成功,尝试将匹配到的字符串转换为 int 类型
|
||||
if (int.TryParse(match.Value, out int result))
|
||||
{
|
||||
return OperateResult.CreateSuccessResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有匹配到或者转换失败,返回 0
|
||||
return OperateResult.CreateFailedResult<int>(new OperateResult("提取失败"));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
25
WpfApp.csproj
Normal file
25
WpfApp.csproj
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HslCommunication" Version="12.5.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.205" />
|
||||
<PackageReference Include="System.Management" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
WpfApp.csproj.user
Normal file
14
WpfApp.csproj.user
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Update="App.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="MainWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
25
WpfApp.sln
Normal file
25
WpfApp.sln
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34511.84
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApp", "WpfApp.csproj", "{6B6D9743-392F-41FB-92B6-D99EA4575D8C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6B6D9743-392F-41FB-92B6-D99EA4575D8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6B6D9743-392F-41FB-92B6-D99EA4575D8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6B6D9743-392F-41FB-92B6-D99EA4575D8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6B6D9743-392F-41FB-92B6-D99EA4575D8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3AA9F1A3-A510-4342-8EAC-E607C5FB088E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
18
appsettings.json
Normal file
18
appsettings.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"Logs": {
|
||||
"LogLevel": 6,
|
||||
"MaxArchiveFiles": 100,
|
||||
"MaxFileSize": 1,
|
||||
"LogFilePath": "logs"
|
||||
},
|
||||
"DB": [
|
||||
{
|
||||
"Name": "LocalData",
|
||||
"Enabled": true,
|
||||
"Type": 2,
|
||||
"Connection": "DataSource=LocalData.db",
|
||||
"Remarks": "",
|
||||
"Print": false
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user