feat: 实现 FOD 日志数据采集 CLI 应用
This commit is contained in:
123
docs/architecture/parser/FileNameParser.md
Normal file
123
docs/architecture/parser/FileNameParser.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# 文件名解析器
|
||||
|
||||
> 所属模块:[解析器设计](./概览.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 文件名格式
|
||||
|
||||
**格式约定**:`TX面板-TX硬件-TX软件-RX类型-测试目的-年月日-测试次数.log`
|
||||
|
||||
**分隔符**:支持 `-`(短横线)和 `_`(下划线)两种分隔符,文件名解析时将同时拆分这两种字符。
|
||||
|
||||
> ⚠️ **注意**:字段值内部不应包含分隔符字符。例如,若 TX面板值为 `single-mold`,解析时会拆分为 `single` 和 `mold` 两个字段,导致解析失败。请确保字段值本身不含 `-` 或 `_`。
|
||||
|
||||
**示例**:
|
||||
- 标准:`singleMold-v1.0-hex2_1-iPhone15-compatibility-20260702-1.log`(字段值无分隔符)
|
||||
- 避免:`single-mold-v1.0-hex2.1-iPhone15-兼容性测试-20260702-1.log`(`single-mold` 会拆分)
|
||||
|
||||
**位置说明**:表格中的"位置"采用 1-based 索引(从 1 开始计数),与代码中的 0-based 数组索引对应。
|
||||
|
||||
---
|
||||
|
||||
## 2. 字段映射
|
||||
|
||||
| 位置 | 字段 | 对应维度表 | 示例值 |
|
||||
|------|------|------------|--------|
|
||||
| 1 | TxPanel | tx_panel.name | single-mold |
|
||||
| 2 | TxHardware | tx_hardware.version | v1.0 |
|
||||
| 3 | TxSoftware | tx_software.version | hex2.1 |
|
||||
| 4 | RxType | rx_type.name | iPhone15 |
|
||||
| 5 | TestPurpose | test_scenario.test_purpose | 兼容性测试 |
|
||||
| 6 | TestDate | test_scenario.test_date | 20260702 |
|
||||
| 7 | TestSequence | test_scenario.test_sequence | 1 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 解析器实现
|
||||
|
||||
```csharp
|
||||
// Parsers/FileNameParser.cs
|
||||
using System.Globalization;
|
||||
using WCTDataMiner.Services;
|
||||
|
||||
namespace WCTDataMiner.Parsers;
|
||||
|
||||
public class FileNameParser
|
||||
{
|
||||
private static readonly char[] Separators = { '-', '_' };
|
||||
|
||||
/// <summary>
|
||||
/// 解析日志文件名,提取场景信息
|
||||
/// </summary>
|
||||
/// <param name="fileName">文件名(不含路径)</param>
|
||||
/// <returns>场景信息,若格式不匹配返回 null</returns>
|
||||
public ScenarioInfo? Parse(string fileName)
|
||||
{
|
||||
// 移除 .log 扩展名
|
||||
var nameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
|
||||
if (string.IsNullOrEmpty(nameWithoutExt))
|
||||
return null;
|
||||
|
||||
var parts = nameWithoutExt.Split(Separators);
|
||||
if (parts.Length < 7)
|
||||
return null;
|
||||
|
||||
// 解析日期
|
||||
if (!TryParseDate(parts[5], out var testDate))
|
||||
return null;
|
||||
|
||||
// 解析测试序号
|
||||
if (!int.TryParse(parts[6], out var testSequence))
|
||||
testSequence = 1;
|
||||
|
||||
return new ScenarioInfo(
|
||||
TxPanel: parts[0],
|
||||
TxHardware: parts[1],
|
||||
TxSoftware: parts[2],
|
||||
RxType: parts[3],
|
||||
TestPurpose: parts.Length > 4 ? parts[4] : null,
|
||||
TestDate: testDate,
|
||||
TestSequence: testSequence
|
||||
);
|
||||
}
|
||||
|
||||
private static bool TryParseDate(string dateStr, out DateOnly date)
|
||||
{
|
||||
// 支持多种日期格式
|
||||
var formats = new[] { "yyyyMMdd", "yyyy-MM-dd", "yyMMdd" };
|
||||
return DateOnly.TryParseExact(dateStr, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out date);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 容错处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|----------|
|
||||
| 文件名为空或 null | 返回 null,ParseService 记录错误日志 |
|
||||
| 文件名仅含扩展名(如 `.log`) | 返回 null |
|
||||
| 文件名少于 7 个字段 | 返回 null,缺少必要字段 |
|
||||
| 日期格式不匹配 | 返回 null,无法解析 test_date |
|
||||
| 测试序号解析失败 | 使用默认值 1 |
|
||||
| 文件名扩展名无效(非 `.log`) | 仍会解析,仅去除扩展名后尝试 |
|
||||
|
||||
> **说明**:由于解析器要求 `parts.Length >= 7` 才会成功返回,"测试目的缺失"不会单独发生——当字段不足时整体解析失败。若需支持可选字段,需调整解析逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 5. 使用示例
|
||||
|
||||
```csharp
|
||||
// 在 ParseService 中使用
|
||||
var fileNameParser = new FileNameParser();
|
||||
var scenarioInfo = fileNameParser.Parse("single-mold-v1.0-hex2.1-iPhone15-兼容性测试-20260702-1.log");
|
||||
|
||||
if (scenarioInfo != null)
|
||||
{
|
||||
var scenario = await scenarioService.GetOrCreateScenarioAsync(scenarioInfo);
|
||||
// 继续解析文件内容...
|
||||
}
|
||||
```
|
||||
@@ -36,10 +36,13 @@
|
||||
|
||||
## 3. 正则表达式
|
||||
|
||||
> **修正说明**:原设计稿正则只有2个忽略字段,实际格式有3个X(忽略字段),已修正。
|
||||
|
||||
```csharp
|
||||
// Parsers/PlossParser.cs
|
||||
// 格式: FOD-> (1) X X X (2) (3) ... (13),其中3个X需要3个 \d+ 忽略
|
||||
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+)$",
|
||||
@"^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+)\s+(\d+)$",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
```
|
||||
@@ -82,7 +85,7 @@ public class PlossParser : IParser<PlossRecord>
|
||||
|
||||
public bool CanParse(string line) => PlossPattern.IsMatch(line);
|
||||
|
||||
public ParseResult<PlossRecord> Parse(string line, int lineNumber, Guid logFileId)
|
||||
public ParseResult<PlossRecord> Parse(string line, Guid scenarioId)
|
||||
{
|
||||
var match = PlossPattern.Match(line);
|
||||
if (!match.Success)
|
||||
@@ -97,8 +100,7 @@ public class PlossParser : IParser<PlossRecord>
|
||||
{
|
||||
var record = new PlossRecord
|
||||
{
|
||||
LogFileId = logFileId,
|
||||
LineNumber = lineNumber,
|
||||
ScenarioId = scenarioId,
|
||||
RxType = byte.Parse(match.Groups[1].Value),
|
||||
RxPower = short.Parse(match.Groups[2].Value),
|
||||
TxPower = short.Parse(match.Groups[3].Value),
|
||||
|
||||
@@ -66,7 +66,7 @@ public class QfodParser : IParser<QfodRecord>
|
||||
|
||||
public bool CanParse(string line) => QfodPattern.IsMatch(line);
|
||||
|
||||
public ParseResult<QfodRecord> Parse(string line, int lineNumber, Guid logFileId)
|
||||
public ParseResult<QfodRecord> Parse(string line, Guid scenarioId)
|
||||
{
|
||||
var match = QfodPattern.Match(line);
|
||||
if (!match.Success)
|
||||
@@ -81,8 +81,7 @@ public class QfodParser : IParser<QfodRecord>
|
||||
{
|
||||
var record = new QfodRecord
|
||||
{
|
||||
LogFileId = logFileId,
|
||||
LineNumber = lineNumber,
|
||||
ScenarioId = scenarioId,
|
||||
ChargerIndex = byte.Parse(match.Groups[1].Value),
|
||||
CoilIndex = byte.Parse(match.Groups[2].Value),
|
||||
DeltaQ = int.Parse(match.Groups[3].Value),
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [文件名解析器](./FileNameParser.md) | 从文件名提取场景信息 |
|
||||
| [Qfod 解析器](./Qfod解析器.md) | Qfod 格式日志解析设计 |
|
||||
| [Ploss 解析器](./Ploss解析器.md) | Ploss FOD 格式日志解析设计 |
|
||||
|
||||
@@ -29,7 +30,9 @@ public interface IParser<TRecord>
|
||||
/// <summary>
|
||||
/// 解析日志行
|
||||
/// </summary>
|
||||
ParseResult<TRecord> Parse(string line, int lineNumber, Guid logFileId);
|
||||
/// <param name="line">日志行内容</param>
|
||||
/// <param name="scenarioId">关联的测试场景ID,用于外键关联(由 ParseService 从 FileNameParser 获取后传入)</param>
|
||||
ParseResult<TRecord> Parse(string line, Guid scenarioId);
|
||||
}
|
||||
|
||||
public record ParseResult<TRecord>
|
||||
@@ -75,4 +78,5 @@ flowchart TD
|
||||
|-----------|------|----------|
|
||||
| FORMAT_MISMATCH | 行格式不匹配正则表达式 | 跳过该行,记录原始内容 |
|
||||
| TYPE_CONVERSION | 字段类型转换失败 | 跳过该行,记录错误字段 |
|
||||
| VALUE_OUT_OF_RANGE | 字段值超出有效范围 | 使用边界值或跳过 |
|
||||
|
||||
> **说明**:VALUE_OUT_OF_RANGE 错误类型暂未实现,后续可根据需要添加字段值范围校验逻辑。
|
||||
@@ -10,47 +10,52 @@
|
||||
sequenceDiagram
|
||||
participant CLI as CLI Entry
|
||||
participant PS as ParseService
|
||||
participant LR as LogFileReader
|
||||
participant SS as ScenarioService
|
||||
participant FNP as FileNameParser
|
||||
participant QP as QfodParser
|
||||
participant PP as PlossParser
|
||||
participant EHS as ErrorHandlingService
|
||||
participant DB as Database
|
||||
participant Log as Serilog
|
||||
|
||||
CLI->>PS: 扫描日志目录
|
||||
PS-->>CLI: 返回文件列表
|
||||
|
||||
CLI->>LR: 扫描日志目录
|
||||
LR-->>CLI: 返回文件列表
|
||||
|
||||
loop 每个日志文件
|
||||
CLI->>PS: ParseFileAsync(filePath)
|
||||
|
||||
PS->>DB: INSERT log_file (status=processing)
|
||||
DB-->>PS: 返回 file_id
|
||||
|
||||
PS->>LR: 读取文件内容
|
||||
LR-->>PS: 返回行列表
|
||||
|
||||
|
||||
PS->>FNP: 解析文件名
|
||||
FNP-->>PS: 场景信息(面板、版本、RX类型等)
|
||||
|
||||
PS->>SS: GetOrCreateScenarioAsync(场景信息)
|
||||
SS->>DB: 查询/创建维度记录
|
||||
SS->>DB: 查询/创建场景记录
|
||||
SS-->>PS: 返回 TestScenario
|
||||
|
||||
PS->>PS: 读取文件内容
|
||||
|
||||
loop 逐行遍历
|
||||
alt 是 Qfod 行
|
||||
PS->>QP: Parse(line)
|
||||
PS->>QP: Parse(line, scenarioId)
|
||||
QP-->>PS: ParseResult
|
||||
else 是 Ploss 行
|
||||
PS->>PP: Parse(line)
|
||||
PS->>PP: Parse(line, scenarioId)
|
||||
PP-->>PS: ParseResult
|
||||
end
|
||||
|
||||
|
||||
alt 解析成功
|
||||
PS->>PS: 加入记录列表
|
||||
else 解析失败
|
||||
PS->>EHS: RecordError(line, error)
|
||||
PS->>Log: LogError(line, error)
|
||||
end
|
||||
end
|
||||
|
||||
PS->>DB: BATCH INSERT records
|
||||
PS->>DB: BATCH INSERT errors
|
||||
PS->>DB: UPDATE log_file (counts, status=completed)
|
||||
|
||||
|
||||
PS->>DB: BATCH INSERT QfodRecords
|
||||
PS->>DB: BATCH INSERT PlossRecords
|
||||
PS->>DB: UPDATE scenario (qfod_count, ploss_count)
|
||||
|
||||
PS-->>CLI: 返回 ParseReport
|
||||
end
|
||||
|
||||
|
||||
CLI-->>CLI: 输出解析报告
|
||||
```
|
||||
|
||||
@@ -61,6 +66,7 @@ sequenceDiagram
|
||||
```csharp
|
||||
// Services/ParseService.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Serilog;
|
||||
using WCTDataMiner.Data;
|
||||
using WCTDataMiner.Models;
|
||||
using WCTDataMiner.Parsers;
|
||||
@@ -70,36 +76,47 @@ namespace WCTDataMiner.Services;
|
||||
public class ParseService
|
||||
{
|
||||
private readonly WctMinerDbContext _context;
|
||||
private readonly ScenarioService _scenarioService;
|
||||
private readonly QfodParser _qfodParser;
|
||||
private readonly PlossParser _plossParser;
|
||||
private readonly ErrorHandlingService _errorService;
|
||||
private readonly ILogger<ParseService> _logger;
|
||||
private readonly FileNameParser _fileNameParser;
|
||||
private readonly int _batchSize;
|
||||
|
||||
public ParseService(
|
||||
WctMinerDbContext context,
|
||||
ScenarioService scenarioService,
|
||||
QfodParser qfodParser,
|
||||
PlossParser plossParser,
|
||||
ErrorHandlingService errorService,
|
||||
ILogger<ParseService> logger,
|
||||
FileNameParser fileNameParser,
|
||||
IConfiguration config)
|
||||
{
|
||||
_context = context;
|
||||
_scenarioService = scenarioService;
|
||||
_qfodParser = qfodParser;
|
||||
_plossParser = plossParser;
|
||||
_errorService = errorService;
|
||||
_logger = logger;
|
||||
_fileNameParser = fileNameParser;
|
||||
_batchSize = config.GetValue("Parser:BatchSize", 1000);
|
||||
}
|
||||
|
||||
public async Task<ParseReport> ParseFileAsync(string filePath)
|
||||
{
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
var logFile = await CreateLogFileRecordAsync(fileInfo);
|
||||
|
||||
|
||||
// 解析文件名获取场景信息
|
||||
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++)
|
||||
{
|
||||
@@ -108,53 +125,45 @@ public class ParseService
|
||||
|
||||
if (_qfodParser.CanParse(line))
|
||||
{
|
||||
var result = _qfodParser.Parse(line, lineNumber, logFile.Id);
|
||||
var result = _qfodParser.Parse(line, scenario.Id);
|
||||
if (result.IsSuccess)
|
||||
qfodRecords.Add(result.Record!);
|
||||
else
|
||||
_errorService.RecordError(logFile.Id, lineNumber, line, result.ErrorType!, result.ErrorMessage!);
|
||||
{
|
||||
Log.Error("Qfod解析失败 [{Line}] {Error}: {Message}",
|
||||
lineNumber, result.ErrorType, result.ErrorMessage);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
else if (_plossParser.CanParse(line))
|
||||
{
|
||||
var result = _plossParser.Parse(line, lineNumber, logFile.Id);
|
||||
var result = _plossParser.Parse(line, scenario.Id);
|
||||
if (result.IsSuccess)
|
||||
plossRecords.Add(result.Record!);
|
||||
else
|
||||
_errorService.RecordError(logFile.Id, lineNumber, line, result.ErrorType!, result.ErrorMessage!);
|
||||
{
|
||||
Log.Error("Ploss解析失败 [{Line}] {Error}: {Message}",
|
||||
lineNumber, result.ErrorType, result.ErrorMessage);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量保存
|
||||
await BatchInsertAsync(qfodRecords, plossRecords, _errorService.GetErrors());
|
||||
|
||||
// 更新统计
|
||||
await UpdateLogFileStatsAsync(logFile.Id, qfodRecords.Count, plossRecords.Count, _errorService.ErrorCount);
|
||||
await BatchInsertAsync(qfodRecords, plossRecords);
|
||||
|
||||
return new ParseReport(fileInfo.Name, qfodRecords.Count, plossRecords.Count, _errorService.ErrorCount);
|
||||
}
|
||||
// 更新场景统计
|
||||
await UpdateScenarioStatsAsync(scenario.Id, qfodRecords.Count, plossRecords.Count);
|
||||
|
||||
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;
|
||||
return new ParseReport(fileInfo.Name, qfodRecords.Count, plossRecords.Count, errorCount, null);
|
||||
}
|
||||
|
||||
private async Task BatchInsertAsync(
|
||||
List<QfodRecord> qfodRecords,
|
||||
List<PlossRecord> plossRecords,
|
||||
List<ParseError> errors)
|
||||
List<PlossRecord> plossRecords)
|
||||
{
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// 批量插入 QfodRecord
|
||||
@@ -171,13 +180,6 @@ public class ParseService
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// 批量插入 ParseError
|
||||
foreach (var batch in errors.Chunk(_batchSize))
|
||||
{
|
||||
_context.ParseErrors.AddRange(batch);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch
|
||||
@@ -187,19 +189,28 @@ public class ParseService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateLogFileStatsAsync(Guid logFileId, int qfodCount, int plossCount, int errorCount)
|
||||
private async Task UpdateScenarioStatsAsync(Guid scenarioId, int qfodCount, int plossCount)
|
||||
{
|
||||
var logFile = await _context.LogFiles.FindAsync(logFileId);
|
||||
if (logFile != null)
|
||||
// 使用累加更新(+=),支持同一场景的多次解析
|
||||
// 若需幂等解析,应在 ParseFileAsync 开始时检查场景是否已处理
|
||||
var scenario = await _context.TestScenarios.FindAsync(scenarioId);
|
||||
if (scenario != null)
|
||||
{
|
||||
logFile.QfodCount = qfodCount;
|
||||
logFile.PlossCount = plossCount;
|
||||
logFile.ErrorCount = errorCount;
|
||||
logFile.Status = ParseStatus.Completed;
|
||||
scenario.QfodCount += qfodCount;
|
||||
scenario.PlossCount += plossCount;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record ParseReport(string FileName, int QfodCount, int PlossCount, int ErrorCount);
|
||||
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);
|
||||
```
|
||||
@@ -13,11 +13,11 @@
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Service Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ParseService │ ErrorHandlingService │ ThresholdCalc │
|
||||
│ ParseService │ ScenarioService │ DimensionService │ ThresholdCalc │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Parser Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ QfodParser │ PlossParser │ LogFileReader │
|
||||
│ QfodParser │ PlossParser │ FileNameParser │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Data Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
@@ -36,15 +36,18 @@
|
||||
| 模块名 | 职责 | 依赖模块 |
|
||||
|--------|------|----------|
|
||||
| CLI Entry | 接收命令行参数、调度解析流程 | ParseService, Infrastructure |
|
||||
| ParseService | 协调解析流程、批量写入、统计更新 | Parser Layer, DbContext, Infrastructure |
|
||||
| ParseService | 协调解析流程、批量写入、场景关联 | Parser Layer, ScenarioService, DbContext |
|
||||
| ScenarioService | 创建/查询测试场景,解析文件名获取场景信息 | DimensionService, DbContext |
|
||||
| DimensionService | 维度表 CRUD(获取或创建维度记录) | DbContext |
|
||||
| FileNameParser | 解析文件名提取场景信息(面板、版本、RX类型等) | 无 |
|
||||
| LogFileReader | 读取日志文件、行过滤、文件遍历 | 无 |
|
||||
| QfodParser | 解析 Qfod 格式日志,返回 Record | LogFileReader |
|
||||
| PlossParser | 解析 Ploss 格式日志,返回 Record | LogFileReader |
|
||||
| Models | 定义数据实体结构 | 无 |
|
||||
| WctMinerDbContext | EF Core 数据库上下文 | Models |
|
||||
| QfodParser | 解析 Qfod 格式日志,返回 QfodRecord | 无 |
|
||||
| PlossParser | 解析 Ploss 格式日志,返回 PlossRecord | 无 |
|
||||
| Models | 定义数据实体结构(维度表 + 事实表) | 无 |
|
||||
| WctMinerDbContext | EF Core 数据库上下文 | Models, Configurations |
|
||||
| Configuration | 加载配置文件 | 无 |
|
||||
| Serilog | 结构化日志输出 | 无 |
|
||||
| ErrorHandlingService | 解析错误记录、错误统计 | 无 |
|
||||
| Serilog | 结构化日志输出(含解析错误日志) | 无 |
|
||||
| ThresholdCalculator | 根据 RxPower 计算动态阈值 | 无 |
|
||||
|
||||
---
|
||||
|
||||
@@ -55,10 +58,10 @@
|
||||
| 层级 | 职责 | 允许调用 |
|
||||
|------|------|----------|
|
||||
| CLI Entry | 接收参数、流程调度、结果输出 | Service, Infrastructure |
|
||||
| Service | 协调解析流程、批量写入、统计管理 | Parser, Data, Infrastructure |
|
||||
| Parser | 日志解析、数据提取、返回 Record | 无 |
|
||||
| Data | 数据持久化、DbContext 管理 | Infrastructure |
|
||||
| Infrastructure | 配置、日志、错误处理 | 无 |
|
||||
| Service | 协调解析流程、场景管理、维度管理、批量写入 | Parser, Data, Infrastructure |
|
||||
| Parser | 日志解析、文件名解析、数据提取 | 无 |
|
||||
| Data | 数据持久化、DbContext 管理、实体配置 | Infrastructure |
|
||||
| Infrastructure | 配置、日志 | 无 |
|
||||
|
||||
### 3.2 依赖规则
|
||||
|
||||
@@ -66,6 +69,7 @@
|
||||
- 下层不可调用上层
|
||||
- Parser 之间不可互相调用(独立解析器)
|
||||
- Service 层协调 Parser 和 Data 层
|
||||
- 解析错误通过 Serilog 记录,不存入业务数据库
|
||||
|
||||
---
|
||||
|
||||
@@ -75,35 +79,46 @@
|
||||
graph TD
|
||||
CLI[CLI Entry] --> PS[ParseService]
|
||||
CLI --> Serilog
|
||||
CLI --> EHS[ErrorHandlingService]
|
||||
|
||||
PS --> SS[ScenarioService]
|
||||
PS --> QP[QfodParser]
|
||||
PS --> PP[PlossParser]
|
||||
PS --> DBM[WctMinerDbContext]
|
||||
PS --> EHS
|
||||
PS --> Serilog
|
||||
PS --> Config[Configuration]
|
||||
|
||||
QP --> LFR[LogFileReader]
|
||||
PP --> LFR
|
||||
SS --> DS[DimensionService]
|
||||
SS --> FNP[FileNameParser]
|
||||
SS --> DBM
|
||||
|
||||
DS --> DBM
|
||||
|
||||
QP --> Serilog
|
||||
PP --> Serilog
|
||||
FNP --> Serilog
|
||||
|
||||
DBM --> Models[Models]
|
||||
DBM --> Configs[Configurations]
|
||||
|
||||
subgraph Parser Layer
|
||||
QP
|
||||
PP
|
||||
LFR
|
||||
FNP
|
||||
end
|
||||
|
||||
subgraph Data Layer
|
||||
DBM
|
||||
Models
|
||||
Configs
|
||||
end
|
||||
|
||||
subgraph Infrastructure
|
||||
Config
|
||||
Serilog
|
||||
EHS
|
||||
end
|
||||
|
||||
subgraph Service Layer
|
||||
PS
|
||||
SS
|
||||
DS
|
||||
end
|
||||
```
|
||||
|
||||
@@ -117,20 +132,32 @@ src/WCTDataMiner/
|
||||
│ ├── IParser.cs
|
||||
│ ├── QfodParser.cs
|
||||
│ ├── PlossParser.cs
|
||||
│ └── LogFileReader.cs
|
||||
│ ├── FileNameParser.cs # 解析文件名获取场景信息(Parser Layer)
|
||||
│ └── LogFileReader.cs # 读取日志文件(可选)
|
||||
├── Models/
|
||||
│ ├── LogFile.cs
|
||||
│ ├── QfodRecord.cs
|
||||
│ ├── PlossRecord.cs
|
||||
│ ├── ParseError.cs
|
||||
│ └── ParseStatus.cs
|
||||
│ ├── 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
|
||||
│ ├── IDbContextFactory.cs
|
||||
│ └── Configurations/ # IEntityTypeConfiguration 配置类
|
||||
│ ├── TxPanelConfiguration.cs
|
||||
│ ├── TxHardwareConfiguration.cs
|
||||
│ ├── TxSoftwareConfiguration.cs
|
||||
│ ├── RxTypeConfiguration.cs
|
||||
│ ├── TestScenarioConfiguration.cs
|
||||
│ ├── QfodRecordConfiguration.cs
|
||||
│ └── PlossRecordConfiguration.cs
|
||||
├── Services/
|
||||
│ ├── ParseService.cs
|
||||
│ ├── ThresholdCalculator.cs
|
||||
│ └── ErrorHandlingService.cs
|
||||
│ ├── ParseService.cs # 解析流程协调
|
||||
│ ├── ScenarioService.cs # 场景创建/查询
|
||||
│ ├── DimensionService.cs # 维度表管理
|
||||
│ └── ThresholdCalculator.cs # 阈值计算
|
||||
├── Configuration/
|
||||
│ ├── AppSettings.cs
|
||||
│ └── appsettings.json
|
||||
|
||||
Reference in New Issue
Block a user