新增CncReplay重放工具:按时间顺序重放采集日志重新计算产量

main
haoliang 1 month ago
parent cdb88744cf
commit c13ed696aa

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="BusinessConnection" connectionString="Server=localhost;Database=cnc_business;Uid=root;Pwd=root;Charset=utf8mb4;SslMode=None;" />
<add name="LogConnection" connectionString="Server=localhost;Database=cnc_log;Uid=root;Pwd=root;Charset=utf8mb4;SslMode=None;" />
</connectionStrings>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /></startup>
</configuration>

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<RootNamespace>CncReplay</RootNamespace>
<AssemblyName>CncReplay</AssemblyName>
<OutputType>Exe</OutputType>
<OutputPath>bin\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net472" Version="1.0.3" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="MySqlConnector" Version="2.3.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CncModels\CncModels.csproj" />
<ProjectReference Include="..\CncRepository\CncRepository.csproj" />
<ProjectReference Include="..\CncCollector\CncCollector.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,240 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using Dapper;
using MySqlConnector;
using Newtonsoft.Json.Linq;
using CncCollector.Core;
using CncModels.Entity;
namespace CncReplay
{
/// <summary>
/// 采集日志重放工具:按时间顺序重放 log_collect_raw 中的采集日志,
/// 用新的产量跟踪逻辑重新计算产量。
/// </summary>
class Program
{
static void Main(string[] args)
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.WriteLine("========== CNC采集日志重放工具 ==========");
Console.WriteLine();
// 1. 读取连接串
var businessConnStr = ConfigurationManager.ConnectionStrings["BusinessConnection"]?.ConnectionString;
var logConnStr = ConfigurationManager.ConnectionStrings["LogConnection"]?.ConnectionString;
if (string.IsNullOrEmpty(businessConnStr) || string.IsNullOrEmpty(logConnStr))
{
Console.WriteLine("错误未找到数据库连接串配置BusinessConnection / LogConnection。");
Console.WriteLine("请检查 App.config 中的 connectionStrings 配置。");
return;
}
// 2. 查询待重放日志按时间顺序取最近的N条成功记录
List<LogEntry> logs;
using (var conn = new MySqlConnection(logConnStr))
{
conn.Open();
logs = conn.Query<LogEntry>(
"SELECT id AS Id, request_time AS RequestTime, raw_json AS RawJson, collect_address_id AS CollectAddressId " +
"FROM log_collect_raw WHERE is_success = 1 ORDER BY request_time ASC LIMIT 10").AsList();
}
if (logs.Count == 0)
{
Console.WriteLine("未找到待重放的采集日志is_success=1 的记录)。");
return;
}
Console.WriteLine($"找到 {logs.Count} 条待重放日志。");
Console.WriteLine();
// 3. 加载品牌配置 + 字段映射 + 机床列表
Brand brand;
List<BrandFieldMapping> mappings;
List<Machine> machines;
using (var conn = new MySqlConnection(businessConnStr))
{
conn.Open();
// 加载 FANUC 品牌配置id=1
brand = conn.QueryFirstOrDefault<Brand>(
"SELECT id AS Id, brand_name AS BrandName, device_field AS DeviceField, tags_path AS TagsPath, " +
"is_enabled AS IsEnabled, created_at AS CreatedAt, updated_at AS UpdatedAt " +
"FROM cnc_brand WHERE id = 1");
if (brand == null)
{
Console.WriteLine("错误未找到品牌配置cnc_brand.id=1请先配置品牌。");
return;
}
// 加载品牌字段映射
mappings = conn.Query<BrandFieldMapping>(
"SELECT id AS Id, brand_id AS BrandId, standard_field AS StandardField, field_name AS FieldName, " +
"match_by AS MatchBy, data_type AS DataType, is_required AS IsRequired, is_enabled AS IsEnabled, " +
"created_at AS CreatedAt FROM cnc_brand_field_mapping WHERE brand_id = @BrandId",
new { BrandId = brand.Id }).AsList();
if (mappings == null || mappings.Count == 0)
{
Console.WriteLine("警告:未找到品牌字段映射,解析将无法提取数据。");
mappings = new List<BrandFieldMapping>();
}
// 加载所有启用的机床
machines = conn.Query<Machine>(
"SELECT id AS Id, device_code AS DeviceCode, name AS Name, workshop_id AS WorkshopId, " +
"collect_address_id AS CollectAddressId, ip_address AS IpAddress, brand_id AS BrandId, " +
"is_enabled AS IsEnabled, last_ping_time AS LastPingTime, last_collect_time AS LastCollectTime, " +
"last_device_status AS LastDeviceStatus, last_run_status AS LastRunStatus, " +
"last_program_name AS LastProgramName, last_part_count AS LastPartCount, " +
"last_operate_mode AS LastOperateMode, last_machining_status AS LastMachiningStatus, " +
"created_at AS CreatedAt, updated_at AS UpdatedAt " +
"FROM cnc_machine WHERE is_enabled = 1").AsList();
if (machines == null || machines.Count == 0)
{
Console.WriteLine("警告:未找到启用的机床配置。");
machines = new List<Machine>();
}
}
Console.WriteLine($"品牌: {brand.BrandName},字段映射: {mappings.Count}条,机床: {machines.Count}台");
Console.WriteLine();
// 构建 device_code → machine 的查找字典
var machineDict = machines
.Where(m => !string.IsNullOrEmpty(m.DeviceCode))
.ToDictionary(m => m.DeviceCode, StringComparer.OrdinalIgnoreCase);
// 4. 创建产量跟踪器
using (var tracker = new ProductionTracker(businessConnStr))
{
var swTotal = Stopwatch.StartNew();
int processed = 0;
// 5. 逐条重放
foreach (var log in logs)
{
var startTime = DateTime.Now;
var analysisLogs = new List<string>();
analysisLogs.Add("========== CNC采集产量分析日志(重放) ==========");
analysisLogs.Add("原始采集时间:" + log.RequestTime.ToString("yyyy-MM-dd HH:mm:ss"));
JArray devices;
try
{
devices = JArray.Parse(log.RawJson);
}
catch (Exception ex)
{
Console.WriteLine($" [警告] 日志 #{log.Id} JSON解析失败: {ex.Message},跳过。");
continue;
}
var records = new List<CollectRecord>();
foreach (var deviceToken in devices)
{
var deviceObj = deviceToken as JObject;
if (deviceObj == null) continue;
// 提取 device 字段值
string deviceCode = DataParser.ExtractDeviceCode(deviceObj, brand.DeviceField ?? "device");
if (string.IsNullOrEmpty(deviceCode)) continue;
// 匹配机床
Machine machine;
if (!machineDict.TryGetValue(deviceCode, out machine))
{
analysisLogs.Add($"---未知设备: device={deviceCode}---");
continue;
}
// 解析字段
var parsed = DataParser.ParseDevice(deviceObj, brand, mappings);
var programName = GetStringValue(parsed, "program_name");
var partCount = GetDecimalValue(parsed, "part_count");
var totalPartCount = GetDecimalValue(parsed, "total_part_count");
// 跳过无效数据(断电设备)
if (string.IsNullOrEmpty(programName) && !partCount.HasValue)
continue;
var record = new CollectRecord
{
MachineId = machine.Id,
CollectTime = log.RequestTime,
ProgramName = programName,
PartCount = partCount,
TotalPartCount = totalPartCount,
DeviceStatus = GetStringValue(parsed, "device_status"),
RunStatus = GetStringValue(parsed, "run_status"),
OperateMode = GetStringValue(parsed, "operate_mode"),
PowerOnTime = GetDecimalValue(parsed, "power_on_time"),
RunTime = GetDecimalValue(parsed, "run_time")
};
records.Add(record);
// 调用产量跟踪
var (logText, changed, todayTotal) = tracker.Track(
machine.Id, programName, totalPartCount, log.RequestTime);
analysisLogs.Add("---机床:" + machine.Name + "---");
analysisLogs.Add("程序名=" + (programName ?? "空") + ",加工零件总数=" + (totalPartCount?.ToString() ?? "空"));
analysisLogs.Add("结果:" + logText + ",当日产量=" + todayTotal.ToString("F0"));
}
// 写入采集记录 + 更新分析日志
analysisLogs.Add("==========================================");
CollectRecordWriter.WriteBatchReplay(businessConnStr, logConnStr,
records, log.Id, log.CollectAddressId, log.RequestTime,
string.Join("\n", analysisLogs));
processed++;
Console.WriteLine($"[{processed}/{logs.Count}] {log.RequestTime:yyyy-MM-dd HH:mm:ss} → " +
$"{records.Count}台设备,耗时{(DateTime.Now - startTime).TotalMilliseconds:F0}ms");
}
swTotal.Stop();
Console.WriteLine();
Console.WriteLine($"重放完成:{processed}条日志,总耗时{swTotal.Elapsed.TotalSeconds:F0}秒");
}
}
/// <summary>
/// 从解析结果中获取字段的字符串值
/// </summary>
static string GetStringValue(Dictionary<string, DataParser.ParsedField> parsed, string field)
{
DataParser.ParsedField pf;
return parsed.TryGetValue(field, out pf) ? pf.StringValue : null;
}
/// <summary>
/// 从解析结果中获取字段的数值
/// </summary>
static decimal? GetDecimalValue(Dictionary<string, DataParser.ParsedField> parsed, string field)
{
DataParser.ParsedField pf;
return parsed.TryGetValue(field, out pf) ? pf.NumericValue : null;
}
}
/// <summary>
/// log_collect_raw 表的查询结果映射
/// </summary>
class LogEntry
{
public long Id { get; set; }
public DateTime RequestTime { get; set; }
public string RawJson { get; set; }
public int CollectAddressId { get; set; }
}
}
Loading…
Cancel
Save