吉安感知网项目-后端
linwei
6 days ago 05fb356099b5af472ee23d9164bca61962d9c2ed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
/*
 *      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<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列表
     * @return 保存成功后的成果ID列表
     */
    @Override
    public List<Long> batchSaveWithIds(List<GdTaskResultDTO> gdTaskResults) {
        // 步骤1:转换DTO为实体
        List<GdTaskResultEntity> 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<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转义字符还原为原始字符
     * 主要处理 &amp; 转换为 &
     *
     * @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("&amp;", "&");     // &符号
        result = result.replace("&lt;", "<");      // 小于号
        result = result.replace("&gt;", ">");      // 大于号
        result = result.replace("&quot;", "\"");   // 双引号
        result = result.replace("&#39;", "'");     // 单引号
        result = result.replace("&apos;", "'");    // 单引号
 
        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";
    }
 
}