Compare commits
25 Commits
9e3a759646
...
b9555b807c
| Author | SHA1 | Date |
|---|---|---|
|
|
b9555b807c | 9 hours ago |
|
|
1600570b60 | 9 hours ago |
|
|
06d04c244e | 10 hours ago |
|
|
72cb43c493 | 11 hours ago |
|
|
4b70b8eacf | 12 hours ago |
|
|
b74c3db6af | 12 hours ago |
|
|
add981876b | 13 hours ago |
|
|
ccdfec31bb | 15 hours ago |
|
|
0563da73e8 | 17 hours ago |
|
|
089f3e502a | 17 hours ago |
|
|
78b7dfea19 | 2 days ago |
|
|
2d698b277d | 2 days ago |
|
|
e09fdc1329 | 2 days ago |
|
|
c9cca32757 | 2 days ago |
|
|
6e468089ea | 2 days ago |
|
|
7d9634af48 | 2 days ago |
|
|
e3f37d5433 | 2 days ago |
|
|
23eda3751f | 2 days ago |
|
|
5a7c1b3436 | 2 days ago |
|
|
eedf5fa8be | 2 days ago |
|
|
acdc502be2 | 2 days ago |
|
|
0212ed6afc | 4 days ago |
|
|
d69817bf45 | 4 days ago |
|
|
d8f59250d7 | 4 days ago |
|
|
e9802a195d | 4 days ago |
@ -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,25 @@
|
||||
<?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>
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
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。
|
||||
@ -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,28 @@
|
||||
{
|
||||
"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 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
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,26 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<RootNamespace>CncService.Tests</RootNamespace>
|
||||
<AssemblyName>CncService.Tests</AssemblyName>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<IsPackable>false</IsPackable>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net472" Version="1.0.3" PrivateAssets="all" />
|
||||
<PackageReference Include="xunit" Version="2.8.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\CncModels\CncModels.csproj" />
|
||||
<ProjectReference Include="..\..\src\CncRepository\CncRepository.csproj" />
|
||||
<ProjectReference Include="..\..\src\CncService\CncService.csproj" />
|
||||
<ProjectReference Include="../../src/CncService/CncService.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,149 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
using CncModels.Dto.Dashboard;
|
||||
using CncService;
|
||||
using CncModels.Entity;
|
||||
using CncRepository.Interface;
|
||||
using CncService.Interface;
|
||||
using CncService.Impl;
|
||||
using Xunit;
|
||||
|
||||
namespace CncService.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// DashboardService 仪表盘测试
|
||||
/// 测试场景:汇总查询、车间产量、机床排名、工人排名、趋势、状态分布、采集器状态
|
||||
/// </summary>
|
||||
[Collection("Database")]
|
||||
public class DashboardServiceTests : IDisposable
|
||||
// Fake repositories to isolate DashboardService.GetCollectorStatus tests
|
||||
public class FakeDashboardRepository : IDashboardRepository
|
||||
{
|
||||
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数量()
|
||||
{
|
||||
var result = _service.GetMachineRank(null, null, 5);
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
// ======== GetWorkerRank ========
|
||||
|
||||
[Fact]
|
||||
public void GetWorkerRank_无数据_返回空列表()
|
||||
{
|
||||
var result = _service.GetWorkerRank(null, null, 10);
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
// ======== GetProductionTrend ========
|
||||
|
||||
[Fact]
|
||||
public void GetProductionTrend_默认7天()
|
||||
{
|
||||
var result = _service.GetProductionTrend();
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetProductionTrend_指定天数()
|
||||
{
|
||||
var result = _service.GetProductionTrend(30);
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
// ======== GetMachineStatusDistribution ========
|
||||
public DashboardSummaryResponse GetSummary(int something) => new DashboardSummaryResponse();
|
||||
public List<WorkshopProductionResponse> GetWorkshopProduction(DateTime startDate, DateTime endDate) => new List<WorkshopProductionResponse>();
|
||||
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>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMachineStatusDistribution_无数据_返回结果()
|
||||
{
|
||||
var result = _service.GetMachineStatusDistribution();
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
public class FakeCollectorHeartbeatRepository : ICollectorHeartbeatRepository
|
||||
{
|
||||
private readonly CollectorHeartbeat _latest;
|
||||
public FakeCollectorHeartbeatRepository(CollectorHeartbeat latest) { _latest = latest; }
|
||||
public long Create(CollectorHeartbeat entity) => 1;
|
||||
public CollectorHeartbeat GetLatest(string serviceId) => _latest;
|
||||
public int DeleteBeforeDate(DateTime date) => 0;
|
||||
}
|
||||
|
||||
// ======== GetRecentAlerts ========
|
||||
public class FakeWindowsServiceChecker : IWindowsServiceChecker
|
||||
{
|
||||
private readonly ServiceStatusEnum _status;
|
||||
public FakeWindowsServiceChecker(ServiceStatusEnum status) { _status = status; }
|
||||
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");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRecentAlerts_无数据_返回空列表()
|
||||
{
|
||||
var result = _service.GetRecentAlerts(5);
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
public class FakeSysConfigRepository : ISysConfigRepository
|
||||
{
|
||||
public SysConfig GetByKey(string configKey) => new SysConfig { ConfigKey = configKey, ConfigValue = "300" };
|
||||
public List<SysConfig> GetAll() => new List<SysConfig>();
|
||||
public bool UpdateValue(int id, string value) => true;
|
||||
}
|
||||
|
||||
public class DashboardServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetRecentAlerts_有告警数据()
|
||||
public void GetCollectorStatus_With_NotInstalled_Service_Returns_NotInstalled_State()
|
||||
{
|
||||
TestDb.Execute(@"INSERT INTO cnc_collect_address (name, url, brand_id, collect_interval, is_enabled, created_at, updated_at)
|
||||
VALUES ('测试地址', 'http://test', 1, 30, 1, NOW(), NOW())");
|
||||
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)
|
||||
VALUES ('M001', '机床1', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())");
|
||||
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);
|
||||
// Arrange
|
||||
var latest = new CollectorHeartbeat { Id = 1, ServiceId = "collector-service", Status = "running", UptimeSeconds = 120, LastCollectTime = DateTime.Now, CreatedAt = DateTime.Now };
|
||||
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);
|
||||
}
|
||||
|
||||
// ======== GetCollectorStatus ========
|
||||
|
||||
[Fact]
|
||||
public void GetCollectorStatus_无心跳_返回未运行()
|
||||
public void GetCollectorStatus_With_Running_Heartbeats_Returns_Running_State()
|
||||
{
|
||||
var result = _service.GetCollectorStatus();
|
||||
Assert.NotNull(result);
|
||||
var latest = new CollectorHeartbeat { Id = 1, ServiceId = "collector-service", Status = "running", UptimeSeconds = 60, LastCollectTime = DateTime.Now, CreatedAt = DateTime.Now };
|
||||
var dashboardRepo = new FakeDashboardRepository();
|
||||
var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest);
|
||||
var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.Running);
|
||||
var svc = new DashboardService(dashboardRepo, heartbeatRepo, new FakeSysConfigRepository(), checker);
|
||||
|
||||
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("Running", serviceStatusVal);
|
||||
Assert.Equal("running", statusVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCollectorStatus_有最近心跳_返回运行中()
|
||||
public void GetCollectorStatus_With_Starting_ServiceStatus_Returns_Starting_State()
|
||||
{
|
||||
TestDb.Execute(@"INSERT INTO log_collector_heartbeat (service_id, status, last_collect_time, success_count, fail_count, created_at)
|
||||
VALUES ('collector-service', 'running', NOW(), 1, 0, NOW())");
|
||||
|
||||
var result = _service.GetCollectorStatus();
|
||||
Assert.NotNull(result);
|
||||
var latest = new CollectorHeartbeat { Id = 2, ServiceId = "collector-service", Status = "running", UptimeSeconds = 120, LastCollectTime = DateTime.Now, CreatedAt = DateTime.Now };
|
||||
var dashboardRepo = new FakeDashboardRepository();
|
||||
var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest);
|
||||
var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.Starting);
|
||||
var svc = new DashboardService(dashboardRepo, heartbeatRepo, new FakeSysConfigRepository(), checker);
|
||||
|
||||
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