Compare commits

..

No commits in common. "1.0.0-multi" and "master" have entirely different histories.

396 changed files with 0 additions and 64719 deletions

View File

@ -1,151 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.multictrl</groupId>
<artifactId>Dock-MultiCtrl</artifactId>
<version>5.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>admin</artifactId>
<packaging>jar</packaging>
<description>admin</description>
<properties>
<quartz.version>2.5.2</quartz.version>
<shiro.version>2.1.0</shiro.version>
<captcha.version>1.6.2</captcha.version>
<easyexcel.version>3.2.1</easyexcel.version>
<minio.version>8.6.0</minio.version>
<okhttp.version>4.9.2</okhttp.version>
<mqtt.version>6.3.1</mqtt.version>
<influxdb.version>7.2.0</influxdb.version>
<dom4j.version>1.6.1</dom4j.version>
<metadata.version>2.19.0</metadata.version>
<spatial4j.version>0.8</spatial4j.version>
</properties>
<dependencies>
<dependency>
<groupId>com.multictrl</groupId>
<artifactId>common</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>com.multictrl</groupId>
<artifactId>dynamic-datasource</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<classifier>jakarta</classifier>
<version>${shiro.version}</version>
<!-- 排除仍使用了javax.servlet的依赖 -->
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入适配jakarta的依赖包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<classifier>jakarta</classifier>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<classifier>jakarta</classifier>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>${captcha.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- mqtt -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
<version>${mqtt.version}</version>
</dependency>
<!-- influxdb -->
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>${influxdb.version}</version>
</dependency>
<!-- dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>${dom4j.version}</version>
</dependency>
<!-- metadata -->
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>${metadata.version}</version>
</dependency>
<!-- spatial4j -->
<dependency>
<groupId>org.locationtech.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
<version>${spatial4j.version}</version>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<finalName>dj-multictrl-admin</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,45 +0,0 @@
package com.multictrl;
import com.multictrl.common.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.Date;
/**
* DJ-MultiCtrl
*
* @author Sdy
*/
@Slf4j
@EnableScheduling
@EnableFeignClients
@SpringBootApplication
public class AdminApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AdminApplication.class);
}
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady(ApplicationReadyEvent event) {
log.info("\n" +
"====================================================================================================================\n" +
" [DJ-MultiCtrl] 系统启动完成!\n" +
" 当前时间: " + DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN) + "\n" +
"====================================================================================================================\n");
}
}

View File

@ -1,29 +0,0 @@
package com.multictrl.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Swagger文档目录排序注解
* <p>
* 用法在Controller类上与@Tag一起使用value值越小越靠前
* <pre>{@code
* @Tag(name = "机库信息表")
* @ApiOrder(1)
* }</pre>
*
* @author Sdy
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiOrder {
/**
* 排序序号数值越小显示越靠前
*/
int value() default 9999;
}

View File

@ -1,29 +0,0 @@
package com.multictrl.common.annotation;
import java.lang.annotation.*;
/**
* 数据过滤注解
*
* @author Sdy
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataFilter {
/**
* 表的别名
*/
String tableAlias() default "";
/**
* 用户ID
*/
String userId() default "creator";
/**
* 部门ID
*/
String deptId() default "dept_id";
}

View File

@ -1,20 +0,0 @@
package com.multictrl.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 操作日志注解
*
* @author Sdy
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogOperation {
String value() default "";
}

View File

@ -1,99 +0,0 @@
package com.multictrl.common.aspect;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.multictrl.common.annotation.DataFilter;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.exception.RenException;
import com.multictrl.common.interceptor.DataScope;
import com.multictrl.modules.security.user.SecurityUser;
import com.multictrl.modules.security.user.UserDetail;
import com.multictrl.modules.sys.enums.SuperAdminEnum;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
/**
* 数据过滤切面处理类
*
* @author Sdy
*/
@Aspect
@Component
public class DataFilterAspect {
@Pointcut("@annotation(com.multictrl.common.annotation.DataFilter)")
public void dataFilterCut() {
}
@Before("dataFilterCut()")
public void dataFilter(JoinPoint point) {
Object params = point.getArgs()[0];
if (params instanceof Map) {
UserDetail user = SecurityUser.getUser();
//如果是超级管理员则不进行数据过滤
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
return;
}
try {
//否则进行数据过滤
Map map = (Map) params;
String sqlFilter = getSqlFilter(user, point);
map.put(Constant.SQL_FILTER, new DataScope(sqlFilter));
} catch (Exception ignored) {
}
return;
}
throw new RenException(ErrorCode.DATA_SCOPE_PARAMS_ERROR);
}
/**
* 获取数据过滤的SQL
*/
private String getSqlFilter(UserDetail user, JoinPoint point) throws Exception {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = point.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getParameterTypes());
DataFilter dataFilter = method.getAnnotation(DataFilter.class);
//获取表的别名
String tableAlias = dataFilter.tableAlias();
if (StrUtil.isNotBlank(tableAlias)) {
tableAlias += ".";
}
StringBuilder sqlFilter = new StringBuilder();
sqlFilter.append(" (");
//部门ID列表
List<Long> deptIdList = user.getDeptIdList();
if (CollUtil.isNotEmpty(deptIdList)) {
sqlFilter.append(tableAlias).append(dataFilter.deptId());
sqlFilter.append(" in(").append(CollUtil.join(deptIdList, ",")).append(")");
}
//查询本人数据
if (CollUtil.isNotEmpty(deptIdList)) {
sqlFilter.append(" or ");
}
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getId());
sqlFilter.append(")");
return sqlFilter.toString();
}
}

View File

@ -1,102 +0,0 @@
package com.multictrl.common.aspect;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.utils.HttpContextUtils;
import com.multictrl.common.utils.IpUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.log.entity.SysLogOperationEntity;
import com.multictrl.modules.log.enums.OperationStatusEnum;
import com.multictrl.modules.log.service.SysLogOperationService;
import com.multictrl.modules.security.user.SecurityUser;
import com.multictrl.modules.security.user.UserDetail;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 操作日志切面处理类
*
* @author Sdy
*/
@Aspect
@Component
@AllArgsConstructor
public class LogOperationAspect {
private final SysLogOperationService sysLogOperationService;
@Pointcut("@annotation(com.multictrl.common.annotation.LogOperation)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
try {
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveLog(point, time, OperationStatusEnum.SUCCESS.value());
return result;
} catch (Exception e) {
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveLog(point, time, OperationStatusEnum.FAIL.value());
throw e;
}
}
private void saveLog(ProceedingJoinPoint joinPoint, long time, Integer status) throws Exception {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getParameterTypes());
LogOperation annotation = method.getAnnotation(LogOperation.class);
SysLogOperationEntity log = new SysLogOperationEntity();
if (annotation != null) {
//注解上的描述
log.setOperation(annotation.value());
}
//登录用户信息
UserDetail user = SecurityUser.getUser();
log.setCreatorName(user.getUsername());
log.setStatus(status);
log.setRequestTime((int) time);
//请求相关信息
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
if (request != null) {
log.setIp(IpUtils.getIpAddr(request));
log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
log.setRequestUri(request.getRequestURI());
log.setRequestMethod(request.getMethod());
}
//请求参数
Object[] args = joinPoint.getArgs();
try {
String params = JsonUtils.toJsonString(args[0]);
log.setRequestParams(params);
} catch (Exception ignored) {
}
//保存到DB
sysLogOperationService.save(log);
}
}

View File

@ -1,25 +0,0 @@
package com.multictrl.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 大疆机场属性配置
*
* @author Sdy
* @since 1.0.0 2026/4/22
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "dji")
public class DJIConfig {
private String appId;
private String appKey;
private String appLicense;
private String ntpServerHost;
private Integer ntpServerPort;
private String organizationName = "dji";
private String organizationId = "djiId";
private String deviceBindCode = "djiCode";
}

View File

@ -1,69 +0,0 @@
package com.multictrl.common.config;
import com.influxdb.LogLevel;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.InfluxDBClientOptions;
import lombok.Data;
import okhttp3.ConnectionPool;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* influxdb配置
*
* @author Sdy
* @since 1.0.0 2026/5/3
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "influx")
public class InfluxConfig {
private Boolean saveOpen;
private String url;
private String token;
private String org;
private String bucket;
@Bean
public InfluxDBClient influxDBClient() {
// 1. 构建高性能 OkHttpClient连接池+重试
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(ConnectionSpec.CLEARTEXT))
.connectTimeout(Duration.ofSeconds(15))
.readTimeout(Duration.ofSeconds(300))
.writeTimeout(Duration.ofSeconds(120))
.connectionPool(new ConnectionPool(
20,
300,
TimeUnit.SECONDS
))
.retryOnConnectionFailure(true)
.addInterceptor(new HttpLoggingInterceptor(message ->
LoggerFactory.getLogger("InfluxDB").debug("HTTP: {}", message)
).setLevel(HttpLoggingInterceptor.Level.NONE));
// 2. 通过 Options 精确配置
InfluxDBClientOptions options = InfluxDBClientOptions.builder()
.url(url)
.authenticateToken(token.toCharArray())
.org(org)
.bucket(bucket)
.okHttpClient(okHttpClient)
.build();
InfluxDBClient client = InfluxDBClientFactory.create(options);
client.setLogLevel(LogLevel.NONE);
return client;
}
}

View File

@ -1,36 +0,0 @@
package com.multictrl.common.config;
import io.minio.MinioClient;
import lombok.*;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* minio配置类
*
* @author Sdy
* @since 1.0.0 2026/4/16
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private Other other = new Other();
@Data
public static class Other {
private String dataPath;
}
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}

View File

@ -1,154 +0,0 @@
package com.multictrl.common.config;
import com.multictrl.common.handler.Mqtt1MessageHandler;
import com.multictrl.common.handler.Mqtt2MessageHandler;
import lombok.Data;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
/**
* MQTT双客户端配置
*/
@Data
@Configuration
@EnableIntegration
@ConfigurationProperties(prefix = "mqtt")
public class MqttConfig {
private Client client1 = new Client();
private Client client2 = new Client();
@Data
public static class Client {
private String url;
private String username;
private String password;
private String subClientId;
private String subTopic;
private String pubClientId;
}
// ==================== 通用工厂方法 ====================
private DefaultMqttPahoClientFactory createFactory(Client c) {
MqttConnectOptions opt = new MqttConnectOptions();
opt.setCleanSession(true);
opt.setServerURIs(new String[]{c.getUrl()});
opt.setUserName(c.getUsername());
opt.setPassword(c.getPassword().toCharArray());
opt.setConnectionTimeout(10);
opt.setKeepAliveInterval(60);
opt.setAutomaticReconnect(true);
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(opt);
return factory;
}
private MqttPahoMessageDrivenChannelAdapter createInbound(Client c, MessageChannel channel) {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
c.getSubClientId(), createFactory(c), splitTopics(c.getSubTopic()));
adapter.setQos(2);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setOutputChannel(channel);
adapter.setCompletionTimeout(5000);
return adapter;
}
private MessageHandler createOutbound(Client c) {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler(c.getPubClientId(), createFactory(c));
handler.setDefaultQos(2);
handler.setDefaultTopic("/default/topic");
handler.setAsync(true);
handler.setCompletionTimeout(5000);
return handler;
}
private String[] splitTopics(String topics) {
if (topics == null || topics.isBlank()) return new String[0];
return java.util.Arrays.stream(topics.split(",")).map(String::trim).filter(t -> !t.isEmpty()).toArray(String[]::new);
}
// ==================== 客户端1 ====================
@Bean("mqttInputChannel1")
public MessageChannel mqttInputChannel1() {
return new DirectChannel();
}
@Bean("mqttInboundAdapter1")
public MqttPahoMessageDrivenChannelAdapter mqttInboundAdapter1() {
return createInbound(client1, mqttInputChannel1());
}
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel1")
public MessageHandler messageHandler1(Mqtt1MessageHandler receiverMessageHandler) {
return receiverMessageHandler;
}
@Bean("mqttOutboundChannel1")
public MessageChannel mqttOutboundChannel1() {
return new DirectChannel();
}
@Bean("mqttOutboundHandler1")
@ServiceActivator(inputChannel = "mqttOutboundChannel1")
public MessageHandler mqttOutboundHandler1() {
return createOutbound(client1);
}
/*@Bean("mqttOutboundFlow1")
public IntegrationFlow mqttOutboundFlow1() {
return IntegrationFlow.from(mqttOutboundChannel1())
.handle(mqttOutboundHandler1())
.get();
}*/
// ==================== 客户端2 ====================
@Bean("mqttInputChannel2")
public MessageChannel mqttInputChannel2() {
return new DirectChannel();
}
@Bean("mqttInboundAdapter2")
public MqttPahoMessageDrivenChannelAdapter mqttInboundAdapter2() {
return createInbound(client2, mqttInputChannel2());
}
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel2")
public MessageHandler messageHandler2(Mqtt2MessageHandler receiverMessageHandler) {
return receiverMessageHandler;
}
@Bean("mqttOutboundChannel2")
public MessageChannel mqttOutboundChannel2() {
return new DirectChannel();
}
@Bean("mqttOutboundHandler2")
@ServiceActivator(inputChannel = "mqttOutboundChannel2")
public MessageHandler mqttOutboundHandler2() {
return createOutbound(client2);
}
/*@Bean("mqttOutboundFlow2")
public IntegrationFlow mqttOutboundFlow2() {
return IntegrationFlow.from(mqttOutboundChannel2())
.handle(mqttOutboundHandler2())
.get();
}*/
}

View File

@ -1,31 +0,0 @@
package com.multictrl.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
/**
* MQTT线程池配置 + Channel绑定
*/
@Configuration
public class MqttThreadConfig {
@Bean("mqttExecutor1")
public ExecutorService mqttExecutor1() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(200), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
// JVM关闭时优雅关闭线程池
Runtime.getRuntime().addShutdownHook(new Thread(executor::shutdown));
return executor;
}
@Bean("mqttExecutor2")
public ExecutorService mqttExecutor2() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(200), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
// JVM关闭时优雅关闭线程池
Runtime.getRuntime().addShutdownHook(new Thread(executor::shutdown));
return executor;
}
}

View File

@ -1,35 +0,0 @@
package com.multictrl.common.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.multictrl.common.interceptor.DataFilterInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* mybatis-plus配置
*
* @author Sdy
* @since 1.0.0
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 数据权限
mybatisPlusInterceptor.addInnerInterceptor(new DataFilterInterceptor());
// 分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
// 乐观锁
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表更新与删除
mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return mybatisPlusInterceptor;
}
}

View File

@ -1,29 +0,0 @@
package com.multictrl.common.config;
import feign.auth.BasicAuthRequestInterceptor;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 直播服务器配置
*
* @author Sdy
* @since 1.0.0 2026/5/7
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "srs")
public class SrsConfig {
private String ip;
private String rtmpPort;
private String rtmpToken;
private String username;
private String password;
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor(username, password);
}
}

View File

@ -1,131 +0,0 @@
package com.multictrl.common.config;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.constant.Constant;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Swagger配置
*
* @author Sdy
*/
@Slf4j
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI createRestApi() {
return new OpenAPI()
.info(apiInfo())
.security(security());
}
/**
* 按Controller上的@ApiOrder注解对Swagger Tag排序
*/
@Bean
public OpenApiCustomizer openApiCustomizer(@Lazy ApplicationContext ctx) {
return openApi -> {
Map<String, Integer> tagOrderMap = buildTagOrderMap(ctx);
log.info("=== Swagger Tag Order Map: {} ===", tagOrderMap);
// springdoc 3.x: openApi.getTags() 为空需要从Paths中收集Tag
List<io.swagger.v3.oas.models.tags.Tag> allTags = collectTagsFromPaths(openApi);
log.info("=== Collected Tags: {} ===",
allTags.stream().map(io.swagger.v3.oas.models.tags.Tag::getName).collect(Collectors.toList()));
// @ApiOrder排序
allTags.sort(Comparator.comparingInt(
t -> tagOrderMap.getOrDefault(t.getName(), 9999)
));
openApi.tags(allTags);
};
}
/**
* 从OpenAPI的Paths中收集所有用到的Tag去重
*/
private List<io.swagger.v3.oas.models.tags.Tag> collectTagsFromPaths(OpenAPI openApi) {
Set<String> tagNameSet = new LinkedHashSet<>();
if (openApi.getPaths() != null) {
for (PathItem pathItem : openApi.getPaths().values()) {
for (Operation op : pathItem.readOperations()) {
if (op.getTags() != null) {
tagNameSet.addAll(op.getTags());
}
}
}
}
return tagNameSet.stream()
.map(name -> new io.swagger.v3.oas.models.tags.Tag().name(name))
.collect(Collectors.toList());
}
/**
* 扫描所有带@RestController和@Tag的类读取@ApiOrder值
*/
private Map<String, Integer> buildTagOrderMap(ApplicationContext ctx) {
Map<String, Integer> orderMap = new ConcurrentHashMap<>();
try {
String[] beanNames = ctx.getBeanNamesForAnnotation(RestController.class);
for (String beanName : beanNames) {
Object bean = ctx.getBean(beanName);
Class<?> clazz = bean.getClass();
// 处理CGLIB/JDK代理获取原始类
while (clazz.getName().contains("$$") || clazz.getName().contains("Proxy")) {
Class<?> superClazz = clazz.getSuperclass();
if (superClazz != null && !superClazz.equals(Object.class)) {
clazz = superClazz;
} else {
break;
}
}
ApiOrder apiOrder = AnnotationUtils.findAnnotation(clazz, ApiOrder.class);
Tag tag = AnnotationUtils.findAnnotation(clazz, Tag.class);
if (tag != null && apiOrder != null) {
orderMap.put(tag.name(), apiOrder.value());
log.info("Found ApiOrder: {} -> {}", tag.name(), apiOrder.value());
}
}
} catch (Exception e) {
log.warn("Failed to build tag order map: {}", e.getMessage());
}
return orderMap;
}
private Info apiInfo() {
return new Info()
.title("大疆机场操控系统")
.description("api文档")
.version("5.x");
}
private List<SecurityRequirement> security() {
SecurityRequirement key = new SecurityRequirement();
key.addList(Constant.TOKEN_HEADER, Constant.TOKEN_HEADER);
List<SecurityRequirement> list = new ArrayList<>();
list.add(key);
return list;
}
}

View File

@ -1,23 +0,0 @@
package com.multictrl.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 系统配置信息
*
* @author Sdy
* @since 1.0.0 2026/4/26
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "host")
public class SysConfig {
private String ip;
private String port;
private Boolean isSsl;
}

View File

