From b74c3db6af93f3ab1a9e1ffbaed644bf2dbbf1d2 Mon Sep 17 00:00:00 2001
From: haoliang <821644@qq.com>
Date: Wed, 6 May 2026 20:52:52 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=E6=A0=B9=E7=9B=AE=E5=BD=95?=
=?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=96=87=E4=BB=B6=E5=92=8C=E6=97=A7=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81=EF=BC=9B=E4=BF=AE=E5=A4=8D=E9=87=87=E9=9B=86=E6=9C=8D?=
=?UTF-8?q?=E5=8A=A1=E5=90=8D=E7=A7=B0=E4=B8=8D=E5=8C=B9=E9=85=8D=EF=BC=88?=
=?UTF-8?q?collector-service=E2=86=92CncCollector=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.txt | 280 ++++++++++++++++++
Api/CollectorApiServer.cs | 133 ---------
CncSimulator.csproj | 19 --
Config/ConfigLoader.cs | 133 ---------
Config/SimulatorConfig.cs | 51 ----
Core/CollectRecordWriter.cs | 92 ------
Core/CollectWorker.cs | 124 --------
Core/CollectorEngine.cs | 92 ------
Core/DailySummaryJob.cs | 41 ---
Core/DataParser.cs | 98 ------
Core/LogRecorder.cs | 66 -----
Core/ProductionTracker.cs | 104 -------
Core/SimulatorEngine.cs | 65 ----
Core/SimulatorServer.cs | 149 ----------
Device/DeviceSimulator.cs | 75 -----
Device/DeviceState.cs | 41 ---
Device/ScenarioPlayer.cs | 20 --
Generator/FanucDataGenerator.cs | 98 ------
Generator/IBrandGenerator.cs | 11 -
InstallUtil.InstallLog | 28 --
Program.cs | 61 ----
_test_pages.js | 23 --
deploy-admin.ps1 | 67 -----
src/CncService/Impl/DashboardService.cs | 4 +-
test-date-debug.js | 50 ----
test-production-full.js | 279 -----------------
test-production-page.js | 79 -----
test-production-v2.js | 52 ----
test-production-v3.js | 45 ---
.../CncService.Tests/LogSerializationTests.cs | 29 --
30 files changed, 282 insertions(+), 2127 deletions(-)
create mode 100644 .txt
delete mode 100644 Api/CollectorApiServer.cs
delete mode 100644 CncSimulator.csproj
delete mode 100644 Config/ConfigLoader.cs
delete mode 100644 Config/SimulatorConfig.cs
delete mode 100644 Core/CollectRecordWriter.cs
delete mode 100644 Core/CollectWorker.cs
delete mode 100644 Core/CollectorEngine.cs
delete mode 100644 Core/DailySummaryJob.cs
delete mode 100644 Core/DataParser.cs
delete mode 100644 Core/LogRecorder.cs
delete mode 100644 Core/ProductionTracker.cs
delete mode 100644 Core/SimulatorEngine.cs
delete mode 100644 Core/SimulatorServer.cs
delete mode 100644 Device/DeviceSimulator.cs
delete mode 100644 Device/DeviceState.cs
delete mode 100644 Device/ScenarioPlayer.cs
delete mode 100644 Generator/FanucDataGenerator.cs
delete mode 100644 Generator/IBrandGenerator.cs
delete mode 100644 InstallUtil.InstallLog
delete mode 100644 Program.cs
delete mode 100644 _test_pages.js
delete mode 100644 deploy-admin.ps1
delete mode 100644 test-date-debug.js
delete mode 100644 test-production-full.js
delete mode 100644 test-production-page.js
delete mode 100644 test-production-v2.js
delete mode 100644 test-production-v3.js
delete mode 100644 tests/CncService.Tests/LogSerializationTests.cs
diff --git a/.txt b/.txt
new file mode 100644
index 0000000..0096a9f
--- /dev/null
+++ b/.txt
@@ -0,0 +1,280 @@
+[
+ {
+ "device": "fanake_1.8",
+ "desc": "西-1.8",
+ "tags": [
+ {
+ "id": "_io_status",
+ "desc": "设备状态",
+ "quality": "0",
+ "value": "1.00000",
+ "time": "2026-04-10 17:36:38"
+ },
+ {
+ "id": "Tag2",
+ "desc": "当前轴数",
+ "quality": "0",
+ "value": "4.00000",
+ "time": "2026-04-10 17:36:34"
+ },
+ {
+ "id": "Tag5",
+ "desc": "执行的NC主程序名",
+ "quality": "0",
+ "value": "1566.NC",
+ "time": "2026-04-10 17:36:35"
+ },
+ {
+ "id": "Tag6",
+ "desc": "执行的NC主程序号",
+ "quality": "0",
+ "value": "N0",
+ "time": "2026-04-10 17:36:35"
+ },
+ {
+ "id": "Tag7",
+ "desc": "当前加工程序内容",
+ "quality": "0",
+ "value": "<1566.NC>\nG40G49G80\n( NAME: Administrator )\n( M",
+ "time": "2026-04-10 17:36:35"
+ },
+ {
+ "id": "Tag8",
+ "desc": "当前加工零件数",
+ "quality": "0",
+ "value": "1219.00000",
+ "time": "2026-04-10 17:36:35"
+ },
+ {
+ "id": "Tag9",
+ "desc": "运行状态",
+ "quality": "0",
+ "value": "0.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag11",
+ "desc": "操作模式",
+ "quality": "0",
+ "value": "1.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag14",
+ "desc": "当前主轴倍率",
+ "quality": "0",
+ "value": "100.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag17",
+ "desc": "主轴设定速度",
+ "quality": "0",
+ "value": "300.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag18",
+ "desc": "进给设定速度",
+ "quality": "0",
+ "value": "0.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag19",
+ "desc": "主轴实际速度",
+ "quality": "0",
+ "value": "0.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag20",
+ "desc": "进给实际转速",
+ "quality": "0",
+ "value": "0.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag21",
+ "desc": "主轴负载",
+ "quality": "0",
+ "value": "0.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag22",
+ "desc": "开机时间",
+ "quality": "0",
+ "value": "23558160.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag23",
+ "desc": "运行时间",
+ "quality": "0",
+ "value": "18224.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag24",
+ "desc": "切削时间",
+ "quality": "0",
+ "value": "6848959.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag25",
+ "desc": "循环时间",
+ "quality": "0",
+ "value": "699.00000",
+ "time": "2026-04-10 17:36:38"
+ },
+ {
+ "id": "Tag26",
+ "desc": "加工状态",
+ "quality": "0",
+ "value": "G01",
+ "time": "2026-04-10 17:36:38"
+ }
+ ]
+ },
+ {
+ "device": "fanake_1.9",
+ "desc": "西-1.9",
+ "tags": [
+ {
+ "id": "_io_status",
+ "desc": "设备状态",
+ "quality": "0",
+ "value": "1.00000",
+ "time": "2026-04-10 17:36:38"
+ },
+ {
+ "id": "Tag2",
+ "desc": "当前轴数",
+ "quality": "0",
+ "value": "4.00000",
+ "time": "2026-04-10 17:36:34"
+ },
+ {
+ "id": "Tag5",
+ "desc": "执行的NC主程序名",
+ "quality": "0",
+ "value": "O1",
+ "time": "2026-04-10 17:36:35"
+ },
+ {
+ "id": "Tag6",
+ "desc": "执行的NC主程序号",
+ "quality": "0",
+ "value": "N20",
+ "time": "2026-04-10 17:36:35"
+ },
+ {
+ "id": "Tag7",
+ "desc": "当前加工程序内容",
+ "quality": "0",
+ "value": "G99 G83 Z-43.000 Q3.000 R3.000 F60. \nG80 \nG00 Z",
+ "time": "2026-04-10 17:36:35"
+ },
+ {
+ "id": "Tag8",
+ "desc": "当前加工零件数",
+ "quality": "0",
+ "value": "62.00000",
+ "time": "2026-04-10 17:36:35"
+ },
+ {
+ "id": "Tag9",
+ "desc": "运行状态",
+ "quality": "0",
+ "value": "3.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag11",
+ "desc": "操作模式",
+ "quality": "0",
+ "value": "10.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag14",
+ "desc": "当前主轴倍率",
+ "quality": "0",
+ "value": "100.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag17",
+ "desc": "主轴设定速度",
+ "quality": "0",
+ "value": "450.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag18",
+ "desc": "进给设定速度",
+ "quality": "0",
+ "value": "60.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag19",
+ "desc": "主轴实际速度",
+ "quality": "0",
+ "value": "450.00000",
+ "time": "2026-04-10 17:36:36"
+ },
+ {
+ "id": "Tag20",
+ "desc": "进给实际转速",
+ "quality": "0",
+ "value": "60.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag21",
+ "desc": "主轴负载",
+ "quality": "0",
+ "value": "25.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag22",
+ "desc": "开机时间",
+ "quality": "0",
+ "value": "23784960.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag23",
+ "desc": "运行时间",
+ "quality": "0",
+ "value": "24253.00000",
+ "time": "2026-04-10 17:36:37"
+ },
+ {
+ "id": "Tag24",
+ "desc": "切削时间",
+ "quality": "0",
+ "value": "8009398.00000",
+ "time": "2026-04-10 17:36:38"
+ },
+ {
+ "id": "Tag25",
+ "desc": "循环时间",
+ "quality": "0",
+ "value": "82.00000",
+ "time": "2026-04-10 17:36:38"
+ },
+ {
+ "id": "Tag26",
+ "desc": "加工状态",
+ "quality": "0",
+ "value": "G01",
+ "time": "2026-04-10 17:36:38"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/Api/CollectorApiServer.cs b/Api/CollectorApiServer.cs
deleted file mode 100644
index bd00463..0000000
--- a/Api/CollectorApiServer.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System;
-using System.Net;
-using System.Text;
-using System.Threading.Tasks;
-using Newtonsoft.Json;
-using log4net;
-using CncCollector.Core;
-using System.IO;
-
-namespace CncCollector.Api
-{
- ///
- /// Lightweight HTTP API server,用于控制 CollectEngine 的状态与配置刷新。
- ///
- public class CollectorApiServer
- {
- private readonly CollectorEngine _engine;
- private readonly string _apiKey;
- private readonly int _port;
- private readonly HttpListener _listener = new HttpListener();
- private readonly ILog _log = LogManager.GetLogger(typeof(CollectorApiServer));
- private Task _listenTask;
- private bool _running;
-
- public CollectorApiServer(CollectorEngine engine, string apiKey, int port = 0)
- {
- _engine = engine;
- _apiKey = apiKey;
- _port = port > 0 ? port : 8080;
- }
-
- public void Start()
- {
- var prefix = $"http://+:{_port}/api/collector/";
- _listener.Prefixes.Add(prefix);
- _listener.Start();
- _running = true;
- _log.Info("CollectorApiServer started on " + prefix);
- _listenTask = Task.Run(() => ListenLoop());
- }
-
- public void Stop()
- {
- _running = false;
- _listener.Stop();
- }
-
- private async Task ListenLoop()
- {
- while (_running)
- {
- try
- {
- var ctx = await _listener.GetContextAsync();
- _ = Task.Run(() => ProcessRequest(ctx));
- }
- catch (HttpListenerException)
- {
- // 监听关闭
- break;
- }
- catch (Exception ex)
- {
- _log.Error("CollectorApiServer 处理请求异常", ex);
- }
- }
- }
-
- private void ProcessRequest(HttpListenerContext ctx)
- {
- var req = ctx.Request;
- var res = ctx.Response;
- // 简单的 API-Key 认证
- if (!ValidateApiKey(req))
- {
- res.StatusCode = 401;
- WriteJson(res, new { code = 1, message = "Unauthorized" });
- return;
- }
-
- string path = req.Url.AbsolutePath.ToLower();
- switch (req.HttpMethod)
- {
- case "GET":
- if (path.EndsWith("/status"))
- WriteJson(res, new { code = 0, message = "success", data = new { status = _engine.Status } });
- else
- WriteJson(res, new { code = 0, message = "unknown endpoint" });
- break;
- case "POST":
- if (path.EndsWith("/start"))
- {
- _engine.Start();
- WriteJson(res, new { code = 0, message = "started" });
- }
- else if (path.EndsWith("/stop"))
- {
- _engine.Stop();
- WriteJson(res, new { code = 0, message = "stopped" });
- }
- else if (path.EndsWith("/refresh"))
- {
- _engine.Refresh();
- WriteJson(res, new { code = 0, message = "refreshed" });
- }
- else
- {
- WriteJson(res, new { code = 0, message = "unknown endpoint" });
- }
- break;
- default:
- res.StatusCode = 405;
- WriteJson(res, new { code = 1, message = "method not allowed" });
- break;
- }
- }
-
- private bool ValidateApiKey(HttpListenerRequest req)
- {
- var header = req.Headers["X-Api-Key"];
- return !string.IsNullOrEmpty(_apiKey) && string.Equals(_apiKey, header);
- }
-
- private void WriteJson(HttpListenerResponse res, object data)
- {
- var json = JsonConvert.SerializeObject(data);
- var buffer = Encoding.UTF8.GetBytes(json);
- res.ContentType = "application/json";
- res.ContentLength64 = buffer.Length;
- res.OutputStream.Write(buffer, 0, buffer.Length);
- }
- }
-}
diff --git a/CncSimulator.csproj b/CncSimulator.csproj
deleted file mode 100644
index 7e99188..0000000
--- a/CncSimulator.csproj
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
- Exe
- net472
- enable
-
-
-
-
-
-
-
-
-
- Always
-
-
-
-
diff --git a/Config/ConfigLoader.cs b/Config/ConfigLoader.cs
deleted file mode 100644
index ff7f4f4..0000000
--- a/Config/ConfigLoader.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Dapper;
-using MySql.Data.MySqlClient;
-using log4net;
-
-// 文件用途:从 CNC 系统配置表 cnc_sys_config 读取运行时配置,并覆盖默认 CollectorConfig 的设置
-// 设计目标:在独立进程的采集服务中,不依赖仓储层,直接从数据库加载运行时参数
-namespace CncCollector.Config
-{
- ///
- /// 运行时配置加载器:从 cnc_sys_config 表读取配置,覆盖 CollectorConfig 的默认值
- ///
- public static class ConfigLoader
- {
- private static readonly ILog _log = LogManager.GetLogger(typeof(ConfigLoader));
-
- ///
- /// 从数据库加载运行时配置并覆盖给定的 CollectorConfig 实例的默认值。
- ///
- /// MySQL/MariaDB 连接字符串
- /// 需要被覆盖的 CollectorConfig 实例(来自 CNC 采集服务)
- public static void LoadRuntimeConfig(string connectionString, CncModels.Entity.CollectorConfig config)
- {
- if (string.IsNullOrWhiteSpace(connectionString))
- {
- _log.Warn("配置加载失败:连接字符串为空");
- return;
- }
-
- if (config == null)
- {
- _log.Warn("配置加载失败:传入的 CollectorConfig 为 null");
- return;
- }
-
- try
- {
- using (var conn = new MySqlConnection(connectionString))
- {
- conn.Open();
- // 需要加载的键集合
- var keys = new[]
- {
- "collector_api_port",
- "collector_api_key",
- "heartbeat_interval",
- "config_poll_interval",
- "daily_summary_time",
- "collect_retry_count",
- "collect_retry_interval",
- "collect_fail_alert_threshold"
- };
-
- foreach (var key in keys)
- {
- var value = conn.QueryFirstOrDefault(
- "SELECT config_value FROM cnc_sys_config WHERE config_key = @Key",
- new { Key = key });
- if (value == null) continue;
- Apply(config, key, value);
- }
- }
- }
- catch (Exception ex)
- {
- _log.Error("加载运行时配置异常", ex);
- }
- }
-
- private static void Apply(CncModels.Entity.CollectorConfig config, string key, string value)
- {
- try
- {
- switch (key)
- {
- case "collector_api_port":
- SetIntProperty(config, nameof(config.ApiPort), value);
- break;
- case "collector_api_key":
- SetStringProperty(config, nameof(config.ApiKey), value);
- break;
- case "heartbeat_interval":
- SetIntProperty(config, nameof(config.HeartbeatInterval), value);
- break;
- case "config_poll_interval":
- SetIntProperty(config, nameof(config.ConfigPollInterval), value);
- break;
- case "daily_summary_time":
- SetTimeSpanProperty(config, nameof(config.DailySummaryTime), value);
- break;
- case "collect_retry_count":
- SetIntProperty(config, nameof(config.CollectRetryCount), value);
- break;
- case "collect_retry_interval":
- SetIntProperty(config, nameof(config.CollectRetryIntervalSeconds), value);
- break;
- case "collect_fail_alert_threshold":
- SetIntProperty(config, nameof(config.CollectFailAlertThreshold), value);
- break;
- }
- }
- catch
- {
- // 忽略单项配置解析失败,继续加载其他配置
- }
- }
-
- private static void SetIntProperty(CncModels.Entity.CollectorConfig obj, string propName, string value)
- {
- if (obj == null) return;
- var p = obj.GetType().GetProperty(propName);
- if (p == null || !p.CanWrite) return;
- if (int.TryParse(value, out var v)) p.SetValue(obj, v);
- }
-
- private static void SetStringProperty(CncModels.Entity.CollectorConfig obj, string propName, string value)
- {
- if (obj == null) return;
- var p = obj.GetType().GetProperty(propName);
- if (p == null || !p.CanWrite) return;
- p.SetValue(obj, value);
- }
-
- private static void SetTimeSpanProperty(CncModels.Entity.CollectorConfig obj, string propName, string value)
- {
- if (obj == null) return;
- var p = obj.GetType().GetProperty(propName);
- if (p == null || !p.CanWrite) return;
- if (TimeSpan.TryParse(value, out var ts)) p.SetValue(obj, ts);
- }
- }
-}
diff --git a/Config/SimulatorConfig.cs b/Config/SimulatorConfig.cs
deleted file mode 100644
index 3fad6ea..0000000
--- a/Config/SimulatorConfig.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System.Collections.Generic;
-using Newtonsoft.Json;
-
-namespace CncSimulator.Config
-{
- /// 模拟器配置
- public class SimulatorConfig
- {
- [JsonProperty("gatewayPort")]
- public int GatewayPort { get; set; } = 9000;
-
- [JsonProperty("addresses")]
- public List Addresses { get; set; } = new List();
- }
-
- public class AddressConfig
- {
- [JsonProperty("name")]
- public string Name { get; set; }
-
- [JsonProperty("port")]
- public int Port { get; set; }
-
- [JsonProperty("brand")]
- public string Brand { get; set; } = "fanuc";
-
- [JsonProperty("dataChangeInterval")]
- public int DataChangeInterval { get; set; } = 10;
-
- [JsonProperty("scenarioMode")]
- public string ScenarioMode { get; set; } = "auto";
-
- [JsonProperty("devices")]
- public List Devices { get; set; } = new List();
- }
-
- public class DeviceConfig
- {
- [JsonProperty("deviceCode")]
- public string DeviceCode { get; set; }
-
- [JsonProperty("desc")]
- public string Desc { get; set; }
-
- [JsonProperty("initialProgram")]
- public string InitialProgram { get; set; } = "O0001";
-
- [JsonProperty("initialPartCount")]
- public int InitialPartCount { get; set; } = 0;
- }
-}
diff --git a/Core/CollectRecordWriter.cs b/Core/CollectRecordWriter.cs
deleted file mode 100644
index e75dd47..0000000
--- a/Core/CollectRecordWriter.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Dapper;
-using MySql.Data.MySqlClient;
-using log4net;
-using CncModels.Entity;
-
-namespace CncCollector.Core
-{
- ///
- /// 采集数据批量写入:cnc_collect_record 与 log_collect_raw,以及更新 CNC 机床状态。
- ///
- public static class CollectRecordWriter
- {
- private static readonly ILog Log = LogManager.GetLogger(typeof(CollectRecordWriter));
-
- ///
- /// 将批量结构化记录写入 cnc_collect_record,并记录原始 JSON 到 log_collect_raw,同时更新机床实时状态。
- ///
- /// 数据库连接字符串
- /// 结构化记录集合
- /// 原始 JSON 日志
- /// 采集地址标识
- public static void WriteBatch(string connectionString, IEnumerable records, string rawJson, int collectAddressId)
- {
- if (records == null) return;
- using (var conn = new MySqlConnection(connectionString))
- {
- conn.Open();
- using (var tran = conn.BeginTransaction())
- {
- try
- {
- // 插入原始日志
- const string sqlLog = @"INSERT INTO log_collect_raw
- (collect_address_id, request_time, response_time, response_duration, is_success, status_code, raw_json, error_message, created_at)
- VALUES
- (@CollectAddressId, @RequestTime, @ResponseTime, @ResponseDuration, @IsSuccess, @StatusCode, @RawJson, @ErrorMessage, NOW())";
- conn.Execute(sqlLog, new
- {
- CollectAddressId = collectAddressId,
- RequestTime = DateTime.Now,
- ResponseTime = DateTime.Now,
- ResponseDuration = 0,
- IsSuccess = 1,
- StatusCode = 200,
- RawJson = rawJson,
- ErrorMessage = (string)null
- }, tran);
-
- // 插入结构化记录(逐条,保持简单且具备可编译性)
- const string sqlRec = @"INSERT INTO cnc_collect_record
- (machine_id, collect_time, device_time, program_name, part_count, device_status, run_status, operate_mode, spindle_speed_set, feed_speed_set, spindle_speed_actual, feed_speed_actual, spindle_load, spindle_override, power_on_time, run_time, cutting_time, cycle_time, machining_status, extra_data, created_at)
- VALUES
- (@MachineId, @CollectTime, @DeviceTime, @ProgramName, @PartCount, @DeviceStatus, @RunStatus, @OperateMode, @SpindleSpeedSet, @FeedSpeedSet, @SpindleSpeedActual, @FeedSpeedActual, @SpindleLoad, @SpindleOverride, @PowerOnTime, @RunTime, @CuttingTime, @CycleTime, @MachiningStatus, @ExtraData, NOW())";
- foreach (var r in records)
- {
- r.CreatedAt = DateTime.Now;
- conn.Execute(sqlRec, r, tran);
- }
-
- // 简单的机床状态更新(按单条记录的最后一条)
- foreach (var r in records)
- {
- const string sqlMach = @"UPDATE cnc_machine
- SET last_collect_time = @CollectTime, last_device_status = @DeviceStatus, last_run_status = @RunStatus, last_program_name = @ProgramName, last_part_count = @PartCount, last_operate_mode = @OperateMode, last_machining_status = @MachiningStatus
- WHERE machine_id = @MachineId";
- conn.Execute(sqlMach, new
- {
- MachineId = r.MachineId,
- CollectTime = r.CollectTime,
- DeviceStatus = r.DeviceStatus,
- RunStatus = r.RunStatus,
- ProgramName = r.ProgramName,
- PartCount = r.PartCount,
- OperateMode = r.OperateMode,
- MachiningStatus = r.MachiningStatus
- }, tran);
- }
- tran.Commit();
- }
- catch (Exception ex)
- {
- tran.Rollback();
- Log.Error("批量写入采集记录失败", ex);
- throw;
- }
- }
- }
- }
- }
-}
diff --git a/Core/CollectWorker.cs b/Core/CollectWorker.cs
deleted file mode 100644
index 40c836d..0000000
--- a/Core/CollectWorker.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Net.NetworkInformation;
-using System.Threading;
-using System.Threading.Tasks;
-using MySql.Data.MySqlClient;
-using log4net;
-using CncModels.Entity;
-using Newtonsoft.Json;
-using System.Linq;
-
-namespace CncCollector.Core
-{
- ///
- /// 单个采集地址工作线程:负责定时抓取、解析、入库及健康状态上报。
- ///
- public class CollectWorker
- {
- private readonly CollectAddress _address;
- private readonly string _connectionString;
- private readonly ProductionTracker _tracker;
- private readonly string _apiKey;
- private readonly ILog _log = LogManager.GetLogger(typeof(CollectWorker));
- private Thread _thread;
- private volatile bool _stop;
-
- public CollectWorker(CollectAddress address, string connectionString, ProductionTracker tracker, string apiKey)
- {
- _address = address ?? throw new ArgumentNullException(nameof(address));
- _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
- _tracker = tracker ?? throw new ArgumentNullException(nameof(tracker));
- _apiKey = apiKey;
- }
-
- public void Start()
- {
- _stop = false;
- _thread = new Thread(Run)
- {
- IsBackground = true,
- Name = $"CollectWorker-{_address?.Id ?? 0}"
- };
- _thread.Start();
- }
-
- public void Stop()
- {
- _stop = true;
- _thread?.Join();
- }
-
- private void Run()
- {
- while (!_stop)
- {
- try
- {
- // 1) Ping 测试连通性
- if (!string.IsNullOrWhiteSpace(_address?.Url))
- {
- try
- {
- var host = new Uri(_address.Url).Host;
- var ping = new Ping();
- var reply = ping.Send(host, 1000);
- // 写入简要在线状态
- // 实际实现:更新 cnc_machine.is_online 等字段
- }
- catch { /* 忽略 Ping 失败带来的异常 */ }
- }
-
- // 2) HTTP GET 采集
- if (!string.IsNullOrWhiteSpace(_address?.Url))
- {
- using (var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) })
- {
- var resp = http.GetAsync(_address.Url).Result;
- if (resp.IsSuccessStatusCode)
- {
- var json = resp.Content.ReadAsStringAsync().Result;
- // 解析(品牌信息在实际实现中注入)
- var parsed = Core.DataParser.Parse(_address.BrandName, json, null);
- if (parsed != null && parsed.Count > 0)
- {
- // 将解析后的字段映射为 CollectRecord,简化实现:创建空记录集合以便调用写入
- var recs = new List();
- // 实际实现应根据 parsed 构造 CollectRecord 对象
- if (recs.Count > 0)
- {
- CollectRecordWriter.WriteBatch(_connectionString, recs, json, _address.Id);
- }
- // 产量跟踪(简化实现:尝试从第一个字段的值计算产量)
- int produced = 0;
- var first = parsed.Values.FirstOrDefault();
- if (first != null && first.Value != null)
- {
- if (first.Value is int iv) produced = iv;
- else if (first.Value is decimal dv) produced = (int)dv;
- else if (first.Value is long lv) produced = (int)lv;
- }
- _tracker?.Track(_address.MachineId, parsed.Values.Select(v => v.FieldName).FirstOrDefault() ?? string.Empty, produced, DateTime.Now);
- }
- }
- }
- }
- }
- catch (Exception ex)
- {
- _log.Error("CollectWorker 异常", ex);
- }
-
- // 采集间隔,若未配置则 30 秒
- var interval = 30;
- try
- {
- if (_address != null && _address.CollectInterval > 0) interval = _address.CollectInterval;
- }
- catch { }
- Thread.Sleep(interval * 1000);
- }
- }
- }
-}
diff --git a/Core/CollectorEngine.cs b/Core/CollectorEngine.cs
deleted file mode 100644
index 6f64d63..0000000
--- a/Core/CollectorEngine.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using Dapper;
-using MySql.Data.MySqlClient;
-using log4net;
-using CncModels.Entity;
-using CncModels.Enum;
-
-namespace CncCollector.Core
-{
- ///
- /// 采集引擎:负责加载采集地址、创建工作线程、心跳和配置轮询等核心调度。
- ///
- public class CollectorEngine
- {
- private readonly string _connectionString;
- private readonly CollectorConfig _config;
- private readonly List _workers = new List();
- private readonly ProductionTracker _tracker;
- private readonly DailySummaryJob _dailyJob;
- private readonly ILog _log = LogManager.GetLogger(typeof(CollectorEngine));
- private Thread _pollThread;
- private volatile bool _running;
-
- public CollectorEngine(string connectionString, CollectorConfig config)
- {
- _connectionString = connectionString;
- _config = config;
- _tracker = new ProductionTracker(connectionString);
- _dailyJob = new DailySummaryJob(connectionString);
- }
-
- public void Start()
- {
- _log.Info("CollectorEngine starting...");
- _running = true;
- // 加载并启动地址采集 worker
- LoadAddressesAndStartWorkers();
- // 启动配置轮询
- _pollThread = new Thread(PollLoop) { IsBackground = true, Name = "CollectorConfigPoll" };
- _pollThread.Start();
- // 简化心跳机制:直接输出日志
- }
-
- public void Stop()
- {
- _log.Info("CollectorEngine stopping...");
- _running = false;
- foreach (var w in _workers) w.Stop();
- _workers.Clear();
- }
-
- public string Status => _running ? "Running" : "Stopped";
-
- private void LoadAddressesAndStartWorkers()
- {
- using (var conn = new MySqlConnection(_connectionString))
- {
- conn.Open();
- var addresses = conn.Query("SELECT * FROM cnc_collect_address WHERE is_enabled=1");
- foreach (var addr in addresses)
- {
- var w = new CollectWorker(addr, _connectionString, _tracker, _config.ApiKey);
- w.Start();
- _workers.Add(w);
- }
- }
- }
-
- private void PollLoop()
- {
- while (_running)
- {
- try
- {
- // 轮询配置变更(简化实现)
- Thread.Sleep(30000);
- }
- catch { }
- }
- }
-
- /// 重新加载地址配置并重启工作线程
- public void Refresh()
- {
- Stop();
- Start();
- }
- }
-}
diff --git a/Core/DailySummaryJob.cs b/Core/DailySummaryJob.cs
deleted file mode 100644
index e6d6192..0000000
--- a/Core/DailySummaryJob.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System;
-using System.Data;
-using Dapper;
-using MySql.Data.MySqlClient;
-using log4net;
-using CncModels.Enum;
-using CncModels.Entity;
-
-namespace CncCollector.Core
-{
- ///
- /// 日终汇总作业:在指定时间点执行,结账活跃段、聚合产量并标记完成。
- ///
- public class DailySummaryJob
- {
- private readonly string _connectionString;
- private readonly ILog _log = LogManager.GetLogger(typeof(DailySummaryJob));
-
- public DailySummaryJob(string connectionString)
- {
- _connectionString = connectionString;
- }
-
- ///
- /// 触发日终汇总逻辑。
- ///
- public void Run(DateTime now)
- {
- using (var conn = new MySqlConnection(_connectionString))
- {
- conn.Open();
- // 1) 结账所有活跃段
- const string sqlCloseAll = @"UPDATE cnc_production_segment SET end_time=@EndTime, close_reason=@Reason, is_settled=1 WHERE end_time IS NULL";
- conn.Execute(sqlCloseAll, new { EndTime = now, Reason = SegmentCloseReason.EndOfDay.ToString() });
-
- // 2) 汇总逻辑(简化:省略复杂聚合,日志输出)
- _log.Info("Daily summary executed: production segments closed and daily tables updated (简化实现). ");
- }
- }
- }
-}
diff --git a/Core/DataParser.cs b/Core/DataParser.cs
deleted file mode 100644
index 03acf16..0000000
--- a/Core/DataParser.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using CncModels.Entity;
-using System.Linq;
-using log4net;
-
-namespace CncCollector.Core
-{
- ///
- /// JSON 解析引擎:将原始 JSON 与品牌字段映射表结合,输出结构化字段字典。
- ///
- public static class DataParser
- {
- /// 解析结果中的字段
- public class ParsedField
- {
- /// 字段名(标准化后)
- public string FieldName { get; set; }
- /// 字段值
- public object Value { get; set; }
- /// 数据类型
- public string DataType { get; set; }
- }
-
- private static readonly ILog Log = LogManager.GetLogger(typeof(DataParser));
-
- ///
- /// 解析原始 JSON,并根据品牌映射提取字段。
- /// 该实现尽量适配常见结构:JSON 为数组,元素包含一个 tags 数组,tags 中的元素拥有 id/value。
- ///
- /// 品牌名称,用于定位字段映射(若为 null,尝试使用空映射)
- /// 原始 JSON 字符串
- /// 可选的品牌对象,包含映射信息
- /// 解析后的字段字典,Key 为标准字段名
- public static Dictionary Parse(string brandName, string json, Brand brand = null)
- {
- var result = new Dictionary();
- if (string.IsNullOrWhiteSpace(json)) return result;
-
- try
- {
- var root = JArray.Parse(json);
- // 优先使用传入的 brand(若品牌包含字段映射)来定位 tags
- var mappings = brand?.BrandFieldMappings?.ToList() ?? new List();
- foreach (var item in root)
- {
- // 定位 tags 列表,默认字段路径为 "tags"
- var tagsPath = brand?.TagsPath ?? "tags";
- var tagsToken = item.SelectToken(tagsPath);
- if (tagsToken == null) continue;
- if (tagsToken is JArray tags)
- {
- foreach (var map in mappings)
- {
- var tag = tags.FirstOrDefault(t => string.Equals(t["id"]?.ToString(), map.FieldName, StringComparison.OrdinalIgnoreCase));
- if (tag != null)
- {
- var raw = tag["value"]?.ToString();
- var value = ConvertValue(raw, map.DataType);
- var field = new ParsedField
- {
- FieldName = map.StandardField,
- Value = value,
- DataType = map.DataType
- };
- result[field.FieldName] = field;
- }
- }
- }
- // 解析到一个设备后就返回,简化实现
- break;
- }
- }
- catch (Exception ex)
- {
- Log.Error("DataParser 解析异常", ex);
- }
- return result;
- }
-
- private static object ConvertValue(string raw, string dataType)
- {
- if (string.IsNullOrWhiteSpace(raw)) return null;
- if (string.Equals(dataType, "number", StringComparison.OrdinalIgnoreCase))
- {
- if (decimal.TryParse(raw, out var d)) return d;
- }
- // 去除形如 1219.00000 的尾缀
- if (raw.EndsWith(".00000") && decimal.TryParse(raw.Replace(".00000", ""), out var d2))
- {
- return d2;
- }
- return raw;
- }
- }
-}
diff --git a/Core/LogRecorder.cs b/Core/LogRecorder.cs
deleted file mode 100644
index be34136..0000000
--- a/Core/LogRecorder.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using log4net;
-
-namespace CncSimulator.Core
-{
- public class LogEntry
- {
- public DateTime Timestamp { get; set; }
- public int DeviceCount { get; set; }
- public string KeyData { get; set; }
- public string FullJson { get; set; }
- public long DurationMs { get; set; }
- }
-
- /// 日志记录器:内存环形缓冲 + 文件日志输出
- public class LogRecorder
- {
- private readonly int _capacity;
- private readonly List _buffer = new List();
- private readonly object _lock = new object();
- private static readonly ILog _logger = LogManager.GetLogger(typeof(LogRecorder));
-
- public LogRecorder(int capacity = 200)
- {
- _capacity = capacity;
- }
-
- public void Record(int deviceCount, string keyData, string fullJson, long durationMs)
- {
- var entry = new LogEntry
- {
- Timestamp = DateTime.Now,
- DeviceCount = deviceCount,
- KeyData = keyData,
- FullJson = fullJson,
- DurationMs = durationMs
- };
- lock (_lock)
- {
- _buffer.Add(entry);
- if (_buffer.Count > _capacity) _buffer.RemoveAt(0);
- }
- // 触发日志到文件输出
- _logger.Info($"[{entry.Timestamp:yyyy-MM-dd HH:mm:ss}] D={deviceCount} Key={keyData} Dur={durationMs}ms");
- }
-
- public List GetRecentLogs(int count)
- {
- lock (_lock)
- {
- return _buffer.Skip(Math.Max(0, _buffer.Count - count)).Take(count).ToList();
- }
- }
-
- public LogEntry GetLatest()
- {
- lock (_lock)
- {
- return _buffer.Count == 0 ? null : _buffer[_buffer.Count - 1];
- }
- }
- }
-}
diff --git a/Core/ProductionTracker.cs b/Core/ProductionTracker.cs
deleted file mode 100644
index cc00d1f..0000000
--- a/Core/ProductionTracker.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Data;
-using Dapper;
-using MySql.Data.MySqlClient;
-using CncModels.Enum;
-using CncModels.Entity;
-using log4net;
-
-namespace CncCollector.Core
-{
- ///
- /// 零件产量分段跟踪引擎:维护内存中的活跃段状态,并定期写入数据库。
- ///
- public class ProductionTracker
- {
- private readonly string _connectionString;
- private readonly object _lock = new object();
- private static readonly ILog Log = LogManager.GetLogger(typeof(ProductionTracker));
-
- public ProductionTracker(string connectionString)
- {
- _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
- }
-
- ///
- /// 处理一个采集记录后的产量跟踪逻辑。
- ///
- public void Track(int machineId, string programName, int partCount, DateTime collectTime)
- {
- lock (_lock)
- {
- using (var conn = new MySqlConnection(_connectionString))
- {
- conn.Open();
- // 查找当前未结算的活跃段
- var active = conn.QueryFirstOrDefault(
- "SELECT * FROM cnc_production_segment WHERE machine_id=@MachineId AND is_settled=0 AND end_time IS NULL",
- new { MachineId = machineId });
-
- if (active == null)
- {
- // 开新段
- const string sqlStart = @"INSERT INTO cnc_production_segment
- (machine_id, program_name, production_date, start_time, start_part_count, is_settled)
- VALUES
- (@MachineId, @ProgramName, @ProductionDate, @StartTime, @StartPartCount, 0)";
- conn.Execute(sqlStart, new
- {
- MachineId = machineId,
- ProgramName = programName,
- ProductionDate = collectTime.Date,
- StartTime = collectTime,
- StartPartCount = partCount
- });
- }
- else
- {
- // 程序名变更则结账并开新段
- if (!string.Equals(active.ProgramName, programName, StringComparison.OrdinalIgnoreCase))
- {
- const string sqlClose = @"UPDATE cnc_production_segment
- SET end_time=@EndTime, end_part_count=@EndPartCount, is_settled=1, close_reason=@Reason
- WHERE id=@Id";
- conn.Execute(sqlClose, new
- {
- EndTime = collectTime,
- EndPartCount = active.EndPartCount ?? active.StartPartCount,
- Id = active.Id,
- Reason = SegmentCloseReason.ProgramChange.ToString()
- });
-
- const string sqlStart = @"INSERT INTO cnc_production_segment
- (machine_id, program_name, production_date, start_time, start_part_count, is_settled)
- VALUES
- (@MachineId, @ProgramName, @ProductionDate, @StartTime, @StartPartCount, 0)";
- conn.Execute(sqlStart, new
- {
- MachineId = machineId,
- ProgramName = programName,
- ProductionDate = collectTime.Date,
- StartTime = collectTime,
- StartPartCount = partCount
- });
- }
- else
- {
- // 更新当前段的结束时间与结束时的部件数
- const string sqlUpdate = @"UPDATE cnc_production_segment
- SET end_time=@EndTime, end_part_count=@EndPartCount
- WHERE id=@Id";
- conn.Execute(sqlUpdate, new
- {
- EndTime = collectTime,
- EndPartCount = partCount,
- Id = active.Id
- });
- }
- }
- }
- }
- }
- }
-}
diff --git a/Core/SimulatorEngine.cs b/Core/SimulatorEngine.cs
deleted file mode 100644
index 749edae..0000000
--- a/Core/SimulatorEngine.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using Newtonsoft.Json;
-using CncSimulator.Config;
-using CncSimulator.Core;
-using CncSimulator.Device;
-
-namespace CncSimulator.Core
-{
- /// 引擎:管理多个 SimulatorServer 实例
- public class SimulatorEngine
- {
- private readonly List _servers = new List();
-
- public void LoadConfig(string jsonPath)
- {
- var json = File.ReadAllText(jsonPath);
- var cfg = JsonConvert.DeserializeObject(json);
- LoadConfig(cfg);
- }
-
- public void LoadConfig(SimulatorConfig cfg)
- {
- _servers.Clear();
- foreach (var addr in cfg.Addresses)
- {
- var devices = new List();
- foreach (var d in addr.Devices)
- {
- devices.Add(new DeviceSimulator(d, addr.DataChangeInterval));
- }
- var server = new SimulatorServer(addr, devices);
- _servers.Add(server);
- }
- }
-
- public void StartAll()
- {
- foreach (var s in _servers) s.Start();
- }
-
- public void StopAll()
- {
- foreach (var s in _servers) s.Stop();
- }
-
- public object GetStatus()
- {
- return _servers.Select(s => new
- {
- address = s.Address.Name,
- port = s.Address.Port,
- running = s.IsRunning,
- totalDevices = s.TotalDeviceCount,
- onlineDevices = s.OnlineDeviceCount
- }).ToList();
- }
-
- public SimulatorServer FindByPort(int port)
- {
- return _servers.FirstOrDefault(s => s.Address.Port == port);
- }
- }
-}
diff --git a/Core/SimulatorServer.cs b/Core/SimulatorServer.cs
deleted file mode 100644
index bbb0866..0000000
--- a/Core/SimulatorServer.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Timers;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using CncSimulator.Config;
-using CncSimulator.Device;
-using CncSimulator.Generator;
-using CncSimulator.Core;
-
-namespace CncSimulator.Core
-{
- /// 单个地址的 HTTP 服务器及设备仿真集合
- public class SimulatorServer
- {
- public AddressConfig Address { get; private set; }
- public List Devices { get; private set; }
- private readonly FanucDataGenerator _generator = new FanucDataGenerator();
- private readonly LogRecorder _logRecorder = new LogRecorder();
- private HttpListener _http;
- private Timer _tickTimer;
- public bool IsRunning { get; private set; } = false;
- public int RequestCount { get; private set; } = 0;
- public int TotalDeviceCount => Devices?.Count ?? 0;
- public int OnlineDeviceCount => Devices?.Count(d => d.State?.IsOnline == true) ?? 0;
- public DateTime StartTime { get; private set; }
-
- public SimulatorServer(AddressConfig address, List devices)
- {
- Address = address;
- Devices = devices ?? new List();
- }
-
- public void Start()
- {
- if (IsRunning) return;
- _http = new HttpListener();
- _http.Prefixes.Add($"http://+:{Address.Port}/");
- _http.Start();
- StartTime = DateTime.Now;
- // 每个地址用一个定时器驱动数据变化
- _tickTimer = new Timer(Address.DataChangeInterval * 1000);
- _tickTimer.Elapsed += (s, e) => TickDevices();
- _tickTimer.Start();
- // 简化的请求循环(阻塞模式)
- var t = new Thread(HandleRequests) { IsBackground = true };
- t.Start();
- IsRunning = true;
- }
-
- public void Stop()
- {
- if (!IsRunning) return;
- _tickTimer?.Stop();
- _http?.Close();
- IsRunning = false;
- }
-
- private void TickDevices()
- {
- foreach (var d in Devices)
- {
- d.Tick();
- }
- }
-
- private void HandleRequests()
- {
- while (_http != null && _http.IsListening)
- {
- try
- {
- var ctx = _http.GetContext();
- ProcessContext(ctx);
- }
- catch (Exception)
- {
- // 忽略单次请求异常,继续监听
- }
- }
- }
-
- private void ProcessContext(HttpListenerContext ctx)
- {
- RequestCount++;
- var req = ctx.Request;
- var resp = ctx.Response;
- resp.ContentType = "application/json";
- string path = req.Url.AbsolutePath.ToLower();
- if (path == "/" || path == "/data")
- {
- // 生成当前设备数据集合
- var sw = new System.Diagnostics.Stopwatch();
- sw.Start();
- var list = new List();
- foreach (var d in Devices)
- {
- var json = _generator.GenerateDevice(d.State);
- if (json != null) list.Add(json);
- }
- var final = new JObject { ["devices"] = new JArray(list) };
- var payload = final.ToString();
- sw.Stop();
- _logRecorder.Record(Devices.Count, string.Join(" ", Devices.Select(v => v.State?.DeviceCode ?? "")), payload, sw.ElapsedMilliseconds);
- WriteResponse(resp, payload);
- }
- else if (path.StartsWith("/admin"))
- {
- string html = "管理界面开发中
";
- WriteResponse(resp, html, contentType: "text/html");
- }
- else if (path == "/admin/api/logs")
- {
- var logs = _logRecorder.GetRecentLogs(50);
- var arr = new JArray();
- foreach (var l in logs)
- {
- arr.Add(new JObject
- {
- ["timestamp"] = l.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"),
- ["deviceCount"] = l.DeviceCount,
- ["keyData"] = l.KeyData,
- ["durationMs"] = l.DurationMs
- });
- }
- var payload = arr.ToString();
- WriteResponse(resp, payload);
- }
- else
- {
- WriteResponse(resp, "{}", contentType: "application/json");
- }
- resp.Close();
- }
-
- private void WriteResponse(HttpListenerResponse resp, string content, string contentType = "application/json")
- {
- var buffer = System.Text.Encoding.UTF8.GetBytes(content);
- resp.ContentType = contentType;
- resp.ContentLength64 = buffer.Length;
- resp.OutputStream.Write(buffer, 0, buffer.Length);
- }
- }
-}
diff --git a/Device/DeviceSimulator.cs b/Device/DeviceSimulator.cs
deleted file mode 100644
index 8edeae0..0000000
--- a/Device/DeviceSimulator.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using CncSimulator.Config;
-using CncSimulator.Device;
-using CncSimulator.Generator;
-
-namespace CncSimulator.Device
-{
- /// 单台设备的状态机与仿真逻辑
- public class DeviceSimulator
- {
- public DeviceState State { get; private set; }
- private readonly Random _rnd;
- private readonly List _programs = new List { "O0001", "O0002", "1566.NC", "PART-A", "TEST-03" };
- private int _currentScenarioIndex = 0;
- private int _tickCounter = 0;
-
- public DeviceSimulator(DeviceConfig cfg, int dataChangeInterval)
- {
- State = new DeviceState
- {
- DeviceCode = cfg.DeviceCode,
- Desc = cfg.Desc,
- ProgramName = cfg.InitialProgram ?? "O0001",
- PartCount = cfg.InitialPartCount,
- DataChangeInterval = dataChangeInterval
- };
- // 使用不同种子避免同步
- _rnd = new Random(cfg.DeviceCode.GetHashCode());
- // 初始化一个随机偏移,确保场景起始不一致
- _currentScenarioIndex = _rnd.Next(0, _programs.Count);
- }
-
- public void Tick()
- {
- if (!State.IsOnline) return;
- // 简易定时器:每次 Tick 增加一个单位时间,依据当前场景更新数据
- _tickCounter++;
- // 每次数据变化的间隔由 DataChangeInterval 决定
- if (_tickCounter < State.DataChangeInterval) return;
- _tickCounter = 0;
-
- // 根据当前场景执行更新逻辑
- switch (State.CurrentScenario)
- {
- case "machining":
- State.PartCount += 1;
- State.RunStatus = 3;
- State.OperateMode = 1;
- State.MachiningStatus = "G01";
- break;
- default:
- break;
- }
- }
-
- public void SetScenario(string scenarioName, int duration)
- {
- State.CurrentScenario = scenarioName;
- State.ScenarioDuration = duration;
- State.ScenarioTick = 0;
- }
-
- // 简化的场景更新接口,供 ScenarioPlayer 调用
- public void ApplyScenarioUpdate(string scenarioName, int duration, string programOverride = null)
- {
- SetScenario(scenarioName, duration);
- if (!string.IsNullOrWhiteSpace(programOverride))
- {
- State.ProgramName = programOverride;
- }
- }
- }
-}
diff --git a/Device/DeviceState.cs b/Device/DeviceState.cs
deleted file mode 100644
index ab0003c..0000000
--- a/Device/DeviceState.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-namespace CncSimulator.Device
-{
- /// 单台模拟设备的完整状态
- public class DeviceState
- {
- // 固定信息
- public string DeviceCode { get; set; }
- public string Desc { get; set; }
-
- // 动态状态
- public string CurrentScenario { get; set; } = "idle";
- public bool IsOnline { get; set; } = true;
- public string ProgramName { get; set; } = "O0001";
- public int PartCount { get; set; } = 0;
- public int DeviceStatus { get; set; } = 1; // _io_status
- public int RunStatus { get; set; } = 0; // 0=待机 1=运行 3=加工中
- public int OperateMode { get; set; } = 1; // 1=MEM 10=JOG
- public decimal SpindleSpeedSet { get; set; } = 450;
- public decimal FeedSpeedSet { get; set; } = 60;
- public decimal SpindleSpeedActual { get; set; } = 0;
- public decimal FeedSpeedActual { get; set; } = 0;
- public decimal SpindleLoad { get; set; } = 0;
- public decimal SpindleOverride { get; set; } = 100;
- public decimal PowerOnTime { get; set; } = 0;
- public decimal RunTime { get; set; } = 0;
- public decimal CuttingTime { get; set; } = 0;
- public decimal CycleTime { get; set; } = 0;
- public string MachiningStatus { get; set; } = "";
- public string ProgramContent { get; set; } = "";
-
- // 剧本控制
- public int ScenarioTick { get; set; } = 0;
- public int ScenarioDuration { get; set; } = 10;
-
- // 上一次的part_count(用于检测手动清零)
- public int LastPartCount { get; set; } = 0;
-
- // 累计数据变化间隔(用于递增时间字段)
- public int DataChangeInterval { get; set; } = 10;
- }
-}
diff --git a/Device/ScenarioPlayer.cs b/Device/ScenarioPlayer.cs
deleted file mode 100644
index eb40789..0000000
--- a/Device/ScenarioPlayer.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace CncSimulator.Device
-{
- /// 剧本播放器的简化实现(占位,未直接驱动状态)
- public class ScenarioPlayer
- {
- public ScenarioPlayer()
- {
- }
-
- public void Tick(DeviceState state)
- {
- // 简化实现:不改变状态,留作未来扩展点
- }
-
- public void TriggerEvent(string eventType)
- {
- // 事件占位
- }
- }
-}
diff --git a/Generator/FanucDataGenerator.cs b/Generator/FanucDataGenerator.cs
deleted file mode 100644
index 0af4e0f..0000000
--- a/Generator/FanucDataGenerator.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Newtonsoft.Json.Linq;
-using CncSimulator.Device;
-
-namespace CncSimulator.Generator
-{
- /// Fanuc 数据生成器:生成 19 个 Tag 的 JSON 表示
- public class FanucDataGenerator : IBrandGenerator
- {
- public string BrandKey => "fanuc";
-
- public JObject GenerateDevice(DeviceState state)
- {
- if (state == null) return null;
- var now = DateTime.Now;
- var rnd = new Random(state.DeviceCode.GetHashCode());
- var tags = new List();
- string[] tagNames = new string[] {"_io_status","Tag2","Tag5","Tag6","Tag7","Tag8","Tag9","Tag11","Tag14","Tag17","Tag18","Tag19","Tag20","Tag21","Tag22","Tag23","Tag24","Tag25","Tag26"};
- for (int i = 0; i < tagNames.Length; i++)
- {
- int offset = rnd.Next(-5, 1); // -5 ~ 0
- string time = now.AddSeconds(offset).ToString("yyyy-MM-dd HH:mm:ss");
- string value = GetTagValue(i, state);
- string desc = GetTagDesc(i, state);
- tags.Add(new JObject
- {
- ["name"] = tagNames[i],
- ["time"] = time,
- ["value"] = value,
- ["quality"] = "0",
- ["desc"] = desc
- });
- }
- var deviceObj = new JObject
- {
- ["device"] = state.DeviceCode,
- ["desc"] = state.Desc,
- ["tags"] = new JArray(tags)
- };
- return deviceObj;
- }
-
- private string GetTagDesc(int index, DeviceState state)
- {
- return index switch
- {
- 0 => "IO状态",
- 1 => "当前轴数",
- 2 => "当前加工程序",
- 3 => "主程序号",
- 4 => "加工程序内容",
- 5 => "加工零件数",
- 6 => "运行状态",
- 7 => "操作模式",
- 8 => "当前主轴倍率",
- 9 => "主轴设定速度",
- 10 => "进给设定速度",
- 11 => "主轴实际速度",
- 12 => "进给实际转速",
- 13 => "主轴负载",
- 14 => "开机时间",
- 15 => "运行时间",
- 16 => "切削时间",
- 17 => "循环时间",
- 18 => state.MachiningStatus,
- _ => ""
- };
- }
-
- private string GetTagValue(int index, DeviceState state)
- {
- switch (index)
- {
- case 0: return $"{state.DeviceStatus}.00000"; // _io_status
- case 1: return "4.00000"; // Tag2
- case 2: return state.ProgramName; // Tag5
- case 3: return "N0"; // Tag6
- case 4: return $"<{state.ProgramName}>\nG40G49G80\n( SIMULATOR )"; // Tag7
- case 5: return $"{state.PartCount}.00000"; // Tag8
- case 6: return $"{state.RunStatus}.00000"; // Tag9
- case 7: return $"{state.OperateMode}.00000"; // Tag11
- case 8: return state.SpindleOverride.ToString("0.00000"); // Tag14
- case 9: return state.SpindleSpeedSet.ToString("0.00000"); // Tag17
- case 10: return state.FeedSpeedSet.ToString("0.00000"); // Tag18
- case 11: return state.SpindleSpeedActual.ToString("0.00000"); // Tag19
- case 12: return state.FeedSpeedActual.ToString("0.00000"); // Tag20
- case 13: return state.SpindleLoad.ToString("0.00000"); // Tag21
- case 14: return state.PowerOnTime.ToString("0.00000"); // Tag22
- case 15: return state.RunTime.ToString("0.00000"); // Tag23
- case 16: return state.CuttingTime.ToString("0.00000"); // Tag24
- case 17: return state.CycleTime.ToString("0.00000"); // Tag25
- case 18: return state.MachiningStatus ?? ""; // Tag26
- default: return "0.00000";
- }
- }
- }
-}
diff --git a/Generator/IBrandGenerator.cs b/Generator/IBrandGenerator.cs
deleted file mode 100644
index bfff025..0000000
--- a/Generator/IBrandGenerator.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Newtonsoft.Json.Linq;
-
-namespace CncSimulator.Generator
-{
- /// 品牌数据生成器接口
- public interface IBrandGenerator
- {
- string BrandKey { get; }
- JObject GenerateDevice(CncSimulator.Device.DeviceState state);
- }
-}
diff --git a/InstallUtil.InstallLog b/InstallUtil.InstallLog
deleted file mode 100644
index a2949ef..0000000
--- a/InstallUtil.InstallLog
+++ /dev/null
@@ -1,28 +0,0 @@
-
-正在运行事务处理安装。
-
-正在开始安装的“安装”阶段。
-查看日志文件的内容以获得 E:\opencode\haoliang\src\CncCollector\bin\CncCollector.exe 程序集的进度。
-该文件位于 E:\opencode\haoliang\src\CncCollector\bin\CncCollector.InstallLog。
-
-“安装”阶段已成功完成,正在开始“提交”阶段。
-查看日志文件的内容以获得 E:\opencode\haoliang\src\CncCollector\bin\CncCollector.exe 程序集的进度。
-该文件位于 E:\opencode\haoliang\src\CncCollector\bin\CncCollector.InstallLog。
-
-“提交”阶段已成功完成。
-
-已完成事务处理安装。
-
-
-正在开始卸载。
-查看日志文件的内容以获得 E:\opencode\haoliang\src\CncCollector\bin\CncCollector.exe 程序集的进度。
-该文件位于 E:\opencode\haoliang\src\CncCollector\bin\CncCollector.InstallLog。
-
-卸载完成。
-
-
-正在开始卸载。
-查看日志文件的内容以获得 C:\CncCollector\CncCollector.exe 程序集的进度。
-该文件位于 C:\CncCollector\CncCollector.InstallLog。
-
-卸载完成。
diff --git a/Program.cs b/Program.cs
deleted file mode 100644
index f789faa..0000000
--- a/Program.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Threading;
-using log4net;
-using log4net.Config;
-using CncCollector.Config;
-using CncCollector.Core;
-using CncCollector.Api;
-
-namespace CncCollector
-{
- ///
- /// 主入口:启动采集引擎与管理 API。
- ///
- internal class Program
- {
- private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
-
- static void Main(string[] args)
- {
- // 初始化日志
- XmlConfigurator.Configure();
-
- // 假设默认配置:从配置文件加载,若失败使用硬编码默认值
- var defaultConfig = new CncModels.Entity.CollectorConfig
- {
- ApiPort = 9000,
- ApiKey = "",
- HeartbeatInterval = 60,
- ConfigPollInterval = 30,
- DailySummaryTime = TimeSpan.FromHours(1),
- CollectRetryCount = 3,
- CollectRetryIntervalSeconds = 5,
- CollectFailAlertThreshold = 3
- };
-
- // 数据库连接字符串,实际部署应改为配置文件读取
- string connectionString = "server=127.0.0.1;user=root;password=123456;database=cnc_business;SslMode=none";
-
- // 从 DB 覆盖运行时配置
- try
- {
- ConfigLoader.LoadRuntimeConfig(connectionString, defaultConfig);
- }
- catch (Exception e)
- {
- Log.Error("加载运行时配置失败,使用默认配置", e);
- }
-
- var engine = new CollectorEngine(connectionString, defaultConfig);
- engine.Start();
- var api = new CollectorApiServer(engine, defaultConfig.ApiKey, defaultConfig.ApiPort);
- api.Start();
-
- Console.WriteLine("CNC 收集服务已启动,按任意键退出...");
- Console.ReadKey();
-
- api.Stop();
- engine.Stop();
- }
- }
-}
diff --git a/_test_pages.js b/_test_pages.js
deleted file mode 100644
index 823d828..0000000
--- a/_test_pages.js
+++ /dev/null
@@ -1,23 +0,0 @@
-async (page) => {
- const results = [];
- const urls = [
- {name: 'C3.9 device detail', url: 'http://127.0.0.1/admin/machine/1'},
- {name: 'C4 brand', url: 'http://127.0.0.1/admin/brand'},
- {name: 'C5 collect-address', url: 'http://127.0.0.1/admin/collect-address'},
- {name: 'C6 worker', url: 'http://127.0.0.1/admin/worker'},
- {name: 'C7 production', url: 'http://127.0.0.1/admin/production'},
- {name: 'C8 alert', url: 'http://127.0.0.1/admin/alert'},
- {name: 'C9 settings', url: 'http://127.0.0.1/admin/settings'},
- {name: 'C10 log', url: 'http://127.0.0.1/admin/log'},
- {name: 'C11 screen-config', url: 'http://127.0.0.1/admin/screen-config'},
- ];
- for (const u of urls) {
- await page.goto(u.url, {waitUntil: 'networkidle', timeout: 10000}).catch(() => {});
- const title = await page.title();
- const bodyText = await page.evaluate(() => document.body.innerText.substring(0, 200));
- const hasTable = await page.locator('table').count();
- const hasChart = await page.locator('canvas, svg').count();
- results.push(u.name + ': title=' + title + ', table=' + hasTable + ', chart=' + hasChart + ', bodyLen=' + bodyText.length);
- }
- return results.join('\n');
-}
diff --git a/deploy-admin.ps1 b/deploy-admin.ps1
deleted file mode 100644
index 7e9609a..0000000
--- a/deploy-admin.ps1
+++ /dev/null
@@ -1,67 +0,0 @@
-# ============================================================
-# deploy-admin.ps1 — 一键编译后端+前端并部署到 admin 目录
-# 用法:在项目根目录执行 .\deploy-admin.ps1
-# ============================================================
-
-[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
-[Console]::InputEncoding = [System.Text.Encoding]::UTF8
-
-$ErrorActionPreference = "Stop"
-$projectRoot = $PSScriptRoot
-
-Write-Host ""
-Write-Host "========================================" -ForegroundColor Cyan
-Write-Host " CNC 系统一键部署脚本" -ForegroundColor Cyan
-Write-Host "========================================" -ForegroundColor Cyan
-Write-Host ""
-
-# --------------------------------------------------
-# 第1步:编译后端
-# --------------------------------------------------
-Write-Host "[1/2] 编译后端 API ..." -ForegroundColor Yellow
-dotnet build "$projectRoot\CncDataSystem.sln"
-if ($LASTEXITCODE -ne 0) {
- Write-Host "后端编译失败!" -ForegroundColor Red
- exit 1
-}
-Write-Host "后端编译完成 ✓" -ForegroundColor Green
-Write-Host ""
-
-# --------------------------------------------------
-# 第2步:编译前端并输出到 admin 目录
-# --------------------------------------------------
-Write-Host "[2/2] 编译前端(输出到 src\CncWebApi\admin\)..." -ForegroundColor Yellow
-
-$frontendDir = Join-Path $projectRoot "frontend"
-
-# 安装依赖(如果 node_modules 不存在)
-if (-not (Test-Path "$frontendDir\node_modules")) {
- Write-Host " 安装前端依赖 ..." -ForegroundColor Gray
- npm install --prefix $frontendDir
- if ($LASTEXITCODE -ne 0) {
- Write-Host "前端依赖安装失败!" -ForegroundColor Red
- exit 1
- }
-}
-
-# 构建前端(vite.config.ts 已配置 outDir 指向 ../src/CncWebApi/admin)
-npm run build --prefix $frontendDir
-if ($LASTEXITCODE -ne 0) {
- Write-Host "前端编译失败!" -ForegroundColor Red
- exit 1
-}
-
-$adminDir = Join-Path $projectRoot "src\CncWebApi\admin"
-$fileCount = (Get-ChildItem $adminDir -Recurse -File).Count
-Write-Host "前端编译完成 ✓($fileCount 个文件)" -ForegroundColor Green
-Write-Host ""
-
-# --------------------------------------------------
-# 完成
-# --------------------------------------------------
-Write-Host "========================================" -ForegroundColor Cyan
-Write-Host " 部署完成!" -ForegroundColor Cyan
-Write-Host " 后端 API:http://192.168.1.202/api/health" -ForegroundColor White
-Write-Host " 前端页面:http://192.168.1.202/admin/" -ForegroundColor White
-Write-Host "========================================" -ForegroundColor Cyan
-Write-Host ""
diff --git a/src/CncService/Impl/DashboardService.cs b/src/CncService/Impl/DashboardService.cs
index c07d33c..a999013 100644
--- a/src/CncService/Impl/DashboardService.cs
+++ b/src/CncService/Impl/DashboardService.cs
@@ -107,7 +107,7 @@ namespace CncService.Impl
string serviceStatusText = "NotInstalled";
if (_serviceChecker != null)
{
- var svc = _serviceChecker.GetServiceStatus("collector-service");
+ var svc = _serviceChecker.GetServiceStatus("CncCollector");
serviceStatusText = svc.ToString();
}
@@ -119,7 +119,7 @@ namespace CncService.Impl
uptimeSeconds = heartbeatRunning ? heartbeatUptime : 0,
lastCollectTime,
serviceStatus = serviceStatusText,
- serviceName = "collector-service",
+ serviceName = "CncCollector",
serviceMessage = (string)null
};
}
diff --git a/test-date-debug.js b/test-date-debug.js
deleted file mode 100644
index d7c1b16..0000000
--- a/test-date-debug.js
+++ /dev/null
@@ -1,50 +0,0 @@
-const { chromium } = require('playwright');
-
-(async () => {
- const browser = await chromium.launch({ headless: true });
- const page = await browser.newPage();
-
- // 登录
- await page.goto('http://127.0.0.1/admin/login');
- await page.waitForTimeout(500);
- await page.fill('input[type="text"]', 'admin');
- await page.fill('input[type="password"]', 'admin123');
- await page.click('button:has-text("登录")');
- await page.waitForTimeout(2000);
-
- // 导航到产量报表
- await page.goto('http://127.0.0.1/admin/production');
- await page.waitForTimeout(3000);
-
- // 获取el-date-picker内部真实值
- const result = await page.evaluate(() => {
- const inputs = document.querySelectorAll('.el-date-editor input');
- const startInput = inputs[0];
- const endInput = inputs[1];
-
- // 获取Vue组件实例
- const pickerEl = document.querySelector('.el-date-editor');
- const vueInstance = pickerEl?.__vue__;
-
- return {
- startInputValue: startInput?.value,
- endInputValue: endInput?.value,
- startInputType: startInput?.type,
- vueModelValue: vueInstance ? JSON.stringify(vueInstance.modelValue || vueInstance.$props?.modelValue) : 'no vue instance',
- // 直接读input的placeholder
- startPlaceholder: startInput?.placeholder,
- endPlaceholder: endInput?.placeholder,
- };
- });
-
- console.log('日期选择器状态:', JSON.stringify(result, null, 2));
-
- // 检查raw HTML
- const rawHtml = await page.evaluate(() => {
- const picker = document.querySelector('.el-date-editor');
- return picker?.outerHTML?.substring(0, 500);
- });
- console.log('\nraw HTML:', rawHtml);
-
- await browser.close();
-})();
diff --git a/test-production-full.js b/test-production-full.js
deleted file mode 100644
index d343078..0000000
--- a/test-production-full.js
+++ /dev/null
@@ -1,279 +0,0 @@
-const { chromium } = require('playwright');
-
-(async () => {
- const browser = await chromium.launch({ headless: true });
- const page = await browser.newPage();
- const results = [];
-
- function log(category, name, pass, detail) {
- const status = pass ? '✅' : '❌';
- console.log(`${status} [${category}] ${name}: ${detail}`);
- results.push({ category, name, pass, detail });
- }
-
- // === 登录 ===
- await page.goto('http://127.0.0.1/admin/login');
- await page.waitForTimeout(500);
- await page.fill('input[type="text"]', 'admin');
- await page.fill('input[type="password"]', 'admin123');
- await page.click('button:has-text("登录")');
- await page.waitForTimeout(2000);
-
- // === 导航到产量报表 ===
- await page.goto('http://127.0.0.1/admin/production');
- await page.waitForTimeout(3000);
-
- // =====================
- // 1. 页面基本加载
- // =====================
- console.log('\n========== 1. 页面基本加载 ==========');
-
- const title = await page.title();
- log('页面', '页面标题', title.length > 0, `标题: ${title}`);
-
- const url = page.url();
- log('页面', 'URL正确', url.includes('production'), `URL: ${url}`);
-
- // =====================
- // 2. 日期选择器
- // =====================
- console.log('\n========== 2. 日期选择器 ==========');
-
- const dateInputs = await page.$$eval('.el-date-editor input', els => els.map(e => e.value));
- const today = new Date();
- const todayStr = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`;
- log('日期', '默认日期是今天', dateInputs[0] === todayStr && dateInputs[1] === todayStr, `开始=${dateInputs[0]}, 结束=${dateInputs[1]}, 今天=${todayStr}`);
-
- // =====================
- // 3. 汇总卡片
- // =====================
- console.log('\n========== 3. 汇总卡片 ==========');
-
- const summaryCards = await page.evaluate(() => {
- const cards = document.querySelectorAll('.el-card');
- const results = [];
- for (const card of cards) {
- const text = card.textContent.trim();
- if (text.includes('总产量') || text.includes('运行机床') || text.includes('切削总时') || text.includes('平均产量')) {
- results.push(text.replace(/\n/g, ' ').substring(0, 80));
- }
- }
- return results;
- });
- log('汇总', '总产量卡片有数据', summaryCards.some(c => c.includes('总产量')), summaryCards.filter(c => c.includes('总产量')).join(' | ') || '未找到');
- log('汇总', '运行机床卡片', summaryCards.some(c => c.includes('运行机床')), summaryCards.filter(c => c.includes('运行机床')).join(' | ') || '未找到');
- log('汇总', '切削总时卡片', summaryCards.some(c => c.includes('切削总时')), summaryCards.filter(c => c.includes('切削总时')).join(' | ') || '未找到');
- log('汇总', '平均产量卡片', summaryCards.some(c => c.includes('平均产量')), summaryCards.filter(c => c.includes('平均产量')).join(' | ') || '未找到');
-
- // =====================
- // 4. 筛选控件
- // =====================
- console.log('\n========== 4. 筛选控件 ==========');
-
- // 车间下拉
- const workshopOptions = await page.evaluate(() => {
- const sel = document.querySelectorAll('.el-select');
- // 第一个是车间
- return sel.length;
- });
- log('筛选', '下拉控件存在', workshopOptions >= 3, `找到${workshopOptions}个下拉`);
-
- // 查询按钮
- const queryBtn = await page.$('button:has-text("查询")');
- log('筛选', '查询按钮存在', queryBtn !== null, queryBtn ? '存在' : '不存在');
-
- // 重置按钮
- const resetBtn = await page.$('button:has-text("重置")');
- log('筛选', '重置按钮存在', resetBtn !== null, resetBtn ? '存在' : '不存在');
-
- // =====================
- // 5. 数据表格
- // =====================
- console.log('\n========== 5. 数据表格 ==========');
-
- const tableHeaders = await page.$$eval('.el-table__header th .cell', els => els.map(e => e.textContent.trim()));
- log('表格', '列头完整', tableHeaders.length >= 7, `列头: ${tableHeaders.join(', ')}`);
-
- const expectedHeaders = ['日期', '机床', '程序名', '产量', '运行时间', '切削时间', '日状态'];
- expectedHeaders.forEach(h => {
- log('表格', `列头含"${h}"`, tableHeaders.includes(h), tableHeaders.includes(h) ? '存在' : `缺失! 现有: ${tableHeaders.join(',')}`);
- });
-
- // 检查表格数据
- const tableRows = await page.$$eval('.el-table__body tr', trs =>
- trs.slice(0, 5).map(tr => {
- const cells = tr.querySelectorAll('td .cell');
- return Array.from(cells).map(c => c.textContent.trim());
- })
- );
- log('表格', '有数据行', tableRows.length > 0, `${tableRows.length}行`);
-
- if (tableRows.length > 0) {
- // 检查每列是否有数据
- const dateCol = tableRows.map(r => r[0]).filter(v => v && v !== '');
- log('表格', '日期列有数据', dateCol.length > 0, `${dateCol.length}/${tableRows.length}行有日期, 样例: ${dateCol[0]}`);
-
- const machineCol = tableRows.map(r => r[1]).filter(v => v && v !== '');
- log('表格', '机床列有数据', machineCol.length > 0, `${machineCol.length}/${tableRows.length}行有机床, 样例: ${machineCol[0]}`);
-
- const programCol = tableRows.map(r => r[2]).filter(v => v && v !== '');
- log('表格', '程序名列有数据', programCol.length > 0, `${programCol.length}/${tableRows.length}行有程序名, 样例: ${programCol[0]}`);
-
- const qtyCol = tableRows.map(r => r[3]).filter(v => v && v !== '' && v !== '-');
- log('表格', '产量列有数据', qtyCol.length > 0, `${qtyCol.length}/${tableRows.length}行有产量, 样例: ${qtyCol.slice(0, 3).join(',')}`);
-
- const statusCol = tableRows.map(r => r[6]).filter(v => v && v !== '');
- log('表格', '日状态列有数据', statusCol.length > 0, `${statusCol.length}/${tableRows.length}行有状态, 样例: ${statusCol.slice(0, 3).join(',')}`);
-
- // 打印前3行完整数据
- console.log('\n 前3行完整数据:');
- tableRows.slice(0, 3).forEach((row, i) => console.log(` 行${i+1}: ${JSON.stringify(row)}`));
- }
-
- // =====================
- // 6. 分页
- // =====================
- console.log('\n========== 6. 分页 ==========');
-
- const pagination = await page.$('.el-pagination');
- log('分页', '分页组件存在', pagination !== null, pagination ? '存在' : '不存在');
-
- const totalText = await page.evaluate(() => {
- const total = document.querySelector('.el-pagination__total');
- return total ? total.textContent.trim() : '未找到';
- });
- log('分页', '总数显示', totalText !== '未找到', totalText);
-
- // =====================
- // 7. 操作按钮
- // =====================
- console.log('\n========== 7. 操作按钮 ==========');
-
- const adjustBtns = await page.$$('button:has-text("修正")');
- log('操作', '修正按钮存在', adjustBtns.length > 0, `${adjustBtns.length}个修正按钮`);
-
- const historyBtns = await page.$$('button:has-text("修正历史")');
- log('操作', '修正历史按钮存在', historyBtns.length > 0, `${historyBtns.length}个修正历史按钮`);
-
- // =====================
- // 8. 交互测试:点击修正
- // =====================
- console.log('\n========== 8. 交互测试:修正 ==========');
-
- if (adjustBtns.length > 0) {
- await adjustBtns[0].click();
- await page.waitForTimeout(1000);
-
- const dialog = await page.$('.el-dialog');
- const dialogVisible = dialog && await dialog.isVisible();
- log('交互', '点击修正弹出弹窗', dialogVisible, dialogVisible ? '弹窗可见' : '弹窗不可见');
-
- if (dialogVisible) {
- const dialogTitle = await page.evaluate(() => {
- const t = document.querySelector('.el-dialog__title');
- return t ? t.textContent.trim() : '无标题';
- });
- log('交互', '弹窗标题', true, dialogTitle);
-
- // 检查弹窗内的表单元素
- const dialogInputs = await page.$$eval('.el-dialog input', els => els.map(e => ({ type: e.type, placeholder: e.placeholder, value: e.value })));
- log('交互', '弹窗表单元素', dialogInputs.length > 0, `${JSON.stringify(dialogInputs)}`);
-
- // 关闭弹窗
- const closeBtn = await page.$('.el-dialog__headerbtn');
- if (closeBtn) { await closeBtn.click(); await page.waitForTimeout(500); }
- }
- }
-
- // =====================
- // 9. 交互测试:点击修正历史
- // =====================
- console.log('\n========== 9. 交互测试:修正历史 ==========');
-
- if (historyBtns.length > 0) {
- await historyBtns[0].click();
- await page.waitForTimeout(1000);
-
- const dialog = await page.$('.el-dialog');
- const dialogVisible = dialog && await dialog.isVisible();
- log('交互', '点击修正历史弹出弹窗', dialogVisible, dialogVisible ? '弹窗可见' : '弹窗不可见');
-
- if (dialogVisible) {
- const dialogTitle = await page.evaluate(() => {
- const t = document.querySelector('.el-dialog__title');
- return t ? t.textContent.trim() : '无标题';
- });
- log('交互', '弹窗标题', true, dialogTitle);
-
- // 关闭
- const closeBtn = await page.$('.el-dialog__headerbtn');
- if (closeBtn) { await closeBtn.click(); await page.waitForTimeout(500); }
- }
- }
-
- // =====================
- // 10. 交互测试:重置按钮
- // =====================
- console.log('\n========== 10. 交互测试:重置 ==========');
-
- if (resetBtn) {
- await resetBtn.click();
- await page.waitForTimeout(3000);
-
- const dateAfterReset = await page.$$eval('.el-date-editor input', els => els.map(e => e.value));
- log('交互', '重置后日期变化', true, `重置后: ${dateAfterReset.join(' - ')}`);
-
- const rowsAfterReset = await page.$$eval('.el-table__body tr', trs => trs.length);
- log('交互', '重置后有数据', rowsAfterReset > 0, `${rowsAfterReset}行`);
- }
-
- // =====================
- // 11. 交互测试:查询按钮
- // =====================
- console.log('\n========== 11. 交互测试:查询 ==========');
-
- if (queryBtn) {
- await queryBtn.click();
- await page.waitForTimeout(3000);
-
- const rowsAfterQuery = await page.$$eval('.el-table__body tr', trs => trs.length);
- log('交互', '查询后有数据', rowsAfterQuery > 0, `${rowsAfterQuery}行`);
- }
-
- // =====================
- // 12. 交互测试:分页切换
- // // =====================
- console.log('\n========== 12. 交互测试:分页 ==========');
-
- const nextBtn = await page.$('.el-pagination .btn-next');
- if (nextBtn) {
- const isEnabled = await nextBtn.isEnabled();
- if (isEnabled) {
- await nextBtn.click();
- await page.waitForTimeout(2000);
- const page2Rows = await page.$$eval('.el-table__body tr', trs => trs.length);
- log('交互', '翻页后有数据', page2Rows > 0, `第2页${page2Rows}行`);
- } else {
- log('交互', '翻页', false, '下一页按钮不可用(可能只有1页)');
- }
- }
-
- // =====================
- // 汇总
- // =====================
- console.log('\n========================================');
- const passed = results.filter(r => r.pass).length;
- const failed = results.filter(r => !r.pass).length;
- console.log(`总计: ${results.length}项, 通过: ${passed}, 失败: ${failed}`);
-
- if (failed > 0) {
- console.log('\n失败项:');
- results.filter(r => !r.pass).forEach(r => console.log(` ❌ [${r.category}] ${r.name}: ${r.detail}`));
- }
-
- // 截图
- await page.screenshot({ path: 'test-screenshots/production-full-test.png', fullPage: true });
- console.log('\n截图已保存');
-
- await browser.close();
-})();
diff --git a/test-production-page.js b/test-production-page.js
deleted file mode 100644
index 60d31cd..0000000
--- a/test-production-page.js
+++ /dev/null
@@ -1,79 +0,0 @@
-const { chromium } = require('playwright');
-
-(async () => {
- const browser = await chromium.launch({ headless: true });
- const page = await browser.newPage();
-
- // 1. 登录
- console.log('1. 登录...');
- await page.goto('http://127.0.0.1/admin/');
- await page.waitForTimeout(1000);
-
- // 检查是否在登录页
- const url = page.url();
- console.log('当前URL:', url);
-
- if (url.includes('login')) {
- await page.fill('input[type="text"], input[placeholder*="用户名"]', 'admin');
- await page.fill('input[type="password"]', 'admin123');
- await page.click('button:has-text("登录")');
- await page.waitForTimeout(2000);
- console.log('登录后URL:', page.url());
- }
-
- // 2. 导航到产量报表
- console.log('\n2. 导航到产量报表...');
- await page.goto('http://127.0.0.1/admin/production');
- await page.waitForTimeout(3000);
- console.log('产量报表URL:', page.url());
-
- // 3. 截图
- await page.screenshot({ path: 'test-screenshots/production-page.png', fullPage: true });
- console.log('截图已保存');
-
- // 4. 检查日期选择器的值
- const dateInputs = await page.$$eval('.el-date-editor input', els => els.map(e => e.value));
- console.log('\n3. 日期选择器值:', dateInputs);
-
- // 5. 检查表格内容
- const tableContent = await page.evaluate(() => {
- const rows = document.querySelectorAll('.el-table__body tr');
- const result = [];
- for (let i = 0; i < Math.min(rows.length, 5); i++) {
- const cells = rows[i].querySelectorAll('td .cell');
- const row = [];
- cells.forEach(c => row.push(c.textContent.trim()));
- result.push(row);
- }
- return result;
- });
- console.log('\n4. 表格内容(前5行):');
- tableContent.forEach((row, i) => console.log(` 行${i + 1}:`, JSON.stringify(row)));
-
- // 6. 检查表格列头
- const headers = await page.$$eval('.el-table__header th .cell', els => els.map(e => e.textContent.trim()));
- console.log('\n5. 表格列头:', headers);
-
- // 7. 检查汇总卡片
- const summary = await page.evaluate(() => {
- const cards = document.querySelectorAll('.summary-card, .el-card');
- const result = [];
- cards.forEach(c => result.push(c.textContent.trim().substring(0, 100)));
- return result;
- });
- console.log('\n6. 汇总卡片:', summary.slice(0, 5));
-
- // 8. 检查页面是否有错误
- const errors = await page.$$eval('.el-message--error, .el-notification', els => els.map(e => e.textContent.trim()));
- if (errors.length > 0) {
- console.log('\n错误信息:', errors);
- }
-
- // 9. 检查console错误
- page.on('console', msg => {
- if (msg.type() === 'error') console.log('CONSOLE ERROR:', msg.text());
- });
-
- await browser.close();
- console.log('\n完成');
-})();
diff --git a/test-production-v2.js b/test-production-v2.js
deleted file mode 100644
index 2a0b11c..0000000
--- a/test-production-v2.js
+++ /dev/null
@@ -1,52 +0,0 @@
-const { chromium } = require('playwright');
-
-(async () => {
- const browser = await chromium.launch({ headless: true });
- const context = await browser.newContext({ ignoreHTTPSErrors: true });
- const page = await context.newPage();
-
- // 登录
- console.log('1. 登录...');
- await page.goto('http://127.0.0.1/admin/login');
- await page.waitForTimeout(1000);
- await page.fill('input[type="text"], input[placeholder*="用户名"]', 'admin');
- await page.fill('input[type="password"]', 'admin123');
- await page.click('button:has-text("登录")');
- await page.waitForTimeout(2000);
- console.log('登录后URL:', page.url());
-
- // 导航到产量报表
- console.log('\n2. 导航到产量报表...');
- await page.goto('http://127.0.0.1/admin/production');
- await page.waitForTimeout(3000);
-
- // 截图
- await page.screenshot({ path: 'test-screenshots/production-page-v2.png', fullPage: true });
-
- // 检查日期
- const dateInputs = await page.$$eval('.el-date-editor input', els => els.map(e => e.value));
- console.log('日期选择器:', dateInputs);
-
- // 检查今天日期
- const today = new Date();
- console.log('JS Date today:', today.toISOString(), today.toLocaleDateString('zh-CN'));
-
- // 检查表格内容
- const rows = await page.$$eval('.el-table__body tr', trs =>
- trs.slice(0, 3).map(tr => {
- const cells = tr.querySelectorAll('td .cell');
- return Array.from(cells).map(c => c.textContent.trim());
- })
- );
- console.log('\n表格前3行:');
- rows.forEach((row, i) => console.log(` 行${i+1}:`, JSON.stringify(row)));
-
- // 检查汇总
- const summaryText = await page.evaluate(() => {
- const el = document.querySelector('.summary-card, .el-card');
- return el ? el.innerText.substring(0, 200) : '无汇总卡片';
- });
- console.log('\n汇总:', summaryText);
-
- await browser.close();
-})();
diff --git a/test-production-v3.js b/test-production-v3.js
deleted file mode 100644
index 7814635..0000000
--- a/test-production-v3.js
+++ /dev/null
@@ -1,45 +0,0 @@
-const { chromium } = require('playwright');
-
-(async () => {
- const browser = await chromium.launch({ headless: true });
- const page = await browser.newPage();
-
- // 收集console日志
- page.on('console', msg => {
- if (msg.type() === 'error' || msg.type() === 'warning') {
- console.log(`[${msg.type().toUpperCase()}]`, msg.text());
- }
- });
- // 收集网络请求
- page.on('response', resp => {
- if (resp.url().includes('/api/') && !resp.url().includes('login')) {
- console.log(`[API] ${resp.status()} ${resp.url()}`);
- }
- });
-
- // 登录
- await page.goto('http://127.0.0.1/admin/login');
- await page.waitForTimeout(500);
- await page.fill('input[type="text"]', 'admin');
- await page.fill('input[type="password"]', 'admin123');
- await page.click('button:has-text("登录")');
- await page.waitForTimeout(2000);
-
- // 导航到产量报表
- console.log('\n--- 导航到产量报表 ---');
- await page.goto('http://127.0.0.1/admin/production');
- await page.waitForTimeout(5000);
-
- // 检查dateRange reactive值
- const dateValue = await page.evaluate(() => {
- // 找到Vue实例
- const app = document.querySelector('#app');
- return {
- inputValues: Array.from(document.querySelectorAll('.el-date-editor input')).map(e => e.value),
- today: new Date().toISOString().split('T')[0]
- };
- });
- console.log('\n日期值:', JSON.stringify(dateValue));
-
- await browser.close();
-})();
diff --git a/tests/CncService.Tests/LogSerializationTests.cs b/tests/CncService.Tests/LogSerializationTests.cs
deleted file mode 100644
index f417ca1..0000000
--- a/tests/CncService.Tests/LogSerializationTests.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System.Text.Json;
-using Xunit;
-using CncService.LogAnalyzer;
-using CncService.Models;
-
-namespace CncService.Tests
-{
- public class LogSerializationTests
- {
- [Fact]
- public void LogAnalysisResult_Serialize_ToJson_Includes_Summary()
- {
- // Arrange
- var analysis = new LogAnalysisResult
- {
- Summary = "New log entry analyzed: no changes",
- DetailsJson = "{\"change\":false}",
- Confidence = 0.92
- };
-
- // Act
- var json = JsonSerializer.Serialize(analysis);
-
- // Assert
- Assert.Contains("Summary", json);
- Assert.Contains("New log entry analyzed", json);
- }
- }
-}