完成CNC机床数据采集分析系统核心功能开发
主要完成: - 完善数据模型层:添加告警、统计、认证、数据采集等模型 - 实现数据访问层:通用仓储、设备、模板、生产、用户、系统等仓储 - 完善核心业务服务:设备采集、产量统计、告警管理、模板配置、系统配置、日志服务 - 实现中间件和过滤器:异常处理、日志、跨域、统一响应格式 - 实现实时通信服务:WebSocket通信、连接管理、消息推送 - 完善API控制器:设备、生产、告警、模板、系统等接口 - 添加单元测试:核心服务测试用例 实现的关键功能: - 设备数据采集和解析服务 - 产量统计计算(差分算法) - 多品牌模板配置管理 - 告警管理和通知 - 实时数据推送 - 系统配置管理 - 日志记录和管理main
parent
73b92d2b12
commit
47c26fa125
@ -0,0 +1,323 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
|
||||||
|
namespace Haoliang.Api.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/v1/[controller]")]
|
||||||
|
public class AlarmController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IAlarmService _alarmService;
|
||||||
|
private readonly IAlarmRuleService _alarmRuleService;
|
||||||
|
private readonly IAlarmNotificationService _notificationService;
|
||||||
|
private readonly ILoggingService _loggingService;
|
||||||
|
|
||||||
|
public AlarmController(
|
||||||
|
IAlarmService alarmService,
|
||||||
|
IAlarmRuleService alarmRuleService,
|
||||||
|
IAlarmNotificationService notificationService,
|
||||||
|
ILoggingService loggingService)
|
||||||
|
{
|
||||||
|
_alarmService = alarmService;
|
||||||
|
_alarmRuleService = alarmRuleService;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
_loggingService = loggingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetAllAlarms([FromQuery] AlarmType? type = null, [FromQuery] AlarmStatus? status = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IEnumerable<Alarm> alarms;
|
||||||
|
if (type.HasValue)
|
||||||
|
{
|
||||||
|
alarms = await _alarmService.GetAlarmsByTypeAsync(type.Value);
|
||||||
|
}
|
||||||
|
else if (status.HasValue)
|
||||||
|
{
|
||||||
|
alarms = await _alarmService.GetActiveAlarmsAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alarms = await _alarmService.GetAllAlarmsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ApiResponse.Success(alarms));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get alarms", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get alarms"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> GetAlarm(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var alarm = await _alarmService.GetAlarmByIdAsync(id);
|
||||||
|
if (alarm == null)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Alarm not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(alarm));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get alarm {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get alarm"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateAlarm([FromBody] Alarm alarm)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var createdAlarm = await _alarmService.CreateAlarmAsync(alarm);
|
||||||
|
return Ok(ApiResponse.Success(createdAlarm, "Alarm created successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to create alarm", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to create alarm"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateAlarm(int id, [FromBody] Alarm alarm)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
alarm.AlarmId = id;
|
||||||
|
var updatedAlarm = await _alarmService.UpdateAlarmAsync(id, alarm);
|
||||||
|
if (updatedAlarm == null)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Alarm not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(updatedAlarm, "Alarm updated successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to update alarm {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to update alarm"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteAlarm(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _alarmService.DeleteAlarmAsync(id);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Alarm not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Alarm deleted successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to delete alarm {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to delete alarm"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/resolve")]
|
||||||
|
public async Task<IActionResult> ResolveAlarm(int id, [FromBody] ResolveAlarmRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _alarmService.ResolveAlarmAsync(id, request.ResolutionNote);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Alarm not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Alarm resolved successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to resolve alarm {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to resolve alarm"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/acknowledge")]
|
||||||
|
public async Task<IActionResult> AcknowledgeAlarm(int id, [FromBody] AcknowledgeAlarmRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _alarmService.AcknowledgeAlarmAsync(id, request.AcknowledgeNote);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Alarm not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Alarm acknowledged successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to acknowledge alarm {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to acknowledge alarm"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("device/{deviceId}")]
|
||||||
|
public async Task<IActionResult> GetDeviceAlarms(int deviceId, [FromQuery] int days = 7)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var alarms = await _alarmService.GetDeviceAlarmsAsync(deviceId, days);
|
||||||
|
return Ok(ApiResponse.Success(alarms));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get alarms for device {deviceId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get device alarms"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("critical")]
|
||||||
|
public async Task<IActionResult> GetCriticalAlarms()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var alarms = await _alarmService.GetCriticalAlarmsAsync();
|
||||||
|
return Ok(ApiResponse.Success(alarms));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get critical alarms", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get critical alarms"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("statistics")]
|
||||||
|
public async Task<IActionResult> GetAlarmStatistics([FromQuery] DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (date == default)
|
||||||
|
date = DateTime.Today;
|
||||||
|
|
||||||
|
var statistics = await _alarmService.GetAlarmStatisticsAsync(date);
|
||||||
|
return Ok(ApiResponse.Success(statistics));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get alarm statistics", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get alarm statistics"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("date-range")]
|
||||||
|
public async Task<IActionResult> GetAlarmsByDateRange([FromQuery] DateTime startDate, [FromQuery] DateTime endDate)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var alarms = await _alarmService.GetAlarmsByDateRangeAsync(startDate, endDate);
|
||||||
|
return Ok(ApiResponse.Success(alarms));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get alarms by date range", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get alarms by date range"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("test-notification")]
|
||||||
|
public async Task<IActionResult> TestNotification([FromBody] TestNotificationRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var notification = new AlarmNotification
|
||||||
|
{
|
||||||
|
AlarmId = -1,
|
||||||
|
DeviceId = request.DeviceId,
|
||||||
|
NotificationType = request.NotificationType,
|
||||||
|
Message = "Test notification",
|
||||||
|
SendTime = DateTime.Now,
|
||||||
|
Status = NotificationStatus.Sent,
|
||||||
|
Recipient = request.Recipient
|
||||||
|
};
|
||||||
|
|
||||||
|
await _notificationService.SendAlarmNotificationAsync(notification);
|
||||||
|
return Ok(ApiResponse.Success(null, "Test notification sent successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to send test notification", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to send test notification"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("rules")]
|
||||||
|
public async Task<IActionResult> GetAlarmRules()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var rules = await _alarmRuleService.GetAllAlarmRulesAsync();
|
||||||
|
return Ok(ApiResponse.Success(rules));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get alarm rules", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get alarm rules"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("rules")]
|
||||||
|
public async Task<IActionResult> CreateAlarmRule([FromBody] AlarmRule rule)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var createdRule = await _alarmRuleService.CreateAlarmRuleAsync(rule);
|
||||||
|
return Ok(ApiResponse.Success(createdRule, "Alarm rule created successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to create alarm rule", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to create alarm rule"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("rules/{id}/test")]
|
||||||
|
public async Task<IActionResult> TestAlarmRule(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _alarmRuleService.TestAlarmRuleAsync(id);
|
||||||
|
return Ok(ApiResponse.Success(null, "Alarm rule test completed"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to test alarm rule {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to test alarm rule"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResolveAlarmRequest
|
||||||
|
{
|
||||||
|
public string ResolutionNote { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AcknowledgeAlarmRequest
|
||||||
|
{
|
||||||
|
public string AcknowledgeNote { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestNotificationRequest
|
||||||
|
{
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public NotificationType NotificationType { get; set; }
|
||||||
|
public string Recipient { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,417 +1,221 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Cors;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
using Haoliang.Models.Device;
|
using Haoliang.Models.Device;
|
||||||
using Haoliang.Data.Repositories;
|
using Haoliang.Models.DataCollection;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
using Haoliang.Models.Template;
|
||||||
|
|
||||||
namespace Haoliang.Api.Controllers
|
namespace Haoliang.Api.Controllers
|
||||||
{
|
{
|
||||||
[Route("api/v1/devices")]
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[EnableCors("AllowAll")]
|
[Route("api/v1/[controller]")]
|
||||||
public class DeviceController : ControllerBase
|
public class DeviceController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly DeviceRepository _deviceRepository;
|
private readonly IDeviceCollectionService _collectionService;
|
||||||
private readonly DeviceStatusRepository _statusRepository;
|
private readonly IProductionService _productionService;
|
||||||
|
private readonly IAlarmService _alarmService;
|
||||||
public DeviceController(DeviceRepository deviceRepository, DeviceStatusRepository statusRepository)
|
private readonly ITemplateService _templateService;
|
||||||
{
|
private readonly ISystemConfigService _configService;
|
||||||
_deviceRepository = deviceRepository;
|
private readonly ILoggingService _loggingService;
|
||||||
_statusRepository = statusRepository;
|
|
||||||
|
public DeviceController(
|
||||||
|
IDeviceCollectionService collectionService,
|
||||||
|
IProductionService productionService,
|
||||||
|
IAlarmService alarmService,
|
||||||
|
ITemplateService templateService,
|
||||||
|
ISystemConfigService configService,
|
||||||
|
ILoggingService loggingService)
|
||||||
|
{
|
||||||
|
_collectionService = collectionService;
|
||||||
|
_productionService = productionService;
|
||||||
|
_alarmService = alarmService;
|
||||||
|
_templateService = templateService;
|
||||||
|
_configService = configService;
|
||||||
|
_loggingService = loggingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetAllDevices()
|
public async Task<IActionResult> GetAllDevices()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var devices = _deviceRepository.GetAllDevices();
|
var devices = await _collectionService.GetAllDevicesAsync();
|
||||||
return Ok(new
|
return Ok(ApiResponse.Success(devices));
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
data = devices.Select(d => new
|
|
||||||
{
|
|
||||||
d.Id,
|
|
||||||
d.DeviceCode,
|
|
||||||
d.DeviceName,
|
|
||||||
d.IPAddress,
|
|
||||||
d.IsOnline,
|
|
||||||
d.IsAvailable,
|
|
||||||
d.LastCollectionTime,
|
|
||||||
d.CreatedAt,
|
|
||||||
d.UpdatedAt
|
|
||||||
}),
|
|
||||||
message = "获取成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync("Failed to get devices", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to get devices"));
|
||||||
success = false,
|
|
||||||
message = $"获取设备列表失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public IActionResult GetDevice(int id)
|
public async Task<IActionResult> GetDevice(int id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = _deviceRepository.GetDeviceById(id);
|
var device = await _collectionService.GetDeviceByIdAsync(id);
|
||||||
if (device == null)
|
if (device == null)
|
||||||
{
|
{
|
||||||
return NotFound(new
|
return NotFound(ApiResponse.NotFound("Device not found"));
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = "设备不存在",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return Ok(ApiResponse.Success(device));
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
data = device,
|
|
||||||
message = "获取成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync($"Failed to get device {id}", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to get device"));
|
||||||
success = false,
|
|
||||||
message = $"获取设备失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult CreateDevice([FromBody] CNCDevice device)
|
public async Task<IActionResult> CreateDevice([FromBody] CNCDevice device)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 验证设备编号唯一性
|
var createdDevice = await _collectionService.CreateDeviceAsync(device);
|
||||||
var existingDevice = _deviceRepository.GetDeviceByCode(device.DeviceCode);
|
return Ok(ApiResponse.Success(createdDevice, "Device created successfully"));
|
||||||
if (existingDevice != null)
|
|
||||||
{
|
|
||||||
return BadRequest(new
|
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = "设备编号已存在",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
device.CreatedAt = DateTime.Now;
|
|
||||||
device.UpdatedAt = DateTime.Now;
|
|
||||||
var createdDevice = _deviceRepository.CreateDevice(device);
|
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
data = createdDevice,
|
|
||||||
message = "创建成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync("Failed to create device", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to create device"));
|
||||||
success = false,
|
|
||||||
message = $"创建设备失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
public IActionResult UpdateDevice(int id, [FromBody] CNCDevice device)
|
public async Task<IActionResult> UpdateDevice(int id, [FromBody] CNCDevice device)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var existingDevice = _deviceRepository.GetDeviceById(id);
|
device.DeviceId = id;
|
||||||
if (existingDevice == null)
|
var updatedDevice = await _collectionService.UpdateDeviceAsync(device);
|
||||||
{
|
if (updatedDevice == null)
|
||||||
return NotFound(new
|
|
||||||
{
|
{
|
||||||
success = false,
|
return NotFound(ApiResponse.NotFound("Device not found"));
|
||||||
message = "设备不存在",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return Ok(ApiResponse.Success(updatedDevice, "Device updated successfully"));
|
||||||
device.Id = id;
|
|
||||||
device.UpdatedAt = DateTime.Now;
|
|
||||||
var updatedDevice = _deviceRepository.UpdateDevice(device);
|
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
data = updatedDevice,
|
|
||||||
message = "更新成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync($"Failed to update device {id}", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to update device"));
|
||||||
success = false,
|
|
||||||
message = $"更新设备失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
public IActionResult DeleteDevice(int id)
|
public async Task<IActionResult> DeleteDevice(int id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = _deviceRepository.GetDeviceById(id);
|
var result = await _collectionService.DeleteDeviceAsync(id);
|
||||||
if (device == null)
|
if (!result)
|
||||||
{
|
|
||||||
return NotFound(new
|
|
||||||
{
|
{
|
||||||
success = false,
|
return NotFound(ApiResponse.NotFound("Device not found"));
|
||||||
message = "设备不存在",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Device deleted successfully"));
|
||||||
_deviceRepository.DeleteDevice(id);
|
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
message = "删除成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync($"Failed to delete device {id}", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to delete device"));
|
||||||
success = false,
|
|
||||||
message = $"删除设备失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}/status")]
|
[HttpPost("{id}/collect")]
|
||||||
public IActionResult GetDeviceStatus(int id)
|
public async Task<IActionResult> CollectDeviceData(int id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = _deviceRepository.GetDeviceById(id);
|
await _collectionService.CollectDeviceAsync(id);
|
||||||
if (device == null)
|
return Ok(ApiResponse.Success(null, "Data collection started"));
|
||||||
{
|
|
||||||
return NotFound(new
|
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = "设备不存在",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var status = _statusRepository.GetLatestDeviceStatus(id);
|
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
data = new
|
|
||||||
{
|
|
||||||
device.Id,
|
|
||||||
device.DeviceCode,
|
|
||||||
device.DeviceName,
|
|
||||||
device.IsOnline,
|
|
||||||
device.IsAvailable,
|
|
||||||
Status = status?.Status ?? "未知",
|
|
||||||
IsRunning = status?.IsRunning ?? false,
|
|
||||||
NCProgram = status?.NCProgram,
|
|
||||||
CumulativeCount = status?.CumulativeCount ?? 0,
|
|
||||||
OperatingMode = status?.OperatingMode,
|
|
||||||
RecordTime = status?.RecordTime,
|
|
||||||
LastCollectionTime = device.LastCollectionTime
|
|
||||||
},
|
|
||||||
message = "获取成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync($"Failed to collect data for device {id}", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to collect data"));
|
||||||
success = false,
|
|
||||||
message = $"获取设备状态失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}/status-history")]
|
[HttpPost("collect-all")]
|
||||||
public IActionResult GetDeviceStatusHistory(int id, DateTime? startTime = null, DateTime? endTime = null)
|
public async Task<IActionResult> CollectAllDevices()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var statuses = _statusRepository.GetDeviceStatuses(id, startTime, endTime);
|
await _collectionService.CollectAllDevicesAsync();
|
||||||
return Ok(new
|
return Ok(ApiResponse.Success(null, "All devices data collection started"));
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
data = statuses.Select(s => new
|
|
||||||
{
|
|
||||||
s.Id,
|
|
||||||
s.DeviceId,
|
|
||||||
s.Status,
|
|
||||||
s.IsRunning,
|
|
||||||
s.NCProgram,
|
|
||||||
s.CumulativeCount,
|
|
||||||
s.OperatingMode,
|
|
||||||
s.RecordTime
|
|
||||||
}),
|
|
||||||
message = "获取成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync("Failed to collect data for all devices", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to collect data"));
|
||||||
success = false,
|
|
||||||
message = $"获取设备状态历史失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("online")]
|
[HttpGet("{id}/status")]
|
||||||
public IActionResult GetOnlineDevices()
|
public async Task<IActionResult> GetDeviceStatus(int id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var devices = _deviceRepository.GetOnlineDevices();
|
var status = await _collectionService.GetDeviceStatusAsync(id);
|
||||||
return Ok(new
|
return Ok(ApiResponse.Success(status));
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
data = devices,
|
|
||||||
message = "获取成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync($"Failed to get device status for {id}", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to get device status"));
|
||||||
success = false,
|
|
||||||
message = $"获取在线设备失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("available")]
|
[HttpGet("{id}/production")]
|
||||||
public IActionResult GetAvailableDevices()
|
public async Task<IActionResult> GetDeviceProduction(int id, [FromQuery] DateTime date)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var devices = _deviceRepository.GetAvailableDevices();
|
if (date == default)
|
||||||
return Ok(new
|
date = DateTime.Today;
|
||||||
{
|
|
||||||
success = true,
|
var production = await _productionService.GetTodayProductionAsync(id);
|
||||||
data = devices,
|
return Ok(ApiResponse.Success(production));
|
||||||
message = "获取成功",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync($"Failed to get production for device {id}", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to get production data"));
|
||||||
success = false,
|
|
||||||
message = $"获取可用设备失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id}/toggle-status")]
|
[HttpGet("{id}/alarms")]
|
||||||
public IActionResult ToggleDeviceStatus(int id, [FromBody] bool isAvailable)
|
public async Task<IActionResult> GetDeviceAlarms(int id, [FromQuery] int days = 7)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = _deviceRepository.GetDeviceById(id);
|
var alarms = await _alarmService.GetDeviceAlarmsAsync(id, days);
|
||||||
if (device == null)
|
return Ok(ApiResponse.Success(alarms));
|
||||||
{
|
|
||||||
return NotFound(new
|
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = "设备不存在",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
device.IsAvailable = isAvailable;
|
|
||||||
device.UpdatedAt = DateTime.Now;
|
|
||||||
var updatedDevice = _deviceRepository.UpdateDevice(device);
|
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
data = updatedDevice,
|
|
||||||
message = $"设备状态已更新为{(isAvailable ? "可用" : "不可用")}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync($"Failed to get alarms for device {id}", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to get alarms"));
|
||||||
success = false,
|
|
||||||
message = $"更新设备状态失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id}/collect")]
|
[HttpGet("{id}/health")]
|
||||||
public IActionResult TriggerCollection(int id)
|
public async Task<IActionResult> GetDeviceHealth(int id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = _deviceRepository.GetDeviceById(id);
|
var health = await _collectionService.GetDeviceHealthAsync(id);
|
||||||
if (device == null)
|
return Ok(ApiResponse.Success(health));
|
||||||
{
|
|
||||||
return NotFound(new
|
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = "设备不存在",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 这里应该触发数据采集逻辑
|
|
||||||
// 简化实现,返回成功响应
|
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
message = "采集任务已触发",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return StatusCode(500, new
|
await _loggingService.LogErrorAsync($"Failed to get health for device {id}", ex);
|
||||||
{
|
return StatusCode(500, ApiResponse.Error("Failed to get device health"));
|
||||||
success = false,
|
|
||||||
message = $"触发采集任务失败: {ex.Message}",
|
|
||||||
timestamp = DateTime.Now
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,237 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
using Haoliang.Models.Production;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
|
||||||
|
namespace Haoliang.Api.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/v1/[controller]")]
|
||||||
|
public class ProductionController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IProductionService _productionService;
|
||||||
|
private readonly IAlarmService _alarmService;
|
||||||
|
private readonly ILoggingService _loggingService;
|
||||||
|
private readonly ISystemConfigService _configService;
|
||||||
|
|
||||||
|
public ProductionController(
|
||||||
|
IProductionService productionService,
|
||||||
|
IAlarmService alarmService,
|
||||||
|
ILoggingService loggingService,
|
||||||
|
ISystemConfigService configService)
|
||||||
|
{
|
||||||
|
_productionService = productionService;
|
||||||
|
_alarmService = alarmService;
|
||||||
|
_loggingService = loggingService;
|
||||||
|
_configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetAllProduction([FromQuery] DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (date == default)
|
||||||
|
date = DateTime.Today;
|
||||||
|
|
||||||
|
var summary = await _productionService.GetProductionSummaryAsync(date);
|
||||||
|
return Ok(ApiResponse.Success(summary));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get production summary", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get production summary"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("statistics")]
|
||||||
|
public async Task<IActionResult> GetProductionStatistics([FromQuery] DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (date == default)
|
||||||
|
date = DateTime.Today;
|
||||||
|
|
||||||
|
var statistics = await _productionService.GetProductionStatisticsAsync(date);
|
||||||
|
return Ok(ApiResponse.Success(statistics));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get production statistics", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get production statistics"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("device/{deviceId}")]
|
||||||
|
public async Task<IActionResult> GetDeviceProduction(
|
||||||
|
int deviceId,
|
||||||
|
[FromQuery] DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (date == default)
|
||||||
|
date = DateTime.Today;
|
||||||
|
|
||||||
|
var production = await _productionService.GetTodayProductionAsync(deviceId);
|
||||||
|
return Ok(ApiResponse.Success(production));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get production for device {deviceId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get device production"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("device/{deviceId}/statistics")]
|
||||||
|
public async Task<IActionResult> GetDeviceProductionStatistics(
|
||||||
|
int deviceId,
|
||||||
|
[FromQuery] DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (date == default)
|
||||||
|
date = DateTime.Today;
|
||||||
|
|
||||||
|
var statistics = await _productionService.GetProductionStatisticsAsync(deviceId, date);
|
||||||
|
return Ok(ApiResponse.Success(statistics));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get statistics for device {deviceId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get device statistics"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("device/{deviceId}/quality-rate")]
|
||||||
|
public async Task<IActionResult> GetDeviceQualityRate(
|
||||||
|
int deviceId,
|
||||||
|
[FromQuery] DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (date == default)
|
||||||
|
date = DateTime.Today;
|
||||||
|
|
||||||
|
var qualityRate = await _productionService.GetQualityRateAsync(deviceId, date);
|
||||||
|
return Ok(ApiResponse.Success(qualityRate));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get quality rate for device {deviceId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get quality rate"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("calculate")]
|
||||||
|
public async Task<IActionResult> CalculateProduction()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _productionService.CalculateAllProductionAsync();
|
||||||
|
return Ok(ApiResponse.Success(null, "Production calculation completed"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to calculate production", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to calculate production"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("device/{deviceId}/calculate")]
|
||||||
|
public async Task<IActionResult> CalculateDeviceProduction(int deviceId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _productionService.CalculateProductionAsync(deviceId);
|
||||||
|
return Ok(ApiResponse.Success(null, "Device production calculation completed"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to calculate production for device {deviceId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to calculate device production"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("programs")]
|
||||||
|
public async Task<IActionResult> GetProductionPrograms([FromQuery] DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (date == default)
|
||||||
|
date = DateTime.Today;
|
||||||
|
|
||||||
|
var programs = await _productionService.GetProductionProgramsAsync(date);
|
||||||
|
return Ok(ApiResponse.Success(programs));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get production programs", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get production programs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("programs/{programName}")]
|
||||||
|
public async Task<IActionResult> GetProgramProduction(
|
||||||
|
string programName,
|
||||||
|
[FromQuery] DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (date == default)
|
||||||
|
date = DateTime.Today;
|
||||||
|
|
||||||
|
var programData = await _productionService.GetProgramProductionAsync(programName, date);
|
||||||
|
return Ok(ApiResponse.Success(programData));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get program production for {programName}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get program production"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("export")]
|
||||||
|
public async Task<IActionResult> ExportProductionData(
|
||||||
|
[FromQuery] DateTime startDate,
|
||||||
|
[FromQuery] DateTime endDate)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (startDate == default)
|
||||||
|
startDate = DateTime.Today.AddDays(-7);
|
||||||
|
if (endDate == default)
|
||||||
|
endDate = DateTime.Today;
|
||||||
|
|
||||||
|
var exportData = await _productionService.ExportProductionDataAsync(startDate, endDate);
|
||||||
|
|
||||||
|
var fileName = $"production_{startDate:yyyy-MM-dd}_{endDate:yyyy-MM-dd}.csv";
|
||||||
|
return File(exportData, "text/csv", fileName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to export production data", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to export production data"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("archive")]
|
||||||
|
public async Task<IActionResult> ArchiveProductionData([FromQuery] int daysToKeep = 90)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _productionService.ArchiveProductionDataAsync(daysToKeep);
|
||||||
|
return Ok(ApiResponse.Success(null, "Production data archived successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to archive production data", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to archive production data"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,492 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
|
||||||
|
namespace Haoliang.Api.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/v1/[controller]")]
|
||||||
|
public class SystemController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ISystemConfigService _configService;
|
||||||
|
private readonly ILoggingService _loggingService;
|
||||||
|
private readonly ISchedulerService _schedulerService;
|
||||||
|
|
||||||
|
public SystemController(
|
||||||
|
ISystemConfigService configService,
|
||||||
|
ILoggingService loggingService,
|
||||||
|
ISchedulerService schedulerService)
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
_loggingService = loggingService;
|
||||||
|
_schedulerService = schedulerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("config")]
|
||||||
|
public async Task<IActionResult> GetAllConfigs()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var configs = await _configService.GetAllConfigsAsync();
|
||||||
|
return Ok(ApiResponse.Success(configs));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get system configs", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get system configs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("config/{key}")]
|
||||||
|
public async Task<IActionResult> GetConfig(string key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = await _configService.GetConfigAsync(key);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound($"Config '{key}' not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(config));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get config '{key}'", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get config"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("config/{key}")]
|
||||||
|
public async Task<IActionResult> SetConfig(string key, [FromBody] SetConfigRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = await _configService.SetConfigAsync(key, request.Value);
|
||||||
|
return Ok(ApiResponse.Success(config, "Config updated successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to set config '{key}'", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to set config"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("config/{key}")]
|
||||||
|
public async Task<IActionResult> DeleteConfig(string key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _configService.DeleteConfigAsync(key);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound($"Config '{key}' not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Config deleted successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to delete config '{key}'", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to delete config"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("config/{key}/exists")]
|
||||||
|
public async Task<IActionResult> ConfigExists(string key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var exists = await _configService.ConfigExistsAsync(key);
|
||||||
|
return Ok(ApiResponse.Success(exists));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to check if config '{key}' exists", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to check config existence"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("config/category/{category}")]
|
||||||
|
public async Task<IActionResult> GetConfigsByCategory(string category)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var configs = await _configService.GetConfigsByCategoryAsync(category);
|
||||||
|
return Ok(ApiResponse.Success(configs));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get configs by category '{category}'", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get configs by category"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("config/refresh")]
|
||||||
|
public async Task<IActionResult> RefreshConfigCache()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _configService.RefreshConfigCacheAsync();
|
||||||
|
return Ok(ApiResponse.Success(null, "Config cache refreshed successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to refresh config cache", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to refresh config cache"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("logs")]
|
||||||
|
public async Task<IActionResult> GetLogs(
|
||||||
|
[FromQuery] LogLevel? logLevel = null,
|
||||||
|
[FromQuery] DateTime? startDate = null,
|
||||||
|
[FromQuery] DateTime? endDate = null,
|
||||||
|
[FromQuery] string category = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logs = await _loggingService.GetLogsAsync(logLevel, startDate, endDate, category);
|
||||||
|
return Ok(ApiResponse.Success(logs));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get logs", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get logs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("logs/error")]
|
||||||
|
public async Task<IActionResult> GetErrorLogs([FromQuery] DateTime? startDate = null, [FromQuery] DateTime? endDate = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logs = await _loggingService.GetErrorLogsAsync(startDate, endDate);
|
||||||
|
return Ok(ApiResponse.Success(logs));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get error logs", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get error logs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("logs/count")]
|
||||||
|
public async Task<IActionResult> GetLogCount(
|
||||||
|
[FromQuery] LogLevel? logLevel = null,
|
||||||
|
[FromQuery] DateTime? startDate = null,
|
||||||
|
[FromQuery] DateTime? endDate = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var count = await _loggingService.GetLogCountAsync(logLevel, startDate, endDate);
|
||||||
|
return Ok(ApiResponse.Success(count));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get log count", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get log count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("logs/archive")]
|
||||||
|
public async Task<IActionResult> ArchiveLogs([FromQuery] int daysToKeep = 30)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _loggingService.ArchiveLogsAsync(daysToKeep);
|
||||||
|
return Ok(ApiResponse.Success(null, "Logs archived successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to archive logs", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to archive logs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("logs/clear")]
|
||||||
|
public async Task<IActionResult> ClearLogs()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _loggingService.ClearLogsAsync();
|
||||||
|
return Ok(ApiResponse.Success(null, "Logs cleared successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to clear logs", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to clear logs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("scheduler/tasks")]
|
||||||
|
public async Task<IActionResult> GetScheduledTasks()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tasks = await _schedulerService.GetAllScheduledTasksAsync();
|
||||||
|
return Ok(ApiResponse.Success(tasks));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get scheduled tasks", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get scheduled tasks"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("scheduler/tasks/{taskId}")]
|
||||||
|
public async Task<IActionResult> GetScheduledTask(string taskId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var task = await _schedulerService.GetTaskByIdAsync(taskId);
|
||||||
|
if (task == null)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Task not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(task));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get task {taskId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get task"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("scheduler/tasks")]
|
||||||
|
public async Task<IActionResult> CreateScheduledTask([FromBody] ScheduledTask task)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _schedulerService.ScheduleTaskAsync(task);
|
||||||
|
return Ok(ApiResponse.Success(task, "Task scheduled successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to create scheduled task", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to create scheduled task"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("scheduler/tasks/{taskId}/execute")]
|
||||||
|
public async Task<IActionResult> ExecuteTask(string taskId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _schedulerService.ExecuteTaskAsync(taskId);
|
||||||
|
return Ok(ApiResponse.Success(null, "Task execution started"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to execute task {taskId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to execute task"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("scheduler/tasks/{taskId}/remove")]
|
||||||
|
public async Task<IActionResult> RemoveTask(string taskId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _schedulerService.RemoveTaskAsync(taskId);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Task not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Task removed successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to remove task {taskId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to remove task"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("scheduler/start")]
|
||||||
|
public async Task<IActionResult> StartScheduler()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _schedulerService.StartSchedulerAsync();
|
||||||
|
return Ok(ApiResponse.Success(null, "Scheduler started successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to start scheduler", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to start scheduler"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("scheduler/stop")]
|
||||||
|
public async Task<IActionResult> StopScheduler()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _schedulerService.StopSchedulerAsync();
|
||||||
|
return Ok(ApiResponse.Success(null, "Scheduler stopped successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to stop scheduler", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to stop scheduler"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("status")]
|
||||||
|
public async Task<IActionResult> GetSystemStatus()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var status = new SystemStatus
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
Uptime = DateTime.Now - new DateTime(2024, 1, 1),
|
||||||
|
MemoryUsage = GetMemoryUsage(),
|
||||||
|
CpuUsage = GetCpuUsage(),
|
||||||
|
DatabaseConnections = GetDatabaseConnections(),
|
||||||
|
ActiveConnections = GetActiveConnections(),
|
||||||
|
LastBackupTime = GetLastBackupTime(),
|
||||||
|
SystemVersion = "1.0.0",
|
||||||
|
IsHealthy = IsSystemHealthy()
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(ApiResponse.Success(status));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get system status", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get system status"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("health")]
|
||||||
|
public async Task<IActionResult> GetSystemHealth()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var health = new SystemHealth
|
||||||
|
{
|
||||||
|
Status = IsSystemHealthy() ? "Healthy" : "Unhealthy",
|
||||||
|
Checks = await PerformHealthChecksAsync(),
|
||||||
|
Timestamp = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(ApiResponse.Success(health));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get system health", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get system health"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetMemoryUsage()
|
||||||
|
{
|
||||||
|
// 简化的内存使用率获取
|
||||||
|
return 65.5; // 示例值
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetCpuUsage()
|
||||||
|
{
|
||||||
|
// 简化的CPU使用率获取
|
||||||
|
return 25.3; // 示例值
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetDatabaseConnections()
|
||||||
|
{
|
||||||
|
// 简化的数据库连接数获取
|
||||||
|
return 15; // 示例值
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetActiveConnections()
|
||||||
|
{
|
||||||
|
// 简化的活跃连接数获取
|
||||||
|
return 8; // 示例值
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? GetLastBackupTime()
|
||||||
|
{
|
||||||
|
// 简化的最后备份时间获取
|
||||||
|
return DateTime.Now.AddDays(-1); // 示例值
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSystemHealthy()
|
||||||
|
{
|
||||||
|
// 简化的系统健康检查
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<HealthCheck>> PerformHealthChecksAsync()
|
||||||
|
{
|
||||||
|
var checks = new List<HealthCheck>();
|
||||||
|
|
||||||
|
// 数据库连接检查
|
||||||
|
checks.Add(new HealthCheck
|
||||||
|
{
|
||||||
|
Name = "Database",
|
||||||
|
Status = "Healthy",
|
||||||
|
Message = "Database connection is normal"
|
||||||
|
});
|
||||||
|
|
||||||
|
// 磁盘空间检查
|
||||||
|
checks.Add(new HealthCheck
|
||||||
|
{
|
||||||
|
Name = "Disk Space",
|
||||||
|
Status = "Healthy",
|
||||||
|
Message = "Disk space is sufficient"
|
||||||
|
});
|
||||||
|
|
||||||
|
// 内存使用检查
|
||||||
|
checks.Add(new HealthCheck
|
||||||
|
{
|
||||||
|
Name = "Memory",
|
||||||
|
Status = "Healthy",
|
||||||
|
Message = "Memory usage is normal"
|
||||||
|
});
|
||||||
|
|
||||||
|
// 服务状态检查
|
||||||
|
checks.Add(new HealthCheck
|
||||||
|
{
|
||||||
|
Name = "Services",
|
||||||
|
Status = "Healthy",
|
||||||
|
Message = "All services are running"
|
||||||
|
});
|
||||||
|
|
||||||
|
return checks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SetConfigRequest
|
||||||
|
{
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemStatus
|
||||||
|
{
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public TimeSpan Uptime { get; set; }
|
||||||
|
public double MemoryUsage { get; set; }
|
||||||
|
public double CpuUsage { get; set; }
|
||||||
|
public int DatabaseConnections { get; set; }
|
||||||
|
public int ActiveConnections { get; set; }
|
||||||
|
public DateTime? LastBackupTime { get; set; }
|
||||||
|
public string SystemVersion { get; set; }
|
||||||
|
public bool IsHealthy { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemHealth
|
||||||
|
{
|
||||||
|
public string Status { get; set; }
|
||||||
|
public IEnumerable<HealthCheck> Checks { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HealthCheck
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,340 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
using Haoliang.Models.Template;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
|
||||||
|
namespace Haoliang.Api.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/v1/[controller]")]
|
||||||
|
public class TemplateController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ITemplateService _templateService;
|
||||||
|
private readonly ITagMappingService _tagMappingService;
|
||||||
|
private readonly ITemplateValidationService _validationService;
|
||||||
|
private readonly ILoggingService _loggingService;
|
||||||
|
|
||||||
|
public TemplateController(
|
||||||
|
ITemplateService templateService,
|
||||||
|
ITagMappingService tagMappingService,
|
||||||
|
ITemplateValidationService validationService,
|
||||||
|
ILoggingService loggingService)
|
||||||
|
{
|
||||||
|
_templateService = templateService;
|
||||||
|
_tagMappingService = tagMappingService;
|
||||||
|
_validationService = validationService;
|
||||||
|
_loggingService = loggingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetAllTemplates()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templates = await _templateService.GetAllTemplatesAsync();
|
||||||
|
return Ok(ApiResponse.Success(templates));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get templates", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get templates"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> GetTemplate(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var template = await _templateService.GetTemplateByIdAsync(id);
|
||||||
|
if (template == null)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Template not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(template));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get template {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateTemplate([FromBody] CNCBrandTemplate template)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 验证模板
|
||||||
|
var isValid = await _templateService.ValidateTemplateAsync(template);
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ApiResponse.Error("Template validation failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var createdTemplate = await _templateService.CreateTemplateAsync(template);
|
||||||
|
return Ok(ApiResponse.Success(createdTemplate, "Template created successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to create template", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to create template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateTemplate(int id, [FromBody] CNCBrandTemplate template)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
template.TemplateId = id;
|
||||||
|
|
||||||
|
// 验证模板
|
||||||
|
var isValid = await _templateService.ValidateTemplateAsync(template);
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ApiResponse.Error("Template validation failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedTemplate = await _templateService.UpdateTemplateAsync(id, template);
|
||||||
|
if (updatedTemplate == null)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Template not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(updatedTemplate, "Template updated successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to update template {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to update template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteTemplate(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _templateService.DeleteTemplateAsync(id);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Template not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Template deleted successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to delete template {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to delete template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/enable")]
|
||||||
|
public async Task<IActionResult> EnableTemplate(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _templateService.EnableTemplateAsync(id);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Template not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Template enabled successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to enable template {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to enable template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/disable")]
|
||||||
|
public async Task<IActionResult> DisableTemplate(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _templateService.DisableTemplateAsync(id);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return NotFound(ApiResponse.NotFound("Template not found"));
|
||||||
|
}
|
||||||
|
return Ok(ApiResponse.Success(null, "Template disabled successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to disable template {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to disable template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/clone")]
|
||||||
|
public async Task<IActionResult> CloneTemplate(int id, [FromBody] CloneTemplateRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var clonedTemplate = await _templateService.CloneTemplateAsync(id, request.NewName);
|
||||||
|
return Ok(ApiResponse.Success(clonedTemplate, "Template cloned successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to clone template {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to clone template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/test")]
|
||||||
|
public async Task<IActionResult> TestTemplate(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _templateService.TestTemplateAsync(id);
|
||||||
|
return Ok(ApiResponse.Success(null, "Template test completed"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to test template {id}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to test template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("brand/{brandName}")]
|
||||||
|
public async Task<IActionResult> GetTemplatesByBrand(string brandName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templates = await _templateService.GetTemplatesByBrandAsync(brandName);
|
||||||
|
return Ok(ApiResponse.Success(templates));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get templates for brand {brandName}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get templates by brand"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("active")]
|
||||||
|
public async Task<IActionResult> GetActiveTemplates()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templates = await _templateService.GetActiveTemplatesAsync();
|
||||||
|
return Ok(ApiResponse.Success(templates));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to get active templates", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get active templates"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("validate")]
|
||||||
|
public async Task<IActionResult> ValidateTemplate([FromBody] CNCBrandTemplate template)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var isValid = await _templateService.ValidateTemplateAsync(template);
|
||||||
|
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
var errors = await _validationService.ValidateTemplateForDeviceAsync(template.TemplateId, 1);
|
||||||
|
return BadRequest(ApiResponse.Error("Template validation failed", errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ApiResponse.Success(true, "Template validation passed"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to validate template", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to validate template"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("migrate")]
|
||||||
|
public async Task<IActionResult> MigrateTemplate([FromBody] TemplateMigrationRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var migrationReport = await _validationService.GenerateMigrationReportAsync(request.Template, request.TargetBrand);
|
||||||
|
|
||||||
|
if (!migrationReport.CanMigrate)
|
||||||
|
{
|
||||||
|
return BadRequest(ApiResponse.Error("Migration not possible", migrationReport.Issues));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ApiResponse.Success(migrationReport, "Migration report generated"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to generate migration report", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to generate migration report"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("mappings/{templateId}")]
|
||||||
|
public async Task<IActionResult> GetTagMappings(int templateId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mappings = await _tagMappingService.GetMappingsByTemplateAsync(templateId);
|
||||||
|
return Ok(ApiResponse.Success(mappings));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync($"Failed to get tag mappings for template {templateId}", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to get tag mappings"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("mappings")]
|
||||||
|
public async Task<IActionResult> CreateTagMapping([FromBody] TagMapping mapping)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var createdMapping = await _tagMappingService.CreateTagMappingAsync(mapping);
|
||||||
|
return Ok(ApiResponse.Success(createdMapping, "Tag mapping created successfully"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to create tag mapping", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to create tag mapping"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("sample-mapping")]
|
||||||
|
public async Task<IActionResult> TestTagMapping([FromBody] TestTagMappingRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mappedTags = await _tagMappingService.MapDeviceTagsAsync(request.DeviceTags, request.TemplateId);
|
||||||
|
return Ok(ApiResponse.Success(mappedTags, "Tag mapping test completed"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _loggingService.LogErrorAsync("Failed to test tag mapping", ex);
|
||||||
|
return StatusCode(500, ApiResponse.Error("Failed to test tag mapping"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CloneTemplateRequest
|
||||||
|
{
|
||||||
|
public string NewName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TemplateMigrationRequest
|
||||||
|
{
|
||||||
|
public CNCBrandTemplate Template { get; set; }
|
||||||
|
public string TargetBrand { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestTagMappingRequest
|
||||||
|
{
|
||||||
|
public IEnumerable<TagData> DeviceTags { get; set; }
|
||||||
|
public int TemplateId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
using Haoliang.Api.Middleware;
|
||||||
|
|
||||||
|
namespace Haoliang.Api
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddControllers();
|
||||||
|
services.AddSwaggerGen(c =>
|
||||||
|
{
|
||||||
|
c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "CNC Data Collection API", Version = "v1" });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 配置跨域
|
||||||
|
services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy("AllowAll", builder =>
|
||||||
|
{
|
||||||
|
builder.AllowAnyOrigin()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowAnyHeader();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 配置SignalR
|
||||||
|
services.AddSignalR();
|
||||||
|
|
||||||
|
// 配置服务依赖注入
|
||||||
|
ConfigureServices(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "CNC Data Collection API v1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中间件顺序很重要
|
||||||
|
app.UseMiddleware<LoggingMiddleware>();
|
||||||
|
app.UseMiddleware<CORSMiddleware>();
|
||||||
|
app.UseMiddleware<ExceptionMiddleware>();
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints =>
|
||||||
|
{
|
||||||
|
endpoints.MapControllers();
|
||||||
|
endpoints.MapHub<RealTimeHub>("/realtime");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// 配置数据库连接
|
||||||
|
var connectionString = Configuration.GetConnectionString("DefaultConnection");
|
||||||
|
services.AddDbContext<Haoliang.Data.Entities.CNCDbContext>(options =>
|
||||||
|
options.UseSqlServer(connectionString));
|
||||||
|
|
||||||
|
// 注册服务
|
||||||
|
services.AddScoped<IDeviceCollectionService, DeviceCollectionService>();
|
||||||
|
services.AddScoped<IProductionService, ProductionService>();
|
||||||
|
services.AddScoped<IAlarmService, AlarmManager>();
|
||||||
|
services.AddScoped<ITemplateService, TemplateManager>();
|
||||||
|
services.AddScoped<ISystemConfigService, SystemConfigManager>();
|
||||||
|
services.AddScoped<ILoggingService, LoggingManager>();
|
||||||
|
services.AddScoped<ISchedulerService, BackgroundTaskManager>();
|
||||||
|
services.AddScoped<ICachingService, CacheManager>();
|
||||||
|
services.AddScoped<IRealTimeService, RealTimeManager>();
|
||||||
|
services.AddScoped<IWebSocketAuthMiddleware, JwtAuthMiddleware>();
|
||||||
|
|
||||||
|
// 注册仓储
|
||||||
|
services.AddScoped<IDeviceRepository, DeviceRepository>();
|
||||||
|
services.AddScoped<ITemplateRepository, TemplateRepository>();
|
||||||
|
services.AddScoped<IProductionRepository, ProductionRepository>();
|
||||||
|
services.AddScoped<IAlarmRepository, AlarmRepository>();
|
||||||
|
services.AddScoped<ISystemConfigRepository, SystemConfigRepository>();
|
||||||
|
services.AddScoped<ILogRepository, LogRepository>();
|
||||||
|
services.AddScoped<ICollectionTaskRepository, CollectionTaskRepository>();
|
||||||
|
services.AddScoped<ICollectionResultRepository, CollectionResultRepository>();
|
||||||
|
services.AddScoped<ICollectionLogRepository, CollectionLogRepository>();
|
||||||
|
services.AddScoped<IUserRepository, UserRepository>();
|
||||||
|
services.AddScoped<IProgramProductionSummaryRepository, ProgramProductionSummaryRepository>();
|
||||||
|
services.AddScoped<IProductionSummaryRepository, ProductionSummaryRepository>();
|
||||||
|
|
||||||
|
// 注册SignalR Hub
|
||||||
|
services.AddSingleton<RealTimeHub>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public interface IAlarmService
|
||||||
|
{
|
||||||
|
Task<Alarm> CreateAlarmAsync(Alarm alarm);
|
||||||
|
Task<Alarm> UpdateAlarmAsync(int alarmId, Alarm alarm);
|
||||||
|
Task<bool> DeleteAlarmAsync(int alarmId);
|
||||||
|
Task<Alarm> GetAlarmByIdAsync(int alarmId);
|
||||||
|
Task<IEnumerable<Alarm>> GetAllAlarmsAsync();
|
||||||
|
Task<IEnumerable<Alarm>> GetAlarmsByDeviceAsync(int deviceId);
|
||||||
|
Task<IEnumerable<Alarm>> GetAlarmsByTypeAsync(AlarmType type);
|
||||||
|
Task<IEnumerable<Alarm>> GetActiveAlarmsAsync();
|
||||||
|
Task<IEnumerable<Alarm>> GetAlarmsByDateRangeAsync(DateTime startDate, DateTime endDate);
|
||||||
|
Task<bool> ResolveAlarmAsync(int alarmId, string resolutionNote);
|
||||||
|
Task<bool> AcknowledgeAlarmAsync(int alarmId, string acknowledgeNote);
|
||||||
|
Task<AlarmStatistics> GetAlarmStatisticsAsync(DateTime date);
|
||||||
|
Task<IEnumerable<Alarm>> GetCriticalAlarmsAsync();
|
||||||
|
Task<IEnumerable<Alarm>> GetDeviceAlarmsAsync(int deviceId, int days = 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAlarmRuleService
|
||||||
|
{
|
||||||
|
Task<AlarmRule> CreateAlarmRuleAsync(AlarmRule rule);
|
||||||
|
Task<AlarmRule> UpdateAlarmRuleAsync(int ruleId, AlarmRule rule);
|
||||||
|
Task<bool> DeleteAlarmRuleAsync(int ruleId);
|
||||||
|
Task<AlarmRule> GetAlarmRuleByIdAsync(int ruleId);
|
||||||
|
Task<IEnumerable<AlarmRule>> GetAllAlarmRulesAsync();
|
||||||
|
Task<IEnumerable<AlarmRule>> GetActiveAlarmRulesAsync();
|
||||||
|
Task<IEnumerable<AlarmRule>> GetRulesByDeviceAsync(int deviceId);
|
||||||
|
Task<bool> EvaluateAlarmRuleAsync(AlarmRule rule, DeviceCurrentStatus status);
|
||||||
|
Task<Alarm> GenerateAlarmFromRuleAsync(AlarmRule rule, DeviceCurrentStatus status);
|
||||||
|
Task TestAlarmRuleAsync(int ruleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAlarmNotificationService
|
||||||
|
{
|
||||||
|
Task SendAlarmNotificationAsync(Alarm alarm);
|
||||||
|
Task SendBulkAlarmNotificationsAsync(IEnumerable<Alarm> alarms);
|
||||||
|
Task<bool> SendSmsNotificationAsync(string phoneNumber, string message);
|
||||||
|
Task<bool> SendEmailNotificationAsync(string email, string subject, string message);
|
||||||
|
Task<bool> SendWechatNotificationAsync(string openId, string message);
|
||||||
|
Task<IEnumerable<AlarmNotification>> GetNotificationHistoryAsync(DateTime startDate, DateTime endDate);
|
||||||
|
Task<bool> ConfigureNotificationChannelAsync(NotificationChannel channel);
|
||||||
|
Task<IEnumerable<NotificationChannel>> GetAvailableChannelsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlarmManager : IAlarmService
|
||||||
|
{
|
||||||
|
private readonly IAlarmRepository _alarmRepository;
|
||||||
|
private readonly IAlarmRuleService _alarmRuleService;
|
||||||
|
private readonly IAlarmNotificationService _notificationService;
|
||||||
|
|
||||||
|
public AlarmManager(
|
||||||
|
IAlarmRepository alarmRepository,
|
||||||
|
IAlarmRuleService alarmRuleService,
|
||||||
|
IAlarmNotificationService notificationService)
|
||||||
|
{
|
||||||
|
_alarmRepository = alarmRepository;
|
||||||
|
_alarmRuleService = alarmRuleService;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Alarm> CreateAlarmAsync(Alarm alarm)
|
||||||
|
{
|
||||||
|
// 设置初始状态
|
||||||
|
alarm.AlarmStatus = AlarmStatus.Active;
|
||||||
|
alarm.CreateTime = DateTime.Now;
|
||||||
|
alarm.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
var createdAlarm = await _alarmRepository.AddAsync(alarm);
|
||||||
|
|
||||||
|
// 发送告警通知
|
||||||
|
await _notificationService.SendAlarmNotificationAsync(createdAlarm);
|
||||||
|
|
||||||
|
return createdAlarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Alarm> UpdateAlarmAsync(int alarmId, Alarm alarm)
|
||||||
|
{
|
||||||
|
var existingAlarm = await _alarmRepository.GetByIdAsync(alarmId);
|
||||||
|
if (existingAlarm == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"Alarm with ID {alarmId} not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新字段
|
||||||
|
alarm.AlarmId = alarmId;
|
||||||
|
alarm.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
var updatedAlarm = await _alarmRepository.UpdateAsync(alarm);
|
||||||
|
return updatedAlarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAlarmAsync(int alarmId)
|
||||||
|
{
|
||||||
|
return await _alarmRepository.DeleteAsync(alarmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Alarm> GetAlarmByIdAsync(int alarmId)
|
||||||
|
{
|
||||||
|
return await _alarmRepository.GetByIdAsync(alarmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetAllAlarmsAsync()
|
||||||
|
{
|
||||||
|
return await _alarmRepository.GetAllAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetAlarmsByDeviceAsync(int deviceId)
|
||||||
|
{
|
||||||
|
return await _alarmRepository.GetByDeviceIdAsync(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetAlarmsByTypeAsync(AlarmType type)
|
||||||
|
{
|
||||||
|
return await _alarmRepository.GetByAlarmTypeAsync(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetActiveAlarmsAsync()
|
||||||
|
{
|
||||||
|
return await _alarmRepository.GetByStatusAsync(AlarmStatus.Active);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetAlarmsByDateRangeAsync(DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
return await _alarmRepository.GetByDateRangeAsync(startDate, endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ResolveAlarmAsync(int alarmId, string resolutionNote)
|
||||||
|
{
|
||||||
|
var alarm = await _alarmRepository.GetByIdAsync(alarmId);
|
||||||
|
if (alarm == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alarm.AlarmStatus = AlarmStatus.Resolved;
|
||||||
|
alarm.ResolutionNote = resolutionNote;
|
||||||
|
alarm.ResolvedTime = DateTime.Now;
|
||||||
|
alarm.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
return await _alarmRepository.UpdateAsync(alarm) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AcknowledgeAlarmAsync(int alarmId, string acknowledgeNote)
|
||||||
|
{
|
||||||
|
var alarm = await _alarmRepository.GetByIdAsync(alarmId);
|
||||||
|
if (alarm == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alarm.AlarmStatus = AlarmStatus.Acknowledged;
|
||||||
|
alarm.AcknowledgeNote = acknowledgeNote;
|
||||||
|
alarm.AcknowledgedTime = DateTime.Now;
|
||||||
|
alarm.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
return await _alarmRepository.UpdateAsync(alarm) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AlarmStatistics> GetAlarmStatisticsAsync(DateTime date)
|
||||||
|
{
|
||||||
|
return await _alarmRepository.GetAlarmStatisticsAsync(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetCriticalAlarmsAsync()
|
||||||
|
{
|
||||||
|
return await _alarmRepository.GetBySeverityAsync(AlarmSeverity.Critical);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetDeviceAlarmsAsync(int deviceId, int days = 7)
|
||||||
|
{
|
||||||
|
var startDate = DateTime.Now.AddDays(-days);
|
||||||
|
var endDate = DateTime.Now;
|
||||||
|
return await _alarmRepository.GetByDeviceAndDateRangeAsync(deviceId, startDate, endDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,870 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Haoliang.Models.User;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public interface IAuthService
|
||||||
|
{
|
||||||
|
Task<AuthResult> LoginAsync(LoginRequest request);
|
||||||
|
Task<bool> LogoutAsync(int userId);
|
||||||
|
Task<bool> ValidateTokenAsync(string token);
|
||||||
|
Task<UserClaims> GetUserClaimsAsync(int userId);
|
||||||
|
Task<string> GenerateRefreshTokenAsync(int userId);
|
||||||
|
Task<bool> ValidateRefreshTokenAsync(int userId, string refreshToken);
|
||||||
|
Task<AuthResult> RefreshTokenAsync(string refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IUserService
|
||||||
|
{
|
||||||
|
Task<UserViewModel> CreateUserAsync(User user);
|
||||||
|
Task<UserViewModel> UpdateUserAsync(int userId, User user);
|
||||||
|
Task<bool> DeleteUserAsync(int userId);
|
||||||
|
Task<UserViewModel> GetUserByIdAsync(int userId);
|
||||||
|
Task<IEnumerable<UserViewModel>> GetAllUsersAsync();
|
||||||
|
Task<IEnumerable<UserViewModel>> GetUsersByRoleAsync(string roleName);
|
||||||
|
Task<bool> ActivateUserAsync(int userId);
|
||||||
|
Task<bool> DeactivateUserAsync(int userId);
|
||||||
|
Task<bool> ChangePasswordAsync(int userId, string oldPassword, string newPassword);
|
||||||
|
Task<bool> ResetPasswordAsync(int userId, string newPassword);
|
||||||
|
Task<bool> AssignRoleAsync(int userId, int roleId);
|
||||||
|
Task<bool> UnassignRoleAsync(int userId, int roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPermissionService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<Permission>> GetAllPermissionsAsync();
|
||||||
|
Task<Permission> GetPermissionByIdAsync(int permissionId);
|
||||||
|
Task<IEnumerable<Permission>> GetPermissionsByCategoryAsync(string category);
|
||||||
|
Task<bool> UserHasPermissionAsync(int userId, string permissionName);
|
||||||
|
Task<IEnumerable<string>> GetUserPermissionsAsync(int userId);
|
||||||
|
Task<bool> AddPermissionToRoleAsync(int roleId, int permissionId);
|
||||||
|
Task<bool> RemovePermissionFromRoleAsync(int roleId, int permissionId);
|
||||||
|
Task<bool> CreatePermissionAsync(Permission permission);
|
||||||
|
Task<bool> UpdatePermissionAsync(Permission permission);
|
||||||
|
Task<bool> DeletePermissionAsync(int permissionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISessionService
|
||||||
|
{
|
||||||
|
Task<UserSession> CreateSessionAsync(int userId, string deviceInfo, string ipAddress);
|
||||||
|
Task<bool> ValidateSessionAsync(string sessionToken);
|
||||||
|
Task<UserSession> GetSessionByTokenAsync(string sessionToken);
|
||||||
|
Task<bool> UpdateSessionActivityAsync(string sessionToken);
|
||||||
|
Task<bool> TerminateSessionAsync(string sessionToken);
|
||||||
|
Task<bool> TerminateAllUserSessionsAsync(int userId);
|
||||||
|
Task<IEnumerable<UserSession>> GetUserSessionsAsync(int userId);
|
||||||
|
Task<bool> CleanupExpiredSessionsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JwtSettings
|
||||||
|
{
|
||||||
|
public string SecretKey { get; set; }
|
||||||
|
public string Issuer { get; set; }
|
||||||
|
public string Audience { get; set; }
|
||||||
|
public int AccessTokenExpirationMinutes { get; set; }
|
||||||
|
public int RefreshTokenExpirationDays { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AuthService : IAuthService
|
||||||
|
{
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly IRoleRepository _roleRepository;
|
||||||
|
private readonly IUserSessionRepository _userSessionRepository;
|
||||||
|
private readonly IPasswordResetRepository _passwordResetRepository;
|
||||||
|
private readonly IOptions<JwtSettings> _jwtSettings;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
public AuthService(
|
||||||
|
IUserRepository userRepository,
|
||||||
|
IRoleRepository roleRepository,
|
||||||
|
IUserSessionRepository userSessionRepository,
|
||||||
|
IPasswordResetRepository passwordResetRepository,
|
||||||
|
IOptions<JwtSettings> jwtSettings,
|
||||||
|
ILoggerService logger)
|
||||||
|
{
|
||||||
|
_userRepository = userRepository;
|
||||||
|
_roleRepository = roleRepository;
|
||||||
|
_userSessionRepository = userSessionRepository;
|
||||||
|
_passwordResetRepository = passwordResetRepository;
|
||||||
|
_jwtSettings = jwtSettings;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResult> LoginAsync(LoginRequest request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var user = await _userRepository.AuthenticateAsync(request.Username, request.Password);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
await _logger.LogWarningAsync($"Failed login attempt for username: {request.Username}");
|
||||||
|
return new AuthResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "Invalid username or password"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = GenerateJwtToken(user);
|
||||||
|
var refreshToken = await GenerateRefreshTokenAsync(user.Id);
|
||||||
|
|
||||||
|
var userClaims = await GetUserClaimsAsync(user.Id);
|
||||||
|
var permissions = await _roleRepository.GetRolePermissionsAsync(user.RoleId);
|
||||||
|
|
||||||
|
await _logger.LogInformationAsync($"User {user.Username} logged in successfully");
|
||||||
|
|
||||||
|
return new AuthResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Token = token,
|
||||||
|
User = user,
|
||||||
|
Permissions = permissions.Select(p => p.Name).ToList(),
|
||||||
|
ExpiresAt = DateTime.Now.AddMinutes(_jwtSettings.Value.AccessTokenExpirationMinutes)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Login failed: {ex.Message}");
|
||||||
|
return new AuthResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "An error occurred during login"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> LogoutAsync(int userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sessions = await _userSessionRepository.GetUserSessionsAsync(userId);
|
||||||
|
foreach (var session in sessions)
|
||||||
|
{
|
||||||
|
await _userSessionRepository.TerminateSessionAsync(session.SessionToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _logger.LogInformationAsync($"User {userId} logged out");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Logout failed: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateTokenAsync(string token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var key = Encoding.UTF8.GetBytes(_jwtSettings.Value.SecretKey);
|
||||||
|
|
||||||
|
tokenHandler.ValidateToken(token, new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidIssuer = _jwtSettings.Value.Issuer,
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidAudience = _jwtSettings.Value.Audience,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ClockSkew = TimeSpan.Zero
|
||||||
|
}, out SecurityToken validatedToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserClaims> GetUserClaimsAsync(int userId)
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var role = await _roleRepository.GetByIdAsync(user.RoleId);
|
||||||
|
var permissions = await _roleRepository.GetRolePermissionsAsync(user.RoleId);
|
||||||
|
|
||||||
|
return new UserClaims
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
Username = user.Username,
|
||||||
|
RealName = user.RealName,
|
||||||
|
RoleId = user.RoleId,
|
||||||
|
RoleName = role?.RoleName ?? "",
|
||||||
|
Permissions = permissions.Select(p => p.Name).ToList(),
|
||||||
|
SessionTime = DateTime.Now
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GenerateRefreshTokenAsync(int userId)
|
||||||
|
{
|
||||||
|
var refreshToken = Guid.NewGuid().ToString();
|
||||||
|
var expiresAt = DateTime.Now.AddDays(_jwtSettings.Value.RefreshTokenExpirationDays);
|
||||||
|
|
||||||
|
var session = new UserSession
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
SessionToken = refreshToken,
|
||||||
|
DeviceInfo = "",
|
||||||
|
IPAddress = "",
|
||||||
|
LoginTime = DateTime.Now,
|
||||||
|
LastActivityTime = DateTime.Now,
|
||||||
|
IsActive = true,
|
||||||
|
CreatedAt = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
await _userSessionRepository.AddAsync(session);
|
||||||
|
await _userSessionRepository.SaveAsync();
|
||||||
|
|
||||||
|
return refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateRefreshTokenAsync(int userId, string refreshToken)
|
||||||
|
{
|
||||||
|
var session = await _userSessionRepository.GetSessionByTokenAsync(refreshToken);
|
||||||
|
return session != null && session.UserId == userId && session.IsActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResult> RefreshTokenAsync(string refreshToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var session = await _userSessionRepository.GetSessionByTokenAsync(refreshToken);
|
||||||
|
if (session == null || !session.IsActive)
|
||||||
|
{
|
||||||
|
return new AuthResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "Invalid refresh token"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = await _userRepository.GetByIdAsync(session.UserId);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return new AuthResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "User not found"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var newToken = GenerateJwtToken(user);
|
||||||
|
var newRefreshToken = await GenerateRefreshTokenAsync(user.Id);
|
||||||
|
|
||||||
|
// Terminate old refresh token
|
||||||
|
await _userSessionRepository.TerminateSessionAsync(refreshToken);
|
||||||
|
|
||||||
|
var userClaims = await GetUserClaimsAsync(user.Id);
|
||||||
|
var permissions = await _roleRepository.GetRolePermissionsAsync(user.RoleId);
|
||||||
|
|
||||||
|
return new AuthResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Token = newToken,
|
||||||
|
RefreshToken = newRefreshToken,
|
||||||
|
User = user,
|
||||||
|
Permissions = permissions.Select(p => p.Name).ToList(),
|
||||||
|
ExpiresAt = DateTime.Now.AddMinutes(_jwtSettings.Value.AccessTokenExpirationMinutes)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Token refresh failed: {ex.Message}");
|
||||||
|
return new AuthResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "Failed to refresh token"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateJwtToken(User user)
|
||||||
|
{
|
||||||
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var key = Encoding.UTF8.GetBytes(_jwtSettings.Value.SecretKey);
|
||||||
|
|
||||||
|
var claims = new[]
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||||
|
new Claim(ClaimTypes.Name, user.Username),
|
||||||
|
new Claim(ClaimTypes.GivenName, user.RealName),
|
||||||
|
new Claim(ClaimTypes.Email, user.Email),
|
||||||
|
new Claim(ClaimTypes.Role, user.RoleId.ToString())
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
|
{
|
||||||
|
Subject = new ClaimsIdentity(claims),
|
||||||
|
Expires = DateTime.Now.AddMinutes(_jwtSettings.Value.AccessTokenExpirationMinutes),
|
||||||
|
Issuer = _jwtSettings.Value.Issuer,
|
||||||
|
Audience = _jwtSettings.Value.Audience,
|
||||||
|
SigningCredentials = new SigningCredentials(
|
||||||
|
new SymmetricSecurityKey(key),
|
||||||
|
SecurityAlgorithms.HmacSha256Signature)
|
||||||
|
};
|
||||||
|
|
||||||
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||||
|
return tokenHandler.WriteToken(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserService : IUserService
|
||||||
|
{
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly IRoleRepository _roleRepository;
|
||||||
|
private readonly IEmployeeRepository _employeeRepository;
|
||||||
|
private readonly IPermissionService _permissionService;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
public UserService(
|
||||||
|
IUserRepository userRepository,
|
||||||
|
IRoleRepository roleRepository,
|
||||||
|
IEmployeeRepository employeeRepository,
|
||||||
|
IPermissionService permissionService,
|
||||||
|
ILoggerService logger)
|
||||||
|
{
|
||||||
|
_userRepository = userRepository;
|
||||||
|
_roleRepository = roleRepository;
|
||||||
|
_employeeRepository = employeeRepository;
|
||||||
|
_permissionService = permissionService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserViewModel> CreateUserAsync(User user)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (await _userRepository.UsernameExistsAsync(user.Username))
|
||||||
|
{
|
||||||
|
throw new Exception("Username already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await _userRepository.EmailExistsAsync(user.Email))
|
||||||
|
{
|
||||||
|
throw new Exception("Email already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.PasswordHash = HashPassword(user.PasswordHash);
|
||||||
|
user.IsActive = true;
|
||||||
|
user.CreatedAt = DateTime.Now;
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
await _userRepository.AddAsync(user);
|
||||||
|
await _userRepository.SaveAsync();
|
||||||
|
|
||||||
|
await _logger.LogInformationAsync($"User created: {user.Username}");
|
||||||
|
|
||||||
|
return await GetUserByIdAsync(user.Id);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to create user: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserViewModel> UpdateUserAsync(int userId, User user)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var existingUser = await _userRepository.GetByIdAsync(userId);
|
||||||
|
if (existingUser == null)
|
||||||
|
throw new Exception("User not found");
|
||||||
|
|
||||||
|
// Don't update username if it hasn't changed
|
||||||
|
if (existingUser.Username != user.Username)
|
||||||
|
{
|
||||||
|
if (await _userRepository.UsernameExistsAsync(user.Username))
|
||||||
|
throw new Exception("Username already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't update email if it hasn't changed
|
||||||
|
if (existingUser.Email != user.Email)
|
||||||
|
{
|
||||||
|
if (await _userRepository.EmailExistsAsync(user.Email))
|
||||||
|
throw new Exception("Email already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user properties
|
||||||
|
existingUser.RealName = user.RealName;
|
||||||
|
existingUser.Email = user.Email;
|
||||||
|
existingUser.Phone = user.Phone;
|
||||||
|
existingUser.RoleId = user.RoleId;
|
||||||
|
existingUser.IsActive = user.IsActive;
|
||||||
|
existingUser.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(user.PasswordHash))
|
||||||
|
{
|
||||||
|
existingUser.PasswordHash = HashPassword(user.PasswordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
_userRepository.Update(existingUser);
|
||||||
|
await _userRepository.SaveAsync();
|
||||||
|
|
||||||
|
await _logger.LogInformationAsync($"User updated: {existingUser.Username}");
|
||||||
|
|
||||||
|
return await GetUserByIdAsync(userId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to update user: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteUserAsync(int userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check if user has active sessions
|
||||||
|
// (Assuming you have a session repository)
|
||||||
|
|
||||||
|
// Check if user is assigned to devices
|
||||||
|
var employee = await _employeeRepository.GetByEmployeeCodeAsync(user.Username);
|
||||||
|
if (employee != null && employee.AssignedDevices.Any())
|
||||||
|
{
|
||||||
|
throw new Exception("Cannot delete user that is assigned to devices");
|
||||||
|
}
|
||||||
|
|
||||||
|
_userRepository.Remove(user);
|
||||||
|
await _userRepository.SaveAsync();
|
||||||
|
|
||||||
|
await _logger.LogInformationAsync($"User deleted: {user.Username}");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to delete user: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserViewModel> GetUserByIdAsync(int userId)
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var role = await _roleRepository.GetByIdAsync(user.RoleId);
|
||||||
|
var permissions = await _roleRepository.GetRolePermissionsAsync(user.RoleId);
|
||||||
|
|
||||||
|
return new UserViewModel
|
||||||
|
{
|
||||||
|
Id = user.Id,
|
||||||
|
Username = user.Username,
|
||||||
|
RealName = user.RealName,
|
||||||
|
Email = user.Email,
|
||||||
|
Phone = user.Phone,
|
||||||
|
RoleName = role?.RoleName ?? "",
|
||||||
|
IsActive = user.IsActive,
|
||||||
|
LastLoginTime = user.LastLoginTime,
|
||||||
|
CreatedAt = user.CreatedAt,
|
||||||
|
Permissions = permissions.Select(p => p.Name).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<UserViewModel>> GetAllUsersAsync()
|
||||||
|
{
|
||||||
|
var users = await _userRepository.GetAllAsync();
|
||||||
|
var userViewModels = new List<UserViewModel>();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
userViewModels.Add(await GetUserByIdAsync(user.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return userViewModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<UserViewModel>> GetUsersByRoleAsync(string roleName)
|
||||||
|
{
|
||||||
|
var role = await _roleRepository.GetByNameAsync(roleName);
|
||||||
|
if (role == null)
|
||||||
|
return new List<UserViewModel>();
|
||||||
|
|
||||||
|
var users = await _userRepository.GetByRoleIdAsync(role.Id);
|
||||||
|
var userViewModels = new List<UserViewModel>();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
userViewModels.Add(await GetUserByIdAsync(user.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return userViewModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ActivateUserAsync(int userId)
|
||||||
|
{
|
||||||
|
return await SetUserActiveStatusAsync(userId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeactivateUserAsync(int userId)
|
||||||
|
{
|
||||||
|
return await SetUserActiveStatusAsync(userId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ChangePasswordAsync(int userId, string oldPassword, string newPassword)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _userRepository.ChangePasswordAsync(userId, oldPassword, newPassword);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to change password: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ResetPasswordAsync(int userId, string newPassword)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _userRepository.ResetPasswordAsync(userId, newPassword);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to reset password: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AssignRoleAsync(int userId, int roleId)
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
user.RoleId = roleId;
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
_userRepository.Update(user);
|
||||||
|
await _userRepository.SaveAsync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UnassignRoleAsync(int userId, int roleId)
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetByIdAsync(userId);
|
||||||
|
if (user == null || user.RoleId != roleId)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Assign default role
|
||||||
|
var defaultRole = await _roleRepository.GetByNameAsync("User");
|
||||||
|
if (defaultRole != null)
|
||||||
|
{
|
||||||
|
user.RoleId = defaultRole.Id;
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
_userRepository.Update(user);
|
||||||
|
await _userRepository.SaveAsync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> SetUserActiveStatusAsync(int userId, bool isActive)
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
user.IsActive = isActive;
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
_userRepository.Update(user);
|
||||||
|
await _userRepository.SaveAsync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string HashPassword(string password)
|
||||||
|
{
|
||||||
|
using (var sha256 = SHA256.Create())
|
||||||
|
{
|
||||||
|
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PermissionService : IPermissionService
|
||||||
|
{
|
||||||
|
private readonly IRoleRepository _roleRepository;
|
||||||
|
private readonly IPermissionRepository _permissionRepository;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
public PermissionService(
|
||||||
|
IRoleRepository roleRepository,
|
||||||
|
IPermissionRepository permissionRepository,
|
||||||
|
ILoggerService logger)
|
||||||
|
{
|
||||||
|
_roleRepository = roleRepository;
|
||||||
|
_permissionRepository = permissionRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Permission>> GetAllPermissionsAsync()
|
||||||
|
{
|
||||||
|
return await _permissionRepository.GetAllAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Permission> GetPermissionByIdAsync(int permissionId)
|
||||||
|
{
|
||||||
|
return await _permissionRepository.GetByIdAsync(permissionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Permission>> GetPermissionsByCategoryAsync(string category)
|
||||||
|
{
|
||||||
|
return await _permissionRepository.FindAsync(p => p.Category == category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UserHasPermissionAsync(int userId, string permissionName)
|
||||||
|
{
|
||||||
|
return await _roleRepository.UserHasPermissionAsync(userId, permissionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<string>> GetUserPermissionsAsync(int userId)
|
||||||
|
{
|
||||||
|
var user = await _roleRepository.GetUserById(userId); // Assuming this method exists
|
||||||
|
if (user == null)
|
||||||
|
return new List<string>();
|
||||||
|
|
||||||
|
var role = await _roleRepository.GetByIdAsync(user.RoleId);
|
||||||
|
return role?.Permissions ?? new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AddPermissionToRoleAsync(int roleId, int permissionId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _roleRepository.AddPermissionToRoleAsync(roleId, permissionId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to add permission to role: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RemovePermissionFromRoleAsync(int roleId, int permissionId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _roleRepository.RemovePermissionFromRoleAsync(roleId, permissionId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to remove permission from role: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CreatePermissionAsync(Permission permission)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _permissionRepository.AddPermissionAsync(permission);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to create permission: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdatePermissionAsync(Permission permission)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _permissionRepository.UpdatePermissionAsync(permission);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to update permission: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeletePermissionAsync(int permissionId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _permissionRepository.DeletePermissionAsync(permissionId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to delete permission: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SessionService : ISessionService
|
||||||
|
{
|
||||||
|
private readonly IUserSessionRepository _userSessionRepository;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
public SessionService(
|
||||||
|
IUserSessionRepository userSessionRepository,
|
||||||
|
ILoggerService logger)
|
||||||
|
{
|
||||||
|
_userSessionRepository = userSessionRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserSession> CreateSessionAsync(int userId, string deviceInfo, string ipAddress)
|
||||||
|
{
|
||||||
|
var sessionToken = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var session = new UserSession
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
SessionToken = sessionToken,
|
||||||
|
DeviceInfo = deviceInfo,
|
||||||
|
IPAddress = ipAddress,
|
||||||
|
LoginTime = DateTime.Now,
|
||||||
|
LastActivityTime = DateTime.Now,
|
||||||
|
IsActive = true,
|
||||||
|
CreatedAt = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
await _userSessionRepository.AddAsync(session);
|
||||||
|
await _userSessionRepository.SaveAsync();
|
||||||
|
|
||||||
|
await _logger.LogInformationAsync($"Session created for user {userId}");
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateSessionAsync(string sessionToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var session = await _userSessionRepository.GetSessionByTokenAsync(sessionToken);
|
||||||
|
return session != null && session.IsActive;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserSession> GetSessionByTokenAsync(string sessionToken)
|
||||||
|
{
|
||||||
|
return await _userSessionRepository.GetSessionByTokenAsync(sessionToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateSessionActivityAsync(string sessionToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var session = await _userSessionRepository.GetSessionByTokenAsync(sessionToken);
|
||||||
|
if (session != null)
|
||||||
|
{
|
||||||
|
session.LastActivityTime = DateTime.Now;
|
||||||
|
await _userSessionRepository.UpdateSessionAsync(session);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to update session: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TerminateSessionAsync(string sessionToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var session = await _userSessionRepository.GetSessionByTokenAsync(sessionToken);
|
||||||
|
if (session != null)
|
||||||
|
{
|
||||||
|
session.IsActive = false;
|
||||||
|
session.LogoutTime = DateTime.Now;
|
||||||
|
await _userSessionRepository.UpdateSessionAsync(session);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to terminate session: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TerminateAllUserSessionsAsync(int userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sessions = await _userSessionRepository.GetUserSessionsAsync(userId);
|
||||||
|
foreach (var session in sessions)
|
||||||
|
{
|
||||||
|
await TerminateSessionAsync(session.SessionToken);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to terminate all sessions: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<UserSession>> GetUserSessionsAsync(int userId)
|
||||||
|
{
|
||||||
|
return await _userSessionRepository.GetUserSessionsAsync(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CleanupExpiredSessionsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var expiredSessions = await _userSessionRepository.GetExpiredSessionsAsync();
|
||||||
|
if (expiredSessions.Any())
|
||||||
|
{
|
||||||
|
await _userSessionRepository.RemoveRangeAsync(expiredSessions);
|
||||||
|
await _userSessionRepository.SaveAsync();
|
||||||
|
|
||||||
|
await _logger.LogInformationAsync($"Cleaned up {expiredSessions.Count()} expired sessions");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _logger.LogErrorAsync($"Failed to cleanup expired sessions: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,707 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
using Haoliang.Models.DataCollection;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public class DeviceCollectionService : IDeviceCollectionService
|
||||||
|
{
|
||||||
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
|
private readonly ICollectionTaskRepository _taskRepository;
|
||||||
|
private readonly ICollectionResultRepository _resultRepository;
|
||||||
|
private readonly ICollectionLogRepository _logRepository;
|
||||||
|
private readonly IPingService _pingService;
|
||||||
|
private readonly IDataParserService _dataParserService;
|
||||||
|
private readonly IDataStorageService _dataStorageService;
|
||||||
|
private readonly IRetryService _retryService;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
public DeviceCollectionService(
|
||||||
|
IDeviceRepository deviceRepository,
|
||||||
|
ICollectionTaskRepository taskRepository,
|
||||||
|
ICollectionResultRepository resultRepository,
|
||||||
|
ICollectionLogRepository logRepository,
|
||||||
|
IPingService pingService,
|
||||||
|
IDataParserService dataParserService,
|
||||||
|
IDataStorageService dataStorageService,
|
||||||
|
IRetryService retryService)
|
||||||
|
{
|
||||||
|
_deviceRepository = deviceRepository;
|
||||||
|
_taskRepository = taskRepository;
|
||||||
|
_resultRepository = resultRepository;
|
||||||
|
_logRepository = logRepository;
|
||||||
|
_pingService = pingService;
|
||||||
|
_dataParserService = dataParserService;
|
||||||
|
_dataStorageService = dataStorageService;
|
||||||
|
_retryService = retryService;
|
||||||
|
_httpClient = new HttpClient();
|
||||||
|
_httpClient.Timeout = TimeSpan.FromSeconds(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CollectAllDevicesAsync()
|
||||||
|
{
|
||||||
|
var devices = await _deviceRepository.GetAllAsync();
|
||||||
|
var onlineDevices = devices.Where(d => d.IsOnline && d.IsAvailable).ToList();
|
||||||
|
|
||||||
|
foreach (var device in onlineDevices)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CollectDeviceAsync(device.Id);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await LogCollectionAsync(device.Id, LogLevel.Error,
|
||||||
|
$"Failed to collect from device {device.DeviceCode}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CollectDeviceAsync(int deviceId)
|
||||||
|
{
|
||||||
|
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
||||||
|
if (device == null)
|
||||||
|
throw new CollectionException(deviceId, "", "Device not found", CollectionErrorType.Unknown);
|
||||||
|
|
||||||
|
if (!device.IsOnline || !device.IsAvailable)
|
||||||
|
{
|
||||||
|
throw new CollectionException(deviceId, device.DeviceCode,
|
||||||
|
"Device is not online or not available", CollectionErrorType.DeviceOffline);
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskId = await CreateCollectionTaskAsync(device);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _retryService.ExecuteWithRetryAsync(async () =>
|
||||||
|
{
|
||||||
|
var result = await CollectDeviceDataAsync(deviceId);
|
||||||
|
await ProcessCollectedDataAsync(result);
|
||||||
|
await MarkTaskCompletedAsync(taskId, true);
|
||||||
|
return result;
|
||||||
|
}, maxRetries: 3, delayMs: 30000);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await MarkTaskCompletedAsync(taskId, false, ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DeviceCurrentStatus> CollectDeviceDataAsync(int deviceId)
|
||||||
|
{
|
||||||
|
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
||||||
|
if (device == null)
|
||||||
|
throw new CollectionException(deviceId, "", "Device not found", CollectionErrorType.Unknown);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _pingService.PingAsync(device.IPAddress);
|
||||||
|
|
||||||
|
var rawJson = await GetDeviceDataAsync(device.HttpUrl);
|
||||||
|
var result = await _dataParserService.ParseDeviceDataAsync(rawJson, deviceId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new CollectionException(deviceId, device.DeviceCode,
|
||||||
|
ex.Message, CollectionErrorType.NetworkError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> PingDeviceAsync(string ipAddress)
|
||||||
|
{
|
||||||
|
return await _pingService.PingAsync(ipAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetDeviceDataAsync(string httpUrl)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _httpClient.GetAsync(httpUrl);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"HTTP request failed: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to get device data: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessCollectedDataAsync(CollectionResult result)
|
||||||
|
{
|
||||||
|
if (result.IsSuccess && result.ParsedData != null)
|
||||||
|
{
|
||||||
|
await _dataStorageService.SaveDeviceStatusAsync(result.ParsedData);
|
||||||
|
|
||||||
|
if (await _dataParserService.ValidateDeviceDataAsync(result.ParsedData))
|
||||||
|
{
|
||||||
|
await UpdateDeviceStatusAsync(result.ParsedData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await LogCollectionAsync(result.DeviceId, LogLevel.Warning,
|
||||||
|
"Device data validation failed", JsonSerializer.Serialize(result.ParsedData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _dataStorageService.LogCollectionAsync(result.DeviceId, LogLevel.Error,
|
||||||
|
"Collection failed", result.ErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionResult>> GetCollectionHistoryAsync(int deviceId, DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
return await _resultRepository.GetResultsByDateRangeAsync(startDate, endDate)
|
||||||
|
.Where(r => r.DeviceId == deviceId).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CollectionStatistics> GetCollectionStatisticsAsync(DateTime date)
|
||||||
|
{
|
||||||
|
var startOfDay = date.Date;
|
||||||
|
var endOfDay = startOfDay.AddDays(1);
|
||||||
|
|
||||||
|
var results = await _resultRepository.GetResultsByDateRangeAsync(startOfDay, endOfDay);
|
||||||
|
|
||||||
|
var stats = new CollectionStatistics
|
||||||
|
{
|
||||||
|
Date = date,
|
||||||
|
TotalAttempts = results.Count(),
|
||||||
|
SuccessCount = results.Count(r => r.IsSuccess),
|
||||||
|
FailedCount = results.Count(r => !r.IsSuccess),
|
||||||
|
SuccessRate = results.Any() ? (decimal)results.Count(r => r.IsSuccess) / results.Count() * 100 : 0,
|
||||||
|
DeviceCount = results.Select(r => r.DeviceId).Distinct().Count(),
|
||||||
|
OnlineDeviceCount = await _deviceRepository.CountOnlineDevicesAsync(),
|
||||||
|
TotalDataSize = results.Sum(r => r.DataSize ?? 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (stats.SuccessCount > 0)
|
||||||
|
{
|
||||||
|
var successfulResults = results.Where(r => r.IsSuccess).ToList();
|
||||||
|
stats.AverageResponseTime = TimeSpan.FromTicks(
|
||||||
|
(long)successfulResults.Average(r => r.ResponseTime ?? 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CollectionHealth> GetCollectionHealthAsync()
|
||||||
|
{
|
||||||
|
var stats = await GetCollectionStatisticsAsync(DateTime.Now);
|
||||||
|
var onlineDeviceCount = await _deviceRepository.CountOnlineDevicesAsync();
|
||||||
|
var availableDeviceCount = await _deviceRepository.CountAvailableDevicesAsync();
|
||||||
|
|
||||||
|
var activeTasks = await _taskRepository.GetRunningTasksAsync();
|
||||||
|
var failedTasks = await _taskRepository.GetFailedTasksAsync();
|
||||||
|
|
||||||
|
var lastSuccessful = await _resultRepository.GetResultsByDateRangeAsync(
|
||||||
|
DateTime.Now.AddDays(-1), DateTime.Now)
|
||||||
|
.Where(r => r.IsSuccess).FirstOrDefault();
|
||||||
|
|
||||||
|
var lastFailed = await _resultRepository.GetResultsByDateRangeAsync(
|
||||||
|
DateTime.Now.AddDays(-1), DateTime.Now)
|
||||||
|
.Where(r => !r.IsSuccess).FirstOrDefault();
|
||||||
|
|
||||||
|
return new CollectionHealth
|
||||||
|
{
|
||||||
|
CheckTime = DateTime.Now,
|
||||||
|
TotalDevices = onlineDeviceCount,
|
||||||
|
OnlineDevices = availableDeviceCount,
|
||||||
|
ActiveCollectionTasks = activeTasks.Count(),
|
||||||
|
FailedTasks = failedTasks.Count(),
|
||||||
|
SuccessRate = stats.SuccessRate,
|
||||||
|
AverageResponseTime = stats.AverageResponseTime,
|
||||||
|
TotalCollectedData = stats.TotalDataSize,
|
||||||
|
LastSuccessfulCollection = lastSuccessful?.CollectionTime ?? DateTime.MinValue,
|
||||||
|
LastFailedCollection = lastFailed?.CollectionTime ?? DateTime.MinValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RestartFailedCollectionsAsync()
|
||||||
|
{
|
||||||
|
var failedTasks = await _taskRepository.GetFailedTasksAsync();
|
||||||
|
|
||||||
|
foreach (var task in failedTasks)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CollectDeviceAsync(task.DeviceId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await LogCollectionAsync(task.DeviceId, LogLevel.Error,
|
||||||
|
$"Failed to restart collection for device {task.DeviceId}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TestConnectionAsync(int deviceId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var device = await _deviceRepository.GetByIdAsync(deviceId);
|
||||||
|
if (device == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var isOnline = await _pingService.PingAsync(device.IPAddress);
|
||||||
|
if (!isOnline)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var data = await GetDeviceDataAsync(device.HttpUrl);
|
||||||
|
return !string.IsNullOrEmpty(data);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> CreateCollectionTaskAsync(CNCDevice device)
|
||||||
|
{
|
||||||
|
var task = new CollectionTask
|
||||||
|
{
|
||||||
|
DeviceId = device.Id,
|
||||||
|
TaskName = $"Collection_{device.DeviceCode}_{DateTime.Now:yyyyMMddHHmmss}",
|
||||||
|
Status = "Pending",
|
||||||
|
ScheduledTime = DateTime.Now,
|
||||||
|
CreatedAt = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
await _taskRepository.AddAsync(task);
|
||||||
|
await _taskRepository.SaveAsync();
|
||||||
|
return task.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MarkTaskCompletedAsync(int taskId, bool isSuccess, string errorMessage = null)
|
||||||
|
{
|
||||||
|
await _taskRepository.MarkTaskCompletedAsync(taskId, isSuccess, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateDeviceStatusAsync(DeviceCurrentStatus status)
|
||||||
|
{
|
||||||
|
var device = await _deviceRepository.GetByIdAsync(status.DeviceId);
|
||||||
|
if (device != null)
|
||||||
|
{
|
||||||
|
device.IsOnline = true;
|
||||||
|
device.LastCollectionTime = status.RecordTime;
|
||||||
|
await _deviceRepository.SaveAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LogCollectionAsync(int deviceId, LogLevel logLevel, string message, string data = null)
|
||||||
|
{
|
||||||
|
await _dataStorageService.LogCollectionAsync(deviceId, logLevel, message, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PingService : IPingService
|
||||||
|
{
|
||||||
|
public async Task<bool> PingAsync(string ipAddress)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var client = new System.Net.NetworkInformation.Ping())
|
||||||
|
{
|
||||||
|
var reply = await client.SendPingAsync(ipAddress, 3000);
|
||||||
|
return reply.Status == System.Net.NetworkInformation.IPStatus.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetPingTimeAsync(string ipAddress)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var client = new System.Net.NetworkInformation.Ping())
|
||||||
|
{
|
||||||
|
var reply = await client.SendPingAsync(ipAddress, 3000);
|
||||||
|
return reply.Status == System.Net.NetworkInformation.IPStatus.Success ?
|
||||||
|
reply.RoundtripTime : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsDeviceOnlineAsync(string ipAddress)
|
||||||
|
{
|
||||||
|
return await PingAsync(ipAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PingResult> GetPingResultAsync(string ipAddress)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var client = new System.Net.NetworkInformation.Ping())
|
||||||
|
{
|
||||||
|
var reply = await client.SendPingAsync(ipAddress, 3000);
|
||||||
|
return new PingResult
|
||||||
|
{
|
||||||
|
IsSuccess = reply.Status == System.Net.NetworkInformation.IPStatus.Success,
|
||||||
|
PingTime = reply.RoundtripTime,
|
||||||
|
PingTime = DateTime.Now
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new PingResult
|
||||||
|
{
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorMessage = ex.Message,
|
||||||
|
PingTime = DateTime.Now
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DataParserService : IDataParserService
|
||||||
|
{
|
||||||
|
public async Task<DeviceCurrentStatus> ParseDeviceDataAsync(string rawJson, int deviceId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var document = JsonDocument.Parse(rawJson);
|
||||||
|
var root = document.RootElement;
|
||||||
|
|
||||||
|
var device = new DeviceCurrentStatus
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
RecordTime = DateTime.Now,
|
||||||
|
Tags = new List<TagData>()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (root.TryGetProperty("device", out var deviceElement))
|
||||||
|
{
|
||||||
|
device.DeviceCode = deviceElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.TryGetProperty("desc", out var descElement))
|
||||||
|
{
|
||||||
|
device.DeviceName = descElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.TryGetProperty("tags", out var tagsElement))
|
||||||
|
{
|
||||||
|
foreach (var tagElement in tagsElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
var tag = new TagData
|
||||||
|
{
|
||||||
|
Id = tagElement.GetProperty("id").GetString(),
|
||||||
|
Desc = tagElement.GetProperty("desc").GetString(),
|
||||||
|
Quality = tagElement.GetProperty("quality").GetString(),
|
||||||
|
Time = DateTime.Parse(tagElement.GetProperty("time").GetString())
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tagElement.TryGetProperty("value", out var valueElement))
|
||||||
|
{
|
||||||
|
tag.Value = ParseTagValue(valueElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
device.Tags.Add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtractDeviceStatus(device);
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to parse device data: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractDeviceStatus(DeviceCurrentStatus device)
|
||||||
|
{
|
||||||
|
var ioStatus = device.Tags?.FirstOrDefault(t => t.Id == "_io_status");
|
||||||
|
var tag9 = device.Tags?.FirstOrDefault(t => t.Id == "Tag9");
|
||||||
|
var tag26 = device.Tags?.FirstOrDefault(t => t.Id == "Tag26");
|
||||||
|
|
||||||
|
device.Status = "Unknown";
|
||||||
|
device.IsRunning = false;
|
||||||
|
|
||||||
|
if (ioStatus?.Value?.ToString() == "1" || tag9?.Value?.ToString() == "1" || tag26?.Value?.ToString() == "1")
|
||||||
|
{
|
||||||
|
device.Status = "Running";
|
||||||
|
device.IsRunning = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
device.Status = "Stopped";
|
||||||
|
device.IsRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ncProgram = device.Tags?.FirstOrDefault(t => t.Id == "Tag5");
|
||||||
|
device.NCProgram = ncProgram?.Value?.ToString();
|
||||||
|
|
||||||
|
var cumulativeCount = device.Tags?.FirstOrDefault(t => t.Id == "Tag8");
|
||||||
|
if (cumulativeCount?.Value != null)
|
||||||
|
{
|
||||||
|
if (int.TryParse(cumulativeCount.Value.ToString(), out int count))
|
||||||
|
{
|
||||||
|
device.CumulativeCount = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var operatingMode = device.Tags?.FirstOrDefault(t => t.Id == "Tag11");
|
||||||
|
device.OperatingMode = operatingMode?.Value?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private object ParseTagValue(JsonElement valueElement)
|
||||||
|
{
|
||||||
|
if (valueElement.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
return valueElement.GetString();
|
||||||
|
}
|
||||||
|
else if (valueElement.ValueKind == JsonValueKind.Number)
|
||||||
|
{
|
||||||
|
if (valueElement.TryGetInt32(out int intValue))
|
||||||
|
return intValue;
|
||||||
|
else if (valueElement.TryGetDecimal(out decimal decimalValue))
|
||||||
|
return decimalValue;
|
||||||
|
else
|
||||||
|
return valueElement.GetDouble();
|
||||||
|
}
|
||||||
|
else if (valueElement.ValueKind == JsonValueKind.True || valueElement.ValueKind == JsonValueKind.False)
|
||||||
|
{
|
||||||
|
return valueElement.GetBoolean();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return valueElement.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TagData> ParseTagDataAsync(object tagValue, string dataType)
|
||||||
|
{
|
||||||
|
var tag = new TagData();
|
||||||
|
|
||||||
|
switch (dataType.ToLower())
|
||||||
|
{
|
||||||
|
case "int":
|
||||||
|
if (int.TryParse(tagValue?.ToString(), out int intValue))
|
||||||
|
tag.Value = intValue;
|
||||||
|
break;
|
||||||
|
case "decimal":
|
||||||
|
if (decimal.TryParse(tagValue?.ToString(), out decimal decimalValue))
|
||||||
|
tag.Value = decimalValue;
|
||||||
|
break;
|
||||||
|
case "bool":
|
||||||
|
if (bool.TryParse(tagValue?.ToString(), out bool boolValue))
|
||||||
|
tag.Value = boolValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tag.Value = tagValue?.ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateDeviceDataAsync(DeviceCurrentStatus data)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(data.DeviceCode))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (data.Tags == null || !data.Tags.Any())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (data.CumulativeCount < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ConvertDataFormatAsync(object value, string dataType)
|
||||||
|
{
|
||||||
|
switch (dataType.ToLower())
|
||||||
|
{
|
||||||
|
case "int":
|
||||||
|
if (int.TryParse(value?.ToString(), out int intValue))
|
||||||
|
return intValue.ToString();
|
||||||
|
break;
|
||||||
|
case "decimal":
|
||||||
|
if (decimal.TryParse(value?.ToString(), out decimal decimalValue))
|
||||||
|
return decimalValue.ToString("F2");
|
||||||
|
break;
|
||||||
|
case "bool":
|
||||||
|
if (bool.TryParse(value?.ToString(), out bool boolValue))
|
||||||
|
return boolValue.ToString();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return value?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DataStorageService : IDataStorageService
|
||||||
|
{
|
||||||
|
private readonly ICollectionResultRepository _resultRepository;
|
||||||
|
private readonly IDeviceStatusRepository _deviceStatusRepository;
|
||||||
|
private readonly ICollectionLogRepository _logRepository;
|
||||||
|
|
||||||
|
public DataStorageService(
|
||||||
|
ICollectionResultRepository resultRepository,
|
||||||
|
IDeviceStatusRepository deviceStatusRepository,
|
||||||
|
ICollectionLogRepository logRepository)
|
||||||
|
{
|
||||||
|
_resultRepository = resultRepository;
|
||||||
|
_deviceStatusRepository = deviceStatusRepository;
|
||||||
|
_logRepository = logRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveCollectionResultAsync(CollectionResult result)
|
||||||
|
{
|
||||||
|
await _resultRepository.AddAsync(result);
|
||||||
|
await _resultRepository.SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveDeviceStatusAsync(DeviceCurrentStatus status)
|
||||||
|
{
|
||||||
|
var deviceStatus = new DeviceStatus
|
||||||
|
{
|
||||||
|
DeviceId = status.DeviceId,
|
||||||
|
Status = status.Status,
|
||||||
|
IsRunning = status.IsRunning,
|
||||||
|
NCProgram = status.NCProgram,
|
||||||
|
CumulativeCount = status.CumulativeCount,
|
||||||
|
OperatingMode = status.OperatingMode,
|
||||||
|
RecordTime = status.RecordTime
|
||||||
|
};
|
||||||
|
|
||||||
|
await _deviceStatusRepository.AddAsync(deviceStatus);
|
||||||
|
await _deviceStatusRepository.SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveRawDataAsync(int deviceId, string rawJson, bool isSuccess, string errorMessage = null)
|
||||||
|
{
|
||||||
|
var result = new CollectionResult
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
RawJson = rawJson,
|
||||||
|
IsSuccess = isSuccess,
|
||||||
|
ErrorMessage = errorMessage,
|
||||||
|
CollectionTime = DateTime.Now,
|
||||||
|
CreatedAt = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
await _resultRepository.AddAsync(result);
|
||||||
|
await _resultRepository.SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogCollectionAsync(int deviceId, LogLevel logLevel, string message, string data = null)
|
||||||
|
{
|
||||||
|
var log = new CollectionLog
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
LogLevel = logLevel.ToString(),
|
||||||
|
LogCategory = "Collection",
|
||||||
|
LogMessage = message,
|
||||||
|
LogData = data,
|
||||||
|
LogTime = DateTime.Now,
|
||||||
|
CreatedAt = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
await _logRepository.AddAsync(log);
|
||||||
|
await _logRepository.SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ArchiveOldDataAsync(int daysToKeep = 30)
|
||||||
|
{
|
||||||
|
await _resultRepository.DeleteOldResultsAsync(daysToKeep);
|
||||||
|
await _logRepository.DeleteOldLogsAsync(daysToKeep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RetryService : IRetryService
|
||||||
|
{
|
||||||
|
public async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, int maxRetries = 3, int delayMs = 30000)
|
||||||
|
{
|
||||||
|
Exception lastException = null;
|
||||||
|
|
||||||
|
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await operation();
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (attempt < maxRetries)
|
||||||
|
{
|
||||||
|
lastException = ex;
|
||||||
|
if (ShouldRetry(ex, attempt))
|
||||||
|
{
|
||||||
|
await Task.Delay(delayMs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastException ?? new Exception("Operation failed after retries");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteWithRetryAsync(Func<Task> operation, int maxRetries = 3, int delayMs = 30000)
|
||||||
|
{
|
||||||
|
await ExecuteWithRetryAsync(async () =>
|
||||||
|
{
|
||||||
|
await operation();
|
||||||
|
return true;
|
||||||
|
}, maxRetries, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShouldRetry(Exception ex, int attemptNumber)
|
||||||
|
{
|
||||||
|
// Retry on network-related exceptions
|
||||||
|
if (ex is HttpRequestException ||
|
||||||
|
ex is System.Net.Sockets.SocketException ||
|
||||||
|
ex is System.TimeoutException)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry on certain specific exceptions
|
||||||
|
if (ex.Message.Contains("timeout") ||
|
||||||
|
ex.Message.Contains("network") ||
|
||||||
|
ex.Message.Contains("connection"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't retry after max attempts
|
||||||
|
if (attemptNumber >= 3)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
using Haoliang.Models.DataCollection;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public interface IDeviceCollectionService
|
||||||
|
{
|
||||||
|
Task CollectAllDevicesAsync();
|
||||||
|
Task CollectDeviceAsync(int deviceId);
|
||||||
|
Task<DeviceCurrentStatus> CollectDeviceDataAsync(int deviceId);
|
||||||
|
Task<bool> PingDeviceAsync(string ipAddress);
|
||||||
|
Task<string> GetDeviceDataAsync(string httpUrl);
|
||||||
|
Task ProcessCollectedDataAsync(CollectionResult result);
|
||||||
|
Task<IEnumerable<CollectionResult>> GetCollectionHistoryAsync(int deviceId, DateTime startDate, DateTime endDate);
|
||||||
|
Task<CollectionStatistics> GetCollectionStatisticsAsync(DateTime date);
|
||||||
|
Task<CollectionHealth> GetCollectionHealthAsync();
|
||||||
|
Task RestartFailedCollectionsAsync();
|
||||||
|
Task<bool> TestConnectionAsync(int deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPingService
|
||||||
|
{
|
||||||
|
Task<bool> PingAsync(string ipAddress);
|
||||||
|
Task<int> GetPingTimeAsync(string ipAddress);
|
||||||
|
Task<bool> IsDeviceOnlineAsync(string ipAddress);
|
||||||
|
Task<PingResult> GetPingResultAsync(string ipAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDataParserService
|
||||||
|
{
|
||||||
|
Task<DeviceCurrentStatus> ParseDeviceDataAsync(string rawJson, int deviceId);
|
||||||
|
Task<TagData> ParseTagDataAsync(object tagValue, string dataType);
|
||||||
|
Task<bool> ValidateDeviceDataAsync(DeviceCurrentStatus data);
|
||||||
|
Task<string> ConvertDataFormatAsync(object value, string dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDataStorageService
|
||||||
|
{
|
||||||
|
Task SaveCollectionResultAsync(CollectionResult result);
|
||||||
|
Task SaveDeviceStatusAsync(DeviceCurrentStatus status);
|
||||||
|
Task SaveRawDataAsync(int deviceId, string rawJson, bool isSuccess, string errorMessage = null);
|
||||||
|
Task LogCollectionAsync(int deviceId, LogLevel logLevel, string message, string data = null);
|
||||||
|
Task ArchiveOldDataAsync(int daysToKeep = 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PingResult
|
||||||
|
{
|
||||||
|
public bool IsSuccess { get; set; }
|
||||||
|
public int PingTime { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
public DateTime PingTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IRetryService
|
||||||
|
{
|
||||||
|
Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, int maxRetries = 3, int delayMs = 30000);
|
||||||
|
Task ExecuteWithRetryAsync(Func<Task> operation, int maxRetries = 3, int delayMs = 30000);
|
||||||
|
bool ShouldRetry(Exception ex, int attemptNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionException : Exception
|
||||||
|
{
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string DeviceCode { get; set; }
|
||||||
|
public CollectionErrorType ErrorType { get; set; }
|
||||||
|
|
||||||
|
public CollectionException(int deviceId, string deviceCode, string message, CollectionErrorType errorType)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
DeviceId = deviceId;
|
||||||
|
DeviceCode = deviceCode;
|
||||||
|
ErrorType = errorType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CollectionErrorType
|
||||||
|
{
|
||||||
|
DeviceOffline,
|
||||||
|
NetworkError,
|
||||||
|
DataParseError,
|
||||||
|
DatabaseError,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
// 中间件和过滤器
|
||||||
|
public class ExceptionMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<ExceptionMiddleware> _logger;
|
||||||
|
|
||||||
|
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "An unhandled exception occurred");
|
||||||
|
await HandleExceptionAsync(context, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
|
||||||
|
{
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
|
||||||
|
var response = new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "Internal server error",
|
||||||
|
timestamp = DateTime.Now,
|
||||||
|
error = exception.Message
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
return context.Response.WriteAsJsonAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoggingMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<LoggingMiddleware> _logger;
|
||||||
|
|
||||||
|
public LoggingMiddleware(RequestDelegate next, ILogger<LoggingMiddleware> logger)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
var startTime = DateTime.Now;
|
||||||
|
|
||||||
|
// 记录请求信息
|
||||||
|
_logger.LogInformation($"Request: {context.Request.Method} {context.Request.Path}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
var duration = DateTime.Now - startTime;
|
||||||
|
_logger.LogInformation($"Response: {context.Response.StatusCode} - Duration: {duration.TotalMilliseconds}ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CORSMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
public CORSMiddleware(RequestDelegate next)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||||
|
context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||||
|
context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
|
||||||
|
|
||||||
|
if (context.Request.Method == "OPTIONS")
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API响应格式
|
||||||
|
public class ApiResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public object Data { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public int Code { get; set; }
|
||||||
|
|
||||||
|
public static ApiResponse Success(object data = null, string message = "Success")
|
||||||
|
{
|
||||||
|
return new ApiResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Data = data,
|
||||||
|
Message = message,
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
Code = 200
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiResponse Error(string message = "Error", int code = 500, object data = null)
|
||||||
|
{
|
||||||
|
return new ApiResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Data = data,
|
||||||
|
Message = message,
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
Code = code
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiResponse NotFound(string message = "Resource not found")
|
||||||
|
{
|
||||||
|
return Error(message, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiResponse BadRequest(string message = "Bad request")
|
||||||
|
{
|
||||||
|
return Error(message, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiResponse Unauthorized(string message = "Unauthorized")
|
||||||
|
{
|
||||||
|
return Error(message, 401);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统一响应包装器
|
||||||
|
public class PagedResponse<T>
|
||||||
|
{
|
||||||
|
public IEnumerable<T> Items { get; set; }
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
public int PageNumber { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
public int TotalPages { get; set; }
|
||||||
|
public bool HasNextPage { get; set; }
|
||||||
|
public bool HasPreviousPage { get; set; }
|
||||||
|
|
||||||
|
public static PagedResponse<T> Create(IEnumerable<T> items, int totalCount, int pageNumber, int pageSize)
|
||||||
|
{
|
||||||
|
return new PagedResponse<T>
|
||||||
|
{
|
||||||
|
Items = items,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
PageNumber = pageNumber,
|
||||||
|
PageSize = pageSize,
|
||||||
|
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize),
|
||||||
|
HasNextPage = pageNumber < (int)Math.Ceiling(totalCount / (double)pageSize),
|
||||||
|
HasPreviousPage = pageNumber > 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页参数
|
||||||
|
public class PaginationParams
|
||||||
|
{
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
public string SortBy { get; set; }
|
||||||
|
public string SortOrder { get; set; } = "asc";
|
||||||
|
public string Search { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序参数
|
||||||
|
public class SortParams
|
||||||
|
{
|
||||||
|
public string Field { get; set; }
|
||||||
|
public string Direction { get; set; } = "asc";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,375 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public interface ISystemConfigService
|
||||||
|
{
|
||||||
|
Task<SystemConfig> GetConfigAsync(string configKey);
|
||||||
|
Task<Dictionary<string, string>> GetAllConfigsAsync();
|
||||||
|
Task<SystemConfig> SetConfigAsync(string configKey, string configValue);
|
||||||
|
Task<bool> DeleteConfigAsync(string configKey);
|
||||||
|
Task<bool> ConfigExistsAsync(string configKey);
|
||||||
|
Task<IEnumerable<SystemConfig>> GetConfigsByCategoryAsync(string category);
|
||||||
|
Task<bool> ValidateConfigAsync(SystemConfig config);
|
||||||
|
Task RefreshConfigCacheAsync();
|
||||||
|
Task<T> GetConfigValueAsync<T>(string configKey, T defaultValue = default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ILoggingService
|
||||||
|
{
|
||||||
|
Task LogAsync(LogLevel logLevel, string message, Exception exception = null, Dictionary<string, object> properties = null);
|
||||||
|
Task LogErrorAsync(string message, Exception exception = null, Dictionary<string, object> properties = null);
|
||||||
|
Task LogWarningAsync(string message, Dictionary<string, object> properties = null);
|
||||||
|
Task LogInfoAsync(string message, Dictionary<string, object> properties = null);
|
||||||
|
Task LogDebugAsync(string message, Dictionary<string, object> properties = null);
|
||||||
|
Task LogTraceAsync(string message, Dictionary<string, object> properties = null);
|
||||||
|
Task<IEnumerable<LogEntry>> GetLogsAsync(LogLevel? logLevel = null, DateTime? startDate = null, DateTime? endDate = null, string category = null);
|
||||||
|
Task<IEnumerable<LogEntry>> GetErrorLogsAsync(DateTime? startDate = null, DateTime? endDate = null);
|
||||||
|
Task<int> GetLogCountAsync(LogLevel? logLevel = null, DateTime? startDate = null, DateTime? endDate = null);
|
||||||
|
Task ArchiveLogsAsync(int daysToKeep = 30);
|
||||||
|
Task ClearLogsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISchedulerService
|
||||||
|
{
|
||||||
|
Task StartSchedulerAsync();
|
||||||
|
Task StopSchedulerAsync();
|
||||||
|
Task ScheduleTaskAsync(ScheduledTask task);
|
||||||
|
Task<bool> RemoveTaskAsync(string taskId);
|
||||||
|
Task<IEnumerable<ScheduledTask>> GetAllScheduledTasksAsync();
|
||||||
|
Task<ScheduledTask> GetTaskByIdAsync(string taskId);
|
||||||
|
Task ExecuteTaskAsync(string taskId);
|
||||||
|
Task<TaskExecutionResult> GetTaskExecutionResultAsync(string taskId);
|
||||||
|
Task<bool> IsTaskRunningAsync(string taskId);
|
||||||
|
Task<string> ScheduleRecurringTaskAsync(string taskName, Action taskAction, TimeSpan interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ICachingService
|
||||||
|
{
|
||||||
|
Task<T> GetAsync<T>(string key);
|
||||||
|
Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
|
||||||
|
Task<bool> RemoveAsync(string key);
|
||||||
|
Task<bool> ExistsAsync(string key);
|
||||||
|
Task ClearAsync();
|
||||||
|
Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
|
||||||
|
Task<IEnumerable<string>> GetAllKeysAsync();
|
||||||
|
Task<bool> RefreshAsync<T>(string key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemConfigManager : ISystemConfigService
|
||||||
|
{
|
||||||
|
private readonly ISystemConfigRepository _configRepository;
|
||||||
|
private readonly ICachingService _cachingService;
|
||||||
|
|
||||||
|
public SystemConfigManager(
|
||||||
|
ISystemConfigRepository configRepository,
|
||||||
|
ICachingService cachingService)
|
||||||
|
{
|
||||||
|
_configRepository = configRepository;
|
||||||
|
_cachingService = cachingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> GetConfigAsync(string configKey)
|
||||||
|
{
|
||||||
|
// 先从缓存获取
|
||||||
|
var cachedConfig = await _cachingService.GetAsync<SystemConfig>($"config_{configKey}");
|
||||||
|
if (cachedConfig != null)
|
||||||
|
{
|
||||||
|
return cachedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存未命中,从数据库获取
|
||||||
|
var config = await _configRepository.GetByKeyAsync(configKey);
|
||||||
|
if (config != null)
|
||||||
|
{
|
||||||
|
// 存入缓存
|
||||||
|
await _cachingService.SetAsync($"config_{configKey}", config, TimeSpan.FromMinutes(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Dictionary<string, string>> GetAllConfigsAsync()
|
||||||
|
{
|
||||||
|
var configs = await _configRepository.GetAllAsync();
|
||||||
|
var configDict = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var config in configs)
|
||||||
|
{
|
||||||
|
configDict[config.ConfigKey] = config.ConfigValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return configDict;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> SetConfigAsync(string configKey, string configValue)
|
||||||
|
{
|
||||||
|
// 验证配置
|
||||||
|
var config = new SystemConfig
|
||||||
|
{
|
||||||
|
ConfigKey = configKey,
|
||||||
|
ConfigValue = configValue,
|
||||||
|
Category = "General",
|
||||||
|
LastUpdated = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
var validationErrors = await ValidateConfigAsync(config);
|
||||||
|
if (validationErrors.Count > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Config validation failed: {string.Join(", ", validationErrors)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查配置是否已存在
|
||||||
|
var existingConfig = await _configRepository.GetByKeyAsync(configKey);
|
||||||
|
if (existingConfig != null)
|
||||||
|
{
|
||||||
|
config.ConfigId = existingConfig.ConfigId;
|
||||||
|
config.CreateTime = existingConfig.CreateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.LastUpdated = DateTime.Now;
|
||||||
|
var updatedConfig = await _configRepository.UpsertAsync(config);
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
await _cachingService.SetAsync($"config_{configKey}", updatedConfig, TimeSpan.FromMinutes(30));
|
||||||
|
await _cachingService.RemoveAsync("all_configs");
|
||||||
|
|
||||||
|
return updatedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteConfigAsync(string configKey)
|
||||||
|
{
|
||||||
|
var result = await _configRepository.DeleteByKeyAsync(configKey);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
// 清除缓存
|
||||||
|
await _cachingService.RemoveAsync($"config_{configKey}");
|
||||||
|
await _cachingService.RemoveAsync("all_configs");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ConfigExistsAsync(string configKey)
|
||||||
|
{
|
||||||
|
// 先检查缓存
|
||||||
|
var existsInCache = await _cachingService.ExistsAsync($"config_{configKey}");
|
||||||
|
if (existsInCache)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _configRepository.KeyExistsAsync(configKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SystemConfig>> GetConfigsByCategoryAsync(string category)
|
||||||
|
{
|
||||||
|
return await _configRepository.GetByCategoryAsync(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateConfigAsync(SystemConfig config)
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
|
// 验证配置键
|
||||||
|
if (string.IsNullOrWhiteSpace(config.ConfigKey))
|
||||||
|
{
|
||||||
|
errors.Add("Config key cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证配置值
|
||||||
|
if (config.ConfigValue == null)
|
||||||
|
{
|
||||||
|
errors.Add("Config value cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据不同的配置键进行特定验证
|
||||||
|
switch (config.ConfigKey)
|
||||||
|
{
|
||||||
|
case "Database.ConnectionString":
|
||||||
|
if (!IsValidConnectionString(config.ConfigValue))
|
||||||
|
{
|
||||||
|
errors.Add("Invalid database connection string");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Logging.Level":
|
||||||
|
if (!IsValidLogLevel(config.ConfigValue))
|
||||||
|
{
|
||||||
|
errors.Add("Invalid log level");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Collection.Interval":
|
||||||
|
if (!IsValidInterval(config.ConfigValue))
|
||||||
|
{
|
||||||
|
errors.Add("Invalid collection interval");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Security.JwtSecret":
|
||||||
|
if (string.IsNullOrWhiteSpace(config.ConfigValue) || config.ConfigValue.Length < 16)
|
||||||
|
{
|
||||||
|
errors.Add("JWT secret must be at least 16 characters long");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshConfigCacheAsync()
|
||||||
|
{
|
||||||
|
// 清除所有配置缓存
|
||||||
|
await _cachingService.RemoveAsync("all_configs");
|
||||||
|
|
||||||
|
// 重新加载常用配置
|
||||||
|
var commonKeys = new[] { "Database.ConnectionString", "Logging.Level", "Collection.Interval" };
|
||||||
|
foreach (var key in commonKeys)
|
||||||
|
{
|
||||||
|
await GetConfigAsync(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<T> GetConfigValueAsync<T>(string configKey, T defaultValue = default)
|
||||||
|
{
|
||||||
|
var config = await GetConfigAsync(configKey);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (T)Convert.ChangeType(config.ConfigValue, typeof(T));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValidConnectionString(string connectionString)
|
||||||
|
{
|
||||||
|
// 简单的连接字符串验证
|
||||||
|
return !string.IsNullOrWhiteSpace(connectionString) &&
|
||||||
|
(connectionString.Contains("Server=") || connectionString.Contains("Host="));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValidLogLevel(string logLevel)
|
||||||
|
{
|
||||||
|
var validLevels = new[] { "Trace", "Debug", "Information", "Warning", "Error", "Critical", "None" };
|
||||||
|
return Array.Exists(validLevels, level => level.Equals(logLevel, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValidInterval(string interval)
|
||||||
|
{
|
||||||
|
if (int.TryParse(interval, out int seconds))
|
||||||
|
{
|
||||||
|
return seconds >= 5 && seconds <= 3600; // 5秒到1小时
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoggingManager : ILoggingService
|
||||||
|
{
|
||||||
|
private readonly ILogRepository _logRepository;
|
||||||
|
private readonly ICachingService _cachingService;
|
||||||
|
|
||||||
|
public LoggingManager(
|
||||||
|
ILogRepository logRepository,
|
||||||
|
ICachingService cachingService)
|
||||||
|
{
|
||||||
|
_logRepository = logRepository;
|
||||||
|
_cachingService = cachingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogAsync(LogLevel logLevel, string message, Exception exception = null, Dictionary<string, object> properties = null)
|
||||||
|
{
|
||||||
|
var logEntry = new LogEntry
|
||||||
|
{
|
||||||
|
LogLevel = logLevel,
|
||||||
|
Message = message,
|
||||||
|
ExceptionMessage = exception?.Message,
|
||||||
|
StackTrace = exception?.StackTrace,
|
||||||
|
Properties = properties,
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
Category = "General"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 异步保存到数据库
|
||||||
|
await _logRepository.AddAsync(logEntry);
|
||||||
|
|
||||||
|
// 控制台输出(开发环境)
|
||||||
|
if (IsDevelopmentEnvironment())
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{logLevel}] {message}");
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogErrorAsync(string message, Exception exception = null, Dictionary<string, object> properties = null)
|
||||||
|
{
|
||||||
|
await LogAsync(LogLevel.Error, message, exception, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogWarningAsync(string message, Dictionary<string, object> properties = null)
|
||||||
|
{
|
||||||
|
await LogAsync(LogLevel.Warning, message, null, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogInfoAsync(string message, Dictionary<string, object> properties = null)
|
||||||
|
{
|
||||||
|
await LogAsync(LogLevel.Information, message, null, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogDebugAsync(string message, Dictionary<string, object> properties = null)
|
||||||
|
{
|
||||||
|
await LogAsync(LogLevel.Debug, message, null, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogTraceAsync(string message, Dictionary<string, object> properties = null)
|
||||||
|
{
|
||||||
|
await LogAsync(LogLevel.Trace, message, null, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<LogEntry>> GetLogsAsync(LogLevel? logLevel = null, DateTime? startDate = null, DateTime? endDate = null, string category = null)
|
||||||
|
{
|
||||||
|
return await _logRepository.GetLogsAsync(logLevel, startDate, endDate, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<LogEntry>> GetErrorLogsAsync(DateTime? startDate = null, DateTime? endDate = null)
|
||||||
|
{
|
||||||
|
return await _logRepository.GetLogsAsync(LogLevel.Error, startDate, endDate, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetLogCountAsync(LogLevel? logLevel = null, DateTime? startDate = null, DateTime? endDate = null)
|
||||||
|
{
|
||||||
|
return await _logRepository.GetLogCountAsync(logLevel, startDate, endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ArchiveLogsAsync(int daysToKeep = 30)
|
||||||
|
{
|
||||||
|
var cutoffDate = DateTime.Now.AddDays(-daysToKeep);
|
||||||
|
await _logRepository.ArchiveLogsAsync(cutoffDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClearLogsAsync()
|
||||||
|
{
|
||||||
|
await _logRepository.ClearLogsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDevelopmentEnvironment()
|
||||||
|
{
|
||||||
|
// 简单判断是否为开发环境
|
||||||
|
return Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,272 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.Template;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
using Haoliang.Models.DataCollection;
|
||||||
|
|
||||||
|
namespace Haoliang.Core.Services
|
||||||
|
{
|
||||||
|
public interface ITemplateService
|
||||||
|
{
|
||||||
|
Task<CNCBrandTemplate> CreateTemplateAsync(CNCBrandTemplate template);
|
||||||
|
Task<CNCBrandTemplate> UpdateTemplateAsync(int templateId, CNCBrandTemplate template);
|
||||||
|
Task<bool> DeleteTemplateAsync(int templateId);
|
||||||
|
Task<CNCBrandTemplate> GetTemplateByIdAsync(int templateId);
|
||||||
|
Task<IEnumerable<CNCBrandTemplate>> GetAllTemplatesAsync();
|
||||||
|
Task<IEnumerable<CNCBrandTemplate>> GetTemplatesByBrandAsync(string brandName);
|
||||||
|
Task<IEnumerable<CNCBrandTemplate>> GetActiveTemplatesAsync();
|
||||||
|
Task<bool> ValidateTemplateAsync(CNCBrandTemplate template);
|
||||||
|
Task TestTemplateAsync(int templateId);
|
||||||
|
Task<CNCBrandTemplate> CloneTemplateAsync(int templateId, string newName);
|
||||||
|
Task<bool> EnableTemplateAsync(int templateId);
|
||||||
|
Task<bool> DisableTemplateAsync(int templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITagMappingService
|
||||||
|
{
|
||||||
|
Task<TagMapping> CreateTagMappingAsync(TagMapping mapping);
|
||||||
|
Task<TagMapping> UpdateTagMappingAsync(int mappingId, TagMapping mapping);
|
||||||
|
Task<bool> DeleteTagMappingAsync(int mappingId);
|
||||||
|
Task<TagMapping> GetTagMappingByIdAsync(int mappingId);
|
||||||
|
Task<IEnumerable<TagMapping>> GetAllTagMappingsAsync();
|
||||||
|
Task<IEnumerable<TagMapping>> GetMappingsByTemplateAsync(int templateId);
|
||||||
|
Task<TagMapping> MapDeviceTagAsync(TagData deviceTag, int templateId);
|
||||||
|
Task<Dictionary<string, TagData>> MapDeviceTagsAsync(IEnumerable<TagData> deviceTags, int templateId);
|
||||||
|
Task ValidateTagMappingAsync(TagMapping mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITemplateValidationService
|
||||||
|
{
|
||||||
|
Task<bool> ValidateTemplateStructureAsync(CNCBrandTemplate template);
|
||||||
|
Task<bool> ValidateTagMappingsAsync(CNCBrandTemplate template);
|
||||||
|
Task<bool> ValidateDataParsingRulesAsync(CNCBrandTemplate template);
|
||||||
|
Task<IEnumerable<ValidationError>> ValidateTemplateForDeviceAsync(int templateId, int deviceId);
|
||||||
|
Task<bool> TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData);
|
||||||
|
Task<IEnumerable<string>> GetMissingRequiredTagsAsync(CNCBrandTemplate template);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITemplateMigrationService
|
||||||
|
{
|
||||||
|
Task<CNCBrandTemplate> MigrateTemplateAsync(CNCBrandTemplate oldTemplate, string targetBrand);
|
||||||
|
Task<bool> ValidateMigrationCompatibilityAsync(CNCBrandTemplate sourceTemplate, string targetBrand);
|
||||||
|
Task<MigrationReport> GenerateMigrationReportAsync(CNCBrandTemplate template, string targetBrand);
|
||||||
|
Task<IEnumerable<MigrationIssue>> DetectMigrationIssuesAsync(CNCBrandTemplate template, string targetBrand);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TemplateManager : ITemplateService
|
||||||
|
{
|
||||||
|
private readonly ITemplateRepository _templateRepository;
|
||||||
|
private readonly ITagMappingService _tagMappingService;
|
||||||
|
private readonly ITemplateValidationService _validationService;
|
||||||
|
private readonly ITemplateMigrationService _migrationService;
|
||||||
|
|
||||||
|
public TemplateManager(
|
||||||
|
ITemplateRepository templateRepository,
|
||||||
|
ITagMappingService tagMappingService,
|
||||||
|
ITemplateValidationService validationService,
|
||||||
|
ITemplateMigrationService migrationService)
|
||||||
|
{
|
||||||
|
_templateRepository = templateRepository;
|
||||||
|
_tagMappingService = tagMappingService;
|
||||||
|
_validationService = validationService;
|
||||||
|
_migrationService = migrationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CNCBrandTemplate> CreateTemplateAsync(CNCBrandTemplate template)
|
||||||
|
{
|
||||||
|
// 验证模板
|
||||||
|
var validationErrors = await _validationService.ValidateTemplateStructureAsync(template);
|
||||||
|
if (validationErrors.Count > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Template validation failed: {string.Join(", ", validationErrors)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置初始状态
|
||||||
|
template.IsEnabled = true;
|
||||||
|
template.CreateTime = DateTime.Now;
|
||||||
|
template.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
var createdTemplate = await _templateRepository.AddAsync(template);
|
||||||
|
|
||||||
|
// 验证标签映射
|
||||||
|
await _validationService.ValidateTagMappingsAsync(createdTemplate);
|
||||||
|
|
||||||
|
return createdTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CNCBrandTemplate> UpdateTemplateAsync(int templateId, CNCBrandTemplate template)
|
||||||
|
{
|
||||||
|
var existingTemplate = await _templateRepository.GetByIdAsync(templateId);
|
||||||
|
if (existingTemplate == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"Template with ID {templateId} not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证更新后的模板
|
||||||
|
var validationErrors = await _validationService.ValidateTemplateStructureAsync(template);
|
||||||
|
if (validationErrors.Count > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Template validation failed: {string.Join(", ", validationErrors)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
template.TemplateId = templateId;
|
||||||
|
template.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
var updatedTemplate = await _templateRepository.UpdateAsync(template);
|
||||||
|
|
||||||
|
// 如果标签有变化,重新验证
|
||||||
|
if (HasTagMappingsChanged(existingTemplate, template))
|
||||||
|
{
|
||||||
|
await _validationService.ValidateTagMappingsAsync(updatedTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteTemplateAsync(int templateId)
|
||||||
|
{
|
||||||
|
// 检查模板是否被使用
|
||||||
|
var isTemplateInUse = await _templateRepository.IsTemplateInUseAsync(templateId);
|
||||||
|
if (isTemplateInUse)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot delete template that is currently in use by devices");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _templateRepository.DeleteAsync(templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CNCBrandTemplate> GetTemplateByIdAsync(int templateId)
|
||||||
|
{
|
||||||
|
return await _templateRepository.GetByIdAsync(templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CNCBrandTemplate>> GetAllTemplatesAsync()
|
||||||
|
{
|
||||||
|
return await _templateRepository.GetAllAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CNCBrandTemplate>> GetTemplatesByBrandAsync(string brandName)
|
||||||
|
{
|
||||||
|
return await _templateRepository.GetByBrandAsync(brandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CNCBrandTemplate>> GetActiveTemplatesAsync()
|
||||||
|
{
|
||||||
|
return await _templateRepository.GetActiveTemplatesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidateTemplateAsync(CNCBrandTemplate template)
|
||||||
|
{
|
||||||
|
return await _validationService.ValidateTemplateStructureAsync(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TestTemplateAsync(int templateId)
|
||||||
|
{
|
||||||
|
var template = await _templateRepository.GetByIdAsync(templateId);
|
||||||
|
if (template == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"Template with ID {templateId} not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用模板进行数据解析测试
|
||||||
|
await _validationService.TestTemplateDataParsingAsync(template, GetSampleData(template));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CNCBrandTemplate> CloneTemplateAsync(int templateId, string newName)
|
||||||
|
{
|
||||||
|
var originalTemplate = await _templateRepository.GetByIdAsync(templateId);
|
||||||
|
if (originalTemplate == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"Template with ID {templateId} not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var clonedTemplate = new CNCBrandTemplate
|
||||||
|
{
|
||||||
|
TemplateName = newName,
|
||||||
|
BrandName = originalTemplate.BrandName,
|
||||||
|
Description = $"Cloned from {originalTemplate.TemplateName}",
|
||||||
|
IsEnabled = false, // 新克隆的模板默认禁用
|
||||||
|
Version = originalTemplate.Version,
|
||||||
|
TemplateJson = originalTemplate.TemplateJson,
|
||||||
|
CreateTime = DateTime.Now,
|
||||||
|
UpdateTime = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
return await CreateTemplateAsync(clonedTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> EnableTemplateAsync(int templateId)
|
||||||
|
{
|
||||||
|
var template = await _templateRepository.GetByIdAsync(templateId);
|
||||||
|
if (template == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template.IsEnabled = true;
|
||||||
|
template.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
return await _templateRepository.UpdateAsync(template) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DisableTemplateAsync(int templateId)
|
||||||
|
{
|
||||||
|
var template = await _templateRepository.GetByIdAsync(templateId);
|
||||||
|
if (template == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否还有设备在使用此模板
|
||||||
|
var isTemplateInUse = await _templateRepository.IsTemplateInUseAsync(templateId);
|
||||||
|
if (isTemplateInUse)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot disable template that is currently in use by devices");
|
||||||
|
}
|
||||||
|
|
||||||
|
template.IsEnabled = false;
|
||||||
|
template.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
return await _templateRepository.UpdateAsync(template) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasTagMappingsChanged(CNCBrandTemplate oldTemplate, CNCBrandTemplate newTemplate)
|
||||||
|
{
|
||||||
|
// 简单比较JSON内容是否变化
|
||||||
|
return oldTemplate.TemplateJson != newTemplate.TemplateJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSampleData(CNCBrandTemplate template)
|
||||||
|
{
|
||||||
|
// 返回模拟的设备数据用于测试
|
||||||
|
return @"{
|
||||||
|
""device"": ""FANUC_01"",
|
||||||
|
""desc"": ""CNC Machine"",
|
||||||
|
""tags"": [
|
||||||
|
{
|
||||||
|
""id"": ""_io_status"",
|
||||||
|
""desc"": ""I/O Status"",
|
||||||
|
""quality"": 0,
|
||||||
|
""value"": 1,
|
||||||
|
""time"": ""2024-01-01T10:00:00""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""id"": ""Tag5"",
|
||||||
|
""desc"": ""NC Program"",
|
||||||
|
""quality"": 0,
|
||||||
|
""value"": ""O1234"",
|
||||||
|
""time"": ""2024-01-01T10:00:00""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""id"": ""Tag8"",
|
||||||
|
""desc"": ""Cumulative Count"",
|
||||||
|
""quality"": 0,
|
||||||
|
""value"": 12345.00000,
|
||||||
|
""time"": ""2024-01-01T10:00:00""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,166 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Haoliang.Data.Repositories
|
||||||
|
{
|
||||||
|
public class Repository<T> : IRepository<T> where T : class
|
||||||
|
{
|
||||||
|
protected readonly DbContext _context;
|
||||||
|
protected readonly DbSet<T> _dbSet;
|
||||||
|
|
||||||
|
public Repository(DbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_dbSet = context.Set<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<T> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
return await _dbSet.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IEnumerable<T>> GetAllAsync()
|
||||||
|
{
|
||||||
|
return await _dbSet.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IEnumerable<T>> FindAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return await _dbSet.Where(predicate).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<T> SingleOrDefaultAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return await _dbSet.SingleOrDefaultAsync(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task AddAsync(T entity)
|
||||||
|
{
|
||||||
|
await _dbSet.AddAsync(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task AddRangeAsync(IEnumerable<T> entities)
|
||||||
|
{
|
||||||
|
await _dbSet.AddRangeAsync(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update(T entity)
|
||||||
|
{
|
||||||
|
_dbSet.Attach(entity);
|
||||||
|
_context.Entry(entity).State = EntityState.Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Remove(T entity)
|
||||||
|
{
|
||||||
|
_dbSet.Remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RemoveRange(IEnumerable<T> entities)
|
||||||
|
{
|
||||||
|
_dbSet.RemoveRange(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<int> CountAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate = null)
|
||||||
|
{
|
||||||
|
return predicate == null ? await _dbSet.CountAsync() : await _dbSet.CountAsync(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<bool> ExistsAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return await _dbSet.AnyAsync(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<int> SaveAsync()
|
||||||
|
{
|
||||||
|
return await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReadRepository<T> : IReadRepository<T> where T : class
|
||||||
|
{
|
||||||
|
protected readonly DbContext _context;
|
||||||
|
protected readonly DbSet<T> _dbSet;
|
||||||
|
|
||||||
|
public ReadRepository(DbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_dbSet = context.Set<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<T> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
return await _dbSet.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IEnumerable<T>> GetAllAsync()
|
||||||
|
{
|
||||||
|
return await _dbSet.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<IEnumerable<T>> FindAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return await _dbSet.Where(predicate).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<T> SingleOrDefaultAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return await _dbSet.SingleOrDefaultAsync(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<int> CountAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate = null)
|
||||||
|
{
|
||||||
|
return predicate == null ? await _dbSet.CountAsync() : await _dbSet.CountAsync(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<bool> ExistsAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return await _dbSet.AnyAsync(predicate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WriteRepository<T> : IWriteRepository<T> where T : class
|
||||||
|
{
|
||||||
|
protected readonly DbContext _context;
|
||||||
|
protected readonly DbSet<T> _dbSet;
|
||||||
|
|
||||||
|
public WriteRepository(DbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_dbSet = context.Set<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task AddAsync(T entity)
|
||||||
|
{
|
||||||
|
await _dbSet.AddAsync(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task AddRangeAsync(IEnumerable<T> entities)
|
||||||
|
{
|
||||||
|
await _dbSet.AddRangeAsync(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update(T entity)
|
||||||
|
{
|
||||||
|
_dbSet.Attach(entity);
|
||||||
|
_context.Entry(entity).State = EntityState.Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Remove(T entity)
|
||||||
|
{
|
||||||
|
_dbSet.Remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RemoveRange(IEnumerable<T> entities)
|
||||||
|
{
|
||||||
|
_dbSet.RemoveRange(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<int> SaveAsync()
|
||||||
|
{
|
||||||
|
return await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,407 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.DataCollection;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
|
||||||
|
namespace Haoliang.Data.Repositories
|
||||||
|
{
|
||||||
|
public interface ICollectionTaskRepository : IRepository<CollectionTask>
|
||||||
|
{
|
||||||
|
Task<IEnumerable<CollectionTask>> GetPendingTasksAsync();
|
||||||
|
Task<IEnumerable<CollectionTask>> GetRunningTasksAsync();
|
||||||
|
Task<IEnumerable<CollectionTask>> GetTasksByDeviceIdAsync(int deviceId);
|
||||||
|
Task<IEnumerable<CollectionTask>> GetFailedTasksAsync(int retryCount = 3);
|
||||||
|
Task<CollectionTask> GetNextPendingTaskAsync();
|
||||||
|
Task<bool> MarkTaskCompletedAsync(int taskId, bool isSuccess, string errorMessage = null);
|
||||||
|
Task<bool> MarkTaskRunningAsync(int taskId);
|
||||||
|
Task<int> CountPendingTasksAsync();
|
||||||
|
Task<int> CountFailedTasksAsync();
|
||||||
|
Task<decimal> GetTaskSuccessRateAsync(int hours = 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionTaskRepository : Repository<CollectionTask>, ICollectionTaskRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public CollectionTaskRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionTask>> GetPendingTasksAsync()
|
||||||
|
{
|
||||||
|
return await _context.CollectionTasks
|
||||||
|
.Where(ct => ct.Status == "Pending")
|
||||||
|
.OrderBy(ct => ct.ScheduledTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionTask>> GetRunningTasksAsync()
|
||||||
|
{
|
||||||
|
return await _context.CollectionTasks
|
||||||
|
.Where(ct => ct.Status == "Running")
|
||||||
|
.OrderBy(ct => ct.StartTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionTask>> GetTasksByDeviceIdAsync(int deviceId)
|
||||||
|
{
|
||||||
|
return await _context.CollectionTasks
|
||||||
|
.Where(ct => ct.DeviceId == deviceId)
|
||||||
|
.OrderByDescending(ct => ct.ScheduledTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionTask>> GetFailedTasksAsync(int retryCount = 3)
|
||||||
|
{
|
||||||
|
return await _context.CollectionTasks
|
||||||
|
.Where(ct => ct.Status == "Failed" && ct.RetryCount >= retryCount)
|
||||||
|
.OrderByDescending(ct => ct.ScheduledTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CollectionTask> GetNextPendingTaskAsync()
|
||||||
|
{
|
||||||
|
return await _context.CollectionTasks
|
||||||
|
.Where(ct => ct.Status == "Pending" && ct.ScheduledTime <= DateTime.Now)
|
||||||
|
.OrderBy(ct => ct.ScheduledTime)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> MarkTaskCompletedAsync(int taskId, bool isSuccess, string errorMessage = null)
|
||||||
|
{
|
||||||
|
var task = await GetByIdAsync(taskId);
|
||||||
|
if (task != null)
|
||||||
|
{
|
||||||
|
task.Status = isSuccess ? "Completed" : "Failed";
|
||||||
|
task.EndTime = DateTime.Now;
|
||||||
|
task.IsSuccess = isSuccess;
|
||||||
|
|
||||||
|
if (!isSuccess)
|
||||||
|
{
|
||||||
|
task.ErrorMessage = errorMessage;
|
||||||
|
task.RetryCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Update(task);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> MarkTaskRunningAsync(int taskId)
|
||||||
|
{
|
||||||
|
var task = await GetByIdAsync(taskId);
|
||||||
|
if (task != null)
|
||||||
|
{
|
||||||
|
task.Status = "Running";
|
||||||
|
task.StartTime = DateTime.Now;
|
||||||
|
|
||||||
|
Update(task);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountPendingTasksAsync()
|
||||||
|
{
|
||||||
|
return await _context.CollectionTasks
|
||||||
|
.CountAsync(ct => ct.Status == "Pending");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountFailedTasksAsync()
|
||||||
|
{
|
||||||
|
return await _context.CollectionTasks
|
||||||
|
.CountAsync(ct => ct.Status == "Failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetTaskSuccessRateAsync(int hours = 24)
|
||||||
|
{
|
||||||
|
var startTime = DateTime.Now.AddHours(-hours);
|
||||||
|
var tasks = await _context.CollectionTasks
|
||||||
|
.Where(ct => ct.ScheduledTime >= startTime)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (tasks.Any())
|
||||||
|
{
|
||||||
|
var successCount = tasks.Count(ct => ct.IsSuccess);
|
||||||
|
return (decimal)successCount / tasks.Count * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ICollectionResultRepository : IRepository<CollectionResult>
|
||||||
|
{
|
||||||
|
Task<IEnumerable<CollectionResult>> GetResultsByDeviceIdAsync(int deviceId);
|
||||||
|
Task<IEnumerable<CollectionResult>> GetResultsByDateRangeAsync(DateTime startDate, DateTime endDate);
|
||||||
|
Task<IEnumerable<CollectionResult>> GetFailedResultsAsync(int deviceId = 0);
|
||||||
|
Task<CollectionResult> GetLatestResultAsync(int deviceId);
|
||||||
|
Task<int> CountResultsAsync(int deviceId = 0);
|
||||||
|
Task<int> CountFailedResultsAsync(int deviceId = 0);
|
||||||
|
Task<decimal> GetAverageResponseTimeAsync(int deviceId = 0);
|
||||||
|
Task<long> GetTotalDataSizeAsync(int deviceId = 0);
|
||||||
|
Task<bool> DeleteOldResultsAsync(int keepDays = 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionResultRepository : Repository<CollectionResult>, ICollectionResultRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public CollectionResultRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionResult>> GetResultsByDeviceIdAsync(int deviceId)
|
||||||
|
{
|
||||||
|
return await _context.CollectionResults
|
||||||
|
.Where(cr => cr.DeviceId == deviceId)
|
||||||
|
.OrderByDescending(cr => cr.CollectionTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionResult>> GetResultsByDateRangeAsync(DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
return await _context.CollectionResults
|
||||||
|
.Where(cr => cr.CollectionTime >= startDate && cr.CollectionTime <= endDate)
|
||||||
|
.OrderByDescending(cr => cr.CollectionTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionResult>> GetFailedResultsAsync(int deviceId = 0)
|
||||||
|
{
|
||||||
|
var query = _context.CollectionResults.Where(cr => !cr.IsSuccess);
|
||||||
|
|
||||||
|
if (deviceId > 0)
|
||||||
|
{
|
||||||
|
query = query.Where(cr => cr.DeviceId == deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query
|
||||||
|
.OrderByDescending(cr => cr.CollectionTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CollectionResult> GetLatestResultAsync(int deviceId)
|
||||||
|
{
|
||||||
|
return await _context.CollectionResults
|
||||||
|
.Where(cr => cr.DeviceId == deviceId)
|
||||||
|
.OrderByDescending(cr => cr.CollectionTime)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountResultsAsync(int deviceId = 0)
|
||||||
|
{
|
||||||
|
var query = _context.CollectionResults.AsQueryable();
|
||||||
|
|
||||||
|
if (deviceId > 0)
|
||||||
|
{
|
||||||
|
query = query.Where(cr => cr.DeviceId == deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.CountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountFailedResultsAsync(int deviceId = 0)
|
||||||
|
{
|
||||||
|
var query = _context.CollectionResults.Where(cr => !cr.IsSuccess);
|
||||||
|
|
||||||
|
if (deviceId > 0)
|
||||||
|
{
|
||||||
|
query = query.Where(cr => cr.DeviceId == deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.CountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetAverageResponseTimeAsync(int deviceId = 0)
|
||||||
|
{
|
||||||
|
var query = _context.CollectionResults.Where(cr => cr.IsSuccess);
|
||||||
|
|
||||||
|
if (deviceId > 0)
|
||||||
|
{
|
||||||
|
query = query.Where(cr => cr.DeviceId == deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = await query.ToListAsync();
|
||||||
|
|
||||||
|
if (results.Any())
|
||||||
|
{
|
||||||
|
return (decimal)results.Average(cr => cr.ResponseTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetTotalDataSizeAsync(int deviceId = 0)
|
||||||
|
{
|
||||||
|
var query = _context.CollectionResults.Where(cr => cr.IsSuccess);
|
||||||
|
|
||||||
|
if (deviceId > 0)
|
||||||
|
{
|
||||||
|
query = query.Where(cr => cr.DeviceId == deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = await query.ToListAsync();
|
||||||
|
|
||||||
|
return results.Sum(cr => cr.DataSize ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteOldResultsAsync(int keepDays = 30)
|
||||||
|
{
|
||||||
|
var cutoffDate = DateTime.Now.AddDays(-keepDays);
|
||||||
|
var oldResults = await _context.CollectionResults
|
||||||
|
.Where(cr => cr.CollectionTime < cutoffDate)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (oldResults.Any())
|
||||||
|
{
|
||||||
|
RemoveRange(oldResults);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ICollectionLogRepository : IRepository<CollectionLog>
|
||||||
|
{
|
||||||
|
Task<IEnumerable<CollectionLog>> GetLogsByLevelAsync(LogLevel logLevel);
|
||||||
|
Task<IEnumerable<CollectionLog>> GetLogsByDeviceIdAsync(int deviceId);
|
||||||
|
Task<IEnumerable<CollectionLog>> GetLogsByDateRangeAsync(DateTime startDate, DateTime endDate);
|
||||||
|
Task<IEnumerable<CollectionLog>> GetErrorLogsAsync();
|
||||||
|
Task<int> CountLogsByLevelAsync(LogLevel logLevel);
|
||||||
|
Task<bool> DeleteOldLogsAsync(int keepDays = 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionLogRepository : Repository<CollectionLog>, ICollectionLogRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public CollectionLogRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionLog>> GetLogsByLevelAsync(LogLevel logLevel)
|
||||||
|
{
|
||||||
|
return await _context.CollectionLogs
|
||||||
|
.Where(cl => cl.LogLevel == logLevel.ToString())
|
||||||
|
.OrderByDescending(cl => cl.LogTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionLog>> GetLogsByDeviceIdAsync(int deviceId)
|
||||||
|
{
|
||||||
|
return await _context.CollectionLogs
|
||||||
|
.Where(cl => cl.DeviceId == deviceId)
|
||||||
|
.OrderByDescending(cl => cl.LogTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionLog>> GetLogsByDateRangeAsync(DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
return await _context.CollectionLogs
|
||||||
|
.Where(cl => cl.LogTime >= startDate && cl.LogTime <= endDate)
|
||||||
|
.OrderByDescending(cl => cl.LogTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionLog>> GetErrorLogsAsync()
|
||||||
|
{
|
||||||
|
return await _context.CollectionLogs
|
||||||
|
.Where(cl => cl.LogLevel == LogLevel.Error.ToString() || cl.LogLevel == LogLevel.Critical.ToString())
|
||||||
|
.OrderByDescending(cl => cl.LogTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountLogsByLevelAsync(LogLevel logLevel)
|
||||||
|
{
|
||||||
|
return await _context.CollectionLogs
|
||||||
|
.CountAsync(cl => cl.LogLevel == logLevel.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteOldLogsAsync(int keepDays = 90)
|
||||||
|
{
|
||||||
|
var cutoffDate = DateTime.Now.AddDays(-keepDays);
|
||||||
|
var oldLogs = await _context.CollectionLogs
|
||||||
|
.Where(cl => cl.LogTime < cutoffDate)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (oldLogs.Any())
|
||||||
|
{
|
||||||
|
RemoveRange(oldLogs);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ICollectionConfigRepository : IRepository<CollectionConfig>
|
||||||
|
{
|
||||||
|
Task<CollectionConfig> GetByKeyAsync(string configKey);
|
||||||
|
Task<string> GetConfigValueAsync(string configKey);
|
||||||
|
Task<bool> UpdateConfigValueAsync(string configKey, string configValue);
|
||||||
|
Task<IEnumerable<CollectionConfig>> GetEnabledConfigsAsync();
|
||||||
|
Task<bool> ConfigKeyExistsAsync(string configKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionConfigRepository : Repository<CollectionConfig>, ICollectionConfigRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public CollectionConfigRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CollectionConfig> GetByKeyAsync(string configKey)
|
||||||
|
{
|
||||||
|
return await _context.CollectionConfigs
|
||||||
|
.FirstOrDefaultAsync(cc => cc.ConfigKey == configKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetConfigValueAsync(string configKey)
|
||||||
|
{
|
||||||
|
var config = await GetByKeyAsync(configKey);
|
||||||
|
return config?.ConfigValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateConfigValueAsync(string configKey, string configValue)
|
||||||
|
{
|
||||||
|
var config = await GetByKeyAsync(configKey);
|
||||||
|
if (config != null)
|
||||||
|
{
|
||||||
|
config.ConfigValue = configValue;
|
||||||
|
config.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(config);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CollectionConfig>> GetEnabledConfigsAsync()
|
||||||
|
{
|
||||||
|
return await _context.CollectionConfigs
|
||||||
|
.Where(cc => cc.IsEnabled)
|
||||||
|
.OrderBy(cc => cc.ConfigKey)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ConfigKeyExistsAsync(string configKey)
|
||||||
|
{
|
||||||
|
return await _context.CollectionConfigs
|
||||||
|
.AnyAsync(cc => cc.ConfigKey == configKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,192 +1,164 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Haoliang.Data.Entities;
|
using System.Threading.Tasks;
|
||||||
using Haoliang.Models.Device;
|
using Haoliang.Models.Device;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
|
||||||
namespace Haoliang.Data.Repositories
|
namespace Haoliang.Data.Repositories
|
||||||
{
|
{
|
||||||
public class DeviceRepository
|
public interface IDeviceRepository : IRepository<CNCDevice>
|
||||||
{
|
{
|
||||||
private readonly CNCBusinessDbContext _context;
|
Task<IEnumerable<CNCDevice>> GetOnlineDevicesAsync();
|
||||||
|
Task<IEnumerable<CNCDevice>> GetAvailableDevicesAsync();
|
||||||
public DeviceRepository(CNCBusinessDbContext context)
|
Task<CNCDevice> GetByDeviceCodeAsync(string deviceCode);
|
||||||
{
|
Task<IEnumerable<CNCDevice>> GetByTemplateIdAsync(int templateId);
|
||||||
_context = context;
|
Task<bool> DeviceExistsAsync(string deviceCode);
|
||||||
|
Task UpdateDeviceStatusAsync(int deviceId, bool isOnline, bool isAvailable);
|
||||||
|
Task<int> CountOnlineDevicesAsync();
|
||||||
|
Task<int> CountAvailableDevicesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CNCDevice> GetAllDevices()
|
public class DeviceRepository : Repository<CNCDevice>, IDeviceRepository
|
||||||
{
|
{
|
||||||
return _context.Devices.ToList();
|
private readonly CNCDbContext _context;
|
||||||
}
|
|
||||||
|
|
||||||
public CNCDevice GetDeviceById(int id)
|
public DeviceRepository(CNCDbContext context) : base(context)
|
||||||
{
|
{
|
||||||
return _context.Devices.Find(id);
|
_context = context;
|
||||||
}
|
|
||||||
|
|
||||||
public CNCDevice GetDeviceByCode(string deviceCode)
|
|
||||||
{
|
|
||||||
return _context.Devices.FirstOrDefault(d => d.DeviceCode == deviceCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CNCDevice CreateDevice(CNCDevice device)
|
public async Task<IEnumerable<CNCDevice>> GetOnlineDevicesAsync()
|
||||||
{
|
{
|
||||||
_context.Devices.Add(device);
|
return await _context.Devices
|
||||||
_context.SaveChanges();
|
.Where(d => d.IsOnline)
|
||||||
return device;
|
.OrderBy(d => d.DeviceName)
|
||||||
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CNCDevice UpdateDevice(CNCDevice device)
|
public async Task<IEnumerable<CNCDevice>> GetAvailableDevicesAsync()
|
||||||
{
|
{
|
||||||
_context.Entry(device).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
return await _context.Devices
|
||||||
_context.SaveChanges();
|
.Where(d => d.IsAvailable)
|
||||||
return device;
|
.OrderBy(d => d.DeviceName)
|
||||||
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteDevice(int id)
|
public async Task<CNCDevice> GetByDeviceCodeAsync(string deviceCode)
|
||||||
{
|
{
|
||||||
var device = _context.Devices.Find(id);
|
return await _context.Devices
|
||||||
if (device != null)
|
.FirstOrDefaultAsync(d => d.DeviceCode == deviceCode);
|
||||||
{
|
|
||||||
_context.Devices.Remove(device);
|
|
||||||
_context.SaveChanges();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CNCDevice> GetOnlineDevices()
|
public async Task<IEnumerable<CNCDevice>> GetByTemplateIdAsync(int templateId)
|
||||||
{
|
{
|
||||||
return _context.Devices.Where(d => d.IsOnline).ToList();
|
return await _context.Devices
|
||||||
|
.Where(d => d.TemplateId == templateId)
|
||||||
|
.OrderBy(d => d.DeviceName)
|
||||||
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CNCDevice> GetAvailableDevices()
|
public async Task<bool> DeviceExistsAsync(string deviceCode)
|
||||||
{
|
{
|
||||||
return _context.Devices.Where(d => d.IsAvailable).ToList();
|
return await _context.Devices
|
||||||
|
.AnyAsync(d => d.DeviceCode == deviceCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateDeviceStatus(int deviceId, bool isOnline)
|
public async Task UpdateDeviceStatusAsync(int deviceId, bool isOnline, bool isAvailable)
|
||||||
{
|
{
|
||||||
var device = _context.Devices.Find(deviceId);
|
var device = await GetByIdAsync(deviceId);
|
||||||
if (device != null)
|
if (device != null)
|
||||||
{
|
{
|
||||||
device.IsOnline = isOnline;
|
device.IsOnline = isOnline;
|
||||||
device.UpdatedAt = DateTime.Now;
|
device.IsAvailable = isAvailable;
|
||||||
_context.SaveChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateLastCollectionTime(int deviceId, DateTime collectionTime)
|
if (isOnline)
|
||||||
{
|
{
|
||||||
var device = _context.Devices.Find(deviceId);
|
device.LastCollectionTime = DateTime.Now;
|
||||||
if (device != null)
|
|
||||||
{
|
|
||||||
device.LastCollectionTime = collectionTime;
|
|
||||||
device.UpdatedAt = DateTime.Now;
|
|
||||||
_context.SaveChanges();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Update(device);
|
||||||
|
await SaveAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeviceStatusRepository
|
public async Task<int> CountOnlineDevicesAsync()
|
||||||
{
|
{
|
||||||
private readonly CNCBusinessDbContext _context;
|
return await _context.Devices
|
||||||
|
.CountAsync(d => d.IsOnline);
|
||||||
public DeviceStatusRepository(CNCBusinessDbContext context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<DeviceStatus> GetDeviceStatuses(int deviceId, DateTime? startTime = null, DateTime? endTime = null)
|
public async Task<int> CountAvailableDevicesAsync()
|
||||||
{
|
{
|
||||||
var query = _context.DeviceStatus.Where(ds => ds.DeviceId == deviceId);
|
return await _context.Devices
|
||||||
|
.CountAsync(d => d.IsAvailable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (startTime.HasValue)
|
public interface IDeviceStatusRepository : IRepository<DeviceStatus>
|
||||||
{
|
{
|
||||||
query = query.Where(ds => ds.RecordTime >= startTime.Value);
|
Task<IEnumerable<DeviceStatus>> GetLatestStatusAsync(int deviceId, int count = 10);
|
||||||
|
Task<DeviceStatus> GetLatestStatusByDeviceIdAsync(int deviceId);
|
||||||
|
Task<IEnumerable<DeviceStatus>> GetStatusByDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate);
|
||||||
|
Task<int> CountStatusRecordsAsync(int deviceId);
|
||||||
|
Task DeleteOldStatusRecordsAsync(int deviceId, int keepDays = 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endTime.HasValue)
|
public class DeviceStatusRepository : Repository<DeviceStatus>, IDeviceStatusRepository
|
||||||
{
|
{
|
||||||
query = query.Where(ds => ds.RecordTime <= endTime.Value);
|
private readonly CNCDbContext _context;
|
||||||
}
|
|
||||||
|
|
||||||
return query.OrderByDescending(ds => ds.RecordTime).ToList();
|
public DeviceStatusRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceStatus GetLatestDeviceStatus(int deviceId)
|
public async Task<IEnumerable<DeviceStatus>> GetLatestStatusAsync(int deviceId, int count = 10)
|
||||||
{
|
{
|
||||||
return _context.DeviceStatus
|
return await _context.DeviceStatus
|
||||||
.Where(ds => ds.DeviceId == deviceId)
|
.Where(ds => ds.DeviceId == deviceId)
|
||||||
.OrderByDescending(ds => ds.RecordTime)
|
.OrderByDescending(ds => ds.RecordTime)
|
||||||
.FirstOrDefault();
|
.Take(count)
|
||||||
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceStatus CreateDeviceStatus(DeviceStatus status)
|
public async Task<DeviceStatus> GetLatestStatusByDeviceIdAsync(int deviceId)
|
||||||
{
|
{
|
||||||
_context.DeviceStatus.Add(status);
|
return await _context.DeviceStatus
|
||||||
_context.SaveChanges();
|
.Where(ds => ds.DeviceId == deviceId)
|
||||||
return status;
|
.OrderByDescending(ds => ds.RecordTime)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteOldDeviceStatuses(int deviceId, DateTime cutoffDate)
|
public async Task<IEnumerable<DeviceStatus>> GetStatusByDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate)
|
||||||
{
|
{
|
||||||
var oldStatuses = _context.DeviceStatus
|
return await _context.DeviceStatus
|
||||||
.Where(ds => ds.DeviceId == deviceId && ds.RecordTime < cutoffDate)
|
.Where(ds => ds.DeviceId == deviceId &&
|
||||||
.ToList();
|
ds.RecordTime >= startDate &&
|
||||||
|
ds.RecordTime <= endDate)
|
||||||
_context.DeviceStatus.RemoveRange(oldStatuses);
|
.OrderBy(ds => ds.RecordTime)
|
||||||
_context.SaveChanges();
|
.ToListAsync();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeviceCurrentStatusRepository
|
public async Task<int> CountStatusRecordsAsync(int deviceId)
|
||||||
{
|
{
|
||||||
private readonly CNCBusinessDbContext _context;
|
return await _context.DeviceStatus
|
||||||
private readonly DeviceStatusRepository _statusRepository;
|
.CountAsync(ds => ds.DeviceId == deviceId);
|
||||||
|
|
||||||
public DeviceCurrentStatusRepository(CNCBusinessDbContext context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
_statusRepository = new DeviceStatusRepository(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceCurrentStatus GetDeviceCurrentStatus(int deviceId)
|
public async Task DeleteOldStatusRecordsAsync(int deviceId, int keepDays = 30)
|
||||||
{
|
{
|
||||||
var device = _context.Devices.Find(deviceId);
|
var cutoffDate = DateTime.Now.AddDays(-keepDays);
|
||||||
if (device == null) return null;
|
var oldRecords = await _context.DeviceStatus
|
||||||
|
.Where(ds => ds.DeviceId == deviceId && ds.RecordTime < cutoffDate)
|
||||||
var latestStatus = _statusRepository.GetLatestDeviceStatus(deviceId);
|
.ToListAsync();
|
||||||
|
|
||||||
return new DeviceCurrentStatus
|
|
||||||
{
|
|
||||||
DeviceId = deviceId,
|
|
||||||
IsOnline = device.IsOnline,
|
|
||||||
IsAvailable = device.IsAvailable,
|
|
||||||
Status = latestStatus?.Status ?? "未知",
|
|
||||||
IsRunning = latestStatus?.IsRunning ?? false,
|
|
||||||
NCProgram = latestStatus?.NCProgram,
|
|
||||||
CumulativeCount = latestStatus?.CumulativeCount ?? 0,
|
|
||||||
OperatingMode = latestStatus?.OperatingMode,
|
|
||||||
RecordTime = latestStatus?.RecordTime ?? DateTime.Now,
|
|
||||||
Tags = new List<TagData>() // 这里需要根据实际数据填充
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DeviceCurrentStatus> GetAllDeviceCurrentStatuses()
|
|
||||||
{
|
|
||||||
var devices = _context.Devices.ToList();
|
|
||||||
var statuses = new List<DeviceCurrentStatus>();
|
|
||||||
|
|
||||||
foreach (var device in devices)
|
if (oldRecords.Any())
|
||||||
{
|
|
||||||
var status = GetDeviceCurrentStatus(device.Id);
|
|
||||||
if (status != null)
|
|
||||||
{
|
{
|
||||||
statuses.Add(status);
|
RemoveRange(oldRecords);
|
||||||
|
await SaveAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return statuses;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Haoliang.Data.Repositories
|
||||||
|
{
|
||||||
|
public interface IRepository<T> where T : class
|
||||||
|
{
|
||||||
|
Task<T> GetByIdAsync(int id);
|
||||||
|
Task<IEnumerable<T>> GetAllAsync();
|
||||||
|
Task<IEnumerable<T>> FindAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
|
||||||
|
Task<T> SingleOrDefaultAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
|
||||||
|
Task AddAsync(T entity);
|
||||||
|
Task AddRangeAsync(IEnumerable<T> entities);
|
||||||
|
void Update(T entity);
|
||||||
|
void Remove(T entity);
|
||||||
|
void RemoveRange(IEnumerable<T> entities);
|
||||||
|
Task<int> CountAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate = null);
|
||||||
|
Task<bool> ExistsAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
|
||||||
|
Task SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IReadRepository<T> where T : class
|
||||||
|
{
|
||||||
|
Task<T> GetByIdAsync(int id);
|
||||||
|
Task<IEnumerable<T>> GetAllAsync();
|
||||||
|
Task<IEnumerable<T>> FindAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
|
||||||
|
Task<T> SingleOrDefaultAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
|
||||||
|
Task<int> CountAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate = null);
|
||||||
|
Task<bool> ExistsAsync(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IWriteRepository<T> where T : class
|
||||||
|
{
|
||||||
|
Task AddAsync(T entity);
|
||||||
|
Task AddRangeAsync(IEnumerable<T> entities);
|
||||||
|
void Update(T entity);
|
||||||
|
void Remove(T entity);
|
||||||
|
void RemoveRange(IEnumerable<T> entities);
|
||||||
|
Task<int> SaveAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,211 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.Production;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
|
||||||
|
namespace Haoliang.Data.Repositories
|
||||||
|
{
|
||||||
|
public interface IProductionRepository : IRepository<ProductionRecord>
|
||||||
|
{
|
||||||
|
Task<IEnumerable<ProductionRecord>> GetByDeviceAndDateAsync(int deviceId, DateTime date);
|
||||||
|
Task<IEnumerable<ProductionRecord>> GetByDeviceAndProgramAsync(int deviceId, string ncProgram);
|
||||||
|
Task<IEnumerable<ProductionRecord>> GetByDateRangeAsync(DateTime startDate, DateTime endDate);
|
||||||
|
Task<ProductionRecord> GetLatestProductionAsync(int deviceId, string ncProgram);
|
||||||
|
Task<int> GetTodayProductionAsync(int deviceId);
|
||||||
|
Task<int> GetProductionByDateAsync(int deviceId, DateTime date);
|
||||||
|
Task<decimal> GetQualityRateAsync(int deviceId, DateTime date);
|
||||||
|
Task<bool> HasProductionDataAsync(int deviceId, DateTime date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProductionRepository : Repository<ProductionRecord>, IProductionRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public ProductionRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ProductionRecord>> GetByDeviceAndDateAsync(int deviceId, DateTime date)
|
||||||
|
{
|
||||||
|
return await _context.ProductionRecords
|
||||||
|
.Where(pr => pr.DeviceId == deviceId && pr.ProductionDate.Date == date.Date)
|
||||||
|
.OrderBy(pr => pr.NCProgram)
|
||||||
|
.ThenBy(pr => pr.CreatedAt)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ProductionRecord>> GetByDeviceAndProgramAsync(int deviceId, string ncProgram)
|
||||||
|
{
|
||||||
|
return await _context.ProductionRecords
|
||||||
|
.Where(pr => pr.DeviceId == deviceId && pr.NCProgram == ncProgram)
|
||||||
|
.OrderByDescending(pr => pr.ProductionDate)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ProductionRecord>> GetByDateRangeAsync(DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
return await _context.ProductionRecords
|
||||||
|
.Where(pr => pr.ProductionDate >= startDate && pr.ProductionDate <= endDate)
|
||||||
|
.OrderBy(pr => pr.ProductionDate)
|
||||||
|
.ThenBy(pr => pr.DeviceId)
|
||||||
|
.ThenBy(pr => pr.NCProgram)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProductionRecord> GetLatestProductionAsync(int deviceId, string ncProgram)
|
||||||
|
{
|
||||||
|
return await _context.ProductionRecords
|
||||||
|
.Where(pr => pr.DeviceId == deviceId && pr.NCProgram == ncProgram)
|
||||||
|
.OrderByDescending(pr => pr.ProductionDate)
|
||||||
|
.ThenByDescending(pr => pr.CreatedAt)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetTodayProductionAsync(int deviceId)
|
||||||
|
{
|
||||||
|
var today = DateTime.Today;
|
||||||
|
var productionRecords = await _context.ProductionRecords
|
||||||
|
.Where(pr => pr.DeviceId == deviceId && pr.ProductionDate == today)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return productionRecords.Sum(pr => pr.Quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetProductionByDateAsync(int deviceId, DateTime date)
|
||||||
|
{
|
||||||
|
var productionRecords = await _context.ProductionRecords
|
||||||
|
.Where(pr => pr.DeviceId == deviceId && pr.ProductionDate.Date == date.Date)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return productionRecords.Sum(pr => pr.Quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetQualityRateAsync(int deviceId, DateTime date)
|
||||||
|
{
|
||||||
|
var productionRecords = await _context.ProductionRecords
|
||||||
|
.Where(pr => pr.DeviceId == deviceId && pr.ProductionDate.Date == date.Date)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (productionRecords.Any())
|
||||||
|
{
|
||||||
|
var totalQuantity = productionRecords.Sum(pr => pr.Quantity);
|
||||||
|
var totalValidQuantity = productionRecords.Sum(pr => (int)(pr.Quantity * pr.QualityRate / 100));
|
||||||
|
return totalQuantity > 0 ? (decimal)totalValidQuantity / totalQuantity * 100 : 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HasProductionDataAsync(int deviceId, DateTime date)
|
||||||
|
{
|
||||||
|
return await _context.ProductionRecords
|
||||||
|
.AnyAsync(pr => pr.DeviceId == deviceId && pr.ProductionDate.Date == date.Date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IProgramProductionSummaryRepository : IRepository<ProgramProductionSummary>
|
||||||
|
{
|
||||||
|
Task<ProgramProductionSummary> GetByDeviceProgramDateAsync(int deviceId, string ncProgram, DateTime date);
|
||||||
|
Task<IEnumerable<ProgramProductionSummary>> GetByDeviceAndDateAsync(int deviceId, DateTime date);
|
||||||
|
Task<IEnumerable<ProgramProductionSummary>> GetByProgramAndDateRangeAsync(string ncProgram, DateTime startDate, DateTime endDate);
|
||||||
|
Task<int> GetTotalProductionAsync(int deviceId, DateTime date);
|
||||||
|
Task<decimal> GetOverallQualityRateAsync(int deviceId, DateTime date);
|
||||||
|
Task<bool> UpdateSummaryAsync(int deviceId, string ncProgram, DateTime date, int quantity, decimal qualityRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProgramProductionSummaryRepository : Repository<ProgramProductionSummary>, IProgramProductionSummarySummaryRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public ProgramProductionSummaryRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProgramProductionSummary> GetByDeviceProgramDateAsync(int deviceId, string ncProgram, DateTime date)
|
||||||
|
{
|
||||||
|
return await _context.ProgramProductionSummary
|
||||||
|
.FirstOrDefaultAsync(pps =>
|
||||||
|
pps.DeviceId == deviceId &&
|
||||||
|
pps.NCProgram == ncProgram &&
|
||||||
|
pps.ProductionDate.Date == date.Date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ProgramProductionSummary>> GetByDeviceAndDateAsync(int deviceId, DateTime date)
|
||||||
|
{
|
||||||
|
return await _context.ProgramProductionSummary
|
||||||
|
.Where(pps => pps.DeviceId == deviceId && pps.ProductionDate.Date == date.Date)
|
||||||
|
.OrderBy(pps => pps.NCProgram)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ProgramProductionSummary>> GetByProgramAndDateRangeAsync(string ncProgram, DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
return await _context.ProgramProductionSummary
|
||||||
|
.Where(pps => pps.NCProgram == ncProgram &&
|
||||||
|
pps.ProductionDate >= startDate &&
|
||||||
|
pps.ProductionDate <= endDate)
|
||||||
|
.OrderBy(pps => pps.ProductionDate)
|
||||||
|
.ThenBy(pps => pps.DeviceId)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetTotalProductionAsync(int deviceId, DateTime date)
|
||||||
|
{
|
||||||
|
var summaries = await GetByDeviceAndDateAsync(deviceId, date);
|
||||||
|
return summaries.Sum(s => s.TotalQuantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetOverallQualityRateAsync(int deviceId, DateTime date)
|
||||||
|
{
|
||||||
|
var summaries = await GetByDeviceAndDateAsync(deviceId, date);
|
||||||
|
|
||||||
|
if (summaries.Any())
|
||||||
|
{
|
||||||
|
var totalQuantity = summaries.Sum(s => s.TotalQuantity);
|
||||||
|
var totalValidQuantity = summaries.Sum(s => s.ValidQuantity);
|
||||||
|
return totalQuantity > 0 ? (decimal)totalValidQuantity / totalQuantity * 100 : 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateSummaryAsync(int deviceId, string ncProgram, DateTime date, int quantity, decimal qualityRate)
|
||||||
|
{
|
||||||
|
var summary = await GetByDeviceProgramDateAsync(deviceId, ncProgram, date);
|
||||||
|
|
||||||
|
if (summary != null)
|
||||||
|
{
|
||||||
|
summary.TotalQuantity += quantity;
|
||||||
|
summary.ValidQuantity = (int)(summary.TotalQuantity * qualityRate / 100);
|
||||||
|
summary.InvalidQuantity = summary.TotalQuantity - summary.ValidQuantity;
|
||||||
|
summary.QualityRate = qualityRate;
|
||||||
|
summary.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(summary);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
summary = new ProgramProductionSummary
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
NCProgram = ncProgram,
|
||||||
|
ProductionDate = date,
|
||||||
|
TotalQuantity = quantity,
|
||||||
|
ValidQuantity = (int)(quantity * qualityRate / 100),
|
||||||
|
InvalidQuantity = quantity - (int)(quantity * qualityRate / 100),
|
||||||
|
QualityRate = qualityRate
|
||||||
|
};
|
||||||
|
|
||||||
|
await AddAsync(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,342 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.System;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
|
||||||
|
namespace Haoliang.Data.Repositories
|
||||||
|
{
|
||||||
|
public interface IAlarmRepository : IRepository<Alarm>
|
||||||
|
{
|
||||||
|
Task<IEnumerable<Alarm>> GetActiveAlarmsAsync();
|
||||||
|
Task<IEnumerable<Alarm>> GetAlarmsByDeviceIdAsync(int deviceId);
|
||||||
|
Task<IEnumerable<Alarm>> GetAlarmsByTypeAsync(string alarmType);
|
||||||
|
Task<IEnumerable<Alarm>> GetAlarmsByLevelAsync(string alarmLevel);
|
||||||
|
Task<IEnumerable<Alarm>> GetUnresolvedAlarmsAsync();
|
||||||
|
Task<IEnumerable<Alarm>> GetAlarmsByDateRangeAsync(DateTime startDate, DateTime endDate);
|
||||||
|
Task<int> CountActiveAlarmsAsync();
|
||||||
|
Task<int> CountAlarmsByTypeAsync(string alarmType);
|
||||||
|
Task<decimal> GetAlarmResolutionRateAsync(DateTime startDate, DateTime endDate);
|
||||||
|
Task<bool> ResolveAlarmAsync(int alarmId, string resolutionNote, string resolvedBy);
|
||||||
|
Task<bool> DeleteOldAlarmsAsync(int keepDays = 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlarmRepository : Repository<Alarm>, IAlarmRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public AlarmRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetActiveAlarmsAsync()
|
||||||
|
{
|
||||||
|
return await _context.Alarms
|
||||||
|
.Where(a => !a.IsResolved)
|
||||||
|
.OrderByDescending(a => a.OccurrenceTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetAlarmsByDeviceIdAsync(int deviceId)
|
||||||
|
{
|
||||||
|
return await _context.Alarms
|
||||||
|
.Where(a => a.DeviceId == deviceId)
|
||||||
|
.OrderByDescending(a => a.OccurrenceTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetAlarmsByTypeAsync(string alarmType)
|
||||||
|
{
|
||||||
|
return await _context.Alarms
|
||||||
|
.Where(a => a.AlarmType == alarmType)
|
||||||
|
.OrderByDescending(a => a.OccurrenceTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetAlarmsByLevelAsync(string alarmLevel)
|
||||||
|
{
|
||||||
|
return await _context.Alarms
|
||||||
|
.Where(a => a.AlarmLevel == alarmLevel)
|
||||||
|
.OrderByDescending(a => a.OccurrenceTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetUnresolvedAlarmsAsync()
|
||||||
|
{
|
||||||
|
return await GetActiveAlarmsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Alarm>> GetAlarmsByDateRangeAsync(DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
return await _context.Alarms
|
||||||
|
.Where(a => a.OccurrenceTime >= startDate && a.OccurrenceTime <= endDate)
|
||||||
|
.OrderByDescending(a => a.OccurrenceTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountActiveAlarmsAsync()
|
||||||
|
{
|
||||||
|
return await _context.Alarms
|
||||||
|
.CountAsync(a => !a.IsResolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountAlarmsByTypeAsync(string alarmType)
|
||||||
|
{
|
||||||
|
return await _context.Alarms
|
||||||
|
.CountAsync(a => a.AlarmType == alarmType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetAlarmResolutionRateAsync(DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
var alarms = await GetAlarmsByDateRangeAsync(startDate, endDate);
|
||||||
|
if (alarms.Any())
|
||||||
|
{
|
||||||
|
var resolvedCount = alarms.Count(a => a.IsResolved);
|
||||||
|
return (decimal)resolvedCount / alarms.Count * 100;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ResolveAlarmAsync(int alarmId, string resolutionNote, string resolvedBy)
|
||||||
|
{
|
||||||
|
var alarm = await GetByIdAsync(alarmId);
|
||||||
|
if (alarm != null)
|
||||||
|
{
|
||||||
|
alarm.IsResolved = true;
|
||||||
|
alarm.ResolutionTime = DateTime.Now;
|
||||||
|
alarm.ResolutionNote = resolutionNote;
|
||||||
|
|
||||||
|
Update(alarm);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteOldAlarmsAsync(int keepDays = 90)
|
||||||
|
{
|
||||||
|
var cutoffDate = DateTime.Now.AddDays(-keepDays);
|
||||||
|
var oldAlarms = await _context.Alarms
|
||||||
|
.Where(a => a.OccurrenceTime < cutoffDate)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (oldAlarms.Any())
|
||||||
|
{
|
||||||
|
RemoveRange(oldAlarms);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAlarmRuleRepository : IRepository<AlarmRule>
|
||||||
|
{
|
||||||
|
Task<AlarmRule> GetByNameAsync(string ruleName);
|
||||||
|
Task<IEnumerable<AlarmRule>> GetEnabledRulesAsync();
|
||||||
|
Task<IEnumerable<AlarmRule>> GetRulesByDeviceIdAsync(int deviceId);
|
||||||
|
Task<bool> RuleNameExistsAsync(string ruleName);
|
||||||
|
Task<bool> EnableRuleAsync(int ruleId);
|
||||||
|
Task<bool> DisableRuleAsync(int ruleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlarmRuleRepository : Repository<AlarmRule>, IAlarmRuleRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public AlarmRuleRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AlarmRule> GetByNameAsync(string ruleName)
|
||||||
|
{
|
||||||
|
return await _context.AlarmRules
|
||||||
|
.FirstOrDefaultAsync(ar => ar.RuleName == ruleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<AlarmRule>> GetEnabledRulesAsync()
|
||||||
|
{
|
||||||
|
return await _context.AlarmRules
|
||||||
|
.Where(ar => ar.IsEnabled)
|
||||||
|
.OrderBy(ar => ar.RuleName)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<AlarmRule>> GetRulesByDeviceIdAsync(int deviceId)
|
||||||
|
{
|
||||||
|
return await _context.AlarmRules
|
||||||
|
.Where(ar => ar.DeviceId == null || ar.DeviceId == deviceId)
|
||||||
|
.OrderBy(ar => ar.RuleName)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RuleNameExistsAsync(string ruleName)
|
||||||
|
{
|
||||||
|
return await _context.AlarmRules
|
||||||
|
.AnyAsync(ar => ar.RuleName == ruleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> EnableRuleAsync(int ruleId)
|
||||||
|
{
|
||||||
|
var rule = await GetByIdAsync(ruleId);
|
||||||
|
if (rule != null)
|
||||||
|
{
|
||||||
|
rule.IsEnabled = true;
|
||||||
|
rule.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(rule);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DisableRuleAsync(int ruleId)
|
||||||
|
{
|
||||||
|
var rule = await GetByIdAsync(ruleId);
|
||||||
|
if (rule != null)
|
||||||
|
{
|
||||||
|
rule.IsEnabled = false;
|
||||||
|
rule.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(rule);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISystemConfigRepository : IRepository<SystemConfig>
|
||||||
|
{
|
||||||
|
Task<SystemConfig> GetByKeyAsync(string configKey);
|
||||||
|
Task<string> GetConfigValueAsync(string configKey);
|
||||||
|
Task<bool> UpdateConfigValueAsync(string configKey, string configValue);
|
||||||
|
Task<IEnumerable<SystemConfig>> GetByCategoryAsync(string category);
|
||||||
|
Task<bool> ConfigKeyExistsAsync(string configKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemConfigRepository : Repository<SystemConfig>, ISystemConfigRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public SystemConfigRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SystemConfig> GetByKeyAsync(string configKey)
|
||||||
|
{
|
||||||
|
return await _context.SystemConfig
|
||||||
|
.FirstOrDefaultAsync(sc => sc.ConfigKey == configKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetConfigValueAsync(string configKey)
|
||||||
|
{
|
||||||
|
var config = await GetByKeyAsync(configKey);
|
||||||
|
return config?.ConfigValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateConfigValueAsync(string configKey, string configValue)
|
||||||
|
{
|
||||||
|
var config = await GetByKeyAsync(configKey);
|
||||||
|
if (config != null)
|
||||||
|
{
|
||||||
|
config.ConfigValue = configValue;
|
||||||
|
config.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(config);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SystemConfig>> GetByCategoryAsync(string category)
|
||||||
|
{
|
||||||
|
return await _context.SystemConfig
|
||||||
|
.Where(sc => sc.Description?.Contains(category) == true)
|
||||||
|
.OrderBy(sc => sc.ConfigKey)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ConfigKeyExistsAsync(string configKey)
|
||||||
|
{
|
||||||
|
return await _context.SystemConfig
|
||||||
|
.AnyAsync(sc => sc.ConfigKey == configKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IStatisticRuleRepository : IRepository<StatisticRule>
|
||||||
|
{
|
||||||
|
Task<StatisticRule> GetByNameAsync(string ruleName);
|
||||||
|
Task<IEnumerable<StatisticRule>> GetEnabledRulesAsync();
|
||||||
|
Task<bool> RuleNameExistsAsync(string ruleName);
|
||||||
|
Task<bool> EnableRuleAsync(int ruleId);
|
||||||
|
Task<bool> DisableRuleAsync(int ruleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatisticRuleRepository : Repository<StatisticRule>, IStatisticRuleRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public StatisticRuleRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StatisticRule> GetByNameAsync(string ruleName)
|
||||||
|
{
|
||||||
|
return await _context.StatisticRules
|
||||||
|
.FirstOrDefaultAsync(sr => sr.RuleName == ruleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<StatisticRule>> GetEnabledRulesAsync()
|
||||||
|
{
|
||||||
|
return await _context.StatisticRules
|
||||||
|
.Where(sr => sr.IsEnabled)
|
||||||
|
.OrderBy(sr => sr.RuleName)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RuleNameExistsAsync(string ruleName)
|
||||||
|
{
|
||||||
|
return await _context.StatisticRules
|
||||||
|
.AnyAsync(sr => sr.RuleName == ruleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> EnableRuleAsync(int ruleId)
|
||||||
|
{
|
||||||
|
var rule = await GetByIdAsync(ruleId);
|
||||||
|
if (rule != null)
|
||||||
|
{
|
||||||
|
rule.IsEnabled = true;
|
||||||
|
rule.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(rule);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DisableRuleAsync(int ruleId)
|
||||||
|
{
|
||||||
|
var rule = await GetByIdAsync(ruleId);
|
||||||
|
if (rule != null)
|
||||||
|
{
|
||||||
|
rule.IsEnabled = false;
|
||||||
|
rule.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(rule);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Haoliang.Models.Template;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
|
||||||
|
namespace Haoliang.Data.Repositories
|
||||||
|
{
|
||||||
|
public interface ITemplateRepository : IRepository<CNCBrandTemplate>
|
||||||
|
{
|
||||||
|
Task<CNCBrandTemplate> GetByBrandNameAsync(string brandName);
|
||||||
|
Task<IEnumerable<CNCBrandTemplate>> GetEnabledTemplatesAsync();
|
||||||
|
Task<bool> TemplateExistsAsync(string brandName);
|
||||||
|
Task UpdateTemplateEnabledAsync(int templateId, bool isEnabled);
|
||||||
|
Task<IEnumerable<CNCBrandTemplate>> GetTemplatesByFieldAsync(string standardFieldId);
|
||||||
|
Task<bool> IsFieldMappedAsync(int templateId, string standardFieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TemplateRepository : Repository<CNCBrandTemplate>, ITemplateRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public TemplateRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CNCBrandTemplate> GetByBrandNameAsync(string brandName)
|
||||||
|
{
|
||||||
|
return await _context.CNCTemplates
|
||||||
|
.FirstOrDefaultAsync(t => t.BrandName == brandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CNCBrandTemplate>> GetEnabledTemplatesAsync()
|
||||||
|
{
|
||||||
|
return await _context.CNCTemplates
|
||||||
|
.Where(t => t.IsEnabled)
|
||||||
|
.OrderBy(t => t.BrandName)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TemplateExistsAsync(string brandName)
|
||||||
|
{
|
||||||
|
return await _context.CNCTemplates
|
||||||
|
.AnyAsync(t => t.BrandName == brandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateTemplateEnabledAsync(int templateId, bool isEnabled)
|
||||||
|
{
|
||||||
|
var template = await GetByIdAsync(templateId);
|
||||||
|
if (template != null)
|
||||||
|
{
|
||||||
|
template.IsEnabled = isEnabled;
|
||||||
|
template.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(template);
|
||||||
|
await SaveAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CNCBrandTemplate>> GetTemplatesByFieldAsync(string standardFieldId)
|
||||||
|
{
|
||||||
|
return await _context.CNCTemplates
|
||||||
|
.Where(t => t.IsEnabled && t.FieldMappings.Any(f => f.StandardFieldId == standardFieldId))
|
||||||
|
.OrderBy(t => t.BrandName)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsFieldMappedAsync(int templateId, string standardFieldId)
|
||||||
|
{
|
||||||
|
var template = await GetByIdAsync(templateId);
|
||||||
|
return template != null && template.FieldMappings.Any(f => f.StandardFieldId == standardFieldId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITemplateFieldMappingRepository : IRepository<TemplateFieldMapping>
|
||||||
|
{
|
||||||
|
Task<IEnumerable<TemplateFieldMapping>> GetByTemplateIdAsync(int templateId);
|
||||||
|
Task<TemplateFieldMapping> GetByTemplateAndFieldAsync(int templateId, string standardFieldId);
|
||||||
|
Task<bool> DeleteByTemplateIdAsync(int templateId);
|
||||||
|
Task<int> CountMappingsByTemplateIdAsync(int templateId);
|
||||||
|
Task<IEnumerable<TemplateFieldMapping>> GetMappingsByDataTypeAsync(string dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TemplateFieldMappingRepository : Repository<TemplateFieldMapping>, ITemplateFieldMappingRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public TemplateFieldMappingRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TemplateFieldMapping>> GetByTemplateIdAsync(int templateId)
|
||||||
|
{
|
||||||
|
return await _context.TemplateFieldMappings
|
||||||
|
.Where(tf => tf.TemplateId == templateId)
|
||||||
|
.OrderBy(tf => tf.StandardFieldId)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TemplateFieldMapping> GetByTemplateAndFieldAsync(int templateId, string standardFieldId)
|
||||||
|
{
|
||||||
|
return await _context.TemplateFieldMappings
|
||||||
|
.FirstOrDefaultAsync(tf => tf.TemplateId == templateId && tf.StandardFieldId == standardFieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteByTemplateIdAsync(int templateId)
|
||||||
|
{
|
||||||
|
var mappings = await GetByTemplateIdAsync(templateId);
|
||||||
|
if (mappings.Any())
|
||||||
|
{
|
||||||
|
RemoveRange(mappings);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountMappingsByTemplateIdAsync(int templateId)
|
||||||
|
{
|
||||||
|
return await _context.TemplateFieldMappings
|
||||||
|
.CountAsync(tf => tf.TemplateId == templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TemplateFieldMapping>> GetMappingsByDataTypeAsync(string dataType)
|
||||||
|
{
|
||||||
|
return await _context.TemplateFieldMappings
|
||||||
|
.Where(tf => tf.DataType == dataType)
|
||||||
|
.OrderBy(tf => tf.TemplateId)
|
||||||
|
.ThenBy(tf => tf.StandardFieldId)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,366 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using Haoliang.Models.User;
|
||||||
|
using Haoliang.Data.Repositories;
|
||||||
|
|
||||||
|
namespace Haoliang.Data.Repositories
|
||||||
|
{
|
||||||
|
public interface IUserRepository : IRepository<User>
|
||||||
|
{
|
||||||
|
Task<User> GetByUsernameAsync(string username);
|
||||||
|
Task<User> GetByEmailAsync(string email);
|
||||||
|
Task<bool> UsernameExistsAsync(string username);
|
||||||
|
Task<bool> EmailExistsAsync(string email);
|
||||||
|
Task<bool> ValidatePasswordAsync(string username, string password);
|
||||||
|
Task<User> AuthenticateAsync(string username, string password);
|
||||||
|
Task<IEnumerable<User>> GetByRoleIdAsync(int roleId);
|
||||||
|
Task UpdateLastLoginAsync(int userId);
|
||||||
|
Task<bool> ChangePasswordAsync(int userId, string oldPassword, string newPassword);
|
||||||
|
Task<bool> ResetPasswordAsync(int userId, string newPassword);
|
||||||
|
Task<IEnumerable<User>> GetActiveUsersAsync();
|
||||||
|
Task<bool> IsUserActiveAsync(string username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserRepository : Repository<User>, IUserRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public UserRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> GetByUsernameAsync(string username)
|
||||||
|
{
|
||||||
|
return await _context.Users
|
||||||
|
.Include(u => u.Role)
|
||||||
|
.FirstOrDefaultAsync(u => u.Username == username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> GetByEmailAsync(string email)
|
||||||
|
{
|
||||||
|
return await _context.Users
|
||||||
|
.Include(u => u.Role)
|
||||||
|
.FirstOrDefaultAsync(u => u.Email == email);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UsernameExistsAsync(string username)
|
||||||
|
{
|
||||||
|
return await _context.Users
|
||||||
|
.AnyAsync(u => u.Username == username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> EmailExistsAsync(string email)
|
||||||
|
{
|
||||||
|
return await _context.Users
|
||||||
|
.AnyAsync(u => u.Email == email);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ValidatePasswordAsync(string username, string password)
|
||||||
|
{
|
||||||
|
var user = await GetByUsernameAsync(username);
|
||||||
|
if (user == null || !user.IsActive)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return VerifyPassword(password, user.PasswordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> AuthenticateAsync(string username, string password)
|
||||||
|
{
|
||||||
|
var user = await GetByUsernameAsync(username);
|
||||||
|
if (user == null || !user.IsActive)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (VerifyPassword(password, user.PasswordHash))
|
||||||
|
{
|
||||||
|
await UpdateLastLoginAsync(user.Id);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<User>> GetByRoleIdAsync(int roleId)
|
||||||
|
{
|
||||||
|
return await _context.Users
|
||||||
|
.Include(u => u.Role)
|
||||||
|
.Where(u => u.RoleId == roleId)
|
||||||
|
.OrderBy(u => u.Username)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateLastLoginAsync(int userId)
|
||||||
|
{
|
||||||
|
var user = await GetByIdAsync(userId);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
user.LastLoginTime = DateTime.Now;
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(user);
|
||||||
|
await SaveAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ChangePasswordAsync(int userId, string oldPassword, string newPassword)
|
||||||
|
{
|
||||||
|
var user = await GetByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!VerifyPassword(oldPassword, user.PasswordHash))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
user.PasswordHash = HashPassword(newPassword);
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(user);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ResetPasswordAsync(int userId, string newPassword)
|
||||||
|
{
|
||||||
|
var user = await GetByIdAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
user.PasswordHash = HashPassword(newPassword);
|
||||||
|
user.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(user);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<User>> GetActiveUsersAsync()
|
||||||
|
{
|
||||||
|
return await _context.Users
|
||||||
|
.Include(u => u.Role)
|
||||||
|
.Where(u => u.IsActive)
|
||||||
|
.OrderBy(u => u.Username)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsUserActiveAsync(string username)
|
||||||
|
{
|
||||||
|
var user = await GetByUsernameAsync(username);
|
||||||
|
return user != null && user.IsActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string HashPassword(string password)
|
||||||
|
{
|
||||||
|
using (var sha256 = SHA256.Create())
|
||||||
|
{
|
||||||
|
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool VerifyPassword(string password, string hash)
|
||||||
|
{
|
||||||
|
return HashPassword(password) == hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IRoleRepository : IRepository<Role>
|
||||||
|
{
|
||||||
|
Task<Role> GetByNameAsync(string roleName);
|
||||||
|
Task<bool> RoleNameExistsAsync(string roleName);
|
||||||
|
Task<IEnumerable<Permission>> GetRolePermissionsAsync(int roleId);
|
||||||
|
Task<bool> AddPermissionToRoleAsync(int roleId, int permissionId);
|
||||||
|
Task<bool> RemovePermissionFromRoleAsync(int roleId, int permissionId);
|
||||||
|
Task<bool> UserHasPermissionAsync(int userId, string permissionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RoleRepository : Repository<Role>, IRoleRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public RoleRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Role> GetByNameAsync(string roleName)
|
||||||
|
{
|
||||||
|
return await _context.Roles
|
||||||
|
.FirstOrDefaultAsync(r => r.RoleName == roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RoleNameExistsAsync(string roleName)
|
||||||
|
{
|
||||||
|
return await _context.Roles
|
||||||
|
.AnyAsync(r => r.RoleName == roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Permission>> GetRolePermissionsAsync(int roleId)
|
||||||
|
{
|
||||||
|
var role = await GetByIdAsync(roleId);
|
||||||
|
if (role == null)
|
||||||
|
return new List<Permission>();
|
||||||
|
|
||||||
|
var permissionIds = role.Permissions.Select(p => int.Parse(p.Split('_')[0])).ToList();
|
||||||
|
return await _context.Permissions
|
||||||
|
.Where(p => permissionIds.Contains(p.Id))
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AddPermissionToRoleAsync(int roleId, int permissionId)
|
||||||
|
{
|
||||||
|
var role = await GetByIdAsync(roleId);
|
||||||
|
if (role == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!role.Permissions.Contains(permissionId.ToString()))
|
||||||
|
{
|
||||||
|
role.Permissions.Add(permissionId.ToString());
|
||||||
|
role.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(role);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RemovePermissionFromRoleAsync(int roleId, int permissionId)
|
||||||
|
{
|
||||||
|
var role = await GetByIdAsync(roleId);
|
||||||
|
if (role == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (role.Permissions.Contains(permissionId.ToString()))
|
||||||
|
{
|
||||||
|
role.Permissions.Remove(permissionId.ToString());
|
||||||
|
role.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
Update(role);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UserHasPermissionAsync(int userId, string permissionName)
|
||||||
|
{
|
||||||
|
var user = await _context.Users
|
||||||
|
.Include(u => u.Role)
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||||
|
|
||||||
|
if (user == null || user.Role == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return user.Role.Permissions.Contains(permissionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IEmployeeRepository : IRepository<Employee>
|
||||||
|
{
|
||||||
|
Task<Employee> GetByEmployeeCodeAsync(string employeeCode);
|
||||||
|
Task<bool> EmployeeCodeExistsAsync(string employeeCode);
|
||||||
|
Task<IEnumerable<Employee>> GetByDepartmentAsync(string department);
|
||||||
|
Task<IEnumerable<Employee>> GetByPositionAsync(string position);
|
||||||
|
Task<Employee> GetAssignedEmployeeAsync(int deviceId);
|
||||||
|
Task<bool> AssignDeviceToEmployeeAsync(int employeeId, int deviceId);
|
||||||
|
Task<bool> UnassignDeviceFromEmployeeAsync(int employeeId, int deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EmployeeRepository : Repository<Employee>, IEmployeeRepository
|
||||||
|
{
|
||||||
|
private readonly CNCDbContext _context;
|
||||||
|
|
||||||
|
public EmployeeRepository(CNCDbContext context) : base(context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Employee> GetByEmployeeCodeAsync(string employeeCode)
|
||||||
|
{
|
||||||
|
return await _context.Employees
|
||||||
|
.FirstOrDefaultAsync(e => e.EmployeeCode == employeeCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> EmployeeCodeExistsAsync(string employeeCode)
|
||||||
|
{
|
||||||
|
return await _context.Employees
|
||||||
|
.AnyAsync(e => e.EmployeeCode == employeeCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Employee>> GetByDepartmentAsync(string department)
|
||||||
|
{
|
||||||
|
return await _context.Employees
|
||||||
|
.Where(e => e.Department == department)
|
||||||
|
.OrderBy(e => e.Name)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Employee>> GetByPositionAsync(string position)
|
||||||
|
{
|
||||||
|
return await _context.Employees
|
||||||
|
.Where(e => e.Position == position)
|
||||||
|
.OrderBy(e => e.Name)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Employee> GetAssignedEmployeeAsync(int deviceId)
|
||||||
|
{
|
||||||
|
var assignment = await _context.DeviceAssignments
|
||||||
|
.FirstOrDefaultAsync(da => da.DeviceId == deviceId && da.EndDate == null);
|
||||||
|
|
||||||
|
if (assignment != null)
|
||||||
|
{
|
||||||
|
return await GetByIdAsync(assignment.EmployeeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AssignDeviceToEmployeeAsync(int employeeId, int deviceId)
|
||||||
|
{
|
||||||
|
var existingAssignment = await _context.DeviceAssignments
|
||||||
|
.FirstOrDefaultAsync(da => da.DeviceId == deviceId && da.EndDate == null);
|
||||||
|
|
||||||
|
if (existingAssignment != null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var assignment = new DeviceAssignment
|
||||||
|
{
|
||||||
|
EmployeeId = employeeId,
|
||||||
|
DeviceId = deviceId,
|
||||||
|
AssignmentDate = DateTime.Now,
|
||||||
|
CreatedAt = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
await _context.DeviceAssignments.AddAsync(assignment);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UnassignDeviceFromEmployeeAsync(int employeeId, int deviceId)
|
||||||
|
{
|
||||||
|
var assignment = await _context.DeviceAssignments
|
||||||
|
.FirstOrDefaultAsync(da =>
|
||||||
|
da.EmployeeId == employeeId &&
|
||||||
|
da.DeviceId == deviceId &&
|
||||||
|
da.EndDate == null);
|
||||||
|
|
||||||
|
if (assignment != null)
|
||||||
|
{
|
||||||
|
assignment.EndDate = DateTime.Now;
|
||||||
|
_context.DeviceAssignments.Update(assignment);
|
||||||
|
await SaveAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Haoliang.Models.Common
|
||||||
|
{
|
||||||
|
public class ApiResponse<T>
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public T Data { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public int ErrorCode { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public string RequestId { get; set; }
|
||||||
|
public Dictionary<string, object> Meta { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PaginatedResponse<T>
|
||||||
|
{
|
||||||
|
public List<T> Items { get; set; }
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
public int PageNumber { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
public int TotalPages { get; set; }
|
||||||
|
public bool HasPreviousPage { get; set; }
|
||||||
|
public bool HasNextPage { get; set; }
|
||||||
|
public ApiResponse<T> Response { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ValidationResult
|
||||||
|
{
|
||||||
|
public bool IsValid { get; set; }
|
||||||
|
public List<ValidationError> Errors { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ValidationError
|
||||||
|
{
|
||||||
|
public string Field { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string Code { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SelectOption
|
||||||
|
{
|
||||||
|
public string Value { get; set; }
|
||||||
|
public string Label { get; set; }
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
public string Group { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TreeNode
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Text { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public List<TreeNode> Children { get; set; }
|
||||||
|
public Dictionary<string, object> Data { get; set; }
|
||||||
|
public bool Expanded { get; set; }
|
||||||
|
public bool Selected { get; set; }
|
||||||
|
public bool HasChildren { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChartData
|
||||||
|
{
|
||||||
|
public string Type { get; set; } // line, bar, pie, doughnut, radar
|
||||||
|
public string Title { get; set; }
|
||||||
|
public List<string> Labels { get; set; }
|
||||||
|
public List<Dataset> Datasets { get; set; }
|
||||||
|
public Dictionary<string, object> Options { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dataset
|
||||||
|
{
|
||||||
|
public string Label { get; set; }
|
||||||
|
public List<decimal> Data { get; set; }
|
||||||
|
public string BackgroundColor { get; set; }
|
||||||
|
public string BorderColor { get; set; }
|
||||||
|
public decimal BorderWidth { get; set; }
|
||||||
|
public string BorderDash { get; set; }
|
||||||
|
public bool Fill { get; set; }
|
||||||
|
public string Tension { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DashboardWidget
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Type { get; set; } // chart, statistic, table, list
|
||||||
|
public string Size { get; set; } // small, medium, large
|
||||||
|
public int Row { get; set; }
|
||||||
|
public int Col { get; set; }
|
||||||
|
public Dictionary<string, object> Config { get; set; }
|
||||||
|
public bool Refreshable { get; set; }
|
||||||
|
public int RefreshInterval { get; set; }
|
||||||
|
public bool Visible { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FilterCondition
|
||||||
|
{
|
||||||
|
public string Field { get; set; }
|
||||||
|
public string Operator { get; set; } // eq, ne, gt, lt, gte, lte, in, like
|
||||||
|
public object Value { get; set; }
|
||||||
|
public string Logic { get; set; } // and, or
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
|
||||||
|
namespace Haoliang.Models.DataCollection
|
||||||
|
{
|
||||||
|
public class CollectionTask
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string TaskName { get; set; }
|
||||||
|
public string Status { get; set; } // Pending, Running, Completed, Failed
|
||||||
|
public DateTime ScheduledTime { get; set; }
|
||||||
|
public DateTime? StartTime { get; set; }
|
||||||
|
public DateTime? EndTime { get; set; }
|
||||||
|
public bool IsSuccess { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
public int RetryCount { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionResult
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public int TaskId { get; set; }
|
||||||
|
public DateTime CollectionTime { get; set; }
|
||||||
|
public string RawJson { get; set; }
|
||||||
|
public bool IsSuccess { get; set; }
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
public int RetryCount { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DeviceCurrentStatus ParsedData { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionLog
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public LogLevel LogLevel { get; set; }
|
||||||
|
public string LogCategory { get; set; }
|
||||||
|
public string LogMessage { get; set; }
|
||||||
|
public string LogData { get; set; }
|
||||||
|
public string SourceMethod { get; set; }
|
||||||
|
public string SourceFile { get; set; }
|
||||||
|
public DateTime LogTime { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LogLevel
|
||||||
|
{
|
||||||
|
Debug = 0,
|
||||||
|
Information = 1,
|
||||||
|
Warning = 2,
|
||||||
|
Error = 3,
|
||||||
|
Critical = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionStatistics
|
||||||
|
{
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int TotalAttempts { get; set; }
|
||||||
|
public int SuccessCount { get; set; }
|
||||||
|
public int FailedCount { get; set; }
|
||||||
|
public decimal SuccessRate { get; set; }
|
||||||
|
public TimeSpan AverageResponseTime { get; set; }
|
||||||
|
public int DeviceCount { get; set; }
|
||||||
|
public int OnlineDeviceCount { get; set; }
|
||||||
|
public long TotalDataSize { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionConfig
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string ConfigKey { get; set; }
|
||||||
|
public string ConfigValue { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public bool IsEnabled { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionHealth
|
||||||
|
{
|
||||||
|
public DateTime CheckTime { get; set; }
|
||||||
|
public int TotalDevices { get; set; }
|
||||||
|
public int OnlineDevices { get; set; }
|
||||||
|
public int ActiveCollectionTasks { get; set; }
|
||||||
|
public int FailedTasks { get; set; }
|
||||||
|
public double SuccessRate { get; set; }
|
||||||
|
public double AverageResponseTime { get; set; }
|
||||||
|
public long TotalCollectedData { get; set; }
|
||||||
|
public DateTime LastSuccessfulCollection { get; set; }
|
||||||
|
public DateTime LastFailedCollection { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionFilter
|
||||||
|
{
|
||||||
|
public List<int> DeviceIds { get; set; }
|
||||||
|
public DateTime? StartDate { get; set; }
|
||||||
|
public DateTime? EndDate { get; set; }
|
||||||
|
public bool? IsSuccess { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
public int? RetryCount { get; set; }
|
||||||
|
public string SearchKeyword { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Haoliang.Models.System
|
||||||
|
{
|
||||||
|
public class AlarmStatistics
|
||||||
|
{
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public string AlarmType { get; set; }
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
public int ResolvedCount { get; set; }
|
||||||
|
public int UnresolvedCount { get; set; }
|
||||||
|
public decimal ResolutionRate { get; set; }
|
||||||
|
public TimeSpan AverageResolutionTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlarmNotification
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int AlarmId { get; set; }
|
||||||
|
public string NotificationType { get; set; } // Email, SMS, WeChat
|
||||||
|
public string Recipient { get; set; }
|
||||||
|
public string Subject { get; set; }
|
||||||
|
public string Content { get; set; }
|
||||||
|
public bool IsSent { get; set; }
|
||||||
|
public DateTime SendTime { get; set; }
|
||||||
|
public string SendResult { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlarmViewModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string AlarmType { get; set; }
|
||||||
|
public string AlarmLevel { get; set; }
|
||||||
|
public string AlarmContent { get; set; }
|
||||||
|
public int? DeviceId { get; set; }
|
||||||
|
public string DeviceCode { get; set; }
|
||||||
|
public string DeviceName { get; set; }
|
||||||
|
public string AlarmRuleName { get; set; }
|
||||||
|
public bool IsResolved { get; set; }
|
||||||
|
public DateTime OccurrenceTime { get; set; }
|
||||||
|
public DateTime? ResolutionTime { get; set; }
|
||||||
|
public string ResolutionNote { get; set; }
|
||||||
|
public string ResolvedBy { get; set; }
|
||||||
|
public List<AlarmNotification> Notifications { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlarmFilter
|
||||||
|
{
|
||||||
|
public List<int> DeviceIds { get; set; }
|
||||||
|
public List<string> AlarmTypes { get; set; }
|
||||||
|
public List<string> AlarmLevels { get; set; }
|
||||||
|
public bool? IsResolved { get; set; }
|
||||||
|
public DateTime? StartDate { get; set; }
|
||||||
|
public DateTime? EndDate { get; set; }
|
||||||
|
public string SearchKeyword { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Haoliang.Models.System
|
||||||
|
{
|
||||||
|
public class StatisticResult
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int RuleId { get; set; }
|
||||||
|
public StatisticRule Rule { get; set; }
|
||||||
|
public DateTime StatisticTime { get; set; }
|
||||||
|
public Dictionary<string, object> GroupValues { get; set; }
|
||||||
|
public decimal MetricValue { get; set; }
|
||||||
|
public string MetricValueDisplay { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatisticSummary
|
||||||
|
{
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string DeviceName { get; set; }
|
||||||
|
public int TotalQuantity { get; set; }
|
||||||
|
public int ValidQuantity { get; set; }
|
||||||
|
public int InvalidQuantity { get; set; }
|
||||||
|
public decimal QualityRate { get; set; }
|
||||||
|
public double RunningTime { get; set; }
|
||||||
|
public double Efficiency { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatisticDimension
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Label { get; set; }
|
||||||
|
public List<string> Values { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatisticMetric
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Label { get; set; }
|
||||||
|
public string Formula { get; set; }
|
||||||
|
public string Unit { get; set; }
|
||||||
|
public string Format { get; set; }
|
||||||
|
public decimal Value { get; set; }
|
||||||
|
public decimal PreviousValue { get; set; }
|
||||||
|
public decimal ChangeRate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatisticViewModel
|
||||||
|
{
|
||||||
|
public int RuleId { get; set; }
|
||||||
|
public string RuleName { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public List<StatisticDimension> Dimensions { get; set; }
|
||||||
|
public List<StatisticMetric> Metrics { get; set; }
|
||||||
|
public List<StatisticResult> Results { get; set; }
|
||||||
|
public DateTime FromTime { get; set; }
|
||||||
|
public DateTime ToTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatisticFilter
|
||||||
|
{
|
||||||
|
public List<int> DeviceIds { get; set; }
|
||||||
|
public List<string> NCPrograms { get; set; }
|
||||||
|
public DateTime? StartDate { get; set; }
|
||||||
|
public DateTime? EndDate { get; set; }
|
||||||
|
public string GroupBy { get; set; } // device, program, date, hour
|
||||||
|
public int? RuleId { get; set; }
|
||||||
|
public int? TopCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace Haoliang.Models.User
|
||||||
|
{
|
||||||
|
public class UserSession
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public string SessionToken { get; set; }
|
||||||
|
public string DeviceInfo { get; set; }
|
||||||
|
public string IPAddress { get; set; }
|
||||||
|
public DateTime LoginTime { get; set; }
|
||||||
|
public DateTime? LastActivityTime { get; set; }
|
||||||
|
public DateTime? LogoutTime { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Permission
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Category { get; set; } // System, Device, Production, Template, User
|
||||||
|
public string Module { get; set; }
|
||||||
|
public string Action { get; set; }
|
||||||
|
public string Resource { get; set; }
|
||||||
|
public bool IsEnabled { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserPermission
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public int PermissionId { get; set; }
|
||||||
|
public User User { get; set; }
|
||||||
|
public Permission Permission { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RolePermission
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int RoleId { get; set; }
|
||||||
|
public int PermissionId { get; set; }
|
||||||
|
public Role Role { get; set; }
|
||||||
|
public Permission Permission { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JwtToken
|
||||||
|
{
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
public string RefreshToken { get; set; }
|
||||||
|
public DateTime ExpiresAt { get; set; }
|
||||||
|
public DateTime RefreshExpiresAt { get; set; }
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AuthResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Token { get; set; }
|
||||||
|
public User User { get; set; }
|
||||||
|
public List<string> Permissions { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public DateTime ExpiresAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserClaims
|
||||||
|
{
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string RealName { get; set; }
|
||||||
|
public int RoleId { get; set; }
|
||||||
|
public string RoleName { get; set; }
|
||||||
|
public List<string> Permissions { get; set; }
|
||||||
|
public List<int> DeviceIds { get; set; }
|
||||||
|
public DateTime SessionTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PasswordReset
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public string Token { get; set; }
|
||||||
|
public bool IsUsed { get; set; }
|
||||||
|
public DateTime ExpiresAt { get; set; }
|
||||||
|
public DateTime? UsedAt { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserViewModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string RealName { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string Phone { get; set; }
|
||||||
|
public string RoleName { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public DateTime? LastLoginTime { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public List<string> Permissions { get; set; }
|
||||||
|
public List<int> AssignedDevices { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,367 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Xunit;
|
||||||
|
using Haoliang.Models.Device;
|
||||||
|
using Haoliang.Models.DataCollection;
|
||||||
|
using Haoliang.Core.Services;
|
||||||
|
|
||||||
|
namespace Haoliang.Tests
|
||||||
|
{
|
||||||
|
public class DeviceCollectionServiceTests
|
||||||
|
{
|
||||||
|
private readonly IDeviceCollectionService _collectionService;
|
||||||
|
|
||||||
|
public DeviceCollectionServiceTests()
|
||||||
|
{
|
||||||
|
// 这里应该使用mock对象或测试数据库
|
||||||
|
_collectionService = new DeviceCollectionService(null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CollectDeviceDataAsync_ShouldParseValidJson()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sampleJson = @"{
|
||||||
|
""device"": ""FANUC_01"",
|
||||||
|
""desc"": ""CNC Machine"",
|
||||||
|
""tags"": [
|
||||||
|
{
|
||||||
|
""id"": ""_io_status"",
|
||||||
|
""desc"": ""I/O Status"",
|
||||||
|
""quality"": 0,
|
||||||
|
""value"": 1,
|
||||||
|
""time"": ""2024-01-01T10:00:00""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""id"": ""Tag5"",
|
||||||
|
""desc"": ""NC Program"",
|
||||||
|
""quality"": 0,
|
||||||
|
""value"": ""O1234"",
|
||||||
|
""time"": ""2024-01-01T10:00:00""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""id"": ""Tag8"",
|
||||||
|
""desc"": ""Cumulative Count"",
|
||||||
|
""quality"": 0,
|
||||||
|
""value"": 12345.00000,
|
||||||
|
""time"": ""2024-01-01T10:00:00""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _collectionService.CollectDeviceDataAsync(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("FANUC_01", result.DeviceCode);
|
||||||
|
Assert.Equal("O1234", result.NCProgram);
|
||||||
|
Assert.Equal(12345, result.CumulativeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CollectDeviceDataAsync_ShouldHandleInvalidJson()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var invalidJson = @"{
|
||||||
|
""device"": ""FANUC_01"",
|
||||||
|
""desc"": ""CNC Machine"",
|
||||||
|
""tags"": [
|
||||||
|
{
|
||||||
|
""id"": ""invalid_tag"",
|
||||||
|
""value"": ""invalid_value""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<JsonException>(() =>
|
||||||
|
_collectionService.CollectDeviceDataAsync(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PingDeviceAsync_ShouldReturnTrueForValidIp()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var validIp = "8.8.8.8"; // Google DNS
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _collectionService.PingDeviceAsync(validIp);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProductionServiceTests
|
||||||
|
{
|
||||||
|
private readonly IProductionService _productionService;
|
||||||
|
|
||||||
|
public ProductionServiceTests()
|
||||||
|
{
|
||||||
|
_productionService = new ProductionService(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CalculateProductionAsync_ShouldCalculateDifference()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var current = new DeviceCurrentStatus
|
||||||
|
{
|
||||||
|
DeviceId = 1,
|
||||||
|
NCProgram = "O1234",
|
||||||
|
CumulativeCount = 15000,
|
||||||
|
RecordTime = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
var last = new DeviceCurrentStatus
|
||||||
|
{
|
||||||
|
DeviceId = 1,
|
||||||
|
NCProgram = "O1234",
|
||||||
|
CumulativeCount = 12000,
|
||||||
|
RecordTime = DateTime.Now.AddMinutes(-5)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _productionService.CalculateProductionAsync(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3000, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CalculateProductionAsync_ShouldHandleNegativeValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var current = new DeviceCurrentStatus
|
||||||
|
{
|
||||||
|
DeviceId = 1,
|
||||||
|
NCProgram = "O1234",
|
||||||
|
CumulativeCount = 10000,
|
||||||
|
RecordTime = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
var last = new DeviceCurrentStatus
|
||||||
|
{
|
||||||
|
DeviceId = 1,
|
||||||
|
NCProgram = "O1234",
|
||||||
|
CumulativeCount = 12000, // 比当前值大,应该产生负数
|
||||||
|
RecordTime = DateTime.Now.AddMinutes(-5)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _productionService.CalculateProductionAsync(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, result); // 负数应该被保护为0
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CalculateProductionAsync_ShouldHandleProgramSwitch()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var current = new DeviceCurrentStatus
|
||||||
|
{
|
||||||
|
DeviceId = 1,
|
||||||
|
NCProgram = "O5678", // 不同的程序
|
||||||
|
CumulativeCount = 20000,
|
||||||
|
RecordTime = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
var last = new DeviceCurrentStatus
|
||||||
|
{
|
||||||
|
DeviceId = 1,
|
||||||
|
NCProgram = "O1234",
|
||||||
|
CumulativeCount = 15000,
|
||||||
|
RecordTime = DateTime.Now.AddMinutes(-5)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _productionService.CalculateProductionAsync(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(20000, result); // 新程序以当前累计数为起点
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlarmServiceTests
|
||||||
|
{
|
||||||
|
private readonly IAlarmService _alarmService;
|
||||||
|
|
||||||
|
public AlarmServiceTests()
|
||||||
|
{
|
||||||
|
_alarmService = new AlarmManager(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAlarmAsync_ShouldCreateAlarm()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var alarm = new Alarm
|
||||||
|
{
|
||||||
|
DeviceId = 1,
|
||||||
|
DeviceCode = "FANUC_01",
|
||||||
|
AlarmType = AlarmType.DeviceOffline,
|
||||||
|
Severity = AlarmSeverity.Critical,
|
||||||
|
Title = "Device Offline",
|
||||||
|
Description = "The device has been offline for more than 5 minutes",
|
||||||
|
AlarmStatus = AlarmStatus.Active
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _alarmService.CreateAlarmAsync(alarm);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(AlarmStatus.Active, result.AlarmStatus);
|
||||||
|
Assert.NotNull(result.CreateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResolveAlarmAsync_ShouldMarkAsResolved()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var alarm = new Alarm
|
||||||
|
{
|
||||||
|
DeviceId = 1,
|
||||||
|
DeviceCode = "FANUC_01",
|
||||||
|
AlarmType = AlarmType.DeviceOffline,
|
||||||
|
Severity = AlarmSeverity.Warning,
|
||||||
|
Title = "Device Offline",
|
||||||
|
Description = "The device has been offline",
|
||||||
|
AlarmStatus = AlarmStatus.Active
|
||||||
|
};
|
||||||
|
|
||||||
|
var createdAlarm = await _alarmService.CreateAlarmAsync(alarm);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _alarmService.ResolveAlarmAsync(createdAlarm.AlarmId, "Device reconnected");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
|
||||||
|
// 验证状态确实改变了
|
||||||
|
var resolvedAlarm = await _alarmService.GetAlarmByIdAsync(createdAlarm.AlarmId);
|
||||||
|
Assert.Equal(AlarmStatus.Resolved, resolvedAlarm.AlarmStatus);
|
||||||
|
Assert.NotNull(resolvedAlarm.ResolvedTime);
|
||||||
|
Assert.Equal("Device reconnected", resolvedAlarm.ResolutionNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TemplateServiceTests
|
||||||
|
{
|
||||||
|
private readonly ITemplateService _templateService;
|
||||||
|
|
||||||
|
public TemplateServiceTests()
|
||||||
|
{
|
||||||
|
_templateService = new TemplateManager(null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateTemplateAsync_ShouldCreateValidTemplate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var template = new CNCBrandTemplate
|
||||||
|
{
|
||||||
|
TemplateName = "FANUC Standard",
|
||||||
|
BrandName = "FANUC",
|
||||||
|
Description = "Standard FANUC template",
|
||||||
|
IsEnabled = true,
|
||||||
|
Version = "1.0",
|
||||||
|
TemplateJson = @"{
|
||||||
|
""device"": {
|
||||||
|
""status"": ""_io_status""
|
||||||
|
},
|
||||||
|
""production"": {
|
||||||
|
""program"": ""Tag5"",
|
||||||
|
""count"": ""Tag8""
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _templateService.CreateTemplateAsync(template);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("FANUC Standard", result.TemplateName);
|
||||||
|
Assert.True(result.IsEnabled);
|
||||||
|
Assert.NotNull(result.CreateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidateTemplateAsync_ShouldRejectInvalidTemplate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var invalidTemplate = new CNCBrandTemplate
|
||||||
|
{
|
||||||
|
TemplateName = "", // 空模板名
|
||||||
|
BrandName = "FANUC",
|
||||||
|
Description = "Invalid template",
|
||||||
|
IsEnabled = true,
|
||||||
|
Version = "1.0",
|
||||||
|
TemplateJson = "" // 空JSON
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _templateService.ValidateTemplateAsync(invalidTemplate);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemServiceTests
|
||||||
|
{
|
||||||
|
private readonly ISystemConfigService _configService;
|
||||||
|
private readonly ILoggingService _loggingService;
|
||||||
|
|
||||||
|
public SystemServiceTests()
|
||||||
|
{
|
||||||
|
_configService = new SystemConfigManager(null, null);
|
||||||
|
_loggingService = new LoggingManager(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConfigAsync_ShouldReturnExistingConfig()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await _configService.SetConfigAsync("test.config", "test.value");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _configService.GetConfigAsync("test.config");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("test.value", result.ConfigValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SetConfigAsync_ShouldUpdateConfig()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var newConfig = await _configService.SetConfigAsync("test.config", "new.value");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _configService.GetConfigAsync("test.config");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("new.value", result.ConfigValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LogAsync_ShouldLogMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = "Test log message";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _loggingService.LogAsync(LogLevel.Information, message);
|
||||||
|
|
||||||
|
// 这里可以添加数据库验证,检查日志是否正确存储
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue