/*
|
* 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.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<GdTaskResultMapper, GdTaskResultEntity> 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<GdTaskResultVO> selectGdTaskResultPage(IPage<GdTaskResultVO> page, GdTaskResultVO gdTaskResult) {
|
return page.setRecords(baseMapper.selectGdTaskResultPage(page, gdTaskResult));
|
}
|
|
/**
|
* 根据巡查任务ID查询成果列表
|
*
|
* @param patrolTaskId 巡查任务ID
|
* @param attachmentTypes 附件类型列表(可选,为null或空时查询全部)
|
* @return 成果列表
|
*/
|
@Override
|
public List<GdTaskResultVO> listByPatrolTaskId(Long patrolTaskId, List<Integer> attachmentTypes) {
|
// 步骤1:查询成果列表
|
List<GdTaskResultVO> gdTaskResultVOS = baseMapper.selectGdTaskResultListByPatrolTaskId(patrolTaskId, attachmentTypes);
|
// 步骤2:处理URL转义字符
|
for (GdTaskResultVO gdTaskResultVO : gdTaskResultVOS) {
|
gdTaskResultVO.setResultUrl(unescapeUrl(gdTaskResultVO.getResultUrl()));
|
}
|
return gdTaskResultVOS;
|
}
|
|
|
@Override
|
public List<GdTaskResultExcel> exportGdTaskResult(Wrapper<GdTaskResultEntity> queryWrapper) {
|
List<GdTaskResultExcel> gdTaskResultList = baseMapper.exportGdTaskResult(queryWrapper);
|
//gdTaskResultList.forEach(gdTaskResult -> {
|
// gdTaskResult.setTypeName(DictCache.getValue(DictEnum.YES_NO, GdTaskResult.getType()));
|
//});
|
return gdTaskResultList;
|
}
|
|
@Override
|
public boolean saveBatchTaskResult(List<GdTaskResultDTO> gdTaskResults) {
|
if (gdTaskResults == null || gdTaskResults.isEmpty()) {
|
log.error("saveBatchTaskResult-批量新增-对外接口数据为空");
|
return false;
|
}
|
|
// 按dateType分组处理
|
Map<Integer, List<GdTaskResultDTO>> groupedByDateType = gdTaskResults.stream()
|
.collect(Collectors.groupingBy(dto -> dto.getDateType() != null ? dto.getDateType() : 0));
|
|
// 处理 dateType != 4 的数据,保存到成果表
|
List<GdTaskResultDTO> 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<GdTaskResultDTO> logResults = groupedByDateType.get(4);
|
if (logResults != null && !logResults.isEmpty()) {
|
saveDeviceCallData(logResults);
|
}
|
|
return true;
|
}
|
|
/**
|
* 保存普通成果数据(dateType != 4)
|
*
|
* @param gdTaskResults 成果DTO列表
|
*/
|
private void saveNormalTaskResults(List<GdTaskResultDTO> gdTaskResults) {
|
List<GdTaskResultEntity> 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<GdTaskResultDTO> 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<DeviceCallLogItem> 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<GdDeviceCallDetailEntity> detailEntities = buildDeviceCallDetailEntities(callEntity.getId(), logItems);
|
boolean b1 = deviceCallDetailService.saveBatch(detailEntities);
|
}
|
}
|
|
/**
|
* 解析geojson字段,提取设备调用日志列表
|
*
|
* @param geojson geojson字符串
|
* @return 日志项列表
|
*/
|
private List<DeviceCallLogItem> parseGeojson(String geojson) {
|
try {
|
return JSON.parseObject(geojson, new TypeReference<List<DeviceCallLogItem>>() {});
|
} catch (Exception e) {
|
log.error("解析geojson失败: " + geojson, e);
|
return null;
|
}
|
}
|
|
/**
|
* 构建设备调用主表实体
|
*
|
* @param patrolTask 巡查任务实体
|
* @param logItems 日志项列表
|
* @param manageDeviceService 设备管理服务
|
* @return 设备调用实体
|
*/
|
private GdDeviceCallEntity buildDeviceCallEntity(GdPatrolTaskEntity patrolTask, List<DeviceCallLogItem> 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<Date> 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<GdDeviceCallDetailEntity> buildDeviceCallDetailEntities(Long patrolTaskId, List<DeviceCallLogItem> logItems) {
|
List<GdDeviceCallDetailEntity> 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<Long> idList = Func.toLongList(ids);
|
List<GdTaskResultEntity> 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";
|
}
|
|
}
|