项目初始化,完成日志服务,事件服务,数据库服务

This commit is contained in:
LAPTOP-SA27O0CB\guoke 2025-09-30 14:31:53 +08:00
commit b09382e51a
18 changed files with 2742 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
*/obj
/obj
*/bin
/bin
*/.vs
.vs/
Server/wwwroot/dist/
Model/
Server/Air.db

8
App.xaml Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

233
Services/EventService.cs Normal file
View 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
View 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;
}
}

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

View 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
View 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
View 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
View 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
View 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
}
]
}