zhaowenyu
2025-11-25 aae8b5dc994fffdc211b1f0fc860840cb0d4687a
Merge remote-tracking branch 'origin/dev' into dev
5 files added
11 files modified
496 ■■■■■ changed files
ard-admin/src/main/java/com/ard/web/controller/work/UavManualFlyController.java 19 ●●●●● patch | view | raw | blame | history
ard-route/src/main/java/com/ard/route/domain/route/ArdRouteInfo.java 3 ●●●●● patch | view | raw | blame | history
ard-route/src/main/java/com/ard/route/service/impl/ArdRouteInfoServiceImpl.java 22 ●●●●● patch | view | raw | blame | history
ard-route/src/main/java/com/ard/route/service/impl/UavRouteServiceImpl.java 63 ●●●●● patch | view | raw | blame | history
ard-route/src/main/java/com/ard/route/util/ToolUtils.java 4 ●●●●● patch | view | raw | blame | history
ard-route/src/main/resources/mapper/ArdRouteInfoMapper.xml 9 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/command/manual/FlyRoundControlCancelCmd.java 33 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/command/manual/FlyRoundControlCmd.java 50 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/command/manual/FlyRoundReplyCmd.java 35 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/netty/handler/MessageProtocolDecoder.java 2 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/component/websocket/handler/TyMessageHandler.java 13 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/domain/dto/manual/FlyRoundControlCancelDTO.java 22 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/domain/dto/manual/FlyRoundControlDTO.java 51 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/ITyService.java 11 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java 122 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java 37 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/work/UavManualFlyController.java
@@ -108,6 +108,25 @@
        return AjaxResult.success();
    }
    @ApiOperation(value = "开始环绕飞行", notes = "速度speed:0~20 单位m/s " +
            "环绕半径radius:单位m,"+
            "飞行模式flyMode:0-节能模式【飞机直线飞到目标点,需飞机在空中】," +
            "1-安全模式【飞机飞行至目标点高度再平飞到目标点,飞机可以在地面或空中】 默认为节能模式")
    @ApiOperationSupport(order = 9)
    @PostMapping("/flyRoundControl")
    public AjaxResult flyRoundControl(@RequestBody FlyRoundControlDTO flyRoundControlDTO) {
        tyService.FlyRoundControl(flyRoundControlDTO);
        return AjaxResult.success();
    }
    @ApiOperation("停止环绕飞行")
    @ApiOperationSupport(order = 10)
    @PostMapping("/flyRoundControlCancel")
    public AjaxResult flyRoundControlCancel(@RequestBody FlyRoundControlCancelDTO flyRoundControlCancelDTO) {
        tyService.FlyRoundControlCancel(flyRoundControlCancelDTO);
        return AjaxResult.success();
    }
    @ApiOperation(value = "紧急制动", notes = "动作参数actionParam: 0-解除紧急制动 1-紧急制动")
    @ApiOperationSupport(order = 11)
    @PostMapping("/emeragencyBrake")
ard-route/src/main/java/com/ard/route/domain/route/ArdRouteInfo.java
@@ -64,4 +64,7 @@
    /** 航线数据 */
    private byte[] routeData;
    /** 行点总数 */
    private Integer pointCount;
}
ard-route/src/main/java/com/ard/route/service/impl/ArdRouteInfoServiceImpl.java
@@ -21,6 +21,7 @@
import com.ard.route.domain.dto.ArdNoflyDto;
import com.ard.route.domain.route.ArdRouteInfo;
import com.ard.route.domain.sixhe.ArdRouteChange;
import com.ard.route.domain.vo.KmzInfoVo;
import com.ard.route.domain.vo.KmzResultVo;
import com.ard.route.mapper.ArdRouteInfoMapper;
import com.ard.route.service.IArdRouteInfoService;
@@ -121,6 +122,7 @@
        KmzResultVo kmzResult = routeService.buildKmz(ardRouteInfo.getUavRouteReq(), ardRouteInfo.getName());
        ardRouteInfo.setPath(kmzResult.getPath());
        ardRouteInfo.setReadOnly("0");
        ardRouteInfo.setPointCount(getPointCount(ardRouteInfo));
        ardRouteInfoMapper.insertArdRouteInfo(ardRouteInfo);
        return kmzResult.getPath();
    }
