diff --git a/database/sqls/06-online-timeout-config.sql b/database/sqls/06-online-timeout-config.sql new file mode 100644 index 0000000..270efea --- /dev/null +++ b/database/sqls/06-online-timeout-config.sql @@ -0,0 +1,25 @@ +-- ============================================================ +-- 迁移脚本06: 删除is_online列 + 新增在线超时配置项 +-- 幂等执行:先加配置,再删列(IF EXISTS) +-- ============================================================ + +-- 1. 新增系统配置项:在线超时阈值(秒) +INSERT INTO cnc_sys_config (config_key, config_value, value_type, description, updated_at) +SELECT 'online_timeout', '300', 'number', '在线超时阈值(秒),超过此时间未Ping的机床判定为离线', NOW() +FROM DUAL +WHERE NOT EXISTS (SELECT 1 FROM cnc_sys_config WHERE config_key = 'online_timeout'); + +-- 2. 删除 is_online 列(幂等:IF EXISTS 在 MariaDB 10.0.2+ 支持) +-- 注意:MariaDB 不支持 ALTER TABLE DROP COLUMN IF EXISTS,用存储过程实现 +DROP PROCEDURE IF EXISTS drop_column_if_exists; +DELIMITER // +CREATE PROCEDURE drop_column_if_exists() +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'cnc_machine' AND COLUMN_NAME = 'is_online') THEN + ALTER TABLE cnc_machine DROP COLUMN is_online; + END IF; +END // +DELIMITER ; +CALL drop_column_if_exists(); +DROP PROCEDURE IF EXISTS drop_column_if_exists; diff --git a/frontend/src/views/brand/BrandEditPage.vue b/frontend/src/views/brand/BrandEditPage.vue index a05c8c9..ff4aaec 100644 --- a/frontend/src/views/brand/BrandEditPage.vue +++ b/frontend/src/views/brand/BrandEditPage.vue @@ -13,7 +13,7 @@ - + diff --git a/src/CncCollector/Core/CollectWorker.cs b/src/CncCollector/Core/CollectWorker.cs index 7d6d408..7d1cc58 100644 --- a/src/CncCollector/Core/CollectWorker.cs +++ b/src/CncCollector/Core/CollectWorker.cs @@ -247,10 +247,10 @@ namespace CncCollector.Core using (var conn = new MySqlConnection(_businessConnStr)) { if (onlineIds.Count > 0) - conn.Execute(@"UPDATE cnc_machine SET is_online = 1, last_ping_time = NOW(), updated_at = NOW() WHERE id IN @Ids", + conn.Execute(@"UPDATE cnc_machine SET last_ping_time = NOW(), updated_at = NOW() WHERE id IN @Ids", new { Ids = onlineIds }); if (offlineIds.Count > 0) - conn.Execute(@"UPDATE cnc_machine SET is_online = 0, last_ping_time = NOW(), updated_at = NOW() WHERE id IN @Ids", + conn.Execute(@"UPDATE cnc_machine SET last_ping_time = NOW(), updated_at = NOW() WHERE id IN @Ids", new { Ids = offlineIds }); } @@ -309,7 +309,7 @@ namespace CncCollector.Core // 加载此地址下的机床列表 machines = conn.Query( - "SELECT id as Id, device_code as DeviceCode, name as Name, workshop_id as WorkshopId, collect_address_id as CollectAddressId, ip_address as IpAddress, brand_id as BrandId, is_enabled as IsEnabled, is_online as IsOnline, last_ping_time as LastPingTime, last_collect_time as LastCollectTime, last_device_status as LastDeviceStatus, last_run_status as LastRunStatus, last_program_name as LastProgramName, last_part_count as LastPartCount, last_operate_mode as LastOperateMode, last_machining_status as LastMachiningStatus, created_at as CreatedAt, updated_at as UpdatedAt FROM cnc_machine WHERE collect_address_id = @AddrId AND is_enabled = 1", + "SELECT id as Id, device_code as DeviceCode, name as Name, workshop_id as WorkshopId, collect_address_id as CollectAddressId, ip_address as IpAddress, brand_id as BrandId, is_enabled as IsEnabled, last_ping_time as LastPingTime, last_collect_time as LastCollectTime, last_device_status as LastDeviceStatus, last_run_status as LastRunStatus, last_program_name as LastProgramName, last_part_count as LastPartCount, last_operate_mode as LastOperateMode, last_machining_status as LastMachiningStatus, created_at as CreatedAt, updated_at as UpdatedAt FROM cnc_machine WHERE collect_address_id = @AddrId AND is_enabled = 1", new { AddrId = _address.Id }).AsList(); } diff --git a/src/CncModels/Entity/Machine.cs b/src/CncModels/Entity/Machine.cs index 35b6fb0..d1c7917 100644 --- a/src/CncModels/Entity/Machine.cs +++ b/src/CncModels/Entity/Machine.cs @@ -31,10 +31,7 @@ namespace CncModels.Entity /// 是否启用 public int IsEnabled { get; set; } - /// 是否在线 - public int IsOnline { get; set; } - - /// 最近Ping时间 + /// 最近Ping时间(在线状态由 last_ping_time 实时计算) public DateTime? LastPingTime { get; set; } /// 最近采集时间 diff --git a/src/CncRepository/Impl/Dashboard/DashboardRepository.cs b/src/CncRepository/Impl/Dashboard/DashboardRepository.cs index 9442d4b..767d74b 100644 --- a/src/CncRepository/Impl/Dashboard/DashboardRepository.cs +++ b/src/CncRepository/Impl/Dashboard/DashboardRepository.cs @@ -44,12 +44,13 @@ namespace CncRepository.Impl.Dashboard ) all_days"; /// 汇总卡片数据 - public DashboardSummaryResponse GetSummary() + public DashboardSummaryResponse GetSummary(int onlineTimeout = 300) { using (var conn = CreateConnection()) { - var onlineCount = conn.ExecuteScalar(@"SELECT COUNT(1) FROM cnc_machine WHERE is_online = 1"); - var totalMachines = conn.ExecuteScalar(@"SELECT COUNT(1) FROM cnc_machine"); + var onlineCount = conn.ExecuteScalar(@"SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 1 AND last_ping_time IS NOT NULL AND last_ping_time >= NOW() - INTERVAL @OnlineTimeout SECOND", + new { OnlineTimeout = onlineTimeout }); + var totalMachines = conn.ExecuteScalar(@"SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 1"); // 今日总产量:直接从产量分段实时计算(今日一定没有日终汇总) var todayProduction = conn.ExecuteScalar(@" SELECT COALESCE(SUM(CASE WHEN is_settled=1 THEN quantity @@ -130,7 +131,7 @@ namespace CncRepository.Impl.Dashboard } /// 机床排行 - public List GetMachineRank(DateTime startDate, DateTime endDate, int top) + public List GetMachineRank(DateTime startDate, DateTime endDate, int top, int onlineTimeout = 300) { using (var conn = CreateConnection()) { @@ -138,7 +139,7 @@ namespace CncRepository.Impl.Dashboard SELECT m.id AS MachineId, m.name AS MachineName, COALESCE(SUM(ad.day_quantity), 0) AS Quantity, - CAST(m.is_online AS SIGNED) AS Status, + (CASE WHEN m.is_enabled = 1 AND m.last_ping_time IS NOT NULL AND m.last_ping_time >= NOW() - INTERVAL @OnlineTimeout SECOND THEN 1 ELSE 0 END) AS Status, (SELECT seg.program_name FROM cnc_production_segment seg WHERE seg.machine_id = m.id AND seg.production_date = CURDATE() ORDER BY seg.id DESC LIMIT 1) AS Program @@ -162,10 +163,10 @@ namespace CncRepository.Impl.Dashboard ) GROUP BY seg.machine_id, seg.production_date ) ad ON ad.machine_id = m.id - GROUP BY m.id, m.name, m.is_online + GROUP BY m.id, m.name, m.is_enabled, m.last_ping_time ORDER BY Quantity DESC LIMIT @Top"; - var rows = conn.Query(sql, new { StartDate = startDate, EndDate = endDate, Top = top }).ToList(); + var rows = conn.Query(sql, new { StartDate = startDate, EndDate = endDate, Top = top, OnlineTimeout = onlineTimeout }).ToList(); // 填充排名 for (int i = 0; i < rows.Count; i++) rows[i].Rank = i + 1; return rows; @@ -244,12 +245,14 @@ namespace CncRepository.Impl.Dashboard } /// 机床状态分布 - public object GetMachineStatusDistribution() + public object GetMachineStatusDistribution(int onlineTimeout = 300) { using (var conn = CreateConnection()) { - var online = conn.ExecuteScalar("SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 1 AND is_online = 1"); - var offline = conn.ExecuteScalar("SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 1 AND is_online = 0"); + var online = conn.ExecuteScalar("SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 1 AND last_ping_time IS NOT NULL AND last_ping_time >= NOW() - INTERVAL @OnlineTimeout SECOND", + new { OnlineTimeout = onlineTimeout }); + var offline = conn.ExecuteScalar("SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 1 AND (last_ping_time IS NULL OR last_ping_time < NOW() - INTERVAL @OnlineTimeout SECOND)", + new { OnlineTimeout = onlineTimeout }); var disabled = conn.ExecuteScalar("SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 0"); return new { online, offline, disabled }; } diff --git a/src/CncRepository/Impl/MachineRepository.cs b/src/CncRepository/Impl/MachineRepository.cs index 4dfa2c4..2ddd711 100644 --- a/src/CncRepository/Impl/MachineRepository.cs +++ b/src/CncRepository/Impl/MachineRepository.cs @@ -17,18 +17,22 @@ namespace CncRepository.Impl public MachineRepository(string connectionString) : base(connectionString) { } /// 机床SELECT列映射模板(snake_case列名 → PascalCase属性名) - private const string SelectColumns = @"id as Id, device_code as DeviceCode, name as Name, workshop_id as WorkshopId, collect_address_id as CollectAddressId, ip_address as IpAddress, brand_id as BrandId, is_enabled as IsEnabled, is_online as IsOnline, last_ping_time as LastPingTime, last_collect_time as LastCollectTime, last_device_status as LastDeviceStatus, last_run_status as LastRunStatus, last_program_name as LastProgramName, last_part_count as LastPartCount, last_operate_mode as LastOperateMode, last_machining_status as LastMachiningStatus, created_at as CreatedAt, updated_at as UpdatedAt"; + /// 在线判断SQL片段:已启用且最近Ping在超时阈值内视为在线。参数 @OnlineTimeout + private const string OnlineExpr = "(CASE WHEN is_enabled = 1 AND last_ping_time IS NOT NULL AND last_ping_time >= NOW() - INTERVAL @OnlineTimeout SECOND THEN 1 ELSE 0 END)"; - public Machine GetById(int id) + private const string SelectColumns = @"id as Id, device_code as DeviceCode, name as Name, workshop_id as WorkshopId, collect_address_id as CollectAddressId, ip_address as IpAddress, brand_id as BrandId, is_enabled as IsEnabled, {0} as IsOnline, last_ping_time as LastPingTime, last_collect_time as LastCollectTime, last_device_status as LastDeviceStatus, last_run_status as LastRunStatus, last_program_name as LastProgramName, last_part_count as LastPartCount, last_operate_mode as LastOperateMode, last_machining_status as LastMachiningStatus, created_at as CreatedAt, updated_at as UpdatedAt"; + + public Machine GetById(int id, int onlineTimeout = 300) { using (var conn = CreateConnection()) { - var sql = $"SELECT {SelectColumns} FROM cnc_machine WHERE id = @Id"; - return conn.QuerySingleOrDefault(sql, new { Id = id }); + var cols = string.Format(SelectColumns, OnlineExpr); + var sql = $"SELECT {cols} FROM cnc_machine WHERE id = @Id"; + return conn.QuerySingleOrDefault(sql, new { Id = id, OnlineTimeout = onlineTimeout }); } } - public MachineDetailResponse GetDetailById(int id) + public MachineDetailResponse GetDetailById(int id, int onlineTimeout = 300) { using (var conn = CreateConnection()) { @@ -37,7 +41,8 @@ namespace CncRepository.Impl m.collect_address_id as CollectAddressId, m.brand_id as BrandId, b.brand_name as BrandName, m.ip_address as IpAddress, - m.is_enabled as IsEnabled, m.is_online as IsOnline, + m.is_enabled as IsEnabled, + (CASE WHEN m.is_enabled = 1 AND m.last_ping_time IS NOT NULL AND m.last_ping_time >= NOW() - INTERVAL @OnlineTimeout SECOND THEN 1 ELSE 0 END) as IsOnline, w.id as WorkerId, w.name as WorkerName, m.last_program_name as LastProgramName, m.last_collect_time as LastCollectTime FROM cnc_machine m @@ -46,16 +51,17 @@ namespace CncRepository.Impl LEFT JOIN cnc_worker_machine wm ON m.id = wm.machine_id LEFT JOIN cnc_worker w ON wm.worker_id = w.id WHERE m.id = @Id"; - return conn.QuerySingleOrDefault(sql, new { Id = id }); + return conn.QuerySingleOrDefault(sql, new { Id = id, OnlineTimeout = onlineTimeout }); } } - public PagedResult GetList(MachineQuery query) + public PagedResult GetList(MachineQuery query, int onlineTimeout = 300) { using (var conn = CreateConnection()) { var where = " WHERE 1=1"; var p = new DynamicParameters(); + p.Add("OnlineTimeout", onlineTimeout); if (!string.IsNullOrWhiteSpace(query.Keyword)) { where += " AND (m.name LIKE @Keyword OR m.device_code LIKE @Keyword)"; @@ -68,8 +74,10 @@ namespace CncRepository.Impl } if (query.IsOnline.HasValue) { - where += " AND m.is_online = @IsOnline"; - p.Add("IsOnline", query.IsOnline.Value); + if (query.IsOnline.Value == 1) + where += " AND m.is_enabled = 1 AND m.last_ping_time IS NOT NULL AND m.last_ping_time >= NOW() - INTERVAL @OnlineTimeout SECOND"; + else + where += " AND (m.is_enabled = 0 OR m.last_ping_time IS NULL OR m.last_ping_time < NOW() - INTERVAL @OnlineTimeout SECOND)"; } if (query.BrandId.HasValue) { @@ -78,7 +86,9 @@ namespace CncRepository.Impl } var limit = query.PageSize; var offset = query.Offset; - var sql = @"SELECT m.id as Id, m.device_code as DeviceCode, m.name as Name, m.workshop_id as WorkshopId, ws.name as WorkshopName, m.collect_address_id as CollectAddressId, m.brand_id as BrandId, b.brand_name as BrandName, m.ip_address as IpAddress, m.is_enabled as IsEnabled, m.is_online as IsOnline, m.last_program_name as LastProgramName, m.last_collect_time as LastCollectTime, w.id as WorkerId, w.name as WorkerName + var sql = @"SELECT m.id as Id, m.device_code as DeviceCode, m.name as Name, m.workshop_id as WorkshopId, ws.name as WorkshopName, m.collect_address_id as CollectAddressId, m.brand_id as BrandId, b.brand_name as BrandName, m.ip_address as IpAddress, m.is_enabled as IsEnabled, + (CASE WHEN m.is_enabled = 1 AND m.last_ping_time IS NOT NULL AND m.last_ping_time >= NOW() - INTERVAL @OnlineTimeout SECOND THEN 1 ELSE 0 END) as IsOnline, + m.last_program_name as LastProgramName, m.last_collect_time as LastCollectTime, w.id as WorkerId, w.name as WorkerName FROM cnc_machine m LEFT JOIN cnc_workshop ws ON m.workshop_id = ws.id LEFT JOIN cnc_brand b ON m.brand_id = b.id @@ -103,8 +113,8 @@ namespace CncRepository.Impl { using (var conn = CreateConnection()) { - var sql = @"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 (@DeviceCode, @Name, @WorkshopId, @CollectAddressId, @IpAddress, @BrandId, @IsEnabled, @IsOnline, @CreatedAt, @UpdatedAt); + var sql = @"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) + VALUES (@DeviceCode, @Name, @WorkshopId, @CollectAddressId, @IpAddress, @BrandId, @IsEnabled, @CreatedAt, @UpdatedAt); SELECT LAST_INSERT_ID();"; return conn.QuerySingle(sql, entity); } @@ -114,7 +124,7 @@ namespace CncRepository.Impl { using (var conn = CreateConnection()) { - var sql = @"UPDATE cnc_machine SET device_code = @DeviceCode, name = @Name, workshop_id = @WorkshopId, collect_address_id = @CollectAddressId, ip_address = @IpAddress, brand_id = @BrandId, is_enabled = @IsEnabled, is_online = @IsOnline, updated_at = @UpdatedAt, last_program_name = @LastProgramName, last_collect_time = @LastCollectTime, last_device_status = @LastDeviceStatus, last_run_status = @LastRunStatus, last_machining_status = @LastMachiningStatus WHERE id = @Id"; + var sql = @"UPDATE cnc_machine SET device_code = @DeviceCode, name = @Name, workshop_id = @WorkshopId, collect_address_id = @CollectAddressId, ip_address = @IpAddress, brand_id = @BrandId, is_enabled = @IsEnabled, updated_at = @UpdatedAt, last_program_name = @LastProgramName, last_collect_time = @LastCollectTime, last_device_status = @LastDeviceStatus, last_run_status = @LastRunStatus, last_machining_status = @LastMachiningStatus WHERE id = @Id"; return conn.Execute(sql, entity) > 0; } } @@ -147,39 +157,33 @@ namespace CncRepository.Impl } } - public Machine GetByDeviceCode(string deviceCode) - { - using (var conn = CreateConnection()) - { - var sql = $"SELECT {SelectColumns} FROM cnc_machine WHERE device_code = @DeviceCode"; - return conn.QuerySingleOrDefault(sql, new { DeviceCode = deviceCode }); - } - } - - public List GetEnabledByAddressId(int collectAddressId) + public Machine GetByDeviceCode(string deviceCode, int onlineTimeout = 300) { using (var conn = CreateConnection()) { - var sql = $"SELECT {SelectColumns} FROM cnc_machine WHERE collect_address_id = @CollectAddressId AND is_enabled = 1"; - return conn.Query(sql, new { CollectAddressId = collectAddressId }).ToList(); + var cols = string.Format(SelectColumns, OnlineExpr); + var sql = $"SELECT {cols} FROM cnc_machine WHERE device_code = @DeviceCode"; + return conn.QuerySingleOrDefault(sql, new { DeviceCode = deviceCode, OnlineTimeout = onlineTimeout }); } } - public List GetEnabledOnline() + public List GetEnabledByAddressId(int collectAddressId, int onlineTimeout = 300) { using (var conn = CreateConnection()) { - var sql = $"SELECT {SelectColumns} FROM cnc_machine WHERE is_enabled = 1 AND is_online = 1"; - return conn.Query(sql).ToList(); + var cols = string.Format(SelectColumns, OnlineExpr); + var sql = $"SELECT {cols} FROM cnc_machine WHERE collect_address_id = @CollectAddressId AND is_enabled = 1"; + return conn.Query(sql, new { CollectAddressId = collectAddressId, OnlineTimeout = onlineTimeout }).ToList(); } } - public void UpdateOnlineStatus(int id, bool isOnline) + public List GetEnabledOnline(int onlineTimeout = 300) { using (var conn = CreateConnection()) { - var sql = @"UPDATE cnc_machine SET is_online = @IsOnline, updated_at = NOW() WHERE id = @Id"; - conn.Execute(sql, new { Id = id, IsOnline = isOnline ? 1 : 0 }); + var cols = string.Format(SelectColumns, OnlineExpr); + var sql = $"SELECT {cols} FROM cnc_machine WHERE is_enabled = 1 AND last_ping_time IS NOT NULL AND last_ping_time >= NOW() - INTERVAL @OnlineTimeout SECOND"; + return conn.Query(sql, new { OnlineTimeout = onlineTimeout }).ToList(); } } diff --git a/src/CncRepository/Interface/IDashboardRepository.cs b/src/CncRepository/Interface/IDashboardRepository.cs index 3609645..24e1af3 100644 --- a/src/CncRepository/Interface/IDashboardRepository.cs +++ b/src/CncRepository/Interface/IDashboardRepository.cs @@ -9,17 +9,17 @@ namespace CncRepository.Interface /// public interface IDashboardRepository { - DashboardSummaryResponse GetSummary(); + DashboardSummaryResponse GetSummary(int onlineTimeout = 300); List GetWorkshopProduction(DateTime startDate, DateTime endDate); - List GetMachineRank(DateTime startDate, DateTime endDate, int top); + List GetMachineRank(DateTime startDate, DateTime endDate, int top, int onlineTimeout = 300); List GetWorkerRank(DateTime startDate, DateTime endDate, int top); List GetProductionTrend(int days); - object GetMachineStatusDistribution(); + object GetMachineStatusDistribution(int onlineTimeout = 300); List GetRecentAlerts(int count); } diff --git a/src/CncRepository/Interface/IMachineRepository.cs b/src/CncRepository/Interface/IMachineRepository.cs index 6d811dc..3cf81c6 100644 --- a/src/CncRepository/Interface/IMachineRepository.cs +++ b/src/CncRepository/Interface/IMachineRepository.cs @@ -10,18 +10,17 @@ namespace CncRepository.Interface /// public interface IMachineRepository { - Machine GetById(int id); - MachineDetailResponse GetDetailById(int id); - PagedResult GetList(MachineQuery query); + Machine GetById(int id, int onlineTimeout = 300); + MachineDetailResponse GetDetailById(int id, int onlineTimeout = 300); + PagedResult GetList(MachineQuery query, int onlineTimeout = 300); int Create(Machine entity); bool Update(Machine entity); bool Delete(int id); int BatchDelete(List ids); bool ToggleEnabled(int id); - Machine GetByDeviceCode(string deviceCode); - List GetEnabledByAddressId(int collectAddressId); - List GetEnabledOnline(); - void UpdateOnlineStatus(int id, bool isOnline); + Machine GetByDeviceCode(string deviceCode, int onlineTimeout = 300); + List GetEnabledByAddressId(int collectAddressId, int onlineTimeout = 300); + List GetEnabledOnline(int onlineTimeout = 300); void UpdateLastCollect(int id, Machine entity); /// 设置机床所属的采集地址 void SetCollectAddress(int machineId, int? collectAddressId); diff --git a/src/CncService/Impl/CollectAddressService.cs b/src/CncService/Impl/CollectAddressService.cs index 30058aa..dcc6729 100644 --- a/src/CncService/Impl/CollectAddressService.cs +++ b/src/CncService/Impl/CollectAddressService.cs @@ -20,18 +20,29 @@ namespace CncService.Impl private readonly IBrandRepository _brandRepository; private readonly IWorkshopRepository _workshopRepository; private readonly ICollectRawRepository _collectRawRepository; + private readonly ISysConfigRepository _sysConfigRepository; public CollectAddressService(ICollectAddressRepository collectAddressRepository, IMachineRepository machineRepository, IBrandRepository brandRepository, IWorkshopRepository workshopRepository, - ICollectRawRepository collectRawRepository) + ICollectRawRepository collectRawRepository, + ISysConfigRepository sysConfigRepository) { _collectAddressRepository = collectAddressRepository; _machineRepository = machineRepository; _brandRepository = brandRepository; _workshopRepository = workshopRepository; _collectRawRepository = collectRawRepository; + _sysConfigRepository = sysConfigRepository; + } + + /// 从sys_config读取在线超时阈值(秒) + private int GetOnlineTimeout() + { + var cfg = _sysConfigRepository.GetByKey("online_timeout"); + if (cfg != null && int.TryParse(cfg.ConfigValue, out var val) && val > 0) return val; + return 300; } public PagedResult GetList(CollectAddressQuery query) @@ -161,7 +172,7 @@ namespace CncService.Impl MachineName = m.Name ?? m.DeviceCode, DeviceCode = m.DeviceCode, WorkshopName = workshopName, - IsOnline = m.IsOnline == 1, + IsOnline = m.IsEnabled == 1 && m.LastPingTime.HasValue && (DateTime.Now - m.LastPingTime.Value).TotalSeconds <= GetOnlineTimeout(), ProgramName = m.LastProgramName }); } diff --git a/src/CncService/Impl/DashboardService.cs b/src/CncService/Impl/DashboardService.cs index fd492f2..c07d33c 100644 --- a/src/CncService/Impl/DashboardService.cs +++ b/src/CncService/Impl/DashboardService.cs @@ -14,20 +14,31 @@ namespace CncService.Impl private readonly IDashboardRepository _dashboardRepository; private readonly ICollectorHeartbeatRepository _collectorHeartbeatRepository; private readonly IWindowsServiceChecker _serviceChecker; + private readonly ISysConfigRepository _sysConfigRepository; public DashboardService(IDashboardRepository dashboardRepository, ICollectorHeartbeatRepository collectorHeartbeatRepository, + ISysConfigRepository sysConfigRepository, IWindowsServiceChecker serviceChecker = null) { _dashboardRepository = dashboardRepository ?? throw new ArgumentNullException(nameof(dashboardRepository)); _collectorHeartbeatRepository = collectorHeartbeatRepository ?? throw new ArgumentNullException(nameof(collectorHeartbeatRepository)); + _sysConfigRepository = sysConfigRepository ?? throw new ArgumentNullException(nameof(sysConfigRepository)); _serviceChecker = serviceChecker; } + /// 从sys_config读取online_timeout,默认300秒 + private int GetOnlineTimeout() + { + var cfg = _sysConfigRepository.GetByKey("online_timeout"); + if (cfg != null && int.TryParse(cfg.ConfigValue, out var val) && val > 0) return val; + return 300; + } + /// public DashboardSummaryResponse GetSummary() { - return _dashboardRepository.GetSummary(); + return _dashboardRepository.GetSummary(GetOnlineTimeout()); } /// @@ -43,7 +54,7 @@ namespace CncService.Impl { var s = startDate ?? DateTime.Today; var e = endDate ?? DateTime.Today; - return _dashboardRepository.GetMachineRank(s, e, top); + return _dashboardRepository.GetMachineRank(s, e, top, GetOnlineTimeout()); } /// @@ -63,7 +74,7 @@ namespace CncService.Impl /// public object GetMachineStatusDistribution() { - return _dashboardRepository.GetMachineStatusDistribution(); + return _dashboardRepository.GetMachineStatusDistribution(GetOnlineTimeout()); } /// diff --git a/src/CncService/Impl/MachineService.cs b/src/CncService/Impl/MachineService.cs index 5e9b1e8..f6a1235 100644 --- a/src/CncService/Impl/MachineService.cs +++ b/src/CncService/Impl/MachineService.cs @@ -65,7 +65,6 @@ namespace CncService.Impl BrandId = request.BrandId, IpAddress = request.IpAddress, IsEnabled = 1, - IsOnline = 0, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; diff --git a/src/CncService/Impl/WorkerService.cs b/src/CncService/Impl/WorkerService.cs index f778923..d2f6ae5 100644 --- a/src/CncService/Impl/WorkerService.cs +++ b/src/CncService/Impl/WorkerService.cs @@ -19,15 +19,26 @@ namespace CncService.Impl private readonly IWorkerRepository _workerRepository; private readonly IWorkerMachineRepository _workerMachineRepository; private readonly IMachineRepository _machineRepository; + private readonly ISysConfigRepository _sysConfigRepository; public WorkerService( IWorkerRepository workerRepository, IWorkerMachineRepository workerMachineRepository, - IMachineRepository machineRepository) + IMachineRepository machineRepository, + ISysConfigRepository sysConfigRepository) { _workerRepository = workerRepository ?? throw new ArgumentNullException(nameof(workerRepository)); - _workerMachineRepository = workerMachineRepository ?? throw new ArgumentNullException(nameof(workerMachineRepository)); + _workerMachineRepository = workerMachineRepository ?? throw new ArgumentNullException(nameof(_workerMachineRepository)); _machineRepository = machineRepository ?? throw new ArgumentNullException(nameof(machineRepository)); + _sysConfigRepository = sysConfigRepository ?? throw new ArgumentNullException(nameof(sysConfigRepository)); + } + + /// 从sys_config读取在线超时阈值(秒) + private int GetOnlineTimeout() + { + var cfg = _sysConfigRepository.GetByKey("online_timeout"); + if (cfg != null && int.TryParse(cfg.ConfigValue, out var val) && val > 0) return val; + return 300; } /// @@ -193,7 +204,7 @@ namespace CncService.Impl DeviceCode = m.DeviceCode, WorkshopName = workshopName, BrandName = brandName, - IsOnline = m.IsOnline == 1, + IsOnline = m.IsEnabled == 1 && m.LastPingTime.HasValue && (DateTime.Now - m.LastPingTime.Value).TotalSeconds <= GetOnlineTimeout(), ProgramName = m.LastProgramName }); } diff --git a/src/CncWebApi/Infrastructure/ServiceResolver.cs b/src/CncWebApi/Infrastructure/ServiceResolver.cs index 51bcc21..e521321 100644 --- a/src/CncWebApi/Infrastructure/ServiceResolver.cs +++ b/src/CncWebApi/Infrastructure/ServiceResolver.cs @@ -114,6 +114,7 @@ namespace CncWebApi.Infrastructure return new CncService.Impl.DashboardService( new CncRepository.Impl.Dashboard.DashboardRepository(_businessConn), new CncRepository.Impl.Log.CollectorHeartbeatRepository(_logConn), + new CncRepository.Impl.SysConfigRepository(_businessConn), serviceChecker); } @@ -141,7 +142,8 @@ namespace CncWebApi.Infrastructure new CncRepository.Impl.MachineRepository(_businessConn), new CncRepository.Impl.BrandRepository(_businessConn), new CncRepository.Impl.WorkshopRepository(_businessConn), - new CncRepository.Impl.Log.CollectRawRepository(_logConn)); + new CncRepository.Impl.Log.CollectRawRepository(_logConn), + new CncRepository.Impl.SysConfigRepository(_businessConn)); } private IWorkerService ResolveWorkerService() @@ -149,7 +151,8 @@ namespace CncWebApi.Infrastructure return new CncService.Impl.WorkerService( new CncRepository.Impl.WorkerRepository(_businessConn), new CncRepository.Impl.WorkerMachineRepository(_businessConn), - new CncRepository.Impl.MachineRepository(_businessConn)); + new CncRepository.Impl.MachineRepository(_businessConn), + new CncRepository.Impl.SysConfigRepository(_businessConn)); } private IProductionService ResolveProductionService() diff --git a/tests/CncModels.Tests/EntityTests.cs b/tests/CncModels.Tests/EntityTests.cs index 6d9f401..3605654 100644 --- a/tests/CncModels.Tests/EntityTests.cs +++ b/tests/CncModels.Tests/EntityTests.cs @@ -665,7 +665,6 @@ namespace CncModels.Tests Assert.Null(m0.IpAddress); Assert.Equal(0, m0.BrandId); Assert.Equal(0, m0.IsEnabled); - Assert.Equal(0, m0.IsOnline); Assert.Null(m0.LastPingTime); Assert.Null(m0.LastCollectTime); Assert.Null(m0.LastDeviceStatus); @@ -687,7 +686,6 @@ namespace CncModels.Tests IpAddress = "192.168.0.10", BrandId = 3, IsEnabled = 1, - IsOnline = 1, LastPingTime = new DateTime(2026, 4, 28, 12, 0, 0), LastCollectTime = new DateTime(2026, 4, 28, 12, 5, 0), LastDeviceStatus = "OK", @@ -707,7 +705,6 @@ namespace CncModels.Tests Assert.Equal("192.168.0.10", m.IpAddress); Assert.Equal(3, m.BrandId); Assert.Equal(1, m.IsEnabled); - Assert.Equal(1, m.IsOnline); Assert.Equal(new DateTime(2026, 4, 28, 12, 0, 0), m.LastPingTime); Assert.Equal(new DateTime(2026, 4, 28, 12, 5, 0), m.LastCollectTime); Assert.Equal("OK", m.LastDeviceStatus); diff --git a/tests/CncRepository.Tests/MachineRepositoryTests.cs b/tests/CncRepository.Tests/MachineRepositoryTests.cs index c007420..d7799d1 100644 --- a/tests/CncRepository.Tests/MachineRepositoryTests.cs +++ b/tests/CncRepository.Tests/MachineRepositoryTests.cs @@ -41,7 +41,6 @@ namespace CncRepository.Tests BrandId = 1, IpAddress = "10.1.1.8", IsEnabled = 1, - IsOnline = 0, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; diff --git a/tests/CncService.Tests/AlertServiceTests.cs b/tests/CncService.Tests/AlertServiceTests.cs index 109895a..88e3320 100644 --- a/tests/CncService.Tests/AlertServiceTests.cs +++ b/tests/CncService.Tests/AlertServiceTests.cs @@ -38,8 +38,8 @@ namespace CncService.Tests { 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_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) + VALUES ('M001', '机床1', 1, 1, '0.0.0.0', 1, 1, NOW(), NOW())"); } TestDb.Execute(@"INSERT INTO cnc_alert (alert_type, machine_id, title, is_resolved, created_at) VALUES (@alertType, 1, '测试告警', @isResolved, NOW())", diff --git a/tests/CncService.Tests/CollectAddressServiceTests.cs b/tests/CncService.Tests/CollectAddressServiceTests.cs index ca91c94..d50dbc9 100644 --- a/tests/CncService.Tests/CollectAddressServiceTests.cs +++ b/tests/CncService.Tests/CollectAddressServiceTests.cs @@ -180,8 +180,8 @@ namespace CncService.Tests { var addressId = InsertTestAddress(); // 关联机床 - 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, @addressId, '0.0.0.0', 1, 1, 0, NOW(), NOW())", + TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) + VALUES ('M001', '机床1', 1, @addressId, '0.0.0.0', 1, 1, NOW(), NOW())", new { addressId }); var result = _service.Delete(addressId); diff --git a/tests/CncService.Tests/DashboardServiceTests.cs b/tests/CncService.Tests/DashboardServiceTests.cs index 4af4988..353dbcf 100644 --- a/tests/CncService.Tests/DashboardServiceTests.cs +++ b/tests/CncService.Tests/DashboardServiceTests.cs @@ -12,12 +12,12 @@ namespace CncService.Tests // Fake repositories to isolate DashboardService.GetCollectorStatus tests public class FakeDashboardRepository : IDashboardRepository { - public DashboardSummaryResponse GetSummary() => new DashboardSummaryResponse(); + public DashboardSummaryResponse GetSummary(int something) => new DashboardSummaryResponse(); public List GetWorkshopProduction(DateTime startDate, DateTime endDate) => new List(); - public List GetMachineRank(DateTime startDate, DateTime endDate, int top) => new List(); + public List GetMachineRank(DateTime startDate, DateTime endDate, int top, int something) => new List(); public List GetWorkerRank(DateTime startDate, DateTime endDate, int top) => new List(); public List GetProductionTrend(int days) => new List(); - public object GetMachineStatusDistribution() => new object(); + public object GetMachineStatusDistribution(int something) => new object(); public List GetRecentAlerts(int count) => new List(); } @@ -42,6 +42,13 @@ namespace CncService.Tests true, "Stopped"); } + public class FakeSysConfigRepository : ISysConfigRepository + { + public SysConfig GetByKey(string configKey) => new SysConfig { ConfigKey = configKey, ConfigValue = "300" }; + public List GetAll() => new List(); + public bool UpdateValue(int id, string value) => true; + } + public class DashboardServiceTests { [Fact] @@ -53,7 +60,7 @@ namespace CncService.Tests var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest); var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.NotInstalled); - var svc = new DashboardService(dashboardRepo, heartbeatRepo, checker); + var svc = new DashboardService(dashboardRepo, heartbeatRepo, new FakeSysConfigRepository(), checker); // Act var resultObj = svc.GetCollectorStatus(); @@ -77,7 +84,7 @@ namespace CncService.Tests var dashboardRepo = new FakeDashboardRepository(); var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest); var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.Running); - var svc = new DashboardService(dashboardRepo, heartbeatRepo, checker); + var svc = new DashboardService(dashboardRepo, heartbeatRepo, new FakeSysConfigRepository(), checker); var resultObj = svc.GetCollectorStatus(); var t = resultObj.GetType(); @@ -96,7 +103,7 @@ namespace CncService.Tests var dashboardRepo = new FakeDashboardRepository(); var heartbeatRepo = new FakeCollectorHeartbeatRepository(latest); var checker = new FakeWindowsServiceChecker(CncService.Interface.ServiceStatusEnum.Starting); - var svc = new DashboardService(dashboardRepo, heartbeatRepo, checker); + var svc = new DashboardService(dashboardRepo, heartbeatRepo, new FakeSysConfigRepository(), checker); var resultObj = svc.GetCollectorStatus(); var t = resultObj.GetType(); diff --git a/tests/CncService.Tests/ProductionServiceTests.cs b/tests/CncService.Tests/ProductionServiceTests.cs index 9ccbd23..fae2557 100644 --- a/tests/CncService.Tests/ProductionServiceTests.cs +++ b/tests/CncService.Tests/ProductionServiceTests.cs @@ -51,8 +51,8 @@ namespace CncService.Tests // 插入机床+日产量数据 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_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) + VALUES ('M001', '机床1', 1, 1, '0.0.0.0', 1, 1, NOW(), NOW())"); TestDb.Execute(@"INSERT INTO cnc_daily_production (machine_id, production_date, program_name, total_quantity, created_at, updated_at) VALUES (1, CURDATE(), 'O0001', 100, NOW(), NOW())"); @@ -75,8 +75,8 @@ namespace CncService.Tests { 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_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) + VALUES ('M001', '机床1', 1, 1, '0.0.0.0', 1, 1, NOW(), NOW())"); TestDb.Execute(@"INSERT INTO cnc_daily_production (machine_id, production_date, program_name, total_quantity, created_at, updated_at) VALUES (1, CURDATE(), 'O0001', 150, NOW(), NOW())"); diff --git a/tests/CncService.Tests/ServiceFactory.cs b/tests/CncService.Tests/ServiceFactory.cs index 1847ba4..1f04b5a 100644 --- a/tests/CncService.Tests/ServiceFactory.cs +++ b/tests/CncService.Tests/ServiceFactory.cs @@ -65,13 +65,13 @@ namespace CncService.Tests /// 创建CollectAddressService public static CollectAddressService CreateCollectAddressService() { - return new CollectAddressService(NewCollectAddressRepo(), NewMachineRepo(), NewBrandRepo(), NewWorkshopRepo(), NewCollectRawRepo()); + return new CollectAddressService(NewCollectAddressRepo(), NewMachineRepo(), NewBrandRepo(), NewWorkshopRepo(), NewCollectRawRepo(), NewSysConfigRepo()); } /// 创建WorkerService public static WorkerService CreateWorkerService() { - return new WorkerService(NewWorkerRepo(), NewWorkerMachineRepo(), NewMachineRepo()); + return new WorkerService(NewWorkerRepo(), NewWorkerMachineRepo(), NewMachineRepo(), NewSysConfigRepo()); } /// 创建ProductionService @@ -101,7 +101,7 @@ namespace CncService.Tests /// 创建DashboardService public static DashboardService CreateDashboardService() { - return new DashboardService(NewDashboardRepo(), NewCollectorHeartbeatRepo()); + return new DashboardService(NewDashboardRepo(), NewCollectorHeartbeatRepo(), NewSysConfigRepo()); } /// 创建CollectDataService diff --git a/tests/CncService.Tests/WorkerServiceTests.cs b/tests/CncService.Tests/WorkerServiceTests.cs index 2e626b5..27219d5 100644 --- a/tests/CncService.Tests/WorkerServiceTests.cs +++ b/tests/CncService.Tests/WorkerServiceTests.cs @@ -44,8 +44,8 @@ namespace CncService.Tests { 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 (@code, '测试机床', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())", + TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) + VALUES (@code, '测试机床', 1, 1, '0.0.0.0', 1, 1, NOW(), NOW())", new { code = deviceCode }); return TestDb.QuerySingle("SELECT MAX(id) FROM cnc_machine"); } diff --git a/tests/CncService.Tests/WorkshopServiceTests.cs b/tests/CncService.Tests/WorkshopServiceTests.cs index dd9d5fa..2bfcd34 100644 --- a/tests/CncService.Tests/WorkshopServiceTests.cs +++ b/tests/CncService.Tests/WorkshopServiceTests.cs @@ -242,8 +242,8 @@ namespace CncService.Tests // 先插入有效的采集地址 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_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) + VALUES ('M001', '机床1', 1, 1, '0.0.0.0', 1, 1, NOW(), NOW())"); var ex = Assert.Throws(() => _service.Delete(1)); Assert.Equal(ErrorCode.DataReferenced, ex.Code); @@ -292,9 +292,9 @@ namespace CncService.Tests // 先插入一个有效的采集地址,满足cnc_machine的外键约束 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()), - ('M002', '机床2', 1, 1, '0.0.0.0', 1, 1, 0, NOW(), NOW())"); + TestDb.Execute(@"INSERT INTO cnc_machine (device_code, name, workshop_id, collect_address_id, ip_address, brand_id, is_enabled, created_at, updated_at) + VALUES ('M001', '机床1', 1, 1, '0.0.0.0', 1, 1, NOW(), NOW()), + ('M002', '机床2', 1, 1, '0.0.0.0', 1, 1, NOW(), NOW())"); var count = _service.GetMachineCount(1); Assert.Equal(2, count); diff --git a/tests/CncWebApi.Tests/ControllerFactory.cs b/tests/CncWebApi.Tests/ControllerFactory.cs index dd3717a..2ce8fc1 100644 --- a/tests/CncWebApi.Tests/ControllerFactory.cs +++ b/tests/CncWebApi.Tests/ControllerFactory.cs @@ -48,11 +48,11 @@ namespace CncWebApi.Tests #region Service 创建 private static IAuthService CreateAuthService() => new AuthService(SysConfigRepo(), _jwtSecret); - private static IDashboardService CreateDashboardService() => new DashboardService(DashboardRepo(), HeartbeatRepo()); + private static IDashboardService CreateDashboardService() => new DashboardService(DashboardRepo(), HeartbeatRepo(), SysConfigRepo()); private static IMachineService CreateMachineService() => new MachineService(MachineRepo(), CollectAddressRepo(), WorkerMachineRepo(), BrandRepo()); private static IBrandService CreateBrandService() => new BrandService(BrandRepo(), BrandFieldMappingRepo(), CollectAddressRepo()); - private static ICollectAddressService CreateCollectAddressService() => new CollectAddressService(CollectAddressRepo(), MachineRepo(), BrandRepo(), WorkshopRepo(), CollectRawRepo()); - private static IWorkerService CreateWorkerService() => new WorkerService(WorkerRepo(), WorkerMachineRepo(), MachineRepo()); + private static ICollectAddressService CreateCollectAddressService() => new CollectAddressService(CollectAddressRepo(), MachineRepo(), BrandRepo(), WorkshopRepo(), CollectRawRepo(), SysConfigRepo()); + private static IWorkerService CreateWorkerService() => new WorkerService(WorkerRepo(), WorkerMachineRepo(), MachineRepo(), SysConfigRepo()); private static IProductionService CreateProductionService() => new ProductionService(DailyProductionRepo(), ProductionSegmentRepo(), ProductionAdjustmentRepo()); private static IAlertService CreateAlertService() => new AlertService(AlertRepo()); private static IWorkshopService CreateWorkshopService() => new WorkshopService(WorkshopRepo());