增加导入航线(未测试)

This commit is contained in:
sdy 2026-06-22 13:43:20 +08:00
parent 86aabebb80
commit f4e0dae1b7
7 changed files with 507 additions and 5 deletions

View File

@ -2,11 +2,15 @@ package com.multictrl.common.utils.kmz;
import com.multictrl.common.exception.RenException;
import com.multictrl.modules.business.dto.RouteDTO;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@ -96,4 +100,90 @@ public class KmzUtils {
}
}
}
/**
* 解析 KMZ 文件提取指定内容为 byte[]
*
* @param inputStream KMZ 文件流
* @return 包含文件名与 byte[] 的映射
* @throws IOException IO 异常
*/
public static Map<String, byte[]> extractKmzAsBytes(InputStream inputStream) throws IOException {
Map<String, byte[]> result = new HashMap<>();
try (ZipArchiveInputStream zipStream = new ZipArchiveInputStream(inputStream)) {
ZipArchiveEntry entry;
while ((entry = zipStream.getNextEntry()) != null) {
String entryName = entry.getName();
if (WPMZ_TEMPLATE_KML.equals(entryName)) {
result.put("template.kml", readStreamToBytes(zipStream));
} else if (WPMZ_WAYLINES_WPML.equals(entryName)) {
result.put("waylines.wpml", readStreamToBytes(zipStream));
}
}
}
return result;
}
/**
* InputStream 读取为 byte[]
*
* @param inputStream 输入流
* @return 字节数组
* @throws IOException IO 异常
*/
private static byte[] readStreamToBytes(InputStream inputStream) throws IOException {
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
byte[] data = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytesRead);
}
return buffer.toByteArray();
}
}
/**
* zip 输入流解压到目标目录完整保留 zip 内的目录结构
*
* @param zipStream zip 输入流调用方负责关闭外层流
* @param destDir 目标目录
* @throws IOException 解压失败
*/
public static void unzipToDirectory(InputStream zipStream, File destDir) throws Exception {
if (!destDir.exists()) {
Files.createDirectories(destDir.toPath());
}
try (ZipInputStream zis = new ZipInputStream(zipStream)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String entryName = entry.getName();
File targetFile = new File(destDir, entryName);
Path targetPath = targetFile.getCanonicalFile().toPath();
Path destPath = destDir.getCanonicalFile().toPath();
if (!targetPath.startsWith(destPath)) {
throw new RenException("非法路径:" + entryName);
}
if (entry.isDirectory()) {
Files.createDirectories(targetFile.toPath());
} else {
Files.createDirectories(targetFile.getParentFile().toPath());
try (FileOutputStream fos = new FileOutputStream(targetFile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
}
zis.closeEntry();
}
}
}
}

View File

