using Infrastructure; using Infrastructure.Attribute; using ZR.Model; using ZR.Model.Business; using ZR.Model.Business.Dto; using ZR.Model.Dto; using ZR.Repository; using ZR.Service.Business.IBusinessService; namespace ZR.Service.Business { /// /// 干线故障Service业务层处理 /// [AppService(ServiceType = typeof(IOdfCableFaultsService), ServiceLifetime = LifeTime.Transient)] public class OdfCableFaultsService : BaseService, IOdfCableFaultsService { /// /// 按 CableId 分页查询故障列表,联查光缆名称,按 FaultTime DESC 排序 /// public PagedInfo GetList(OdfCableFaultsQueryDto parm) { var predicate = QueryExp(parm); var total = 0; var list = Queryable() .Where(predicate.ToExpression()) .LeftJoin((f, c) => f.CableId == c.Id) .Select((f, c) => new { f.Id, f.CableId, f.FaultTime, f.Personnel, f.FaultReason, f.Mileage, f.MileageCorrection, f.Location, f.Latitude, f.Longitude, f.Remark, f.CreatedAt, f.FaultCount, CableName = c.CableName }) .MergeTable() .OrderByDescending(it => it.FaultTime) .ToPageList(parm.PageNum, parm.PageSize, ref total); // 批量查询频次时间记录 var faultIds = list.Select(x => x.Id).ToList(); var allFaultTimes = GetFaultTimesByFaultIds(faultIds); var resultList = list.Select(item => { var times = new List(); if (allFaultTimes.ContainsKey(item.Id)) { times = allFaultTimes[item.Id].Select(t => t.ToString("yyyy-MM-dd HH:mm:ss")).ToList(); } return new { item.Id, item.CableId, item.FaultTime, item.Personnel, item.FaultReason, item.Mileage, item.MileageCorrection, item.Location, item.Latitude, item.Longitude, item.Remark, item.CreatedAt, item.FaultCount, item.CableName, FaultTimes = times }; }).ToList(); return new PagedInfo { Result = resultList.Cast().ToList(), TotalNum = total, PageIndex = parm.PageNum, PageSize = parm.PageSize }; } /// /// 查询故障详情,联查光缆名称和图片列表 /// public object GetDetail(int id) { var fault = Queryable() .LeftJoin((f, c) => f.CableId == c.Id) .Where((f, c) => f.Id == id) .Select((f, c) => new { f.Id, f.CableId, f.FaultTime, f.Personnel, f.FaultReason, f.Mileage, f.MileageCorrection, f.Location, f.Latitude, f.Longitude, f.Remark, f.UserId, f.CreatedAt, CableName = c.CableName }) .First(); if (fault == null) { throw new CustomException("故障记录不存在"); } // 查询关联图片列表 var images = Context.Queryable() .Where(img => img.FaultId == id) .OrderBy(img => img.Id) .Select(img => new { img.Id, img.ImageUrl }) .ToList(); var faultTimes = Context.Queryable() .Where(t => t.FaultId == id) .OrderBy(t => t.FaultTime) .Select(t => new { t.Id, t.FaultTime }) .ToList(); return new { fault.Id, fault.CableId, fault.FaultTime, fault.Personnel, fault.FaultReason, fault.Mileage, fault.MileageCorrection, fault.Location, fault.Latitude, fault.Longitude, fault.Remark, fault.CableName, fault.CreatedAt, FaultCount = Context.Queryable().Where(f => f.Id == id).First()?.FaultCount ?? 1, FaultTimes = faultTimes, Images = images }; } /// /// 新增故障(图片已上传至 COS,仅保存 URL) /// public async Task AddFault(OdfCableFaultAddDto dto) { // 校验 CableId 存在 var cable = Context.Queryable() .Where(c => c.Id == dto.CableId) .First(); if (cable == null) { throw new CustomException("光缆不存在"); } // 校验至少 1 张图片 if (dto.ImageUrls == null || dto.ImageUrls.Length == 0) { throw new CustomException("请至少上传一张图片"); } // 插入故障记录 var model = new OdfCableFaults { CableId = dto.CableId, FaultTime = DateTime.Parse(dto.FaultTime), Personnel = dto.Personnel, FaultReason = dto.FaultReason, Mileage = dto.Mileage, MileageCorrection = dto.MileageCorrection, Location = dto.Location, Latitude = dto.Latitude, Longitude = dto.Longitude, Remark = dto.Remark, UserId = dto.UserId, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; var faultEntity = Insertable(model).ExecuteReturnEntity(); int faultId = faultEntity.Id; // 插入图片记录(COS URL) foreach (var imageUrl in dto.ImageUrls) { if (string.IsNullOrWhiteSpace(imageUrl)) continue; var imageRecord = new OdfCableFaultImages { FaultId = faultId, ImageUrl = imageUrl, CreatedAt = DateTime.Now }; Context.Insertable(imageRecord).ExecuteCommand(); } return faultId; } /// /// 删除故障记录并级联删除关联图片和频次时间记录 /// public int Delete(int id) { // 删除关联频次时间记录 Context.Deleteable() .Where(t => t.FaultId == id) .ExecuteCommand(); // 删除关联图片记录 Context.Deleteable() .Where(img => img.FaultId == id) .ExecuteCommand(); // 删除故障记录 return base.Delete(id); } /// /// 导出故障列表 /// public List ExportList(OdfCableFaultsQueryDto parm) { var predicate = QueryExp(parm); return Queryable() .Where(predicate.ToExpression()) .LeftJoin((f, c) => f.CableId == c.Id) .Select((f, c) => new OdfCableFaultExportDto { Id = f.Id, CableId = f.CableId, FaultTime = f.FaultTime, Personnel = f.Personnel, FaultReason = f.FaultReason, Mileage = f.Mileage, MileageCorrection = f.MileageCorrection, Location = f.Location, Latitude = f.Latitude, Longitude = f.Longitude, Remark = f.Remark, CreatedAt = f.CreatedAt, FaultCount = f.FaultCount, CableName = c.CableName }) .MergeTable() .OrderByDescending(it => it.FaultTime) .ToList(); } /// /// 增加故障频次:FaultCount+1,同时插入一条时间记录 /// public object IncrementFaultCount(int id) { var fault = GetFirst(f => f.Id == id); if (fault == null) { throw new CustomException("故障记录不存在"); } fault.FaultCount += 1; fault.UpdatedAt = DateTime.Now; Update(fault, true); var now = DateTime.Now; var timeRecord = new OdfCableFaultTimes { FaultId = id, FaultTime = now, CreatedAt = now }; Context.Insertable(timeRecord).ExecuteCommand(); var faultTimes = Context.Queryable() .Where(t => t.FaultId == id) .OrderBy(t => t.FaultTime) .Select(t => new { t.Id, t.FaultTime }) .ToList(); return new { FaultCount = fault.FaultCount, FaultTimes = faultTimes }; } /// /// 批量查询故障频次时间记录,按故障ID分组 /// public Dictionary> GetFaultTimesByFaultIds(List faultIds) { if (faultIds == null || faultIds.Count == 0) return new Dictionary>(); var records = Context.Queryable() .Where(t => faultIds.Contains(t.FaultId)) .OrderBy(t => t.FaultTime) .ToList(); return records .GroupBy(t => t.FaultId) .ToDictionary(g => g.Key, g => g.Select(t => t.FaultTime).ToList()); } /// /// 查询表达式 /// private static Expressionable QueryExp(OdfCableFaultsQueryDto parm) { var predicate = Expressionable.Create(); predicate = predicate.AndIF(parm.CableId != null, it => it.CableId == parm.CableId); predicate = predicate.AndIF(parm.BeginFaultTime != null, it => it.FaultTime >= parm.BeginFaultTime); predicate = predicate.AndIF(parm.EndFaultTime != null, it => it.FaultTime <= parm.EndFaultTime); predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.FaultReason), it => it.FaultReason.Contains(parm.FaultReason)); return predicate; } /// /// 批量导入故障(导出的文件可直接导入) /// public (int successCount, int errorCount, string errorMsg) ImportFaults(List list) { var logger = NLog.LogManager.GetCurrentClassLogger(); int successCount = 0; int errorCount = 0; var errorMsgs = new List(); // 预查所有光缆,用于按名称匹配CableId var cableNames = list.Where(x => !string.IsNullOrWhiteSpace(x.CableName)).Select(x => x.CableName.Trim()).Distinct().ToList(); var cables = Context.Queryable().Where(c => cableNames.Contains(c.CableName)).ToList(); for (int i = 0; i < list.Count; i++) { var item = list[i]; int rowNum = i + 2; // Excel行号(第1行是表头) try { // 确定CableId:优先用光缆编号,其次按所属光缆名称匹配 int? cableId = item.CableId; if ((cableId == null || cableId == 0) && !string.IsNullOrWhiteSpace(item.CableName)) { var cable = cables.Find(c => c.CableName == item.CableName.Trim()); if (cable != null) { cableId = cable.Id; } } if (cableId == null || cableId == 0) { errorCount++; errorMsgs.Add($"第{rowNum}行:未找到匹配的光缆"); continue; } // 解析故障时间:导出时可能是多行时间拼接(\n分隔),需解析所有有效时间 var allTimes = new List(); if (!string.IsNullOrWhiteSpace(item.FaultTime)) { var rawFaultTime = item.FaultTime; var escapedRaw = rawFaultTime.Replace("\r", "\\r").Replace("\n", "\\n"); logger.Info($"[导入调试] 行{rowNum} FaultTime原始值=[{escapedRaw}] 长度={rawFaultTime.Length}"); // 打印每个字符的Unicode编码,排查隐藏字符 var charCodes = string.Join(",", rawFaultTime.Select(c => $"U+{(int)c:X4}")); logger.Info($"[导入调试] 行{rowNum} FaultTime字符编码=[{charCodes}]"); var timeParts = item.FaultTime.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); logger.Info($"[导入调试] 行{rowNum} Split后得到 {timeParts.Length} 段"); for (int k = 0; k < timeParts.Length; k++) { logger.Info($"[导入调试] 行{rowNum} 时间段[{k}]=[{timeParts[k].Trim()}]"); if (DateTime.TryParse(timeParts[k].Trim(), out var parsed)) { allTimes.Add(parsed); logger.Info($"[导入调试] 行{rowNum} 时间段[{k}] 解析成功={parsed:yyyy-MM-dd HH:mm:ss}"); } else { logger.Info($"[导入调试] 行{rowNum} 时间段[{k}] 解析失败"); } } } else { logger.Info($"[导入调试] 行{rowNum} FaultTime为空"); } logger.Info($"[导入调试] 行{rowNum} 共解析到 {allTimes.Count} 个有效时间"); // 按时间排序,第一个作为主故障时间 allTimes.Sort(); DateTime faultTime = allTimes.Count > 0 ? allTimes[0] : DateTime.Now; // FaultCount:以解析到的时间数量为准,若只有1个或0个则取导入值或默认1 int faultCount = allTimes.Count > 1 ? allTimes.Count : (item.FaultCount ?? 1); var model = new OdfCableFaults { CableId = cableId.Value, FaultTime = faultTime, Personnel = item.Personnel, FaultReason = item.FaultReason, Mileage = item.Mileage, MileageCorrection = item.MileageCorrection, Location = item.Location, Latitude = item.Latitude ?? 0, Longitude = item.Longitude ?? 0, Remark = item.Remark, FaultCount = faultCount, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; var faultEntity = Insertable(model).ExecuteReturnEntity(); // 将第2个及之后的时间插入频次时间记录表 if (allTimes.Count > 1) { for (int j = 1; j < allTimes.Count; j++) { var timeRecord = new OdfCableFaultTimes { FaultId = faultEntity.Id, FaultTime = allTimes[j], CreatedAt = DateTime.Now }; Context.Insertable(timeRecord).ExecuteCommand(); } } successCount++; } catch (Exception) { errorCount++; errorMsgs.Add($"第{rowNum}行:导入失败"); } } string errorMsg = errorMsgs.Count > 0 ? string.Join(";", errorMsgs.Take(10)) : ""; return (successCount, errorCount, errorMsg); } /// /// 更新表显里程矫正 /// public int UpdateMileageCorrection(int id, string mileageCorrection) { var fault = GetFirst(f => f.Id == id); if (fault == null) { throw new CustomException("故障记录不存在"); } fault.MileageCorrection = mileageCorrection; fault.UpdatedAt = DateTime.Now; return Update(fault, true); } } }