完成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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Haoliang.Core.Services;
|
||||
using Haoliang.Models.Device;
|
||||
using Haoliang.Data.Repositories;
|
||||
using Haoliang.Models.DataCollection;
|
||||
using Haoliang.Models.System;
|
||||
using Haoliang.Models.Template;
|
||||
|
||||
namespace Haoliang.Api.Controllers
|
||||
{
|
||||
[Route("api/v1/devices")]
|
||||
[ApiController]
|
||||
[EnableCors("AllowAll")]
|
||||
[Route("api/v1/[controller]")]
|
||||
public class DeviceController : ControllerBase
|
||||
{
|
||||
private readonly DeviceRepository _deviceRepository;
|
||||
private readonly DeviceStatusRepository _statusRepository;
|
||||
|
||||
public DeviceController(DeviceRepository deviceRepository, DeviceStatusRepository statusRepository)
|
||||
private readonly IDeviceCollectionService _collectionService;
|
||||
private readonly IProductionService _productionService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
private readonly ITemplateService _templateService;
|
||||
private readonly ISystemConfigService _configService;
|
||||
private readonly ILoggingService _loggingService;
|
||||
|
||||
public DeviceController(
|
||||
IDeviceCollectionService collectionService,
|
||||
IProductionService productionService,
|
||||
IAlarmService alarmService,
|
||||
ITemplateService templateService,
|
||||
ISystemConfigService configService,
|
||||
ILoggingService loggingService)
|
||||
{
|
||||
_deviceRepository = deviceRepository;
|
||||
_statusRepository = statusRepository;
|
||||
_collectionService = collectionService;
|
||||
_productionService = productionService;
|
||||
_alarmService = alarmService;
|
||||
_templateService = templateService;
|
||||
_configService = configService;
|
||||
_loggingService = loggingService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetAllDevices()
|
||||
public async Task<IActionResult> GetAllDevices()
|
||||
{
|
||||
try
|
||||
{
|
||||
var devices = _deviceRepository.GetAllDevices();
|
||||
return Ok(new
|
||||
{
|
||||
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
|
||||
});
|
||||
var devices = await _collectionService.GetAllDevicesAsync();
|
||||
return Ok(ApiResponse.Success(devices));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"获取设备列表失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync("Failed to get devices", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to get devices"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult GetDevice(int id)
|
||||
public async Task<IActionResult> GetDevice(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = _deviceRepository.GetDeviceById(id);
|
||||
var device = await _collectionService.GetDeviceByIdAsync(id);
|
||||
if (device == null)
|
||||
{
|
||||
return NotFound(new
|
||||
{
|
||||
success = false,
|
||||
message = "设备不存在",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
return NotFound(ApiResponse.NotFound("Device not found"));
|
||||
}
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = device,
|
||||
message = "获取成功",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
return Ok(ApiResponse.Success(device));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"获取设备失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync($"Failed to get device {id}", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to get device"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult CreateDevice([FromBody] CNCDevice device)
|
||||
public async Task<IActionResult> CreateDevice([FromBody] CNCDevice device)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证设备编号唯一性
|
||||
var existingDevice = _deviceRepository.GetDeviceByCode(device.DeviceCode);
|
||||
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
|
||||
});
|
||||
var createdDevice = await _collectionService.CreateDeviceAsync(device);
|
||||
return Ok(ApiResponse.Success(createdDevice, "Device created successfully"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"创建设备失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync("Failed to create device", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to create device"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public IActionResult UpdateDevice(int id, [FromBody] CNCDevice device)
|
||||
public async Task<IActionResult> UpdateDevice(int id, [FromBody] CNCDevice device)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existingDevice = _deviceRepository.GetDeviceById(id);
|
||||
if (existingDevice == null)
|
||||
device.DeviceId = id;
|
||||
var updatedDevice = await _collectionService.UpdateDeviceAsync(device);
|
||||
if (updatedDevice == null)
|
||||
{
|
||||
return NotFound(new
|
||||
{
|
||||
success = false,
|
||||
message = "设备不存在",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
return NotFound(ApiResponse.NotFound("Device not found"));
|
||||
}
|
||||
|
||||
device.Id = id;
|
||||
device.UpdatedAt = DateTime.Now;
|
||||
var updatedDevice = _deviceRepository.UpdateDevice(device);
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = updatedDevice,
|
||||
message = "更新成功",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
return Ok(ApiResponse.Success(updatedDevice, "Device updated successfully"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"更新设备失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync($"Failed to update device {id}", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to update device"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public IActionResult DeleteDevice(int id)
|
||||
public async Task<IActionResult> DeleteDevice(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = _deviceRepository.GetDeviceById(id);
|
||||
if (device == null)
|
||||
var result = await _collectionService.DeleteDeviceAsync(id);
|
||||
if (!result)
|
||||
{
|
||||
return NotFound(new
|
||||
{
|
||||
success = false,
|
||||
message = "设备不存在",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
return NotFound(ApiResponse.NotFound("Device not found"));
|
||||
}
|
||||
|
||||
_deviceRepository.DeleteDevice(id);
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
message = "删除成功",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
return Ok(ApiResponse.Success(null, "Device deleted successfully"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"删除设备失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync($"Failed to delete device {id}", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to delete device"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}/status")]
|
||||
public IActionResult GetDeviceStatus(int id)
|
||||
[HttpPost("{id}/collect")]
|
||||
public async Task<IActionResult> CollectDeviceData(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = _deviceRepository.GetDeviceById(id);
|
||||
if (device == null)
|
||||
{
|
||||
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
|
||||
});
|
||||
await _collectionService.CollectDeviceAsync(id);
|
||||
return Ok(ApiResponse.Success(null, "Data collection started"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"获取设备状态失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync($"Failed to collect data for device {id}", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to collect data"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}/status-history")]
|
||||
public IActionResult GetDeviceStatusHistory(int id, DateTime? startTime = null, DateTime? endTime = null)
|
||||
[HttpPost("collect-all")]
|
||||
public async Task<IActionResult> CollectAllDevices()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statuses = _statusRepository.GetDeviceStatuses(id, startTime, endTime);
|
||||
return Ok(new
|
||||
{
|
||||
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
|
||||
});
|
||||
await _collectionService.CollectAllDevicesAsync();
|
||||
return Ok(ApiResponse.Success(null, "All devices data collection started"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"获取设备状态历史失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync("Failed to collect data for all devices", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to collect data"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("online")]
|
||||
public IActionResult GetOnlineDevices()
|
||||
[HttpGet("{id}/status")]
|
||||
public async Task<IActionResult> GetDeviceStatus(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var devices = _deviceRepository.GetOnlineDevices();
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = devices,
|
||||
message = "获取成功",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
var status = await _collectionService.GetDeviceStatusAsync(id);
|
||||
return Ok(ApiResponse.Success(status));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"获取在线设备失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync($"Failed to get device status for {id}", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to get device status"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("available")]
|
||||
public IActionResult GetAvailableDevices()
|
||||
[HttpGet("{id}/production")]
|
||||
public async Task<IActionResult> GetDeviceProduction(int id, [FromQuery] DateTime date)
|
||||
{
|
||||
try
|
||||
{
|
||||
var devices = _deviceRepository.GetAvailableDevices();
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = devices,
|
||||
message = "获取成功",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
if (date == default)
|
||||
date = DateTime.Today;
|
||||
|
||||
var production = await _productionService.GetTodayProductionAsync(id);
|
||||
return Ok(ApiResponse.Success(production));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"获取可用设备失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync($"Failed to get production for device {id}", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to get production data"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{id}/toggle-status")]
|
||||
public IActionResult ToggleDeviceStatus(int id, [FromBody] bool isAvailable)
|
||||
[HttpGet("{id}/alarms")]
|
||||
public async Task<IActionResult> GetDeviceAlarms(int id, [FromQuery] int days = 7)
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = _deviceRepository.GetDeviceById(id);
|
||||
if (device == null)
|
||||
{
|
||||
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
|
||||
});
|
||||
var alarms = await _alarmService.GetDeviceAlarmsAsync(id, days);
|
||||
return Ok(ApiResponse.Success(alarms));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"更新设备状态失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync($"Failed to get alarms for device {id}", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to get alarms"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{id}/collect")]
|
||||
public IActionResult TriggerCollection(int id)
|
||||
[HttpGet("{id}/health")]
|
||||
public async Task<IActionResult> GetDeviceHealth(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = _deviceRepository.GetDeviceById(id);
|
||||
if (device == null)
|
||||
{
|
||||
return NotFound(new
|
||||
{
|
||||
success = false,
|
||||
message = "设备不存在",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
// 这里应该触发数据采集逻辑
|
||||
// 简化实现,返回成功响应
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
message = "采集任务已触发",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
var health = await _collectionService.GetDeviceHealthAsync(id);
|
||||
return Ok(ApiResponse.Success(health));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new
|
||||
{
|
||||
success = false,
|
||||
message = $"触发采集任务失败: {ex.Message}",
|
||||
timestamp = DateTime.Now
|
||||
});
|
||||
await _loggingService.LogErrorAsync($"Failed to get health for device {id}", ex);
|
||||
return StatusCode(500, ApiResponse.Error("Failed to get device health"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -1,53 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Haoliang.Data.Entities;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
// Configure Database Connection
|
||||
var connectionString = builder.Configuration.GetConnectionString("CNCBusinessDB")
|
||||
?? "server=localhost;database=cnc_business;user=root;password=;";
|
||||
builder.Services.AddDbContext<CNCBusinessDbContext>(options =>
|
||||
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
|
||||
|
||||
builder.Services.AddDbContext<CNCLLogDbContext>(options =>
|
||||
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Haoliang.Api;
|
||||
|
||||
namespace Haoliang.Api
|
||||
{
|
||||
options.AddPolicy("AllowAll", builder =>
|
||||
public class Program
|
||||
{
|
||||
builder.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
// Add repositories as services
|
||||
builder.Services.AddScoped<Haoliang.Data.Repositories.DeviceRepository>();
|
||||
builder.Services.AddScoped<Haoliang.Data.Repositories.DeviceStatusRepository>();
|
||||
builder.Services.AddScoped<Haoliang.Data.Repositories.DeviceCurrentStatusRepository>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCors("AllowAll");
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Haoliang.Data.Entities;
|
||||
using System.Threading.Tasks;
|
||||
using Haoliang.Models.Device;
|
||||
using 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();
|
||||
Task<CNCDevice> GetByDeviceCodeAsync(string deviceCode);
|
||||
Task<IEnumerable<CNCDevice>> GetByTemplateIdAsync(int templateId);
|
||||
Task<bool> DeviceExistsAsync(string deviceCode);
|
||||
Task UpdateDeviceStatusAsync(int deviceId, bool isOnline, bool isAvailable);
|
||||
Task<int> CountOnlineDevicesAsync();
|
||||
Task<int> CountAvailableDevicesAsync();
|
||||
}
|
||||
|
||||
public class DeviceRepository : Repository<CNCDevice>, IDeviceRepository
|
||||
{
|
||||
private readonly CNCDbContext _context;
|
||||
|
||||
public DeviceRepository(CNCBusinessDbContext context)
|
||||
public DeviceRepository(CNCDbContext context) : base(context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public List<CNCDevice> GetAllDevices()
|
||||
public async Task<IEnumerable<CNCDevice>> GetOnlineDevicesAsync()
|
||||
{
|
||||
return _context.Devices.ToList();
|
||||
return await _context.Devices
|
||||
.Where(d => d.IsOnline)
|
||||
.OrderBy(d => d.DeviceName)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public CNCDevice GetDeviceById(int id)
|
||||
public async Task<IEnumerable<CNCDevice>> GetAvailableDevicesAsync()
|
||||
{
|
||||
return _context.Devices.Find(id);
|
||||
return await _context.Devices
|
||||
.Where(d => d.IsAvailable)
|
||||
.OrderBy(d => d.DeviceName)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public CNCDevice GetDeviceByCode(string deviceCode)
|
||||
public async Task<CNCDevice> GetByDeviceCodeAsync(string deviceCode)
|
||||
{
|
||||
return _context.Devices.FirstOrDefault(d => d.DeviceCode == deviceCode);
|
||||
return await _context.Devices
|
||||
.FirstOrDefaultAsync(d => d.DeviceCode == deviceCode);
|
||||
}
|
||||
|
||||
public CNCDevice CreateDevice(CNCDevice device)
|
||||
public async Task<IEnumerable<CNCDevice>> GetByTemplateIdAsync(int templateId)
|
||||
{
|
||||
_context.Devices.Add(device);
|
||||
_context.SaveChanges();
|
||||
return device;
|
||||
return await _context.Devices
|
||||
.Where(d => d.TemplateId == templateId)
|
||||
.OrderBy(d => d.DeviceName)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public CNCDevice UpdateDevice(CNCDevice device)
|
||||
public async Task<bool> DeviceExistsAsync(string deviceCode)
|
||||
{
|
||||
_context.Entry(device).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
_context.SaveChanges();
|
||||
return device;
|
||||
return await _context.Devices
|
||||
.AnyAsync(d => d.DeviceCode == deviceCode);
|
||||
}
|
||||
|
||||
public void DeleteDevice(int id)
|
||||
public async Task UpdateDeviceStatusAsync(int deviceId, bool isOnline, bool isAvailable)
|
||||
{
|
||||
var device = _context.Devices.Find(id);
|
||||
var device = await GetByIdAsync(deviceId);
|
||||
if (device != null)
|
||||
{
|
||||
_context.Devices.Remove(device);
|
||||
_context.SaveChanges();
|
||||
device.IsOnline = isOnline;
|
||||
device.IsAvailable = isAvailable;
|
||||
|
||||
if (isOnline)
|
||||
{
|
||||
device.LastCollectionTime = DateTime.Now;
|
||||
}
|
||||
|
||||
Update(device);
|
||||
await SaveAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public List<CNCDevice> GetOnlineDevices()
|
||||
public async Task<int> CountOnlineDevicesAsync()
|
||||
{
|
||||
return _context.Devices.Where(d => d.IsOnline).ToList();
|
||||
return await _context.Devices
|
||||
.CountAsync(d => d.IsOnline);
|
||||
}
|
||||
|
||||
public List<CNCDevice> GetAvailableDevices()
|
||||
public async Task<int> CountAvailableDevicesAsync()
|
||||
{
|
||||
return _context.Devices.Where(d => d.IsAvailable).ToList();
|
||||
}
|
||||
|
||||
public void UpdateDeviceStatus(int deviceId, bool isOnline)
|
||||
{
|
||||
var device = _context.Devices.Find(deviceId);
|
||||
if (device != null)
|
||||
{
|
||||
device.IsOnline = isOnline;
|
||||
device.UpdatedAt = DateTime.Now;
|
||||
_context.SaveChanges();
|
||||
}
|
||||
return await _context.Devices
|
||||
.CountAsync(d => d.IsAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateLastCollectionTime(int deviceId, DateTime collectionTime)
|
||||
{
|
||||
var device = _context.Devices.Find(deviceId);
|
||||
if (device != null)
|
||||
{
|
||||
device.LastCollectionTime = collectionTime;
|
||||
device.UpdatedAt = DateTime.Now;
|
||||
_context.SaveChanges();
|
||||
}
|
||||
}
|
||||
public interface IDeviceStatusRepository : IRepository<DeviceStatus>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public class DeviceStatusRepository
|
||||
public class DeviceStatusRepository : Repository<DeviceStatus>, IDeviceStatusRepository
|
||||
{
|
||||
private readonly CNCBusinessDbContext _context;
|
||||
private readonly CNCDbContext _context;
|
||||
|
||||
public DeviceStatusRepository(CNCBusinessDbContext context)
|
||||
public DeviceStatusRepository(CNCDbContext context) : base(context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public List<DeviceStatus> GetDeviceStatuses(int deviceId, DateTime? startTime = null, DateTime? endTime = null)
|
||||
public async Task<IEnumerable<DeviceStatus>> GetLatestStatusAsync(int deviceId, int count = 10)
|
||||
{
|
||||
var query = _context.DeviceStatus.Where(ds => ds.DeviceId == deviceId);
|
||||
|
||||
if (startTime.HasValue)
|
||||
{
|
||||
query = query.Where(ds => ds.RecordTime >= startTime.Value);
|
||||
}
|
||||
|
||||
if (endTime.HasValue)
|
||||
{
|
||||
query = query.Where(ds => ds.RecordTime <= endTime.Value);
|
||||
}
|
||||
|
||||
return query.OrderByDescending(ds => ds.RecordTime).ToList();
|
||||
}
|
||||
|
||||
public DeviceStatus GetLatestDeviceStatus(int deviceId)
|
||||
{
|
||||
return _context.DeviceStatus
|
||||
return await _context.DeviceStatus
|
||||
.Where(ds => ds.DeviceId == deviceId)
|
||||
.OrderByDescending(ds => ds.RecordTime)
|
||||
.FirstOrDefault();
|
||||
.Take(count)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public DeviceStatus CreateDeviceStatus(DeviceStatus status)
|
||||
public async Task<DeviceStatus> GetLatestStatusByDeviceIdAsync(int deviceId)
|
||||
{
|
||||
_context.DeviceStatus.Add(status);
|
||||
_context.SaveChanges();
|
||||
return status;
|
||||
}
|
||||
|
||||
public void DeleteOldDeviceStatuses(int deviceId, DateTime cutoffDate)
|
||||
{
|
||||
var oldStatuses = _context.DeviceStatus
|
||||
.Where(ds => ds.DeviceId == deviceId && ds.RecordTime < cutoffDate)
|
||||
.ToList();
|
||||
|
||||
_context.DeviceStatus.RemoveRange(oldStatuses);
|
||||
_context.SaveChanges();
|
||||
return await _context.DeviceStatus
|
||||
.Where(ds => ds.DeviceId == deviceId)
|
||||
.OrderByDescending(ds => ds.RecordTime)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceCurrentStatusRepository
|
||||
{
|
||||
private readonly CNCBusinessDbContext _context;
|
||||
private readonly DeviceStatusRepository _statusRepository;
|
||||
|
||||
public DeviceCurrentStatusRepository(CNCBusinessDbContext context)
|
||||
public async Task<IEnumerable<DeviceStatus>> GetStatusByDateRangeAsync(int deviceId, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
_context = context;
|
||||
_statusRepository = new DeviceStatusRepository(context);
|
||||
return await _context.DeviceStatus
|
||||
.Where(ds => ds.DeviceId == deviceId &&
|
||||
ds.RecordTime >= startDate &&
|
||||
ds.RecordTime <= endDate)
|
||||
.OrderBy(ds => ds.RecordTime)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public DeviceCurrentStatus GetDeviceCurrentStatus(int deviceId)
|
||||
public async Task<int> CountStatusRecordsAsync(int deviceId)
|
||||
{
|
||||
var device = _context.Devices.Find(deviceId);
|
||||
if (device == null) return null;
|
||||
|
||||
var latestStatus = _statusRepository.GetLatestDeviceStatus(deviceId);
|
||||
|
||||
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>() // 这里需要根据实际数据填充
|
||||
};
|
||||
return await _context.DeviceStatus
|
||||
.CountAsync(ds => ds.DeviceId == deviceId);
|
||||
}
|
||||
|
||||
public List<DeviceCurrentStatus> GetAllDeviceCurrentStatuses()
|
||||
public async Task DeleteOldStatusRecordsAsync(int deviceId, int keepDays = 30)
|
||||
{
|
||||
var devices = _context.Devices.ToList();
|
||||
var statuses = new List<DeviceCurrentStatus>();
|
||||
var cutoffDate = DateTime.Now.AddDays(-keepDays);
|
||||
var oldRecords = await _context.DeviceStatus
|
||||
.Where(ds => ds.DeviceId == deviceId && ds.RecordTime < cutoffDate)
|
||||
.ToListAsync();
|
||||
|
||||
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