zhaowenyu
2025-10-17 1ab85e9fd1b392d36c6f2b08754249e4574ea4e8
Merge remote-tracking branch 'origin/dev' into dev
6 files added
26 files modified
2 files deleted
1092 ■■■■■ changed files
ard-admin/src/main/java/com/ard/web/controller/data/ArdMapController.java 12 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/data/ArdModelController.java 12 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/data/ArdTileController.java 12 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/device/ArdCameraController.java 16 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/work/ArdDroneInfoController.java 20 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/work/ArdServerInfoController.java 6 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/work/UavBaseController.java 21 ●●●●● patch | view | raw | blame | history
ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyBox.java 15 ●●●●● patch | view | raw | blame | history
ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyZone.java 24 ●●●●● patch | view | raw | blame | history
ard-system/src/main/java/com/ard/data/noflyzone/domain/Vertex.java 30 ●●●●● patch | view | raw | blame | history
ard-system/src/main/java/com/ard/data/noflyzone/utils/GeoUtils.java 171 ●●●●● patch | view | raw | blame | history
ard-vtdu/pom.xml 1 ●●●●● patch | view | raw | blame | history
ard-vtdu/src/main/java/com/ard/vtdu/client/MediamtxAuthInterceptor.java 54 ●●●●● patch | view | raw | blame | history
ard-work/pom.xml 1 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/forest/TokenInterceptor.java 24 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/TcpClientSession.java 27 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/TcpClientUtil.java 42 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/TyTcpClientManager.java 97 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/cache/TyTcpServerCache.java 68 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/handler/ClientHandler.java 10 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/domain/cache/TyTcpServerCache.java 38 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/domain/entity/ArdDroneInfo.java 7 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/domain/entity/ArdServerInfo.java 8 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/mapper/ArdServerInfoMapper.java 3 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/IArdDroneInfoService.java 5 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/IArdServerInfoService.java 2 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/ITyService.java 9 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/impl/ArdDroneInfoServiceImpl.java 138 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/impl/ArdServerInfoServiceImpl.java 4 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java 100 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/util/UtilMethod.java 35 ●●●●● patch | view | raw | blame | history
ard-work/src/main/resources/mapper/ArdDroneInfoMapper.xml 66 ●●●●● patch | view | raw | blame | history
ard-work/src/main/resources/mapper/ArdServerInfoMapper.xml 6 ●●●●● patch | view | raw | blame | history
pom.xml 8 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/data/ArdMapController.java
@@ -42,7 +42,7 @@
     * 查询地图管理列表
     */
    @ApiOperation("查询地图管理列表")
    @PreAuthorize("@ss.hasPermi('system:map:list')")
    @PreAuthorize("@ss.hasPermi('data:map:list')")
    @GetMapping("/list")
    public TableDataInfo list(ArdMap ardMap) {
        startPage();
@@ -54,7 +54,7 @@
     * 导出地图管理列表
     */
    @ApiOperation("导出地图管理列表")
    @PreAuthorize("@ss.hasPermi('system:map:export')")
    @PreAuthorize("@ss.hasPermi('data:map:export')")
    @Log(title = "地图管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, ArdMap ardMap) {
@@ -67,7 +67,7 @@
     * 获取地图管理详细信息
     */
    @ApiOperation("获取地图管理详细信息")
    @PreAuthorize("@ss.hasPermi('system:map:query')")
    @PreAuthorize("@ss.hasPermi('data:map:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") String id) {
        return success(ardMapService.selectArdMapById(id));
@@ -77,7 +77,7 @@
     * 新增地图管理
     */
    @ApiOperation("新增地图管理")
    @PreAuthorize("@ss.hasPermi('system:map:add')")
    @PreAuthorize("@ss.hasPermi('data:map:add')")
    @Log(title = "地图管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody ArdMap ardMap) {
@@ -88,7 +88,7 @@
     * 修改地图管理
     */
    @ApiOperation("修改地图管理")
    @PreAuthorize("@ss.hasPermi('system:map:edit')")
    @PreAuthorize("@ss.hasPermi('data:map:edit')")
    @Log(title = "地图管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody ArdMap ardMap) {
@@ -99,7 +99,7 @@
     * 删除地图管理
     */
    @ApiOperation("删除地图管理")
    @PreAuthorize("@ss.hasPermi('system:map:remove')")
    @PreAuthorize("@ss.hasPermi('data:map:remove')")
    @Log(title = "地图管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable String[] ids) {
ard-admin/src/main/java/com/ard/web/controller/data/ArdModelController.java
@@ -42,7 +42,7 @@
     * 查询模型管理列表
     */
    @ApiOperation("查询模型管理列表")
    @PreAuthorize("@ss.hasPermi('system:model:list')")
    @PreAuthorize("@ss.hasPermi('data:model:list')")
    @GetMapping("/list")
    public TableDataInfo list(ArdModel ardModel) {
        startPage();
@@ -54,7 +54,7 @@
     * 导出模型管理列表
     */
    @ApiOperation("导出模型管理列表")
    @PreAuthorize("@ss.hasPermi('system:model:export')")
    @PreAuthorize("@ss.hasPermi('data:model:export')")
    @Log(title = "模型管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, ArdModel ardModel) {
@@ -67,7 +67,7 @@
     * 获取模型管理详细信息
     */
    @ApiOperation("获取模型管理详细信息")
    @PreAuthorize("@ss.hasPermi('system:model:query')")
    @PreAuthorize("@ss.hasPermi('data:model:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") String id) {
        return success(ardModelService.selectArdModelById(id));
@@ -77,7 +77,7 @@
     * 新增模型管理
     */
    @ApiOperation("新增模型管理")
    @PreAuthorize("@ss.hasPermi('system:model:add')")
    @PreAuthorize("@ss.hasPermi('data:model:add')")
    @Log(title = "模型管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody ArdModel ardModel) {
@@ -88,7 +88,7 @@
     * 修改模型管理
     */
    @ApiOperation("修改模型管理")
    @PreAuthorize("@ss.hasPermi('system:model:edit')")
    @PreAuthorize("@ss.hasPermi('data:model:edit')")
    @Log(title = "模型管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody ArdModel ardModel) {
@@ -99,7 +99,7 @@
     * 删除模型管理
     */
    @ApiOperation("删除模型管理")
    @PreAuthorize("@ss.hasPermi('system:model:remove')")
    @PreAuthorize("@ss.hasPermi('data:model:remove')")
    @Log(title = "模型管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable String[] ids) {
ard-admin/src/main/java/com/ard/web/controller/data/ArdTileController.java
@@ -42,7 +42,7 @@
     * 查询tile管理列表
     */
    @ApiOperation("查询tile管理列表")
    @PreAuthorize("@ss.hasPermi('system:tile:list')")
    @PreAuthorize("@ss.hasPermi('data:tile:list')")
    @GetMapping("/list")
    public TableDataInfo list(ArdTile ardTile) {
        startPage();
@@ -54,7 +54,7 @@
     * 导出tile管理列表
     */
    @ApiOperation("导出tile管理列表")
    @PreAuthorize("@ss.hasPermi('system:tile:export')")
    @PreAuthorize("@ss.hasPermi('data:tile:export')")
    @Log(title = "tile管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, ArdTile ardTile) {
@@ -67,7 +67,7 @@
     * 获取tile管理详细信息
     */
    @ApiOperation("获取tile管理详细信息")
    @PreAuthorize("@ss.hasPermi('system:tile:query')")
    @PreAuthorize("@ss.hasPermi('data:tile:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") String id) {
        return success(ardTileService.selectArdTileById(id));
@@ -77,7 +77,7 @@
     * 新增tile管理
     */
    @ApiOperation("新增tile管理")
    @PreAuthorize("@ss.hasPermi('system:tile:add')")
    @PreAuthorize("@ss.hasPermi('data:tile:add')")
    @Log(title = "tile管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody ArdTile ardTile) {
@@ -88,7 +88,7 @@
     * 修改tile管理
     */
    @ApiOperation("修改tile管理")
    @PreAuthorize("@ss.hasPermi('system:tile:edit')")
    @PreAuthorize("@ss.hasPermi('data:tile:edit')")
    @Log(title = "tile管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody ArdTile ardTile) {
@@ -99,7 +99,7 @@
     * 删除tile管理
     */
    @ApiOperation("删除tile管理")
    @PreAuthorize("@ss.hasPermi('system:tile:remove')")
    @PreAuthorize("@ss.hasPermi('data:tile:remove')")
    @Log(title = "tile管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable String[] ids) {
ard-admin/src/main/java/com/ard/web/controller/device/ArdCameraController.java
@@ -29,7 +29,7 @@
 */
@Slf4j
@RestController
@RequestMapping("/device/cameras")
@RequestMapping("/device/camera")
@Api(tags = "相机管理接口")
public class ArdCameraController extends BaseController {
@@ -39,7 +39,7 @@
    /**
     * 查询相机设备列表
     */
    @PreAuthorize("@ss.hasPermi('device:cameras:list')")
    @PreAuthorize("@ss.hasPermi('device:camera:list')")
    @GetMapping("/list")
    @ApiOperation("查询相机设备列表")
    public TableDataInfo list(ArdCamera ardCamera) {
@@ -53,7 +53,7 @@
     * 导出相机设备列表
     */
    @ApiOperation("导出相机设备列表")
    @PreAuthorize("@ss.hasPermi('device:cameras:export')")
    @PreAuthorize("@ss.hasPermi('device:camera:export')")
    @Log(title = "相机设备", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, ArdCamera ardCamera) {
@@ -66,7 +66,7 @@
     * 获取相机设备详细信息
     */
    @ApiOperation("获取相机设备详细信息")
    @PreAuthorize("@ss.hasPermi('device:cameras:query')")
    @PreAuthorize("@ss.hasPermi('device:camera:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") String id) {
        return success(ardCameraService.selectArdCameraById(id));
@@ -76,7 +76,7 @@
     * 新增相机设备
     */
    @ApiOperation("新增相机设备")
    @PreAuthorize("@ss.hasPermi('device:cameras:add')")
    @PreAuthorize("@ss.hasPermi('device:camera:add')")
    @Log(title = "相机设备", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody ArdCamera camera) {
@@ -90,7 +90,7 @@
     * 修改相机设备
     */
    @ApiOperation("修改相机设备")
    @PreAuthorize("@ss.hasPermi('device:cameras:edit')")
    @PreAuthorize("@ss.hasPermi('device:camera:edit')")
    @Log(title = "相机设备", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody ArdCamera camera) {
@@ -104,7 +104,7 @@
     * 删除相机设备
     */
    @ApiOperation("删除相机设备")
    @PreAuthorize("@ss.hasPermi('device:cameras:remove')")
    @PreAuthorize("@ss.hasPermi('device:camera:remove')")
    @Log(title = "相机设备", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable String[] ids) {
@@ -113,7 +113,7 @@
    @Log(title = "导入相机设备", businessType = BusinessType.IMPORT)
    @PreAuthorize("@ss.hasPermi('device:cameras:import')")
    @PreAuthorize("@ss.hasPermi('device:camera:import')")
    @PostMapping("/importData")
    @ApiOperation("导入相机设备")
    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
ard-admin/src/main/java/com/ard/web/controller/work/ArdDroneInfoController.java
@@ -106,4 +106,24 @@
        return toAjax(ardDroneInfoService.deleteArdDroneByIds(ids));
    }
    /**
     * 批量新增无人机盒子
     */
    @ApiOperation("批量新增无人机盒子")
    @PreAuthorize("@ss.hasPermi('work:drone:insertArdDroneLs')")
    @Log(title = "无人机管理", businessType = BusinessType.DELETE)
    @PostMapping("insertArdDroneLs/{boxSn}/{deptId}")
    public AjaxResult insertArdDroneLs(@PathVariable String[] boxSn, @PathVariable Long deptId) {
        try {
            int result = ardDroneInfoService.insertArdDroneLs(boxSn, deptId);
            if (result > 0) {
                return AjaxResult.success("成功新增 " + result + " 个无人机盒子");
            } else {
                return AjaxResult.success("没有需要新增的无人机盒子");
            }
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }
}
ard-admin/src/main/java/com/ard/web/controller/work/ArdServerInfoController.java
@@ -112,9 +112,9 @@
    @ApiOperation("修改服务器状态")
    @PreAuthorize("@ss.hasPermi('work:server:updateStatus')")
    @Log(title = "服务器管理", businessType = BusinessType.UPDATE)
    @PutMapping("/{id}")
    public AjaxResult updateServerStatus(@PathVariable("id") String id) {
        return toAjax(ardServerInfoService.updateServerStatus(id));
    @PutMapping("/{id}/{status}")
    public AjaxResult updateServerStatus(@PathVariable("id") String id,@PathVariable("status") String status) {
        return toAjax(ardServerInfoService.updateServerStatus(id,status));
    }
}
ard-admin/src/main/java/com/ard/web/controller/work/UavBaseController.java
@@ -43,16 +43,19 @@
        return AjaxResult.success(tyService.getAccessToken(serverId));
    }
    @ApiOperation("获取六合盒子列表")
    @ApiOperation("获取盒子列表")
    @ApiOperationSupport(order = 2)
    @GetMapping("/getBoxList")
    public AjaxResult getBoxList() {
        List<BoxInfoReceiver> boxList = tyService.getBoxList();
        return AjaxResult.success(boxList);
    public AjaxResult getBoxList(@RequestParam(required = false) String serverId) {
        if (serverId == null) {
            return AjaxResult.success(tyService.getBoxList());
        } else {
            return AjaxResult.success(tyService.getBoxList(serverId));
        }
    }
    @ApiOperation("获取飞行历史")
    @ApiOperationSupport(order = 3)
    @ApiOperationSupport(order = 4)
    @PostMapping("/getBoxHistory")
    public AjaxResult getBoxHistory(@RequestBody BoxHistoryDTO boxHistoryDTO) {
        List<BoxHistoryReceiver> boxHistory = tyService.getBoxHistory(boxHistoryDTO);
@@ -60,7 +63,7 @@
    }
    @ApiOperation("获取任务照片")
    @ApiOperationSupport(order = 4)
    @ApiOperationSupport(order = 5)
    @PostMapping("/getBoxPhotos")
    public AjaxResult getBoxPhotos(@RequestBody BoxPhotosDTO boxPhotosDTO) {
        List<BoxPhotosReceiver> boxPhotos = tyService.getBoxPhotos(boxPhotosDTO);
@@ -68,7 +71,7 @@
    }
    @ApiOperation("获取直播地址")
    @ApiOperationSupport(order = 5)
    @ApiOperationSupport(order = 6)
    @GetMapping("/getBoxList/{boxSn}")
    public AjaxResult getBoxLiveUrl(@PathVariable("boxSn") String boxSn) {
        BoxStreamUrlReceiver boxLiveUrl = tyService.getBoxLiveUrl(boxSn);
@@ -76,7 +79,7 @@
    }
    @ApiOperation("修改云盒设置")
    @ApiOperationSupport(order = 6)
    @ApiOperationSupport(order = 7)
    @PostMapping("/updateBoxSettings")
    public AjaxResult updateBoxSettings(@RequestBody BoxSettingsDTO boxSettingsDTO) {
        Boolean state = tyService.updateBoxSettings(boxSettingsDTO);
@@ -84,7 +87,7 @@
    }
    @ApiOperation("根据[服务器id]获取六合盒子列表")
    @ApiOperationSupport(order = 7)
    @ApiOperationSupport(order = 8)
    @GetMapping("/getBoxSnToList/{id}")
    public TableDataInfo getBoxSnToList(@PathVariable("id") String id) {
        startPage();
ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyBox.java
New file
@@ -0,0 +1,15 @@
package com.ard.data.noflyzone.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ArdNoFlyBox {
    private double minx;
    private double miny;
    private double maxx;
    private double maxy;
}
ard-system/src/main/java/com/ard/data/noflyzone/domain/ArdNoFlyZone.java
@@ -1,10 +1,13 @@
package com.ard.data.noflyzone.domain;
import com.ard.data.noflyzone.utils.GeoUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.ard.common.annotation.Excel;
import com.ard.common.core.domain.BaseEntity;
import java.util.List;
/**
 * 禁飞区管理对象 ard_no_fly_zone
@@ -29,4 +32,25 @@
    /** 数据json */
    @Excel(name = "数据json")
    private FeatureCollection geojson;
    /** 禁飞区外接矩形 */
    private ArdNoFlyBox ardNoFlyBox;
    private List<Vertex> vertices;
    // 懒加载 / 动态计算
    public ArdNoFlyBox getArdNoFlyBox() {
        if (ardNoFlyBox == null && geojson != null) {
            ardNoFlyBox = GeoUtils.getArdNoFlyBox(geojson);
        }
        return ardNoFlyBox;
    }
    public List<Vertex> getVertices() {
        if (vertices == null && geojson != null) {
            vertices = GeoUtils.toVertexes(geojson);
        }
        return vertices;
    }
}
ard-system/src/main/java/com/ard/data/noflyzone/domain/Vertex.java
New file
@@ -0,0 +1,30 @@
package com.ard.data.noflyzone.domain;
public class Vertex {
    public double x;
    public double y;
    public Vertex() {
    }
    public Vertex(double x, double y) {
        this.x = x;
        this.y = y;
    }
    // 判断这个点是不是和另外一个点重合(允许极小的误差,算作同一个点
    public boolean IsSame(Vertex anotherV) {
        double almostZero = 0.00000001;
        if ((Math.abs(x - anotherV.x) < almostZero) && (Math.abs(y - anotherV.y) < almostZero)) {
            return true;
        } else {
            return false;
        }
    }
    // 直线距离
    public double Distance(Vertex anotherV) {
        return Math.sqrt(Math.pow(x - anotherV.x, 2) + Math.pow(y - anotherV.y, 2));
    }
}
ard-system/src/main/java/com/ard/data/noflyzone/utils/GeoUtils.java
New file
@@ -0,0 +1,171 @@
package com.ard.data.noflyzone.utils;
import com.ard.data.noflyzone.domain.ArdNoFlyBox;
import com.ard.data.noflyzone.domain.FeatureCollection;
import com.ard.data.noflyzone.domain.Vertex;
import java.util.ArrayList;
import java.util.List;
/**
 * 几何工具类
 */
public class GeoUtils {
    /**
     * 根据 GeoJSON(FeatureCollection)计算外接矩形
     */
    @SuppressWarnings("unchecked")
    public static ArdNoFlyBox getArdNoFlyBox(FeatureCollection featureCollection) {
        if (featureCollection == null || featureCollection.getFeatures() == null) {
            return null;
        }
        double minx = Double.MAX_VALUE;
        double miny = Double.MAX_VALUE;
        double maxx = Double.MIN_VALUE;
        double maxy = Double.MIN_VALUE;
        for (FeatureCollection.Feature feature : featureCollection.getFeatures()) {
            FeatureCollection.Geometry geometry = feature.getGeometry();
            if (geometry == null || geometry.getCoordinates() == null) {
                continue;
            }
            String type = geometry.getType();
            Object coords = geometry.getCoordinates();
            if ("Polygon".equalsIgnoreCase(type)) {
                // Polygon: [[[lon, lat], [lon, lat], ...]]
                List<List<List<Double>>> rings = (List<List<List<Double>>>) coords;
                for (List<List<Double>> ring : rings) {
                    for (List<Double> point : ring) {
                        if (point.size() < 2) {
                            continue;
                        }
                        double x = point.get(0);
                        double y = point.get(1);
                        minx = Math.min(minx, x);
                        miny = Math.min(miny, y);
                        maxx = Math.max(maxx, x);
                        maxy = Math.max(maxy, y);
                    }
                }
            }
            else if ("MultiPolygon".equalsIgnoreCase(type)) {
                // MultiPolygon: [[[[lon, lat], ...]], ...]
                List<List<List<List<Double>>>> polygons = (List<List<List<List<Double>>>>) coords;
                for (List<List<List<Double>>> polygon : polygons) {
                    for (List<List<Double>> ring : polygon) {
                        for (List<Double> point : ring) {
                            if (point.size() < 2) {
                                continue;
                            }
                            double x = point.get(0);
                            double y = point.get(1);
                            minx = Math.min(minx, x);
                            miny = Math.min(miny, y);
                            maxx = Math.max(maxx, x);
                            maxy = Math.max(maxy, y);
                        }
                    }
                }
            }
        }
        return new ArdNoFlyBox(minx, miny, maxx, maxy);
    }
    /**
     * 判断点是否在多边形内(包括边界)
     * 射线法:从该点向右发射水平射线,计算交点个数
     *
     * @param vertex 点对象
     * @param vertexes 多边形顶点列表(按顺序闭合)
     * @return true 表示点在多边形内或在边上
     */
    public static boolean isVertexInPoly(Vertex vertex, List<Vertex> vertexes) {
        if (vertex == null || vertexes == null || vertexes.size() < 3) {
            return false; // 无效输入
        }
        int count = 0;
        int n = vertexes.size();
        for (int i = 0; i < n; i++) {
            Vertex v1 = vertexes.get(i);
            Vertex v2 = vertexes.get((i + 1) % n);
            // 1. 点就是多边形的顶点
            if (v1.IsSame(vertex)) {
                return true;
            }
            // 2. 点在线段上
            double minX = Math.min(v1.x, v2.x);
            double maxX = Math.max(v1.x, v2.x);
            double minY = Math.min(v1.y, v2.y);
            double maxY = Math.max(v1.y, v2.y);
            // 水平线段
            if (minY == maxY) {
                if (vertex.y == minY && vertex.x >= minX && vertex.x <= maxX) {
                    return true;
                } else {
                    continue; // 不计入交点
                }
            }
            // 点在线段纵向范围外
            if (vertex.y < minY || vertex.y > maxY) {
                continue;
            }
            // 计算射线交点的 X 坐标
            double xIntersect = v1.x + (vertex.y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y);
            // 交点在点左侧,不计入
            if (xIntersect < vertex.x) {
                continue;
            }
            // 交点就在点上,认为在边界
            if (xIntersect == vertex.x) {
                return true;
            }
            // 避免射线穿过线段下端点重复计数
            if (vertex.y == minY) {
                continue;
            }
            // 其它情况,交点计数
            count++;
        }
        // 奇数交点 → 点在多边形内
        return count % 2 != 0;
    }
    /**
     * 将 GeoJSON Polygon 的外环坐标转换成 Vertex 列表
     */
    public static List<Vertex> toVertexes(FeatureCollection featureCollection) {
        List<Vertex> vertexes = new ArrayList<>();
        if (featureCollection == null || featureCollection.getFeatures() == null || featureCollection.getFeatures().isEmpty()) {
            return vertexes;
        }
        FeatureCollection.Feature feature = featureCollection.getFeatures().get(0);
        if (feature.getGeometry() != null) {
            List<List<List<Double>>> coords = feature.getGeometry().getCoordinates();
            if (coords != null && !coords.isEmpty()) {
                List<List<Double>> outerRing = coords.get(0);
                for (List<Double> point : outerRing) {
                    if (point.size() >= 2) {
                        vertexes.add(new Vertex(point.get(0), point.get(1)));
                    }
                }
            }
        }
        return vertexes;
    }
}
ard-vtdu/pom.xml
@@ -26,7 +26,6 @@
        <dependency>
            <groupId>com.dtflys.forest</groupId>
            <artifactId>forest-spring-boot-starter</artifactId>
            <version>1.5.33</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
ard-vtdu/src/main/java/com/ard/vtdu/client/MediamtxAuthInterceptor.java
New file
@@ -0,0 +1,54 @@
package com.ard.vtdu.client;
import com.ard.vtdu.config.MediamtxConfig;
import com.dtflys.forest.http.ForestRequest;
import com.dtflys.forest.interceptor.ForestInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Base64;
/**
 * 这个拦截器暂时先不使用,需要时在启用
 * 用于mediaMtx流媒体接口安全请求
 *
 * @author lsy
 * @date 2025/10/16
 */
@Component
@Slf4j
public class MediamtxAuthInterceptor implements ForestInterceptor {
    /**
     * 该方法在请求发送之前被调用, 若返回false则不会继续发送请求
     *
     * @Param request Forest请求对象
     */
    @Override
    public boolean beforeExecute(ForestRequest req) {
        try {
            String username = "admin";
            String password = "mypass";
            //对应mediamtx.yml
            //- user: argon2:$argon2id$v=19$m=4096,t=3,p=1$c2FsdEl0V2l0aFNhbHQ$Mi4H+feNHQk/E0sm46PEz3dnHPTzZMNWV5LVdDD/4SI
            //  pass: argon2:$argon2id$v=19$m=4096,t=3,p=1$c2FsdEl0V2l0aFNhbHQ$5VEdlOBG/er50vvc4GZO4uA3juhk/uvUzYOzTMv6RiQ
            // 拼接用户名和密码
            String authString = username + ":" + password;
            // Base64 编码
            String encodedAuth = Base64.getEncoder().encodeToString(authString.getBytes());
            // 最终的 Authorization 头
            String authorizationHeader = "Basic " + encodedAuth;
            log.debug("Authorization header: " + authorizationHeader);
            req.addHeader("Authorization", authorizationHeader);
        } catch (Exception e) {
            log.error("TokenInterceptor 处理异常:{}", e.getMessage());
            return false;
        }
        return true;
    }
}
ard-work/pom.xml
@@ -29,7 +29,6 @@
        <dependency>
            <groupId>com.dtflys.forest</groupId>
            <artifactId>forest-spring-boot-starter</artifactId>
            <version>1.8.0</version>
        </dependency>
        <!-- netty依赖 springboot2.x自动导入版本 -->
ard-work/src/main/java/com/ard/work/component/forest/TokenInterceptor.java
@@ -3,14 +3,14 @@
import com.ard.work.domain.receiver.AccessTokenReceiver;
import com.ard.work.service.IArdServerInfoService;
import com.dtflys.forest.http.ForestRequest;
import com.dtflys.forest.interceptor.Interceptor;
import com.dtflys.forest.interceptor.ForestInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@Slf4j
public class TokenInterceptor<T> implements Interceptor<T> {
public class TokenInterceptor implements ForestInterceptor {
    @Resource
    private IArdServerInfoService ardServerService;
@@ -21,11 +21,19 @@
     */
    @Override
    public boolean beforeExecute(ForestRequest req) {
        log.debug("invoke Simple beforeExecute");
        String serverId = (String) req.getVariableValue("serverId");
        AccessTokenReceiver accessToken = ardServerService.getAccessToken(serverId);
        req.addHeader("x-cid", accessToken.getCompanyId());
        req.addHeader("x-token", accessToken.getAccessToken());
        return true;  // 继续执行请求返回true
        try {
            String serverId = (String) req.getVariableValue("serverId");
            AccessTokenReceiver accessToken = ardServerService.getAccessToken(serverId);
            if (accessToken == null) {
                log.warn("AccessToken 为 null, serverId={}", serverId);
                return false; // 阻止请求继续
            }
            req.addHeader("x-cid", accessToken.getCompanyId());
            req.addHeader("x-token", accessToken.getAccessToken());
        } catch (Exception e) {
            log.error("TokenInterceptor 处理异常:{}", e.getMessage());
            return false;
        }
        return true;
    }
}
ard-work/src/main/java/com/ard/work/component/netty/TcpClientSession.java
File was deleted
ard-work/src/main/java/com/ard/work/component/netty/TcpClientUtil.java
New file
@@ -0,0 +1,42 @@
package com.ard.work.component.netty;
import com.ard.work.component.netty.command.BaseCmd;
import com.ard.work.component.netty.cache.TyTcpServerCache;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
/**
 * Description:
 * Author: lsy
 * Date: 2025年10月17日14:27
 **/
@Slf4j
public class TcpClientUtil {
    /**
     * 向指定边缘服务器发送消息
     */
    public static void sendMessage(String serverId, BaseCmd msg) {
        try {
            Channel channel = TyTcpServerCache.getChannel(serverId);
            if (channel == null) {
                log.error("未找到边缘服务器 [{}] 的连接", serverId);
                return;
            }
            if (!channel.isActive()) {
                log.warn("边缘服务器 [{}] 连接未激活,无法发送消息", serverId);
                return;
            }
            InetSocketAddress remote = (InetSocketAddress) channel.remoteAddress();
            channel.writeAndFlush(msg);
            log.warn("→ 向边缘服务器 [{}] ({}:{}) 发送消息,命令码:0x{}",
                    serverId, remote.getHostString(), remote.getPort(),
                    String.format("%02X", msg.getCmdCode() & 0xFF));
        } catch (Exception e) {
            log.error("发送消息到边缘服务器 [{}] 异常:{}", serverId, e.getMessage());
        }
    }
}
ard-work/src/main/java/com/ard/work/component/netty/TyTcpClientManager.java
@@ -1,11 +1,10 @@
package com.ard.work.component.netty;
import com.ard.work.component.netty.command.BaseCmd;
import com.ard.work.component.netty.command.register.HeartbeatCmd;
import com.ard.work.component.netty.handler.ClientHandler;
import com.ard.work.component.netty.handler.MessageProtocolDecoder;
import com.ard.work.component.netty.handler.MessageProtocolEncoder;
import com.ard.work.domain.cache.TyTcpServerCache;
import com.ard.work.component.netty.cache.TyTcpServerCache;
import com.ard.work.domain.entity.ArdServerInfo;
import com.ard.work.service.IArdServerInfoService;
import io.netty.bootstrap.Bootstrap;
@@ -18,20 +17,16 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class TyTcpClientManager {
    /**
     * 以 serverId 作为唯一 key
     */
    public static final Map<String, TcpClientSession> channels = new ConcurrentHashMap<>();
    private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
@@ -63,7 +58,9 @@
        return bootstrap;
    }
    /** 新增或重连服务器 */
    /**
     * 新增或重连服务器
     */
    public void connectServer(ArdServerInfo serverInfo) {
        String serverId = serverInfo.getId();
        String host = serverInfo.getHost();
@@ -73,66 +70,42 @@
        disconnectServer(serverId);
        Bootstrap bootstrap = getBootstrap();
        ChannelFuture future = bootstrap.connect(host, port).addListener((ChannelFuture f) -> {
        bootstrap.connect(host, port).addListener((ChannelFuture f) -> {
            if (f.isSuccess()) {
                log.info("✅ 服务器 [{}] 连接成功", serverId);
                Channel channel = f.channel();
                channel.attr(AttributeKey.valueOf("serverId")).set(serverId);
                TyTcpServerCache.putTcpServerCache(serverId, true);
                // 断开服务器,修改状态为: 0 - 在线
                ardServerService.updateServerStatus(serverId, "1");
                // 成功后再加入 channels
                TyTcpServerCache.putChannel(serverId, channel);
            } else {
                log.warn("❌ 服务器 [{}] 连接失败", serverId);
                TyTcpServerCache.putTcpServerCache(serverId, false);
                // 断开服务器,修改状态为: 0 - 离线
                ardServerService.updateServerStatus(serverId, "0");
            }
        });
        TcpClientSession session = TcpClientSession.builder()
                .ClientId("TY_" + serverId)
                .channelFuture(future)
                .isReConnect(true)
                .serverId(serverId)
                .host(host)
                .port(port)
                .build();
        channels.put(serverId, session);
    }
    /** 断开服务器连接 */
    /**
     * 断开服务器连接
     */
    public void disconnectServer(String serverId) {
        TcpClientSession session = channels.remove(serverId);
        if (session != null && session.getChannelFuture() != null) {
            session.getChannelFuture().channel().close();
        Channel channel = TyTcpServerCache.getChannel(serverId);
        if (channel != null) {
            channel.close();
            log.info("断开服务器 [{}] 连接", serverId);
            // 断开服务器,修改状态为: 0 - 离线
            ardServerService.updateServerStatus(serverId, "0");
            TyTcpServerCache.removeChannel(serverId);
        }
        TyTcpServerCache.removeTcpServerCache(serverId);
    }
    /**
     * 向指定边缘服务器发送消息
     */
    public void sendMessage(Long serverId, BaseCmd msg) {
        try {
            TcpClientSession session = channels.get(serverId);
            if (session == null) {
                log.error("未找到六合服务器 [{}] 的连接", serverId);
                return;
            }
            ChannelFuture future = session.getChannelFuture();
            if (future == null || !future.channel().isActive()) {
                log.warn("六合服务器 [{}] 连接未激活,无法发送消息", serverId);
                return;
            }
            InetSocketAddress remote = (InetSocketAddress) future.channel().remoteAddress();
            future.channel().writeAndFlush(msg);
            log.debug("→ 向六合服务器 [{}] ({}:{}) 发送消息,命令码:0x{}",
                    serverId, remote.getHostString(), remote.getPort(),
                    String.format("%02X", msg.getCmdCode() & 0xFF));
        } catch (Exception e) {
            log.error("发送消息到六合服务器 [{}] 异常:{}", serverId, e.getMessage());
        }
    }
    /**
     * 启动时从数据库加载所有边缘服务器并建立 TCP 连接
@@ -162,6 +135,7 @@
            if (Boolean.FALSE.equals(state)) { // 连接失败的服务器
                ArdServerInfo serverInfo = ardServerService.selectArdServerInfoById(key);
                if (serverInfo != null) {
                    log.warn("服务器 [{}] 连接失败,尝试重连", key);
                    connectServer(serverInfo);
                }
            }
@@ -174,19 +148,14 @@
    @Scheduled(fixedRate = 5000) // 每5秒执行一次
    public void sendHeartBeat() {
        long timestamp = System.currentTimeMillis();
        channels.values().stream()
                .filter(session -> {
                    ChannelFuture future = session.getChannelFuture();
                    // 只保留活跃连接
                    return future != null && future.channel().isActive();
                })
                .forEach(session -> {
        TyTcpServerCache.getChannels().stream()
                // 过滤出仍然活跃的连接
                .filter(channel -> channel != null && channel.isActive())
                .forEach(channel -> {
                    String serverId = (String) channel.attr(AttributeKey.valueOf("serverId")).get();
                    HeartbeatCmd heartbeat = new HeartbeatCmd(timestamp);
                    ChannelFuture future = session.getChannelFuture();
                    future.channel().writeAndFlush(heartbeat);
                    log.info("→ 向边缘服务器 [{}] ({}:{}) 发送心跳,时间戳={}",
                            session.getServerId(), session.getHost(), session.getPort(), timestamp);
                    channel.writeAndFlush(heartbeat);
                    log.info("→ 向边缘服务器 [{}]发送心跳,时间戳={}", serverId, timestamp);
                });
    }
}
ard-work/src/main/java/com/ard/work/component/netty/cache/TyTcpServerCache.java
New file
@@ -0,0 +1,68 @@
package com.ard.work.component.netty.cache;
import io.netty.channel.Channel;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 六合tcp服务器缓存
 *
 * @author liusuyi
 * @date 2024年11月01日13:53
 **/
public class TyTcpServerCache {
    /**
     * 以 serverId 作为唯一 key 存储 Channel
     */
    private static Map<String, Channel> channels = new ConcurrentHashMap<>();
    //存储通道
    public static void putChannel(String serverId, Channel channel) {
        channels.put(serverId, channel);
    }
    //获取通道
    public static Channel getChannel(String serverId) {
        return channels.get(serverId);
    }
    //删除通道
    public static void removeChannel(String serverId) {
        channels.remove(serverId);
    }
    //获取所有
    public static Collection<Channel> getChannels() {
        return Collections.unmodifiableCollection(channels.values());
    }
    /**
     * 以 serverId 作为唯一 key 存储 连接状态
     */
    private static final ConcurrentHashMap<String, Boolean> tcpServerCache = new ConcurrentHashMap<>();
    // 存储TCP服务连接状态到缓存
    public static void putTcpServerCache(String serverId, Boolean state) {
        tcpServerCache.put(serverId, state);
    }
    // 从缓存中获取TCP服务连接状态
    public static Boolean getTcpServerCache(String serverId) {
        return tcpServerCache.getOrDefault(serverId, false);
    }
    // 删除缓存中的TCP服务连接状态
    public static void removeTcpServerCache(String serverId) {
        tcpServerCache.remove(serverId);
    }
    public static Map<String, Boolean> getTcpServerCacheMap() {
        return new HashMap<>(tcpServerCache); // 返回一个副本,避免外部修改原始缓存
    }
}
ard-work/src/main/java/com/ard/work/component/netty/handler/ClientHandler.java
@@ -1,22 +1,19 @@
package com.ard.work.component.netty.handler;
import com.ard.work.component.netty.TyTcpClientManager;
import com.ard.work.component.netty.command.register.HeartbeatCmd;
import com.ard.work.domain.cache.TyTcpServerCache;
import com.ard.work.component.netty.command.BaseCmd;
import com.ard.work.component.netty.command.register.HeartbeatCmd;
import com.ard.work.component.netty.cache.TyTcpServerCache;
import com.ard.work.service.ITyService;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
@Component
@@ -26,9 +23,6 @@
    @Resource
    private ITyService tyService;
    @Resource
    private TyTcpClientManager tyTcpClientManager;
    @Override
    public void channelRead0(ChannelHandlerContext ctx, BaseCmd command) {
ard-work/src/main/java/com/ard/work/domain/cache/TyTcpServerCache.java
File was deleted
ard-work/src/main/java/com/ard/work/domain/entity/ArdDroneInfo.java
@@ -3,7 +3,7 @@
import com.ard.common.annotation.Excel;
import com.ard.common.core.domain.BaseEntity;
import com.ard.work.domain.receiver.BoxInfoReceiver;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import java.util.Date;
@@ -39,6 +39,7 @@
    private String boxVersion;
    /** 启动时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "启动时间")
    private Date startTime;
@@ -70,6 +71,10 @@
    @Excel(name = "服务器管理ID")
    private String serverId;
    /** 盒子别名 */
    @Excel(name = "盒子别名")
    private String boxAliasName;
    /**
     * 服务器实体对象
     */
ard-work/src/main/java/com/ard/work/domain/entity/ArdServerInfo.java
@@ -52,8 +52,12 @@
    /** 部门ID */
    private Long deptId;
    /** 服务器状态 */
    private Long serverStatus;
    /**
     * 服务器状态
     * 0:离线
     * 1:在线
     */
    private String serverStatus;
    private List<ArdDroneInfo> ardDroneInfoList;
ard-work/src/main/java/com/ard/work/mapper/ArdServerInfoMapper.java
@@ -1,5 +1,6 @@
package com.ard.work.mapper;
import com.ard.work.domain.entity.ArdServerInfo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -65,6 +66,6 @@
     * @param id 服务器管理主键
     * @return 结果
     */
    public int updateServerStatus(String id);
    public int updateServerStatus(@Param("id") String id, @Param("status") String status);
}
ard-work/src/main/java/com/ard/work/service/IArdDroneInfoService.java
@@ -73,9 +73,10 @@
    /**
     * 批量新增无人机盒子
     *
     * @param ids 无人机盒子主键集合
     * @param boxSn 盒子编码
     * @param deptId 部门ID
     * @return 结果
     */
    public int insertArdDroneLs(String[] ids);
    public int insertArdDroneLs(String[] boxSn,Long deptId);
}
ard-work/src/main/java/com/ard/work/service/IArdServerInfoService.java
@@ -68,6 +68,6 @@
    /**
     * 修改服务器状态
     */
    public int updateServerStatus(String id);
    public int updateServerStatus(String id,String status);
}
ard-work/src/main/java/com/ard/work/service/ITyService.java
@@ -38,12 +38,19 @@
    /**
     * 获取云盒列表
     * 获取全部服务器云盒列表
     * @return
     */
    List<BoxInfoReceiver> getBoxList();
    /**
     * 获取指定服务器云盒列表
     *
     * @return List<BoxInfoReceiver>
     */
    List<BoxInfoReceiver> getBoxList(String serverId);
    /**
     * 获取直播地址
     * @return
     */
ard-work/src/main/java/com/ard/work/service/impl/ArdDroneInfoServiceImpl.java
@@ -1,6 +1,7 @@
package com.ard.work.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.ard.common.exception.ServiceException;
import com.ard.common.utils.DateUtils;
import com.ard.common.utils.uuid.IdUtils;
import com.ard.system.authorize.service.AuthorizeService;
@@ -10,14 +11,12 @@
import com.ard.work.service.IArdDroneInfoService;
import com.ard.work.service.ITyService;
import com.ard.work.util.UtilMethod;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -26,6 +25,7 @@
 * @author ruoyi
 * @date 2025-03-04
 */
@Slf4j
@Service
public class ArdDroneInfoServiceImpl implements IArdDroneInfoService {
@@ -34,6 +34,9 @@
    @Resource
    private ITyService tyService;
    @Resource
    private AuthorizeService authorizeService;
    UtilMethod utilMethod = new UtilMethod();
@@ -120,24 +123,123 @@
    /**
     * 批量新增无人机盒子
     *
     * @param ids 无人机盒子主键集合
     * @return 结果
     * @param boxSn 盒子编码数组
     * @param deptId 部门ID
     * @return 成功插入的记录数
     */
    @Override
    public int insertArdDroneLs(String[] ids) {
        // 获取无人机盒子信息
        List<ArdDroneInfo> drone = new ArrayList<>();
        for (String id : ids) {
            ArdDroneInfo ardDrone = ArdDroneInfoMapper.selectArdDroneById(id);
            drone.add(ardDrone);
    public int insertArdDroneLs(String[] boxSn, Long deptId) {
        // 参数校验
        if (boxSn == null || boxSn.length == 0) {
            log.warn("传入的盒子编码为空");
            return 1;
        }
        // 检验数量droneNum是否合规。
        utilMethod.authorizationVerification(drone.size());
        // 插入
        if (!drone.isEmpty()) {
            return ArdDroneInfoMapper.insertArdDroneLs(drone);
        if (deptId == null) {
            throw new IllegalArgumentException("部门ID不能为空");
        }
        return 0;
        try {
            // 1. 检查已存在的无人机盒子
            List<ArdDroneInfo> existingDrones = getExistingDrones(boxSn);
            // 2. 授权数量验证
            validateAuthorization(existingDrones.size(), boxSn.length);
            // 3. 获取并组装新的无人机信息
            List<ArdDroneInfo> newDrones = assembleNewDrones(boxSn, deptId);
            // 4. 批量插入
            return batchInsertDrones(newDrones);
        } catch (Exception e) {
            log.error("批量新增无人机盒子失败, boxSn: {}, deptId: {}", Arrays.toString(boxSn), deptId, e);
            throw new ServiceException("批量新增无人机盒子失败: " + e.getMessage());
        }
    }
    /**
     * 获取已存在的无人机信息
     */
    private List<ArdDroneInfo> getExistingDrones(String[] boxSn) {
        return Arrays.stream(boxSn)
                .map(ArdDroneInfoMapper::selectArdDroneByBoxSn)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
    /**
     * 验证授权数量
     */
    private void validateAuthorization(int existingCount, int newCount) {
        int totalCount = existingCount + newCount;
        JSONObject authInfo = authorizeService.getAuthorizationInfo();
        String authType = authInfo.getString("Type");
        switch (authType) {
            case "normal":
                int allowedDroneNum = authInfo.getIntValue("droneNum");
                if (totalCount > allowedDroneNum) {
                    throw new RuntimeException(String.format(
                            "授权数量%d,当前操作将导致总数达到%d,超过授权限制",
                            allowedDroneNum, totalCount));
                }
                break;
            case "expired":
                throw new RuntimeException("授权文件已过期");
            case "mismatch":
                throw new RuntimeException("授权文件不匹配");
            default:
                throw new RuntimeException("未知的授权类型: " + authType);
        }
    }
    /**
     * 组装新的无人机信息
     */
    private List<ArdDroneInfo> assembleNewDrones(String[] boxSn, Long deptId) {
        List<BoxInfoReceiver> allBoxes = tyService.getBoxList();
        Set<String> targetBoxSnSet = new HashSet<>(Arrays.asList(boxSn));
        return allBoxes.stream()
                .filter(box -> targetBoxSnSet.contains(box.getBoxSn()))
                .map(box -> createDroneInfo(box, deptId))
                .collect(Collectors.toList());
    }
    /**
     * 创建无人机信息对象
     */
    private ArdDroneInfo createDroneInfo(BoxInfoReceiver box, Long deptId) {
        ArdDroneInfo drone = new ArdDroneInfo();
        drone.setId(IdUtils.simpleUUID());
        drone.setBoxSn(box.getBoxSn());
        drone.setBoxName(box.getBoxName());
        drone.setBoxAliasName(box.getBoxName());
        drone.setStartTime(new Date());
        drone.setOnLine(box.getOnLine());
        drone.setVideoServer(box.getVideoServer());
        drone.setVideoPort(box.getVideoPort());
        drone.setLastTaskId(box.getLastTaskId());
        drone.setDeptId(deptId);
        // 设置创建信息和更新信息
        setCreateAndUpdateInfo(drone);
        return drone;
    }
    /**
     * 设置创建和更新信息
     */
    private void setCreateAndUpdateInfo(ArdDroneInfo drone) {
        Date currentTime = new Date();
        drone.setCreateTime(currentTime);
        drone.setUpdateTime(currentTime);
    }
    /**
     * 批量插入无人机信息
     */
    private int batchInsertDrones(List<ArdDroneInfo> drones) {
        if (drones.isEmpty()) {
            return 0;
        }
        return ArdDroneInfoMapper.insertArdDroneLs(drones);
    }
    /**
ard-work/src/main/java/com/ard/work/service/impl/ArdServerInfoServiceImpl.java
@@ -144,8 +144,8 @@
     * 修改服务器状态
     */
    @Override
    public int updateServerStatus(String id) {
        return ardServerInfoMapper.updateServerStatus(id);
    public int updateServerStatus(String id,String status) {
        return ardServerInfoMapper.updateServerStatus(id,status);
    }
    private String getEncryptStr(ArdServerInfo serverInfo, long timeStamp) {
ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java
@@ -1,66 +1,61 @@
package com.ard.work.service.impl;
import com.ard.common.utils.ByteUtils;
import com.ard.work.aspect.annotation.CheckTakeoffAltitude;
import com.ard.work.aspect.annotation.CheckControlRight;
import com.ard.work.aspect.annotation.CheckTakeoffAltitude;
import com.ard.work.component.forest.TyClient;
import com.ard.work.component.netty.TcpClientUtil;
import com.ard.work.component.netty.command.BaseCmd;
import com.ard.work.component.netty.command.camera.*;
import com.ard.work.component.netty.command.manual.*;
import com.ard.work.component.netty.command.register.*;
import com.ard.work.component.netty.command.settings.*;
import com.ard.work.component.netty.command.speaker.*;
import com.ard.work.component.netty.command.wayline.*;
import com.ard.work.domain.dto.camera.*;
import com.ard.work.domain.dto.manual.*;
import com.ard.work.domain.dto.speaker.*;
import com.ard.work.domain.dto.wayline.*;
import com.ard.work.domain.entity.ArdDroneInfo;
import com.ard.work.domain.entity.ArdServerInfo;
import com.ard.work.domain.receiver.*;
import com.ard.work.component.forest.TyClient;
import com.ard.work.domain.cache.UavSignalInfoCache;
import com.ard.work.domain.cache.UavStateCache;
import com.ard.work.domain.cache.UavTelemetryDataCache;
import com.ard.work.domain.dto.BoxHistoryDTO;
import com.ard.work.domain.dto.BoxPhotosDTO;
import com.ard.work.domain.dto.BoxSettingsDTO;
import com.ard.work.domain.dto.control.AbsAngleControlDTO;
import com.ard.work.domain.dto.control.AngleControlDTO;
import com.ard.work.domain.dto.control.SpeedControlDTO;
import com.ard.work.domain.dto.converter.wayline.PlanLineDataConverter;
import com.ard.work.domain.dto.settings.ReturnHeightSettingDTO;
import com.ard.work.domain.dto.settings.ReturnPointSettingDTO;
import com.ard.work.domain.dto.settings.VideoStreamSettingDTO;
import com.ard.work.domain.dto.signal.SignalInfo;
import com.ard.work.domain.dto.state.UavState;
import com.ard.work.domain.dto.telemetry.TelemetryData;
import com.ard.work.component.netty.command.control.AbsAngleControlCmd;
import com.ard.work.component.netty.command.control.AngleControlCmd;
import com.ard.work.component.netty.command.control.SpeedControlCmd;
import com.ard.work.component.netty.command.manual.*;
import com.ard.work.component.netty.command.notice.BoxPowerStatusReplyCmd;
import com.ard.work.component.netty.command.notice.FlyWayEndReplyCmd;
import com.ard.work.component.netty.command.notice.UavReadyReplyCmd;
import com.ard.work.component.netty.command.real.SignalInfoCmd;
import com.ard.work.component.netty.command.real.UavStateCmd;
import com.ard.work.component.netty.command.real.UavTelemetryDataCmd;
import com.ard.work.component.netty.command.register.*;
import com.ard.work.component.netty.command.settings.*;
import com.ard.work.component.netty.command.speaker.*;
import com.ard.work.component.netty.command.wayline.*;
import com.ard.work.component.netty.handler.ErrorCodeEnum;
import com.ard.work.component.netty.protobuf.TelemetryDataPB;
import com.ard.work.domain.cache.UavSignalInfoCache;
import com.ard.work.domain.cache.UavStateCache;
import com.ard.work.domain.cache.UavTelemetryDataCache;
import com.ard.work.domain.dto.BoxHistoryDTO;
import com.ard.work.domain.dto.BoxPhotosDTO;
import com.ard.work.domain.dto.BoxSettingsDTO;
import com.ard.work.domain.dto.camera.*;
import com.ard.work.domain.dto.control.AbsAngleControlDTO;
import com.ard.work.domain.dto.control.AngleControlDTO;
import com.ard.work.domain.dto.control.SpeedControlDTO;
import com.ard.work.domain.dto.converter.wayline.PlanLineDataConverter;
import com.ard.work.domain.dto.manual.*;
import com.ard.work.domain.dto.settings.ReturnHeightSettingDTO;
import com.ard.work.domain.dto.settings.ReturnPointSettingDTO;
import com.ard.work.domain.dto.settings.VideoStreamSettingDTO;
import com.ard.work.domain.dto.signal.SignalInfo;
import com.ard.work.domain.dto.speaker.*;
import com.ard.work.domain.dto.state.UavState;
import com.ard.work.domain.dto.telemetry.TelemetryData;
import com.ard.work.domain.dto.wayline.*;
import com.ard.work.domain.entity.ArdDroneInfo;
import com.ard.work.domain.entity.ArdServerInfo;
import com.ard.work.domain.message.UavControlType;
import com.ard.work.domain.receiver.*;
import com.ard.work.service.IArdDroneInfoService;
import com.ard.work.service.IArdServerInfoService;
import com.ard.work.service.ITyService;
import com.ard.work.domain.message.UavControlType;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.ard.work.component.netty.TyTcpClientManager.channels;
/**
 * TyServiceImpl
@@ -89,7 +84,7 @@
    }
    /**
     * 获取云盒列表
     * 获取全部云盒列表
     *
     * @return List<BoxInfoReceiver>
     */
@@ -98,7 +93,16 @@
        List<ArdServerInfo> ardServerInfos = ardServerService.selectArdServerInfoList(new ArdServerInfo());
        return getBoxList(ardServerInfos);
    }
    /**
     * 获取指定服务器云盒列表
     *
     * @return List<BoxInfoReceiver>
     */
    @Override
    public List<BoxInfoReceiver> getBoxList(String serverId) {
        TyHttpCommonReceiver<List<BoxInfoReceiver>> boxList = tyClient.getBoxList(serverId);
        return boxList.getContent();
    }
    /**
     * 获取直播地址
     *
@@ -873,6 +877,7 @@
    private List<BoxInfoReceiver> getBoxList(List<ArdServerInfo> ls) {
        return ls.stream()
                .filter(ardServerInfo -> ardServerInfo.getServerStatus().equals("1"))
                .flatMap(ardServerInfo -> {
                    try {
                        List<BoxInfoReceiver> boxes = tyClient.getBoxList(ardServerInfo.getId()).getContent();
@@ -899,25 +904,12 @@
                ErrorCodeEnum.getDescByCode(errorCode));
    }
    /**
     * 发送消息
     */
    private void sendMessage(String key, BaseCmd msg) {
        try {
            ChannelFuture future = channels.get(key).getChannelFuture();
            Channel channel = future.channel();
            if (channel.isActive()) {
                InetSocketAddress ipSocket = (InetSocketAddress) channel.remoteAddress();
                String host = ipSocket.getHostString();
                int port = ipSocket.getPort();
                channel.writeAndFlush(msg); // 发送编码后的数据
                log.debug("向六合服务器【{}:{}】发送数据,命令码:{}", host, port, String.format("0x%02X", msg.getCmdCode() & 0xFF));
            } else {
                log.error("与六合服务器链接断开,无法发送数据!");
            }
        } catch (Exception e) {
            log.error("发送消息异常:{}", e.getMessage());
        }
        TcpClientUtil.sendMessage(key, msg);
    }
}
ard-work/src/main/java/com/ard/work/util/UtilMethod.java
@@ -2,6 +2,9 @@
import com.alibaba.fastjson2.JSONObject;
import com.ard.system.authorize.service.AuthorizeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -10,25 +13,35 @@
 * @date: 2025/3/4
 * @description:
 */
@Service
public class UtilMethod {
    @Resource
    @Autowired
    private AuthorizeService authorizeService;
    /**
     * 根据授权数量,验证授权信息是否合规
     */
    public void authorizationVerification(int ls){
        JSONObject data = authorizeService.getAuthorizationInfo();
        if ("normal".equals(data.get("Type"))){
            int droneNum = (int) data.get("droneNum");
            if (ls > droneNum){
                throw new RuntimeException("添加无人机数量超过当前授权数量!!!");
            }
        }else if ("expired".equals(data.get("Type"))){
            throw new RuntimeException("授权文件过期!!!");
        }else if ("mismatch".equals(data.get("Type"))){
            throw new RuntimeException("授权文件不匹配!!!");
        JSONObject authInfo = authorizeService.getAuthorizationInfo();
        String authType = authInfo.getString("Type");
        switch (authType) {
            case "normal":
                int allowedDroneNum = authInfo.getIntValue("droneNum");
                if (ls > allowedDroneNum) {
                    throw new RuntimeException(String.format(
                            "授权数量%d,当前操作将导致总数达到%d,超过授权限制",
                            allowedDroneNum, ls));
                }
                break;
            case "expired":
                throw new RuntimeException("授权文件已过期");
            case "mismatch":
                throw new RuntimeException("授权文件不匹配");
            default:
                throw new RuntimeException("未知的授权类型: " + authType);
        }
    }
ard-work/src/main/resources/mapper/ArdDroneInfoMapper.xml
@@ -16,6 +16,7 @@
        <result property="lastTaskId"    column="last_task_id"    />
        <result property="userId"    column="user_id"    />
        <result property="deptId"    column="dept_id"    />
        <result property="boxAliasName"    column="box_alias_name"    />
        <result property="createBy"    column="create_by"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateBy"    column="update_by"    />
@@ -37,24 +38,25 @@
    <sql id="selectArdDroneVo">
        select id,
            box_sn,
            box_name,
            box_version,
            start_time,
            on_line,
            video_server,
            video_port,
            last_task_id,
            server_id,
            user_id,
            dept_id
               box_sn,
               box_name,
               box_version,
               start_time,
               on_line,
               video_server,
               video_port,
               last_task_id,
               server_id,
               user_id,
               dept_id,
               box_alias_name
        from ard_drone_info
    </sql>
    <select id="selectArdDroneList" parameterType="ArdDroneInfo" resultMap="ArdDroneInfoResult">
        <include refid="selectArdDroneVo"/>
        <where>
            <if test="boxSn != null  and boxSn != ''"> and box_sn = #{boxSn}</if>
        <where>
            <if test="boxSn != null  and boxSn != ''"> and box_sn like concat('%', #{boxSn}, '%')</if>
            <if test="boxName != null  and boxName != ''"> and box_name like concat('%', #{boxName}, '%')</if>
            <if test="boxVersion != null  and boxVersion != ''"> and box_version = #{boxVersion}</if>
            <if test="onLine != null "> and onLine = #{onLine}</if>
@@ -84,8 +86,10 @@
            <if test="videoServer != null">video_server,</if>
            <if test="videoPort != null">video_port,</if>
            <if test="lastTaskId != null">last_task_id,</if>
            <if test="serverId != null">server_id,</if>
            <if test="userId != null">user_id,</if>
            <if test="deptId != null">dept_id,</if>
            <if test="boxAliasName != null">box_alias_name,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
@@ -102,8 +106,10 @@
            <if test="videoServer != null">#{videoServer},</if>
            <if test="videoPort != null">#{videoPort},</if>
            <if test="lastTaskId != null">#{lastTaskId},</if>
            <if test="serverId != null">#{serverId},</if>
            <if test="userId != null">#{userId},</if>
            <if test="deptId != null">#{deptId},</if>
            <if test="boxAliasName != null">#{boxAliasName},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
@@ -123,8 +129,10 @@
            <if test="videoServer != null">video_server = #{videoServer},</if>
            <if test="videoPort != null">video_port = #{videoPort},</if>
            <if test="lastTaskId != null">last_task_id = #{lastTaskId},</if>
            <if test="serverId != null">server_id = #{serverId},</if>
            <if test="userId != null">user_id = #{userId},</if>
            <if test="deptId != null">dept_id = #{deptId},</if>
            <if test="boxAliasName != null">box_alias_name = #{boxAliasName},</if>
            <if test="createBy != null">create_by = #{createBy},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
@@ -145,7 +153,7 @@
        </foreach>
    </delete>
    <insert id="insertArdDroneLs">
    <insert id="insertArdDroneLs" parameterType="java.util.List">
        insert into ard_drone_info(
            id,
            box_sn,
@@ -159,6 +167,7 @@
            server_id,
            user_id,
            dept_id,
            box_alias_name,
            create_by,
            create_time,
            update_by,
@@ -167,19 +176,22 @@
        ) values
        <foreach item="item" index="index" collection="list" separator=",">
            (#{item.id},
            #{item.boxSn}),
            #{item.boxName}),
            #{item.boxVersion}),
            #{item.startTime}),
            #{item.videoServer}),
            #{item.videoPort}),
            #{item.lastTaskId}),
            #{item.userId}),
            #{item.deptId}),
            #{item.createBy}),
            #{item.createTime}),
            #{item.updateBy}),
            #{item.updateTime}),
            #{item.boxSn},
            #{item.boxName},
            #{item.boxVersion},
            #{item.startTime},
            #{item.onLine},
            #{item.videoServer},
            #{item.videoPort},
            #{item.lastTaskId},
            #{item.serverId},
            #{item.userId},
            #{item.deptId},
            #{item.boxAliasName},
            #{item.createBy},
            #{item.createTime},
            #{item.updateBy},
            #{item.updateTime},
            #{item.remark})
        </foreach>
    </insert>
ard-work/src/main/resources/mapper/ArdServerInfoMapper.xml
@@ -43,7 +43,7 @@
        <include refid="selectArdServerInfoVo"/>
        <where>
            <if test="id != null and id != ''"> and c.id = #{id}</if>
            <if test="serverName != null and serverName != ''"> and server_name like c.concat('%', #{serverName}, '%')</if>
            <if test="serverName != null and serverName != ''"> and c.server_name like concat('%', #{serverName}, '%')</if>
            <if test="accessKeyId != null and accessKeyId != ''"> and c.access_key_id = #{accessKeyId}</if>
            <if test="accessKeySecret != null and accessKeySecret != ''"> and c.access_key_secret = #{accessKeySecret}</if>
            <if test="serverStatus != null and serverStatus != ''"> and c.server_status = #{serverStatus}</if>
@@ -113,10 +113,10 @@
        where id = #{id}
    </update>
    <update id="updateServerStatus" parameterType="String">
    <update id="updateServerStatus">
        update ard_server_info
        <trim prefix="SET" suffixOverrides=",">
            <if test="serverStatus != null">server_status = #{serverStatus},</if>
            <if test="status != null">server_status = #{status},</if>
        </trim>
        where id = #{id}
    </update>
pom.xml
@@ -34,6 +34,7 @@
        <okhttp.version>4.8.1</okhttp.version>
        <mqtt.version>5.5.5</mqtt.version>
        <minio.version>8.3.7</minio.version>
        <forest.version>1.8.0</forest.version>
        <spring-boot-admin.version>2.5.1</spring-boot-admin.version>
    </properties>
@@ -240,6 +241,13 @@
                <version>${minio.version}</version>
            </dependency>
            <!-- Forest -->
            <dependency>
                <groupId>com.dtflys.forest</groupId>
                <artifactId>forest-spring-boot-starter</artifactId>
                <version>${forest.version}</version>
            </dependency>
            <!-- SpringBootAdmin管控台 -->
            <dependency>
                <groupId>de.codecentric</groupId>