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