@ -0,0 +1,279 @@
package com.multictrl.common.utils.kmz;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.constant.SetActionParams;
import com.multictrl.common.exception.RenException;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.dto.RouteWaypointDTO;
import com.multictrl.modules.business.dto.WaypointActionDTO;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
/**
* kml解析
*
* @author Sdy
* @since 1.0.0 2026/6/18
*/
public class WpmlKmlParser {
public static RouteDTO parseKmlFile(byte[] bytes, Boolean accurateImport) throws DocumentException {
RouteDTO dto = new RouteDTO();
SAXReader reader = new SAXReader();
Document document = reader.read(new ByteArrayInputStream(bytes));
Element root = document.getRootElement();
Element doc = root.element("Document");
if (doc == null) {
throw new RenException("kml文件有误无法解析");
}
Element folder = doc.element("Folder");
if (folder == null) {
throw new RenException("模板信息不存在,无法解析");
}
//解析高度模式
Element waylineCoordinateSysParam = folder.element("waylineCoordinateSysParam");
if (waylineCoordinateSysParam == null) {
if (!accurateImport) {
throw new RenException("坐标系参数有误,无法解析");
}
} else {
String heightMode = filedValid(waylineCoordinateSysParam.elementText("heightMode"), accurateImport);
dto.setHeightModel(heightMode);
}
// 解析
String templateType = folder.elementText("templateType");
if (StringUtils.isEmpty(templateType) && accurateImport) {
return dto;
}
if (StringUtils.isEmpty(templateType) && !accurateImport) {
throw new RenException("模板类型不存在,解析失败");
}
dto.setTemplateType(templateType);
String autoFlightSpeed = filedValid(folder.elementText("autoFlightSpeed"), accurateImport);
dto.setFlightSpeed(autoFlightSpeed == null ? null : Double.parseDouble(autoFlightSpeed));
String globalHeight = filedValid(folder.elementText("globalHeight"), accurateImport);
dto.setFlightHeight(globalHeight == null ? null : Double.parseDouble(globalHeight));
List<RouteWaypointDTO> waypointList = new ArrayList<>();
List<Element> placemarks = folder.elements("Placemark");
if (CollectionUtil.isEmpty(placemarks)) {
if (accurateImport) {
dto.setRouteWaypointList(waypointList);
} else {
throw new RenException("航点信息不存在,解析失败");
}
} else {
for (Element placemark : placemarks) {
RouteWaypointDTO waypoint = new RouteWaypointDTO();
String useGlobalSpeed = placemark.elementText("useGlobalSpeed");
String useGlobalHeight = placemark.elementText("useGlobalHeight");
waypoint.setFollowRouteSpeed(filedValid(useGlobalSpeed, accurateImport) == null ? null : "1".equals(useGlobalSpeed));
waypoint.setFollowRouteHeight(filedValid(useGlobalHeight, accurateImport) == null ? null : "1".equals(useGlobalHeight));
waypointList.add(waypoint);
}
dto.setRouteWaypointList(waypointList);
}
return dto;
}
public static void parseWpmlFile(byte[] bytes, RouteDTO dto, Boolean accurateImport) throws DocumentException, IOException {
SAXReader reader = new SAXReader();
Document document = reader.read(new ByteArrayInputStream(bytes));
Element root = document.getRootElement();
Element doc = root.element("Document");
if (doc == null) {
throw new RenException("wpml文件有误无法解析");
}
Element missionConfig = doc.element("missionConfig");
if (missionConfig == null) {
throw new RenException("任务信息不存在,无法解析");
}
dto.setFlyToWaylineMode(filedValid(missionConfig.elementText("flyToWaylineMode"), accurateImport));
dto.setFinishAction(filedValid(missionConfig.elementText("finishAction"), accurateImport));
String exitOnRCLost = filedValid(missionConfig.elementText("exitOnRCLost"), accurateImport);
dto.setExitOnRcLost(exitOnRCLost);
if ("executeLostAction".equals(exitOnRCLost)) {
dto.setExecuteRcLostAction(filedValid(missionConfig.elementText("executeRCLostAction"), accurateImport));
}
String takeOffSecurityHeight = filedValid(missionConfig.elementText("takeOffSecurityHeight"), accurateImport);
dto.setTakeoffSecurityHeight(StringUtils.isBlank(takeOffSecurityHeight) ? null : Double.parseDouble(missionConfig.elementText("takeOffSecurityHeight")));
String globalTransitionalSpeed = filedValid(missionConfig.elementText("globalTransitionalSpeed"), accurateImport);
dto.setGlobalTransitionalSpeed(StringUtils.isBlank(globalTransitionalSpeed) ? null : Double.parseDouble(globalTransitionalSpeed));
String globalRTHHeight = filedValid(missionConfig.elementText("globalRTHHeight"), accurateImport);
dto.setGlobalRthHeight(StringUtils.isBlank(globalRTHHeight) ? null : Double.parseDouble(globalRTHHeight));
/*Element droneInfo = missionConfig.element("droneInfo");
if (droneInfo == null) {
if (!accurateImport) {
throw new RenException("飞行器机型信息不存在,无法解析");
}
} else {
String droneEnumValue = filedValid(droneInfo.elementText("droneEnumValue"), accurateImport);
String droneSubEnumValue = filedValid(droneInfo.elementText("droneSubEnumValue"), accurateImport);
}*/
/*Element payloadInfo = missionConfig.element("payloadInfo");
if (payloadInfo == null) {
if (!accurateImport) {
throw new RenException("负载机型信息不存在,无法解析");
}
} else {
String payloadEnumValue = filedValid(payloadInfo.elementText("payloadEnumValue"), accurateImport);
String cameraName = CameraModel.getNameByType(payloadEnumValue);
dto.setCameraType(cameraName);
}*/
Element folder = doc.element("Folder");
if (folder == null) {
if (!accurateImport) {
throw new RenException("航线信息不存在,无法解析");
}
} else {
String autoFlightSpeed = filedValid(folder.elementText("autoFlightSpeed"), accurateImport);
dto.setFlightSpeed(StringUtils.isBlank(autoFlightSpeed) ? null : Double.parseDouble(autoFlightSpeed));
List<Element> placemarks = folder.elements("Placemark");
if (CollectionUtil.isEmpty(placemarks)) {
if (!accurateImport) {
throw new RenException("航点信息不存在,无法解析");
}
} else {
List<RouteWaypointDTO> waypointList = dto.getRouteWaypointList();
if (waypointList == null || waypointList.isEmpty()) {
return;
}
for (int i = 0; i < waypointList.size(); i++) {
RouteWaypointDTO waypoint = waypointList.get(i);
Element placemark = placemarks.get(i);
Element point = placemark.element("Point");
if (point == null) {
if (!accurateImport) {
throw new RenException("航点经纬度不存在,无法解析");
}
} else {
String coordinates = point.elementText("coordinates");
String[] split = coordinates.split(",");
waypoint.setLongitude(Double.valueOf(split[0]));
waypoint.setLatitude(Double.valueOf(split[1]));
}
String index = filedValid(placemark.elementText("index"), accurateImport);
waypoint.setWaypointSort(StringUtils.isBlank(index) ? null : Integer.valueOf(index));
String executeHeight = filedValid(placemark.elementText("executeHeight"), accurateImport);
waypoint.setFlightHeight(StringUtils.isBlank(executeHeight) ? null : Double.parseDouble(executeHeight));
String waypointSpeed = filedValid(placemark.elementText("waypointSpeed"), accurateImport);
waypoint.setFlightSpeed(StringUtils.isBlank(waypointSpeed) ? null : Double.parseDouble(waypointSpeed));
Element waypointHeadingParam = placemark.element("waypointHeadingParam");
if (waypointHeadingParam == null) {
if (!accurateImport) {
throw new RenException("偏航角模式参数不存在,无法解析");
}
} else {
waypoint.setWaypointHeadingMode(filedValid(waypointHeadingParam.elementText("waypointHeadingMode"), accurateImport));
String waypointHeadingAngle = filedValid(waypointHeadingParam.elementText("waypointHeadingAngle"), accurateImport);
if (StrUtil.isNotBlank(waypointHeadingAngle)) {
waypoint.setWaypointHeadingAngle(waypointHeadingAngle);
}
String waypointPoiPoint = waypointHeadingParam.elementText("waypointPoiPoint");
if (StrUtil.isNotBlank(waypointPoiPoint)) {
waypoint.setWaypointPoiPoint(waypointPoiPoint);
}
String waypointHeadingPathMode = filedValid(waypointHeadingParam.elementText("waypointHeadingPathMode"), accurateImport);
waypoint.setWaypointHeadingPathMode(StringUtils.isBlank(waypointHeadingPathMode) ? "" : waypointHeadingPathMode);
}
Element waypointTurnParam = placemark.element("waypointTurnParam");
if (waypointTurnParam == null) {
if (!accurateImport) {
throw new RenException("航点转弯模式不存在,无法解析");
}
} else {
waypoint.setWaypointTruningMode(filedValid(waypointTurnParam.elementText("waypointTurnMode"), accurateImport));
String waypointTurnDampingDist = filedValid(waypointTurnParam.elementText("waypointTurnDampingDist"), accurateImport);
if (StrUtil.isNotBlank(waypointTurnDampingDist)) {
waypoint.setWaypointTurnDampingDist(waypointTurnDampingDist);
}
}
List<WaypointActionDTO> actionList = waypoint.getWaypointActionList();
if (CollectionUtil.isEmpty(actionList)) {
actionList = new ArrayList<>();
}
List<Element> actionGroups = placemark.elements("actionGroup");
for (Element actionGroup : actionGroups) {
Element actionTrigger = actionGroup.element("actionTrigger");
String actionTriggerType = actionTrigger.elementText("actionTriggerType");
if ("multipleTiming".equals(actionTriggerType) || "multipleDistance".equals(actionTriggerType)) {
String actionGroupId = actionGroup.elementText("actionGroupId");
String actionTriggerParam = actionTrigger.elementText("actionTriggerParam");
WaypointActionDTO action = new WaypointActionDTO();
action.setActionType(actionTriggerType);
action.setActionValue(actionTriggerParam);
action.setActionSort(Integer.parseInt(actionGroupId));
actionList.add(action);
String actionGroupEndIndex = actionGroup.elementText("actionGroupEndIndex");
RouteWaypointDTO uavWaypointDTO = waypointList.get(Integer.parseInt(actionGroupEndIndex));
List<WaypointActionDTO> actionList1 = uavWaypointDTO.getWaypointActionList();
if (CollectionUtil.isEmpty(actionList1)) {
actionList1 = new ArrayList<>();
}
WaypointActionDTO action2 = new WaypointActionDTO();
action2.setActionType(BusinessConstant.FINISH_TAKE_PHOTO_FLAG);
action2.setActionValue(actionGroupEndIndex);
action.setActionSort(Integer.parseInt(actionGroupId));
actionList1.add(action2);
uavWaypointDTO.setWaypointActionList(actionList1);
} else {
List<Element> actions = actionGroup.elements("action");
for (Element act : actions) {
WaypointActionDTO action = new WaypointActionDTO();
String actionActuatorFunc = act.elementText("actionActuatorFunc");
Element actionActuatorFuncParam = act.element("actionActuatorFuncParam");
if ("gimbalRotate".equals(actionActuatorFunc)) {
String gimbalYawRotateEnable = actionActuatorFuncParam.elementText("gimbalYawRotateEnable");
if ("1".equals(gimbalYawRotateEnable)) {
action.setActionType(actionActuatorFunc + "_yaw");
} else {
action.setActionType(actionActuatorFunc);
}
} else {
action.setActionType(actionActuatorFunc);
}
SetActionParams.getAction(action, actionActuatorFuncParam);
actionList.add(action);
}
}
}
waypoint.setWaypointActionList(actionList);
}
}
}
}
//赋值器
public static String filedValid(String value, Boolean accurateImport) {
if (StringUtils.isBlank(value)) {
if (accurateImport) {
return null;
} else {
throw new RenException("字段缺失,无法解析");
}
} else {
return value;
}
}
}

