docs: 更新项目架构与数据模型文档

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scottxjw
2026-08-13 15:01:32 +08:00
parent a3100c73c5
commit 2b031995e1
9 changed files with 678 additions and 885 deletions

View File

@@ -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);
```

View File

@@ -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; public static IServiceCollection AddDatabaseServices(
using Gpulse.WCT.DataAnalyzer.Data; this IServiceCollection services,
IConfiguration configuration)
namespace Gpulse.WCT.DataAnalyzer.Extensions;
public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddDatabaseServices( // 本地库
this IServiceCollection services, services.AddScoped<IDbContextFactory, DbContextFactory>();
IConfiguration configuration) services.AddScoped<WctMinerDbContext>(sp =>
{ {
// 注册 DbContext 工厂 var factory = sp.GetRequiredService<IDbContextFactory>();
services.AddScoped<IDbContextFactory, DbContextFactory>(); return factory.CreateDbContext();
});
// 注册 DbContext通过工厂创建
services.AddScoped<WctMinerDbContext>(sp =>
{
var factory = sp.GetRequiredService<IDbContextFactory>();
return factory.CreateDbContext();
});
return services; // 发布库(独立)
} services.AddScoped<IReleaseDbContextFactory, ReleaseDbContextFactory>();
services.AddScoped<ReleaseDbContext>(sp =>
{
var factory = sp.GetRequiredService<IReleaseDbContextFactory>();
return factory.CreateDbContext();
});
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);
``` ```

View File

@@ -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 | ✅ 发布库 |
| 双库隔离 | ✅ 无外键/导航属性 |

View File

@@ -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
```
两个数据库独立迁移,不共享迁移历史。

View File

