/*
|
* 【机场1】从 dock2 原类 com.ard.sample.wayline.service.impl.FlightTaskServiceImpl 复制/独立适配(DJI Dock 1 / M30)。
|
* 与 dock2 原类同服务共存;本文件内的适配差异以「// 机场1:」内联注释标出。
|
*/
|
package com.ard.sample.dock1.wayline.service.impl;
|
|
import com.ard.common.utils.uuid.IdUtils;
|
import com.ard.route.domain.route.ArdRouteInfo;
|
import com.ard.route.service.IArdRouteInfoService;
|
import com.ard.route.service.IArdRouteMissionService;
|
import com.ard.sample.common.error.CommonErrorEnum;
|
import com.ard.sample.common.model.ResponseResult;
|
import com.ard.sample.component.mqtt.model.*;
|
import com.ard.sample.component.mqtt.service.IMessageSenderService;
|
import com.ard.common.core.redis.RedisConst;
|
import com.ard.common.core.redis.RedisOpsUtils;
|
import com.ard.sample.component.websocket.enums.MessageType;
|
import com.ard.sample.component.websocket.handler.DockWebSocketHandler;
|
import com.ard.sample.component.websocket.model.BizCodeEnum;
|
import com.ard.sample.component.websocket.model.CustomWebSocketMessage;
|
import com.ard.sample.component.websocket.service.ISendMessageService;
|
import com.ard.sample.component.websocket.service.IWebSocketManageService;
|
import com.ard.sample.manage.model.dto.DeviceDTO;
|
import com.ard.sample.manage.model.enums.UserTypeEnum;
|
// 机场1:任务状态(type="1")与轨迹点(type="0")必须写入同一个共享轨迹服务缓存。
|
import com.ard.sample.manage.service.IWaylineTrajectoryService;
|
import com.ard.sample.media.model.MediaFileCountDTO;
|
import com.ard.sample.wayline.model.dto.WaylineJobDTO;
|
import com.ard.sample.wayline.model.dto.WaylineTaskProgressExt;
|
import com.ard.sample.wayline.model.dto.WaylineTaskProgressReceiver;
|
import com.ard.sample.wayline.model.entity.WaylineJobHistoryEntity;
|
import com.ard.sample.wayline.model.enums.WaylineJobStatusEnum;
|
import com.ard.sample.dock1.wayline.service.IFlightTaskService;
|
import com.ard.sample.wayline.service.IWaylineJobHistoryService;
|
import com.ard.sample.dock1.wayline.service.IWaylineJobService;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.http.HttpStatus;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.integration.mqtt.support.MqttHeaders;
|
import org.springframework.messaging.MessageHeaders;
|
import org.springframework.stereotype.Service;
|
|
import java.time.LocalDateTime;
|
import java.util.HashMap;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Optional;
|
import java.util.concurrent.TimeUnit;
|
import com.ard.sample.wayline.service.impl.FlightTaskServiceImpl;
|
|
/**
|
* @author sean
|
* @version 1.1
|
* @date 2022/6/9
|
*/
|
@Service
|
@Slf4j
|
public class Dock1FlightTaskServiceImpl implements IFlightTaskService {
|
|
@Autowired
|
private IMessageSenderService messageSender;
|
|
@Autowired
|
private ObjectMapper mapper;
|
|
@Autowired
|
private DockWebSocketHandler _dockWebSocketHandler;
|
|
@Autowired
|
private ISendMessageService websocketMessageService;
|
|
@Autowired
|
private IWebSocketManageService webSocketManageService;
|
|
@Autowired
|
private IWaylineJobService waylineJobService;
|
|
@Autowired
|
private IWaylineTrajectoryService _waylineTrajectoryService;
|
|
@Autowired
|
private IWaylineJobHistoryService _waylineJobHistoryService;
|
|
@Autowired
|
private IArdRouteMissionService _ardRouteMissionService;
|
|
@Autowired
|
private IArdRouteInfoService _ardRouteInfoService;
|
|
@Override
|
// 机场1:不复制原 ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS),消费职责由共享层原类承担
|
public void handleProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
|
String receivedTopic = String.valueOf(headers.get(MqttHeaders.RECEIVED_TOPIC));
|
String dockSn = receivedTopic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(),
|
receivedTopic.indexOf(TopicConst.EVENTS_SUF));
|
EventsReceiver<WaylineTaskProgressReceiver> eventsReceiver = mapper.convertValue(receiver.getData(),
|
new TypeReference<EventsReceiver<WaylineTaskProgressReceiver>>() {
|
});
|
eventsReceiver.setBid(receiver.getBid());
|
eventsReceiver.setSn(receiver.getGateway());
|
eventsReceiver.setGateway(receiver.getGateway());
|
eventsReceiver.setType(MessageType.STATUS.getType() + "-flighttask_progress");
|
eventsReceiver.setMethod(EventsMethodEnum.FLIGHT_TASK_PROGRESS.getMethod());
|
|
|
WaylineTaskProgressReceiver output = eventsReceiver.getOutput();
|
|
log.info("Task progress: {}", output.getProgress().toString());
|
|
if (eventsReceiver.getResult() != ResponseResult.CODE_SUCCESS) {
|
String errorMsg = CommonErrorEnum.getMsgByCode(eventsReceiver.getResult());
|
log.error("Task progress ===> Error code: {},Error Msg: {}", eventsReceiver.getResult(), errorMsg);
|
}
|
|
EventsResultStatusEnum statusEnum = EventsResultStatusEnum.find(output.getStatus());
|
String key = RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn;
|
RedisOpsUtils.setWithExpire(key, eventsReceiver, RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND);
|
|
if (statusEnum.getEnd()) {
|
WaylineJobDTO job = WaylineJobDTO.builder()
|
.jobId(receiver.getBid())
|
.status(WaylineJobStatusEnum.SUCCESS.getVal())
|
.completedTime(LocalDateTime.now())
|
.mediaCount(output.getExt().getMediaCount())
|
.build();
|
|
// record the update of the media count.
|
if (Objects.nonNull(job.getMediaCount()) && job.getMediaCount() != 0) {
|
RedisOpsUtils.hashSet(RedisConst.MEDIA_FILE_PREFIX + receiver.getGateway(), job.getJobId(),
|
MediaFileCountDTO.builder().jobId(receiver.getBid()).mediaCount(job.getMediaCount()).uploadedCount(0).build());
|
}
|
|
if (EventsResultStatusEnum.OK != statusEnum) {
|
job.setCode(eventsReceiver.getResult());
|
job.setStatus(WaylineJobStatusEnum.FAILED.getVal());
|
}
|
|
waylineJobService.updateJob(job);
|
RedisOpsUtils.del(key);
|
RedisOpsUtils.del(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + receiver.getBid());
|
}
|
|
DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway());
|
websocketMessageService.sendBatch(
|
webSocketManageService.getValueAndUserType(UserTypeEnum.WEB.getVal()),
|
CustomWebSocketMessage.builder()
|
.data(eventsReceiver)
|
.timestamp(System.currentTimeMillis())
|
.bizCode(BizCodeEnum.FLIGHT_TASK_PROGRESS.getCode())
|
.build());
|
|
if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) {
|
messageSender.publish(receivedTopic + TopicConst._REPLY_SUF,
|
CommonTopicResponse.builder()
|
.tid(receiver.getTid())
|
.bid(receiver.getBid())
|
.method(EventsMethodEnum.FLIGHT_TASK_PROGRESS.getMethod())
|
.timestamp(System.currentTimeMillis())
|
.data(RequestsReply.success())
|
.build());
|
}
|
// 进度
|
// if (eventsReceiver.getOutput().getProgress().getPercent() >= 95) {
|
// return;
|
// }
|
|
|
|
String json = "";
|
try {
|
json = mapper.writeValueAsString(eventsReceiver);
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
_dockWebSocketHandler.sendMessageToSubscribers(dockSn, MessageType.STATUS.getType(), json, "");
|
|
_waylineTrajectoryService.setList(dockSn,"1",json);
|
|
// 计划任务成功后,保存计划任务到历史记录
|
String status = eventsReceiver.getOutput().getStatus();
|
String jobId = eventsReceiver.getOutput().getExt().getFlightId();
|
if(status.equals("ok") && jobId != null) {
|
Optional<WaylineJobDTO> job = waylineJobService.getJobByJobId(jobId);
|
if(job.isPresent()) {
|
if (_waylineJobHistoryService.getJobByJobId(jobId).isPresent()) {
|
return;
|
}
|
WaylineJobHistoryEntity waylineJobHistoryEntity = _waylineJobHistoryService.dto2Entity(job.get());
|
Optional<WaylineJobDTO> jobHistory = _waylineJobHistoryService.createWaylineJob(waylineJobHistoryEntity);
|
if(jobHistory.isPresent()) {
|
log.info("任务执行成功,保存到历史记录。 {}", jobHistory);
|
}
|
// 同步更新4.2的计划任务的状态;等待夏吉建的接口。1:已执行 0:未执行
|
// 在任务中查找航线文件id,取航线文件信息中的状态,是3的修改状态
|
ArdRouteInfo ardRouteInfo = _ardRouteInfoService.selectArdRouteInfoById(job.get().getFileId());
|
if(ardRouteInfo != null && ardRouteInfo.getType().equals("3")) {
|
_ardRouteMissionService.updateArdRouteMissionStatus(jobId, "1");
|
log.info("任务航线-执行成功,更新航线状态。 jobId:{}", jobId);
|
}
|
}
|
|
}
|
}
|
|
/**
|
* 定时检查并处理航线定时任务
|
* <p>
|
* 该方法每5秒执行一次,从Redis有序集合中获取最早需要执行的任务,检查其执行时间:
|
* 1. 若任务已过期(超过当前时间30秒),则标记为失败并删除
|
* 2. 若任务在允许的时间窗口内(当前时间±30秒),则执行任务并删除
|
* </p>
|
*
|
* <p>任务ID格式:{workspace_id}:{dock_sn}:{job_id}</p>
|
*/
|
// 机场1:不复制原 Scheduled 定时任务,定时职责由共享层原类承担
|
private void checkScheduledJob() {
|
Object jobIdValue = RedisOpsUtils.zGetMin(RedisConst.WAYLINE_JOB_TIMED_EXECUTE);
|
if (Objects.isNull(jobIdValue)) {
|
return;
|
}
|
log.info("检查 wayline 的定时任务。 {}", jobIdValue);
|
// format: {workspace_id}:{dock_sn}:{job_id}
|
String[] jobArr = String.valueOf(jobIdValue).split(RedisConst.DELIMITER);
|
double time = RedisOpsUtils.zScore(RedisConst.WAYLINE_JOB_TIMED_EXECUTE, jobIdValue);
|
// region 取定时任务信息websocket发给前端
|
// 定时任务执行前,判断有没有任务正在执行,在执行则不发送websocket消息
|
String key = RedisConst.WAYLINE_JOB_RUNNING_PREFIX + jobArr[0];
|
if (!RedisOpsUtils.checkExist(key)) {
|
// 取到待执行的定时任务ID,获取任务信息websocket发给前端
|
// pending status状态待执行
|
EventsReceiver<WaylineTaskProgressReceiver> eventsReceiver = new EventsReceiver<>();
|
WaylineTaskProgressReceiver waylineTaskProgressReceiver = new WaylineTaskProgressReceiver();
|
WaylineTaskProgressExt waylineTaskProgressExt = new WaylineTaskProgressExt();
|
waylineTaskProgressExt.setFlightId(jobArr[1]);
|
waylineTaskProgressReceiver.setStatus("pending");
|
waylineTaskProgressReceiver.setExt(waylineTaskProgressExt);
|
eventsReceiver.setOutput(waylineTaskProgressReceiver);
|
eventsReceiver.setResult(ResponseResult.CODE_SUCCESS);
|
eventsReceiver.setBid(IdUtils.simpleUUID());
|
eventsReceiver.setSn(jobArr[0]);
|
eventsReceiver.setGateway(jobArr[0]);
|
eventsReceiver.setType(MessageType.STATUS.getType() + "-flighttask_progress");
|
eventsReceiver.setMethod(EventsMethodEnum.FLIGHT_TASK_PROGRESS.getMethod());
|
String json = "";
|
try {
|
json = mapper.writeValueAsString(eventsReceiver);
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
_dockWebSocketHandler.sendMessageToSubscribers(jobArr[0], MessageType.STATUS.getType(), json, "");
|
log.info("定时任务自定义状态为待执行,json: {}", json);
|
}
|
// endregion
|
long now = System.currentTimeMillis();
|
int offset = 30_000;
|
|
// 过期任务会直接删除。
|
if (time < now - offset) {
|
log.info("定时任务超时,jobId: {}", jobArr[1]);
|
RedisOpsUtils.zRemove(RedisConst.WAYLINE_JOB_TIMED_EXECUTE, jobIdValue);
|
waylineJobService.updateJob(WaylineJobDTO.builder()
|
.jobId(jobArr[1])
|
.status(WaylineJobStatusEnum.FAILED.getVal())
|
.executeTime(LocalDateTime.now())
|
.completedTime(LocalDateTime.now())
|
.code(HttpStatus.SC_REQUEST_TIMEOUT).build());
|
return;
|
}
|
|
if (now <= time && time <= now + offset) {
|
try {
|
waylineJobService.executeFlightTask(jobArr[1]);
|
} catch (Exception e) {
|
log.info("计划中的任务交付失败了。jobId: {},异常信息:{}", jobArr[1], e.getMessage());
|
waylineJobService.updateJob(WaylineJobDTO.builder()
|
.jobId(jobArr[1])
|
.status(WaylineJobStatusEnum.FAILED.getVal())
|
.executeTime(LocalDateTime.now())
|
.completedTime(LocalDateTime.now())
|
.code(HttpStatus.SC_INTERNAL_SERVER_ERROR).build());
|
} finally {
|
RedisOpsUtils.zRemove(RedisConst.WAYLINE_JOB_TIMED_EXECUTE, jobIdValue);
|
}
|
}
|
}
|
}
|