@ -1,82 +0,0 @@
package com.multictrl.common.constant;
/**
* 业务常量类
*
* @author Sdy
* @since 1.0.0 2026/4/16
*/
public interface BusinessConstant {
//********************************* minio *********************************//
String ROUTE_IMG_BUCKET = "route-images";//航线图片桶
String ROUTE_KMZ_BUCKET = "route-kmz";//航线桶
String DOCK_MEDIA_BUCKET = "dock-media";//机库回传媒体桶
String REMOTE_LOG_BUCKET = "remote-log";//机库回传媒体桶
String DEVICE_FIRMWARE_BUCKET = "device-firmware";
//********************************* route action *********************************//
String DEFAULT_ACTION_TRIGGER_TYPE = "reachPoint";//默认动作触发器类型 到达航点执行
String START_VIDEO_FLAG = "startRecord";//开始录像
String STOP_VIDEO_FLAG = "stopRecord";//停止录像
String EQUAL_TIME_TAKE_PHOTO_FLAG = "multipleTiming";//等时拍照
String EQUAL_DISTANCE_TAKE_PHOTO_FLAG = "multipleDistance";//等距拍照
String FINISH_TAKE_PHOTO_FLAG = "takePhotoFinish";//结束间隔拍照
String TAKE_PHOTO_FLAG = "takePhoto";//拍照
//********************************* dj topic *********************************//
String REQUESTS = "requests";
String STATUS = "status";
String OSD = "osd";
String STATE = "state";
String EVENTS = "events";
String SERVICES_REPLY = "services_reply";
String STORAGE_CONFIG_GET = "storage_config_get";
String _REPLY = "_reply";
//********************************* dj payload key *********************************//
String METHOD = "method";
String GATEWAY = "gateway";
String DATA = "data";
String NEED_REPLY = "need_reply";
//********************************* dj requests topic method *********************************//
String CONFIG = "config";
String AIRPORT_BIND_STATUS = "airport_bind_status";
String AIRPORT_ORGANIZATION_GET = "airport_organization_get";
String AIRPORT_ORGANIZATION_BIND = "airport_organization_bind";
String FLIGHT_TASK_RESOURCE_GET = "flighttask_resource_get";
//私有协议为了确认跳飞场景下妙算pskd设备是否开启成功
String PRIVATE_GET_MULTI_MIAOSUAN_PSDK_STATUS = "private_get_multi_miaosuan_psdk_status";
//********************************* dj events topic method *********************************//
String FLIGHTTASK_PROGRESS = "flighttask_progress";
String TAKEOFF_TO_POINT_PROGRESS = "takeoff_to_point_progress";
String FLY_TO_POINT_PROGRESS = "fly_to_point_progress";
String FILE_UPLOAD_CALLBACK = "file_upload_callback";
String FILEUPLOAD_PROGRESS = "fileupload_progress";
String OTA_PROGRESS = "ota_progress";
String HMS = "hms";
//********************************* dj status topic method *********************************//
String UPDATE_TOPO = "update_topo";
//********************************* dj cache key *********************************//
String DOCK_VIDEO_ID = "dock_video_id_";
String UAV_VIDEO_ID = "uav_video_id_";
String UAV_CAMERA_INDEX = "uav_camera_index_";
String DOCK_OSD = "dock_osd_";
String UAV_OSD = "uav_osd_";
String WORKING_TASK_ID = "working_task_id_";
String IN_FLIGHT_WORKING_TASK_ID = "in_flight_working_task_id_";
String UAV_VIDEO_TYPE = "uav_video_type_";
String FLIGHT_TASK_PROGRESS = "flight_task_progress_";
String FLIGHT_TASK_PROGRESS_STATUS = "flight_task_progress_status_";
String FLIGHT_TASK_PROGRESS_FLIGHT_ID = "flight_task_progress_flight_id_";
String DRC_HEART_BEAT_SN = "DRC_HEART_BEAT_SN";
String DOCK_IN_WORK = "dock_in_work_";
String FLIGHT_TASK_KMZ_FILE_INFO = "flight_task_kmz_file_info_";
String UAV_SPEAKER_INDEX = "uav_speaker_index_";
//********************************* other *********************************//
String HTTP_PROTOCOL = "http://";
String HTTPS_PROTOCOL = "https://";
String TCP_PROTOCOL = "tcp://";
String DRC = "drc_";
String FILE_PATH = "file/";
}

View File

@ -1,14 +0,0 @@
package com.multictrl.common.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum DeviceType {
DOCK("dock", "机场"),
UAV("uav", "飞机");
private final String code;
private final String desc;
}

View File

@ -1,38 +0,0 @@
package com.multictrl.common.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum DockMode {
IDLE(0, "空闲中"),
FIELD_DEBUG(1, "现场调试"),
REMOTE_DEBUG(2, "远程调试"),
FIRMWARE_UPGRADING(3, "固件升级中"),
WORKING(4, "作业中"),
PENDING_CALIBRATION(5, "待标定");
private final Integer code;
private final String desc;
// 根据code获取DockMode
public static DockMode getByCode(Integer code) {
for (DockMode dockMode : DockMode.values()) {
if (dockMode.getCode().equals(code)) {
return dockMode;
}
}
return null;
}
//根据code获取desc
public static String getDescByCode(Integer code) {
for (DockMode dockMode : DockMode.values()) {
if (dockMode.getCode().equals(code)) {
return dockMode.getDesc();
}
}
return null;
}
}

View File

@ -1,21 +0,0 @@
package com.multictrl.common.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum FlightTaskProgressStatus {
CANCELED("canceled", "取消或终止"),
FAILED("failed", "失败"),
IN_PROGRESS("in_progress", "执行中"),
OK("ok", "执行成功"),
PARTIALLY_DONE("partially_done", "部分完成"),
PAUSED("paused", "暂停"),
REJECTED("rejected", "拒绝"),
SENT("sent", "已下发"),
TIMEOUT("timeout", "超时");
private final String code;
private final String desc;
}

View File

@ -1,16 +0,0 @@
package com.multictrl.common.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum FlightTaskStatus {
FAILED(-1, "任务失败"),
IN_PROGRESS(0, "任务进行中"),
FINISHED(1, "任务完成"),
PREVENTED(2, "任务被阻止");
private final Integer code;
private final String desc;
}

View File

@ -1,212 +0,0 @@
package com.multictrl.common.constant;
import com.multictrl.modules.business.dto.WaypointActionDTO;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Element;
/**
* 动作类型
*
* @author Sdy
* @since 1.0.0
*/
@Slf4j
public enum SetActionParams {
takePhoto {//拍照
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
actionActuatorFuncParam.addElement("wpml:payloadPositionIndex").setText("0");
actionActuatorFuncParam.addElement("wpml:fileSuffix").setText("航点" + actionDTO.getWaypointOrder());
actionActuatorFuncParam.addElement("wpml:payloadLensIndex").setText("wide,zoom,ir,visable");
actionActuatorFuncParam.addElement("wpml:useGlobalPayloadLensIndex").setText("1");
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
// String payloadLensIndex = actionActuatorFuncParam.elementText("payloadLensIndex");
}
}, startRecord {//开始录像
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
takePhoto.setActionParam(actionDTO, actionActuatorFuncParam);
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
}
}, stopRecord {//结束录像
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
actionActuatorFuncParam.addElement("wpml:payloadPositionIndex").setText("0");
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
}
}, zoom {//变焦
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
actionActuatorFuncParam.addElement("wpml:payloadPositionIndex").setText("0");
actionActuatorFuncParam.addElement("wpml:focalLength").setText(
Math.round(Double.parseDouble(actionDTO.getActionValue()) * 23.75f) + "");
actionActuatorFuncParam.addElement("wpml:isUseFocalFactor").setText("0");
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
String focalLength = actionActuatorFuncParam.elementText("focalLength");
actionDTO.setActionValue(Math.round(Double.parseDouble(focalLength) / 23.75f) + "");
}
}, hover {//悬停
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
actionActuatorFuncParam.addElement("wpml:hoverTime").setText(actionDTO.getActionValue());
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
String hoverTime = actionActuatorFuncParam.elementText("hoverTime");
actionDTO.setActionValue(hoverTime);
}
}, rotateYaw {//飞行器偏航角
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
actionActuatorFuncParam.addElement("wpml:aircraftHeading").setText(actionDTO.getActionValue());
actionActuatorFuncParam.addElement("wpml:aircraftPathMode").setText("counterClockwise");
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
String aircraftHeading = actionActuatorFuncParam.elementText("aircraftHeading");
actionDTO.setActionValue(aircraftHeading);
}
}, gimbalRotate {//云台俯仰角
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
setGimbalParam(actionActuatorFuncParam);
actionActuatorFuncParam.addElement("wpml:gimbalYawRotateEnable").setText("0");
actionActuatorFuncParam.addElement("wpml:gimbalYawRotateAngle").setText("0");
actionActuatorFuncParam.addElement("wpml:gimbalPitchRotateEnable").setText("1");
actionActuatorFuncParam.addElement("wpml:gimbalPitchRotateAngle").setText(actionDTO.getActionValue());
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
String gimbalPitchRotateAngle = actionActuatorFuncParam.elementText("gimbalPitchRotateAngle");
actionDTO.setActionValue(gimbalPitchRotateAngle);
}
}, gimbalRotate_yaw {//云台偏航角
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
setGimbalParam(actionActuatorFuncParam);
actionActuatorFuncParam.addElement("wpml:gimbalPitchRotateEnable").setText("0");
actionActuatorFuncParam.addElement("wpml:gimbalPitchRotateAngle").setText("0");
actionActuatorFuncParam.addElement("wpml:gimbalYawRotateEnable").setText("1");
actionActuatorFuncParam.addElement("wpml:gimbalYawRotateAngle").setText(actionDTO.getActionValue());
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
String gimbalYawRotateAngle = actionActuatorFuncParam.elementText("gimbalYawRotateAngle");
actionDTO.setActionValue(gimbalYawRotateAngle);
}
}, panoShot {//全景拍照
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
takePhoto.setActionParam(actionDTO, actionActuatorFuncParam);
actionActuatorFuncParam.addElement("wpml:panoShotSubMode").setText("panoShot_360");
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
}
}, multipleTiming {//等时拍照
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
takePhoto.setActionParam(actionDTO, actionActuatorFuncParam);
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
}
}, multipleDistance {//等距拍照
@Override
public void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
takePhoto.setActionParam(actionDTO, actionActuatorFuncParam);
}
@Override
public void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
}
};
/**
* 设置参数
*/
public abstract void setActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam);
/**
* 获取值
*/
public abstract void getActionParam(WaypointActionDTO actionDTO, Element actionActuatorFuncParam);
/**
* 根据传入的动作类型执行对应的方法
*
* @param actionDTO 动作
* @param actionActuatorFuncParam 参数元素
*/
public static void executeAction(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
try {
SetActionParams.valueOf(actionDTO.getActionType()).setActionParam(actionDTO, actionActuatorFuncParam);
} catch (IllegalArgumentException e) {
// 处理未知动作类型的异常情况
System.err.println("未知的动作类型: " + actionDTO.getActionType());
}
}
/**
* 根据传入的动作类型执行对应的方法
*
* @param actionDTO 动作
* @param actionActuatorFuncParam 参数元素
*/
public static void getAction(WaypointActionDTO actionDTO, Element actionActuatorFuncParam) {
try {
SetActionParams.valueOf(actionDTO.getActionType()).getActionParam(actionDTO, actionActuatorFuncParam);
} catch (IllegalArgumentException e) {
// 处理未知动作类型的异常情况
log.error("未知的动作类型: {}", actionDTO.getActionType());
}
}
/**
* 云台参数
*/
private static void setGimbalParam(Element actionActuatorFuncParam) {
actionActuatorFuncParam.addElement("wpml:gimbalRotateMode").setText("absoluteAngle");
actionActuatorFuncParam.addElement("wpml:gimbalRollRotateEnable").setText("0");
actionActuatorFuncParam.addElement("wpml:gimbalRollRotateAngle").setText("0");
actionActuatorFuncParam.addElement("wpml:gimbalRotateTimeEnable").setText("0");
actionActuatorFuncParam.addElement("wpml:gimbalRotateTime").setText("0");
actionActuatorFuncParam.addElement("wpml:payloadPositionIndex").setText("0");
}
}

View File

@ -1,84 +0,0 @@
package com.multictrl.common.exception;
import cn.hutool.core.map.MapUtil;
import com.multictrl.common.utils.HttpContextUtils;
import com.multictrl.common.utils.IpUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.common.utils.Result;
import com.multictrl.modules.log.entity.SysLogErrorEntity;
import com.multictrl.modules.log.service.SysLogErrorService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Map;
/**
* 异常处理器
*
* @author Sdy
* @since 1.0.0
*/
@Slf4j
@RestControllerAdvice
@AllArgsConstructor
public class RenExceptionHandler {
private final SysLogErrorService sysLogErrorService;
/**
* 处理自定义异常
*/
@ExceptionHandler(RenException.class)
public Result<Object> handleRenException(RenException ex) {
Result<Object> result = new Result<Object>();
result.error(ex.getCode(), ex.getMsg());
return result;
}
@ExceptionHandler(DuplicateKeyException.class)
public Result<Object> handleDuplicateKeyException(DuplicateKeyException ex) {
Result<Object> result = new Result<Object>();
result.error(ErrorCode.DB_RECORD_EXISTS);
return result;
}
@ExceptionHandler(Exception.class)
public Result<Object> handleException(Exception ex) {
log.error(ex.getMessage(), ex);
saveLog(ex);
return new Result<Object>().error();
}
/**
* 保存异常日志
*/
private void saveLog(Exception ex) {
SysLogErrorEntity log = new SysLogErrorEntity();
//请求相关信息
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
if (request != null) {
log.setIp(IpUtils.getIpAddr(request));
log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
log.setRequestUri(request.getRequestURI());
log.setRequestMethod(request.getMethod());
Map<String, String> params = HttpContextUtils.getParameterMap(request);
if (MapUtil.isNotEmpty(params)) {
log.setRequestParams(JsonUtils.toJsonString(params));
}
}
//异常信息
log.setErrorInfo(ExceptionUtils.getErrorStackTrace(ex));
//保存
sysLogErrorService.save(log);
}
}

View File

@ -1,50 +0,0 @@
package com.multictrl.common.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.multictrl.modules.security.user.SecurityUser;
import com.multictrl.modules.security.user.UserDetail;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 公共字段自动填充值
*
* @author Sdy
*/
@Component
public class FieldMetaObjectHandler implements MetaObjectHandler {
private final static String CREATE_DATE = "createDate";
private final static String CREATOR = "creator";
private final static String UPDATE_DATE = "updateDate";
private final static String UPDATER = "updater";
private final static String DEPT_ID = "deptId";
@Override
public void insertFill(MetaObject metaObject) {
UserDetail user = SecurityUser.getUser();
Date date = new Date();
//创建者
strictInsertFill(metaObject, CREATOR, Long.class, user.getId());
//创建时间
strictInsertFill(metaObject, CREATE_DATE, Date.class, date);
//创建者所属部门
strictInsertFill(metaObject, DEPT_ID, Long.class, user.getDeptId());
//更新者
strictInsertFill(metaObject, UPDATER, Long.class, user.getId());
//更新时间
strictInsertFill(metaObject, UPDATE_DATE, Date.class, date);
}
@Override
public void updateFill(MetaObject metaObject) {
//更新者
strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId());
//更新时间
strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date());
}
}

View File

@ -1,47 +0,0 @@
package com.multictrl.common.handler;
import com.multictrl.common.exception.ExceptionUtils;
import com.multictrl.modules.business.handler.TopicDistributor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
/**
* MQTT消息处理器
*/
@Slf4j
@Component
public class Mqtt1MessageHandler implements MessageHandler {
@Autowired
@Qualifier("mqttExecutor1")
private ExecutorService executor;
@Autowired
private TopicDistributor topicDistributor;
@Override
public void handleMessage(@NotNull Message<?> message) throws MessagingException {
try {
executor.submit(() -> {
String topic = String.valueOf(message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC));
String payload = message.getPayload().toString();
try {
// log.debug("client1接收到的消息:{} {}", topic, payload);
topicDistributor.route(topic, payload);
} catch (Exception e) {
log.error("消费信息异常: {}", ExceptionUtils.getErrorStackTrace(e));
}
});
} catch (Exception e) {
log.error("handleMessage异常: {}", ExceptionUtils.getErrorStackTrace(e));
}
}
}

View File

@ -1,77 +0,0 @@
package com.multictrl.common.handler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.exception.ExceptionUtils;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.common.utils.Utils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
/**
* MQTT消息处理器
*/
@Slf4j
@Component
public class Mqtt2MessageHandler implements MessageHandler {
@Autowired
@Qualifier("mqttExecutor2")
private ExecutorService executor;
private static final List<String> VALID_METHODS = Arrays.asList(
"drone_emergency_stop",
"drc_speaker_play_volume_set",
"drc_speaker_play_mode_set",
"drc_speaker_tts_set",
"drc_speaker_play_stop",
"drc_speaker_replay",
"drc_speaker_tts_play_start"
);
@Override
public void handleMessage(@NotNull Message<?> message) throws MessagingException {
try {
executor.submit(() -> {
String topic = String.valueOf(message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC));
String payload = message.getPayload().toString();
try {
// log.info("client2接收到的消息:{} {}", topic, message.getPayload());
String method = Utils.getLastTwoSegments(topic, "/");
String afterProduct = StrUtil.subAfter(topic, "product/", false); // "8UUXP1P00A107D/drc/up"
String gateway = StrUtil.subBefore(afterProduct, "/", false);
if ("drc/up".equals(method)) {
log.debug("drc 回复--> topic:{} ,payload:{}", topic, payload);
JSONObject messageJson = JsonUtils.parseObject(payload, JSONObject.class);
if (messageJson != null) {
String methodJson = messageJson.getStr(BusinessConstant.METHOD);
if (VALID_METHODS.contains(methodJson)) {
JSONObject dataJson = messageJson.getJSONObject(BusinessConstant.DATA);
if (dataJson == null) {
dataJson = new JSONObject();
dataJson.set("result", 0);
}
CacheUtils.set(gateway + "_" + methodJson, dataJson);
}
}
}
} catch (Exception e) {
log.error("消费信息异常: {}", ExceptionUtils.getErrorStackTrace(e));
}
});
} catch (Exception e) {
log.error("handleMessage异常: {}", ExceptionUtils.getErrorStackTrace(e));
}
}
}

View File

@ -1,80 +0,0 @@
package com.multictrl.common.interceptor;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Map;
/**
* 数据过滤
*
* @author Sdy
*/
public class DataFilterInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
DataScope scope = getDataScope(parameter);
// 不进行数据过滤
if(scope == null || StrUtil.isBlank(scope.getSqlFilter())){
return;
}
// 拼接新SQL
String buildSql = getSelect(boundSql.getSql(), scope);
// 重写SQL
PluginUtils.mpBoundSql(boundSql).sql(buildSql);
}
private DataScope getDataScope(Object parameter){
if (parameter == null){
return null;
}
// 判断参数里是否有DataScope对象
if (parameter instanceof Map<?, ?> parameterMap) {
for (Map.Entry entry : parameterMap.entrySet()) {
if (entry.getValue() != null && entry.getValue() instanceof DataScope) {
return (DataScope) entry.getValue();
}
}
} else if (parameter instanceof DataScope) {
return (DataScope) parameter;
}
return null;
}
private String getSelect(String buildSql, DataScope scope){
try {
Select select = (Select) CCJSqlParserUtil.parse(buildSql);
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
Expression expression = plainSelect.getWhere();
if(expression == null){
plainSelect.setWhere(new StringValue(scope.getSqlFilter()));
}else{
AndExpression andExpression = new AndExpression(expression, new StringValue(scope.getSqlFilter()));
plainSelect.setWhere(andExpression);
}
return select.toString().replaceAll("'", "");
}catch (JSQLParserException e){
return buildSql;
}
}
}

View File

@ -1,25 +0,0 @@
package com.multictrl.common.interceptor;
import lombok.Getter;
import lombok.Setter;
/**
* 数据范围
*
* @author Sdy
* @since 1.0.0
*/
@Setter
@Getter
public class DataScope {
private String sqlFilter;
public DataScope(String sqlFilter) {
this.sqlFilter = sqlFilter;
}
@Override
public String toString() {
return this.sqlFilter;
}
}

View File

@ -1,50 +0,0 @@
package com.multictrl.common.task;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.modules.business.service.MqttPushService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
* DRC心跳任务
*
* @author Sdy
* @since 1.0.0 2026/4/27
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DrcHeartbeat {
private final MqttPushService mqttPushService;
@Scheduled(fixedRate = 10 * 1000)
public void drcHeartBeat() {
List<String> dockSnList = Optional.ofNullable(CacheUtils.get(BusinessConstant.DRC_HEART_BEAT_SN))
.filter(obj -> obj instanceof List)
.map(obj -> (List<String>) obj)
.orElse(Collections.emptyList());
log.info("drc-->{}, HeartBeat", dockSnList);
for (String dockSn : dockSnList) {
String topic = "thing/product/" + dockSn + "/drc/down";
JSONObject message = new JSONObject();
message.set("timestamp", System.currentTimeMillis());
message.set("method", "heart_beat");
//适配机场1
message.set("seq", System.currentTimeMillis());
JSONObject data = new JSONObject();
data.set("timestamp", System.currentTimeMillis());
message.set("data", data);
mqttPushService.pushMessageByClient2(topic, message.toString());
log.info("drc-->{}, HeartBeat topic:{}, data:{}", dockSn, topic, data);
}
}
}

View File

@ -1,77 +0,0 @@
package com.multictrl.common.utils;
import cn.hutool.cache.impl.TimedCache;
/**
* 缓存工具类
* 支持为每个缓存项单独设置过期时间过期后自动删除
*
* @author Sdy
* @since 1.0.0 2026/4/24
*/
public class CacheUtils {
private static final TimedCache<String, Object> timedCache = new TimedCache<>(1000 * 60 * 60);
static {
// 设置每5分钟自动清理一次过期缓存必须调用
timedCache.schedulePrune(1000 * 60 * 5);
}
/**
* 获取缓存值默认会重置过期时间
*
* @param key 缓存键
* @return 缓存值
*/
public static Object get(String key) {
return timedCache.get(key);
}
/**
* 获取缓存值不重置过期时间
*
* @param key 缓存键
* @return 缓存值
*/
public static Object getWithoutReset(String key) {
return timedCache.get(key, false);
}
/**
* 设置缓存值使用默认过期时间
*
* @param key 缓存键
* @param value 缓存值
*/
public static void set(String key, Object value) {
timedCache.put(key, value);
}
/**
* 设置缓存值并指定过期时间
*
* @param key 缓存键
* @param value 缓存值
* @param timeout 过期时间毫秒
*/
public static void set(String key, Object value, long timeout) {
timedCache.put(key, value, timeout);
}
/**
* 删除缓存
*
* @param key 缓存键
*/
public static void delete(String key) {
timedCache.remove(key);
}
/**
* 清空所有缓存
*/
public static void clear() {
timedCache.clear();
}
}

View File

@ -1,63 +0,0 @@
package com.multictrl.common.utils;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.multictrl.modules.business.dto.error.DJICloudErrorCode;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* DJI错误码
*
* @author Sdy
* @since 1.0.0 2026/4/24
*/
@Slf4j
@Component
public class DJIErrorCodeUtils {
public static Map<String, DJICloudErrorCode> ERROR_CODE_INFO = new HashMap<>();
@PostConstruct
private void init() {
try (InputStream inputStream = this.getClass().getClassLoader()
.getResourceAsStream("dji/dji_cloud_api_error_code.csv")) {
EasyExcel.read(inputStream, DJICloudErrorCode.class,
new AnalysisEventListener<DJICloudErrorCode>() {
@Override
public void invoke(DJICloudErrorCode djiCloudErrorCode,
AnalysisContext analysisContext) {
if (StrUtil.isEmpty(djiCloudErrorCode.getCode())) {
return;
}
ERROR_CODE_INFO.put(djiCloudErrorCode.getCode().trim(), djiCloudErrorCode);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//log.info("dji_cloud_api_error_code--->{}", ERROR_CODE_INFO);
}
}).excelType(ExcelTypeEnum.CSV).sheet().doRead();
log.info("大疆 dji_cloud_api_error_code.csv 初始化成功,长度:{}", ERROR_CODE_INFO.size());
//log.info("dji_cloud_api_error_code.csv 错误码:{}", ERROR_CODE_INFO);
} catch (Exception e) {
log.error("dji_cloud_api_error_code.csv 初始化失败", e);
}
}
/**
* 获取错误码信息
*/
public static DJICloudErrorCode getErrorMsg(String errorCode) {
if (ERROR_CODE_INFO.containsKey(errorCode)) {
return ERROR_CODE_INFO.get(errorCode);
}
return null;
}
}

View File

@ -1,67 +0,0 @@
package com.multictrl.common.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* excel工具类
*
* @author Sdy
*/
public class ExcelUtils {
/**
* Excel导出
*
* @param response response
* @param fileName 文件名
* @param sheetName sheetName
* @param list 数据List
* @param pojoClass 对象Class
*/
public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, List<?> list,
Class<?> pojoClass) throws IOException {
if (StrUtil.isBlank(fileName)) {
//当前日期
fileName = DateUtils.format(new Date());
}
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("UTF-8");
fileName = URLUtil.encode(fileName, StandardCharsets.UTF_8);
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), pojoClass).registerConverter(new LongStringConverter()).sheet(sheetName).doWrite(list);
}
/**
* Excel导出先sourceList转换成List<targetClass>再导出
*
* @param response response
* @param fileName 文件名
* @param sheetName sheetName
* @param sourceList 原数据List
* @param targetClass 目标对象Class
*/
public static void exportExcelToTarget(HttpServletResponse response, String fileName, String sheetName, List<?> sourceList,
Class<?> targetClass) throws Exception {
List<Object> targetList = new ArrayList<>(sourceList.size());
for (Object source : sourceList) {
Object target = targetClass.newInstance();
BeanUtils.copyProperties(source, target);
targetList.add(target);
}
exportExcel(response, fileName, sheetName, targetList, targetClass);
}
}

View File

@ -1,84 +0,0 @@
package com.multictrl.common.utils;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* 图像音频处理
*
* @author Sdy
* @since 1.0.0 2026/4/29
*/
@Slf4j
public class FfmpegUtils {
//视频预览图片帧抽取
private static final String FFMPEG_VIDEO_COVER_IMAGE_COMMAND =
"ffmpeg -i %s -vf \"select=eq(pict_type\\,I)\" -frames:v 1 -pix_fmt yuvj422p -vsync vfr -qscale:v 2 -f image2 %s -y";
/**
* 生成视频封面图片
*/
public static void generateVideoCover(String videoPath, String videoCoverPath) {
File file = new File(videoPath);
if (file.exists()) {
File imageFile = new File(videoCoverPath);
if (!imageFile.exists()) {
String command = String.format(FFMPEG_VIDEO_COVER_IMAGE_COMMAND, videoPath, videoCoverPath);
String[] cmd = {"/bin/sh", "-c", command};
try {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(cmd).inheritIO();
processBuilder.environment().clear();
processBuilder.environment().put("LANG", "en_US.UTF-8");
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
InputStream inputStream = process.getInputStream();
InputStreamReader reader = new InputStreamReader(inputStream, "GBK");
char[] chars = new char[1024];
int len;
while ((len = reader.read(chars)) != -1) {
String string = new String(chars, 0, len);
log.debug("ffmpeg->video->cover : {}", string);
}
reader.close();
inputStream.close();
} catch (Exception e) {
log.error("ffmpeg->video->cover: error", e);
}
log.debug("mp4 file = {} ,image file={} ,执行ffmpeg命令={} , imageFile_exists_flag={}",
file.getAbsolutePath(),
imageFile.getAbsolutePath(), command, imageFile.exists());
}
}
}
/**
* 指令运行
*/
public static void runCommand(String command) {
String[] cmd = {"/bin/sh", "-c", command};
try {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(cmd).inheritIO();
processBuilder.environment().clear();
processBuilder.environment().put("LANG", "en_US.UTF-8");
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
InputStream inputStream = process.getInputStream();
InputStreamReader reader = new InputStreamReader(inputStream, "GBK");
char[] chars = new char[1024];
int len;
while ((len = reader.read(chars)) != -1) {
String string = new String(chars, 0, len);
log.info("ffmpeg->shout->file->cover : {}", string);
}
reader.close();
inputStream.close();
} catch (Exception e) {
log.error("ffmpeg->shout->file->cover: error", e);
}
}
}

View File

@ -1,105 +0,0 @@
package com.multictrl.common.utils;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* HMS健康告警工具类
*
* @author Sdy
* @since 1.0.0 2026/5/10
*/
@Slf4j
@Component
public class HmsUtils {
private static final String HMS_CONFIG_PATH = "dji/hms.json";
private static final String ZH_FIELD = "zh";
private static final String KEY_DELIMITER = "_";
/**
* 转换后的 HMS 映射key = 第三段( 0x1BA10004), value = 中文文本
*/
private static Map<String, String> hmsMap;
@PostConstruct
private void init() {
hmsMap = loadAndTransformHms();
}
//加载并转换 HMS 配置
private Map<String, String> loadAndTransformHms() {
JSONObject rawJson = loadRawJson();
if (rawJson == null || rawJson.isEmpty()) {
log.warn("大疆 hms.json 为空或加载失败,返回空映射");
return Collections.emptyMap();
}
log.info("大疆 hms.json 加载成功,原始条目数:{}", rawJson.size());
Map<String, String> result = new HashMap<>();
for (String key : rawJson.keySet()) {
String[] parts = key.split(KEY_DELIMITER);
// 格式要求至少三段 "fpv_tip_0x1BA10004"
if (parts.length < 3) {
log.debug("跳过格式不正确的key: {}", key);
continue;
}
String code = parts[2]; // 取第三段作为映射的key
JSONObject data = rawJson.getJSONObject(key);
if (data != null && data.containsKey(ZH_FIELD)) {
String zhText = data.getStr(ZH_FIELD);
if (zhText != null && !zhText.isEmpty()) {
result.put(code, zhText);
} else {
log.debug("key: {} 的中文文本为空", key);
}
} else {
log.debug("key: {} 缺少'zh'字段", key);
}
}
log.info("转换完成,有效条目数:{}", result.size());
return Collections.unmodifiableMap(result); // 返回只读视图防止后续意外修改
}
//仅负责从 classpath 读取并解析 JSON返回原始 JSONObject
private JSONObject loadRawJson() {
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(HMS_CONFIG_PATH)) {
if (inputStream == null) {
log.error("资源文件不存在: {}", HMS_CONFIG_PATH);
return null;
}
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
JSONObject json = JSONUtil.parseObj(reader);
if (json == null) {
log.error("解析 hms.json 结果为 null");
}
return json;
}
} catch (IOException e) {
log.error("读取或解析 hms.json 失败", e);
return null;
}
}
/**
* 根据 HMS 代码查找对应的中文文本
*/
public static String getHmsText(String code) {
if (hmsMap.containsKey(code)) {
return hmsMap.get(code);
}
return "未知错误";
}
}

View File

@ -1,58 +0,0 @@
package com.multictrl.common.utils;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.impl.PointImpl;
/**
* 地理空间工具
*
* @author Sdy
* @since 1.0.0 2026/5/6
*/
public class Spatial4jUtils {
// 使用 WGS84 坐标系地球经纬度
private static final SpatialContext ctx = SpatialContext.GEO;
/**
* 计算两个经纬度点之间的球面距离单位
*
* @param lat1 第一个点纬度
* @param lon1 第一个点经度
* @param lat2 第二个点纬度
* @param lon2 第二个点经度
* @return 距离单位
*/
public static double distance(double lat1, double lon1, double lat2, double lon2) {
Point p1 = new PointImpl(lon1, lat1, ctx);
Point p2 = new PointImpl(lon2, lat2, ctx);
return distance(p1, p2);
}
/**
* 如果你已经有 Point 对象可以直接传入计算
*/
public static double distance(Point p1, Point p2) {
// 计算两点之间的角度
double distanceRad = ctx.getDistCalc().distance(p1, p2);
// 使用地球平均半径公里转为米弧度*半径
return DistanceUtils.degrees2Dist(distanceRad, DistanceUtils.EARTH_MEAN_RADIUS_KM * 1000);
}
public static double distance(String lat1Str, String lon1Str, String lat2Str, String lon2Str) {
try {
double lat1 = Double.parseDouble(lat1Str);
double lon1 = Double.parseDouble(lon1Str);
double lat2 = Double.parseDouble(lat2Str);
double lon2 = Double.parseDouble(lon2Str);
return distance(lat1, lon1, lat2, lon2);
} catch (NumberFormatException e) {
System.err.println("Invalid input: " + e.getMessage());
return 0; // 或抛出异常 throw new IllegalArgumentException("Invalid coordinate format", e);
}
}
}

View File

@ -1,244 +0,0 @@
package com.multictrl.common.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.drew.imaging.jpeg.JpegMetadataReader;
import com.drew.imaging.jpeg.JpegProcessingException;
import com.drew.imaging.mp4.Mp4MetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.multictrl.common.exception.ExceptionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* 通用工具
*
* @author Sdy
* @since 1.0.0 2026/4/24
*/
@Slf4j
public class Utils {
/**
* 线程休息n毫秒
*/
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
log.error("线程睡眠异常:{}", ExceptionUtils.getErrorStackTrace(e));
}
}
/*
* 驼峰转下划线
*/
public static JSONObject beanToSnakeJson(Object obj) {
JSONObject camelJson = JSONUtil.parseObj(obj);
JSONObject snakeJson = new JSONObject();
for (String key : camelJson.keySet()) {
String snakeKey = StrUtil.toUnderlineCase(key); // 驼峰转下划线
Object value = camelJson.get(key);
// 递归处理嵌套 JSONObject JSONArray
if (value instanceof JSONObject) {
value = beanToSnakeJson(value); // 递归转换
} else if (value instanceof JSONArray) {
JSONArray arr = new JSONArray();
for (Object item : (JSONArray) value) {
if (item instanceof JSONObject) {
arr.add(beanToSnakeJson(item));
} else {
arr.add(item);
}
}
value = arr;
}
snakeJson.set(snakeKey, value);
}
return snakeJson;
}
/**
* 获取最后两个字符
* "a/b/c/d" "c/d"
*/
public static String getLastTwoSegments(String str, String separator) {
if (StrUtil.isBlank(str)) return str;
// 按分隔符切割
List<String> parts = StrUtil.split(str, separator);
if (parts.size() <= 2) {
return str; // 不足两段则原样返回
} else {
// 取最后两段
List<String> lastTwo = parts.subList(parts.size() - 2, parts.size());
return CollUtil.join(lastTwo, String.valueOf(separator));
}
}
/**
* 文件转md5
*/
public static String md5File(String path) {
StringBuilder sb = new StringBuilder();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(FileUtils.readFileToByteArray(new File(path)));
byte[] b = md.digest();
int d;
for (byte value : b) {
d = value;
if (d < 0) {
d = value & 0xff;
// 与上一行效果等同
// i += 256;
}
if (d < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(d));
}
} catch (NoSuchAlgorithmException | IOException e) {
log.error("md5 fail", e);
}
return sb.toString();
}
/**
* 获取文本的md5
*/
public static String md5Txt(String context) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] buffer = context.getBytes();
md.update(buffer, 0, buffer.length);
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException exception) {
log.error("md5 fail", exception);
}
return null;
}
/**
* 获取图片的Windows XP Keywords
*/
public static String getImageWindowsXpKeywords(String mediaPath) {
Metadata metadata;
try {
metadata = JpegMetadataReader.readMetadata(new File(mediaPath));
for (Directory exif : metadata.getDirectories()) {
for (Tag tag : exif.getTags()) {
if ("Windows XP Keywords".equals(tag.getTagName())) {
return tag.getDescription();
}
}
}
} catch (JpegProcessingException | IOException e) {
log.error("error", e);
}
return null;
}
public static long getImageTime(String mediaPath) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
Metadata metadata;
try {
metadata = JpegMetadataReader.readMetadata(new File(mediaPath));
for (Directory exif : metadata.getDirectories()) {
for (Tag tag : exif.getTags()) {
if ("Date/Time".equals(tag.getTagName())) {
return sdf.parse(tag.getDescription()).getTime();
}
}
}
} catch (JpegProcessingException | IOException e) {
log.error("error", e);
} catch (ParseException e) {
throw new RuntimeException(e);
}
return 0;
}
public static long getVideoTime(String videoPath) {
Metadata metadata;
try {
metadata = Mp4MetadataReader.readMetadata(new File(videoPath));
for (Directory exif : metadata.getDirectories()) {
for (Tag tag : exif.getTags()) {
if ("Creation Time".equals(tag.getTagName())) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy",
Locale.US);
Date d = sdf.parse(tag.getDescription());
return d.getTime();
} catch (ParseException ignored) {
}
}
}
}
} catch (IOException e) {
log.error("error", e);
}
return 0;
}
/**
* 格式化double保留指定位小数四舍五入
*
* @param value 原始数值
* @param scale 小数位数
* @return 格式化后的字符串
*/
private static String format(double value, int scale) {
if (Double.isNaN(value) || Double.isInfinite(value)) {
return "0";
}
BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(scale, RoundingMode.HALF_UP);
return bd.stripTrailingZeros().toPlainString();
}
/**
* 字节数格式化为人性化字符串自动选择B, KB, MB, GB, TB
* 1024进制
*
* @param bytes 字节数
* @return "1.45 GB"
*/
public static String formatBytes(long bytes) {
if (bytes < 0) {
return "0 B";
}
if (bytes < 1024) {
return bytes + " B";
}
int exp = (int) (Math.log(bytes) / Math.log(1024));
char pre = "KMGTPE".charAt(exp - 1);
double result = bytes / Math.pow(1024, exp);
return format(result, 2) + " " + pre + "B";
}
}

View File

@ -1,49 +0,0 @@
package com.multictrl.common.utils.kmz;
import com.multictrl.modules.business.dto.RouteDTO;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* kmz工具类
*
* @author Sdy
* @since 1.0.0 2026/4/21
*/
public class KmzUtils {
private static final String WPMZ_TEMPLATE_KML = "wpmz/template.kml";
private static final String WPMZ_WAYLINES_WPML = "wpmz/waylines.wpml";
/**
* 生成byte[]数组
*/
public static byte[] generateKmzBytes(RouteDTO route) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(byteArrayOutputStream)) {
// 添加 wpmz/template.kml 文件
String kmlContent = TemplateUtils.createTemplateContent(route);
addEntry(zos, WPMZ_TEMPLATE_KML, kmlContent);
// 添加 wpmz/waylines.wpl 文件
String waylinesContent = WaylinesUtils.createTemplateContent(route);
addEntry(zos, WPMZ_WAYLINES_WPML, waylinesContent);
zos.finish();
return byteArrayOutputStream.toByteArray();
}
}
/**
* 将指定内容写入 ZIP 中指定路径
*/
private static void addEntry(ZipOutputStream zos, String path, String content) throws IOException {
zos.putNextEntry(new ZipEntry(path));
zos.write(content.getBytes(StandardCharsets.UTF_8));
zos.closeEntry();
}
}

View File

@ -1,97 +0,0 @@
package com.multictrl.common.utils.kmz;
import cn.hutool.core.util.StrUtil;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.dto.RouteWaypointDTO;
import com.multictrl.modules.business.dto.WaypointActionDTO;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.util.List;
import java.util.Map;
/**
* template文件工具
*
* @author Sdy
* @since 1.0.0 2026/4/21
*/
@Slf4j
public class TemplateUtils {
/**
* 创建航线template内容
*/
public static String createTemplateContent(RouteDTO route) {
Document document = createDocument(route);
//生成内容
return XmlUtils.documentToString(document);
}
/**
* 配置Document
*/
public static Document createDocument(RouteDTO route) {
//创建document对象
Document document = DocumentHelper.createDocument();
//创建根kml节点
Element kml = document.addElement("kml");
kml.addNamespace("", "http://www.opengis.net/kml/2.2")
.addNamespace("wpml", "http://www.dji.com/wpmz/1.0.6");
//创建Document节点创建信息
Element dc = kml.addElement("Document");
dc.addElement("wpml:createTime").setText(System.currentTimeMillis() + "");
dc.addElement("wpml:updateTime").setText(System.currentTimeMillis() + "");
//创建missionConfig节点任务信息
XmlUtils.addMissionConfigElement(dc, route);
//创建folder节点飞行信息
Element folder = dc.addElement("Folder");
String templateType = route.getTemplateType();
folder.addElement("wpml:templateType").setText(templateType);
folder.addElement("wpml:templateId").setText("0");
Element waylineCoordinateSysParam = folder.addElement("wpml:waylineCoordinateSysParam");
waylineCoordinateSysParam.addElement("wpml:coordinateMode").setText("WGS84");
waylineCoordinateSysParam.addElement("wpml:heightMode").setText(StrUtil.isBlank(route.getHeightModel()) ? "relativeToStartPoint" : route.getHeightModel());
waylineCoordinateSysParam.addElement("wpml:positioningType").setText("GPS");
folder.addElement("wpml:autoFlightSpeed").setText(route.getFlightSpeed() + "");
folder.addElement("wpml:globalHeight").setText(route.getFlightHeight() + "");
folder.addElement("wpml:caliFlightEnable").setText("0");
folder.addElement("wpml:gimbalPitchMode").setText("manual");
folder.addElement("wpml:globalWaypointTurnMode").setText("toPointAndStopWithDiscontinuityCurvature");
folder.addElement("wpml:globalUseStraightLine").setText("1");
List<RouteWaypointDTO> waypointList = route.getRouteWaypointList();
for (RouteWaypointDTO waypoint : waypointList) {
Element placemark = folder.addElement("Placemark");
Element point = placemark.addElement("Point");
point.addElement("coordinates").setText(waypoint.getLongitude() + "," + waypoint.getLatitude());
placemark.addElement("wpml:index").setText(waypoint.getWaypointSort() + "");
placemark.addElement("wpml:ellipsoidHeight").setText(waypoint.getFlightHeight() + "");
placemark.addElement("wpml:height").setText(waypoint.getFlightHeight() + "");
placemark.addElement("wpml:waypointSpeed").setText(waypoint.getFlightSpeed() + "");
placemark.addElement("wpml:useGlobalSpeed").setText(waypoint.getFollowRouteSpeed() == true ? "1" : "0");
placemark.addElement("wpml:useGlobalHeight").setText(waypoint.getFollowRouteHeight() == true ? "1" : "0");
placemark.addElement("wpml:useGlobalHeadingParam").setText("0");
placemark.addElement("wpml:useGlobalTurnParam").setText("0");
//添加转弯模式/飞机偏航角模式
XmlUtils.addWaypointTurnMode(placemark, waypoint);
//航点动作
Map<Integer, List<WaypointActionDTO>> maps = XmlUtils.getWaypointActionMap(waypointList);
XmlUtils.addWaypointAction(placemark, waypoint, maps);
}
Element payloadParam = folder.addElement("wpml:payloadParam");
payloadParam.addElement("wpml:payloadPositionIndex").setText("0");
payloadParam.addElement("wpml:imageFormat").setText("wide,zoom,ir,visable");
return document;
}
}

View File

@ -1,83 +0,0 @@
package com.multictrl.common.utils.kmz;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.dto.RouteWaypointDTO;
import com.multictrl.modules.business.dto.WaypointActionDTO;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.util.List;
import java.util.Map;
/**
* waylines文件工具
*
* @author Sdy
* @since 1.0.0 2026/4/21
*/
@Slf4j
public class WaylinesUtils {
/**
* 创建waylines文件
*/
public static String createTemplateContent(RouteDTO route) {
Document document = createDocument(route);
//生成内容
return XmlUtils.documentToString(document);
}
/**
* 配置document
*/
public static Document createDocument(RouteDTO route) {
//创建document对象
Document document = DocumentHelper.createDocument();
//创建根kml节点
Element kml = document.addElement("kml");
kml.addNamespace("", "http://www.opengis.net/kml/2.2")
.addNamespace("wpml", "http://www.dji.com/wpmz/1.0.6");
//创建Document节点创建信息
Element dc = kml.addElement("Document");
//创建missionConfig节点任务信息
XmlUtils.addMissionConfigElement(dc, route);
//创建folder节点飞行信息
Element folder = dc.addElement("Folder");
folder.addElement("wpml:templateId").setText("0");
folder.addElement("wpml:executeHeightMode").setText("relativeToStartPoint");
folder.addElement("wpml:waylineId").setText("0");
// folder.addElement("wpml:distance").setText("360.651885986328");
// folder.addElement("wpml:duration").setText("73.1895170211792");
folder.addElement("wpml:autoFlightSpeed").setText(route.getFlightSpeed() + "");
//航点信息
List<RouteWaypointDTO> waypointList = route.getRouteWaypointList();
for (RouteWaypointDTO waypoint : waypointList) {
Element placemark = folder.addElement("Placemark");
Element point = placemark.addElement("Point");
point.addElement("coordinates").setText(waypoint.getLongitude() + "," + waypoint.getLatitude());
placemark.addElement("wpml:index").setText(waypoint.getWaypointSort() + "");
placemark.addElement("wpml:executeHeight").setText(waypoint.getFlightHeight() + "");
placemark.addElement("wpml:waypointSpeed").setText(waypoint.getFlightSpeed() + "");
//添加转弯模式/飞机偏航角模式
XmlUtils.addWaypointTurnMode(placemark, waypoint);
//添加动作
Map<Integer, List<WaypointActionDTO>> maps = XmlUtils.getWaypointActionMap(waypointList);
XmlUtils.addWaypointAction(placemark, waypoint, maps);
Element waypointGimbalHeadingParam = placemark.addElement("wpml:waypointGimbalHeadingParam");
waypointGimbalHeadingParam.addElement("wpml:waypointGimbalPitchAngle").setText("0");
waypointGimbalHeadingParam.addElement("wpml:waypointGimbalYawAngle").setText("0");
}
return document;
}
}

View File

@ -1,244 +0,0 @@
package com.multictrl.common.utils.kmz;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.constant.SetActionParams;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.dto.RouteWaypointDTO;
import com.multictrl.modules.business.dto.WaypointActionDTO;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Xml生成工具
*
* @author Sdy
* @since 1.0.0 2026/4/21
*/
public class XmlUtils {
/**
* 添加航点航线missionConfig节点任务信息
*/
public static void addMissionConfigElement(Element dc, RouteDTO route) {
//创建missionConfig节点任务信息
Element missionConfig = dc.addElement("wpml:missionConfig");
missionConfig.addElement("wpml:flyToWaylineMode").setText(route.getFlyToWaylineMode());
missionConfig.addElement("wpml:finishAction").setText(route.getFinishAction());
missionConfig.addElement("wpml:exitOnRCLost").setText(route.getExitOnRcLost());
if ("executeLostAction".equals(route.getExitOnRcLost())) {
missionConfig.addElement("wpml:executeRCLostAction").setText(route.getExecuteRcLostAction());
}
missionConfig.addElement("wpml:takeOffSecurityHeight").setText(route.getTakeoffSecurityHeight() + "");
missionConfig.addElement("wpml:globalTransitionalSpeed").setText(route.getGlobalTransitionalSpeed() + "");
missionConfig.addElement("wpml:globalRTHHeight").setText(route.getGlobalRthHeight() + "");
//todo飞行器信息/负载信息
Element droneInfo = missionConfig.addElement("wpml:droneInfo");
/*droneInfo.addElement("wpml:droneEnumValue").setText(uavModel.getType());
droneInfo.addElement("wpml:droneSubEnumValue").setText(uavModel.getSubType());*/
Element payloadInfo = missionConfig.addElement("wpml:payloadInfo");
/*payloadInfo.addElement("wpml:payloadEnumValue").setText(type);
payloadInfo.addElement("wpml:payloadSubEnumValue").setText("0");
payloadInfo.addElement("wpml:payloadPositionIndex").setText("0");*/
}
/**
* 获取航点动作
*/
public static Map<Integer, List<WaypointActionDTO>> getWaypointActionMap(List<RouteWaypointDTO> waypointList) {
Map<Integer, List<WaypointActionDTO>> maps = new HashMap<>();
for (RouteWaypointDTO waypoint : waypointList) {
List<WaypointActionDTO> actionList = waypoint.getWaypointActionList();
if (CollectionUtil.isEmpty(actionList)) {
actionList = Lists.newArrayList();
}
maps.put(waypoint.getWaypointSort(), actionList);
}
return maps;
}
/**
* 添加转弯模式/偏航模式
*/
public static void addWaypointTurnMode(Element placemark, RouteWaypointDTO waypoint) {
Element waypointHeadingParam = placemark.addElement("wpml:waypointHeadingParam");
waypointHeadingParam.addElement("wpml:waypointHeadingMode").setText(waypoint.getWaypointHeadingMode());
// waypointHeadingParam.addElement("wpml:waypointHeadingAngle").setText("0");
if (StrUtil.isNotBlank(waypoint.getWaypointHeadingAngle())) {
waypointHeadingParam.addElement("wpml:waypointHeadingAngle").setText(waypoint.getWaypointHeadingAngle());
}
// waypointHeadingParam.addElement("wpml:waypointPoiPoint").setText("0.000000,0.000000,0.000000");
if (StrUtil.isNotBlank(waypoint.getWaypointPoiPoint())) {
waypointHeadingParam.addElement("wpml:waypointPoiPoint").setText(waypoint.getWaypointPoiPoint());
}
waypointHeadingParam.addElement("wpml:waypointHeadingPathMode").setText(waypoint.getWaypointHeadingPathMode());
Element waypointTurnParam = placemark.addElement("wpml:waypointTurnParam");
waypointTurnParam.addElement("wpml:waypointTurnMode").setText(waypoint.getWaypointTruningMode());
if (StrUtil.isNotBlank(waypoint.getWaypointTurnDampingDist())) {
waypointTurnParam.addElement("wpml:waypointTurnDampingDist").setText(waypoint.getWaypointTurnDampingDist());
}
placemark.addElement("wpml:useStraightLine").setText("1");
}
/**
* 添加航点动作
*
* @param placemark 元素
* @param waypoint 点位
*/
public static void addWaypointAction(Element placemark, RouteWaypointDTO waypoint, Map<Integer, List<WaypointActionDTO>> maps) {
List<WaypointActionDTO> actionList = waypoint.getWaypointActionList();
List<WaypointActionDTO> reachPoint = Lists.newArrayList();
List<WaypointActionDTO> otherAction = Lists.newArrayList();
for (WaypointActionDTO actionDTO : actionList) {
String actionType = actionDTO.getActionType();
if (BusinessConstant.EQUAL_TIME_TAKE_PHOTO_FLAG.equals(actionType) ||
BusinessConstant.EQUAL_DISTANCE_TAKE_PHOTO_FLAG.equals(actionType) || BusinessConstant.FINISH_TAKE_PHOTO_FLAG.equals(actionType)) {
otherAction.add(actionDTO);
} else {
reachPoint.add(actionDTO);
}
}
if (CollectionUtil.isNotEmpty(reachPoint)) {
Element actionGroup = placemark.addElement("wpml:actionGroup");
actionGroup.addElement("wpml:actionGroupId").setText(getNextGlobalActionGroupId(actionGroup) + "");
actionGroup.addElement("wpml:actionGroupStartIndex").setText(waypoint.getWaypointSort() + "");
actionGroup.addElement("wpml:actionGroupEndIndex").setText(waypoint.getWaypointSort() + "");
actionGroup.addElement("wpml:actionGroupMode").setText("sequence");
Element actionTrigger = actionGroup.addElement("wpml:actionTrigger");
actionTrigger.addElement("wpml:actionTriggerType").setText(BusinessConstant.DEFAULT_ACTION_TRIGGER_TYPE);
for (int j = 0; j < reachPoint.size(); j++) {
WaypointActionDTO actionDTO = reachPoint.get(j);
Element action = actionGroup.addElement("wpml:action");
action.addElement("wpml:actionId").setText(j + "");
action.addElement("wpml:actionActuatorFunc").setText(StrUtil.splitTrim(actionDTO.getActionType(), "_").get(0));
Element actionActuatorFuncParam = action.addElement("wpml:actionActuatorFuncParam");
actionDTO.setWaypointOrder(waypoint.getWaypointSort() + "");
SetActionParams.executeAction(actionDTO, actionActuatorFuncParam);
}
}
//等时等距处理
if (CollectionUtil.isNotEmpty(otherAction)) {
addEqualTimeEqualDistanceTakePhotoAction(placemark, waypoint, otherAction, maps);
}
}
/**
* 查找下一个可用的 actionGroupId从0开始递增
*
* @param element 节点
* @return 下一个可用的 actionGroupId
*/
private static int getNextGlobalActionGroupId(Element element) {
Element rootElement = element.getDocument().getRootElement();
int maxId = -1;
List<Element> actionGroups = rootElement.elements("wpml:actionGroup");
for (Element actionGroup : actionGroups) {
List<Element> existingElements = actionGroup.elements("wpml:actionGroupId");
for (Element e : existingElements) {
try {
int id = Integer.parseInt(e.getText());
if (id > maxId) {
maxId = id;
}
} catch (NumberFormatException ignored) {
}
}
}
return maxId + 1; // 返回下一个可用 ID
}
/**
* 等时等距拍照
*/
private static void addEqualTimeEqualDistanceTakePhotoAction(Element placemark, RouteWaypointDTO waypoint
, List<WaypointActionDTO> otherAction, Map<Integer, List<WaypointActionDTO>> maps) {
for (int i = 0; i < otherAction.size(); i++) {
WaypointActionDTO currentAction = otherAction.get(i);
String actionType = currentAction.getActionType();
if (BusinessConstant.FINISH_TAKE_PHOTO_FLAG.equals(actionType)) continue;
String finishActionValue = null;
for (int j = i + 1; j < otherAction.size(); j++) {
WaypointActionDTO futureAction = otherAction.get(j);
if (BusinessConstant.FINISH_TAKE_PHOTO_FLAG.equals(futureAction.getActionType())) {
finishActionValue = futureAction.getActionValue();
break;
}
}
// 如果当前列表没有找到 finish 动作尝试查找后续列表中的 finish 动作
if (finishActionValue == null) {
for (int ii = waypoint.getWaypointSort() + 1; ii < maps.size(); ii++) {
List<WaypointActionDTO> dtos = maps.get(ii);
for (WaypointActionDTO futureAction : dtos) {
if (BusinessConstant.FINISH_TAKE_PHOTO_FLAG.equals(futureAction.getActionType())) {
finishActionValue = futureAction.getActionValue();
break;
}
}
if (finishActionValue != null) break;
}
}
if (finishActionValue == null) break;
if (BusinessConstant.EQUAL_DISTANCE_TAKE_PHOTO_FLAG.equals(actionType) || BusinessConstant.EQUAL_TIME_TAKE_PHOTO_FLAG.equals(actionType)) {
Integer waypointIndex = waypoint.getWaypointSort();
Element actionGroup = placemark.addElement("wpml:actionGroup");
actionGroup.addElement("wpml:actionGroupId").setText(getNextGlobalActionGroupId(actionGroup) + "");
actionGroup.addElement("wpml:actionGroupStartIndex").setText(waypointIndex + "");
actionGroup.addElement("wpml:actionGroupEndIndex").setText(finishActionValue);
actionGroup.addElement("wpml:actionGroupMode").setText("sequence");
Element actionTrigger = actionGroup.addElement("wpml:actionTrigger");
actionTrigger.addElement("wpml:actionTriggerType").setText(actionType);
actionTrigger.addElement("wpml:actionTriggerParam").setText(currentAction.getActionValue());
Element action = actionGroup.addElement("wpml:action");
action.addElement("wpml:actionId").setText("0");
action.addElement("wpml:actionActuatorFunc").setText(BusinessConstant.TAKE_PHOTO_FLAG);
Element actionActuatorFuncParam = action.addElement("wpml:actionActuatorFuncParam");
currentAction.setWaypointOrder(waypoint.getWaypointSort() + "");
SetActionParams.executeAction(currentAction, actionActuatorFuncParam);
}
}
}
/**
* 文档转字符串
*
* @param document 文档
* @return 返回字符串
*/
public static String documentToString(Document document) {
OutputFormat format = OutputFormat.createPrettyPrint();
StringWriter stringWriter = new StringWriter();
XMLWriter writer = new XMLWriter(stringWriter, format);
writer.setEscapeText(false);
try {
writer.write(document);
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return stringWriter.toString();
}
}

View File

@ -1,233 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.modules.business.dto.camera.*;
import com.multictrl.modules.business.service.CameraService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import java.util.Objects;
/**
* 云台相机控制
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@RestController
@RequestMapping("business/camera")
@Tag(name = "云台相机控制")
@ApiOrder(7)
@RequiredArgsConstructor
public class CameraController {
private final CameraService cameraService;
@LogOperation("框选变焦")
@PostMapping("/frameZoom/{dockSn}")
@Operation(summary = "框选变焦")
@RequiresPermissions("bus:camera:frameZoom")
public Result<Object> frameZoom(@PathVariable String dockSn, @RequestBody FrameZoom frameZoom) {
ValidatorUtils.validateEntity(frameZoom);
return new Result<>().ok(cameraService.frameZoom(dockSn, frameZoom));
}
@LogOperation("切换相机模式")
@PostMapping("/modeSwitch/{dockSn}")
@Operation(summary = "切换相机模式")
@RequiresPermissions("bus:camera:modeSwitch")
public Result<Object> modeSwitch(@PathVariable String dockSn,
@Parameter(name = "mode", description = "模式 0:拍照 1:录像 2:智能低光 3:全景拍照")
@RequestParam Integer mode) {
return new Result<>().ok(cameraService.modeSwitch(dockSn, mode));
}
@LogOperation("拍照录像")
@PostMapping("/photoVideo/{dockSn}")
@Operation(summary = "拍照录像")
@RequiresPermissions("bus:camera:photoVideo")
public Result<Object> photoVideo(@PathVariable String dockSn,
@Parameter(name = "mode", description = "类型 camera_photo_take开始拍照 camera_photo_stop停止拍照目前仅支持全景拍照模式 " +
"camera_recording_start开始录像 camera_recording_stop停止录像")
@RequestParam String mode) {
if (!mode.equals("camera_photo_take") && !mode.equals("camera_photo_stop") &&
!mode.equals("camera_recording_start") && !mode.equals("camera_recording_sto")) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<>().ok(cameraService.photoVideo(dockSn, mode));
}
@LogOperation("画面拖动控制")
@PostMapping("/screenDrag/{dockSn}")
@Operation(summary = "画面拖动控制")
@RequiresPermissions("bus:camera:screenDrag")
public Result<Object> screenDrag(@PathVariable String dockSn, @RequestBody ScreenDrag screenDrag) {
ValidatorUtils.validateEntity(screenDrag);
return new Result<>().ok(cameraService.screenDrag(dockSn, screenDrag));
}
@LogOperation("双击成为AIM")
@PostMapping("/aim/{dockSn}")
@Operation(summary = "双击成为AIM")
@RequiresPermissions("bus:camera:aim")
public Result<Object> aim(@PathVariable String dockSn, @RequestBody AimParam aimParam) {
ValidatorUtils.validateEntity(aimParam);
return new Result<>().ok(cameraService.aim(dockSn, aimParam));
}
@LogOperation("变焦")
@PostMapping("/focalLengthSet/{dockSn}")
@Operation(summary = "变焦")
@RequiresPermissions("bus:camera:focalLengthSet")
public Result<Object> focalLengthSet(@PathVariable String dockSn, @RequestBody FocalLengthSet focalLengthSet) {
Object videoType = CacheUtils.get(BusinessConstant.UAV_VIDEO_TYPE + dockSn);
focalLengthSet.setCameraType(Objects.requireNonNullElse((String) videoType, "wide"));
ValidatorUtils.validateEntity(focalLengthSet);
return new Result<>().ok(cameraService.focalLengthSet(dockSn, focalLengthSet));
}
@LogOperation("重置云台")
@PostMapping("/gimbalReset/{dockSn}")
@Operation(summary = "重置云台")
@RequiresPermissions("bus:camera:gimbalReset")
public Result<Object> gimbalReset(@PathVariable String dockSn,
@Parameter(name = "mode", description = "模式 0:回中 1:向下 2:偏航回中 3:俯仰向下")
@RequestParam Integer mode) {
return new Result<>().ok(cameraService.gimbalReset(dockSn, mode));
}
@LogOperation("Look At")
@PostMapping("/lookAt/{dockSn}")
@Operation(summary = "Look At", description = "lookat 功能指飞行器将从当前朝向转向实际经纬高度指定的点," +
"在 M30/M30T 机型上建议使用锁定机头的方式,仅云台转动场景下在抵达云台限位角后 lookat 功能将出现异常")
@RequiresPermissions("bus:camera:lookAt")
public Result<Object> lookAt(@PathVariable String dockSn, @RequestBody LookAt lookAt) {
ValidatorUtils.validateEntity(lookAt);
return new Result<>().ok(cameraService.lookAt(dockSn, lookAt));
}
@LogOperation("分屏")
@PostMapping("/screenSplit/{dockSn}")
@Operation(summary = "分屏")
@RequiresPermissions("bus:camera:screenSplit")
public Result<Object> screenSplit(@PathVariable String dockSn,
@Parameter(name = "enable", description = "是否使能分屏 1开启分屏 0关闭分屏")
@RequestParam Integer enable) {
return new Result<>().ok(cameraService.screenSplit(dockSn, enable));
}
@LogOperation("照片、视频存储设置")
@PostMapping("/storageSet/{dockSn}")
@Operation(summary = "照片、视频存储设置")
@RequiresPermissions("bus:camera:storageSet")
public Result<Object> storageSet(@PathVariable String dockSn, @RequestBody StorageSet storageSet) {
ValidatorUtils.validateEntity(storageSet);
return new Result<>().ok(cameraService.storageSet(dockSn, storageSet));
}
@LogOperation("相机曝光模式设置")
@PostMapping("/exposureModeSet/{dockSn}")
@Operation(summary = "相机曝光模式设置")
@RequiresPermissions("bus:camera:exposureModeSet")
public Result<Object> exposureModeSet(@PathVariable String dockSn,
@Parameter(name = "mode", description = "曝光模式 1:自动 2:快门优先曝光 3:光圈优先曝光 4:手动曝光")
@RequestParam Integer mode) {
return new Result<>().ok(cameraService.exposureModeSet(dockSn, mode));
}
@LogOperation("相机曝光值调节")
@PostMapping("/exposureSet/{dockSn}")
@Operation(summary = "相机曝光值调节")
@RequiresPermissions("bus:camera:exposureSet")
public Result<Object> exposureSet(@PathVariable String dockSn,
@Parameter(name = "value", description = "曝光值 1:-5.0EV 2:-4.7EV 3:-4.3EV 4:-4.0EV " +
"5:-3.7EV 6:-3.3EV 7:-3.0EV 8:-2.7EV 9:-2.3EV 10:-2.0EV 11:-1.7EV 12:-1.3EV " +
"13:-1.0EV ...查看接口(获取相机曝光值)")
@RequestParam String value) {
return new Result<>().ok(cameraService.exposureSet(dockSn, value));
}
@LogOperation("相机对焦模式设置")
@PostMapping("/focusModeSet/{dockSn}")
@Operation(summary = "相机对焦模式设置", description = "注意: Matrice 30 系列飞行器只支持 zoom 镜头下配置该参数")
@RequiresPermissions("bus:camera:focusModeSet")
public Result<Object> focusModeSet(@PathVariable("dockSn") String dockSn,
@Parameter(name = "mode", description = "对焦模式 1:MF 2:AFS 3:AFC")
@RequestParam Integer mode) {
return new Result<>().ok(cameraService.focusModeSet(dockSn, mode));
}
@LogOperation("相机对焦值设置")
@PostMapping("/focusSet/{dockSn}")
@Operation(summary = "相机对焦值设置", description = "注意: Matrice 30 系列飞行器只支持 zoom 镜头下配置该参数")
@RequiresPermissions("bus:camera:focusSet")
public Result<Object> focusSet(@PathVariable("dockSn") String dockSn,
@Parameter(name = "value", description = "对焦值:范围参见飞行器物模型属性 zoom_max_focus_value 和 zoom_min_focus_value")
@RequestParam Integer value) {
return new Result<>().ok(cameraService.focusSet(dockSn, value));
}
@LogOperation("点对焦")
@PostMapping("/pointFocusAction/{dockSn}")
@Operation(summary = "点对焦")
@RequiresPermissions("bus:camera:pointFocusAction")
public Result<Object> pointFocusAction(@PathVariable String dockSn, @RequestBody PointFocusAction pointFocusAction) {
ValidatorUtils.validateEntity(pointFocusAction);
return new Result<>().ok(cameraService.pointFocusAction(dockSn, pointFocusAction));
}
@LogOperation("红外测温模式设置")
@PostMapping("/irMeteringModeSet/{dockSn}")
@Operation(summary = "红外测温模式设置", description = "注意: Matrice 30 系列飞行器只支持 zoom 镜头下配置该参数")
@RequiresPermissions("bus:camera:focusSet")
public Result<Object> irMeteringModeSet(@PathVariable("dockSn") String dockSn,
@Parameter(name = "value", description = "对焦值:范围参见飞行器物模型属性 zoom_max_focus_value 和 zoom_min_focus_value")
@RequestParam Integer value) {
return new Result<>().ok(cameraService.focusSet(dockSn, value));
}
@LogOperation("红外测温点设置")
@PostMapping("/irMeteringPointSet/{dockSn}")
@Operation(summary = "红外测温点设置")
@RequiresPermissions("bus:camera:irMeteringPointSet")
public Result<Object> irMeteringPointSet(@PathVariable String dockSn, @RequestBody IrMeteringPointSet irMeteringPointSet) {
ValidatorUtils.validateEntity(irMeteringPointSet);
return new Result<>().ok(cameraService.irMeteringPointSet(dockSn, irMeteringPointSet));
}
@LogOperation("红外测温区域设置")
@PostMapping("/irMeteringAreaSet/{dockSn}")
@Operation(summary = "红外测温区域设置")
@RequiresPermissions("bus:camera:irMeteringAreaSet")
public Result<Object> irMeteringAreaSet(@PathVariable String dockSn, @RequestBody IrMeteringAreaSet irMeteringAreaSet) {
ValidatorUtils.validateEntity(irMeteringAreaSet);
return new Result<>().ok(cameraService.irMeteringAreaSet(dockSn, irMeteringAreaSet));
}
}

View File

@ -1,125 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.modules.business.dto.command.DrcStick;
import com.multictrl.modules.business.dto.command.FlyToPoint;
import com.multictrl.modules.business.dto.command.TakeoffToPoint;
import com.multictrl.modules.business.service.CommandService;
import com.multictrl.modules.business.service.DJIBaseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
/**
* 指令飞行
*
* @author Sdy
* @since 1.0.0 2026/4/27
*/
@RestController
@RequestMapping("business/command")
@Tag(name = "指令飞行", description = "指令飞行功能的开放目的是解决无人机与机场在远程控制过程中,无法即时性操作的限制。" +
"在实际应用场景中,飞行器可以从空闲状态响应或暂停正在执行的航线任务。开发者通过手动方式继续控制设备或负载。" +
"通过指令飞行功能开发者可以获得安全可靠的飞行器控制、高实时性的指令下发与直播画面传输、osd 信息上报以及对负载的控制能力。")
@ApiOrder(6)
@RequiredArgsConstructor
public class CommandController {
private final CommandService commandService;
private final DJIBaseService djiBaseService;
@LogOperation("一键起飞")
@PostMapping("/takeoffToPoint/{dockSn}")
@Operation(summary = "一键起飞")
@RequiresPermissions("bus:command:takeoff")
public Result<Object> takeoffToPoint(@PathVariable String dockSn, @RequestBody TakeoffToPoint takeoffToPoint) {
ValidatorUtils.validateEntity(takeoffToPoint);
return new Result<>().ok(commandService.takeoffToPoint(dockSn, takeoffToPoint));
}
@LogOperation("起飞到备降点100米处悬停")
@PostMapping("/takeOffToAlternateLandPoint/{dockSn}")
@Operation(summary = "起飞到备降点100米处悬停")
@RequiresPermissions("bus:command:takeoff")
public Result<Object> takeOffToAlternateLandPoint(@PathVariable String dockSn) {
return new Result<>().ok(commandService.takeOffToAlternateLandPoint(dockSn));
}
@PostMapping("/flyToPoint/{dockSn}")
@LogOperation("飞向目标点")
@Operation(summary = "飞向目标点")
@Schema(description = "特别说明:飞行器有最低飞行高度(20m)安全保障机制如果飞行器相对起飞点高度低于20m会先上升到20m")
@RequiresPermissions("bus:command:flyToPoint")
public Result<Object> flyToPoint(@PathVariable String dockSn, @RequestBody FlyToPoint flyToPoint) {
ValidatorUtils.validateEntity(flyToPoint);
return new Result<>().ok(commandService.flyToPoint(dockSn, flyToPoint));
}
@PostMapping("/flyToPointStop/{dockSn}")
@LogOperation("结束飞向目标点")
@Operation(summary = "结束飞向目标点")
@RequiresPermissions("bus:command:flyToPointStop")
public Result<Object> flyToPointStop(@PathVariable String dockSn) {
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "fly_to_point_stop"));
}
@PostMapping("/flyToPointUpdate/{dockSn}")
@LogOperation("飞向目标点更新")
@Operation(summary = "飞向目标点更新")
@Schema(description = "「一键起飞」或「flyto 飞向目标点」的过程中,可以通过该命令快速更新目标点")
@RequiresPermissions("bus:command:flyToPointUpdate")
public Result<Object> flyToPointUpdate(@PathVariable String dockSn, @RequestBody FlyToPoint flyToPoint) {
ValidatorUtils.validateEntity(flyToPoint);
return new Result<>().ok(commandService.flyToPointUpdate(dockSn, flyToPoint));
}
@PostMapping("/drcModeEnter/{dockSn}")
@LogOperation("进入指令飞行控制模式")
@Operation(summary = "进入指令飞行控制模式")
@Schema(description = "如果想使用虚拟摇杆控制飞机,必须进入该模式。在进入指令飞行控制模式后,必须发送心跳包,否则飞机会自动退出指令飞行控制模式,1分钟内没心跳退出")
@RequiresPermissions("bus:command:drcModeEnter")
public Result<Object> drcModeEnter(@PathVariable String dockSn) {
return new Result<>().ok(commandService.drcModeEnter(dockSn));
}
@PostMapping("/drcModeExit/{dockSn}")
@LogOperation("退出指令飞行控制模式")
@Operation(summary = "退出指令飞行控制模式")
@RequiresPermissions("bus:command:drcModeExit")
public Result<Object> drcModeExit(@PathVariable String dockSn) {
return new Result<>().ok(commandService.drcModeExit(dockSn));
}
@PostMapping("/drcStickControl/{dockSn}")
// @LogOperation("DRC-杆量控制")
@Operation(summary = "DRC-杆量控制")
@Schema(description = "建立DRC链路之后可通过“DRC-杆量控制”指令控制飞行器和云台姿态。发送频率需要保持5-10hz一秒5~10次才能比较精准地控制飞行器的运动。本协议无回包机制。")
@RequiresPermissions("bus:command:drcStickControl")
public void drcStickControl(@PathVariable String dockSn, @RequestBody DrcStick drcStick) {
ValidatorUtils.validateEntity(drcStick);
commandService.drcStickControl(dockSn, drcStick);
// return new Result<>();
}
@PostMapping("/drcEmergencyStop/{dockSn}")
@LogOperation("DRC-飞行器急停")
@Operation(summary = "DRC-飞行器急停")
@RequiresPermissions("bus:command:drcEmergencyStop")
public Result<Object> drcEmergencyStop(@PathVariable String dockSn) {
return new Result<>().ok(djiBaseService.executeDrcAndReturnResult(dockSn, "drone_emergency_stop"));
}
}

View File

@ -1,87 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.utils.Result;
import com.multictrl.modules.business.service.DJIBaseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 远程调试
*
* @author Sdy
* @since 1.0.0 2026/4/24
*/
@RestController
@RequestMapping("business/debug")
@Tag(name = "远程调试", description = "远程调试为在调试的作业流中实现无人值守,即让作业人员无需到现场,在云端就可以下发命令到设备端," +
"进行设备的远程排障。远程调试命令可分为命令cmd和任务job。命令cmd一般指命令下发后设备能即刻回复的行为而任务job" +
"为任务下发后,设备需要持续动作的行为。")
@ApiOrder(3)
@RequiredArgsConstructor
public class DebugController {
private final DJIBaseService debugService;
@Operation(summary = "调试模式开启")
@GetMapping("/debugModeOpen/{dockSn}")
@LogOperation("调试模式开启")
@RequiresPermissions("bus:debug:operation")
public Result<Object> debugModeOpen(@PathVariable String dockSn) {
return new Result<>().ok(debugService.executeAndReturnResult(dockSn, "debug_mode_open"));
}
@Operation(summary = "调试模式关闭")
@GetMapping("/debugModeClose/{dockSn}")
@LogOperation("调试模式关闭")
@RequiresPermissions("bus:debug:operation")
public Result<Object> debugModeClose(@PathVariable String dockSn) {
return new Result<>().ok(debugService.executeAndReturnResult(dockSn, "debug_mode_close"));
}
@Operation(summary = "强制关舱盖")
@GetMapping("/coverForceClose/{dockSn}")
@LogOperation("强制关舱盖")
@RequiresPermissions("bus:debug:operation")
public Result<Object> coverForceClose(@PathVariable String dockSn) {
return new Result<>().ok(debugService.executeAndReturnResult(dockSn, "cover_force_close"));
}
@Operation(summary = "打开舱盖")
@GetMapping("/coverOpen/{dockSn}")
@LogOperation("打开舱盖")
@RequiresPermissions("bus:debug:operation")
public Result<Object> coverOpen(@PathVariable String dockSn) {
return new Result<>().ok(debugService.executeAndReturnResult(dockSn, "cover_open"));
}
@Operation(summary = "关闭舱盖")
@GetMapping("/coverClose/{dockSn}")
@LogOperation("关闭舱盖")
@RequiresPermissions("bus:debug:operation")
public Result<Object> coverClose(@PathVariable String dockSn) {
return new Result<>().ok(debugService.executeAndReturnResult(dockSn, "cover_close"));
}
@Operation(summary = "飞行器开机")
@GetMapping("/droneOpen/{dockSn}")
@LogOperation("飞行器开机")
@RequiresPermissions("bus:debug:operation")
public Result<Object> droneOpen(@PathVariable String dockSn) {
return new Result<>().ok(debugService.executeAndReturnResult(dockSn, "drone_open"));
}
@Operation(summary = "飞行器关机")
@GetMapping("/droneClose/{dockSn}")
@LogOperation("飞行器关机")
@RequiresPermissions("bus:debug:operation")
public Result<Object> droneClose(@PathVariable String dockSn) {
return new Result<>().ok(debugService.executeAndReturnResult(dockSn, "drone_close"));
}
}

View File

@ -1,101 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.page.PageData;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.AssertUtils;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.DefaultGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import com.multictrl.modules.business.dto.DockDTO;
import com.multictrl.modules.business.service.DockService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* 机库信息表
*
* @author Sdy
* @since 1.0.0 2026-04-17
*/
@RestController
@RequestMapping("business/dock")
@Tag(name = "机库管理")
@ApiOrder(1)
@RequiredArgsConstructor
public class DockController {
private final DockService dockService;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
@Parameter(name = "key", description = "机库名称、机库SN码")
})
@RequiresPermissions("bus:dock:page")
public Result<PageData<DockDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<DockDTO> page = dockService.page(params);
return new Result<PageData<DockDTO>>().ok(page);
}
@GetMapping("{id}")
@Operation(summary = "信息")
@RequiresPermissions("bus:dock:info")
public Result<DockDTO> get(@PathVariable("id") Long id) {
DockDTO data = dockService.get(id);
return new Result<DockDTO>().ok(data);
}
@PostMapping
@Operation(summary = "保存")
@LogOperation("保存")
@RequiresPermissions("bus:dock:save")
public Result<Object> save(@RequestBody DockDTO dto) {
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class);
dockService.save(dto);
return new Result<>();
}
@PutMapping
@Operation(summary = "修改")
@LogOperation("修改")
@RequiresPermissions("bus:dock:update")
public Result<Object> update(@RequestBody DockDTO dto) {
//效验数据
ValidatorUtils.validateEntity(dto, UpdateGroup.class);
dockService.update(dto);
return new Result<>();
}
@DeleteMapping
@Operation(summary = "删除")
@LogOperation("删除")
@RequiresPermissions("bus:dock:delete")
public Result<Object> delete(@RequestBody Long[] ids) {
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
dockService.delete(ids);
return new Result<>();
}
}

View File

@ -1,129 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.page.PageData;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.AssertUtils;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.modules.business.dto.DockDeviceDTO;
import com.multictrl.modules.business.dto.FirmwareDTO;
import com.multictrl.modules.business.dto.firmware.FirmwareVersion;
import com.multictrl.modules.business.service.FirmwareService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
/**
* 固件表
*
* @author Sdy
* @since 1.0.0 2026-05-11
*/
@RestController
@RequestMapping("business/firmware")
@Tag(name = "固件更新")
@ApiOrder(11)
@RequiredArgsConstructor
public class FirmwareController {
private final FirmwareService firmwareService;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数")
})
@RequiresPermissions("bus:firmware:page")
public Result<PageData<FirmwareDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<FirmwareDTO> page = firmwareService.page(params);
return new Result<PageData<FirmwareDTO>>().ok(page);
}
@GetMapping("/getFirmware/{dockSn}")
@Operation(summary = "获取设备固件")
@LogOperation("获取设备固件")
@RequiresPermissions("bus:firmware:getFirmware")
public Result<List<FirmwareDTO>> getFirmware(@PathVariable String dockSn) {
List<FirmwareDTO> data = firmwareService.getFirmwareList(dockSn);
return new Result<List<FirmwareDTO>>().ok(data);
}
@PostMapping
@Operation(summary = "上传固件", description = "上传从大疆官网获取的机库或飞机的固件包格式zip")
@LogOperation("上传固件")
@RequiresPermissions("bus:firmware:upload")
public Result<Object> uploadFirmware(@Parameter(description = "官方固件包") @RequestParam("file") MultipartFile file,
@Parameter(description = "设备名称") @RequestParam("deviceName") String deviceName,
@Parameter(description = "备注") @RequestParam(value = "remark", required = false) String remark) {
firmwareService.uploadFirmware(file, deviceName, remark);
return new Result<>();
}
@PutMapping
@Operation(summary = "修改备注")
@LogOperation("修改备注")
@RequiresPermissions("bus:firmware:update")
public Result<Object> update(@RequestBody FirmwareDTO dto) {
ValidatorUtils.validateEntity(dto);
firmwareService.update(dto);
return new Result<>();
}
@DeleteMapping
@Operation(summary = "删除")
@LogOperation("删除")
@RequiresPermissions("bus:firmware:delete")
public Result<Object> delete(@RequestBody Long[] ids) {
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
firmwareService.deleteFirmware(ids);
return new Result<>();
}
@PostMapping("/ota/{dockSn}")
@LogOperation("固件升级")
@Operation(summary = "固件升级", description = "固件升级,设备必须开机情况下才能升级,如果给飞机升级,需要调试模式打开飞机,然后关闭调试模式,再进行升级")
@RequiresPermissions("bus:firmware:ota")
public Result<Object> ota(@PathVariable String dockSn,
@Parameter(description = "固件id") @RequestParam Long firmwareId) {
return new Result<>().ok(firmwareService.ota(dockSn, firmwareId));
}
@GetMapping("/registerDevices")
@Operation(summary = "设备列表")
@LogOperation("设备列表")
@RequiresPermissions("bus:firmware:registerDevices")
public Result<List<DockDeviceDTO>> registerDevices() {
List<DockDeviceDTO> list = firmwareService.registerDevices();
return new Result<List<DockDeviceDTO>>().ok(list);
}
@GetMapping("/getCurrentFirmwareVersion/{dockSn}")
@Operation(summary = "设备当前版本", description = "获取机库和飞机的固件版本,如果为空,需重启机场")
@LogOperation("设备当前版本")
@RequiresPermissions("bus:firmware:currentFirmware")
public Result<FirmwareVersion> getCurrentFirmwareVersion(@PathVariable String dockSn) {
FirmwareVersion data = firmwareService.getCurrentFirmwareVersion(dockSn);
return new Result<FirmwareVersion>().ok(data);
}
}

View File

@ -1,68 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.page.PageData;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.AssertUtils;
import com.multictrl.modules.business.dto.FlightTaskDTO;
import com.multictrl.modules.business.service.FlightTaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import java.util.Map;
/**
* 飞行架次
*
* @author Sdy
* @since 1.0.0 2026-05-02
*/
@RestController
@RequestMapping("business/task")
@Tag(name = "飞行架次")
@ApiOrder(7)
@RequiredArgsConstructor
public class FlightTaskController {
private final FlightTaskService flightTaskService;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数")
})
@RequiresPermissions("business:task:page")
public Result<PageData<FlightTaskDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<FlightTaskDTO> page = flightTaskService.page(params);
return new Result<PageData<FlightTaskDTO>>().ok(page);
}
@GetMapping("{id}")
@Operation(summary = "信息")
@RequiresPermissions("business:task:info")
public Result<FlightTaskDTO> get(@PathVariable("id") Long id) {
FlightTaskDTO data = flightTaskService.get(id);
return new Result<FlightTaskDTO>().ok(data);
}
@DeleteMapping
@Operation(summary = "删除")
@LogOperation("删除")
@RequiresPermissions("business:task:delete")
public Result<Object> delete(@RequestBody Long[] ids) {
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
flightTaskService.delete(ids);
return new Result<>();
}
}

View File

