Merge remote-tracking branch 'origin/main'

This commit is contained in:
gyb 2026-02-26 17:43:22 +08:00
commit 95b91299c9
25 changed files with 1556 additions and 107 deletions

View File

@ -1 +1 @@
ddddddddddddddddddddddddddddddddddddddddddddddD堆堆ddddddddddddddddddddeeedddddddd
ddddddddddddddddddddddddddddddddddddddddddddddddD堆堆ddddddddddddddddddddeeedddddddd

View File

@ -158,13 +158,13 @@ public class MqttCallbackRegistry {
// 如果是 confirm realTime/data 消息打印详细日志
if (topic.contains("/control/confirm") || topic.contains("/realTime/data")) {
log.info("【MqttCallbackRegistry】处理消息: topic={}, callbackCount={}, messageBody={}",
log.info("【Machine MqttCallbackRegistry】处理消息: topic={}, callbackCount={}, messageBody={}",
topic, callbacks.size(), messageBody);
}
if (callbacks.isEmpty()) {
if (topic.contains("/control/confirm") || topic.contains("/realTime/data")) {
log.warn("MqttCallbackRegistry】没有找到匹配的回调: topic={}", topic);
log.debug("【Machine MqttCallbackRegistry】没有找到匹配的回调: topic={}", topic);
}
return;
}

View File

@ -0,0 +1,5 @@
package com.ruoyi.device.domain.impl.tuohengmqtt.callback;
public interface IAirportFlyControlDataCallback {
void onAirportFlyControlData(String deviceSn, String payload, String topic);
}

View File

@ -5,6 +5,7 @@ import com.ruoyi.device.domain.impl.machine.mqtt.MqttCallbackRegistry;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.IDroneRealTimeCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.IHeartbeatMessageCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.IRealTimeBasicCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.IAirportFlyControlDataCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengEventsCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengOsdCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.ITuohengRealTimeDataCallback;
@ -21,12 +22,14 @@ import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static com.ruoyi.device.domain.impl.tuohengmqtt.service.TuohengMqttClientService.*;
@Slf4j
@Component
public class TuohengMqttMessageHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
private MqttCallbackRegistry mqttCallbackRegistry;
private MqttCallbackRegistry machineCallBackRegistry;
private final List<ITuohengRealTimeDataCallback> realTimeDataCallbacks = new ArrayList<>();
private final List<ITuohengOsdCallback> osdCallbacks = new ArrayList<>();
@ -34,15 +37,17 @@ public class TuohengMqttMessageHandler {
private final List<IRealTimeBasicCallback> realTimeBasicCallbacks = new ArrayList<>();
private final List<IDroneRealTimeCallback> droneRealTimeCallbacks = new ArrayList<>();
private final List<IHeartbeatMessageCallback> heartbeatMessageCallbacks = new ArrayList<>();
private final List<IAirportFlyControlDataCallback> airportFlyControlDataCallbacks = new ArrayList<>();
private static final Pattern TUOHENG_SN_PATTERN = Pattern.compile("^TH[0-9A-Z]+");
/**
* 设置 MQTT 回调注册中心
*/
public void setMqttCallbackRegistry(MqttCallbackRegistry mqttCallbackRegistry) {
this.mqttCallbackRegistry = mqttCallbackRegistry;
log.info("设置 MqttCallbackRegistry 成功");
public void setMachineCallBackRegistry(MqttCallbackRegistry machineCallBackRegistry) {
this.machineCallBackRegistry = machineCallBackRegistry;
log.info("设置 machineCallBackRegistry 成功");
}
public void registerRealTimeDataCallback(ITuohengRealTimeDataCallback callback) {
@ -87,33 +92,29 @@ public class TuohengMqttMessageHandler {
}
}
public void registerAirportFlyControlDataCallback(IAirportFlyControlDataCallback callback) {
if (callback != null && !airportFlyControlDataCallbacks.contains(callback)) {
airportFlyControlDataCallbacks.add(callback);
log.info("注册机场飞行控制数据回调: {}", callback.getClass().getSimpleName());
}
}
public void handleMessage(String topic, String payload) {
try {
log.debug("收到MQTT消息 - Topic: {}", topic);
// 如果是 confirm 消息打印详细日志
if (topic.contains("/control/confirm")) {
log.info("【收到confirm消息】Topic: {}, Payload: {}", topic, payload);
}
// 通知 MqttCallbackRegistry 处理回调用于指令回调
if (mqttCallbackRegistry != null) {
if (machineCallBackRegistry != null) {
try {
// payload 解析为 JSON 对象传递给回调注册中心
Object messageBody = objectMapper.readValue(payload, Object.class);
mqttCallbackRegistry.handleMessage(topic, messageBody);
// 如果是 confirm 消息打印回调处理结果
if (topic.contains("/control/confirm")) {
log.info("【confirm消息已传递给回调注册中心】Topic: {}", topic);
}
machineCallBackRegistry.handleMessage(topic, messageBody);
} catch (Exception e) {
log.debug("通知回调注册中心失败: {}", e.getMessage());
}
}
String deviceSn = extractDeviceSnFromTopic(topic);
if (deviceSn == null) {
log.warn("无法从Topic解析设备SN: {}", topic);
return;
@ -126,31 +127,29 @@ public class TuohengMqttMessageHandler {
}
}
String messageType = extractMessageTypeFromTopic(topic);
if (messageType == null) {
log.warn("无法从Topic解析消息类型: {}", topic);
return;
}
switch (messageType) {
case "realTime/data":
handleRealTimeData(deviceSn, payload, topic);
break;
case "realTime/basic":
// 根据Topic模式匹配分发到不同的处理方法
if (matchTopic(topic, AIRPORT_NEST_REALTIME_TOPIC)) {
handleAirportNestRealTimeData(deviceSn, payload);
} else if (matchTopic(topic, AIRPORT_NEST_BASIC_TOPIC)) {
handleRealTimeBasicData(deviceSn, payload);
break;
case "heartbeat/message":
} else if (matchTopic(topic, AIRPORT_NEST_CONFIRM_TOPIC)) {
// 机场控制确认消息已由 MqttCallbackRegistry 处理
log.debug("机场控制确认消息 - SN: {}", deviceSn);
} else if (matchTopic(topic, AIRPORT_DRONE_REALTIME_TOPIC)) {
handleAirportDroneRealTimeData(deviceSn, payload);
} else if (matchTopic(topic, AIRPORT_FLY_DATA_TOPIC)) {
handleAirportFlyControlData(deviceSn, payload, topic);
} else if (matchTopic(topic, AIRPORT_FLY_CONFIRM_TOPIC)) {
// 飞行控制确认消息已由 MqttCallbackRegistry 处理
log.debug("飞行控制确认消息 - SN: {}", deviceSn);
} else if (matchTopic(topic, HEARTBEAT_MESSAGE_TOPIC)) {
handleHeartbeatMessage(deviceSn, payload);
break;
case "osd":
} else if (matchTopic(topic, PRODUCT_OSD_TOPIC)) {
handleOsdData(deviceSn, payload);
break;
case "events":
} else if (matchTopic(topic, PRODUCT_EVENTS_TOPIC)) {
handleEventsData(deviceSn, payload);
break;
default:
log.debug("未知消息类型: {}", messageType);
} else {
log.debug("未知的消息类型 - Topic: {}", topic);
}
} catch (Exception e) {
@ -158,9 +157,8 @@ public class TuohengMqttMessageHandler {
}
}
private void handleRealTimeData(String deviceSn, String payload, String topic) {
private void handleAirportNestRealTimeData(String deviceSn, String payload) {
try {
if (topic.contains("airportNest")) {
TuohengRealTimeData data = objectMapper.readValue(payload, TuohengRealTimeData.class);
log.debug("处理机场实时数据 - 设备SN: {}", deviceSn);
for (ITuohengRealTimeDataCallback callback : realTimeDataCallbacks) {
@ -170,7 +168,13 @@ public class TuohengMqttMessageHandler {
log.error("实时数据回调执行失败: {}", e.getMessage(), e);
}
}
} else if (topic.contains("airportDrone")) {
} catch (Exception e) {
log.error("处理机场实时数据失败 - SN: {}, Error: {}", deviceSn, e.getMessage(), e);
}
}
private void handleAirportDroneRealTimeData(String deviceSn, String payload) {
try {
DroneRealTimeData data = objectMapper.readValue(payload, DroneRealTimeData.class);
log.debug("处理无人机实时数据 - 设备SN: {}", deviceSn);
for (IDroneRealTimeCallback callback : droneRealTimeCallbacks) {
@ -180,9 +184,8 @@ public class TuohengMqttMessageHandler {
log.error("无人机实时数据回调执行失败: {}", e.getMessage(), e);
}
}
}
} catch (Exception e) {
log.error("处理实时数据失败 - SN: {}, Error: {}", deviceSn, e.getMessage(), e);
log.error("处理无人机实时数据失败 - SN: {}, Error: {}", deviceSn, e.getMessage(), e);
}
}
@ -254,6 +257,22 @@ public class TuohengMqttMessageHandler {
}
}
private void handleAirportFlyControlData(String deviceSn, String payload, String topic) {
try {
log.info("处理机场飞行控制数据 - 设备SN: {}, Topic: {}", deviceSn, topic);
for (IAirportFlyControlDataCallback callback : airportFlyControlDataCallbacks) {
try {
callback.onAirportFlyControlData(deviceSn, payload, topic);
} catch (Exception e) {
log.error("机场飞行控制数据回调执行失败: {}", e.getMessage(), e);
}
}
} catch (Exception e) {
log.error("处理机场飞行控制数据失败 - SN: {}, Error: {}", deviceSn, e.getMessage(), e);
}
}
private String extractDeviceSnFromTopic(String topic) {
if (topic == null) {
return null;
@ -277,37 +296,6 @@ public class TuohengMqttMessageHandler {
return null;
}
private String extractMessageTypeFromTopic(String topic) {
if (topic == null) {
return null;
}
String[] parts = topic.split("/");
// /topic/v1/heartbeat/{deviceSn}/message
// parts[0]="", parts[1]="topic", parts[2]="v1", parts[3]="heartbeat",
// parts[4]=deviceSn, parts[5]="message"
if (topic.startsWith("/topic/v1/heartbeat/")) {
if (parts.length >= 6) {
return "heartbeat/message";
}
}
// /topic/v1/airportNest/{deviceSn}/realTime/data
// parts[0]="", parts[1]="topic", parts[2]="v1", parts[3]="airportNest",
// parts[4]=deviceSn, parts[5]="realTime", parts[6]="data"
else if (topic.startsWith("/topic/v1/")) {
if (parts.length >= 7) {
return parts[5] + "/" + parts[6]; // "realTime/data" or "realTime/basic"
}
}
// thing/product/{deviceSn}/osd
// parts[0]="thing", parts[1]="product", parts[2]=deviceSn, parts[3]="osd"
else if (topic.startsWith("thing/product/")) {
if (parts.length >= 4) {
return parts[3]; // "osd" or "events"
}
}
return null;
}
private boolean isProductTopic(String topic) {
return topic != null && topic.startsWith("thing/product/");
@ -319,4 +307,19 @@ public class TuohengMqttMessageHandler {
}
return TUOHENG_SN_PATTERN.matcher(sn).matches();
}
/**
* 匹配Topic模式
* @param topic 实际topic
* @param pattern 模式topic支持+通配符
* @return 是否匹配
*/
private boolean matchTopic(String topic, String pattern) {
if (topic == null || pattern == null) {
return false;
}
// 将模式中的+替换为正则表达式匹配除斜杠外的任意字符
String regex = pattern.replace("+", "[^/]+");
return topic.matches(regex);
}
}

View File

@ -14,6 +14,9 @@ public class DroneRealTimeData {
@JsonProperty("code")
private Integer code;
@JsonProperty("messageID")
private String messageID;
@JsonProperty("data")
private DroneInfo data;
@ -142,5 +145,8 @@ public class DroneRealTimeData {
@JsonProperty("flowRate")
private String flowRate;
@JsonProperty("jiancha")
private String jiancha;
}
}

View File

@ -20,13 +20,15 @@ public class TuohengMqttClientService {
private final TuohengMqttMessageHandler messageHandler;
private MqttClient mqttClient;
private static final String AIRPORT_NEST_REALTIME_TOPIC = "/topic/v1/airportNest/+/realTime/data";
private static final String AIRPORT_NEST_BASIC_TOPIC = "/topic/v1/airportNest/+/realTime/basic";
private static final String AIRPORT_NEST_CONFIRM_TOPIC = "/topic/v1/airportNest/+/control/confirm";
private static final String AIRPORT_DRONE_REALTIME_TOPIC = "/topic/v1/airportDrone/+/realTime/data";
private static final String HEARTBEAT_MESSAGE_TOPIC = "/topic/v1/heartbeat/+/message";
private static final String PRODUCT_OSD_TOPIC = "thing/product/+/osd";
private static final String PRODUCT_EVENTS_TOPIC = "thing/product/+/events";
public static final String AIRPORT_NEST_REALTIME_TOPIC = "/topic/v1/airportNest/+/realTime/data";
public static final String AIRPORT_NEST_BASIC_TOPIC = "/topic/v1/airportNest/+/realTime/basic";
public static final String AIRPORT_NEST_CONFIRM_TOPIC = "/topic/v1/airportNest/+/control/confirm";
public static final String AIRPORT_DRONE_REALTIME_TOPIC = "/topic/v1/airportDrone/+/realTime/data";
public static final String AIRPORT_FLY_DATA_TOPIC = "/topic/v1/airportFly/+/control/data";
public static final String AIRPORT_FLY_CONFIRM_TOPIC = "/topic/v1/airportFly/+/control/confirm";
public static final String HEARTBEAT_MESSAGE_TOPIC = "/topic/v1/heartbeat/+/message";
public static final String PRODUCT_OSD_TOPIC = "thing/product/+/osd";
public static final String PRODUCT_EVENTS_TOPIC = "thing/product/+/events";
public TuohengMqttClientService(TuohengMqttClientConfig config, TuohengMqttMessageHandler messageHandler) {
this.config = config;
@ -124,6 +126,8 @@ public class TuohengMqttClientService {
AIRPORT_NEST_BASIC_TOPIC,
AIRPORT_NEST_CONFIRM_TOPIC,
AIRPORT_DRONE_REALTIME_TOPIC,
AIRPORT_FLY_DATA_TOPIC,
AIRPORT_FLY_CONFIRM_TOPIC,
HEARTBEAT_MESSAGE_TOPIC,
PRODUCT_OSD_TOPIC,
PRODUCT_EVENTS_TOPIC

View File

@ -0,0 +1,40 @@
package com.ruoyi.device.mapper;
import com.ruoyi.device.mapper.entity.FlightLogEntity;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 飞行日志表Mapper接口
*
* @author ruoyi
* @date 2026-02-25
*/
@Mapper
public interface FlightLogMapper
{
/**
* 根据日志主键查询飞行日志
*
* @param logId 日志主键
* @return 飞行日志信息
*/
FlightLogEntity selectFlightLogByLogId(Long logId);
/**
* 根据飞行ID查询飞行日志列表
*
* @param flightId 飞行ID
* @return 飞行日志集合
*/
List<FlightLogEntity> selectFlightLogListByFlightId(Long flightId);
/**
* 新增飞行日志
*
* @param flightLog 飞行日志信息
* @return 影响行数
*/
int insertFlightLog(FlightLogEntity flightLog);
}

View File

@ -0,0 +1,73 @@
package com.ruoyi.device.mapper;
import com.ruoyi.device.mapper.entity.FlightEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 飞行表Mapper接口
*
* @author ruoyi
* @date 2026-02-25
*/
@Mapper
public interface FlightMapper
{
/**
* 根据飞行主键查询飞行记录
*
* @param flightId 飞行主键
* @return 飞行信息
*/
FlightEntity selectFlightByFlightId(Long flightId);
/**
* 根据设备SN查询最新的飞行记录
*
* @param deviceSn 设备SN号
* @return 飞行信息
*/
FlightEntity selectLatestFlightByDeviceSn(String deviceSn);
/**
* 根据设备SN和状态查询飞行记录
*
* @param deviceSn 设备SN号
* @param status 状态
* @return 飞行信息
*/
FlightEntity selectFlightByDeviceSnAndStatus(@Param("deviceSn") String deviceSn, @Param("status") String status);
/**
* 新增飞行记录
*
* @param flight 飞行信息
* @return 影响行数
*/
int insertFlight(FlightEntity flight);
/**
* 修改飞行记录
*
* @param flight 飞行信息
* @return 影响行数
*/
int updateFlight(FlightEntity flight);
/**
* 更新飞行状态
*
* @param flightId 飞行主键
* @param status 状态
* @return 影响行数
*/
int updateFlightStatus(@Param("flightId") Long flightId, @Param("status") String status);
/**
* 更新返航时间
*
* @param flightId 飞行主键
* @return 影响行数
*/
int updateReturnTime(Long flightId);
}

View File

@ -0,0 +1,49 @@
package com.ruoyi.device.mapper;
import com.ruoyi.device.mapper.entity.PreCheckLogEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 自检日志表Mapper接口
*
* @author ruoyi
* @date 2026-02-25
*/
@Mapper
public interface PreCheckLogMapper
{
/**
* 根据日志主键查询自检日志
*
* @param logId 日志主键
* @return 自检日志信息
*/
PreCheckLogEntity selectPreCheckLogByLogId(Long logId);
/**
* 根据飞行ID查询自检日志列表
*
* @param flightId 飞行ID
* @return 自检日志集合
*/
List<PreCheckLogEntity> selectPreCheckLogListByFlightId(Long flightId);
/**
* 新增自检日志
*
* @param preCheckLog 自检日志信息
* @return 影响行数
*/
int insertPreCheckLog(PreCheckLogEntity preCheckLog);
/**
* 批量新增自检日志
*
* @param preCheckLogList 自检日志集合
* @return 影响行数
*/
int insertPreCheckLogBatch(List<PreCheckLogEntity> preCheckLogList);
}

View File

@ -0,0 +1,92 @@
package com.ruoyi.device.mapper.entity;
import com.ruoyi.common.core.web.domain.BaseEntity;
/**
* 飞行表实体对象 device_flight
* Mapper 层实体对应数据库表
*
* @author ruoyi
* @date 2026-02-25
*/
public class FlightEntity extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 飞行主键 */
private Long flightId;
/** 无人机SN */
private String deviceSn;
/** 外部飞行ID (来自MQTT的taskId) */
private String flightIdExternal;
/** 状态:自检中、飞行中、已返航 */
private String status;
/** 返航时间 */
private java.util.Date returnTime;
public Long getFlightId()
{
return flightId;
}
public void setFlightId(Long flightId)
{
this.flightId = flightId;
}
public String getDeviceSn()
{
return deviceSn;
}
public void setDeviceSn(String deviceSn)
{
this.deviceSn = deviceSn;
}
public String getFlightIdExternal()
{
return flightIdExternal;
}
public void setFlightIdExternal(String flightIdExternal)
{
this.flightIdExternal = flightIdExternal;
}
public String getStatus()
{
return status;
}
public void setStatus(String status)
{
this.status = status;
}
public java.util.Date getReturnTime()
{
return returnTime;
}
public void setReturnTime(java.util.Date returnTime)
{
this.returnTime = returnTime;
}
@Override
public String toString()
{
return "FlightEntity{" +
"flightId=" + flightId +
", deviceSn='" + deviceSn + '\'' +
", flightIdExternal='" + flightIdExternal + '\'' +
", status='" + status + '\'' +
", returnTime=" + returnTime +
'}';
}
}

View File

@ -0,0 +1,64 @@
package com.ruoyi.device.mapper.entity;
import com.ruoyi.common.core.web.domain.BaseEntity;
/**
* 飞行日志表实体对象 device_flight_log
* Mapper 层实体对应数据库表
*
* @author ruoyi
* @date 2026-02-25
*/
public class FlightLogEntity extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 飞行日志主键 */
private Long logId;
/** 关联飞行表ID */
private Long flightId;
/** 日志内容 */
private String logContent;
public Long getLogId()
{
return logId;
}
public void setLogId(Long logId)
{
this.logId = logId;
}
public Long getFlightId()
{
return flightId;
}
public void setFlightId(Long flightId)
{
this.flightId = flightId;
}
public String getLogContent()
{
return logContent;
}
public void setLogContent(String logContent)
{
this.logContent = logContent;
}
@Override
public String toString()
{
return "FlightLogEntity{" +
"logId=" + logId +
", flightId=" + flightId +
", logContent='" + logContent + '\'' +
'}';
}
}

View File

@ -0,0 +1,78 @@
package com.ruoyi.device.mapper.entity;
import com.ruoyi.common.core.web.domain.BaseEntity;
/**
* 自检日志表实体对象 device_pre_check_log
* Mapper 层实体对应数据库表
*
* @author ruoyi
* @date 2026-02-25
*/
public class PreCheckLogEntity extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 自检日志主键 */
private Long logId;
/** 关联飞行表ID */
private Long flightId;
/** 日志内容 (JSON字符串) */
private String logContent;
/** 是否成功 (true=成功, false=失败) */
private Boolean success;
public Long getLogId()
{
return logId;
}
public void setLogId(Long logId)
{
this.logId = logId;
}
public Long getFlightId()
{
return flightId;
}
public void setFlightId(Long flightId)
{
this.flightId = flightId;
}
public String getLogContent()
{
return logContent;
}
public void setLogContent(String logContent)
{
this.logContent = logContent;
}
public Boolean getSuccess()
{
return success;
}
public void setSuccess(Boolean success)
{
this.success = success;
}
@Override
public String toString()
{
return "PreCheckLogEntity{" +
"logId=" + logId +
", flightId=" + flightId +
", logContent='" + logContent + '\'' +
", success=" + success +
'}';
}
}

View File

@ -0,0 +1,96 @@
package com.ruoyi.device.service;
import com.ruoyi.device.mapper.entity.FlightEntity;
import java.util.Map;
/**
* 飞行服务接口
*
* @author ruoyi
* @date 2026-02-25
*/
public interface FlightService
{
/**
* 获取或创建当前正在进行的飞行记录
* 用于MQTT回调如果最新记录已返航则创建新记录
*
* @param deviceSn 设备SN号
* @return 飞行记录
*/
FlightEntity getOrCreateCurrentFlight(String deviceSn);
/**
* 获取或创建飞行记录通过messageID匹配
* 如果存在相同messageID的飞行记录返回该记录
* 如果不存在或messageID不同创建新记录
*
* @param deviceSn 设备SN号
* @param messageId 消息ID对应flightIdExternal
* @return 飞行记录
*/
FlightEntity getOrCreateFlightByMessageId(String deviceSn, String messageId);
/**
* 获取最新的飞行记录包括已返航的
* 用于WebSocket推送
*
* @param deviceSn 设备SN号
* @return 飞行记录
*/
FlightEntity getLatestFlight(String deviceSn);
/**
* 创建新的飞行记录
*
* @param deviceSn 设备SN号
* @return 飞行记录
*/
FlightEntity createFlight(String deviceSn);
/**
* 更新飞行ID外部ID
*
* @param flightId 飞行ID
* @param flightIdExternal 外部飞行ID
*/
void updateFlightIdExternal(Long flightId, String flightIdExternal);
/**
* 更新飞行状态
*
* @param flightId 飞行ID
* @param status 状态自检中飞行中已返航
*/
void updateFlightStatus(Long flightId, String status);
/**
* 更新返航时间
*
* @param flightId 飞行ID
*/
void updateReturnTime(Long flightId);
/**
* 获取飞行记录和日志用于WebSocket推送
*
* @param deviceSn 设备SN号
* @return 飞行记录和日志
*/
Map<String, Object> getLatestFlightWithLogs(String deviceSn);
/**
* 保存自检日志
*
* @param logEntity 自检日志实体
*/
void insertPreCheckLog(com.ruoyi.device.mapper.entity.PreCheckLogEntity logEntity);
/**
* 保存飞行日志
*
* @param logEntity 飞行日志实体
*/
void insertFlightLog(com.ruoyi.device.mapper.entity.FlightLogEntity logEntity);
}

View File

@ -0,0 +1,132 @@
package com.ruoyi.device.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.IAirportFlyControlDataCallback;
import com.ruoyi.device.mapper.entity.FlightEntity;
import com.ruoyi.device.mapper.entity.FlightLogEntity;
import com.ruoyi.device.mapper.entity.PreCheckLogEntity;
import com.ruoyi.device.service.FlightService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class FlightEventCallback implements IAirportFlyControlDataCallback {
@Autowired
private FlightService flightService;
@Override
public void onAirportFlyControlData(String deviceSn, String payload, String topic) {
try {
log.info("处理飞行事件: deviceSn={}, topic={}, payload={}", deviceSn, topic, payload);
JSONObject data = JSONObject.parseObject(payload);
if (data == null) {
return;
}
String msg = data.getString("msg");
String messageID = data.getString("messageID");
String action = data.getString("action");
if (action == null || action.isEmpty()) {
JSONObject dataObj = data.getJSONObject("data");
if (dataObj != null) {
action = dataObj.getString("action");
}
}
FlightEntity flight;
if (messageID != null && !messageID.isEmpty()) {
flight = handleFlightIdExternal(deviceSn, messageID);
} else {
flight = flightService.getOrCreateCurrentFlight(deviceSn);
}
if (msg != null && !msg.isEmpty()) {
if (msg.contains("自检")) {
handlePreCheckLog(deviceSn, msg, data, flight);
} else {
handleFlightLog(deviceSn, msg, flight);
}
handleFlightStatus(deviceSn, action, data, flight);
}
} catch (Exception e) {
log.error("处理飞行事件失败: deviceSn={}, topic={}, error={}", deviceSn, topic, e.getMessage(), e);
}
}
private FlightEntity handleFlightIdExternal(String deviceSn, String messageId) {
if (messageId != null && !messageId.isEmpty()) {
return flightService.getOrCreateFlightByMessageId(deviceSn, messageId);
} else {
return flightService.getOrCreateCurrentFlight(deviceSn);
}
}
private void handleFlightLog(String deviceSn, String message, FlightEntity flight) {
if (flight == null) {
log.warn("飞行记录为空,无法保存飞行日志: deviceSn={}, message={}", deviceSn, message);
return;
}
try {
FlightLogEntity logEntity = new FlightLogEntity();
logEntity.setFlightId(flight.getFlightId());
logEntity.setLogContent(message);
flightService.insertFlightLog(logEntity);
log.info("保存飞行日志: deviceSn={}, flightId={}, message={}",
deviceSn, flight.getFlightId(), message);
} catch (Exception e) {
log.error("保存飞行日志失败: deviceSn={}, message={}, error={}",
deviceSn, message, e.getMessage(), e);
}
}
private void handlePreCheckLog(String deviceSn, String msg, JSONObject data, FlightEntity flight) {
if (flight == null) {
log.warn("飞行记录为空,无法保存自检日志: deviceSn={}, msg={}", deviceSn, msg);
return;
}
try {
Boolean success = msg.contains("通过") || msg.contains("成功");
PreCheckLogEntity logEntity = new PreCheckLogEntity();
logEntity.setFlightId(flight.getFlightId());
logEntity.setLogContent(msg);
logEntity.setSuccess(success);
flightService.insertPreCheckLog(logEntity);
log.info("保存自检日志: deviceSn={}, flightId={}, msg={}, success={}",
deviceSn, flight.getFlightId(), msg, success);
} catch (Exception e) {
log.error("保存自检日志失败: deviceSn={}, msg={}, error={}",
deviceSn, msg, e.getMessage(), e);
}
}
private void handleFlightStatus(String deviceSn, String action, JSONObject data, FlightEntity flight) {
if (flight == null) {
log.warn("飞行记录为空,无法更新状态: deviceSn={}, action={}", deviceSn, action);
return;
}
try {
String msg = data.getString("msg");
String dataContent = data.getString("data");
if ((msg != null && msg.contains("起飞成功")) || (dataContent != null && dataContent.contains("起飞成功"))) {
flightService.updateFlightStatus(flight.getFlightId(), "飞行中");
log.info("飞行状态更新: deviceSn={}, status=飞行中", deviceSn);
} else if ((msg != null && msg.contains("返航成功")) || (dataContent != null && dataContent.contains("返航成功")) ||
(dataContent != null && dataContent.contains("任务飞行完成"))) {
flightService.updateFlightStatus(flight.getFlightId(), "已返航");
log.info("飞行状态更新: deviceSn={}, status=已返航", deviceSn);
}
} catch (Exception e) {
log.error("更新飞行状态失败: deviceSn={}, action={}, error={}",
deviceSn, action, e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,89 @@
package com.ruoyi.device.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.device.domain.impl.tuohengmqtt.callback.IDroneRealTimeCallback;
import com.ruoyi.device.domain.impl.tuohengmqtt.model.DroneRealTimeData;
import com.ruoyi.device.mapper.entity.FlightEntity;
import com.ruoyi.device.mapper.entity.FlightLogEntity;
import com.ruoyi.device.mapper.entity.PreCheckLogEntity;
import com.ruoyi.device.service.FlightService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 飞行日志回调处理器
* 处理自检日志和飞行事件日志
*
* @author ruoyi
* @date 2026-02-25
*/
@Slf4j
@Component
public class FlightLogCallback implements IDroneRealTimeCallback {
@Autowired
private FlightService flightService;
@Override
public void onDroneRealTimeData(String deviceSn, DroneRealTimeData data) {
if (data == null || data.getData() == null) {
return;
}
log.info("处理实时消息: deviceSn={}, messageId={} data={}", deviceSn, data.getMessageID(),JSON.toJSONString(data.getData()));
DroneRealTimeData.DroneInfo droneInfo = data.getData();
try {
if (droneInfo.getJiancha() != null && !droneInfo.getJiancha().isEmpty()) {
log.info("处理实时自检消息: deviceSn={}, messageId={}, checkBody={}", deviceSn, data.getMessageID(),data.getData().getJiancha());
handlePreCheckLog(deviceSn, data.getMessageID(), droneInfo.getJiancha());
}
} catch (Exception e) {
log.error("处理自检日志失败: deviceSn={}, error={}", deviceSn, e.getMessage(), e);
}
}
private void handlePreCheckLog(String deviceSn, String messageID, String jianchaJson) {
try {
FlightEntity flight;
if (messageID != null && !messageID.isEmpty()) {
flight = flightService.getOrCreateFlightByMessageId(deviceSn, messageID);
} else {
flight = flightService.getOrCreateCurrentFlight(deviceSn);
}
JSONArray checkItems = JSON.parseArray(jianchaJson);
if (checkItems != null && !checkItems.isEmpty()) {
for (int i = 0; i < checkItems.size(); i++) {
JSONObject item = checkItems.getJSONObject(i);
PreCheckLogEntity logEntity = new PreCheckLogEntity();
logEntity.setFlightId(flight.getFlightId());
String check = item.getString("check");
String value = item.getString("value");
Boolean result = item.getBoolean("result");
String statusText = result != null && result ? "自检成功" : "自检失败";
String logContent = check + " " + value + " " + statusText;
logEntity.setLogContent(logContent);
logEntity.setSuccess(result != null ? result : false);
flightService.insertPreCheckLog(logEntity);
}
log.info("保存自检日志: deviceSn={}, flightId={}, 检查项数量={}",
deviceSn, flight.getFlightId(), checkItems.size());
}
} catch (Exception e) {
log.error("保存自检日志失败: deviceSn={}, jiancha={}, error={}",
deviceSn, jianchaJson, e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,158 @@
package com.ruoyi.device.service.impl;
import com.ruoyi.device.mapper.FlightLogMapper;
import com.ruoyi.device.mapper.FlightMapper;
import com.ruoyi.device.mapper.PreCheckLogMapper;
import com.ruoyi.device.mapper.entity.FlightEntity;
import com.ruoyi.device.mapper.entity.FlightLogEntity;
import com.ruoyi.device.mapper.entity.PreCheckLogEntity;
import com.ruoyi.device.service.FlightService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 飞行服务实现类
*
* @author ruoyi
* @date 2026-02-25
*/
@Slf4j
@Service
public class FlightServiceImpl implements FlightService
{
@Autowired
private FlightMapper flightMapper;
@Autowired
private PreCheckLogMapper preCheckLogMapper;
@Autowired
private FlightLogMapper flightLogMapper;
@Override
public FlightEntity getOrCreateCurrentFlight(String deviceSn) {
FlightEntity flight = flightMapper.selectLatestFlightByDeviceSn(deviceSn);
if (flight == null || "已返航".equals(flight.getStatus())) {
flight = createFlight(deviceSn);
}
return flight;
}
@Override
@Transactional(rollbackFor = Exception.class)
public FlightEntity getOrCreateFlightByMessageId(String deviceSn, String messageId) {
FlightEntity flight = flightMapper.selectLatestFlightByDeviceSn(deviceSn);
if (flight == null) {
flight = createFlight(deviceSn);
if (messageId != null && !messageId.isEmpty()) {
updateFlightIdExternal(flight.getFlightId(), messageId);
}
return flight;
}
String existingFlightIdExternal = flight.getFlightIdExternal();
if (existingFlightIdExternal == null || existingFlightIdExternal.isEmpty()) {
if (messageId != null && !messageId.isEmpty()) {
updateFlightIdExternal(flight.getFlightId(), messageId);
}
return flight;
}
if (existingFlightIdExternal.equals(messageId)) {
return flight;
}
flight = createFlight(deviceSn);
if (messageId != null && !messageId.isEmpty()) {
updateFlightIdExternal(flight.getFlightId(), messageId);
}
log.info("messageId不同创建新飞行 - deviceSn={}, flightId={}, oldMessageId={}, newMessageId={}",
deviceSn, flight.getFlightId(), existingFlightIdExternal, messageId);
return flight;
}
@Override
public FlightEntity getLatestFlight(String deviceSn) {
return flightMapper.selectLatestFlightByDeviceSn(deviceSn);
}
@Override
@Transactional(rollbackFor = Exception.class)
public FlightEntity createFlight(String deviceSn) {
FlightEntity flight = new FlightEntity();
flight.setDeviceSn(deviceSn);
flight.setStatus("自检中");
flightMapper.insertFlight(flight);
log.info("创建新的飞行记录: deviceSn={}, flightId={}", deviceSn, flight.getFlightId());
return flight;
}
@Override
public void updateFlightIdExternal(Long flightId, String flightIdExternal) {
FlightEntity flight = new FlightEntity();
flight.setFlightId(flightId);
flight.setFlightIdExternal(flightIdExternal);
flightMapper.updateFlight(flight);
log.info("更新飞行ID: flightId={}, flightIdExternal={}", flightId, flightIdExternal);
}
@Override
public void updateFlightStatus(Long flightId, String status) {
flightMapper.updateFlightStatus(flightId, status);
log.info("更新飞行状态: flightId={}, status={}", flightId, status);
if ("已返航".equals(status)) {
flightMapper.updateReturnTime(flightId);
log.info("更新返航时间: flightId={}", flightId);
}
}
@Override
public void updateReturnTime(Long flightId) {
flightMapper.updateReturnTime(flightId);
}
@Override
public Map<String, Object> getLatestFlightWithLogs(String deviceSn) {
FlightEntity flight = flightMapper.selectLatestFlightByDeviceSn(deviceSn);
if (flight == null) {
return null;
}
Map<String, Object> result = new HashMap<>();
result.put("flightId", flight.getFlightId());
result.put("deviceSn", flight.getDeviceSn());
result.put("flightIdExternal", flight.getFlightIdExternal());
result.put("status", flight.getStatus());
result.put("returnTime", flight.getReturnTime());
result.put("createTime", flight.getCreateTime());
List<PreCheckLogEntity> preCheckLogs = preCheckLogMapper.selectPreCheckLogListByFlightId(flight.getFlightId());
result.put("preCheckLogs", preCheckLogs);
List<FlightLogEntity> flightLogs = flightLogMapper.selectFlightLogListByFlightId(flight.getFlightId());
result.put("flightLogs", flightLogs);
return result;
}
@Override
public void insertPreCheckLog(PreCheckLogEntity logEntity) {
preCheckLogMapper.insertPreCheckLog(logEntity);
}
@Override
public void insertFlightLog(FlightLogEntity logEntity) {
flightLogMapper.insertFlightLog(logEntity);
}
}

View File

@ -0,0 +1,78 @@
package com.ruoyi.device.service.impl;
import com.ruoyi.device.domain.impl.machine.state.DroneState;
import com.ruoyi.device.domain.impl.machine.state.MachineStates;
import com.ruoyi.device.domain.impl.machine.statemachine.StateChangeListener;
import com.ruoyi.device.mapper.entity.FlightEntity;
import com.ruoyi.device.service.FlightService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 飞行状态变化监听器
* 监听状态机的状态变化更新飞行表中的状态
*
* @author ruoyi
* @date 2026-02-25
*/
@Slf4j
@Component
public class FlightStateChangeListener implements StateChangeListener {
@Autowired
private FlightService flightService;
@Autowired
private com.ruoyi.device.domain.impl.machine.statemachine.MachineStateManager stateManager;
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
stateManager.registerStateChangeListener("flight-state-changer", this);
log.info("飞行状态变化监听器已注册");
}
@Override
public void onStateChange(String sn, MachineStates newStates) {
try {
DroneState droneState = newStates.getDroneState();
if (droneState == DroneState.UNKNOWN) {
return;
}
FlightEntity flight = flightService.getOrCreateCurrentFlight(sn);
if (flight == null) {
return;
}
String currentStatus = flight.getStatus();
String newStatus = mapDroneStateToFlightStatus(droneState);
if (!currentStatus.equals(newStatus)) {
flightService.updateFlightStatus(flight.getFlightId(), newStatus);
log.info("状态变化更新飞行状态: sn={}, droneState={}, flightStatus={}",
sn, droneState, newStatus);
}
} catch (Exception e) {
log.error("状态变化监听器处理失败: sn={}, error={}", sn, e.getMessage(), e);
}
}
private String mapDroneStateToFlightStatus(DroneState droneState) {
switch (droneState) {
case ONLINE:
return "自检中";
case FLYING:
return "飞行中";
case ARRIVED:
return "已返航";
case RETURNING:
return "已返航";
case UNKNOWN:
default:
return "自检中";
}
}
}

View File

@ -66,6 +66,12 @@ public class TuohengService {
@Autowired
private MqttCallbackRegistry mqttCallbackRegistry;
@Autowired
private FlightEventCallback flightEventCallback;
@Autowired
private FlightLogCallback flightLogCallback;
private final ObjectMapper objectMapper = new ObjectMapper();
/**
@ -99,7 +105,7 @@ public class TuohengService {
TuohengMqttMessageHandler handler = clientManager.getHandler();
// 设置 MqttCallbackRegistry handler用于指令回调
handler.setMqttCallbackRegistry(mqttCallbackRegistry);
handler.setMachineCallBackRegistry(mqttCallbackRegistry);
Map<String, String> mapping = loadAirportDroneMapping();
@ -183,6 +189,10 @@ public class TuohengService {
}
});
handler.registerDroneRealTimeCallback(flightLogCallback);
handler.registerAirportFlyControlDataCallback(flightEventCallback);
log.info("TuohengService 初始化完成,已注册所有回调");
}

View File

@ -0,0 +1,100 @@
package com.ruoyi.device.websocket;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.device.mapper.entity.FlightLogEntity;
import com.ruoyi.device.service.FlightService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 飞行日志WebSocket
*
* @author ruoyi
* @date 2026-02-25
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/flightLog/{deviceSn}")
public class FlightLogWebSocket {
private Session session;
private String deviceSn;
private static final CopyOnWriteArraySet<FlightLogWebSocket> sessions = new CopyOnWriteArraySet<>();
private static final Map<String, FlightLogWebSocket> sessionMap = new ConcurrentHashMap<>();
@Autowired
private FlightService flightService;
@OnOpen
public void onOpen(Session session, @PathParam("deviceSn") String deviceSn) {
this.session = session;
this.deviceSn = deviceSn;
sessions.add(this);
sessionMap.put(session.getId(), this);
log.info("飞行日志WebSocket连接建立: sessionId={}, deviceSn={}", session.getId(), deviceSn);
}
@OnClose
public void onClose() {
sessions.remove(this);
if (session != null) {
sessionMap.remove(session.getId());
log.info("飞行日志WebSocket连接关闭: sessionId={}, deviceSn={}", session.getId(), deviceSn);
} else {
log.info("飞行日志WebSocket连接关闭: session为null");
}
}
@OnMessage
public void onMessage(String message) {
log.info("收到飞行日志WebSocket消息: sessionId={}, message={}", session.getId(), message);
}
@OnError
public void onError(Session session, Throwable error) {
log.error("飞行日志WebSocket错误: sessionId={}, deviceSn={}, error={}",
session.getId(), deviceSn, error.getMessage(), error);
}
private void sendMessage(String message) {
try {
if (session != null && session.isOpen()) {
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
log.error("发送飞行日志WebSocket消息失败: deviceSn={}, error={}", deviceSn, e.getMessage(), e);
}
}
@Scheduled(fixedRate = 3000)
public void broadcast() {
for (FlightLogWebSocket ws : sessions) {
Map<String, Object> flightData = flightService.getLatestFlightWithLogs(ws.deviceSn);
if (flightData == null) {
continue;
}
String status = (String) flightData.get("status");
List<FlightLogEntity> logs = (List<FlightLogEntity>) flightData.get("flightLogs");
Map<String, Object> response = new ConcurrentHashMap<>();
response.put("status", status);
response.put("logs", logs);
ws.sendMessage(JSON.toJSONString(response));
}
}
}

View File

@ -0,0 +1,101 @@
package com.ruoyi.device.websocket;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.device.mapper.entity.PreCheckLogEntity;
import com.ruoyi.device.service.FlightService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 自检日志WebSocket
*
* @author ruoyi
* @date 2026-02-25
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/preCheckLog/{deviceSn}")
public class PreCheckLogWebSocket {
private Session session;
private String deviceSn;
private static final CopyOnWriteArraySet<PreCheckLogWebSocket> sessions = new CopyOnWriteArraySet<>();
private static final Map<String, PreCheckLogWebSocket> sessionMap = new ConcurrentHashMap<>();
@Autowired
private FlightService flightService;
@OnOpen
public void onOpen(Session session, @PathParam("deviceSn") String deviceSn) {
this.session = session;
this.deviceSn = deviceSn;
sessions.add(this);
sessionMap.put(session.getId(), this);
log.info("自检日志WebSocket连接建立: sessionId={}, deviceSn={}", session.getId(), deviceSn);
}
@OnClose
public void onClose() {
sessions.remove(this);
if (session != null) {
sessionMap.remove(session.getId());
log.info("自检日志WebSocket连接关闭: sessionId={}, deviceSn={}", session.getId(), deviceSn);
} else {
log.info("自检日志WebSocket连接关闭: session为null");
}
}
@OnMessage
public void onMessage(String message) {
log.info("收到自检日志WebSocket消息: sessionId={}, message={}", session.getId(), message);
}
@OnError
public void onError(Session session, Throwable error) {
log.error("自检日志WebSocket错误: sessionId={}, deviceSn={}, error={}",
session.getId(), deviceSn, error.getMessage(), error);
}
private void sendMessage(String message) {
try {
if (session != null && session.isOpen()) {
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
log.error("发送自检日志WebSocket消息失败: deviceSn={}, error={}", deviceSn, e.getMessage(), e);
}
}
@Scheduled(fixedRate = 3000)
public void broadcast() {
for (PreCheckLogWebSocket ws : sessions) {
Map<String, Object> flightData = flightService.getLatestFlightWithLogs(ws.deviceSn);
if (flightData == null) {
continue;
}
String status = (String) flightData.get("status");
List<PreCheckLogEntity> logs = (List<PreCheckLogEntity>) flightData.get("preCheckLogs");
Map<String, Object> response = new ConcurrentHashMap<>();
response.put("status", status);
response.put("logs", logs);
ws.sendMessage(JSON.toJSONString(response));
}
}
}

View File

@ -0,0 +1,48 @@
-- ============================================================
-- Flyway Migration Script
-- Version: V3
-- Description: Create flight log tables (flight, pre_check_log, flight_log)
-- Author: ruoyi
-- Date: 2026-02-25
-- ============================================================
-- 创建飞行表
CREATE TABLE IF NOT EXISTS device_flight (
flight_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '飞行主键',
device_sn VARCHAR(100) NOT NULL COMMENT '无人机SN',
flight_id_external VARCHAR(100) COMMENT '外部飞行ID (来自MQTT的taskId)',
status VARCHAR(20) COMMENT '状态:自检中、飞行中、已返航',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
return_time DATETIME COMMENT '返航时间',
create_by VARCHAR(64) COMMENT '创建者',
update_by VARCHAR(64) COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
remark VARCHAR(500) COMMENT '备注',
PRIMARY KEY (flight_id),
KEY idx_flight_device_sn (device_sn),
KEY idx_flight_id_external (flight_id_external),
KEY idx_flight_status (status),
KEY idx_flight_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='飞行表';
-- 创建自检日志表
CREATE TABLE IF NOT EXISTS device_pre_check_log (
log_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '自检日志主键',
flight_id BIGINT NOT NULL COMMENT '关联飞行表ID',
log_content TEXT COMMENT '日志内容 (JSON字符串)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (log_id),
KEY idx_pre_check_flight_id (flight_id),
KEY idx_pre_check_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自检日志表';
-- 创建飞行日志表
CREATE TABLE IF NOT EXISTS device_flight_log (
log_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '飞行日志主键',
flight_id BIGINT NOT NULL COMMENT '关联飞行表ID',
log_content TEXT COMMENT '日志内容',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (log_id),
KEY idx_flight_log_flight_id (flight_id),
KEY idx_flight_log_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='飞行日志表';

View File

@ -0,0 +1,11 @@
-- ============================================================
-- Flyway Migration Script
-- Version: V6
-- Description: Add success field to device_pre_check_log table
-- Author: ruoyi
-- Date: 2026-02-25
-- ============================================================
ALTER TABLE device_pre_check_log
ADD COLUMN success TINYINT(1) DEFAULT 0 COMMENT '是否成功 (1=成功, 0=失败)'
AFTER log_content;

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.device.mapper.FlightLogMapper">
<resultMap type="com.ruoyi.device.mapper.entity.FlightLogEntity" id="FlightLogResult">
<result property="logId" column="log_id" />
<result property="flightId" column="flight_id" />
<result property="logContent" column="log_content" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectFlightLogVo">
select log_id, flight_id, log_content,
create_by, create_time, update_by, update_time, remark
from device_flight_log
</sql>
<select id="selectFlightLogByLogId" parameterType="Long" resultMap="FlightLogResult">
<include refid="selectFlightLogVo"/>
where log_id = #{logId}
</select>
<select id="selectFlightLogListByFlightId" parameterType="Long" resultMap="FlightLogResult">
<include refid="selectFlightLogVo"/>
where flight_id = #{flightId}
order by create_time desc
</select>
<insert id="insertFlightLog" parameterType="com.ruoyi.device.mapper.entity.FlightLogEntity" useGeneratedKeys="true" keyProperty="logId" keyColumn="log_id">
insert into device_flight_log
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="flightId != null">flight_id,</if>
<if test="logContent != null and logContent != ''">log_content,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="remark != null and remark != ''">remark,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="flightId != null">#{flightId},</if>
<if test="logContent != null and logContent != ''">#{logContent},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null and remark != ''">#{remark},</if>
now()
</trim>
</insert>
</mapper>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.device.mapper.FlightMapper">
<resultMap type="com.ruoyi.device.mapper.entity.FlightEntity" id="FlightResult">
<result property="flightId" column="flight_id" />
<result property="deviceSn" column="device_sn" />
<result property="flightIdExternal" column="flight_id_external" />
<result property="status" column="status" />
<result property="returnTime" column="return_time" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectFlightVo">
select flight_id, device_sn, flight_id_external, status, return_time,
create_by, create_time, update_by, update_time, remark
from device_flight
</sql>
<select id="selectFlightByFlightId" parameterType="Long" resultMap="FlightResult">
<include refid="selectFlightVo"/>
where flight_id = #{flightId}
</select>
<select id="selectLatestFlightByDeviceSn" parameterType="String" resultMap="FlightResult">
<include refid="selectFlightVo"/>
where device_sn = #{deviceSn}
order by create_time desc
limit 1
</select>
<select id="selectFlightByDeviceSnAndStatus" resultMap="FlightResult">
<include refid="selectFlightVo"/>
where device_sn = #{deviceSn}
and status = #{status}
order by create_time desc
limit 1
</select>
<insert id="insertFlight" parameterType="com.ruoyi.device.mapper.entity.FlightEntity" useGeneratedKeys="true" keyProperty="flightId" keyColumn="flight_id">
insert into device_flight
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="deviceSn != null and deviceSn != ''">device_sn,</if>
<if test="flightIdExternal != null and flightIdExternal != ''">flight_id_external,</if>
<if test="status != null and status != ''">status,</if>
<if test="returnTime != null">return_time,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="remark != null and remark != ''">remark,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deviceSn != null and deviceSn != ''">#{deviceSn},</if>
<if test="flightIdExternal != null and flightIdExternal != ''">#{flightIdExternal},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="returnTime != null">#{returnTime},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null and remark != ''">#{remark},</if>
now()
</trim>
</insert>
<update id="updateFlight" parameterType="com.ruoyi.device.mapper.entity.FlightEntity">
update device_flight
<trim prefix="SET" suffixOverrides=",">
<if test="flightIdExternal != null and flightIdExternal != ''">flight_id_external = #{flightIdExternal},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="returnTime != null">return_time = #{returnTime},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="remark != null">remark = #{remark},</if>
update_time = now()
</trim>
where flight_id = #{flightId}
</update>
<update id="updateFlightStatus">
update device_flight
set status = #{status},
update_time = now()
where flight_id = #{flightId}
</update>
<update id="updateReturnTime">
update device_flight
set return_time = now(),
update_time = now()
where flight_id = #{flightId}
</update>
</mapper>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.device.mapper.PreCheckLogMapper">
<resultMap type="com.ruoyi.device.mapper.entity.PreCheckLogEntity" id="PreCheckLogResult">
<result property="logId" column="log_id" />
<result property="flightId" column="flight_id" />
<result property="logContent" column="log_content" />
<result property="success" column="success" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectPreCheckLogVo">
select log_id, flight_id, log_content, success,
create_by, create_time, update_by, update_time, remark
from device_pre_check_log
</sql>
<select id="selectPreCheckLogByLogId" parameterType="Long" resultMap="PreCheckLogResult">
<include refid="selectPreCheckLogVo"/>
where log_id = #{logId}
</select>
<select id="selectPreCheckLogListByFlightId" parameterType="Long" resultMap="PreCheckLogResult">
<include refid="selectPreCheckLogVo"/>
where flight_id = #{flightId}
order by create_time desc
</select>
<insert id="insertPreCheckLog" parameterType="com.ruoyi.device.mapper.entity.PreCheckLogEntity" useGeneratedKeys="true" keyProperty="logId" keyColumn="log_id">
insert into device_pre_check_log
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="flightId != null">flight_id,</if>
<if test="logContent != null and logContent != ''">log_content,</if>
<if test="success != null">success,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="remark != null and remark != ''">remark,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="flightId != null">#{flightId},</if>
<if test="logContent != null and logContent != ''">#{logContent},</if>
<if test="success != null">#{success},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null and remark != ''">#{remark},</if>
now()
</trim>
</insert>
<insert id="insertPreCheckLogBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="logId" keyColumn="log_id">
insert into device_pre_check_log (flight_id, log_content, success, create_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.flightId}, #{item.logContent}, #{item.success}, now())
</foreach>
</insert>
</mapper>