Compare commits
67 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
64335ed9d6 | |
|
|
559fdb5d96 | |
|
|
170000bb9e | |
|
|
0bdfd5a3b4 | |
|
|
f4e0dae1b7 | |
|
|
86aabebb80 | |
|
|
d14da94715 | |
|
|
5ac4eb1043 | |
|
|
f633b2179f | |
|
|
02384897ab | |
|
|
f2e69091f0 | |
|
|
1591170585 | |
|
|
305e40da0f | |
|
|
b61dbb1028 | |
|
|
e28d4aa921 | |
|
|
5e8e1586fc | |
|
|
0de80236d7 | |
|
|
44b0221f5c | |
|
|
69b89434b9 | |
|
|
23864f27bd | |
|
|
67cedde4c5 | |
|
|
55273df8d7 | |
|
|
ab47d75ca3 | |
|
|
61ca367330 | |
|
|
139e2eff46 | |
|
|
388cfd2845 | |
|
|
b7e2733345 | |
|
|
4f4a110765 | |
|
|
6d9bd5a7ca | |
|
|
762fdf1d7b | |
|
|
e066b29fa0 | |
|
|
001e997a26 | |
|
|
434726a9af | |
|
|
18c6de38ca | |
|
|
0a8313d30e | |
|
|
30c4ae5eb7 | |
|
|
5c0549e8d0 | |
|
|
d4bb56c863 | |
|
|
7f688f8863 | |
|
|
b3090457a4 | |
|
|
ec9e18163b | |
|
|
7081e66183 | |
|
|
d6d624e705 | |
|
|
d11c25624f | |
|
|
442cd50d21 | |
|
|
4ce9541397 | |
|
|
3162f12835 | |
|
|
7d2b0edf78 | |
|
|
e96fcc42ed | |
|
|
e23e95c511 | |
|
|
0aa306b8a9 | |
|
|
242ea1088b | |
|
|
dbee9fe94a | |
|
|
d92c6e1f2f | |
|
|
9a4c30742e | |
|
|
cfcfd992f8 | |
|
|
161c089635 | |
|
|
3177b26cd9 | |
|
|
4cfcbb644f | |
|
|
e15a249caf | |
|
|
3265568e69 | |
|
|
8029e78127 | |
|
|
24591c0eff | |
|
|
b99551d0a2 | |
|
|
484fa679f3 | |
|
|
87f0333f0e | |
|
|
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,117 @@
|
||||||
|
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.common.interceptor.DataScopeContext;
|
||||||
|
import com.multictrl.modules.security.user.SecurityUser;
|
||||||
|
import com.multictrl.modules.security.user.UserDetail;
|
||||||
|
import com.multictrl.modules.sys.enums.SuperAdminEnum;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@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 != null && params instanceof Map) {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
|
||||||
|
//如果是超级管理员,则不进行数据过滤
|
||||||
|
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* try {
|
||||||
|
//否则进行数据过滤
|
||||||
|
Map map = (Map) params;
|
||||||
|
String sqlFilter = getSqlFilter(user, point);
|
||||||
|
// 放入 ThreadLocal
|
||||||
|
DataScopeContext.setDataScope(new DataScope(sqlFilter));
|
||||||
|
|
||||||
|
map.put(Constant.SQL_FILTER, new DataScope(sqlFilter));
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
}*/
|
||||||
|
try {
|
||||||
|
String sqlFilter = getSqlFilter(user, point);
|
||||||
|
// 放入 ThreadLocal
|
||||||
|
DataScopeContext.setDataScope(new DataScope(sqlFilter));
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RenException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(")");
|
||||||
|
} else {
|
||||||
|
throw new RenException("暂无数据权限,无法访问");
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询本人数据
|
||||||
|
/* if (CollUtil.isNotEmpty(deptIdList)) {
|
||||||
|
sqlFilter.append(" or ");
|
||||||
|
}
|
||||||
|
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getId());*/
|
||||||
|
|
||||||
|
// sqlFilter.append(")");
|
||||||
|
String sqlFilterString = sqlFilter.toString();
|
||||||
|
log.debug("数据过滤SQL:{}", sqlFilterString);
|
||||||
|
|
||||||
|
return sqlFilterString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,51 @@
|
||||||
|
package com.multictrl.common.aspect;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Q20 service 调用日志切面
|
||||||
|
* 在每个 Q20 service 方法执行前后打印开始/结束及耗时,异常时打印错误信息
|
||||||
|
*
|
||||||
|
* @author 938693313@qq.com
|
||||||
|
* @since 1.0.0 2026/5/20
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public class Q20ServiceLogAspect {
|
||||||
|
|
||||||
|
@Pointcut("execution(public * com.multictrl.modules.business.q20.service.impl.*.*(..))")
|
||||||
|
public void q20ServicePointcut() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Around("q20ServicePointcut()")
|
||||||
|
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||||
|
MethodSignature sig = (MethodSignature) point.getSignature();
|
||||||
|
String className = sig.getDeclaringType().getSimpleName();
|
||||||
|
String methodName = sig.getName();
|
||||||
|
String tag = className + "." + methodName;
|
||||||
|
|
||||||
|
// 第一个参数通常是 deviceSn
|
||||||
|
Object[] args = point.getArgs();
|
||||||
|
String deviceSn = (args != null && args.length > 0 && args[0] instanceof String)
|
||||||
|
? (String) args[0] : "-";
|
||||||
|
|
||||||
|
log.info("Q20 [{}] 开始 deviceSn={}", tag, deviceSn);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
Object result = point.proceed();
|
||||||
|
log.info("Q20 [{}] 结束 deviceSn={} 耗时={}ms", tag, deviceSn, System.currentTimeMillis() - start);
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Q20 [{}] 异常 deviceSn={} 耗时={}ms error={}",
|
||||||
|
tag, deviceSn, System.currentTimeMillis() - start, e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
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 DockBind dockBind = new DockBind();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DockBind {
|
||||||
|
private String url;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String organizationName;
|
||||||
|
private String organizationId;
|
||||||
|
private String deviceBindCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,24 @@
|
||||||
|
package com.multictrl.common.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册 Jackson 2.x ObjectMapper bean。
|
||||||
|
* Spring Boot 4.x 默认只注册 tools.jackson.databind.ObjectMapper(Jackson 3.x),
|
||||||
|
* 项目中使用 com.fasterxml.jackson.annotation.JsonProperty 的代码需要此 bean。
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class Jackson2ObjectMapperConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ObjectMapper objectMapper() {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.registerModule(new JavaTimeModule());
|
||||||
|
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,115 @@
|
||||||
|
package com.multictrl.common.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务常量类
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/4/16
|
||||||
|
*/
|
||||||
|
public interface BusinessConstant {
|
||||||
|
|
||||||
|
String ZHIMOU_AI_CALLBACK_TOPIC = "thing/product/%s/zhimou_ai_callback";
|
||||||
|
String WEB_EVENT_TOPIC = "thing/product/%s/web_event";
|
||||||
|
String NOFLY_ZONE_METHOD = "nofly_zone";
|
||||||
|
String ZHIMOU_AI_CALLBACK = "zhimou_ai_callback";
|
||||||
|
String ZHIMOU_AI_METHOD = "zhimou_ai";
|
||||||
|
|
||||||
|
//********************************* 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";//设备固件桶
|
||||||
|
String SPEAKER_AUDIO_BUCKET = "speaker-audio";//喊话器音频桶
|
||||||
|
String GEO_MARK_BUCKET = "geo-mark";//地图标注文件桶
|
||||||
|
String DICT_IMAGE_BUCKET = "source-material";//字典图片桶
|
||||||
|
String MAINTENANCE_IMAGE_BUCKET = "maintenance-images";//字典图片桶
|
||||||
|
|
||||||
|
//********************************* 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 SET_REPLY = "set_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 FLIGHTTASK_READY = "flighttask_ready";
|
||||||
|
String RETURN_HOME_INFO = "return_home_info";
|
||||||
|
String DEVICE_EXIT_HOMING_NOTIFY = "device_exit_homing_notify";
|
||||||
|
String IN_FLIGHT_WAYLINE_PROGRESS = "in_flight_wayline_progress";
|
||||||
|
String OBSTACLE_AVOIDANCE_NOTIFY = "obstacle_avoidance_notify";
|
||||||
|
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_";
|
||||||
|
String UAV_LIGHT_INDEX = "uav_light_index_";
|
||||||
|
String UAV_MODE_CODE = "uav_mode_code_";
|
||||||
|
|
||||||
|
//********************************* other cache key *********************************//
|
||||||
|
String DOCK_NOFLY_ZONE = "dock_nofly_zone_";
|
||||||
|
String DOCK_NOFLY_ZONE_TRIGGER_SIGN = "dock_nofly_zone_trigger_sign_";
|
||||||
|
String ZHIMOU_TOKEN = "zhiMou_token";
|
||||||
|
String DOCK_ZHIMOU_CONFIG = "dock_zhiMou_config_";
|
||||||
|
String ZHIMOU_AI_START = "zhimou_ai_start_";
|
||||||
|
String ZHIMOU_AI_STOP = "zhimou_ai_stop_";
|
||||||
|
|
||||||
|
//********************************* other *********************************//
|
||||||
|
String HTTP_PROTOCOL = "http://";
|
||||||
|
String HTTPS_PROTOCOL = "https://";
|
||||||
|
String TCP_PROTOCOL = "tcp://";
|
||||||
|
String DRC = "drc_";
|
||||||
|
String FILE_PATH = "file/";
|
||||||
|
String IMAGE_PATH = "image/";
|
||||||
|
String VIDEO_PATH = "video/";
|
||||||
|
String GEO_MODEL_DIR = "/geo/model";
|
||||||
|
String GEO_MODEL_DISK_UPLOAD_PATH = "/geo_model";
|
||||||
|
String VIDEO_COVER_SUFFIX = "_cover.jpeg";
|
||||||
|
String DJI_SIGN = "DJI";
|
||||||
|
String MIAO_SUAN_SIGN = "MIAO_SUAN";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.multictrl.common.constant;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum DJIImage {
|
||||||
|
DJI("DJI", "source-material/dji.png"),
|
||||||
|
DOCK1("DJI Dock", "source-material/dock1.png"),
|
||||||
|
DOCK2("DJI Dock2", "source-material/dock2.png"),
|
||||||
|
DOCK3("DJI Dock3", "source-material/dock3.png");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final String imageUrl;
|
||||||
|
|
||||||
|
//根据名称获取图片地址
|
||||||
|
public static String getImageUrlByName(String name) {
|
||||||
|
for (DJIImage djiImage : DJIImage.values()) {
|
||||||
|
if (djiImage.getName().equals(name)) {
|
||||||
|
return djiImage.getImageUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,51 @@
|
||||||
|
package com.multictrl.common.constant;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ExposureValue {
|
||||||
|
// 负曝光值
|
||||||
|
EV_NEG_5_0("1", "-5.0EV"),
|
||||||
|
EV_NEG_4_7("2", "-4.7EV"),
|
||||||
|
EV_NEG_4_3("3", "-4.3EV"),
|
||||||
|
EV_NEG_4_0("4", "-4.0EV"),
|
||||||
|
EV_NEG_3_7("5", "-3.7EV"),
|
||||||
|
EV_NEG_3_3("6", "-3.3EV"),
|
||||||
|
EV_NEG_3_0("7", "-3.0EV"),
|
||||||
|
EV_NEG_2_7("8", "-2.7EV"),
|
||||||
|
EV_NEG_2_3("9", "-2.3EV"),
|
||||||
|
EV_NEG_2_0("10", "-2.0EV"),
|
||||||
|
EV_NEG_1_7("11", "-1.7EV"),
|
||||||
|
EV_NEG_1_3("12", "-1.3EV"),
|
||||||
|
EV_NEG_1_0("13", "-1.0EV"),
|
||||||
|
EV_NEG_0_7("14", "-0.7EV"),
|
||||||
|
EV_NEG_0_3("15", "-0.3EV"),
|
||||||
|
|
||||||
|
// 零曝光值
|
||||||
|
EV_0("16", "0EV"),
|
||||||
|
|
||||||
|
// 正曝光值
|
||||||
|
EV_POS_0_3("17", "0.3EV"),
|
||||||
|
EV_POS_0_7("18", "0.7EV"),
|
||||||
|
EV_POS_1_0("19", "1.0EV"),
|
||||||
|
EV_POS_1_3("20", "1.3EV"),
|
||||||
|
EV_POS_1_7("21", "1.7EV"),
|
||||||
|
EV_POS_2_0("22", "2.0EV"),
|
||||||
|
EV_POS_2_3("23", "2.3EV"),
|
||||||
|
EV_POS_2_7("24", "2.7EV"),
|
||||||
|
EV_POS_3_0("25", "3.0EV"),
|
||||||
|
EV_POS_3_3("26", "3.3EV"),
|
||||||
|
EV_POS_3_7("27", "3.7EV"),
|
||||||
|
EV_POS_4_0("28", "4.0EV"),
|
||||||
|
EV_POS_4_3("29", "4.3EV"),
|
||||||
|
EV_POS_4_7("30", "4.7EV"),
|
||||||
|
EV_POS_5_0("31", "5.0EV"),
|
||||||
|
|
||||||
|
// 特殊值
|
||||||
|
FIXED("255", "FIXED");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String desc;
|
||||||
|
}
|
||||||
|
|
@ -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,15 @@
|
||||||
|
package com.multictrl.common.constant;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum FlightTaskType {
|
||||||
|
ROUTE(1, "航线飞行"),
|
||||||
|
MANUAL(2, "手动飞行"),
|
||||||
|
SCHEDULED(3, "定时飞行");
|
||||||
|
|
||||||
|
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,22 @@
|
||||||
|
package com.multictrl.common.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统参数key
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/6/9
|
||||||
|
*/
|
||||||
|
public interface SysParamsKey {
|
||||||
|
|
||||||
|
//********************************* zhimou *********************************//
|
||||||
|
String ZHIMOU_AI_URL = "zhimou_ai_url";
|
||||||
|
String ZHIMOU_AI_APP_KEY = "zhimou_ai_app_key";
|
||||||
|
String ZHIMOU_AI_APP_SECRET = "zhimou_ai_app_secret";
|
||||||
|
|
||||||
|
//********************************* gaode *********************************//
|
||||||
|
String GAODE_API_KEY = "gaode_api_key";
|
||||||
|
|
||||||
|
//********************************* sys component *********************************//
|
||||||
|
String SRS_RTMP_MAP_URL = "srs_rtmp_map_url";
|
||||||
|
String EMQX_MAP_URL = "emqx_map_url";
|
||||||
|
}
|
||||||
|
|
@ -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.debug("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)) {
|
||||||
|
JSONObject messageJson = JsonUtils.parseObject(payload, JSONObject.class);
|
||||||
|
if (messageJson != null) {
|
||||||
|
String methodJson = messageJson.getStr(BusinessConstant.METHOD);
|
||||||
|
if (VALID_METHODS.contains(methodJson)) {
|
||||||
|
log.debug("drc 回复--> topic:{} ,payload:{}", topic, payload);
|
||||||
|
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,23 @@
|
||||||
|
package com.multictrl.common.interceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据鉴权
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/5/26
|
||||||
|
*/
|
||||||
|
public class DataScopeContext {
|
||||||
|
private static final ThreadLocal<DataScope> DATA_SCOPE_HOLDER = new ThreadLocal<>();
|
||||||
|
|
||||||
|
public static void setDataScope(DataScope dataScope) {
|
||||||
|
DATA_SCOPE_HOLDER.set(dataScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataScope getDataScope() {
|
||||||
|
return DATA_SCOPE_HOLDER.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
DATA_SCOPE_HOLDER.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,195 @@
|
||||||
|
package com.multictrl.common.task;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateTime;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.multictrl.common.utils.DateUtils;
|
||||||
|
import com.multictrl.common.utils.map.Spatial4jUtils;
|
||||||
|
import com.multictrl.modules.business.dao.FlightTaskDao;
|
||||||
|
import com.multictrl.modules.business.entity.FlightTaskEntity;
|
||||||
|
import com.multictrl.modules.business.entity.MediaFileEntity;
|
||||||
|
import com.multictrl.modules.business.influxdb.UavReport;
|
||||||
|
import com.multictrl.modules.business.service.FlightTaskService;
|
||||||
|
import com.multictrl.modules.business.service.InfluxService;
|
||||||
|
import com.multictrl.modules.business.service.MediaFileService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 航线任务矫正
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2025/5/29
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FlightTaskUpdate {
|
||||||
|
private final InfluxService influxService;
|
||||||
|
private final MediaFileService mediaFileService;
|
||||||
|
private final FlightTaskService flightTaskService;
|
||||||
|
private final FlightTaskDao flightTaskDao;
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 60 * 1000) // 每 60秒执行一次(单位:毫秒)
|
||||||
|
public void updateFlightTask() {
|
||||||
|
Date threeHoursAgo = DateUtil.offsetHour(new Date(), -3);
|
||||||
|
LambdaQueryWrapper<FlightTaskEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.gt(FlightTaskEntity::getCreateDate, threeHoursAgo)
|
||||||
|
.and(wrapper2 -> wrapper2
|
||||||
|
.isNull(FlightTaskEntity::getTaskStatus)
|
||||||
|
.or()
|
||||||
|
.eq(FlightTaskEntity::getTaskStatus, 0)
|
||||||
|
.or()
|
||||||
|
.isNull(FlightTaskEntity::getFlightDistance)
|
||||||
|
.or()
|
||||||
|
.isNull(FlightTaskEntity::getFlightDuration)
|
||||||
|
.or()
|
||||||
|
.isNull(FlightTaskEntity::getMediaNum)
|
||||||
|
.or()
|
||||||
|
.eq(FlightTaskEntity::getMediaNum, 0)
|
||||||
|
);
|
||||||
|
List<FlightTaskEntity> taskList = flightTaskDao.selectList(wrapper);
|
||||||
|
Map<String, FlightTaskEntity> updateMap = new HashMap<>();
|
||||||
|
for (FlightTaskEntity entity : taskList) {
|
||||||
|
Date createDate = entity.getCreateDate();
|
||||||
|
long createMillis = createDate.getTime();
|
||||||
|
long diffSeconds = Math.abs((new Date().getTime() - createMillis) / 1000);
|
||||||
|
// 1. 处理状态字段
|
||||||
|
handleStatus(entity, diffSeconds, updateMap);
|
||||||
|
// 2. 处理飞行距离
|
||||||
|
handleFlightDistance(entity, diffSeconds, updateMap);
|
||||||
|
// 3. 处理飞行时长
|
||||||
|
handleFlightDuration(entity, diffSeconds, updateMap);
|
||||||
|
// 4. 处理媒体数量
|
||||||
|
handleMediaNum(entity, diffSeconds, updateMap);
|
||||||
|
}
|
||||||
|
if (!updateMap.isEmpty()) {
|
||||||
|
flightTaskService.updateBatchById(new ArrayList<>(updateMap.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStatus(FlightTaskEntity entity, long diffSeconds, Map<String, FlightTaskEntity> updateMap) {
|
||||||
|
if (entity.getTaskStatus() == null || entity.getTaskStatus() == 0) {
|
||||||
|
if (entity.getFailureReason() != null) {
|
||||||
|
entity.setTaskStatus(-1);
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else if (entity.getInboundDate() != null) {
|
||||||
|
entity.setTaskStatus(1);
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else if (diffSeconds > 7200) {
|
||||||
|
entity.setTaskStatus(-1);
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else if (entity.getTaskStatus() == null) {
|
||||||
|
entity.setTaskStatus(0);
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFlightDistance(FlightTaskEntity entity, long diffSeconds, Map<String, FlightTaskEntity> updateMap) {
|
||||||
|
if (entity.getFlightDistance() == null) {
|
||||||
|
if (entity.getOutboundDate() != null && entity.getInboundDate() != null) {
|
||||||
|
entity.setFlightDistance(getFlightDistance(entity.getDockSn(), entity.getOutboundDate(), entity.getInboundDate()));
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else if (diffSeconds > 7200) {
|
||||||
|
if (entity.getInboundDate() != null) {
|
||||||
|
entity.setFlightDistance(getFlightDistance(entity.getDockSn(), entity.getCreateDate(), entity.getInboundDate()));
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else if (entity.getOutboundDate() != null) {
|
||||||
|
entity.setFlightDistance(getFlightDistance(entity.getDockSn(), entity.getOutboundDate(),
|
||||||
|
DateUtils.getBeforeHour(entity.getOutboundDate(), 1)));
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else {
|
||||||
|
entity.setFlightDistance(getFlightDistance(entity.getDockSn(), entity.getCreateDate(),
|
||||||
|
DateUtils.getBeforeHour(entity.getCreateDate(), 1)));
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取飞行距离
|
||||||
|
private double getFlightDistance(String dockSn, Date startDate, Date endDate) {
|
||||||
|
HashMap<String, Object> condition = Maps.newHashMap();
|
||||||
|
condition.put("dockSn", dockSn);
|
||||||
|
List<UavReport> flyList = influxService.queryData(DateUtils.format(startDate, DateUtils.DATE_TIME_PATTERN),
|
||||||
|
DateUtils.format(endDate, DateUtils.DATE_TIME_PATTERN), "uav_osd", condition, UavReport.class,
|
||||||
|
"longitude", "latitude", "elevation", "_time");
|
||||||
|
double total = 0;
|
||||||
|
for (int i = 0; i < flyList.size() - 1; i++) {
|
||||||
|
UavReport p1 = flyList.get(i);
|
||||||
|
UavReport p2 = flyList.get(i + 1);
|
||||||
|
total += Spatial4jUtils.distance(p1.getLatitude(), p1.getLongitude(), p2.getLatitude(), p2.getLongitude());
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFlightDuration(FlightTaskEntity entity, long diffSeconds, Map<String, FlightTaskEntity> updateMap) {
|
||||||
|
if (entity.getFlightDuration() == null) {
|
||||||
|
if (entity.getOutboundDate() != null && entity.getInboundDate() != null) {
|
||||||
|
setFlightDuration(entity, entity.getOutboundDate(), entity.getInboundDate());
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else if (diffSeconds > 7200) {
|
||||||
|
if (entity.getInboundDate() != null) {
|
||||||
|
setFlightDuration(entity, entity.getCreateDate(), entity.getInboundDate());
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else if (entity.getOutboundDate() != null) {
|
||||||
|
setFlightDuration(entity, entity.getOutboundDate(),
|
||||||
|
DateUtils.getBeforeHour(entity.getOutboundDate(), 1));
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
} else {
|
||||||
|
setFlightDuration(entity, entity.getCreateDate(),
|
||||||
|
DateUtils.getBeforeHour(entity.getCreateDate(), 1));
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置飞行时间
|
||||||
|
private void setFlightDuration(FlightTaskEntity entity, Date startTime, Date endTime) {
|
||||||
|
HashMap<String, Object> condition = Maps.newHashMap();
|
||||||
|
condition.put("dockSn", entity.getDockSn());
|
||||||
|
List<UavReport> flyList = influxService.queryData(DateUtils.format(startTime, DateUtils.DATE_TIME_PATTERN),
|
||||||
|
DateUtils.format(endTime, DateUtils.DATE_TIME_PATTERN), "uav_osd", condition, UavReport.class,
|
||||||
|
"longitude", "latitude", "elevation", "_time");
|
||||||
|
|
||||||
|
UavReport start = null;
|
||||||
|
UavReport end = null;
|
||||||
|
for (int i = 0; i < flyList.size() - 1; i++) {
|
||||||
|
//过滤出起飞和降落的记录,当飞机和机场的相对高度大于0,则认为飞机在飞行中,开始计算飞行时长
|
||||||
|
if (flyList.get(i).getElevation() > 0) {
|
||||||
|
end = flyList.get(i);
|
||||||
|
if (start == null) {
|
||||||
|
start = flyList.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String flightDurationText = "0秒";
|
||||||
|
long flightDuration = 0L;
|
||||||
|
if (start != null && end != null) {
|
||||||
|
DateTime startDate = DateUtil.parseDateTime(start.getTimeStr());
|
||||||
|
DateTime endDate = DateUtil.parseDateTime(end.getTimeStr());
|
||||||
|
flightDurationText = DateUtils.formatDifference(startDate, endDate);
|
||||||
|
flightDuration = Math.abs((endDate.getTime() - startDate.getTime()) / 1000);
|
||||||
|
}
|
||||||
|
entity.setFlightDurationText(flightDurationText);
|
||||||
|
entity.setFlightDuration(flightDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMediaNum(FlightTaskEntity entity, long diffSeconds, Map<String, FlightTaskEntity> updateMap) {
|
||||||
|
if ((entity.getMediaNum() == null || entity.getMediaNum() == 0) && diffSeconds > 7200) {
|
||||||
|
Long count = mediaFileService.getDao().selectCount(new QueryWrapper<MediaFileEntity>()
|
||||||
|
.eq("task_id", entity.getTaskId()).eq("dock_sn", entity.getDockSn()));
|
||||||
|
entity.setMediaNum(count.intValue());
|
||||||
|
updateMap.put(entity.getTaskId(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,282 @@
|
||||||
|
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.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.filter.NameFilter;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 阿里FastJson2驼峰转下划线
|
||||||
|
*/
|
||||||
|
public static com.alibaba.fastjson2.JSONObject beanToSnakeJsonFastJson(Object o) {
|
||||||
|
NameFilter snakeCaseFilter = (object, name, value)
|
||||||
|
-> name.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
|
||||||
|
return com.alibaba.fastjson2.JSONObject.parseObject(JSON.toJSONString(o, snakeCaseFilter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最后两个字符
|
||||||
|
* "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";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将秒转换为分秒
|
||||||
|
*/
|
||||||
|
public static String calculateSeconds(long totalSeconds) {
|
||||||
|
// 处理负数
|
||||||
|
if (totalSeconds < 0) {
|
||||||
|
return "-" + calculateSeconds(-totalSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
long hours = totalSeconds / 3600;
|
||||||
|
long minutes = (totalSeconds % 3600) / 60;
|
||||||
|
long seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (hours > 0) {
|
||||||
|
sb.append(hours).append('h');
|
||||||
|
}
|
||||||
|
if (minutes > 0) {
|
||||||
|
sb.append(minutes).append('m');
|
||||||
|
}
|
||||||
|
// 当所有部分都为0时,至少显示 "0s"
|
||||||
|
if (seconds > 0 || sb.isEmpty()) {
|
||||||
|
sb.append(seconds).append('s');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
package com.multictrl.common.utils.kmz;
|
||||||
|
|
||||||
|
import com.multictrl.common.exception.RenException;
|
||||||
|
import com.multictrl.modules.business.dto.RouteDTO;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 zip 输入流解压到目标目录,完整保留 zip 内的目录结构。
|
||||||
|
*
|
||||||
|
* @param zipStream zip 输入流(调用方负责关闭外层流)
|
||||||
|
* @param destDir 目标目录
|
||||||
|
* @throws IOException 解压失败
|
||||||
|
*/
|
||||||
|
public static void unzipToDirectory(InputStream zipStream, Path destDir) throws IOException {
|
||||||
|
// 确保目标目录存在(已存在时不会重复创建)
|
||||||
|
Files.createDirectories(destDir);
|
||||||
|
// 获取规范化的目标目录路径(用于安全校验)
|
||||||
|
Path normalizedDest = destDir.toAbsolutePath().normalize();
|
||||||
|
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(zipStream)) {
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
String entryName = entry.getName();
|
||||||
|
// 拼接并规范化目标文件路径
|
||||||
|
Path targetPath = normalizedDest.resolve(entryName).normalize();
|
||||||
|
|
||||||
|
// 防止 Zip Slip 攻击:确保解压路径仍在目标目录内
|
||||||
|
if (!targetPath.startsWith(normalizedDest)) {
|
||||||
|
throw new IOException("非法路径:" + entryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
Files.createDirectories(targetPath);
|
||||||
|
} else {
|
||||||
|
// 创建父目录(如果不存在)
|
||||||
|
Path parent = targetPath.getParent();
|
||||||
|
if (parent != null) {
|
||||||
|
Files.createDirectories(parent);
|
||||||
|
}
|
||||||
|
// 使用 NIO 方式将 zip 条目内容写入文件
|
||||||
|
try (OutputStream out = Files.newOutputStream(targetPath)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int len;
|
||||||
|
while ((len = zis.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zis.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 KMZ 文件,提取指定内容为 byte[]
|
||||||
|
*
|
||||||
|
* @param inputStream KMZ 文件流
|
||||||
|
* @return 包含文件名与 byte[] 的映射
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
*/
|
||||||
|
public static Map<String, byte[]> extractKmzAsBytes(InputStream inputStream) throws IOException {
|
||||||
|
Map<String, byte[]> result = new HashMap<>();
|
||||||
|
|
||||||
|
try (ZipArchiveInputStream zipStream = new ZipArchiveInputStream(inputStream)) {
|
||||||
|
ZipArchiveEntry entry;
|
||||||
|
while ((entry = zipStream.getNextEntry()) != null) {
|
||||||
|
String entryName = entry.getName();
|
||||||
|
|
||||||
|
if (WPMZ_TEMPLATE_KML.equals(entryName)) {
|
||||||
|
result.put("template.kml", readStreamToBytes(zipStream));
|
||||||
|
} else if (WPMZ_WAYLINES_WPML.equals(entryName)) {
|
||||||
|
result.put("waylines.wpml", readStreamToBytes(zipStream));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 InputStream 读取为 byte[]
|
||||||
|
*
|
||||||
|
* @param inputStream 输入流
|
||||||
|
* @return 字节数组
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
*/
|
||||||
|
private static byte[] readStreamToBytes(InputStream inputStream) throws IOException {
|
||||||
|
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
|
||||||
|
byte[] data = new byte[1024];
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
|
||||||
|
buffer.write(data, 0, bytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 zip 输入流解压到目标目录,完整保留 zip 内的目录结构。
|
||||||
|
*
|
||||||
|
* @param zipStream zip 输入流(调用方负责关闭外层流)
|
||||||
|
* @param destDir 目标目录
|
||||||
|
* @throws IOException 解压失败
|
||||||
|
*/
|
||||||
|
public static void unzipToDirectory(InputStream zipStream, File destDir) throws Exception {
|
||||||
|
if (!destDir.exists()) {
|
||||||
|
Files.createDirectories(destDir.toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(zipStream)) {
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
String entryName = entry.getName();
|
||||||
|
File targetFile = new File(destDir, entryName);
|
||||||
|
Path targetPath = targetFile.getCanonicalFile().toPath();
|
||||||
|
Path destPath = destDir.getCanonicalFile().toPath();
|
||||||
|
if (!targetPath.startsWith(destPath)) {
|
||||||
|
throw new RenException("非法路径:" + entryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
Files.createDirectories(targetFile.toPath());
|
||||||
|
} else {
|
||||||
|
Files.createDirectories(targetFile.getParentFile().toPath());
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(targetFile)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int len;
|
||||||
|
while ((len = zis.read(buffer)) > 0) {
|
||||||
|
fos.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zis.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
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 (int i = 0; i < waypointList.size(); i++) {
|
||||||
|
RouteWaypointDTO waypoint = waypointList.get(i);
|
||||||
|
Element placemark = folder.addElement("Placemark");
|
||||||
|
Element point = placemark.addElement("Point");
|
||||||
|
point.addElement("coordinates").setText(waypoint.getLongitude() + "," + waypoint.getLatitude());
|
||||||
|
placemark.addElement("wpml:index").setText(i + "");
|
||||||
|
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,84 @@
|
||||||
|
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 (int i = 0; i < waypointList.size(); i++) {
|
||||||
|
RouteWaypointDTO waypoint = waypointList.get(i);
|
||||||
|
Element placemark = folder.addElement("Placemark");
|
||||||
|
Element point = placemark.addElement("Point");
|
||||||
|
point.addElement("coordinates").setText(waypoint.getLongitude() + "," + waypoint.getLatitude());
|
||||||
|
placemark.addElement("wpml:index").setText(i + "");
|
||||||
|
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,279 @@
|
||||||
|
package com.multictrl.common.utils.kmz;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.multictrl.common.constant.BusinessConstant;
|
||||||
|
import com.multictrl.common.constant.SetActionParams;
|
||||||
|
import com.multictrl.common.exception.RenException;
|
||||||
|
import com.multictrl.modules.business.dto.RouteDTO;
|
||||||
|
import com.multictrl.modules.business.dto.RouteWaypointDTO;
|
||||||
|
import com.multictrl.modules.business.dto.WaypointActionDTO;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.dom4j.Document;
|
||||||
|
import org.dom4j.DocumentException;
|
||||||
|
import org.dom4j.Element;
|
||||||
|
import org.dom4j.io.SAXReader;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kml解析
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/6/18
|
||||||
|
*/
|
||||||
|
public class WpmlKmlParser {
|
||||||
|
|
||||||
|
public static RouteDTO parseKmlFile(byte[] bytes, Boolean accurateImport) throws DocumentException {
|
||||||
|
RouteDTO dto = new RouteDTO();
|
||||||
|
SAXReader reader = new SAXReader();
|
||||||
|
Document document = reader.read(new ByteArrayInputStream(bytes));
|
||||||
|
Element root = document.getRootElement();
|
||||||
|
|
||||||
|
Element doc = root.element("Document");
|
||||||
|
if (doc == null) {
|
||||||
|
throw new RenException("kml文件有误,无法解析");
|
||||||
|
}
|
||||||
|
Element folder = doc.element("Folder");
|
||||||
|
if (folder == null) {
|
||||||
|
throw new RenException("模板信息不存在,无法解析");
|
||||||
|
}
|
||||||
|
//解析高度模式
|
||||||
|
Element waylineCoordinateSysParam = folder.element("waylineCoordinateSysParam");
|
||||||
|
if (waylineCoordinateSysParam == null) {
|
||||||
|
if (!accurateImport) {
|
||||||
|
throw new RenException("坐标系参数有误,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String heightMode = filedValid(waylineCoordinateSysParam.elementText("heightMode"), accurateImport);
|
||||||
|
dto.setHeightModel(heightMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析
|
||||||
|
String templateType = folder.elementText("templateType");
|
||||||
|
if (StringUtils.isEmpty(templateType) && accurateImport) {
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(templateType) && !accurateImport) {
|
||||||
|
throw new RenException("模板类型不存在,解析失败");
|
||||||
|
}
|
||||||
|
dto.setTemplateType(templateType);
|
||||||
|
String autoFlightSpeed = filedValid(folder.elementText("autoFlightSpeed"), accurateImport);
|
||||||
|
dto.setFlightSpeed(autoFlightSpeed == null ? null : Double.parseDouble(autoFlightSpeed));
|
||||||
|
String globalHeight = filedValid(folder.elementText("globalHeight"), accurateImport);
|
||||||
|
dto.setFlightHeight(globalHeight == null ? null : Double.parseDouble(globalHeight));
|
||||||
|
|
||||||
|
List<RouteWaypointDTO> waypointList = new ArrayList<>();
|
||||||
|
List<Element> placemarks = folder.elements("Placemark");
|
||||||
|
if (CollectionUtil.isEmpty(placemarks)) {
|
||||||
|
if (accurateImport) {
|
||||||
|
dto.setRouteWaypointList(waypointList);
|
||||||
|
} else {
|
||||||
|
throw new RenException("航点信息不存在,解析失败");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Element placemark : placemarks) {
|
||||||
|
RouteWaypointDTO waypoint = new RouteWaypointDTO();
|
||||||
|
String useGlobalSpeed = placemark.elementText("useGlobalSpeed");
|
||||||
|
String useGlobalHeight = placemark.elementText("useGlobalHeight");
|
||||||
|
waypoint.setFollowRouteSpeed(filedValid(useGlobalSpeed, accurateImport) == null ? null : "1".equals(useGlobalSpeed));
|
||||||
|
waypoint.setFollowRouteHeight(filedValid(useGlobalHeight, accurateImport) == null ? null : "1".equals(useGlobalHeight));
|
||||||
|
waypointList.add(waypoint);
|
||||||
|
}
|
||||||
|
dto.setRouteWaypointList(waypointList);
|
||||||
|
}
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void parseWpmlFile(byte[] bytes, RouteDTO dto, Boolean accurateImport) throws DocumentException, IOException {
|
||||||
|
SAXReader reader = new SAXReader();
|
||||||
|
Document document = reader.read(new ByteArrayInputStream(bytes));
|
||||||
|
Element root = document.getRootElement();
|
||||||
|
|
||||||
|
Element doc = root.element("Document");
|
||||||
|
if (doc == null) {
|
||||||
|
throw new RenException("wpml文件有误,无法解析");
|
||||||
|
}
|
||||||
|
Element missionConfig = doc.element("missionConfig");
|
||||||
|
if (missionConfig == null) {
|
||||||
|
throw new RenException("任务信息不存在,无法解析");
|
||||||
|
}
|
||||||
|
dto.setFlyToWaylineMode(filedValid(missionConfig.elementText("flyToWaylineMode"), accurateImport));
|
||||||
|
dto.setFinishAction(filedValid(missionConfig.elementText("finishAction"), accurateImport));
|
||||||
|
String exitOnRCLost = filedValid(missionConfig.elementText("exitOnRCLost"), accurateImport);
|
||||||
|
dto.setExitOnRcLost(exitOnRCLost);
|
||||||
|
if ("executeLostAction".equals(exitOnRCLost)) {
|
||||||
|
dto.setExecuteRcLostAction(filedValid(missionConfig.elementText("executeRCLostAction"), accurateImport));
|
||||||
|
}
|
||||||
|
String takeOffSecurityHeight = filedValid(missionConfig.elementText("takeOffSecurityHeight"), accurateImport);
|
||||||
|
dto.setTakeoffSecurityHeight(StringUtils.isBlank(takeOffSecurityHeight) ? null : Double.parseDouble(missionConfig.elementText("takeOffSecurityHeight")));
|
||||||
|
String globalTransitionalSpeed = filedValid(missionConfig.elementText("globalTransitionalSpeed"), accurateImport);
|
||||||
|
dto.setGlobalTransitionalSpeed(StringUtils.isBlank(globalTransitionalSpeed) ? null : Double.parseDouble(globalTransitionalSpeed));
|
||||||
|
String globalRTHHeight = filedValid(missionConfig.elementText("globalRTHHeight"), accurateImport);
|
||||||
|
dto.setGlobalRthHeight(StringUtils.isBlank(globalRTHHeight) ? null : Double.parseDouble(globalRTHHeight));
|
||||||
|
/*Element droneInfo = missionConfig.element("droneInfo");
|
||||||
|
if (droneInfo == null) {
|
||||||
|
if (!accurateImport) {
|
||||||
|
throw new RenException("飞行器机型信息不存在,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String droneEnumValue = filedValid(droneInfo.elementText("droneEnumValue"), accurateImport);
|
||||||
|
String droneSubEnumValue = filedValid(droneInfo.elementText("droneSubEnumValue"), accurateImport);
|
||||||
|
}*/
|
||||||
|
/*Element payloadInfo = missionConfig.element("payloadInfo");
|
||||||
|
if (payloadInfo == null) {
|
||||||
|
if (!accurateImport) {
|
||||||
|
throw new RenException("负载机型信息不存在,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String payloadEnumValue = filedValid(payloadInfo.elementText("payloadEnumValue"), accurateImport);
|
||||||
|
String cameraName = CameraModel.getNameByType(payloadEnumValue);
|
||||||
|
dto.setCameraType(cameraName);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
Element folder = doc.element("Folder");
|
||||||
|
if (folder == null) {
|
||||||
|
if (!accurateImport) {
|
||||||
|
throw new RenException("航线信息不存在,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String autoFlightSpeed = filedValid(folder.elementText("autoFlightSpeed"), accurateImport);
|
||||||
|
dto.setFlightSpeed(StringUtils.isBlank(autoFlightSpeed) ? null : Double.parseDouble(autoFlightSpeed));
|
||||||
|
|
||||||
|
List<Element> placemarks = folder.elements("Placemark");
|
||||||
|
if (CollectionUtil.isEmpty(placemarks)) {
|
||||||
|
if (!accurateImport) {
|
||||||
|
throw new RenException("航点信息不存在,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<RouteWaypointDTO> waypointList = dto.getRouteWaypointList();
|
||||||
|
if (waypointList == null || waypointList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < waypointList.size(); i++) {
|
||||||
|
RouteWaypointDTO waypoint = waypointList.get(i);
|
||||||
|
Element placemark = placemarks.get(i);
|
||||||
|
Element point = placemark.element("Point");
|
||||||
|
if (point == null) {
|
||||||
|
if (!accurateImport) {
|
||||||
|
throw new RenException("航点经纬度不存在,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String coordinates = point.elementText("coordinates");
|
||||||
|
String[] split = coordinates.split(",");
|
||||||
|
waypoint.setLongitude(Double.valueOf(split[0]));
|
||||||
|
waypoint.setLatitude(Double.valueOf(split[1]));
|
||||||
|
}
|
||||||
|
String index = filedValid(placemark.elementText("index"), accurateImport);
|
||||||
|
waypoint.setWaypointSort(StringUtils.isBlank(index) ? null : Integer.valueOf(index));
|
||||||
|
String executeHeight = filedValid(placemark.elementText("executeHeight"), accurateImport);
|
||||||
|
waypoint.setFlightHeight(StringUtils.isBlank(executeHeight) ? null : Double.parseDouble(executeHeight));
|
||||||
|
String waypointSpeed = filedValid(placemark.elementText("waypointSpeed"), accurateImport);
|
||||||
|
waypoint.setFlightSpeed(StringUtils.isBlank(waypointSpeed) ? null : Double.parseDouble(waypointSpeed));
|
||||||
|
Element waypointHeadingParam = placemark.element("waypointHeadingParam");
|
||||||
|
if (waypointHeadingParam == null) {
|
||||||
|
if (!accurateImport) {
|
||||||
|
throw new RenException("偏航角模式参数不存在,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
waypoint.setWaypointHeadingMode(filedValid(waypointHeadingParam.elementText("waypointHeadingMode"), accurateImport));
|
||||||
|
String waypointHeadingAngle = filedValid(waypointHeadingParam.elementText("waypointHeadingAngle"), accurateImport);
|
||||||
|
if (StrUtil.isNotBlank(waypointHeadingAngle)) {
|
||||||
|
waypoint.setWaypointHeadingAngle(waypointHeadingAngle);
|
||||||
|
}
|
||||||
|
String waypointPoiPoint = waypointHeadingParam.elementText("waypointPoiPoint");
|
||||||
|
if (StrUtil.isNotBlank(waypointPoiPoint)) {
|
||||||
|
waypoint.setWaypointPoiPoint(waypointPoiPoint);
|
||||||
|
}
|
||||||
|
String waypointHeadingPathMode = filedValid(waypointHeadingParam.elementText("waypointHeadingPathMode"), accurateImport);
|
||||||
|
waypoint.setWaypointHeadingPathMode(StringUtils.isBlank(waypointHeadingPathMode) ? "" : waypointHeadingPathMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element waypointTurnParam = placemark.element("waypointTurnParam");
|
||||||
|
if (waypointTurnParam == null) {
|
||||||
|
if (!accurateImport) {
|
||||||
|
throw new RenException("航点转弯模式不存在,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
waypoint.setWaypointTruningMode(filedValid(waypointTurnParam.elementText("waypointTurnMode"), accurateImport));
|
||||||
|
String waypointTurnDampingDist = filedValid(waypointTurnParam.elementText("waypointTurnDampingDist"), accurateImport);
|
||||||
|
if (StrUtil.isNotBlank(waypointTurnDampingDist)) {
|
||||||
|
waypoint.setWaypointTurnDampingDist(waypointTurnDampingDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<WaypointActionDTO> actionList = waypoint.getWaypointActionList();
|
||||||
|
if (CollectionUtil.isEmpty(actionList)) {
|
||||||
|
actionList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<Element> actionGroups = placemark.elements("actionGroup");
|
||||||
|
for (Element actionGroup : actionGroups) {
|
||||||
|
Element actionTrigger = actionGroup.element("actionTrigger");
|
||||||
|
String actionTriggerType = actionTrigger.elementText("actionTriggerType");
|
||||||
|
if ("multipleTiming".equals(actionTriggerType) || "multipleDistance".equals(actionTriggerType)) {
|
||||||
|
String actionGroupId = actionGroup.elementText("actionGroupId");
|
||||||
|
String actionTriggerParam = actionTrigger.elementText("actionTriggerParam");
|
||||||
|
WaypointActionDTO action = new WaypointActionDTO();
|
||||||
|
action.setActionType(actionTriggerType);
|
||||||
|
action.setActionValue(actionTriggerParam);
|
||||||
|
action.setActionSort(Integer.parseInt(actionGroupId));
|
||||||
|
actionList.add(action);
|
||||||
|
|
||||||
|
String actionGroupEndIndex = actionGroup.elementText("actionGroupEndIndex");
|
||||||
|
RouteWaypointDTO uavWaypointDTO = waypointList.get(Integer.parseInt(actionGroupEndIndex));
|
||||||
|
List<WaypointActionDTO> actionList1 = uavWaypointDTO.getWaypointActionList();
|
||||||
|
if (CollectionUtil.isEmpty(actionList1)) {
|
||||||
|
actionList1 = new ArrayList<>();
|
||||||
|
}
|
||||||
|
WaypointActionDTO action2 = new WaypointActionDTO();
|
||||||
|
action2.setActionType(BusinessConstant.FINISH_TAKE_PHOTO_FLAG);
|
||||||
|
action2.setActionValue(actionGroupEndIndex);
|
||||||
|
action.setActionSort(Integer.parseInt(actionGroupId));
|
||||||
|
actionList1.add(action2);
|
||||||
|
uavWaypointDTO.setWaypointActionList(actionList1);
|
||||||
|
} else {
|
||||||
|
List<Element> actions = actionGroup.elements("action");
|
||||||
|
for (Element act : actions) {
|
||||||
|
WaypointActionDTO action = new WaypointActionDTO();
|
||||||
|
String actionActuatorFunc = act.elementText("actionActuatorFunc");
|
||||||
|
Element actionActuatorFuncParam = act.element("actionActuatorFuncParam");
|
||||||
|
if ("gimbalRotate".equals(actionActuatorFunc)) {
|
||||||
|
String gimbalYawRotateEnable = actionActuatorFuncParam.elementText("gimbalYawRotateEnable");
|
||||||
|
if ("1".equals(gimbalYawRotateEnable)) {
|
||||||
|
action.setActionType(actionActuatorFunc + "_yaw");
|
||||||
|
} else {
|
||||||
|
action.setActionType(actionActuatorFunc);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
action.setActionType(actionActuatorFunc);
|
||||||
|
}
|
||||||
|
SetActionParams.getAction(action, actionActuatorFuncParam);
|
||||||
|
actionList.add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waypoint.setWaypointActionList(actionList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//赋值器
|
||||||
|
public static String filedValid(String value, Boolean accurateImport) {
|
||||||
|
if (StringUtils.isBlank(value)) {
|
||||||
|
if (accurateImport) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw new RenException("字段缺失,无法解析");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,36 @@
|
||||||
|
package com.multictrl.common.utils.map;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经纬度
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/6/23
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class GeoPoint {
|
||||||
|
|
||||||
|
private double longitude;
|
||||||
|
|
||||||
|
private double latitude;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
*
|
||||||
|
* @param location 经纬度x,y
|
||||||
|
*/
|
||||||
|
public GeoPoint(String location) {
|
||||||
|
List<String> list = Arrays.asList(location.split(","));
|
||||||
|
this.longitude = Double.parseDouble(list.get(0));
|
||||||
|
this.latitude = Double.parseDouble(list.get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
package com.multictrl.common.utils.map;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.multictrl.common.exception.RenException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地图数据工具
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/6/23
|
||||||
|
*/
|
||||||
|
public class GeoUtils {
|
||||||
|
|
||||||
|
private static final int EARTH_RADIUS_IN_METERS = 6371000; // 地球半径,单位:米
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建wkt点
|
||||||
|
*
|
||||||
|
* @param longitude 精度
|
||||||
|
* @param latitude 维度
|
||||||
|
* @return 返回WKT格式的点,例如 "POINT(120.123456789 30.987654789)"
|
||||||
|
*/
|
||||||
|
public static String createPointWkt(Double longitude, Double latitude) {
|
||||||
|
if (longitude == null || latitude == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 判断是否是合法的数字
|
||||||
|
if (Double.isNaN(longitude) || Double.isNaN(latitude) ||
|
||||||
|
Double.isInfinite(longitude) || Double.isInfinite(latitude)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return String.format("POINT(%.9f %.9f)", longitude, latitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 WKT 格式的 POINT 字符串,并根据指定的精度返回经度和纬度
|
||||||
|
*
|
||||||
|
* @param pointWkt WKT 格式的 POINT 字符串,例如 "POINT(120.123456789 30.987654789)"
|
||||||
|
* @return 包含经度和纬度的 GeoPoint 对象
|
||||||
|
*/
|
||||||
|
public static GeoPoint parsePointWkt(String pointWkt) {
|
||||||
|
if (pointWkt == null || !pointWkt.startsWith("POINT(") || !pointWkt.endsWith(")")) {
|
||||||
|
throw new RenException("经纬度解析错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉 "POINT(" 和 ")",然后按空格分割
|
||||||
|
String content = pointWkt.substring(6, pointWkt.length() - 1);
|
||||||
|
String[] parts = content.split("\\s+");
|
||||||
|
|
||||||
|
if (parts.length != 2) {
|
||||||
|
throw new RenException("经纬度解析错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
double longitude = Double.parseDouble(parts[0]);
|
||||||
|
double latitude = Double.parseDouble(parts[1]);
|
||||||
|
|
||||||
|
// 根据精度进行四舍五入
|
||||||
|
longitude = roundToPrecision(longitude, 9);
|
||||||
|
latitude = roundToPrecision(latitude, 9);
|
||||||
|
|
||||||
|
return new GeoPoint(longitude, latitude);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RenException("经纬度解析错误", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 四舍五入到指定的小数位数
|
||||||
|
*
|
||||||
|
* @param value 要四舍五入的值
|
||||||
|
* @param precision 小数点后保留的位数
|
||||||
|
* @return 四舍五入后的结果
|
||||||
|
*/
|
||||||
|
private static double roundToPrecision(double value, int precision) {
|
||||||
|
double scale = Math.pow(10, precision);
|
||||||
|
return Math.round(value * scale) / scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 Haversine 公式计算两个经纬度之间的距离(单位:米)
|
||||||
|
*
|
||||||
|
* @param lat1 第一个点的纬度
|
||||||
|
* @param lon1 第一个点的经度
|
||||||
|
* @param lat2 第二个点的纬度
|
||||||
|
* @param lon2 第二个点的经度
|
||||||
|
* @return 距离(米)
|
||||||
|
*/
|
||||||
|
public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||||
|
double lat1Rad = Math.toRadians(lat1);
|
||||||
|
double lon1Rad = Math.toRadians(lon1);
|
||||||
|
double lat2Rad = Math.toRadians(lat2);
|
||||||
|
double lon2Rad = Math.toRadians(lon2);
|
||||||
|
|
||||||
|
double a = Math.pow(Math.sin((lat2Rad - lat1Rad) / 2), 2) +
|
||||||
|
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
|
||||||
|
Math.pow(Math.sin((lon2Rad - lon1Rad) / 2), 2);
|
||||||
|
|
||||||
|
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
return EARTH_RADIUS_IN_METERS * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断两点之间的距离是否在指定范围内(单位:米)
|
||||||
|
*
|
||||||
|
* @param lat1 第一个点的纬度
|
||||||
|
* @param lon1 第一个点的经度
|
||||||
|
* @param lat2 第二个点的纬度
|
||||||
|
* @param lon2 第二个点的经度
|
||||||
|
* @param minDistance 最小距离(单位:米)
|
||||||
|
* @param maxDistance 最大距离(单位:米)
|
||||||
|
* @return 是否在范围内
|
||||||
|
*/
|
||||||
|
public static boolean isDistanceInRange(double lat1, double lon1, double lat2, double lon2,
|
||||||
|
double minDistance, double maxDistance) {
|
||||||
|
double distance = calculateDistance(lat1, lon1, lat2, lon2);
|
||||||
|
return distance >= minDistance && distance <= maxDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 "经度,纬度;经度,纬度" 格式的字符串转换为闭合的坐标数组(用于 GeoJSON Polygon)
|
||||||
|
*
|
||||||
|
* @param mapping 输入的坐标字符串
|
||||||
|
* @return 格式化后的 List<List<Double>>,每个内层 List 为 [经度, 纬度]
|
||||||
|
*/
|
||||||
|
public static List<List<Double>> parseCoordinates(String mapping) {
|
||||||
|
if (mapping == null || mapping.trim().isEmpty()) {
|
||||||
|
throw new RenException("面状范围不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] points = mapping.split(";");
|
||||||
|
List<List<Double>> coordinates = new ArrayList<>();
|
||||||
|
|
||||||
|
List<Double> firstPoint = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < points.length; i++) {
|
||||||
|
String[] latLon = points[i].split(",");
|
||||||
|
if (latLon.length != 2) {
|
||||||
|
throw new RenException("无效坐标对: " + points[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
double lon = Double.parseDouble(latLon[0].trim());
|
||||||
|
double lat = Double.parseDouble(latLon[1].trim());
|
||||||
|
|
||||||
|
List<Double> point = List.of(lon, lat);
|
||||||
|
coordinates.add(point);
|
||||||
|
|
||||||
|
// 记录第一个点用于闭合
|
||||||
|
/*if (i == 0) {
|
||||||
|
firstPoint = point;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// 闭合多边形:最后一个点必须等于第一个点
|
||||||
|
/*if (firstPoint != null && !coordinates.get(coordinates.size() - 1).equals(firstPoint)) {
|
||||||
|
coordinates.add(firstPoint);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据经纬度获取高程(单位:米)
|
||||||
|
*
|
||||||
|
* @param latitude 纬度(如 40.714728)
|
||||||
|
* @param longitude 经度(如 -73.998672)
|
||||||
|
* @return 海拔高度(米)
|
||||||
|
* @throws Exception 如果API请求失败或数据异常
|
||||||
|
*/
|
||||||
|
public static double getElevation(double latitude, double longitude) {
|
||||||
|
// 构建API请求URL(使用aster30m数据集)
|
||||||
|
String url = "https://api.opentopodata.org/v1/aster30m?locations=" + latitude + "," + longitude;
|
||||||
|
|
||||||
|
//发送HTTP请求
|
||||||
|
String response = HttpUtil.get(url);
|
||||||
|
|
||||||
|
// 解析JSON
|
||||||
|
JSONObject json = JSONUtil.parseObj(response);
|
||||||
|
if (!json.containsKey("results") || json.getJSONArray("results").isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一个结果的高程值
|
||||||
|
return json.getJSONArray("results").getJSONObject(0).getDouble("elevation");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算点数集合总时长
|
||||||
|
* 包含了上升时间
|
||||||
|
*/
|
||||||
|
public static long calculateFlightTime(List<JSONObject> waypoints) {
|
||||||
|
if (waypoints == null || waypoints.size() < 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double totalSeconds = 0.0;
|
||||||
|
final double EARTH_RADIUS = 6371000.0; // 地球半径(米)
|
||||||
|
final double VERTICAL_SPEED = 2.0; // 垂直调整速度(m/s,大疆典型值)
|
||||||
|
|
||||||
|
for (int i = 1; i < waypoints.size(); i++) {
|
||||||
|
JSONObject prev = waypoints.get(i - 1);
|
||||||
|
JSONObject curr = waypoints.get(i);
|
||||||
|
|
||||||
|
// 获取位置和速度
|
||||||
|
double lat1 = prev.getDouble("latitude");
|
||||||
|
double lon1 = prev.getDouble("longitude");
|
||||||
|
double alt1 = prev.getDouble("altitude");
|
||||||
|
double lat2 = curr.getDouble("latitude");
|
||||||
|
double lon2 = curr.getDouble("longitude");
|
||||||
|
double alt2 = curr.getDouble("altitude");
|
||||||
|
double horizontalSpeed = prev.getDouble("speed"); // 水平速度(m/s)
|
||||||
|
|
||||||
|
// 1. 计算水平距离(地面投影,单位:米)
|
||||||
|
double lat1Rad = Math.toRadians(lat1);
|
||||||
|
double lon1Rad = Math.toRadians(lon1);
|
||||||
|
double lat2Rad = Math.toRadians(lat2);
|
||||||
|
double lon2Rad = Math.toRadians(lon2);
|
||||||
|
|
||||||
|
double deltaLat = lat2Rad - lat1Rad;
|
||||||
|
double deltaLon = lon2Rad - lon1Rad;
|
||||||
|
|
||||||
|
double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
|
||||||
|
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
|
||||||
|
Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
|
||||||
|
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
double groundDistance = EARTH_RADIUS * c;
|
||||||
|
|
||||||
|
// 2. 计算垂直高度差(绝对值)
|
||||||
|
double heightDiff = Math.abs(alt2 - alt1);
|
||||||
|
|
||||||
|
// 3. 计算两段飞行时间
|
||||||
|
double timeVertical = heightDiff / VERTICAL_SPEED; // 垂直调整时间
|
||||||
|
double timeHorizontal = groundDistance / horizontalSpeed; // 水平飞行时间
|
||||||
|
|
||||||
|
totalSeconds += timeVertical + timeHorizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(totalSeconds); // 四舍五入为整数秒
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.multictrl.common.utils.map;
|
||||||
|
|
||||||
|
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,70 @@
|
||||||
|
package com.multictrl.common.utils.weather;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 雾量工具类
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2025/10/13
|
||||||
|
*/
|
||||||
|
public class FogUtils {
|
||||||
|
// 轻雾类天气现象
|
||||||
|
public static final String[] LIGHT_FOG_TYPES = {
|
||||||
|
"轻雾"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中雾类天气现象
|
||||||
|
public static final String[] MODERATE_FOG_TYPES = {
|
||||||
|
"雾",
|
||||||
|
"浓雾"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 大雾类天气现象
|
||||||
|
public static final String[] HEAVY_FOG_TYPES = {
|
||||||
|
"强浓雾",
|
||||||
|
"大雾",
|
||||||
|
"特强浓雾"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为轻雾类
|
||||||
|
*/
|
||||||
|
public static boolean isMinFog(String fogType) {
|
||||||
|
return contains(LIGHT_FOG_TYPES, fogType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为中雾类
|
||||||
|
*/
|
||||||
|
public static boolean isModerateFog(String fogType) {
|
||||||
|
return contains(MODERATE_FOG_TYPES, fogType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为大雾类
|
||||||
|
*/
|
||||||
|
public static boolean isMaxFog(String fogType) {
|
||||||
|
return contains(HEAVY_FOG_TYPES, fogType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取雾类型所属的类别名称
|
||||||
|
*/
|
||||||
|
public static String getCategory(String fogType) {
|
||||||
|
if (isMinFog(fogType)) return "轻雾";
|
||||||
|
if (isModerateFog(fogType)) return "中雾";
|
||||||
|
if (isMaxFog(fogType)) return "大雾";
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否在数组中
|
||||||
|
*/
|
||||||
|
private static boolean contains(String[] array, String value) {
|
||||||
|
for (String item : array) {
|
||||||
|
if (item.equals(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.multictrl.common.utils.weather;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 霾量工具类
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2025/10/13
|
||||||
|
*/
|
||||||
|
public class HazeUtils {
|
||||||
|
|
||||||
|
// 轻度霾类天气现象
|
||||||
|
public static final String[] LIGHT_HAZE_TYPES = {
|
||||||
|
"霾"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中度霾类天气现象
|
||||||
|
public static final String[] MODERATE_HAZE_TYPES = {
|
||||||
|
"中度霾"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重度霾类天气现象
|
||||||
|
public static final String[] HEAVY_HAZE_TYPES = {
|
||||||
|
"重度霾",
|
||||||
|
"严重霾"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为轻度霾类
|
||||||
|
*/
|
||||||
|
public static boolean isMinHaze(String hazeType) {
|
||||||
|
return contains(LIGHT_HAZE_TYPES, hazeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为中度霾类
|
||||||
|
*/
|
||||||
|
public static boolean isModerateHaze(String hazeType) {
|
||||||
|
return contains(MODERATE_HAZE_TYPES, hazeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为重度霾类
|
||||||
|
*/
|
||||||
|
public static boolean isMaxHaze(String hazeType) {
|
||||||
|
return contains(HEAVY_HAZE_TYPES, hazeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取霾类型所属的类别名称
|
||||||
|
*/
|
||||||
|
public static String getCategory(String hazeType) {
|
||||||
|
if (isMinHaze(hazeType)) return "轻度霾";
|
||||||
|
if (isModerateHaze(hazeType)) return "中度霾";
|
||||||
|
if (isMaxHaze(hazeType)) return "重度霾";
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否在数组中
|
||||||
|
*/
|
||||||
|
private static boolean contains(String[] array, String value) {
|
||||||
|
for (String item : array) {
|
||||||
|
if (item.equals(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.multictrl.common.utils.weather;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 雨量工具类
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2025/10/13
|
||||||
|
*/
|
||||||
|
public class RainfallUtils {
|
||||||
|
|
||||||
|
// 小雨类天气现象
|
||||||
|
public static final String[] LIGHT_RAIN_TYPES = {
|
||||||
|
"毛毛雨/细雨",
|
||||||
|
"小雨",
|
||||||
|
"阵雨",
|
||||||
|
"雷阵雨",
|
||||||
|
"雨雪天气",
|
||||||
|
"雨夹雪",
|
||||||
|
"阵雨夹雪",
|
||||||
|
"冻雨"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中雨类天气现象
|
||||||
|
public static final String[] MODERATE_RAIN_TYPES = {
|
||||||
|
"中雨",
|
||||||
|
"小雨-中雨",
|
||||||
|
"雷阵雨并伴有冰雹"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 大雨类天气现象
|
||||||
|
public static final String[] HEAVY_RAIN_TYPES = {
|
||||||
|
"大雨",
|
||||||
|
"中雨-大雨",
|
||||||
|
"强阵雨",
|
||||||
|
"强雷阵雨",
|
||||||
|
"暴雨",
|
||||||
|
"大暴雨",
|
||||||
|
"特大暴雨",
|
||||||
|
"极端降雨",
|
||||||
|
"大雨-暴雨",
|
||||||
|
"暴雨-大暴雨",
|
||||||
|
"大暴雨-特大暴雨"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为小雨类
|
||||||
|
*/
|
||||||
|
public static boolean isMinRain(String precipitation) {
|
||||||
|
return contains(LIGHT_RAIN_TYPES, precipitation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为中雨类
|
||||||
|
*/
|
||||||
|
public static boolean isModerateRain(String precipitation) {
|
||||||
|
return contains(MODERATE_RAIN_TYPES, precipitation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为大雨类
|
||||||
|
*/
|
||||||
|
public static boolean isMaxRain(String precipitation) {
|
||||||
|
return contains(HEAVY_RAIN_TYPES, precipitation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取降水类型所属的类别名称
|
||||||
|
*/
|
||||||
|
public static String getCategory(String precipitation) {
|
||||||
|
if (isMinRain(precipitation)) return "小雨";
|
||||||
|
if (isModerateRain(precipitation)) return "中雨";
|
||||||
|
if (isMaxRain(precipitation)) return "大雨";
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否在数组中
|
||||||
|
*/
|
||||||
|
private static boolean contains(String[] array, String value) {
|
||||||
|
for (String item : array) {
|
||||||
|
if (item.equals(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.multictrl.common.utils.weather;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 雪量工具类
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2025/10/13
|
||||||
|
*/
|
||||||
|
public class SnowUtils {
|
||||||
|
// 小雪类天气现象
|
||||||
|
public static final String[] LIGHT_SNOW_TYPES = {
|
||||||
|
"雪",
|
||||||
|
"阵雪",
|
||||||
|
"小雪",
|
||||||
|
"雨雪天气",
|
||||||
|
"雨夹雪",
|
||||||
|
"阵雨夹雪"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中雪类天气现象
|
||||||
|
public static final String[] MODERATE_SNOW_TYPES = {
|
||||||
|
"中雪",
|
||||||
|
"小雪-中雪"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 大雪类天气现象
|
||||||
|
public static final String[] HEAVY_SNOW_TYPES = {
|
||||||
|
"大雪",
|
||||||
|
"暴雪",
|
||||||
|
"中雪-大雪",
|
||||||
|
"大雪-暴雪"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为小雪类
|
||||||
|
*/
|
||||||
|
public static boolean isMinSnow(String snowType) {
|
||||||
|
return contains(LIGHT_SNOW_TYPES, snowType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为中雪类
|
||||||
|
*/
|
||||||
|
public static boolean isModerateSnow(String snowType) {
|
||||||
|
return contains(MODERATE_SNOW_TYPES, snowType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为大雪类
|
||||||
|
*/
|
||||||
|
public static boolean isMaxSnow(String snowType) {
|
||||||
|
return contains(HEAVY_SNOW_TYPES, snowType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取雪类型所属的类别名称
|
||||||
|
*/
|
||||||
|
public static String getCategory(String snowType) {
|
||||||
|
if (isMinSnow(snowType)) return "小雪";
|
||||||
|
if (isModerateSnow(snowType)) return "中雪";
|
||||||
|
if (isMaxSnow(snowType)) return "大雪";
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否在数组中
|
||||||
|
*/
|
||||||
|
private static boolean contains(String[] array, String value) {
|
||||||
|
for (String item : array) {
|
||||||
|
if (item.equals(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.multictrl.common.utils.weather;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 飞速级别转换
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2025/10/12
|
||||||
|
*/
|
||||||
|
public class WindSpeedConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据级别名称获取最大风速
|
||||||
|
*
|
||||||
|
* @param levelName 级别名称 ("≤3", "4", "5", ..., "12")
|
||||||
|
* @return 最大风速 (m/s)
|
||||||
|
*/
|
||||||
|
public static double getMaxWindSpeed(String levelName) {
|
||||||
|
return switch (levelName) {
|
||||||
|
case "≤3" -> 5.4;
|
||||||
|
case "4" -> 7.9;
|
||||||
|
case "5" -> 10.7;
|
||||||
|
case "6" -> 13.8;
|
||||||
|
case "7" -> 17.1;
|
||||||
|
case "8" -> 20.7;
|
||||||
|
case "9" -> 24.4;
|
||||||
|
case "10" -> 28.4;
|
||||||
|
case "11" -> 32.6;
|
||||||
|
case "12" -> 56.0;
|
||||||
|
default -> throw new IllegalArgumentException("无效的级别名称: " + levelName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定级别的最小风速
|
||||||
|
*/
|
||||||
|
public static double getMinWindSpeed(String levelName) {
|
||||||
|
return switch (levelName) {
|
||||||
|
case "≤3" -> 0.0;
|
||||||
|
case "4" -> 5.5;
|
||||||
|
case "5" -> 8.0;
|
||||||
|
case "6" -> 10.8;
|
||||||
|
case "7" -> 13.9;
|
||||||
|
case "8" -> 17.2;
|
||||||
|
case "9" -> 20.8;
|
||||||
|
case "10" -> 24.5;
|
||||||
|
case "11" -> 28.5;
|
||||||
|
case "12" -> 32.7;
|
||||||
|
default -> throw new IllegalArgumentException("无效的级别名称: " + levelName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
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.common.validator.group.AddGroup;
|
||||||
|
import com.multictrl.common.validator.group.DefaultGroup;
|
||||||
|
import com.multictrl.common.validator.group.UpdateGroup;
|
||||||
|
import com.multictrl.modules.business.dto.AiWarningDTO;
|
||||||
|
import com.multictrl.modules.business.service.AiWarningService;
|
||||||
|
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 java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI识别预警
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-18
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/ai/warning")
|
||||||
|
@Tag(name = "AI识别预警")
|
||||||
|
@ApiOrder(26)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AiWarningController {
|
||||||
|
private final AiWarningService aiWarningService;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "设置ai预警")
|
||||||
|
@LogOperation("设置ai预警")
|
||||||
|
@RequiresPermissions("bus:ai:warning:save")
|
||||||
|
public Result<Object> save(@RequestBody AiWarningDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, AddGroup.class);
|
||||||
|
aiWarningService.save(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@Operation(summary = "修改ai预警")
|
||||||
|
@LogOperation("修改ai预警")
|
||||||
|
@RequiresPermissions("bus:ai:warning:update")
|
||||||
|
public Result<Object> update(@RequestBody AiWarningDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, UpdateGroup.class);
|
||||||
|
aiWarningService.update(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "获取预警信息")
|
||||||
|
@RequiresPermissions("bus:ai:warning:get")
|
||||||
|
public Result<List<AiWarningDTO>> get() {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("status", "1");
|
||||||
|
List<AiWarningDTO> list = aiWarningService.list(map);
|
||||||
|
|
||||||
|
return new Result<List<AiWarningDTO>>().ok(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
package com.multictrl.modules.business.controller;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
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.List;
|
||||||
|
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_stop")) {
|
||||||
|
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 Integer 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 = "对焦模式 0:MF 1:AFS 2: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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getExposureValue")
|
||||||
|
@Operation(summary = "获取相机曝光值")
|
||||||
|
public Result<List<JSONObject>> getExposureValue() {
|
||||||
|
|
||||||
|
return new Result<List<JSONObject>>().ok(cameraService.getExposureValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,273 @@
|
||||||
|
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.common.validator.ValidatorUtils;
|
||||||
|
import com.multictrl.modules.business.dto.remote.debug.ESIMActivate;
|
||||||
|
import com.multictrl.modules.business.dto.remote.debug.ESIMOperatorSwitch;
|
||||||
|
import com.multictrl.modules.business.dto.remote.debug.RtkCalibration;
|
||||||
|
import com.multictrl.modules.business.dto.remote.debug.SIMSlotSwitch;
|
||||||
|
import com.multictrl.modules.business.service.DJIBaseService;
|
||||||
|
import com.multictrl.modules.business.service.DebugService;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 远程调试
|
||||||
|
*
|
||||||
|
* @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 djiBaseService;
|
||||||
|
private final DebugService debugService;
|
||||||
|
|
||||||
|
@Operation(summary = "调试模式开启")
|
||||||
|
@PostMapping("/debugModeOpen/{dockSn}")
|
||||||
|
@LogOperation("调试模式开启")
|
||||||
|
@RequiresPermissions("bus:debug:operation")
|
||||||
|
public Result<Object> debugModeOpen(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "debug_mode_open"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "调试模式关闭")
|
||||||
|
@PostMapping("/debugModeClose/{dockSn}")
|
||||||
|
@LogOperation("调试模式关闭")
|
||||||
|
@RequiresPermissions("bus:debug:operation")
|
||||||
|
public Result<Object> debugModeClose(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "debug_mode_close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "强制关舱盖",
|
||||||
|
description = "机场“设备属性”推送的 drone_in_dock 字段值为 0 时,且通过机场摄像头观看确认飞行器不在舱内后,可调用该指令强制关闭舱盖。否则可能导致桨叶被夹。")
|
||||||
|
@PostMapping("/coverForceClose/{dockSn}")
|
||||||
|
@LogOperation("强制关舱盖")
|
||||||
|
@RequiresPermissions("bus:debug:operation")
|
||||||
|
public Result<Object> coverForceClose(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "cover_force_close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "打开舱盖")
|
||||||
|
@PostMapping("/coverOpen/{dockSn}")
|
||||||
|
@LogOperation("打开舱盖")
|
||||||
|
@RequiresPermissions("bus:debug:operation")
|
||||||
|
public Result<Object> coverOpen(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "cover_open"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "关闭舱盖")
|
||||||
|
@PostMapping("/coverClose/{dockSn}")
|
||||||
|
@LogOperation("关闭舱盖")
|
||||||
|
@RequiresPermissions("bus:debug:operation")
|
||||||
|
public Result<Object> coverClose(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "cover_close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "飞行器开机")
|
||||||
|
@PostMapping("/droneOpen/{dockSn}")
|
||||||
|
@LogOperation("飞行器开机")
|
||||||
|
@RequiresPermissions("bus:debug:operation")
|
||||||
|
public Result<Object> droneOpen(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "drone_open"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "飞行器关机")
|
||||||
|
@PostMapping("/droneClose/{dockSn}")
|
||||||
|
@LogOperation("飞行器关机")
|
||||||
|
@RequiresPermissions("bus:debug:operation")
|
||||||
|
public Result<Object> droneClose(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "drone_close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "eSIM的运营商切换")
|
||||||
|
@PostMapping("/eSIMOperatorSwitch/{dockSn}")
|
||||||
|
@LogOperation("eSIM的运营商切换")
|
||||||
|
@RequiresPermissions("bus:debug:eSIMOperatorSwitch")
|
||||||
|
public Result<Object> eSIMOperatorSwitch(@PathVariable String dockSn,
|
||||||
|
@RequestBody ESIMOperatorSwitch eSimOperatorSwitch) {
|
||||||
|
ValidatorUtils.validateEntity(eSimOperatorSwitch);
|
||||||
|
return new Result<>().ok(debugService.eSIMOperatorSwitch(dockSn, eSimOperatorSwitch));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "eSIM和SIM切换")
|
||||||
|
@PostMapping("/simSlotSwitch/{dockSn}")
|
||||||
|
@LogOperation("eSIM和SIM切换")
|
||||||
|
@RequiresPermissions("bus:debug:simSlotSwitch")
|
||||||
|
public Result<Object> simSlotSwitch(@PathVariable String dockSn,
|
||||||
|
@RequestBody SIMSlotSwitch simSlotSwitch) {
|
||||||
|
ValidatorUtils.validateEntity(simSlotSwitch);
|
||||||
|
return new Result<>().ok(debugService.simSlotSwitch(dockSn, simSlotSwitch));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "eSIM激活")
|
||||||
|
@PostMapping("/eSimActivate/{dockSn}")
|
||||||
|
@LogOperation("eSIM激活")
|
||||||
|
@RequiresPermissions("bus:debug:eSimActivate")
|
||||||
|
public Result<Object> eSimActivate(@PathVariable String dockSn,
|
||||||
|
@RequestBody ESIMActivate esimActivate) {
|
||||||
|
ValidatorUtils.validateEntity(esimActivate);
|
||||||
|
return new Result<>().ok(debugService.eSimActivate(dockSn, esimActivate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "增强图传开关")
|
||||||
|
@PostMapping("/sdrWorkModeSwitch/{dockSn}")
|
||||||
|
@LogOperation("增强图传开关")
|
||||||
|
@RequiresPermissions("bus:debug:sdrWorkModeSwitch")
|
||||||
|
public Result<Object> sdrWorkModeSwitch(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "图传模式 {0:仅使用 SDR,1:4G 增强模式},在 4G 增强模式下,SDR 与 4G 会同时使用")
|
||||||
|
@RequestParam Integer workMode) {
|
||||||
|
if (workMode != 0 && workMode != 1) {
|
||||||
|
return new Result<>().error(ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
return new Result<>().ok(debugService.sdrWorkModeSwitch(dockSn, workMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "关闭充电")
|
||||||
|
@PostMapping("/chargeClose/{dockSn}")
|
||||||
|
@LogOperation("关闭充电")
|
||||||
|
@RequiresPermissions("bus:debug:chargeClose")
|
||||||
|
public Result<Object> chargeClose(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "charge_close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "打开充电")
|
||||||
|
@PostMapping("/chargeOpen/{dockSn}")
|
||||||
|
@LogOperation("打开充电")
|
||||||
|
@RequiresPermissions("bus:debug:chargeOpen")
|
||||||
|
public Result<Object> chargeOpen(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "charge_open"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "飞行器数据格式化")
|
||||||
|
@PostMapping("/droneFormat/{dockSn}")
|
||||||
|
@LogOperation("飞行器数据格式化")
|
||||||
|
@RequiresPermissions("bus:debug:droneFormat")
|
||||||
|
public Result<Object> droneFormat(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "drone_format"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "机场数据格式化")
|
||||||
|
@PostMapping("/deviceFormat/{dockSn}")
|
||||||
|
@LogOperation("机场数据格式化")
|
||||||
|
@RequiresPermissions("bus:debug:deviceFormat")
|
||||||
|
public Result<Object> deviceFormat(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "device_format"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "机场重启")
|
||||||
|
@PostMapping("/deviceReboot/{dockSn}")
|
||||||
|
@LogOperation("机场重启")
|
||||||
|
@RequiresPermissions("bus:debug:deviceReboot")
|
||||||
|
public Result<Object> deviceReboot(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "device_reboot"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "电池运行模式切换")
|
||||||
|
@PostMapping("/batteryStoreModeSwitch/{dockSn}")
|
||||||
|
@LogOperation("电池运行模式切换")
|
||||||
|
@RequiresPermissions("bus:debug:batteryStoreModeSwitch")
|
||||||
|
public Result<Object> batteryStoreModeSwitch(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "操作 {1:计划模式,2:待命模式}")
|
||||||
|
@RequestParam Integer action) {
|
||||||
|
if (action != 1 && action != 2) {
|
||||||
|
return new Result<>().error(ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
return new Result<>().ok(debugService.batteryStoreModeSwitch(dockSn, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "机场声光报警开关")
|
||||||
|
@PostMapping("/alarmStateSwitch/{dockSn}")
|
||||||
|
@LogOperation("机场声光报警开关")
|
||||||
|
@RequiresPermissions("bus:debug:alarmStateSwitch")
|
||||||
|
public Result<Object> alarmStateSwitch(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "操作 {0:关闭,1:开启}")
|
||||||
|
@RequestParam Integer action) {
|
||||||
|
if (action != 0 && action != 1) {
|
||||||
|
return new Result<>().error(ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
return new Result<>().ok(debugService.alarmStateSwitch(dockSn, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "机场空调工作模式切换")
|
||||||
|
@PostMapping("/airConditionerModeSwitch/{dockSn}")
|
||||||
|
@LogOperation("机场空调工作模式切换")
|
||||||
|
@RequiresPermissions("bus:debug:airConditionerModeSwitch")
|
||||||
|
public Result<Object> airConditionerModeSwitch(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "操作 {0:使机场空调进入空闲模式(关闭制冷、制热或除湿)," +
|
||||||
|
"1:使机场空调进入制冷模式,2:使机场空调进入制热模式," +
|
||||||
|
"3:使机场空调进入除湿模式(除湿包含制冷除湿及制热除湿,由设备端根据自身所处情况自动化处理,无需用户介入}")
|
||||||
|
@RequestParam Integer action) {
|
||||||
|
if (action != 0 && action != 1 && action != 2 && action != 3) {
|
||||||
|
return new Result<>().error(ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
return new Result<>().ok(debugService.airConditionerModeSwitch(dockSn, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "电池保养状态切换")
|
||||||
|
@PostMapping("/batteryMaintenanceSwitch/{dockSn}")
|
||||||
|
@LogOperation("电池保养状态切换")
|
||||||
|
@RequiresPermissions("bus:debug:batteryMaintenanceSwitch")
|
||||||
|
public Result<Object> batteryMaintenanceSwitch(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "操作 {0:关闭,1:开启}")
|
||||||
|
@RequestParam Integer action) {
|
||||||
|
if (action != 0 && action != 1) {
|
||||||
|
return new Result<>().error(ErrorCode.PARAMS_ERROR);
|
||||||
|
}
|
||||||
|
return new Result<>().ok(debugService.batteryMaintenanceSwitch(dockSn, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "关闭补光灯")
|
||||||
|
@PostMapping("/supplementLightClose/{dockSn}")
|
||||||
|
@LogOperation("关闭补光灯")
|
||||||
|
@RequiresPermissions("bus:debug:supplementLightClose")
|
||||||
|
public Result<Object> supplementLightClose(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "supplement_light_close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "打开补光灯")
|
||||||
|
@PostMapping("/supplementLightOpen/{dockSn}")
|
||||||
|
@LogOperation("打开补光灯")
|
||||||
|
@RequiresPermissions("bus:debug:supplementLightOpen")
|
||||||
|
public Result<Object> supplementLightOpen(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "supplement_light_open"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "一键标定(仅支持大疆机场三)")
|
||||||
|
@PostMapping("/rtkCalibration/{dockSn}")
|
||||||
|
@LogOperation("一键标定")
|
||||||
|
@RequiresPermissions("bus:debug:rtkCalibration")
|
||||||
|
public Result<Object> rtkCalibration(@PathVariable String dockSn,
|
||||||
|
@RequestBody RtkCalibration rtkCalibration) {
|
||||||
|
ValidatorUtils.validateEntity(rtkCalibration);
|
||||||
|
return new Result<>().ok(debugService.rtkCalibration(dockSn, rtkCalibration));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "推杆展开(仅支持阿罗斯系列、大疆机场一)")
|
||||||
|
@PostMapping("/putterOpen/{dockSn}")
|
||||||
|
@LogOperation("推杆展开")
|
||||||
|
@RequiresPermissions("bus:debug:putterOpen")
|
||||||
|
public Result<Object> putterOpen(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "putter_open"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "推杆闭合(仅支持阿罗斯系列、大疆机场一)")
|
||||||
|
@PostMapping("/putterClose/{dockSn}")
|
||||||
|
@LogOperation("推杆闭合")
|
||||||
|
@RequiresPermissions("bus:debug:putterClose")
|
||||||
|
public Result<Object> putterClose(@PathVariable String dockSn) {
|
||||||
|
return new Result<>().ok(djiBaseService.executeAndReturnResult(dockSn, "putter_close"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
package com.multictrl.modules.business.controller;
|
||||||
|
|
||||||
|
import com.multictrl.common.annotation.ApiOrder;
|
||||||
|
import com.multictrl.common.annotation.DataFilter;
|
||||||
|
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码")
|
||||||
|
})
|
||||||
|
@DataFilter
|
||||||
|
@RequiresPermissions("bus:dock:page")
|
||||||
|
public Result<PageData<DockDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<DockDTO> page = dockService.pageList(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,280 @@
|
||||||
|
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.DockSetService;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机场属性设置
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/6/17
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/dock/set")
|
||||||
|
@Tag(name = "属性设置")
|
||||||
|
@ApiOrder(25)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DockSetController {
|
||||||
|
private final DockSetService dockSetService;
|
||||||
|
|
||||||
|
@Operation(summary = "空中回传(目前仅大疆机场三支持)")
|
||||||
|
@PostMapping("/airTransfer/{dockSn}")
|
||||||
|
@LogOperation("空中回传")
|
||||||
|
@RequiresPermissions("bus:dockSet:airTransfer")
|
||||||
|
public Result<Object> supplementLightClose(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "空中回传开关 false:关闭,true:开启")
|
||||||
|
@RequestParam Boolean enable) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "air_transfer_enable", null, enable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "机场静音模式")
|
||||||
|
@PostMapping("/silentMode/{dockSn}")
|
||||||
|
@LogOperation("机场静音模式")
|
||||||
|
@RequiresPermissions("bus:dockSet:silentMode")
|
||||||
|
public Result<Object> supplementLightClose(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "机场静音模式 0:非静音模式,1:静音模式")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "silent_mode", null, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户体验改善计划")
|
||||||
|
@PostMapping("/userExperienceImprovement/{dockSn}")
|
||||||
|
@LogOperation("用户体验改善计划")
|
||||||
|
@RequiresPermissions("bus:dockSet:userExperienceImprovement")
|
||||||
|
public Result<Object> userExperienceImprovement(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "用户体验改善计划 0:初始状态,1:拒绝加入用户体验改善计划,2:同意加入用户体验改善计划")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "user_experience_improvement", null, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "飞行器限远设置")
|
||||||
|
@PostMapping("/distanceLimitStatus/{dockSn}")
|
||||||
|
@LogOperation("飞行器限远设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:distanceLimitStatus")
|
||||||
|
public Result<Object> distanceLimitStatus(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "是否开启限远 0:未设置,1:已设置")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "distance_limit_status", "state", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "飞行器限远距离设置")
|
||||||
|
@PostMapping("/distanceLimit/{dockSn}")
|
||||||
|
@LogOperation("飞行器限远距离设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:distanceLimit")
|
||||||
|
public Result<Object> distanceLimit(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "限远距离 {max:8000,min:15}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "distance_limit_status", "distance_limit", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "飞行器限高设置")
|
||||||
|
@PostMapping("/heightLimit/{dockSn}")
|
||||||
|
@LogOperation("飞行器限高设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:heightLimit")
|
||||||
|
public Result<Object> heightLimit(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "限远距离 {max:1500,min:20}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "height_limit", null, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "飞行器夜航灯设置")
|
||||||
|
@PostMapping("/nightLightsState/{dockSn}")
|
||||||
|
@LogOperation("飞行器夜航灯设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:nightLightsState")
|
||||||
|
public Result<Object> nightLightsState(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "飞行器夜航灯状态 0:关闭,1:打开")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "night_lights_state", null, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "调色盘样式设置", description = "红外相机提供多种调色样式,用户可根据不同的场景选择不同的色彩,便于更加清晰地查看目标")
|
||||||
|
@PostMapping("/thermalCurrentPaletteStyle/{dockSn}")
|
||||||
|
@LogOperation("调色盘样式设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:thermalCurrentPaletteStyle")
|
||||||
|
public Result<Object> thermalCurrentPaletteStyle(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "调色盘样式 {0:白热,1:黑热,2:描红,3:医疗,5:彩虹 1,6:铁红,8:北极,11:熔岩,12:热铁,13:彩虹 2}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "type_subtype_gimbalindex", "thermal_current_palette_style", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "增益模式设置", description = "低增益提供更大的测温范围,高增益拥有更高的测温精度")
|
||||||
|
@PostMapping("/thermalGainMode/{dockSn}")
|
||||||
|
@LogOperation("增益模式设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:thermalGainMode")
|
||||||
|
public Result<Object> thermalGainMode(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "增益模式 {0:自动,1:低增益, 测温范围0°C-500°C,2:高增益, 测温范围-20°C-150°C}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "type_subtype_gimbalindex", "thermal_gain_mode", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "开启等温线设置", description = "等温线允许用户观测自己感兴趣的温度区间的内容,让兴趣温度区间的物体能更加凸显")
|
||||||
|
@PostMapping("/thermalIsothermState/{dockSn}")
|
||||||
|
@LogOperation("开启等温线设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:thermalIsothermState")
|
||||||
|
public Result<Object> thermalIsothermState(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "是否开启等温线 0:关闭,1:开启")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "type_subtype_gimbalindex", "thermal_isotherm_state", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "测温区间上限设置", description = "仅启用等温线功能后有效")
|
||||||
|
@PostMapping("/thermalIsothermUpperLimit/{dockSn}")
|
||||||
|
@LogOperation("测温区间上限设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:thermalIsothermUpperLimit")
|
||||||
|
public Result<Object> thermalIsothermUpperLimit(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "测温区间上限 {unit_name:摄氏度 / °C}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "type_subtype_gimbalindex", "thermal_isotherm_upper_limit", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "测温区间下限设置", description = "仅启用等温线功能后有效")
|
||||||
|
@PostMapping("/thermalIsothermLowerLimit/{dockSn}")
|
||||||
|
@LogOperation("测温区间下限设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:thermalIsothermLowerLimit")
|
||||||
|
public Result<Object> thermalIsothermLowerLimit(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "测温区间下限 {unit_name:摄氏度 / °C}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "type_subtype_gimbalindex", "thermal_isotherm_lower_limit", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "指点飞行高度设置", description = "相对(机场)起飞点的高度,相对高 ALT")
|
||||||
|
@PostMapping("/commanderFlightHeight/{dockSn}")
|
||||||
|
@LogOperation("指点飞行高度设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:commanderFlightHeight")
|
||||||
|
public Result<Object> commanderFlightHeight(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "指点飞行高度 {max:3000,min:2,step:0.1,unit_name:米 / m}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "commander_flight_height", null, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "指点飞行模式设置", description = "指点飞行模式设置值")
|
||||||
|
@PostMapping("/commanderFlightMode/{dockSn}")
|
||||||
|
@LogOperation("指点飞行模式设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:commanderFlightMode")
|
||||||
|
public Result<Object> commanderFlightMode(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "指点飞行模式设置值 {0:智能高度飞行,1:设定高度飞行}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "commander_flight_mode", null, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "指点飞行失控动作设置", description = "执行指点飞行时失控了,选择继续执行完,还是执行普通失控行为")
|
||||||
|
@PostMapping("/commanderModeLostAction/{dockSn}")
|
||||||
|
@LogOperation("指点飞行失控动作设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:commanderModeLostAction")
|
||||||
|
public Result<Object> commanderModeLostAction(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "指点飞行失控动作 {0:继续执行指点飞行任务,1:退出指点飞行任务,执行普通失控行为}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "commander_mode_lost_action", null, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
//用户对相机拍摄的照片和录像文件进行水印配置, 目前暂不支持直播画面水印
|
||||||
|
@Operation(summary = "水印显示全局开关设置", description = "需要开启全局开关才能显示水印")
|
||||||
|
@PostMapping("/cameraWatermarkGlobalEnable/{dockSn}")
|
||||||
|
@LogOperation("水印显示全局开关设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:cameraWatermarkGlobalEnable")
|
||||||
|
public Result<Object> cameraWatermarkGlobalEnable(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "水印显示全局开关 0:关闭,1:开启")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "camera_watermark_settings", "global_enable", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "机型显示开关设置")
|
||||||
|
@PostMapping("/cameraWatermarkDroneTypeEnable/{dockSn}")
|
||||||
|
@LogOperation("机型显示开关设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:cameraWatermarkDroneTypeEnable")
|
||||||
|
public Result<Object> cameraWatermarkDroneTypeEnable(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "机型显示开关 0:关闭,1:开启")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "camera_watermark_settings", "drone_type_enable", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "飞行器序列号显示开关设置")
|
||||||
|
@PostMapping("/cameraWatermarkDroneSnEnable/{dockSn}")
|
||||||
|
@LogOperation("飞行器序列号显示开关设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:cameraWatermarkDroneSnEnable")
|
||||||
|
public Result<Object> cameraWatermarkDroneSnEnable(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "飞行器序列号显示开关 0:关闭,1:开启")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "camera_watermark_settings", "drone_sn_enable", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "日期时间显示开关设置", description = "时区默认为当地时区")
|
||||||
|
@PostMapping("/cameraWatermarkDateTimeEnable/{dockSn}")
|
||||||
|
@LogOperation("日期时间显示开关设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:cameraWatermarkDateTimeEnable")
|
||||||
|
public Result<Object> cameraWatermarkDateTimeEnable(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "日期时间显示开关 0:关闭,1:开启")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "camera_watermark_settings", "datetime_enable", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "经纬度&海拔高显示开关设置")
|
||||||
|
@PostMapping("/cameraWatermarkGpsEnable/{dockSn}")
|
||||||
|
@LogOperation("经纬度&海拔高显示开关设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:cameraWatermarkGpsEnable")
|
||||||
|
public Result<Object> cameraWatermarkGpsEnable(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "经纬度&海拔高显示开关 0:关闭,1:开启")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "camera_watermark_settings", "gps_enable", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "自定义文案显示开关设置")
|
||||||
|
@PostMapping("/cameraWatermarkCustomStrEnable/{dockSn}")
|
||||||
|
@LogOperation("自定义文案显示开关设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:cameraWatermarkCustomStrEnable")
|
||||||
|
public Result<Object> cameraWatermarkCustomStrEnable(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "自定义文案显示开关 0:关闭,1:开启")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "camera_watermark_settings", "user_custom_string_enable", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "自定义文案内容设置", description = "最多可显示 250 字节")
|
||||||
|
@PostMapping("/cameraWatermarkCustomStr/{dockSn}")
|
||||||
|
@LogOperation("自定义文案内容设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:cameraWatermarkCustomStr")
|
||||||
|
public Result<Object> cameraWatermarkCustomStr(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "自定义文案内容 (最多可显示 250 字节)")
|
||||||
|
@RequestParam String value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "camera_watermark_settings", "user_custom_string", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "水印在画面中位置设置")
|
||||||
|
@PostMapping("/cameraWatermarkLayout/{dockSn}")
|
||||||
|
@LogOperation("水印在画面中位置设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:cameraWatermarkLayout")
|
||||||
|
public Result<Object> cameraWatermarkLayout(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "水印在画面中位置 {0:左上,1:左下,2:右上,3:右下}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "camera_watermark_settings", "layout", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Operation(summary = "返航高度设置")
|
||||||
|
@PostMapping("/rthAltitude/{dockSn}")
|
||||||
|
@LogOperation("返航高度设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:rthAltitude")
|
||||||
|
public Result<Object> rthAltitude(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "返航高度 {max:500,min:20,unit_name:米 / m}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "rth_altitude", null, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "返航预留电量设置",
|
||||||
|
description = "注意:预留返航电量设置过低可能导致飞机没有足够电量完成返航。对于 DJI Dock 3,我们建议将该值设置在 15% ~ 50% 范围内以提供更安全的返航电量预留。")
|
||||||
|
@PostMapping("/remainingPowerForReturnHome/{dockSn}")
|
||||||
|
@LogOperation("返航预留电量设置")
|
||||||
|
@RequiresPermissions("bus:dockSet:remainingPowerForReturnHome")
|
||||||
|
public Result<Object> remainingPowerForReturnHome(@PathVariable String dockSn,
|
||||||
|
@Parameter(description = "返航预留电量 {max:100,min:0}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
return new Result<>().ok(dockSetService.setProperty(dockSn, "remaining_power_for_return_home", null, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,112 @@
|
||||||
|
package com.multictrl.modules.business.controller;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
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.dto.MediaFileDTO;
|
||||||
|
import com.multictrl.modules.business.entity.SrsRecordEntity;
|
||||||
|
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.List;
|
||||||
|
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 = "每页显示记录数"),
|
||||||
|
@Parameter(name = "key", description = "机库/航线名称"),
|
||||||
|
@Parameter(name = "taskType", description = "任务类型 1:航线飞行 2:手动飞行 3:定时飞行"),
|
||||||
|
@Parameter(name = "routeType", description = "航线类型 航点waypoint"),
|
||||||
|
@Parameter(name = "taskStatus", description = "任务状态 0:进行中 -1:失败 1:完成 2:阻飞")
|
||||||
|
})
|
||||||
|
@RequiresPermissions("bus:task:page")
|
||||||
|
public Result<PageData<FlightTaskDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<FlightTaskDTO> page = flightTaskService.pageList(params);
|
||||||
|
|
||||||
|
return new Result<PageData<FlightTaskDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
@Operation(summary = "信息")
|
||||||
|
@RequiresPermissions("bus: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("bus:task:delete")
|
||||||
|
public Result<Object> delete(@RequestBody Long[] ids) {
|
||||||
|
//效验数据
|
||||||
|
AssertUtils.isArrayEmpty(ids, "id");
|
||||||
|
flightTaskService.delete(ids);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getFlightTrack/{taskId}")
|
||||||
|
@Operation(summary = "飞行轨迹")
|
||||||
|
@RequiresPermissions("bus:task:flightTrack")
|
||||||
|
public Result<List<JSONObject>> getFlightTrack(@PathVariable("taskId") String taskId) {
|
||||||
|
List<JSONObject> data = flightTaskService.getFlightTrack(taskId);
|
||||||
|
|
||||||
|
return new Result<List<JSONObject>>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getFlightLog/{taskId}")
|
||||||
|
@Operation(summary = "飞行日志")
|
||||||
|
@RequiresPermissions("bus:task:flightLog")
|
||||||
|
public Result<List<JSONObject>> getFlightLog(@PathVariable("taskId") String taskId) {
|
||||||
|
List<JSONObject> data = flightTaskService.getFlightLog(taskId);
|
||||||
|
|
||||||
|
return new Result<List<JSONObject>>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getTaskMedia/{taskId}")
|
||||||
|
@Operation(summary = "媒体文件")
|
||||||
|
@RequiresPermissions("bus:task:taskMedia")
|
||||||
|
public Result<List<MediaFileDTO>> getTaskMedia(@PathVariable("taskId") String taskId) {
|
||||||
|
List<MediaFileDTO> data = flightTaskService.getTaskMedia(taskId);
|
||||||
|
|
||||||
|
return new Result<List<MediaFileDTO>>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getFlightVideo/{taskId}")
|
||||||
|
@Operation(summary = "飞行视频")
|
||||||
|
@RequiresPermissions("bus:task:flightVideo")
|
||||||
|
public Result<List<SrsRecordEntity>> getFlightVideo(@PathVariable("taskId") String taskId) {
|
||||||
|
List<SrsRecordEntity> data = flightTaskService.getFlightVideo(taskId);
|
||||||
|
|
||||||
|
return new Result<List<SrsRecordEntity>>().ok(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.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.GeoMarkDTO;
|
||||||
|
import com.multictrl.modules.business.service.GeoMarkService;
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地图标注
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-05-25
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/mark")
|
||||||
|
@Tag(name = "地图标注")
|
||||||
|
@ApiOrder(12)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GeoMarkController {
|
||||||
|
private final GeoMarkService geoMarkService;
|
||||||
|
|
||||||
|
@GetMapping("page")
|
||||||
|
@Operation(summary = "分页")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "dockSn", description = "设备编号"),
|
||||||
|
@Parameter(name = "markType", description = "标注类型"),
|
||||||
|
@Parameter(name = "markName", description = "标注名称"),
|
||||||
|
@Parameter(name = "showAll", description = "是否展示全局", example = "字符串:true")
|
||||||
|
})
|
||||||
|
@RequiresPermissions("bus:mark:page")
|
||||||
|
public Result<PageData<GeoMarkDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<GeoMarkDTO> page = geoMarkService.pageList(params);
|
||||||
|
|
||||||
|
return new Result<PageData<GeoMarkDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
@Operation(summary = "信息")
|
||||||
|
@RequiresPermissions("bus:mark:info")
|
||||||
|
public Result<GeoMarkDTO> get(@PathVariable("id") Long id) {
|
||||||
|
GeoMarkDTO data = geoMarkService.getDetail(id);
|
||||||
|
|
||||||
|
return new Result<GeoMarkDTO>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "保存")
|
||||||
|
@LogOperation("保存")
|
||||||
|
@RequiresPermissions("bus:mark:save")
|
||||||
|
public Result<Object> save(@RequestBody GeoMarkDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
|
geoMarkService.save(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
@Operation(summary = "删除")
|
||||||
|
@LogOperation("删除")
|
||||||
|
@RequiresPermissions("bus:mark:delete")
|
||||||
|
public Result<Object> delete(@RequestBody Long[] ids) {
|
||||||
|
//效验数据
|
||||||
|
AssertUtils.isArrayEmpty(ids, "id");
|
||||||
|
geoMarkService.delete(ids);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.multictrl.modules.business.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
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.modules.business.dto.GeoPhotoDTO;
|
||||||
|
import com.multictrl.modules.business.service.GeoPhotoService;
|
||||||
|
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;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地理照片模型
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-05
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/geophoto")
|
||||||
|
@Tag(name = "地理模型")
|
||||||
|
@ApiOrder(13)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GeoPhotoController {
|
||||||
|
private final GeoPhotoService geoPhotoService;
|
||||||
|
|
||||||
|
@GetMapping("page")
|
||||||
|
@Operation(summary = "分页")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "type", description = "类型 1正射 2倾斜")
|
||||||
|
})
|
||||||
|
@RequiresPermissions("bus:geophoto:page")
|
||||||
|
public Result<PageData<GeoPhotoDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<GeoPhotoDTO> page = geoPhotoService.pageList(params);
|
||||||
|
|
||||||
|
return new Result<PageData<GeoPhotoDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "上传地理资源")
|
||||||
|
@LogOperation("上传地理资源")
|
||||||
|
@RequiresPermissions("bus:geophoto:upload")
|
||||||
|
public Result<Object> upload(@Parameter(description = "模型类型{1:正射,2:倾斜}")
|
||||||
|
@RequestParam("type") String type,
|
||||||
|
@RequestParam("file") MultipartFile file) {
|
||||||
|
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||||
|
Map<String, String> typeSuffixMap = Map.of("1", "tif", "2", "zip");
|
||||||
|
String expectedSuffix = typeSuffixMap.get(type);
|
||||||
|
if (expectedSuffix == null) {
|
||||||
|
return new Result<>().error(ErrorCode.PARAMS_ERROR, "模型类型");
|
||||||
|
}
|
||||||
|
if (!expectedSuffix.equals(suffix)) {
|
||||||
|
return new Result<>().error(ErrorCode.FILE_FORMAT_ERROR, expectedSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
geoPhotoService.upload(type, file);
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "获取地理资源")
|
||||||
|
public Result<List<GeoPhotoDTO>> get(@Parameter(description = "类型{1:正射,2:倾斜}")
|
||||||
|
@RequestParam("type") String type) {
|
||||||
|
List<GeoPhotoDTO> layerInfo = geoPhotoService.getGeoList(type);
|
||||||
|
|
||||||
|
return new Result<List<GeoPhotoDTO>>().ok(layerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
@Operation(summary = "删除")
|
||||||
|
@LogOperation("删除")
|
||||||
|
@RequiresPermissions("bus:geophoto:delete")
|
||||||
|
public Result<Object> delete(@RequestParam Long id) {
|
||||||
|
geoPhotoService.remove(id);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getDiskModel")
|
||||||
|
@Operation(summary = "获取磁盘模型")
|
||||||
|
@LogOperation("获取磁盘模型")
|
||||||
|
@RequiresPermissions("bus:geophoto:getDiskModel")
|
||||||
|
public Result<List<GeoPhotoDTO>> getDiskModel() {
|
||||||
|
List<GeoPhotoDTO> diskData = geoPhotoService.getDiskMap();
|
||||||
|
|
||||||
|
return new Result<List<GeoPhotoDTO>>().ok(diskData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/uploadModelDisk")
|
||||||
|
@Operation(summary = "磁盘上传模型")
|
||||||
|
@LogOperation("磁盘上传模型")
|
||||||
|
@RequiresPermissions("bus:geophoto:uploadModelDisk")
|
||||||
|
public Result<List<GeoPhotoDTO>> uploadModelDisk(@RequestBody List<String> names) {
|
||||||
|
AssertUtils.isListEmpty(names, "模型名称");
|
||||||
|
geoPhotoService.uploadModelDisk(names);
|
||||||
|
|
||||||
|
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,99 @@
|
||||||
|
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.MaintenanceDTO;
|
||||||
|
import com.multictrl.modules.business.service.MaintenanceService;
|
||||||
|
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-06-12
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/maintenance")
|
||||||
|
@Tag(name = "设备维护")
|
||||||
|
@ApiOrder(23)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MaintenanceController {
|
||||||
|
private final MaintenanceService maintenanceService;
|
||||||
|
|
||||||
|
@GetMapping("page")
|
||||||
|
@Operation(summary = "分页")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "dockSn", description = "机库SN"),
|
||||||
|
@Parameter(name = "date", description = "维保日期 yyyy-MM-dd")
|
||||||
|
})
|
||||||
|
@RequiresPermissions("bus:maintenance:page")
|
||||||
|
public Result<PageData<MaintenanceDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<MaintenanceDTO> page = maintenanceService.pageList(params);
|
||||||
|
|
||||||
|
return new Result<PageData<MaintenanceDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
@Operation(summary = "信息")
|
||||||
|
@RequiresPermissions("bus:maintenance:info")
|
||||||
|
public Result<MaintenanceDTO> get(@PathVariable("id") Long id) {
|
||||||
|
MaintenanceDTO data = maintenanceService.getInfo(id);
|
||||||
|
|
||||||
|
return new Result<MaintenanceDTO>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "保存")
|
||||||
|
@LogOperation("保存")
|
||||||
|
@RequiresPermissions("bus:maintenance:save")
|
||||||
|
public Result<Object> save(@RequestBody MaintenanceDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, AddGroup.class);
|
||||||
|
maintenanceService.addMaintenance(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@Operation(summary = "修改")
|
||||||
|
@LogOperation("修改")
|
||||||
|
@RequiresPermissions("bus:maintenance:update")
|
||||||
|
public Result<Object> update(@RequestBody MaintenanceDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, UpdateGroup.class);
|
||||||
|
maintenanceService.updateMaintenance(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
@Operation(summary = "删除")
|
||||||
|
@LogOperation("删除")
|
||||||
|
@RequiresPermissions("bus:maintenance:delete")
|
||||||
|
public Result<Object> delete(@RequestBody Long[] ids) {
|
||||||
|
//效验数据
|
||||||
|
AssertUtils.isArrayEmpty(ids, "id");
|
||||||
|
maintenanceService.deleteMaintenance(ids);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
package com.multictrl.modules.business.controller;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import com.multictrl.common.annotation.ApiOrder;
|
||||||
|
import com.multictrl.common.annotation.DataFilter;
|
||||||
|
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.MediaFileDTO;
|
||||||
|
import com.multictrl.modules.business.service.MediaFileService;
|
||||||
|
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.core.io.Resource;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒体图库
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/5/27
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/media")
|
||||||
|
@Tag(name = "媒体图库")
|
||||||
|
@ApiOrder(8)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MediaFileController {
|
||||||
|
private final MediaFileService mediaFileService;
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "分页")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "dockSn", description = "机库sn"),
|
||||||
|
@Parameter(name = "routeId", description = "航线id"),
|
||||||
|
@Parameter(name = "startTime", description = "开始时间"),
|
||||||
|
@Parameter(name = "endTime", description = "结束时间")
|
||||||
|
})
|
||||||
|
@DataFilter(tableAlias = "t")
|
||||||
|
@RequiresPermissions("bus:media:page")
|
||||||
|
public Result<PageData<JSONObject>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<JSONObject> page = mediaFileService.pageList(params);
|
||||||
|
|
||||||
|
return new Result<PageData<JSONObject>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getAlbum")
|
||||||
|
@Operation(summary = "获取相簿")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "dockSn", description = "机库sn")
|
||||||
|
})
|
||||||
|
@DataFilter(tableAlias = "t")
|
||||||
|
@RequiresPermissions("bus:media:album")
|
||||||
|
public Result<PageData<MediaFileDTO>> getAlbum(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<MediaFileDTO> page = mediaFileService.getAlbum(params);
|
||||||
|
|
||||||
|
return new Result<PageData<MediaFileDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
@Operation(summary = "删除")
|
||||||
|
@LogOperation("删除媒体文件")
|
||||||
|
@RequiresPermissions("bus:media:delete")
|
||||||
|
public Result<Object> delete(@RequestBody Long[] ids) {
|
||||||
|
//效验数据
|
||||||
|
AssertUtils.isArrayEmpty(ids, "id");
|
||||||
|
mediaFileService.deleteMediaFile(ids);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "下载媒体文件")
|
||||||
|
@LogOperation("下载媒体文件")
|
||||||
|
@GetMapping("/downloadMedia")
|
||||||
|
@RequiresPermissions("bus:media:download")
|
||||||
|
public ResponseEntity<Resource> downloadMedia(@RequestParam Long id) {
|
||||||
|
|
||||||
|
return mediaFileService.downloadMedia(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "条件下载媒体文件")
|
||||||
|
@LogOperation("条件下载媒体文件")
|
||||||
|
@GetMapping("/conditionDownloadMedia")
|
||||||
|
@RequiresPermissions("bus:media:download")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = "dockSn", description = "机库sn"),
|
||||||
|
@Parameter(name = "routeId", description = "航线标识"),
|
||||||
|
@Parameter(name = "startTime", description = "开始时间"),
|
||||||
|
@Parameter(name = "endTime", description = "结束时间")
|
||||||
|
})
|
||||||
|
@DataFilter(tableAlias = "t")
|
||||||
|
public ResponseEntity<StreamingResponseBody> conditionDownloadMedia(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
|
||||||
|
return mediaFileService.downloadMediaByZip(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "一键获取下载媒体地址")
|
||||||
|
@LogOperation("一键获取下载媒体地址")
|
||||||
|
@GetMapping("/oneClickGetDownloadPath")
|
||||||
|
@RequiresPermissions("bus:media:download")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = "dockSn", description = "机库sn"),
|
||||||
|
@Parameter(name = "routeId", description = "航线标识"),
|
||||||
|
@Parameter(name = "startTime", description = "开始时间"),
|
||||||
|
@Parameter(name = "endTime", description = "结束时间")
|
||||||
|
})
|
||||||
|
@DataFilter(tableAlias = "t")
|
||||||
|
public Result<Map<String, List<String>>> oneClickGetDownloadPath(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
Map<String, List<String>> map = mediaFileService.oneClickGetDownloadPath(params);
|
||||||
|
|
||||||
|
return new Result<Map<String, List<String>>>().ok(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/conditionDeleteMedia")
|
||||||
|
@Operation(summary = "条件删除媒体文件 -- 参数和条件下载一样,形式为json")
|
||||||
|
@LogOperation("条件删除媒体文件")
|
||||||
|
@RequiresPermissions("bus:media:delete")
|
||||||
|
public Result<Object> conditionDeleteMedia(@RequestBody Map<String, Object> params) {
|
||||||
|
mediaFileService.conditionDeleteMedia(params);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "上传喊话器音频文件")
|
||||||
|
@PostMapping("/uploadSpeakerAudio/{dockSn}")
|
||||||
|
public Result<String> uploadSpeakerAudio(@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.uploadSpeakerAudio(dockSn, file));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "上传地图标注文件")
|
||||||
|
@PostMapping("/uploadGeoMark/{dockSn}")
|
||||||
|
public Result<String> uploadGeoMark(@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.uploadGeoMark(dockSn, file));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "上传字典图片")
|
||||||
|
@PostMapping("/uploadDictImage")
|
||||||
|
public Result<String> uploadDictImage(
|
||||||
|
@RequestParam("file") MultipartFile file) {
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
return new Result<String>().error(ErrorCode.UPLOAD_FILE_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Result<String>().ok(minioService.uploadDictImage(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "上传维保图片")
|
||||||
|
@PostMapping("/uploadMaintenanceImage/{dockSn}")
|
||||||
|
public Result<String> uploadMaintenanceImage(@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.uploadMaintenanceImage(dockSn, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getDockBindInfo")
|
||||||
|
@Operation(summary = "机库注册信息")
|
||||||
|
@RequiresPermissions("bus:misc:getDockBindInfo")
|
||||||
|
public Result<Object> getDockBindInfo() {
|
||||||
|
|
||||||
|
return new Result<>().ok(miscService.getDockBindInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
package com.multictrl.modules.business.controller;
|
||||||
|
|
||||||
|
import com.multictrl.common.annotation.ApiOrder;
|
||||||
|
import com.multictrl.common.annotation.DataFilter;
|
||||||
|
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.DockDTO;
|
||||||
|
import com.multictrl.modules.business.dto.MultiTestDTO;
|
||||||
|
import com.multictrl.modules.business.dto.multi.MultiDockDTO;
|
||||||
|
import com.multictrl.modules.business.dto.multi.MultiGroupDTO;
|
||||||
|
import com.multictrl.modules.business.dto.multi.MultiGroupDeviceDTO;
|
||||||
|
import com.multictrl.modules.business.service.MultiService;
|
||||||
|
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/6/8
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/multi")
|
||||||
|
@Tag(name = "机场蛙跳任务")
|
||||||
|
@ApiOrder(21)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MultiController {
|
||||||
|
private final MultiService multiService;
|
||||||
|
|
||||||
|
@GetMapping("page")
|
||||||
|
@Operation(summary = "蛙跳设备分页列表")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "key", description = "机库名称、机库SN码")
|
||||||
|
})
|
||||||
|
@DataFilter
|
||||||
|
@RequiresPermissions("bus:dock:page")
|
||||||
|
public Result<PageData<DockDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<DockDTO> page = multiService.pageList(params);
|
||||||
|
|
||||||
|
return new Result<PageData<DockDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("pageMultiGroup")
|
||||||
|
@Operation(summary = "蛙跳组分页列表")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "name", description = "蛙跳组名")
|
||||||
|
})
|
||||||
|
@DataFilter
|
||||||
|
@RequiresPermissions("bus:dock:pageMultiGroup")
|
||||||
|
public Result<PageData<MultiGroupDTO>> pageMultiGroup(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<MultiGroupDTO> page = multiService.pageMultiGroup(params);
|
||||||
|
|
||||||
|
return new Result<PageData<MultiGroupDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/saveMultiGroup")
|
||||||
|
@Operation(summary = "新增蛙跳组")
|
||||||
|
@LogOperation("新增蛙跳组")
|
||||||
|
@RequiresPermissions("bus:multi:saveMultiGroup")
|
||||||
|
public Result<Object> saveMultiGroup(@RequestBody MultiGroupDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, AddGroup.class);
|
||||||
|
multiService.saveMultiGroup(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/updateMultiGroup")
|
||||||
|
@Operation(summary = "修改蛙跳组")
|
||||||
|
@LogOperation("修改蛙跳组")
|
||||||
|
@RequiresPermissions("bus:multi:updateMultiGroup")
|
||||||
|
public Result<Object> updateMultiGroup(@RequestBody MultiGroupDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, UpdateGroup.class);
|
||||||
|
multiService.updateMultiGroup(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/deleteMultiGroup")
|
||||||
|
@Operation(summary = "删除蛙跳组")
|
||||||
|
@LogOperation("删除蛙跳组")
|
||||||
|
@RequiresPermissions("bus:multi:deleteMultiGroup")
|
||||||
|
public Result<Object> deleteMultiGroup(@RequestBody List<Long> ids) {
|
||||||
|
AssertUtils.isListEmpty(ids, "ids");
|
||||||
|
multiService.deleteMultiGroup(ids);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/saveMultiGroupDevice")
|
||||||
|
@Operation(summary = "蛙跳组绑定设备")
|
||||||
|
@LogOperation("蛙跳组绑定设备")
|
||||||
|
@RequiresPermissions("bus:multi:saveMultiGroupDevice")
|
||||||
|
public Result<Object> saveMultiGroupDevice(@RequestBody MultiGroupDeviceDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, AddGroup.class);
|
||||||
|
multiService.saveMultiGroupDevice(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/deleteMultiGroupDevice")
|
||||||
|
@Operation(summary = "蛙跳组解绑设备")
|
||||||
|
@LogOperation("蛙跳组解绑设备")
|
||||||
|
@RequiresPermissions("bus:multi:deleteMultiGroupDevice")
|
||||||
|
public Result<Object> deleteMultiGroupDevice(@RequestBody List<String> dockSn) {
|
||||||
|
//效验数据
|
||||||
|
AssertUtils.isListEmpty(dockSn, "dockSn");
|
||||||
|
multiService.deleteMultiGroupDevice(dockSn);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getMultiGroupDockList")
|
||||||
|
@Operation(summary = "获取蛙跳组机库列表")
|
||||||
|
public Result<List<DockDTO>> getMultiGroupDockList(@RequestParam Long multiGroupId) {
|
||||||
|
List<DockDTO> list = multiService.getMultiGroupDockList(multiGroupId);
|
||||||
|
return new Result<List<DockDTO>>().ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/multiGroupDockTest")
|
||||||
|
@Operation(summary = "蛙跳组机库测试")
|
||||||
|
public Result<String> multiGroupDockTest(MultiTestDTO dto) {
|
||||||
|
String message = multiService.multiGroupDockTest(dto);
|
||||||
|
|
||||||
|
return new Result<String>().ok(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
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.NoflyZoneDTO;
|
||||||
|
import com.multictrl.modules.business.service.NoflyZoneService;
|
||||||
|
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.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁飞区
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-09
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/noflyzone")
|
||||||
|
@Tag(name = "禁飞区")
|
||||||
|
@ApiOrder(22)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NoflyZoneController {
|
||||||
|
private final NoflyZoneService noflyZoneService;
|
||||||
|
|
||||||
|
@GetMapping("page")
|
||||||
|
@Operation(summary = "分页")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "name", description = "名称")
|
||||||
|
})
|
||||||
|
@RequiresPermissions("bus:noflyzone:page")
|
||||||
|
public Result<PageData<NoflyZoneDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<NoflyZoneDTO> page = noflyZoneService.pageList(params);
|
||||||
|
|
||||||
|
return new Result<PageData<NoflyZoneDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "保存")
|
||||||
|
@LogOperation("保存")
|
||||||
|
@RequiresPermissions("bus:noflyzone:save")
|
||||||
|
public Result<Object> save(@RequestBody NoflyZoneDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, AddGroup.class);
|
||||||
|
noflyZoneService.saveNoFlying(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@Operation(summary = "修改")
|
||||||
|
@LogOperation("修改")
|
||||||
|
@RequiresPermissions("bus:noflyzone:update")
|
||||||
|
public Result<Object> update(@RequestBody NoflyZoneDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, UpdateGroup.class);
|
||||||
|
noflyZoneService.update(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
@Operation(summary = "删除")
|
||||||
|
@LogOperation("删除")
|
||||||
|
@RequiresPermissions("bus:noflyzone:delete")
|
||||||
|
public Result<Object> delete(@RequestBody Long[] ids) {
|
||||||
|
//效验数据
|
||||||
|
AssertUtils.isArrayEmpty(ids, "id");
|
||||||
|
noflyZoneService.delete(ids);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getNoFlying")
|
||||||
|
@Operation(summary = "获取机库禁飞区", description = "返回机库所属组织以及上级组织设置的禁飞区")
|
||||||
|
public Result<List<NoflyZoneDTO>> getNoFlying(@RequestParam String dockSn) {
|
||||||
|
List<NoflyZoneDTO> list = noflyZoneService.getNoFlyZoneByDockSn(dockSn);
|
||||||
|
|
||||||
|
return new Result<List<NoflyZoneDTO>>().ok(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.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> startSpeakerTts(@PathVariable String dockSn, @RequestBody TtsText ttsText) {
|
||||||
|
ValidatorUtils.validateEntity(ttsText);
|
||||||
|
|
||||||
|
return new Result<>().ok(psdkService.startSpeakerTts(dockSn, ttsText));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("播放模板文本")
|
||||||
|
@PostMapping("/startSpeakerTemplate/{dockSn}")
|
||||||
|
@Operation(summary = "播放模板文本")
|
||||||
|
@RequiresPermissions("bus:speaker:startSpeakerTemplate")
|
||||||
|
public Result<Object> startSpeakerTemplate(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "id", description = "模版编号")
|
||||||
|
@RequestParam Long id) {
|
||||||
|
|
||||||
|
return new Result<>().ok(psdkService.startSpeakerTemplate(dockSn, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,240 @@
|
||||||
|
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.remote.control.LightFineTuningSet;
|
||||||
|
import com.multictrl.modules.business.service.DJIBaseService;
|
||||||
|
import com.multictrl.modules.business.service.RemoteService;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 远程控制
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/5/21
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/remote")
|
||||||
|
@Tag(name = "远程控制", description = "远程控制是开启DRC后执行的特定通道命令,提高命令执行速度")
|
||||||
|
@ApiOrder(11)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RemoteController {
|
||||||
|
private final RemoteService remoteService;
|
||||||
|
private final DJIBaseService djiBaseService;
|
||||||
|
|
||||||
|
@LogOperation("夜景模式设置")
|
||||||
|
@PostMapping("/drcCameraNightModeSet/{dockSn}")
|
||||||
|
@Operation(summary = "夜景模式设置")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraNightModeSet")
|
||||||
|
public Result<Object> drcCameraNightModeSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "mode", description = "{0:关闭,1:开启,2:自动(自动模式根据进光量自动开启)}")
|
||||||
|
@RequestParam Integer mode) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraNightModeSet(dockSn, mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("降噪等级设置")
|
||||||
|
@PostMapping("/drcCameraDenoiseLevelSet/{dockSn}")
|
||||||
|
@Operation(summary = "降噪等级设置")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraDenoiseLevelSet")
|
||||||
|
public Result<Object> drcCameraDenoiseLevelSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "level", description = "{2:增强降噪 15fps,3:超强降噪 5fps}(仅手动开启夜景模式后生效)")
|
||||||
|
@RequestParam Integer level) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraDenoiseLevelSet(dockSn, level));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("黑白夜视使能")
|
||||||
|
@PostMapping("/drcCameraNightVisionEnable/{dockSn}")
|
||||||
|
@Operation(summary = "黑白夜视使能")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraNightVisionEnable")
|
||||||
|
public Result<Object> drcCameraNightVisionEnable(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "enable", description = "{false:关闭,true:开启}(仅变焦7x以上生效)")
|
||||||
|
@RequestParam Boolean enable) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraNightVisionEnable(dockSn, enable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("近红外补光使能")
|
||||||
|
@PostMapping("/drcInfraredFillLightEnable/{dockSn}")
|
||||||
|
@Operation(summary = "近红外补光使能")
|
||||||
|
@RequiresPermissions("bus:remote:drcInfraredFillLightEnable")
|
||||||
|
public Result<Object> drcInfraredFillLightEnable(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "enable", description = "{false:关闭,true:开启}(仅变焦7x以上生效)")
|
||||||
|
@RequestParam Boolean enable) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcInfraredFillLightEnable(dockSn, enable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("强制降落(慎用! 急停可取消)")
|
||||||
|
@PostMapping("/drcForceLanding/{dockSn}")
|
||||||
|
@Operation(summary = "强制降落", description = "调用后,无论是否有障碍飞行器都会直接降到地面,用 drone_emergency_stop 命令可取消。降落完成后只能人工拾取飞行器,慎用!")
|
||||||
|
@RequiresPermissions("bus:remote:drcForceLanding")
|
||||||
|
public Result<Object> drcForceLanding(@PathVariable String dockSn) {
|
||||||
|
|
||||||
|
return new Result<>().ok(djiBaseService.executeDrcAndReturnResult(dockSn, "drc_force_landing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("急停")
|
||||||
|
@PostMapping("/droneEmergencyStop/{dockSn}")
|
||||||
|
@Operation(summary = "急停", description = "可取消强制降落")
|
||||||
|
@RequiresPermissions("bus:remote:droneEmergencyStop")
|
||||||
|
public Result<Object> droneEmergencyStop(@PathVariable String dockSn) {
|
||||||
|
|
||||||
|
return new Result<>().ok(djiBaseService.executeDrcAndReturnResult(dockSn, "drone_emergency_stop"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("紧急降落(慎用! 急停可取消)")
|
||||||
|
@PostMapping("/drcEmergencyLanding/{dockSn}")
|
||||||
|
@Operation(summary = "紧急降落", description = "调用后,飞行器会开始降落,在开启避障时可能会因触发避障而中止。用 drone_emergency_stop 命令可取消。降落完成后只能人工拾取飞行器,慎用!")
|
||||||
|
@RequiresPermissions("bus:remote:drcEmergencyLanding")
|
||||||
|
public Result<Object> drcEmergencyLanding(@PathVariable String dockSn) {
|
||||||
|
|
||||||
|
return new Result<>().ok(djiBaseService.executeDrcAndReturnResult(dockSn, "drc_emergency_landing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("探照灯—亮度设置")
|
||||||
|
@PostMapping("/drcLightBrightnessSet/{dockSn}")
|
||||||
|
@Operation(summary = "探照灯—亮度设置")
|
||||||
|
@RequiresPermissions("bus:remote:drcLightBrightnessSet")
|
||||||
|
public Result<Object> drcLightBrightnessSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "brightness", description = "亮度 {max:100,min:1}")
|
||||||
|
@RequestParam Integer brightness) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcInfraredFillLightEnable(dockSn, brightness));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("探照灯—模式设置")
|
||||||
|
@PostMapping("/drcLightModeSet/{dockSn}")
|
||||||
|
@Operation(summary = "探照灯—模式设置")
|
||||||
|
@RequiresPermissions("bus:remote:drcLightModeSet")
|
||||||
|
public Result<Object> drcLightModeSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "mode", description = "模式{ 0: 关闭, 1: 常亮, 2: 爆闪, 3: 快速爆闪, 4: 交替爆闪 }")
|
||||||
|
@RequestParam Integer mode) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcLightModeSet(dockSn, mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("探照灯—左右角度微调")
|
||||||
|
@PostMapping("/drcLightFineTuningSet/{dockSn}")
|
||||||
|
@Operation(summary = "探照灯—左右角度微调")
|
||||||
|
@RequiresPermissions("bus:remote:drcLightFineTuningSet")
|
||||||
|
public Result<Object> drcLightFineTuningSet(@PathVariable String dockSn, @RequestBody LightFineTuningSet lightFineTuningSet) {
|
||||||
|
ValidatorUtils.validateEntity(lightFineTuningSet);
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcLightFineTuningSet(dockSn, lightFineTuningSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("探照灯—云台校准")
|
||||||
|
@PostMapping("/drcLightCalibration/{dockSn}")
|
||||||
|
@Operation(summary = "探照灯—云台校准")
|
||||||
|
@RequiresPermissions("bus:remote:drcLightCalibration")
|
||||||
|
public Result<Object> drcLightCalibration(@PathVariable String dockSn) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcLightCalibration(dockSn));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("DRC初始状态订阅")
|
||||||
|
@PostMapping("/drcInitialState/{dockSn}")
|
||||||
|
@Operation(summary = "DRC初始状态订阅")
|
||||||
|
@RequiresPermissions("bus:remote:drcInitialState")
|
||||||
|
public Result<Object> drcInitialState(@PathVariable String dockSn) {
|
||||||
|
|
||||||
|
return new Result<>().ok(djiBaseService.executeDrcAndReturnResult(dockSn, "drc_initial_state_subscribe"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("夜航灯设置")
|
||||||
|
@PostMapping("/drcNightLightsStateSet/{dockSn}")
|
||||||
|
@Operation(summary = "夜航灯设置")
|
||||||
|
@RequiresPermissions("bus:remote:drcNightLightsStateSet")
|
||||||
|
public Result<Object> drcNightLightsStateSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "state", description = "状态 {0:关闭 1:开启}")
|
||||||
|
@RequestParam Integer state) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcLightModeSet(dockSn, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("飞行控制—隐蔽模式")
|
||||||
|
@PostMapping("/drcStealthStateSet/{dockSn}")
|
||||||
|
@Operation(summary = "飞行控制—隐蔽模式(关闭所有飞机灯光)", description = "设置飞机的隐蔽模式,用于关闭所有飞机灯光")
|
||||||
|
@RequiresPermissions("bus:remote:drcStealthStateSet")
|
||||||
|
public Result<Object> drcStealthStateSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "state", description = "隐蔽模式 {0:关闭 1:开启}")
|
||||||
|
@RequestParam Integer state) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcStealthStateSet(dockSn, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("负载控制—相机光圈")
|
||||||
|
@PostMapping("/drcCameraApertureValueSet/{dockSn}")
|
||||||
|
@Operation(summary = "负载控制—相机光圈")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraApertureValueSet")
|
||||||
|
public Result<Object> drcCameraApertureValueSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "value", description = "光圈值 {0:F_AUTO 50:F_AUTO ...}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraApertureValueSet(dockSn, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("负载控制—相机快门")
|
||||||
|
@PostMapping("/drcCameraShutterSet/{dockSn}")
|
||||||
|
@Operation(summary = "负载控制—相机快门")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraShutterSet")
|
||||||
|
public Result<Object> drcCameraShutterSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "value", description = "快门设置 {0:1/8000s 1:1/6400s ...}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraShutterSet(dockSn, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("负载控制—ISO设置")
|
||||||
|
@PostMapping("/drcCameraIsoSet/{dockSn}")
|
||||||
|
@Operation(summary = "负载控制—ISO设置")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraIsoSet")
|
||||||
|
public Result<Object> drcCameraIsoSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "value", description = "ISO值 {0:ISO_AUTO 1:ISO_50 ...}")
|
||||||
|
@RequestParam Integer value) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraIsoSet(dockSn, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("负载控制—机械快门")
|
||||||
|
@PostMapping("/drcCameraMechanicalShutterSet/{dockSn}")
|
||||||
|
@Operation(summary = "负载控制—机械快门", description = "支持用户手动关闭机械快门,提高设备作业寿命,Matrice 3D广角模式下机械快门开关")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraMechanicalShutterSet")
|
||||||
|
public Result<Object> drcCameraMechanicalShutterSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "state", description = "是否使能机械快门 {0:关闭 1:开启}")
|
||||||
|
@RequestParam Integer state) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraMechanicalShutterSet(dockSn, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("负载控制—镜头去畸变")
|
||||||
|
@PostMapping("/drcCameraDewarpingSet/{dockSn}")
|
||||||
|
@Operation(summary = "负载控制—镜头去畸变", description = "能让用户的广角视角不再有暗角,Matrice 3D广角模式的镜头去畸变")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraDewarpingSet")
|
||||||
|
public Result<Object> drcCameraDewarpingSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "state", description = "是否使能去畸变 {0:关闭 1:开启}")
|
||||||
|
@RequestParam Integer state) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraDewarpingSet(dockSn, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("红外照片格式设置")
|
||||||
|
@PostMapping("/drcCameraPhotoFormatSet/{dockSn}")
|
||||||
|
@Operation(summary = "红外照片格式设置", description = "用于设置红外照片格式,需要直播镜头先切换为红外才可有效使用")
|
||||||
|
@RequiresPermissions("bus:remote:drcCameraPhotoFormatSet")
|
||||||
|
public Result<Object> drcCameraPhotoFormatSet(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "format", description = "照片格式 {7:RJPEG 16:DLT664}")
|
||||||
|
@RequestParam Integer format) {
|
||||||
|
|
||||||
|
return new Result<>().ok(remoteService.drcCameraPhotoFormatSet(dockSn, format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,159 @@
|
||||||
|
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.RouteAiDTO;
|
||||||
|
import com.multictrl.modules.business.dto.RouteDTO;
|
||||||
|
import com.multictrl.modules.business.dto.RouteEstimateTimeDTO;
|
||||||
|
import com.multictrl.modules.business.dto.UploadRouteDTO;
|
||||||
|
import com.multictrl.modules.business.service.RouteService;
|
||||||
|
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.*;
|
||||||
|
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.pageList(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<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/setRouteAi")
|
||||||
|
@Operation(summary = "设置航线AI任务")
|
||||||
|
@LogOperation("设置航线AI任务")
|
||||||
|
@RequiresPermissions("bus:route:setAi")
|
||||||
|
public Result<Object> setRouteAi(@RequestBody RouteAiDTO dto) {
|
||||||
|
routeService.setRouteAi(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/deleteRouteAi")
|
||||||
|
@Operation(summary = "删除航线AI任务")
|
||||||
|
@LogOperation("删除航线AI任务")
|
||||||
|
@RequiresPermissions("bus:route:deleteAi")
|
||||||
|
public Result<Object> deleteRouteAi(@RequestParam Long routeId) {
|
||||||
|
routeService.deleteRouteAi(routeId);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/upload")
|
||||||
|
@Operation(summary = "导入航线")
|
||||||
|
@LogOperation("导入航线")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = "dockSn", description = "机库SN"),
|
||||||
|
@Parameter(name = "accurateImport", description = "是否精准导入"),
|
||||||
|
@Parameter(name = "globalRthHeight", description = "返航高度"),
|
||||||
|
@Parameter(name = "file", description = "航线文件",
|
||||||
|
schema = @Schema(type = "string", format = "binary"))
|
||||||
|
})
|
||||||
|
@RequiresPermissions("bus:route:upload")
|
||||||
|
public Result<Object> upload(@Parameter(hidden = true) UploadRouteDTO dto) {
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
|
routeService.upload(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/getWaypointEstimateTime")
|
||||||
|
@Operation(summary = "航点航线预计飞行时间")
|
||||||
|
public Result<String> getWaypointEstimateTime(@RequestBody RouteEstimateTimeDTO dto) {
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
|
String estimateTime = routeService.getWaypointEstimateTime(dto);
|
||||||
|
|
||||||
|
return new Result<String>().ok(estimateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/updateGlobalRthHeight")
|
||||||
|
@Operation(summary = "修改返航高度")
|
||||||
|
@LogOperation("修改返航高度")
|
||||||
|
public Result<Object> updateGlobalRthHeight(@RequestParam Long routeId,
|
||||||
|
@RequestParam Integer globalRthHeight) {
|
||||||
|
routeService.updateGlobalRthHeight(routeId, globalRthHeight);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
package com.multictrl.modules.business.controller;
|
||||||
|
|
||||||
|
import com.multictrl.common.annotation.ApiOrder;
|
||||||
|
import com.multictrl.common.annotation.LogOperation;
|
||||||
|
import com.multictrl.common.constant.FlightTaskType;
|
||||||
|
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, true, FlightTaskType.ROUTE.getCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,144 @@
|
||||||
|
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.ValidatorUtils;
|
||||||
|
import com.multictrl.modules.business.dto.SpeakerDTO;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 喊话器控制 大疆官方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;
|
||||||
|
|
||||||
|
@GetMapping("page")
|
||||||
|
@Operation(summary = "分页")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
|
||||||
|
@Parameter(name = "type", description = "类型 1:文本 2:音频"),
|
||||||
|
@Parameter(name = "dockSn", description = "机库SN码")
|
||||||
|
})
|
||||||
|
@RequiresPermissions("bus:speaker:page")
|
||||||
|
public Result<PageData<SpeakerDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<SpeakerDTO> page = speakerService.page(params);
|
||||||
|
|
||||||
|
return new Result<PageData<SpeakerDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("添加喊话模版")
|
||||||
|
@PostMapping("/addTemplate")
|
||||||
|
@Operation(summary = "添加喊话模版")
|
||||||
|
@RequiresPermissions("bus:speaker:addTemplate")
|
||||||
|
public Result<Object> addTemplate(@RequestBody SpeakerDTO speaker) {
|
||||||
|
ValidatorUtils.validateEntity(speaker);
|
||||||
|
speakerService.addTemplate(speaker);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("删除模版")
|
||||||
|
@DeleteMapping("/deleteTemplate")
|
||||||
|
@Operation(summary = "删除模版")
|
||||||
|
@RequiresPermissions("bus:speaker:deleteTemplate")
|
||||||
|
public Result<Object> deleteTemplate(@Parameter(name = "id", description = "模版编号")
|
||||||
|
@RequestParam Long id) {
|
||||||
|
speakerService.deleteTemplate(id);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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> drcStartSpeakerTts(@PathVariable String dockSn, @RequestBody TtsText ttsText) {
|
||||||
|
ValidatorUtils.validateEntity(ttsText);
|
||||||
|
|
||||||
|
return new Result<>().ok(speakerService.drcStartSpeakerTts(dockSn, ttsText));
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogOperation("播放模板文本")
|
||||||
|
@PostMapping("/drcStartSpeakerTemplate/{dockSn}")
|
||||||
|
@Operation(summary = "播放模板文本")
|
||||||
|
@RequiresPermissions("bus:speaker:drcStartSpeakerTemplate")
|
||||||
|
public Result<Object> drcStartSpeakerTemplate(@PathVariable String dockSn,
|
||||||
|
@Parameter(name = "id", description = "模版编号")
|
||||||
|
@RequestParam Long id) {
|
||||||
|
|
||||||
|
return new Result<>().ok(speakerService.drcStartSpeakerTemplate(dockSn, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
srsService.saveSrsRecord(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,78 @@
|
||||||
|
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.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.WeatherNoflyDTO;
|
||||||
|
import com.multictrl.modules.business.service.WeatherNoflyService;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 天气阻飞
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-15
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/weathernofly")
|
||||||
|
@Tag(name = "天气阻飞")
|
||||||
|
@ApiOrder(24)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WeatherNoflyController {
|
||||||
|
private final WeatherNoflyService weatherNoflyService;
|
||||||
|
|
||||||
|
@GetMapping("{dockSn}")
|
||||||
|
@Operation(summary = "信息")
|
||||||
|
@RequiresPermissions("bus:weathernofly:info")
|
||||||
|
public Result<WeatherNoflyDTO> get(@PathVariable String dockSn) {
|
||||||
|
WeatherNoflyDTO data = weatherNoflyService.getInfoByDockSn(dockSn);
|
||||||
|
|
||||||
|
return new Result<WeatherNoflyDTO>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "保存")
|
||||||
|
@LogOperation("保存")
|
||||||
|
@RequiresPermissions("bus:weathernofly:save")
|
||||||
|
public Result<Object> save(@RequestBody WeatherNoflyDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, AddGroup.class);
|
||||||
|
weatherNoflyService.addWeatherNofly(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@Operation(summary = "修改")
|
||||||
|
@LogOperation("修改")
|
||||||
|
@RequiresPermissions("bus:weathernofly:update")
|
||||||
|
public Result<Object> update(@RequestBody WeatherNoflyDTO dto) {
|
||||||
|
//效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, UpdateGroup.class);
|
||||||
|
weatherNoflyService.update(dto);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
@Operation(summary = "删除")
|
||||||
|
@LogOperation("删除")
|
||||||
|
@RequiresPermissions("bus:weathernofly:delete")
|
||||||
|
public Result<Object> delete(@RequestBody Long[] ids) {
|
||||||
|
//效验数据
|
||||||
|
AssertUtils.isArrayEmpty(ids, "id");
|
||||||
|
weatherNoflyService.delete(ids);
|
||||||
|
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
package com.multictrl.modules.business.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
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.zhimou.DownloadReport;
|
||||||
|
import com.multictrl.modules.business.dto.zhimou.Osd;
|
||||||
|
import com.multictrl.modules.business.dto.zhimou.ZhiMouAiStart;
|
||||||
|
import com.multictrl.modules.business.service.ZhiMouService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智眸AI接口封装
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026/6/9
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("business/zhiMou")
|
||||||
|
@Tag(name = "智眸AI接口")
|
||||||
|
@ApiOrder(23)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ZhiMouController {
|
||||||
|
private final ZhiMouService zhiMouService;
|
||||||
|
|
||||||
|
@Operation(summary = "获取应用列表")
|
||||||
|
@GetMapping("/getApp")
|
||||||
|
@RequiresPermissions("bus:zhimou:get")
|
||||||
|
public Result<Object> getApp() {
|
||||||
|
JSONObject data = zhiMouService.getApp();
|
||||||
|
return zhiMouService.resultHandle(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "开始AI检测任务")
|
||||||
|
@LogOperation("开始AI检测任务")
|
||||||
|
@PostMapping("/startByWeb")
|
||||||
|
@RequiresPermissions("bus:zhimou:start")
|
||||||
|
public Result<Object> startByWeb(@RequestBody ZhiMouAiStart zhiMouAiStart) {
|
||||||
|
ValidatorUtils.validateEntity(zhiMouAiStart);
|
||||||
|
JSONObject data = zhiMouService.startByWeb(zhiMouAiStart);
|
||||||
|
return zhiMouService.resultHandle(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "停止AI检测任务")
|
||||||
|
@LogOperation("停止AI检测任务")
|
||||||
|
@PostMapping("/stopByWeb")
|
||||||
|
@RequiresPermissions("bus:zhimou:stop")
|
||||||
|
public Result<Object> stopByWeb(@RequestParam String dockSn) {
|
||||||
|
JSONObject data = zhiMouService.stopByWeb(dockSn);
|
||||||
|
return zhiMouService.resultHandle(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取AI检测任务")
|
||||||
|
@GetMapping("/getRunningTaskByWeb")
|
||||||
|
public Result<Object> getRunningTaskByWeb(@RequestParam String dockSn) {
|
||||||
|
JSONObject data = zhiMouService.getRunningTaskByWeb(dockSn);
|
||||||
|
return new Result<>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "配置AI检测任务")
|
||||||
|
@LogOperation("配置AI检测任务")
|
||||||
|
@PostMapping("/configurationByWeb")
|
||||||
|
@RequiresPermissions("bus:zhimou:config")
|
||||||
|
public Result<Object> configurationByWeb(@RequestBody Osd osd) {
|
||||||
|
JSONObject data = zhiMouService.configurationByWeb(osd);
|
||||||
|
return zhiMouService.resultHandle(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取实时检测结果")
|
||||||
|
@GetMapping("/currentTaskMessageRecord")
|
||||||
|
public Result<Object> currentTaskMessageRecord(@RequestParam String dockSn) {
|
||||||
|
JSONObject data = zhiMouService.currentTaskMessageRecord(dockSn);
|
||||||
|
return zhiMouService.resultHandle(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取飞行任务检测结果")
|
||||||
|
@GetMapping("/getTaskMessageRecord")
|
||||||
|
@RequiresPermissions("bus:zhiMou:image")
|
||||||
|
public Result<Object> getTaskMessageRecordList(@RequestParam String taskId) {
|
||||||
|
JSONArray data = zhiMouService.getTaskMessageRecordList(taskId);
|
||||||
|
return new Result<>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "下载AI检测报告")
|
||||||
|
@PostMapping("/downloadReport")
|
||||||
|
@RequiresPermissions("bus:zhiMou:downloadReport")
|
||||||
|
public ResponseEntity<byte[]> getReportByBusinessId(@RequestBody DownloadReport downloadReport) {
|
||||||
|
ValidatorUtils.validateEntity(downloadReport);
|
||||||
|
byte[] reportBytes = zhiMouService.downloadReport(downloadReport);
|
||||||
|
|
||||||
|
String fileName = "AI报告_" + downloadReport.getTaskId() + ".zip";
|
||||||
|
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodedFileName)
|
||||||
|
.body(reportBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "是否有AI服务")
|
||||||
|
@GetMapping("/hasAiServer")
|
||||||
|
@RequiresPermissions("bus:zhiMou:hasAiServer")
|
||||||
|
public Result<Object> hasAiServer() {
|
||||||
|
boolean result = zhiMouService.hasAiService();
|
||||||
|
|
||||||
|
return new Result<>().ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.multictrl.modules.business.dao;
|
||||||
|
|
||||||
|
import com.multictrl.common.dao.BaseDao;
|
||||||
|
import com.multictrl.modules.business.entity.AiWarningEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI识别预警
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-18
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AiWarningDao extends BaseDao<AiWarningEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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,21 @@
|
||||||
|
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> {
|
||||||
|
|
||||||
|
//获取最新的任务
|
||||||
|
FlightTaskEntity getLatestRouteTask(String dockSn);
|
||||||
|
|
||||||
|
//获取当前任务的上一条任务
|
||||||
|
FlightTaskEntity getPreviousTask(String taskId, String dockSn);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.multictrl.modules.business.dao;
|
||||||
|
|
||||||
|
import com.multictrl.common.dao.BaseDao;
|
||||||
|
import com.multictrl.modules.business.entity.GeoMarkEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地图标注
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-05-25
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface GeoMarkDao extends BaseDao<GeoMarkEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.multictrl.modules.business.dao;
|
||||||
|
|
||||||
|
import com.multictrl.common.dao.BaseDao;
|
||||||
|
import com.multictrl.modules.business.entity.GeoPhotoEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地理照片模型
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-05
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface GeoPhotoDao extends BaseDao<GeoPhotoEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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,21 @@
|
||||||
|
package com.multictrl.modules.business.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.multictrl.common.dao.BaseDao;
|
||||||
|
import com.multictrl.modules.business.entity.MaintenanceEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备维护信息
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-12
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface MaintenanceDao extends BaseDao<MaintenanceEntity> {
|
||||||
|
|
||||||
|
//分页列表查询
|
||||||
|
IPage<MaintenanceEntity> pageList(IPage<MaintenanceEntity> page, @Param("ew") QueryWrapper<MaintenanceEntity> wrapper);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.multictrl.modules.business.dao;
|
||||||
|
|
||||||
|
import com.multictrl.common.dao.BaseDao;
|
||||||
|
import com.multictrl.modules.business.entity.MaintenanceImgEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 维保图片
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-12
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface MaintenanceImgDao extends BaseDao<MaintenanceImgEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.multictrl.modules.business.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.multictrl.common.dao.BaseDao;
|
||||||
|
import com.multictrl.modules.business.entity.MediaFileEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒体资源
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-04-29
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface MediaFileDao extends BaseDao<MediaFileEntity> {
|
||||||
|
|
||||||
|
//分页列表
|
||||||
|
IPage<MediaFileEntity> pageList(IPage<MediaFileEntity> page, @Param("ew") QueryWrapper<MediaFileEntity> queryWrapper);
|
||||||
|
|
||||||
|
//获取相簿
|
||||||
|
IPage<MediaFileEntity> getAlbum(IPage<MediaFileEntity> page, @Param("deptId") Long deptId, @Param("dockSn") String dockSn);
|
||||||
|
|
||||||
|
//列表
|
||||||
|
List<MediaFileEntity> list(@Param("ew") QueryWrapper<MediaFileEntity> queryWrapper);
|
||||||
|
}
|
||||||
|
|
@ -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,30 @@
|
||||||
|
package com.multictrl.modules.business.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.multictrl.common.dao.BaseDao;
|
||||||
|
import com.multictrl.modules.business.dto.NoflyZoneDTO;
|
||||||
|
import com.multictrl.modules.business.entity.NoflyZoneEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁飞区
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-09
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface NoflyZoneDao extends BaseDao<NoflyZoneEntity> {
|
||||||
|
|
||||||
|
//分页列表
|
||||||
|
IPage<NoflyZoneDTO> pageList(IPage<NoflyZoneEntity> page, @Param("ew") QueryWrapper<NoflyZoneEntity> ew);
|
||||||
|
|
||||||
|
//新增禁飞区
|
||||||
|
void saveNoFlyZone(NoflyZoneEntity noflyZoneEntity);
|
||||||
|
|
||||||
|
//查询禁飞区
|
||||||
|
List<NoflyZoneDTO> getNoFlyZoneList(@Param("ew") QueryWrapper<NoflyZoneEntity> ew);
|
||||||
|
}
|
||||||
|
|
@ -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.RouteAiEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 航线AI任务
|
||||||
|
*
|
||||||
|
* @author Sdy
|
||||||
|
* @since 1.0.0 2026-06-18
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface RouteAiDao extends BaseDao<RouteAiEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue