合并功能分支:模拟采集集成+仪表盘优化+采集服务部署修复
commit
b9555b807c
@ -0,0 +1,37 @@
|
||||
name: CI-Windows-WindowsServiceStatus
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, feat/windows-service-status-auto ]
|
||||
pull_request:
|
||||
branches: [ main, feat/windows-service-status-auto ]
|
||||
|
||||
jobs:
|
||||
build-test:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore NuGet packages
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build backend
|
||||
run: dotnet build -c Release --no-restore
|
||||
|
||||
- name: Run Windows service related tests
|
||||
run: dotnet test tests/CncService.Tests/CncService.Tests.csproj -c Release --no-build -v minimal --filter "FullyQualifiedName~WindowsServiceCheckerTests|FullyQualifiedName~DashboardServiceTests"
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd frontend
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Test summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "=== CI Summary ==="
|
||||
echo "Backend: Build + WindowsService/Dashboard tests"
|
||||
echo "Frontend: Build (vue-tsc + vite)"
|
||||
echo "=================="
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
using CncService.LogAnalyzer;
|
||||
using CncService.Models;
|
||||
|
||||
namespace CncService
|
||||
{
|
||||
// 扩展日志写入与分析结果传回接口,供分区日志写入及分析摘要能力使用
|
||||
public interface ILogIngestionService
|
||||
{
|
||||
// 写入采集日志及其分析摘要,返回写入是否成功
|
||||
Task<bool> WriteLogAsync(LogRecord record, LogAnalysisResult analysis);
|
||||
|
||||
// 读取最新一条日志及其分析摘要(用于后台看板等场景的快速查询示例)
|
||||
Task<LogIngestionResult> GetLatestLogAsync(string machineId, string programName);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace CncService.Models
|
||||
{
|
||||
// Minimal result wrapper for latest log fetch
|
||||
public class LogIngestionResult
|
||||
{
|
||||
public long LogId { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace CncService.Models
|
||||
{
|
||||
// Represents a raw log entry captured by the ingestion service
|
||||
public class LogRecord
|
||||
{
|
||||
public long LogId { get; set; }
|
||||
public string MachineId { get; set; }
|
||||
public string ProgramName { get; set; }
|
||||
public DateTime LogTime { get; set; }
|
||||
public string Action { get; set; }
|
||||
public string Result { get; set; }
|
||||
public string RawData { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="log4net" Version="2.0.16" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="simulator.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using CncService;
|
||||
using CncService.Models;
|
||||
using CncService.LogAnalyzer;
|
||||
|
||||
namespace CncWebApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class LogIngestionController : ControllerBase
|
||||
{
|
||||
private readonly ILogIngestionService _logIngestionService;
|
||||
|
||||
public LogIngestionController(ILogIngestionService logIngestionService)
|
||||
{
|
||||
_logIngestionService = logIngestionService;
|
||||
}
|
||||
|
||||
[HttpPost("ingest")]
|
||||
public async Task<IActionResult> Ingest([FromBody] LogIngestionRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
return BadRequest("请求为空");
|
||||
|
||||
var record = new LogRecord
|
||||
{
|
||||
LogId = request.LogId,
|
||||
MachineId = request.MachineId,
|
||||
ProgramName = request.ProgramName,
|
||||
LogTime = request.LogTime ?? DateTime.UtcNow,
|
||||
Action = request.Action,
|
||||
Result = request.Result,
|
||||
RawData = request.RawData
|
||||
};
|
||||
|
||||
var analysis = new LogAnalysisResult
|
||||
{
|
||||
Summary = request.AnalysisSummary,
|
||||
DetailsJson = request.DetailsJson,
|
||||
Confidence = request.Confidence
|
||||
};
|
||||
|
||||
var ok = await _logIngestionService.WriteLogAsync(record, analysis);
|
||||
if (ok)
|
||||
{
|
||||
return Ok(new { success = true, logId = record.LogId, analysisSummary = analysis.Summary });
|
||||
}
|
||||
return StatusCode(500, new { success = false, message = "写入失败" });
|
||||
}
|
||||
}
|
||||
|
||||
public class LogIngestionRequest
|
||||
{
|
||||
public long LogId { get; set; }
|
||||
public string MachineId { get; set; }
|
||||
public string ProgramName { get; set; }
|
||||
public DateTime? LogTime { get; set; }
|
||||
public string Action { get; set; }
|
||||
public string Result { get; set; }
|
||||
public string RawData { get; set; }
|
||||
public string AnalysisSummary { get; set; }
|
||||
public string DetailsJson { get; set; }
|
||||
public double? Confidence { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CncSimulator.Config
|
||||
{
|
||||
/// <summary>模拟器配置</summary>
|
||||
public class SimulatorConfig
|
||||
{
|
||||
[JsonProperty("gatewayPort")]
|
||||
public int GatewayPort { get; set; } = 9000;
|
||||
|
||||
[JsonProperty("addresses")]
|
||||
public List<AddressConfig> Addresses { get; set; } = new List<AddressConfig>();
|
||||
}
|
||||
|
||||
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<DeviceConfig> Devices { get; set; } = new List<DeviceConfig>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集引擎:负责加载采集地址、创建工作线程、心跳和配置轮询等核心调度。
|
||||
/// </summary>
|
||||
public class CollectorEngine
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly CollectorConfig _config;
|
||||
private readonly List<CollectWorker> _workers = new List<CollectWorker>();
|
||||
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<CollectAddress>("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 { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>重新加载地址配置并重启工作线程</summary>
|
||||
public void Refresh()
|
||||
{
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 日终汇总作业:在指定时间点执行,结账活跃段、聚合产量并标记完成。
|
||||
/// </summary>
|
||||
public class DailySummaryJob
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly ILog _log = LogManager.GetLogger(typeof(DailySummaryJob));
|
||||
|
||||
public DailySummaryJob(string connectionString)
|
||||
{
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发日终汇总逻辑。
|
||||
/// </summary>
|
||||
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 (简化实现). ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
/// <summary>日志记录器:内存环形缓冲 + 文件日志输出</summary>
|
||||
public class LogRecorder
|
||||
{
|
||||
private readonly int _capacity;
|
||||
private readonly List<LogEntry> _buffer = new List<LogEntry>();
|
||||
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<LogEntry> 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 零件产量分段跟踪引擎:维护内存中的活跃段状态,并定期写入数据库。
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理一个采集记录后的产量跟踪逻辑。
|
||||
/// </summary>
|
||||
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<ProductionSegment>(
|
||||
"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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>引擎:管理多个 SimulatorServer 实例</summary>
|
||||
public class SimulatorEngine
|
||||
{
|
||||
private readonly List<SimulatorServer> _servers = new List<SimulatorServer>();
|
||||
|
||||
public void LoadConfig(string jsonPath)
|
||||
{
|
||||
var json = File.ReadAllText(jsonPath);
|
||||
var cfg = JsonConvert.DeserializeObject<SimulatorConfig>(json);
|
||||
LoadConfig(cfg);
|
||||
}
|
||||
|
||||
public void LoadConfig(SimulatorConfig cfg)
|
||||
{
|
||||
_servers.Clear();
|
||||
foreach (var addr in cfg.Addresses)
|
||||
{
|
||||
var devices = new List<DeviceSimulator>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>单个地址的 HTTP 服务器及设备仿真集合</summary>
|
||||
public class SimulatorServer
|
||||
{
|
||||
public AddressConfig Address { get; private set; }
|
||||
public List<DeviceSimulator> 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<DeviceSimulator> devices)
|
||||
{
|
||||
Address = address;
|
||||
Devices = devices ?? new List<DeviceSimulator>();
|
||||
}
|
||||
|
||||
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<JObject>();
|
||||
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 = "<html><body><h2>管理界面开发中</h2></body></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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>单台设备的状态机与仿真逻辑</summary>
|
||||
public class DeviceSimulator
|
||||
{
|
||||
public DeviceState State { get; private set; }
|
||||
private readonly Random _rnd;
|
||||
private readonly List<string> _programs = new List<string> { "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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
namespace CncSimulator.Device
|
||||
{
|
||||
/// <summary>剧本播放器的简化实现(占位,未直接驱动状态)</summary>
|
||||
public class ScenarioPlayer
|
||||
{
|
||||
public ScenarioPlayer()
|
||||
{
|
||||
}
|
||||
|
||||
public void Tick(DeviceState state)
|
||||
{
|
||||
// 简化实现:不改变状态,留作未来扩展点
|
||||
}
|
||||
|
||||
public void TriggerEvent(string eventType)
|
||||
{
|
||||
// 事件占位
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using CncSimulator.Device;
|
||||
|
||||
namespace CncSimulator.Generator
|
||||
{
|
||||
/// <summary>Fanuc 数据生成器:生成 19 个 Tag 的 JSON 表示</summary>
|
||||
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<JObject>();
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace CncSimulator.Generator
|
||||
{
|
||||
/// <summary>品牌数据生成器接口</summary>
|
||||
public interface IBrandGenerator
|
||||
{
|
||||
string BrandKey { get; }
|
||||
JObject GenerateDevice(CncSimulator.Device.DeviceState state);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 主入口:启动采集引擎与管理 API。
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
-- Partitioned logs table draft
|
||||
-- 目标:按月分区日志表,提升写入吞吐和查询历史的性能
|
||||
-- 说明:本草案为初步设计,待评审后落地实现
|
||||
-- Assumptions:
|
||||
-- - MariaDB 10.x 版本,支持分区按 RANGE (TO_DAYS(log_time))
|
||||
-- - 日志字段与现有采集日志表接近
|
||||
-- - 每月一个分区,覆盖历史数据的归档策略待定
|
||||
DROP TABLE IF EXISTS logs_partitioned;
|
||||
CREATE TABLE logs_partitioned (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
machine_id INT NOT NULL,
|
||||
program_name VARCHAR(128) NOT NULL,
|
||||
log_time DATETIME NOT NULL,
|
||||
log_level VARCHAR(16) DEFAULT 'INFO',
|
||||
raw_payload JSON,
|
||||
analysis_summary TEXT,
|
||||
analysis_version VARCHAR(64) DEFAULT 'v1',
|
||||
-- 便于按机床与时间筛选的组合索引
|
||||
KEY idx_machine_time (machine_id, log_time),
|
||||
KEY idx_program_time (program_name, log_time)
|
||||
)
|
||||
PARTITION BY RANGE (TO_DAYS(log_time)) (
|
||||
PARTITION p202401 VALUES LESS THAN (TO_DAYS('2024-02-01')),
|
||||
PARTITION p202402 VALUES LESS THAN (TO_DAYS('2024-03-01')),
|
||||
PARTITION p202403 VALUES LESS THAN (TO_DAYS('2024-04-01')),
|
||||
PARTITION p202404 VALUES LESS THAN (TO_DAYS('2024-05-01')),
|
||||
PARTITION p202405 VALUES LESS THAN (TO_DAYS('2024-06-01')),
|
||||
PARTITION p202406 VALUES LESS THAN (TO_DAYS('2024-07-01')),
|
||||
PARTITION p202407 VALUES LESS THAN (TO_DAYS('2024-08-01')),
|
||||
PARTITION p202408 VALUES LESS THAN (TO_DAYS('2024-09-01')),
|
||||
PARTITION p202409 VALUES LESS THAN (TO_DAYS('2024-10-01')),
|
||||
PARTITION p202410 VALUES LESS THAN (TO_DAYS('2024-11-01')),
|
||||
PARTITION p202411 VALUES LESS THAN (TO_DAYS('2024-12-01')),
|
||||
PARTITION p202412 VALUES LESS THAN (TO_DAYS('2025-01-01')),
|
||||
PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01'))
|
||||
);
|
||||
|
||||
-- 备注:
|
||||
- 未来月份的分区建议通过定期执行脚本自动追加分区
|
||||
- 可以通过 ALTER TABLE logs_partitioned REORGANIZE PARTITION ...? 进行滚动归档
|
||||
@ -0,0 +1,94 @@
|
||||
import request from '@/utils/request'
|
||||
import type { ApiResponse, PaginatedResponse } from '@/types'
|
||||
|
||||
// --- 采集日志数据模型 ---
|
||||
export interface CollectAnalysis {
|
||||
id: number
|
||||
analysisTime: string
|
||||
collectAddressId: number
|
||||
addressName?: string
|
||||
machineId: number
|
||||
machineName?: string
|
||||
analysisType: string
|
||||
previousProgram?: string
|
||||
currentProgram?: string
|
||||
partCountDelta?: number
|
||||
analysisSummary?: string
|
||||
}
|
||||
|
||||
export interface CollectCycle {
|
||||
id: number
|
||||
cycleTime: string
|
||||
collectAddressId: number
|
||||
addressName?: string
|
||||
totalMachines: number
|
||||
successCount: number
|
||||
failCount: number
|
||||
hasAnomaly: number
|
||||
changeDistribution?: string
|
||||
cycleSummary?: string
|
||||
}
|
||||
|
||||
export interface CollectRaw {
|
||||
id: number
|
||||
logTime: string
|
||||
sourceAddress?: string
|
||||
contentPreview?: string
|
||||
}
|
||||
|
||||
// --- 公开的 API 封装 ---
|
||||
// 获取分析记录列表
|
||||
export function fetchAnalysisList(params?: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
dateRange?: string[] | null
|
||||
addressId?: number
|
||||
machineId?: number
|
||||
analysisType?: string
|
||||
programName?: string
|
||||
keyword?: string
|
||||
}) {
|
||||
return request.get<{ items: CollectAnalysis[]; total: number }>(
|
||||
'/admin/collect-log/analysis',
|
||||
{ params }
|
||||
)
|
||||
}
|
||||
|
||||
// 获取分析详情
|
||||
export function fetchAnalysisDetail(id: number) {
|
||||
return request.get<CollectAnalysis>(`/admin/collect-log/analysis/${id}`)
|
||||
}
|
||||
|
||||
// 根据原始日志检索分析记录
|
||||
export function fetchAnalysisByRaw(rawLogId: number | string) {
|
||||
return request.get<{ items: CollectAnalysis[] }>(`/admin/collect-log/analysis/by-raw/${rawLogId}`)
|
||||
}
|
||||
|
||||
// 获取采集周期列表
|
||||
export function fetchCycleList(params?: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
dateRange?: string[] | null
|
||||
addressId?: number
|
||||
hasAnomaly?: string
|
||||
}) {
|
||||
return request.get<{ items: CollectCycle[]; total: number }>(
|
||||
'/admin/collect-log/cycle',
|
||||
{ params }
|
||||
)
|
||||
}
|
||||
|
||||
// 获取原始日志列表
|
||||
export function fetchRawList(params?: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
dateRange?: string[] | null
|
||||
addressId?: number
|
||||
}) {
|
||||
return request.get<{ items: CollectRaw[]; total: number }>(
|
||||
'/admin/collect-log/raw',
|
||||
{ params }
|
||||
)
|
||||
}
|
||||
|
||||
export default {}
|
||||
@ -0,0 +1,223 @@
|
||||
import request from '@/utils/request'
|
||||
import type { ApiResponse } from '@/types'
|
||||
|
||||
// --- 模拟器数据模型 ---
|
||||
|
||||
/** 模拟器连接状态 */
|
||||
export interface SimulatorPing {
|
||||
running: boolean
|
||||
}
|
||||
|
||||
/** 数据库采集地址(模拟器返回) */
|
||||
export interface SimulatorAddress {
|
||||
dbId: number
|
||||
name: string
|
||||
url: string
|
||||
machineCount: number
|
||||
machines: { id: number; deviceCode: string; name: string }[]
|
||||
isRunning: boolean
|
||||
runningPort: number
|
||||
}
|
||||
|
||||
/** 模拟状态汇总 */
|
||||
export interface SimulatorStatus {
|
||||
dbAddressId: number
|
||||
name: string
|
||||
port: number
|
||||
isRunning: boolean
|
||||
totalDevices: number
|
||||
onlineDevices: number
|
||||
requestCount: number
|
||||
dataChangeInterval: number
|
||||
totalParts: number
|
||||
}
|
||||
|
||||
/** 设备状态 */
|
||||
export interface DeviceStatus {
|
||||
deviceCode: string
|
||||
desc: string
|
||||
scenario: string
|
||||
isOnline: boolean
|
||||
programName: string
|
||||
partCount: number
|
||||
runStatus: number
|
||||
operateMode: number
|
||||
spindleSpeedSet: number
|
||||
spindleSpeedActual: number
|
||||
feedSpeedSet: number
|
||||
feedSpeedActual: number
|
||||
spindleLoad: number
|
||||
machiningStatus: string
|
||||
scenarioTick: number
|
||||
scenarioDuration: number
|
||||
}
|
||||
|
||||
/** 单地址详情状态 */
|
||||
export interface AddressStatus {
|
||||
name: string
|
||||
port: number
|
||||
isRunning: boolean
|
||||
requestCount: number
|
||||
successCount: number
|
||||
failCount: number
|
||||
totalDevices: number
|
||||
onlineDevices: number
|
||||
dataChangeInterval: number
|
||||
scenarioMode: string
|
||||
networkError: string
|
||||
startTime: string
|
||||
uptime: string
|
||||
devices: DeviceStatus[]
|
||||
}
|
||||
|
||||
/** 零件统计 */
|
||||
export interface AddressStats {
|
||||
totalDevices: number
|
||||
onlineDevices: number
|
||||
totalParts: number
|
||||
partsByDevice: Record<string, {
|
||||
desc: string
|
||||
totalParts: number
|
||||
currentProgram: string
|
||||
currentPartCount: number
|
||||
programs: Record<string, number>
|
||||
}>
|
||||
}
|
||||
|
||||
/** 请求日志 */
|
||||
export interface SimulatorLog {
|
||||
index: number
|
||||
timestamp: string
|
||||
deviceCount: number
|
||||
keyData: string
|
||||
duration: number
|
||||
fullJson: string
|
||||
}
|
||||
|
||||
/** 事件历史 */
|
||||
export interface EventHistory {
|
||||
timestamp: string
|
||||
deviceCode: string
|
||||
eventType: string
|
||||
oldProgram: string
|
||||
newProgram: string
|
||||
partCountBefore: number
|
||||
partCountAfter: number
|
||||
detail: string
|
||||
}
|
||||
|
||||
// --- 网关API ---
|
||||
|
||||
/** 探测模拟器是否运行 */
|
||||
export function pingSimulator() {
|
||||
return request.get<SimulatorPing>('/admin/simulator/ping')
|
||||
}
|
||||
|
||||
/** 获取数据库采集地址列表 */
|
||||
export function fetchSimulatorAddresses() {
|
||||
return request.get<SimulatorAddress[]>('/admin/simulator/addresses')
|
||||
}
|
||||
|
||||
/** 获取所有模拟状态汇总 */
|
||||
export function fetchSimulatorStatus() {
|
||||
return request.get<SimulatorStatus[]>('/admin/simulator/status')
|
||||
}
|
||||
|
||||
/** 启动指定地址的模拟 */
|
||||
export function startSimulator(data: { dbAddressId: number; deviceCodes?: string[] }) {
|
||||
return request.post('/admin/simulator/start', data)
|
||||
}
|
||||
|
||||
/** 停止指定地址的模拟 */
|
||||
export function stopSimulator(data: { dbAddressId: number }) {
|
||||
return request.post('/admin/simulator/stop', data)
|
||||
}
|
||||
|
||||
/** 启动所有地址的模拟 */
|
||||
export function startAllSimulators() {
|
||||
return request.post('/admin/simulator/start-all')
|
||||
}
|
||||
|
||||
/** 停止所有地址的模拟 */
|
||||
export function stopAllSimulators() {
|
||||
return request.post('/admin/simulator/stop-all')
|
||||
}
|
||||
|
||||
/** 重新加载数据库配置 */
|
||||
export function reloadSimulator() {
|
||||
return request.post('/admin/simulator/reload')
|
||||
}
|
||||
|
||||
// --- 单地址API ---
|
||||
|
||||
/** 获取单地址状态 */
|
||||
export function fetchAddressStatus(port: number) {
|
||||
return request.get<AddressStatus>(`/admin/simulator/address/${port}/status`)
|
||||
}
|
||||
|
||||
/** 启动单地址数据模拟 */
|
||||
export function startAddressSimulation(port: number) {
|
||||
return request.post(`/admin/simulator/address/${port}/start`)
|
||||
}
|
||||
|
||||
/** 停止单地址数据模拟 */
|
||||
export function stopAddressSimulation(port: number) {
|
||||
return request.post(`/admin/simulator/address/${port}/stop`)
|
||||
}
|
||||
|
||||
/** 触发设备事件 */
|
||||
export function triggerDeviceEvent(port: number, data: { deviceId: string; eventType: string }) {
|
||||
return request.post(`/admin/simulator/address/${port}/event`, data)
|
||||
}
|
||||
|
||||
/** 修改数据变化频率 */
|
||||
export function setAddressInterval(port: number, data: { value: number }) {
|
||||
return request.post(`/admin/simulator/address/${port}/interval`, data)
|
||||
}
|
||||
|
||||
/** 设置网络异常类型 */
|
||||
export function setNetworkError(port: number, data: { type: string }) {
|
||||
return request.post(`/admin/simulator/address/${port}/network`, data)
|
||||
}
|
||||
|
||||
/** 切换剧本模式 */
|
||||
export function setScenarioMode(port: number, data: { mode: string }) {
|
||||
return request.post(`/admin/simulator/address/${port}/mode`, data)
|
||||
}
|
||||
|
||||
/** 获取请求日志 */
|
||||
export function fetchAddressLogs(port: number) {
|
||||
return request.get<SimulatorLog[]>(`/admin/simulator/address/${port}/logs`)
|
||||
}
|
||||
|
||||
/** 获取零件统计 */
|
||||
export function fetchAddressStats(port: number) {
|
||||
return request.get<AddressStats>(`/admin/simulator/address/${port}/stats`)
|
||||
}
|
||||
|
||||
/** 添加设备 */
|
||||
export function addDevice(port: number, data: { deviceCode: string; desc: string }) {
|
||||
return request.post(`/admin/simulator/address/${port}/add-device`, data)
|
||||
}
|
||||
|
||||
/** 移除设备 */
|
||||
export function removeDevice(port: number, data: { deviceCode: string }) {
|
||||
return request.post(`/admin/simulator/address/${port}/remove-device`, data)
|
||||
}
|
||||
|
||||
/** 获取事件历史 */
|
||||
export function fetchEventHistory(port: number) {
|
||||
return request.get<EventHistory[]>(`/admin/simulator/address/${port}/event-history`)
|
||||
}
|
||||
|
||||
/** 获取完整汇总 */
|
||||
export function fetchFullSummary(port: number) {
|
||||
return request.get(`/admin/simulator/address/${port}/full-summary`)
|
||||
}
|
||||
|
||||
/** 获取异常日志 */
|
||||
export function fetchErrorLog(port: number) {
|
||||
return request.get(`/admin/simulator/address/${port}/error-log`)
|
||||
}
|
||||
|
||||
export default {}
|
||||
@ -0,0 +1,15 @@
|
||||
namespace CncModels.Dto.CollectLog
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集分析详情(基于 CollectAnalysisListItem 的扩展字段)
|
||||
/// </summary>
|
||||
public class CollectAnalysisDetail : CollectAnalysisListItem
|
||||
{
|
||||
public decimal? PreviousPartCount { get; set; }
|
||||
public decimal? CurrentPartCount { get; set; }
|
||||
public string PreviousStatus { get; set; }
|
||||
public string CurrentStatus { get; set; }
|
||||
public string AnalysisDetail { get; set; }
|
||||
public long RawLogId { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
namespace CncModels.Dto.CollectLog
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集分析列表项
|
||||
/// </summary>
|
||||
public class CollectAnalysisListItem
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string AnalysisTime { get; set; }
|
||||
public int CollectAddressId { get; set; }
|
||||
public int MachineId { get; set; }
|
||||
public string MachineName { get; set; }
|
||||
public string AnalysisType { get; set; }
|
||||
public string PreviousProgram { get; set; }
|
||||
public string CurrentProgram { get; set; }
|
||||
public decimal? PartCountDelta { get; set; }
|
||||
public string AnalysisSummary { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace CncModels.Dto.CollectLog
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集分析列表查询条件(分页)
|
||||
/// </summary>
|
||||
public class CollectAnalysisQuery : PagedQuery
|
||||
{
|
||||
public DateTime? StartDate { get; set; }
|
||||
public DateTime? EndDate { get; set; }
|
||||
public int? CollectAddressId { get; set; }
|
||||
public int? MachineId { get; set; }
|
||||
public string AnalysisType { get; set; }
|
||||
public string ProgramName { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
namespace CncModels.Dto.CollectLog
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集周期列表项
|
||||
/// </summary>
|
||||
public class CollectCycleListItem
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string CycleTime { get; set; }
|
||||
public int CollectAddressId { get; set; }
|
||||
public string AddressName { get; set; }
|
||||
public int TotalMachines { get; set; }
|
||||
public int SuccessCount { get; set; }
|
||||
public int FailCount { get; set; }
|
||||
public int HasAnomaly { get; set; }
|
||||
public string ChangeDistribution { get; set; }
|
||||
public string CycleSummary { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace CncModels.Dto.CollectLog
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集周期查询条件(分页)
|
||||
/// </summary>
|
||||
public class CollectCycleQuery : PagedQuery
|
||||
{
|
||||
public DateTime? StartDate { get; set; }
|
||||
public DateTime? EndDate { get; set; }
|
||||
public int? CollectAddressId { get; set; }
|
||||
public int? HasAnomaly { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace CncModels.Dto.CollectLog
|
||||
{
|
||||
/// <summary>回放请求参数</summary>
|
||||
public class ReplayRequest { public DateTime Date { get; set; } }
|
||||
|
||||
/// <summary>回放预览结果</summary>
|
||||
public class ReplayPreview {
|
||||
public DateTime Date { get; set; }
|
||||
public int RawLogCount { get; set; }
|
||||
public int AffectedMachineCount { get; set; }
|
||||
public int AffectedRecordCount { get; set; }
|
||||
public int AffectedSegmentCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>回放执行结果</summary>
|
||||
public class ReplayResult {
|
||||
public DateTime Date { get; set; }
|
||||
public int ClearedRecordCount { get; set; }
|
||||
public int ClearedSegmentCount { get; set; }
|
||||
public int RebuiltRecordCount { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
namespace CncModels.Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集分析的分析类型枚举(以字符串常量形式提供)
|
||||
/// </summary>
|
||||
public static class AnalysisType
|
||||
{
|
||||
public const string NORMAL_UNCHANGED = "NORMAL_UNCHANGED";
|
||||
public const string PART_COUNT_INCREASE = "PART_COUNT_INCREASE";
|
||||
public const string PROGRAM_SWITCH = "PROGRAM_SWITCH";
|
||||
public const string MANUAL_RESET = "MANUAL_RESET";
|
||||
public const string DEVICE_ONLINE = "DEVICE_ONLINE";
|
||||
public const string DEVICE_OFFLINE = "DEVICE_OFFLINE";
|
||||
public const string NEW_DEVICE_FOUND = "NEW_DEVICE_FOUND";
|
||||
public const string DATA_ANOMALY = "DATA_ANOMALY";
|
||||
public const string COLLECTION_FAILED = "COLLECTION_FAILED";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CncModels.Dto;
|
||||
using CncModels.Dto.CollectLog;
|
||||
using CncModels.Entity;
|
||||
|
||||
namespace CncRepository.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集分析仓储接口
|
||||
/// </summary>
|
||||
public interface ICollectAnalysisRepository
|
||||
{
|
||||
PagedResult<CollectAnalysisListItem> GetAnalysisList(CollectAnalysisQuery query);
|
||||
CollectAnalysisDetail GetAnalysisDetail(long id);
|
||||
List<CollectAnalysisListItem> GetAnalysisByRawLogId(long rawLogId);
|
||||
long Create(CollectAnalysis entity);
|
||||
int DeleteBeforeDate(DateTime date);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CncModels.Dto;
|
||||
using CncModels.Dto.CollectLog;
|
||||
using CncModels.Entity;
|
||||
|
||||
namespace CncRepository.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集周期仓储接口
|
||||
/// </summary>
|
||||
public interface ICollectCycleRepository
|
||||
{
|
||||
PagedResult<CollectCycleListItem> GetCycleList(CollectCycleQuery query);
|
||||
long Create(CollectCycle entity);
|
||||
int DeleteBeforeDate(DateTime date);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CncModels.Dto;
|
||||
using CncModels.Dto.CollectLog;
|
||||
using CncModels.Constants;
|
||||
using CncService.Interface;
|
||||
using CncRepository.Interface;
|
||||
|
||||
namespace CncService.Impl
|
||||
{
|
||||
/// <summary>
|
||||
/// 采集日志相关的业务实现
|
||||
/// </summary>
|
||||
public class CollectLogService : ICollectLogService
|
||||
{
|
||||
private readonly ICollectAnalysisRepository _analysisRepository;
|
||||
private readonly ICollectCycleRepository _cycleRepository;
|
||||
|
||||
public CollectLogService(ICollectAnalysisRepository analysisRepository, ICollectCycleRepository cycleRepository)
|
||||
{
|
||||
_analysisRepository = analysisRepository ?? throw new ArgumentNullException(nameof(analysisRepository));
|
||||
_cycleRepository = cycleRepository ?? throw new ArgumentNullException(nameof(cycleRepository));
|
||||
}
|
||||
|
||||
public PagedResult<CollectAnalysisListItem> GetAnalysisList(CollectAnalysisQuery query)
|
||||
{
|
||||
if (query == null) throw new BusinessException(ErrorCode.BadRequest, "查询参数不能为空");
|
||||
return _analysisRepository.GetAnalysisList(query);
|
||||
}
|
||||
|
||||
public CollectAnalysisDetail GetAnalysisDetail(long id)
|
||||
{
|
||||
var detail = _analysisRepository.GetAnalysisDetail(id);
|
||||
if (detail == null) throw new BusinessException(ErrorCode.NotFound, "采集分析记录不存在");
|
||||
return detail;
|
||||
}
|
||||
|
||||
public List<CollectAnalysisListItem> GetAnalysisByRawLogId(long rawLogId)
|
||||
{
|
||||
return _analysisRepository.GetAnalysisByRawLogId(rawLogId);
|
||||
}
|
||||
|
||||
public PagedResult<CollectCycleListItem> GetCycleList(CollectCycleQuery query)
|
||||
{
|
||||
if (query == null) throw new BusinessException(ErrorCode.BadRequest, "查询参数不能为空");
|
||||
return _cycleRepository.GetCycleList(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using CncModels.Dto;
|
||||
using CncModels.Dto.CollectLog;
|
||||
|
||||
namespace CncService.Interface
|
||||
{
|
||||
public interface ICollectLogService
|
||||
{
|
||||
/// <summary>分页查询采集分析日志</summary>
|
||||
PagedResult<CollectAnalysisListItem> GetAnalysisList(CollectAnalysisQuery query);
|
||||
|
||||
/// <summary>获取单条采集分析日志的详情</summary>
|
||||
CollectAnalysisDetail GetAnalysisDetail(long id);
|
||||
|
||||
/// <summary>根据原始日志ID查找相关联的分析记录</summary>
|
||||
List<CollectAnalysisListItem> GetAnalysisByRawLogId(long rawLogId);
|
||||
|
||||
/// <summary>分页查询采集周期信息</summary>
|
||||
PagedResult<CollectCycleListItem> GetCycleList(CollectCycleQuery query);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue