docs: 完善数据模型设计
- 重命名 log_file 为 test_scenario(测试场景) - 拆分维度表:tx_panel、tx_hardware、tx_software、rx_type - 采用星型模型设计,消除数据冗余 - 为所有表添加软删除字段 is_deleted - 更新关系说明,包含全局查询过滤器配置 - 更新 ER 图和查询示例 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
168
docs/architecture/CLI命令设计.md
Normal file
168
docs/architecture/CLI命令设计.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# CLI 命令设计
|
||||
|
||||
> 所属模块:[架构概览](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 命令概览
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `parse` | 解析日志文件并存储到数据库 |
|
||||
| `stats` | 查询统计信息 |
|
||||
| `export` | 导出数据 |
|
||||
| `clean` | 清理数据 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 命令详解
|
||||
|
||||
### 2.1 parse - 解析日志文件
|
||||
|
||||
```bash
|
||||
# 解析单个文件
|
||||
dotnet run parse --file path/to/log.txt
|
||||
|
||||
# 解析目录下所有文件
|
||||
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 - 查询统计
|
||||
|
||||
```bash
|
||||
# 查看文件解析统计
|
||||
dotnet run stats --file log.txt
|
||||
|
||||
# 查看整体统计
|
||||
dotnet run stats --summary
|
||||
|
||||
# 按 FOD 类型统计
|
||||
dotnet run stats --by-fod-type
|
||||
```
|
||||
|
||||
### 2.3 export - 导出数据
|
||||
|
||||
```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 --all --confirm
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 命令实现
|
||||
|
||||
```csharp
|
||||
// Commands/ParseCommand.cs
|
||||
using System.CommandLine;
|
||||
|
||||
namespace WCTDataMiner.Commands;
|
||||
|
||||
public class ParseCommand : Command
|
||||
{
|
||||
public ParseCommand(ParseService parseService)
|
||||
: base("parse", "Parse log files and store data to database")
|
||||
{
|
||||
var fileOption = new Option<string>(
|
||||
"--file",
|
||||
"Path to a single log file to parse"
|
||||
);
|
||||
|
||||
var dirOption = new Option<string>(
|
||||
"--dir",
|
||||
"Path to directory containing log files"
|
||||
);
|
||||
|
||||
var recursiveOption = new Option<bool>(
|
||||
"--recursive",
|
||||
() => false,
|
||||
"Recursively search subdirectories"
|
||||
);
|
||||
|
||||
var forceOption = new Option<bool>(
|
||||
"--force",
|
||||
() => false,
|
||||
"Force re-parse even if file already parsed"
|
||||
);
|
||||
|
||||
AddOption(fileOption);
|
||||
AddOption(dirOption);
|
||||
AddOption(recursiveOption);
|
||||
AddOption(forceOption);
|
||||
|
||||
this.SetHandler(async (file, dir, recursive, force) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(file))
|
||||
{
|
||||
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. Program.cs 入口
|
||||
|
||||
```csharp
|
||||
// Program.cs
|
||||
using System.CommandLine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using WCTDataMiner.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("WCTDataMiner - 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);
|
||||
```
|
||||
122
docs/architecture/database/DbContext工厂.md
Normal file
122
docs/architecture/database/DbContext工厂.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# DbContext 工厂
|
||||
|
||||
> 所属模块:[数据库切换支持](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口定义
|
||||
|
||||
```csharp
|
||||
// Data/IDbContextFactory.cs
|
||||
using WCTDataMiner.Data;
|
||||
|
||||
namespace WCTDataMiner.Data;
|
||||
|
||||
public interface IDbContextFactory
|
||||
{
|
||||
WctMinerDbContext CreateDbContext();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 工厂实现
|
||||
|
||||
```csharp
|
||||
// Data/DbContextFactory.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace WCTDataMiner.Data;
|
||||
|
||||
public class DbContextFactory : IDbContextFactory
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 依赖注入扩展
|
||||
|
||||
```csharp
|
||||
// Extensions/ServiceCollectionExtensions.cs
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using WCTDataMiner.Data;
|
||||
|
||||
namespace WCTDataMiner.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
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();
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
```
|
||||
45
docs/architecture/database/概览.md
Normal file
45
docs/architecture/database/概览.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 数据库切换支持
|
||||
|
||||
> 所属模块:[架构概览](../概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 文档索引
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [DbContext 工厂](./DbContext工厂.md) | 数据库上下文工厂设计 |
|
||||
| [迁移策略](./迁移策略.md) | 数据库迁移和切换步骤 |
|
||||
|
||||
---
|
||||
|
||||
## 切换策略概览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ appsettings.json │
|
||||
│ "Database": { "Type": "sqlite" | "postgresql" } │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ DbContextFactory │
|
||||
│ 读取配置 → 选择 Provider → 创建 DbContext │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WctMinerDbContext │
|
||||
│ 统一的 DbModel 配置,适配两种数据库 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 当前状态
|
||||
|
||||
| 项目 | 状态 |
|
||||
|------|------|
|
||||
| SQLite | ✅ 默认启用 |
|
||||
| PostgreSQL | ✅ 架构支持,待配置 |
|
||||
| 切换方式 | 配置文件切换 |
|
||||
81
docs/architecture/database/迁移策略.md
Normal file
81
docs/architecture/database/迁移策略.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 数据库迁移策略
|
||||
|
||||
> 所属模块:[数据库切换支持](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 迁移命令
|
||||
|
||||
### SQLite
|
||||
|
||||
```bash
|
||||
# 添加迁移
|
||||
dotnet ef migrations add InitialCreate --output-dir Migrations/SQLite
|
||||
|
||||
# 应用迁移
|
||||
dotnet ef database update
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```bash
|
||||
# 切换配置后添加迁移
|
||||
dotnet ef migrations add InitialCreate --output-dir Migrations/PostgreSQL -- --environment PostgreSQL
|
||||
|
||||
# 应用迁移
|
||||
dotnet ef database update -- --environment PostgreSQL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 切换步骤
|
||||
|
||||
### 从 SQLite 切换到 PostgreSQL
|
||||
|
||||
| 步骤 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| 1 | 修改配置 | `appsettings.json` 中 `Database:Type` 改为 `postgresql` |
|
||||
| 2 | 设置连接 | 配置 `Host`, `Port`, `Name`, `User`, `Password` |
|
||||
| 3 | 创建数据库 | `CREATE DATABASE wctminer;` |
|
||||
| 4 | 应用迁移 | `dotnet ef database update` |
|
||||
| 5 | 数据迁移 | 使用工具迁移 SQLite 数据到 PostgreSQL |
|
||||
|
||||
---
|
||||
|
||||
## 3. 配置文件示例
|
||||
|
||||
### SQLite (默认)
|
||||
|
||||
```json
|
||||
{
|
||||
"Database": {
|
||||
"Type": "sqlite",
|
||||
"Path": "./data/database/wctminer.db"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```json
|
||||
{
|
||||
"Database": {
|
||||
"Type": "postgresql",
|
||||
"Host": "localhost",
|
||||
"Port": 5432,
|
||||
"Name": "wctminer",
|
||||
"User": "wctminer_user",
|
||||
"Password": "${DB_PASSWORD}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据迁移工具
|
||||
|
||||
推荐使用 [pgloader](https://github.com/dimitri/pgloader) 迁移 SQLite 到 PostgreSQL:
|
||||
|
||||
```bash
|
||||
pgloader ./data/database/wctminer.db postgresql://user:password@localhost:5432/wctminer
|
||||
```
|
||||
165
docs/architecture/parser/Ploss解析器.md
Normal file
165
docs/architecture/parser/Ploss解析器.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# Ploss 解析器
|
||||
|
||||
> 所属模块:[解析器设计](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 日志格式
|
||||
|
||||
**格式:** `FOD-> (1) X X X (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13)`
|
||||
|
||||
**示例:** `FOD-> 4 2 1 127 10690 12013 35377 9904 1213 -2882 750 0 0 1 500 1`
|
||||
|
||||
> 括号数字表示有效字段编号,X 表示忽略该位置数据
|
||||
|
||||
---
|
||||
|
||||
## 2. 字段定义
|
||||
|
||||
| 编号 | 字段名称 | 数据类型 | 说明 |
|
||||
|------|----------|----------|------|
|
||||
| (1) | RxType | byte | 接收端类型标识 |
|
||||
| (2) | RxPower | short | 接收端功率(mW) |
|
||||
| (3) | TxPower | short | 发射端功率(mW) |
|
||||
| (4) | Vcoil | short | 发射线圈电压 |
|
||||
| (5) | Vin | short | 输入电压 |
|
||||
| (6) | Isns | short | 输入电流采样值 |
|
||||
| (7) | Ploss | int | **核心值**:计算损耗(负数表示安全) |
|
||||
| (8) | Threshold | int | **安全红线**:当前功率段阈值 |
|
||||
| (9) | TriggerCount | short | 触发计数器 |
|
||||
| (10) | FodResult | short | FOD判定结果状态 |
|
||||
| (11) | ProtocolType | byte | 充电协议类型 |
|
||||
| (12) | PwmDuty | short | PWM控制占空比 |
|
||||
| (13) | CoilIndex | byte | 充电线圈索引 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 正则表达式
|
||||
|
||||
```csharp
|
||||
// Parsers/PlossParser.cs
|
||||
private static readonly Regex PlossPattern = new(
|
||||
@"^FOD->\s+(\d+)\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
```
|
||||
|
||||
**捕获组映射:**
|
||||
|
||||
| 捕获组 | 字段 | 类型转换 |
|
||||
|--------|------|----------|
|
||||
| Group 1 | RxType | byte.Parse() |
|
||||
| Group 2 | RxPower | short.Parse() |
|
||||
| Group 3 | TxPower | short.Parse() |
|
||||
| Group 4 | Vcoil | short.Parse() |
|
||||
| Group 5 | Vin | short.Parse() |
|
||||
| Group 6 | Isns | short.Parse() |
|
||||
| Group 7 | Ploss | int.Parse() |
|
||||
| Group 8 | Threshold | int.Parse() |
|
||||
| Group 9 | TriggerCount | short.Parse() |
|
||||
| Group 10 | FodResult | short.Parse() |
|
||||
| Group 11 | ProtocolType | byte.Parse() |
|
||||
| Group 12 | PwmDuty | short.Parse() |
|
||||
| Group 13 | CoilIndex | byte.Parse() |
|
||||
|
||||
---
|
||||
|
||||
## 4. 解析器实现
|
||||
|
||||
```csharp
|
||||
// Parsers/PlossParser.cs
|
||||
using System.Text.RegularExpressions;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Parsers;
|
||||
|
||||
public class PlossParser : IParser<PlossRecord>
|
||||
{
|
||||
private static readonly Regex PlossPattern = new(
|
||||
@"^FOD->\s+(\d+)\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
|
||||
public bool CanParse(string line) => PlossPattern.IsMatch(line);
|
||||
|
||||
public ParseResult<PlossRecord> Parse(string line, int lineNumber, Guid logFileId)
|
||||
{
|
||||
var match = PlossPattern.Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
return ParseResult<PlossRecord>.Failure(
|
||||
"FORMAT_MISMATCH",
|
||||
"Line does not match Ploss pattern"
|
||||
);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var record = new PlossRecord
|
||||
{
|
||||
LogFileId = logFileId,
|
||||
LineNumber = lineNumber,
|
||||
RxType = byte.Parse(match.Groups[1].Value),
|
||||
RxPower = short.Parse(match.Groups[2].Value),
|
||||
TxPower = short.Parse(match.Groups[3].Value),
|
||||
Vcoil = short.Parse(match.Groups[4].Value),
|
||||
Vin = short.Parse(match.Groups[5].Value),
|
||||
Isns = short.Parse(match.Groups[6].Value),
|
||||
Ploss = int.Parse(match.Groups[7].Value),
|
||||
Threshold = int.Parse(match.Groups[8].Value),
|
||||
TriggerCount = short.Parse(match.Groups[9].Value),
|
||||
FodResult = short.Parse(match.Groups[10].Value),
|
||||
ProtocolType = byte.Parse(match.Groups[11].Value),
|
||||
PwmDuty = short.Parse(match.Groups[12].Value),
|
||||
CoilIndex = byte.Parse(match.Groups[13].Value)
|
||||
};
|
||||
|
||||
return ParseResult<PlossRecord>.Success(record);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ParseResult<PlossRecord>.Failure(
|
||||
"TYPE_CONVERSION",
|
||||
$"Failed to convert field: {ex.Message}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 核心判定逻辑
|
||||
|
||||
### 5.1 FOD 触发条件
|
||||
|
||||
```
|
||||
if (Ploss > Threshold) → 计数器累积
|
||||
if (Count > Limit) → 触发 FOD 报警,停止充电
|
||||
```
|
||||
|
||||
### 5.2 安全余量计算
|
||||
|
||||
```csharp
|
||||
// 派生属性(不落库)
|
||||
Margin = Threshold - Ploss
|
||||
```
|
||||
|
||||
| Margin 状态 | 含义 |
|
||||
|-------------|------|
|
||||
| Margin > 0 | 安全,损耗低于阈值 |
|
||||
| Margin < 0 | 危险,损耗超过阈值,可能触发 FOD |
|
||||
|
||||
---
|
||||
|
||||
## 6. 动态阈值表
|
||||
|
||||
阈值根据接收功率动态查表:
|
||||
|
||||
| 接收功率区间 (Rx Power) | 阈值 (Threshold) | 备注 |
|
||||
|------------------------|------------------|------|
|
||||
| 0 ~ 5W (0-5000 mW) | 350 mW | 低功率最敏感 |
|
||||
| 5 ~ 10W (5000-10000 mW) | 500 mW | |
|
||||
| 10 ~ 15W (10000-15000 mW) | 750 mW | |
|
||||
| 15 ~ 30W (15000-30000 mW) | 1000 mW | 示例 Log 处于此区间 |
|
||||
| 30 ~ 50W (30000-50000 mW) | 1250 mW | 高功率允许更多损耗 |
|
||||
120
docs/architecture/parser/Qfod解析器.md
Normal file
120
docs/architecture/parser/Qfod解析器.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Qfod 解析器
|
||||
|
||||
> 所属模块:[解析器设计](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 日志格式
|
||||
|
||||
**格式:** `X#:Q A->B C D E`
|
||||
|
||||
**示例:** `0#:Q 1->25 45.5 20.5 2`
|
||||
|
||||
---
|
||||
|
||||
## 2. 字段定义
|
||||
|
||||
| 字段标识 | 字段名称 | 数据类型 | 取值范围 | 说明 |
|
||||
|----------|----------|----------|----------|------|
|
||||
| X | ChargerIndex | byte | 0, 1 | 充电器索引 |
|
||||
| A | CoilIndex | byte | 0, 1, 2 | 线圈索引 |
|
||||
| B | DeltaQ | int | - | Q值变化量(ΔQ = CurrentQ - RawQ) |
|
||||
| C | CurrentQ | float | - | 当前实时Q值 |
|
||||
| D | RawQ | float | - | 原始Q值 |
|
||||
| E | FodType | byte | 见Table 2 | 异物类型编码 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 正则表达式
|
||||
|
||||
```csharp
|
||||
// Parsers/QfodParser.cs
|
||||
private static readonly Regex QfodPattern = new(
|
||||
@"^(\d)#:Q\s+(\d+)->(-?\d+)\s+([-\d.]+)\s+([-\d.]+)\s+(\d+)$",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
```
|
||||
|
||||
**捕获组映射:**
|
||||
|
||||
| 捕获组 | 字段 | 类型转换 |
|
||||
|--------|------|----------|
|
||||
| Group 1 | ChargerIndex | byte.Parse() |
|
||||
| Group 2 | CoilIndex | byte.Parse() |
|
||||
| Group 3 | DeltaQ | int.Parse() |
|
||||
| Group 4 | CurrentQ | float.Parse() |
|
||||
| Group 5 | RawQ | float.Parse() |
|
||||
| Group 6 | FodType | byte.Parse() |
|
||||
|
||||
---
|
||||
|
||||
## 4. 解析器实现
|
||||
|
||||
```csharp
|
||||
// Parsers/QfodParser.cs
|
||||
using System.Text.RegularExpressions;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Parsers;
|
||||
|
||||
public class QfodParser : IParser<QfodRecord>
|
||||
{
|
||||
private static readonly Regex QfodPattern = new(
|
||||
@"^(\d)#:Q\s+(\d+)->(-?\d+)\s+([-\d.]+)\s+([-\d.]+)\s+(\d+)$",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
|
||||
public bool CanParse(string line) => QfodPattern.IsMatch(line);
|
||||
|
||||
public ParseResult<QfodRecord> Parse(string line, int lineNumber, Guid logFileId)
|
||||
{
|
||||
var match = QfodPattern.Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
return ParseResult<QfodRecord>.Failure(
|
||||
"FORMAT_MISMATCH",
|
||||
"Line does not match Qfod pattern"
|
||||
);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var record = new QfodRecord
|
||||
{
|
||||
LogFileId = logFileId,
|
||||
LineNumber = lineNumber,
|
||||
ChargerIndex = byte.Parse(match.Groups[1].Value),
|
||||
CoilIndex = byte.Parse(match.Groups[2].Value),
|
||||
DeltaQ = int.Parse(match.Groups[3].Value),
|
||||
CurrentQ = float.Parse(match.Groups[4].Value),
|
||||
RawQ = float.Parse(match.Groups[5].Value),
|
||||
FodType = byte.Parse(match.Groups[6].Value)
|
||||
};
|
||||
|
||||
return ParseResult<QfodRecord>.Success(record);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ParseResult<QfodRecord>.Failure(
|
||||
"TYPE_CONVERSION",
|
||||
$"Failed to convert field: {ex.Message}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 检测通道组合
|
||||
|
||||
`ChargerIndex + CoilIndex` 组合唯一标识一个检测通道:
|
||||
|
||||
| ChargerIndex | CoilIndex | 检测通道 |
|
||||
|--------------|-----------|----------|
|
||||
| 0 | 0 | 充电器0 线圈0 |
|
||||
| 0 | 1 | 充电器0 线圈1 |
|
||||
| 0 | 2 | 充电器0 线圈2 |
|
||||
| 1 | 0 | 充电器1 线圈0 |
|
||||
| 1 | 1 | 充电器1 线圈1 |
|
||||
| 1 | 2 | 充电器1 线圈2 |
|
||||
78
docs/architecture/parser/概览.md
Normal file
78
docs/architecture/parser/概览.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 解析器设计
|
||||
|
||||
> 所属模块:[架构概览](../概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 文档索引
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [Qfod 解析器](./Qfod解析器.md) | Qfod 格式日志解析设计 |
|
||||
| [Ploss 解析器](./Ploss解析器.md) | Ploss FOD 格式日志解析设计 |
|
||||
|
||||
---
|
||||
|
||||
## 通用接口定义
|
||||
|
||||
```csharp
|
||||
// Parsers/IParser.cs
|
||||
namespace WCTDataMiner.Parsers;
|
||||
|
||||
public interface IParser<TRecord>
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断是否可以解析该行
|
||||
/// </summary>
|
||||
bool CanParse(string line);
|
||||
|
||||
/// <summary>
|
||||
/// 解析日志行
|
||||
/// </summary>
|
||||
ParseResult<TRecord> Parse(string line, int lineNumber, Guid logFileId);
|
||||
}
|
||||
|
||||
public record ParseResult<TRecord>
|
||||
{
|
||||
public bool IsSuccess { get; init; }
|
||||
public TRecord? Record { get; init; }
|
||||
public string? ErrorType { get; init; }
|
||||
public string? ErrorMessage { get; init; }
|
||||
|
||||
public static ParseResult<TRecord> Success(TRecord record) =>
|
||||
new() { IsSuccess = true, Record = record };
|
||||
|
||||
public static ParseResult<TRecord> Failure(string errorType, string errorMessage) =>
|
||||
new() { IsSuccess = false, ErrorType = errorType, ErrorMessage = errorMessage };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 解析流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[读取日志文件] --> B[逐行遍历]
|
||||
B --> C{判断行类型}
|
||||
C -->|Qfod| D[QfodParser.Parse]
|
||||
C -->|Ploss| E[PlossParser.Parse]
|
||||
C -->|其他| F[跳过]
|
||||
D --> G{解析成功?}
|
||||
E --> G
|
||||
G -->|成功| H[添加到记录列表]
|
||||
G -->|失败| I[记录到 ParseError]
|
||||
H --> J[批量保存到数据库]
|
||||
I --> J
|
||||
J --> K[输出解析报告]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误类型定义
|
||||
|
||||
| ErrorType | 说明 | 处理方式 |
|
||||
|-----------|------|----------|
|
||||
| FORMAT_MISMATCH | 行格式不匹配正则表达式 | 跳过该行,记录原始内容 |
|
||||
| TYPE_CONVERSION | 字段类型转换失败 | 跳过该行,记录错误字段 |
|
||||
| VALUE_OUT_OF_RANGE | 字段值超出有效范围 | 使用边界值或跳过 |
|
||||
39
docs/architecture/安全架构.md
Normal file
39
docs/architecture/安全架构.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 安全架构
|
||||
|
||||
> 所属模块:[架构概览](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
> [A] 本系统为本地日志解析工具,无网络暴露,安全需求较低
|
||||
|
||||
---
|
||||
|
||||
## 2. 认证方案
|
||||
|
||||
| 项目 | 方案 |
|
||||
|------|------|
|
||||
| 认证方式 | [A] 无(本地工具) |
|
||||
| 数据库访问 | 本地文件权限控制(SQLite)或连接串认证(PostgreSQL) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据安全
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 数据类型 | [!] 日志数据不含敏感信息,为设备检测数据 |
|
||||
| 数据保护 | 本地存储,操作系统文件权限控制 |
|
||||
| 备份策略 | [?] 需确认:是否需要定期备份? |
|
||||
|
||||
---
|
||||
|
||||
## 4. 安全检查清单
|
||||
|
||||
| 检查项 | 状态 |
|
||||
|--------|------|
|
||||
| 无网络暴露 | ✅ 本地工具 |
|
||||
| 数据库认证 | ✅ SQLite 本地文件权限 / PostgreSQL 连接串 |
|
||||
| 敏感数据处理 | ✅ 无敏感数据 |
|
||||
| 日志脱敏 | ✅ 不需要(设备检测数据) |
|
||||
58
docs/architecture/技术选型.md
Normal file
58
docs/architecture/技术选型.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 技术选型
|
||||
|
||||
> 所属模块:[架构概览](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 技术栈总览
|
||||
|
||||
| 层级 | 技术选型 | 版本 | 选型理由 |
|
||||
|------|----------|------|----------|
|
||||
| 运行时 | .NET | 8.0 LTS | 长期支持版本,性能优秀,跨平台 |
|
||||
| 语言 | C# | 12.0 | 强类型、现代语法、优秀的正则表达式支持 |
|
||||
| ORM | Entity Framework Core | 8.0 | .NET 标准 ORM,Code First 支持,LINQ 查询 |
|
||||
| 数据库 | SQLite / PostgreSQL | - | SQLite 适合单机;PostgreSQL 适合多用户并发 |
|
||||
| 日志框架 | Serilog | 4.0+ | 结构化日志,多种 Sink 支持 |
|
||||
| 配置管理 | Microsoft.Extensions.Configuration | - | 强类型配置,支持 JSON/环境变量 |
|
||||
| CLI 框架 | System.CommandLine | 2.0-beta4 | 官方 CLI 库,参数解析完善 |
|
||||
| 正则表达式 | System.Text.RegularExpressions | - | .NET 原生支持,编译正则高效 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术决策记录
|
||||
|
||||
| # | 决策点 | 选择 | 备选方案 | 决策理由 |
|
||||
|---|--------|------|----------|----------|
|
||||
| 1 | .NET 版本 | .NET 8.0 LTS | .NET 9.0 | LTS 版本稳定,企业级应用首选 |
|
||||
| 2 | 数据库选择 | SQLite(当前)/ PostgreSQL(后续) | SQL Server | 初期单机使用 SQLite,后续可切换 PostgreSQL |
|
||||
| 3 | 解析模式 | 批处理 | 流式处理 | 日志文件是静态文件,批处理更合适 |
|
||||
| 4 | 数据格式 | 结构化存储 | 原始存储 + 计算 | 解析后存储便于后续分析 |
|
||||
| 5 | ORM 模式 | Code First | Database First | 便于版本控制,迁移自动化 |
|
||||
| 6 | 数据库切换 | 抽象 DbContext 工厂 | 硬编码连接 | 支持配置切换数据库类型 |
|
||||
|
||||
---
|
||||
|
||||
## 3. NuGet 包依赖
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<!-- ORM -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
|
||||
<!-- CLI -->
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
|
||||
<!-- 配置 -->
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
|
||||
<!-- 日志 -->
|
||||
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
```
|
||||
205
docs/architecture/数据处理流程.md
Normal file
205
docs/architecture/数据处理流程.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# 数据处理流程
|
||||
|
||||
> 所属模块:[架构概览](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 完整解析流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CLI as CLI Entry
|
||||
participant PS as ParseService
|
||||
participant LR as LogFileReader
|
||||
participant QP as QfodParser
|
||||
participant PP as PlossParser
|
||||
participant EHS as ErrorHandlingService
|
||||
participant DB as Database
|
||||
|
||||
CLI->>LR: 扫描日志目录
|
||||
LR-->>CLI: 返回文件列表
|
||||
|
||||
loop 每个日志文件
|
||||
CLI->>PS: ParseFileAsync(filePath)
|
||||
|
||||
PS->>DB: INSERT log_file (status=processing)
|
||||
DB-->>PS: 返回 file_id
|
||||
|
||||
PS->>LR: 读取文件内容
|
||||
LR-->>PS: 返回行列表
|
||||
|
||||
loop 逐行遍历
|
||||
alt 是 Qfod 行
|
||||
PS->>QP: Parse(line)
|
||||
QP-->>PS: ParseResult
|
||||
else 是 Ploss 行
|
||||
PS->>PP: Parse(line)
|
||||
PP-->>PS: ParseResult
|
||||
end
|
||||
|
||||
alt 解析成功
|
||||
PS->>PS: 加入记录列表
|
||||
else 解析失败
|
||||
PS->>EHS: RecordError(line, error)
|
||||
end
|
||||
end
|
||||
|
||||
PS->>DB: BATCH INSERT records
|
||||
PS->>DB: BATCH INSERT errors
|
||||
PS->>DB: UPDATE log_file (counts, status=completed)
|
||||
|
||||
PS-->>CLI: 返回 ParseReport
|
||||
end
|
||||
|
||||
CLI-->>CLI: 输出解析报告
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. ParseService 实现
|
||||
|
||||
```csharp
|
||||
// Services/ParseService.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WCTDataMiner.Data;
|
||||
using WCTDataMiner.Models;
|
||||
using WCTDataMiner.Parsers;
|
||||
|
||||
namespace WCTDataMiner.Services;
|
||||
|
||||
public class ParseService
|
||||
{
|
||||
private readonly WctMinerDbContext _context;
|
||||
private readonly QfodParser _qfodParser;
|
||||
private readonly PlossParser _plossParser;
|
||||
private readonly ErrorHandlingService _errorService;
|
||||
private readonly ILogger<ParseService> _logger;
|
||||
private readonly int _batchSize;
|
||||
|
||||
public ParseService(
|
||||
WctMinerDbContext context,
|
||||
QfodParser qfodParser,
|
||||
PlossParser plossParser,
|
||||
ErrorHandlingService errorService,
|
||||
ILogger<ParseService> logger,
|
||||
IConfiguration config)
|
||||
{
|
||||
_context = context;
|
||||
_qfodParser = qfodParser;
|
||||
_plossParser = plossParser;
|
||||
_errorService = errorService;
|
||||
_logger = logger;
|
||||
_batchSize = config.GetValue("Parser:BatchSize", 1000);
|
||||
}
|
||||
|
||||
public async Task<ParseReport> ParseFileAsync(string filePath)
|
||||
{
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
var logFile = await CreateLogFileRecordAsync(fileInfo);
|
||||
|
||||
var lines = await File.ReadAllLinesAsync(filePath);
|
||||
var qfodRecords = new List<QfodRecord>();
|
||||
var plossRecords = new List<PlossRecord>();
|
||||
|
||||
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, lineNumber, logFile.Id);
|
||||
if (result.IsSuccess)
|
||||
qfodRecords.Add(result.Record!);
|
||||
else
|
||||
_errorService.RecordError(logFile.Id, lineNumber, line, result.ErrorType!, result.ErrorMessage!);
|
||||
}
|
||||
else if (_plossParser.CanParse(line))
|
||||
{
|
||||
var result = _plossParser.Parse(line, lineNumber, logFile.Id);
|
||||
if (result.IsSuccess)
|
||||
plossRecords.Add(result.Record!);
|
||||
else
|
||||
_errorService.RecordError(logFile.Id, lineNumber, line, result.ErrorType!, result.ErrorMessage!);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量保存
|
||||
await BatchInsertAsync(qfodRecords, plossRecords, _errorService.GetErrors());
|
||||
|
||||
// 更新统计
|
||||
await UpdateLogFileStatsAsync(logFile.Id, qfodRecords.Count, plossRecords.Count, _errorService.ErrorCount);
|
||||
|
||||
return new ParseReport(fileInfo.Name, qfodRecords.Count, plossRecords.Count, _errorService.ErrorCount);
|
||||
}
|
||||
|
||||
private async Task<LogFile> CreateLogFileRecordAsync(FileInfo fileInfo)
|
||||
{
|
||||
var logFile = new LogFile
|
||||
{
|
||||
FileName = fileInfo.Name,
|
||||
FilePath = fileInfo.FullName,
|
||||
FileSize = fileInfo.Length,
|
||||
Status = ParseStatus.Processing
|
||||
};
|
||||
|
||||
_context.LogFiles.Add(logFile);
|
||||
await _context.SaveChangesAsync();
|
||||
return logFile;
|
||||
}
|
||||
|
||||
private async Task BatchInsertAsync(
|
||||
List<QfodRecord> qfodRecords,
|
||||
List<PlossRecord> plossRecords,
|
||||
List<ParseError> errors)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
// 批量插入 ParseError
|
||||
foreach (var batch in errors.Chunk(_batchSize))
|
||||
{
|
||||
_context.ParseErrors.AddRange(batch);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateLogFileStatsAsync(Guid logFileId, int qfodCount, int plossCount, int errorCount)
|
||||
{
|
||||
var logFile = await _context.LogFiles.FindAsync(logFileId);
|
||||
if (logFile != null)
|
||||
{
|
||||
logFile.QfodCount = qfodCount;
|
||||
logFile.PlossCount = plossCount;
|
||||
logFile.ErrorCount = errorCount;
|
||||
logFile.Status = ParseStatus.Completed;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record ParseReport(string FileName, int QfodCount, int PlossCount, int ErrorCount);
|
||||
```
|
||||
68
docs/architecture/概览.md
Normal file
68
docs/architecture/概览.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Architecture: WCTDataMiner
|
||||
|
||||
> 无线充电 FOD(异物检测)日志数据采集系统,从日志文件中解析 Qfod 和 Ploss 数据并存储到数据库
|
||||
|
||||
---
|
||||
|
||||
## 文档索引
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [技术选型](./技术选型.md) | 技术栈总览、决策记录 |
|
||||
| [模块划分](./模块划分.md) | 模块总览、分层架构、依赖关系 |
|
||||
| [安全架构](./安全架构.md) | 认证方案、数据安全 |
|
||||
| [解析器设计](./parser/概览.md) | Qfod/Ploss 解析器详细设计 |
|
||||
| [数据处理流程](./数据处理流程.md) | 解析流程、批量写入、错误处理 |
|
||||
| [配置管理](./配置管理.md) | 配置文件、配置类定义 |
|
||||
| [CLI 命令设计](./CLI命令设计.md) | 命令行接口定义 |
|
||||
| [数据库切换支持](./database/概览.md) | SQLite/PostgreSQL 切换方案 |
|
||||
|
||||
---
|
||||
|
||||
## 架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 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 |
|
||||
| 数据库 | SQLite(当前)/ PostgreSQL(后续),配置切换 |
|
||||
| 解析模式 | 批处理,正则表达式匹配 |
|
||||
| ORM 模式 | Code First,支持迁移自动化 |
|
||||
| CLI 框架 | System.CommandLine,命令模式 |
|
||||
|
||||
---
|
||||
|
||||
## 假设/歧义标注
|
||||
|
||||
| 标注 | 内容 |
|
||||
|------|------|
|
||||
| [A] | 使用 .NET 8.0 LTS 版本 |
|
||||
| [A] | 初期使用 SQLite,架构支持切换 PostgreSQL |
|
||||
| [?] | 增量解析策略需确认 |
|
||||
| [?] | 多次解析同一文件的处理策略需确认 |
|
||||
143
docs/architecture/模块划分.md
Normal file
143
docs/architecture/模块划分.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# 模块划分
|
||||
|
||||
> 所属模块:[架构概览](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块总览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 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 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块职责
|
||||
|
||||
| 模块名 | 职责 | 依赖模块 |
|
||||
|--------|------|----------|
|
||||
| CLI Entry | 接收命令行参数、调度解析流程 | ParseService, Infrastructure |
|
||||
| ParseService | 协调解析流程、批量写入、统计更新 | Parser Layer, DbContext, Infrastructure |
|
||||
| LogFileReader | 读取日志文件、行过滤、文件遍历 | 无 |
|
||||
| QfodParser | 解析 Qfod 格式日志,返回 Record | LogFileReader |
|
||||
| PlossParser | 解析 Ploss 格式日志,返回 Record | LogFileReader |
|
||||
| Models | 定义数据实体结构 | 无 |
|
||||
| WctMinerDbContext | EF Core 数据库上下文 | Models |
|
||||
| Configuration | 加载配置文件 | 无 |
|
||||
| Serilog | 结构化日志输出 | 无 |
|
||||
| ErrorHandlingService | 解析错误记录、错误统计 | 无 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 分层架构
|
||||
|
||||
### 3.1 分层定义
|
||||
|
||||
| 层级 | 职责 | 允许调用 |
|
||||
|------|------|----------|
|
||||
| CLI Entry | 接收参数、流程调度、结果输出 | Service, Infrastructure |
|
||||
| Service | 协调解析流程、批量写入、统计管理 | Parser, Data, Infrastructure |
|
||||
| Parser | 日志解析、数据提取、返回 Record | 无 |
|
||||
| Data | 数据持久化、DbContext 管理 | Infrastructure |
|
||||
| Infrastructure | 配置、日志、错误处理 | 无 |
|
||||
|
||||
### 3.2 依赖规则
|
||||
|
||||
- 上层可调用下层
|
||||
- 下层不可调用上层
|
||||
- Parser 之间不可互相调用(独立解析器)
|
||||
- Service 层协调 Parser 和 Data 层
|
||||
|
||||
---
|
||||
|
||||
## 4. 模块依赖图
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
CLI[CLI Entry] --> PS[ParseService]
|
||||
CLI --> Serilog
|
||||
CLI --> EHS[ErrorHandlingService]
|
||||
|
||||
PS --> QP[QfodParser]
|
||||
PS --> PP[PlossParser]
|
||||
PS --> DBM[WctMinerDbContext]
|
||||
PS --> EHS
|
||||
PS --> Serilog
|
||||
PS --> Config[Configuration]
|
||||
|
||||
QP --> LFR[LogFileReader]
|
||||
PP --> LFR
|
||||
|
||||
DBM --> Models[Models]
|
||||
|
||||
subgraph Parser Layer
|
||||
QP
|
||||
PP
|
||||
LFR
|
||||
end
|
||||
|
||||
subgraph Data Layer
|
||||
DBM
|
||||
Models
|
||||
end
|
||||
|
||||
subgraph Infrastructure
|
||||
Config
|
||||
Serilog
|
||||
EHS
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 目录结构
|
||||
|
||||
```
|
||||
src/WCTDataMiner/
|
||||
├── Parsers/
|
||||
│ ├── IParser.cs
|
||||
│ ├── QfodParser.cs
|
||||
│ ├── PlossParser.cs
|
||||
│ └── LogFileReader.cs
|
||||
├── Models/
|
||||
│ ├── LogFile.cs
|
||||
│ ├── QfodRecord.cs
|
||||
│ ├── PlossRecord.cs
|
||||
│ ├── ParseError.cs
|
||||
│ └── ParseStatus.cs
|
||||
├── Data/
|
||||
│ ├── WctMinerDbContext.cs
|
||||
│ └── IDbContextFactory.cs
|
||||
├── Services/
|
||||
│ ├── ParseService.cs
|
||||
│ ├── ThresholdCalculator.cs
|
||||
│ └── ErrorHandlingService.cs
|
||||
├── Configuration/
|
||||
│ ├── AppSettings.cs
|
||||
│ └── appsettings.json
|
||||
├── Commands/
|
||||
│ ├── ParseCommand.cs
|
||||
│ ├── StatsCommand.cs
|
||||
│ ├── ExportCommand.cs
|
||||
│ └── CleanCommand.cs
|
||||
└── Program.cs
|
||||
```
|
||||
105
docs/architecture/配置管理.md
Normal file
105
docs/architecture/配置管理.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# 配置管理
|
||||
|
||||
> 所属模块:[架构概览](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 配置文件结构
|
||||
|
||||
```json
|
||||
// Configuration/appsettings.json
|
||||
{
|
||||
"Database": {
|
||||
"Type": "sqlite",
|
||||
"Path": "./data/database/wctminer.db"
|
||||
},
|
||||
"Parser": {
|
||||
"BatchSize": 1000
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"WCTDataMiner": "Debug"
|
||||
}
|
||||
},
|
||||
"Paths": {
|
||||
"LogDir": "./data/logs",
|
||||
"OutputDir": "./data/output"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 配置类定义
|
||||
|
||||
```csharp
|
||||
// Configuration/AppSettings.cs
|
||||
namespace WCTDataMiner.Configuration;
|
||||
|
||||
public class AppSettings
|
||||
{
|
||||
public DatabaseSettings Database { get; set; } = new();
|
||||
public ParserSettings Parser { get; set; } = new();
|
||||
public PathSettings Paths { get; set; } = new();
|
||||
}
|
||||
|
||||
public class DatabaseSettings
|
||||
{
|
||||
public string Type { get; set; } = "sqlite";
|
||||
public string Path { get; set; } = "./data/database/wctminer.db";
|
||||
|
||||
// PostgreSQL 配置(可选)
|
||||
public string? Host { get; set; }
|
||||
public int Port { get; set; } = 5432;
|
||||
public string? Name { get; set; }
|
||||
public string? User { get; set; }
|
||||
public string? Password { get; set; }
|
||||
}
|
||||
|
||||
public class ParserSettings
|
||||
{
|
||||
public int BatchSize { get; set; } = 1000;
|
||||
}
|
||||
|
||||
public class PathSettings
|
||||
{
|
||||
public string LogDir { get; set; } = "./data/logs";
|
||||
public string OutputDir { get; set; } = "./data/output";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 配置加载
|
||||
|
||||
```csharp
|
||||
// Program.cs 配置加载部分
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using WCTDataMiner.Configuration;
|
||||
|
||||
var builder = Host.CreateDefaultBuilder(args);
|
||||
|
||||
builder.ConfigureAppConfiguration(config =>
|
||||
{
|
||||
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>();
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user