refactor(project): 迁移项目命名并移除WPF客户端
This commit is contained in:
65
src/Gpulse.WCT.DataAnalyzer/Commands/CleanCommand.cs
Normal file
65
src/Gpulse.WCT.DataAnalyzer/Commands/CleanCommand.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
84
src/Gpulse.WCT.DataAnalyzer/Commands/ExportCommand.cs
Normal file
84
src/Gpulse.WCT.DataAnalyzer/Commands/ExportCommand.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
80
src/Gpulse.WCT.DataAnalyzer/Commands/ParseCommand.cs
Normal file
80
src/Gpulse.WCT.DataAnalyzer/Commands/ParseCommand.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
112
src/Gpulse.WCT.DataAnalyzer/Commands/StatsCommand.cs
Normal file
112
src/Gpulse.WCT.DataAnalyzer/Commands/StatsCommand.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
44
src/Gpulse.WCT.DataAnalyzer/Gpulse.WCT.DataAnalyzer.csproj
Normal file
44
src/Gpulse.WCT.DataAnalyzer/Gpulse.WCT.DataAnalyzer.csproj
Normal 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>
|
||||
89
src/Gpulse.WCT.DataAnalyzer/Program.cs
Normal file
89
src/Gpulse.WCT.DataAnalyzer/Program.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user