@ -1,120 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.utils.Result;
import com.multictrl.modules.business.dto.live.LiveUrl;
import com.multictrl.modules.business.service.LiveService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 直播管理
*
* @author Sdy
* @since 1.0.0 2026/4/24
*/
@RestController
@RequestMapping("business/live")
@Tag(name = "直播管理")
@ApiOrder(4)
@RequiredArgsConstructor
public class LiveController {
private final LiveService liveService;
@LogOperation("开启直播")
@PostMapping("/startLive/{dockSn}")
@Operation(summary = "开启直播")
@RequiresPermissions("bus:live:start")
public Result<Object> startLive(@PathVariable String dockSn,
@Parameter(description = "直播质量0-自适应1-流畅2-标清3-高清4-超清")
@RequestParam Integer videoQuality,
@Parameter(description = "直播类型dock-机场uav-飞机")
@RequestParam String videoType) {
if (videoQuality != 0 && videoQuality != 1 && videoQuality != 2 && videoQuality != 3 && videoQuality != 4) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
if (!videoType.equals("dock") && !videoType.equals("uav")) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<>().ok(liveService.startLive(dockSn, videoQuality, videoType));
}
@LogOperation("停止直播")
@PostMapping("/stopLive/{dockSn}")
@Operation(summary = "停止直播")
@RequiresPermissions("bus:live:stop")
public Result<Object> stopLive(@PathVariable String dockSn,
@Parameter(description = "直播类型dock-机场uav-飞机")
@RequestParam String videoType) {
if (!videoType.equals("dock") && !videoType.equals("uav")) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<>().ok(liveService.stopLive(dockSn, videoType));
}
@LogOperation("机场直播流切换")
@PostMapping("/liveCameraChange/{dockSn}")
@Operation(summary = "机场直播流切换")
@RequiresPermissions("bus:dock:live:change")
public Result<Object> liveCameraChange(@PathVariable String dockSn,
@Parameter(description = "直播流类型0-舱内1-舱外")
@RequestParam Integer cameraPosition) {
if (cameraPosition != 0 && cameraPosition != 1) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<>().ok(liveService.liveCameraChange(dockSn, cameraPosition));
}
@LogOperation("无人机直播镜头切换")
@PostMapping("/liveLensChange/{dockSn}")
@Operation(summary = "无人机直播镜头切换")
@RequiresPermissions("bus:uav:live:change")
public Result<Object> liveLensChange(@PathVariable String dockSn,
@Parameter(description = "镜头类型ir-红外normal-默认wide-广角zoom-变焦")
@RequestParam String videoType) {
if (!videoType.equals("ir") && !videoType.equals("normal") && !videoType.equals("wide") && !videoType.equals("zoom")) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<>().ok(liveService.liveLensChange(dockSn, videoType));
}
@LogOperation("直播清晰度设置")
@PostMapping("/liveSetQuality/{dockSn}")
@Operation(summary = "直播清晰度设置")
@RequiresPermissions("bus:live:set:quality")
public Result<Object> liveSetQuality(@PathVariable String dockSn,
@Parameter(description = "直播清晰度0-自适应1-流畅2-标清3-高清4-超清")
@RequestParam Integer videoQuality,
@Parameter(description = "直播类型dock-机场uav-飞机")
@RequestParam String videoType) {
if (videoQuality != 0 && videoQuality != 1 && videoQuality != 2 && videoQuality != 3 && videoQuality != 4) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
if (!videoType.equals("dock") && !videoType.equals("uav")) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<>().ok(liveService.liveSetQuality(dockSn, videoQuality, videoType));
}
@LogOperation("获取直播地址信息")
@GetMapping("/getLiveUrl/{dockSn}")
@Operation(summary = "获取直播地址信息")
@RequiresPermissions("bus:live:url:get")
public Result<List<LiveUrl>> getLiveUrl(@PathVariable String dockSn) {
return new Result<List<LiveUrl>>().ok(liveService.getLiveUrl(dockSn));
}
}

View File

@ -1,36 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.utils.Result;
import com.multictrl.modules.business.service.MinioService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* minio对象存储
*
* @author Sdy
* @since 1.0.0 2026/4/16
*/
@Tag(name = "媒体文件存储")
@RestController
@RequestMapping("/oss")
@RequiredArgsConstructor
public class MinioController {
private final MinioService minioService;
@Operation(summary = "上传航线媒体照片")
@PostMapping("/uploadRouteImg/{dockSn}")
public Result<String> uploadRouteImg(@PathVariable String dockSn,
@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return new Result<String>().error(ErrorCode.UPLOAD_FILE_EMPTY);
}
return new Result<String>().ok(minioService.uploadRouteImg(dockSn, file));
}
}

View File

@ -1,45 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.utils.Result;
import com.multictrl.modules.business.service.MiscService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* api接口
*
* @author Sdy
* @since 1.0.0 2026/5/10
*/
@RestController
@RequestMapping("business/misc")
@Tag(name = "api接口")
@ApiOrder(0)
@RequiredArgsConstructor
public class MiscController {
private final MiscService miscService;
// @LogOperation("HMS健康日志")
@GetMapping("/getHmsLog")
@Operation(summary = "HMS健康日志")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
@Parameter(name = "dockSn", description = "机库sn码"),
@Parameter(name = "level", description = "告警等级 {0:通知,1:提醒,2:警告},默认查询所有")
})
@RequiresPermissions("bus:misc:getHmsLog")
public Result<Object> getHmsLog(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
return new Result<>().ok(miscService.pageHmsLog(params));
}
}

View File

@ -1,90 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.modules.business.dto.speaker.TtsText;
import com.multictrl.modules.business.service.PsdkService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
/**
* psdk负载控制
*
* @author Sdy
* @since 1.0.0 2026/5/7
*/
@RestController
@RequestMapping("business/psdk")
@Tag(name = "psdk负载控制")
@ApiOrder(9)
@RequiredArgsConstructor
public class PsdkController {
private final PsdkService psdkService;
@LogOperation("设置音量")
@PostMapping("/setVolume/{dockSn}")
@Operation(summary = "设置音量")
@RequiresPermissions("bus:speaker:setVolume")
public Result<Object> setVolume(@PathVariable String dockSn,
@Parameter(name = "volume", description = "音量 0-100")
@RequestParam Integer volume) {
return new Result<>().ok(psdkService.setVolume(dockSn, volume));
}
@LogOperation("设置播放模式")
@PostMapping("/setMode/{dockSn}")
@Operation(summary = "设置播放模式")
@RequiresPermissions("bus:speaker:setMode")
public Result<Object> setMode(@PathVariable String dockSn,
@Parameter(name = "mode", description = "模式 0单次播放 1循环播放单曲")
@RequestParam Integer mode) {
return new Result<>().ok(psdkService.setMode(dockSn, mode));
}
@LogOperation("停止播放")
@PostMapping("/stopSpeaker/{dockSn}")
@Operation(summary = "停止播放")
@RequiresPermissions("bus:speaker:stopSpeaker")
public Result<Object> stopSpeaker(@PathVariable String dockSn) {
return new Result<>().ok(psdkService.stopSpeaker(dockSn));
}
@LogOperation("重新播放")
@PostMapping("/replaySpeaker/{dockSn}")
@Operation(summary = "重新播放")
@RequiresPermissions("bus:speaker:replaySpeaker")
public Result<Object> replaySpeaker(@PathVariable String dockSn) {
return new Result<>().ok(psdkService.replaySpeaker(dockSn));
}
@LogOperation("播放TTS文本")
@PostMapping("/startSpeakerTts/{dockSn}")
@Operation(summary = "播放TTS文本")
@RequiresPermissions("bus:speaker:startSpeakerTts")
public Result<Object> drcSetTts(@PathVariable String dockSn, @RequestBody TtsText ttsText) {
ValidatorUtils.validateEntity(ttsText);
return new Result<>().ok(psdkService.startSpeakerTts(dockSn, ttsText));
}
@LogOperation("播放音频文件")
@PostMapping("/playAudio/{dockSn}")
@Operation(summary = "播放音频文件")
@RequiresPermissions("bus:speaker:playAudio")
public Result<Object> playAudio(@PathVariable String dockSn,
@Parameter(name = "id", description = "模版编号")
@RequestParam Long id) {
return new Result<>().ok(psdkService.playAudio(dockSn, id));
}
}

View File

@ -1,106 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.page.PageData;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.AssertUtils;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.modules.business.dto.RemoteLogDTO;
import com.multictrl.modules.business.dto.remote.log.SyncRemoteLog;
import com.multictrl.modules.business.dto.remote.log.RemoteLog;
import com.multictrl.modules.business.service.RemoteLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 远程日志
*
* @author Sdy
* @since 1.0.0 2026/5/10
*/
@RestController
@RequestMapping("business/remoteLog")
@Tag(name = "远程日志")
@ApiOrder(10)
@RequiredArgsConstructor
public class RemoteLogController {
private final RemoteLogService remoteLogService;
@LogOperation("远程日志列表")
@PostMapping("/getRemoteLogList/{dockSn}")
@Operation(summary = "远程日志列表")
@RequiresPermissions("bus:remoteLog:list")
public Result<List<RemoteLog>> getRemoteLogList(@PathVariable String dockSn,
@Parameter(description = "设备类型 0-飞行器 3-机场")
@RequestParam String module) {
if (!module.equals("0") && !module.equals("3")) {
return new Result<List<RemoteLog>>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<List<RemoteLog>>().ok(remoteLogService.getRemoteLogList(dockSn, module));
}
@LogOperation("同步远程日志")
@PostMapping("/syncRemoteLog/{dockSn}")
@Operation(summary = "同步远程日志",
description = "同步机库或飞机日志到minio,这一步如果成功会立马返回机库会自动将文件上传到minio可以通过fileupload_progress监听上传进度")
@RequiresPermissions("bus:remoteLog:sync")
public Result<Object> syncRemoteLog(@PathVariable String dockSn,
@RequestBody SyncRemoteLog remoteLog) {
ValidatorUtils.validateEntity(remoteLog);
return new Result<>().ok(remoteLogService.syncRemoteLog(dockSn, remoteLog));
}
@LogOperation("取消日志同步")
@PostMapping("/cancelSyncRemoteLog/{dockSn}")
@Operation(summary = "取消日志同步", description = "当前日志上传启动后,暂时不支持上传任务暂停、暂停后恢复、上传任务拒绝")
@RequiresPermissions("bus:remoteLog:cancelSync")
public Result<Object> cancelSyncRemoteLog(@PathVariable String dockSn,
@Parameter(description = "设备类型 0-飞行器 3-机场")
@RequestParam String module) {
if (!module.equals("0") && !module.equals("3")) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<>().ok(remoteLogService.cancelSyncRemoteLog(dockSn, module));
}
@GetMapping("page")
@Operation(summary = "日志列表查询")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
@Parameter(name = "dockSn", description = "机场SN"),
@Parameter(name = "module", description = "设备类型 0飞机 3机场")
})
@RequiresPermissions("bus:remoteLog:page")
public Result<PageData<RemoteLogDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<RemoteLogDTO> page = remoteLogService.queryPage(params);
return new Result<PageData<RemoteLogDTO>>().ok(page);
}
@DeleteMapping
@Operation(summary = "删除日志")
@LogOperation("删除日志")
@RequiresPermissions("bus:remoteLog:delete")
public Result<Object> delete(@RequestBody Long[] ids) {
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
remoteLogService.delete(ids);
return new Result<>();
}
}

View File

@ -1,98 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.page.PageData;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.AssertUtils;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.service.RouteService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import java.util.Map;
/**
* 航线信息表
*
* @author Sdy
* @since 1.0.0 2026-04-20
*/
@RestController
@RequestMapping("business/route")
@Tag(name = "航线管理")
@ApiOrder(2)
@RequiredArgsConstructor
public class RouteController {
private final RouteService routeService;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
@Parameter(name = "routeName", description = "航线名称"),
@Parameter(name = "dockSn", description = "机库SN码")
})
@RequiresPermissions("bus:route:page")
public Result<PageData<RouteDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<RouteDTO> page = routeService.page(params);
return new Result<PageData<RouteDTO>>().ok(page);
}
@GetMapping("{id}")
@Operation(summary = "信息")
@RequiresPermissions("bus:route:info")
public Result<RouteDTO> get(@PathVariable("id") Long id) {
RouteDTO data = routeService.getRoute(id);
return new Result<RouteDTO>().ok(data);
}
@PostMapping
@Operation(summary = "保存")
@LogOperation("保存")
@RequiresPermissions("bus:route:save")
public Result<Object> save(@RequestBody RouteDTO dto) {
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class);
routeService.saveRoute(dto);
return new Result<>();
}
@PutMapping
@Operation(summary = "修改")
@LogOperation("修改")
@RequiresPermissions("bus:route:update")
public Result<Object> update(@RequestBody RouteDTO dto) {
//效验数据
ValidatorUtils.validateEntity(dto, UpdateGroup.class);
routeService.updateRoute(dto);
return new Result<>();
}
@DeleteMapping
@Operation(summary = "删除")
@LogOperation("删除")
@RequiresPermissions("bus:route:delete")
public Result<Object> delete(@RequestBody Long[] ids) {
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
routeService.delete(ids);
return new Result<>();
}
}

View File

@ -1,135 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.utils.Result;
import com.multictrl.modules.business.dto.flight.FlightExecute;
import com.multictrl.modules.business.service.DJIBaseService;
import com.multictrl.modules.business.service.RouteFlightService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 航线飞行管理
*
* @author Sdy
* @since 1.0.0 2026/4/27
*/
@RestController
@RequestMapping("business/command")
@Tag(name = "航线飞行", description = """
航线管理是无人机自主作业的重要功能
可以实现行业领域的批量化智能化作业
上云 API 提供了相关的接口实现了航线任务在云端的共享查看下发执行取消以及进度上报等功能
用户需要遵照航线文件格式规范WPML编写航线文件定义航线任务一个航线任务中可以定义多条航线""")
@ApiOrder(5)
@RequiredArgsConstructor
public class RouteFlightController {
private final DJIBaseService djiBaseService;
private final RouteFlightService routeFlightService;
@LogOperation("一键返航")
@PostMapping("/returnHome/{dockSn}")
@Operation(summary = "一键返航")
@RequiresPermissions("bus:route:flight:returnHome")
public Result<Object> returnHome(@PathVariable String dockSn) {
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "return_home"));
}
@LogOperation("取消返航")
@PostMapping("/returnHomeCancel/{dockSn}")
@Operation(summary = "取消返航", description = "返航后,飞行器会退出航线模式,此时取消返航,飞行器会悬停")
@RequiresPermissions("bus:route:flight:returnHomeCancel")
public Result<Object> returnHomeCancel(@PathVariable String dockSn) {
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "return_home_cancel"));
}
@LogOperation("航线恢复")
@PostMapping("/flightTaskRecovery/{dockSn}")
@Operation(summary = "航线恢复")
@RequiresPermissions("bus:route:flight:flightTaskRecovery")
public Result<Object> flightTaskRecovery(@PathVariable String dockSn) {
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "flighttask_recovery"));
}
@LogOperation("航线暂停")
@PostMapping("/flightTaskPause/{dockSn}")
@Operation(summary = "航线暂停")
@RequiresPermissions("bus:route:flight:flightTaskPause")
public Result<Object> flightTaskPause(@PathVariable String dockSn) {
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "flighttask_pause"));
}
@LogOperation("取消任务")
@PostMapping("/flightTaskUndo/{dockSn}")
@Operation(summary = "取消任务")
@RequiresPermissions("bus:route:flight:flightTaskUndo")
public Result<Object> flightTaskUndo(@PathVariable String dockSn,
@RequestBody List<String> taskIds) {
if (taskIds.isEmpty()) {
return new Result<>().error(ErrorCode.PARAMS_ERROR);
}
return new Result<>().ok(routeFlightService.flightTaskUndo(dockSn, taskIds));
}
@LogOperation("执行航线")
@PostMapping("/flightExecute/{dockSn}")
@Operation(summary = "执行航线")
@RequiresPermissions("bus:route:flight:flightExecute")
public Result<Object> flightExecute(@PathVariable String dockSn,
@RequestBody FlightExecute flightExecute) {
return new Result<>().ok(routeFlightService.flightExecute(dockSn, flightExecute));
}
@LogOperation("空中下发航线")
@PostMapping("/inFlightWaylineDeliver/{dockSn}")
@Operation(summary = "空中下发航线")
@RequiresPermissions("bus:route:flight:inFlightWaylineDeliver")
public Result<Object> inFlightWaylineDeliver(@PathVariable String dockSn,
@Parameter(name = "routeId", description = "航线标识")
@RequestParam Long routeId) {
return new Result<>().ok(routeFlightService.inFlightWaylineDeliver(dockSn, routeId));
}
@LogOperation("暂停空中航线")
@PostMapping("/inFlightWaylineStop/{dockSn}")
@Operation(summary = "暂停空中航线")
@RequiresPermissions("bus:route:flight:inFlightWaylineStop")
public Result<Object> inFlightWaylineStop(@PathVariable String dockSn) {
return new Result<>().ok(routeFlightService.inFlightWaylineStop(dockSn));
}
@LogOperation("恢复空中航线")
@PostMapping("/inFlightWaylineRecover/{dockSn}")
@Operation(summary = "恢复空中航线")
@RequiresPermissions("bus:route:flight:inFlightWaylineRecover")
public Result<Object> inFlightWaylineRecover(@PathVariable String dockSn) {
return new Result<>().ok(routeFlightService.inFlightWaylineRecover(dockSn));
}
@LogOperation("取消空中航线")
@PostMapping("/inFlightWaylineCancel/{dockSn}")
@Operation(summary = "取消空中航线")
@RequiresPermissions("bus:route:flight:inFlightWaylineCancel")
public Result<Object> inFlightWaylineCancel(@PathVariable String dockSn) {
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "in_flight_wayline_cancel"));
}
}

View File

@ -1,90 +0,0 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.modules.business.dto.speaker.SpeakerSet;
import com.multictrl.modules.business.dto.speaker.TtsText;
import com.multictrl.modules.business.service.SpeakerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
/**
* 喊话器控制 大疆官方AS1喊话器
*
* @author Sdy
* @since 1.0.0 2026/5/6
*/
@RestController
@RequestMapping("business/speaker")
@Tag(name = "喊话器控制")
@ApiOrder(8)
@RequiredArgsConstructor
public class SpeakerController {
private final SpeakerService speakerService;
@LogOperation("设置音量")
@PostMapping("/drcSetVolume/{dockSn}")
@Operation(summary = "设置音量")
@RequiresPermissions("bus:speaker:drcSetVolume")
public Result<Object> drcSetVolume(@PathVariable String dockSn,
@Parameter(name = "volume", description = "音量 0-100")
@RequestParam Integer volume) {
return new Result<>().ok(speakerService.drcSetVolume(dockSn, volume));
}
@LogOperation("模式设置")
@PostMapping("/drcSetMode/{dockSn}")
@Operation(summary = "模式设置")
@RequiresPermissions("bus:speaker:drcSetMode")
public Result<Object> drcSetMode(@PathVariable String dockSn,
@Parameter(name = "mode", description = "模式 0单次播放 1循环播放单曲")
@RequestParam Integer mode) {
return new Result<>().ok(speakerService.drcSetMode(dockSn, mode));
}
@LogOperation("喊话设置")
@PostMapping("/drcSetTts/{dockSn}")
@Operation(summary = "喊话设置")
@RequiresPermissions("bus:speaker:drcSetTts")
public Result<Object> drcSetTts(@PathVariable String dockSn, @RequestBody SpeakerSet ttsSpeaker) {
ValidatorUtils.validateEntity(ttsSpeaker);
return new Result<>().ok(speakerService.drcSetTts(dockSn, ttsSpeaker));
}
@LogOperation("停止播放")
@PostMapping("/drcStopSpeaker/{dockSn}")
@Operation(summary = "停止播放")
@RequiresPermissions("bus:speaker:drcStopSpeaker")
public Result<Object> drcStopSpeaker(@PathVariable String dockSn) {
return new Result<>().ok(speakerService.drcStopSpeaker(dockSn));
}
@LogOperation("重新播放")
@PostMapping("/drcReplaySpeaker/{dockSn}")
@Operation(summary = "重新播放")
@RequiresPermissions("bus:speaker:drcReplaySpeaker")
public Result<Object> drcReplaySpeaker(@PathVariable String dockSn) {
return new Result<>().ok(speakerService.drcReplaySpeaker(dockSn));
}
@LogOperation("播放TTS文本")
@PostMapping("/drcStartSpeakerTts/{dockSn}")
@Operation(summary = "播放TTS文本")
@RequiresPermissions("bus:speaker:drcStartSpeakerTts")
public Result<Object> drcSetTts(@PathVariable String dockSn, @RequestBody TtsText ttsText) {
ValidatorUtils.validateEntity(ttsText);
return new Result<>().ok(speakerService.drcStartSpeakerTts(dockSn, ttsText));
}
}

View File

