refactor(project): 迁移项目命名并移除WPF客户端

This commit is contained in:
Scottxjw
2026-08-13 13:29:56 +08:00
parent 5189c665c3
commit 645f46abcd
70 changed files with 84 additions and 1913 deletions

View File

@@ -0,0 +1,65 @@
using System.CommandLine;
using Gpulse.WCT.DataAnalyzer.Core.Services;
namespace Gpulse.WCT.DataAnalyzer.Commands;
/// <summary>
/// 数据清理命令
/// </summary>
public class CleanCommand : Command
{
public CleanCommand(CleanService cleanService)
: base("clean", "Clean data from database (soft delete)")
{
var scenarioOption = new Option<string?>(
"--scenario",
"Scenario ID to clean"
);
var allOption = new Option<bool>(
"--all",
() => false,
"Clean all data"
);
var confirmOption = new Option<bool>(
"--confirm",
() => false,
"Confirm the clean operation"
);
AddOption(scenarioOption);
AddOption(allOption);
AddOption(confirmOption);
this.SetHandler(async (scenario, all, confirm) =>
{
if (!confirm)
{
Console.WriteLine("Please add --confirm to confirm the clean operation.");
return;
}
if (!string.IsNullOrEmpty(scenario))
{
if (!Guid.TryParse(scenario, out var scenarioId))
{
Console.WriteLine($"Invalid scenario ID: {scenario}");
return;
}
var count = await cleanService.CleanByScenarioAsync(scenarioId);
Console.WriteLine($"Cleaned {count} records for scenario {scenarioId}");
}
else if (all)
{
var count = await cleanService.CleanAllAsync();
Console.WriteLine($"Cleaned {count} records in total");
}
else
{
Console.WriteLine("Please specify --scenario <id> or --all");
}
}, scenarioOption, allOption, confirmOption);
}
}

View File

@@ -0,0 +1,84 @@
using System.CommandLine;
using Gpulse.WCT.DataAnalyzer.Core.Services;
namespace Gpulse.WCT.DataAnalyzer.Commands;
/// <summary>
/// 数据导出命令
/// </summary>
public class ExportCommand : Command
{
public ExportCommand(ExportService exportService)
: base("export", "Export data to CSV or JSON")
{
var formatOption = new Option<string>(
"--format",
() => "csv",
"Export format: csv or json"
);
var outputOption = new Option<string>(
"--output",
() => "./data/output",
"Output directory path"
);
var typeOption = new Option<string>(
"--type",
() => "all",
"Data type to export: qfod, ploss, or all"
);
AddOption(formatOption);
AddOption(outputOption);
AddOption(typeOption);
this.SetHandler(async (format, output, type) =>
{
format = format.ToLower();
type = type.ToLower();
Console.WriteLine($"Exporting {type} data as {format.ToUpper()} to {output}...");
try
{
if (format == "csv")
{
if (type is "qfod" or "all")
{
var path = await exportService.ExportQfodToCsvAsync(output);
Console.WriteLine($" Qfod CSV exported: {path}");
}
if (type is "ploss" or "all")
{
var path = await exportService.ExportPlossToCsvAsync(output);
Console.WriteLine($" Ploss CSV exported: {path}");
}
}
else if (format == "json")
{
if (type is "qfod" or "all")
{
var path = await exportService.ExportQfodToJsonAsync(output);
Console.WriteLine($" Qfod JSON exported: {path}");
}
if (type is "ploss" or "all")
{
var path = await exportService.ExportPlossToJsonAsync(output);
Console.WriteLine($" Ploss JSON exported: {path}");
}
}
else
{
Console.WriteLine($"Unknown format: {format}. Use 'csv' or 'json'.");
}
}
catch (Exception ex)
{
Console.WriteLine($"Export failed: {ex.Message}");
}
}, formatOption, outputOption, typeOption);
}
}

View File

