216 lines
6.3 KiB
Markdown
216 lines
6.3 KiB
Markdown
# 数据处理流程
|
||
|
||
> 所属模块:[架构概览](./概览.md)
|
||
|
||
---
|
||
|
||
## 1. 完整解析流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant CLI as CLI Entry
|
||
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
|
||
|
||
CLI->>PS: 扫描日志目录
|
||
PS-->>CLI: 返回文件列表
|
||
|
||
loop 每个日志文件
|
||
CLI->>PS: ParseFileAsync(filePath)
|
||
|
||
PS->>FNP: 解析文件名
|
||
FNP-->>PS: 场景信息(面板、版本、RX类型等)
|
||
|
||
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)
|
||
end
|
||
end
|
||
|
||
PS->>DB: BATCH INSERT QfodRecords
|
||
PS->>DB: BATCH INSERT PlossRecords
|
||
PS->>DB: UPDATE scenario (qfod_count, ploss_count)
|
||
|
||
PS-->>CLI: 返回 ParseReport
|
||
end
|
||
|
||
CLI-->>CLI: 输出解析报告
|
||
```
|
||
|
||
---
|
||
|
||
## 2. ParseService 实现
|
||
|
||
```csharp
|
||
// Services/ParseService.cs
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Serilog;
|
||
using WCTDataMiner.Data;
|
||
using WCTDataMiner.Models;
|
||
using WCTDataMiner.Parsers;
|
||
|
||
namespace WCTDataMiner.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);
|
||
``` |