@ -1,124 +0,0 @@
package com.multictrl.modules.business.controller;
import cn.hutool.json.JSONArray;
import com.multictrl.common.utils.Result;
import com.multictrl.modules.business.dto.SrsCallBackDTO;
import com.multictrl.modules.business.service.SrsService;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* SRS直播流管理
*
* @author Sdy
* @since 1.0.0 2026-05-07
*/
@Slf4j
@Hidden
@RestController
@RequestMapping("/srs")
@Tag(name = "SRS直播流管理")
@RequiredArgsConstructor
public class SrsController {
private final SrsService srsService;
@PostMapping("/streamsBySn/{dockSn}")
@Operation(summary = "根据sn号查询流列表查所有数据时sn传all")
public Result<JSONArray> streamsBySn(@PathVariable String dockSn) {
return new Result<JSONArray>().ok(srsService.streamsBySn(dockSn));
}
@Operation(hidden = true)
@PostMapping("/on_connect")
public String onConnect(@RequestBody SrsCallBackDTO srsCallBack) {
// log.info("on_connect {}", srsCallBack);
return "0";
}
@Operation(hidden = true)
@PostMapping("/on_close")
public String onClose(@RequestBody SrsCallBackDTO srsCallBack) {
// log.info("on_close {}", srsCallBack);
return "0";
}
@Operation(hidden = true)
@PostMapping("/on_publish")
public String onPublish(@RequestBody SrsCallBackDTO srsCallBack) {
// log.info("on_publish {}", srsCallBack);
if (srsService.checkToken(srsCallBack)) {
return "0";
}
return "-1";
}
@Operation(hidden = true)
@PostMapping("/on_unpublish")
public String onUnpublish(@RequestBody SrsCallBackDTO srsCallBack) {
// log.info("on_unpublish {}", srsCallBack);
return "0";
}
@Operation(hidden = true)
@PostMapping("/on_play")
public String onPlay(@RequestBody SrsCallBackDTO srsCallBack) {
// log.debug("on_play {}", srsCallBack);
return "0";
}
@Operation(hidden = true)
@PostMapping("/on_stop")
public String onStop(@RequestBody SrsCallBackDTO srsCallBack) {
// log.debug("on_stop {}", srsCallBack);
return "0";
}
@Operation(hidden = true)
@PostMapping("/on_dvr")
public String onDvr(@RequestBody SrsCallBackDTO srsCallBack) {
// log.info("on_dvr {}", srsCallBack);
return "0";
}
public static void main(String[] args) {
String file = "/live_record/zc/aaaaa/2025-04-03_16-33-43-404.mp4";
String dateTime = (file = file.substring(file.lastIndexOf("/") + 1))
.substring(0, file.indexOf("."));
dateTime = dateTime.split("_")[0] + " " + dateTime.split("_")[1]
.replaceAll("-", ":");
System.out.println(dateTime);
}
@Operation(hidden = true)
@PostMapping("/on_hls")
public String onHls(@RequestBody SrsCallBackDTO srsCallBack) {
// log.debug("on_hls {}", srsCallBack);
return "0";
}
/*@Operation(hidden = true)
@PostMapping("/on_hls_notify")
public String onHlsNotify(@RequestBody SrsCallBackDTO srsCallBack) {
log.debug("on_hls_notify {}", srsCallBack);
return "0";
}*/
@Operation(hidden = true)
@RequestMapping(value = "/on_hls_notify", method = {RequestMethod.GET, RequestMethod.POST})
public String onHlsNotify(@RequestParam Map<String, String> allParams) {
// log.debug("on_hls_notify: {}", allParams);
return "0";
}
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.CacheEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 数据缓存表
*
* @author Sdy
* @since 1.0.0 2026-04-27
*/
@Mapper
public interface CacheDao extends BaseDao<CacheEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.DeviceDicEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 设备字典
*
* @author Sdy
* @since 1.0.0 2026-05-11
*/
@Mapper
public interface DeviceDicDao extends BaseDao<DeviceDicEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.DockEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 机库信息表
*
* @author Sdy
* @since 1.0.0 2026-04-17
*/
@Mapper
public interface DockDao extends BaseDao<DockEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.DockDeviceEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 机场设备信息
*
* @author Sdy
* @since 1.0.0 2026-04-23
*/
@Mapper
public interface DockDeviceDao extends BaseDao<DockDeviceEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.FirmwareEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 固件表
*
* @author Sdy
* @since 1.0.0 2026-05-11
*/
@Mapper
public interface FirmwareDao extends BaseDao<FirmwareEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.FlightTaskEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 飞行架次
*
* @author Sdy
* @since 1.0.0 2026-05-02
*/
@Mapper
public interface FlightTaskDao extends BaseDao<FlightTaskEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.HmsEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 健康告警
*
* @author Sdy
* @since 1.0.0 2026-05-10
*/
@Mapper
public interface HmsDao extends BaseDao<HmsEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.MediaFileEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 媒体资源
*
* @author Sdy
* @since 1.0.0 2026-04-29
*/
@Mapper
public interface MediaFileDao extends BaseDao<MediaFileEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.MultiGroupEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 蛙跳组信息
*
* @author 张闯
* @since 2026-05-28
*/
@Mapper
public interface MultiGroupDao extends BaseDao<MultiGroupEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.MultiGroupDeviceEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 蛙跳组绑定的设备信息
*
* @author 张闯
* @since 2026-05-28
*/
@Mapper
public interface MultiGroupDeviceDao extends BaseDao<MultiGroupDeviceEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.RemoteLogEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 远程日志
*
* @author Sdy
* @since 1.0.0 2026-05-10
*/
@Mapper
public interface RemoteLogDao extends BaseDao<RemoteLogEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.RouteEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 航线信息表
*
* @author Sdy
* @since 1.0.0 2026-04-20
*/
@Mapper
public interface RouteDao extends BaseDao<RouteEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.RouteWaypointEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 航线航点信息
*
* @author Sdy
* @since 1.0.0 2026-04-20
*/
@Mapper
public interface RouteWaypointDao extends BaseDao<RouteWaypointEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.SpeakerEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 喊话器内容
*
* @author Sdy
* @since 1.0.0 2026-05-08
*/
@Mapper
public interface SpeakerDao extends BaseDao<SpeakerEntity> {
}

View File

@ -1,20 +0,0 @@
package com.multictrl.modules.business.dao;
import cn.hutool.json.JSONObject;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* srs直播流管理
*
* @author Sdy
* @since 1.0.0 2026/5/7
*/
@FeignClient(name = "srs-api", url = "${srs.server-url}")
public interface SrsClient {
@RequestMapping(value = "/api/v1/streams", method = RequestMethod.GET)
JSONObject serviceSrsStream(@RequestParam("start") int start, @RequestParam("count") int count);
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.SrsRecordEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 直播流记录
*
* @author Sdy
* @since 1.0.0 2026-05-07
*/
@Mapper
public interface SrsRecordDao extends BaseDao<SrsRecordEntity> {
}

View File

@ -1,16 +0,0 @@
package com.multictrl.modules.business.dao;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.entity.WaypointActionEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 航点动作信息
*
* @author Sdy
* @since 1.0.0 2026-04-20
*/
@Mapper
public interface WaypointActionDao extends BaseDao<WaypointActionEntity> {
}

View File

@ -1,93 +0,0 @@
package com.multictrl.modules.business.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 机库信息表
*
* @author Sdy
* @since 1.0.0 2026-04-17
*/
@Data
@Schema(name = "机库信息表")
public class DockDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Null(message = "{id.null}", groups = AddGroup.class)
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
@Schema(description = "主键")
private Long id;
@NotBlank(message = "{dock.name.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "机库名称")
private String dockName;
@NotBlank(message = "{dock.sn.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "SN码")
private String dockSn;
@NotBlank(message = "{dock.type.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "机库类型")
private String dockType;
@NotBlank(message = "{dock.model.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "机库型号")
private String dockModel;
@NotBlank(message = "{dock.model.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "使用场景")
private String scenario;
@NotNull(message = "{dept.id.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "所属部门")
private Long deptId;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "机库经度")
private Double dockLongitude;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "机库纬度")
private Double dockLatitude;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "机库高度(椭球高)")
private Double dockHeight;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "备降点经度")
private Double deputyLongitude;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "备降点纬度")
private Double deputyLatitude;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "备降点高度")
private Double deputyHeight;
@Schema(description = "备注")
private String remarks;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "更新时间")
private Date updateDate;
}

View File

@ -1,101 +0,0 @@
package com.multictrl.modules.business.dto;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/**
* 机场设备信息
*
* @author Sdy
* @since 1.0.0 2026-04-23
*/
@Data
@Schema(name = "机场设备信息")
public class DockDeviceDTO {
@Schema(description = "主键")
private Long id;
/**
* 设备序列号
*/
@Schema(description = "设备序列号")
private String sn;
/**
* 设备名称
*/
@Schema(description = "设备名称")
private String deviceName;
/**
* 产品型号
*/
@Schema(description = "产品型号")
private String deviceModelKey;
/**
* 设备绑定码
*/
@Schema(description = "设备绑定码")
private String deviceBindingCode;
/**
* 组织 ID
*/
@Schema(description = "组织 ID")
private String organizationId;
/**
* 设备在组织中的名称
*/
@Schema(description = "设备在组织中的名称")
private String deviceCallsign;
/**
* 父类飞机的父类是机场
*/
@Schema(description = "父类(飞机的父类是机场)")
private String parentSn;
/**
* 绑定时间
*/
@Schema(description = "绑定时间")
private Date bindDate;
/**
* 领域
* 0飞机类
* 1负载类
* 2遥控器类
* 3机场类
*/
@Schema(description = "领域")
private Integer domain;
/**
* 设备类型
*/
@Schema(description = "设备类型")
private Integer deviceType;
/**
* 设备子类型
*/
@Schema(description = "设备子类型")
private Integer subType;
/**
* 修改时间
*/
@Schema(description = "时间")
private Date updateDate;
/**
* 设备密钥
*/
@Schema(description = "设备密钥")
private String deviceSecret;
/**
* nonce
*/
@Schema(description = "nonce")
private String nonce;
/**
* 设备物模型版本
*/
@Schema(description = "设备物模型版本")
private String thingVersion;
}

View File

@ -1,62 +0,0 @@
package com.multictrl.modules.business.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 固件表
*
* @author Sdy
* @since 1.0.0 2026-05-11
*/
@Data
@Schema(name = "固件表")
public class FirmwareDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
@NotNull(message = "标识不能为空")
private Long id;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "设备名称")
private String deviceName;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "文件名称")
private String fileName;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "固件版本")
private String firmwareVersion;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "文件路径")
private String path;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "文件长度")
private Long fileSize;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "文件md5")
private String fileMd5;
@Schema(description = "备注")
private String remark;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "发布时间")
private Date releasedDate;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "创建时间")
private Date createDate;
}

View File

@ -1,103 +0,0 @@
package com.multictrl.modules.business.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 飞行架次参数
*
* @author Sdy
* @since 1.0.0 2026-05-02
*/
@Data
@Schema(name = "飞行架次参数")
public class FlightTaskDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "任务标识")
private String taskId;
@Schema(description = "任务类型 (1航线飞行 2手动飞行 3定时飞行)")
private Integer taskType;
@Schema(description = "机库SN")
private String dockSn;
@Schema(description = "机库名称")
private String dockName;
@Schema(description = "机库类型")
private String dockType;
@Schema(description = "机库型号")
private String dockModel;
@Schema(description = "航线标识")
private Long routeId;
@Schema(description = "航线名称")
private String routeName;
@Schema(description = "航线类型")
private String routeType;
@Schema(description = "航线距离")
private Double routeDistance;
@Schema(description = "航点数量")
private Integer waypointNum;
@Schema(description = "部门标识")
private Long deptId;
@Schema(description = "任务状态0进行中、-1失败、1完成、2阻飞")
private Integer taskStatus;
@Schema(description = "飞行距离")
private Double flightDistance;
@Schema(description = "飞行时长(秒)")
private Long flightDuration;
@Schema(description = "飞行时长")
private String flightDurationText;
@Schema(description = "媒体数量")
private Integer mediaNum;
@Schema(description = "是否断点续飞")
private Boolean isBreakpointFly;
@Schema(description = "断点续飞最大时间")
private String breakpointFlyMaxTime;
@Schema(description = "断点续飞最小时间")
private String breakpointFlyMinTime;
@Schema(description = "出库时间")
private Date outboundDate;
@Schema(description = "入库时间")
private Date inboundDate;
@Schema(description = "任务时间")
private Date createDate;
@Schema(description = "操作人")
private Long creator;
@Schema(description = "操作人名称")
private String createName;
@Schema(description = "失败原因")
private String failureReason;
@Schema(description = "飞机SN")
private String uavSn;
}

View File

@ -1,58 +0,0 @@
package com.multictrl.modules.business.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 健康告警
*
* @author Sdy
* @since 1.0.0 2026-05-10
*/
@Data
@Schema(name = "健康告警")
public class HmsDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(name = "网关编号")
private String gateway;
@Schema(name = "设备编号")
private String dockSn;
@Schema(name = "设备类型")
private String deviceType;
@Schema(name = "设备名称")
private String deviceName;
@Schema(name = "告警码")
private String code;
@Schema(name = "事件描述")
private String description;
@Schema(name = "是否为及时性的")
private String imminent;
@Schema(name = "是否飞行")
private String inTheSky;
@Schema(name = "告警等级 {0:通知,1:提醒,2:警告}")
private Integer level;
@Schema(name = "事件模块")
private String module;
@Schema(name = "创建时间")
private Date createDate;
@Schema(name = "上报时间")
private Date reportDate;
}

View File

@ -1,19 +0,0 @@
package com.multictrl.modules.business.dto;
import lombok.Data;
/**
* 经纬度参数
*
* @author Sdy
* @since 1.0.0 2026/4/27
*/
@Data
public class LatLonDTO {
private Double latitude;
private Double longitude;
private Double height;
}

View File

@ -1,70 +0,0 @@
package com.multictrl.modules.business.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 媒体资源
*
* @author Sdy
* @since 1.0.0 2026-04-29
*/
@Data
@Schema(description = "媒体资源参数")
public class MediaFileDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "架次编号")
private String taskId;
@Schema(description = "设备编号")
private String dockSn;
@Schema(description = "飞行型号")
private String droneModelKey;
@Schema(description = "云台型号")
private String payloadModelKey;
@Schema(description = "是否原文件")
private Boolean original;
@Schema(description = "文件名称")
private String name;
@Schema(description = "文件路径")
private String objectKey;
@Schema(description = "文件的业务路径")
private String path;
@Schema(description = "拍摄经度")
private String longitude;
@Schema(description = "拍摄纬度")
private String latitude;
@Schema(description = "相对高度")
private String relativeAltitude;
@Schema(description = "海拔高度")
private String absoluteAltitude;
@Schema(description = "云台偏航角")
private String gimbalYawDegree;
@Schema(description = "文件类型")
private String subFileType;
@Schema(description = "拍摄时间")
private Date createdTime;
@Schema(description = "事件收到时间")
private Date createTime;
}

View File

@ -1,71 +0,0 @@
package com.multictrl.modules.business.dto;
import com.multictrl.modules.business.dto.error.DJICloudErrorCode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 远程日志
*
* @author Sdy
* @since 1.0.0 2026-05-10
*/
@Data
@Schema(name = "远程日志")
public class RemoteLogDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(name = "主键")
private Long id;
@Schema(name = "设备类型 0:飞机 3:机库")
private String module;
@Schema(name = "导出文件的名称")
private String filename;
@Schema(name = "状态")
private String status;
@Schema(name = "当前步骤")
private Integer currentStep;
@Schema(name = "步骤总数")
private Integer totalStep;
@Schema(name = "上传速率")
private Integer uploadRate;
@Schema(name = "上传速率文本")
private String uploadRateText;
@Schema(name = "文件大小")
private Long size;
@Schema(name = "文件大小文本")
private String sizeText;
@Schema(name = "响应码")
private Integer code;
@Schema(name = "文件路径")
private String path;
@Schema(name = "当前日志的时间集合,逗号隔开")
private String times;
@Schema(name = "机库SN")
private String dockSn;
@Schema(name = "创建时间")
private Date createDate;
@Schema(name = "错误信息")
private DJICloudErrorCode cloudErrorCode;
}

View File