View File

@ -11,6 +11,7 @@ import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import com.multictrl.modules.business.dto.RouteAiDTO;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.dto.UploadRouteDTO;
import com.multictrl.modules.business.service.RouteService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -116,4 +117,21 @@ public class RouteController {
return new Result<>();
}
@PostMapping("/upload")
@Operation(summary = "导入航线")
@LogOperation("导入航线")
@Parameters({
@Parameter(name = "dockSn", description = "机库SN"),
@Parameter(name = "accurateImport", description = "是否精准导入"),
@Parameter(name = "globalRthHeight", description = "返航高度"),
@Parameter(name = "file", description = "航线文件")
})
@RequiresPermissions("bus:route:upload")
public Result<Object> upload(UploadRouteDTO dto) {
ValidatorUtils.validateEntity(dto);
routeService.upload(dto);
return new Result<>();
}
}

View File

@ -0,0 +1,49 @@
package com.multictrl.modules.business.dto;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.multictrl.common.validator.group.DefaultGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.Objects;
/**
* 导入航线参数
*
* @author Sdy
* @since 1.0.0 2026/6/18
*/
@Data
@Schema(name = "导入航线参数")
public class UploadRouteDTO {
@NotBlank(message = "机库SN不能为空")
// @Schema(description = "机库SN")
private String dockSn;
@NotNull(message = "是否精准导入不能为空")
// @Schema(description = "是否精准导入 true false")
private Boolean accurateImport;
// @Schema(description = "返航高度")
private Double globalRthHeight;
@NotNull(message = "航线文件不能为空")
// @Schema(description = "航线文件")
private MultipartFile file;
@JsonIgnore
@Schema(hidden = true)
@AssertTrue(message = "返航高度不能为空")
public boolean isGlobalRthHeightValid() {
if (accurateImport) {
return globalRthHeight != null;
}
return true;
}
}

