docs: 更新项目架构与数据模型文档
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,18 +6,19 @@
|
||||
|
||||
## 1. 命令概览
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `parse` | 解析日志文件并存储到数据库 |
|
||||
| `stats` | 查询统计信息 |
|
||||
| `export` | 导出数据 |
|
||||
| `clean` | 清理数据 |
|
||||
| 命令 | 说明 | 数据流 |
|
||||
|------|------|--------|
|
||||
| `parse` | 解析日志文件到本地库 | log → local.db |
|
||||
| `aggregate` | 从本地库汇总生成发布库 | local.db → release.db |
|
||||
| `export` | 导出发布库到 CSV | release.db → CSV |
|
||||
| `stats` | 查询本地库统计信息 | 只读 local.db |
|
||||
| `clean` | 清理本地库数据(软删除) | 只写 local.db |
|
||||
|
||||
---
|
||||
|
||||
## 2. 命令详解
|
||||
|
||||
### 2.1 parse - 解析日志文件
|
||||
### 2.1 parse
|
||||
|
||||
```bash
|
||||
# 解析单个文件
|
||||
@@ -29,41 +30,60 @@ dotnet run parse --dir path/to/logs/
|
||||
# 解析目录(递归)
|
||||
dotnet run parse --dir path/to/logs/ --recursive
|
||||
|
||||
# 强制重新解析(覆盖已有数据)
|
||||
# 强制重新解析
|
||||
dotnet run parse --file log.txt --force
|
||||
```
|
||||
|
||||
### 2.2 stats - 查询统计
|
||||
### 2.2 aggregate
|
||||
|
||||
```bash
|
||||
# 查看文件解析统计
|
||||
dotnet run stats --file log.txt
|
||||
# 从 local.db 汇总生成 release.db(默认重建)
|
||||
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
|
||||
|
||||
# 按 FOD 类型统计
|
||||
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
|
||||
# 导出为 CSV
|
||||
dotnet run export --format csv --output ./output/
|
||||
|
||||
# 导出为 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 --scenario <guid> --confirm
|
||||
|
||||
# 清理所有数据
|
||||
dotnet run clean --all --confirm
|
||||
@@ -71,98 +91,20 @@ dotnet run clean --all --confirm
|
||||
|
||||
---
|
||||
|
||||
## 3. 命令实现
|
||||
## 3. 典型工作流
|
||||
|
||||
```csharp
|
||||
// Commands/ParseCommand.cs
|
||||
using System.CommandLine;
|
||||
```bash
|
||||
# 1. 解析日志
|
||||
dotnet run parse --dir ./data/test_input --recursive
|
||||
|
||||
namespace Gpulse.WCT.DataAnalyzer.Commands;
|
||||
# 2. 查看统计
|
||||
dotnet run stats --summary
|
||||
|
||||
public class ParseCommand : Command
|
||||
{
|
||||
public ParseCommand(ParseService parseService)
|
||||
: base("parse", "Parse log files and store data to database")
|
||||
{
|
||||
var fileOption = new Option<string>(
|
||||
"--file",
|
||||
"Path to a single log file to parse"
|
||||
);
|
||||
# 3. 汇总生成发布库
|
||||
dotnet run aggregate
|
||||
|
||||
var dirOption = new Option<string>(
|
||||
"--dir",
|
||||
"Path to directory containing log files"
|
||||
);
|
||||
|
||||
var recursiveOption = new Option<bool>(
|
||||
"--recursive",
|
||||
() => false,
|
||||
"Recursively search subdirectories"
|
||||
);
|
||||
|
||||
var forceOption = new Option<bool>(
|
||||
"--force",
|
||||
() => false,
|
||||
"Force re-parse even if file already parsed"
|
||||
);
|
||||
|
||||
AddOption(fileOption);
|
||||
AddOption(dirOption);
|
||||
AddOption(recursiveOption);
|
||||
AddOption(forceOption);
|
||||
|
||||
this.SetHandler(async (file, dir, recursive, force) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(file))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
# 4. 导出正式 CSV
|
||||
dotnet run export --output ./data/output
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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);
|
||||
```
|
||||
输出文件: `./data/output/WCT-ChargingParameterDatabase.csv`
|
||||
@@ -1,16 +1,14 @@
|
||||
# DbContext 工厂
|
||||
|
||||
> 所属模块:[数据库切换支持](./概览.md)
|
||||
> 所属模块:[数据库支持](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口定义
|
||||
## 1. 本地库工厂
|
||||
|
||||
```csharp
|
||||
// Data/IDbContextFactory.cs
|
||||
using Gpulse.WCT.DataAnalyzer.Data;
|
||||
|
||||
namespace Gpulse.WCT.DataAnalyzer.Data;
|
||||
// Infrastructure/LocalData/IDbContextFactory.cs
|
||||
namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
|
||||
|
||||
public interface IDbContextFactory
|
||||
{
|
||||
@@ -18,105 +16,68 @@ public interface IDbContextFactory
|
||||
}
|
||||
```
|
||||
|
||||
工厂实现读取 `Database:LocalPath`(fallback `Database:Path`),支持 SQLite 和 PostgreSQL。
|
||||
|
||||
---
|
||||
|
||||
## 2. 工厂实现
|
||||
## 2. 发布库工厂
|
||||
|
||||
```csharp
|
||||
// Data/DbContextFactory.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
// Infrastructure/ReleaseData/IReleaseDbContextFactory.cs
|
||||
namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.ReleaseData;
|
||||
|
||||
namespace Gpulse.WCT.DataAnalyzer.Data;
|
||||
|
||||
public class DbContextFactory : IDbContextFactory
|
||||
public interface IReleaseDbContextFactory
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
ReleaseDbContext CreateDbContext();
|
||||
}
|
||||
```
|
||||
|
||||
工厂实现读取 `Database:ReleasePath`,支持 SQLite 和 PostgreSQL。PostgreSQL 时使用 `Release*` 前缀配置(`ReleaseHost`, `ReleasePort`, `ReleaseName`, `ReleaseUser`, `ReleasePassword`),未设置时回退到 local 的对应配置。
|
||||
|
||||
---
|
||||
|
||||
## 3. 依赖注入扩展
|
||||
## 3. DI 注册
|
||||
|
||||
```csharp
|
||||
// Extensions/ServiceCollectionExtensions.cs
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Gpulse.WCT.DataAnalyzer.Data;
|
||||
|
||||
namespace Gpulse.WCT.DataAnalyzer.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
// Infrastructure/Extensions/ServiceCollectionExtensions.cs
|
||||
public static IServiceCollection AddDatabaseServices(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
// 注册 DbContext 工厂
|
||||
// 本地库
|
||||
services.AddScoped<IDbContextFactory, DbContextFactory>();
|
||||
|
||||
// 注册 DbContext(通过工厂创建)
|
||||
services.AddScoped<WctMinerDbContext>(sp =>
|
||||
{
|
||||
var factory = sp.GetRequiredService<IDbContextFactory>();
|
||||
return factory.CreateDbContext();
|
||||
});
|
||||
|
||||
// 发布库(独立)
|
||||
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);
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
# 数据库切换支持
|
||||
# 数据库切换与双数据库架构
|
||||
|
||||
> 所属模块:[架构概览](../概览.md)
|
||||
|
||||
@@ -8,38 +8,48 @@
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [DbContext 工厂](./DbContext工厂.md) | 数据库上下文工厂设计 |
|
||||
| [迁移策略](./迁移策略.md) | 数据库迁移和切换步骤 |
|
||||
| [DbContext 工厂](./DbContext工厂.md) | 两个数据库上下文工厂设计 |
|
||||
| [迁移策略](./迁移策略.md) | 数据库迁移步骤 |
|
||||
|
||||
---
|
||||
|
||||
## 切换策略概览
|
||||
## 双数据库架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ appsettings.json │
|
||||
│ "Database": { "Type": "sqlite" | "postgresql" } │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ Database:LocalPath → local.db │
|
||||
│ Database:ReleasePath → release.db │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DbContextFactory │
|
||||
│ 读取配置 → 选择 Provider → 创建 DbContext │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ 读取配置 → 选择 Provider → 创建 WctMinerDbContext │
|
||||
│ ReleaseDbContextFactory │
|
||||
│ 读取配置 → 选择 Provider → 创建 ReleaseDbContext │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WctMinerDbContext │
|
||||
│ 统一的 DbModel 配置,适配两种数据库 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ local.db release.db │
|
||||
│ ──────── ────────── │
|
||||
│ tx_panel charging_parameter │
|
||||
│ tx_hardware 独立实体,无外键关联 │
|
||||
│ tx_software │
|
||||
│ rx_type │
|
||||
│ test_scenario │
|
||||
│ qfod_record │
|
||||
│ ploss_record │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 当前状态
|
||||
|
||||
| 项目 | 状态 |
|
||||
|------|------|
|
||||
| SQLite | ✅ 默认启用 |
|
||||
| PostgreSQL | ✅ 架构支持,待配置 |
|
||||
| 切换方式 | 配置文件切换 |
|
||||
| PostgreSQL | ✅ 架构支持 |
|
||||
| local.db | ✅ 解析库 |
|
||||
| release.db | ✅ 发布库 |
|
||||
| 双库隔离 | ✅ 无外键/导航属性 |
|
||||
@@ -1,16 +1,17 @@
|
||||
# 数据库迁移策略
|
||||
|
||||
> 所属模块:[数据库切换支持](./概览.md)
|
||||
> 所属模块:[数据库支持](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 迁移命令
|
||||
## 1. 本地库迁移
|
||||
|
||||
### SQLite
|
||||
|
||||
```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
|
||||
@@ -20,42 +21,43 @@ dotnet ef database update
|
||||
|
||||
```bash
|
||||
# 切换配置后添加迁移
|
||||
dotnet ef migrations add InitialCreate --output-dir Migrations/PostgreSQL -- --environment PostgreSQL
|
||||
|
||||
# 应用迁移
|
||||
dotnet ef database update -- --environment PostgreSQL
|
||||
dotnet ef migrations add InitialCreate --output-dir Migrations/PostgreSQL \
|
||||
--context WctMinerDbContext --project src/Gpulse.WCT.DataAnalyzer.Core \
|
||||
-- --environment Production
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 切换步骤
|
||||
## 2. 发布库迁移
|
||||
|
||||
### 从 SQLite 切换到 PostgreSQL
|
||||
发布库的迁移需要使用 `ReleaseDbContext`:
|
||||
|
||||
| 步骤 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| 1 | 修改配置 | `appsettings.json` 中 `Database:Type` 改为 `postgresql` |
|
||||
| 2 | 设置连接 | 配置 `Host`, `Port`, `Name`, `User`, `Password` |
|
||||
| 3 | 创建数据库 | `CREATE DATABASE wctminer;` |
|
||||
| 4 | 应用迁移 | `dotnet ef database update` |
|
||||
| 5 | 数据迁移 | 使用工具迁移 SQLite 数据到 PostgreSQL |
|
||||
```bash
|
||||
# 添加迁移
|
||||
dotnet ef migrations add InitialCreate --output-dir Migrations/Release \
|
||||
--context ReleaseDbContext --project src/Gpulse.WCT.DataAnalyzer.Core
|
||||
|
||||
# 应用迁移
|
||||
dotnet ef database update --context ReleaseDbContext
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 配置文件示例
|
||||
|
||||
### SQLite (默认)
|
||||
### SQLite(默认双库)
|
||||
|
||||
```json
|
||||
{
|
||||
"Database": {
|
||||
"Type": "sqlite",
|
||||
"Path": "./data/database/wctminer.db"
|
||||
"LocalPath": "./data/database/local.db",
|
||||
"ReleasePath": "./data/database/release.db"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
### PostgreSQL(双库独立)
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -63,9 +65,8 @@ dotnet ef database update -- --environment PostgreSQL
|
||||
"Type": "postgresql",
|
||||
"Host": "localhost",
|
||||
"Port": 5432,
|
||||
"Name": "wctminer",
|
||||
"User": "wctminer_user",
|
||||
"Password": "${DB_PASSWORD}"
|
||||
"LocalPath": "Server=localhost;Database=wct_local",
|
||||
"ReleasePath": "Server=db-server;Database=wct_charging_parameters"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -77,5 +78,11 @@ dotnet ef database update -- --environment PostgreSQL
|
||||
推荐使用 [pgloader](https://github.com/dimitri/pgloader) 迁移 SQLite 到 PostgreSQL:
|
||||
|
||||
```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
|
||||
sequenceDiagram
|
||||
participant CLI as CLI Entry
|
||||
participant CLI as CLI
|
||||
participant PS as ParseService
|
||||
participant SS as ScenarioService
|
||||
participant FNP as FileNameParser
|
||||
participant QP as QfodParser
|
||||
participant PP as PlossParser
|
||||
participant DB as Database
|
||||
participant Log as Serilog
|
||||
participant DB as local.db
|
||||
|
||||
CLI->>PS: 扫描日志目录
|
||||
PS-->>CLI: 返回文件列表
|
||||
|
||||
loop 每个日志文件
|
||||
CLI->>PS: ParseFileAsync(filePath)
|
||||
|
||||
PS->>FNP: 解析文件名
|
||||
FNP-->>PS: 场景信息(面板、版本、RX类型等)
|
||||
FNP-->>PS: ScenarioInfo
|
||||
PS->>SS: GetOrCreateScenarioAsync
|
||||
SS->>DB: 维度 upsert + 场景 upsert
|
||||
SS-->>PS: TestScenario
|
||||
PS->>PS: 逐行读取文件
|
||||
|
||||
PS->>SS: GetOrCreateScenarioAsync(场景信息)
|
||||
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)
|
||||
loop 每行
|
||||
alt Qfod 行
|
||||
PS->>QP: Parse(line)
|
||||
QP-->>PS: QfodRecord
|
||||
else Ploss 行
|
||||
PS->>PP: Parse(line)
|
||||
PP-->>PS: PlossRecord
|
||||
end
|
||||
end
|
||||
|
||||
PS->>DB: BATCH INSERT QfodRecords
|
||||
PS->>DB: BATCH INSERT PlossRecords
|
||||
PS->>DB: UPDATE scenario (qfod_count, ploss_count)
|
||||
PS->>DB: BatchInsert QfodRecords
|
||||
PS->>DB: BatchInsert PlossRecords
|
||||
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
|
||||
// Services/ParseService.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Serilog;
|
||||
using Gpulse.WCT.DataAnalyzer.Data;
|
||||
using Gpulse.WCT.DataAnalyzer.Models;
|
||||
using Gpulse.WCT.DataAnalyzer.Parsers;
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CLI as CLI
|
||||
participant ES as ExportService
|
||||
participant RDB as release.db
|
||||
participant FS as Filesystem
|
||||
|
||||
namespace Gpulse.WCT.DataAnalyzer.Services;
|
||||
|
||||
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(
|
||||
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);
|
||||
CLI->>ES: ExportChargingParametersToCsvAsync(outputDir)
|
||||
ES->>RDB: 读取 ChargingParameters (排序)
|
||||
ES->>FS: 写入 WCT-ChargingParameterDatabase.csv
|
||||
ES-->>CLI: 文件路径
|
||||
```
|
||||
|
||||
**导出规格:**
|
||||
|
||||
- 固定文件名: `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
|
||||
|
||||
> 无线充电 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) | 认证方案、数据安全 |
|
||||
| [解析器设计](./parser/概览.md) | Qfod/Ploss 解析器详细设计 |
|
||||
| [数据处理流程](./数据处理流程.md) | 解析流程、批量写入、错误处理 |
|
||||
| [配置管理](./配置管理.md) | 配置文件、配置类定义 |
|
||||
| [CLI 命令设计](./CLI命令设计.md) | 命令行接口定义 |
|
||||
| [数据库切换支持](./database/概览.md) | SQLite/PostgreSQL 切换方案 |
|
||||
| [技术选型](./技术选型.md) | 技术栈、决策记录 |
|
||||
| [模块划分](./模块划分.md) | 分层架构、目录结构、模块职责 |
|
||||
| [数据处理流程](./数据处理流程.md) | 解析→汇总→导出完整流程 |
|
||||
| [CLI 命令设计](./CLI命令设计.md) | 命令行接口 |
|
||||
| [配置管理](./配置管理.md) | 配置结构与双数据库配置 |
|
||||
| [数据库支持](./database/概览.md) | 本地/发布双数据库架构 |
|
||||
| [安全架构](./安全架构.md) | 路径校验、安全策略 |
|
||||
| [解析器设计](./parser/概览.md) | Qfod/Ploss 解析器 |
|
||||
|
||||
---
|
||||
## 架构分层
|
||||
|
||||
## 架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ 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 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
├──────────────────────────────────────────────────────────────────────┤
|
||||
│ 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 │ │ │ │ │ │ │ │
|
||||
│ └───────────────┘ └──────────────┘ └────────────┘ └──────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心设计原则
|
||||
|
||||
| 原则 | 说明 |
|
||||
|------|------|
|
||||
| 技术栈 | .NET 8.0 LTS + C# 12.0 + EF Core 8.0 |
|
||||
| 数据库 | SQLite(当前)/ PostgreSQL(后续),配置切换 |
|
||||
| 解析模式 | 批处理,正则表达式匹配 |
|
||||
| ORM 模式 | Code First,支持迁移自动化 |
|
||||
| CLI 框架 | System.CommandLine,命令模式 |
|
||||
| 技术栈 | .NET 8.0 + C# 12.0 + EF Core 8.0 |
|
||||
| 数据库 | local.db / release.db 两个独立 SQLite 数据库 |
|
||||
| 本地/发布隔离 | 无外键、无导航属性、无数据库级关联 |
|
||||
| 解析模式 | 批处理,正则表达式 |
|
||||
| CLI | System.CommandLine,命令模式 |
|
||||
| 汇总规则 | 按业务键分组取平均值,缺失字段允许为空 |
|
||||
|
||||
---
|
||||
## 目录结构
|
||||
|
||||
## 假设/歧义标注
|
||||
|
||||
| 标注 | 内容 |
|
||||
|------|------|
|
||||
| [A] | 使用 .NET 8.0 LTS 版本 |
|
||||
| [A] | 初期使用 SQLite,架构支持切换 PostgreSQL |
|
||||
| [?] | 增量解析策略需确认 |
|
||||
| [?] | 多次解析同一文件的处理策略需确认 |
|
||||
```text
|
||||
src/
|
||||
├── Gpulse.WCT.DataAnalyzer/ # CLI 入口
|
||||
│ ├── Commands/ # 命令实现
|
||||
│ │ ├── ParseCommand.cs
|
||||
│ │ ├── 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 │
|
||||
│ (Program.cs / System.CommandLine) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Service Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ParseService │ ScenarioService │ DimensionService │ ThresholdCalc │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Parser Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ QfodParser │ PlossParser │ FileNameParser │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Data Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Models │ WctMinerDbContext │ IDbContextFactory │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Infrastructure │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Configuration │ Serilog │ AppSettings │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
| 层级 | 目录 | 职责 | 依赖 |
|
||||
|------|------|------|------|
|
||||
| CLI | `Gpulse.WCT.DataAnalyzer/Commands/` | 接收参数、流程调度、结果输出 | Application |
|
||||
| Application | `Core/Application/` | 业务流程编排 | Domain, Infrastructure |
|
||||
| Domain | `Core/Domain/Models/` | 实体定义,无行为 | 无 |
|
||||
| Infrastructure | `Core/Infrastructure/` | 数据持久化、配置、安全 | Domain |
|
||||
|
||||
**依赖规则:**
|
||||
|
||||
- 上层可依赖下层,下层不可依赖上层
|
||||
- Application 依 Domain 和 Infrastructure
|
||||
- Infrastructure 依 Domain
|
||||
- Domain 不依赖任何其他层
|
||||
- 解析器之间不可互相调用
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块职责
|
||||
## 2. Application 模块职责
|
||||
|
||||
| 模块名 | 职责 | 依赖模块 |
|
||||
|--------|------|----------|
|
||||
| CLI Entry | 接收命令行参数、调度解析流程 | ParseService, Infrastructure |
|
||||
| ParseService | 协调解析流程、批量写入、场景关联 | Parser Layer, ScenarioService, DbContext |
|
||||
| ScenarioService | 创建/查询测试场景,解析文件名获取场景信息 | DimensionService, DbContext |
|
||||
| DimensionService | 维度表 CRUD(获取或创建维度记录) | DbContext |
|
||||
| FileNameParser | 解析文件名提取场景信息(面板、版本、RX类型等) | 无 |
|
||||
| LogFileReader | 读取日志文件、行过滤、文件遍历 | 无 |
|
||||
| QfodParser | 解析 Qfod 格式日志,返回 QfodRecord | 无 |
|
||||
| PlossParser | 解析 Ploss 格式日志,返回 PlossRecord | 无 |
|
||||
| Models | 定义数据实体结构(维度表 + 事实表) | 无 |
|
||||
| WctMinerDbContext | EF Core 数据库上下文 | Models, Configurations |
|
||||
| Configuration | 加载配置文件 | 无 |
|
||||
| Serilog | 结构化日志输出(含解析错误日志) | 无 |
|
||||
| ThresholdCalculator | 根据 RxPower 计算动态阈值 | 无 |
|
||||
### 2.1 Parsing — 解析
|
||||
|
||||
| 类 | 职责 |
|
||||
|----|------|
|
||||
| `FileNameParser` | 从文件名提取场景信息(面板、硬件、软件、RX类型、日期、序号) |
|
||||
| `QfodParser` | 解析 Qfod 格式日志行 → `QfodRecord` |
|
||||
| `PlossParser` | 解析 Ploss 单行/两行格式日志 → `PlossRecord` |
|
||||
|
||||
### 2.2 LocalIngestion — 本地解析
|
||||
|
||||
| 类 | 职责 |
|
||||
|----|------|
|
||||
| `ParseService` | 协调解析流程:读取文件 → 解析 → 批量写入 local.db |
|
||||
| `ScenarioService` | 创建/查询测试场景,并发安全 upsert |
|
||||
| `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 |
|
||||
| Service | 协调解析流程、场景管理、维度管理、批量写入 | Parser, Data, Infrastructure |
|
||||
| Parser | 日志解析、文件名解析、数据提取 | 无 |
|
||||
| Data | 数据持久化、DbContext 管理、实体配置 | Infrastructure |
|
||||
| Infrastructure | 配置、日志 | 无 |
|
||||
| 实体 | 表 | 说明 |
|
||||
|------|----|------|
|
||||
| `TxPanel` | tx_panel | TX 面板类型维度 |
|
||||
| `TxHardware` | tx_hardware | TX 硬件版本维度 |
|
||||
| `TxSoftware` | tx_software | TX 软件版本维度 |
|
||||
| `RxType` | rx_type | RX 类型维度 |
|
||||
| `TestScenario` | test_scenario | 测试场景(4 个维度外键 + 日期 + 序号) |
|
||||
| `QfodRecord` | qfod_record | Qfod 检测数据 |
|
||||
| `PlossRecord` | ploss_record | Ploss FOD 数据 |
|
||||
|
||||
### 3.2 依赖规则
|
||||
关系:维度表 1:N TestScenario 1:N QfodRecord/PlossRecord(星型模型)
|
||||
|
||||
- 上层可调用下层
|
||||
- 下层不可调用上层
|
||||
- Parser 之间不可互相调用(独立解析器)
|
||||
- Service 层协调 Parser 和 Data 层
|
||||
- 解析错误通过 Serilog 记录,不存入业务数据库
|
||||
### 3.2 发布实体(release.db)
|
||||
|
||||
| 实体 | 表 | 唯一键 |
|
||||
|------|----|--------|
|
||||
| `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
|
||||
graph TD
|
||||
CLI[CLI Entry] --> PS[ParseService]
|
||||
CLI --> Serilog
|
||||
CLI[CLI Commands] --> Parsing[Parsing]
|
||||
CLI --> LocalIngestion[LocalIngestion]
|
||||
CLI --> Publishing[Publishing]
|
||||
CLI --> Exporting[Exporting]
|
||||
CLI --> Admin[Administration]
|
||||
|
||||
PS --> SS[ScenarioService]
|
||||
PS --> QP[QfodParser]
|
||||
PS --> PP[PlossParser]
|
||||
PS --> DBM[WctMinerDbContext]
|
||||
PS --> Serilog
|
||||
LocalIngestion --> Parsing
|
||||
LocalIngestion --> LocalData[LocalData]
|
||||
Publishing --> LocalData
|
||||
Publishing --> ReleaseData[ReleaseData]
|
||||
Exporting --> LocalData
|
||||
Exporting --> ReleaseData
|
||||
Admin --> LocalData
|
||||
|
||||
SS --> DS[DimensionService]
|
||||
SS --> FNP[FileNameParser]
|
||||
SS --> DBM
|
||||
LocalData --> Domain[Domain Models]
|
||||
ReleaseData --> Domain
|
||||
Parsing --> Domain
|
||||
|
||||
DS --> DBM
|
||||
|
||||
QP --> Serilog
|
||||
PP --> Serilog
|
||||
FNP --> Serilog
|
||||
|
||||
DBM --> Models[Models]
|
||||
DBM --> Configs[Configurations]
|
||||
|
||||
subgraph Parser Layer
|
||||
QP
|
||||
PP
|
||||
FNP
|
||||
subgraph Application
|
||||
Parsing
|
||||
LocalIngestion
|
||||
Publishing
|
||||
Exporting
|
||||
Admin
|
||||
end
|
||||
|
||||
subgraph Data Layer
|
||||
DBM
|
||||
Models
|
||||
Configs
|
||||
subgraph Domain
|
||||
Domain
|
||||
end
|
||||
|
||||
subgraph Infrastructure
|
||||
Serilog
|
||||
end
|
||||
|
||||
subgraph Service Layer
|
||||
PS
|
||||
SS
|
||||
DS
|
||||
LocalData
|
||||
ReleaseData
|
||||
Configuration
|
||||
Security
|
||||
Extensions
|
||||
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
|
||||
// Configuration/appsettings.json
|
||||
{
|
||||
"Database": {
|
||||
"Type": "sqlite",
|
||||
"Path": "./data/database/wctminer.db"
|
||||
"LocalPath": "./data/database/local.db",
|
||||
"ReleasePath": "./data/database/release.db"
|
||||
},
|
||||
"Parser": {
|
||||
"BatchSize": 1000
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Gpulse.WCT.DataAnalyzer": "Debug"
|
||||
}
|
||||
},
|
||||
"Paths": {
|
||||
"LogDir": "./data/logs",
|
||||
"OutputDir": "./data/output"
|
||||
},
|
||||
"Aggregation": {
|
||||
"TxPanelMappings": {
|
||||
"single-mold": "奇瑞",
|
||||
"dual-rapid": "智己"
|
||||
},
|
||||
"CarModelMappings": {
|
||||
"L6": "L6",
|
||||
"CM3": "CM3"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -34,8 +38,8 @@
|
||||
## 2. 配置类定义
|
||||
|
||||
```csharp
|
||||
// Configuration/AppSettings.cs
|
||||
namespace Gpulse.WCT.DataAnalyzer.Configuration;
|
||||
// Infrastructure/Configuration/AppSettings.cs
|
||||
namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.Configuration;
|
||||
|
||||
public class AppSettings
|
||||
{
|
||||
@@ -47,7 +51,8 @@ public class AppSettings
|
||||
public class DatabaseSettings
|
||||
{
|
||||
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 配置(可选)
|
||||
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);
|
||||
config.AddJsonFile($"appsettings.{environment}.json", optional: true);
|
||||
config.AddEnvironmentVariables();
|
||||
config.AddCommandLine(args);
|
||||
});
|
||||
|
||||
builder.ConfigureServices((context, services) =>
|
||||
{
|
||||
services.Configure<AppSettings>(context.Configuration.GetSection(""));
|
||||
|
||||
// 注册数据库服务
|
||||
services.AddDatabaseServices(context.Configuration);
|
||||
|
||||
// 注册其他服务
|
||||
services.AddScoped<QfodParser>();
|
||||
services.AddScoped<PlossParser>();
|
||||
services.AddScoped<ParseService>();
|
||||
services.AddScoped<ErrorHandlingService>();
|
||||
});
|
||||
"Database": {
|
||||
"Type": "postgresql",
|
||||
"Host": "localhost",
|
||||
"Port": 5432,
|
||||
"Name": "wct_local",
|
||||
"User": "wct_user",
|
||||
"Password": "...",
|
||||
"ReleaseHost": "db-server",
|
||||
"ReleaseName": "wct_charging_parameters",
|
||||
"ReleaseUser": "wct_release",
|
||||
"ReleasePassword": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
> Gpulse.WCT.DataAnalyzer 数据库模型规格,采用维度表设计,消除数据冗余
|
||||
> 双数据库数据模型:本地解析库 + 正式发布库,相互独立。
|
||||
|
||||
---
|
||||
|
||||
## 文档索引
|
||||
|
||||
### 维度表
|
||||
### 本地库(local.db)
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
@@ -14,253 +14,74 @@
|
||||
| [TX硬件版本维度](./entities/TxHardware维度.md) | TX硬件版本维度 |
|
||||
| [TX软件版本维度](./entities/TxSoftware维度.md) | TX软件版本维度 |
|
||||
| [RX类型维度](./entities/RxType维度.md) | RX类型维度 |
|
||||
|
||||
### 事实表
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [测试场景实体](./entities/测试场景.md) | 测试场景(外键组合) |
|
||||
| [Qfod 记录实体](./entities/Qfod记录.md) | Qfod 检测数据 |
|
||||
| [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
|
||||
erDiagram
|
||||
%% 本地库
|
||||
tx_panel ||--o{ test_scenario : "引用"
|
||||
tx_hardware ||--o{ test_scenario : "引用"
|
||||
tx_software ||--o{ test_scenario : "引用"
|
||||
rx_type ||--o{ test_scenario : "引用"
|
||||
|
||||
test_scenario ||--o{ qfod_record : "包含"
|
||||
test_scenario ||--o{ ploss_record : "包含"
|
||||
|
||||
tx_panel {
|
||||
uuid id PK "主键"
|
||||
varchar name UK "面板类型"
|
||||
}
|
||||
|
||||
tx_hardware {
|
||||
uuid id PK "主键"
|
||||
varchar version UK "硬件版本"
|
||||
}
|
||||
|
||||
tx_software {
|
||||
uuid id PK "主键"
|
||||
varchar version UK "软件版本"
|
||||
}
|
||||
|
||||
rx_type {
|
||||
uuid id PK "主键"
|
||||
varchar name UK "RX类型"
|
||||
}
|
||||
|
||||
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判定结果"
|
||||
%% 发布库(独立)
|
||||
charging_parameter {
|
||||
uuid id PK
|
||||
varchar car_factory UK
|
||||
varchar car_model UK
|
||||
varchar phone_brand UK
|
||||
varchar phone_model UK
|
||||
double power_350mw
|
||||
double power_500mw
|
||||
double power_750mw
|
||||
double power_1000mw
|
||||
double power_1250mw
|
||||
double power_1500mw
|
||||
double power_1750mw
|
||||
double power_2000mw
|
||||
double power_2250mw
|
||||
double q_value
|
||||
double q_base_value
|
||||
double pq_coefficient
|
||||
double resonance_frequency
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
框外是本地库,框内是发布库——两库之间没有外键或导航属性。
|
||||
|
||||
## 设计说明
|
||||
|
||||
### 星型模型架构
|
||||
### 本地库:星型模型
|
||||
|
||||
本设计采用星型模型(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` 直接存储在场景表中,不拆分维度表,原因:
|
||||
- 测试目的描述较自由,不适合标准化为维度
|
||||
- 便于用户灵活输入
|
||||
|
||||
### 场景唯一性
|
||||
|
||||
场景表设置唯一约束,相同属性组合不重复创建:
|
||||
|
||||
```
|
||||
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 |
|
||||
- 代码中两个实体的命名空间不同(Domain.Models)
|
||||
- 两个 DbContext 不共享 DbSet
|
||||
- 发布库不从本地库继承实体配置
|
||||
- aggregate 通过纯值映射,不依赖导航属性
|
||||
Reference in New Issue
Block a user