@ -1,133 +0,0 @@
package com.multictrl.modules.business.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 航线信息表
*
* @author Sdy
* @since 1.0.0 2026-04-20
*/
@Data
@Schema(name = "航线信息表")
public class RouteDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Null(message = "{id.null}", groups = AddGroup.class)
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
@Schema(description = "标识")
private Long id;
@NotBlank(message = "{route.name.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "航线名称")
private String routeName;
@NotBlank(message = "{route.type.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "航线类型")
private String templateType;
@NotBlank(message = "{route.height.model.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "高度模式")
private String heightModel;
@NotNull(message = "{route.flight.speed.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "飞行速度")
private Double flightSpeed;
@NotNull(message = "{route.flight.height.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "飞行高度")
private Double flightHeight;
@NotNull(message = "{route.global.rth.height.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "返航高度")
private Double globalRthHeight;
@NotNull(message = "{route.takeoff.security.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "安全起飞高度")
private Double takeoffSecurityHeight;
@NotBlank(message = "{route.finish.action.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "结束动作")
private String finishAction;
@NotBlank(message = "{route.exit.on.rc.lost.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "失控操作")
private String exitOnRcLost;
@Schema(description = "失控动作")
private String executeRcLostAction;
@NotNull(message = "{route.global.transitional.speed.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "全局航线过渡速度")
private Double globalTransitionalSpeed;
@NotBlank(message = "{route.fly.to.wayline.mode.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "飞向首航点模式")
private String flyToWaylineMode;
@NotBlank(message = "{dock.sn.require}", groups = {AddGroup.class, UpdateGroup.class})
@JsonProperty(required = true)
@Schema(description = "机库SN码")
private String dockSn;
/* @Schema(description = "所属部门")
private Long deptId;*/
@Schema(description = "图片地址")
private String imgUrl;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "kmz地址")
private String kmzUrl;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "航点个数")
private Integer waypointNum;
@JsonProperty(required = true)
@NotNull(message = "{route.total.distance.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "总距离")
private Double totalDistance;
@JsonProperty(required = true)
@NotNull(message = "{route.expect.flight.time.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "预计飞行时长")
private String expectFlightTime;
@JsonProperty(required = true)
@NotNull(message = "{route.is.accurate.import.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "是否精准导入")
private Boolean isAccurateImport;
@Schema(description = "更新时间")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Date updateDate;
@JsonProperty(required = true)
@Schema(description = "航点信息")
private List<RouteWaypointDTO> routeWaypointList;
}

View File

@ -1,87 +0,0 @@
package com.multictrl.modules.business.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 航线航点信息
*
* @author Sdy
* @since 1.0.0 2026-04-20
*/
@Data
@Schema(name = "航线航点信息")
public class RouteWaypointDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@JsonProperty(required = true)
@NotNull(message = "{route.waypoint.longitude.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "航点经度")
private Double longitude;
@JsonProperty(required = true)
@NotNull(message = "{route.waypoint.latitude.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "航点纬度")
private Double latitude;
@JsonProperty(required = true)
@NotNull(message = "{route.waypoint.flight.speed.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "航点飞行速度")
private Double flightSpeed;
@JsonProperty(required = true)
@NotNull(message = "{route.waypoint.flight.height.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "航点飞行高度")
private Double flightHeight;
@JsonProperty(required = true)
@NotNull(message = "{route.waypoint.follow.route.speed.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "跟随航线速度")
private Boolean followRouteSpeed;
@JsonProperty(required = true)
@NotNull(message = "{route.waypoint.follow.route.height.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "跟随航线高度")
private Boolean followRouteHeight;
@JsonProperty(required = true)
@NotBlank(message = "{route.waypoint.heading.mode.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "飞行器偏航角模式")
private String waypointHeadingMode;
@Schema(description = "飞行器偏航角度")
private String waypointHeadingAngle;
@JsonProperty(required = true)
@NotBlank(message = "{route.waypoint.heading.path.mode.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "飞行器偏航角转向")
private String waypointHeadingPathMode;
@Schema(description = "兴趣点")
private String waypointPoiPoint;
@JsonProperty(required = true)
@NotBlank(message = "{route.waypoint.turning.mode.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "航点转弯模式")
private String waypointTruningMode;
@Schema(description = "航点转弯截距")
private String waypointTurnDampingDist;
@Schema(description = "航点动作")
private List<WaypointActionDTO> waypointActionList;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "航点顺序")
private Integer waypointSort;
}

View File

@ -1,48 +0,0 @@
package com.multictrl.modules.business.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 喊话器内容
*
* @author Sdy
* @since 1.0.0 2026-05-08
*/
@Data
@Schema(name = "喊话器内容")
public class SpeakerDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(name = "主键")
private Long id;
@Schema(name = "类型 1 文本 2 音频")
private Integer type;
@Schema(name = "机场编号")
private String dockSn;
@Schema(name = "模版名称")
private String name;
@Schema(name = "文本内容")
private String textContent;
@Schema(name = "pcm音频地址")
private String pcmPath;
@Schema(name = "源音频文件地址")
private String mediaPath;
@Schema(name = "文件md5")
private String md5;
@Schema(name = "创建时间")
private Date createDate;
}

View File

@ -1,31 +0,0 @@
package com.multictrl.modules.business.dto;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class SrsCallBackDTO {
String action;
String client_id;
String ip;
String vhost;
String app;
String tcUrl;
String send_bytes;
String recv_bytes;
String stream;
String param;
String duration;
String cwd;
String file;
String url;
String m3u8_url;
String seq_no;
String server_id;
String stream_url;
String stream_id;
}

View File

@ -1,34 +0,0 @@
package com.multictrl.modules.business.dto;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 航点动作信息
*
* @author Sdy
* @since 1.0.0 2026-04-20
*/
@Data
@Schema(name = "航点动作信息")
public class WaypointActionDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@NotBlank(message = "{route.waypoint.action.type.require}", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "航点动作类型")
private String actionType;
@Schema(description = "动作值")
private String actionValue;
@Schema(description = "航点顺序", hidden = true)
// @JsonIgnore
private String waypointOrder;
}

View File

@ -1,37 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 双击成为 AIM参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "双击成为 AIM参数")
public class AimParam {
//机头和云台的相对关系是否锁定 {"false":"仅云台转,机身不转","true":"锁定机头,云台和机身一起转"}
// private Boolean locked;
/*@NotBlank(message = "相机模式不能为空")
@Schema(description = "相机模式 ir:红外 wide:广角 zoom:变焦")
private String cameraType;*/
@DecimalMax(value = "1", message = "目标坐标 x最大值为1")
@DecimalMin(value = "0", message = "目标坐标 x最小值为0")
@NotNull(message = "目标坐标 x不能为空")
@Schema(description = "目标坐标 x以镜头的左上角为坐标中心点水平方向为 x")
private Double x;
@DecimalMax(value = "1", message = "目标坐标 y最大值为1")
@DecimalMin(value = "0", message = "目标坐标 y最小值为0")
@NotNull(message = "目标坐标 y不能为空")
@Schema(description = "目标坐标 y以镜头的左上角为坐标中心点竖直方向为 y")
private Double y;
}

View File

@ -1,36 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 变焦参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "变焦参数")
public class FocalLengthSet {
// @NotBlank(message = "相机模式不能为空")
@Schema(description = "相机模式 ir:红外 wide:广角 zoom:变焦", hidden = true)
private String cameraType;
@NotNull(message = "变焦倍数不能为空")
@Schema(description = "变焦倍数可见光是2-200红外是2-20")
private Double zoomFactor;
@JsonIgnore
@Schema(hidden = true)
@AssertTrue(message = "变焦倍数设置错误")
public boolean isZoomFactorValid() {
if (cameraType.equals("ir")) {
return 2 <= zoomFactor && zoomFactor <= 20;
} else {
return 2 <= zoomFactor && zoomFactor <= 200;
}
}
}

View File

@ -1,42 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 框选变焦参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "框选变焦参数")
public class FrameZoom {
@DecimalMax(value = "1", message = "目标框左上角点坐标x最大值为1")
@DecimalMin(value = "0", message = "目标框左上角点坐标x最小值为0")
@NotNull(message = "目标框左上角点坐标x不能为空")
@Schema(description = "目标框左上角点坐标x")
private Float x;
@DecimalMax(value = "1", message = "目标框左上角点坐标y最大值为1")
@DecimalMin(value = "0", message = "目标框左上角点坐标y最小值为0")
@NotNull(message = "目标框左上角点坐标y不能为空")
@Schema(description = "目标框左上角点坐标y")
private Float y;
@DecimalMax(value = "1", message = "目标框宽度最大值为1")
@DecimalMin(value = "0", message = "目标框宽度最小值为0")
@NotNull(message = "目标框宽度不能为空")
@Schema(description = "目标框宽度")
private Float width;
@DecimalMax(value = "1", message = "目标框高度最大值为1")
@DecimalMin(value = "0", message = "目标框高度最小值为0")
@NotNull(message = "目标框高度不能为空")
@Schema(description = "目标框高度")
private Float height;
}

View File

@ -1,42 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 红外测温区域设置参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "红外测温区域设置参数")
public class IrMeteringAreaSet {
@NotNull(message = "测温区域左上角点坐标x不能为空")
@DecimalMax(value = "1", message = "测温区域左上角点坐标x 范围为0~1")
@DecimalMin(value = "0", message = "测温区域左上角点坐标x 范围为0~1")
@Schema(description = "测温区域左上角点坐标x以镜头的左上角为坐标中心点水平方向为x")
private Double x;
@NotNull(message = "测温区域左上角点坐标y不能为空")
@DecimalMax(value = "1", message = "测温区域左上角点坐标y 范围为0~1")
@DecimalMin(value = "0", message = "测温区域左上角点坐标y 范围为0~1")
@Schema(description = "测温区域左上角点坐标y以镜头的左上角为坐标中心点竖直方向为 y")
private Double y;
@NotNull(message = "测温区域宽度不能为空")
@DecimalMax(value = "1", message = "测温区域宽度 范围为0~1")
@DecimalMin(value = "0", message = "测温区域宽度 范围为0~1")
@Schema(description = "测温区域宽度")
private Double width;
@NotNull(message = "测温区域高度不能为空")
@DecimalMax(value = "1", message = "测温区域高度 范围为0~1")
@DecimalMin(value = "0", message = "测温区域高度 范围为0~1")
@Schema(description = "测温区域高度")
private Double height;
}

View File

@ -1,30 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 红外测温点设置参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "红外测温点设置参数")
public class IrMeteringPointSet {
@NotNull(message = "测温点坐标x不能为空")
@DecimalMax(value = "1", message = "测温点坐标x 范围为0~1")
@DecimalMin(value = "0", message = "测温点坐标x 范围为0~1")
@Schema(description = "测温点坐标 x以镜头的左上角为坐标中心点水平方向为 x")
private Double x;
@NotNull(message = "测温点坐标y不能为空")
@DecimalMax(value = "1", message = "测温点坐标y 范围为0~1")
@DecimalMin(value = "0", message = "测温点坐标y 范围为0~1")
@Schema(description = "测温点坐标 y以镜头的左上角为坐标中心点竖直方向为 y")
private Double y;
}

View File

@ -1,36 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* LookAt参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "LookAt参数")
public class LookAt {
@NotNull(message = "目标点纬度不能为空")
@DecimalMax(value = "90", message = "目标点纬度范围 -90~90")
@DecimalMin(value = "-90", message = "目标点纬度范围 -90~90")
@Schema(description = "目标点纬度")
private Double latitude;
@NotNull(message = "目标点经度不能为空")
@DecimalMax(value = "90", message = "目标点经度范围 -180~180")
@DecimalMin(value = "-90", message = "目标点经度范围 -180~180")
@Schema(description = "目标点经度")
private Double longitude;
@NotNull(message = "目标点高度不能为空")
@DecimalMax(value = "5000", message = "目标点高度 2~5000")
@DecimalMin(value = "2", message = "目标点高度 2~5000")
@Schema(description = "目标点高度(相对高度)")
private Float height;
}

View File

@ -1,34 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 点对焦参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "点对焦参数")
public class PointFocusAction {
/* @NotBlank(message = "相机类型不能为空")
@Schema(description = "相机类型 wide:广角 zoom:变焦 相机类型枚举,注意: Matrice 30 系列飞行器只支持 zoom 镜头下配置该参数")
private String type;*/
@NotNull(message = "对焦点坐标x不能为空")
@DecimalMax(value = "1", message = "对焦点坐标x 范围为0~1")
@DecimalMin(value = "0", message = "对焦点坐标x 范围为0~1")
@Schema(description = "对焦点坐标 x以镜头的左上角为坐标中心点水平方向为 x")
private Double x;
@NotNull(message = "对焦点坐标y不能为空")
@DecimalMax(value = "1", message = "对焦点坐标y 范围为0~1")
@DecimalMin(value = "0", message = "对焦点坐标y 范围为0~1")
@Schema(description = "对焦点坐标 y以镜头的左上角为坐标中心点竖直方向为 y")
private Double y;
}

View File

@ -1,27 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 画面拖动控制参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "画面拖动控制参数")
public class ScreenDrag {
//机头和云台的相对关系是否锁定 {"false":"仅云台转,机身不转","true":"锁定机头,云台和机身一起转"}
// private Boolean locked;
@NotNull(message = "云台 pitch 速度不能为空")
@Schema(description = "云台 pitch 速度")
private Double pitchSpeed;
@NotNull(message = "云台 yaw 速度不能为空")
@Schema(description = "云台 yaw 速度,仅不锁机头时才生效")
private Double yawSpeed;
}

View File

@ -1,27 +0,0 @@
package com.multictrl.modules.business.dto.camera;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* 照片视频存储参数
*
* @author Sdy
* @since 1.0.0 2026/4/28
*/
@Data
@Schema(name = "照片视频存储参数")
public class StorageSet {
@NotBlank(message = "类型不能为空")
@Schema(description = "类型 photo_storage_set:照片 video_storage_set:视频")
private String type;
@NotEmpty(message = "存储类型不能为空")
@Schema(description = "存储类型{current, wide, zoom, ir}")
private List<String> setList;
}

View File

@ -1,33 +0,0 @@
package com.multictrl.modules.business.dto.command;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* Drc虚拟摇杆
*
* @author Sdy
* @since 1.0.0 2026/4/27
*/
@Schema(name = "Drc虚拟摇杆参数")
@Data
public class DrcStick {
@Schema(description = """
方向
roll 横滚通道对应遥控器A通道控制飞行器横滚表现为左右平移数值增大表示向右倾斜减小表示向左倾斜
pitch 俯仰通道对应遥控器E通道控制飞行器俯仰表现为前后平移数值增大表示向前俯冲减小表示向后抬头
throttle 升降通道对应遥控器T通道控制飞行器升降数值增大表示升高减小表示降低
yaw 偏航通道对应遥控器R通道控制飞行器偏航表现为左右旋转数值增大表示顺时针旋转减小表示逆时针旋转""")
@NotBlank(message = "杆量方向不能为空")
private String stickDirection;
@Schema(description = """
杆量值-100~100
正值和负值看上面参数解释例如当stickDirection为roll时>0会向右倾斜<0 会向左倾斜
100是杆量的比例绝对值越大杆量越大飞机动作的越快""")
@NotNull(message = "杆量值不能为空")
private Integer stickInput;
}

View File

@ -1,60 +0,0 @@
package com.multictrl.modules.business.dto.command;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.Data;
import java.util.List;
/**
* 飞向目标点参数
*
* @author Sdy
* @since 1.0.0 2026/4/27
*/
@Data
@Schema(name = "飞向目标点请求参数")
public class FlyToPoint {
@Schema(hidden = true)
private String flyToId;
@NotNull(message = "最大速度不能为空")
@Min(value = 0, message = "最大速度最小值为0米/秒")
@Max(value = 15, message = "最大速度最大值为15米/秒")
@Schema(description = "flyto的飞行过程中能达到的最大速度", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer maxSpeed;
@Valid
@NotNull(message = "目标点列表不能为空")
@Size(min = 1, max = 1, message = "仅支持1个目标点")
@Schema(description = "flyto目标点列表仅支持1个目标点", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Point> points;
@Data
@Schema(description = "目标点坐标信息")
public static class Point {
@NotNull(message = "目标点纬度不能为空")
@DecimalMin(value = "-90.0", message = "纬度最小值为-90")
@DecimalMax(value = "90.0", message = "纬度最大值为90")
@Schema(description = "目标点纬度角度值南纬是负北纬是正精度到小数点后6位", requiredMode = Schema.RequiredMode.REQUIRED,
example = "39.908724")
private Double latitude;
@NotNull(message = "目标点经度不能为空")
@DecimalMin(value = "-180.0", message = "经度最小值为-180")
@DecimalMax(value = "180.0", message = "经度最大值为180")
@Schema(description = "目标点经度角度值东经是正西经是负精度到小数点后6位", requiredMode = Schema.RequiredMode.REQUIRED,
example = "116.397460")
private Double longitude;
@NotNull(message = "目标点高度不能为空")
@DecimalMin(value = "2.0", message = "目标点高度最小值为2米")
@DecimalMax(value = "10000.0", message = "目标点高度最大值为10000米")
@Schema(description = "目标点高度(相对机场高度)", requiredMode = Schema.RequiredMode.REQUIRED,
example = "100.5")
private Float height;
}
}

View File

@ -1,125 +0,0 @@
package com.multictrl.modules.business.dto.command;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.Data;
/**
* 一键起飞参数
*
* @author Sdy
* @since 1.0.0 2026/4/27
*/
@Data
@Schema(name = "一键起飞请求参数")
public class TakeoffToPoint {
@NotNull(message = "目标点纬度不能为空")
@DecimalMin(value = "-90.0", message = "纬度最小值为-90")
@DecimalMax(value = "90.0", message = "纬度最大值为90")
@Schema(description = "目标点纬度角度值南纬是负北纬是正精度到小数点后6位",
requiredMode = Schema.RequiredMode.REQUIRED, example = "31.029651")
private Double targetLatitude;
@NotNull(message = "目标点经度不能为空")
@DecimalMin(value = "-180.0", message = "经度最小值为-180")
@DecimalMax(value = "180.0", message = "经度最大值为180")
@Schema(description = "目标点经度角度值东经是正西经是负精度到小数点后6位",
requiredMode = Schema.RequiredMode.REQUIRED, example = "120.766442")
private Double targetLongitude;
@NotNull(message = "目标点高度不能为空(相对机场高度)")
@DecimalMin(value = "2.0", message = "目标点高度最小值为2米")
@DecimalMax(value = "1500.0", message = "目标点高度最大值为1500米")
@Schema(description = "目标点高度(相对机场高度),飞行器到点后默认行为:悬停",
requiredMode = Schema.RequiredMode.REQUIRED, example = "100.5")
private Double height;
//@Schema(description = "目标点高度(椭球高),使用 WGS84 模型,飞行器到点后默认行为:悬停",
// requiredMode = Schema.RequiredMode.REQUIRED, example = "100.5")
@Schema(hidden = true)
private Float targetHeight;
//@NotNull(message = "安全起飞高度不能为空")
@DecimalMin(value = "20.0", message = "安全起飞高度最小值为20米")
@DecimalMax(value = "1500.0", message = "安全起飞高度最大值为1500米")
@Schema(description = "相对(机场)起飞点的高度ALT飞行器先升到特定的高度然后再飞向目标点", example = "150.0", defaultValue = "目标点高度")
private Float securityTakeoffHeight;
//@NotNull(message = "返航模式不能为空")
@Schema(description = "【必填】返航模式设置值0-智能高度,1-设定高度。大疆机场当前不支持设置返航高度模式,只能选择'设定高度'模式",
allowableValues = {"0", "1"}, example = "1", hidden = true)
private Integer rthMode;
//@NotNull(message = "返航高度不能为空")
@Min(value = 2, message = "返航高度最小值为2米")
@Max(value = 1500, message = "返航高度最大值为1500米")
@Schema(description = "相对(机场)起飞点的高度,相对高 ALT", example = "100", defaultValue = "目标点高度+100d")
private Integer rthAltitude;
//@NotNull(message = "遥控器失控动作不能为空")
@Schema(description = "遥控器失控动作0-悬停,1-着陆(降落),2-返航",
allowableValues = {"0", "1", "2"}, example = "2", defaultValue = "2")
private Integer rcLostAction;
//@NotNull(message = "指点飞行失控动作不能为空")
@Schema(description = "【必填】指点飞行失控动作0-继续执行指点飞行任务,1-退出指点飞行任务,执行普通失控行为",
allowableValues = {"0", "1"}, example = "1", defaultValue = "1")
private Integer commanderModeLostAction;
//@NotNull(message = "指点飞行模式不能为空")
// @Schema(description = "【必填】指点飞行模式设置值0-智能高度飞行,1-设定高度飞行",
// requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"0", "1"}, example = "1", defaultValue = "1")
@Schema(hidden = true)
private Integer commanderFlightMode;
// @NotNull(message = "指点飞行高度不能为空")
// @DecimalMin(value = "2.0", message = "指点飞行高度最小值为2米")
// @DecimalMax(value = "3000.0", message = "指点飞行高度最大值为3000米")
// @Schema(description = "【必填】指点飞行高度,相对(机场)起飞点的高度,相对高 ALT",
// requiredMode = Schema.RequiredMode.REQUIRED, example = "120.5")
@Schema(hidden = true)
private Float commanderFlightHeight;
//@NotBlank(message = "任务UUID不能为空")
// @Schema(description = "无需填写,由后端生成。" +
// " 一键起飞任务 UUID全局唯一用于染色云端区分该值是普通计划任务还是一键起飞任务",
// example = "a1b2c3d4-e5f6-7890-abcd-ef1234567890")
@Schema(hidden = true)
private String flightId;
//@NotNull(message = "最大速度不能为空")
@Min(value = 1, message = "最大速度最小值为1米/秒")
@Max(value = 15, message = "最大速度最大值为15米/秒")
@Schema(description = "一键起飞的飞行过程中能达到的最大速度", example = "10", defaultValue = "10")
private Integer maxSpeed;
@Valid
@Schema(description = "可选字段,用于在室内进行模拟任务调试。注意:进行模拟飞行前,请务必取下桨叶,以防舱盖关闭时夹断桨叶")
private SimulateMission simulateMission;
@Schema(description = "飞行安全预检查0-关闭,1-开启。设置一键起飞和航线任务中的飞行安全是否预先检查。此字段为可选默认为0",
example = "1", allowableValues = {"0", "1"})
private Integer flightSafetyAdvanceCheck = 0;
@Data
@Schema(description = "模拟任务配置")
public static class SimulateMission {
@NotNull(message = "是否开启模拟器不能为空")
@Schema(description = "是否开启模拟器任务0-不开启,1-开启",
requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"0", "1"}, example = "0")
private Integer isEnable;
@DecimalMin(value = "-90.0", message = "纬度最小值为-90")
@DecimalMax(value = "90.0", message = "纬度最大值为90")
@Schema(description = "纬度", example = "39.908724")
private Double latitude;
@DecimalMin(value = "-180.0", message = "经度最小值为-180")
@DecimalMax(value = "180.0", message = "经度最大值为180")
@Schema(description = "经度", example = "116.397460")
private Double longitude;
}
}

View File

@ -1,23 +0,0 @@
package com.multictrl.modules.business.dto.error;
import lombok.Data;
/**
* 错误信息
*
* @author Sdy
* @since 1.0.0 2026/4/24
*/
@Data
public class DJICloudErrorCode {
//错误码
private String code;
//错误信息
private String message;
//错误来源
private String from;
//原因
private String reason;
//解决方案
private String solution;
}

View File

@ -1,19 +0,0 @@
package com.multictrl.modules.business.dto.firmware;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 固件版本
*
* @author Sdy
* @since 1.0.0 2026/5/11
*/
@Schema(name = "固件版本")
@Data
public class FirmwareVersion {
@Schema(description = "机库固件")
private String dockFirmware;
@Schema(description = "飞机固件")
private String uavFirmware;
}

View File

@ -1,30 +0,0 @@
package com.multictrl.modules.business.dto.flight;
import com.multictrl.modules.business.dto.multi.PrivateMultiFlightBindDockInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import lombok.Data;
/**
* 执行航线
*
* @author Sdy
* @since 1.0.0 2026/4/29
*/
@Schema(name = "航线参数")
@Data
public class FlightExecute {
@Schema(description = "航线id")
@NotNull(message = "航线id不能为空")
private Long routeId;
@Schema(description = "模拟器执行")
private FlightTaskPrepare.SimulateMission simulateMission;
@Schema(hidden = true)
List<PrivateMultiFlightBindDockInfo> privateMultiFlightBindDockInfos;
@Schema(hidden = true)
private String taskId;
}

Some files were not shown because too many files have changed in this diff Show More