@@ -149,7 +151,15 @@
            }
            ardRouteInfo.setPath(kmzResult.getPath());
        }
        ardRouteInfo.setPointCount(getPointCount(ardRouteInfo));
        return ardRouteInfoMapper.updateArdRouteInfo(ardRouteInfo);
    }
    private Integer getPointCount(ArdRouteInfo ardRouteInfo) {
        if (ardRouteInfo.getUavRouteReq().getRoutePointList() != null) {
            return ardRouteInfo.getUavRouteReq().getRoutePointList().size();
        }
        return 0;
    }
    /**
@@ -194,6 +204,7 @@
            information.setPath(url);
            information.setGroupId("0");     // 默认分组
            information.setReadOnly("1");   // 默认只读
            // 增加航线实体类信息
            insertRouteInfomation(information);
            return MinioUploadResult.builder()
                    .url(url)
@@ -331,9 +342,10 @@
        if (autoFlightSpeed != null){
            planLineData.setAutoSpeed(8);
        }
        if (globalTransitionalSpeed != null){
            planLineData.setMaxSpeed(15);
        }
        planLineData.setMaxSpeed(15);
//        if (globalTransitionalSpeed != null){
//            planLineData.setMaxSpeed(15);
//        }
        if (exitOnRcLostAction != null){
            planLineData.setFinishedAction(1);
        }
@@ -359,11 +371,11 @@
            if (height != null){
                pointData.setHeight(height.floatValue());
            }
            Double longitude = routePointReq.getLatitude();
            Double longitude = routePointReq.getLongitude();
            if (longitude != null){
                pointData.setLng(longitude.floatValue());
            }
            Double latitude = routePointReq.getLongitude();
            Double latitude = routePointReq.getLatitude();
            if (latitude != null){
                pointData.setLat(latitude.floatValue());
            }
ard-route/src/main/java/com/ard/route/service/impl/UavRouteServiceImpl.java
@@ -178,54 +178,31 @@
        String kml = XML_HEADER + xStream.toXML(kmlInfo);
        String wpml = XML_HEADER + xStream.toXML(wpmlInfo);
        KmzResultVo result = new KmzResultVo();
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
            zipOutputStream.setLevel(0);
        // 判断本地生成kmz文件
        if (isLocal) {
            try (FileOutputStream fileOutputStream = new FileOutputStream(localFilePath + fileName + ".kmz");
                 ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream)) {
                zipOutputStream.setLevel(0); // 0 表示不压缩,存储方式
            // 创建 wpmz 目录中的 template.kml 文件条目
            buildZipFile("wpmz/template.kml", zipOutputStream, kml);
            // 创建 wpmz 目录中的 waylines.wpml 文件条目
            buildZipFile("wpmz/waylines.wpml", zipOutputStream, wpml);
                // 创建KMZ文件内容
                buildZipFile("wpmz/template.kml", zipOutputStream, kml);
                buildZipFile("wpmz/waylines.wpml", zipOutputStream, wpml);
            zipOutputStream.finish();
            zipOutputStream.flush();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            result.setPath(localFilePath + fileName + ".kmz");
            // 创建MultipartFile对象
            MultipartFile multipartFile = new MockMultipartFile(
                    fileName + ".kmz",
                    fileName + ".kmz",
                    "application/vnd.google-earth.kmz",
                    new ByteArrayInputStream(baos.toByteArray())
            );
            String kmzFilePath = ossService.upload(kmzPath,multipartFile);
            result.setPath(kmzFilePath);
            return result;
        }else {
            try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                 ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
                zipOutputStream.setLevel(0);
                // 创建 wpmz 目录中的 template.kml 文件条目
                buildZipFile("wpmz/template.kml", zipOutputStream, kml);
                // 创建 wpmz 目录中的 waylines.wpml 文件条目
                buildZipFile("wpmz/waylines.wpml", zipOutputStream, wpml);
                zipOutputStream.finish();
                zipOutputStream.flush();
                // 获取文件大小
                Double fileSize = (double) baos.size(); // 字节数
                // 创建MultipartFile对象
                MultipartFile multipartFile = new MockMultipartFile(
                        fileName + ".kmz",
                        fileName + ".kmz",
                        "application/vnd.google-earth.kmz",
                        new ByteArrayInputStream(baos.toByteArray())
                );
                String kmzFilePath = ossService.upload(kmzPath,multipartFile);
                result.setPath(kmzFilePath);
                return result;
            } catch (Exception e) {
                throw new RuntimeException("KMZ文件生成或上传失败", e);
            }
        } catch (Exception e) {
            throw new RuntimeException("KMZ文件生成或上传失败", e);
        }
    }
ard-route/src/main/java/com/ard/route/util/ToolUtils.java
@@ -9,10 +9,6 @@
 */