@@ -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: 扫描日志目录 CLI->>PS: ParseFileAsync(filePath)
PS-->>CLI: 返回文件列表 PS->>FNP: 解析文件名
FNP-->>PS: ScenarioInfo
PS->>SS: GetOrCreateScenarioAsync
SS->>DB: 维度 upsert + 场景 upsert
SS-->>PS: TestScenario
PS->>PS: 逐行读取文件
loop 每个日志文件 loop 每
CLI->>PS: ParseFileAsync(filePath) alt Qfod 行
PS->>QP: Parse(line)
PS->>FNP: 解析文件名 QP-->>PS: QfodRecord
FNP-->>PS: 场景信息面板、版本、RX类型等 else Ploss 行
PS->>PP: Parse(line)
PS->>SS: GetOrCreateScenarioAsync(场景信息) PP-->>PS: PlossRecord
SS->>DB: 查询/创建维度记录
SS->>DB: 查询/创建场景记录
SS-->>PS: 返回 TestScenario
PS->>PS: 读取文件内容
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
PS->>DB: BATCH INSERT QfodRecords
PS->>DB: BATCH INSERT PlossRecords
PS->>DB: UPDATE scenario (qfod_count, ploss_count)
PS-->>CLI: 返回 ParseReport
end end
CLI-->>CLI: 输出解析报告 PS->>DB: BatchInsert QfodRecords
PS->>DB: BatchInsert PlossRecords
PS->>DB: UpdateScenarioStats
PS-->>CLI: ParseReport
```
**关键点:**
- 文件名格式: `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 拆分 | 取第一个分隔符后部分 |
| 350mW2250mW9列 | 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 (排序)
ES->>FS: 写入 WCT-ChargingParameterDatabase.csv
ES-->>CLI: 文件路径
```
public class ParseService **导出规格:**
{
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( - 固定文件名: `WCT-ChargingParameterDatabase.csv`
WctMinerDbContext context, - 固定 17 列 UTF-8 BOM
ScenarioService scenarioService, - RFC 4180 转义
QfodParser qfodParser, - 数字使用 invariant culture
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);
// 解析文件名获取场景信息 ## 5. 数据库隔离
var scenarioInfo = _fileNameParser.Parse(fileInfo.Name);
if (scenarioInfo == null)
{
Log.Error("无法解析文件名: {FileName}", fileInfo.Name);
return new ParseReport(fileInfo.Name, 0, 0, 1, "文件名格式不匹配");
}
// 获取或创建测试场景 ```text
var scenario = await _scenarioService.GetOrCreateScenarioAsync(scenarioInfo); local.db release.db
───────── ──────────
tx_panel charging_parameter
tx_hardware (独立实体,无外键)
tx_software
rx_type
test_scenario
qfod_record
ploss_record
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++) - 代码中不存在 local entity 到 release entity 的导航属性
{ - 数据库中没有跨库外键
var line = lines[i]; - export 不读 local.dbaggregate 不写 local.db
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);
```

View File

@@ -1,68 +1,149 @@
# Architecture: Gpulse.WCT.DataAnalyzer # Architecture: Gpulse.WCT.DataAnalyzer
> 无线充电 FOD异物检测日志数据采集系统从日志文件解析 QfodPloss 数据并存储到数据库 > 无线充电参数采集系统。从 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 │
│ (Program.cs / System.CommandLine) │
├──────────────────────────────────────────────────────────────────────┤
│ Application Layer │
│ ┌───────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ LocalIngestion│ │ Publishing │ │ Exporting │ │Administration│ │
│ │ ParseService │ │AggregationSvc│ │ExportSvc │ │ StatsService │ │
│ │ ScenarioSvc │ │ThresholdCalc │ │ │ │ CleanService │ │
│ │ DimensionSvc │ │ │ │ │ │ │ │
│ └───────────────┘ └──────────────┘ └────────────┘ └──────────────┘ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Parsing │ │
│ │ QfodParser │ PlossParser │ FileNameParser │ │
│ └──────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ ┌─────────────────────────┐ ┌──────────────────────────────────────┐ │
│ │ Local/ (本地实体) │ │ Release/ (发布实体) │ │
│ │ TxPanel, TxHardware │ │ ChargingParameterRecord │ │
│ │ TxSoftware, RxType │ │ │ │
│ │ TestScenario │ │ │ │
│ │ QfodRecord, PlossRecord │ │ │ │
│ └─────────────────────────┘ └──────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ ┌───────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ LocalData │ │ ReleaseData │ │Configuration│ │ Security │ │
│ │ WctMinerDbCtx │ │ReleaseDbCtx │ │AppSettings │ │PathValidator │ │
│ │ DbCtxFactory │ │ReleaseDbCtxF │ │ │ │ │ │
│ │ SeedData │ │ │ │ │ │ │ │
│ └───────────────┘ └──────────────┘ └────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
``` ```
┌─────────────────────────────────────────────────────────────┐
│ CLI Entry Point │
│ (Program.cs / System.CommandLine) │
├─────────────────────────────────────────────────────────────┤
│ Service Layer │
├─────────────────────────────────────────────────────────────┤
│ ParseService │ ErrorHandlingService │ ThresholdCalc │
├─────────────────────────────────────────────────────────────┤
│ Parser Layer │
├─────────────────────────────────────────────────────────────┤
│ QfodParser │ PlossParser │ LogFileReader │
├─────────────────────────────────────────────────────────────┤
│ Data Layer │
├─────────────────────────────────────────────────────────────┤
│ Models │ WctMinerDbContext │ IDbContextFactory │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure │
├─────────────────────────────────────────────────────────────┤
│ Configuration │ Serilog │ AppSettings │
└─────────────────────────────────────────────────────────────┘
```
---
## 核心设计原则 ## 核心设计原则
| 原则 | 说明 | | 原则 | 说明 |
|------|------| |------|------|
| 技术栈 | .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
```

View File

@@ -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
ReleaseData
Configuration
Security
Extensions
end end
subgraph Service Layer
PS
SS
DS
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
``` ```

View File

@@ -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,8 +51,9 @@ 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; }
public int Port { get; set; } = 5432; public int Port { get; set; } = 5432;
@@ -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",
"Password": "...",
"ReleaseHost": "db-server",
"ReleaseName": "wct_charging_parameters",
"ReleaseUser": "wct_release",
"ReleasePassword": "..."
}
}
```
builder.ConfigureServices((context, services) => Release 数据库的 PostgreSQL 配置前缀为 `Release*`,未设置时回退到 local 的对应配置。
{
services.Configure<AppSettings>(context.Configuration.GetSection("")); ---
// 注册数据库服务 ## 4. 聚合配置
services.AddDatabaseServices(context.Configuration);
| 配置键 | 说明 |
// 注册其他服务 |--------|------|
services.AddScoped<QfodParser>(); | `Aggregation:TxPanelMappings` | TxPanel 名称 → 车厂名称的映射表 |
services.AddScoped<PlossParser>(); | `Aggregation:CarModelMappings` | TxHardware 版本 → 车型名称的映射表 |
services.AddScoped<ParseService>();
services.AddScoped<ErrorHandlingService>(); 未配置映射时,直接使用 TxPanel.Name 和 TxHardware.Version 作为最终值。手机品牌/型号从 RxType.Name 自动拆分(分隔符 `/_:`)。
});
```

View File

@@ -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 |