diff --git a/src/CncService/CncService.csproj b/src/CncService/CncService.csproj index 2296ca9..2423637 100644 --- a/src/CncService/CncService.csproj +++ b/src/CncService/CncService.csproj @@ -13,6 +13,11 @@ + + + + + diff --git a/src/CncService/Impl/AlertService.cs b/src/CncService/Impl/AlertService.cs new file mode 100644 index 0000000..03d3d60 --- /dev/null +++ b/src/CncService/Impl/AlertService.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using CncService.Interface; +using CncModels.Dto.Alert; +using CncModels.Dto; +using CncModels.Constants; +using CncRepository.Interface; + +namespace CncService.Impl +{ + /// + /// 告警管理实现 + /// + public class AlertService : IAlertService + { + private readonly IAlertRepository _alertRepository; + + public AlertService(IAlertRepository alertRepository) + { + _alertRepository = alertRepository ?? throw new ArgumentNullException(nameof(alertRepository)); + } + + /// + public PagedResult GetList(AlertQuery query) + { + if (query == null) throw new BusinessException(ErrorCode.BadRequest, "查询参数不能为空"); + return _alertRepository.GetList(query); + } + + /// + public bool Resolve(long id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的告警ID"); + return _alertRepository.Resolve(id); + } + + /// + public int BatchResolve(List ids) + { + if (ids == null || ids.Count == 0) throw new BusinessException(ErrorCode.BadRequest, "无效的ID列表"); + return _alertRepository.BatchResolve(ids); + } + + /// + public AlertStatisticsResponse GetStatistics() + { + return _alertRepository.GetStatistics(); + } + } +} diff --git a/src/CncService/Impl/AuthService.cs b/src/CncService/Impl/AuthService.cs new file mode 100644 index 0000000..e7cdd75 --- /dev/null +++ b/src/CncService/Impl/AuthService.cs @@ -0,0 +1,101 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using BCrypt.Net; +using CncModels.Constants; +using CncModels.Dto.Login; +using CncService.Interface; +using CncService; +using CncRepository.Interface; +using System.Linq; + +namespace CncService.Impl +{ + /// + /// 登录认证实现 + /// + public class AuthService : IAuthService + { + private readonly ISysConfigRepository _sysConfigRepository; + private readonly string _jwtSecret; + + public AuthService(ISysConfigRepository sysConfigRepository, string jwtSecret) + { + _sysConfigRepository = sysConfigRepository ?? throw new ArgumentNullException(nameof(sysConfigRepository)); + _jwtSecret = jwtSecret ?? throw new ArgumentNullException(nameof(jwtSecret)); + } + + /// + public LoginResponse Login(LoginRequest request) + { + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数错误"); + // 读取管理员用户名与哈希 + var userCfg = _sysConfigRepository.GetByKey("admin_username"); + var pwdCfg = _sysConfigRepository.GetByKey("admin_password_hash"); + + var usernameStored = userCfg?.ConfigValue; + var passwordHash = pwdCfg?.ConfigValue; + + if (string.IsNullOrWhiteSpace(usernameStored) || string.IsNullOrWhiteSpace(passwordHash)) + { + throw new BusinessException(ErrorCode.BadRequest, "用户名或密码错误"); + } + + if (!string.Equals(request.Username, usernameStored, StringComparison.OrdinalIgnoreCase)) + { + throw new BusinessException(ErrorCode.BadRequest, "用户名或密码错误"); + } + + // 验证密码 + if (!BCrypt.Net.BCrypt.Verify(request.Password ?? string.Empty, passwordHash)) + { + throw new BusinessException(ErrorCode.BadRequest, "用户名或密码错误"); + } + + // 过期时间 + int expiresIn = (request.RememberMe ?? false) ? 24 * 3600 : 8 * 3600; + + // 生成简易 JWT + var token = GenerateJwtToken(request.Username, expiresIn); + + return new LoginResponse + { + Token = token, + ExpiresIn = expiresIn + }; + } + + private string GenerateJwtToken(string username, int expiresInSeconds) + { + // Header + const string headerJson = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; + long exp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + expiresInSeconds; + // Payload + string payloadJson = $"{{\"sub\":\"admin\",\"name\":\"{Escape(username)}\",\"exp\":{exp}}}"; + + string header = Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson)); + string payload = Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson)); + string unsigned = header + "." + payload; + + // Signature + using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_jwtSecret))) + { + var sig = hmac.ComputeHash(Encoding.UTF8.GetBytes(unsigned)); + string signature = Base64UrlEncode(sig); + return unsigned + "." + signature; + } + } + + private string Escape(string input) + { + if (string.IsNullOrEmpty(input)) return string.Empty; + return input.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + + private string Base64UrlEncode(byte[] input) + { + var base64 = Convert.ToBase64String(input); + return base64.Replace("+", "-").Replace("/", "_").TrimEnd('='); + } + } +} diff --git a/src/CncService/Impl/BrandService.cs b/src/CncService/Impl/BrandService.cs new file mode 100644 index 0000000..5d8b17f --- /dev/null +++ b/src/CncService/Impl/BrandService.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CncModels.Dto.Brand; +using CncModels.Entity; +using CncRepository.Interface; +using CncService.Interface; +using CncService; +using CncModels.Constants; + +namespace CncService.Impl +{ + /// + /// 品牌模板实现 + /// + public class BrandService : IBrandService + { + private readonly IBrandRepository _brandRepository; + private readonly IBrandFieldMappingRepository _mappingRepository; + private readonly ICollectAddressRepository _collectAddressRepository; + + public BrandService(IBrandRepository brandRepository, + IBrandFieldMappingRepository mappingRepository, + ICollectAddressRepository collectAddressRepository) + { + _brandRepository = brandRepository; + _mappingRepository = mappingRepository; + _collectAddressRepository = collectAddressRepository; + } + + public List GetList() + { + var brands = _brandRepository.GetAll(); + return brands.Select(b => new BrandListItem + { + Id = b.Id, + BrandName = b.BrandName, + DeviceField = b.DeviceField, + TagsPath = b.TagsPath, + IsEnabled = b.IsEnabled, + FieldCount = _brandRepository.GetFieldMappingCount(b.Id) + }).ToList(); + } + + public BrandDetailResponse GetById(int id) + { + var brand = _brandRepository.GetById(id); + if (brand == null) throw new BusinessException(ErrorCode.NotFound, "品牌不存在"); + + var mappings = _mappingRepository.GetByBrandId(id); + var detail = new BrandDetailResponse + { + Id = brand.Id, + BrandName = brand.BrandName, + DeviceField = brand.DeviceField, + TagsPath = brand.TagsPath, + IsEnabled = brand.IsEnabled, + FieldCount = mappings?.Count ?? 0, + Mappings = mappings?.Select(m => new BrandFieldMappingDto + { + StandardField = m.StandardField, + FieldName = m.FieldName, + MatchBy = m.MatchBy, + DataType = m.DataType, + IsRequired = m.IsRequired + }).ToList() ?? new List() + }; + return detail; + } + + public int Create(CreateBrandRequest request) + { + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数错误"); + // 验证唯一性 + if (_brandRepository.GetAll().Any(b => string.Equals(b.BrandName, request.BrandName, StringComparison.OrdinalIgnoreCase))) + { + throw new BusinessException(ErrorCode.Conflict, "品牌名称已存在"); + } + + var entity = new Brand + { + BrandName = request.BrandName, + DeviceField = request.DeviceField, + TagsPath = request.TagsPath, + IsEnabled = 1, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + return _brandRepository.Create(entity); + } + + public bool Update(int id, UpdateBrandRequest request) + { + var brand = _brandRepository.GetById(id); + if (brand == null) throw new BusinessException(ErrorCode.NotFound, "品牌不存在"); + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数错误"); + + brand.BrandName = request.BrandName ?? brand.BrandName; + brand.DeviceField = request.DeviceField ?? brand.DeviceField; + brand.TagsPath = request.TagsPath ?? brand.TagsPath; + brand.UpdatedAt = DateTime.Now; + + return _brandRepository.Update(brand); + } + + public bool Delete(int id) + { + var brand = _brandRepository.GetById(id); + if (brand == null) throw new BusinessException(ErrorCode.NotFound, "品牌不存在"); + + // 删除前检查是否有关联的采集地址 + var countCollectAddress = _collectAddressRepository.GetList(new CncModels.Dto.CollectAddress.CollectAddressQuery { BrandId = id }).Total; + if (countCollectAddress > 0) return false; + + return _brandRepository.Delete(id); + } + + public bool ToggleEnabled(int id) + { + return _brandRepository.ToggleEnabled(id); + } + + public int Copy(int id) + { + var brand = _brandRepository.GetById(id); + if (brand == null) throw new BusinessException(ErrorCode.NotFound, "品牌不存在"); + + var newBrand = new Brand + { + BrandName = brand.BrandName + "_Copy", + DeviceField = brand.DeviceField, + TagsPath = brand.TagsPath, + IsEnabled = brand.IsEnabled, + CreatedAt = DateTime.Now, + }; + int newBrandId = _brandRepository.Create(newBrand); + + // 复制字段映射 + var mappings = _mappingRepository.GetByBrandId(id); + if (mappings != null && mappings.Count > 0) + { + var newMappings = mappings.Select(m => new BrandFieldMapping + { + BrandId = newBrandId, + StandardField = m.StandardField, + FieldName = m.FieldName, + MatchBy = m.MatchBy, + DataType = m.DataType, + IsRequired = m.IsRequired, + CreatedAt = DateTime.Now + }).ToList(); + _mappingRepository.BatchCreate(newBrandId, newMappings); + } + return newBrandId; + } + + public List GetStandardFields() + { + // 硬编码的16个标准字段示例 + var list = new List(); + for (int i = 1; i <= 16; i++) + { + list.Add(new StandardFieldResponse + { + StandardField = $"Field{i}", + Description = $"标准字段描述{i}" + }); + } + return list; + } + } +} diff --git a/src/CncService/Impl/CollectAddressService.cs b/src/CncService/Impl/CollectAddressService.cs new file mode 100644 index 0000000..0f94f91 --- /dev/null +++ b/src/CncService/Impl/CollectAddressService.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using CncModels.Dto; +using CncModels.Dto.CollectAddress; +using CncRepository.Interface; +using CncService.Interface; +using CncService; +using CncModels.Entity; + +namespace CncService.Impl +{ + /// + /// 采集地址实现 + /// + public class CollectAddressService : ICollectAddressService + { + private readonly ICollectAddressRepository _collectAddressRepository; + private readonly IMachineRepository _machineRepository; + private readonly IBrandRepository _brandRepository; + + public CollectAddressService(ICollectAddressRepository collectAddressRepository, + IMachineRepository machineRepository, + IBrandRepository brandRepository) + { + _collectAddressRepository = collectAddressRepository; + _machineRepository = machineRepository; + _brandRepository = brandRepository; + } + + public PagedResult GetList(CollectAddressQuery query) + { + return _collectAddressRepository.GetList(query); + } + + public CollectAddressDetailResponse GetById(int id) + { + var detail = _collectAddressRepository.GetById(id); + if (detail == null) throw new BusinessException(CncModels.Constants.ErrorCode.NotFound, "采集地址不存在"); + string brandName = null; + var brand = _brandRepository.GetById(detail.BrandId); + if (brand != null) brandName = brand.BrandName; + // 直接映射到输出 DTO + return new CollectAddressDetailResponse + { + Id = detail.Id, + Name = detail.Name, + Url = detail.Url, + BrandId = detail.BrandId, + BrandName = brandName, + CollectInterval = detail.CollectInterval, + IsEnabled = detail.IsEnabled + }; + } + + public int Create(CreateCollectAddressRequest request) + { + if (request == null) throw new CncService.BusinessException(CncModels.Constants.ErrorCode.BadRequest, "请求参数错误"); + if (_brandRepository.GetById(request.BrandId) == null) throw new CncService.BusinessException(CncModels.Constants.ErrorCode.NotFound, "品牌不存在"); + var entity = new CollectAddress + { + Name = request.Name, + Url = request.Url, + BrandId = request.BrandId, + CollectInterval = request.CollectInterval, + IsEnabled = 1, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + return _collectAddressRepository.Create(entity); + } + + public bool Update(int id, UpdateCollectAddressRequest request) + { + var address = _collectAddressRepository.GetById(id); + if (address == null) throw new CncService.BusinessException(CncModels.Constants.ErrorCode.NotFound, "采集地址不存在"); + if (request == null) throw new CncService.BusinessException(CncModels.Constants.ErrorCode.BadRequest, "请求参数错误"); + + address.Name = request.Name ?? address.Name; + address.Url = request.Url ?? address.Url; + address.BrandId = (request.BrandId != 0) ? request.BrandId : address.BrandId; + address.CollectInterval = (request.CollectInterval != 0) ? request.CollectInterval : address.CollectInterval; + address.UpdatedAt = DateTime.Now; + return _collectAddressRepository.Update(address); + } + + public bool Delete(int id) + { + // 检查关联机床数量 + if (_collectAddressRepository.GetMachineCount(id) > 0) return false; + return _collectAddressRepository.Delete(id); + } + + public bool ToggleEnabled(int id) + { + return _collectAddressRepository.ToggleEnabled(id); + } + } +} diff --git a/src/CncService/Impl/CollectDataService.cs b/src/CncService/Impl/CollectDataService.cs new file mode 100644 index 0000000..fd97ef2 --- /dev/null +++ b/src/CncService/Impl/CollectDataService.cs @@ -0,0 +1,38 @@ +using System; +using CncService.Interface; +using CncModels.Dto; +using CncModels.Entity; +using CncModels.Constants; +using CncRepository.Interface; + +namespace CncService.Impl +{ + /// + /// 采集数据查询实现 + /// + public class CollectDataService : ICollectDataService + { + private readonly ICollectRawRepository _collectRawRepository; + + public CollectDataService(ICollectRawRepository collectRawRepository) + { + _collectRawRepository = collectRawRepository ?? throw new ArgumentNullException(nameof(collectRawRepository)); + } + + /// + public PagedResult GetRawByAddress(int addressId, int page, int pageSize) + { + if (addressId <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的地址ID"); + if (page <= 0) throw new BusinessException(ErrorCode.BadRequest, "页码无效"); + if (pageSize <= 0) throw new BusinessException(ErrorCode.BadRequest, "页大小无效"); + return _collectRawRepository.GetByAddressId(addressId, page, pageSize); + } + + /// + public CollectRaw GetLatestRaw(int addressId) + { + if (addressId <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的地址ID"); + return _collectRawRepository.GetLatestByAddressId(addressId); + } + } +} diff --git a/src/CncService/Impl/DashboardService.cs b/src/CncService/Impl/DashboardService.cs new file mode 100644 index 0000000..e7f0394 --- /dev/null +++ b/src/CncService/Impl/DashboardService.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto.Dashboard; +using CncRepository.Interface; +using CncService.Interface; + +namespace CncService.Impl +{ + /// + /// 仪表盘实现 + /// + public class DashboardService : IDashboardService + { + private readonly IDashboardRepository _dashboardRepository; + private readonly ICollectorHeartbeatRepository _collectorHeartbeatRepository; + + public DashboardService(IDashboardRepository dashboardRepository, + ICollectorHeartbeatRepository collectorHeartbeatRepository) + { + _dashboardRepository = dashboardRepository ?? throw new ArgumentNullException(nameof(dashboardRepository)); + _collectorHeartbeatRepository = collectorHeartbeatRepository ?? throw new ArgumentNullException(nameof(collectorHeartbeatRepository)); + } + + /// + public DashboardSummaryResponse GetSummary() + { + return _dashboardRepository.GetSummary(); + } + + /// + public List GetWorkshopProduction(DateTime? startDate, DateTime? endDate) + { + var s = startDate ?? DateTime.Today; + var e = endDate ?? DateTime.Today; + return _dashboardRepository.GetWorkshopProduction(s, e); + } + + /// + public List GetMachineRank(DateTime? startDate, DateTime? endDate, int top = 10) + { + var s = startDate ?? DateTime.Today; + var e = endDate ?? DateTime.Today; + return _dashboardRepository.GetMachineRank(s, e, top); + } + + /// + public List GetWorkerRank(DateTime? startDate, DateTime? endDate, int top = 10) + { + var s = startDate ?? DateTime.Today; + var e = endDate ?? DateTime.Today; + return _dashboardRepository.GetWorkerRank(s, e, top); + } + + /// + public object GetProductionTrend(int days = 7) + { + return _dashboardRepository.GetProductionTrend(days); + } + + /// + public object GetMachineStatusDistribution() + { + return _dashboardRepository.GetMachineStatusDistribution(); + } + + /// + public List GetRecentAlerts(int count = 5) + { + return _dashboardRepository.GetRecentAlerts(count); + } + + /// + public object GetCollectorStatus() + { + var latest = _collectorHeartbeatRepository.GetLatest("collector-service"); + bool isRunning = latest != null && latest.LastCollectTime.HasValue && + (DateTime.Now - latest.LastCollectTime.Value).TotalMinutes < 5; + return new { IsRunning = isRunning, LastCollectTime = latest?.LastCollectTime }; + } + } +} diff --git a/src/CncService/Impl/MachineService.cs b/src/CncService/Impl/MachineService.cs new file mode 100644 index 0000000..b89afde --- /dev/null +++ b/src/CncService/Impl/MachineService.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using CncService.Interface; +using CncModels.Dto; +using CncModels.Dto.Machine; +using CncModels.Entity; +using CncModels.Constants; +using CncRepository.Interface; + +namespace CncService.Impl +{ + /// + /// 机床管理实现 + /// + public class MachineService : IMachineService + { + private readonly IMachineRepository _machineRepository; + private readonly ICollectAddressRepository _addressRepository; + private readonly IWorkerMachineRepository _workerMachineRepository; + private readonly IBrandRepository _brandRepository; + + public MachineService( + IMachineRepository machineRepository, + ICollectAddressRepository addressRepository, + IWorkerMachineRepository workerMachineRepository, + IBrandRepository brandRepository) + { + _machineRepository = machineRepository ?? throw new ArgumentNullException(nameof(machineRepository)); + _addressRepository = addressRepository ?? throw new ArgumentNullException(nameof(addressRepository)); + _workerMachineRepository = workerMachineRepository ?? throw new ArgumentNullException(nameof(workerMachineRepository)); + _brandRepository = brandRepository ?? throw new ArgumentNullException(nameof(brandRepository)); + } + + /// + public PagedResult GetList(MachineQuery query) + { + if (query == null) throw new BusinessException(ErrorCode.BadRequest, "查询参数不能为空"); + return _machineRepository.GetList(query); + } + + /// + public MachineDetailResponse GetById(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的机床ID"); + var machine = _machineRepository.GetById(id); + if (machine == null) throw new BusinessException(ErrorCode.NotFound, "机床未找到"); + + var detail = new MachineDetailResponse + { + Id = machine.Id, + DeviceCode = machine.DeviceCode, + Name = machine.Name, + WorkshopId = machine.WorkshopId, + CollectAddressId = machine.CollectAddressId, + BrandId = machine.BrandId, + IpAddress = machine.IpAddress, + IsEnabled = machine.IsEnabled, + IsOnline = machine.IsOnline, + LastProgramName = machine.LastProgramName, + LastCollectTime = machine.LastCollectTime + }; + + // 获取绑定工人信息 + var binding = _workerMachineRepository.GetByMachineId(id); + if (binding != null) + { + detail.WorkerId = binding.WorkerId; + } + + return detail; + } + + /// + public int Create(CreateMachineRequest request) + { + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数不能为空"); + if (string.IsNullOrWhiteSpace(request.DeviceCode)) throw new BusinessException(ErrorCode.BadRequest, "设备编码不能为空"); + // 唯一性校验 + var existing = _machineRepository.GetByDeviceCode(request.DeviceCode); + if (existing != null) throw new BusinessException(ErrorCode.Conflict, "设备编码已存在"); + + var entity = new Machine + { + DeviceCode = request.DeviceCode, + Name = request.Name, + WorkshopId = request.WorkshopId, + CollectAddressId = request.CollectAddressId, + BrandId = request.BrandId, + IpAddress = request.IpAddress, + IsEnabled = 1, + IsOnline = 0, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + return _machineRepository.Create(entity); + } + + /// + public bool Update(int id, UpdateMachineRequest request) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的机床ID"); + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数不能为空"); + var entity = _machineRepository.GetById(id); + if (entity == null) throw new BusinessException(ErrorCode.NotFound, "机床未找到"); + + entity.Name = request.Name ?? entity.Name; + entity.WorkshopId = request.WorkshopId; + entity.CollectAddressId = request.CollectAddressId; + entity.BrandId = request.BrandId; + entity.IpAddress = request.IpAddress ?? entity.IpAddress; + entity.UpdatedAt = DateTime.Now; + return _machineRepository.Update(entity); + } + + /// + public bool Delete(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的机床ID"); + // 解绑工人 + _workerMachineRepository.DeleteByMachineId(id); + return _machineRepository.Delete(id); + } + + /// + public bool ToggleEnabled(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的机床ID"); + return _machineRepository.ToggleEnabled(id); + } + } +} diff --git a/src/CncService/Impl/ProductionService.cs b/src/CncService/Impl/ProductionService.cs new file mode 100644 index 0000000..5bcfe62 --- /dev/null +++ b/src/CncService/Impl/ProductionService.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using CncService.Interface; +using CncModels.Dto; +using CncModels.Dto.Production; +using CncModels.Entity; +using CncModels.Constants; +using CncRepository.Interface; + +namespace CncService.Impl +{ + /// + /// 产量管理实现 + /// + public class ProductionService : IProductionService + { + private readonly IDailyProductionRepository _dailyProductionRepository; + private readonly IProductionSegmentRepository _productionSegmentRepository; + private readonly IProductionAdjustmentRepository _productionAdjustmentRepository; + + public ProductionService( + IDailyProductionRepository dailyProductionRepository, + IProductionSegmentRepository productionSegmentRepository, + IProductionAdjustmentRepository productionAdjustmentRepository) + { + _dailyProductionRepository = dailyProductionRepository ?? throw new ArgumentNullException(nameof(dailyProductionRepository)); + _productionSegmentRepository = productionSegmentRepository ?? throw new ArgumentNullException(nameof(productionSegmentRepository)); + _productionAdjustmentRepository = productionAdjustmentRepository ?? throw new ArgumentNullException(nameof(productionAdjustmentRepository)); + } + + /// + public PagedResult GetList(ProductionQuery query) + { + if (query == null) throw new BusinessException(ErrorCode.BadRequest, "查询参数不能为空"); + return _dailyProductionRepository.GetList(query); + } + + /// + public DailySummaryResponse GetSummary(DateTime? date, int? workshopId) + { + var targetDate = date ?? DateTime.Today; + var total = _dailyProductionRepository.GetTotalByDateRange(targetDate, targetDate, workshopId); + return new DailySummaryResponse + { + TotalQuantity = (int)total, + MachineCount = 0, + NormalCount = 0, + OfflineCount = 0 + }; + } + + /// + public decimal GetTotalByDateRange(DateTime startDate, DateTime endDate, int? workshopId) + { + return _dailyProductionRepository.GetTotalByDateRange(startDate, endDate, workshopId); + } + + /// + public bool Adjust(ProductionAdjustRequest request) + { + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数不能为空"); + decimal newValue; + decimal.TryParse(request.NewValue, out newValue); + var entity = new ProductionAdjustment + { + TargetTable = request.TargetTable, + TargetId = request.TargetId, + FieldName = request.FieldName, + OldValue = null, + NewValue = newValue, + Reason = request.Reason, + OperatorIp = "", + CreatedAt = DateTime.Now + }; + _productionAdjustmentRepository.Create(entity); + return true; + } + } +} diff --git a/src/CncService/Impl/ScreenService.cs b/src/CncService/Impl/ScreenService.cs new file mode 100644 index 0000000..415724b --- /dev/null +++ b/src/CncService/Impl/ScreenService.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using CncService.Interface; +using CncModels.Dto.Screen; +using CncModels.Entity; +using CncModels.Constants; +using CncRepository.Interface; + +namespace CncService.Impl +{ + /// + /// 大屏配置实现 + /// + public class ScreenService : IScreenService + { + private readonly IScreenConfigRepository _screenConfigRepository; + private readonly IScreenFilterRepository _screenFilterRepository; + private readonly IWorkshopRepository _workshopRepository; + + public ScreenService( + IScreenConfigRepository screenConfigRepository, + IScreenFilterRepository screenFilterRepository, + IWorkshopRepository workshopRepository) + { + _screenConfigRepository = screenConfigRepository ?? throw new ArgumentNullException(nameof(screenConfigRepository)); + _screenFilterRepository = screenFilterRepository ?? throw new ArgumentNullException(nameof(screenFilterRepository)); + _workshopRepository = workshopRepository ?? throw new ArgumentNullException(nameof(workshopRepository)); + } + + /// + public ScreenSummaryResponse GetSummary() + { + // 简化实现,返回默认值(实际由Controller调用Dashboard服务获取真实数据) + return new ScreenSummaryResponse + { + MachineCount = 0, + ProductionToday = 0, + AlertCount = 0, + OnlineCount = 0 + }; + } + + /// + public List GetConfigs() + { + return _screenConfigRepository.GetAll(); + } + + /// + public bool UpdateConfig(ScreenConfig entity) + { + if (entity == null) throw new BusinessException(ErrorCode.BadRequest, "配置不能为空"); + return _screenConfigRepository.Update(entity); + } + + /// + public List GetFilters(string screenKey) + { + if (string.IsNullOrWhiteSpace(screenKey)) throw new BusinessException(ErrorCode.BadRequest, "screenKey不能为空"); + return _screenFilterRepository.GetByScreenKey(screenKey); + } + + /// + public int CreateFilter(ScreenFilter entity) + { + if (entity == null) throw new BusinessException(ErrorCode.BadRequest, "筛选项不能为空"); + return _screenFilterRepository.Create(entity); + } + + /// + public bool UpdateFilter(ScreenFilter entity) + { + if (entity == null) throw new BusinessException(ErrorCode.BadRequest, "筛选项不能为空"); + return _screenFilterRepository.Update(entity); + } + + /// + public bool DeleteFilter(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的筛选ID"); + return _screenFilterRepository.Delete(id); + } + } +} diff --git a/src/CncService/Impl/SystemLogService.cs b/src/CncService/Impl/SystemLogService.cs new file mode 100644 index 0000000..df20865 --- /dev/null +++ b/src/CncService/Impl/SystemLogService.cs @@ -0,0 +1,29 @@ +using System; +using CncService.Interface; +using CncModels.Dto; +using CncModels.Dto.Log; +using CncModels.Constants; +using CncRepository.Interface; + +namespace CncService.Impl +{ + /// + /// 系统日志实现 + /// + public class SystemLogService : ISystemLogService + { + private readonly ISystemLogRepository _systemLogRepository; + + public SystemLogService(ISystemLogRepository systemLogRepository) + { + _systemLogRepository = systemLogRepository ?? throw new ArgumentNullException(nameof(systemLogRepository)); + } + + /// + public PagedResult GetList(SystemLogQuery query) + { + if (query == null) throw new BusinessException(ErrorCode.BadRequest, "查询参数不能为空"); + return _systemLogRepository.GetList(query); + } + } +} diff --git a/src/CncService/Impl/WorkerService.cs b/src/CncService/Impl/WorkerService.cs new file mode 100644 index 0000000..75b2b8d --- /dev/null +++ b/src/CncService/Impl/WorkerService.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CncService.Interface; +using CncModels.Dto; +using CncModels.Dto.Worker; +using CncModels.Entity; +using CncModels.Constants; +using CncRepository.Interface; + +namespace CncService.Impl +{ + /// + /// 员工管理实现 + /// + public class WorkerService : IWorkerService + { + private readonly IWorkerRepository _workerRepository; + private readonly IWorkerMachineRepository _workerMachineRepository; + private readonly IMachineRepository _machineRepository; + + public WorkerService( + IWorkerRepository workerRepository, + IWorkerMachineRepository workerMachineRepository, + IMachineRepository machineRepository) + { + _workerRepository = workerRepository ?? throw new ArgumentNullException(nameof(workerRepository)); + _workerMachineRepository = workerMachineRepository ?? throw new ArgumentNullException(nameof(workerMachineRepository)); + _machineRepository = machineRepository ?? throw new ArgumentNullException(nameof(machineRepository)); + } + + /// + public PagedResult GetList(WorkerQuery query) + { + if (query == null) throw new BusinessException(ErrorCode.BadRequest, "查询参数不能为空"); + return _workerRepository.GetList(query); + } + + /// + public WorkerDetailResponse GetById(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的员工ID"); + var w = _workerRepository.GetById(id); + if (w == null) throw new BusinessException(ErrorCode.NotFound, "员工未找到"); + + var bindings = _workerMachineRepository.GetByWorkerId(id); + var machineNames = new List(); + foreach (var b in bindings) + { + var m = _machineRepository.GetById(b.MachineId); + if (m != null) machineNames.Add(m.Name ?? m.DeviceCode); + } + + return new WorkerDetailResponse + { + Id = w.Id, + Code = w.Code, + Name = w.Name, + IsEnabled = w.IsEnabled, + MachineCount = bindings.Count, + MachineNames = string.Join(", ", machineNames) + }; + } + + /// + public int Create(CreateWorkerRequest request) + { + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数不能为空"); + if (string.IsNullOrWhiteSpace(request.Code)) throw new BusinessException(ErrorCode.BadRequest, "工号不能为空"); + // 唯一性校验 + var existing = _workerRepository.GetByCode(request.Code); + if (existing != null) throw new BusinessException(ErrorCode.Conflict, "工号已存在"); + + var entity = new Worker + { + Name = request.Name, + Code = request.Code, + IsEnabled = 1, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + return _workerRepository.Create(entity); + } + + /// + public bool Update(int id, UpdateWorkerRequest request) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的员工ID"); + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数不能为空"); + var entity = _workerRepository.GetById(id); + if (entity == null) throw new BusinessException(ErrorCode.NotFound, "员工未找到"); + + entity.Name = request.Name ?? entity.Name; + entity.UpdatedAt = DateTime.Now; + return _workerRepository.Update(entity); + } + + /// + public bool Delete(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的员工ID"); + // 解绑所有机床 + _workerMachineRepository.DeleteByWorkerId(id); + return _workerRepository.Delete(id); + } + + /// + public bool ToggleEnabled(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的员工ID"); + return _workerRepository.ToggleEnabled(id); + } + + /// + public bool BindMachine(int workerId, int machineId) + { + if (workerId <= 0 || machineId <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的参数"); + // 检查是否已绑定 + var existing = _workerMachineRepository.GetByMachineId(machineId); + if (existing != null) throw new BusinessException(ErrorCode.Conflict, "该机床已绑定其他工人"); + _workerMachineRepository.Create(workerId, machineId); + return true; + } + + /// + public bool UnbindMachine(int workerId, int machineId) + { + if (workerId <= 0 || machineId <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的参数"); + return _workerMachineRepository.DeleteByMachineId(machineId); + } + } +} diff --git a/src/CncService/Impl/WorkshopService.cs b/src/CncService/Impl/WorkshopService.cs new file mode 100644 index 0000000..a5b74ae --- /dev/null +++ b/src/CncService/Impl/WorkshopService.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CncService.Interface; +using CncModels.Dto.Settings; +using CncModels.Entity; +using CncModels.Constants; +using CncRepository.Interface; + +namespace CncService.Impl +{ + /// + /// 车间管理实现 + /// + public class WorkshopService : IWorkshopService + { + private readonly IWorkshopRepository _workshopRepository; + + public WorkshopService(IWorkshopRepository workshopRepository) + { + _workshopRepository = workshopRepository ?? throw new ArgumentNullException(nameof(workshopRepository)); + } + + /// + public List GetList(string keyword) + { + var paged = _workshopRepository.GetList(keyword ?? string.Empty); + // WorkshopRepository.GetList返回PagedResult,转换为WorkshopListItem + return paged.Items.Select(w => new WorkshopListItem + { + Id = w.Id, + Name = w.Name, + SortOrder = w.SortOrder, + IsEnabled = w.IsEnabled, + MachineCount = _workshopRepository.GetMachineCount(w.Id) + }).ToList(); + } + + /// + public Workshop GetById(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的车间ID"); + var w = _workshopRepository.GetById(id); + if (w == null) throw new BusinessException(ErrorCode.NotFound, "车间未找到"); + return w; + } + + /// + public int Create(CreateWorkshopRequest request) + { + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数不能为空"); + if (string.IsNullOrWhiteSpace(request.Name)) throw new BusinessException(ErrorCode.BadRequest, "车间名称不能为空"); + // 唯一性检查 + var existing = _workshopRepository.GetList(request.Name); + if (existing.Items.Any(w => string.Equals(w.Name, request.Name, StringComparison.OrdinalIgnoreCase))) + throw new BusinessException(ErrorCode.Conflict, "车间名称已存在"); + + var entity = new Workshop + { + Name = request.Name, + SortOrder = request.SortOrder, + IsEnabled = 1, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + return _workshopRepository.Create(entity); + } + + /// + public bool Update(int id, UpdateWorkshopRequest request) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的车间ID"); + if (request == null) throw new BusinessException(ErrorCode.BadRequest, "请求参数不能为空"); + var entity = _workshopRepository.GetById(id); + if (entity == null) throw new BusinessException(ErrorCode.NotFound, "车间未找到"); + + entity.Name = request.Name ?? entity.Name; + entity.SortOrder = request.SortOrder; + entity.UpdatedAt = DateTime.Now; + return _workshopRepository.Update(entity); + } + + /// + public bool Delete(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的车间ID"); + int machineCount = _workshopRepository.GetMachineCount(id); + if (machineCount > 0) throw new BusinessException(ErrorCode.DataReferenced, "车间下有机床,无法删除"); + return _workshopRepository.Delete(id); + } + + /// + public bool ToggleEnabled(int id) + { + if (id <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的车间ID"); + return _workshopRepository.ToggleEnabled(id); + } + + /// + public int GetMachineCount(int workshopId) + { + if (workshopId <= 0) throw new BusinessException(ErrorCode.BadRequest, "无效的车间ID"); + return _workshopRepository.GetMachineCount(workshopId); + } + } +} diff --git a/src/CncService/Interface/IAlertService.cs b/src/CncService/Interface/IAlertService.cs new file mode 100644 index 0000000..6038279 --- /dev/null +++ b/src/CncService/Interface/IAlertService.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto.Alert; +using CncModels.Dto; + +namespace CncService.Interface +{ + public interface IAlertService + { + PagedResult GetList(AlertQuery query); + bool Resolve(long id); + int BatchResolve(List ids); + AlertStatisticsResponse GetStatistics(); + } +} diff --git a/src/CncService/Interface/IAuthService.cs b/src/CncService/Interface/IAuthService.cs new file mode 100644 index 0000000..0b36f87 --- /dev/null +++ b/src/CncService/Interface/IAuthService.cs @@ -0,0 +1,17 @@ +using CncModels.Dto.Login; + +namespace CncService.Interface +{ + /// + /// 登录认证服务接口 + /// + public interface IAuthService + { + /// + /// 管理员登录 + /// + /// 登录请求参数 + /// 登录响应(包含 JWT Token 及有效期) + LoginResponse Login(LoginRequest request); + } +} diff --git a/src/CncService/Interface/IBrandService.cs b/src/CncService/Interface/IBrandService.cs new file mode 100644 index 0000000..d6226f3 --- /dev/null +++ b/src/CncService/Interface/IBrandService.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using CncModels.Dto.Brand; + +namespace CncService.Interface +{ + /// + /// 品牌模板服务接口 + /// + public interface IBrandService + { + List GetList(); + BrandDetailResponse GetById(int id); + int Create(CreateBrandRequest request); + bool Update(int id, UpdateBrandRequest request); + bool Delete(int id); + bool ToggleEnabled(int id); + int Copy(int id); + List GetStandardFields(); + } +} diff --git a/src/CncService/Interface/ICollectAddressService.cs b/src/CncService/Interface/ICollectAddressService.cs new file mode 100644 index 0000000..ac2b27c --- /dev/null +++ b/src/CncService/Interface/ICollectAddressService.cs @@ -0,0 +1,17 @@ +using CncModels.Dto; +using CncModels.Dto.CollectAddress; +namespace CncService.Interface +{ + /// + /// 采集地址服务接口 + /// + public interface ICollectAddressService + { + PagedResult GetList(CollectAddressQuery query); + CollectAddressDetailResponse GetById(int id); + int Create(CreateCollectAddressRequest request); + bool Update(int id, UpdateCollectAddressRequest request); + bool Delete(int id); + bool ToggleEnabled(int id); + } +} diff --git a/src/CncService/Interface/ICollectDataService.cs b/src/CncService/Interface/ICollectDataService.cs new file mode 100644 index 0000000..a5636d9 --- /dev/null +++ b/src/CncService/Interface/ICollectDataService.cs @@ -0,0 +1,17 @@ +using CncModels.Dto; +using CncModels.Entity; + +namespace CncService.Interface +{ + /// + /// 采集数据查询服务接口 + /// + public interface ICollectDataService + { + /// 按地址ID分页查询原始采集记录 + PagedResult GetRawByAddress(int addressId, int page, int pageSize); + + /// 获取最新采集记录 + CollectRaw GetLatestRaw(int addressId); + } +} diff --git a/src/CncService/Interface/IDashboardService.cs b/src/CncService/Interface/IDashboardService.cs new file mode 100644 index 0000000..17d19c2 --- /dev/null +++ b/src/CncService/Interface/IDashboardService.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto.Dashboard; + +namespace CncService.Interface +{ + /// + /// 仪表盘服务接口 + /// + public interface IDashboardService + { + DashboardSummaryResponse GetSummary(); + + List GetWorkshopProduction(DateTime? startDate, DateTime? endDate); + + List GetMachineRank(DateTime? startDate, DateTime? endDate, int top = 10); + + List GetWorkerRank(DateTime? startDate, DateTime? endDate, int top = 10); + + object GetProductionTrend(int days = 7); + + object GetMachineStatusDistribution(); + + List GetRecentAlerts(int count = 5); + + object GetCollectorStatus(); + } +} diff --git a/src/CncService/Interface/IMachineService.cs b/src/CncService/Interface/IMachineService.cs new file mode 100644 index 0000000..113f8f7 --- /dev/null +++ b/src/CncService/Interface/IMachineService.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto.Machine; +using CncModels.Dto; + +namespace CncService.Interface +{ + /// + /// 机床管理服务接口 + /// + public interface IMachineService + { + /// + /// 分页查询机床列表 + /// + /// 查询条件 + /// 分页结果 + PagedResult GetList(MachineQuery query); + + /// + /// 根据ID获取机床详情 + /// + /// 机床ID + /// 机床详情 + MachineDetailResponse GetById(int id); + + /// + /// 新增机床 + /// + /// 创建参数 + /// 新建机床的ID + int Create(CreateMachineRequest request); + + /// + /// 编辑机床信息 + /// + /// 机床ID + /// 修改参数 + /// 是否更新成功 + bool Update(int id, UpdateMachineRequest request); + + /// + /// 删除机床并解绑相关工人 + /// + /// 机床ID + /// 是否删除成功 + bool Delete(int id); + + /// + /// 启用或禁用机床 + /// + /// 机床ID + /// 是否切换成功 + bool ToggleEnabled(int id); + } +} diff --git a/src/CncService/Interface/IProductionService.cs b/src/CncService/Interface/IProductionService.cs new file mode 100644 index 0000000..74dcab8 --- /dev/null +++ b/src/CncService/Interface/IProductionService.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto; +using CncModels.Dto.Production; + +namespace CncService.Interface +{ + /// + /// 产量管理服务接口 + /// + public interface IProductionService + { + /// 分页查询产量记录 + PagedResult GetList(ProductionQuery query); + + /// 获取日汇总统计 + DailySummaryResponse GetSummary(DateTime? date, int? workshopId); + + /// 获取日期范围总产量 + decimal GetTotalByDateRange(DateTime startDate, DateTime endDate, int? workshopId); + + /// 产量修正 + bool Adjust(ProductionAdjustRequest request); + } +} diff --git a/src/CncService/Interface/IScreenService.cs b/src/CncService/Interface/IScreenService.cs new file mode 100644 index 0000000..19fb7a5 --- /dev/null +++ b/src/CncService/Interface/IScreenService.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using CncModels.Dto.Screen; +using CncModels.Dto.ScreenConfig; +using CncModels.Entity; + +namespace CncService.Interface +{ + /// + /// 大屏配置服务接口 + /// + public interface IScreenService + { + /// 获取大屏汇总数据 + ScreenSummaryResponse GetSummary(); + + /// 获取所有卡片配置 + List GetConfigs(); + + /// 更新卡片配置 + bool UpdateConfig(ScreenConfig entity); + + /// 获取筛选项列表 + List GetFilters(string screenKey); + + /// 创建筛选项 + int CreateFilter(ScreenFilter entity); + + /// 更新筛选项 + bool UpdateFilter(ScreenFilter entity); + + /// 删除筛选项 + bool DeleteFilter(int id); + } +} diff --git a/src/CncService/Interface/ISystemLogService.cs b/src/CncService/Interface/ISystemLogService.cs new file mode 100644 index 0000000..aee89e7 --- /dev/null +++ b/src/CncService/Interface/ISystemLogService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto.Log; +using CncModels.Dto; + +namespace CncService.Interface +{ + public interface ISystemLogService + { + PagedResult GetList(SystemLogQuery query); + } +} diff --git a/src/CncService/Interface/IWorkerService.cs b/src/CncService/Interface/IWorkerService.cs new file mode 100644 index 0000000..fc2f89a --- /dev/null +++ b/src/CncService/Interface/IWorkerService.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto.Worker; +using CncModels.Dto; + +namespace CncService.Interface +{ + public interface IWorkerService + { + PagedResult GetList(WorkerQuery query); + WorkerDetailResponse GetById(int id); + int Create(CreateWorkerRequest request); + bool Update(int id, UpdateWorkerRequest request); + bool Delete(int id); + bool ToggleEnabled(int id); + bool BindMachine(int workerId, int machineId); + bool UnbindMachine(int workerId, int machineId); + } +} diff --git a/src/CncService/Interface/IWorkshopService.cs b/src/CncService/Interface/IWorkshopService.cs new file mode 100644 index 0000000..00a4316 --- /dev/null +++ b/src/CncService/Interface/IWorkshopService.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using CncModels.Dto.Settings; +using CncModels.Entity; + +namespace CncService.Interface +{ + /// + /// 车间管理服务接口 + /// + public interface IWorkshopService + { + /// 获取车间列表 + List GetList(string keyword); + + /// 按ID获取车间 + Workshop GetById(int id); + + /// 新增车间 + int Create(CreateWorkshopRequest request); + + /// 编辑车间 + bool Update(int id, UpdateWorkshopRequest request); + + /// 删除车间 + bool Delete(int id); + + /// 启停车间 + bool ToggleEnabled(int id); + + /// 获取车间下机床数量 + int GetMachineCount(int workshopId); + } +}