@@ -0,0 +1,80 @@
using System.CommandLine;
using Gpulse.WCT.DataAnalyzer.Core.Services;
namespace Gpulse.WCT.DataAnalyzer.Commands;
/// <summary>
/// 解析日志文件命令
/// </summary>
public class ParseCommand : Command
{
public ParseCommand(ParseService parseService)
: base("parse", "Parse log files and store data to database")
{
var fileOption = new Option<string>(
"--file",
"Path to a single log file to parse"
);
var dirOption = new Option<string>(
"--dir",
"Path to directory containing log files"
);
var recursiveOption = new Option<bool>(
"--recursive",
() => false,
"Recursively search subdirectories"
);
var forceOption = new Option<bool>(
"--force",
() => false,
"Force re-parse even if file already parsed"
);
AddOption(fileOption);
AddOption(dirOption);
AddOption(recursiveOption);
AddOption(forceOption);
this.SetHandler(async (file, dir, recursive, force) =>
{
if (!string.IsNullOrEmpty(file))
{
var report = await parseService.ParseFileAsync(file, force);
PrintReport(report);
}
else if (!string.IsNullOrEmpty(dir))
{
var reports = await parseService.ParseDirectoryAsync(dir, recursive, force);
Console.WriteLine($"\n=== Summary: {reports.Count} files processed ===");
foreach (var report in reports)
{
PrintReport(report);
}
var totalQfod = reports.Sum(r => r.QfodCount);
var totalPloss = reports.Sum(r => r.PlossCount);
var totalErrors = reports.Sum(r => r.ErrorCount);
Console.WriteLine($"\nTotal: Qfod={totalQfod}, Ploss={totalPloss}, Errors={totalErrors}");
}
else
{
Console.WriteLine("Please specify --file or --dir");
}
}, fileOption, dirOption, recursiveOption, forceOption);
}
private static void PrintReport(ParseReport report)
{
if (report.IsSuccess)
{
Console.WriteLine($" [{report.FileName}] Qfod: {report.QfodCount}, Ploss: {report.PlossCount}, Errors: {report.ErrorCount}");
}
else
{
Console.WriteLine($" [{report.FileName}] FAILED: {report.ErrorMessage}");
}
}
}

View File

@@ -0,0 +1,112 @@
using System.CommandLine;
using Gpulse.WCT.DataAnalyzer.Core.Services;
namespace Gpulse.WCT.DataAnalyzer.Commands;
/// <summary>
/// 统计查询命令
/// </summary>
public class StatsCommand : Command
{
public StatsCommand(StatsService statsService)
: base("stats", "Query statistics from database")
{
var summaryOption = new Option<bool>(
"--summary",
() => false,
"Show overall statistics summary"
);
var byFodTypeOption = new Option<bool>(
"--by-fod-type",
() => false,
"Show statistics by FOD type"
);
var byPanelOption = new Option<bool>(
"--by-panel",
() => false,
"Show statistics by TX panel"
);
var byHardwareOption = new Option<bool>(
"--by-hardware",
() => false,
"Show statistics by TX hardware version"
);
var byRxTypeOption = new Option<bool>(
"--by-rx-type",
() => false,
"Show statistics by RX type"
);
AddOption(summaryOption);
AddOption(byFodTypeOption);
AddOption(byPanelOption);
AddOption(byHardwareOption);
AddOption(byRxTypeOption);
this.SetHandler(async (summary, byFodType, byPanel, byHardware, byRxType) =>
{
// 如果没有指定任何选项,默认显示摘要
if (!summary && !byFodType && !byPanel && !byHardware && !byRxType)
{
summary = true;
}
if (summary)
{
var s = await statsService.GetSummaryAsync();
Console.WriteLine("=== Statistics Summary ===");
Console.WriteLine($" Scenarios: {s.ScenarioCount}");
Console.WriteLine($" Qfod Records: {s.QfodCount}");
Console.WriteLine($" Ploss Records: {s.PlossCount}");
Console.WriteLine($" TX Panels: {s.PanelCount}");
Console.WriteLine($" TX Hardware: {s.HardwareCount}");
Console.WriteLine($" TX Software: {s.SoftwareCount}");
Console.WriteLine($" RX Types: {s.RxTypeCount}");
}
if (byFodType)
{
Console.WriteLine("\n=== By FOD Type ===");
var stats = await statsService.GetStatsByFodTypeAsync();
foreach (var s in stats)
{
Console.WriteLine($" FOD Type {s.FodType}: {s.Count}");
}
}
if (byPanel)
{
Console.WriteLine("\n=== By TX Panel ===");
var stats = await statsService.GetStatsByPanelAsync();
foreach (var s in stats)
{
Console.WriteLine($" {s.Name}: {s.Count}");
}
}
if (byHardware)
{
Console.WriteLine("\n=== By TX Hardware ===");
var stats = await statsService.GetStatsByHardwareAsync();
foreach (var s in stats)
{
Console.WriteLine($" {s.Name}: {s.Count}");
}
}
if (byRxType)
{
Console.WriteLine("\n=== By RX Type ===");
var stats = await statsService.GetStatsByRxTypeAsync();
foreach (var s in stats)
{
Console.WriteLine($" {s.Name}: {s.Count}");
}
}
}, summaryOption, byFodTypeOption, byPanelOption, byHardwareOption, byRxTypeOption);
}
}

