Compare commits
No commits in common. 'b9555b807c72b877e469f3cc43fc2abecf767456' and '9e3a759646f4999fcee60220daf29de3fc42acd1' have entirely different histories.
b9555b807c
...
9e3a759646
@ -1,37 +0,0 @@
|
|||||||
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 "=================="
|
|
||||||
@ -1,280 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<configSections>
|
|
||||||
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" processFilters="true" />
|
|
||||||
</configSections>
|
|
||||||
|
|
||||||
<log4net>
|
|
||||||
<root>
|
|
||||||
<level value="INFO" />
|
|
||||||
<appender-ref value="RollingFileAppender" />
|
|
||||||
</root>
|
|
||||||
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
|
|
||||||
<!-- 将日志分等级滚动,按时间与大小混合滚动 -->
|
|
||||||
<file value="logs/simulator-" />
|
|
||||||
<datePattern value="yyyy-MM-dd'.log'" />
|
|
||||||
<rollingStyle value="Composite" />
|
|
||||||
<MaxSizeRollBackups value="10" />
|
|
||||||
<MaximumFileSize value="50MB" />
|
|
||||||
<layout type="log4net.Layout.PatternLayout">
|
|
||||||
<conversionPattern value="[%date %-5level] %message%newline" />
|
|
||||||
</layout>
|
|
||||||
</appender>
|
|
||||||
</log4net>
|
|
||||||
|
|
||||||
</configuration>
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
namespace CncService.Models
|
|
||||||
{
|
|
||||||
// Minimal result wrapper for latest log fetch
|
|
||||||
public class LogIngestionResult
|
|
||||||
{
|
|
||||||
public long LogId { get; set; }
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
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,67 +0,0 @@
|
|||||||
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,3 +0,0 @@
|
|||||||
CncSimulator 项目根 README
|
|
||||||
This repository contains a self-contained CNC data sampling simulator for .NET Framework 4.7.2.
|
|
||||||
具体实现参考 simulator.json、Config/SimulatorConfig.cs、Device/DeviceState.cs、Device/DeviceSimulator.cs、Generator/FanucDataGenerator.cs、Core/SimulatorServer.cs、Core/SimulatorEngine.cs、Program.cs。
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
-- 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 ...? 进行滚动归档
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
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 {}
|
|
||||||
@ -1,223 +0,0 @@
|
|||||||
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 {}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"gatewayPort": 9000,
|
|
||||||
"addresses": [
|
|
||||||
{
|
|
||||||
"name": "FANUC-1号模拟",
|
|
||||||
"port": 9001,
|
|
||||||
"brand": "fanuc",
|
|
||||||
"dataChangeInterval": 10,
|
|
||||||
"scenarioMode": "auto",
|
|
||||||
"devices": [
|
|
||||||
{ "deviceCode": "CNC-A001", "desc": "西栋1号", "initialProgram": "O0001", "initialPartCount": 50 },
|
|
||||||
{ "deviceCode": "CNC-006", "desc": "6号机床", "initialProgram": "O0002", "initialPartCount": 120 },
|
|
||||||
{ "deviceCode": "CNC-008", "desc": "8号机床", "initialProgram": "O0003", "initialPartCount": 0 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "FANUC-2号模拟",
|
|
||||||
"port": 9002,
|
|
||||||
"brand": "fanuc",
|
|
||||||
"dataChangeInterval": 15,
|
|
||||||
"scenarioMode": "auto",
|
|
||||||
"devices": [
|
|
||||||
{ "deviceCode": "CNC-B002", "desc": "B栋2号", "initialProgram": "1566.NC", "initialPartCount": 80 },
|
|
||||||
{ "deviceCode": "CNC-005", "desc": "验证机床", "initialProgram": "TEST001", "initialPartCount": 10 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace CncService.Interface
|
|
||||||
{
|
|
||||||
// Windows 服务状态枚举,用于和心跳状态区分不同场景
|
|
||||||
public enum ServiceStatusEnum
|
|
||||||
{
|
|
||||||
NotInstalled,
|
|
||||||
Stopped,
|
|
||||||
Running,
|
|
||||||
Starting,
|
|
||||||
StartFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Windows 服务检测接口(用于管理后台对采集服务的状态检测与控制)
|
|
||||||
/// </summary>
|
|
||||||
public interface IWindowsServiceChecker
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取指定服务的当前状态
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceName">服务名</param>
|
|
||||||
/// <returns>服务状态枚举</returns>
|
|
||||||
ServiceStatusEnum GetServiceStatus(string serviceName);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 尝试启动指定服务,并在给定超时内等待就绪
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceName">服务名</param>
|
|
||||||
/// <param name="timeoutSeconds">超时(秒)</param>
|
|
||||||
/// <returns>(是否成功, 详细信息)</returns>
|
|
||||||
(bool Success, string Message) TryStartService(string serviceName, int timeoutSeconds);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 尝试停止指定服务,并在给定超时内等待停止
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceName">服务名</param>
|
|
||||||
/// <param name="timeoutSeconds">超时(秒)</param>
|
|
||||||
/// <returns>(是否成功, 详细信息)</returns>
|
|
||||||
(bool Success, string Message) TryStopService(string serviceName, int timeoutSeconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Web.Http;
|
|
||||||
using CncModels.Dto;
|
|
||||||
using CncModels.Dto.CollectLog;
|
|
||||||
using CncModels.Entity;
|
|
||||||
using CncService.Interface;
|
|
||||||
using CncRepository.Interface;
|
|
||||||
using CncWebApi.Infrastructure;
|
|
||||||
using System.Web.Http.Description;
|
|
||||||
|
|
||||||
namespace CncWebApi.Controllers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 采集日志管理控制器
|
|
||||||
/// </summary>
|
|
||||||
[RoutePrefix("api/admin/collect-log")]
|
|
||||||
[JwtAuthFilter]
|
|
||||||
public class CollectLogController : ApiController
|
|
||||||
{
|
|
||||||
private readonly ICollectLogService _collectLogService;
|
|
||||||
private readonly ICollectRawRepository _rawRepository;
|
|
||||||
|
|
||||||
public CollectLogController(ICollectLogService collectLogService, ICollectRawRepository rawRepository)
|
|
||||||
{
|
|
||||||
_collectLogService = collectLogService ?? throw new ArgumentNullException(nameof(collectLogService));
|
|
||||||
_rawRepository = rawRepository ?? throw new ArgumentNullException(nameof(rawRepository));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>分页查询采集分析记录</summary>
|
|
||||||
[HttpGet]
|
|
||||||
[Route("analysis")]
|
|
||||||
[ResponseType(typeof(ApiResponse<PagedResult<CollectAnalysisListItem>>))]
|
|
||||||
public IHttpActionResult GetAnalysisList([FromUri] CollectAnalysisQuery query)
|
|
||||||
{
|
|
||||||
if (query == null) query = new CollectAnalysisQuery();
|
|
||||||
var result = _collectLogService.GetAnalysisList(query);
|
|
||||||
return Ok(ApiResponse<PagedResult<CollectAnalysisListItem>>.Success(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>获取采集分析详情</summary>
|
|
||||||
[HttpGet]
|
|
||||||
[Route("analysis/{id:long}")]
|
|
||||||
[ResponseType(typeof(ApiResponse<CollectAnalysisDetail>))]
|
|
||||||
public IHttpActionResult GetAnalysisDetail(long id)
|
|
||||||
{
|
|
||||||
var detail = _collectLogService.GetAnalysisDetail(id);
|
|
||||||
return Ok(ApiResponse<CollectAnalysisDetail>.Success(detail));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>根据原始日志ID查询关联的分析记录</summary>
|
|
||||||
[HttpGet]
|
|
||||||
[Route("analysis/by-raw/{rawLogId:long}")]
|
|
||||||
[ResponseType(typeof(ApiResponse<List<CollectAnalysisListItem>>))]
|
|
||||||
public IHttpActionResult GetAnalysisByRawLogId(long rawLogId)
|
|
||||||
{
|
|
||||||
var list = _collectLogService.GetAnalysisByRawLogId(rawLogId);
|
|
||||||
return Ok(ApiResponse<List<CollectAnalysisListItem>>.Success(list));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>分页查询采集周期</summary>
|
|
||||||
[HttpGet]
|
|
||||||
[Route("cycle")]
|
|
||||||
[ResponseType(typeof(ApiResponse<PagedResult<CollectCycleListItem>>))]
|
|
||||||
public IHttpActionResult GetCycleList([FromUri] CollectCycleQuery query)
|
|
||||||
{
|
|
||||||
if (query == null) query = new CollectCycleQuery();
|
|
||||||
var result = _collectLogService.GetCycleList(query);
|
|
||||||
return Ok(ApiResponse<PagedResult<CollectCycleListItem>>.Success(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>查询原始采集日志</summary>
|
|
||||||
[HttpGet]
|
|
||||||
[Route("raw")]
|
|
||||||
[ResponseType(typeof(ApiResponse<PagedResult<CollectRaw>>))]
|
|
||||||
public IHttpActionResult GetRawList([FromUri] int? collectAddressId = null, [FromUri] int page = 1, [FromUri] int pageSize = 20)
|
|
||||||
{
|
|
||||||
var result = _rawRepository.GetByAddressId(collectAddressId ?? 0, page, pageSize);
|
|
||||||
return Ok(ApiResponse<PagedResult<CollectRaw>>.Success(result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Web.Http;
|
|
||||||
using System.Web.Http.Description;
|
|
||||||
using CncModels.Dto;
|
|
||||||
using CncModels.Dto.CollectLog;
|
|
||||||
using CncService.Interface;
|
|
||||||
using CncWebApi.Infrastructure;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace CncWebApi.Controllers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 数据回放控制器
|
|
||||||
/// </summary>
|
|
||||||
[RoutePrefix("api/admin/replay")]
|
|
||||||
[JwtAuthFilter]
|
|
||||||
public class ReplayController : ApiController
|
|
||||||
{
|
|
||||||
private readonly IReplayService _replayService;
|
|
||||||
|
|
||||||
public ReplayController(IReplayService replayService)
|
|
||||||
{
|
|
||||||
_replayService = replayService ?? throw new ArgumentNullException(nameof(replayService));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>预览回放影响范围</summary>
|
|
||||||
[HttpPost]
|
|
||||||
[Route("preview")]
|
|
||||||
[ResponseType(typeof(ApiResponse<ReplayPreview>))]
|
|
||||||
public IHttpActionResult Preview([FromBody] ReplayRequest request)
|
|
||||||
{
|
|
||||||
if (request == null) return BadRequest("请求参数错误");
|
|
||||||
var result = _replayService.PreviewReplay(request.Date);
|
|
||||||
return Ok(ApiResponse<ReplayPreview>.Success(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>执行回放</summary>
|
|
||||||
[HttpPost]
|
|
||||||
[Route("execute")]
|
|
||||||
[ResponseType(typeof(ApiResponse<ReplayResult>))]
|
|
||||||
public IHttpActionResult Execute([FromBody] ReplayRequest request)
|
|
||||||
{
|
|
||||||
if (request == null) return BadRequest("请求参数错误");
|
|
||||||
var result = _replayService.ExecuteReplay(request.Date);
|
|
||||||
return Ok(ApiResponse<ReplayResult>.Success(result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using CncModels.Entity;
|
|
||||||
using CncRepository.Impl;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace CncRepository.Tests
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 品牌字段映射仓储测试
|
|
||||||
/// </summary>
|
|
||||||
[Collection("Database")]
|
|
||||||
public class BrandFieldMappingRepositoryTests : IDisposable
|
|
||||||
{
|
|
||||||
private readonly BrandFieldMappingRepository _repo;
|
|
||||||
|
|
||||||
public BrandFieldMappingRepositoryTests()
|
|
||||||
{
|
|
||||||
_repo = new BrandFieldMappingRepository(TestDb.ConnectionString);
|
|
||||||
TestDb.TruncateAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
TestDb.TruncateAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetByBrandId_返回所有映射含禁用()
|
|
||||||
{
|
|
||||||
// 插入2条启用 + 1条禁用
|
|
||||||
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
|
|
||||||
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW()),
|
|
||||||
(1, 'part_count', 'Tag8', 'id', 'number', 1, 1, NOW()),
|
|
||||||
(1, 'spindle_load', 'Tag21', 'id', 'number', 0, 0, NOW())");
|
|
||||||
var result = _repo.GetByBrandId(1);
|
|
||||||
Assert.Equal(3, result.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetEnabledByBrandId_只返回启用的映射()
|
|
||||||
{
|
|
||||||
TestDb.Execute(@"INSERT IGNORE INTO cnc_brand (id, brand_name, device_field, tags_path, is_enabled, created_at, updated_at)
|
|
||||||
VALUES (1, 'FANUC', 'device', 'tags', 1, NOW(), NOW())");
|
|
||||||
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
|
|
||||||
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW()),
|
|
||||||
(1, 'part_count', 'Tag8', 'id', 'number', 1, 1, NOW()),
|
|
||||||
(1, 'spindle_load', 'Tag21', 'id', 'number', 0, 0, NOW())");
|
|
||||||
var result = _repo.GetEnabledByBrandId(1);
|
|
||||||
Assert.Equal(2, result.Count);
|
|
||||||
Assert.All(result, m => Assert.Equal(1, m.IsEnabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Create_默认启用()
|
|
||||||
{
|
|
||||||
// 确保 brand_id=1 存在
|
|
||||||
TestDb.Execute(@"INSERT IGNORE INTO cnc_brand (id, brand_name, device_field, tags_path, is_enabled, created_at, updated_at)
|
|
||||||
VALUES (1, 'FANUC', 'device', 'tags', 1, NOW(), NOW())");
|
|
||||||
var entity = new BrandFieldMapping
|
|
||||||
{
|
|
||||||
BrandId = 1,
|
|
||||||
StandardField = "program_name",
|
|
||||||
FieldName = "Tag5",
|
|
||||||
MatchBy = "id",
|
|
||||||
DataType = "string",
|
|
||||||
IsRequired = 1,
|
|
||||||
IsEnabled = 1,
|
|
||||||
CreatedAt = DateTime.Now
|
|
||||||
};
|
|
||||||
var id = _repo.Create(entity);
|
|
||||||
Assert.True(id > 0);
|
|
||||||
var loaded = _repo.GetById(id);
|
|
||||||
Assert.Equal(1, loaded.IsEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Update_修改启用状态()
|
|
||||||
{
|
|
||||||
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
|
|
||||||
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW())");
|
|
||||||
var id = TestDb.QuerySingle<int>("SELECT MAX(id) FROM cnc_brand_field_mapping");
|
|
||||||
var entity = _repo.GetById(id);
|
|
||||||
entity.IsEnabled = 0;
|
|
||||||
var result = _repo.Update(entity);
|
|
||||||
Assert.True(result);
|
|
||||||
Assert.Equal(0, _repo.GetById(id).IsEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BatchCreate_批量插入保留启用状态()
|
|
||||||
{
|
|
||||||
// 确保 brand_id=1 存在
|
|
||||||
TestDb.Execute(@"INSERT IGNORE INTO cnc_brand (id, brand_name, device_field, tags_path, is_enabled, created_at, updated_at)
|
|
||||||
VALUES (1, 'FANUC', 'device', 'tags', 1, NOW(), NOW())");
|
|
||||||
var mappings = new[]
|
|
||||||
{
|
|
||||||
new BrandFieldMapping { StandardField = "f1", FieldName = "Tag1", MatchBy = "id", DataType = "string", IsRequired = 0, IsEnabled = 1, CreatedAt = DateTime.Now },
|
|
||||||
new BrandFieldMapping { StandardField = "f2", FieldName = "Tag2", MatchBy = "id", DataType = "number", IsRequired = 0, IsEnabled = 1, CreatedAt = DateTime.Now },
|
|
||||||
new BrandFieldMapping { StandardField = "f3", FieldName = "Tag3", MatchBy = "id", DataType = "string", IsRequired = 0, IsEnabled = 0, CreatedAt = DateTime.Now },
|
|
||||||
}.ToList();
|
|
||||||
var count = _repo.BatchCreate(1, mappings);
|
|
||||||
Assert.Equal(3, count);
|
|
||||||
var all = _repo.GetByBrandId(1);
|
|
||||||
Assert.Equal(3, all.Count);
|
|
||||||
var enabled = _repo.GetEnabledByBrandId(1);
|
|
||||||
Assert.Equal(2, enabled.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Update_修改字段名和启用状态同时生效()
|
|
||||||
{
|
|
||||||
TestDb.Execute(@"INSERT INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required, is_enabled, created_at)
|
|
||||||
VALUES (1, 'program_name', 'Tag5', 'id', 'string', 1, 1, NOW())");
|
|
||||||
var id = TestDb.QuerySingle<int>("SELECT MAX(id) FROM cnc_brand_field_mapping");
|
|
||||||
var entity = _repo.GetById(id);
|
|
||||||
entity.FieldName = "Tag5_New";
|
|
||||||
entity.IsEnabled = 0;
|
|
||||||
_repo.Update(entity);
|
|
||||||
var loaded = _repo.GetById(id);
|
|
||||||
Assert.Equal("Tag5_New", loaded.FieldName);
|
|
||||||
Assert.Equal(0, loaded.IsEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +1,26 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net472</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
<RootNamespace>CncService.Tests</RootNamespace>
|
||||||
|
<AssemblyName>CncService.Tests</AssemblyName>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net472" Version="1.0.3" PrivateAssets="all" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit" Version="2.8.1" />
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
</PackageReference>
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../src/CncService/CncService.csproj" />
|
<ProjectReference Include="..\..\src\CncModels\CncModels.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\CncRepository\CncRepository.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\CncService\CncService.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,118 +1,149 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Xunit;
|
|
||||||
using CncModels.Dto.Dashboard;
|
using CncModels.Dto.Dashboard;
|
||||||
using CncModels.Entity;
|
using CncService;
|
||||||
using CncRepository.Interface;
|
|
||||||
using CncService.Interface;
|
|
||||||
using CncService.Impl;
|
using CncService.Impl;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
namespace CncService.Tests
|
namespace CncService.Tests
|
||||||
{
|
{
|
||||||
// Fake repositories to isolate DashboardService.GetCollectorStatus tests
|
/// <summary>
|
||||||
public class FakeDashboardRepository : IDashboardRepository
|
/// DashboardService 仪表盘测试
|
||||||
|
/// 测试场景:汇总查询、车间产量、机床排名、工人排名、趋势、状态分布、采集器状态
|
||||||
|
/// </summary>
|
||||||
|
[Collection("Database")]
|
||||||
|
public class DashboardServiceTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly DashboardService _service;
|
||||||
|
|
||||||
|
public DashboardServiceTests()
|
||||||
|
{
|
||||||
|
TestDb.TruncateAll();
|
||||||
|
_service = ServiceFactory.CreateDashboardService();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
TestDb.TruncateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== GetSummary ========
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetSummary_无数据_返回默认汇总()
|
||||||
|
{
|
||||||
|
var summary = _service.GetSummary();
|
||||||
|
Assert.NotNull(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== GetWorkshopProduction ========
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetWorkshopProduction_无数据_返回空列表()
|
||||||
|
{
|
||||||
|
var result = _service.GetWorkshopProduction(null, null);
|
||||||
|
Assert.NotNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetWorkshopProduction_指定日期范围()
|
||||||
|
{
|
||||||
|
var start = new DateTime(2026, 1, 1);
|
||||||
|
var end = new DateTime(2026, 12, 31);
|
||||||
|
var result = _service.GetWorkshopProduction(start, end);
|
||||||
|
Assert.NotNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== GetMachineRank ========
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMachineRank_无数据_返回空列表()
|
||||||
|
{
|
||||||
|
var result = _service.GetMachineRank(null, null, 10);
|
||||||
|
Assert.NotNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMachineRank_指定Top数量()
|
||||||
{
|
{
|
||||||
public DashboardSummaryResponse GetSummary(int something) => new DashboardSummaryResponse();
|
var result = _service.GetMachineRank(null, null, 5);
|
||||||
public List<WorkshopProductionResponse> GetWorkshopProduction(DateTime startDate, DateTime endDate) => new List<WorkshopProductionResponse>();
|
Assert.NotNull(result);
|
||||||
public List<MachineRankResponse> GetMachineRank(DateTime startDate, DateTime endDate, int top, int something, string sortOrder = "desc") => new List<MachineRankResponse>();
|
|
||||||
public List<WorkerRankResponse> GetWorkerRank(DateTime startDate, DateTime endDate, int top, string sortOrder = "desc") => new List<WorkerRankResponse>();
|
|
||||||
public List<dynamic> GetProductionTrend(int days) => new List<dynamic>();
|
|
||||||
public object GetMachineStatusDistribution(int something) => new object();
|
|
||||||
public List<AlertListItem> GetRecentAlerts(int count) => new List<AlertListItem>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FakeCollectorHeartbeatRepository : ICollectorHeartbeatRepository
|
// ======== GetWorkerRank ========
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetWorkerRank_无数据_返回空列表()
|
||||||
{
|
{
|
||||||
private readonly CollectorHeartbeat _latest;
|
var result = _service.GetWorkerRank(null, null, 10);
|
||||||
public FakeCollectorHeartbeatRepository(CollectorHeartbeat latest) { _latest = latest; }
|
Assert.NotNull(result);
|
||||||
public long Create(CollectorHeartbeat entity) => 1;
|
|
||||||
public CollectorHeartbeat GetLatest(string serviceId) => _latest;
|
|
||||||
public int DeleteBeforeDate(DateTime date) => 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FakeWindowsServiceChecker : IWindowsServiceChecker
|
// ======== GetProductionTrend ========
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetProductionTrend_默认7天()
|
||||||
{
|
{
|
||||||
private readonly ServiceStatusEnum _status;
|
var result = _service.GetProductionTrend();
|
||||||
public FakeWindowsServiceChecker(ServiceStatusEnum status) { _status = status; }
|
Assert.NotNull(result);
|
||||||
public ServiceStatusEnum GetServiceStatus(string serviceName) => _status;
|
|
||||||
public (bool, string) TryStartService(string serviceName, int timeoutSeconds) => (
|
|
||||||
_status == ServiceStatusEnum.NotInstalled ? false : true,
|
|
||||||
_status == ServiceStatusEnum.NotInstalled ? "NotInstalled" : "Started");
|
|
||||||
public (bool, string) TryStopService(string serviceName, int timeoutSeconds) => (
|
|
||||||
true, "Stopped");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FakeSysConfigRepository : ISysConfigRepository
|
[Fact]
|
||||||
|
public void GetProductionTrend_指定天数()
|
||||||
{
|
{
|
||||||
public SysConfig GetByKey(string configKey) => new SysConfig { ConfigKey = configKey, ConfigValue = "300" };
|
var result = _service.GetProductionTrend(30);
|
||||||
public List<SysConfig> GetAll() => new List<SysConfig>();
|
Assert.NotNull(result);
|
||||||
public bool UpdateValue(int id, string value) => true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DashboardServiceTests
|
// ======== GetMachineStatusDistribution ========
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMachineStatusDistribution_无数据_返回结果()
|
||||||
{
|
{
|
||||||
|
var result = _service.GetMachineStatusDistribution();
|
||||||
|
Assert.NotNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== GetRecentAlerts ========
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetCollectorStatus_With_NotInstalled_Service_Returns_NotInstalled_State()
|
public void GetRecentAlerts_无数据_返回空列表()
|
||||||
{
|
{
|
||||||
// Arrange
|
var result = _service.GetRecentAlerts(5);
|
||||||
var latest = new CollectorHeartbeat { Id = 1, ServiceId = "collector-service", Status = "running", UptimeSeconds = 120, LastCollectTime = DateTime.Now, CreatedAt = DateTime.Now };
|
Assert.NotNull(result);
|
||||||
var dashboardRepo = new FakeDashboardRepository();
|
|
||||||
var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest);
|
|
||||||
var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.NotInstalled);
|
|
||||||
|
|
||||||
var svc = new DashboardService(dashboardRepo, heartbeatRepo, new FakeSysConfigRepository(), checker);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var resultObj = svc.GetCollectorStatus();
|
|
||||||
var t = resultObj.GetType();
|
|
||||||
var statusProp = t.GetProperty("status");
|
|
||||||
var serviceStatusProp = t.GetProperty("serviceStatus");
|
|
||||||
var uptimeProp = t.GetProperty("uptimeSeconds");
|
|
||||||
var lastCollectTimeProp = t.GetProperty("lastCollectTime");
|
|
||||||
Assert.NotNull(statusProp);
|
|
||||||
Assert.NotNull(serviceStatusProp);
|
|
||||||
var statusVal = statusProp.GetValue(resultObj) as string;
|
|
||||||
var serviceStatusVal = serviceStatusProp.GetValue(resultObj) as string;
|
|
||||||
Assert.Equal("stopped", statusVal);
|
|
||||||
Assert.Equal("NotInstalled", serviceStatusVal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetCollectorStatus_With_Running_Heartbeats_Returns_Running_State()
|
public void GetRecentAlerts_有告警数据()
|
||||||
{
|
{
|
||||||
var latest = new CollectorHeartbeat { Id = 1, ServiceId = "collector-service", Status = "running", UptimeSeconds = 60, LastCollectTime = DateTime.Now, CreatedAt = DateTime.Now };
|
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
|
||||||
var dashboardRepo = new FakeDashboardRepository();
|
VALUES ('测试地址', 'http://test', 1, 30, 1, NOW(), NOW())");
|
||||||
var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest);
|
TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, is_online, created_at, updated_at)
|
||||||
var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.Running);
|
VALUES ('M001', '机床1', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())");
|
||||||
var svc = new DashboardService(dashboardRepo, heartbeatRepo, new FakeSysConfigRepository(), checker);
|
TestDb.Execute(@"INSERT INTO cnc_alert (alert_type, machine_id, title, is_resolved, created_at)
|
||||||
|
VALUES ('offline', 1, '告警1', 0, NOW()),
|
||||||
|
('offline', 1, '告警2', 0, NOW())");
|
||||||
|
|
||||||
|
var result = _service.GetRecentAlerts(5);
|
||||||
|
Assert.True(result.Count >= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== GetCollectorStatus ========
|
||||||
|
|
||||||
var resultObj = svc.GetCollectorStatus();
|
[Fact]
|
||||||
var t = resultObj.GetType();
|
public void GetCollectorStatus_无心跳_返回未运行()
|
||||||
var serviceStatusProp = t.GetProperty("serviceStatus");
|
{
|
||||||
var statusProp = t.GetProperty("status");
|
var result = _service.GetCollectorStatus();
|
||||||
var serviceStatusVal = serviceStatusProp.GetValue(resultObj) as string;
|
Assert.NotNull(result);
|
||||||
var statusVal = statusProp.GetValue(resultObj) as string;
|
|
||||||
Assert.Equal("Running", serviceStatusVal);
|
|
||||||
Assert.Equal("running", statusVal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetCollectorStatus_With_Starting_ServiceStatus_Returns_Starting_State()
|
public void GetCollectorStatus_有最近心跳_返回运行中()
|
||||||
{
|
{
|
||||||
var latest = new CollectorHeartbeat { Id = 2, ServiceId = "collector-service", Status = "running", UptimeSeconds = 120, LastCollectTime = DateTime.Now, CreatedAt = DateTime.Now };
|
TestDb.Execute(@"INSERT INTO log_collector_heartbeat (service_id, status, last_collect_time, success_count, fail_count, created_at)
|
||||||
var dashboardRepo = new FakeDashboardRepository();
|
VALUES ('collector-service', 'running', NOW(), 1, 0, NOW())");
|
||||||
var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest);
|
|
||||||
var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.Starting);
|
var result = _service.GetCollectorStatus();
|
||||||
var svc = new DashboardService(dashboardRepo, heartbeatRepo, new FakeSysConfigRepository(), checker);
|
Assert.NotNull(result);
|
||||||
|
|
||||||
var resultObj = svc.GetCollectorStatus();
|
|
||||||
var t = resultObj.GetType();
|
|
||||||
var serviceStatusProp = t.GetProperty("serviceStatus");
|
|
||||||
var statusProp = t.GetProperty("status");
|
|
||||||
var serviceStatusVal = serviceStatusProp.GetValue(resultObj) as string;
|
|
||||||
var statusVal = statusProp.GetValue(resultObj) as string;
|
|
||||||
Assert.Equal("Starting", serviceStatusVal);
|
|
||||||
Assert.Equal("running", statusVal);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue