diff --git a/admin/src/main/java/com/multictrl/common/constant/FlightTaskType.java b/admin/src/main/java/com/multictrl/common/constant/FlightTaskType.java new file mode 100644 index 0000000..068adc6 --- /dev/null +++ b/admin/src/main/java/com/multictrl/common/constant/FlightTaskType.java @@ -0,0 +1,15 @@ +package com.multictrl.common.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum FlightTaskType { + ROUTE(1, "航线飞行"), + MANUAL(2, "手动飞行"), + SCHEDULED(3, "定时飞行"); + + private final Integer code; + private final String desc; +} diff --git a/admin/src/main/java/com/multictrl/common/constant/SysParamsKey.java b/admin/src/main/java/com/multictrl/common/constant/SysParamsKey.java index 0a98330..3ac610e 100644 --- a/admin/src/main/java/com/multictrl/common/constant/SysParamsKey.java +++ b/admin/src/main/java/com/multictrl/common/constant/SysParamsKey.java @@ -13,6 +13,9 @@ public interface SysParamsKey { String ZHIMOU_AI_APP_KEY = "zhimou_ai_app_key"; String ZHIMOU_AI_APP_SECRET = "zhimou_ai_app_secret"; + //********************************* gaode *********************************// + String GAODE_API_KEY = "gaode_api_key"; + //********************************* sys component *********************************// String SRS_RTMP_MAP_URL = "srs_rtmp_map_url"; String EMQX_MAP_URL = "emqx_map_url"; diff --git a/admin/src/main/java/com/multictrl/common/utils/weather/FogUtils.java b/admin/src/main/java/com/multictrl/common/utils/weather/FogUtils.java new file mode 100644 index 0000000..7872922 --- /dev/null +++ b/admin/src/main/java/com/multictrl/common/utils/weather/FogUtils.java @@ -0,0 +1,70 @@ +package com.multictrl.common.utils.weather; + +/** + * 雾量工具类 + * + * @author Sdy + * @since 1.0.0 2025/10/13 + */ +public class FogUtils { + // 轻雾类天气现象 + public static final String[] LIGHT_FOG_TYPES = { + "轻雾" + }; + + // 中雾类天气现象 + public static final String[] MODERATE_FOG_TYPES = { + "雾", + "浓雾" + }; + + // 大雾类天气现象 + public static final String[] HEAVY_FOG_TYPES = { + "强浓雾", + "大雾", + "特强浓雾" + }; + + /** + * 判断是否为轻雾类 + */ + public static boolean isMinFog(String fogType) { + return contains(LIGHT_FOG_TYPES, fogType); + } + + /** + * 判断是否为中雾类 + */ + public static boolean isModerateFog(String fogType) { + return contains(MODERATE_FOG_TYPES, fogType); + } + + /** + * 判断是否为大雾类 + */ + public static boolean isMaxFog(String fogType) { + return contains(HEAVY_FOG_TYPES, fogType); + } + + /** + * 获取雾类型所属的类别名称 + */ + public static String getCategory(String fogType) { + if (isMinFog(fogType)) return "轻雾"; + if (isModerateFog(fogType)) return "中雾"; + if (isMaxFog(fogType)) return "大雾"; + return "未知"; + } + + /** + * 检查字符串是否在数组中 + */ + private static boolean contains(String[] array, String value) { + for (String item : array) { + if (item.equals(value)) { + return true; + } + } + return false; + } +} diff --git a/admin/src/main/java/com/multictrl/common/utils/weather/HazeUtils.java b/admin/src/main/java/com/multictrl/common/utils/weather/HazeUtils.java new file mode 100644 index 0000000..e7f5015 --- /dev/null +++ b/admin/src/main/java/com/multictrl/common/utils/weather/HazeUtils.java @@ -0,0 +1,69 @@ +package com.multictrl.common.utils.weather; + +/** + * 霾量工具类 + * + * @author Sdy + * @since 1.0.0 2025/10/13 + */ +public class HazeUtils { + + // 轻度霾类天气现象 + public static final String[] LIGHT_HAZE_TYPES = { + "霾" + }; + + // 中度霾类天气现象 + public static final String[] MODERATE_HAZE_TYPES = { + "中度霾" + }; + + // 重度霾类天气现象 + public static final String[] HEAVY_HAZE_TYPES = { + "重度霾", + "严重霾" + }; + + /** + * 判断是否为轻度霾类 + */ + public static boolean isMinHaze(String hazeType) { + return contains(LIGHT_HAZE_TYPES, hazeType); + } + + /** + * 判断是否为中度霾类 + */ + public static boolean isModerateHaze(String hazeType) { + return contains(MODERATE_HAZE_TYPES, hazeType); + } + + /** + * 判断是否为重度霾类 + */ + public static boolean isMaxHaze(String hazeType) { + return contains(HEAVY_HAZE_TYPES, hazeType); + } + + /** + * 获取霾类型所属的类别名称 + */ + public static String getCategory(String hazeType) { + if (isMinHaze(hazeType)) return "轻度霾"; + if (isModerateHaze(hazeType)) return "中度霾"; + if (isMaxHaze(hazeType)) return "重度霾"; + return "未知"; + } + + /** + * 检查字符串是否在数组中 + */ + private static boolean contains(String[] array, String value) { + for (String item : array) { + if (item.equals(value)) { + return true; + } + } + return false; + } +} diff --git a/admin/src/main/java/com/multictrl/common/utils/weather/RainfallUtils.java b/admin/src/main/java/com/multictrl/common/utils/weather/RainfallUtils.java new file mode 100644 index 0000000..257e24a --- /dev/null +++ b/admin/src/main/java/com/multictrl/common/utils/weather/RainfallUtils.java @@ -0,0 +1,87 @@ +package com.multictrl.common.utils.weather; + +/** + * 雨量工具类 + * + * @author Sdy + * @since 1.0.0 2025/10/13 + */ +public class RainfallUtils { + + // 小雨类天气现象 + public static final String[] LIGHT_RAIN_TYPES = { + "毛毛雨/细雨", + "小雨", + "阵雨", + "雷阵雨", + "雨雪天气", + "雨夹雪", + "阵雨夹雪", + "冻雨" + }; + + // 中雨类天气现象 + public static final String[] MODERATE_RAIN_TYPES = { + "中雨", + "小雨-中雨", + "雷阵雨并伴有冰雹" + }; + + // 大雨类天气现象 + public static final String[] HEAVY_RAIN_TYPES = { + "大雨", + "中雨-大雨", + "强阵雨", + "强雷阵雨", + "暴雨", + "大暴雨", + "特大暴雨", + "极端降雨", + "大雨-暴雨", + "暴雨-大暴雨", + "大暴雨-特大暴雨" + }; + + /** + * 判断是否为小雨类 + */ + public static boolean isMinRain(String precipitation) { + return contains(LIGHT_RAIN_TYPES, precipitation); + } + + /** + * 判断是否为中雨类 + */ + public static boolean isModerateRain(String precipitation) { + return contains(MODERATE_RAIN_TYPES, precipitation); + } + + /** + * 判断是否为大雨类 + */ + public static boolean isMaxRain(String precipitation) { + return contains(HEAVY_RAIN_TYPES, precipitation); + } + + /** + * 获取降水类型所属的类别名称 + */ + public static String getCategory(String precipitation) { + if (isMinRain(precipitation)) return "小雨"; + if (isModerateRain(precipitation)) return "中雨"; + if (isMaxRain(precipitation)) return "大雨"; + return "未知"; + } + + /** + * 检查字符串是否在数组中 + */ + private static boolean contains(String[] array, String value) { + for (String item : array) { + if (item.equals(value)) { + return true; + } + } + return false; + } +} diff --git a/admin/src/main/java/com/multictrl/common/utils/weather/SnowUtils.java b/admin/src/main/java/com/multictrl/common/utils/weather/SnowUtils.java new file mode 100644 index 0000000..e0ce22d --- /dev/null +++ b/admin/src/main/java/com/multictrl/common/utils/weather/SnowUtils.java @@ -0,0 +1,76 @@ +package com.multictrl.common.utils.weather; + +/** + * 雪量工具类 + * + * @author Sdy + * @since 1.0.0 2025/10/13 + */ +public class SnowUtils { + // 小雪类天气现象 + public static final String[] LIGHT_SNOW_TYPES = { + "雪", + "阵雪", + "小雪", + "雨雪天气", + "雨夹雪", + "阵雨夹雪" + }; + + // 中雪类天气现象 + public static final String[] MODERATE_SNOW_TYPES = { + "中雪", + "小雪-中雪" + }; + + // 大雪类天气现象 + public static final String[] HEAVY_SNOW_TYPES = { + "大雪", + "暴雪", + "中雪-大雪", + "大雪-暴雪" + }; + + /** + * 判断是否为小雪类 + */ + public static boolean isMinSnow(String snowType) { + return contains(LIGHT_SNOW_TYPES, snowType); + } + + /** + * 判断是否为中雪类 + */ + public static boolean isModerateSnow(String snowType) { + return contains(MODERATE_SNOW_TYPES, snowType); + } + + /** + * 判断是否为大雪类 + */ + public static boolean isMaxSnow(String snowType) { + return contains(HEAVY_SNOW_TYPES, snowType); + } + + /** + * 获取雪类型所属的类别名称 + */ + public static String getCategory(String snowType) { + if (isMinSnow(snowType)) return "小雪"; + if (isModerateSnow(snowType)) return "中雪"; + if (isMaxSnow(snowType)) return "大雪"; + return "未知"; + } + + /** + * 检查字符串是否在数组中 + */ + private static boolean contains(String[] array, String value) { + for (String item : array) { + if (item.equals(value)) { + return true; + } + } + return false; + } +} diff --git a/admin/src/main/java/com/multictrl/common/utils/weather/WindSpeedConverter.java b/admin/src/main/java/com/multictrl/common/utils/weather/WindSpeedConverter.java new file mode 100644 index 0000000..573b44d --- /dev/null +++ b/admin/src/main/java/com/multictrl/common/utils/weather/WindSpeedConverter.java @@ -0,0 +1,51 @@ +package com.multictrl.common.utils.weather; + +/** + * 飞速级别转换 + * + * @author Sdy + * @since 1.0.0 2025/10/12 + */ +public class WindSpeedConverter { + + /** + * 根据级别名称获取最大风速 + * + * @param levelName 级别名称 ("≤3", "4", "5", ..., "12") + * @return 最大风速 (m/s) + */ + public static double getMaxWindSpeed(String levelName) { + return switch (levelName) { + case "≤3" -> 5.4; + case "4" -> 7.9; + case "5" -> 10.7; + case "6" -> 13.8; + case "7" -> 17.1; + case "8" -> 20.7; + case "9" -> 24.4; + case "10" -> 28.4; + case "11" -> 32.6; + case "12" -> 56.0; + default -> throw new IllegalArgumentException("无效的级别名称: " + levelName); + }; + } + + /** + * 获取指定级别的最小风速 + */ + public static double getMinWindSpeed(String levelName) { + return switch (levelName) { + case "≤3" -> 0.0; + case "4" -> 5.5; + case "5" -> 8.0; + case "6" -> 10.8; + case "7" -> 13.9; + case "8" -> 17.2; + case "9" -> 20.8; + case "10" -> 24.5; + case "11" -> 28.5; + case "12" -> 32.7; + default -> throw new IllegalArgumentException("无效的级别名称: " + levelName); + }; + } +} diff --git a/admin/src/main/java/com/multictrl/modules/business/controller/RouteFlightController.java b/admin/src/main/java/com/multictrl/modules/business/controller/RouteFlightController.java index 35a04bb..5a3bd1b 100644 --- a/admin/src/main/java/com/multictrl/modules/business/controller/RouteFlightController.java +++ b/admin/src/main/java/com/multictrl/modules/business/controller/RouteFlightController.java @@ -2,6 +2,7 @@ package com.multictrl.modules.business.controller; import com.multictrl.common.annotation.ApiOrder; import com.multictrl.common.annotation.LogOperation; +import com.multictrl.common.constant.FlightTaskType; import com.multictrl.common.exception.ErrorCode; import com.multictrl.common.utils.Result; import com.multictrl.modules.business.dto.flight.FlightExecute; @@ -25,10 +26,10 @@ import java.util.List; @RestController @RequestMapping("business/command") @Tag(name = "航线飞行", description = """ - 航线管理是无人机自主作业的重要功能, - 可以实现行业领域的批量化、智能化作业。 - 上云 API 提供了相关的接口,实现了航线任务在云端的共享查看、下发执行、取消以及进度上报等功能。 - 用户需要遵照航线文件格式规范(WPML)编写航线文件,定义航线任务。一个航线任务中可以定义多条航线。""") + 航线管理是无人机自主作业的重要功能, + 可以实现行业领域的批量化、智能化作业。 + 上云 API 提供了相关的接口,实现了航线任务在云端的共享查看、下发执行、取消以及进度上报等功能。 + 用户需要遵照航线文件格式规范(WPML)编写航线文件,定义航线任务。一个航线任务中可以定义多条航线。""") @ApiOrder(5) @RequiredArgsConstructor public class RouteFlightController { @@ -77,7 +78,7 @@ public class RouteFlightController { @Operation(summary = "取消任务") @RequiresPermissions("bus:route:flight:flightTaskUndo") public Result flightTaskUndo(@PathVariable String dockSn, - @RequestBody List taskIds) { + @RequestBody List taskIds) { if (taskIds.isEmpty()) { return new Result<>().error(ErrorCode.PARAMS_ERROR); } @@ -90,9 +91,9 @@ public class RouteFlightController { @Operation(summary = "执行航线") @RequiresPermissions("bus:route:flight:flightExecute") public Result flightExecute(@PathVariable String dockSn, - @RequestBody FlightExecute flightExecute) { + @RequestBody FlightExecute flightExecute) { - return new Result<>().ok(routeFlightService.flightExecute(dockSn, flightExecute, true)); + return new Result<>().ok(routeFlightService.flightExecute(dockSn, flightExecute, true, FlightTaskType.ROUTE.getCode())); } @LogOperation("空中下发航线") @@ -100,8 +101,8 @@ public class RouteFlightController { @Operation(summary = "空中下发航线") @RequiresPermissions("bus:route:flight:inFlightWaylineDeliver") public Result inFlightWaylineDeliver(@PathVariable String dockSn, - @Parameter(name = "routeId", description = "航线标识") - @RequestParam Long routeId) { + @Parameter(name = "routeId", description = "航线标识") + @RequestParam Long routeId) { return new Result<>().ok(routeFlightService.inFlightWaylineDeliver(dockSn, routeId)); } diff --git a/admin/src/main/java/com/multictrl/modules/business/controller/WeatherNoflyController.java b/admin/src/main/java/com/multictrl/modules/business/controller/WeatherNoflyController.java new file mode 100644 index 0000000..238d521 --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/controller/WeatherNoflyController.java @@ -0,0 +1,78 @@ +package com.multictrl.modules.business.controller; + +import com.multictrl.common.annotation.ApiOrder; +import com.multictrl.common.annotation.LogOperation; +import com.multictrl.common.utils.Result; +import com.multictrl.common.validator.AssertUtils; +import com.multictrl.common.validator.ValidatorUtils; +import com.multictrl.common.validator.group.AddGroup; +import com.multictrl.common.validator.group.DefaultGroup; +import com.multictrl.common.validator.group.UpdateGroup; +import com.multictrl.modules.business.dto.WeatherNoflyDTO; +import com.multictrl.modules.business.service.WeatherNoflyService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.*; + +/** + * 天气阻飞 + * + * @author Sdy + * @since 1.0.0 2026-06-15 + */ +@RestController +@RequestMapping("business/weathernofly") +@Tag(name = "天气阻飞") +@ApiOrder(24) +@RequiredArgsConstructor +public class WeatherNoflyController { + private final WeatherNoflyService weatherNoflyService; + + @GetMapping("{dockSn}") + @Operation(summary = "信息") + @RequiresPermissions("bus:weathernofly:info") + public Result get(@PathVariable String dockSn) { + WeatherNoflyDTO data = weatherNoflyService.getInfoByDockSn(dockSn); + + return new Result().ok(data); + } + + @PostMapping + @Operation(summary = "保存") + @LogOperation("保存") + @RequiresPermissions("bus:weathernofly:save") + public Result save(@RequestBody WeatherNoflyDTO dto) { + //效验数据 + ValidatorUtils.validateEntity(dto, AddGroup.class); + weatherNoflyService.addWeatherNofly(dto); + + return new Result<>(); + } + + @PutMapping + @Operation(summary = "修改") + @LogOperation("修改") + @RequiresPermissions("bus:weathernofly:update") + public Result update(@RequestBody WeatherNoflyDTO dto) { + //效验数据 + ValidatorUtils.validateEntity(dto, UpdateGroup.class); + weatherNoflyService.update(dto); + + return new Result<>(); + } + + @DeleteMapping + @Operation(summary = "删除") + @LogOperation("删除") + @RequiresPermissions("bus:weathernofly:delete") + public Result delete(@RequestBody Long[] ids) { + //效验数据 + AssertUtils.isArrayEmpty(ids, "id"); + weatherNoflyService.delete(ids); + + return new Result<>(); + } + +} diff --git a/admin/src/main/java/com/multictrl/modules/business/dao/WeatherNoflyDao.java b/admin/src/main/java/com/multictrl/modules/business/dao/WeatherNoflyDao.java new file mode 100644 index 0000000..980d26a --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/dao/WeatherNoflyDao.java @@ -0,0 +1,16 @@ +package com.multictrl.modules.business.dao; + +import com.multictrl.common.dao.BaseDao; +import com.multictrl.modules.business.entity.WeatherNoflyEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * 天气阻飞 + * + * @author Sdy + * @since 1.0.0 2026-06-15 + */ +@Mapper +public interface WeatherNoflyDao extends BaseDao { + +} \ No newline at end of file diff --git a/admin/src/main/java/com/multictrl/modules/business/dto/WeatherNoflyDTO.java b/admin/src/main/java/com/multictrl/modules/business/dto/WeatherNoflyDTO.java new file mode 100644 index 0000000..4a4ac1e --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/dto/WeatherNoflyDTO.java @@ -0,0 +1,114 @@ +package com.multictrl.modules.business.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.multictrl.common.validator.group.AddGroup; +import com.multictrl.common.validator.group.UpdateGroup; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Null; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 天气阻飞 + * + * @author Sdy + * @since 1.0.0 2026-06-15 + */ +@Data +@Schema(name = "天气阻飞") +public class WeatherNoflyDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @Null(message = "标识必须为空", groups = AddGroup.class) + @NotNull(message = "标识不能为空", groups = UpdateGroup.class) + @Schema(description = "标识") + private Long id; + + @NotNull(message = "机库SN不能为空", groups = {AddGroup.class, UpdateGroup.class}) + @Schema(description = "机库sn") + private String dockSn; + + @NotNull(message = "是否开启阻飞不能为空", groups = {AddGroup.class, UpdateGroup.class}) + @Schema(description = "是否开启阻飞(开启后使用机库上报数据)") + private Boolean isOpen; + + @Schema(description = "风速m/s") + private Double windSpeed; + + @Schema(description = "雨(0关闭 1小雨 2中雨 3大雨)") + private Integer rainfall; + + @JsonIgnore + @Schema(hidden = true) + @AssertTrue(message = "参数为空或错误", groups = {AddGroup.class, UpdateGroup.class}) + public boolean isIsOpenValid() { + if (isOpen) { + return windSpeed != null && rainfall != null && (rainfall == 0 || rainfall == 1); + } + return true; + } + + @NotNull(message = "是否全部阻飞不能为空", groups = {AddGroup.class, UpdateGroup.class}) + @Schema(description = "是否全部阻飞(开启后全部阻飞不开启只阻飞计划库)") + private Boolean isAllPrevent; + + @NotNull(message = "是否开启天气阻飞不能为空", groups = {AddGroup.class, UpdateGroup.class}) + @Schema(description = "是否开启天气阻飞(开启后在机库数据上增加天气预报数据)") + private Boolean isOpenWeather; + + @Schema(description = "风速m/s(天气预报)") + private Double windSpeedWeather; + + @Schema(description = "雨(天气预报 0关闭 1小雨 2中雨 3大雨)") + private Integer rainfallWeather; + + @Schema(description = "雪(天气预报 0关闭 1小雪 2中雪 3大雪)") + private Integer snowWeather; + + @Schema(description = "雾(天气预报 0关闭 1轻雾 2中雾 3大雾)") + private Integer fogWeather; + + @Schema(description = "霾(天气预报 0关闭 1轻度霾 2中度霾 3重度霾)") + private Integer hazeWeather; + + @Schema(description = "冰雹阻飞") + private Boolean isHailWeather; + + @Schema(description = "沙尘暴阻飞") + private Boolean isSandstormWeather; + + @Schema(description = "龙卷风阻飞") + private Boolean isTornadoWeather; + + @Schema(description = "温度上限(摄氏度)") + private Double temperatureMax; + + @Schema(description = "温度下限(摄氏度)") + private Double temperatureMin; + + @JsonIgnore + @Schema(hidden = true) + @AssertTrue(message = "天气参数为空或错误", groups = {AddGroup.class, UpdateGroup.class}) + public boolean isIsOpenWeatherValid() { + if (isOpenWeather) { + return windSpeedWeather != null + && (rainfallWeather != null && rainfallWeather >= 0 && rainfallWeather <= 3) + && (snowWeather != null && snowWeather >= 0 && snowWeather <= 3) + && (fogWeather != null && fogWeather >= 0 && fogWeather <= 3) + && (hazeWeather != null && hazeWeather >= 0 && hazeWeather <= 3) + && isHailWeather != null + && isSandstormWeather != null + && isTornadoWeather != null + && temperatureMax != null + && temperatureMin != null; + } + return true; + } +} diff --git a/admin/src/main/java/com/multictrl/modules/business/dto/WeatherNoflyTaskDTO.java b/admin/src/main/java/com/multictrl/modules/business/dto/WeatherNoflyTaskDTO.java new file mode 100644 index 0000000..3e24f9a --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/dto/WeatherNoflyTaskDTO.java @@ -0,0 +1,31 @@ +package com.multictrl.modules.business.dto; + +import lombok.Data; + +/** + * 天气阻飞任务记录参数 + * + * @author Sdy + * @since 1.0.0 2026/6/17 + */ +@Data +public class WeatherNoflyTaskDTO { + + //任务标识 + private String taskId; + + //机库SN + private String dockSn; + + //航线标识 + private Long routeId; + + //定时任务标识 + private Long jobId; + + //任务类型 1航线 2手动 3定时 + private Integer taskType; + + //失败原因 + private String reason; +} diff --git a/admin/src/main/java/com/multictrl/modules/business/entity/WeatherNoflyEntity.java b/admin/src/main/java/com/multictrl/modules/business/entity/WeatherNoflyEntity.java new file mode 100644 index 0000000..e0436b3 --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/entity/WeatherNoflyEntity.java @@ -0,0 +1,96 @@ +package com.multictrl.modules.business.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.multictrl.common.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 天气阻飞 + * + * @author Sdy + * @since 1.0.0 2026-06-15 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("bus_weather_nofly") +public class WeatherNoflyEntity extends BaseEntity { + /** + * 机库sn + */ + private String dockSn; + /** + * 是否开启阻飞(开启后使用机库上报数据) + */ + private Boolean isOpen; + /** + * 风速m/s + */ + private Double windSpeed; + /** + * 雨(0关闭 1小雨 2中雨 3大雨) + */ + private Integer rainfall; + /** + * 是否全部阻飞(开启后全部阻飞不开启只阻飞计划库) + */ + private Boolean isAllPrevent; + /** + * 是否开启天气阻飞(开启后在机库数据上增加天气预报数据) + */ + private Boolean isOpenWeather; + /** + * 风速m/s(天气预报) + */ + private Double windSpeedWeather; + /** + * 雨(天气预报 0关闭 1小雨 2中雨 3大雨) + */ + private Integer rainfallWeather; + /** + * 雪(天气预报 0关闭 1小雪 2中雪 3大雪) + */ + private Integer snowWeather; + /** + * 雾(天气预报 0关闭 1轻雾 2中雾 3大雾) + */ + private Integer fogWeather; + /** + * 霾(天气预报 0关闭 1轻度霾 2中度霾 3重度霾) + */ + private Integer hazeWeather; + /** + * 冰雹阻飞 + */ + private Boolean isHailWeather; + /** + * 沙尘暴阻飞 + */ + private Boolean isSandstormWeather; + /** + * 龙卷风阻飞 + */ + private Boolean isTornadoWeather; + /** + * 温度上限(摄氏度) + */ + private Double temperatureMax; + /** + * 温度下限(摄氏度) + */ + private Double temperatureMin; + /** + * 修改人 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updater; + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateDate; +} \ No newline at end of file diff --git a/admin/src/main/java/com/multictrl/modules/business/service/DJIBaseService.java b/admin/src/main/java/com/multictrl/modules/business/service/DJIBaseService.java index a62bbbd..a219d8f 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/DJIBaseService.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/DJIBaseService.java @@ -47,6 +47,9 @@ public interface DJIBaseService { //获取机场备降点 LatLonDTO getDockAlternateLandPoint(String dockSn); + //获取机库位置 + LatLonDTO getDockLocation(String dockSn); + //机场是否在线 Boolean isDockOnline(String dockSn); diff --git a/admin/src/main/java/com/multictrl/modules/business/service/FlightTaskService.java b/admin/src/main/java/com/multictrl/modules/business/service/FlightTaskService.java index ba51bda..2dc4485 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/FlightTaskService.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/FlightTaskService.java @@ -5,6 +5,7 @@ import com.multictrl.common.page.PageData; import com.multictrl.common.service.CrudService; import com.multictrl.modules.business.dto.FlightTaskDTO; import com.multictrl.modules.business.dto.MediaFileDTO; +import com.multictrl.modules.business.dto.WeatherNoflyTaskDTO; import com.multictrl.modules.business.entity.FlightTaskEntity; import com.multictrl.modules.business.entity.SrsRecordEntity; import com.multictrl.modules.business.influxdb.UavReport; @@ -30,6 +31,8 @@ public interface FlightTaskService extends CrudService taskIds); //执行航线 - String flightExecute(String dockSn, FlightExecute flightExecute, Boolean isRecord); + String flightExecute(String dockSn, FlightExecute flightExecute, Boolean isRecord, Integer taskType); //空中下发航线 String inFlightWaylineDeliver(String dockSn, Long routeId); diff --git a/admin/src/main/java/com/multictrl/modules/business/service/WeatherNoflyService.java b/admin/src/main/java/com/multictrl/modules/business/service/WeatherNoflyService.java new file mode 100644 index 0000000..879115e --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/service/WeatherNoflyService.java @@ -0,0 +1,24 @@ +package com.multictrl.modules.business.service; + +import cn.hutool.json.JSONObject; +import com.multictrl.common.service.CrudService; +import com.multictrl.modules.business.dto.WeatherNoflyDTO; +import com.multictrl.modules.business.entity.WeatherNoflyEntity; + +/** + * 天气阻飞 + * + * @author Sdy + * @since 1.0.0 2026-06-15 + */ +public interface WeatherNoflyService extends CrudService { + + //信息 + WeatherNoflyDTO getInfoByDockSn(String dockSn); + + //新增阻飞 + void addWeatherNofly(WeatherNoflyDTO dto); + + //判断机库是否阻飞 + JSONObject allowFlight(String dockSn, boolean isJobTask); +} \ No newline at end of file diff --git a/admin/src/main/java/com/multictrl/modules/business/service/WeatherService.java b/admin/src/main/java/com/multictrl/modules/business/service/WeatherService.java new file mode 100644 index 0000000..fdb0b4b --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/service/WeatherService.java @@ -0,0 +1,13 @@ +package com.multictrl.modules.business.service; + +/** + * 天气服务 + * + * @author Sdy + * @since 1.0.0 2026/6/16 + */ +public interface WeatherService { + + //根据经纬度获取实时天气 + String getWeatherByLonLat(Double longitude, Double latitude); +} diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/CommandServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/CommandServiceImpl.java index abc5b30..4c78555 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/impl/CommandServiceImpl.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/CommandServiceImpl.java @@ -4,25 +4,21 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.multictrl.common.config.MqttConfig; import com.multictrl.common.constant.BusinessConstant; import com.multictrl.common.constant.DockMode; import com.multictrl.common.constant.FlightTaskProgressStatus; +import com.multictrl.common.constant.FlightTaskType; import com.multictrl.common.exception.ErrorCode; import com.multictrl.common.exception.RenException; import com.multictrl.common.utils.CacheUtils; import com.multictrl.common.utils.Utils; -import com.multictrl.modules.business.dao.DockDeviceDao; import com.multictrl.modules.business.dto.LatLonDTO; +import com.multictrl.modules.business.dto.WeatherNoflyTaskDTO; import com.multictrl.modules.business.dto.command.DrcStick; import com.multictrl.modules.business.dto.command.FlyToPoint; import com.multictrl.modules.business.dto.command.TakeoffToPoint; -import com.multictrl.modules.business.entity.DockDeviceEntity; -import com.multictrl.modules.business.service.CommandService; -import com.multictrl.modules.business.service.DJIBaseService; -import com.multictrl.modules.business.service.FlightTaskService; -import com.multictrl.modules.business.service.MqttPushService; +import com.multictrl.modules.business.service.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -45,9 +41,9 @@ import java.util.concurrent.locks.ReentrantLock; public class CommandServiceImpl implements CommandService { private final DJIBaseService djiBaseService; private final MqttConfig mqttConfig; - private final DockDeviceDao dockDeviceDao; private final MqttPushService mqttPushService; private final FlightTaskService flightTaskService; + private final WeatherNoflyService weatherNoflyService; private final Map cacheLock = new ConcurrentHashMap<>(); @Override @@ -104,6 +100,19 @@ public class CommandServiceImpl implements CommandService { //设置默认相机模式,需要定频推送前端 CacheUtils.set(BusinessConstant.UAV_VIDEO_TYPE, "wide"); + //判断天气阻飞 + JSONObject allowFlightResult = weatherNoflyService.allowFlight(dockSn, false); + Boolean isAllow = allowFlightResult.getBool("isAllow"); + if (!isAllow) { + String reason = allowFlightResult.getStr("reason"); + WeatherNoflyTaskDTO dto = new WeatherNoflyTaskDTO(); + dto.setTaskId(taskId); + dto.setDockSn(dockSn); + dto.setTaskType(FlightTaskType.MANUAL.getCode()); + dto.setReason(reason); + flightTaskService.addWeatherNoflyTask(dto); + return reason; + } //发送执行 String result = djiBaseService.executeAndReturnResult(dockSn, "takeoff_to_point", Utils.beanToSnakeJson(takeoffToPoint)); flightTaskService.addTakeoffTask(taskId, dockSn); diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/DJIBaseServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/DJIBaseServiceImpl.java index 8b889d3..1c56e89 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/impl/DJIBaseServiceImpl.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/DJIBaseServiceImpl.java @@ -29,7 +29,9 @@ import java.util.concurrent.ConcurrentHashMap; public class DJIBaseServiceImpl implements DJIBaseService { private final MqttPushService mqttPushService; - /** 设备SN → 当前正在等待回复的命令信息(供模拟回复使用) */ + /** + * 设备SN → 当前正在等待回复的命令信息(供模拟回复使用) + */ private final ConcurrentHashMap pendingCmds = new ConcurrentHashMap<>(); @Override @@ -237,6 +239,20 @@ public class DJIBaseServiceImpl implements DJIBaseService { return null; } + @Override + public LatLonDTO getDockLocation(String dockSn) { + Object o = CacheUtils.get(BusinessConstant.DOCK_OSD + dockSn); + if (o != null) { + JSONObject data = (JSONObject) o; + LatLonDTO gps = new LatLonDTO(); + gps.setLatitude(data.getDouble("latitude")); + gps.setLongitude(data.getDouble("longitude")); + gps.setHeight(data.getDouble("height")); + return gps; + } + return null; + } + @Override public Boolean isDockOnline(String dockSn) { Object object = CacheUtils.get(BusinessConstant.DOCK_OSD + dockSn); diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/FlightTaskServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/FlightTaskServiceImpl.java index 001de07..47e2fa0 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/impl/FlightTaskServiceImpl.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/FlightTaskServiceImpl.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.google.common.collect.Maps; import com.multictrl.common.constant.BusinessConstant; import com.multictrl.common.constant.FlightTaskStatus; +import com.multictrl.common.constant.FlightTaskType; import com.multictrl.common.exception.ErrorCode; import com.multictrl.common.exception.RenException; import com.multictrl.common.page.PageData; @@ -19,6 +20,7 @@ import com.multictrl.modules.business.dao.FlightTaskDao; import com.multictrl.modules.business.dto.FlightTaskDTO; import com.multictrl.modules.business.dto.MediaFileDTO; import com.multictrl.modules.business.dto.RouteDTO; +import com.multictrl.modules.business.dto.WeatherNoflyTaskDTO; import com.multictrl.modules.business.entity.*; import com.multictrl.modules.business.influxdb.UavReport; import com.multictrl.modules.business.service.*; @@ -141,6 +143,48 @@ public class FlightTaskServiceImpl extends CrudServiceImpl 下发航线给妙算 flightExecute:{}", flightExecute); - return routeFlightService.flightExecute(multiTaskDTO.getMiaoSuan().getDockSn(), flightExecute, true); + return routeFlightService.flightExecute(multiTaskDTO.getMiaoSuan().getDockSn(), flightExecute, true, FlightTaskType.ROUTE.getCode()); } @Override diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/RouteFlightServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/RouteFlightServiceImpl.java index 0febbbb..b6dfdc8 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/impl/RouteFlightServiceImpl.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/RouteFlightServiceImpl.java @@ -6,20 +6,20 @@ import cn.hutool.json.JSONObject; import com.multictrl.common.config.MinioConfig; import com.multictrl.common.constant.BusinessConstant; import com.multictrl.common.constant.DockMode; +import com.multictrl.common.constant.FlightTaskType; import com.multictrl.common.exception.ErrorCode; import com.multictrl.common.exception.RenException; import com.multictrl.common.utils.CacheUtils; import com.multictrl.common.utils.JsonUtils; import com.multictrl.common.utils.Utils; import com.multictrl.modules.business.dto.RouteDTO; +import com.multictrl.modules.business.dto.WeatherNoflyDTO; +import com.multictrl.modules.business.dto.WeatherNoflyTaskDTO; import com.multictrl.modules.business.dto.flight.FlightExecute; import com.multictrl.modules.business.dto.flight.FlightTaskPrepare; import com.multictrl.modules.business.dto.flight.InFlightWaylineDeliver; import com.multictrl.modules.business.dto.multi.PrivateMultiFlightBindDockInfo; -import com.multictrl.modules.business.service.DJIBaseService; -import com.multictrl.modules.business.service.FlightTaskService; -import com.multictrl.modules.business.service.RouteFlightService; -import com.multictrl.modules.business.service.RouteService; +import com.multictrl.modules.business.service.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -46,6 +46,7 @@ public class RouteFlightServiceImpl implements RouteFlightService { private final RouteService routeService; private final MinioConfig minioConfig; private final FlightTaskService flightTaskService; + private final WeatherNoflyService weatherNoflyService; private final Map cacheLock = new ConcurrentHashMap<>(); @Override @@ -59,7 +60,7 @@ public class RouteFlightServiceImpl implements RouteFlightService { * multiFlightBindDockInfos aros机场蛙跳模式参数,把N个参与到蛙跳的的机场信息给到妙算,返航时,自动计算离哪个机库位置近,就降落到哪个机库 */ @Override - public String flightExecute(String dockSn, FlightExecute flightExecute, Boolean isRecord) { + public String flightExecute(String dockSn, FlightExecute flightExecute, Boolean isRecord, Integer taskType) { ReentrantLock lock = cacheLock.computeIfAbsent(dockSn, k -> new ReentrantLock()); lock.lock(); try { @@ -99,6 +100,20 @@ public class RouteFlightServiceImpl implements RouteFlightService { if (globalRthHeight <= 50) { throw new RenException(ErrorCode.GLOBAL_RTH_HEIGHT_NOT_BELOW, "50"); } + //判断天气阻飞 + JSONObject allowFlightResult = weatherNoflyService.allowFlight(dockSn, Objects.equals(taskType, FlightTaskType.SCHEDULED.getCode())); + Boolean isAllow = allowFlightResult.getBool("isAllow"); + if (!isAllow) { + String reason = allowFlightResult.getStr("reason"); + WeatherNoflyTaskDTO dto = new WeatherNoflyTaskDTO(); + dto.setTaskId(taskId); + dto.setDockSn(dockSn); + dto.setRouteId(route.getId()); + dto.setTaskType(taskType); + dto.setReason(reason); + flightTaskService.addWeatherNoflyTask(dto); + return reason; + } //下发任务 int exitOnRcLost = "executeLostAction".equals(route.getExitOnRcLost()) ? 1 : 0; String kmzUrl = minioConfig.getEndpoint() + "/" + route.getKmzUrl(); @@ -120,7 +135,7 @@ public class RouteFlightServiceImpl implements RouteFlightService { } if (flightExecute.getPrivateMultiFlightBindDockInfos() != null) { flightTaskPrepare.setPrivate_multi_flight_bind_dock_info( - flightExecute.getPrivateMultiFlightBindDockInfos()); + flightExecute.getPrivateMultiFlightBindDockInfos()); } JSONObject data = JsonUtils.parseObject(JsonUtils.toJsonString(flightTaskPrepare), JSONObject.class); String result = djiBaseService.executeAndReturnResult(dockSn, "flighttask_prepare", data); @@ -128,7 +143,7 @@ public class RouteFlightServiceImpl implements RouteFlightService { //执行任务 Utils.sleep(1000); String resultMsg = djiBaseService.executeAndReturnResult(dockSn, "flighttask_execute", - new JSONObject().set("flight_id", taskId)); + new JSONObject().set("flight_id", taskId)); if (isRecord) { flightTaskService.addRouteTask(taskId, dockSn, route.getId()); } @@ -192,7 +207,7 @@ public class RouteFlightServiceImpl implements RouteFlightService { throw new RenException(ErrorCode.ROUTE_TASK_NOT_EXIST); } return djiBaseService.executeAndReturnResult(dockSn, "in_flight_wayline_stop", - new JSONObject().set("in_flight_wayline_id", flightId)); + new JSONObject().set("in_flight_wayline_id", flightId)); } @Override @@ -202,6 +217,6 @@ public class RouteFlightServiceImpl implements RouteFlightService { throw new RenException(ErrorCode.ROUTE_TASK_NOT_EXIST); } return djiBaseService.executeAndReturnResult(dockSn, "in_flight_wayline_recover", - new JSONObject().set("in_flight_wayline_id", flightId)); + new JSONObject().set("in_flight_wayline_id", flightId)); } } diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/WeatherNoflyServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/WeatherNoflyServiceImpl.java new file mode 100644 index 0000000..4bbe051 --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/WeatherNoflyServiceImpl.java @@ -0,0 +1,275 @@ +package com.multictrl.modules.business.service.impl; + +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.multictrl.common.constant.BusinessConstant; +import com.multictrl.common.service.impl.CrudServiceImpl; +import com.multictrl.common.utils.CacheUtils; +import com.multictrl.common.utils.ConvertUtils; +import com.multictrl.common.utils.JsonUtils; +import com.multictrl.common.utils.weather.*; +import com.multictrl.modules.business.dao.WeatherNoflyDao; +import com.multictrl.modules.business.dto.LatLonDTO; +import com.multictrl.modules.business.dto.WeatherNoflyDTO; +import com.multictrl.modules.business.entity.WeatherNoflyEntity; +import com.multictrl.modules.business.service.DJIBaseService; +import com.multictrl.modules.business.service.WeatherNoflyService; +import cn.hutool.core.util.StrUtil; +import com.multictrl.modules.business.service.WeatherService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * 天气阻飞 + * + * @author Sdy + * @since 1.0.0 2026-06-15 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class WeatherNoflyServiceImpl extends CrudServiceImpl implements WeatherNoflyService { + private final DJIBaseService djiBaseService; + private final WeatherService weatherService; + + @Override + public QueryWrapper getWrapper(Map params) { + String id = (String) params.get("id"); + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq(StrUtil.isNotBlank(id), "id", id); + + return wrapper; + } + + @Override + public WeatherNoflyDTO getInfoByDockSn(String dockSn) { + WeatherNoflyEntity entity = baseDao.selectOne(new QueryWrapper().eq("dock_sn", dockSn)); + return ConvertUtils.sourceToTarget(entity, WeatherNoflyDTO.class); + } + + @Override + public void addWeatherNofly(WeatherNoflyDTO dto) { + WeatherNoflyEntity entity = baseDao.selectOne(new QueryWrapper().eq("dock_sn", dto.getDockSn())); + if (entity != null) { + deleteById(entity.getId()); + } + save(dto); + } + + @Override + public JSONObject allowFlight(String dockSn, boolean isJobTask) { + JSONObject jsonObject = new JSONObject(); + jsonObject.set("isAllow", true); + //查询机库是否有阻飞信息 + WeatherNoflyEntity entity = baseDao.selectOne(new QueryWrapper() + .eq("dock_sn", dockSn).eq("is_open", true)); + if (entity != null) {//存在阻飞设置 + Boolean isAllPrevent = entity.getIsAllPrevent(); + if (isAllPrevent || isJobTask) { + Boolean isOpenWeather = entity.getIsOpenWeather();//判断天气阻飞 + if (isOpenWeather) { + LatLonDTO dockLocation = djiBaseService.getDockLocation(dockSn); + String weatherString = weatherService.getWeatherByLonLat(dockLocation.getLongitude(), dockLocation.getLatitude()); + JSONObject weatherObject = JsonUtils.parseObject(weatherString, JSONObject.class); + if (weatherObject != null) { + //风速判断 + String windpower = weatherObject.getStr("windpower");//风力级别 + double maxWindSpeed = WindSpeedConverter.getMaxWindSpeed(windpower); + if (maxWindSpeed >= entity.getWindSpeedWeather()) { + log.info("风力过大,级别:{},最大风速:{},阀值:{}", windpower, maxWindSpeed, entity.getWindSpeedWeather()); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "风力过大,阀值:{" + entity.getWindSpeedWeather() + "},起飞时风速:{" + maxWindSpeed + "}"); + } + + //温度判断 + double temperature = Double.parseDouble(weatherObject.getStr("temperature"));//温度 + if (temperature < entity.getTemperatureMin()) { + log.info("气温过低,当前温度:{},设置温度下限:{}", temperature, entity.getTemperatureMin()); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "气温过低,阀值:{" + entity.getTemperatureMin() + "},起飞时温度:{" + temperature + "}"); + } + if (temperature > entity.getTemperatureMax()) { + log.info("气温过高,当前温度:{},设置温度上限:{}", temperature, entity.getTemperatureMax()); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "气温过高,阀值:{" + entity.getTemperatureMax() + "},起飞时温度:{" + temperature + "}"); + } + + String weather = weatherObject.getStr("weather");//天气现象 + //雨量判断 + Integer rainfallWeather = entity.getRainfallWeather(); + if (rainfallWeather > 0) { + if (rainfallWeather == 1) { + if (RainfallUtils.isMinRain(weather) || RainfallUtils.isModerateRain(weather) || RainfallUtils.isMaxRain(weather)) { + log.info("已达雨量上限,设置:小雨,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雨量{小雨}阻飞,起飞时天气:{" + weather + "}"); + } + } else if (rainfallWeather == 2) { + if (RainfallUtils.isModerateRain(weather) || RainfallUtils.isMaxRain(weather)) { + log.info("已达雨量上限,设置:中雨,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雨量{中雨}阻飞,起飞时天气:{" + weather + "}"); + } + } else if (rainfallWeather == 3) { + if (RainfallUtils.isMaxRain(weather)) { + log.info("已达雨量上限,设置:大雨,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雨量{大雨}阻飞,起飞时天气:{" + weather + "}"); + } + } + } + + //雪量判断 + Integer snowWeather = entity.getSnowWeather(); + if (snowWeather > 0) { + if (snowWeather == 1) { + if (SnowUtils.isMinSnow(weather) || SnowUtils.isModerateSnow(weather) || SnowUtils.isMaxSnow(weather)) { + log.info("已达雪量上限,设置:小雪,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雪量{小雪}阻飞,起飞时天气:{" + weather + "}"); + } + } else if (snowWeather == 2) { + if (SnowUtils.isModerateSnow(weather) || SnowUtils.isMaxSnow(weather)) { + log.info("已达雪量上限,设置:中雪,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雪量{中雪}阻飞,起飞时天气:{" + weather + "}"); + } + } else if (snowWeather == 3) { + if (SnowUtils.isMaxSnow(weather)) { + log.info("已达雪量上限,设置:大雪,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雪量{大雪}阻飞,起飞时天气:{" + weather + "}"); + } + } + } + + //雾量判断 + Integer fogWeather = entity.getFogWeather(); + if (fogWeather > 0) { + if (fogWeather == 1) { + if (FogUtils.isMinFog(weather) || FogUtils.isModerateFog(weather) || FogUtils.isMaxFog(weather)) { + log.info("已达雾量上限,设置:轻雾,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雾量{轻雾}阻飞,起飞时天气:{" + weather + "}"); + } + } else if (fogWeather == 2) { + if (FogUtils.isModerateFog(weather) || FogUtils.isMaxFog(weather)) { + log.info("已达雾量上限,设置:中雾,当前:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雾量{中雾}阻飞,起飞时天气:{" + weather + "}"); + } + } else if (fogWeather == 3) { + if (FogUtils.isMaxFog(weather)) { + log.info("已达雾量上限,设置:大雾,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发雾量{大雾}阻飞,起飞时天气:{" + weather + "}"); + } + } + } + + //霾量判断 + Integer hazeWeather = entity.getHazeWeather(); + if (hazeWeather > 0) { + if (hazeWeather == 1) { + if (HazeUtils.isMinHaze(weather) || HazeUtils.isModerateHaze(weather) || HazeUtils.isMaxHaze(weather)) { + log.info("已达霾量上限,设置:轻度霾,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发霾量{轻度霾}阻飞,起飞时天气:{" + weather + "}"); + } + } else if (hazeWeather == 2) { + if (HazeUtils.isModerateHaze(weather) || HazeUtils.isMaxHaze(weather)) { + log.info("已达霾量上限,设置:中度霾,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发霾量{中度霾}阻飞,起飞时天气:{" + weather + "}"); + } + } else if (hazeWeather == 3) { + if (HazeUtils.isMaxHaze(weather)) { + log.info("已达霾量上限,设置:重度霾,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发霾量{重度霾}阻飞,起飞时天气:{" + weather + "}"); + } + } + } + + //冰雹判断 + Boolean isHailWeather = entity.getIsHailWeather(); + if (isHailWeather) { + if ("雷阵雨并伴有冰雹".equals(weather)) { + log.info("已达冰雹阻飞,设置:开启冰雹阻飞,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发冰雹阻飞,起飞时天气:{" + weather + "}"); + } + } + + //沙尘暴判断 + Boolean isSandstormWeather = entity.getIsSandstormWeather(); + if (isSandstormWeather) { + if ("沙尘暴".equals(weather) || "强沙尘暴".equals(weather)) { + log.info("已达沙尘暴阻飞,设置:开启沙尘暴阻飞,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发沙尘暴阻飞,起飞时天气:{" + weather + "}"); + } + } + + //龙卷风判断 + Boolean isTornadoWeather = entity.getIsTornadoWeather(); + if (isTornadoWeather) { + if ("龙卷风".equals(weather)) { + log.info("已达龙卷风阻飞,设置:开启龙卷风阻飞,实际:{}", weather); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发龙卷风阻飞,起飞时天气:{" + weather + "}"); + } + } + } + } + + //云端阻飞 + Integer rainfall = entity.getRainfall(); + Double windSpeed = entity.getWindSpeed(); + JSONObject data = (JSONObject) CacheUtils.get(BusinessConstant.DOCK_OSD + dockSn); + if (data != null) { + if (rainfall == 1 || rainfall == 2 || rainfall == 3) { + //0无雨 1小雨 2中雨 3大雨 + int value = data.getInt("rainfall"); + if (rainfall == 1) { + if (value >= 1) { + String message = value == 1 ? "小雨" : value == 2 ? "中雨" : "大雨"; + log.info("已达下雨阻飞,设置:小雨阻飞,实际:{}", message); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发下雨阻飞,起飞时天气:{" + message + "}"); + } + } + if (rainfall == 2) { + if (value >= 2) { + String message = value == 2 ? "中雨" : "大雨"; + log.info("已达下雨阻飞,设置:中雨阻飞,实际:{}", message); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发下雨阻飞,起飞时天气:{" + message + "}"); + } + } + if (rainfall == 3) { + if (value >= 3) { + log.info("已达下雨阻飞,设置:大雨阻飞,实际:{大雨}"); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "触发下雨阻飞,起飞时天气:{大雨}"); + } + } + } + //风速 + Float value = data.getFloat("wind_speed"); + if (value >= windSpeed) { + log.info("风力过大,设置:风速阻飞:{},实际风速:{}", windSpeed, value); + jsonObject.set("isAllow", false); + jsonObject.set("reason", "风力过大,阀值:{" + windSpeed + "},起飞时风速:{" + value + "}"); + } + } + } + } + + return jsonObject; + } +} \ No newline at end of file diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/WeatherServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/WeatherServiceImpl.java new file mode 100644 index 0000000..057becf --- /dev/null +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/WeatherServiceImpl.java @@ -0,0 +1,132 @@ +package com.multictrl.modules.business.service.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.multictrl.common.constant.SysParamsKey; +import com.multictrl.common.exception.ErrorCode; +import com.multictrl.common.exception.RenException; +import com.multictrl.common.utils.JsonUtils; +import com.multictrl.modules.business.service.WeatherService; +import com.multictrl.modules.sys.dao.SysParamsDao; +import com.multictrl.modules.sys.entity.SysParamsEntity; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 天气服务 + * + * @author Sdy + * @since 1.0.0 2026/6/16 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class WeatherServiceImpl implements WeatherService { + private final SysParamsDao paramsDao; + + private final static String GAODE_GEOCODE_REGEO = "https://restapi.amap.com/v3/geocode/regeo?key=%s&location=%s&radius=1&extensions=all"; + public static final String GAODE_WEATHER_API = "https://restapi.amap.com/v3/weather/weatherInfo?key=%s&city=%s&extensions=base"; + + @Override + public String getWeatherByLonLat(Double longitude, Double latitude) { + String location = longitude + "," + latitude; + try { + String adCode = getAdCode(location); + if (StrUtil.isEmpty(adCode)) { + log.debug("获取 adcode 失败,经纬度:{}", location); + throw new RenException(ErrorCode.GAODE_REQUEST_ERROR); + } + String weatherByAdCode = getWeatherByAdCode(adCode); + if (StrUtil.isEmpty(weatherByAdCode)) { + log.debug("获取 weatherByAdCode 失败,经纬度:{}, adCode:{}", location, adCode); + throw new RenException(ErrorCode.GAODE_REQUEST_ERROR); + } + return weatherByAdCode; + } catch (Exception e) { + log.error("获取天气信息异常,经纬度:{}", location, e); + throw new RenException(ErrorCode.GAODE_REQUEST_ERROR); + } + } + + /** + * 获取高德Key + */ + private String getGaoDeKey() { + SysParamsEntity entity = paramsDao.selectOne(new QueryWrapper() + .eq("param_code", SysParamsKey.GAODE_API_KEY)); + if (entity == null) { + throw new RenException(ErrorCode.GAODE_API_KEY_NO_CONFIG); + } + + return entity.getParamValue(); + } + + /** + * 调用高德地理编码 API 获取 adcode + */ + private String getAdCode(String location) { + String url = String.format(GAODE_GEOCODE_REGEO, getGaoDeKey(), location); + try { + String response = HttpUtil.get(url); + if (StrUtil.isEmpty(response)) { + log.debug("gaode_地理编码接口返回空,url:{}", url); + return null; + } + JSONObject jsonObject = JsonUtils.parseObject(response, JSONObject.class); + if (jsonObject == null || !"OK".equals(jsonObject.getStr("info"))) { + log.debug("gaode_地理编码接口返回失败,url:{}, response:{}", url, response); + return null; + } + JSONObject regeocode = jsonObject.getJSONObject("regeocode"); + if (regeocode == null) { + return null; + } + JSONObject addressComponent = regeocode.getJSONObject("addressComponent"); + if (addressComponent == null) { + return null; + } + return addressComponent.getStr("adcode"); + } catch (Exception e) { + log.error("gaode_地理编码请求异常,url:{}", url, e); + return null; + } + } + + /** + * 根据 adcode 调用高德天气 API 获取实时天气 + */ + private String getWeatherByAdCode(String adCode) { + String url = String.format(GAODE_WEATHER_API, getGaoDeKey(), adCode); + try { + String response = HttpUtil.get(url); + if (StrUtil.isEmpty(response)) { + log.debug("gaode_天气接口返回空,adCode:{}", adCode); + return null; + } + JSONObject weatherObject = JsonUtils.parseObject(response, JSONObject.class); + if (weatherObject == null) { + log.debug("gaode_天气接口返回非 JSON,adCode:{}", adCode); + return null; + } + // 高德天气成功状态为 "1" + if (!"1".equals(weatherObject.getStr("status"))) { + log.debug("gaode_天气接口返回失败,adCode:{}, response:{}", adCode, response); + return null; + } + JSONArray lives = weatherObject.getJSONArray("lives"); + if (lives == null || lives.isEmpty()) { + log.debug("gaode_天气接口返回 lives 为空,adCode:{}", adCode); + return null; + } + JSONObject firstLive = lives.getJSONObject(0); + return firstLive.toString(); + } catch (Exception e) { + log.error("gaode_天气请求异常,url:{}", url, e); + return null; + } + } +} diff --git a/admin/src/main/java/com/multictrl/modules/job/task/RouteTask.java b/admin/src/main/java/com/multictrl/modules/job/task/RouteTask.java index 26c9e3b..d84c80b 100644 --- a/admin/src/main/java/com/multictrl/modules/job/task/RouteTask.java +++ b/admin/src/main/java/com/multictrl/modules/job/task/RouteTask.java @@ -2,9 +2,11 @@ package com.multictrl.modules.job.task; import cn.hutool.json.JSONObject; import com.multictrl.common.constant.BusinessConstant; +import com.multictrl.common.constant.FlightTaskType; import com.multictrl.common.utils.CacheUtils; import com.multictrl.common.utils.JsonUtils; import com.multictrl.common.utils.Utils; +import com.multictrl.modules.business.dto.WeatherNoflyTaskDTO; import com.multictrl.modules.business.dto.flight.FlightExecute; import com.multictrl.modules.business.service.FlightTaskService; import com.multictrl.modules.business.service.RouteFlightService; @@ -50,10 +52,19 @@ public class RouteTask implements ITask { //执行任务 FlightExecute flightExecute = new FlightExecute(); flightExecute.setRouteId(Long.parseLong(dto.getParams())); - routeFlightService.flightExecute(dockSn, flightExecute, false); + String result = routeFlightService.flightExecute(dockSn, flightExecute, false, FlightTaskType.SCHEDULED.getCode()); //添加日志 Utils.sleep(1000); String taskId = (String) CacheUtils.get(BusinessConstant.WORKING_TASK_ID + dockSn); - flightTaskService.addJobRouteTask(taskId, jobId); + if (result.contains("阻飞")) { + WeatherNoflyTaskDTO noflyTask = new WeatherNoflyTaskDTO(); + noflyTask.setTaskId(taskId); + noflyTask.setJobId(jobId); + noflyTask.setTaskType(FlightTaskType.SCHEDULED.getCode()); + noflyTask.setReason(result); + flightTaskService.addWeatherNoflyTask(noflyTask); + } else { + flightTaskService.addJobRouteTask(taskId, jobId); + } } } diff --git a/admin/src/main/java/com/multictrl/modules/sys/controller/IndexController.java b/admin/src/main/java/com/multictrl/modules/sys/controller/IndexController.java index 24c60c8..138fead 100644 --- a/admin/src/main/java/com/multictrl/modules/sys/controller/IndexController.java +++ b/admin/src/main/java/com/multictrl/modules/sys/controller/IndexController.java @@ -1,14 +1,17 @@ package com.multictrl.modules.sys.controller; +import com.alibaba.fastjson2.JSONObject; import com.google.common.collect.Maps; import com.multictrl.common.annotation.ApiOrder; import com.multictrl.common.utils.Result; import com.multictrl.modules.business.influxdb.UavReport; import com.multictrl.modules.business.service.InfluxService; +import com.multictrl.modules.business.service.WeatherService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; @@ -25,6 +28,7 @@ import java.util.List; @RequiredArgsConstructor public class IndexController { private final InfluxService influxService; + private final WeatherService weatherService; @GetMapping("/") public Result index() { @@ -42,4 +46,11 @@ public class IndexController { System.out.println(flyList); return new Result<>(); } + + @Operation(summary = "经纬度获取天气信息") + @GetMapping("/getWeatherByLocation") + public Result getWeatherByLocation(@RequestParam Double lon, @RequestParam Double lat) { + String data = weatherService.getWeatherByLonLat(lon, lat); + return new Result().ok(JSONObject.parseObject(data)); + } } diff --git a/common/src/main/java/com/multictrl/common/exception/ErrorCode.java b/common/src/main/java/com/multictrl/common/exception/ErrorCode.java index df44f9a..f744c68 100644 --- a/common/src/main/java/com/multictrl/common/exception/ErrorCode.java +++ b/common/src/main/java/com/multictrl/common/exception/ErrorCode.java @@ -72,4 +72,6 @@ public interface ErrorCode { int ZHIMOU_NO_CONFIG = 20031; int ZHIMOU_REQUEST_ERROR = 20032; int ZHIMOU_TASK_NOT_EXIST = 20033; + int GAODE_API_KEY_NO_CONFIG = 20034; + int GAODE_REQUEST_ERROR = 20035; } diff --git a/common/src/main/resources/i18n/messages.properties b/common/src/main/resources/i18n/messages.properties index 00bdf9f..567cee9 100644 --- a/common/src/main/resources/i18n/messages.properties +++ b/common/src/main/resources/i18n/messages.properties @@ -60,4 +60,6 @@ 20030=\u5220\u9664{0}\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 20031=AI\u56FE\u50CF\u68C0\u6D4B\u7B97\u6CD5\u4FE1\u606F\u672A\u914D\u7F6E 20032=AI\u56FE\u50CF\u68C0\u6D4B\u7B97\u6CD5\u8BF7\u6C42\u51FA\u9519 -20033=AI\u4EFB\u52A1\u672A\u5F00\u542F \ No newline at end of file +20033=AI\u4EFB\u52A1\u672A\u5F00\u542F +20034=\u5929\u6C14\u4FE1\u606F\u8BBF\u95EEkey\u672A\u914D\u7F6E +20035=\u5929\u6C14\u63A5\u53E3\u8BF7\u6C42\u51FA\u9519 \ No newline at end of file diff --git a/prj-deploy/file/pgsql/init.sql b/prj-deploy/file/pgsql/init.sql index cf53b2a..369cda4 100644 --- a/prj-deploy/file/pgsql/init.sql +++ b/prj-deploy/file/pgsql/init.sql @@ -2351,4 +2351,80 @@ ALTER TABLE "public"."bus_maintenance_img" ALTER TABLE public.bus_flight_task ADD ai_media_num int4 NULL; COMMENT -ON COLUMN public.bus_flight_task.ai_media_num IS 'ai媒体数量'; \ No newline at end of file +ON COLUMN public.bus_flight_task.ai_media_num IS 'ai媒体数量'; + +--2026/6/17 +DROP TABLE IF EXISTS "public"."bus_weather_nofly"; +CREATE TABLE "public"."bus_weather_nofly" +( + "id" int8 NOT NULL, + "dock_sn" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, + "is_open" bool NOT NULL, + "wind_speed" float8, + "rainfall" int4, + "is_all_prevent" bool NOT NULL, + "is_open_weather" bool NOT NULL, + "wind_speed_weather" float8, + "rainfall_weather" int4, + "snow_weather" int4, + "fog_weather" int4, + "haze_weather" int4, + "is_hail_weather" bool, + "is_sandstorm_weather" bool, + "is_tornado_weather" bool, + "temperature_max" float8, + "temperature_min" float8, + "creator" int8, + "create_date" timestamp(6), + "updater" int8, + "update_date" timestamp(6) +) +; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."dock_sn" IS '机库sn'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."is_open" IS '是否开启阻飞(开启后使用机库上报数据)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."wind_speed" IS '风速m/s'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."rainfall" IS '雨(0关闭 1小雨 2中雨 3大雨)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."is_all_prevent" IS '是否全部阻飞(开启后全部阻飞不开启只阻飞计划库)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."is_open_weather" IS '是否开启天气阻飞(开启后在机库数据上增加天气预报数据)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."wind_speed_weather" IS '风速m/s(天气预报)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."rainfall_weather" IS '雨(天气预报 0关闭 1小雨 2中雨 3大雨)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."snow_weather" IS '雪(天气预报 0关闭 1小雪 2中雪 3大雪)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."fog_weather" IS '雾(天气预报 0关闭 1轻雾 2中雾 3大雾)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."haze_weather" IS '霾(天气预报 0关闭 1轻度霾 2中度霾 3重度霾)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."is_hail_weather" IS '冰雹阻飞'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."is_sandstorm_weather" IS '沙尘暴阻飞'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."is_tornado_weather" IS '龙卷风阻飞'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."temperature_max" IS '温度上限(摄氏度)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."temperature_min" IS '温度下限(摄氏度)'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."creator" IS '创建人'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."create_date" IS '创建时间'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."updater" IS '修改人'; +COMMENT +ON COLUMN "public"."bus_weather_nofly"."update_date" IS '修改时间'; +COMMENT +ON TABLE "public"."bus_weather_nofly" IS '天气阻飞'; + +-- ---------------------------- +-- Primary Key structure for table bus_weather_nofly +-- ---------------------------- +ALTER TABLE "public"."bus_weather_nofly" + ADD CONSTRAINT "uav_hangar_prevent_pkey" PRIMARY KEY ("id"); \ No newline at end of file