/* * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the dreamlu.net developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: Chill 庄骞 (smallchill@163.com) */ package org.sxkj.gd.workorder.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.extern.slf4j.Slf4j; import org.springblade.core.secure.utils.AuthUtil; import org.sxkj.gd.workorder.dto.GdTaskResultDTO; import org.sxkj.gd.workorder.entity.GdDeviceCallDetailEntity; import org.sxkj.gd.workorder.entity.GdDeviceCallEntity; import org.sxkj.gd.workorder.entity.GdManageDeviceEntity; import org.sxkj.gd.workorder.entity.GdPatrolTaskEntity; import org.sxkj.gd.workorder.entity.GdTaskResultEntity; import org.sxkj.gd.workorder.service.IGdDeviceCallDetailService; import org.sxkj.gd.workorder.service.IGdDeviceCallService; import org.sxkj.gd.workorder.service.IGdManageDeviceService; import org.sxkj.gd.workorder.service.IGdPatrolTaskService; import org.sxkj.gd.workorder.service.IGdTaskResultService; import org.sxkj.gd.workorder.vo.GdTaskResultVO; import org.sxkj.gd.workorder.excel.GdTaskResultExcel; import org.sxkj.gd.workorder.mapper.GdTaskResultMapper; import org.sxkj.common.utils.HeaderUtils; import org.sxkj.common.utils.SpringContextUtil; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.StringUtil; import org.sxkj.gd.workorder.wrapper.GdTaskResultWrapper; import org.sxkj.system.cache.SysCache; import org.sxkj.system.cache.UserCache; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * 成果表 服务实现类 * * @author lw * @since 2026-01-14 */ @Slf4j @Service public class GdTaskResultServiceImpl extends BaseServiceImpl implements IGdTaskResultService { /** * 日志数据解析后的内部类 */ @lombok.Data private static class DeviceCallLogItem { private String content; private String createTime; private String createUserName; private String delFlag; private String flyDataId; private String id; private String pilotSn; } @Override public IPage selectGdTaskResultPage(IPage page, GdTaskResultVO gdTaskResult) { return page.setRecords(baseMapper.selectGdTaskResultPage(page, gdTaskResult)); } /** * 根据巡查任务ID查询成果列表 * * @param patrolTaskId 巡查任务ID * @param attachmentTypes 附件类型列表(可选,为null或空时查询全部) * @return 成果列表 */ @Override public List listByPatrolTaskId(Long patrolTaskId, List attachmentTypes) { // 步骤1:查询成果列表 List gdTaskResultVOS = baseMapper.selectGdTaskResultListByPatrolTaskId(patrolTaskId, attachmentTypes); // 步骤2:处理URL转义字符 for (GdTaskResultVO gdTaskResultVO : gdTaskResultVOS) { gdTaskResultVO.setResultUrl(unescapeUrl(gdTaskResultVO.getResultUrl())); } return gdTaskResultVOS; } @Override public List exportGdTaskResult(Wrapper queryWrapper) { List gdTaskResultList = baseMapper.exportGdTaskResult(queryWrapper); //gdTaskResultList.forEach(gdTaskResult -> { // gdTaskResult.setTypeName(DictCache.getValue(DictEnum.YES_NO, GdTaskResult.getType())); //}); return gdTaskResultList; } @Override public boolean saveBatchTaskResult(List gdTaskResults) { if (gdTaskResults == null || gdTaskResults.isEmpty()) { log.error("saveBatchTaskResult-批量新增-对外接口数据为空"); return false; } // 按dateType分组处理 Map> groupedByDateType = gdTaskResults.stream() .collect(Collectors.groupingBy(dto -> dto.getDateType() != null ? dto.getDateType() : 0)); // 处理 dateType != 4 的数据,保存到成果表 List normalResults = groupedByDateType.entrySet().stream() .filter(entry -> entry.getKey() != 4) .flatMap(entry -> entry.getValue().stream()) .collect(Collectors.toList()); if (!normalResults.isEmpty()) { saveNormalTaskResults(normalResults); } // 处理 dateType == 4 的数据,保存到设备调用表和设备调用详情表 List logResults = groupedByDateType.get(4); if (logResults != null && !logResults.isEmpty()) { saveDeviceCallData(logResults); } return true; } /** * 保存普通成果数据(dateType != 4) * * @param gdTaskResults 成果DTO列表 * @return 保存成功后的成果ID列表 */ @Override public List batchSaveWithIds(List gdTaskResults) { // 步骤1:转换DTO为实体 List gdTaskResultEntities = GdTaskResultWrapper.build().listEntity(gdTaskResults); // 步骤2:处理每个实体的属性 gdTaskResultEntities.forEach(gdTaskResult -> { String processedAreaCode = HeaderUtils.processAreaCode(gdTaskResult.getAreaCode()); gdTaskResult.setAreaCode(processedAreaCode); // 如果resultCode为空,生成时间戳作为默认值 if (StringUtil.isEmpty(gdTaskResult.getResultCode())) { gdTaskResult.setResultCode(String.valueOf(System.currentTimeMillis())); } if (gdTaskResult.getShootTime() == null) { gdTaskResult.setShootTime(new Date()); } gdTaskResult.setStatus(0); gdTaskResult.setUpdateTime(new Date()); gdTaskResult.setCreateTime(new Date()); gdTaskResult.setIsDeleted(0); gdTaskResult.setCreateDept(Long.valueOf(AuthUtil.getDeptId())); gdTaskResult.setUpdateUser(AuthUtil.getUserId()); gdTaskResult.setDistributeStatus(0); }); // 步骤3:批量插入数据 int insertCount = baseMapper.insertBatch(gdTaskResultEntities); // 步骤4:收集并返回保存后的ID列表 return gdTaskResultEntities.stream() .map(GdTaskResultEntity::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); } /** * 保存普通成果数据(dateType != 4) * * @param gdTaskResults 成果DTO列表 */ private void saveNormalTaskResults(List gdTaskResults) { List gdTaskResultEntities = GdTaskResultWrapper.build().listEntity(gdTaskResults); IGdPatrolTaskService patrolTaskService = SpringContextUtil.getBean(IGdPatrolTaskService.class); // 查询巡查任务信息 GdPatrolTaskEntity patrolTask = patrolTaskService.getById(gdTaskResultEntities.get(0).getPatrolTaskId()); if (patrolTask == null) { log.warn("巡查任务不存在:" + patrolTask.getId()); return; } gdTaskResultEntities.forEach(gdTaskResult -> { String processedAreaCode = HeaderUtils.processAreaCode(gdTaskResult.getAreaCode()); gdTaskResult.setAreaCode(processedAreaCode); // 如果resultCode为空,生成时间戳作为默认值 if (StringUtil.isEmpty(gdTaskResult.getResultCode())) { gdTaskResult.setResultCode(String.valueOf(System.currentTimeMillis())); } if (gdTaskResult.getShootTime() == null) { gdTaskResult.setShootTime(new Date()); } gdTaskResult.setStatus(0); gdTaskResult.setUpdateTime(new Date()); gdTaskResult.setCreateTime(new Date()); gdTaskResult.setIsDeleted(0); gdTaskResult.setCreateDept(patrolTask.getCreateDept()); gdTaskResult.setUpdateUser(patrolTask.getCreateUser()); gdTaskResult.setDistributeStatus(0); }); int i = baseMapper.insertBatch(gdTaskResultEntities); } /** * 保存设备调用数据(dateType == 4) * geojson字段解析后保存到设备调用详情表,同时创建一条设备调用主表记录 * * @param logResults 日志类型的成果DTO列表 */ private void saveDeviceCallData(List logResults) { // 使用SpringContextUtil动态获取服务,避免循环依赖 IGdPatrolTaskService patrolTaskService = SpringContextUtil.getBean(IGdPatrolTaskService.class); IGdDeviceCallService deviceCallService = SpringContextUtil.getBean(IGdDeviceCallService.class); IGdDeviceCallDetailService deviceCallDetailService = SpringContextUtil.getBean(IGdDeviceCallDetailService.class); IGdManageDeviceService manageDeviceService = SpringContextUtil.getBean(IGdManageDeviceService.class); for (GdTaskResultDTO logResult : logResults) { Long patrolTaskId = logResult.getPatrolTaskId(); String geojson = logResult.getGeojson(); if (patrolTaskId == null || StringUtil.isEmpty(geojson)) { log.warn("设备调用数据不完整,patrolTaskId: {}, geojson: {}", patrolTaskId, geojson); continue; } // 查询巡查任务信息 GdPatrolTaskEntity patrolTask = patrolTaskService.getById(patrolTaskId); if (patrolTask == null) { log.warn("巡查任务不存在:" + patrolTaskId); continue; } // 解析geojson数据 List logItems = parseGeojson(geojson); if (logItems == null || logItems.isEmpty()) { log.warn("geojson解析结果为空:" + geojson); continue; } // 保存设备调用主表记录 GdDeviceCallEntity callEntity = buildDeviceCallEntity(patrolTask, logItems, manageDeviceService); boolean b = deviceCallService.saveOrUpdate(callEntity); // 保存设备调用详情记录 List detailEntities = buildDeviceCallDetailEntities(callEntity.getId(), logItems); boolean b1 = deviceCallDetailService.saveBatch(detailEntities); } } /** * 解析geojson字段,提取设备调用日志列表 * * @param geojson geojson字符串 * @return 日志项列表 */ private List parseGeojson(String geojson) { try { return JSON.parseObject(geojson, new TypeReference>() {}); } catch (Exception e) { log.error("解析geojson失败: " + geojson, e); return null; } } /** * 构建设备调用主表实体 * * @param patrolTask 巡查任务实体 * @param logItems 日志项列表 * @param manageDeviceService 设备管理服务 * @return 设备调用实体 */ private GdDeviceCallEntity buildDeviceCallEntity(GdPatrolTaskEntity patrolTask, List logItems, IGdManageDeviceService manageDeviceService) { GdDeviceCallEntity callEntity = new GdDeviceCallEntity(); callEntity.setPatrolTaskName(patrolTask.getPatrolTaskName()); callEntity.setTaskDepartment(SysCache.getDeptName(patrolTask.getCreateDept())); callEntity.setTaskDepartmentId(patrolTask.getCreateDept()); callEntity.setTaskInitiator(String.valueOf(patrolTask.getCreateUser())); callEntity.setTaskInitiatorId(patrolTask.getCreateUser()); callEntity.setPlanExecuteTime(patrolTask.getExecuteTime()); // 从日志项中获取设备SN String deviceSn = logItems.stream() .map(DeviceCallLogItem::getPilotSn) .filter(StringUtil::hasText) .findFirst() .orElse(null); if (StringUtil.hasText(deviceSn)) { GdManageDeviceEntity device = manageDeviceService.getOne( Wrappers.lambdaQuery(GdManageDeviceEntity.class) .eq(GdManageDeviceEntity::getDeviceSn, deviceSn), false ); if (device != null) { callEntity.setDeviceId(device.getId()); callEntity.setDeviceName(device.getDeviceName()); } } // 计算飞行时长(使用日志中最小时间和最大时间做差值计算) if (!logItems.isEmpty()) { try { List validDates = logItems.stream() .map(item -> parseDateTime(item.getCreateTime())) .filter(Objects::nonNull) .sorted() .collect(Collectors.toList()); if (!validDates.isEmpty()) { Date startTime = validDates.get(0); Date endTime = validDates.get(validDates.size() - 1); long durationSeconds = (endTime.getTime() - startTime.getTime()) / 1000; callEntity.setFlightDuration(durationSeconds); callEntity.setActualExecuteTime(startTime); } } catch (Exception e) { log.warn("计算飞行时长失败", e); callEntity.setFlightDuration(0L); } } return callEntity; } /** * 构建设备调用详情实体列表 * * @param patrolTaskId 巡查任务ID * @param logItems 日志项列表 * @return 设备调用详情实体列表 */ private List buildDeviceCallDetailEntities(Long patrolTaskId, List logItems) { List detailEntities = new ArrayList<>(); int sort = 1; for (DeviceCallLogItem logItem : logItems) { GdDeviceCallDetailEntity detailEntity = new GdDeviceCallDetailEntity(); detailEntity.setCallId(patrolTaskId); detailEntity.setDeviceStatus(logItem.getContent()); detailEntity.setOccurTime(parseDateTime(logItem.getCreateTime())); detailEntity.setSort(sort++); detailEntities.add(detailEntity); } return detailEntities; } /** * 解析日期时间字符串 * * @param dateTimeStr 日期时间字符串 * @return Date对象 */ private Date parseDateTime(String dateTimeStr) { if (StringUtil.isEmpty(dateTimeStr)) { return null; } try { if (dateTimeStr.contains("T")) { dateTimeStr = dateTimeStr.replace("T", " "); } if (dateTimeStr.contains(".")) { dateTimeStr = dateTimeStr.substring(0, dateTimeStr.indexOf(".")); } return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateTimeStr); } catch (Exception e) { log.warn("日期解析失败: " + dateTimeStr, e); return null; } } @Override public boolean updateTaskResultById(GdTaskResultEntity taskResult) { int result = baseMapper.updateTaskResultById(taskResult); return result > 0; } /** * 将URL中的HTML/XML转义字符还原为原始字符 * 主要处理 & 转换为 & * * @param url 包含转义字符的URL * @return 还原后的URL */ public static String unescapeUrl(String url) { if (url == null || url.isEmpty()) { return url; } // 按照优先级顺序进行替换,避免重复替换问题 String result = url; // 处理常见的HTML/XML转义字符 result = result.replace("&", "&"); // &符号 result = result.replace("<", "<"); // 小于号 result = result.replace(">", ">"); // 大于号 result = result.replace(""", "\""); // 双引号 result = result.replace("'", "'"); // 单引号 result = result.replace("'", "'"); // 单引号 return result; } @Override public void downloadResultFiles(String ids, HttpServletResponse response) { // 根据ID列表查询成果记录 List idList = Func.toLongList(ids); List resultList = listByIds(idList); if (resultList == null || resultList.isEmpty()) { throw new RuntimeException("未找到成果数据"); } // 设置响应头 response.setContentType("application/zip"); response.setHeader("Content-Disposition", "attachment; filename=result_files.zip"); try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) { for (GdTaskResultEntity result : resultList) { String resultUrl = result.getResultUrl(); if (resultUrl == null || resultUrl.isEmpty()) { continue; } // 处理转义字符 resultUrl = unescapeUrl(resultUrl); try { // 从URL下载文件 URL url = new URL(resultUrl); URLConnection connection = url.openConnection(); connection.setConnectTimeout(5000); connection.setReadTimeout(10000); // 从URL中提取文件名 String fileName = extractFileName(resultUrl, result.getId()); // 添加到ZIP ZipEntry zipEntry = new ZipEntry(fileName); zos.putNextEntry(zipEntry); try (InputStream is = connection.getInputStream()) { byte[] buffer = new byte[4096]; int len; while ((len = is.read(buffer)) > 0) { zos.write(buffer, 0, len); } } zos.closeEntry(); } catch (Exception e) { // 单个文件下载失败,继续处理其他文件 // 可以记录日志 } } } catch (IOException e) { throw new RuntimeException("下载文件失败", e); } } /** * 从URL中提取文件名 */ private String extractFileName(String url, Long resultId) { if (url == null || url.isEmpty()) { return resultId + "_unknown"; } // 从URL中提取最后一部分作为文件名 int lastSlash = url.lastIndexOf('/'); if (lastSlash != -1 && lastSlash < url.length() - 1) { String fileName = url.substring(lastSlash + 1); // 处理可能的查询参数 int questionMark = fileName.indexOf('?'); if (questionMark != -1) { fileName = fileName.substring(0, questionMark); } return resultId + "_" + fileName; } return resultId + "_file"; } }