View File

@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Gpulse.WCT.DataAnalyzer</AssemblyName>
<RootNamespace>Gpulse.WCT.DataAnalyzer</RootNamespace>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>12.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<!-- CLI -->
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<!-- Configuration -->
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<!-- EF Core Tools -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<!-- Logging -->
<PackageReference Include="Serilog" Version="4.0.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Gpulse.WCT.DataAnalyzer.Core\Gpulse.WCT.DataAnalyzer.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,89 @@
using System.CommandLine;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Gpulse.WCT.DataAnalyzer.Commands;
using Gpulse.WCT.DataAnalyzer.Core.Data;
using Gpulse.WCT.DataAnalyzer.Core.Extensions;
using Gpulse.WCT.DataAnalyzer.Core.Services;
namespace Gpulse.WCT.DataAnalyzer;
/// <summary>
/// Gpulse.WCT.DataAnalyzer - 无线充电FOD日志数据采集系统
/// </summary>
public class Program
{
public static async Task<int> Main(string[] args)
{
// 1. 构建引导配置(用于 Serilog 初始化)
var bootstrapConfig = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
// 2. 创建引导日志器
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(bootstrapConfig)
.Enrich.FromLogContext()
.CreateBootstrapLogger();
try
{
var builder = Host.CreateApplicationBuilder(args);
// 3. 使用 AddConfiguration 添加已构建的配置(关键修复)
builder.Configuration.AddConfiguration(bootstrapConfig);
// 4. 配置 Serilog
builder.Services.AddSerilog((_, loggerConfig) =>
{
loggerConfig
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext();
});
// 5. 注册服务
builder.Services.AddDatabaseServices(builder.Configuration);
builder.Services.AddApplicationServices();
var app = builder.Build();
// 初始化数据库
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<WctMinerDbContext>();
await dbContext.Database.EnsureCreatedAsync();
// 种子数据
SeedData.Initialize(dbContext);
}
// 创建 CLI 命令
using var commandScope = app.Services.CreateScope();
var provider = commandScope.ServiceProvider;
var rootCommand = new RootCommand("Gpulse.WCT.DataAnalyzer - FOD Log Data Parser");
rootCommand.AddCommand(new ParseCommand(provider.GetRequiredService<ParseService>()));
rootCommand.AddCommand(new StatsCommand(provider.GetRequiredService<StatsService>()));
rootCommand.AddCommand(new ExportCommand(provider.GetRequiredService<ExportService>()));
rootCommand.AddCommand(new CleanCommand(provider.GetRequiredService<CleanService>()));
return await rootCommand.InvokeAsync(args);
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
return 1;
}
finally
{
await Log.CloseAndFlushAsync();
}
}
}