public class ToolUtils {
    public static Boolean isLocal = false;
    public static String localFilePath = "KmzFile/kmz/";
    public static String kmzPath = "kmz";
    /**
ard-route/src/main/resources/mapper/ArdRouteInfoMapper.xml
@@ -18,6 +18,7 @@
        <result property="createTime"    column="create_time"    />
        <result property="updateBy"    column="update_by"    />
        <result property="updateTime"    column="update_time"    />
        <result property="pointCount"    column="point_count"    />
    </resultMap>
    <sql id="selectArdRouteInfoVo">
@@ -33,7 +34,8 @@
               create_by,
               create_time,
               update_by,
               update_time
               update_time,
               point_count
        from ard_route_info
    </sql>
@@ -74,6 +76,7 @@
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="pointCount != null">point_count,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="id != null">#{id},</if>
@@ -89,6 +92,7 @@
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="pointCount != null">#{pointCount},</if>
         </trim>
    </insert>
@@ -107,6 +111,7 @@
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="pointCount != null">point_count = #{pointCount},</if>
        </trim>
        where id = #{id}
    </update>
@@ -135,7 +140,7 @@
    </delete>
    <select id="selectArdRouteInfoByGroupId" parameterType="String" resultMap="ArdRouteInfoResult">
        select id,name,path,group_id,type,aircraft,payload,read_only,create_by,create_time,update_by,update_time from ard_route_info
        select id,name,path,group_id,type,aircraft,payload,read_only,create_by,create_time,update_by,update_time,point_count from ard_route_info
        <where>
            <if test="groupId != null "> and group_id = #{groupId}</if>
        </where>
ard-work/src/main/java/com/ard/work/component/netty/command/manual/FlyRoundControlCancelCmd.java
New file
@@ -0,0 +1,33 @@
package com.ard.work.component.netty.command.manual;
import com.ard.work.component.netty.command.BaseCmd;
import com.ard.work.domain.dto.manual.FlyRoundControlCancelDTO;
import com.ard.work.domain.dto.manual.FlyRoundControlDTO;
import io.netty.buffer.ByteBuf;
/**
 * 环绕飞行控制指令
 *
 * @author liusuyi
 * @date 2024年10月26日15:43
 **/
public class FlyRoundControlCancelCmd extends BaseCmd {
    public FlyRoundControlCancelCmd(FlyRoundControlCancelDTO flyRoundControlCancelDTO) {
        setDataHeader((short) 0x7479);
        setDataLength((short) 18);
        setBoxSn(flyRoundControlCancelDTO.getBoxSn().getBytes());
        setCmdCode((byte) 0xD1);
    }
    public void encode(ByteBuf out) {
        out.writeShort(getDataHeader());    // 写入数据头
        out.writeShort(getDataLength());    // 写入数据长度
        out.writeBytes(getBoxSn());         // 写入盒子SN
        out.writeByte(getCmdCode());        // 写入命令码
        out.writeByte(0x00);          // 写入加密标志
        out.writeByte(0x3C);          // 写入动作编号
    }
}
ard-work/src/main/java/com/ard/work/component/netty/command/manual/FlyRoundControlCmd.java
New file
@@ -0,0 +1,50 @@
package com.ard.work.component.netty.command.manual;
import com.ard.work.component.netty.command.BaseCmd;
import com.ard.work.domain.dto.manual.FlyPointControlDTO;
import com.ard.work.domain.dto.manual.FlyRoundControlDTO;
import io.netty.buffer.ByteBuf;
/**
 * 环绕飞行控制指令
 *
 * @author liusuyi
 * @date 2024年10月26日15:43
 **/
public class FlyRoundControlCmd extends BaseCmd {
    private final double longitude;                  // 兴趣点经度
    private final double latitude;                   // 兴趣点纬度
    private final float altitude;                    // 兴趣点高度
    private final float speed;                       // 环绕速度
    private final float radius;                      // 环绕半径
    private final byte flyMode;                      // 飞行模式
    public FlyRoundControlCmd(FlyRoundControlDTO flyRoundControlDTO) {
        setDataHeader((short) 0x7479);
        setDataLength((short) 47);
        setBoxSn(flyRoundControlDTO.getBoxSn().getBytes());
        setCmdCode((byte) 0xD1);
        this.longitude = flyRoundControlDTO.getLongitude();
        this.latitude = flyRoundControlDTO.getLatitude();
        this.altitude = flyRoundControlDTO.getAltitude();
        this.speed = flyRoundControlDTO.getSpeed();
        this.radius= flyRoundControlDTO.getRadius();
        this.flyMode = flyRoundControlDTO.getFlyMode();
    }
    public void encode(ByteBuf out) {
        out.writeShort(getDataHeader());    // 写入数据头
        out.writeShort(getDataLength());    // 写入数据长度
        out.writeBytes(getBoxSn());         // 写入盒子SN
        out.writeByte(getCmdCode());        // 写入命令码
        out.writeByte(0x00);          // 写入加密标志
        out.writeByte(0x3B);          // 写入动作编号
        out.writeDouble(longitude);         // 写入兴趣点经度
        out.writeDouble(latitude);          // 写入兴趣点纬度
        out.writeFloat(altitude);           // 写入兴趣点高度
        out.writeFloat(speed);              // 写入环绕速度
        out.writeFloat(radius);             // 写入环绕半径
        out.writeByte(flyMode);             // 写入飞行模式
    }
}
ard-work/src/main/java/com/ard/work/component/netty/command/manual/FlyRoundReplyCmd.java
New file
@@ -0,0 +1,35 @@
package com.ard.work.component.netty.command.manual;
import com.ard.work.component.netty.command.BaseCmd;
import io.netty.buffer.ByteBuf;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 到达环绕位置回复指令
 *
 * @author liusuyi
 * @date 2024年10月26日15:43
 **/
@Data
@EqualsAndHashCode(callSuper = false)
public class FlyRoundReplyCmd extends BaseCmd {
    private final byte encryptFlag = 0x00; // 加密标志
    private final byte actionCode = 0x3B;  // 动作编号
    private final byte result;             // 执行结果
    private final Integer errorCode;       // 错误码
    public FlyRoundReplyCmd(String boxSn, Byte result, Integer errorCode) {
        setDataHeader((short) 0x6A77);
        setDataLength((short) 19);
        setBoxSn(boxSn.getBytes());
        setCmdCode((byte) 0xD1);
        this.result = result;
        this.errorCode = errorCode;
    }
    public void encode(ByteBuf out) {
    }
}
ard-work/src/main/java/com/ard/work/component/netty/handler/MessageProtocolDecoder.java
@@ -174,6 +174,8 @@
                return new EmergencyBrakeReplyCmd(boxSn, result, errorCode);
            case 0x39:
                return new PointControlReachedReplyCmd(boxSn, result, errorCode);
            case 0x3B:
                return new FlyRoundReplyCmd(boxSn, result, errorCode);
            case 0x22:
                return new CameraModeReplyCmd(boxSn, result, errorCode);
            case 0x23:
ard-work/src/main/java/com/ard/work/component/websocket/handler/TyMessageHandler.java
@@ -290,14 +290,7 @@
        Long userId = Long.parseLong(userIdStr);
        WebSocketSessionManager.removeSession(userId);
        log.info("【cmd】size: {}", WebSocketSessionManager.getOnlineUserCount());
        // 释放该用户所有 UAV 控制权
        uavControlService.releaseAllControls(userId);
        uavStreamManager.stopAllPullStreamByUser(userId);
        uavStateManager.removeSubscribe(userId);
        uavTelemetryManager.removeSubscribe(userId);
        uavSignalManager.removeSubscribe(userId);
        uavControlManager.removeSubscribe(userId);
        // 延迟10秒删除token(若期间没重连)
        CompletableFuture.runAsync(() -> {
@@ -309,6 +302,12 @@
            if (!WebSocketSessionManager.containsUser(userId)) {
                LoginUser loginUser = tokenService.getLoginUserByUserId(userId);
                if (loginUser != null) {
                    uavControlService.releaseAllControls(userId);
                    uavStreamManager.stopAllPullStreamByUser(userId);
                    uavStateManager.removeSubscribe(userId);
                    uavTelemetryManager.removeSubscribe(userId);
                    uavSignalManager.removeSubscribe(userId);
                    uavControlManager.removeSubscribe(userId);
                    tokenService.delLoginUser(loginUser.getToken(), userId);
                    log.info("【cmd】用户 [{}] 超时未重连,已删除 token", userId);
                }
ard-work/src/main/java/com/ard/work/domain/dto/manual/FlyRoundControlCancelDTO.java
New file
@@ -0,0 +1,22 @@
package com.ard.work.domain.dto.manual;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * 环绕飞行控制实体对象
 *
 * @author liusuyi
 * @date 2024年10月26日9:02
 **/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FlyRoundControlCancelDTO {
    private String boxSn;
}
ard-work/src/main/java/com/ard/work/domain/dto/manual/FlyRoundControlDTO.java
New file
@@ -0,0 +1,51 @@
package com.ard.work.domain.dto.manual;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * 环绕飞行控制实体对象
 *
 * @author liusuyi
 * @date 2024年10月26日9:02
 **/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FlyRoundControlDTO {
    private String boxSn;
    /**
     * 兴趣点经度
     */
    private Double longitude;
    /**
     * 兴趣点纬度
     */
    private Double latitude;
    /**
     * 兴趣点高度
     */
    private Float altitude;
    /**
     * 环绕速度
     */
    private Float speed;
    /**
     * 环绕半径
     */
    private Float radius;
    /**
     * 飞行模式
     */
    @Builder.Default
    private byte flyMode = 0;
}
ard-work/src/main/java/com/ard/work/service/ITyService.java
@@ -206,6 +206,17 @@
     */
    Boolean flyPointControlCancel(FlyPointControlCancelDTO flyPointControlCancelDTO);
    /**
     * 环绕飞行控制
     */
    Boolean FlyRoundControl(FlyRoundControlDTO flyRoundControlDTO);
    /**
     * 停止环绕飞行
     */
    Boolean FlyRoundControlCancel(FlyRoundControlCancelDTO flyRoundControlCancelDTO);
    /**
     * 紧急制动
     */
ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java
@@ -1,5 +1,8 @@
package com.ard.work.service.impl;
import com.ard.common.constant.CacheConstants;
import com.ard.common.core.domain.entity.SysUser;
import com.ard.common.core.redis.RedisCache;
import com.ard.common.utils.DateUtils;
import com.ard.common.utils.file.MimeTypeUtils;
import com.ard.common.utils.uuid.IdUtils;
@@ -16,8 +19,10 @@
import com.ard.work.mapper.ArdMediaMapper;
import com.ard.work.service.IArdFlightTaskService;
import com.ard.work.service.IArdServerInfoService;
import com.ard.work.service.control.UavControlService;
import com.ard.work.util.osSelect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
@@ -57,7 +62,10 @@
    private OssServiceContext ossServiceContext;
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;  // 使用线程池执行异步任务
    @Resource
    private UavControlService uavControlService;
    @Autowired
    private RedisCache redisCache;
    /**
     * 查询飞行架次列表
     *
@@ -128,6 +136,14 @@
                    .startTime(DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, boxHistoryReceiver.getStartTime()))
                    .endTime(DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, boxHistoryReceiver.getEndTime()))
                    .build();
            Long flyUserId = uavControlService.getCurrentController(flightTask.getBoxSn(), "fly");
            if (flyUserId != null) {
                SysUser flyUser = redisCache.getCacheObject(CacheConstants.SYS_USER_KEY + flyUserId);
                if (flyUser != null && flyUser.getNickName() != null) {
                    flightTask.setCreateBy(flyUser.getNickName());
                }
            }
            flightTask.setCreateTime(DateUtils.getNowDate());
            // 插入新记录
            int rows = ardFlightTaskMapper.insertArdFlightTask(flightTask);
@@ -144,12 +160,46 @@
        } else {
            // 如果存在,则更新记录
            log.debug("Updating task {}", boxHistoryReceiver.getTaskId());
            updateFlightTask(ardFlightTask, boxHistoryReceiver);
            // 增量更新
            updateVideoToDatabase(ardFlightTask, boxHistoryReceiver.getVideoPathList());
            // 增量更新图片
            updatePhotoToDatabase(ardFlightTask, serverId, boxHistoryReceiver.getBoxSn());
        }
    }
    private void updateFlightTask(ArdFlightTask local, BoxHistoryReceiver remote) {
        boolean needUpdate = false;
        // endTime 更新(重要!!!)
        Date newEnd = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, remote.getEndTime());
        if (newEnd != null && (local.getEndTime() == null || !local.getEndTime().equals(newEnd))) {
            local.setEndTime(newEnd);
            needUpdate = true;
        }
        // photoCount 更新
        int remotePhotoCount = Integer.parseInt(remote.getPhotoCount());
        if (local.getPhotoCount() != remotePhotoCount) {
            local.setPhotoCount(remotePhotoCount);
            needUpdate = true;
        }
        // videoCount 更新
        int remoteVideoCount = remote.getVideoPathList() != null ? remote.getVideoPathList().size() : 0;
        if (local.getVideoCount() != remoteVideoCount) {
            local.setVideoCount(remoteVideoCount);
            needUpdate = true;
        }
        if (needUpdate) {
            ardFlightTaskMapper.updateArdFlightTask(local);
        }
        // 其它媒体增量更新
        updateVideoToDatabase(local, remote.getVideoPathList());
        updatePhotoToDatabase(local, local.getServerId(), remote.getBoxSn());
    }
@@ -415,70 +465,6 @@
    }
    // 生成视频缩略图
    public ByteArrayInputStream generateThumbnail1(String videoFile, int skipFrames, int targetWidth) {
//        long start = System.currentTimeMillis();
//        FFmpegFrameGrabber grabber = null;
//
//        try {
//            grabber = new FFmpegFrameGrabber(videoFile);
//            grabber.start();
//
//            int totalFrames = grabber.getLengthInFrames();
//            System.out.println("视频总帧数: " + totalFrames);
//            System.out.println("视频时长: " + grabber.getLengthInTime() / 1000000 + "秒");
//
//            Frame frame = null;
//            int currentFrame = 0;
//
//            // 读取指定帧数后的第一帧有效图像
//            while (currentFrame < totalFrames) {
//                frame = grabber.grabImage();
//                if (frame == null) {
//                    break;
//                }
//
//                if (currentFrame > skipFrames && frame.image != null) {
//                    break;
//                }
//                currentFrame++;
//            }
//
//            if (frame == null || frame.image == null) {
//                throw new Exception("无法获取有效视频帧");
//            }
//
//            // 转换 Frame 为 BufferedImage
//            Java2DFrameConverter converter = new Java2DFrameConverter();
//            BufferedImage image = converter.getBufferedImage(frame);
//
//            // 按目标宽度等比缩放
//            int targetHeight = (int) ((double) targetWidth / image.getWidth() * image.getHeight());
//            BufferedImage scaledImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_3BYTE_BGR);
//            scaledImage.getGraphics().drawImage(image, 0, 0, targetWidth, targetHeight, null);
//
//            // 转成 ByteArrayInputStream
//            ByteArrayOutputStream baos = new ByteArrayOutputStream();
//            ImageIO.write(scaledImage, "jpg", baos);
//            long cost = System.currentTimeMillis() - start;
//            System.out.println("处理耗时: " + cost + "ms");
//
//            return new ByteArrayInputStream(baos.toByteArray());
//
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        } finally {
//            if (grabber != null) {
//                try {
//                    grabber.stop();
//                    grabber.close();
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            }
//        }
        return null;
    }
    public ByteArrayInputStream generateThumbnail(
            String videoFile,
            int skipSeconds,
@@ -492,6 +478,12 @@
        try {
            List<String> command = new ArrayList<>();
            command.add(ffmpegPath);
            command.add("-timeout");
            command.add("3000000");
            command.add("-rw_timeout");
            command.add("3000000");
            command.add("-ss");
            command.add(String.format("00:00:%02d", skipSeconds));
            command.add("-i");
ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java
@@ -603,6 +603,37 @@
        return true;
    }
    /**
     * 环绕飞行控制
     */
    @Override
    public Boolean FlyRoundControl(FlyRoundControlDTO flyRoundControlDTO) {
        String boxSn = flyRoundControlDTO.getBoxSn();
        ArdDroneInfo ardDroneInfo = ardDroneService.selectArdDroneByboxSn(boxSn);
        if (ardDroneInfo == null) {
            throw new ControlException("未找到对应的飞行器信息,boxSn:{}" + boxSn);
        }
        FlyRoundControlCmd flyPointControlCmd = new FlyRoundControlCmd(flyRoundControlDTO);
        this.sendMessage(ardDroneInfo.getServerId(), flyPointControlCmd);
        return true;
    }
    /**
     * 停止环绕飞行控制
     */
    @Override
    public Boolean FlyRoundControlCancel(FlyRoundControlCancelDTO flyRoundControlCancelDTO) {
        String boxSn = flyRoundControlCancelDTO.getBoxSn();
        ArdDroneInfo ardDroneInfo = ardDroneService.selectArdDroneByboxSn(boxSn);
        if (ardDroneInfo == null) {
            throw new ControlException("未找到对应的飞行器信息,boxSn:{}" + boxSn);
        }
        FlyRoundControlCancelCmd flyRoundControlCancelCmd = new FlyRoundControlCancelCmd(flyRoundControlCancelDTO);
        this.sendMessage(ardDroneInfo.getServerId(), flyRoundControlCancelCmd);
        return true;
    }
    /**
     * 紧急制动
     */
@@ -1042,6 +1073,12 @@
                    logPrint("设置返航点", returnPointSettingReplyCmd.getResult(),
                            returnPointSettingReplyCmd.getErrorCode());
                }
                //到达环绕位置回复
                if (tyCommand instanceof FlyRoundReplyCmd) {
                    FlyRoundReplyCmd flyRoundReplyCmd = (FlyRoundReplyCmd) tyCommand;
                    logPrint("到达环绕位置", flyRoundReplyCmd.getResult(),
                            flyRoundReplyCmd.getErrorCode());
                }
                break;
            case (byte) 0xD2:
                //设置视频码流回复