# 数据处理流程 > 所属模块:[架构概览](./概览.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 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(); var plossRecords = new List(); 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 qfodRecords, List 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); ```