|
|
|
|
@ -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; }
|
|
|
|
|
}
|
|
|
|
|
}
|