2026-07-02 15:53:30 +08:00
|
|
|
|
# 数据处理流程
|
|
|
|
|
|
|
|
|
|
|
|
> 所属模块:[架构概览](./概览.md)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 完整解析流程
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant CLI as CLI Entry
|
|
|
|
|
|
participant PS as ParseService
|
2026-07-02 18:13:56 +08:00
|
|
|
|
participant SS as ScenarioService
|
|
|
|
|
|
participant FNP as FileNameParser
|
2026-07-02 15:53:30 +08:00
|
|
|
|
participant QP as QfodParser
|
|
|
|
|
|
participant PP as PlossParser
|
|
|
|
|
|
participant DB as Database
|
2026-07-02 18:13:56 +08:00
|
|
|
|
participant Log as Serilog
|
|
|
|
|
|
|
|
|
|
|
|
CLI->>PS: 扫描日志目录
|
|
|
|
|
|
PS-->>CLI: 返回文件列表
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
|
|
|
|
|
loop 每个日志文件
|
|
|
|
|
|
CLI->>PS: ParseFileAsync(filePath)
|
2026-07-02 18:13:56 +08:00
|
|
|
|
|
|
|
|
|
|
PS->>FNP: 解析文件名
|
|
|
|
|
|
FNP-->>PS: 场景信息(面板、版本、RX类型等)
|
|
|
|
|
|
|
|
|
|
|
|
PS->>SS: GetOrCreateScenarioAsync(场景信息)
|
|
|
|
|
|
SS->>DB: 查询/创建维度记录
|
|
|
|
|
|
SS->>DB: 查询/创建场景记录
|
|
|
|
|
|
SS-->>PS: 返回 TestScenario
|
|
|
|
|
|
|
|
|
|
|
|
PS->>PS: 读取文件内容
|
|
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
loop 逐行遍历
|
|
|
|
|
|
alt 是 Qfod 行
|
2026-07-02 18:13:56 +08:00
|
|
|
|
PS->>QP: Parse(line, scenarioId)
|
2026-07-02 15:53:30 +08:00
|
|
|
|
QP-->>PS: ParseResult
|
|
|
|
|
|
else 是 Ploss 行
|
2026-07-02 18:13:56 +08:00
|
|
|
|
PS->>PP: Parse(line, scenarioId)
|
2026-07-02 15:53:30 +08:00
|
|
|
|
PP-->>PS: ParseResult
|
|
|
|
|
|
end
|
2026-07-02 18:13:56 +08:00
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
alt 解析成功
|
|
|
|
|
|
PS->>PS: 加入记录列表
|
|
|
|
|
|
else 解析失败
|
2026-07-02 18:13:56 +08:00
|
|
|
|
PS->>Log: LogError(line, error)
|
2026-07-02 15:53:30 +08:00
|
|
|
|
end
|
|
|
|
|
|
end
|
2026-07-02 18:13:56 +08:00
|
|
|
|
|
|
|
|
|
|
PS->>DB: BATCH INSERT QfodRecords
|
|
|
|
|
|
PS->>DB: BATCH INSERT PlossRecords
|
|
|
|
|
|
PS->>DB: UPDATE scenario (qfod_count, ploss_count)
|
|
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
PS-->>CLI: 返回 ParseReport
|
|
|
|
|
|
end
|
2026-07-02 18:13:56 +08:00
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
CLI-->>CLI: 输出解析报告
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. ParseService 实现
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
// Services/ParseService.cs
|
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2026-07-02 18:13:56 +08:00
|
|
|
|
using Serilog;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
using WCTDataMiner.Data;
|
|
|
|
|
|
using WCTDataMiner.Models;
|
|
|
|
|
|
using WCTDataMiner.Parsers;
|
|
|
|
|
|
|
|
|
|
|
|
namespace WCTDataMiner.Services;
|
|
|
|
|
|
|
|
|
|
|
|
public class ParseService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly WctMinerDbContext _context;
|
2026-07-02 18:13:56 +08:00
|
|
|
|
private readonly ScenarioService _scenarioService;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
private readonly QfodParser _qfodParser;
|
|
|
|
|
|
private readonly PlossParser _plossParser;
|
2026-07-02 18:13:56 +08:00
|
|
|
|
private readonly FileNameParser _fileNameParser;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
private readonly int _batchSize;
|
|
|
|
|
|
|
|
|
|
|
|
public ParseService(
|
|
|
|
|
|
WctMinerDbContext context,
|
2026-07-02 18:13:56 +08:00
|
|
|
|
ScenarioService scenarioService,
|
2026-07-02 15:53:30 +08:00
|
|
|
|
QfodParser qfodParser,
|
|
|
|
|
|
PlossParser plossParser,
|
2026-07-02 18:13:56 +08:00
|
|
|
|
FileNameParser fileNameParser,
|
2026-07-02 15:53:30 +08:00
|
|
|
|
IConfiguration config)
|
|
|
|
|
|
{
|
|
|
|
|
|
_context = context;
|
2026-07-02 18:13:56 +08:00
|
|
|
|
_scenarioService = scenarioService;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
_qfodParser = qfodParser;
|
|
|
|
|
|
_plossParser = plossParser;
|
2026-07-02 18:13:56 +08:00
|
|
|
|
_fileNameParser = fileNameParser;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
_batchSize = config.GetValue("Parser:BatchSize", 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ParseReport> ParseFileAsync(string filePath)
|
|
|
|
|
|
{
|
|
|
|
|
|
var fileInfo = new FileInfo(filePath);
|
2026-07-02 18:13:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 解析文件名获取场景信息
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
var lines = await File.ReadAllLinesAsync(filePath);
|
|
|
|
|
|
var qfodRecords = new List<QfodRecord>();
|
|
|
|
|
|
var plossRecords = new List<PlossRecord>();
|
2026-07-02 18:13:56 +08:00
|
|
|
|
int errorCount = 0;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < lines.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var line = lines[i];
|
|
|
|
|
|
var lineNumber = i + 1;
|
|
|
|
|
|
|
|
|
|
|
|
if (_qfodParser.CanParse(line))
|
|
|
|
|
|
{
|
2026-07-02 18:13:56 +08:00
|
|
|
|
var result = _qfodParser.Parse(line, scenario.Id);
|
2026-07-02 15:53:30 +08:00
|
|
|
|
if (result.IsSuccess)
|
|
|
|
|
|
qfodRecords.Add(result.Record!);
|
|
|
|
|
|
else
|
2026-07-02 18:13:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
Log.Error("Qfod解析失败 [{Line}] {Error}: {Message}",
|
|
|
|
|
|
lineNumber, result.ErrorType, result.ErrorMessage);
|
|
|
|
|
|
errorCount++;
|
|
|
|
|
|
}
|
2026-07-02 15:53:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
else if (_plossParser.CanParse(line))
|
|
|
|
|
|
{
|
2026-07-02 18:13:56 +08:00
|
|
|
|
var result = _plossParser.Parse(line, scenario.Id);
|
2026-07-02 15:53:30 +08:00
|
|
|
|
if (result.IsSuccess)
|
|
|
|
|
|
plossRecords.Add(result.Record!);
|
|
|
|
|
|
else
|
2026-07-02 18:13:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
Log.Error("Ploss解析失败 [{Line}] {Error}: {Message}",
|
|
|
|
|
|
lineNumber, result.ErrorType, result.ErrorMessage);
|
|
|
|
|
|
errorCount++;
|
|
|
|
|
|
}
|
2026-07-02 15:53:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量保存
|
2026-07-02 18:13:56 +08:00
|
|
|
|
await BatchInsertAsync(qfodRecords, plossRecords);
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
2026-07-02 18:13:56 +08:00
|
|
|
|
// 更新场景统计
|
|
|
|
|
|
await UpdateScenarioStatsAsync(scenario.Id, qfodRecords.Count, plossRecords.Count);
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
2026-07-02 18:13:56 +08:00
|
|
|
|
return new ParseReport(fileInfo.Name, qfodRecords.Count, plossRecords.Count, errorCount, null);
|
2026-07-02 15:53:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task BatchInsertAsync(
|
|
|
|
|
|
List<QfodRecord> qfodRecords,
|
2026-07-02 18:13:56 +08:00
|
|
|
|
List<PlossRecord> plossRecords)
|
2026-07-02 15:53:30 +08:00
|
|
|
|
{
|
|
|
|
|
|
await using var transaction = await _context.Database.BeginTransactionAsync();
|
2026-07-02 18:13:56 +08:00
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-02 18:13:56 +08:00
|
|
|
|
private async Task UpdateScenarioStatsAsync(Guid scenarioId, int qfodCount, int plossCount)
|
2026-07-02 15:53:30 +08:00
|
|
|
|
{
|
2026-07-02 18:13:56 +08:00
|
|
|
|
// 使用累加更新(+=),支持同一场景的多次解析
|
|
|
|
|
|
// 若需幂等解析,应在 ParseFileAsync 开始时检查场景是否已处理
|
|
|
|
|
|
var scenario = await _context.TestScenarios.FindAsync(scenarioId);
|
|
|
|
|
|
if (scenario != null)
|
2026-07-02 15:53:30 +08:00
|
|
|
|
{
|
2026-07-02 18:13:56 +08:00
|
|
|
|
scenario.QfodCount += qfodCount;
|
|
|
|
|
|
scenario.PlossCount += plossCount;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-02 18:13:56 +08:00
|
|
|
|
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);
|
2026-07-02 15:53:30 +08:00
|
|
|
|
```
|