View File

@ -28,6 +28,9 @@ public class WaypointActionDTO implements Serializable {
@Schema(description = "动作值")
private String actionValue;
@Schema(description = "动作顺序")
private Integer actionSort;
@Schema(description = "航点顺序", hidden = true)
// @JsonIgnore
private String waypointOrder;

View File

@ -4,6 +4,7 @@ import com.multictrl.common.page.PageData;
import com.multictrl.common.service.CrudService;
import com.multictrl.modules.business.dto.RouteAiDTO;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.dto.UploadRouteDTO;
import com.multictrl.modules.business.entity.RouteEntity;
import java.util.Map;
@ -36,4 +37,7 @@ public interface RouteService extends CrudService<RouteEntity, RouteDTO> {
//删除AI任务
void deleteRouteAi(Long routeId);
//上传航线
void upload(UploadRouteDTO dto);
}

View File

@ -1,24 +1,26 @@
package com.multictrl.modules.business.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import cn.hutool.core.util.StrUtil;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.exception.ExceptionUtils;
import com.multictrl.common.exception.RenException;
import com.multictrl.common.page.PageData;
import com.multictrl.common.service.impl.CrudServiceImpl;
import com.multictrl.common.utils.ConvertUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.common.utils.kmz.KmzUtils;
import com.multictrl.common.utils.kmz.WpmlKmlParser;
import com.multictrl.common.validator.AssertUtils;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.modules.business.dao.RouteAiDao;
import com.multictrl.modules.business.dao.RouteDao;
import com.multictrl.modules.business.dao.RouteWaypointDao;
import com.multictrl.modules.business.dao.WaypointActionDao;
import com.multictrl.modules.business.dto.RouteAiDTO;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.dto.RouteWaypointDTO;
import com.multictrl.modules.business.dto.WaypointActionDTO;
import com.multictrl.modules.business.dto.*;
import com.multictrl.modules.business.entity.RouteAiEntity;
import com.multictrl.modules.business.entity.RouteEntity;
import com.multictrl.modules.business.entity.RouteWaypointEntity;
@ -27,8 +29,10 @@ import com.multictrl.modules.business.service.MinioService;
import com.multictrl.modules.business.service.RouteService;
import com.multictrl.modules.security.user.SecurityUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
@ -41,8 +45,9 @@ import java.util.Map;
* @author Sdy
* @since 1.0.0 2026-04-20
*/
@RequiredArgsConstructor
@Slf4j
@Service
@RequiredArgsConstructor
public class RouteServiceImpl extends CrudServiceImpl<RouteDao, RouteEntity, RouteDTO> implements RouteService {
private final RouteWaypointDao routeWaypointDao;
private final WaypointActionDao waypointActionDao;
@ -211,6 +216,60 @@ public class RouteServiceImpl extends CrudServiceImpl<RouteDao, RouteEntity, Rou
routeAiDao.delete(new QueryWrapper<RouteAiEntity>().eq("route_id", routeId));
}
@Override
public void upload(UploadRouteDTO dto) {
MultipartFile file = dto.getFile();
String originalFilename = file.getOriginalFilename();
String name = FileUtil.getPrefix(originalFilename);
String suffix = FileUtil.getSuffix(originalFilename);
if (!"kmz".equals(suffix)) {
throw new RenException(ErrorCode.FILE_FORMAT_ERROR, "kmz");
}
//判断航线名称是否已存在
checkRouteExist(null, name);
RouteDTO routeDTO = null;
try {
Map<String, byte[]> kmzInfo = KmzUtils.extractKmzAsBytes(file.getInputStream());
byte[] template = kmzInfo.get("template.kml");
routeDTO = WpmlKmlParser.parseKmlFile(template, dto.getAccurateImport());
byte[] waylines = kmzInfo.get("waylines.wpml");
WpmlKmlParser.parseWpmlFile(waylines, routeDTO, dto.getAccurateImport());
log.debug("解析结果:{}", JsonUtils.toJsonString(dto));
} catch (Exception e) {
log.error("解析结果失败:{}", ExceptionUtils.getErrorStackTrace(e));
// throw new RenException(ErrorCode.FILE_PARSER_ERROR);
throw new RenException(e.getMessage());
}
routeDTO.setRouteName(name);
routeDTO.setGlobalRthHeight(dto.getGlobalRthHeight());
String path = dto.getDockSn() + "/" + originalFilename;
try {
minioService.uploadFile(file.getInputStream(), BusinessConstant.ROUTE_KMZ_BUCKET, path);
} catch (IOException e) {
log.error("文件上传失败:{}", ExceptionUtils.getErrorStackTrace(e));
throw new RenException(ErrorCode.OSS_UPLOAD_FILE_ERROR);
}
routeDTO.setKmzUrl(BusinessConstant.ROUTE_KMZ_BUCKET + "/" + path);
routeDTO.setIsAccurateImport(dto.getAccurateImport());
routeDTO.setDockSn(dto.getDockSn());
//航点动作处理
List<RouteWaypointDTO> routeWaypointList = routeDTO.getRouteWaypointList();
handleWaypointAction(routeWaypointList);
//保存信息
RouteEntity routeEntity = ConvertUtils.sourceToTarget(routeDTO, currentModelClass());
routeEntity.setDeptId(SecurityUser.getDeptId());
routeEntity.setWaypointNum(routeWaypointList.size());
baseDao.insert(routeEntity);
//保存航点信息
saveWaypoint(routeEntity.getId(), routeWaypointList);
}
//判断航线是否存在
private void checkRouteExist(Long id, String routeName) {
RouteEntity uavRouteEntity = baseDao.selectOne(new QueryWrapper<RouteEntity>()