Compare commits
2 Commits
master
...
1.0.0-mult
| Author | SHA1 | Date |
|---|---|---|
|
|
5c0549e8d0 | |
|
|
92553d35be |
|
|
@ -0,0 +1,151 @@
|
|||
<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>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
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";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
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 "";
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
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";
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
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();
|
||||
}*/
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
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/";
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
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 "未知错误";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
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";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
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<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
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<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
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<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
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<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
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";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
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
Loading…
Reference in New Issue