docs: 更新项目架构与数据模型文档
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,18 +6,19 @@
|
|||||||
|
|
||||||
## 1. 命令概览
|
## 1. 命令概览
|
||||||
|
|
||||||
| 命令 | 说明 |
|
| 命令 | 说明 | 数据流 |
|
||||||
|------|------|
|
|------|------|--------|
|
||||||
| `parse` | 解析日志文件并存储到数据库 |
|
| `parse` | 解析日志文件到本地库 | log → local.db |
|
||||||
| `stats` | 查询统计信息 |
|
| `aggregate` | 从本地库汇总生成发布库 | local.db → release.db |
|
||||||
| `export` | 导出数据 |
|
| `export` | 导出发布库到 CSV | release.db → CSV |
|
||||||
| `clean` | 清理数据 |
|
| `stats` | 查询本地库统计信息 | 只读 local.db |
|
||||||
|
| `clean` | 清理本地库数据(软删除) | 只写 local.db |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 命令详解
|
## 2. 命令详解
|
||||||
|
|
||||||
### 2.1 parse - 解析日志文件
|
### 2.1 parse
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 解析单个文件
|
# 解析单个文件
|
||||||
@@ -29,41 +30,60 @@ dotnet run parse --dir path/to/logs/
|
|||||||
# 解析目录(递归)
|
# 解析目录(递归)
|
||||||
dotnet run parse --dir path/to/logs/ --recursive
|
dotnet run parse --dir path/to/logs/ --recursive
|
||||||
|
|
||||||
# 强制重新解析(覆盖已有数据)
|
# 强制重新解析
|
||||||
dotnet run parse --file log.txt --force
|
dotnet run parse --file log.txt --force
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.2 stats - 查询统计
|
### 2.2 aggregate
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 查看文件解析统计
|
# 从 local.db 汇总生成 release.db(默认重建)
|
||||||
dotnet run stats --file log.txt
|
dotnet run aggregate
|
||||||
|
|
||||||
|
# 追加模式(不删除已有记录)
|
||||||
|
dotnet run aggregate --rebuild false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 export
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 导出正式充电参数 CSV(默认)
|
||||||
|
dotnet run export --output ./data/output
|
||||||
|
|
||||||
|
# 导出正式充电参数 CSV + legacy Qfod/Ploss
|
||||||
|
dotnet run export --type all --output ./data/output
|
||||||
|
|
||||||
|
# 仅导出 legacy Qfod CSV
|
||||||
|
dotnet run export --type qfod --format csv
|
||||||
|
|
||||||
|
# 导出 JSON
|
||||||
|
dotnet run export --format json --type qfod
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 stats
|
||||||
|
|
||||||
|
```bash
|
||||||
# 查看整体统计
|
# 查看整体统计
|
||||||
dotnet run stats --summary
|
dotnet run stats --summary
|
||||||
|
|
||||||
# 按 FOD 类型统计
|
# 按 FOD 类型统计
|
||||||
dotnet run stats --by-fod-type
|
dotnet run stats --by-fod-type
|
||||||
|
|
||||||
|
# 按面板统计
|
||||||
|
dotnet run stats --by-panel
|
||||||
|
|
||||||
|
# 按硬件版本统计
|
||||||
|
dotnet run stats --by-hardware
|
||||||
|
|
||||||
|
# 按 RX 类型统计
|
||||||
|
dotnet run stats --by-rx-type
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.3 export - 导出数据
|
### 2.5 clean
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 导出为 CSV
|
# 清理指定场景
|
||||||
dotnet run export --format csv --output ./output/
|
dotnet run clean --scenario <guid> --confirm
|
||||||
|
|
||||||
# 导出为 JSON
|
|
||||||
dotnet run export --format json --output ./output/
|
|
||||||
|
|
||||||
# 按文件导出
|
|
||||||
dotnet run export --file log.txt --format csv
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 clean - 清理数据
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 清理指定文件的数据
|
|
||||||
dotnet run clean --file log.txt --confirm
|
|
||||||
|
|
||||||
# 清理所有数据
|
# 清理所有数据
|
||||||
dotnet run clean --all --confirm
|
dotnet run clean --all --confirm
|
||||||
@@ -71,98 +91,20 @@ dotnet run clean --all --confirm
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 命令实现
|
## 3. 典型工作流
|
||||||
|
|
||||||
```csharp
|
```bash
|
||||||
// Commands/ParseCommand.cs
|
# 1. 解析日志
|
||||||
using System.CommandLine;
|
dotnet run parse --dir ./data/test_input --recursive
|
||||||
|
|
||||||
namespace Gpulse.WCT.DataAnalyzer.Commands;
|
# 2. 查看统计
|
||||||
|
dotnet run stats --summary
|
||||||
|
|
||||||
public class ParseCommand : Command
|
# 3. 汇总生成发布库
|
||||||
{
|
dotnet run aggregate
|
||||||
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>(
|
# 4. 导出正式 CSV
|
||||||
"--dir",
|
dotnet run export --output ./data/output
|
||||||
"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))
|
|
||||||
{
|
|
||||||
await parseService.ParseFileAsync(file, force);
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(dir))
|
|
||||||
{
|
|
||||||
await parseService.ParseDirectoryAsync(dir, recursive, force);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("Please specify --file or --dir");
|
|
||||||
}
|
|
||||||
}, fileOption, dirOption, recursiveOption, forceOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
输出文件: `./data/output/WCT-ChargingParameterDatabase.csv`
|
||||||
|
|
||||||
## 4. Program.cs 入口
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Program.cs
|
|
||||||
using System.CommandLine;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Gpulse.WCT.DataAnalyzer.Commands;
|
|
||||||
|
|
||||||
var builder = Host.CreateApplicationBuilder(args);
|
|
||||||
|
|
||||||
// 注册服务
|
|
||||||
builder.Services.AddScoped<ParseService>();
|
|
||||||
builder.Services.AddScoped<StatsService>();
|
|
||||||
builder.Services.AddScoped<ExportService>();
|
|
||||||
builder.Services.AddScoped<CleanService>();
|
|
||||||
builder.Services.AddDatabaseServices(builder.Configuration);
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// 创建根命令
|
|
||||||
var rootCommand = new RootCommand("Gpulse.WCT.DataAnalyzer - FOD Log Data Parser");
|
|
||||||
|
|
||||||
// 使用服务提供者获取服务
|
|
||||||
using var scope = app.Services.CreateScope();
|
|
||||||
var provider = scope.ServiceProvider;
|
|
||||||
|
|
||||||
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);
|
|
||||||
```
|
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
# DbContext 工厂
|
# DbContext 工厂
|
||||||
|
|
||||||
> 所属模块:[数据库切换支持](./概览.md)
|
> 所属模块:[数据库支持](./概览.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 接口定义
|
## 1. 本地库工厂
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Data/IDbContextFactory.cs
|
// Infrastructure/LocalData/IDbContextFactory.cs
|
||||||
using Gpulse.WCT.DataAnalyzer.Data;
|
namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
|
||||||
|
|
||||||
namespace Gpulse.WCT.DataAnalyzer.Data;
|
|
||||||
|
|
||||||
public interface IDbContextFactory
|
public interface IDbContextFactory
|
||||||
{
|
{
|
||||||
@@ -18,105 +16,68 @@ public interface IDbContextFactory
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
工厂实现读取 `Database:LocalPath`(fallback `Database:Path`),支持 SQLite 和 PostgreSQL。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 工厂实现
|
## 2. 发布库工厂
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Data/DbContextFactory.cs
|
// Infrastructure/ReleaseData/IReleaseDbContextFactory.cs
|
||||||
using Microsoft.EntityFrameworkCore;
|
namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.ReleaseData;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace Gpulse.WCT.DataAnalyzer.Data;
|
public interface IReleaseDbContextFactory
|
||||||
|
|
||||||
public class DbContextFactory : IDbContextFactory
|
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _configuration;
|
ReleaseDbContext CreateDbContext();
|
||||||
|
|
||||||
public DbContextFactory(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
_configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WctMinerDbContext CreateDbContext()
|
|
||||||
{
|
|
||||||
var dbType = _configuration["Database:Type"]?.ToLower() ?? "sqlite";
|
|
||||||
var optionsBuilder = new DbContextOptionsBuilder<WctMinerDbContext>();
|
|
||||||
|
|
||||||
switch (dbType)
|
|
||||||
{
|
|
||||||
case "postgresql":
|
|
||||||
case "postgres":
|
|
||||||
var pgConnStr = BuildPostgreSqlConnectionString();
|
|
||||||
optionsBuilder.UseNpgsql(pgConnStr);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "sqlite":
|
|
||||||
default:
|
|
||||||
var dbPath = _configuration["Database:Path"] ?? "./data/database/wctminer.db";
|
|
||||||
EnsureDirectoryExists(dbPath);
|
|
||||||
optionsBuilder.UseSqlite($"Data Source={dbPath}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
optionsBuilder.EnableSensitiveDataLogging();
|
|
||||||
optionsBuilder.EnableDetailedErrors();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return new WctMinerDbContext(optionsBuilder.Options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string BuildPostgreSqlConnectionString()
|
|
||||||
{
|
|
||||||
var host = _configuration["Database:Host"] ?? "localhost";
|
|
||||||
var port = _configuration["Database:Port"] ?? "5432";
|
|
||||||
var name = _configuration["Database:Name"] ?? "wctminer";
|
|
||||||
var user = _configuration["Database:User"] ?? "postgres";
|
|
||||||
var password = _configuration["Database:Password"] ?? "";
|
|
||||||
|
|
||||||
return $"Host={host};Port={port};Database={name};Username={user};Password={password}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EnsureDirectoryExists(string dbPath)
|
|
||||||
{
|
|
||||||
var directory = Path.GetDirectoryName(dbPath);
|
|
||||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(directory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
工厂实现读取 `Database:ReleasePath`,支持 SQLite 和 PostgreSQL。PostgreSQL 时使用 `Release*` 前缀配置(`ReleaseHost`, `ReleasePort`, `ReleaseName`, `ReleaseUser`, `ReleasePassword`),未设置时回退到 local 的对应配置。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 依赖注入扩展
|
## 3. DI 注册
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Extensions/ServiceCollectionExtensions.cs
|
// Infrastructure/Extensions/ServiceCollectionExtensions.cs
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Gpulse.WCT.DataAnalyzer.Data;
|
|
||||||
|
|
||||||
namespace Gpulse.WCT.DataAnalyzer.Extensions;
|
|
||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddDatabaseServices(
|
public static IServiceCollection AddDatabaseServices(
|
||||||
this IServiceCollection services,
|
this IServiceCollection services,
|
||||||
IConfiguration configuration)
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
// 注册 DbContext 工厂
|
// 本地库
|
||||||
services.AddScoped<IDbContextFactory, DbContextFactory>();
|
services.AddScoped<IDbContextFactory, DbContextFactory>();
|
||||||
|
|
||||||
// 注册 DbContext(通过工厂创建)
|
|
||||||
services.AddScoped<WctMinerDbContext>(sp =>
|
services.AddScoped<WctMinerDbContext>(sp =>
|
||||||
{
|
{
|
||||||
var factory = sp.GetRequiredService<IDbContextFactory>();
|
var factory = sp.GetRequiredService<IDbContextFactory>();
|
||||||
return factory.CreateDbContext();
|
return factory.CreateDbContext();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 发布库(独立)
|
||||||
|
services.AddScoped<IReleaseDbContextFactory, ReleaseDbContextFactory>();
|
||||||
|
services.AddScoped<ReleaseDbContext>(sp =>
|
||||||
|
{
|
||||||
|
var factory = sp.GetRequiredService<IReleaseDbContextFactory>();
|
||||||
|
return factory.CreateDbContext();
|
||||||
|
});
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
```
|
||||||
|
|
||||||
|
## 4. 启动初始化
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Program.cs
|
||||||
|
using var scope = app.Services.CreateScope();
|
||||||
|
|
||||||
|
// 初始化本地库
|
||||||
|
var dbContext = scope.ServiceProvider.GetRequiredService<WctMinerDbContext>();
|
||||||
|
await dbContext.Database.EnsureCreatedAsync();
|
||||||
|
|
||||||
|
// 初始化发布库
|
||||||
|
var releaseContext = scope.ServiceProvider.GetRequiredService<ReleaseDbContext>();
|
||||||
|
await releaseContext.Database.EnsureCreatedAsync();
|
||||||
|
|
||||||
|
// 本地库种子数据
|
||||||
|
SeedData.Initialize(dbContext);
|
||||||
```
|
```
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# 数据库切换支持
|
# 数据库切换与双数据库架构
|
||||||
|
|
||||||
> 所属模块:[架构概览](../概览.md)
|
> 所属模块:[架构概览](../概览.md)
|
||||||
|
|
||||||
@@ -8,38 +8,48 @@
|
|||||||
|
|
||||||
| 文档 | 说明 |
|
| 文档 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| [DbContext 工厂](./DbContext工厂.md) | 数据库上下文工厂设计 |
|
| [DbContext 工厂](./DbContext工厂.md) | 两个数据库上下文工厂设计 |
|
||||||
| [迁移策略](./迁移策略.md) | 数据库迁移和切换步骤 |
|
| [迁移策略](./迁移策略.md) | 数据库迁移步骤 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 切换策略概览
|
## 双数据库架构
|
||||||
|
|
||||||
```
|
```text
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ appsettings.json │
|
│ appsettings.json │
|
||||||
│ "Database": { "Type": "sqlite" | "postgresql" } │
|
│ Database:LocalPath → local.db │
|
||||||
└─────────────────────────────────────────────────────────────┘
|
│ Database:ReleasePath → release.db │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ DbContextFactory │
|
│ DbContextFactory │
|
||||||
│ 读取配置 → 选择 Provider → 创建 DbContext │
|
│ 读取配置 → 选择 Provider → 创建 WctMinerDbContext │
|
||||||
└─────────────────────────────────────────────────────────────┘
|
│ ReleaseDbContextFactory │
|
||||||
|
│ 读取配置 → 选择 Provider → 创建 ReleaseDbContext │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ WctMinerDbContext │
|
│ local.db release.db │
|
||||||
│ 统一的 DbModel 配置,适配两种数据库 │
|
│ ──────── ────────── │
|
||||||
└─────────────────────────────────────────────────────────────┘
|
│ tx_panel charging_parameter │
|
||||||
|
│ tx_hardware 独立实体,无外键关联 │
|
||||||
|
│ tx_software │
|
||||||
|
│ rx_type │
|
||||||
|
│ test_scenario │
|
||||||
|
│ qfod_record │
|
||||||
|
│ ploss_record │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 当前状态
|
## 当前状态
|
||||||
|
|
||||||
| 项目 | 状态 |
|
| 项目 | 状态 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| SQLite | ✅ 默认启用 |
|
| SQLite | ✅ 默认启用 |
|
||||||
| PostgreSQL | ✅ 架构支持,待配置 |
|
| PostgreSQL | ✅ 架构支持 |
|
||||||
| 切换方式 | 配置文件切换 |
|
| local.db | ✅ 解析库 |
|
||||||
|
| release.db | ✅ 发布库 |
|
||||||
|
| 双库隔离 | ✅ 无外键/导航属性 |
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
# 数据库迁移策略
|
# 数据库迁移策略
|
||||||
|
|
||||||
> 所属模块:[数据库切换支持](./概览.md)
|
> 所属模块:[数据库支持](./概览.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 迁移命令
|
## 1. 本地库迁移
|
||||||
|
|
||||||
### SQLite
|
### SQLite
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 添加迁移
|
# 添加迁移
|
||||||
dotnet ef migrations add InitialCreate --output-dir Migrations/SQLite
|
dotnet ef migrations add InitialCreate --output-dir Migrations/SQLite \
|
||||||
|
--context WctMinerDbContext --project src/Gpulse.WCT.DataAnalyzer.Core
|
||||||
|
|
||||||
# 应用迁移
|
# 应用迁移
|
||||||
dotnet ef database update
|
dotnet ef database update
|
||||||
@@ -20,42 +21,43 @@ dotnet ef database update
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 切换配置后添加迁移
|
# 切换配置后添加迁移
|
||||||
dotnet ef migrations add InitialCreate --output-dir Migrations/PostgreSQL -- --environment PostgreSQL
|
dotnet ef migrations add InitialCreate --output-dir Migrations/PostgreSQL \
|
||||||
|
--context WctMinerDbContext --project src/Gpulse.WCT.DataAnalyzer.Core \
|
||||||
# 应用迁移
|
-- --environment Production
|
||||||
dotnet ef database update -- --environment PostgreSQL
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 切换步骤
|
## 2. 发布库迁移
|
||||||
|
|
||||||
### 从 SQLite 切换到 PostgreSQL
|
发布库的迁移需要使用 `ReleaseDbContext`:
|
||||||
|
|
||||||
| 步骤 | 操作 | 说明 |
|
```bash
|
||||||
|------|------|------|
|
# 添加迁移
|
||||||
| 1 | 修改配置 | `appsettings.json` 中 `Database:Type` 改为 `postgresql` |
|
dotnet ef migrations add InitialCreate --output-dir Migrations/Release \
|
||||||
| 2 | 设置连接 | 配置 `Host`, `Port`, `Name`, `User`, `Password` |
|
--context ReleaseDbContext --project src/Gpulse.WCT.DataAnalyzer.Core
|
||||||
| 3 | 创建数据库 | `CREATE DATABASE wctminer;` |
|
|
||||||
| 4 | 应用迁移 | `dotnet ef database update` |
|
# 应用迁移
|
||||||
| 5 | 数据迁移 | 使用工具迁移 SQLite 数据到 PostgreSQL |
|
dotnet ef database update --context ReleaseDbContext
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 配置文件示例
|
## 3. 配置文件示例
|
||||||
|
|
||||||
### SQLite (默认)
|
### SQLite(默认双库)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Database": {
|
"Database": {
|
||||||
"Type": "sqlite",
|
"Type": "sqlite",
|
||||||
"Path": "./data/database/wctminer.db"
|
"LocalPath": "./data/database/local.db",
|
||||||
|
"ReleasePath": "./data/database/release.db"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### PostgreSQL
|
### PostgreSQL(双库独立)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -63,9 +65,8 @@ dotnet ef database update -- --environment PostgreSQL
|
|||||||
"Type": "postgresql",
|
"Type": "postgresql",
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 5432,
|
"Port": 5432,
|
||||||
"Name": "wctminer",
|
"LocalPath": "Server=localhost;Database=wct_local",
|
||||||
"User": "wctminer_user",
|
"ReleasePath": "Server=db-server;Database=wct_charging_parameters"
|
||||||
"Password": "${DB_PASSWORD}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -77,5 +78,11 @@ dotnet ef database update -- --environment PostgreSQL
|
|||||||
推荐使用 [pgloader](https://github.com/dimitri/pgloader) 迁移 SQLite 到 PostgreSQL:
|
推荐使用 [pgloader](https://github.com/dimitri/pgloader) 迁移 SQLite 到 PostgreSQL:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pgloader ./data/database/wctminer.db postgresql://user:password@localhost:5432/wctminer
|
# 本地库
|
||||||
|
pgloader ./data/database/local.db postgresql://user:password@localhost:5432/wct_local
|
||||||
|
|
||||||
|
# 发布库
|
||||||
|
pgloader ./data/database/release.db postgresql://user:password@db-server:5432/wct_charging_parameters
|
||||||
```
|
```
|
||||||
|
|
||||||
|
两个数据库独立迁移,不共享迁移历史。
|
||||||
@@ -4,213 +4,158 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 完整解析流程
|
## 1. 完整流程
|
||||||
|
|
||||||
|
```text
|
||||||
|
log 文件
|
||||||
|
→ parse
|
||||||
|
→ local.db
|
||||||
|
→ aggregate
|
||||||
|
→ release.db
|
||||||
|
→ export
|
||||||
|
→ WCT-ChargingParameterDatabase.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 阶段一:parse → local.db
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant CLI as CLI Entry
|
participant CLI as CLI
|
||||||
participant PS as ParseService
|
participant PS as ParseService
|
||||||
participant SS as ScenarioService
|
participant SS as ScenarioService
|
||||||
participant FNP as FileNameParser
|
participant FNP as FileNameParser
|
||||||
participant QP as QfodParser
|
participant QP as QfodParser
|
||||||
participant PP as PlossParser
|
participant PP as PlossParser
|
||||||
participant DB as Database
|
participant DB as local.db
|
||||||
participant Log as Serilog
|
|
||||||
|
|
||||||
CLI->>PS: 扫描日志目录
|
|
||||||
PS-->>CLI: 返回文件列表
|
|
||||||
|
|
||||||
loop 每个日志文件
|
|
||||||
CLI->>PS: ParseFileAsync(filePath)
|
CLI->>PS: ParseFileAsync(filePath)
|
||||||
|
|
||||||
PS->>FNP: 解析文件名
|
PS->>FNP: 解析文件名
|
||||||
FNP-->>PS: 场景信息(面板、版本、RX类型等)
|
FNP-->>PS: ScenarioInfo
|
||||||
|
PS->>SS: GetOrCreateScenarioAsync
|
||||||
|
SS->>DB: 维度 upsert + 场景 upsert
|
||||||
|
SS-->>PS: TestScenario
|
||||||
|
PS->>PS: 逐行读取文件
|
||||||
|
|
||||||
PS->>SS: GetOrCreateScenarioAsync(场景信息)
|
loop 每行
|
||||||
SS->>DB: 查询/创建维度记录
|
alt Qfod 行
|
||||||
SS->>DB: 查询/创建场景记录
|
PS->>QP: Parse(line)
|
||||||
SS-->>PS: 返回 TestScenario
|
QP-->>PS: QfodRecord
|
||||||
|
else Ploss 行
|
||||||
PS->>PS: 读取文件内容
|
PS->>PP: Parse(line)
|
||||||
|
PP-->>PS: PlossRecord
|
||||||
loop 逐行遍历
|
|
||||||
alt 是 Qfod 行
|
|
||||||
PS->>QP: Parse(line, scenarioId)
|
|
||||||
QP-->>PS: ParseResult
|
|
||||||
else 是 Ploss 行
|
|
||||||
PS->>PP: Parse(line, scenarioId)
|
|
||||||
PP-->>PS: ParseResult
|
|
||||||
end
|
|
||||||
|
|
||||||
alt 解析成功
|
|
||||||
PS->>PS: 加入记录列表
|
|
||||||
else 解析失败
|
|
||||||
PS->>Log: LogError(line, error)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PS->>DB: BATCH INSERT QfodRecords
|
PS->>DB: BatchInsert QfodRecords
|
||||||
PS->>DB: BATCH INSERT PlossRecords
|
PS->>DB: BatchInsert PlossRecords
|
||||||
PS->>DB: UPDATE scenario (qfod_count, ploss_count)
|
PS->>DB: UpdateScenarioStats
|
||||||
|
PS-->>CLI: ParseReport
|
||||||
|
```
|
||||||
|
|
||||||
PS-->>CLI: 返回 ParseReport
|
**关键点:**
|
||||||
end
|
|
||||||
|
|
||||||
CLI-->>CLI: 输出解析报告
|
- 文件名格式: `TxPanel-TxHardware-TxSoftware-RxType-Purpose-Date-Seq.log`
|
||||||
|
- 支持两行 Ploss 格式(header 行 + 数据行)
|
||||||
|
- 事务批量写入,读取 `Parser:BatchSize` 配置分批
|
||||||
|
- 解析错误记录到 Serilog,不阻断流程
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 阶段二:aggregate → release.db
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant CLI as CLI
|
||||||
|
participant AS as AggregationService
|
||||||
|
participant LDB as local.db
|
||||||
|
participant RDB as release.db
|
||||||
|
|
||||||
|
CLI->>AS: AggregateAsync()
|
||||||
|
AS->>LDB: 读取所有 PlossRecord + 关联维度
|
||||||
|
AS->>AS: 按 (车厂, 车型, 手机厂商, 型号) 分组
|
||||||
|
AS->>AS: 计算 9 个功率列平均值
|
||||||
|
AS->>AS: 计算 Q值/Q基值/P-Q系数
|
||||||
|
AS->>RDB: DELETE + INSERT (事务)
|
||||||
|
AS-->>CLI: AggregationReport
|
||||||
|
```
|
||||||
|
|
||||||
|
**聚合规则:**
|
||||||
|
|
||||||
|
| 字段 | 来源 | 计算方式 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 车厂 (CarFactory) | 配置映射 `Aggregation:TxPanelMappings` | 找不到映射时 fallback 为 TxPanel.Name |
|
||||||
|
| 车型 (CarModel) | 配置映射 `Aggregation:CarModelMappings` | fallback 为 TxHardware.Version |
|
||||||
|
| 手机厂商 (PhoneBrand) | RxType.Name 拆分 | 取第一个分隔符前部分 |
|
||||||
|
| 型号 (PhoneModel) | RxType.Name 拆分 | 取第一个分隔符后部分 |
|
||||||
|
| 350mW~2250mW(9列) | PlossRecord.Field7 | 按功率匹配后取平均值 |
|
||||||
|
| Q值 (QValue) | QfodRecord.CurrentQ | 平均值 |
|
||||||
|
| Q基值 (QBaseValue) | QfodRecord.RawQ | 平均值 |
|
||||||
|
| P-Q值系数 (PqCoefficient) | PlossRecord.DeltaP | 平均值 |
|
||||||
|
| 谐振频率 (ResonanceFrequency) | 暂无 | 允许为空 |
|
||||||
|
|
||||||
|
**替代配置:**
|
||||||
|
|
||||||
|
配置文件 `appsettings.json` 中可添加映射:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Aggregation": {
|
||||||
|
"TxPanelMappings": {
|
||||||
|
"single-mold": "奇瑞",
|
||||||
|
"dual-rapid": "智己"
|
||||||
|
},
|
||||||
|
"CarModelMappings": {
|
||||||
|
"L6": "L6",
|
||||||
|
"CM3": "CM3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. ParseService 实现
|
## 4. 阶段三:export → CSV
|
||||||
|
|
||||||
```csharp
|
```mermaid
|
||||||
// Services/ParseService.cs
|
sequenceDiagram
|
||||||
using Microsoft.EntityFrameworkCore;
|
participant CLI as CLI
|
||||||
using Serilog;
|
participant ES as ExportService
|
||||||
using Gpulse.WCT.DataAnalyzer.Data;
|
participant RDB as release.db
|
||||||
using Gpulse.WCT.DataAnalyzer.Models;
|
participant FS as Filesystem
|
||||||
using Gpulse.WCT.DataAnalyzer.Parsers;
|
|
||||||
|
|
||||||
namespace Gpulse.WCT.DataAnalyzer.Services;
|
CLI->>ES: ExportChargingParametersToCsvAsync(outputDir)
|
||||||
|
ES->>RDB: 读取 ChargingParameters (排序)
|
||||||
public class ParseService
|
ES->>FS: 写入 WCT-ChargingParameterDatabase.csv
|
||||||
{
|
ES-->>CLI: 文件路径
|
||||||
private readonly WctMinerDbContext _context;
|
|
||||||
private readonly ScenarioService _scenarioService;
|
|
||||||
private readonly QfodParser _qfodParser;
|
|
||||||
private readonly PlossParser _plossParser;
|
|
||||||
private readonly FileNameParser _fileNameParser;
|
|
||||||
private readonly int _batchSize;
|
|
||||||
|
|
||||||
public ParseService(
|
|
||||||
WctMinerDbContext context,
|
|
||||||
ScenarioService scenarioService,
|
|
||||||
QfodParser qfodParser,
|
|
||||||
PlossParser plossParser,
|
|
||||||
FileNameParser fileNameParser,
|
|
||||||
IConfiguration config)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
_scenarioService = scenarioService;
|
|
||||||
_qfodParser = qfodParser;
|
|
||||||
_plossParser = plossParser;
|
|
||||||
_fileNameParser = fileNameParser;
|
|
||||||
_batchSize = config.GetValue("Parser:BatchSize", 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ParseReport> ParseFileAsync(string filePath)
|
|
||||||
{
|
|
||||||
var fileInfo = new FileInfo(filePath);
|
|
||||||
|
|
||||||
// 解析文件名获取场景信息
|
|
||||||
var scenarioInfo = _fileNameParser.Parse(fileInfo.Name);
|
|
||||||
if (scenarioInfo == null)
|
|
||||||
{
|
|
||||||
Log.Error("无法解析文件名: {FileName}", fileInfo.Name);
|
|
||||||
return new ParseReport(fileInfo.Name, 0, 0, 1, "文件名格式不匹配");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取或创建测试场景
|
|
||||||
var scenario = await _scenarioService.GetOrCreateScenarioAsync(scenarioInfo);
|
|
||||||
|
|
||||||
var lines = await File.ReadAllLinesAsync(filePath);
|
|
||||||
var qfodRecords = new List<QfodRecord>();
|
|
||||||
var plossRecords = new List<PlossRecord>();
|
|
||||||
int errorCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < lines.Length; i++)
|
|
||||||
{
|
|
||||||
var line = lines[i];
|
|
||||||
var lineNumber = i + 1;
|
|
||||||
|
|
||||||
if (_qfodParser.CanParse(line))
|
|
||||||
{
|
|
||||||
var result = _qfodParser.Parse(line, scenario.Id);
|
|
||||||
if (result.IsSuccess)
|
|
||||||
qfodRecords.Add(result.Record!);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Error("Qfod解析失败 [{Line}] {Error}: {Message}",
|
|
||||||
lineNumber, result.ErrorType, result.ErrorMessage);
|
|
||||||
errorCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_plossParser.CanParse(line))
|
|
||||||
{
|
|
||||||
var result = _plossParser.Parse(line, scenario.Id);
|
|
||||||
if (result.IsSuccess)
|
|
||||||
plossRecords.Add(result.Record!);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Error("Ploss解析失败 [{Line}] {Error}: {Message}",
|
|
||||||
lineNumber, result.ErrorType, result.ErrorMessage);
|
|
||||||
errorCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量保存
|
|
||||||
await BatchInsertAsync(qfodRecords, plossRecords);
|
|
||||||
|
|
||||||
// 更新场景统计
|
|
||||||
await UpdateScenarioStatsAsync(scenario.Id, qfodRecords.Count, plossRecords.Count);
|
|
||||||
|
|
||||||
return new ParseReport(fileInfo.Name, qfodRecords.Count, plossRecords.Count, errorCount, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task BatchInsertAsync(
|
|
||||||
List<QfodRecord> qfodRecords,
|
|
||||||
List<PlossRecord> plossRecords)
|
|
||||||
{
|
|
||||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 批量插入 QfodRecord
|
|
||||||
foreach (var batch in qfodRecords.Chunk(_batchSize))
|
|
||||||
{
|
|
||||||
_context.QfodRecords.AddRange(batch);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量插入 PlossRecord
|
|
||||||
foreach (var batch in plossRecords.Chunk(_batchSize))
|
|
||||||
{
|
|
||||||
_context.PlossRecords.AddRange(batch);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
await transaction.CommitAsync();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await transaction.RollbackAsync();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateScenarioStatsAsync(Guid scenarioId, int qfodCount, int plossCount)
|
|
||||||
{
|
|
||||||
// 使用累加更新(+=),支持同一场景的多次解析
|
|
||||||
// 若需幂等解析,应在 ParseFileAsync 开始时检查场景是否已处理
|
|
||||||
var scenario = await _context.TestScenarios.FindAsync(scenarioId);
|
|
||||||
if (scenario != null)
|
|
||||||
{
|
|
||||||
scenario.QfodCount += qfodCount;
|
|
||||||
scenario.PlossCount += plossCount;
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record ParseReport(string FileName, int QfodCount, int PlossCount, int ErrorCount, string? ErrorMessage);
|
|
||||||
|
|
||||||
public record ScenarioInfo(
|
|
||||||
string TxPanel,
|
|
||||||
string TxHardware,
|
|
||||||
string TxSoftware,
|
|
||||||
string RxType,
|
|
||||||
string? TestPurpose,
|
|
||||||
DateOnly TestDate,
|
|
||||||
int TestSequence);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**导出规格:**
|
||||||
|
|
||||||
|
- 固定文件名: `WCT-ChargingParameterDatabase.csv`
|
||||||
|
- 固定 17 列 UTF-8 BOM
|
||||||
|
- RFC 4180 转义
|
||||||
|
- 数字使用 invariant culture
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 数据库隔离
|
||||||
|
|
||||||
|
```text
|
||||||
|
local.db release.db
|
||||||
|
───────── ──────────
|
||||||
|
tx_panel charging_parameter
|
||||||
|
tx_hardware (独立实体,无外键)
|
||||||
|
tx_software
|
||||||
|
rx_type
|
||||||
|
test_scenario
|
||||||
|
qfod_record
|
||||||
|
ploss_record
|
||||||
|
|
||||||
|
无关联 ←→ 无关联
|
||||||
|
```
|
||||||
|
|
||||||
|
- 代码中不存在 local entity 到 release entity 的导航属性
|
||||||
|
- 数据库中没有跨库外键
|
||||||
|
- export 不读 local.db,aggregate 不写 local.db
|
||||||
@@ -1,68 +1,149 @@
|
|||||||
# Architecture: Gpulse.WCT.DataAnalyzer
|
# Architecture: Gpulse.WCT.DataAnalyzer
|
||||||
|
|
||||||
> 无线充电 FOD(异物检测)日志数据采集系统,从日志文件中解析 Qfod 和 Ploss 数据并存储到数据库
|
> 无线充电参数采集系统。从 log 文件解析 Qfod/Ploss 数据 → 本地分析库 → 汇总生成正式充电参数数据库 → 导出 CSV。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 数据流
|
||||||
|
|
||||||
|
```text
|
||||||
|
log 文件
|
||||||
|
→ parse
|
||||||
|
→ local.db(本地解析库)
|
||||||
|
→ aggregate
|
||||||
|
→ release.db(正式发布库)
|
||||||
|
→ export
|
||||||
|
→ WCT-ChargingParameterDatabase.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
| 阶段 | 命令 | 存储 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 解析 | `parse` | `local.db` | 本地日志解析库,保存原始 Qfod/Ploss 记录 |
|
||||||
|
| 汇总 | `aggregate` | `release.db` | 从 local.db 汇总生成正式充电参数记录 |
|
||||||
|
| 导出 | `export` | CSV | 读取 release.db,输出固定格式 CSV |
|
||||||
|
|
||||||
## 文档索引
|
## 文档索引
|
||||||
|
|
||||||
| 文档 | 说明 |
|
| 文档 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| [技术选型](./技术选型.md) | 技术栈总览、决策记录 |
|
| [技术选型](./技术选型.md) | 技术栈、决策记录 |
|
||||||
| [模块划分](./模块划分.md) | 模块总览、分层架构、依赖关系 |
|
| [模块划分](./模块划分.md) | 分层架构、目录结构、模块职责 |
|
||||||
| [安全架构](./安全架构.md) | 认证方案、数据安全 |
|
| [数据处理流程](./数据处理流程.md) | 解析→汇总→导出完整流程 |
|
||||||
| [解析器设计](./parser/概览.md) | Qfod/Ploss 解析器详细设计 |
|
| [CLI 命令设计](./CLI命令设计.md) | 命令行接口 |
|
||||||
| [数据处理流程](./数据处理流程.md) | 解析流程、批量写入、错误处理 |
|
| [配置管理](./配置管理.md) | 配置结构与双数据库配置 |
|
||||||
| [配置管理](./配置管理.md) | 配置文件、配置类定义 |
|
| [数据库支持](./database/概览.md) | 本地/发布双数据库架构 |
|
||||||
| [CLI 命令设计](./CLI命令设计.md) | 命令行接口定义 |
|
| [安全架构](./安全架构.md) | 路径校验、安全策略 |
|
||||||
| [数据库切换支持](./database/概览.md) | SQLite/PostgreSQL 切换方案 |
|
| [解析器设计](./parser/概览.md) | Qfod/Ploss 解析器 |
|
||||||
|
|
||||||
---
|
## 架构分层
|
||||||
|
|
||||||
## 架构图
|
```text
|
||||||
|
┌──────────────────────────────────────────────────────────────────────┐
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ CLI Entry Point │
|
│ CLI Entry Point │
|
||||||
│ (Program.cs / System.CommandLine) │
|
│ (Program.cs / System.CommandLine) │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────────────────────────┤
|
||||||
│ Service Layer │
|
│ Application Layer │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
│ ┌───────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ │
|
||||||
│ ParseService │ ErrorHandlingService │ ThresholdCalc │
|
│ │ LocalIngestion│ │ Publishing │ │ Exporting │ │Administration│ │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
│ │ ParseService │ │AggregationSvc│ │ExportSvc │ │ StatsService │ │
|
||||||
│ Parser Layer │
|
│ │ ScenarioSvc │ │ThresholdCalc │ │ │ │ CleanService │ │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
│ │ DimensionSvc │ │ │ │ │ │ │ │
|
||||||
│ QfodParser │ PlossParser │ LogFileReader │
|
│ └───────────────┘ └──────────────┘ └────────────┘ └──────────────┘ │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
||||||
│ Data Layer │
|
│ │ Parsing │ │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
│ │ QfodParser │ PlossParser │ FileNameParser │ │
|
||||||
│ Models │ WctMinerDbContext │ IDbContextFactory │
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────────────────────────┤
|
||||||
│ Infrastructure │
|
│ Domain Layer │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
│ ┌─────────────────────────┐ ┌──────────────────────────────────────┐ │
|
||||||
│ Configuration │ Serilog │ AppSettings │
|
│ │ Local/ (本地实体) │ │ Release/ (发布实体) │ │
|
||||||
└─────────────────────────────────────────────────────────────┘
|
│ │ TxPanel, TxHardware │ │ ChargingParameterRecord │ │
|
||||||
|
│ │ TxSoftware, RxType │ │ │ │
|
||||||
|
│ │ TestScenario │ │ │ │
|
||||||
|
│ │ QfodRecord, PlossRecord │ │ │ │
|
||||||
|
│ └─────────────────────────┘ └──────────────────────────────────────┘ │
|
||||||
|
├──────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ Infrastructure Layer │
|
||||||
|
│ ┌───────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ LocalData │ │ ReleaseData │ │Configuration│ │ Security │ │
|
||||||
|
│ │ WctMinerDbCtx │ │ReleaseDbCtx │ │AppSettings │ │PathValidator │ │
|
||||||
|
│ │ DbCtxFactory │ │ReleaseDbCtxF │ │ │ │ │ │
|
||||||
|
│ │ SeedData │ │ │ │ │ │ │ │
|
||||||
|
│ └───────────────┘ └──────────────┘ └────────────┘ └──────────────┘ │
|
||||||
|
└──────────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 核心设计原则
|
## 核心设计原则
|
||||||
|
|
||||||
| 原则 | 说明 |
|
| 原则 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 技术栈 | .NET 8.0 LTS + C# 12.0 + EF Core 8.0 |
|
| 技术栈 | .NET 8.0 + C# 12.0 + EF Core 8.0 |
|
||||||
| 数据库 | SQLite(当前)/ PostgreSQL(后续),配置切换 |
|
| 数据库 | local.db / release.db 两个独立 SQLite 数据库 |
|
||||||
| 解析模式 | 批处理,正则表达式匹配 |
|
| 本地/发布隔离 | 无外键、无导航属性、无数据库级关联 |
|
||||||
| ORM 模式 | Code First,支持迁移自动化 |
|
| 解析模式 | 批处理,正则表达式 |
|
||||||
| CLI 框架 | System.CommandLine,命令模式 |
|
| CLI | System.CommandLine,命令模式 |
|
||||||
|
| 汇总规则 | 按业务键分组取平均值,缺失字段允许为空 |
|
||||||
|
|
||||||
---
|
## 目录结构
|
||||||
|
|
||||||
## 假设/歧义标注
|
```text
|
||||||
|
src/
|
||||||
| 标注 | 内容 |
|
├── Gpulse.WCT.DataAnalyzer/ # CLI 入口
|
||||||
|------|------|
|
│ ├── Commands/ # 命令实现
|
||||||
| [A] | 使用 .NET 8.0 LTS 版本 |
|
│ │ ├── ParseCommand.cs
|
||||||
| [A] | 初期使用 SQLite,架构支持切换 PostgreSQL |
|
│ │ ├── AggregateCommand.cs
|
||||||
| [?] | 增量解析策略需确认 |
|
│ │ ├── StatsCommand.cs
|
||||||
| [?] | 多次解析同一文件的处理策略需确认 |
|
│ │ ├── ExportCommand.cs
|
||||||
|
│ │ └── CleanCommand.cs
|
||||||
|
│ ├── Program.cs
|
||||||
|
│ └── appsettings.json
|
||||||
|
│
|
||||||
|
└── Gpulse.WCT.DataAnalyzer.Core/ # 核心库
|
||||||
|
├── Application/ # 应用层
|
||||||
|
│ ├── Parsing/ # 解析器
|
||||||
|
│ │ ├── IParser.cs
|
||||||
|
│ │ ├── QfodParser.cs
|
||||||
|
│ │ ├── PlossParser.cs
|
||||||
|
│ │ └── FileNameParser.cs
|
||||||
|
│ ├── LocalIngestion/ # 本地日志解析
|
||||||
|
│ │ ├── ParseService.cs
|
||||||
|
│ │ ├── ScenarioService.cs
|
||||||
|
│ │ └── DimensionService.cs
|
||||||
|
│ ├── Publishing/ # 发布汇总
|
||||||
|
│ │ ├── AggregationService.cs
|
||||||
|
│ │ └── ThresholdCalculator.cs
|
||||||
|
│ ├── Exporting/ # 导出
|
||||||
|
│ │ └── ExportService.cs
|
||||||
|
│ └── Administration/ # 管理
|
||||||
|
│ ├── StatsService.cs
|
||||||
|
│ └── CleanService.cs
|
||||||
|
├── Domain/ # 领域实体
|
||||||
|
│ └── Models/
|
||||||
|
│ ├── TxPanel.cs # 本地-维度
|
||||||
|
│ ├── TxHardware.cs
|
||||||
|
│ ├── TxSoftware.cs
|
||||||
|
│ ├── RxType.cs
|
||||||
|
│ ├── TestScenario.cs
|
||||||
|
│ ├── QfodRecord.cs
|
||||||
|
│ ├── PlossRecord.cs
|
||||||
|
│ └── ChargingParameterRecord.cs # 发布-实体
|
||||||
|
└── Infrastructure/ # 基础设施
|
||||||
|
├── LocalData/ # 本地数据库
|
||||||
|
│ ├── WctMinerDbContext.cs
|
||||||
|
│ ├── DbContextFactory.cs
|
||||||
|
│ ├── IDbContextFactory.cs
|
||||||
|
│ ├── SeedData.cs
|
||||||
|
│ └── Configurations/
|
||||||
|
├── ReleaseData/ # 发布数据库
|
||||||
|
│ ├── ReleaseDbContext.cs
|
||||||
|
│ ├── ReleaseDbContextFactory.cs
|
||||||
|
│ ├── IReleaseDbContextFactory.cs
|
||||||
|
│ └── Configurations/
|
||||||
|
├── Configuration/ # 配置
|
||||||
|
│ └── AppSettings.cs
|
||||||
|
├── Security/ # 安全
|
||||||
|
│ ├── PathValidator.cs
|
||||||
|
│ └── SecurityConstants.cs
|
||||||
|
└── Extensions/ # DI 扩展
|
||||||
|
└── ServiceCollectionExtensions.cs
|
||||||
|
```
|
||||||
@@ -4,167 +4,172 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 模块总览
|
## 1. 分层架构
|
||||||
|
|
||||||
```
|
| 层级 | 目录 | 职责 | 依赖 |
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|------|------|------|------|
|
||||||
│ CLI Entry Point │
|
| CLI | `Gpulse.WCT.DataAnalyzer/Commands/` | 接收参数、流程调度、结果输出 | Application |
|
||||||
│ (Program.cs / System.CommandLine) │
|
| Application | `Core/Application/` | 业务流程编排 | Domain, Infrastructure |
|
||||||
├─────────────────────────────────────────────────────────────┤
|
| Domain | `Core/Domain/Models/` | 实体定义,无行为 | 无 |
|
||||||
│ Service Layer │
|
| Infrastructure | `Core/Infrastructure/` | 数据持久化、配置、安全 | Domain |
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ ParseService │ ScenarioService │ DimensionService │ ThresholdCalc │
|
**依赖规则:**
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ Parser Layer │
|
- 上层可依赖下层,下层不可依赖上层
|
||||||
├─────────────────────────────────────────────────────────────┤
|
- Application 依 Domain 和 Infrastructure
|
||||||
│ QfodParser │ PlossParser │ FileNameParser │
|
- Infrastructure 依 Domain
|
||||||
├─────────────────────────────────────────────────────────────┤
|
- Domain 不依赖任何其他层
|
||||||
│ Data Layer │
|
- 解析器之间不可互相调用
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ Models │ WctMinerDbContext │ IDbContextFactory │
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ Infrastructure │
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ Configuration │ Serilog │ AppSettings │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 模块职责
|
## 2. Application 模块职责
|
||||||
|
|
||||||
| 模块名 | 职责 | 依赖模块 |
|
### 2.1 Parsing — 解析
|
||||||
|--------|------|----------|
|
|
||||||
| CLI Entry | 接收命令行参数、调度解析流程 | ParseService, Infrastructure |
|
| 类 | 职责 |
|
||||||
| ParseService | 协调解析流程、批量写入、场景关联 | Parser Layer, ScenarioService, DbContext |
|
|----|------|
|
||||||
| ScenarioService | 创建/查询测试场景,解析文件名获取场景信息 | DimensionService, DbContext |
|
| `FileNameParser` | 从文件名提取场景信息(面板、硬件、软件、RX类型、日期、序号) |
|
||||||
| DimensionService | 维度表 CRUD(获取或创建维度记录) | DbContext |
|
| `QfodParser` | 解析 Qfod 格式日志行 → `QfodRecord` |
|
||||||
| FileNameParser | 解析文件名提取场景信息(面板、版本、RX类型等) | 无 |
|
| `PlossParser` | 解析 Ploss 单行/两行格式日志 → `PlossRecord` |
|
||||||
| LogFileReader | 读取日志文件、行过滤、文件遍历 | 无 |
|
|
||||||
| QfodParser | 解析 Qfod 格式日志,返回 QfodRecord | 无 |
|
### 2.2 LocalIngestion — 本地解析
|
||||||
| PlossParser | 解析 Ploss 格式日志,返回 PlossRecord | 无 |
|
|
||||||
| Models | 定义数据实体结构(维度表 + 事实表) | 无 |
|
| 类 | 职责 |
|
||||||
| WctMinerDbContext | EF Core 数据库上下文 | Models, Configurations |
|
|----|------|
|
||||||
| Configuration | 加载配置文件 | 无 |
|
| `ParseService` | 协调解析流程:读取文件 → 解析 → 批量写入 local.db |
|
||||||
| Serilog | 结构化日志输出(含解析错误日志) | 无 |
|
| `ScenarioService` | 创建/查询测试场景,并发安全 upsert |
|
||||||
| ThresholdCalculator | 根据 RxPower 计算动态阈值 | 无 |
|
| `DimensionService` | 维度表 get-or-create |
|
||||||
|
|
||||||
|
### 2.3 Publishing — 发布汇总
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|----|------|
|
||||||
|
| `AggregationService` | 读取 local.db → 按业务键分组计算 → upsert 到 release.db |
|
||||||
|
| `ThresholdCalculator` | 动态阈值计算 |
|
||||||
|
|
||||||
|
### 2.4 Exporting — 导出
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|----|------|
|
||||||
|
| `ExportService` | 导出 release.db 到固定 CSV,也支持 legacy Qfod/Ploss 导出 |
|
||||||
|
|
||||||
|
### 2.5 Administration — 管理
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|----|------|
|
||||||
|
| `StatsService` | 本地库统计查询 |
|
||||||
|
| `CleanService` | 软删除管理 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 分层架构
|
## 3. Domain 实体
|
||||||
|
|
||||||
### 3.1 分层定义
|
### 3.1 本地实体(local.db)
|
||||||
|
|
||||||
| 层级 | 职责 | 允许调用 |
|
| 实体 | 表 | 说明 |
|
||||||
|------|------|----------|
|
|------|----|------|
|
||||||
| CLI Entry | 接收参数、流程调度、结果输出 | Service, Infrastructure |
|
| `TxPanel` | tx_panel | TX 面板类型维度 |
|
||||||
| Service | 协调解析流程、场景管理、维度管理、批量写入 | Parser, Data, Infrastructure |
|
| `TxHardware` | tx_hardware | TX 硬件版本维度 |
|
||||||
| Parser | 日志解析、文件名解析、数据提取 | 无 |
|
| `TxSoftware` | tx_software | TX 软件版本维度 |
|
||||||
| Data | 数据持久化、DbContext 管理、实体配置 | Infrastructure |
|
| `RxType` | rx_type | RX 类型维度 |
|
||||||
| Infrastructure | 配置、日志 | 无 |
|
| `TestScenario` | test_scenario | 测试场景(4 个维度外键 + 日期 + 序号) |
|
||||||
|
| `QfodRecord` | qfod_record | Qfod 检测数据 |
|
||||||
|
| `PlossRecord` | ploss_record | Ploss FOD 数据 |
|
||||||
|
|
||||||
### 3.2 依赖规则
|
关系:维度表 1:N TestScenario 1:N QfodRecord/PlossRecord(星型模型)
|
||||||
|
|
||||||
- 上层可调用下层
|
### 3.2 发布实体(release.db)
|
||||||
- 下层不可调用上层
|
|
||||||
- Parser 之间不可互相调用(独立解析器)
|
| 实体 | 表 | 唯一键 |
|
||||||
- Service 层协调 Parser 和 Data 层
|
|------|----|--------|
|
||||||
- 解析错误通过 Serilog 记录,不存入业务数据库
|
| `ChargingParameterRecord` | charging_parameter | (car_factory, car_model, phone_brand, phone_model) |
|
||||||
|
|
||||||
|
**两个数据库不建立外键或导航属性关联。**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 模块依赖图
|
## 4. Infrastructure 模块
|
||||||
|
|
||||||
|
### 4.1 LocalData — 本地数据库
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|----|------|
|
||||||
|
| `WctMinerDbContext` | 本地解析库 EF Core 上下文 |
|
||||||
|
| `DbContextFactory` | 根据配置创建 SQLite/PostgreSQL 本地上下文 |
|
||||||
|
| `IDbContextFactory` | 工厂接口 |
|
||||||
|
| `SeedData` | 默认维度数据初始化 |
|
||||||
|
| `Configurations/*` | EF Core `IEntityTypeConfiguration` 实现 |
|
||||||
|
|
||||||
|
### 4.2 ReleaseData — 发布数据库
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|----|------|
|
||||||
|
| `ReleaseDbContext` | 发布库 EF Core 上下文 |
|
||||||
|
| `ReleaseDbContextFactory` | 创建发布库上下文 |
|
||||||
|
| `IReleaseDbContextFactory` | 工厂接口 |
|
||||||
|
| `Configurations/*` | 发布实体配置 |
|
||||||
|
|
||||||
|
### 4.3 Configuration
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|----|------|
|
||||||
|
| `AppSettings` | 应用配置 POCO |
|
||||||
|
|
||||||
|
### 4.4 Security
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|----|------|
|
||||||
|
| `PathValidator` | 路径安全校验(遍历保护、白名单、应用目录限制) |
|
||||||
|
| `SecurityConstants` | 安全常量 |
|
||||||
|
|
||||||
|
### 4.5 Extensions
|
||||||
|
|
||||||
|
| 类 | 职责 |
|
||||||
|
|----|------|
|
||||||
|
| `ServiceCollectionExtensions` | `AddDatabaseServices` / `AddApplicationServices` DI 注册 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 依赖图
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
CLI[CLI Entry] --> PS[ParseService]
|
CLI[CLI Commands] --> Parsing[Parsing]
|
||||||
CLI --> Serilog
|
CLI --> LocalIngestion[LocalIngestion]
|
||||||
|
CLI --> Publishing[Publishing]
|
||||||
|
CLI --> Exporting[Exporting]
|
||||||
|
CLI --> Admin[Administration]
|
||||||
|
|
||||||
PS --> SS[ScenarioService]
|
LocalIngestion --> Parsing
|
||||||
PS --> QP[QfodParser]
|
LocalIngestion --> LocalData[LocalData]
|
||||||
PS --> PP[PlossParser]
|
Publishing --> LocalData
|
||||||
PS --> DBM[WctMinerDbContext]
|
Publishing --> ReleaseData[ReleaseData]
|
||||||
PS --> Serilog
|
Exporting --> LocalData
|
||||||
|
Exporting --> ReleaseData
|
||||||
|
Admin --> LocalData
|
||||||
|
|
||||||
SS --> DS[DimensionService]
|
LocalData --> Domain[Domain Models]
|
||||||
SS --> FNP[FileNameParser]
|
ReleaseData --> Domain
|
||||||
SS --> DBM
|
Parsing --> Domain
|
||||||
|
|
||||||
DS --> DBM
|
subgraph Application
|
||||||
|
Parsing
|
||||||
QP --> Serilog
|
LocalIngestion
|
||||||
PP --> Serilog
|
Publishing
|
||||||
FNP --> Serilog
|
Exporting
|
||||||
|
Admin
|
||||||
DBM --> Models[Models]
|
|
||||||
DBM --> Configs[Configurations]
|
|
||||||
|
|
||||||
subgraph Parser Layer
|
|
||||||
QP
|
|
||||||
PP
|
|
||||||
FNP
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Data Layer
|
subgraph Domain
|
||||||
DBM
|
Domain
|
||||||
Models
|
|
||||||
Configs
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Infrastructure
|
subgraph Infrastructure
|
||||||
Serilog
|
LocalData
|
||||||
end
|
ReleaseData
|
||||||
|
Configuration
|
||||||
subgraph Service Layer
|
Security
|
||||||
PS
|
Extensions
|
||||||
SS
|
|
||||||
DS
|
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 目录结构
|
|
||||||
|
|
||||||
```
|
|
||||||
src/Gpulse.WCT.DataAnalyzer/
|
|
||||||
├── Parsers/
|
|
||||||
│ ├── IParser.cs
|
|
||||||
│ ├── QfodParser.cs
|
|
||||||
│ ├── PlossParser.cs
|
|
||||||
│ ├── FileNameParser.cs # 解析文件名获取场景信息(Parser Layer)
|
|
||||||
│ └── LogFileReader.cs # 读取日志文件(可选)
|
|
||||||
├── Models/
|
|
||||||
│ ├── TxPanel.cs # TX面板维度
|
|
||||||
│ ├── TxHardware.cs # TX硬件版本维度
|
|
||||||
│ ├── TxSoftware.cs # TX软件版本维度
|
|
||||||
│ ├── RxType.cs # RX类型维度
|
|
||||||
│ ├── TestScenario.cs # 测试场景
|
|
||||||
│ ├── QfodRecord.cs # Qfod记录
|
|
||||||
│ └── PlossRecord.cs # Ploss记录
|
|
||||||
├── Data/
|
|
||||||
│ ├── WctMinerDbContext.cs
|
|
||||||
│ ├── IDbContextFactory.cs
|
|
||||||
│ └── Configurations/ # IEntityTypeConfiguration 配置类
|
|
||||||
│ ├── TxPanelConfiguration.cs
|
|
||||||
│ ├── TxHardwareConfiguration.cs
|
|
||||||
│ ├── TxSoftwareConfiguration.cs
|
|
||||||
│ ├── RxTypeConfiguration.cs
|
|
||||||
│ ├── TestScenarioConfiguration.cs
|
|
||||||
│ ├── QfodRecordConfiguration.cs
|
|
||||||
│ └── PlossRecordConfiguration.cs
|
|
||||||
├── Services/
|
|
||||||
│ ├── ParseService.cs # 解析流程协调
|
|
||||||
│ ├── ScenarioService.cs # 场景创建/查询
|
|
||||||
│ ├── DimensionService.cs # 维度表管理
|
|
||||||
│ └── ThresholdCalculator.cs # 阈值计算
|
|
||||||
├── Configuration/
|
|
||||||
│ ├── AppSettings.cs
|
|
||||||
│ └── appsettings.json
|
|
||||||
├── Commands/
|
|
||||||
│ ├── ParseCommand.cs
|
|
||||||
│ ├── StatsCommand.cs
|
|
||||||
│ ├── ExportCommand.cs
|
|
||||||
│ └── CleanCommand.cs
|
|
||||||
└── Program.cs
|
|
||||||
```
|
|
||||||
@@ -4,27 +4,31 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 配置文件结构
|
## 1. 配置文件
|
||||||
|
|
||||||
```json
|
```json
|
||||||
// Configuration/appsettings.json
|
|
||||||
{
|
{
|
||||||
"Database": {
|
"Database": {
|
||||||
"Type": "sqlite",
|
"Type": "sqlite",
|
||||||
"Path": "./data/database/wctminer.db"
|
"LocalPath": "./data/database/local.db",
|
||||||
|
"ReleasePath": "./data/database/release.db"
|
||||||
},
|
},
|
||||||
"Parser": {
|
"Parser": {
|
||||||
"BatchSize": 1000
|
"BatchSize": 1000
|
||||||
},
|
},
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Gpulse.WCT.DataAnalyzer": "Debug"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Paths": {
|
"Paths": {
|
||||||
"LogDir": "./data/logs",
|
"LogDir": "./data/logs",
|
||||||
"OutputDir": "./data/output"
|
"OutputDir": "./data/output"
|
||||||
|
},
|
||||||
|
"Aggregation": {
|
||||||
|
"TxPanelMappings": {
|
||||||
|
"single-mold": "奇瑞",
|
||||||
|
"dual-rapid": "智己"
|
||||||
|
},
|
||||||
|
"CarModelMappings": {
|
||||||
|
"L6": "L6",
|
||||||
|
"CM3": "CM3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -34,8 +38,8 @@
|
|||||||
## 2. 配置类定义
|
## 2. 配置类定义
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Configuration/AppSettings.cs
|
// Infrastructure/Configuration/AppSettings.cs
|
||||||
namespace Gpulse.WCT.DataAnalyzer.Configuration;
|
namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.Configuration;
|
||||||
|
|
||||||
public class AppSettings
|
public class AppSettings
|
||||||
{
|
{
|
||||||
@@ -47,7 +51,8 @@ public class AppSettings
|
|||||||
public class DatabaseSettings
|
public class DatabaseSettings
|
||||||
{
|
{
|
||||||
public string Type { get; set; } = "sqlite";
|
public string Type { get; set; } = "sqlite";
|
||||||
public string Path { get; set; } = "./data/database/wctminer.db";
|
public string LocalPath { get; set; } = "./data/database/local.db";
|
||||||
|
public string ReleasePath { get; set; } = "./data/database/release.db";
|
||||||
|
|
||||||
// PostgreSQL 配置(可选)
|
// PostgreSQL 配置(可选)
|
||||||
public string? Host { get; set; }
|
public string? Host { get; set; }
|
||||||
@@ -71,35 +76,51 @@ public class PathSettings
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 配置加载
|
## 3. 数据库配置说明
|
||||||
|
|
||||||
```csharp
|
### 双数据库路径
|
||||||
// Program.cs 配置加载部分
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Gpulse.WCT.DataAnalyzer.Configuration;
|
|
||||||
|
|
||||||
var builder = Host.CreateDefaultBuilder(args);
|
| 配置键 | 默认值 | 说明 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| `Database:LocalPath` | `./data/database/local.db` | 本地解析库 |
|
||||||
|
| `Database:ReleasePath` | `./data/database/release.db` | 正式发布库 |
|
||||||
|
|
||||||
builder.ConfigureAppConfiguration(config =>
|
两个数据库独立配置,可以位于不同路径,也可以使用不同类型的数据库。
|
||||||
|
|
||||||
|
### 兼容旧配置
|
||||||
|
|
||||||
|
`Database:Path` 仍然作为 local 数据库的 fallback 路径,推荐使用 `Database:LocalPath`。
|
||||||
|
|
||||||
|
### PostgreSQL 支持
|
||||||
|
|
||||||
|
当 `Database:Type` 设为 `postgresql` 时,local 和 release 都可以使用独立的 PostgreSQL 连接:
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
config.AddJsonFile("appsettings.json", optional: false);
|
"Database": {
|
||||||
config.AddJsonFile($"appsettings.{environment}.json", optional: true);
|
"Type": "postgresql",
|
||||||
config.AddEnvironmentVariables();
|
"Host": "localhost",
|
||||||
config.AddCommandLine(args);
|
"Port": 5432,
|
||||||
});
|
"Name": "wct_local",
|
||||||
|
"User": "wct_user",
|
||||||
builder.ConfigureServices((context, services) =>
|
"Password": "...",
|
||||||
{
|
"ReleaseHost": "db-server",
|
||||||
services.Configure<AppSettings>(context.Configuration.GetSection(""));
|
"ReleaseName": "wct_charging_parameters",
|
||||||
|
"ReleaseUser": "wct_release",
|
||||||
// 注册数据库服务
|
"ReleasePassword": "..."
|
||||||
services.AddDatabaseServices(context.Configuration);
|
}
|
||||||
|
}
|
||||||
// 注册其他服务
|
|
||||||
services.AddScoped<QfodParser>();
|
|
||||||
services.AddScoped<PlossParser>();
|
|
||||||
services.AddScoped<ParseService>();
|
|
||||||
services.AddScoped<ErrorHandlingService>();
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Release 数据库的 PostgreSQL 配置前缀为 `Release*`,未设置时回退到 local 的对应配置。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 聚合配置
|
||||||
|
|
||||||
|
| 配置键 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| `Aggregation:TxPanelMappings` | TxPanel 名称 → 车厂名称的映射表 |
|
||||||
|
| `Aggregation:CarModelMappings` | TxHardware 版本 → 车型名称的映射表 |
|
||||||
|
|
||||||
|
未配置映射时,直接使用 TxPanel.Name 和 TxHardware.Version 作为最终值。手机品牌/型号从 RxType.Name 自动拆分(分隔符 `/_:`)。
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
# Data Model: Gpulse.WCT.DataAnalyzer
|
# Data Model: Gpulse.WCT.DataAnalyzer
|
||||||
|
|
||||||
> Gpulse.WCT.DataAnalyzer 数据库模型规格,采用维度表设计,消除数据冗余
|
> 双数据库数据模型:本地解析库 + 正式发布库,相互独立。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 文档索引
|
## 文档索引
|
||||||
|
|
||||||
### 维度表
|
### 本地库(local.db)
|
||||||
|
|
||||||
| 文档 | 说明 |
|
| 文档 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
@@ -14,253 +14,74 @@
|
|||||||
| [TX硬件版本维度](./entities/TxHardware维度.md) | TX硬件版本维度 |
|
| [TX硬件版本维度](./entities/TxHardware维度.md) | TX硬件版本维度 |
|
||||||
| [TX软件版本维度](./entities/TxSoftware维度.md) | TX软件版本维度 |
|
| [TX软件版本维度](./entities/TxSoftware维度.md) | TX软件版本维度 |
|
||||||
| [RX类型维度](./entities/RxType维度.md) | RX类型维度 |
|
| [RX类型维度](./entities/RxType维度.md) | RX类型维度 |
|
||||||
|
|
||||||
### 事实表
|
|
||||||
|
|
||||||
| 文档 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| [测试场景实体](./entities/测试场景.md) | 测试场景(外键组合) |
|
| [测试场景实体](./entities/测试场景.md) | 测试场景(外键组合) |
|
||||||
| [Qfod 记录实体](./entities/Qfod记录.md) | Qfod 检测数据 |
|
| [Qfod 记录实体](./entities/Qfod记录.md) | Qfod 检测数据 |
|
||||||
| [Ploss 记录实体](./entities/Ploss记录.md) | Ploss FOD 数据 |
|
| [Ploss 记录实体](./entities/Ploss记录.md) | Ploss FOD 数据 |
|
||||||
| [关系说明](./关系说明.md) | 实体间关系定义 |
|
| [关系说明](./关系说明.md) | 本地库实体间关系 |
|
||||||
|
|
||||||
|
### 发布库(release.db)
|
||||||
|
|
||||||
|
| 实体 | 表 | 说明 |
|
||||||
|
|------|----|------|
|
||||||
|
| ChargingParameterRecord | charging_parameter | 正式充电参数记录 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 实体列表
|
## 架构概览
|
||||||
|
|
||||||
### 维度表(4张)
|
|
||||||
|
|
||||||
| 表名 | 职责 | 关键字段 |
|
|
||||||
|------|------|----------|
|
|
||||||
| tx_panel | TX面板类型维度 | name (UNIQUE) |
|
|
||||||
| tx_hardware | TX硬件版本维度 | version (UNIQUE) |
|
|
||||||
| tx_software | TX软件版本维度 | version (UNIQUE) |
|
|
||||||
| rx_type | RX类型维度 | name (UNIQUE) |
|
|
||||||
|
|
||||||
### 事实表(3张)
|
|
||||||
|
|
||||||
| 表名 | 职责 | 关键字段 |
|
|
||||||
|------|------|----------|
|
|
||||||
| test_scenario | 测试场景组合 | 4个维度外键 + test_purpose + test_date |
|
|
||||||
| qfod_record | Qfod 检测数据 | scenario_id, delta_q, fod_type |
|
|
||||||
| ploss_record | Ploss FOD 数据 | scenario_id, ploss, threshold |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ER 关系图
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
erDiagram
|
erDiagram
|
||||||
|
%% 本地库
|
||||||
tx_panel ||--o{ test_scenario : "引用"
|
tx_panel ||--o{ test_scenario : "引用"
|
||||||
tx_hardware ||--o{ test_scenario : "引用"
|
tx_hardware ||--o{ test_scenario : "引用"
|
||||||
tx_software ||--o{ test_scenario : "引用"
|
tx_software ||--o{ test_scenario : "引用"
|
||||||
rx_type ||--o{ test_scenario : "引用"
|
rx_type ||--o{ test_scenario : "引用"
|
||||||
|
|
||||||
test_scenario ||--o{ qfod_record : "包含"
|
test_scenario ||--o{ qfod_record : "包含"
|
||||||
test_scenario ||--o{ ploss_record : "包含"
|
test_scenario ||--o{ ploss_record : "包含"
|
||||||
|
|
||||||
tx_panel {
|
%% 发布库(独立)
|
||||||
uuid id PK "主键"
|
charging_parameter {
|
||||||
varchar name UK "面板类型"
|
uuid id PK
|
||||||
}
|
varchar car_factory UK
|
||||||
|
varchar car_model UK
|
||||||
tx_hardware {
|
varchar phone_brand UK
|
||||||
uuid id PK "主键"
|
varchar phone_model UK
|
||||||
varchar version UK "硬件版本"
|
double power_350mw
|
||||||
}
|
double power_500mw
|
||||||
|
double power_750mw
|
||||||
tx_software {
|
double power_1000mw
|
||||||
uuid id PK "主键"
|
double power_1250mw
|
||||||
varchar version UK "软件版本"
|
double power_1500mw
|
||||||
}
|
double power_1750mw
|
||||||
|
double power_2000mw
|
||||||
rx_type {
|
double power_2250mw
|
||||||
uuid id PK "主键"
|
double q_value
|
||||||
varchar name UK "RX类型"
|
double q_base_value
|
||||||
}
|
double pq_coefficient
|
||||||
|
double resonance_frequency
|
||||||
test_scenario {
|
|
||||||
uuid id PK "主键"
|
|
||||||
uuid tx_panel_id FK "面板外键"
|
|
||||||
uuid tx_hardware_id FK "硬件外键"
|
|
||||||
uuid tx_software_id FK "软件外键"
|
|
||||||
uuid rx_type_id FK "RX外键"
|
|
||||||
varchar test_purpose "测试目的"
|
|
||||||
date test_date "测试日期"
|
|
||||||
int test_sequence "测试序号"
|
|
||||||
int qfod_count "Qfod记录数"
|
|
||||||
int ploss_count "Ploss记录数"
|
|
||||||
}
|
|
||||||
|
|
||||||
qfod_record {
|
|
||||||
uuid id PK "主键"
|
|
||||||
uuid scenario_id FK "场景外键"
|
|
||||||
tinyint charger_index "充电器索引"
|
|
||||||
tinyint coil_index "线圈索引"
|
|
||||||
int delta_q "Q值变化量"
|
|
||||||
float current_q "当前Q值"
|
|
||||||
float raw_q "原始Q值"
|
|
||||||
tinyint fod_type "异物类型"
|
|
||||||
}
|
|
||||||
|
|
||||||
ploss_record {
|
|
||||||
uuid id PK "主键"
|
|
||||||
uuid scenario_id FK "场景外键"
|
|
||||||
tinyint rx_type "接收端类型"
|
|
||||||
smallint rx_power "接收端功率"
|
|
||||||
smallint tx_power "发射端功率"
|
|
||||||
int ploss "计算损耗值"
|
|
||||||
int threshold "阈值"
|
|
||||||
smallint fod_result "FOD判定结果"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
框外是本地库,框内是发布库——两库之间没有外键或导航属性。
|
||||||
|
|
||||||
## 设计说明
|
## 设计说明
|
||||||
|
|
||||||
### 星型模型架构
|
### 本地库:星型模型
|
||||||
|
|
||||||
本设计采用星型模型(Star Schema):
|
- 维度表:TxPanel, TxHardware, TxSoftware, RxType
|
||||||
|
- 事实表:TestScenario, QfodRecord, PlossRecord
|
||||||
|
- 所有表支持软删除(is_deleted + query filter)
|
||||||
|
|
||||||
- **维度表**:存储可能重复的属性值(面板、硬件版本、软件版本、RX类型)
|
### 发布库:独立扁平表
|
||||||
- **事实表**:存储外键组合和度量数据
|
|
||||||
|
|
||||||
**优势**:
|
- 一个业务多维表,无外键依赖
|
||||||
- 维度值只存一份,消除冗余
|
- 唯一键:(car_factory, car_model, phone_brand, phone_model)
|
||||||
- 维度可复用,相同硬件版本在不同场景中引用同一记录
|
- 无 is_deleted、无软删除、无审计字段
|
||||||
- 查询可按维度灵活筛选和分组
|
- 更新时间 updated_at 记录发布记录的最后变更时间
|
||||||
|
|
||||||
### test_purpose 处理
|
### 隔离原则
|
||||||
|
|
||||||
`test_purpose` 直接存储在场景表中,不拆分维度表,原因:
|
- 代码中两个实体的命名空间不同(Domain.Models)
|
||||||
- 测试目的描述较自由,不适合标准化为维度
|
- 两个 DbContext 不共享 DbSet
|
||||||
- 便于用户灵活输入
|
- 发布库不从本地库继承实体配置
|
||||||
|
- aggregate 通过纯值映射,不依赖导航属性
|
||||||
### 场景唯一性
|
|
||||||
|
|
||||||
场景表设置唯一约束,相同属性组合不重复创建:
|
|
||||||
|
|
||||||
```
|
|
||||||
UNIQUE(tx_panel_id, tx_hardware_id, tx_software_id, rx_type_id, test_purpose, test_date, test_sequence)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 软删除设计
|
|
||||||
|
|
||||||
所有表均包含 `is_deleted` 字段,支持软删除:
|
|
||||||
|
|
||||||
| 字段名 | 类型 | 约束 | 说明 |
|
|
||||||
|--------|------|------|------|
|
|
||||||
| is_deleted | BOOLEAN | NOT NULL, DEFAULT FALSE | 软删除标记 |
|
|
||||||
|
|
||||||
**删除行为**:
|
|
||||||
- 删除操作设置 `is_deleted = true`,不物理删除数据
|
|
||||||
- 查询时默认过滤已删除记录(`WHERE is_deleted = false`)
|
|
||||||
- 支持数据恢复和审计追溯
|
|
||||||
|
|
||||||
**查询示例**:
|
|
||||||
```csharp
|
|
||||||
// 默认查询(不含已删除)
|
|
||||||
var activeRecords = context.TxPanels
|
|
||||||
.Where(t => !t.IsDeleted)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// 包含已删除记录
|
|
||||||
var allRecords = context.TxPanels
|
|
||||||
.IgnoreQueryFilters()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// 软删除操作
|
|
||||||
public async Task SoftDeleteAsync(Guid id)
|
|
||||||
{
|
|
||||||
var entity = await context.TxPanels.FindAsync(id);
|
|
||||||
if (entity != null)
|
|
||||||
{
|
|
||||||
entity.IsDeleted = true;
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实体配置方式
|
|
||||||
|
|
||||||
采用 `IEntityTypeConfiguration<T>` Fluent API 配置,实体类保持纯净 POCO:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Data/WctMinerDbContext.cs
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Gpulse.WCT.DataAnalyzer.Models;
|
|
||||||
|
|
||||||
namespace Gpulse.WCT.DataAnalyzer.Data;
|
|
||||||
|
|
||||||
public class WctMinerDbContext : DbContext
|
|
||||||
{
|
|
||||||
public WctMinerDbContext(DbContextOptions<WctMinerDbContext> options)
|
|
||||||
: base(options) { }
|
|
||||||
|
|
||||||
public DbSet<TxPanel> TxPanels => Set<TxPanel>();
|
|
||||||
public DbSet<TxHardware> TxHardwares => Set<TxHardware>();
|
|
||||||
public DbSet<TxSoftware> TxSoftwares => Set<TxSoftware>();
|
|
||||||
public DbSet<RxType> RxTypes => Set<RxType>();
|
|
||||||
public DbSet<TestScenario> TestScenarios => Set<TestScenario>();
|
|
||||||
public DbSet<QfodRecord> QfodRecords => Set<QfodRecord>();
|
|
||||||
public DbSet<PlossRecord> PlossRecords => Set<PlossRecord>();
|
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
base.OnModelCreating(modelBuilder);
|
|
||||||
|
|
||||||
// 自动应用所有 IEntityTypeConfiguration<T> 配置
|
|
||||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(WctMinerDbContext).Assembly);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **数据库兼容性说明**:
|
|
||||||
> - `HasDefaultValueSql("NOW()")` 适用于 PostgreSQL
|
|
||||||
> - 若使用 SQLite,需改为 `HasDefaultValueSql("datetime('now')")`
|
|
||||||
> - 若使用 MySQL,需改为 `HasDefaultValueSql("NOW()")`(MySQL 也支持)
|
|
||||||
> - 若使用 SQL Server,需改为 `HasDefaultValueSql("GETDATE()")`
|
|
||||||
|
|
||||||
**配置类列表**:
|
|
||||||
|
|
||||||
| 配置类 | 文件路径 | 说明 |
|
|
||||||
|--------|----------|------|
|
|
||||||
| TxPanelConfiguration | Data/Configurations/TxPanelConfiguration.cs | TX面板维度 |
|
|
||||||
| TxHardwareConfiguration | Data/Configurations/TxHardwareConfiguration.cs | TX硬件版本维度 |
|
|
||||||
| TxSoftwareConfiguration | Data/Configurations/TxSoftwareConfiguration.cs | TX软件版本维度 |
|
|
||||||
| RxTypeConfiguration | Data/Configurations/RxTypeConfiguration.cs | RX类型维度 |
|
|
||||||
| TestScenarioConfiguration | Data/Configurations/TestScenarioConfiguration.cs | 测试场景 |
|
|
||||||
| QfodRecordConfiguration | Data/Configurations/QfodRecordConfiguration.cs | Qfod记录 |
|
|
||||||
| PlossRecordConfiguration | Data/Configurations/PlossRecordConfiguration.cs | Ploss记录 |
|
|
||||||
|
|
||||||
> **迁移注意事项**:从 DataAnnotations `DateTime.UtcNow` 迁移到 `HasDefaultValueSql("NOW()")` 后,测试代码需注意:未显式设置 `CreatedAt` 时,值由数据库生成而非 C# 运行时。单元测试中需 mock 数据库或显式设置值。
|
|
||||||
|
|
||||||
### 系统日志处理
|
|
||||||
|
|
||||||
解析过程中的错误日志通过 Serilog 记录到系统日志文件,不存入业务数据库。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 数据量估算
|
|
||||||
|
|
||||||
| 表 | 增长方式 | 预估规模 |
|
|
||||||
|------|----------|----------|
|
|
||||||
| tx_panel | 预置数据 | 7 条(固定枚举) |
|
|
||||||
| tx_hardware | 随版本迭代 | 10-50 条/年 |
|
|
||||||
| tx_software | 随版本迭代 | 10-50 条/年 |
|
|
||||||
| rx_type | 随设备增加 | 20-100 条/年 |
|
|
||||||
| test_scenario | 随测试批次 | 3,650 条/年 |
|
|
||||||
| qfod_record | 随测试数据 | 3.6M-18M 条/年 |
|
|
||||||
| ploss_record | 随测试数据 | 3.6M-18M 条/年 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 假设/歧义标注
|
|
||||||
|
|
||||||
| 标注 | 内容 |
|
|
||||||
|------|------|
|
|
||||||
| [A] | 使用 UUID 作为主键 |
|
|
||||||
| [?] | FOD Type 具体编码含义需确认 |
|
|
||||||
| [?] | Ploss 日志格式与实际日志对应关系需确认 |
|
|
||||||
| [!] | ploss 负数表示安全,正数可能触发 FOD |
|
|
||||||
Reference in New Issue
Block a user