From 17835b967b48f2676cce3bbed081c6580602ee1c Mon Sep 17 00:00:00 2001
From: sean.zhou <sean.zhou@dji.com>
Date: Mon, 21 Mar 2022 22:17:00 +0800
Subject: [PATCH] 1.0.0 initial
---
src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java | 32
src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java | 34
src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java | 32
src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java | 30
src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java | 28
src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java | 13
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java | 60
src/main/java/com/dji/sample/media/model/MediaFileDTO.java | 34
src/main/java/com/dji/sample/common/model/PaginationData.java | 27
src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java | 25
src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java | 18
src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java | 25
src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java | 43
src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java | 52
src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java | 37
src/main/java/com/dji/sample/component/GlobalExceptionHandler.java | 33
src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java | 17
src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java | 20
src/main/java/com/dji/sample/map/model/dto/ElementType.java | 43
src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java | 39
src/main/java/com/dji/sample/manage/controller/LoginController.java | 47
src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java | 72
src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java | 38
src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java | 14
src/main/java/com/dji/sample/media/model/CredentialsDTO.java | 40
src/main/java/com/dji/sample/manage/controller/DeviceController.java | 138
src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java | 26
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java | 90
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java | 112
src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java | 41
src/main/java/com/dji/sample/manage/service/ITopologyService.java | 20
src/main/java/com/dji/sample/common/error/CommonErrorEnum.java | 40
src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java | 45
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java | 48
src/main/java/com/dji/sample/manage/controller/LiveStreamController.java | 113
src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java | 20
pom.xml | 148
src/main/java/com/dji/sample/component/ApplicationBootInitial.java | 40
src/main/java/com/dji/sample/common/util/JwtUtil.java | 103
src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java | 34
src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java | 68
src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java | 23
src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java | 66
src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java | 33
src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java | 12
src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java | 69
src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java | 57
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java | 33
src/main/java/com/dji/sample/media/service/IMediaService.java | 36
src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java | 27
src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java | 26
src/main/java/com/dji/sample/manage/handler/StateSplitter.java | 59
src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java | 35
src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java | 28
src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java | 69
src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java | 59
src/main/java/com/dji/sample/media/model/FileUploadDTO.java | 26
src/main/java/com/dji/sample/media/model/FileMetadataDTO.java | 31
src/main/java/com/dji/sample/manage/service/ITSAService.java | 17
src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java | 55
src/main/resources/application.yml | 86
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java | 326 +
src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java | 74
src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java | 104
src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java | 27
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java | 46
src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java | 46
src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java | 38
src/main/java/com/dji/sample/manage/handler/StatusRouter.java | 70
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java | 553 +++
src/main/java/com/dji/sample/CloudApiSampleApplication.java | 17
src/main/java/com/dji/sample/storage/controller/StorageController.java | 51
src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java | 24
src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java | 70
src/main/java/com/dji/sample/common/error/IErrorInfo.java | 22
src/main/java/com/dji/sample/media/model/PositionDTO.java | 16
src/main/java/com/dji/sample/common/model/Pagination.java | 36
src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java | 69
src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java | 25
src/main/java/com/dji/sample/component/GlobalScheduleService.java | 58
src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java | 51
src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java | 44
src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java | 93
.gitignore | 38
src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java | 12
src/main/java/com/dji/sample/media/controller/MediaController.java | 73
src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java | 69
src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java | 29
src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java | 66
src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java | 18
src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java | 27
src/main/java/com/dji/sample/manage/dao/IUserMapper.java | 8
src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java | 141
src/main/java/com/dji/sample/manage/service/IWorkspaceService.java | 23
src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java | 46
src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java | 25
src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java | 33
src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java | 55
src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java | 32
src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java | 25
src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java | 41
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java | 66
src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java | 28
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java | 41
src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java | 59
src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java | 35
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java | 37
src/main/java/com/dji/sample/map/service/IGroupService.java | 24
src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java | 25
src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java | 55
src/main/java/com/dji/sample/storage/service/IStorageService.java | 17
src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java | 14
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java | 172
src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java | 28
src/main/java/com/dji/sample/map/service/IGroupElementService.java | 54
src/main/java/com/dji/sample/manage/service/IUserService.java | 32
src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java | 37
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java | 29
src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java | 18
src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java | 8
src/main/java/com/dji/sample/manage/model/dto/UserDTO.java | 37
src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java | 23
src/main/java/com/dji/sample/map/model/entity/GroupEntity.java | 49
src/main/java/com/dji/sample/media/model/MediaFileEntity.java | 62
src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java | 179
src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java | 25
src/main/java/com/dji/sample/media/service/IFileService.java | 37
src/main/java/com/dji/sample/manage/model/entity/UserEntity.java | 41
src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java | 48
src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java | 12
src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java | 99
src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java | 24
src/main/java/com/dji/sample/manage/controller/TopologyController.java | 40
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java | 20
src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java | 37
src/main/java/com/dji/sample/manage/service/ILiveStreamService.java | 51
src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java | 63
src/main/java/com/dji/sample/map/service/IElementCoordinateService.java | 35
src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java | 24
src/main/java/com/dji/sample/manage/controller/UserController.java | 29
src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java | 94
src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java | 47
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java | 50
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java | 27
src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java | 199 +
src/main/java/com/dji/sample/manage/model/Chan.java | 45
src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java | 22
src/main/java/com/dji/sample/map/dao/IGroupMapper.java | 12
src/main/java/com/dji/sample/common/model/CustomClaim.java | 88
src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java | 63
src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java | 19
src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java | 33
src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java | 184 +
src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java | 18
src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java | 26
src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java | 47
src/main/java/com/dji/sample/component/oss/service/IOssService.java | 27
src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java | 20
src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java | 27
src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java | 30
sql/cloud_sample.sql | 324 +
src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java | 109
src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java | 42
src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java | 41
src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java | 56
src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java | 112
src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java | 12
src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java | 32
src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java | 12
src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java | 39
src/main/java/com/dji/sample/common/error/LiveErrorEnum.java | 78
src/main/java/com/dji/sample/manage/handler/StateRouter.java | 92
src/main/java/com/dji/sample/manage/service/ICameraVideoService.java | 37
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java | 47
src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java | 95
src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java | 55
src/main/java/com/dji/sample/common/model/ResponseResult.java | 69
src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java | 25
src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java | 29
src/main/java/com/dji/sample/map/model/dto/GroupDTO.java | 38
src/main/java/com/dji/sample/media/dao/IFileMapper.java | 12
src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java | 65
src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java | 150
src/main/java/com/dji/sample/media/controller/FileController.java | 36
src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java | 26
src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java | 34
src/main/java/com/dji/sample/media/model/FileExtensionDTO.java | 26
src/main/java/com/dji/sample/manage/controller/WorkspaceController.java | 41
src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java | 34
src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java | 45
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java | 157
src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java | 31
src/main/java/com/dji/sample/manage/service/IDeviceService.java | 135
src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java | 24
src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java | 71
src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java | 29
src/main/java/com/dji/sample/component/AuthInterceptor.java | 60
src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java | 29
src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java | 24
src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java | 33
src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java | 39
src/main/java/com/dji/sample/component/CorsFilter.java | 35
src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java | 35
src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java | 22
src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java | 12
205 files changed, 10,748 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b53c3c1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+/.mvn/
+/mvnw
+/mvnw.cmd
+/logs/
+/src/test/
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5cbe4c6
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>2.4.11</version>
+ <relativePath/> <!-- lookup parent from repository -->
+ </parent>
+
+ <groupId>com.dji</groupId>
+ <artifactId>cloud-api-sample</artifactId>
+ <version>1.0.0</version>
+ <name>cloud-api-sample</name>
+
+ <properties>
+ <java.version>11</java.version>
+ <mybatis-plus.version>3.4.2</mybatis-plus.version>
+ <druid.version>1.2.6</druid.version>
+ <jwt.version>3.12.1</jwt.version>
+ <mqtt.version>5.5.5</mqtt.version>
+ <minio.version>8.3.7</minio.version>
+ <okhttp3.version>4.9.1</okhttp3.version>
+ <aliyun-sdk-sts.version>3.1.0</aliyun-sdk-sts.version>
+ <aliyun-oss.version>3.12.0</aliyun-oss.version>
+ <javax-activation.version>1.1.1</javax-activation.version>
+ <glassfish-jaxb.version>2.3.3</glassfish-jaxb.version>
+ <log4j2.version>2.15.0</log4j2.version>
+ <javax-jaxb.version>2.3.0</javax-jaxb.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-boot-starter</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>druid-spring-boot-starter</artifactId>
+ <version>${druid.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.auth0</groupId>
+ <artifactId>java-jwt</artifactId>
+ <version>${jwt.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.integration</groupId>
+ <artifactId>spring-integration-mqtt</artifactId>
+ <version>${mqtt.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-websocket</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains</groupId>
+ <artifactId>annotations</artifactId>
+ <version>RELEASE</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.minio</groupId>
+ <artifactId>minio</artifactId>
+ <version>${minio.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ <version>${okhttp3.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.aliyun</groupId>
+ <artifactId>aliyun-java-sdk-sts</artifactId>
+ <version>${aliyun-sdk-sts.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.aliyun.oss</groupId>
+ <artifactId>aliyun-sdk-oss</artifactId>
+ <version>${aliyun-oss.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.xml.bind</groupId>
+ <artifactId>jaxb-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.activation</groupId>
+ <artifactId>activation</artifactId>
+ <version>${javax-activation.version}</version>
+ </dependency>
+ <!-- no more than 2.3.3-->
+ <dependency>
+ <groupId>org.glassfish.jaxb</groupId>
+ <artifactId>jaxb-runtime</artifactId>
+ <version>${glassfish-jaxb.version}</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/sql/cloud_sample.sql b/sql/cloud_sample.sql
new file mode 100644
index 0000000..4cfe7fd
--- /dev/null
+++ b/sql/cloud_sample.sql
@@ -0,0 +1,324 @@
+CREATE DATABASE IF NOT EXISTS `cloud_sample` /*!40100 DEFAULT CHARACTER SET utf8 */;
+USE `cloud_sample`;
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+SET NAMES utf8mb4;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE='NO_AUTO_VALUE_ON_ZERO', SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+
+# manage_camera_video
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `manage_camera_video`;
+
+CREATE TABLE `manage_camera_video` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `camera_id` int NOT NULL,
+ `video_index` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `video_type` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+
+
+# manage_capacity_camera
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `manage_capacity_camera`;
+
+CREATE TABLE `manage_capacity_camera` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined',
+ `description` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ `camera_index` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `coexist_video_number_max` int NOT NULL,
+ `available_video_number` int NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+
+
+# manage_device
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `manage_device`;
+
+CREATE TABLE `manage_device` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined',
+ `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `device_type` smallint NOT NULL,
+ `sub_type` smallint NOT NULL,
+ `domain` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `version` smallint NOT NULL,
+ `device_index` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ `child_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ `create_time` bigint NOT NULL,
+ `update_time` bigint NOT NULL,
+ `device_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ `url_normal` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ `url_select` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `product_sn_UNIQUE` (`device_sn`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+
+
+# manage_device_dictionary
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `manage_device_dictionary`;
+
+CREATE TABLE `manage_device_dictionary` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `domain` int NOT NULL,
+ `device_type` int NOT NULL,
+ `sub_type` int NOT NULL,
+ `device_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `device_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+LOCK TABLES `manage_device_dictionary` WRITE;
+/*!40000 ALTER TABLE `manage_device_dictionary` DISABLE KEYS */;
+
+INSERT INTO `manage_device_dictionary` (`id`, `domain`, `device_type`, `sub_type`, `device_name`, `device_desc`)
+VALUES
+ (1,0,60,0,'Matrice 300 RTK',NULL),
+ (16,2,56,0,'DJI Smart Controller','Remote control for M300'),
+ (17,1,20,0,'Z30',NULL),
+ (18,1,26,0,'XT2',NULL),
+ (19,1,39,0,'FPV',NULL),
+ (20,1,41,0,'XTS',NULL),
+ (21,1,42,0,'H20',NULL),
+ (22,1,43,0,'H20T',NULL),
+ (24,1,90742,0,'L1',NULL),
+ (27,1,50,0,'P1 24mm lens',NULL),
+ (28,1,50,1,'P1 35mm lens',NULL),
+ (29,1,50,2,'P1 50mm lens',NULL),
+ (30,0,67,0,'Matrice 30',NULL),
+ (31,0,67,1,'Matrice 30T',NULL),
+ (32,2,119,0,'DJI RC Plus','Remote control for M30'),
+ (33,1,52,0,'M30',NULL),
+ (34,1,53,1,'M30T',NULL);
+
+/*!40000 ALTER TABLE `manage_device_dictionary` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# manage_device_payload
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `manage_device_payload`;
+
+CREATE TABLE `manage_device_payload` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `payload_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `payload_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined',
+ `payload_type` smallint NOT NULL,
+ `sub_type` smallint NOT NULL,
+ `version` smallint DEFAULT NULL,
+ `payload_index` smallint NOT NULL,
+ `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `payload_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ `create_time` bigint NOT NULL,
+ `update_time` bigint NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `payload_sn_UNIQUE` (`payload_sn`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+
+
+# manage_user
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `manage_user`;
+
+CREATE TABLE `manage_user` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `user_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `workspace_id` int NOT NULL,
+ `user_type` smallint NOT NULL,
+ `mqtt_username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `mqtt_password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `create_time` bigint NOT NULL,
+ `update_time` bigint NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id_UNIQUE` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+LOCK TABLES `manage_user` WRITE;
+/*!40000 ALTER TABLE `manage_user` DISABLE KEYS */;
+
+INSERT INTO `manage_user` (`id`, `user_id`, `username`, `password`, `workspace_id`, `user_type`, `mqtt_username`, `mqtt_password`, `create_time`, `update_time`)
+VALUES
+ (1,'a1559e7c-8dd8-4780-b952-100cc4797da2','adminPC','adminPC',1,1,'admin','admin',1634898410751,1634898410751),
+ (2,'be7c6c3d-afe9-4be4-b9eb-c55066c0914e','pilot','pilot123',1,2,'pilot','pilot123',1634898410751,1634898410751);
+
+/*!40000 ALTER TABLE `manage_user` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# manage_workspace
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `manage_workspace`;
+
+CREATE TABLE `manage_workspace` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `workspace_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `workspace_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `platform_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `create_time` bigint NOT NULL,
+ `update_time` bigint NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `workspace_id_UNIQUE` (`workspace_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+LOCK TABLES `manage_workspace` WRITE;
+/*!40000 ALTER TABLE `manage_workspace` DISABLE KEYS */;
+
+INSERT INTO `manage_workspace` (`id`, `workspace_id`, `workspace_name`, `workspace_desc`, `platform_name`, `create_time`, `update_time`)
+VALUES
+ (1,'e3dea0f5-37f2-4d79-ae58-490af3228069','Test Group One','Cloud Sample Test Platform','Cloud Api Platform',1634898410751,1634898410751);
+
+/*!40000 ALTER TABLE `manage_workspace` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# map_element_coordinate
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `map_element_coordinate`;
+
+CREATE TABLE `map_element_coordinate` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `element_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `longitude` decimal(18,14) NOT NULL,
+ `latitude` decimal(17,14) NOT NULL,
+ `altitude` decimal(17,14) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+
+
+# map_group
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `map_group`;
+
+CREATE TABLE `map_group` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `group_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `group_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `group_type` int NOT NULL,
+ `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `is_distributed` tinyint(1) NOT NULL DEFAULT '1',
+ `is_lock` tinyint(1) NOT NULL DEFAULT '0',
+ `create_time` bigint NOT NULL,
+ `update_time` bigint NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `group_id_UNIQUE` (`group_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;
+
+LOCK TABLES `map_group` WRITE;
+/*!40000 ALTER TABLE `map_group` DISABLE KEYS */;
+
+INSERT INTO `map_group` (`id`, `group_id`, `group_name`, `group_type`, `workspace_id`, `is_distributed`, `is_lock`, `create_time`, `update_time`)
+VALUES
+ (1,'e3dea0f5-37f2-4d79-ae58-490af3228060','Pilot Share Layer',2,'e3dea0f5-37f2-4d79-ae58-490af3228069',1,0,1638330077356,1638330077356),
+ (2,'e3dea0f5-37f2-4d79-ae58-490af3228011','Default Layer',1,'e3dea0f5-37f2-4d79-ae58-490af3228069',1,0,1638330077356,1638330077356);
+
+/*!40000 ALTER TABLE `map_group` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# map_group_element
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `map_group_element`;
+
+CREATE TABLE `map_group_element` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `element_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `element_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `display` smallint NOT NULL DEFAULT '1',
+ `group_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `element_type` smallint NOT NULL,
+ `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `color` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `clamp_to_ground` tinyint(1) NOT NULL DEFAULT '0',
+ `create_time` bigint NOT NULL,
+ `update_time` bigint NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `element_id_UNIQUE` (`element_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;
+
+
+
+# media_file
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `media_file`;
+
+CREATE TABLE `media_file` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `file_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `file_path` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `fingerprint` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `tinny_fingerprint` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `object_key` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `sub_file_type` int NOT NULL,
+ `is_original` tinyint(1) NOT NULL,
+ `drone` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined',
+ `payload` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined',
+ `create_time` bigint NOT NULL,
+ `update_time` bigint NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `fingerprint_UNIQUE` (`fingerprint`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+
+
+# wayline_file
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `wayline_file`;
+
+CREATE TABLE `wayline_file` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `wayline_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `drone_model_key` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `payload_model_keys` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+ `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `favorited` tinyint(1) NOT NULL DEFAULT '0',
+ `template_types` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `object_key` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
+ `user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+ `create_time` bigint NOT NULL,
+ `update_time` bigint NOT NULL COMMENT 'required, can not modify.',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `wayline_id_UNIQUE` (`wayline_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+
+
+
+
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+
diff --git a/src/main/java/com/dji/sample/CloudApiSampleApplication.java b/src/main/java/com/dji/sample/CloudApiSampleApplication.java
new file mode 100644
index 0000000..14cca32
--- /dev/null
+++ b/src/main/java/com/dji/sample/CloudApiSampleApplication.java
@@ -0,0 +1,17 @@
+package com.dji.sample;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@MapperScan("com.dji.sample.*.dao")
+@SpringBootApplication
+@EnableScheduling
+public class CloudApiSampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CloudApiSampleApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/common/error/CommonErrorEnum.java b/src/main/java/com/dji/sample/common/error/CommonErrorEnum.java
new file mode 100644
index 0000000..1dab9d1
--- /dev/null
+++ b/src/main/java/com/dji/sample/common/error/CommonErrorEnum.java
@@ -0,0 +1,40 @@
+package com.dji.sample.common.error;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/25
+ */
+public enum CommonErrorEnum implements IErrorInfo {
+
+ SYSTEM_ERROR(600500, "system error"),
+
+ SECRET_INVALID(600100, "secret invalid"),
+
+ NO_TOKEN(600101, "accss_token is null"),
+
+ TOKEN_EXPIRED(600102, "token is expired"),
+
+ TOKEN_INVALID(600103, "token invalid"),
+
+ SIGN_INVALID(600104, "sign invalid");
+
+ private String msg;
+
+ private int code;
+
+ CommonErrorEnum(int code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ @Override
+ public String getErrorMsg() {
+ return this.msg;
+ }
+
+ @Override
+ public Integer getErrorCode() {
+ return this.code;
+ }
+}
diff --git a/src/main/java/com/dji/sample/common/error/IErrorInfo.java b/src/main/java/com/dji/sample/common/error/IErrorInfo.java
new file mode 100644
index 0000000..3332dd3
--- /dev/null
+++ b/src/main/java/com/dji/sample/common/error/IErrorInfo.java
@@ -0,0 +1,22 @@
+package com.dji.sample.common.error;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/25
+ */
+public interface IErrorInfo {
+
+ /**
+ * Get error message.
+ * @return error message
+ */
+ String getErrorMsg();
+
+ /**
+ * Get error code.
+ * @return error code
+ */
+ Integer getErrorCode();
+
+}
diff --git a/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java b/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java
new file mode 100644
index 0000000..0a65eed
--- /dev/null
+++ b/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java
@@ -0,0 +1,78 @@
+package com.dji.sample.common.error;
+
+/**
+ * Live streaming related error codes. When on-demand via mqtt,
+ * it can be matched with the error code information replied by the pilot.
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/25
+ */
+public enum LiveErrorEnum implements IErrorInfo {
+
+ NO_AIRCRAFT(613001, "No aircraft."),
+
+ NO_CAMERA(613002, "No camera."),
+
+ LIVE_STREAM_ALREADY_STARTED(613003, "The camera has started live streaming."),
+
+ FUNCTION_NOT_SUPPORT(613004, "The function is not supported."),
+
+ STRATEGY_NOT_SUPPORT(613005, "The strategy is not supported."),
+
+ NOT_IN_CAMERA_INTERFACE(613006, "The current app is not in the camera interface."),
+
+ NO_FLIGHT_CONTROL(613007, "The remote control has no flight control rights and cannot respond to control commands"),
+
+ NO_STREAM_DATA(613008, "The current app has no stream data."),
+
+ TOO_FREQUENT(613009, "The operation is too frequent."),
+
+ ENABLE_FAILED(613010, "Please check whether the live stream service is normal."),
+
+ NO_LIVE_STREAM(613011, "There are no live stream currently."),
+
+ SWITCH_NOT_SUPPORT(613012, "There is already another camera in the live stream. It's not support to switch the stream directly."),
+
+ URL_TYPE_NOT_SUPPORTED(613013, "This url type is not supported."),
+
+ ERROR_PARAMETERS(613014, "The live stream parameters are abnormal or incomplete."),
+
+ NO_REPLY(613098, "No live reply received."),
+
+ UNKNOWN(613099, "UNKNOWN");
+
+
+ private String msg;
+
+ private int code;
+
+ LiveErrorEnum(int code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ @Override
+ public String getErrorMsg() {
+ return this.msg;
+ }
+
+ @Override
+ public Integer getErrorCode() {
+ return this.code;
+ }
+
+ /**
+ * Get the corresponding enumeration object based on the error code.
+ * @param code error code
+ * @return enumeration object
+ */
+ public static LiveErrorEnum find(int code) {
+
+ for (LiveErrorEnum errorEnum : LiveErrorEnum.class.getEnumConstants()) {
+ if (errorEnum.code == code) {
+ return errorEnum;
+ }
+ }
+ return UNKNOWN;
+ }
+}
diff --git a/src/main/java/com/dji/sample/common/model/CustomClaim.java b/src/main/java/com/dji/sample/common/model/CustomClaim.java
new file mode 100644
index 0000000..85c16a9
--- /dev/null
+++ b/src/main/java/com/dji/sample/common/model/CustomClaim.java
@@ -0,0 +1,88 @@
+package com.dji.sample.common.model;
+
+import com.auth0.jwt.interfaces.Claim;
+import com.fasterxml.jackson.annotation.JsonAlias;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A custom claim for storing custom information in the token.
+ * @author sean.zhou
+ * @date 2021/11/16
+ * @version 0.1
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+@Slf4j
+public class CustomClaim {
+
+ /**
+ * The id of the account.
+ */
+ private String id;
+
+ private String username;
+
+ @JsonAlias("user_type")
+ private Integer userType;
+
+ @JsonAlias("workspace_id")
+ private String workspaceId;
+
+ /**
+ * Convert the custom claim data type to the Map type.
+ * @return map
+ */
+ public ConcurrentHashMap<String, String> convertToMap() {
+ ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(4);
+ try {
+ Field[] declaredFields = this.getClass().getDeclaredFields();
+ for (Field field : declaredFields) {
+ JsonAlias annotation = field.getAnnotation(JsonAlias.class);
+ field.setAccessible(true);
+ // The value of key is named underscore.
+ map.put(annotation != null ? annotation.value()[0] : field.getName(),
+ field.get(this).toString());
+ }
+ } catch (IllegalAccessException e) {
+ log.info("CustomClaim converts failed. {}", this.toString());
+ e.printStackTrace();
+ }
+ return map;
+ }
+
+ /**
+ * Convert the data in Map into a custom claim object.
+ * @param claimMap
+ */
+ public CustomClaim (Map<String, Claim> claimMap) {
+ Field[] declaredFields = this.getClass().getDeclaredFields();
+ for (Field field : declaredFields) {
+ field.setAccessible(true);
+ JsonAlias annotation = field.getAnnotation(JsonAlias.class);
+
+ Claim value = claimMap.get(annotation == null ? field.getName() : annotation.value()[0]);
+ try {
+ Class<?> type = field.getType();
+ if (Integer.class.equals(type)) {
+ field.set(this, Integer.valueOf(value.asString()));
+ continue;
+ }
+ if (String.class.equals(type)) {
+ field.set(this, value.asString());
+ continue;
+ }
+ } catch (IllegalAccessException e) {
+ log.info("Claim parses failed. {}", claimMap.toString());
+ e.printStackTrace();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/common/model/Pagination.java b/src/main/java/com/dji/sample/common/model/Pagination.java
new file mode 100644
index 0000000..74d1e8a
--- /dev/null
+++ b/src/main/java/com/dji/sample/common/model/Pagination.java
@@ -0,0 +1,36 @@
+package com.dji.sample.common.model;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.Data;
+
+/**
+ * Used for paging display in the wayline. These field names cannot be changed.
+ * Because they need to be the same as the pilot.
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@Data
+public class Pagination {
+
+ /**
+ * The current page number.
+ */
+ private long page;
+
+ /**
+ * The amount of data displayed per page.
+ */
+ private long pageSize;
+
+ /**
+ * The total amount of all data.
+ */
+ private long total;
+
+ public Pagination(Page page) {
+ this.page = page.getCurrent();
+ this.pageSize = page.getSize();
+ this.total = page.getTotal();
+ }
+}
diff --git a/src/main/java/com/dji/sample/common/model/PaginationData.java b/src/main/java/com/dji/sample/common/model/PaginationData.java
new file mode 100644
index 0000000..95e2442
--- /dev/null
+++ b/src/main/java/com/dji/sample/common/model/PaginationData.java
@@ -0,0 +1,27 @@
+package com.dji.sample.common.model;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * The format of the data response when a paginated display is required.
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@Data
+public class PaginationData<T> {
+
+ /**
+ * The collection in which the data list is stored.
+ */
+ private List<T> list;
+
+ private Pagination pagination;
+
+ public PaginationData(List<T> list, Pagination pagination) {
+ this.list = list;
+ this.pagination = pagination;
+ }
+}
diff --git a/src/main/java/com/dji/sample/common/model/ResponseResult.java b/src/main/java/com/dji/sample/common/model/ResponseResult.java
new file mode 100644
index 0000000..8ad8e1b
--- /dev/null
+++ b/src/main/java/com/dji/sample/common/model/ResponseResult.java
@@ -0,0 +1,69 @@
+package com.dji.sample.common.model;
+
+import com.dji.sample.common.error.IErrorInfo;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.http.HttpStatus;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@JsonInclude
+public class ResponseResult<T> {
+
+ public static final int CODE_SUCCESS = 0;
+ public static final String MESSAGE_SUCCESS = "success";
+
+ private int code;
+
+ private String message;
+
+ private T data;
+
+ public static <T> ResponseResult<T> success(T data) {
+ return ResponseResult.<T>builder()
+ .code(CODE_SUCCESS)
+ .message(MESSAGE_SUCCESS)
+ .data(data)
+ .build();
+ }
+
+ public static ResponseResult success() {
+ return ResponseResult.builder()
+ .code(0)
+ .message(MESSAGE_SUCCESS)
+ .build();
+ }
+
+ public static ResponseResult error() {
+ return ResponseResult.builder()
+ .code(HttpStatus.INTERNAL_SERVER_ERROR.value())
+ .message(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
+ .build();
+ }
+
+ public static ResponseResult error(String message) {
+ return ResponseResult.builder()
+ .code(HttpStatus.INTERNAL_SERVER_ERROR.value())
+ .message(message)
+ .build();
+ }
+
+ public static ResponseResult error(int code, String message) {
+ return ResponseResult.builder()
+ .code(code)
+ .message(message)
+ .build();
+ }
+
+ public static ResponseResult error(IErrorInfo errorInfo) {
+ return ResponseResult.builder()
+ .code(errorInfo.getErrorCode())
+ .message(errorInfo.getErrorMsg())
+ .build();
+ }
+}
diff --git a/src/main/java/com/dji/sample/common/util/JwtUtil.java b/src/main/java/com/dji/sample/common/util/JwtUtil.java
new file mode 100644
index 0000000..9c2cb9a
--- /dev/null
+++ b/src/main/java/com/dji/sample/common/util/JwtUtil.java
@@ -0,0 +1,103 @@
+package com.dji.sample.common.util;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTCreator;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.TokenExpiredException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.dji.sample.common.model.CustomClaim;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.Optional;
+
+@Slf4j
+@Component
+public class JwtUtil {
+
+ private static String issuer;
+
+ private static String subject;
+
+ private static long age;
+
+ private static String secret;
+
+ private static Algorithm algorithm;
+
+ @Value("${jwt.issuer: DJI}")
+ private void setIssuer(String issuer) {
+ JwtUtil.issuer = issuer;
+ }
+
+ @Value("${jwt.subject: CloudApiSample}")
+ private void setSubject(String subject) {
+ JwtUtil.subject = subject;
+ }
+
+ @Value("${jwt.age: 86400}")
+ private void setAge(long age) {
+ JwtUtil.age = age * 1000;
+ }
+
+ @Value("${jwt.secret: CloudApiSample}")
+ private void setSecret(String secret) {
+ JwtUtil.secret = secret;
+ setAlgorithm();
+ }
+
+ private void setAlgorithm() {
+ JwtUtil.algorithm = Algorithm.HMAC256(secret);
+ }
+
+ /**
+ * Create a token based on custom information.
+ * @param claims custom information
+ * @return token
+ */
+ public static String createToken(Map<String, String> claims) {
+ Date now = new Date();
+ JWTCreator.Builder builder = JWT.create();
+ // Add custom information to the token's payload segment.
+ claims.forEach(builder::withClaim);
+ String token = builder.withIssuer(issuer)
+ .withSubject(subject)
+ .withIssuedAt(now)
+ .withExpiresAt(new Date(now.getTime() + age))
+ .withNotBefore(now)
+ .sign(algorithm);
+ log.debug("token created. " + token);
+ return token;
+ }
+
+ /**
+ * Verify that the token is valid.
+ * @param token
+ * @return
+ * @throws TokenExpiredException
+ */
+ public static DecodedJWT verifyToken(String token) {
+ try {
+ JWTVerifier verifier = JWT.require(algorithm).build();
+ return verifier.verify(token);
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Parses the custom information in the token into a CustomClaim object.
+ * @param token
+ * @return custom claim
+ */
+ public static Optional<CustomClaim> parseToken(String token) {
+ DecodedJWT jwt = verifyToken(token);
+ return jwt == null ? Optional.empty() : Optional.of(new CustomClaim(jwt.getClaims()));
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/ApplicationBootInitial.java b/src/main/java/com/dji/sample/component/ApplicationBootInitial.java
new file mode 100644
index 0000000..226fe1a
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/ApplicationBootInitial.java
@@ -0,0 +1,40 @@
+package com.dji.sample.component;
+
+import com.dji.sample.manage.model.DeviceStatusManager;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.param.DeviceQueryParam;
+import com.dji.sample.manage.service.IDeviceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/24
+ * @version 0.1
+ */
+@Component
+public class ApplicationBootInitial implements CommandLineRunner {
+
+ @Autowired
+ private IDeviceService deviceService;
+
+ /**
+ * Subscribe to the devices that exist in the database when the program starts,
+ * to prevent the data from being different from the pilot side due to program interruptions.
+ * @param args
+ * @throws Exception
+ */
+ @Override
+ public void run(String... args) throws Exception {
+ deviceService.getDevicesByParams(DeviceQueryParam.builder().build())
+ .forEach(device -> {
+ deviceService.subscribeTopicOnline(device.getDeviceSn());
+ DeviceStatusManager.STATUS_MANAGER.put(
+ DeviceDomainEnum.getVal(device.getDomain()) + "/"
+ + device.getDeviceSn(), LocalDateTime.now());
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/AuthInterceptor.java b/src/main/java/com/dji/sample/component/AuthInterceptor.java
new file mode 100644
index 0000000..0d3b8f7
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/AuthInterceptor.java
@@ -0,0 +1,60 @@
+package com.dji.sample.component;
+
+import com.dji.sample.common.error.CommonErrorEnum;
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.util.JwtUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Optional;
+
+@Slf4j
+@Component
+public class AuthInterceptor implements HandlerInterceptor {
+
+ public static final String PARAM_TOKEN = "x-auth-token";
+
+ public static final String TOKEN_CLAIM = "customClaim";
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ String uri = request.getRequestURI();
+ log.debug("request uri: {}", uri);
+ // The options method is passed directly.
+ if (HttpMethod.OPTIONS.matches(request.getMethod())) {
+ response.setStatus(HttpStatus.OK.value());
+ return false;
+ }
+ String token = request.getHeader(PARAM_TOKEN);
+ // Check if the token exists.
+ if (!StringUtils.hasText(token)) {
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ log.error(CommonErrorEnum.NO_TOKEN.getErrorMsg());
+ return false;
+ }
+
+ // Check if the current token is valid.
+ Optional<CustomClaim> customClaimOpt = JwtUtil.parseToken(token);
+ if (customClaimOpt.isEmpty()) {
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ return false;
+ }
+
+ // Put the custom data from the token into the request.
+ request.setAttribute(TOKEN_CLAIM, customClaimOpt.get());
+ return true;
+ }
+
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+ // Delete the custom data in the request after the request ends.
+ request.removeAttribute(TOKEN_CLAIM);
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/CorsFilter.java b/src/main/java/com/dji/sample/component/CorsFilter.java
new file mode 100644
index 0000000..a4c245f
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/CorsFilter.java
@@ -0,0 +1,35 @@
+package com.dji.sample.component;
+
+import org.springframework.stereotype.Component;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static com.dji.sample.component.AuthInterceptor.PARAM_TOKEN;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/22
+ */
+@Component
+public class CorsFilter implements Filter {
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletResponse res = (HttpServletResponse) response;
+ res.addHeader("Access-Control-Allow-Credentials", "true");
+ res.addHeader("Access-Control-Allow-Origin", "*");
+ res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
+ res.addHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Headers," +
+ "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, "+
+ "Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive," +
+ " User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma," + PARAM_TOKEN);
+ if (((HttpServletRequest) request).getMethod().equals("OPTIONS")) {
+ return;
+ }
+ filterChain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java
new file mode 100644
index 0000000..1d5eb14
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java
@@ -0,0 +1,33 @@
+package com.dji.sample.component;
+
+import com.dji.sample.common.model.ResponseResult;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/1
+ */
+@ControllerAdvice
+@ResponseBody
+public class GlobalExceptionHandler {
+
+ /**
+ * Please do not return directly like this, there is a risk.
+ * @param e
+ * @return
+ */
+ @ExceptionHandler(Exception.class)
+ public ResponseResult exceptionHandler(Exception e) {
+ e.printStackTrace();
+ return ResponseResult.error(e.getLocalizedMessage());
+ }
+
+ @ExceptionHandler(NullPointerException.class)
+ public ResponseResult nullPointerExceptionHandler(NullPointerException e) {
+ e.printStackTrace();
+ return ResponseResult.error("A null object appeared.");
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/GlobalScheduleService.java b/src/main/java/com/dji/sample/component/GlobalScheduleService.java
new file mode 100644
index 0000000..ea44a62
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/GlobalScheduleService.java
@@ -0,0 +1,58 @@
+package com.dji.sample.component;
+
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.service.IDeviceService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static com.dji.sample.manage.model.DeviceStatusManager.DEFAULT_ALIVE_SECOND;
+import static com.dji.sample.manage.model.DeviceStatusManager.STATUS_MANAGER;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/24
+ * @version 0.1
+ */
+@Component
+@Slf4j
+public class GlobalScheduleService {
+
+ @Autowired
+ private IDeviceService deviceService;
+
+ /**
+ * Check the status of the devices every 30 seconds. It is recommended to use cache.
+ */
+ @Scheduled(fixedRate = 30, timeUnit = TimeUnit.SECONDS)
+ private void deviceStatusListen() {
+ for (Map.Entry<String, LocalDateTime> entry : STATUS_MANAGER.entrySet()) {
+ if (entry.getValue().isAfter(
+ LocalDateTime.now().minusSeconds(DEFAULT_ALIVE_SECOND))) {
+ continue;
+ }
+
+ String device = entry.getKey();
+ int index = device.indexOf("/");
+
+ STATUS_MANAGER.remove(device);
+
+ int type = Integer.parseInt(device.substring(0, index));
+ String sn = device.substring(index + 1);
+ // Determine whether it is a gateway device.
+ if (DeviceDomainEnum.GATEWAY.getVal() == type) {
+ deviceService.deviceOffline(sn);
+ deviceService.unsubscribeTopicOffline(sn);
+ continue;
+ }
+
+ deviceService.subDeviceOffline(sn);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java b/src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java
new file mode 100644
index 0000000..1b2bdf2
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java
@@ -0,0 +1,95 @@
+package com.dji.sample.component.mqtt.config;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.integration.annotation.Router;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.integration.router.AbstractMessageRouter;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+import static com.dji.sample.component.mqtt.model.TopicConst.*;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Component
+@Slf4j
+public class InboundMessageRouter extends AbstractMessageRouter {
+
+ @Resource(name = ChannelName.INBOUND)
+ private MessageChannel inboundChannel;
+
+ @Resource(name = ChannelName.INBOUND_STATUS)
+ private MessageChannel statusChannel;
+
+ @Resource(name = ChannelName.INBOUND_STATE)
+ private MessageChannel stateChannel;
+
+ @Resource(name = ChannelName.DEFAULT)
+ private MessageChannel defaultChannel;
+
+ @Resource(name = ChannelName.INBOUND_SERVICE_REPLY)
+ private MessageChannel serviceReplyChannel;
+
+ @Resource(name = ChannelName.INBOUND_OSD)
+ private MessageChannel osdChannel;
+
+ private static final Pattern PATTERN_TOPIC_STATUS =
+ Pattern.compile("^" + BASIC_PRE + PRODUCT + REGEX_SN + STATUS_SUF + "$");
+
+ private static final Pattern PATTERN_TOPIC_STATE =
+ Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + STATE_SUF + "$");
+
+ private static final Pattern PATTERN_TOPIC_SERVICE_REPLY =
+ Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + SERVICES_SUF + _REPLY_SUF + "$");
+
+ private static final Pattern PATTERN_TOPIC_OSD =
+ Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + OSD_SUF);
+
+ /**
+ * All mqtt broker messages will arrive here before distributing them to different channels.
+ * @param message message from mqtt broker
+ * @return channel
+ */
+ @Override
+ @Router(inputChannel = ChannelName.INBOUND)
+ protected Collection<MessageChannel> determineTargetChannels(Message<?> message) {
+ MessageHeaders headers = message.getHeaders();
+ String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString();
+ byte[] payload = (byte[])message.getPayload();
+
+ // osd
+ if (PATTERN_TOPIC_OSD.matcher(topic).matches()) {
+ return Collections.singleton(osdChannel);
+ }
+
+ log.debug("received topic :{} \t payload :{}", topic, new String(payload));
+
+ // status
+ if (PATTERN_TOPIC_STATUS.matcher(topic).matches()) {
+ return Collections.singleton(statusChannel);
+ }
+
+ // state
+ if (PATTERN_TOPIC_STATE.matcher(topic).matches()) {
+ return Collections.singleton(stateChannel);
+ }
+
+ // services_reply
+ if (PATTERN_TOPIC_SERVICE_REPLY.matcher(topic).matches()) {
+ return Collections.singleton(serviceReplyChannel);
+ }
+ return Collections.singleton(defaultChannel);
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java
new file mode 100644
index 0000000..750ba58
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java
@@ -0,0 +1,63 @@
+package com.dji.sample.component.mqtt.config;
+
+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.mqtt.core.DefaultMqttPahoClientFactory;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Configuration
+@Data
+@ConfigurationProperties(prefix = "mqtt")
+public class MqttConfiguration {
+
+ private String protocol;
+
+ private String host;
+
+ private Integer port;
+
+ private String username;
+
+ private String password;
+
+ private String clientId;
+
+ /**
+ * The topic to subscribe to immediately when client connects.
+ */
+ private String inboundTopic;
+
+ @Bean
+ public MqttConnectOptions mqttConnectOptions() {
+ MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setServerURIs(new String[]{
+ new StringBuilder()
+ .append(protocol.trim())
+ .append("://")
+ .append(host.trim())
+ .append(":")
+ .append(port)
+ .toString()});
+ mqttConnectOptions.setUserName(username);
+ mqttConnectOptions.setPassword(password.toCharArray());
+ mqttConnectOptions.setAutomaticReconnect(true);
+ mqttConnectOptions.setKeepAliveInterval(10);
+ return mqttConnectOptions;
+ }
+
+ @Bean
+ public MqttPahoClientFactory mqttClientFactory() {
+ DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
+ factory.setConnectionOptions(mqttConnectOptions());
+ return factory;
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java
new file mode 100644
index 0000000..df6f04f
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java
@@ -0,0 +1,69 @@
+package com.dji.sample.component.mqtt.config;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.IntegrationComponentScan;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.endpoint.MessageProducerSupport;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
+import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+
+import javax.annotation.Resource;
+
+/**
+ * Client configuration for inbound messages.
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Slf4j
+@Configuration
+@IntegrationComponentScan
+public class MqttInboundConfiguration {
+
+ @Autowired
+ private MqttConfiguration mqttConfiguration;
+
+ @Autowired
+ private MqttPahoClientFactory mqttClientFactory;
+
+ @Resource(name = ChannelName.INBOUND)
+ private MessageChannel inboundChannel;
+
+ /**
+ * Clients of inbound message channels.
+ * @return
+ */
+ @Bean(name = "adapter")
+ public MessageProducerSupport mqttInbound() {
+ MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
+ mqttConfiguration.getClientId() + "_consumer_" + System.currentTimeMillis(),
+ mqttClientFactory, mqttConfiguration.getInboundTopic().split(","));
+ DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter();
+ // use byte types uniformly
+ converter.setPayloadAsBytes(true);
+ adapter.setConverter(converter);
+ adapter.setQos(1);
+ adapter.setOutputChannel(inboundChannel);
+ return adapter;
+ }
+
+ /**
+ * Define a default channel to handle messages that have no effect.
+ * @return
+ */
+ @Bean
+ @ServiceActivator(inputChannel = ChannelName.DEFAULT)
+ public MessageHandler defaultInboundHandler() {
+ return message -> {
+ log.info("The default channel does not handle messages.");
+ };
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java
new file mode 100644
index 0000000..2f06938
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java
@@ -0,0 +1,90 @@
+package com.dji.sample.component.mqtt.config;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.channel.ExecutorChannel;
+import org.springframework.messaging.MessageChannel;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Definition classes for all channels
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Configuration
+public class MqttMessageChannel {
+
+ @Autowired
+ private Executor threadPool;
+
+ @Bean(name = ChannelName.INBOUND)
+ public MessageChannel inboundChannel() {
+ return new ExecutorChannel(threadPool);
+ }
+
+ @Bean(name = ChannelName.INBOUND_STATUS)
+ public MessageChannel statusChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_STATUS_ONLINE)
+ public MessageChannel statusOnlineChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_STATUS_OFFLINE)
+ public MessageChannel statusOffChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_STATE)
+ public MessageChannel stateChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_STATE_BASIC)
+ public MessageChannel stateBasicChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_STATE_PAYLOAD)
+ public MessageChannel statePayloadChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_SERVICE_REPLY)
+ public MessageChannel serviceReplyChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_STATE_CAPACITY)
+ public MessageChannel stateCapacityChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE)
+ public MessageChannel statePayloadUpdateChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.INBOUND_OSD)
+ public MessageChannel osdChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.DEFAULT)
+ public MessageChannel defaultChannel() {
+ return new DirectChannel();
+ }
+
+ @Bean(name = ChannelName.OUTBOUND)
+ public MessageChannel outboundChannel() {
+ return new DirectChannel();
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java
new file mode 100644
index 0000000..69b919b
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java
@@ -0,0 +1,47 @@
+package com.dji.sample.component.mqtt.config;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
+import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
+import org.springframework.messaging.MessageHandler;
+
+/**
+ * Client configuration for outbound messages.
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Configuration
+public class MqttOutboundConfiguration {
+
+ @Autowired
+ private MqttConfiguration mqttConfiguration;
+
+ @Autowired
+ private MqttPahoClientFactory mqttClientFactory;
+
+ /**
+ * Clients of outbound message channels.
+ * @return
+ */
+ @Bean
+ @ServiceActivator(inputChannel = ChannelName.OUTBOUND)
+ public MessageHandler mqttOutbound() {
+ MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(
+ mqttConfiguration.getClientId() + "_producer_" + System.currentTimeMillis(),
+ mqttClientFactory);
+ DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter();
+ // use byte types uniformly
+ converter.setPayloadAsBytes(true);
+
+ messageHandler.setAsync(true);
+ messageHandler.setDefaultQos(0);
+ messageHandler.setConverter(converter);
+ return messageHandler;
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java b/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java
new file mode 100644
index 0000000..c8f5f01
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java
@@ -0,0 +1,46 @@
+package com.dji.sample.component.mqtt.model;
+
+/**
+ * The name of all channels.
+ *
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+public class ChannelName {
+
+ public static final String INBOUND = "inbound";
+
+ public static final String INBOUND_STATUS = "inboundStatus";
+
+ public static final String INBOUND_STATUS_ROUTER = "inboundStatusRouter";
+
+ public static final String INBOUND_STATUS_ONLINE = "inboundStatusOnline";
+
+ public static final String INBOUND_STATUS_OFFLINE = "inboundStatusOffline";
+
+ public static final String INBOUND_STATE = "inboundState";
+
+ public static final String INBOUND_STATE_SPLITTER = "inboundStateSplitter";
+
+ public static final String INBOUND_STATE_ROUTER = "inboundStateRouter";
+
+ public static final String INBOUND_STATE_BASIC = "inboundStateBasic";
+
+ public static final String INBOUND_STATE_PAYLOAD = "inboundStatePayload";
+
+ public static final String INBOUND_STATE_PAYLOAD_UPDATE = "inboundStatePayloadUpdate";
+
+ public static final String INBOUND_STATE_CAPACITY = "inboundStateCapacity";
+
+ public static final String INBOUND_STATE_LIST = "inboundStateList";
+
+ public static final String INBOUND_SERVICE_REPLY = "inboundStateServiceReply";
+
+ public static final String INBOUND_OSD = "inboundOsd";
+
+ public static final String DEFAULT = "default";
+
+ public static final String OUTBOUND = "outbound";
+
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java
new file mode 100644
index 0000000..5a6fe45
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java
@@ -0,0 +1,29 @@
+package com.dji.sample.component.mqtt.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+/**
+ * Unified topic receiving format.
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CommonTopicReceiver<T> {
+
+ /**
+ * The command is sent and the response is matched by the tid and bid fields in the message,
+ * and the reply should keep the tid and bid the same.
+ */
+ private String tid;
+
+ private String bid;
+
+ private String method;
+
+ private Long timestamp;
+
+ private T data;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java
new file mode 100644
index 0000000..effd5c1
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java
@@ -0,0 +1,33 @@
+package com.dji.sample.component.mqtt.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * Unified Topic response format
+ * @author sean.zhou
+ * @date 2021/11/15
+ * @version 0.1
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CommonTopicResponse<T> {
+
+ /**
+ * The command is sent and the response is matched by the tid and bid fields in the message,
+ * and the reply should keep the tid and bid the same.
+ */
+ private String tid;
+
+ private String bid;
+
+ private String method;
+
+ private T data;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java b/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java
new file mode 100644
index 0000000..667c969
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java
@@ -0,0 +1,29 @@
+package com.dji.sample.component.mqtt.model;
+
+/**
+ * All the topics that need to be used in the project.
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+public class TopicConst {
+
+ public static final String BASIC_PRE = "sys/";
+
+ public static final String THING_MODEL_PRE = "thing/";
+
+ public static final String PRODUCT = "product/";
+
+ public static final String STATUS_SUF = "/status";
+
+ public static final String _REPLY_SUF = "_reply";
+
+ public static final String STATE_SUF = "/state";
+
+ public static final String SERVICES_SUF = "/services";
+
+ public static final String OSD_SUF = "/osd";
+
+ public static final String REGEX_SN = "[A-Za-z0-9]+";
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java b/src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java
new file mode 100644
index 0000000..522a4f8
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java
@@ -0,0 +1,29 @@
+package com.dji.sample.component.mqtt.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+/**
+ * The data format of the state topic.
+ * @author sean.zhou
+ * @date 2021/11/17
+ * @version 0.1
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TopicStateReceiver<T> {
+
+ private String tid;
+
+ private String bid;
+
+ private Long timestamp;
+
+ /**
+ * The sn of the gateway device.
+ */
+ private String gateway;
+
+ private T data;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java b/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java
new file mode 100644
index 0000000..7930d41
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java
@@ -0,0 +1,27 @@
+package com.dji.sample.component.mqtt.service;
+
+import com.dji.sample.component.mqtt.model.CommonTopicResponse;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/25
+ */
+public interface IMessageSenderService {
+
+ /**
+ * Publish a message to a specific topic.
+ * @param topic target
+ * @param response message
+ */
+ void publish(String topic, CommonTopicResponse response);
+
+ /**
+ * Use a specific qos to push messages to a specific topic.
+ * @param topic target
+ * @param qos qos
+ * @param response message
+ */
+ void publish(String topic, int qos, CommonTopicResponse response);
+
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java b/src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java
new file mode 100644
index 0000000..cb50b12
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java
@@ -0,0 +1,33 @@
+package com.dji.sample.component.mqtt.service;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import org.springframework.integration.annotation.MessagingGateway;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Component
+@MessagingGateway(defaultRequestChannel = ChannelName.OUTBOUND)
+public interface IMqttMessageGateway {
+
+ /**
+ * Publish a message to a specific topic.
+ * @param topic target
+ * @param payload message
+ */
+ void publish(@Header(MqttHeaders.TOPIC) String topic, byte[] payload);
+
+ /**
+ * Use a specific qos to push messages to a specific topic.
+ * @param topic target
+ * @param payload message
+ * @param qos qos
+ */
+ void publish(@Header(MqttHeaders.TOPIC) String topic, byte[] payload, @Header(MqttHeaders.QOS) int qos);
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java b/src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java
new file mode 100644
index 0000000..6983109
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java
@@ -0,0 +1,38 @@
+package com.dji.sample.component.mqtt.service;
+
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.handler.annotation.Header;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+public interface IMqttTopicService {
+
+ /**
+ * Subscribe to a specific topic.
+ * @param topic target
+ */
+ void subscribe(@Header(MqttHeaders.TOPIC) String topic);
+
+ /**
+ * Subscribe to a specific topic using a specific qos.
+ * @param topic target
+ * @param qos qos
+ */
+ void subscribe(@Header(MqttHeaders.TOPIC) String topic, int qos);
+
+ /**
+ * Unsubscribe from a specific topic.
+ * @param topic target
+ */
+ void unsubscribe(@Header(MqttHeaders.TOPIC) String topic);
+
+ /**
+ * Get all the subscribed topics.
+ * @return topics
+ */
+ String[] getSubscribedTopic();
+}
diff --git a/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java b/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java
new file mode 100644
index 0000000..2510ead
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java
@@ -0,0 +1,50 @@
+package com.dji.sample.component.mqtt.service.impl;
+
+import com.dji.sample.component.mqtt.model.CommonTopicResponse;
+import com.dji.sample.component.mqtt.service.IMessageSenderService;
+import com.dji.sample.component.mqtt.service.IMqttMessageGateway;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/16
+ * @version 0.1
+ */
+@Service
+@Slf4j
+public class MessageSenderServiceImpl implements IMessageSenderService {
+
+ @Autowired
+ private IMqttMessageGateway messageGateway;
+
+ public void publish(String topic, CommonTopicResponse response) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ // Only parameters whose value is not null will be serialised.
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+ messageGateway.publish(topic, mapper.writeValueAsBytes(response));
+ } catch (JsonProcessingException e) {
+ log.info("Failed to publish the message. {}", response.toString());
+ e.printStackTrace();
+ }
+ }
+
+ public void publish(String topic, int qos, CommonTopicResponse response) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ // Only parameters whose value is not null will be serialised.
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+ messageGateway.publish(topic, mapper.writeValueAsBytes(response), qos);
+ } catch (JsonProcessingException e) {
+ log.info("Failed to publish the message. {}", response.toString());
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java b/src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java
new file mode 100644
index 0000000..6880ecf
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java
@@ -0,0 +1,44 @@
+package com.dji.sample.component.mqtt.service.impl;
+
+import com.dji.sample.component.mqtt.service.IMqttTopicService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Component
+@Slf4j
+public class MqttTopicServiceImpl implements IMqttTopicService {
+
+ @Resource
+ private MqttPahoMessageDrivenChannelAdapter adapter;
+
+ @Override
+ public void subscribe(String topic) {
+ log.debug("subscribe topic: {}", topic);
+ adapter.addTopic(topic);
+ }
+
+ @Override
+ public void subscribe(String topic, int qos) {
+ log.debug("subscribe topic: {}", topic);
+ adapter.addTopic(topic, qos);
+ }
+
+ @Override
+ public void unsubscribe(String topic) {
+ log.debug("unsubscribe topic: {}", topic);
+ adapter.removeTopic(topic);
+ }
+
+ public String[] getSubscribedTopic() {
+ return adapter.getTopic();
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java b/src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java
new file mode 100644
index 0000000..2992641
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java
@@ -0,0 +1,24 @@
+package com.dji.sample.component.mybatis;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@Configuration
+public class MybatisPlusConfiguration {
+
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ // select database
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+ return interceptor;
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java b/src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java
new file mode 100644
index 0000000..88170ed
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java
@@ -0,0 +1,37 @@
+package com.dji.sample.component.mybatis;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+/**
+ * Automatic filling for set values
+ */
+@Component
+public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
+
+ /**
+ * Automatic filling when inserting into the database.
+ * @param metaObject
+ */
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ this.strictInsertFill(metaObject, "createTime", Long.class,
+ LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
+ this.strictInsertFill(metaObject, "updateTime", Long.class,
+ LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
+ }
+
+ /**
+ * Automatic filling when updating the data.
+ * @param metaObject
+ */
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ this.strictUpdateFill(metaObject, "updateTime", Long.class,
+ LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java b/src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java
new file mode 100644
index 0000000..32624ea
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java
@@ -0,0 +1,104 @@
+package com.dji.sample.component.oss.model;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+@Configuration
+public class AliyunOSSConfiguration {
+
+ /**
+ * default
+ */
+ public static final String PROVIDER = "ali";
+
+ /**
+ * Whether to use the current storage service.
+ */
+ public static boolean enable;
+
+ /**
+ * The protocol needs to be included at the beginning of the address.
+ */
+ public static String endpoint;
+
+ public static String accessKey;
+
+ public static String secretKey;
+
+ public static String region;
+
+ public static Long expire;
+
+ public static String roleSessionName;
+
+ public static String roleArn;
+
+ public static String bucket;
+
+ public static String objectDirPrefix;
+
+ @Value("${aliyun.oss.endpoint}")
+ private void setEndpoint(String endpoint) {
+ AliyunOSSConfiguration.endpoint = endpoint;
+ }
+
+ @Value("${aliyun.oss.access-key}")
+ private void setAccessKey(String accessKey) {
+ AliyunOSSConfiguration.accessKey = accessKey;
+ }
+
+ @Value("${aliyun.oss.secret-key}")
+ private void setSecretKey(String secretKey) {
+ AliyunOSSConfiguration.secretKey = secretKey;
+ }
+
+ @Value("${aliyun.oss.region}")
+ private void setRegion(String region) {
+ AliyunOSSConfiguration.region = region;
+ }
+
+ @Value("${aliyun.oss.expire: 3600}")
+ private void setExpire(Long expire) {
+ AliyunOSSConfiguration.expire = expire;
+ }
+
+ @Value("${aliyun.oss.enable: false}")
+ private void setEnable(boolean enable) {
+ AliyunOSSConfiguration.enable = enable;
+ }
+
+ @Value("${aliyun.oss.role-session-name}")
+ private void setRoleSessionName(String roleSessionName) {
+ AliyunOSSConfiguration.roleSessionName = roleSessionName;
+ }
+
+ @Value("${aliyun.oss.role-arn}")
+ private void setRoleArn(String roleArn) {
+ AliyunOSSConfiguration.roleArn = roleArn;
+ }
+
+ @Value("${aliyun.oss.bucket}")
+ private void setBucket(String bucket) {
+ AliyunOSSConfiguration.bucket = bucket;
+ }
+
+ @Value("${aliyun.oss.object-dir-prefix: wayline}")
+ private void setObjectDir(String objectDirPrefix) {
+ AliyunOSSConfiguration.objectDirPrefix = objectDirPrefix;
+ }
+
+ @Bean
+ @Lazy
+ public OSS ossClient() {
+ return new OSSClientBuilder().build(endpoint, accessKey, secretKey);
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java b/src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java
new file mode 100644
index 0000000..5768023
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java
@@ -0,0 +1,93 @@
+package com.dji.sample.component.oss.model;
+
+import io.minio.MinioClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/7
+ */
+@Configuration
+public class MinIOConfiguration {
+
+ /**
+ * default
+ */
+ public static final String PROVIDER = "aws";
+
+ /**
+ * Whether to use the current storage service.
+ */
+ public static boolean enable;
+
+ public static String endpoint;
+
+ public static String accessKey;
+
+ public static String secretKey;
+
+ public static String region;
+
+ public static String bucket;
+
+ public static Integer expire;
+
+ public static String objectDirPrefix;
+
+ @Value("${minio.endpoint: http://localhost:9000/}")
+ private void setEndpoint(String endpoint) {
+ MinIOConfiguration.endpoint = endpoint;
+ }
+
+ @Value("${minio.access-key: minioadmin}")
+ private void setAccessKey(String accessKey) {
+ MinIOConfiguration.accessKey = accessKey;
+ }
+
+ @Value("${minio.secret-key: minioadmin}")
+ private void setSecretKey(String secretKey) {
+ MinIOConfiguration.secretKey = secretKey;
+ }
+
+ @Value("${minio.region: }")
+ private void setRegion(String region) {
+ MinIOConfiguration.region = region;
+ }
+
+ @Value("${minio.bucket: test}")
+ private void setBucket(String bucket) {
+ MinIOConfiguration.bucket = bucket;
+ }
+
+ @Value("${minio.expire: 3600}")
+ private void setExpire(Integer expire) {
+ MinIOConfiguration.expire = expire;
+ }
+
+ @Value("${minio.enable: false}")
+ private void setEnable(boolean enable) {
+ MinIOConfiguration.enable = enable;
+ }
+
+ @Value("${minio.object-dir-prefix: wayline}")
+ private void setObjectDir(String objectDirPrefix) {
+ MinIOConfiguration.objectDirPrefix = objectDirPrefix;
+ }
+
+ @Bean
+ @Lazy
+ public MinioClient minioClient() {
+ MinioClient.Builder builder = MinioClient.builder()
+ .endpoint(endpoint)
+ .credentials(accessKey, secretKey);
+ if (StringUtils.hasText(region)) {
+ builder.region(MinIOConfiguration.region);
+ }
+ return builder.build();
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/oss/service/IOssService.java b/src/main/java/com/dji/sample/component/oss/service/IOssService.java
new file mode 100644
index 0000000..beb9db6
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/oss/service/IOssService.java
@@ -0,0 +1,27 @@
+package com.dji.sample.component.oss.service;
+
+import com.dji.sample.media.model.CredentialsDTO;
+
+import java.net.URL;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/23
+ */
+public interface IOssService {
+
+ /**
+ * Get temporary credentials.
+ * @return
+ */
+ CredentialsDTO getCredentials();
+
+ /**
+ * Get the address of the object based on the bucket name and the object name.
+ * @param bucket bucket name
+ * @param objectKey object name
+ * @return download link
+ */
+ URL getObjectUrl(String bucket, String objectKey);
+}
diff --git a/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java b/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java
new file mode 100644
index 0000000..f9f92d3
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java
@@ -0,0 +1,68 @@
+package com.dji.sample.component.oss.service.impl;
+
+import com.aliyun.oss.OSS;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
+import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
+import com.dji.sample.component.oss.model.AliyunOSSConfiguration;
+import com.dji.sample.component.oss.service.IOssService;
+import com.dji.sample.media.model.CredentialsDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.net.URL;
+import java.util.Date;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/23
+ */
+@Service
+@Slf4j
+public class AliyunOssServiceImpl implements IOssService {
+
+ @Autowired
+ private OSS ossClient;
+
+ @Override
+ public CredentialsDTO getCredentials() {
+
+ try {
+ DefaultProfile profile = DefaultProfile.getProfile(
+ AliyunOSSConfiguration.region, AliyunOSSConfiguration.accessKey, AliyunOSSConfiguration.secretKey);
+ IAcsClient client = new DefaultAcsClient(profile);
+
+ AssumeRoleRequest request = new AssumeRoleRequest();
+ request.setDurationSeconds(AliyunOSSConfiguration.expire);
+ request.setRoleArn(AliyunOSSConfiguration.roleArn);
+ request.setRoleSessionName(AliyunOSSConfiguration.roleSessionName);
+
+ AssumeRoleResponse response = client.getAcsResponse(request);
+ return new CredentialsDTO(response.getCredentials(), AliyunOSSConfiguration.expire);
+
+ } catch (ClientException e) {
+ log.debug("Failed to obtain sts.");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public URL getObjectUrl(String bucket, String objectKey) {
+ if (!StringUtils.hasText(bucket) || !StringUtils.hasText(objectKey)) {
+ return null;
+ }
+ // First check if the object can be fetched.
+ ossClient.getObject(bucket, objectKey);
+
+ return ossClient.generatePresignedUrl(bucket, objectKey,
+ new Date(System.currentTimeMillis() + AliyunOSSConfiguration.expire * 1000));
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java b/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java
new file mode 100644
index 0000000..1b5e74e
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java
@@ -0,0 +1,66 @@
+package com.dji.sample.component.oss.service.impl;
+
+import com.dji.sample.component.oss.model.MinIOConfiguration;
+import com.dji.sample.component.oss.service.IOssService;
+import com.dji.sample.media.model.CredentialsDTO;
+import io.minio.GetPresignedObjectUrlArgs;
+import io.minio.MinioClient;
+import io.minio.credentials.AssumeRoleProvider;
+import io.minio.errors.*;
+import io.minio.http.Method;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/23
+ */
+@Service
+@Slf4j
+public class MinIOServiceImpl implements IOssService {
+
+ @Autowired
+ private MinioClient client;
+
+ @Override
+ public CredentialsDTO getCredentials() {
+ try {
+ AssumeRoleProvider provider = new AssumeRoleProvider(MinIOConfiguration.endpoint, MinIOConfiguration.accessKey,
+ MinIOConfiguration.secretKey, MinIOConfiguration.expire,
+ null, null, null, null, null, null);
+ return new CredentialsDTO(provider.fetch(), MinIOConfiguration.expire);
+ } catch (NoSuchAlgorithmException e) {
+ log.debug("Failed to obtain sts.");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public URL getObjectUrl(String bucket, String objectKey) {
+ try {
+ return new URL(
+ client.getPresignedObjectUrl(
+ GetPresignedObjectUrlArgs.builder()
+ .method(Method.GET)
+ .bucket(bucket)
+ .object(objectKey)
+ .expiry(MinIOConfiguration.expire)
+ .build()));
+ } catch (ErrorResponseException | InsufficientDataException | InternalException |
+ InvalidKeyException | InvalidResponseException | IOException |
+ NoSuchAlgorithmException | XmlParserException | ServerException e) {
+ log.error("The file does not exist on the oss.");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java b/src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java
new file mode 100644
index 0000000..68f070e
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java
@@ -0,0 +1,70 @@
+package com.dji.sample.component.websocket.config;
+
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.util.JwtUtil;
+import com.dji.sample.component.AuthInterceptor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import java.security.Principal;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/16
+ * @version 0.1
+ */
+@Slf4j
+@Component
+public class AuthPrincipalHandler extends DefaultHandshakeHandler {
+
+ @Override
+ protected boolean isValidOrigin(ServerHttpRequest request) {
+
+ if (request instanceof ServletServerHttpRequest) {
+ HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
+ String token = servletRequest.getParameter(AuthInterceptor.PARAM_TOKEN);
+
+ if (!StringUtils.hasText(token)) {
+ return false;
+ }
+
+ Optional<CustomClaim> customClaim = JwtUtil.parseToken(token);
+ if (customClaim.isEmpty()) {
+ return false;
+ }
+
+ servletRequest.setAttribute(AuthInterceptor.TOKEN_CLAIM, customClaim.get());
+ return true;
+ }
+ return false;
+
+ }
+
+ /**
+ * The principal's name: {workspaceId}/{userType}/{userId}
+ * @param request
+ * @param wsHandler
+ * @param attributes
+ * @return
+ */
+ @Override
+ protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
+ if (request instanceof ServletServerHttpRequest) {
+
+ // get the custom claim
+ CustomClaim claim = (CustomClaim) ((ServletServerHttpRequest) request).getServletRequest()
+ .getAttribute(AuthInterceptor.TOKEN_CLAIM);
+
+ return () -> claim.getWorkspaceId() + "/" + claim.getUserType() + "/" + claim.getId();
+ }
+ return () -> null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java b/src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java
new file mode 100644
index 0000000..cd13365
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java
@@ -0,0 +1,25 @@
+package com.dji.sample.component.websocket.config;
+
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/24
+ */
+public class ConcurrentWebSocketSession extends ConcurrentWebSocketSessionDecorator {
+
+ private static final int SEND_BUFFER_SIZE_LIMIT = 1024 * 1024;
+
+ private static final int SEND_TIME_LIMIT = 1000;
+
+ private ConcurrentWebSocketSession(WebSocketSession delegate, int sendTimeLimit, int bufferSizeLimit) {
+ super(delegate, sendTimeLimit, bufferSizeLimit);
+ }
+
+ ConcurrentWebSocketSession(WebSocketSession delegate) {
+ this(delegate, SEND_TIME_LIMIT, SEND_BUFFER_SIZE_LIMIT);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java b/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java
new file mode 100644
index 0000000..4fdd774
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java
@@ -0,0 +1,20 @@
+package com.dji.sample.component.websocket.config;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/16
+ * @version 0.1
+ */
+@Component
+public class WebSocketDefaultFactory implements WebSocketHandlerDecoratorFactory {
+
+ @Override
+ public WebSocketHandler decorate(WebSocketHandler handler) {
+ return new WebSocketDefaultHandler(handler);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java b/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java
new file mode 100644
index 0000000..d4c7e7a
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java
@@ -0,0 +1,60 @@
+package com.dji.sample.component.websocket.config;
+
+import com.dji.sample.component.websocket.model.WebSocketManager;
+import com.dji.sample.component.websocket.service.ISendMessageService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.WebSocketMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
+
+import java.security.Principal;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/16
+ * @version 0.1
+ */
+@Slf4j
+public class WebSocketDefaultHandler extends WebSocketHandlerDecorator {
+
+ @Autowired
+ private ISendMessageService sendMessageService;
+
+ WebSocketDefaultHandler(WebSocketHandler delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+ Principal principal = session.getPrincipal();
+ if (StringUtils.hasText(principal.getName())) {
+ WebSocketManager.put(principal.getName(), new ConcurrentWebSocketSession(session));
+ log.debug("{} is connected. ID: {}. WebSocketSession[current count: {}]",
+ principal.getName(), session.getId(), WebSocketManager.getConnectedCount());
+ return;
+ }
+ session.close();
+ }
+
+ @Override
+ public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
+ Principal principal = session.getPrincipal();
+ if (StringUtils.hasText(principal.getName())) {
+ WebSocketManager.remove(principal.getName(), session.getId());
+ log.debug("{} is disconnected. ID: {}. WebSocketSession[current count: {}]",
+ principal.getName(), session.getId(), WebSocketManager.getConnectedCount());
+ }
+
+ }
+
+ @Override
+ public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
+ log.debug("received message: {}", message.getPayload());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java b/src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java
new file mode 100644
index 0000000..85a696c
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java
@@ -0,0 +1,39 @@
+package com.dji.sample.component.websocket.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/17
+ * @version 0.1
+ */
+@EnableWebSocketMessageBroker
+@Configuration
+public class WebSocketMessageConfiguration implements WebSocketMessageBrokerConfigurer {
+
+ @Autowired
+ private AuthPrincipalHandler authPrincipalHandler;
+
+ @Autowired
+ private WebSocketDefaultFactory webSocketDefaultFactory;
+
+ @Override
+ public void registerStompEndpoints(StompEndpointRegistry registry) {
+ // Set the WebSocket connection address
+ registry.addEndpoint("/api/v1/ws").setAllowedOriginPatterns("*")
+ .setHandshakeHandler(authPrincipalHandler);
+ }
+
+ @Override
+ public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
+ registry.addDecoratorFactory(webSocketDefaultFactory);
+ registry.setTimeToFirstMessage(60000 * 60 * 24 * 10);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java b/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java
new file mode 100644
index 0000000..6ac35c2
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java
@@ -0,0 +1,37 @@
+package com.dji.sample.component.websocket.model;
+
+/**
+ * @author sean
+ * @version 0.1
+ * @date 2021/11/26
+ */
+public enum BizCodeEnum {
+
+ DEVICE_ONLINE("device_online"),
+
+ DEVICE_OFFLINE("device_offline"),
+
+ DEVICE_UPDATE_TOPO("device_update_topo"),
+
+ DEVICE_OSD("device_osd"),
+
+ GATEWAY_OSD("gateway_osd"),
+
+ MAP_ELEMENT_CREATE("map_element_create"),
+
+ MAP_ELEMENT_UPDATE("map_element_update"),
+
+ MAP_ELEMENT_DELETE("map_element_delete"),
+
+ MAP_GROUP_REFRESH("map_group_refresh");
+
+ private String code;
+
+ BizCodeEnum(String code) {
+ this.code = code;
+ }
+
+ public String getCode() {
+ return code;
+ }
+}
diff --git a/src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java b/src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java
new file mode 100644
index 0000000..c1d162d
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java
@@ -0,0 +1,30 @@
+package com.dji.sample.component.websocket.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * The format of WebSocket messages that the pilot can receive.
+ * @author sean.zhou
+ * @date 2021/11/17
+ * @version 0.1
+ */
+@Data
+@Builder
+public class CustomWebSocketMessage<T> {
+
+ /**
+ * @see BizCodeEnum
+ * specific value
+ */
+ @JsonProperty("biz_code")
+ private String bizCode;
+
+ @Builder.Default
+ private String version = "1.0";
+
+ private Long timestamp;
+
+ private T data;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java b/src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java
new file mode 100644
index 0000000..da48ae1
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java
@@ -0,0 +1,112 @@
+package com.dji.sample.component.websocket.model;
+
+import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
+import com.dji.sample.manage.model.enums.UserTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Manage all WebSocket connection objects.
+ * @author sean.zhou
+ * @date 2021/11/16
+ * @version 0.1
+ */
+@Slf4j
+public class WebSocketManager {
+
+ private static final ConcurrentHashMap<String,
+ ConcurrentHashMap<String,
+ ConcurrentHashMap<String, ConcurrentWebSocketSession>>> MANAGER = new ConcurrentHashMap<>(16);
+
+ /**
+ * WebSocket connection from the pilot.
+ */
+ private static final Set<ConcurrentWebSocketSession> PILOT_SESSION = ConcurrentHashMap.newKeySet(16);
+
+ /**
+ * WebSocket connection from the web.
+ */
+ private static final Set<ConcurrentWebSocketSession> WEB_SESSION = ConcurrentHashMap.newKeySet(16);
+
+ public static void put(String key, ConcurrentWebSocketSession val) {
+
+ String[] name = key.split("/");
+ if (name.length != 3) {
+ log.debug("The key is out of format. [{workspaceId}/{userType}/{userId}]");
+ return;
+ }
+
+ ConcurrentHashMap<String, ConcurrentHashMap<String, ConcurrentWebSocketSession>> workspaceSessions =
+ MANAGER.getOrDefault(name[0], new ConcurrentHashMap<>(16));
+
+ ConcurrentHashMap<String, ConcurrentWebSocketSession> userSessions = workspaceSessions.getOrDefault(
+ name[2], new ConcurrentHashMap<>(16));
+ userSessions.put(val.getId(), val);
+ workspaceSessions.put(name[2], userSessions);
+ MANAGER.put(name[0], workspaceSessions);
+
+ getSetByUserType(Integer.valueOf(name[1])).add(val);
+
+ }
+
+ public static void remove(String key, String sessionId) {
+ String[] name = key.split("/");
+ if (name.length != 3) {
+ log.debug("The key is out of format. [{workspaceId}/{userType}/{userId}]");
+ return;
+ }
+ ConcurrentHashMap<String, ConcurrentWebSocketSession> userSession = MANAGER.get(name[0]).get(name[2]);
+
+ Set<ConcurrentWebSocketSession> typeSession = getSetByUserType(Integer.valueOf(name[1]));
+
+ ConcurrentWebSocketSession session = userSession.get(sessionId);
+ typeSession.remove(session);
+ userSession.remove(sessionId);
+ }
+
+ public static int getConnectedCount() {
+ return PILOT_SESSION.size() + WEB_SESSION.size();
+ }
+
+ public static Collection<ConcurrentWebSocketSession> getValueWithWorkspace(String workspaceId) {
+ Set<ConcurrentWebSocketSession> sessions = ConcurrentHashMap.newKeySet();
+
+ MANAGER.get(workspaceId)
+ .forEach((userId, userSessions) -> {
+ sessions.addAll(userSessions.values());
+ });
+ return sessions;
+ }
+
+ public static Collection<ConcurrentWebSocketSession> getValueWithWorkspaceAndUserType(String workspaceId, Integer userType) {
+ Set<ConcurrentWebSocketSession> sessions = ConcurrentHashMap.newKeySet();
+ Set<ConcurrentWebSocketSession> typeSessions = getSetByUserType(userType);
+
+ MANAGER.getOrDefault(workspaceId, new ConcurrentHashMap<>())
+ .forEach((userId, userSessions) -> {
+ Collection<ConcurrentWebSocketSession> sessionList = userSessions.values();
+ if (!sessionList.isEmpty()) {
+ ConcurrentWebSocketSession session = sessionList.iterator().next();
+ if (typeSessions.contains(session)) {
+ sessions.addAll(sessionList);
+ }
+ }
+ });
+ return sessions;
+ }
+
+ private static Set<ConcurrentWebSocketSession> getSetByUserType(Integer userType) {
+ if (UserTypeEnum.PILOT.getVal() == userType) {
+ return PILOT_SESSION;
+ }
+
+ if (UserTypeEnum.WEB.getVal() == userType) {
+ return WEB_SESSION;
+ }
+ return new HashSet<>();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java b/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java
new file mode 100644
index 0000000..e1d67e7
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java
@@ -0,0 +1,28 @@
+package com.dji.sample.component.websocket.service;
+
+import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
+import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
+
+import java.util.Collection;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/24
+ * @version 0.1
+ */
+public interface ISendMessageService {
+
+ /**
+ * Send a message to the specific connection.
+ * @param session A WebSocket connection object
+ * @param message message
+ */
+ void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message);
+
+ /**
+ * Send the same message to specific connection.
+ * @param sessions A collection of WebSocket connection objects.
+ * @param message message
+ */
+ void sendBatch(Collection<ConcurrentWebSocketSession> sessions, CustomWebSocketMessage message);
+}
diff --git a/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java b/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java
new file mode 100644
index 0000000..8ee6131
--- /dev/null
+++ b/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java
@@ -0,0 +1,72 @@
+package com.dji.sample.component.websocket.service.impl;
+
+import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
+import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
+import com.dji.sample.component.websocket.service.ISendMessageService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.web.socket.TextMessage;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/24
+ */
+@Service
+@Slf4j
+public class SendMessageServiceImpl implements ISendMessageService {
+
+ @Override
+ public void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message) {
+ if (session == null) {
+ return;
+ }
+
+ try {
+ if (!session.isOpen()) {
+ session.close();
+ log.debug("This session is closed.");
+ return;
+ }
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ session.sendMessage(new TextMessage(mapper.writeValueAsBytes(message)));
+ } catch (IOException e) {
+ log.info("Failed to publish the message. {}", message.toString());
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void sendBatch(Collection<ConcurrentWebSocketSession> sessions, CustomWebSocketMessage message) {
+ if (sessions.isEmpty()) {
+ return;
+ }
+
+ try {
+
+ ObjectMapper mapper = new ObjectMapper();
+ TextMessage data = new TextMessage(mapper.writeValueAsBytes(message));
+
+ for (ConcurrentWebSocketSession session : sessions) {
+ if (!session.isOpen()) {
+ session.close();
+ log.debug("This session is closed.");
+ return;
+ }
+ session.sendMessage(data);
+
+ }
+
+ } catch (IOException e) {
+ log.info("Failed to publish the message. {}", message.toString());
+
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java b/src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java
new file mode 100644
index 0000000..0dfc05c
--- /dev/null
+++ b/src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java
@@ -0,0 +1,35 @@
+package com.dji.sample.configuration;
+
+import com.dji.sample.component.AuthInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Configuration
+public class GlobalMVCConfigurer implements WebMvcConfigurer {
+
+ @Autowired
+ private AuthInterceptor authInterceptor;
+
+ private static List<String> excludePaths = new ArrayList<>();
+
+ @Value("${url.manage.prefix}")
+ private String managePrefix;
+
+ @Value("${url.manage.version}")
+ private String manageVersion;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // Exclude the login interface.
+ excludePaths.add(managePrefix + manageVersion + "/login");
+ excludePaths.add(managePrefix + manageVersion + "/token/refresh");
+ // Intercept for all request interfaces.
+ registry.addInterceptor(authInterceptor).addPathPatterns("/**").excludePathPatterns(excludePaths);
+ }
+}
diff --git a/src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java b/src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java
new file mode 100644
index 0000000..af7a92d
--- /dev/null
+++ b/src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java
@@ -0,0 +1,42 @@
+package com.dji.sample.configuration;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.*;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+@Configuration
+public class GlobalThreadPoolConfiguration {
+
+ @Value("${thread.pool.core-pool-size: 10}")
+ private int corePoolSize;
+
+ @Value("${thread.pool.maximum-pool-size: 20}")
+ private int maximumPoolSize;
+
+ @Value("${thread.pool.keep-alive-time: 60}")
+ private long keepAliveTime;
+
+ @Value("${thread.pool.queue.capacity: 1000}")
+ private int capacity;
+
+ /**
+ * A custom thread pool.
+ * @return
+ */
+ @Bean
+ public Executor threadPool() {
+ return new ThreadPoolExecutor(corePoolSize,
+ maximumPoolSize, keepAliveTime,
+ TimeUnit.SECONDS, new LinkedBlockingQueue<>(capacity),
+ Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/manage/controller/DeviceController.java b/src/main/java/com/dji/sample/manage/controller/DeviceController.java
new file mode 100644
index 0000000..08a245d
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/controller/DeviceController.java
@@ -0,0 +1,138 @@
+package com.dji.sample.manage.controller;
+
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.component.AuthInterceptor;
+import com.dji.sample.component.mqtt.model.ChannelName;
+import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
+import com.dji.sample.component.mqtt.model.CommonTopicResponse;
+import com.dji.sample.component.websocket.model.BizCodeEnum;
+import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
+import com.dji.sample.component.websocket.model.WebSocketManager;
+import com.dji.sample.component.websocket.service.ISendMessageService;
+import com.dji.sample.manage.model.dto.DeviceDTO;
+import com.dji.sample.manage.model.dto.WorkspaceDTO;
+import com.dji.sample.manage.model.enums.UserTypeEnum;
+import com.dji.sample.manage.model.param.DeviceQueryParam;
+import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
+import com.dji.sample.manage.service.IDeviceService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/15
+ */
+@RestController
+@Slf4j
+@RequestMapping("${url.manage.prefix}${url.manage.version}/devices")
+public class DeviceController {
+
+ @Autowired
+ private IDeviceService deviceService;
+
+ @Autowired
+ private ISendMessageService sendMessageService;
+
+ /**
+ * Handles the message that the drone goes online.
+ * @param receiver The drone information is not empty.
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS_ONLINE, outputChannel = ChannelName.OUTBOUND)
+ public void deviceOnline(CommonTopicReceiver<StatusGatewayReceiver> receiver) {
+ boolean online = deviceService.deviceOnline(receiver.getData());
+ if (online) {
+ // Notify pilot that the drone is online successfully.
+ deviceService.publishStatusReply(receiver.getData().getSn(),
+ CommonTopicResponse.builder()
+ .tid(receiver.getTid())
+ .bid(receiver.getBid())
+ .build());
+
+ // Publish the latest device topology information in the current workspace to the pilot.
+ deviceService.pushDeviceOnlineTopo(WorkspaceDTO.DEFAULT_WORKSPACE_ID,
+ receiver.getData().getSn(), receiver.getData().getSubDevices().get(0).getSn());
+ }
+ }
+
+ /**
+ * Handles the message that the drone goes offline.
+ * @param receiver The drone information is empty.
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS_OFFLINE, outputChannel = ChannelName.OUTBOUND)
+ public void deviceOffline(CommonTopicReceiver<StatusGatewayReceiver> receiver) {
+
+ boolean offline = deviceService.deviceOffline(receiver.getData().getSn());
+ if (offline) {
+ // Notify pilot that the device is offline successfully.
+ deviceService.publishStatusReply(receiver.getData().getSn(),
+ CommonTopicResponse.builder()
+ .tid(receiver.getTid())
+ .bid(receiver.getBid())
+ .build());
+
+ // Publish the latest device topology information in the current workspace to the pilot.
+ deviceService.pushDeviceOfflineTopo(WorkspaceDTO.DEFAULT_WORKSPACE_ID, receiver.getData().getSn());
+ }
+ }
+
+ /**
+ * Get the topology list of all devices in the current user workspace.
+ * @param request
+ * @return
+ */
+ @GetMapping("/devices")
+ public ResponseResult<List<DeviceDTO>> getDevices(HttpServletRequest request) {
+ // Get information about the current user.
+ CustomClaim claim = (CustomClaim)request.getAttribute(AuthInterceptor.TOKEN_CLAIM);
+ String workspaceId = claim.getWorkspaceId();
+ // Get information about the devices in the current user's workspace.
+ List<DeviceDTO> devicesList = deviceService.getDevicesTopoForWeb(workspaceId);
+
+ return ResponseResult.success(devicesList);
+ }
+
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_OSD)
+ public void osdRealTime(Message<?> message) {
+ String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
+ byte[] payload = (byte[])message.getPayload();
+ deviceService.handleOSD(topic, payload);
+ }
+
+ /**
+ * Handles the payloads data of the drone.
+ * @param deviceSn drone's sn
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE)
+ public void pushWebSocketDevices(String deviceSn) {
+ List<DeviceDTO> devicesList = deviceService.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .deviceSn(deviceSn)
+ .build());
+ // Get drone information based on the sn of the drone. The sn of the drone is unique.
+ DeviceDTO device = devicesList.get(0);
+ // Set the remote controller and payloads information of the drone.
+ deviceService.spliceDeviceTopo(device);
+
+ CustomWebSocketMessage wsMessage = CustomWebSocketMessage.builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.DEVICE_UPDATE_TOPO.getCode())
+ .data(device)
+ .build();
+ // Update the topology of the drone via WebSocket notifications to the web side.
+ sendMessageService.sendBatch(WebSocketManager
+ .getValueWithWorkspaceAndUserType(
+ device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
+ wsMessage);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java b/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java
new file mode 100644
index 0000000..0c0ba2c
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java
@@ -0,0 +1,66 @@
+package com.dji.sample.manage.controller;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import com.dji.sample.manage.model.receiver.DeviceBasicReceiver;
+import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
+import com.dji.sample.manage.service.IDevicePayloadService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+@RestController
+@Slf4j
+public class DevicePayloadController {
+
+ @Autowired
+ private IDevicePayloadService devicePayloadService;
+
+ /**
+ * Handles the data for the payload messages in the state topic.
+ * @param payloadsList List of payload information.
+ * @return drone's sn
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_PAYLOAD,
+ outputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE)
+ public String statePayload(List<DevicePayloadReceiver> payloadsList) {
+ // Delete all payload information for the drone based on the drone's sn.
+ devicePayloadService.deletePayloadsByDeviceSn(List.of(payloadsList.get(0).getDeviceSn()));
+
+ // Save the new payload information.
+ devicePayloadService.savePayloadDTOs(payloadsList);
+
+ log.debug("The result of saving the payload is successful.");
+
+ return payloadsList.get(0).getDeviceSn();
+ }
+
+ /**
+ * Handles messages in the state topic about basic drone data.
+ *
+ * Note: Only the data of the drone payload is handled here. You can handle other data from the drone
+ * according to your business needs.
+ * @param deviceBasic basic drone data
+ * @return drone's sn
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC,
+ outputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE)
+ public String stateBasic(DeviceBasicReceiver deviceBasic) {
+ // Delete all payload information for the drone based on the drone's sn.
+ devicePayloadService.deletePayloadsByDeviceSn(List.of(deviceBasic.getDeviceSn()));
+
+ // Save the new payload information.
+ boolean isSave = devicePayloadService.savePayloadDTOs(deviceBasic.getPayloads());
+
+ log.debug("The result of saving the payloads is {}.", isSave);
+
+ return deviceBasic.getDeviceSn();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java b/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java
new file mode 100644
index 0000000..ef89f34
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java
@@ -0,0 +1,113 @@
+package com.dji.sample.manage.controller;
+
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.component.mqtt.model.ChannelName;
+import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
+import com.dji.sample.manage.model.Chan;
+import com.dji.sample.manage.model.dto.CapacityDeviceDTO;
+import com.dji.sample.manage.model.dto.LiveTypeDTO;
+import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
+import com.dji.sample.manage.model.receiver.ServiceReplyReceiver;
+import com.dji.sample.manage.service.ILiveStreamService;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.messaging.Message;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.List;
+
+import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/19
+ */
+
+@RestController
+@Slf4j
+@RequestMapping("${url.manage.prefix}${url.manage.version}/live")
+public class LiveStreamController {
+
+ @Autowired
+ private ILiveStreamService liveStreamService;
+
+ /**
+ * Analyze the live streaming capabilities of drones.
+ * This data is necessary if drones are required for live streaming.
+ * @param device the capacity of drone
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_CAPACITY)
+ public void stateCapacity(CapacityDeviceReceiver device) {
+ boolean parseCapacity = liveStreamService.saveLiveCapacity(device);
+ log.debug("The result of parsing the live capacity is {}.", parseCapacity);
+ }
+
+ /**
+ * Get live capability data of all drones in the current user's workspace from the database.
+ * @param request
+ * @return live capability
+ */
+ @GetMapping("/capacity")
+ public ResponseResult<List<CapacityDeviceDTO>> getLiveCapacity(HttpServletRequest request) {
+ // Get information about the current user.
+ CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
+
+ List<CapacityDeviceDTO> liveCapacity = liveStreamService.getLiveCapacity(customClaim.getWorkspaceId());
+
+ return ResponseResult.success(liveCapacity);
+ }
+
+ /**
+ * Live streaming according to the parameters passed in from the web side.
+ * @param liveParam Live streaming parameters.
+ * @return
+ */
+ @PostMapping("/streams/start")
+ public ResponseResult liveStart(@RequestBody LiveTypeDTO liveParam) {
+ return liveStreamService.liveStart(liveParam);
+ }
+
+ /**
+ * Stop live streaming according to the parameters passed in from the web side.
+ * @param liveParam Live streaming parameters.
+ * @return
+ */
+ @PostMapping("/streams/stop")
+ public ResponseResult liveStop(@RequestBody LiveTypeDTO liveParam) {
+ return liveStreamService.liveStop(liveParam.getVideoId());
+ }
+
+ /**
+ * Set the quality of the live streaming according to the parameters passed in from the web side.
+ * @param liveParam Live streaming parameters.
+ * @return
+ */
+ @PostMapping("/streams/update")
+ public ResponseResult liveSetQuality(@RequestBody LiveTypeDTO liveParam) {
+ return liveStreamService.liveSetQuality(liveParam);
+ }
+
+ /**
+ * Handle the reply message from the pilot side to the on-demand video.
+ * @param message reply message
+ * @throws IOException
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_SERVICE_REPLY)
+ public void serviceReply(Message<?> message) throws IOException {
+ byte[] payload = (byte[])message.getPayload();
+ ObjectMapper mapper = new ObjectMapper();
+ CommonTopicReceiver<ServiceReplyReceiver> receiver = mapper.readValue(payload,
+ new TypeReference<CommonTopicReceiver<ServiceReplyReceiver>>() {
+ });
+ Chan<CommonTopicReceiver> chan = Chan.getInstance();
+ // Put the message to the chan object.
+ chan.put(receiver);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/controller/LoginController.java b/src/main/java/com/dji/sample/manage/controller/LoginController.java
new file mode 100644
index 0000000..b0cacb4
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/controller/LoginController.java
@@ -0,0 +1,47 @@
+package com.dji.sample.manage.controller;
+
+import com.dji.sample.common.error.CommonErrorEnum;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.manage.model.dto.UserDTO;
+import com.dji.sample.manage.model.dto.UserLoginDTO;
+import com.dji.sample.manage.service.IUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Optional;
+
+import static com.dji.sample.component.AuthInterceptor.PARAM_TOKEN;
+
+@RestController
+@RequestMapping("${url.manage.prefix}${url.manage.version}")
+public class LoginController {
+
+ @Autowired
+ private IUserService userService;
+
+ @PostMapping("/login")
+ public ResponseResult login(@RequestBody UserLoginDTO loginDTO) {
+ String username = loginDTO.getUsername();
+ String password = loginDTO.getPassword();
+ return userService.userLogin(username, password);
+ }
+
+ @PostMapping("/token/refresh")
+ public ResponseResult refreshToken(HttpServletRequest request, HttpServletResponse response) {
+ String token = request.getHeader(PARAM_TOKEN);
+ Optional<UserDTO> user = userService.refreshToken(token);
+
+ if (user.isEmpty()) {
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ return ResponseResult.error(CommonErrorEnum.NO_TOKEN.getErrorMsg());
+ }
+
+ return ResponseResult.success(user);
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/controller/TopologyController.java b/src/main/java/com/dji/sample/manage/controller/TopologyController.java
new file mode 100644
index 0000000..3f2b80d
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/controller/TopologyController.java
@@ -0,0 +1,40 @@
+package com.dji.sample.manage.controller;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.manage.model.dto.TopologyDTO;
+import com.dji.sample.manage.service.ITopologyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/8
+ */
+@RestController
+@RequestMapping("${url.manage.prefix}${url.manage.version}/workspaces")
+public class TopologyController {
+
+ @Autowired
+ private ITopologyService topologyService;
+
+ /**
+ * Get the topology list of all devices in the current user workspace for pilot display.
+ * @param workspaceId
+ * @return
+ */
+ @GetMapping("/{workspace_id}/devices/topologies")
+ public ResponseResult<Map<String, List<TopologyDTO>>> getDevicesTopologiesForPilot(
+ @PathVariable(name = "workspace_id") String workspaceId) {
+ List<TopologyDTO> topologyList = topologyService.getDeviceTopology(workspaceId);
+ return ResponseResult.success(new ConcurrentHashMap<>(Map.of("list", topologyList)));
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/manage/controller/UserController.java b/src/main/java/com/dji/sample/manage/controller/UserController.java
new file mode 100644
index 0000000..06cfb4d
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/controller/UserController.java
@@ -0,0 +1,29 @@
+package com.dji.sample.manage.controller;
+
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.manage.service.IUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
+
+
+@RestController
+@RequestMapping("${url.manage.prefix}${url.manage.version}/users")
+public class UserController {
+
+ @Autowired
+ private IUserService userService;
+
+ @GetMapping("/current")
+ public ResponseResult getCurrentUserInfo(HttpServletRequest request) {
+ CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
+ return userService.getUserByUsername(customClaim.getUsername(), customClaim.getWorkspaceId());
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/manage/controller/WorkspaceController.java b/src/main/java/com/dji/sample/manage/controller/WorkspaceController.java
new file mode 100644
index 0000000..e2f5f43
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/controller/WorkspaceController.java
@@ -0,0 +1,41 @@
+package com.dji.sample.manage.controller;
+
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.manage.model.dto.WorkspaceDTO;
+import com.dji.sample.manage.service.IWorkspaceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Optional;
+
+import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@RestController
+@RequestMapping("${url.manage.prefix}${url.manage.version}/workspaces")
+public class WorkspaceController {
+
+ @Autowired
+ private IWorkspaceService workspaceService;
+
+ /**
+ * Gets information about the workspace that the current user is in.
+ * @param request
+ * @return
+ */
+ @GetMapping("/current")
+ public ResponseResult getCurrentWorkspace(HttpServletRequest request) {
+ CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
+ Optional<WorkspaceDTO> workspaceOpt = workspaceService.getWorkspaceByWorkspaceId(customClaim.getWorkspaceId());
+
+ return workspaceOpt.isEmpty() ? ResponseResult.error() : ResponseResult.success(workspaceOpt.get());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java b/src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java
new file mode 100644
index 0000000..0784954
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java
@@ -0,0 +1,12 @@
+package com.dji.sample.manage.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.manage.model.entity.CameraVideoEntity;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/19
+ */
+public interface ICameraVideoMapper extends BaseMapper<CameraVideoEntity> {
+}
diff --git a/src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java b/src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java
new file mode 100644
index 0000000..777d6d0
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java
@@ -0,0 +1,12 @@
+package com.dji.sample.manage.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.manage.model.entity.CapacityCameraEntity;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+public interface ICapacityCameraMapper extends BaseMapper<CapacityCameraEntity> {
+}
diff --git a/src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java b/src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java
new file mode 100644
index 0000000..cce8f7e
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java
@@ -0,0 +1,13 @@
+package com.dji.sample.manage.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.manage.model.entity.DeviceDictionaryEntity;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/15
+ * @version 0.1
+ */
+public interface IDeviceDictionaryMapper extends BaseMapper<DeviceDictionaryEntity> {
+}
diff --git a/src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java b/src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java
new file mode 100644
index 0000000..758d8e5
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java
@@ -0,0 +1,14 @@
+package com.dji.sample.manage.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.manage.model.entity.DeviceEntity;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+public interface IDeviceMapper extends BaseMapper<DeviceEntity> {
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java b/src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java
new file mode 100644
index 0000000..f90231c
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java
@@ -0,0 +1,12 @@
+package com.dji.sample.manage.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.manage.model.entity.DevicePayloadEntity;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+public interface IDevicePayloadMapper extends BaseMapper<DevicePayloadEntity> {
+}
diff --git a/src/main/java/com/dji/sample/manage/dao/IUserMapper.java b/src/main/java/com/dji/sample/manage/dao/IUserMapper.java
new file mode 100644
index 0000000..a6179ae
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/dao/IUserMapper.java
@@ -0,0 +1,8 @@
+package com.dji.sample.manage.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.manage.model.entity.UserEntity;
+
+public interface IUserMapper extends BaseMapper<UserEntity> {
+
+}
diff --git a/src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java b/src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java
new file mode 100644
index 0000000..79bc240
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java
@@ -0,0 +1,8 @@
+package com.dji.sample.manage.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.manage.model.entity.WorkspaceEntity;
+
+public interface IWorkspaceMapper extends BaseMapper<WorkspaceEntity> {
+
+}
diff --git a/src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java b/src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java
new file mode 100644
index 0000000..5c51c42
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java
@@ -0,0 +1,35 @@
+package com.dji.sample.manage.handler;
+
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/2/21
+ */
+public abstract class AbstractStateTopicHandler {
+
+ protected AbstractStateTopicHandler handler;
+ protected static ObjectMapper mapper = new ObjectMapper();;
+
+ protected AbstractStateTopicHandler(AbstractStateTopicHandler handler){
+ this.handler = handler;
+ mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
+
+ /**
+ * Passing dataNode data, using different processing methods depending on the data selection.
+ * @param dataNode
+ * @param stateReceiver
+ * @param sn
+ * @return
+ * @throws JsonProcessingException
+ */
+ public abstract TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException;
+}
diff --git a/src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java b/src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java
new file mode 100644
index 0000000..42140b6
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java
@@ -0,0 +1,25 @@
+package com.dji.sample.manage.handler;
+
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/3/21
+ */
+@Service
+public class StateDefaultHandler extends AbstractStateTopicHandler {
+
+ protected StateDefaultHandler() {
+ super(null);
+ }
+
+ @Override
+ public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException {
+ // If no suitable handler is found for the data, it is not processed.
+ return stateReceiver;
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java b/src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java
new file mode 100644
index 0000000..cb773eb
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java
@@ -0,0 +1,34 @@
+package com.dji.sample.manage.handler;
+
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.manage.model.receiver.DeviceBasicReceiver;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/2/21
+ */
+@Service
+public class StateDeviceBasicHandler extends AbstractStateTopicHandler {
+
+ public StateDeviceBasicHandler(@Autowired @Qualifier("statePayloadHandler") AbstractStateTopicHandler handler) {
+ super(handler);
+ }
+
+ @Override
+ public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException {
+ // handle device basic data
+ if (dataNode.size() != 1) {
+ DeviceBasicReceiver data = mapper.treeToValue(dataNode, DeviceBasicReceiver.class);
+ data.setDeviceSn(sn);
+ stateReceiver.setData(data);
+ return stateReceiver;
+ }
+ return handler.handleState(dataNode, stateReceiver, sn);
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java b/src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java
new file mode 100644
index 0000000..9e79d6d
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java
@@ -0,0 +1,39 @@
+package com.dji.sample.manage.handler;
+
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.manage.model.enums.StateDataEnum;
+import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/2/21
+ */
+@Service
+public class StateLiveCapacityHandler extends AbstractStateTopicHandler {
+
+ private static final String DEVICE_LIST = "device_list";
+
+ protected StateLiveCapacityHandler(@Autowired @Qualifier("stateDefaultHandler") AbstractStateTopicHandler handler) {
+ super(handler);
+ }
+
+ @Override
+ public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException {
+ String name = dataNode.fieldNames().next();
+ JsonNode childNode = dataNode.findPath(name);
+ // Determine if it is live capacity data based on name.
+ if (name.equals(StateDataEnum.LIVE_CAPACITY.getDesc())) {
+ JsonNode deviceNode = childNode.findPath(DEVICE_LIST);
+ stateReceiver.setData(
+ mapper.treeToValue(deviceNode.get(0), CapacityDeviceReceiver.class));
+ return stateReceiver;
+ }
+ return handler.handleState(dataNode, stateReceiver, sn);
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java b/src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java
new file mode 100644
index 0000000..a14e0d0
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java
@@ -0,0 +1,55 @@
+package com.dji.sample.manage.handler;
+
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.manage.model.enums.StateDataEnum;
+import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/2/21
+ */
+@Service
+public class StatePayloadHandler extends AbstractStateTopicHandler {
+
+ protected StatePayloadHandler(@Autowired @Qualifier("stateLiveCapacityHandler") AbstractStateTopicHandler handler) {
+ super(handler);
+ }
+
+ @Override
+ public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException {
+ String name = dataNode.fieldNames().next();
+ JsonNode childNode = dataNode.findPath(name);
+ // Determine if it is payload data based on name.
+ if (name.equals(StateDataEnum.PAYLOADS.getDesc())) {
+ List<DevicePayloadReceiver> payloadsList = new ArrayList<>();
+
+ Iterator<JsonNode> payloadsNode = childNode.elements();
+ while (payloadsNode.hasNext()) {
+ DevicePayloadReceiver payloadReceiver = mapper.treeToValue(
+ payloadsNode.next(), DevicePayloadReceiver.class);
+ payloadReceiver.setDeviceSn(sn);
+ payloadsList.add(payloadReceiver);
+ }
+
+ if (payloadsList.isEmpty()) {
+ DevicePayloadReceiver payloadReceiver = new DevicePayloadReceiver();
+ payloadReceiver.setDeviceSn(sn);
+ payloadsList.add(payloadReceiver);
+ }
+
+ stateReceiver.setData(payloadsList);
+ return stateReceiver;
+ }
+ return handler.handleState(dataNode, stateReceiver, sn);
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/handler/StateRouter.java b/src/main/java/com/dji/sample/manage/handler/StateRouter.java
new file mode 100644
index 0000000..3fe3404
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/handler/StateRouter.java
@@ -0,0 +1,92 @@
+package com.dji.sample.manage.handler;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
+import com.dji.sample.manage.model.receiver.DeviceBasicReceiver;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.MessageEndpoint;
+import org.springframework.integration.annotation.Router;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.integration.router.MessageRouter;
+import org.springframework.integration.router.PayloadTypeRouter;
+import org.springframework.messaging.Message;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static com.dji.sample.component.mqtt.model.TopicConst.*;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/17
+ * @version 0.1
+ */
+@MessageEndpoint
+@Slf4j
+@Configuration
+public class StateRouter {
+
+ @Resource(name = "stateDeviceBasicHandler")
+ private AbstractStateTopicHandler handler;
+
+ /**
+ * Handles the routing of state topic messages. Depending on the data, it is assigned to different channels for handling.
+ * @param message
+ * @return
+ * @throws IOException
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE, outputChannel = ChannelName.INBOUND_STATE_SPLITTER)
+ public TopicStateReceiver<?> resolveStateData(Message<?> message) throws IOException {
+ byte[] payload = (byte[])message.getPayload();
+ String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ TopicStateReceiver stateReceiver = mapper.readValue(payload, TopicStateReceiver.class);
+ // Get the sn of the topic source.
+ String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(),
+ topic.indexOf(STATE_SUF));
+
+ try {
+ JsonNode dataNode = mapper.readTree(payload).findPath("data");
+ return handler.handleState(dataNode, stateReceiver, from);
+
+ } catch (UnrecognizedPropertyException e) {
+ log.info("The {} data is not processed.", e.getPropertyName());
+ }
+ return stateReceiver;
+ }
+
+ @Bean
+ @Router(inputChannel = ChannelName.INBOUND_STATE_ROUTER)
+ public MessageRouter resolveStateRouter() {
+ PayloadTypeRouter router = new PayloadTypeRouter();
+ // // Channel mapping for basic data.
+ router.setChannelMapping(DeviceBasicReceiver.class.getName(),
+ ChannelName.INBOUND_STATE_BASIC);
+ // Channel mapping for live streaming capabilities.
+ router.setChannelMapping(CapacityDeviceReceiver.class.getName(),
+ ChannelName.INBOUND_STATE_CAPACITY);
+ // Channel mapping for payload data.
+ router.setChannelMapping(List.class.getName(),
+ ChannelName.INBOUND_STATE_PAYLOAD);
+ router.setChannelMapping(Map.class.getName(),
+ ChannelName.DEFAULT);
+ return router;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/handler/StateSplitter.java b/src/main/java/com/dji/sample/manage/handler/StateSplitter.java
new file mode 100644
index 0000000..e47fd53
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/handler/StateSplitter.java
@@ -0,0 +1,59 @@
+package com.dji.sample.manage.handler;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.MessageEndpoint;
+import org.springframework.integration.annotation.Splitter;
+import org.springframework.integration.dsl.IntegrationFlow;
+import org.springframework.integration.dsl.IntegrationFlows;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/17
+ * @version 0.1
+ */
+@MessageEndpoint
+@Configuration
+public class StateSplitter {
+
+ /**
+ * Split the state message data to different channels for handling according to their different types.
+ * @param receiver state message
+ * @return
+ */
+ @Splitter(inputChannel = ChannelName.INBOUND_STATE_SPLITTER, outputChannel = ChannelName.INBOUND_STATE_ROUTER)
+ public Collection<Object> splitState(TopicStateReceiver receiver) {
+ ArrayList<Object> type = new ArrayList<>();
+ type.add(receiver.getData());
+ return type;
+ }
+
+ /**
+ * Split according to the different types in the list.
+ * @return
+ */
+ @Bean
+ public IntegrationFlow splitList() {
+ return IntegrationFlows
+ .from(ChannelName.INBOUND_STATE_LIST)
+ .split()
+ .<Object, String> route(dataType -> {
+ Class<?> clazz = dataType.getClass();
+ if (DevicePayloadReceiver.class.isAssignableFrom(clazz)) {
+ return ChannelName.INBOUND_STATE_PAYLOAD;
+ }
+ return null;
+ }, mapping -> {
+ mapping.channelMapping(ChannelName.INBOUND_STATE_PAYLOAD,
+ ChannelName.INBOUND_STATE_PAYLOAD);
+ })
+ .get();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/handler/StatusRouter.java b/src/main/java/com/dji/sample/manage/handler/StatusRouter.java
new file mode 100644
index 0000000..3fa6bc8
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/handler/StatusRouter.java
@@ -0,0 +1,70 @@
+package com.dji.sample.manage.handler;
+
+import com.dji.sample.component.mqtt.model.ChannelName;
+import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import org.springframework.integration.annotation.MessageEndpoint;
+import org.springframework.integration.annotation.Router;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.util.CollectionUtils;
+
+import static com.dji.sample.component.mqtt.model.TopicConst.*;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/12
+ * @version 0.1
+ */
+@MessageEndpoint
+public class StatusRouter {
+
+ /**
+ * Converts the status data sent by the gateway device into an object.
+ * @param message
+ * @return
+ */
+ @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS, outputChannel = ChannelName.INBOUND_STATUS_ROUTER)
+ public CommonTopicReceiver<StatusGatewayReceiver> resolveStatus(Message<?> message) {
+
+ CommonTopicReceiver<StatusGatewayReceiver> statusReceiver = new CommonTopicReceiver<>();
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
+ try {
+ statusReceiver = mapper.readValue(
+ (byte[])message.getPayload(),
+ new TypeReference<CommonTopicReceiver<StatusGatewayReceiver>>() {});
+
+ String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
+
+ // set gateway's domain
+ statusReceiver.getData().setDomain(DeviceDomainEnum.GATEWAY.getVal());
+ // set gateway's sn
+ statusReceiver.getData().setSn(
+ topic.substring((BASIC_PRE + PRODUCT).length(),
+ topic.indexOf(STATUS_SUF)));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return statusReceiver;
+ }
+
+ /**
+ * Handles the routing of status topic messages. Depending on the data, it is assigned to different channels for handling.
+ * @param receiver
+ * @return
+ */
+ @Router(inputChannel = ChannelName.INBOUND_STATUS_ROUTER)
+ public String resolveStatusRouter(CommonTopicReceiver<StatusGatewayReceiver> receiver) {
+ // Determine whether the drone is online or offline according to whether the data of the sub-device is empty.
+ return CollectionUtils.isEmpty(receiver.getData().getSubDevices()) ?
+ ChannelName.INBOUND_STATUS_OFFLINE : ChannelName.INBOUND_STATUS_ONLINE;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/Chan.java b/src/main/java/com/dji/sample/manage/model/Chan.java
new file mode 100644
index 0000000..5aa7961
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/Chan.java
@@ -0,0 +1,45 @@
+package com.dji.sample.manage.model;
+
+import java.util.concurrent.locks.LockSupport;
+
+/**
+ * The demo is only for functional closure, which is not recommended.
+ * @author sean.zhou
+ * @date 2021/11/22
+ * @version 0.1
+ */
+public class Chan<T> {
+
+ private static final long THREAD_WAIT_TIME = 1000_000 * 500;
+
+ private volatile T data;
+
+ private volatile Thread t;
+
+ private Chan () {
+
+ }
+
+ public static Chan getInstance() {
+ return ChanSingleton.INSTANCE;
+ }
+
+ public T get(Object blocker) {
+ this.t = Thread.currentThread();
+ LockSupport.parkNanos(blocker, THREAD_WAIT_TIME);
+ this.t = null;
+ return data;
+ }
+
+ public void put(T data) {
+ this.data = data;
+ if (t == null) {
+ return;
+ }
+ LockSupport.unpark(t);
+ }
+
+ private static class ChanSingleton {
+ private static final Chan<?> INSTANCE = new Chan<>();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java b/src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java
new file mode 100644
index 0000000..2bc6960
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java
@@ -0,0 +1,20 @@
+package com.dji.sample.manage.model;
+
+import java.time.LocalDateTime;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * The demo is only for functional closure, which is not recommended,
+ * and it is recommended to use caching for handling.
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/25
+ */
+public class DeviceStatusManager {
+
+ public static final ConcurrentHashMap<String, LocalDateTime> STATUS_MANAGER =
+ new ConcurrentHashMap<>(16);
+
+ public static final Integer DEFAULT_ALIVE_SECOND = 30;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java b/src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java
new file mode 100644
index 0000000..dbc6481
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java
@@ -0,0 +1,34 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/22
+ * @version 0.1
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CapacityCameraDTO {
+
+ private Integer id;
+
+ private String deviceSn;
+
+ private String name;
+
+ private String description;
+
+ private String index;
+
+ private String type;
+
+ private List<CapacityVideoDTO> videosList;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java
new file mode 100644
index 0000000..efa0bcc
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java
@@ -0,0 +1,26 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/22
+ * @version 0.1
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CapacityDeviceDTO {
+
+ private String sn;
+
+ private String name;
+
+ private List<CapacityCameraDTO> camerasList;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java b/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java
new file mode 100644
index 0000000..1d9dc3a
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java
@@ -0,0 +1,24 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/22
+ * @version 0.1
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CapacityVideoDTO {
+
+ private Integer id;
+
+ private String index;
+
+ private String type;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java
new file mode 100644
index 0000000..2571dff
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java
@@ -0,0 +1,47 @@
+package com.dji.sample.manage.model.dto;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class DeviceDTO {
+
+ private String deviceSn;
+
+ private String deviceName;
+
+ private String workspaceId;
+
+ private String deviceIndex;
+
+ private String deviceDesc;
+
+ private String childDeviceSn;
+
+ private String domain;
+
+ private Integer type;
+
+ private Integer subType;
+
+ private List<DeviceDTO> gatewaysList;
+
+ private List<DevicePayloadDTO> payloadsList;
+
+ private IconUrlDTO iconUrl;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java
new file mode 100644
index 0000000..9c0170d
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java
@@ -0,0 +1,28 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class DeviceDictionaryDTO {
+
+ private Integer domain;
+
+ private Integer deviceType;
+
+ private Integer subType;
+
+ private String deviceName;
+
+ private String deviceDesc;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java
new file mode 100644
index 0000000..d7559ff
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java
@@ -0,0 +1,27 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/8
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class DeviceModelDTO {
+
+ private String key;
+
+ private String domain;
+
+ private String type;
+
+ private String subType;
+
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java
new file mode 100644
index 0000000..bb16641
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java
@@ -0,0 +1,29 @@
+package com.dji.sample.manage.model.dto;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class DevicePayloadDTO {
+
+ private String payloadSn;
+
+ private String payloadName;
+
+ private Integer payloadIndex;
+
+ private String payloadDesc;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java b/src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java
new file mode 100644
index 0000000..effbf1c
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java
@@ -0,0 +1,25 @@
+package com.dji.sample.manage.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/1/5
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class IconUrlDTO {
+
+ @JsonProperty("normal_icon_url")
+ private String normalUrl;
+
+ @JsonProperty("selected_icon_url")
+ private String selectUrl;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java
new file mode 100644
index 0000000..29841c2
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java
@@ -0,0 +1,20 @@
+package com.dji.sample.manage.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class LiveDTO {
+
+ private String url;
+
+ private String username;
+
+ private String password;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java
new file mode 100644
index 0000000..ffe341e
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java
@@ -0,0 +1,26 @@
+package com.dji.sample.manage.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * Receive live parameters.
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/22
+ */
+@Data
+public class LiveTypeDTO {
+
+ @JsonProperty("url_type")
+ private Integer urlType;
+
+ private String url;
+
+ @JsonProperty("video_id")
+ private String videoId;
+
+ @JsonProperty("video_quality")
+ private Integer videoQuality;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java
new file mode 100644
index 0000000..1f9f781
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java
@@ -0,0 +1,20 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/23
+ * @version 0.1
+ */
+@Data
+public class LiveUrlAgoraDTO {
+
+ private String channel;
+
+ private String sn;
+
+ private String token;
+
+ private Integer uid;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java
new file mode 100644
index 0000000..d57f06a
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java
@@ -0,0 +1,27 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@Data
+public class LiveUrlGB28181DTO {
+
+ private String serverIP;
+
+ private Integer serverPort;
+
+ private String serverID;
+
+ private String agentID;
+
+ private String agentPassword;
+
+ private Integer localPort;
+
+ private String channel;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java
new file mode 100644
index 0000000..8e57734
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java
@@ -0,0 +1,18 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@Data
+public class LiveUrlRTSPDTO {
+
+ private String userName;
+
+ private String password;
+
+ private Integer port;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java b/src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java
new file mode 100644
index 0000000..8f7f9d5
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java
@@ -0,0 +1,22 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/8
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class TelemetryDTO {
+
+ private TelemetryDeviceDTO host;
+
+ private String sn;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java
new file mode 100644
index 0000000..1905804
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java
@@ -0,0 +1,32 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/8
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TelemetryDeviceDTO {
+
+ private Double latitude;
+
+ private Double longitude;
+
+ private Double altitude;
+
+ private Float attitudeHead;
+
+ private Double elevation;
+
+ private Float horizontalSpeed;
+
+ private Float verticalSpeed;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java b/src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java
new file mode 100644
index 0000000..d91cd3a
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java
@@ -0,0 +1,24 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/8
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TopologyDTO {
+
+ private List<TopologyDeviceDTO> hosts;
+
+ private List<TopologyDeviceDTO> parents;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java
new file mode 100644
index 0000000..c36e88d
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java
@@ -0,0 +1,35 @@
+package com.dji.sample.manage.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/8
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class TopologyDeviceDTO {
+
+ private String sn;
+
+ private DeviceModelDTO deviceModel;
+
+ @Builder.Default
+ private Boolean onlineStatus = true;
+
+ private String deviceCallsign;
+
+ private String userId;
+
+ private String userCallsign;
+
+ private IconUrlDTO iconUrls;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/UserDTO.java b/src/main/java/com/dji/sample/manage/model/dto/UserDTO.java
new file mode 100644
index 0000000..1e67c72
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/UserDTO.java
@@ -0,0 +1,37 @@
+package com.dji.sample.manage.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class UserDTO {
+
+ @JsonProperty("user_id")
+ private String userId;
+
+ private String username;
+
+ @JsonProperty("workspace_id")
+ private String workspaceId;
+
+ @JsonProperty("user_type")
+ private Integer userType;
+
+ @JsonProperty("mqtt_username")
+ private String mqttUsername;
+
+ @JsonProperty("mqtt_password")
+ private String mqttPassword;
+
+ @JsonProperty("access_token")
+ private String accessToken;
+
+ @JsonProperty("mqtt_addr")
+ private String mqttAddr;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java b/src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java
new file mode 100644
index 0000000..b82ca44
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java
@@ -0,0 +1,14 @@
+package com.dji.sample.manage.model.dto;
+
+import lombok.Data;
+import lombok.NonNull;
+
+@Data
+public class UserLoginDTO {
+
+ @NonNull
+ private String username;
+
+ @NonNull
+ private String password;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java
new file mode 100644
index 0000000..0ce7d15
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java
@@ -0,0 +1,33 @@
+package com.dji.sample.manage.model.dto;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/22
+ * @version 0.1
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class WorkspaceDTO {
+
+ public static final String DEFAULT_WORKSPACE_ID = "e3dea0f5-37f2-4d79-ae58-490af3228069";
+
+ private Integer id;
+
+ private String workspaceId;
+
+ private String workspaceName;
+
+ private String workspaceDesc;
+
+ private String platformName;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java b/src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java
new file mode 100644
index 0000000..a2e7269
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java
@@ -0,0 +1,37 @@
+package com.dji.sample.manage.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+@TableName(value = "manage_camera_video")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class CameraVideoEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField(value = "camera_id")
+ private Integer cameraId;
+
+ @TableField(value = "video_index")
+ private String videoIndex;
+
+ @TableField(value = "video_type")
+ private String videoType;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java b/src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java
new file mode 100644
index 0000000..c57f496
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java
@@ -0,0 +1,46 @@
+package com.dji.sample.manage.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "manage_capacity_camera")
+public class CapacityCameraEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField(value = "device_sn")
+ private String deviceSn;
+
+ @TableField(value = "name")
+ private String name;
+
+ @TableField(value = "description")
+ private String description;
+
+ @TableField(value = "camera_index")
+ private String cameraIndex;
+
+ @TableField(value = "coexist_video_number_max")
+ private Integer coexistVideoNumberMax;
+
+ @TableField(value = "available_video_number")
+ private Integer availableVideoNumber;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java b/src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java
new file mode 100644
index 0000000..c01ec6f
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java
@@ -0,0 +1,45 @@
+package com.dji.sample.manage.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/15
+ * @version 0.1
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName("manage_device_dictionary")
+public class DeviceDictionaryEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField(value = "domain")
+ private Integer domain;
+
+ @TableField(value = "device_type")
+ private Integer deviceType;
+
+ @TableField(value = "sub_type")
+ private Integer subType;
+
+ @TableField(value = "device_name")
+ private String deviceName;
+
+ @TableField(value = "device_desc")
+ private String deviceDesc;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java b/src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java
new file mode 100644
index 0000000..f019a01
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java
@@ -0,0 +1,69 @@
+package com.dji.sample.manage.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * The entity class of the device
+ *
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/10
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName(value = "manage_device")
+public class DeviceEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField(value = "device_sn")
+ private String deviceSn;
+
+ @TableField(value = "device_name")
+ private String deviceName;
+
+ @TableField(value = "workspace_id")
+ private String workspaceId;
+
+ @TableField(value = "device_type")
+ private Integer deviceType;
+
+ @TableField(value = "sub_type")
+ private Integer subType;
+
+ @TableField(value = "domain")
+ private Integer domain;
+
+ @TableField(value = "version")
+ private Integer version;
+
+ @TableField(value = "device_index")
+ private String deviceIndex;
+
+ @TableField(value = "child_sn")
+ private String childSn;
+
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Long createTime;
+
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Long updateTime;
+
+ @TableField(value = "device_desc")
+ private String deviceDesc;
+
+ @TableField(value = "url_normal")
+ private String urlNormal;
+
+ @TableField(value = "url_select")
+ private String urlSelect;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java b/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java
new file mode 100644
index 0000000..184fb0f
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java
@@ -0,0 +1,56 @@
+package com.dji.sample.manage.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "manage_device_payload")
+public class DevicePayloadEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField(value = "payload_sn")
+ private String payloadSn;
+
+ @TableField(value = "payload_name")
+ private String payloadName;
+
+ @TableField(value = "payload_type")
+ private Integer payloadType;
+
+ @TableField(value = "sub_type")
+ private Integer subType;
+
+ @TableField(value = "version")
+ private Integer version;
+
+ @TableField(value = "payload_index")
+ private Integer payloadIndex;
+
+ @TableField(value = "device_sn")
+ private String deviceSn;
+
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Long createTime;
+
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Long updateTime;
+
+ @TableField(value = "payload_desc")
+ private String payloadDesc;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/entity/UserEntity.java b/src/main/java/com/dji/sample/manage/model/entity/UserEntity.java
new file mode 100644
index 0000000..67edecf
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/entity/UserEntity.java
@@ -0,0 +1,41 @@
+package com.dji.sample.manage.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@TableName(value = "manage_user")
+@Data
+public class UserEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField(value = "user_id")
+ private String userId;
+
+ @TableField(value = "username")
+ private String username;
+
+ @TableField(value = "password")
+ private String password;
+
+ @TableField(value = "workspace_id")
+ private Integer workspaceId;
+
+ @TableField(value = "user_type")
+ private Integer userType;
+
+ @TableField(value = "mqtt_username")
+ private String mqttUsername;
+
+ @TableField(value = "mqtt_password")
+ private String mqttPassword;
+
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Long createTime;
+
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Long updateTime;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java b/src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java
new file mode 100644
index 0000000..8a84ff0
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java
@@ -0,0 +1,33 @@
+package com.dji.sample.manage.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@TableName(value = "manage_workspace")
+@Data
+public class WorkspaceEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField(value = "workspace_id")
+ private String workspaceId;
+
+ @TableField(value = "workspace_name")
+ private String workspaceName;
+
+ @TableField(value = "workspace_desc")
+ private String workspaceDesc;
+
+ @TableField(value = "platform_name")
+ private String platformName;
+
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Long createTime;
+
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Long updateTime;
+
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java
new file mode 100644
index 0000000..cd8fd39
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java
@@ -0,0 +1,63 @@
+package com.dji.sample.manage.model.enums;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/15
+ * @version 0.1
+ */
+public enum DeviceDomainEnum {
+
+ SUB_DEVICE(0, "sub-device"),
+
+ GATEWAY(2, "gateway"),
+
+ PAYLOAD(1, "payload"),
+
+ UNKNOWN(-1, "unknown");
+
+ private int val;
+
+ private String desc;
+
+ DeviceDomainEnum(int val, String desc) {
+ this.val = val;
+ this.desc = desc;
+ }
+
+ public int getVal() {
+ return val;
+ }
+
+ public static String getDesc(int val) {
+ if (SUB_DEVICE.val == val) {
+ return SUB_DEVICE.desc;
+ }
+
+ if (GATEWAY.val == val) {
+ return GATEWAY.desc;
+ }
+
+ if (PAYLOAD.val == val) {
+ return PAYLOAD.desc;
+ }
+ return UNKNOWN.desc;
+ }
+
+ public static int getVal(String desc) {
+ if (SUB_DEVICE.desc.equals(desc)) {
+ return SUB_DEVICE.val;
+ }
+
+ if (GATEWAY.desc.equals(desc)) {
+ return GATEWAY.val;
+ }
+
+ if (PAYLOAD.desc.equals(desc)) {
+ return PAYLOAD.val;
+ }
+ return UNKNOWN.val;
+ }
+
+
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java b/src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java
new file mode 100644
index 0000000..3803d2f
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java
@@ -0,0 +1,37 @@
+package com.dji.sample.manage.model.enums;
+
+/**
+ * The system icon that comes with the pilot.
+ * @author sean
+ * @version 0.3
+ * @date 2022/1/5
+ */
+public enum IconUrlEnum {
+
+ SELECT_CAR("resource://pilot/drawable/tsa_car_select"),
+
+ NORMAL_CAR("resource://pilot/drawable/tsa_car_normal"),
+
+ SELECT_PERSON("resource://pilot/drawable/tsa_person_select"),
+
+ NORMAL_PERSON("resource://pilot/drawable/tsa_person_normal"),
+
+ SELECT_EQUIPMENT("resource://pilot/drawable/tsa_equipment_select"),
+
+ NORMAL_EQUIPMENT("resource://pilot/drawable/tsa_equipment_normal");
+
+ /**
+ * You can use icons from the web, and the App internally downloads and caches these icons and
+ * loads them at a fixed size (28dp) to display on the map.
+ * Example: http://r56978dr7.hn-bkt.clouddn.com/tsa_equipment_normal.png
+ */
+ private String url;
+
+ IconUrlEnum(String url) {
+ this.url = url;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java b/src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java
new file mode 100644
index 0000000..eebe35c
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java
@@ -0,0 +1,27 @@
+package com.dji.sample.manage.model.enums;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/22
+ * @version 0.1
+ */
+public enum LiveMethodEnum {
+
+ LIVE_START_PUSH("live_start_push"),
+
+ LIVE_STOP_PUSH("live_stop_push"),
+
+ LIVE_SET_QUALITY("live_set_quality"),
+
+ UNKNOWN("unknown");
+
+ private String method;
+
+ LiveMethodEnum(String method) {
+ this.method = method;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java b/src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java
new file mode 100644
index 0000000..163b520
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java
@@ -0,0 +1,41 @@
+package com.dji.sample.manage.model.enums;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/22
+ */
+public enum LiveUrlTypeEnum {
+
+ AGORA(0),
+
+ RTMP(1),
+
+ RTSP(2),
+
+ GB28181(3),
+
+ UNKNOWN(-1);
+
+ private int val;
+
+ LiveUrlTypeEnum(int val) {
+ this.val = val;
+ }
+
+ public static LiveUrlTypeEnum find(Integer val) {
+ if (AGORA.val == val) {
+ return AGORA;
+ }
+ if (RTMP.val == val) {
+ return RTMP;
+ }
+ if (RTSP.val == val) {
+ return RTSP;
+ }
+ if (GB28181.val == val) {
+ return GB28181;
+ }
+ return UNKNOWN;
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java b/src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java
new file mode 100644
index 0000000..3ad42d3
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java
@@ -0,0 +1,51 @@
+package com.dji.sample.manage.model.enums;
+
+/**
+ * @author sean
+ * @version 0.1
+ * @date 2021/11/26
+ */
+public enum LiveVideoQualityEnum {
+
+ AUTO (0),
+
+ SMOOTH(1),
+
+ STANDARD_DEFINITION(2),
+
+ HIGH_DEFINITION(3),
+
+ ULTRA_HD(4),
+
+ UNKNOWN(-1);
+
+ private int val;
+
+ LiveVideoQualityEnum(int val) {
+ this.val = val;
+ }
+
+ public static LiveVideoQualityEnum find(int val) {
+ if (AUTO.val == val) {
+ return AUTO;
+ }
+
+ if (SMOOTH.val == val) {
+ return SMOOTH;
+ }
+
+ if (STANDARD_DEFINITION.val == val) {
+ return STANDARD_DEFINITION;
+ }
+
+ if (HIGH_DEFINITION.val == val) {
+ return HIGH_DEFINITION;
+ }
+
+ if (ULTRA_HD.val == val) {
+ return ULTRA_HD;
+ }
+
+ return UNKNOWN;
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java b/src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java
new file mode 100644
index 0000000..5a93cf3
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java
@@ -0,0 +1,26 @@
+package com.dji.sample.manage.model.enums;
+
+/**
+ *
+ * @author sean.zhou
+ * @date 2021/11/18
+ * @version 0.1
+ */
+public enum StateDataEnum {
+
+ BATTERIES("batteries"),
+
+ LIVE_CAPACITY("live_capacity"),
+
+ PAYLOADS("payloads");
+
+ private String desc;
+
+ StateDataEnum(String desc) {
+ this.desc = desc;
+ }
+
+ public String getDesc() {
+ return this.desc;
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java b/src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java
new file mode 100644
index 0000000..311669e
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java
@@ -0,0 +1,24 @@
+package com.dji.sample.manage.model.enums;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/2
+ */
+public enum UserTypeEnum {
+
+ WEB(1),
+
+ PILOT(2);
+
+ private int val;
+
+
+ UserTypeEnum(int val) {
+ this.val = val;
+ }
+
+ public int getVal() {
+ return val;
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java b/src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java
new file mode 100644
index 0000000..9233121
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java
@@ -0,0 +1,32 @@
+package com.dji.sample.manage.model.param;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * The object of the device query field.
+ *
+ * @author sean.zhou
+ * @date 2021/11/16
+ * @version 0.1
+ */
+@Data
+@Builder
+public class DeviceQueryParam {
+
+ private String deviceSn;
+
+ private String workspaceId;
+
+ private Integer deviceType;
+
+ private Integer subType;
+
+ private Integer domain;
+
+ private String childSn;
+
+ private boolean orderBy;
+
+ private boolean isAsc;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java
new file mode 100644
index 0000000..dc0162f
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java
@@ -0,0 +1,27 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/1/27
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class BatteryReceiver {
+
+ private List<BatteryStateReceiver> batteries;
+
+ private Integer capacityPercent;
+
+ private Integer landingPower;
+
+ private Integer remainFlightTime;
+
+ private Integer returnHomePower;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java
new file mode 100644
index 0000000..275adc1
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java
@@ -0,0 +1,34 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/24
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class BatteryStateReceiver {
+
+ private String firmwareVersion;
+
+ private Integer index;
+
+ private Integer loopTimes;
+
+ private Integer capacityPercent;
+
+ private String sn;
+
+ private Integer subType;
+
+ private Float temperature;
+
+ private Integer type;
+
+ private Integer voltage;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java
new file mode 100644
index 0000000..e5321fb
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java
@@ -0,0 +1,28 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/18
+ * @version 0.1
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class CapacityCameraReceiver {
+
+ private Integer availableVideoNumber;
+
+ private Integer coexistVideoNumberMax;
+
+ private String cameraIndex;
+
+ @JsonProperty(value = "video_list")
+ private List<CapacityVideoReceiver> videosList;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java
new file mode 100644
index 0000000..750db59
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java
@@ -0,0 +1,25 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/18
+ * @version 0.1
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class CapacityDeviceReceiver {
+
+ private String sn;
+
+ private Integer availableVideoNumber;
+
+ private Integer coexistVideoNumberMax;
+
+ private List<CapacityCameraReceiver> cameraList;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java
new file mode 100644
index 0000000..616b5a9
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java
@@ -0,0 +1,19 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/18
+ * @version 0.1
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class CapacityVideoReceiver {
+
+ private String videoIndex;
+
+ private String videoType;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java
new file mode 100644
index 0000000..1ae46d8
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java
@@ -0,0 +1,39 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/18
+ * @version 0.1
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class DeviceBasicReceiver {
+
+ private String controlSource;
+
+ private String deviceSn;
+
+ private Integer flightMode;
+
+ private Integer flightStatus;
+
+ private Double homeLatitude;
+
+ private Double homeLongitude;
+
+ private Integer lowBatteryWarningThreshold;
+
+ private Integer positionMode;
+
+ private Integer seriousLowBatteryWarningThreshold;
+
+ private List<DevicePayloadReceiver> payloads;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java
new file mode 100644
index 0000000..2b3f998
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java
@@ -0,0 +1,29 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/18
+ * @version 0.1
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class DevicePayloadReceiver {
+
+ private String deviceSn;
+
+ private String controlSource;
+
+ private String payloadIndex;
+
+ private String sn;
+
+ private Integer version;
+
+ private Integer workMode;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java
new file mode 100644
index 0000000..463cbf8
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java
@@ -0,0 +1,23 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class LiveStatusReceiver {
+
+ private Long liveTime;
+
+ private Integer liveTrendline;
+
+ private String videoId;
+
+ private Integer videoQuality;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java
new file mode 100644
index 0000000..4e0a299
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java
@@ -0,0 +1,31 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class OsdGatewayReceiver {
+
+ private Double latitude;
+
+ private Double longitude;
+
+ @JsonProperty("capacity_percent")
+ private Integer remainPower;
+
+ private Integer transmissionSignalQuality;
+
+ private LiveStatusReceiver liveStatus;
+
+ private WirelessLinkStateReceiver wirelessLinkState;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java
new file mode 100644
index 0000000..24d101e
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java
@@ -0,0 +1,55 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class OsdSubDeviceReceiver {
+
+ private Float attitudeHead;
+
+ private Double attitudePitch;
+
+ private Double attitudeRoll;
+
+ private Double elevation;
+
+ private BatteryReceiver battery;
+
+ private String firmwareVersion;
+
+ private Integer gear;
+
+ private Double height;
+
+ private Double homeDistance;
+
+ private Float horizontalSpeed;
+
+ private Double latitude;
+
+ private Double longitude;
+
+ private Integer modeCode;
+
+ private Double totalFlightDistance;
+
+ private Double totalFlightTime;
+
+ private Float verticalSpeed;
+
+ private Double windDirection;
+
+ private Double windSpeed;
+
+ private PositionStateReceiver positionState;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java
new file mode 100644
index 0000000..5c6787f
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java
@@ -0,0 +1,23 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/1/27
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class PositionStateReceiver {
+
+ private Integer gpsNumber;
+
+ private Integer isFixed;
+
+ private Integer quality;
+
+ private Integer rtkNumber;
+}
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java
new file mode 100644
index 0000000..c877c0c
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java
@@ -0,0 +1,28 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/24
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class RTKStateReceiver {
+
+ private Integer bdsNumber;
+
+ private Integer galNumber;
+
+ private Integer gloNumber;
+
+ private Integer gpsNumber;
+
+ private Boolean isFixed;
+
+ private Integer quality;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java
new file mode 100644
index 0000000..ece269f
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java
@@ -0,0 +1,18 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/22
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ServiceReplyReceiver<T> {
+
+ private Integer result;
+
+ private String info;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java
new file mode 100644
index 0000000..0cee5ed
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java
@@ -0,0 +1,43 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ *
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/12
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class StatusGatewayReceiver {
+
+ private String sn;
+
+ private Integer domain;
+
+ private Integer type;
+
+ @JsonProperty(value = "sub_type")
+ private Integer subType;
+
+ @JsonProperty(value = "device_secret")
+ private String deviceSecret;
+
+ private String nonce;
+
+ private Integer version;
+
+ @JsonProperty(value = "sub_devices")
+ private List<StatusSubDeviceReceiver> subDevices;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java
new file mode 100644
index 0000000..4f0d47a
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java
@@ -0,0 +1,32 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ *
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/12
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class StatusSubDeviceReceiver {
+
+ private String sn;
+
+ private Integer type;
+
+ @JsonProperty(value = "sub_type")
+ private Integer subType;
+
+ private String index;
+
+ @JsonProperty(value = "device_secret")
+ private String deviceSecret;
+
+ private String nonce;
+
+ private Integer version;
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java
new file mode 100644
index 0000000..5dc8bd5
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java
@@ -0,0 +1,22 @@
+package com.dji.sample.manage.model.receiver;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/23
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class WirelessLinkStateReceiver {
+
+ private Integer downloadQuality;
+
+ private Integer frequencyBand;
+
+ private Integer upwardQuality;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/ICameraVideoService.java b/src/main/java/com/dji/sample/manage/service/ICameraVideoService.java
new file mode 100644
index 0000000..808d6dd
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/ICameraVideoService.java
@@ -0,0 +1,37 @@
+package com.dji.sample.manage.service;
+
+import com.dji.sample.manage.model.dto.CapacityVideoDTO;
+import com.dji.sample.manage.model.receiver.CapacityVideoReceiver;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+public interface ICameraVideoService {
+
+ /**
+ * Queries all lens data contained in the camera based on camera id.
+ * @param cameraId
+ * @return
+ */
+ List<CapacityVideoDTO> getCameraVideosByCameraId(Integer cameraId);
+
+ /**
+ * Deletes all the data of this lens, according to the lens id.
+ * @param ids A collection of lens ids.
+ * @return true
+ */
+ Boolean deleteCameraVideosById(List<Integer> ids);
+
+ /**
+ * Save the live capability of all lenses of this camera.
+ * @param capacityVideoReceivers live capability of lens
+ * @param cameraId
+ * @return
+ */
+ Boolean saveCameraVideoDTOList(List<CapacityVideoReceiver> capacityVideoReceivers, Integer cameraId);
+
+}
diff --git a/src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java b/src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java
new file mode 100644
index 0000000..02ab494
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java
@@ -0,0 +1,45 @@
+package com.dji.sample.manage.service;
+
+import com.dji.sample.manage.model.dto.CapacityCameraDTO;
+import com.dji.sample.manage.model.receiver.CapacityCameraReceiver;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+public interface ICapacityCameraService {
+
+ /**
+ * Query all camera data that can be live streamed from this device based on the device sn.
+ * @param deviceSn
+ * @return
+ */
+ List<CapacityCameraDTO> getCapacityCameraByDeviceSn(String deviceSn);
+
+ /**
+ * Query whether this camera data has been saved based on the device sn and camera location.
+ * @param deviceSn
+ * @param cameraIndex
+ * @return
+ */
+ Boolean checkExist(String deviceSn, String cameraIndex);
+
+ /**
+ * Delete all live capability data for this device based on the device sn.
+ * @param deviceSn
+ * @return
+ */
+ Boolean deleteCapacityCameraByDeviceSn(String deviceSn);
+
+ /**
+ * Save the live capability data of the device.
+ * @param capacityCameraReceivers
+ * @param deviceSn
+ * @return
+ */
+ Boolean saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn);
+
+}
diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java b/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java
new file mode 100644
index 0000000..65324ac
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java
@@ -0,0 +1,24 @@
+package com.dji.sample.manage.service;
+
+import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
+
+import java.util.Optional;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/15
+ */
+public interface IDeviceDictionaryService {
+
+ /**
+ * Query the type data of the device based on domain, device type and sub type.
+ * @param domain
+ * @param deviceType
+ * @param subType
+ * @return
+ */
+ Optional<DeviceDictionaryDTO> getOneDictionaryInfoByDomainTypeSubType(
+ Integer domain, Integer deviceType, Integer subType);
+
+}
diff --git a/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java b/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java
new file mode 100644
index 0000000..2eb5c0e
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java
@@ -0,0 +1,48 @@
+package com.dji.sample.manage.service;
+
+import com.dji.sample.manage.model.dto.DevicePayloadDTO;
+import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+public interface IDevicePayloadService {
+
+ /**
+ * Query if the payload has been saved based on the sn of the payload.
+ * @param payloadSn
+ * @return
+ */
+ Integer checkPayloadExist(String payloadSn);
+
+ /**
+ * Save all payload data.
+ * @param payloadReceiverList
+ * @return
+ */
+ Boolean savePayloadDTOs(List<DevicePayloadReceiver> payloadReceiverList);
+
+ /**
+ * Save a payload data.
+ * @param payloadReceiver
+ * @return
+ */
+ Integer saveOnePayloadDTO(DevicePayloadReceiver payloadReceiver);
+
+ /**
+ * Query all payload data on this device based on the device sn.
+ * @param deviceSn
+ * @return
+ */
+ List<DevicePayloadDTO> getDevicePayloadEntitiesByDeviceSn(String deviceSn);
+
+ /**
+ * Delete all payload data on these devices based on the collection of device sns.
+ * @param deviceSns
+ */
+ void deletePayloadsByDeviceSn(List<String> deviceSns);
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceService.java b/src/main/java/com/dji/sample/manage/service/IDeviceService.java
new file mode 100644
index 0000000..444d49d
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/IDeviceService.java
@@ -0,0 +1,135 @@
+package com.dji.sample.manage.service;
+
+import com.dji.sample.component.mqtt.model.CommonTopicResponse;
+import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
+import com.dji.sample.manage.model.dto.DeviceDTO;
+import com.dji.sample.manage.model.dto.TopologyDeviceDTO;
+import com.dji.sample.manage.model.param.DeviceQueryParam;
+import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/10
+ * @version 0.1
+ */
+public interface IDeviceService {
+
+ /**
+ * The device goes online.
+ * @param deviceGateway gateway
+ * @return Whether the online is successful.
+ */
+ Boolean deviceOnline(StatusGatewayReceiver deviceGateway);
+
+ /**
+ * The device goes offline.
+ * @param gatewaySn
+ * @return Whether the offline is successful.
+ */
+ Boolean deviceOffline(String gatewaySn);
+
+ /**
+ * The aircraft goes offline.
+ * @param deviceSn aircraft's SN
+ * @return Whether the offline is successful.
+ */
+ Boolean subDeviceOffline(String deviceSn);
+
+ /**
+ * When the device goes online, it needs to subscribe to topics.
+ * @param sn device's SN
+ */
+ void subscribeTopicOnline(String sn);
+
+ /**
+ * When the device goes offine, it needs to cancel the subscribed topics.
+ * @param sn device's SN
+ */
+ void unsubscribeTopicOffline(String sn);
+
+ /**
+ * Delete all device data according to the SN of the device.
+ * @param ids device's SN
+ * @return
+ */
+ Boolean delDeviceByDeviceSns(List<String> ids);
+
+ /**
+ * Obtain device data according to different query conditions.
+ * @param param query parameters
+ * @return
+ */
+ List<DeviceDTO> getDevicesByParams(DeviceQueryParam param);
+
+ /**
+ * When you receive a status topic message, you need to reply to it.
+ * @param sn the target of sn
+ * @param response
+ */
+ void publishStatusReply(String sn, CommonTopicResponse<Object> response);
+
+ /**
+ * The business interface on the web side. Get all information about all devices in this workspace.
+ * @param workspaceId
+ * @return
+ */
+ List<DeviceDTO> getDevicesTopoForWeb(String workspaceId);
+
+ /**
+ * Set the remote controller and payloads information of the drone.
+ * @param device
+ */
+ void spliceDeviceTopo(DeviceDTO device);
+
+ /**
+ * Push the topology information to the pilot after one device is online.
+ * @param sessions The collection of connection objects on the pilot side.
+ * @param sn
+ */
+ void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn);
+
+ /**
+ * Query the information of the device according to the sn of the device.
+ * @param sn device sn
+ * @return
+ */
+ Optional<TopologyDeviceDTO> getDeviceTopoForPilot(String sn);
+
+ /**
+ * Convert individual device information into topology objects.
+ * @param device
+ * @return
+ */
+ TopologyDeviceDTO deviceConvertToTopologyDTO(DeviceDTO device);
+
+ /**
+ * When the server receives the request of any device online, offline and topology update in the same workspace,
+ * it also broadcasts a push of device online, offline and topology update to PILOT via websocket,
+ * and PILOT will get the device topology list again after receiving the push.
+ * @param workspaceId
+ * @param gatewaySn
+ */
+ void pushDeviceOfflineTopo(String workspaceId, String gatewaySn);
+
+ /**
+ * When the server receives the request of any device online, offline and topology update in the same workspace,
+ * it also broadcasts a push of device online, offline and topology update to PILOT via websocket,
+ * and PILOT will get the device topology list again after receiving the push.
+ * @param workspaceId
+ * @param deviceSn
+ * @param gatewaySn
+ */
+ void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn);
+
+ /**
+ * Handle messages from the osd topic.
+ * @param topic osd
+ * @param payload
+ */
+ void handleOSD(String topic, byte[] payload);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java b/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java
new file mode 100644
index 0000000..09cba9e
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java
@@ -0,0 +1,51 @@
+package com.dji.sample.manage.service;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.manage.model.dto.CapacityDeviceDTO;
+import com.dji.sample.manage.model.dto.LiveTypeDTO;
+import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
+
+import java.util.List;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+public interface ILiveStreamService {
+
+ /**
+ * Get all the drone data that can be broadcast live in this workspace.
+ * @param workspaceId
+ * @return
+ */
+ List<CapacityDeviceDTO> getLiveCapacity(String workspaceId);
+
+ /**
+ * Save live capability data from drone.
+ * @param capacityDeviceReceiver
+ * @return
+ */
+ Boolean saveLiveCapacity(CapacityDeviceReceiver capacityDeviceReceiver);
+
+ /**
+ * Initiate a live streaming by publishing mqtt message.
+ * @param liveParam Parameters needed for on-demand.
+ * @return
+ */
+ ResponseResult liveStart(LiveTypeDTO liveParam);
+
+ /**
+ * Stop the live streaming by publishing mqtt message.
+ * @param videoId
+ * @return
+ */
+ ResponseResult liveStop(String videoId);
+
+ /**
+ * Readjust the clarity of the live streaming by publishing mqtt messages.
+ * @param liveParam
+ * @return
+ */
+ ResponseResult liveSetQuality(LiveTypeDTO liveParam);
+}
diff --git a/src/main/java/com/dji/sample/manage/service/ITSAService.java b/src/main/java/com/dji/sample/manage/service/ITSAService.java
new file mode 100644
index 0000000..a444f92
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/ITSAService.java
@@ -0,0 +1,17 @@
+package com.dji.sample.manage.service;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/2/21
+ */
+public interface ITSAService {
+
+ /**
+ * Real-time push telemetry data.
+ * @param workspaceId
+ * @param osdData
+ * @param sn
+ */
+ void pushTelemetryData(String workspaceId, Object osdData, String sn);
+}
diff --git a/src/main/java/com/dji/sample/manage/service/ITopologyService.java b/src/main/java/com/dji/sample/manage/service/ITopologyService.java
new file mode 100644
index 0000000..e75a857
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/ITopologyService.java
@@ -0,0 +1,20 @@
+package com.dji.sample.manage.service;
+
+import com.dji.sample.manage.model.dto.TopologyDTO;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/8
+ */
+public interface ITopologyService {
+
+ /**
+ * Get the topology list of all devices in the workspace for pilot display.
+ * @param workspaceId
+ * @return
+ */
+ List<TopologyDTO> getDeviceTopology(String workspaceId);
+}
diff --git a/src/main/java/com/dji/sample/manage/service/IUserService.java b/src/main/java/com/dji/sample/manage/service/IUserService.java
new file mode 100644
index 0000000..2ceff96
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/IUserService.java
@@ -0,0 +1,32 @@
+package com.dji.sample.manage.service;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.manage.model.dto.UserDTO;
+
+import java.util.Optional;
+
+public interface IUserService {
+
+ /**
+ * Query user's details based on username.
+ * @param username
+ * @param workspaceId
+ * @return
+ */
+ ResponseResult getUserByUsername(String username, String workspaceId);
+
+ /**
+ * Verify the username and password to log in.
+ * @param username
+ * @param password
+ * @return
+ */
+ ResponseResult userLogin(String username, String password);
+
+ /**
+ * Create a user object containing a new token.
+ * @param token
+ * @return
+ */
+ Optional<UserDTO> refreshToken(String token);
+}
diff --git a/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java b/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java
new file mode 100644
index 0000000..5defa10
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java
@@ -0,0 +1,23 @@
+package com.dji.sample.manage.service;
+
+
+import com.dji.sample.manage.model.dto.WorkspaceDTO;
+
+import java.util.Optional;
+
+public interface IWorkspaceService {
+
+ /**
+ * Query the workspace information based on the primary key id of the database.
+ * @param id primary key id
+ * @return
+ */
+ Optional<WorkspaceDTO> getWorkspaceById(int id);
+
+ /**
+ * Query the information of a workspace based on its workspace id.
+ * @param workspaceId
+ * @return
+ */
+ Optional<WorkspaceDTO> getWorkspaceByWorkspaceId(String workspaceId);
+}
diff --git a/src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java b/src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java
new file mode 100644
index 0000000..35597fa
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java
@@ -0,0 +1,59 @@
+package com.dji.sample.manage.service.impl;
+
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
+import com.dji.sample.component.websocket.model.BizCodeEnum;
+import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
+import com.dji.sample.component.websocket.model.WebSocketManager;
+import com.dji.sample.component.websocket.service.ISendMessageService;
+import com.dji.sample.manage.model.dto.TelemetryDTO;
+import com.dji.sample.manage.model.enums.UserTypeEnum;
+import com.dji.sample.manage.service.ITSAService;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Collection;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/2/21
+ */
+public abstract class AbstractTSAService implements ITSAService {
+
+ protected AbstractTSAService tsaService;
+
+ public AbstractTSAService(AbstractTSAService tsaService) {
+ this.tsaService = tsaService;
+ }
+
+ @Autowired
+ protected ISendMessageService sendMessageService;
+
+ @Override
+ public void pushTelemetryData(String workspaceId, Object osdData, String sn) {
+ // All connected accounts on the pilot side of this workspace.
+ Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
+ .getValueWithWorkspaceAndUserType(
+ workspaceId, UserTypeEnum.PILOT.getVal());
+
+ TelemetryDTO telemetry = TelemetryDTO.builder()
+ .sn(sn)
+ .build();
+ CustomWebSocketMessage<TelemetryDTO> pilotMessage = CustomWebSocketMessage.<TelemetryDTO>builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.DEVICE_OSD.getCode())
+ .data(telemetry)
+ .build();
+
+ this.pushTelemetryData(pilotSessions, pilotMessage, osdData);
+ }
+
+ public abstract void pushTelemetryData(Collection<ConcurrentWebSocketSession> sessions,
+ CustomWebSocketMessage<TelemetryDTO> message, Object Object);
+
+ protected abstract void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode,
+ Collection<ConcurrentWebSocketSession> webSessions, CustomWebSocketMessage wsMessage)
+ throws JsonProcessingException;
+}
diff --git a/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java
new file mode 100644
index 0000000..a2196e4
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java
@@ -0,0 +1,99 @@
+package com.dji.sample.manage.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.manage.dao.ICameraVideoMapper;
+import com.dji.sample.manage.model.dto.CapacityVideoDTO;
+import com.dji.sample.manage.model.entity.CameraVideoEntity;
+import com.dji.sample.manage.model.receiver.CapacityVideoReceiver;
+import com.dji.sample.manage.service.ICameraVideoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/19
+ */
+@Service
+@Transactional
+public class CameraVideoServiceImpl implements ICameraVideoService {
+
+ @Autowired
+ private ICameraVideoMapper mapper;
+
+ @Override
+ public List<CapacityVideoDTO> getCameraVideosByCameraId(Integer cameraId) {
+ return mapper.selectList(
+ new LambdaQueryWrapper<CameraVideoEntity>()
+ .eq(CameraVideoEntity::getCameraId, cameraId))
+ .stream()
+ .map(this::entityConvertToDto)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Boolean deleteCameraVideosById(List<Integer> ids) {
+ if (ids.isEmpty()) {
+ return true;
+ }
+ return mapper.deleteBatchIds(ids) > 0;
+ }
+
+ @Override
+ public Boolean saveCameraVideoDTOList(List<CapacityVideoReceiver> capacityVideoReceivers, Integer cameraId) {
+ for (CapacityVideoReceiver videoDTO : capacityVideoReceivers) {
+ CameraVideoEntity videoEntity = videoDTOConvertToEntity(videoDTO);
+ videoEntity.setCameraId(cameraId);
+ int saveId = this.saveOneCameraVideoEntity(videoEntity);
+ if (saveId <= 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Save the live capability of the lens of this camera.
+ * @param entity lens data
+ * @return
+ */
+ private Integer saveOneCameraVideoEntity(CameraVideoEntity entity) {
+ return mapper.insert(entity) > 0 ? entity.getId() : 0;
+ }
+
+ /**
+ * Convert the received lens capability object into a database entity object.
+ * @param dto received lens object
+ * @return entity
+ */
+ private CameraVideoEntity videoDTOConvertToEntity(CapacityVideoReceiver dto) {
+ CameraVideoEntity.CameraVideoEntityBuilder builder = CameraVideoEntity.builder();
+ if (dto != null) {
+ builder
+ .videoIndex(dto.getVideoIndex())
+ .videoType(dto.getVideoType());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Convert database entity objects into lens data transfer object.
+ * @param entity
+ * @return data transfer object
+ */
+ private CapacityVideoDTO entityConvertToDto(CameraVideoEntity entity) {
+ CapacityVideoDTO.CapacityVideoDTOBuilder builder = CapacityVideoDTO.builder();
+
+ if (entity != null) {
+ builder
+ .id(entity.getId())
+ .index(entity.getVideoIndex())
+ .type(entity.getVideoType());
+ }
+ return builder.build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java
new file mode 100644
index 0000000..7ac4fc4
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java
@@ -0,0 +1,172 @@
+package com.dji.sample.manage.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.manage.dao.ICapacityCameraMapper;
+import com.dji.sample.manage.model.dto.CapacityCameraDTO;
+import com.dji.sample.manage.model.dto.CapacityVideoDTO;
+import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
+import com.dji.sample.manage.model.entity.CapacityCameraEntity;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.receiver.CapacityCameraReceiver;
+import com.dji.sample.manage.service.ICameraVideoService;
+import com.dji.sample.manage.service.ICapacityCameraService;
+import com.dji.sample.manage.service.IDeviceDictionaryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/19
+ * @version 0.1
+ */
+@Service
+@Transactional
+public class CapacityCameraServiceImpl implements ICapacityCameraService {
+
+ @Autowired
+ private ICapacityCameraMapper mapper;
+
+ @Autowired
+ private ICameraVideoService cameraVideoService;
+
+ @Autowired
+ private IDeviceDictionaryService dictionaryService;
+
+ @Override
+ public List<CapacityCameraDTO> getCapacityCameraByDeviceSn(String deviceSn) {
+ List<CapacityCameraDTO> capacityCamerasList = mapper.selectList(
+ new LambdaQueryWrapper<CapacityCameraEntity>()
+ .eq(CapacityCameraEntity::getDeviceSn, deviceSn))
+ .stream()
+ .map(this::entityConvertToDto)
+ .collect(Collectors.toList());
+ capacityCamerasList.forEach(capacityCamera -> {
+ // Set the lens data for this camera.
+ capacityCamera.setVideosList(
+ cameraVideoService.getCameraVideosByCameraId(capacityCamera.getId()));
+ });
+
+ return capacityCamerasList;
+ }
+
+ @Override
+ public Boolean checkExist(String deviceSn, String cameraIndex) {
+ return mapper.selectOne(
+ new LambdaQueryWrapper<CapacityCameraEntity>()
+ .eq(CapacityCameraEntity::getDeviceSn, deviceSn)
+ .eq(CapacityCameraEntity::getCameraIndex, cameraIndex)
+ .last(" limit 1")) != null;
+ }
+
+ @Override
+ public Boolean deleteCapacityCameraByDeviceSn(String deviceSn) {
+
+ List<CapacityCameraDTO> capacityCamerasList = this.getCapacityCameraByDeviceSn(deviceSn);
+ // Return directly if no data exists in the database.
+ if (capacityCamerasList.isEmpty()) {
+ return true;
+ }
+
+ List<Integer> cameraIds = capacityCamerasList
+ .stream()
+ .map(CapacityCameraDTO::getId)
+ .collect(Collectors.toList());
+
+ List<Integer> videoIds = capacityCamerasList
+ .stream()
+ .flatMap(camera -> camera.getVideosList().stream())
+ .map(CapacityVideoDTO::getId)
+ .collect(Collectors.toList());
+
+ return mapper.deleteBatchIds(cameraIds) > 0 && cameraVideoService.deleteCameraVideosById(videoIds);
+ }
+
+ @Override
+ public Boolean saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn) {
+ for (CapacityCameraReceiver cameraDTO : capacityCameraReceivers) {
+ CapacityCameraEntity cameraEntity = receiverConvertToEntity(cameraDTO);
+ cameraEntity.setDeviceSn(deviceSn);
+ int cameraId = this.saveOneCapacityCameraEntity(cameraEntity);
+ if (cameraId <= 0) {
+ continue;
+ }
+ boolean saveVideo = cameraVideoService.saveCameraVideoDTOList(
+ cameraDTO.getVideosList(), cameraId);
+
+ if (!saveVideo) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Save the camera live capability data of the device.
+ * @param cameraEntity
+ * @return
+ */
+ private Integer saveOneCapacityCameraEntity(CapacityCameraEntity cameraEntity) {
+ boolean exist = checkExist(
+ cameraEntity.getDeviceSn(), cameraEntity.getCameraIndex());
+ if (exist) {
+ return -1;
+ }
+ return mapper.insert(cameraEntity) > 0 ? cameraEntity.getId() : 0;
+ }
+
+ /**
+ * Convert the received camera capability object into a database entity object.
+ * @param receiver
+ * @return
+ */
+ private CapacityCameraEntity receiverConvertToEntity(CapacityCameraReceiver receiver) {
+ CapacityCameraEntity.CapacityCameraEntityBuilder builder = CapacityCameraEntity.builder();
+ if (receiver == null) {
+ return builder.build();
+ }
+ int[] indexArr = Arrays.stream(receiver.getCameraIndex().split("-"))
+ .map(Integer::valueOf)
+ .mapToInt(Integer::intValue)
+ .toArray();
+ // The cameraIndex consists of type and subType and the index of the payload hanging on the drone.
+ // type-subType-index
+ if (indexArr.length == 3) {
+ Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
+ .getOneDictionaryInfoByDomainTypeSubType(
+ DeviceDomainEnum.PAYLOAD.getVal(), indexArr[0], indexArr[1]);
+ dictionaryOpt.ifPresent(dictionary ->
+ builder.name(dictionary.getDeviceName()));
+ }
+ return builder
+ .availableVideoNumber(receiver.getAvailableVideoNumber())
+ .coexistVideoNumberMax(receiver.getCoexistVideoNumberMax())
+ .cameraIndex(receiver.getCameraIndex())
+ .build();
+ }
+
+ /**
+ * Convert database entity objects into camera data transfer object.
+ * @param entity
+ * @return
+ */
+ private CapacityCameraDTO entityConvertToDto(CapacityCameraEntity entity) {
+ CapacityCameraDTO.CapacityCameraDTOBuilder builder = CapacityCameraDTO.builder();
+
+ if (entity != null) {
+ builder
+ .id(entity.getId())
+ .name(entity.getName())
+ .description(entity.getDescription())
+ .deviceSn(entity.getDeviceSn())
+ .index(entity.getCameraIndex())
+ .build();
+ }
+ return builder.build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java
new file mode 100644
index 0000000..60da3d5
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java
@@ -0,0 +1,59 @@
+package com.dji.sample.manage.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.manage.dao.IDeviceDictionaryMapper;
+import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
+import com.dji.sample.manage.model.entity.DeviceDictionaryEntity;
+import com.dji.sample.manage.service.IDeviceDictionaryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
+
+/**
+ *
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/15
+ */
+@Service
+@Transactional
+public class DeviceDictionaryServiceImpl implements IDeviceDictionaryService {
+
+ @Autowired
+ private IDeviceDictionaryMapper mapper;
+
+ @Override
+ public Optional<DeviceDictionaryDTO> getOneDictionaryInfoByDomainTypeSubType(
+ Integer domain, Integer deviceType, Integer subType) {
+ if (domain == null || deviceType == null || subType == null) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(
+ entityConvertToDTO(
+ mapper.selectOne(
+ new LambdaQueryWrapper<DeviceDictionaryEntity>()
+ .eq(DeviceDictionaryEntity::getDomain, domain)
+ .eq(DeviceDictionaryEntity::getDeviceType, deviceType)
+ .eq(DeviceDictionaryEntity::getSubType, subType))));
+ }
+
+ /**
+ * Convert database entity objects into dictionary data transfer object.
+ * @param entity
+ * @return
+ */
+ private DeviceDictionaryDTO entityConvertToDTO(DeviceDictionaryEntity entity) {
+ DeviceDictionaryDTO.DeviceDictionaryDTOBuilder builder = DeviceDictionaryDTO.builder();
+
+ if (entity != null) {
+ builder.deviceName(entity.getDeviceName())
+ .deviceDesc(entity.getDeviceDesc())
+ .deviceType(entity.getDeviceType())
+ .domain(entity.getDomain())
+ .subType(entity.getSubType());
+ }
+ return builder.build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java
new file mode 100644
index 0000000..606343c
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java
@@ -0,0 +1,69 @@
+package com.dji.sample.manage.service.impl;
+
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
+import com.dji.sample.component.websocket.model.BizCodeEnum;
+import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
+import com.dji.sample.manage.model.DeviceStatusManager;
+import com.dji.sample.manage.model.dto.TelemetryDTO;
+import com.dji.sample.manage.model.dto.TelemetryDeviceDTO;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/2/21
+ */
+@Service
+public class DeviceOSDServiceImpl extends AbstractTSAService {
+
+ protected DeviceOSDServiceImpl() {
+ super(null);
+ }
+
+ @Override
+ public void pushTelemetryData(Collection<ConcurrentWebSocketSession> sessions,
+ CustomWebSocketMessage<TelemetryDTO> message, Object osdData) {
+ if (osdData instanceof OsdSubDeviceReceiver) {
+ OsdSubDeviceReceiver data = (OsdSubDeviceReceiver) osdData;
+ TelemetryDTO telemetry = message.getData();
+ telemetry.setHost(TelemetryDeviceDTO.builder()
+ .latitude(data.getLatitude())
+ .longitude(data.getLongitude())
+ .altitude(data.getElevation())
+ .attitudeHead(data.getAttitudeHead())
+ .elevation(data.getElevation())
+ .horizontalSpeed(data.getHorizontalSpeed())
+ .verticalSpeed(data.getVerticalSpeed())
+ .build());
+
+ this.sendMessageService.sendBatch(sessions, message);
+ }
+ }
+ @Override
+ protected void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode,
+ Collection<ConcurrentWebSocketSession> webSessions, CustomWebSocketMessage wsMessage) throws JsonProcessingException {
+ // Real-time update of device status in memory
+ DeviceStatusManager.STATUS_MANAGER.put(
+ DeviceDomainEnum.SUB_DEVICE.getVal() + "/" + sn, LocalDateTime.now());
+ wsMessage.setBizCode(BizCodeEnum.DEVICE_OSD.getCode());
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ OsdSubDeviceReceiver data = mapper.treeToValue(hostNode, OsdSubDeviceReceiver.class);
+
+ wsMessage.setData(data);
+
+ sendMessageService.sendBatch(webSessions, wsMessage);
+ this.pushTelemetryData(workspaceId, data, sn);
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java
new file mode 100644
index 0000000..a88504f
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java
@@ -0,0 +1,157 @@
+package com.dji.sample.manage.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.manage.dao.IDevicePayloadMapper;
+import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
+import com.dji.sample.manage.model.dto.DevicePayloadDTO;
+import com.dji.sample.manage.model.entity.DevicePayloadEntity;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
+import com.dji.sample.manage.service.ICapacityCameraService;
+import com.dji.sample.manage.service.IDeviceDictionaryService;
+import com.dji.sample.manage.service.IDevicePayloadService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/19
+ */
+@Service
+@Transactional
+public class DevicePayloadServiceImpl implements IDevicePayloadService {
+
+ @Autowired
+ private IDevicePayloadMapper mapper;
+
+ @Autowired
+ private IDeviceDictionaryService dictionaryService;
+
+ @Autowired
+ private ICapacityCameraService capacityCameraService;
+
+ @Override
+ public Integer checkPayloadExist(String payloadSn) {
+ DevicePayloadEntity devicePayload = mapper.selectOne(
+ new LambdaQueryWrapper<DevicePayloadEntity>()
+ .eq(DevicePayloadEntity::getPayloadSn, payloadSn));
+ return devicePayload != null ? devicePayload.getId() : -1;
+ }
+
+ private Integer saveOnePayloadEntity(DevicePayloadEntity entity) {
+ int id = this.checkPayloadExist(entity.getPayloadSn());
+ // If it already exists, update the data directly.
+ if (id > 0) {
+ entity.setId(id);
+ return mapper.updateById(entity);
+ }
+ return mapper.insert(entity) > 0 ? entity.getId() : 0;
+ }
+
+ @Override
+ public Boolean savePayloadDTOs(List<DevicePayloadReceiver> payloadReceiverList) {
+ for (DevicePayloadReceiver payloadReceiver : payloadReceiverList) {
+ int payloadId = this.saveOnePayloadDTO(payloadReceiver);
+ if (payloadId <= 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Integer saveOnePayloadDTO(DevicePayloadReceiver payloadReceiver) {
+ return this.saveOnePayloadEntity(payloadDTOConvertToEntity(payloadReceiver));
+ }
+
+ @Override
+ public List<DevicePayloadDTO> getDevicePayloadEntitiesByDeviceSn(String deviceSn) {
+ return mapper.selectList(
+ new LambdaQueryWrapper<DevicePayloadEntity>()
+ .eq(DevicePayloadEntity::getDeviceSn, deviceSn))
+ .stream()
+ .map(this::payloadEntityConvertToDTO)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void deletePayloadsByDeviceSn(List<String> deviceSns) {
+ deviceSns.forEach(deviceSn -> {
+ mapper.delete(
+ new LambdaQueryWrapper<DevicePayloadEntity>()
+ .eq(DevicePayloadEntity::getDeviceSn, deviceSn));
+ capacityCameraService.deleteCapacityCameraByDeviceSn(deviceSn);
+ });
+ }
+
+ /**
+ * Convert database entity objects into payload data transfer object.
+ * @param entity
+ * @return
+ */
+ private DevicePayloadDTO payloadEntityConvertToDTO(DevicePayloadEntity entity) {
+ DevicePayloadDTO.DevicePayloadDTOBuilder builder = DevicePayloadDTO.builder();
+ if (entity != null) {
+ builder.payloadSn(entity.getPayloadSn())
+ .payloadName(entity.getPayloadName())
+ .payloadDesc(entity.getPayloadDesc())
+ .payloadIndex(entity.getPayloadIndex());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Convert the received payload object into a database entity object.
+ * @param dto payload
+ * @return
+ */
+ private DevicePayloadEntity payloadDTOConvertToEntity(DevicePayloadReceiver dto) {
+ if (dto == null) {
+ return new DevicePayloadEntity();
+ }
+ DevicePayloadEntity.DevicePayloadEntityBuilder builder = DevicePayloadEntity.builder();
+
+ // The cameraIndex consists of type and subType and the index of the payload hanging on the drone.
+ // type-subType-index
+ String[] payloadIndexArr = dto.getPayloadIndex().split("-");
+ try {
+ int[] arr = Arrays.stream(payloadIndexArr)
+ .map(Integer::valueOf)
+ .mapToInt(Integer::intValue)
+ .toArray();
+
+ if (arr.length == 3) {
+ Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
+ .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(),
+ arr[0], arr[1]);
+ dictionaryOpt.ifPresent(dictionary ->
+ builder.payloadName(dictionary.getDeviceName())
+ .payloadDesc(dictionary.getDeviceDesc()));
+
+ }
+ builder.payloadType(arr[0])
+ .subType(arr[1])
+ .payloadIndex(arr[2]);
+ } catch (NumberFormatException e) {
+ builder.payloadType(Integer.valueOf(payloadIndexArr[0]))
+ .subType(-1)
+ .payloadIndex(Integer.valueOf(payloadIndexArr[2]));
+ }
+
+ return builder
+ .payloadSn(dto.getSn())
+ .version(dto.getVersion())
+ .deviceSn(dto.getSn()
+ .substring(0,
+ dto.getSn().indexOf("-")))
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
new file mode 100644
index 0000000..80e34df
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
@@ -0,0 +1,553 @@
+package com.dji.sample.manage.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.component.mqtt.model.CommonTopicResponse;
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.component.mqtt.service.IMessageSenderService;
+import com.dji.sample.component.mqtt.service.IMqttTopicService;
+import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
+import com.dji.sample.component.websocket.model.BizCodeEnum;
+import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
+import com.dji.sample.component.websocket.model.WebSocketManager;
+import com.dji.sample.component.websocket.service.ISendMessageService;
+import com.dji.sample.manage.dao.IDeviceMapper;
+import com.dji.sample.manage.model.dto.*;
+import com.dji.sample.manage.model.entity.DeviceEntity;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.enums.IconUrlEnum;
+import com.dji.sample.manage.model.enums.UserTypeEnum;
+import com.dji.sample.manage.model.param.DeviceQueryParam;
+import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
+import com.dji.sample.manage.model.receiver.StatusSubDeviceReceiver;
+import com.dji.sample.manage.service.IDeviceDictionaryService;
+import com.dji.sample.manage.service.IDevicePayloadService;
+import com.dji.sample.manage.service.IDeviceService;
+import com.dji.sample.manage.service.IWorkspaceService;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static com.dji.sample.component.mqtt.model.TopicConst.*;
+
+/**
+ *
+ * @author sean.zhou
+ * @version 0.1
+ * @date 2021/11/10
+ */
+@Service
+@Slf4j
+@Transactional
+public class DeviceServiceImpl implements IDeviceService {
+
+ @Autowired
+ private IMessageSenderService messageSender;
+
+ @Autowired
+ private IDeviceMapper mapper;
+
+ @Autowired
+ private IDeviceDictionaryService dictionaryService;
+
+ @Autowired
+ private IMqttTopicService topicService;
+
+ @Autowired
+ private IWorkspaceService workspaceService;
+
+ @Autowired
+ private IDevicePayloadService payloadService;
+
+ @Autowired
+ private ISendMessageService sendMessageService;
+
+ @Autowired
+ @Qualifier("gatewayOSDServiceImpl")
+ private AbstractTSAService tsaService;
+
+ @Override
+ public Boolean deviceOffline(String gatewaySn) {
+ List<DeviceDTO> gatewaysList = this.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .deviceSn(gatewaySn)
+ .build());
+
+ // If no information about this gateway device exists in the database, the drone is considered to be offline.
+ if (gatewaysList.isEmpty()) {
+ log.debug("The drone is already offline.");
+ return true;
+ }
+ // Handle the drone connected to the gateway device offline.
+ return this.subDeviceOffline(gatewaysList.get(0).getChildDeviceSn());
+ }
+
+ @Override
+ public Boolean subDeviceOffline(String deviceSn) {
+ // Cancel drone-related subscriptions.
+ this.unsubscribeTopicOffline(deviceSn);
+
+ List<DeviceDTO> devicesList = this.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .deviceSn(deviceSn)
+ .build());
+
+ // If no information about this drone exists in the database, the drone is considered to be offline.
+ if (devicesList.isEmpty()) {
+ log.debug("{} is already offline.", deviceSn);
+ return true;
+ }
+
+ List<String> ids = devicesList.stream()
+ .map(DeviceDTO::getDeviceSn)
+ .collect(Collectors.toList());
+
+ // Delete all data related to the drone.
+ boolean isDel = this.delDeviceByDeviceSns(ids);
+ payloadService.deletePayloadsByDeviceSn(ids);
+
+ log.debug("{} offline status: {}.", deviceSn, isDel);
+ return isDel;
+ }
+
+ @Override
+ public Boolean deviceOnline(StatusGatewayReceiver deviceGateway) {
+ String deviceSn = deviceGateway.getSubDevices().get(0).getSn();
+
+ List<DeviceDTO> devicesList = this.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .deviceSn(deviceSn)
+ .build());
+ // If the information about this drone exists in the database, the drone is considered to be online.
+ if (!devicesList.isEmpty()) {
+ log.warn("{} is already online.", deviceSn);
+ // Subscribe to topic related to drone and gateway devices.
+ this.subscribeTopicOnline(deviceGateway.getSn());
+ this.subscribeTopicOnline(deviceSn);
+ return true;
+ }
+
+ // Delete the gateway device information that was previously bound to the drone.
+ this.delDeviceByDeviceSns(
+ this.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .childSn(deviceSn)
+ .build())
+ .stream()
+ .map(DeviceDTO::getDeviceSn)
+ .collect(Collectors.toList()));
+
+ DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway);
+ gateway.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID);
+ // Set the icon of the gateway device displayed in the pilot's map, required in the TSA module.
+ gateway.setUrlNormal(IconUrlEnum.NORMAL_PERSON.getUrl());
+ // Set the icon of the gateway device displayed in the pilot's map when it is selected, required in the TSA module.
+ gateway.setUrlSelect(IconUrlEnum.SELECT_PERSON.getUrl());
+
+ DeviceEntity subDevice = subDeviceConvertToDeviceEntity(deviceGateway.getSubDevices().get(0));
+ subDevice.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID);
+ // Set the icon of the drone device displayed in the pilot's map, required in the TSA module.
+ subDevice.setUrlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl());
+ // Set the icon of the drone device displayed in the pilot's map when it is selected, required in the TSA module.
+ subDevice.setUrlNormal(IconUrlEnum.NORMAL_EQUIPMENT.getUrl());
+
+ gateway.setChildSn(subDevice.getDeviceSn());
+
+ boolean isSave = this.saveDevice(gateway) > 0 && this.saveDevice(subDevice) > 0;
+
+ log.debug(subDevice.getDeviceSn() + " online status: {}", isSave);
+ if (isSave) {
+ // Subscribe to topic related to drone and gateway devices.
+ this.subscribeTopicOnline(subDevice.getDeviceSn());
+ this.subscribeTopicOnline(gateway.getDeviceSn());
+
+ }
+ return isSave;
+ }
+
+ @Override
+ public void subscribeTopicOnline(String sn) {
+ String[] subscribedTopic = topicService.getSubscribedTopic();
+ for (String s : subscribedTopic) {
+ // If you have already subscribed to the topic of the device, you do not need to subscribe again.
+ if (s.contains(sn)) {
+ return;
+ }
+ }
+ topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF);
+ topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF);
+ topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF);
+ }
+
+ @Override
+ public void unsubscribeTopicOffline(String sn) {
+ topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF);
+ topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF);
+ topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF);
+ }
+
+ @Override
+ public Boolean delDeviceByDeviceSns(List<String> ids) {
+ if (CollectionUtils.isEmpty(ids)) {
+ return true;
+ }
+ return mapper.delete(new LambdaQueryWrapper<DeviceEntity>()
+ .in(DeviceEntity::getDeviceSn, ids))
+ > 0;
+ }
+
+ @Override
+ public void publishStatusReply(String sn, CommonTopicResponse<Object> response) {
+ Map<String, Integer> result = new ConcurrentHashMap<>(1);
+ result.put("result", 0);
+ response.setData(result);
+
+ messageSender.publish(
+ new StringBuilder()
+ .append(BASIC_PRE)
+ .append(PRODUCT)
+ .append(sn)
+ .append(STATUS_SUF)
+ .append(_REPLY_SUF)
+ .toString(),
+ response);
+ }
+
+ @Override
+ public List<DeviceDTO> getDevicesByParams(DeviceQueryParam param) {
+ return mapper.selectList(
+ new LambdaQueryWrapper<DeviceEntity>()
+ .eq(StringUtils.hasText(param.getDeviceSn()),
+ DeviceEntity::getDeviceSn, param.getDeviceSn())
+ .eq(param.getDeviceType() != null,
+ DeviceEntity::getDeviceType, param.getDeviceType())
+ .eq(param.getSubType() != null,
+ DeviceEntity::getSubType, param.getSubType())
+ .eq(StringUtils.hasText(param.getChildSn()),
+ DeviceEntity::getChildSn, param.getChildSn())
+ .eq(param.getDomain() != null,
+ DeviceEntity::getDomain, param.getDomain())
+ .eq(StringUtils.hasText(param.getWorkspaceId()),
+ DeviceEntity::getWorkspaceId, param.getWorkspaceId())
+ .orderBy(param.isOrderBy(),
+ param.isAsc(), DeviceEntity::getId))
+ .stream()
+ .map(this::deviceEntityConvertToDTO)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<DeviceDTO> getDevicesTopoForWeb(String workspaceId) {
+ List<DeviceDTO> devicesList = this.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .workspaceId(workspaceId)
+ .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
+ .build());
+
+ devicesList.forEach(device -> {
+ this.spliceDeviceTopo(device);
+ device.setWorkspaceId(workspaceId);
+
+ });
+ return devicesList;
+ }
+
+ @Override
+ public void spliceDeviceTopo(DeviceDTO device) {
+
+ // remote controller
+ List<DeviceDTO> gatewaysList = getDevicesByParams(
+ DeviceQueryParam.builder()
+ .childSn(device.getDeviceSn())
+ .build());
+
+ // payloads
+ List<DevicePayloadDTO> payloadsList = payloadService
+ .getDevicePayloadEntitiesByDeviceSn(device.getDeviceSn());
+
+
+ device.setGatewaysList(gatewaysList);
+ device.setPayloadsList(payloadsList);
+
+ }
+
+ @Override
+ public Optional<TopologyDeviceDTO> getDeviceTopoForPilot(String sn) {
+ List<TopologyDeviceDTO> topologyDeviceList = this.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .deviceSn(sn)
+ .build())
+ .stream()
+ .map(this::deviceConvertToTopologyDTO)
+ .collect(Collectors.toList());
+ if (topologyDeviceList.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(topologyDeviceList.get(0));
+ }
+
+ @Override
+ public void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn) {
+
+ CustomWebSocketMessage<TopologyDeviceDTO> pilotMessage =
+ CustomWebSocketMessage.<TopologyDeviceDTO>builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.DEVICE_ONLINE.getCode())
+ .data(new TopologyDeviceDTO())
+ .build();
+
+ this.getDeviceTopoForPilot(sn)
+ .ifPresent(pilotMessage::setData);
+
+ sendMessageService.sendBatch(sessions, pilotMessage);
+ }
+
+ @Override
+ public TopologyDeviceDTO deviceConvertToTopologyDTO(DeviceDTO device) {
+ TopologyDeviceDTO.TopologyDeviceDTOBuilder builder = TopologyDeviceDTO.builder();
+
+ if (device != null) {
+ String domain = String.valueOf(DeviceDomainEnum.getVal(device.getDomain()));
+ String subType = String.valueOf(device.getSubType());
+ String type = String.valueOf(device.getType());
+
+ builder.sn(device.getDeviceSn())
+ .deviceCallsign(device.getDeviceName())
+ .deviceModel(DeviceModelDTO.builder()
+ .domain(domain)
+ .subType(subType)
+ .type(type)
+ .key(domain + "-" + type + "-" + subType)
+ .build())
+ .iconUrls(device.getIconUrl())
+ .build();
+ }
+ return builder.build();
+ }
+
+ @Override
+ public void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn) {
+
+ // All connected accounts on the pilot side of this workspace.
+ Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
+ .getValueWithWorkspaceAndUserType(
+ workspaceId, UserTypeEnum.PILOT.getVal());
+
+ this.pushDeviceOnlineTopo(pilotSessions, deviceSn);
+ this.pushDeviceOnlineTopo(pilotSessions, gatewaySn);
+ this.pushDeviceUpdateTopo(pilotSessions, deviceSn);
+ this.pushDeviceUpdateTopo(pilotSessions, gatewaySn);
+ }
+
+ @Override
+ public void pushDeviceOfflineTopo(String workspaceId, String gatewaySn) {
+ // All connected accounts on the pilot side of this workspace.
+ Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
+ .getValueWithWorkspaceAndUserType(
+ workspaceId, UserTypeEnum.PILOT.getVal());
+
+
+ List<DeviceDTO> gatewaysList = this.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .deviceSn(gatewaySn)
+ .build());
+
+ if (!gatewaysList.isEmpty()) {
+ String deviceSn = gatewaysList.get(0).getChildDeviceSn();
+ this.pushDeviceOfflineTopo(pilotSessions, deviceSn);
+ this.pushDeviceUpdateTopo(pilotSessions, deviceSn);
+ }
+
+ this.pushDeviceOfflineTopo(pilotSessions, gatewaySn);
+ this.pushDeviceUpdateTopo(pilotSessions, gatewaySn);
+ }
+
+ @Override
+ public void handleOSD(String topic, byte[] payload) {
+ TopicStateReceiver receiver;
+ try {
+ String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(),
+ topic.indexOf(OSD_SUF));
+
+ List<DeviceDTO> deviceList = this.getDevicesByParams(
+ DeviceQueryParam.builder().deviceSn(from).build());
+ if (deviceList.isEmpty()) {
+ return;
+ }
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ receiver = mapper.readValue(payload, TopicStateReceiver.class);
+
+ CustomWebSocketMessage wsMessage = CustomWebSocketMessage.builder()
+ .timestamp(System.currentTimeMillis()).build();
+
+ JsonNode hostNode = mapper.readTree(payload).findPath("data");
+
+ String workspaceId = deviceList.get(0).getWorkspaceId();
+ Collection<ConcurrentWebSocketSession> webSessions = WebSocketManager
+ .getValueWithWorkspaceAndUserType(
+ workspaceId, UserTypeEnum.WEB.getVal());
+
+
+ tsaService.handleOSD(receiver, from, workspaceId, hostNode, webSessions, wsMessage);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Notify the pilot side that there is an update of the device topology.
+ * @param sessions
+ * @param deviceSn
+ */
+ private void pushDeviceUpdateTopo(Collection<ConcurrentWebSocketSession> sessions, String deviceSn) {
+
+ CustomWebSocketMessage pilotMessage =
+ CustomWebSocketMessage.builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.DEVICE_UPDATE_TOPO.getCode())
+ .data(new TopologyDeviceDTO())
+ .build();
+ sendMessageService.sendBatch(sessions, pilotMessage);
+ }
+
+ /**
+ * Notify the pilot side that device is offline and needs to reacquire topology information.
+ * @param sessions
+ * @param sn
+ */
+ private void pushDeviceOfflineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn) {
+ CustomWebSocketMessage<TopologyDeviceDTO> pilotMessage =
+ CustomWebSocketMessage.<TopologyDeviceDTO>builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.DEVICE_OFFLINE.getCode())
+ .data(TopologyDeviceDTO.builder()
+ .sn(sn)
+ .onlineStatus(false)
+ .build())
+ .build();
+ sendMessageService.sendBatch(sessions, pilotMessage);
+ }
+
+ /**
+ * Save the device information and update the information directly if the device already exists.
+ * @param entity
+ * @return
+ */
+ private Integer saveDevice(DeviceEntity entity) {
+ DeviceEntity deviceEntity = mapper.selectOne(
+ new LambdaQueryWrapper<DeviceEntity>()
+ .eq(DeviceEntity::getDeviceSn, entity.getDeviceSn()));
+ // Update the information directly if the device already exists.
+ if (deviceEntity != null) {
+ entity.setId(deviceEntity.getId());
+ mapper.updateById(entity);
+ return deviceEntity.getId();
+ }
+ return mapper.insert(entity) > 0 ? entity.getId() : 0;
+ }
+
+ /**
+ * Convert the received gateway device object into a database entity object.
+ * @param gateway
+ * @return
+ */
+ private DeviceEntity deviceGatewayConvertToDeviceEntity(StatusGatewayReceiver gateway) {
+ if (gateway == null) {
+ return new DeviceEntity();
+ }
+ DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
+
+ // Query the model information of this gateway device.
+ Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
+ .getOneDictionaryInfoByDomainTypeSubType(gateway.getDomain(),
+ gateway.getType(), gateway.getSubType());
+
+ dictionaryOpt.ifPresent(entity ->
+ builder.deviceName(entity.getDeviceName())
+ .deviceDesc(entity.getDeviceDesc()));
+
+ return builder
+ .deviceSn(gateway.getSn())
+ .domain(gateway.getDomain())
+ .subType(gateway.getSubType())
+ .deviceType(gateway.getType())
+ .version(gateway.getVersion())
+ .build();
+ }
+
+ /**
+ * Convert the received drone device object into a database entity object.
+ * @param device
+ * @return
+ */
+ private DeviceEntity subDeviceConvertToDeviceEntity(StatusSubDeviceReceiver device) {
+ if (device == null) {
+ return new DeviceEntity();
+ }
+ DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
+
+ // Query the model information of this drone device.
+ Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
+ .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.SUB_DEVICE.getVal(),
+ device.getType(), device.getSubType());
+
+ dictionaryOpt.ifPresent(dictionary ->
+ builder.deviceName(dictionary.getDeviceName())
+ .deviceDesc(dictionary.getDeviceDesc()));
+
+ return builder
+ .deviceSn(device.getSn())
+ .deviceType(device.getType())
+ .subType(device.getSubType())
+ .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
+ .version(device.getVersion())
+ .deviceIndex(device.getIndex())
+ .build();
+ }
+
+ /**
+ * Convert database entity objects into device data transfer object.
+ * @param entity
+ * @return
+ */
+ private DeviceDTO deviceEntityConvertToDTO(DeviceEntity entity) {
+ DeviceDTO.DeviceDTOBuilder builder = DeviceDTO.builder();
+
+ if (entity != null) {
+ builder.deviceSn(entity.getDeviceSn())
+ .childDeviceSn(entity.getChildSn())
+ .deviceName(entity.getDeviceName())
+ .deviceDesc(entity.getDeviceDesc())
+ .deviceIndex(entity.getDeviceIndex())
+ .workspaceId(entity.getWorkspaceId())
+ .type(entity.getDeviceType())
+ .subType(entity.getSubType())
+ .domain(DeviceDomainEnum.getDesc(entity.getDomain()))
+ .iconUrl(IconUrlDTO.builder()
+ .normalUrl(entity.getUrlNormal())
+ .selectUrl(entity.getUrlSelect())
+ .build())
+ .build();
+ }
+ return builder.build();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java
new file mode 100644
index 0000000..a69b9fe
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java
@@ -0,0 +1,74 @@
+package com.dji.sample.manage.service.impl;
+
+import com.dji.sample.component.mqtt.model.TopicStateReceiver;
+import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
+import com.dji.sample.component.websocket.model.BizCodeEnum;
+import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
+import com.dji.sample.manage.model.DeviceStatusManager;
+import com.dji.sample.manage.model.dto.TelemetryDTO;
+import com.dji.sample.manage.model.dto.TelemetryDeviceDTO;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.receiver.OsdGatewayReceiver;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/2/21
+ */
+@Service
+public class GatewayOSDServiceImpl extends AbstractTSAService {
+
+ public GatewayOSDServiceImpl(@Autowired @Qualifier("deviceOSDServiceImpl") AbstractTSAService tsaService) {
+ super(tsaService);
+ }
+
+ @Override
+ public void pushTelemetryData(Collection<ConcurrentWebSocketSession> sessions,
+ CustomWebSocketMessage<TelemetryDTO> message, Object osdData) {
+ if (osdData instanceof OsdGatewayReceiver) {
+ OsdGatewayReceiver data = (OsdGatewayReceiver) osdData;
+ TelemetryDTO telemetry = message.getData();
+ telemetry.setHost(TelemetryDeviceDTO.builder()
+ .latitude(data.getLatitude())
+ .longitude(data.getLongitude())
+ .build());
+ this.sendMessageService.sendBatch(sessions, message);
+ return;
+ }
+ tsaService.pushTelemetryData(sessions, message, osdData);
+ }
+
+ @Override
+ protected void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode,
+ Collection<ConcurrentWebSocketSession> webSessions, CustomWebSocketMessage wsMessage) throws JsonProcessingException {
+ if (sn.equals(receiver.getGateway())) {
+ // Real-time update of device status in memory
+ DeviceStatusManager.STATUS_MANAGER.put(DeviceDomainEnum.GATEWAY.getVal() + "/" + sn,
+ LocalDateTime.now());
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ wsMessage.setBizCode(BizCodeEnum.GATEWAY_OSD.getCode());
+ OsdGatewayReceiver data = mapper.treeToValue(hostNode, OsdGatewayReceiver.class);
+ wsMessage.setData(data);
+
+ this.sendMessageService.sendBatch(webSessions, wsMessage);
+
+ this.pushTelemetryData(workspaceId, data, sn);
+ return;
+ }
+
+ tsaService.handleOSD(receiver, sn, workspaceId, hostNode, webSessions, wsMessage);
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
new file mode 100644
index 0000000..371ad97
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
@@ -0,0 +1,326 @@
+package com.dji.sample.manage.service.impl;
+
+import com.dji.sample.common.error.LiveErrorEnum;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
+import com.dji.sample.component.mqtt.model.CommonTopicResponse;
+import com.dji.sample.component.mqtt.service.IMessageSenderService;
+import com.dji.sample.manage.model.Chan;
+import com.dji.sample.manage.model.dto.*;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.enums.LiveMethodEnum;
+import com.dji.sample.manage.model.enums.LiveUrlTypeEnum;
+import com.dji.sample.manage.model.enums.LiveVideoQualityEnum;
+import com.dji.sample.manage.model.param.DeviceQueryParam;
+import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
+import com.dji.sample.manage.model.receiver.ServiceReplyReceiver;
+import com.dji.sample.manage.service.ICapacityCameraService;
+import com.dji.sample.manage.service.IDeviceService;
+import com.dji.sample.manage.service.ILiveStreamService;
+import com.dji.sample.manage.service.IWorkspaceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.dji.sample.component.mqtt.model.TopicConst.*;
+
+/**
+ * @author sean.zhou
+ * @date 2021/11/22
+ * @version 0.1
+ */
+@Service
+@Transactional
+public class LiveStreamServiceImpl implements ILiveStreamService {
+
+ @Autowired
+ private ICapacityCameraService capacityCameraService;
+
+ @Autowired
+ private IDeviceService deviceService;
+
+ @Autowired
+ private IWorkspaceService workspaceService;
+
+ @Autowired
+ private IMessageSenderService messageSender;
+
+ @Override
+ public List<CapacityDeviceDTO> getLiveCapacity(String workspaceId) {
+
+ // Query all drone data in this workspace.
+ List<DeviceDTO> devicesList = deviceService.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .workspaceId(workspaceId)
+ .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
+ .build());
+
+ List<CapacityDeviceDTO> capacityDevicesList = new ArrayList<>();
+ // Query the live capability of each drone.
+ devicesList.forEach(device -> capacityDevicesList.add(CapacityDeviceDTO.builder()
+ .name(device.getDeviceName())
+ .sn(device.getDeviceSn())
+ .camerasList(capacityCameraService.getCapacityCameraByDeviceSn(device.getDeviceSn()))
+ .build()));
+
+ return capacityDevicesList;
+ }
+
+ @Override
+ public Boolean saveLiveCapacity(CapacityDeviceReceiver capacityDeviceReceiver) {
+ return capacityCameraService.saveCapacityCameraReceiverList(
+ capacityDeviceReceiver.getCameraList(),
+ capacityDeviceReceiver.getSn());
+ }
+
+ @Override
+ public ResponseResult liveStart(LiveTypeDTO liveParam) {
+ // Check if this lens is available live.
+ ResponseResult responseResult = this.checkBeforeLive(liveParam.getVideoId());
+ if (responseResult.getCode() != 0) {
+ return responseResult;
+ }
+
+ List<DeviceDTO> data = (List<DeviceDTO>)responseResult.getData();
+ // target topic
+ String respTopic = THING_MODEL_PRE + PRODUCT +
+ data.get(0).getDeviceSn() + SERVICES_SUF;
+ Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveStart(respTopic, liveParam);
+
+ if (receiveReplyOpt.isEmpty()) {
+ return ResponseResult.error(LiveErrorEnum.NO_REPLY);
+ }
+ if (receiveReplyOpt.get().getResult() != 0) {
+ return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult()));
+ }
+
+ LiveUrlTypeEnum urlType = LiveUrlTypeEnum.find(liveParam.getUrlType());
+ LiveDTO live = new LiveDTO();
+
+ switch (urlType) {
+ case RTMP:
+ live.setUrl(liveParam.getUrl().replace("rtmp", "webrtc"));
+ break;
+ case GB28181:
+ LiveUrlGB28181DTO gb28181 = urlToGB28181(liveParam.getUrl());
+ live.setUrl(new StringBuilder()
+ .append("webrtc://")
+ .append(gb28181.getServerIP())
+ .append("/live/")
+ .append(gb28181.getAgentID())
+ .append("@")
+ .append(gb28181.getChannel())
+ .toString());
+ break;
+ case RTSP:
+ String url = receiveReplyOpt.get().getInfo();
+ this.resolveUrlUser(url, live);
+ break;
+ case UNKNOWN:
+ return ResponseResult.error(LiveErrorEnum.URL_TYPE_NOT_SUPPORTED);
+ }
+
+ return ResponseResult.success(live);
+ }
+
+ @Override
+ public ResponseResult liveStop(String videoId) {
+ ResponseResult<List<DeviceDTO>> responseResult = this.checkBeforeLive(videoId);
+ if (responseResult.getCode() != 0) {
+ return responseResult;
+ }
+
+ String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().get(0).getDeviceSn() + SERVICES_SUF;
+
+ Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveStop(respTopic, videoId);
+ if (receiveReplyOpt.isEmpty()) {
+ return ResponseResult.error(LiveErrorEnum.NO_REPLY);
+ }
+ if (receiveReplyOpt.get().getResult() != 0) {
+ return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult()));
+ }
+
+ return ResponseResult.success();
+ }
+
+ @Override
+ public ResponseResult liveSetQuality(LiveTypeDTO liveParam) {
+ if (liveParam.getVideoQuality() == null ||
+ LiveVideoQualityEnum.UNKNOWN == LiveVideoQualityEnum.find(liveParam.getVideoQuality())) {
+ return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS);
+ }
+
+ ResponseResult<List<DeviceDTO>> responseResult = this.checkBeforeLive(liveParam.getVideoId());
+
+ if (responseResult.getCode() != 0) {
+ return responseResult;
+ }
+
+ String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().get(0).getDeviceSn() + SERVICES_SUF;
+
+ Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveSetQuality(respTopic, liveParam);
+ if (receiveReplyOpt.isEmpty()) {
+ return ResponseResult.error(LiveErrorEnum.NO_REPLY);
+ }
+ if (receiveReplyOpt.get().getResult() != 0) {
+ return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult()));
+ }
+
+ return ResponseResult.success();
+ }
+
+ /**
+ * Check if this lens is available live.
+ * @param videoId
+ * @return
+ */
+ private ResponseResult checkBeforeLive(String videoId) {
+ String[] videoIdArr = videoId.split("/");
+ // drone sn / enumeration value of the location where the payload is mounted / payload lens
+ if (videoIdArr.length != 3) {
+ return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS);
+ }
+
+ List<DeviceDTO> gatewayList = deviceService.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .childSn(videoIdArr[0])
+ .build());
+ // Check if the gateway device connected to this drone exists
+ if (gatewayList.isEmpty()) {
+ return ResponseResult.error(LiveErrorEnum.NO_FLIGHT_CONTROL);
+ }
+ return ResponseResult.success(gatewayList);
+ }
+
+ /**
+ * When using rtsp live, the account and password are parsed from the information returned by the pilot.
+ * @param url
+ * @param live
+ */
+ private void resolveUrlUser(String url, LiveDTO live) {
+ if (!StringUtils.hasText(url)) {
+ return;
+ }
+ int start = url.indexOf("//");
+ int end = url.lastIndexOf("@");
+ String user = url.substring(start + 2, end);
+
+ url = url.replace(user + "@", "");
+ String[] userArr = user.split(":");
+ live.setUsername(userArr[0]);
+ live.setPassword(userArr[1]);
+ live.setUrl(url);
+ }
+
+ /**
+ * When using GB28181 live, url parameters are resolved into objects.
+ * @param url
+ * @return
+ */
+ private LiveUrlGB28181DTO urlToGB28181(String url) {
+ String[] arr = url.split("\\=|\\&");
+ LiveUrlGB28181DTO gb28181 = new LiveUrlGB28181DTO();
+ try {
+ Class<LiveUrlGB28181DTO> clazz = LiveUrlGB28181DTO.class;
+ for (int i = 0; i < arr.length - 1; i += 2) {
+ Field field = clazz.getDeclaredField(arr[i]);
+ field.setAccessible(true);
+
+ if (field.getType().equals(Integer.class)) {
+ field.set(gb28181, Integer.valueOf(arr[i + 1]));
+ continue;
+ }
+ field.set(gb28181, arr[i + 1]);
+ }
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ return gb28181;
+ }
+
+ /**
+ * Send a message to the pilot via mqtt to start the live streaming.
+ * @param topic
+ * @param liveParam
+ * @return
+ */
+ private Optional<ServiceReplyReceiver> publishLiveStart(String topic, LiveTypeDTO liveParam) {
+ CommonTopicResponse<LiveTypeDTO> response = new CommonTopicResponse<>();
+ response.setTid(UUID.randomUUID().toString());
+ response.setBid(UUID.randomUUID().toString());
+ response.setData(liveParam);
+ response.setMethod(LiveMethodEnum.LIVE_START_PUSH.getMethod());
+
+ return this.publishLive(topic, response);
+ }
+
+ /**
+ * Send a message to the pilot via mqtt to set quality.
+ * @param respTopic
+ * @param liveParam
+ * @return
+ */
+ private Optional<ServiceReplyReceiver> publishLiveSetQuality(String respTopic, LiveTypeDTO liveParam) {
+ Map<String, Object> data = new ConcurrentHashMap<>(Map.of(
+ "video_id", liveParam.getVideoId(),
+ "video_quality", liveParam.getVideoQuality()));
+ CommonTopicResponse<Map<String, Object>> response = new CommonTopicResponse<>();
+ response.setTid(UUID.randomUUID().toString());
+ response.setBid(UUID.randomUUID().toString());
+ response.setMethod(LiveMethodEnum.LIVE_SET_QUALITY.getMethod());
+ response.setData(data);
+
+ return this.publishLive(respTopic, response);
+ }
+
+ /**
+ * Send a message to the pilot via mqtt to stop the live streaming.
+ * @param topic
+ * @param videoId
+ * @return
+ */
+ private Optional<ServiceReplyReceiver> publishLiveStop(String topic, String videoId) {
+ Map<String, String> data = new ConcurrentHashMap<>(Map.of("video_id", videoId));
+ CommonTopicResponse<Map<String, String>> response = new CommonTopicResponse<>();
+ response.setTid(UUID.randomUUID().toString());
+ response.setBid(UUID.randomUUID().toString());
+ response.setData(data);
+ response.setMethod(LiveMethodEnum.LIVE_STOP_PUSH.getMethod());
+
+ return this.publishLive(topic, response);
+ }
+
+ /**
+ * Send live streaming start message and receive a response at the same time
+ * @param topic
+ * @param response notification of whether the start is successful.
+ * @return
+ */
+ private Optional<ServiceReplyReceiver> publishLive(String topic, CommonTopicResponse response) {
+ AtomicInteger time = new AtomicInteger(0);
+ // Retry three times
+ while (time.getAndIncrement() < 3) {
+ messageSender.publish(topic, response);
+
+ Chan<CommonTopicReceiver<ServiceReplyReceiver>> chan = Chan.getInstance();
+ // If the message is not received in 0.5 seconds then resend it again.
+ CommonTopicReceiver<ServiceReplyReceiver> receiver = chan.get(response.getMethod());
+ if (receiver == null) {
+ continue;
+ }
+ // Need to match tid and bid.
+ if (receiver.getTid().equals(response.getTid()) &&
+ receiver.getBid().equals(response.getBid())) {
+ return Optional.ofNullable(receiver.getData());
+ }
+ }
+ return Optional.empty();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java
new file mode 100644
index 0000000..aefc059
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java
@@ -0,0 +1,55 @@
+package com.dji.sample.manage.service.impl;
+
+import com.dji.sample.manage.model.dto.DeviceDTO;
+import com.dji.sample.manage.model.dto.TopologyDTO;
+import com.dji.sample.manage.model.dto.TopologyDeviceDTO;
+import com.dji.sample.manage.model.enums.DeviceDomainEnum;
+import com.dji.sample.manage.model.param.DeviceQueryParam;
+import com.dji.sample.manage.service.IDeviceService;
+import com.dji.sample.manage.service.ITopologyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/8
+ */
+@Service
+public class TopologyServiceImpl implements ITopologyService {
+
+ @Autowired
+ private IDeviceService deviceService;
+
+ @Override
+ public List<TopologyDTO> getDeviceTopology(String workspaceId) {
+ // Query the information of all gateway devices in the workspace.
+ List<DeviceDTO> gatewayList = deviceService.getDevicesByParams(
+ DeviceQueryParam.builder()
+ .workspaceId(workspaceId)
+ .domain(DeviceDomainEnum.GATEWAY.getVal())
+ .build());
+
+ List<TopologyDTO> topologyList = new ArrayList<>();
+
+ gatewayList.forEach(device -> {
+ List<TopologyDeviceDTO> parents = new ArrayList<>();
+ TopologyDeviceDTO gateway = deviceService.deviceConvertToTopologyDTO(device);
+ parents.add(gateway);
+
+ // Query the topology data of the drone based on the drone sn.
+ Optional<TopologyDeviceDTO> deviceTopo = deviceService.getDeviceTopoForPilot(device.getChildDeviceSn());
+ List<TopologyDeviceDTO> deviceTopoList = new ArrayList<>();
+ deviceTopo.ifPresent(deviceTopoList::add);
+
+ topologyList.add(TopologyDTO.builder().parents(parents).hosts(deviceTopoList).build());
+ });
+
+ return topologyList;
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..c3c9a1b
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java
@@ -0,0 +1,141 @@
+package com.dji.sample.manage.service.impl;
+
+import com.auth0.jwt.JWT;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.common.util.JwtUtil;
+import com.dji.sample.component.mqtt.config.MqttConfiguration;
+import com.dji.sample.manage.dao.IUserMapper;
+import com.dji.sample.manage.model.dto.UserDTO;
+import com.dji.sample.manage.model.dto.WorkspaceDTO;
+import com.dji.sample.manage.model.entity.UserEntity;
+import com.dji.sample.manage.service.IUserService;
+import com.dji.sample.manage.service.IWorkspaceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.util.Optional;
+
+@Service
+@Transactional
+public class UserServiceImpl implements IUserService {
+
+ @Autowired
+ private IUserMapper mapper;
+
+ @Autowired
+ private MqttConfiguration mqttConfiguration;
+
+ @Autowired
+ private IWorkspaceService workspaceService;
+
+ @Override
+ public ResponseResult getUserByUsername(String username, String workspaceId) {
+
+ UserEntity userEntity = this.getUserByUsername(username);
+ if (userEntity == null) {
+ return ResponseResult.builder()
+ .code(HttpStatus.UNAUTHORIZED.value())
+ .message("invalid username")
+ .build();
+ }
+
+ UserDTO user = this.entityConvertToDTO(userEntity);
+ user.setWorkspaceId(workspaceId);
+
+ return ResponseResult.success(user);
+ }
+
+ @Override
+ public ResponseResult userLogin(String username, String password) {
+ // check user
+ UserEntity userEntity = this.getUserByUsername(username);
+ if (userEntity == null) {
+ return ResponseResult.builder()
+ .code(HttpStatus.UNAUTHORIZED.value())
+ .message("invalid username")
+ .build();
+ }
+ if (!password.equals(userEntity.getPassword())) {
+ return ResponseResult.builder()
+ .code(HttpStatus.UNAUTHORIZED.value())
+ .message("invalid password")
+ .build();
+ }
+
+ Optional<WorkspaceDTO> workspaceOpt = workspaceService.getWorkspaceById(userEntity.getWorkspaceId());
+ if (workspaceOpt.isEmpty()) {
+ return ResponseResult.builder()
+ .code(HttpStatus.UNAUTHORIZED.value())
+ .message("invalid workspace id")
+ .build();
+ }
+
+ CustomClaim customClaim = new CustomClaim(userEntity.getUserId(),
+ userEntity.getUsername(), userEntity.getUserType(),
+ workspaceOpt.get().getWorkspaceId());
+
+ // create token
+ String token = JwtUtil.createToken(customClaim.convertToMap());
+
+ UserDTO userDTO = entityConvertToDTO(userEntity);
+ userDTO.setMqttAddr(new StringBuilder()
+ .append(mqttConfiguration.getProtocol().trim())
+ .append("://")
+ .append(mqttConfiguration.getHost().trim())
+ .append(":")
+ .append(mqttConfiguration.getPort())
+ .toString());
+ userDTO.setAccessToken(token);
+ userDTO.setWorkspaceId(workspaceOpt.get().getWorkspaceId());
+ return ResponseResult.success(userDTO);
+ }
+
+ @Override
+ public Optional<UserDTO> refreshToken(String token) {
+ if (!StringUtils.hasText(token)) {
+ return Optional.empty();
+ }
+ CustomClaim customClaim = new CustomClaim(JWT.decode(token).getClaims());
+ String refreshToken = JwtUtil.createToken(customClaim.convertToMap());
+
+ UserDTO user = entityConvertToDTO(this.getUserByUsername(customClaim.getUsername()));
+ user.setWorkspaceId(customClaim.getWorkspaceId());
+ user.setAccessToken(refreshToken);
+ return Optional.of(user);
+ }
+
+ /**
+ * Query a user by username.
+ * @param username
+ * @return
+ */
+ private UserEntity getUserByUsername(String username) {
+ return mapper.selectOne(new QueryWrapper<UserEntity>()
+ .eq("username", username));
+ }
+
+ private UserDTO entityConvertToDTO(UserEntity entity) {
+ if (entity == null) {
+ return new UserDTO();
+ }
+ return UserDTO.builder()
+ .userId(entity.getUserId())
+ .username(entity.getUsername())
+ .userType(entity.getUserType())
+ .mqttUsername(entity.getMqttUsername())
+ .mqttPassword(entity.getMqttPassword())
+ .mqttAddr(new StringBuilder()
+ .append(mqttConfiguration.getProtocol().trim())
+ .append("://")
+ .append(mqttConfiguration.getHost().trim())
+ .append(":")
+ .append(mqttConfiguration.getPort())
+ .toString())
+ .build();
+ }
+}
diff --git a/src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java
new file mode 100644
index 0000000..d6ed3ab
--- /dev/null
+++ b/src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java
@@ -0,0 +1,52 @@
+package com.dji.sample.manage.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.manage.dao.IWorkspaceMapper;
+import com.dji.sample.manage.model.dto.WorkspaceDTO;
+import com.dji.sample.manage.model.entity.WorkspaceEntity;
+import com.dji.sample.manage.service.IWorkspaceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
+
+@Service
+@Transactional
+public class WorkspaceServiceImpl implements IWorkspaceService {
+
+ @Autowired
+ private IWorkspaceMapper mapper;
+
+ @Override
+ public Optional<WorkspaceDTO> getWorkspaceById(int id) {
+ return Optional.ofNullable(entityConvertToDto(mapper.selectById(id)));
+ }
+
+ @Override
+ public Optional<WorkspaceDTO> getWorkspaceByWorkspaceId(String workspaceId) {
+ return Optional.ofNullable(entityConvertToDto(
+ mapper.selectOne(
+ new LambdaQueryWrapper<WorkspaceEntity>()
+ .eq(WorkspaceEntity::getWorkspaceId, workspaceId))));
+ }
+
+ /**
+ * Convert database entity objects into workspace data transfer object.
+ * @param entity
+ * @return
+ */
+ private WorkspaceDTO entityConvertToDto(WorkspaceEntity entity) {
+ WorkspaceDTO.WorkspaceDTOBuilder builder = WorkspaceDTO.builder();
+ if (entity == null) {
+ return builder.build();
+ }
+ return builder
+ .id(entity.getId())
+ .workspaceId(entity.getWorkspaceId())
+ .platformName(entity.getPlatformName())
+ .workspaceDesc(entity.getWorkspaceDesc())
+ .workspaceName(entity.getWorkspaceName())
+ .build();
+ }
+}
diff --git a/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java b/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java
new file mode 100644
index 0000000..1ad52b8
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java
@@ -0,0 +1,184 @@
+package com.dji.sample.map.controller;
+
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.component.websocket.model.BizCodeEnum;
+import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
+import com.dji.sample.component.websocket.model.WebSocketManager;
+import com.dji.sample.component.websocket.service.ISendMessageService;
+import com.dji.sample.map.model.dto.*;
+import com.dji.sample.map.service.IWorkspaceElementService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@RestController
+@RequestMapping("${url.map.prefix}${url.map.version}/workspaces")
+public class WorkspaceElementController {
+
+ @Autowired
+ private IWorkspaceElementService elementService;
+
+ @Autowired
+ private ISendMessageService sendMessageService;
+
+ /**
+ * In the first connection, pilot will send out this http request to obtain the group element list.
+ * Also, if pilot receives a group refresh instruction from WebSocket,
+ * it needs the same interface to request the group element list.
+ * @param workspaceId
+ * @param groupId
+ * @param isDistributed
+ * @return
+ */
+ @GetMapping("/{workspace_id}/element-groups")
+ public ResponseResult<List<GroupDTO>> getAllElements(@PathVariable(name = "workspace_id") String workspaceId,
+ @RequestParam(name = "group_id", required = false) String groupId,
+ @RequestParam(name = "is_distributed", required = false) Boolean isDistributed) {
+ List<GroupDTO> groupsList = elementService.getAllGroupsByWorkspaceId(workspaceId, groupId, isDistributed);
+ return ResponseResult.success(groupsList);
+ }
+
+ /**
+ * When user draws a point, line or polygon on the PILOT/Web side.
+ * Save the element information to the database.
+ * @param request
+ * @param workspaceId
+ * @param groupId
+ * @param elementCreate
+ * @return
+ */
+ @PostMapping("/{workspace_id}/element-groups/{group_id}/elements")
+ public ResponseResult saveElement(HttpServletRequest request,
+ @PathVariable(name = "workspace_id") String workspaceId,
+ @PathVariable(name = "group_id") String groupId,
+ @RequestBody ElementCreateDTO elementCreate) {
+ CustomClaim claims = (CustomClaim) request.getAttribute(TOKEN_CLAIM);
+ // Set the creator of the element
+ elementCreate.getResource().setUsername(claims.getUsername());
+
+ ResponseResult response = elementService.saveElement(groupId, elementCreate);
+ if (response.getCode() != ResponseResult.CODE_SUCCESS) {
+ return response;
+ }
+
+ // Notify all WebSocket connections in this workspace to be updated when an element is created.
+ elementService.getElementByElementId(elementCreate.getId())
+ .ifPresent(groupElement ->
+ sendMessageService.sendBatch(
+ WebSocketManager.getValueWithWorkspace(workspaceId),
+ CustomWebSocketMessage.<GroupElementDTO>builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.MAP_ELEMENT_CREATE.getCode())
+ .data(groupElement)
+ .build()));
+
+ return ResponseResult.success(new ConcurrentHashMap<>(Map.of("id", elementCreate.getId())));
+ }
+
+ /**
+ * When user edits a point, line or polygon on the PILOT/Web side.
+ * Update the element information to the database.
+ * @param request
+ * @param workspaceId
+ * @param elementId
+ * @param elementUpdate
+ * @return
+ */
+ @PutMapping("/{workspace_id}/elements/{element_id}")
+ public ResponseResult updateElement(HttpServletRequest request,
+ @PathVariable(name = "workspace_id") String workspaceId,
+ @PathVariable(name = "element_id") String elementId,
+ @RequestBody ElementUpdateDTO elementUpdate) {
+
+ CustomClaim claims = (CustomClaim) request.getAttribute(TOKEN_CLAIM);
+
+ ResponseResult response = elementService.updateElement(elementId, elementUpdate, claims.getUsername());
+ if (response.getCode() != ResponseResult.CODE_SUCCESS) {
+ return response;
+ }
+
+ // Notify all WebSocket connections in this workspace to update when there is an element update.
+ elementService.getElementByElementId(elementId)
+ .ifPresent(groupElement ->
+ sendMessageService.sendBatch(
+ WebSocketManager.getValueWithWorkspace(workspaceId),
+ CustomWebSocketMessage.<GroupElementDTO>builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.MAP_ELEMENT_UPDATE.getCode())
+ .data(groupElement)
+ .build()));
+ return response;
+ }
+
+ /**
+ * When user delete a point, line or polygon on the PILOT/Web side,
+ * Delete the element information in the database.
+ * @param workspaceId
+ * @param elementId
+ * @return
+ */
+ @DeleteMapping("/{workspace_id}/elements/{element_id}")
+ public ResponseResult deleteElement(@PathVariable(name = "workspace_id") String workspaceId,
+ @PathVariable(name = "element_id") String elementId) {
+
+ Optional<GroupElementDTO> elementOpt = elementService.getElementByElementId(elementId);
+
+ ResponseResult response = elementService.deleteElement(elementId);
+
+ // Notify all WebSocket connections in this workspace to update when an element is deleted.
+ if (ResponseResult.CODE_SUCCESS == response.getCode()) {
+ elementOpt.ifPresent(element ->
+ sendMessageService.sendBatch(
+ WebSocketManager.getValueWithWorkspace(workspaceId),
+ CustomWebSocketMessage.<WebSocketElementDelDTO>builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.MAP_ELEMENT_DELETE.getCode())
+ .data(WebSocketElementDelDTO.builder()
+ .elementId(elementId)
+ .groupId(element.getGroupId())
+ .build())
+ .build()));
+ }
+ return response;
+ }
+
+ /**
+ * Delete all the element information in this group based on the group id.
+ * @param workspaceId
+ * @param groupId
+ * @return
+ */
+ @DeleteMapping("/{workspace_id}/element-groups/{group_id}/elements")
+ public ResponseResult deleteAllElementByGroupId(@PathVariable(name = "workspace_id") String workspaceId,
+ @PathVariable(name = "group_id") String groupId) {
+
+ ResponseResult response = elementService.deleteAllElementByGroupId(groupId);
+
+ // Notify all WebSocket connections in this workspace to update when elements are deleted.
+ if (ResponseResult.CODE_SUCCESS == response.getCode()) {
+
+ sendMessageService.sendBatch(
+ WebSocketManager.getValueWithWorkspace(workspaceId),
+ CustomWebSocketMessage.builder()
+ .timestamp(System.currentTimeMillis())
+ .bizCode(BizCodeEnum.MAP_GROUP_REFRESH.getCode())
+ // Group ids that need to re-request data
+ .data(new ConcurrentHashMap<String, String[]>(Map.of("ids", new String[]{groupId})))
+ .build());
+ }
+ return response;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java b/src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java
new file mode 100644
index 0000000..5073ce5
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java
@@ -0,0 +1,12 @@
+package com.dji.sample.map.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.map.model.entity.ElementCoordinateEntity;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+public interface IElementCoordinateMapper extends BaseMapper<ElementCoordinateEntity> {
+}
diff --git a/src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java b/src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java
new file mode 100644
index 0000000..4b572c7
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java
@@ -0,0 +1,12 @@
+package com.dji.sample.map.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.map.model.entity.GroupElementEntity;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+public interface IGroupElementMapper extends BaseMapper<GroupElementEntity> {
+}
diff --git a/src/main/java/com/dji/sample/map/dao/IGroupMapper.java b/src/main/java/com/dji/sample/map/dao/IGroupMapper.java
new file mode 100644
index 0000000..747f7fd
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/dao/IGroupMapper.java
@@ -0,0 +1,12 @@
+package com.dji.sample.map.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.map.model.entity.GroupEntity;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+public interface IGroupMapper extends BaseMapper<GroupEntity> {
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java b/src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java
new file mode 100644
index 0000000..e8fe1d2
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java
@@ -0,0 +1,25 @@
+package com.dji.sample.map.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class ContentPropertyDTO {
+
+ private String color;
+
+ @JsonProperty("clampToGround")
+ private Boolean clampToGround;
+
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java
new file mode 100644
index 0000000..e0799c4
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java
@@ -0,0 +1,25 @@
+package com.dji.sample.map.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ElementCoordinateDTO {
+
+ private Double longitude;
+
+ private Double latitude;
+
+ private Double altitude;
+
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java
new file mode 100644
index 0000000..a594bba
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java
@@ -0,0 +1,18 @@
+package com.dji.sample.map.model.dto;
+
+import lombok.Data;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Data
+public class ElementCreateDTO {
+
+ private String id;
+
+ private String name;
+
+ private ElementResourceDTO resource;
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java
new file mode 100644
index 0000000..117790b
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java
@@ -0,0 +1,47 @@
+package com.dji.sample.map.model.dto;
+
+import com.dji.sample.map.model.enums.ElementTypeEnum;
+import lombok.Data;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Data
+public class ElementLineStringDTO extends ElementType {
+
+ private Double[][] coordinates;
+
+ public ElementLineStringDTO() {
+ super(ElementTypeEnum.LINE_STRING.getDesc());
+ }
+
+ @Override
+ public List<ElementCoordinateDTO> convertToList() {
+ List<ElementCoordinateDTO> coordinateList = new ArrayList<>();
+ for (Double[] coordinate : this.coordinates) {
+ coordinateList.add(ElementCoordinateDTO.builder()
+ .longitude(coordinate[0])
+ .latitude(coordinate[1])
+ .build());
+ }
+ return coordinateList;
+ }
+
+ @Override
+ public void adapterCoordinateType(List<ElementCoordinateDTO> coordinateList) {
+ if (CollectionUtils.isEmpty(coordinateList)) {
+ return;
+ }
+ this.coordinates = new Double[coordinateList.size()][2];
+ for (int i = 0; i < this.coordinates.length; i++) {
+ this.coordinates[i][0] = coordinateList.get(i).getLongitude();
+ this.coordinates[i][1] = coordinateList.get(i).getLatitude();
+ }
+ }
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java
new file mode 100644
index 0000000..3eeee7d
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java
@@ -0,0 +1,46 @@
+package com.dji.sample.map.model.dto;
+
+import com.dji.sample.map.model.enums.ElementTypeEnum;
+import lombok.Data;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Data
+public class ElementPointDTO extends ElementType {
+
+ private Double[] coordinates;
+
+ public ElementPointDTO() {
+ super(ElementTypeEnum.POINT.getDesc());
+ }
+
+ @Override
+ public List<ElementCoordinateDTO> convertToList() {
+ List<ElementCoordinateDTO> coordinateList = new ArrayList<>();
+ coordinateList.add(ElementCoordinateDTO.builder()
+ .longitude(this.coordinates[0])
+ .latitude(this.coordinates[1])
+ .altitude(this.coordinates.length == 3 ? this.coordinates[2] : null)
+ .build());
+ return coordinateList;
+ }
+
+ @Override
+ public void adapterCoordinateType(List<ElementCoordinateDTO> coordinateList) {
+ if (CollectionUtils.isEmpty(coordinateList)) {
+ return;
+ }
+ this.coordinates = new Double[]{
+ coordinateList.get(0).getLongitude(),
+ coordinateList.get(0).getLatitude(),
+ coordinateList.get(0).getAltitude()
+ };
+ }
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java
new file mode 100644
index 0000000..35a8d35
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java
@@ -0,0 +1,48 @@
+package com.dji.sample.map.model.dto;
+
+import com.dji.sample.map.model.enums.ElementTypeEnum;
+import lombok.Data;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Data
+public class ElementPolygonDTO extends ElementType {
+
+ private Double[][][] coordinates;
+
+ public ElementPolygonDTO() {
+ super(ElementTypeEnum.POLYGON.getDesc());
+ }
+
+ @Override
+ public List<ElementCoordinateDTO> convertToList() {
+ List<ElementCoordinateDTO> coordinateList = new ArrayList<>();
+
+ for (Double[] coordinate : this.coordinates[0]) {
+ coordinateList.add(ElementCoordinateDTO.builder()
+ .longitude(coordinate[0])
+ .latitude(coordinate[1])
+ .build());
+ }
+ return coordinateList;
+ }
+
+ @Override
+ public void adapterCoordinateType(List<ElementCoordinateDTO> coordinateList) {
+ if (CollectionUtils.isEmpty(coordinateList)) {
+ return;
+ }
+ this.coordinates = new Double[1][coordinateList.size()][2];
+ for (int i = 0; i < this.coordinates[0].length; i++) {
+ this.coordinates[0][i][0] = coordinateList.get(i).getLongitude();
+ this.coordinates[0][i][1] = coordinateList.get(i).getLatitude();
+ }
+ }
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java
new file mode 100644
index 0000000..b89df70
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java
@@ -0,0 +1,26 @@
+package com.dji.sample.map.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class ElementResourceDTO {
+
+ private Integer type;
+
+ @JsonProperty(value = "user_name")
+ private String username;
+
+ private ResourceContentDTO content;
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementType.java b/src/main/java/com/dji/sample/map/model/dto/ElementType.java
new file mode 100644
index 0000000..1fdf3f6
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ElementType.java
@@ -0,0 +1,43 @@
+package com.dji.sample.map.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Data
+@NoArgsConstructor
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type",
+ include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = ElementType.class)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = ElementPointDTO.class, name = "Point"),
+ @JsonSubTypes.Type(value = ElementLineStringDTO.class, name = "LineString"),
+ @JsonSubTypes.Type(value = ElementPolygonDTO.class, name = "Polygon")
+})
+public abstract class ElementType {
+
+ private String type;
+
+ ElementType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * Convert coordinate data into objects and then add them to the collection.
+ * @return
+ */
+ public abstract List<ElementCoordinateDTO> convertToList();
+
+ /**
+ * Converts coordinates in a collection of objects to a specific type.
+ * @param coordinateList
+ */
+ public abstract void adapterCoordinateType(List<ElementCoordinateDTO> coordinateList);
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java
new file mode 100644
index 0000000..58897e2
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java
@@ -0,0 +1,17 @@
+package com.dji.sample.map.model.dto;
+
+import lombok.Data;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/1
+ */
+@Data
+public class ElementUpdateDTO {
+
+ private String name;
+
+ private ResourceContentDTO content;
+
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/GroupDTO.java b/src/main/java/com/dji/sample/map/model/dto/GroupDTO.java
new file mode 100644
index 0000000..1c75e67
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/GroupDTO.java
@@ -0,0 +1,38 @@
+package com.dji.sample.map.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Data
+public class GroupDTO {
+
+ private String id;
+
+ private String name;
+
+ private Integer type;
+
+ private List<GroupElementDTO> elements;
+
+ @JsonProperty(value = "is_distributed")
+ private Boolean isDistributed;
+
+ @JsonProperty(value = "is_lock")
+ private Boolean isLock;
+
+ @JsonProperty(value = "create_time")
+ private Long createTime;
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java b/src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java
new file mode 100644
index 0000000..b48d1b6
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java
@@ -0,0 +1,38 @@
+package com.dji.sample.map.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class GroupElementDTO {
+ private Integer display;
+
+ @JsonProperty("id")
+ private String elementId;
+
+ private String name;
+
+ @JsonProperty(value = "create_time")
+ private Long createTime;
+
+ @JsonProperty(value = "update_time")
+ private Long updateTime;
+
+ private ElementResourceDTO resource;
+
+ @JsonProperty("group_id")
+ private String groupId;
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java b/src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java
new file mode 100644
index 0000000..6ae3c9a
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java
@@ -0,0 +1,25 @@
+package com.dji.sample.map.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class ResourceContentDTO {
+
+ @Builder.Default
+ private String type = "Feature";
+
+ private ContentPropertyDTO properties;
+
+ private ElementType geometry;
+}
diff --git a/src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java b/src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java
new file mode 100644
index 0000000..5d99272
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java
@@ -0,0 +1,25 @@
+package com.dji.sample.map.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/2
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class WebSocketElementDelDTO {
+
+ @JsonProperty("id")
+ private String elementId;
+
+ @JsonProperty("group_id")
+ private String groupId;
+}
diff --git a/src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java b/src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java
new file mode 100644
index 0000000..26d2732
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java
@@ -0,0 +1,41 @@
+package com.dji.sample.map.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName(value = "map_element_coordinate")
+public class ElementCoordinateEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField("element_id")
+ private String elementId;
+
+ @TableField("longitude")
+ private Double longitude;
+
+ @TableField("latitude")
+ private Double latitude;
+
+ @TableField("altitude")
+ private Double altitude;
+
+}
diff --git a/src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java b/src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java
new file mode 100644
index 0000000..7c6748a
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java
@@ -0,0 +1,55 @@
+package com.dji.sample.map.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName(value = "map_group_element")
+public class GroupElementEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField("group_id")
+ private String groupId;
+
+ @TableField("element_id")
+ private String elementId;
+
+ @TableField("element_name")
+ private String elementName;
+
+ @TableField("display")
+ private Integer display;
+
+ @TableField("element_type")
+ private Integer elementType;
+
+ @TableField("username")
+ private String username;
+
+ @TableField("color")
+ private String color;
+
+ @TableField("clamp_to_ground")
+ private Boolean clampToGround;
+
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Long createTime;
+
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Long updateTime;
+}
diff --git a/src/main/java/com/dji/sample/map/model/entity/GroupEntity.java b/src/main/java/com/dji/sample/map/model/entity/GroupEntity.java
new file mode 100644
index 0000000..c639a9b
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/entity/GroupEntity.java
@@ -0,0 +1,49 @@
+package com.dji.sample.map.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName(value = "map_group")
+public class GroupEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField("group_id")
+ private String groupId;
+
+ @TableField("group_name")
+ private String groupName;
+
+ @TableField("group_type")
+ private Integer groupType;
+
+ @TableField("workspace_id")
+ private String workspaceId;
+
+ @TableField("is_distributed")
+ private Boolean isDistributed;
+
+ @TableField("is_lock")
+ private Boolean isLock;
+
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Long createTime;
+
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Long updateTime;
+}
diff --git a/src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java b/src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java
new file mode 100644
index 0000000..886ebe2
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java
@@ -0,0 +1,69 @@
+package com.dji.sample.map.model.enums;
+
+import com.dji.sample.map.model.dto.ElementLineStringDTO;
+import com.dji.sample.map.model.dto.ElementPointDTO;
+import com.dji.sample.map.model.dto.ElementPolygonDTO;
+import com.dji.sample.map.model.dto.ElementType;
+
+import java.util.Optional;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+public enum ElementTypeEnum {
+
+ POINT(0, "Point"),
+
+ LINE_STRING(1, "LineString"),
+
+ POLYGON(2, "Polygon"),
+
+ UNKNOWN(-1, "Unknown");
+
+ private int val;
+
+ private String desc;
+
+ ElementTypeEnum(int val, String desc) {
+ this.val = val;
+ this.desc = desc;
+ }
+
+ public static Optional<ElementType> findType(int val) {
+ if (POINT.val == val) {
+ return Optional.of(new ElementPointDTO());
+ }
+
+ if (LINE_STRING.val == val) {
+ return Optional.of(new ElementLineStringDTO());
+ }
+
+ if (POLYGON.val == val) {
+ return Optional.of(new ElementPolygonDTO());
+ }
+
+ return Optional.empty();
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public static int findVal(String desc) {
+ if (POINT.desc.equals(desc)) {
+ return POINT.val;
+ }
+
+ if (LINE_STRING.desc.equals(desc)) {
+ return LINE_STRING.val;
+ }
+
+ if (POLYGON.desc.equals(desc)) {
+ return POLYGON.val;
+ }
+
+ return UNKNOWN.val;
+ }
+}
diff --git a/src/main/java/com/dji/sample/map/service/IElementCoordinateService.java b/src/main/java/com/dji/sample/map/service/IElementCoordinateService.java
new file mode 100644
index 0000000..7668205
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/service/IElementCoordinateService.java
@@ -0,0 +1,35 @@
+package com.dji.sample.map.service;
+
+import com.dji.sample.map.model.dto.ElementCoordinateDTO;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+public interface IElementCoordinateService {
+
+ /**
+ * Query all the coordinates of the element according to its id.
+ * @param elementId
+ * @return
+ */
+ List<ElementCoordinateDTO> getCoordinateByElementId(String elementId);
+
+ /**
+ * Save all the coordinate data of this element.
+ * @param coordinate
+ * @param elementId
+ * @return
+ */
+ Boolean saveCoordinate(List<ElementCoordinateDTO> coordinate, String elementId);
+
+ /**
+ * Delete all the coordinates of the element according to its id.
+ * @param elementId
+ * @return
+ */
+ Boolean deleteCoordinateByElementId(String elementId);
+}
diff --git a/src/main/java/com/dji/sample/map/service/IGroupElementService.java b/src/main/java/com/dji/sample/map/service/IGroupElementService.java
new file mode 100644
index 0000000..d6513fa
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/service/IGroupElementService.java
@@ -0,0 +1,54 @@
+package com.dji.sample.map.service;
+
+import com.dji.sample.map.model.dto.ElementCreateDTO;
+import com.dji.sample.map.model.dto.ElementUpdateDTO;
+import com.dji.sample.map.model.dto.GroupElementDTO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+public interface IGroupElementService {
+
+ /**
+ * Query all the elements in this group based on the group id.
+ * @param groupId
+ * @return
+ */
+ List<GroupElementDTO> getElementsByGroupId(String groupId);
+
+ /**
+ * Save all the elements.
+ * @param groupId
+ * @param elementCreate
+ * @return
+ */
+ Boolean saveElement(String groupId, ElementCreateDTO elementCreate);
+
+ /**
+ * Query the element information based on the element id and update element.
+ * @param elementId
+ * @param elementUpdate
+ * @param username
+ * @return
+ */
+ Boolean updateElement(String elementId, ElementUpdateDTO elementUpdate, String username);
+
+ /**
+ * Delete the element based on the element id.
+ * @param elementId
+ * @return
+ */
+ Boolean deleteElement(String elementId);
+
+ /**
+ * Query an element based on the element id, including the coordinate information in the elements.
+ * @param elementId
+ * @return
+ */
+ Optional<GroupElementDTO> getElementByElementId(String elementId);
+}
diff --git a/src/main/java/com/dji/sample/map/service/IGroupService.java b/src/main/java/com/dji/sample/map/service/IGroupService.java
new file mode 100644
index 0000000..bead14b
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/service/IGroupService.java
@@ -0,0 +1,24 @@
+package com.dji.sample.map.service;
+
+import com.dji.sample.map.model.dto.GroupDTO;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+public interface IGroupService {
+
+ /**
+ * Query all groups in the workspace based on the workspace's id.
+ * If the group id does not exist, do not add this filter condition.
+ * @param workspaceId
+ * @param groupId
+ * @param isDistributed Used to define if the group needs to be distributed. Default is true.
+ * @return
+ */
+ List<GroupDTO> getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed);
+
+}
diff --git a/src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java b/src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java
new file mode 100644
index 0000000..4d24769
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java
@@ -0,0 +1,71 @@
+package com.dji.sample.map.service;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.map.model.dto.ElementCreateDTO;
+import com.dji.sample.map.model.dto.ElementUpdateDTO;
+import com.dji.sample.map.model.dto.GroupDTO;
+import com.dji.sample.map.model.dto.GroupElementDTO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+public interface IWorkspaceElementService {
+
+ /**
+ * Query all groups in the workspace based on the workspace's id,
+ * including the information of the elements in the group, and the coordinate information in the elements.
+ * @param workspaceId
+ * @param groupId
+ * @param isDistributed
+ * @return
+ */
+ List<GroupDTO> getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed);
+
+ /**
+ * Save all the elements, including the information of the elements in the group,
+ * and the coordinate information in the elements.
+ * @param groupId
+ * @param elementCreate
+ * @return
+ */
+ ResponseResult saveElement(String groupId, ElementCreateDTO elementCreate);
+
+ /**
+ * Update the element information based on the element id,
+ * including the information of the elements in the group, and the coordinate information in the elements.
+ * @param elementId
+ * @param elementUpdate
+ * @param username
+ * @return
+ */
+ ResponseResult updateElement(String elementId, ElementUpdateDTO elementUpdate, String username);
+
+ /**
+ * Delete the element information based on the element id,
+ * including the information of the elements in the group, and the coordinate information in the elements.
+ * @param elementId
+ * @return
+ */
+ ResponseResult deleteElement(String elementId);
+
+ /**
+ * Query an element based on the element id,
+ * including the information of the elements in the group, and the coordinate information in the elements.
+ * @param elementId
+ * @return
+ */
+ Optional<GroupElementDTO> getElementByElementId(String elementId);
+
+ /**
+ * Delete all the elements information based on the group id,
+ * including the information of the elements in the group, and the coordinate information in the elements.
+ * @param groupId
+ * @return
+ */
+ ResponseResult deleteAllElementByGroupId(String groupId);
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java b/src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java
new file mode 100644
index 0000000..bab9775
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java
@@ -0,0 +1,94 @@
+package com.dji.sample.map.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.dji.sample.map.dao.IElementCoordinateMapper;
+import com.dji.sample.map.model.dto.ElementCoordinateDTO;
+import com.dji.sample.map.model.entity.ElementCoordinateEntity;
+import com.dji.sample.map.service.IElementCoordinateService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@Service
+@Transactional
+public class ElementCoordinateServiceImpl implements IElementCoordinateService {
+
+ @Autowired
+ private IElementCoordinateMapper mapper;
+
+ @Override
+ public List<ElementCoordinateDTO> getCoordinateByElementId(String elementId) {
+ return mapper.selectList(
+ new LambdaQueryWrapper<ElementCoordinateEntity>()
+ .eq(ElementCoordinateEntity::getElementId, elementId))
+ .stream()
+ .map(this::entityConvertToDto)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Boolean saveCoordinate(List<ElementCoordinateDTO> coordinateList, String elementId) {
+ for (ElementCoordinateDTO coordinate : coordinateList) {
+ ElementCoordinateEntity entity = this.dtoConvertToEntity(coordinate);
+ entity.setElementId(elementId);
+
+ int insert = mapper.insert(entity);
+ if (insert <= 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Boolean deleteCoordinateByElementId(String elementId) {
+ return mapper.delete(new LambdaUpdateWrapper<ElementCoordinateEntity>()
+ .eq(ElementCoordinateEntity::getElementId, elementId)) > 0;
+ }
+
+ /**
+ * Convert database entity objects into coordinate data transfer object.
+ * @param entity
+ * @return
+ */
+ private ElementCoordinateDTO entityConvertToDto(ElementCoordinateEntity entity) {
+ ElementCoordinateDTO.ElementCoordinateDTOBuilder builder = ElementCoordinateDTO.builder();
+ if (entity == null) {
+ return builder.build();
+ }
+
+ return builder
+ .longitude(entity.getLongitude())
+ .latitude(entity.getLatitude())
+ .altitude(entity.getAltitude())
+ .build();
+ }
+
+ /**
+ * Convert the received coordinate object into a database entity object.
+ * @param coordinate
+ * @return
+ */
+ private ElementCoordinateEntity dtoConvertToEntity(ElementCoordinateDTO coordinate) {
+ ElementCoordinateEntity.ElementCoordinateEntityBuilder builder = ElementCoordinateEntity.builder();
+
+ if (coordinate == null) {
+ return builder.build();
+ }
+
+ return builder
+ .altitude(coordinate.getAltitude())
+ .latitude(coordinate.getLatitude())
+ .longitude(coordinate.getLongitude())
+ .build();
+ }
+}
diff --git a/src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java b/src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java
new file mode 100644
index 0000000..7ee4ce9
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java
@@ -0,0 +1,199 @@
+package com.dji.sample.map.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.map.dao.IGroupElementMapper;
+import com.dji.sample.map.model.dto.*;
+import com.dji.sample.map.model.entity.GroupElementEntity;
+import com.dji.sample.map.model.enums.ElementTypeEnum;
+import com.dji.sample.map.service.IElementCoordinateService;
+import com.dji.sample.map.service.IGroupElementService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@Service
+@Transactional
+public class GroupElementServiceImpl implements IGroupElementService {
+
+ @Autowired
+ private IGroupElementMapper mapper;
+
+ @Autowired
+ private IElementCoordinateService elementCoordinateService;
+
+ @Override
+ public List<GroupElementDTO> getElementsByGroupId(String groupId) {
+ List<GroupElementEntity> elementList = mapper.selectList(
+ new LambdaQueryWrapper<GroupElementEntity>()
+ .eq(GroupElementEntity::getGroupId, groupId));
+
+ List<GroupElementDTO> groupElementList = new ArrayList<>();
+ for (GroupElementEntity elementEntity : elementList) {
+
+ GroupElementDTO groupElement = this.entityConvertToDto(elementEntity);
+ groupElementList.add(groupElement);
+
+ this.addCoordinateToElement(groupElement, elementEntity);
+ }
+ return groupElementList;
+ }
+
+ @Override
+ public Boolean saveElement(String groupId, ElementCreateDTO elementCreate) {
+ Optional<GroupElementEntity> groupElementOpt = this.getEntityByElementId(elementCreate.getId());
+
+ if (groupElementOpt.isPresent()) {
+ return false;
+ }
+ GroupElementEntity groupElement = this.createDtoConvertToEntity(elementCreate);
+ groupElement.setGroupId(groupId);
+
+ return mapper.insert(groupElement) > 0;
+ }
+
+ @Override
+ public Boolean updateElement(String elementId, ElementUpdateDTO elementUpdate, String username) {
+ Optional<GroupElementEntity> groupElementOpt = this.getEntityByElementId(elementId);
+ if (groupElementOpt.isEmpty()) {
+ return false;
+ }
+
+ GroupElementEntity groupElement = groupElementOpt.get();
+ groupElement.setUsername(username);
+ this.updateEntityWithDto(elementUpdate, groupElement);
+
+ return mapper.updateById(groupElement) > 0;
+ }
+
+ @Override
+ public Boolean deleteElement(String elementId) {
+ Optional<GroupElementEntity> groupElementOpt = this.getEntityByElementId(elementId);
+ if (groupElementOpt.isEmpty()) {
+ return true;
+ }
+
+ GroupElementEntity groupElement = groupElementOpt.get();
+ return mapper.deleteById(groupElement.getId()) > 0;
+ }
+
+
+ @Override
+ public Optional<GroupElementDTO> getElementByElementId(String elementId) {
+ Optional<GroupElementEntity> elementEntityOpt = this.getEntityByElementId(elementId);
+ if (elementEntityOpt.isEmpty()) {
+ return Optional.empty();
+ }
+ GroupElementEntity elementEntity = elementEntityOpt.get();
+ GroupElementDTO groupElement = this.entityConvertToDto(elementEntity);
+
+ this.addCoordinateToElement(groupElement, elementEntity);
+
+ return Optional.ofNullable(groupElement);
+ }
+
+ /**
+ * Adds the received coordinate data to the element object.
+ * @param element
+ * @param elementEntity
+ */
+ private void addCoordinateToElement(GroupElementDTO element, GroupElementEntity elementEntity) {
+ Optional<ElementType> coordinateOpt = ElementTypeEnum.findType(elementEntity.getElementType());
+ if (coordinateOpt.isEmpty()) {
+ return;
+ }
+
+ ElementType elementType = coordinateOpt.get();
+
+ element.getResource().setContent(
+ ResourceContentDTO.builder()
+ .properties(ContentPropertyDTO.builder()
+ .clampToGround(elementEntity.getClampToGround())
+ .color(elementEntity.getColor())
+ .build())
+ .geometry(elementType)
+ .build());
+
+ elementType.adapterCoordinateType(
+ elementCoordinateService.getCoordinateByElementId(elementEntity.getElementId()));
+ }
+
+ /**
+ * Query an element based on the element id。
+ * @param elementId
+ * @return
+ */
+ private Optional<GroupElementEntity> getEntityByElementId(String elementId) {
+ return Optional.ofNullable(mapper.selectOne(
+ new LambdaQueryWrapper<GroupElementEntity>()
+ .eq(GroupElementEntity::getElementId, elementId)));
+ }
+
+ /**
+ * Convert database entity objects into element data transfer object.
+ * @param entity
+ * @return
+ */
+ private GroupElementDTO entityConvertToDto(GroupElementEntity entity) {
+ GroupElementDTO.GroupElementDTOBuilder builder = GroupElementDTO.builder();
+ if (entity == null) {
+ return builder.build();
+ }
+
+ return builder
+ .display(entity.getDisplay())
+ .groupId(entity.getGroupId())
+ .elementId(entity.getElementId())
+ .name(entity.getElementName())
+ .createTime(entity.getCreateTime())
+ .updateTime(entity.getUpdateTime())
+ .resource(ElementResourceDTO.builder()
+ .type(entity.getElementType())
+ .username(entity.getUsername())
+ .build())
+ .build();
+ }
+
+ /**
+ * Convert the received element object into a database entity object.
+ * @param elementCreate
+ * @return
+ */
+ private GroupElementEntity createDtoConvertToEntity(ElementCreateDTO elementCreate) {
+ ContentPropertyDTO properties = elementCreate.getResource().getContent().getProperties();
+ return GroupElementEntity.builder()
+ .elementId(elementCreate.getId())
+ .elementName(elementCreate.getName())
+ .username(elementCreate.getResource().getUsername())
+ .elementType(ElementTypeEnum.findVal(elementCreate.getResource().getContent().getGeometry().getType()))
+ .clampToGround(properties.getClampToGround() != null && properties.getClampToGround())
+ .color(properties.getColor())
+ .build();
+ }
+
+ /**
+ * Add the content that needs to be updated to the entity object to be updated.
+ * @param elementUpdate
+ * @param groupElement
+ */
+ private void updateEntityWithDto(ElementUpdateDTO elementUpdate, GroupElementEntity groupElement) {
+ if (elementUpdate == null || groupElement == null) {
+ return;
+ }
+
+ groupElement.setElementName(elementUpdate.getName());
+ groupElement.setElementType(ElementTypeEnum.findVal(elementUpdate.getContent().getGeometry().getType()));
+ groupElement.setColor(elementUpdate.getContent().getProperties().getColor());
+
+ boolean clampToGround = elementUpdate.getContent().getProperties().getClampToGround();
+ groupElement.setClampToGround(clampToGround);
+ }
+}
diff --git a/src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java b/src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java
new file mode 100644
index 0000000..07ac7f1
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java
@@ -0,0 +1,66 @@
+package com.dji.sample.map.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.map.dao.IGroupMapper;
+import com.dji.sample.map.model.dto.GroupDTO;
+import com.dji.sample.map.model.entity.GroupEntity;
+import com.dji.sample.map.service.IGroupElementService;
+import com.dji.sample.map.service.IGroupService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/29
+ */
+@Service
+@Transactional
+public class GroupServiceImpl implements IGroupService {
+
+ @Autowired
+ private IGroupMapper mapper;
+
+ @Autowired
+ private IGroupElementService groupElementService;
+
+ @Override
+ public List<GroupDTO> getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed) {
+
+ return mapper.selectList(
+ new LambdaQueryWrapper<GroupEntity>()
+ .eq(GroupEntity::getWorkspaceId, workspaceId)
+ .eq(StringUtils.hasText(groupId), GroupEntity::getGroupId, groupId)
+ .eq(isDistributed != null, GroupEntity::getIsDistributed, isDistributed))
+ .stream()
+ .map(this::entityConvertToDto)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Convert database entity objects into group data transfer object.
+ * @param entity
+ * @return
+ */
+ private GroupDTO entityConvertToDto(GroupEntity entity) {
+ GroupDTO.GroupDTOBuilder builder = GroupDTO.builder();
+
+ if (entity == null) {
+ return builder.build();
+ }
+
+ return builder
+ .id(entity.getGroupId())
+ .name(entity.getGroupName())
+ .type(entity.getGroupType())
+ .isLock(entity.getIsLock())
+ .isDistributed(entity.getIsDistributed())
+ .createTime(entity.getCreateTime())
+ .build();
+ }
+}
diff --git a/src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java b/src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java
new file mode 100644
index 0000000..49b5805
--- /dev/null
+++ b/src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java
@@ -0,0 +1,109 @@
+package com.dji.sample.map.service.impl;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.map.model.dto.ElementCreateDTO;
+import com.dji.sample.map.model.dto.ElementUpdateDTO;
+import com.dji.sample.map.model.dto.GroupDTO;
+import com.dji.sample.map.model.dto.GroupElementDTO;
+import com.dji.sample.map.service.IElementCoordinateService;
+import com.dji.sample.map.service.IGroupElementService;
+import com.dji.sample.map.service.IGroupService;
+import com.dji.sample.map.service.IWorkspaceElementService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/11/30
+ */
+@Transactional
+@Service
+public class WorkspaceElementServiceImpl implements IWorkspaceElementService {
+
+ @Autowired
+ private IGroupService groupService;
+
+ @Autowired
+ private IGroupElementService groupElementService;
+
+ @Autowired
+ private IElementCoordinateService elementCoordinateService;
+
+ @Override
+ public List<GroupDTO> getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed) {
+ List<GroupDTO> groupList = groupService.getAllGroupsByWorkspaceId(workspaceId, groupId, isDistributed);
+ groupList.forEach(group -> group.setElements(
+ groupElementService.getElementsByGroupId(group.getId())
+ ));
+ return groupList;
+ }
+
+ @Override
+ public ResponseResult saveElement(String groupId, ElementCreateDTO elementCreate) {
+ boolean saveElement = groupElementService.saveElement(groupId, elementCreate);
+ if (!saveElement) {
+ return ResponseResult.error("Failed to save the element.");
+ }
+
+ // save coordinate
+ boolean saveCoordinate = elementCoordinateService.saveCoordinate(
+ elementCreate.getResource().getContent().getGeometry().convertToList(), elementCreate.getId());
+
+ return saveCoordinate ?
+ ResponseResult.success() : ResponseResult.error("Failed to save the coordinate.");
+ }
+
+
+ @Override
+ public ResponseResult updateElement(String elementId, ElementUpdateDTO elementUpdate, String username) {
+ boolean updElement = groupElementService.updateElement(elementId, elementUpdate, username);
+ if (!updElement) {
+ return ResponseResult.error("Failed to update the element.");
+ }
+
+ // delete all coordinates according to element id.
+ boolean delCoordinate = elementCoordinateService.deleteCoordinateByElementId(elementId);
+ // save coordinate
+ boolean saveCoordinate = elementCoordinateService.saveCoordinate(
+ elementUpdate.getContent().getGeometry().convertToList(), elementId);
+
+ return delCoordinate && saveCoordinate ?
+ ResponseResult.success() : ResponseResult.error("Failed to update the coordinate.");
+ }
+
+ @Override
+ public ResponseResult deleteElement(String elementId) {
+ boolean delElement = groupElementService.deleteElement(elementId);
+ if (!delElement) {
+ return ResponseResult.error("Failed to delete the element.");
+ }
+
+ // delete all coordinates according to element id.
+ boolean delCoordinate = elementCoordinateService.deleteCoordinateByElementId(elementId);
+
+ return delCoordinate ?
+ ResponseResult.success() : ResponseResult.error("Failed to delete the coordinate.");
+ }
+
+ @Override
+ public Optional<GroupElementDTO> getElementByElementId(String elementId) {
+ return groupElementService.getElementByElementId(elementId);
+ }
+
+ @Override
+ public ResponseResult deleteAllElementByGroupId(String groupId) {
+ List<GroupElementDTO> groupElementList = groupElementService.getElementsByGroupId(groupId);
+ for (GroupElementDTO groupElement : groupElementList) {
+ ResponseResult response = this.deleteElement(groupElement.getElementId());
+ if (ResponseResult.CODE_SUCCESS != response.getCode()) {
+ return response;
+ }
+ }
+ return ResponseResult.success();
+ }
+}
diff --git a/src/main/java/com/dji/sample/media/controller/FileController.java b/src/main/java/com/dji/sample/media/controller/FileController.java
new file mode 100644
index 0000000..25a6015
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/controller/FileController.java
@@ -0,0 +1,36 @@
+package com.dji.sample.media.controller;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.media.model.MediaFileDTO;
+import com.dji.sample.media.service.IFileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+@RestController
+@RequestMapping("${url.media.prefix}${url.media.version}/files")
+public class FileController {
+
+ @Autowired
+ private IFileService fileService;
+
+ /**
+ * Get information about all the media files in this workspace based on the workspace id.
+ * @param workspaceId
+ * @return
+ */
+ @GetMapping("/{workspace_id}/files")
+ public ResponseResult<List<MediaFileDTO>> getFilesList(@PathVariable(name = "workspace_id") String workspaceId) {
+ List<MediaFileDTO> filesList = fileService.getAllFilesByWorkspaceId(workspaceId);
+ return ResponseResult.success(filesList);
+ }
+}
diff --git a/src/main/java/com/dji/sample/media/controller/MediaController.java b/src/main/java/com/dji/sample/media/controller/MediaController.java
new file mode 100644
index 0000000..0e73743
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/controller/MediaController.java
@@ -0,0 +1,73 @@
+package com.dji.sample.media.controller;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.media.model.FileUploadDTO;
+import com.dji.sample.media.service.IMediaService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/7
+ */
+@Slf4j
+@RestController
+@RequestMapping("${url.media.prefix}${url.media.version}/workspaces")
+public class MediaController {
+
+ @Autowired
+ private IMediaService mediaService;
+
+ /**
+ * Check if the file has been uploaded by the fingerprint.
+ * @param workspaceId
+ * @param file
+ * @return
+ */
+ @PostMapping("/{workspace_id}/fast-upload")
+ public ResponseResult fastUpload(@PathVariable(name = "workspace_id") String workspaceId, @RequestBody FileUploadDTO file) {
+
+ boolean isExist = mediaService.fastUpload(workspaceId, file.getFingerprint());
+
+ return isExist ? ResponseResult.success() : ResponseResult.error(file.getFingerprint() + "already exists.");
+ }
+
+ /**
+ * When the file is uploaded to the storage server by pilot,
+ * the basic information of the file is reported through this interface.
+ * @param workspaceId
+ * @param file
+ * @return
+ */
+ @PostMapping("/{workspace_id}/upload-callback")
+ public ResponseResult<String> uploadCallback(@PathVariable(name = "workspace_id") String workspaceId, @RequestBody FileUploadDTO file) {
+ mediaService.saveMediaFile(workspaceId, file);
+ return ResponseResult.success(file.getObjectKey());
+
+ }
+
+ /**
+ * Query the files that already exist in this workspace based on the workspace id and the collection of tiny fingerprints.
+ * @param workspaceId
+ * @param tinyFingerprints
+ * @return
+ */
+ @GetMapping("/{workspace_id}/files/tiny-fingerprints")
+ public ResponseResult<Map<String, List<String>>> uploadCallback(@PathVariable(name = "workspace_id") String workspaceId,
+ @RequestParam(value = "tiny_fingerprint") List<String> tinyFingerprints) {
+ List<String> tinyFingerprintList = mediaService.getAllTinyFingerprintsByWorkspaceId(workspaceId);
+ List<String> existingList = tinyFingerprints
+ .stream()
+ .filter(tinyFingerprintList::contains)
+ .collect(Collectors.toList());
+ return ResponseResult.success(new ConcurrentHashMap<>(Map.of("tiny_fingerprints", existingList)));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dji/sample/media/dao/IFileMapper.java b/src/main/java/com/dji/sample/media/dao/IFileMapper.java
new file mode 100644
index 0000000..ec01af1
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/dao/IFileMapper.java
@@ -0,0 +1,12 @@
+package com.dji.sample.media.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.media.model.MediaFileEntity;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+public interface IFileMapper extends BaseMapper<MediaFileEntity> {
+}
diff --git a/src/main/java/com/dji/sample/media/model/CredentialsDTO.java b/src/main/java/com/dji/sample/media/model/CredentialsDTO.java
new file mode 100644
index 0000000..2662254
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/model/CredentialsDTO.java
@@ -0,0 +1,40 @@
+package com.dji.sample.media.model;
+
+import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
+import io.minio.credentials.Credentials;
+import lombok.Data;
+
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/7
+ */
+@Data
+public class CredentialsDTO {
+
+ private String accessKeyId;
+
+ private String accessKeySecret;
+
+ private Integer expire;
+
+ private String securityToken;
+
+ public CredentialsDTO(Credentials credentials, int expire) {
+ this.accessKeyId = credentials.accessKey();
+ this.accessKeySecret = credentials.secretKey();
+ this.securityToken = credentials.sessionToken();
+ this.expire = expire;
+ }
+
+ public CredentialsDTO(AssumeRoleResponse.Credentials credentials, long expire) {
+ this.accessKeyId = credentials.getAccessKeyId();
+ this.accessKeySecret = credentials.getAccessKeySecret();
+ this.securityToken = credentials.getSecurityToken();
+ this.expire = Math.toIntExact(expire);
+ }
+
+ public CredentialsDTO() {
+ }
+}
diff --git a/src/main/java/com/dji/sample/media/model/FileExtensionDTO.java b/src/main/java/com/dji/sample/media/model/FileExtensionDTO.java
new file mode 100644
index 0000000..5a33bc6
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/model/FileExtensionDTO.java
@@ -0,0 +1,26 @@
+package com.dji.sample.media.model;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/7
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class FileExtensionDTO {
+
+ private String droneModelKey;
+
+ private Boolean isOriginal;
+
+ private String payloadModelKey;
+
+ private String tinnyFingerprint;
+
+ private String sn;
+
+}
diff --git a/src/main/java/com/dji/sample/media/model/FileMetadataDTO.java b/src/main/java/com/dji/sample/media/model/FileMetadataDTO.java
new file mode 100644
index 0000000..bab4b5a
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/model/FileMetadataDTO.java
@@ -0,0 +1,31 @@
+package com.dji.sample.media.model;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/7
+ */
+@Data
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class FileMetadataDTO {
+
+ private Double absoluteAltitude;
+
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssX")
+ private Date createdTime;
+
+ private Double gimbalYawDegree;
+
+ private PositionDTO photoedPosition;
+
+ private PositionDTO shootPosition;
+
+ private Double relativeAltitude;
+}
diff --git a/src/main/java/com/dji/sample/media/model/FileUploadDTO.java b/src/main/java/com/dji/sample/media/model/FileUploadDTO.java
new file mode 100644
index 0000000..acfa910
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/model/FileUploadDTO.java
@@ -0,0 +1,26 @@
+package com.dji.sample.media.model;
+
+import lombok.Data;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/7
+ */
+@Data
+public class FileUploadDTO {
+
+ private FileExtensionDTO ext;
+
+ private String fingerprint;
+
+ private String name;
+
+ private String path;
+
+ private FileMetadataDTO metadata;
+
+ private String objectKey;
+
+ private Integer subFileType;
+}
diff --git a/src/main/java/com/dji/sample/media/model/MediaFileDTO.java b/src/main/java/com/dji/sample/media/model/MediaFileDTO.java
new file mode 100644
index 0000000..b42acf8
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/model/MediaFileDTO.java
@@ -0,0 +1,34 @@
+package com.dji.sample.media.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MediaFileDTO {
+
+ private String fileName;
+
+ private String filePath;
+
+ private String objectKey;
+
+ private String subFileType;
+
+ private Boolean isOriginal;
+
+ private String drone;
+
+ private String payload;
+
+ private String tinnyFingerprint;
+}
diff --git a/src/main/java/com/dji/sample/media/model/MediaFileEntity.java b/src/main/java/com/dji/sample/media/model/MediaFileEntity.java
new file mode 100644
index 0000000..a3dd2aa
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/model/MediaFileEntity.java
@@ -0,0 +1,62 @@
+package com.dji.sample.media.model;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+@TableName(value = "media_file")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MediaFileEntity implements Serializable {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField("file_name")
+ private String fileName;
+
+ @TableField("file_path")
+ private String filePath;
+
+ @TableField("workspace_id")
+ private String workspaceId;
+
+ @TableField("fingerprint")
+ private String fingerprint;
+
+ @TableField("tinny_fingerprint")
+ private String tinnyFingerprint;
+
+ @TableField("object_key")
+ private String objectKey;
+
+ @TableField("sub_file_type")
+ private Integer subFileType;
+
+ @TableField("is_original")
+ private Boolean isOriginal;
+
+ @TableField("drone")
+ private String drone;
+
+ @TableField("payload")
+ private String payload;
+
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Long createTime;
+
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Long updateTime;
+}
+
diff --git a/src/main/java/com/dji/sample/media/model/PositionDTO.java b/src/main/java/com/dji/sample/media/model/PositionDTO.java
new file mode 100644
index 0000000..1b5cda4
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/model/PositionDTO.java
@@ -0,0 +1,16 @@
+package com.dji.sample.media.model;
+
+import lombok.Data;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/7
+ */
+@Data
+public class PositionDTO {
+
+ private Double lat;
+
+ private Double lng;
+}
diff --git a/src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java b/src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java
new file mode 100644
index 0000000..a4da2ab
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java
@@ -0,0 +1,33 @@
+package com.dji.sample.media.model;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/7
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class StsCredentialsDTO {
+
+ private String bucket;
+
+ private CredentialsDTO credentials;
+
+ private String endpoint;
+
+ private String objectKeyPrefix;
+
+ private String provider;
+
+ private String region;
+}
diff --git a/src/main/java/com/dji/sample/media/service/IFileService.java b/src/main/java/com/dji/sample/media/service/IFileService.java
new file mode 100644
index 0000000..9347ccf
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/service/IFileService.java
@@ -0,0 +1,37 @@
+package com.dji.sample.media.service;
+
+import com.dji.sample.media.model.FileUploadDTO;
+import com.dji.sample.media.model.MediaFileDTO;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+public interface IFileService {
+
+ /**
+ * Query if the file already exists based on the workspace id and the fingerprint of the file.
+ * @param workspaceId
+ * @param fingerprint
+ * @return
+ */
+ Boolean checkExist(String workspaceId, String fingerprint);
+
+ /**
+ * Save the basic information of the file to the database.
+ * @param workspaceId
+ * @param file
+ * @return
+ */
+ Integer saveFile(String workspaceId, FileUploadDTO file);
+
+ /**
+ * Query information about all files in this workspace based on the workspace id.
+ * @param workspaceId
+ * @return
+ */
+ List<MediaFileDTO> getAllFilesByWorkspaceId(String workspaceId);
+}
diff --git a/src/main/java/com/dji/sample/media/service/IMediaService.java b/src/main/java/com/dji/sample/media/service/IMediaService.java
new file mode 100644
index 0000000..f8fc42d
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/service/IMediaService.java
@@ -0,0 +1,36 @@
+package com.dji.sample.media.service;
+
+import com.dji.sample.media.model.FileUploadDTO;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+public interface IMediaService {
+
+ /**
+ * Check if the file has been uploaded by the fingerprint.
+ * @param workspaceId
+ * @param fingerprint
+ * @return
+ */
+ Boolean fastUpload(String workspaceId, String fingerprint);
+
+ /**
+ * Save the basic information of the file to the database.
+ * @param workspaceId
+ * @param file
+ * @return
+ */
+ Integer saveMediaFile(String workspaceId, FileUploadDTO file);
+
+ /**
+ * Query tiny fingerprints about all files in this workspace based on the workspace id.
+ * @param workspaceId
+ * @return
+ */
+ List<String> getAllTinyFingerprintsByWorkspaceId(String workspaceId);
+}
diff --git a/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java b/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java
new file mode 100644
index 0000000..744d9d7
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java
@@ -0,0 +1,112 @@
+package com.dji.sample.media.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
+import com.dji.sample.manage.service.IDeviceDictionaryService;
+import com.dji.sample.media.dao.IFileMapper;
+import com.dji.sample.media.model.FileUploadDTO;
+import com.dji.sample.media.model.MediaFileDTO;
+import com.dji.sample.media.model.MediaFileEntity;
+import com.dji.sample.media.service.IFileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+@Service
+@Transactional
+public class FileServiceImpl implements IFileService {
+
+
+ @Autowired
+ private IFileMapper mapper;
+
+ @Autowired
+ private IDeviceDictionaryService deviceDictionaryService;
+
+ @Override
+ public Boolean checkExist(String workspaceId, String fingerprint) {
+ MediaFileEntity fileEntity = mapper.selectOne(new LambdaQueryWrapper<MediaFileEntity>()
+ .eq(MediaFileEntity::getWorkspaceId, workspaceId)
+ .eq(MediaFileEntity::getFingerprint, fingerprint));
+ return fileEntity != null;
+ }
+
+ @Override
+ public Integer saveFile(String workspaceId, FileUploadDTO file) {
+ MediaFileEntity fileEntity = this.fileUploadConvertToEntity(file);
+ fileEntity.setWorkspaceId(workspaceId);
+ return mapper.insert(fileEntity);
+ }
+
+ @Override
+ public List<MediaFileDTO> getAllFilesByWorkspaceId(String workspaceId) {
+ return mapper.selectList(new LambdaQueryWrapper<MediaFileEntity>()
+ .eq(MediaFileEntity::getWorkspaceId, workspaceId))
+ .stream()
+ .map(this::entityConvertToDto)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Convert the received file object into a database entity object.
+ * @param file
+ * @return
+ */
+ private MediaFileEntity fileUploadConvertToEntity(FileUploadDTO file) {
+ MediaFileEntity.MediaFileEntityBuilder builder = MediaFileEntity.builder();
+
+ if (file != null) {
+ builder.fileName(file.getName())
+ .filePath(file.getPath())
+ .fingerprint(file.getFingerprint())
+ .objectKey(file.getObjectKey())
+ .subFileType(file.getSubFileType())
+ .isOriginal(file.getExt().getIsOriginal())
+ .drone(file.getExt().getSn())
+ .tinnyFingerprint(file.getExt().getTinnyFingerprint());
+
+ // domain-type-subType
+ int[] payloadModel = Arrays.stream(file.getExt().getPayloadModelKey().split("-"))
+ .map(Integer::valueOf)
+ .mapToInt(Integer::intValue)
+ .toArray();
+ Optional<DeviceDictionaryDTO> payloadDict = deviceDictionaryService
+ .getOneDictionaryInfoByDomainTypeSubType(payloadModel[0], payloadModel[1], payloadModel[2]);
+ payloadDict.ifPresent(payload -> builder.payload(payload.getDeviceName()));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Convert database entity objects into file data transfer object.
+ * @param entity
+ * @return
+ */
+ private MediaFileDTO entityConvertToDto(MediaFileEntity entity) {
+ MediaFileDTO.MediaFileDTOBuilder builder = MediaFileDTO.builder();
+
+ if (entity != null) {
+ builder.fileName(entity.getFileName())
+ .filePath(entity.getFilePath())
+ .isOriginal(entity.getIsOriginal())
+ .objectKey(entity.getObjectKey())
+ .tinnyFingerprint(entity.getTinnyFingerprint())
+ .payload(entity.getPayload())
+ .drone(entity.getDrone());
+
+ }
+
+ return builder.build();
+ }
+
+}
diff --git a/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java b/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java
new file mode 100644
index 0000000..f7758d8
--- /dev/null
+++ b/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java
@@ -0,0 +1,41 @@
+package com.dji.sample.media.service.impl;
+
+import com.dji.sample.media.model.FileUploadDTO;
+import com.dji.sample.media.model.MediaFileDTO;
+import com.dji.sample.media.service.IFileService;
+import com.dji.sample.media.service.IMediaService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean
+ * @version 0.2
+ * @date 2021/12/9
+ */
+@Service
+public class MediaServiceImpl implements IMediaService {
+
+ @Autowired
+ private IFileService fileService;
+
+ @Override
+ public Boolean fastUpload(String workspaceId, String fingerprint) {
+ return fileService.checkExist(workspaceId, fingerprint);
+ }
+
+ @Override
+ public Integer saveMediaFile(String workspaceId, FileUploadDTO file) {
+ return fileService.saveFile(workspaceId, file);
+ }
+
+ @Override
+ public List<String> getAllTinyFingerprintsByWorkspaceId(String workspaceId) {
+ return fileService.getAllFilesByWorkspaceId(workspaceId)
+ .stream()
+ .map(MediaFileDTO::getTinnyFingerprint)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/com/dji/sample/storage/controller/StorageController.java b/src/main/java/com/dji/sample/storage/controller/StorageController.java
new file mode 100644
index 0000000..801146c
--- /dev/null
+++ b/src/main/java/com/dji/sample/storage/controller/StorageController.java
@@ -0,0 +1,51 @@
+package com.dji.sample.storage.controller;
+
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.component.oss.model.AliyunOSSConfiguration;
+import com.dji.sample.component.oss.model.MinIOConfiguration;
+import com.dji.sample.media.model.StsCredentialsDTO;
+import com.dji.sample.storage.service.IStorageService;
+import com.dji.sample.storage.service.impl.AliyunStorageServiceImpl;
+import com.dji.sample.storage.service.impl.MinIOStorageServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/29
+ */
+@RestController
+@RequestMapping("${url.storage.prefix}${url.storage.version}/workspaces/")
+public class StorageController {
+
+ private IStorageService storageService;
+
+ @Autowired
+ private void setOssService(@Autowired AliyunStorageServiceImpl aliyunStorageService,
+ @Autowired MinIOStorageServiceImpl minIOStorageService) {
+ if (AliyunOSSConfiguration.enable) {
+ this.storageService = aliyunStorageService;
+ return;
+ }
+ if (MinIOConfiguration.enable) {
+ this.storageService = minIOStorageService;
+ return;
+ }
+ throw new NullPointerException("storageService is null.");
+ }
+ /**
+ * Get temporary credentials for uploading the media and wayline in DJI Pilot.
+ * @param workspaceId
+ * @return
+ */
+ @PostMapping("/{workspace_id}/sts")
+ public ResponseResult<StsCredentialsDTO> getSTSCredentials(@PathVariable(name = "workspace_id") String workspaceId) {
+
+ StsCredentialsDTO stsCredentials = storageService.getSTSCredentials();
+ return ResponseResult.success(stsCredentials);
+ }
+}
diff --git a/src/main/java/com/dji/sample/storage/service/IStorageService.java b/src/main/java/com/dji/sample/storage/service/IStorageService.java
new file mode 100644
index 0000000..0dc98c9
--- /dev/null
+++ b/src/main/java/com/dji/sample/storage/service/IStorageService.java
@@ -0,0 +1,17 @@
+package com.dji.sample.storage.service;
+
+import com.dji.sample.media.model.StsCredentialsDTO;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/29
+ */
+public interface IStorageService {
+
+ /**
+ * Get custom temporary credentials object for uploading the media and wayline.
+ * @return temporary credentials object
+ */
+ StsCredentialsDTO getSTSCredentials();
+}
diff --git a/src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java b/src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java
new file mode 100644
index 0000000..46366a5
--- /dev/null
+++ b/src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java
@@ -0,0 +1,32 @@
+package com.dji.sample.storage.service.impl;
+
+import com.dji.sample.component.oss.model.AliyunOSSConfiguration;
+import com.dji.sample.component.oss.service.impl.AliyunOssServiceImpl;
+import com.dji.sample.media.model.StsCredentialsDTO;
+import com.dji.sample.storage.service.IStorageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2022/3/9
+ */
+@Service
+public class AliyunStorageServiceImpl implements IStorageService {
+
+ @Autowired
+ private AliyunOssServiceImpl ossService;
+
+ @Override
+ public StsCredentialsDTO getSTSCredentials() {
+ return StsCredentialsDTO.builder()
+ .endpoint(AliyunOSSConfiguration.endpoint)
+ .bucket(AliyunOSSConfiguration.bucket)
+ .credentials(ossService.getCredentials())
+ .provider(AliyunOSSConfiguration.PROVIDER)
+ .objectKeyPrefix(AliyunOSSConfiguration.objectDirPrefix)
+ .region(AliyunOSSConfiguration.region)
+ .build();
+ }
+}
diff --git a/src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java b/src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java
new file mode 100644
index 0000000..b03e856
--- /dev/null
+++ b/src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java
@@ -0,0 +1,34 @@
+package com.dji.sample.storage.service.impl;
+
+import com.dji.sample.component.oss.model.MinIOConfiguration;
+import com.dji.sample.component.oss.service.impl.MinIOServiceImpl;
+import com.dji.sample.media.model.StsCredentialsDTO;
+import com.dji.sample.storage.service.IStorageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/31
+ */
+@Service
+public class MinIOStorageServiceImpl implements IStorageService {
+
+ @Autowired
+ private MinIOServiceImpl ossService;
+
+ @Override
+ public StsCredentialsDTO getSTSCredentials() {
+ return StsCredentialsDTO.builder()
+ .endpoint(MinIOConfiguration.endpoint)
+ .bucket(MinIOConfiguration.bucket)
+ .credentials(ossService.getCredentials())
+ .provider(MinIOConfiguration.PROVIDER)
+ .objectKeyPrefix(MinIOConfiguration.objectDirPrefix)
+ .region(MinIOConfiguration.region)
+ .build();
+ }
+
+
+}
diff --git a/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java b/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java
new file mode 100644
index 0000000..dbd9025
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java
@@ -0,0 +1,150 @@
+package com.dji.sample.wayline.controller;
+
+import com.dji.sample.common.model.CustomClaim;
+import com.dji.sample.common.model.PaginationData;
+import com.dji.sample.common.model.ResponseResult;
+import com.dji.sample.component.oss.model.AliyunOSSConfiguration;
+import com.dji.sample.wayline.model.WaylineFileDTO;
+import com.dji.sample.wayline.model.WaylineFileUploadDTO;
+import com.dji.sample.wayline.model.WaylineQueryParam;
+import com.dji.sample.wayline.service.IWaylineFileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@RestController
+@RequestMapping("${url.wayline.prefix}${url.wayline.version}/workspaces")
+public class WaylineFileController {
+
+ @Autowired
+ private IWaylineFileService waylineFileService;
+
+ /**
+ * Query the basic data of the wayline file according to the query conditions.
+ * The query condition field in pilot is fixed.
+ * @param orderBy Sorted fields. Spliced at the end of the sql statement.
+ * @param favorited Whether the wayline file is favorited or not.
+ * @param page
+ * @param pageSize
+ * @param templateType
+ * @param workspaceId
+ * @return
+ */
+ @GetMapping("/{workspace_id}/waylines")
+ public ResponseResult<PaginationData<WaylineFileDTO>> getWaylinesPagination(@RequestParam(name = "order_by") String orderBy,
+ @RequestParam(required = false) boolean favorited, @RequestParam Integer page,
+ @RequestParam(name = "page_size", defaultValue = "10") Integer pageSize,
+ @RequestParam(name = "template_type", required = false) Integer[] templateType,
+ @PathVariable(name = "workspace_id") String workspaceId) {
+ WaylineQueryParam param = WaylineQueryParam.builder()
+ .favorited(favorited)
+ .page(page)
+ .pageSize(pageSize)
+ .orderBy(orderBy)
+ .templateType(templateType)
+ .build();
+ PaginationData<WaylineFileDTO> data = waylineFileService.getWaylinesByParam(workspaceId, param);
+ return ResponseResult.success(data);
+ }
+
+ /**
+ * Query the download address of the file according to the wayline file id,
+ * and redirect to this address directly for download.
+ * @param workspaceId
+ * @param waylineId
+ * @param response
+ */
+ @GetMapping("/{workspace_id}/waylines/{wayline_id}/url")
+ public void getFileUrl(@PathVariable(name = "workspace_id") String workspaceId,
+ @PathVariable(name = "wayline_id") String waylineId, HttpServletResponse response) {
+
+ WaylineFileDTO wayline = waylineFileService.getWaylineByWaylineId(workspaceId, waylineId);
+ URL url = waylineFileService.getObjectUrl(AliyunOSSConfiguration.bucket, wayline.getObjectKey());
+
+ try {
+ response.sendRedirect(url.toString());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * When the wayline file is uploaded to the storage server by pilot,
+ * the basic information of the file is reported through this interface.
+ * @param request
+ * @param workspaceId
+ * @param uploadFile
+ * @return
+ */
+ @PostMapping("/{workspace_id}/upload-callback")
+ public ResponseResult uploadCallBack(HttpServletRequest request,
+ @PathVariable(name = "workspace_id") String workspaceId,
+ @RequestBody WaylineFileUploadDTO uploadFile) {
+
+ CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
+
+ WaylineFileDTO metadata = uploadFile.getMetadata();
+ metadata.setUsername(customClaim.getUsername());
+ metadata.setObjectKey(uploadFile.getObjectKey());
+ metadata.setName(uploadFile.getName());
+
+ int id = waylineFileService.saveWaylineFile(workspaceId, metadata);
+
+ return id <= 0 ? ResponseResult.error() : ResponseResult.success();
+ }
+
+ /**
+ * Favorite the wayline file according to the wayline file id.
+ * @param workspaceId
+ * @param ids wayline file id
+ * @return
+ */
+ @PostMapping("/{workspace_id}/favorites")
+ public ResponseResult markFavorite(@PathVariable(name = "workspace_id") String workspaceId,
+ @RequestParam(name = "id") List<String> ids) {
+ boolean isMark = waylineFileService.markFavorite(workspaceId, ids, true);
+
+ return isMark ? ResponseResult.success() : ResponseResult.error();
+ }
+
+ /**
+ * Delete the favorites of this wayline file based on the wayline file id.
+ * @param workspaceId
+ * @param ids wayline file id
+ * @return
+ */
+ @DeleteMapping("/{workspace_id}/favorites")
+ public ResponseResult unmarkFavorite(@PathVariable(name = "workspace_id") String workspaceId,
+ @RequestParam(name = "id") List<String> ids) {
+ boolean isMark = waylineFileService.markFavorite(workspaceId, ids, false);
+
+ return isMark ? ResponseResult.success() : ResponseResult.error();
+ }
+
+ /**
+ * Checking whether the name already exists according to the wayline name must ensure the uniqueness of the wayline name.
+ * This interface will be called when uploading waylines and must be available.
+ * @param workspaceId
+ * @param names
+ * @return
+ */
+ @GetMapping("/{workspace_id}/waylines/duplicate-names")
+ public ResponseResult checkDuplicateNames(@PathVariable(name = "workspace_id") String workspaceId,
+ @RequestParam(name = "name") List<String> names) {
+ List<String> existNamesList = waylineFileService.getDuplicateNames(workspaceId, names);
+
+ return ResponseResult.success(existNamesList);
+ }
+}
diff --git a/src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java b/src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java
new file mode 100644
index 0000000..10a0520
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java
@@ -0,0 +1,12 @@
+package com.dji.sample.wayline.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.dji.sample.wayline.model.WaylineFileEntity;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+public interface IWaylineFileMapper extends BaseMapper<WaylineFileEntity> {
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java b/src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java
new file mode 100644
index 0000000..9b044db
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java
@@ -0,0 +1,41 @@
+package com.dji.sample.wayline.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class WaylineFileDTO {
+
+ private String name;
+
+ @JsonProperty("id")
+ private String waylineId;
+
+ private String droneModelKey;
+
+ private List<String> payloadModelKeys;
+
+ private Boolean favorited;
+
+ private List<Integer> templateTypes;
+
+ private String objectKey;
+
+ @JsonProperty("user_name")
+ private String username;
+
+ private Long updateTime;
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java b/src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java
new file mode 100644
index 0000000..34295b3
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java
@@ -0,0 +1,57 @@
+package com.dji.sample.wayline.model;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@Data
+@TableName("wayline_file")
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class WaylineFileEntity {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ @TableField("name")
+ private String name;
+
+ @TableField("wayline_id")
+ private String waylineId;
+
+ @TableField("drone_model_key")
+ private String droneModelKey;
+
+ @TableField("payload_model_keys")
+ private String payloadModelKeys;
+
+ @TableField("workspace_id")
+ private String workspaceId;
+
+ @TableField("favorited")
+ private Boolean favorited;
+
+ @TableField("template_types")
+ private String templateTypes;
+
+ @TableField("object_key")
+ private String objectKey;
+
+ @TableField("user_name")
+ private String username;
+
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Long createTime;
+
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Long updateTime;
+
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java b/src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java
new file mode 100644
index 0000000..53b7996
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java
@@ -0,0 +1,18 @@
+package com.dji.sample.wayline.model;
+
+import lombok.Data;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/23
+ */
+@Data
+public class WaylineFileUploadDTO {
+
+ private String objectKey;
+
+ private String name;
+
+ private WaylineFileDTO metadata;
+}
diff --git a/src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java b/src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java
new file mode 100644
index 0000000..21db9fe
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java
@@ -0,0 +1,30 @@
+package com.dji.sample.wayline.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class WaylineQueryParam {
+
+ private boolean favorited;
+
+ @Builder.Default
+ private int page = 1;
+
+ @Builder.Default
+ private int pageSize = 10;
+
+ private String orderBy;
+
+ private Integer[] templateType;
+}
diff --git a/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java b/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java
new file mode 100644
index 0000000..941890d
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java
@@ -0,0 +1,65 @@
+package com.dji.sample.wayline.service;
+
+import com.dji.sample.common.model.PaginationData;
+import com.dji.sample.wayline.model.WaylineFileDTO;
+import com.dji.sample.wayline.model.WaylineQueryParam;
+
+import java.net.URL;
+import java.util.List;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+public interface IWaylineFileService {
+
+ /**
+ * Perform paging queries based on query parameters.
+ * @param workspaceId
+ * @param param
+ * @return
+ */
+ PaginationData<WaylineFileDTO> getWaylinesByParam(String workspaceId, WaylineQueryParam param);
+
+ /**
+ * Query the information of this wayline file according to the wayline file id.
+ * @param workspaceId
+ * @param waylineId
+ * @return
+ */
+ WaylineFileDTO getWaylineByWaylineId(String workspaceId, String waylineId);
+
+ /**
+ * Get the download address of the file object.
+ * @param bucket bucket name
+ * @param objectKey object name
+ * @return
+ */
+ URL getObjectUrl(String bucket, String objectKey);
+
+ /**
+ * Save the basic information of the wayline file.
+ * @param workspaceId
+ * @param metadata
+ * @return
+ */
+ Integer saveWaylineFile(String workspaceId, WaylineFileDTO metadata);
+
+ /**
+ * Updates whether the file is collected or not based on the passed parameters.
+ * @param workspaceId
+ * @param ids wayline id
+ * @param isFavorite Whether the wayline file is favorited or not.
+ * @return
+ */
+ Boolean markFavorite(String workspaceId, List<String> ids, Boolean isFavorite);
+
+ /**
+ * Batch query for duplicate file names in workspace.
+ * @param workspaceId
+ * @param names
+ * @return
+ */
+ List<String> getDuplicateNames(String workspaceId, List<String> names);
+}
diff --git a/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java
new file mode 100644
index 0000000..eb6bc5e
--- /dev/null
+++ b/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java
@@ -0,0 +1,179 @@
+package com.dji.sample.wayline.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.dji.sample.common.model.Pagination;
+import com.dji.sample.common.model.PaginationData;
+import com.dji.sample.component.oss.model.AliyunOSSConfiguration;
+import com.dji.sample.component.oss.model.MinIOConfiguration;
+import com.dji.sample.component.oss.service.IOssService;
+import com.dji.sample.component.oss.service.impl.AliyunOssServiceImpl;
+import com.dji.sample.component.oss.service.impl.MinIOServiceImpl;
+import com.dji.sample.wayline.dao.IWaylineFileMapper;
+import com.dji.sample.wayline.model.WaylineFileDTO;
+import com.dji.sample.wayline.model.WaylineFileEntity;
+import com.dji.sample.wayline.model.WaylineQueryParam;
+import com.dji.sample.wayline.service.IWaylineFileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * @author sean
+ * @version 0.3
+ * @date 2021/12/22
+ */
+@Service
+@Transactional
+public class WaylineFileServiceImpl implements IWaylineFileService {
+
+ @Autowired
+ private IWaylineFileMapper mapper;
+
+ private IOssService ossService;
+
+ @Autowired
+ private void setOssService(@Autowired AliyunOssServiceImpl aliyunOssService, @Autowired MinIOServiceImpl minIOService) {
+ if (AliyunOSSConfiguration.enable) {
+ this.ossService = aliyunOssService;
+ return;
+ }
+ if (MinIOConfiguration.enable) {
+ this.ossService = minIOService;
+ return;
+ }
+ throw new NullPointerException("ossService is null.");
+ }
+
+ @Override
+ public PaginationData<WaylineFileDTO> getWaylinesByParam(String workspaceId, WaylineQueryParam param) {
+ // Paging Query
+ Page<WaylineFileEntity> page = mapper.selectPage(
+ new Page<WaylineFileEntity>(param.getPage(), param.getPageSize()),
+ new LambdaQueryWrapper<WaylineFileEntity>()
+ .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
+ .eq(param.isFavorited(), WaylineFileEntity::getFavorited, param.isFavorited())
+ .and(param.getTemplateType() != null, wrapper -> {
+ for (Integer type : param.getTemplateType()) {
+ wrapper.like(WaylineFileEntity::getTemplateTypes, type).or();
+ }
+ })
+ // There is a risk of SQL injection
+ .last(StringUtils.hasText(param.getOrderBy()), " order by " + param.getOrderBy()));
+
+ // Wrap the results of a paging query into a custom paging object.
+ List<WaylineFileDTO> records = page.getRecords()
+ .stream()
+ .map(this::entityConvertToDTO)
+ .collect(Collectors.toList());
+
+ return new PaginationData<>(records, new Pagination(page));
+ }
+
+ @Override
+ public WaylineFileDTO getWaylineByWaylineId(String workspaceId, String waylineId) {
+ return this.entityConvertToDTO(
+ mapper.selectOne(
+ new LambdaQueryWrapper<WaylineFileEntity>()
+ .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
+ .eq(WaylineFileEntity::getWaylineId, waylineId)));
+ }
+
+ @Override
+ public URL getObjectUrl(String bucket, String objectKey) {
+ return ossService.getObjectUrl(bucket, objectKey);
+ }
+
+ @Override
+ public Integer saveWaylineFile(String workspaceId, WaylineFileDTO metadata) {
+ WaylineFileEntity file = this.dtoConvertToEntity(metadata);
+ file.setWaylineId(UUID.randomUUID().toString());
+ file.setWorkspaceId(workspaceId);
+
+ int insertId = mapper.insert(file);
+ return insertId > 0 ? file.getId() : insertId;
+ }
+
+ @Override
+ public Boolean markFavorite(String workspaceId, List<String> waylineIds, Boolean isFavorite) {
+ if (waylineIds.isEmpty()) {
+ return false;
+ }
+ if (isFavorite == null) {
+ return true;
+ }
+ return mapper.update(null, new LambdaUpdateWrapper<WaylineFileEntity>()
+ .set(WaylineFileEntity::getFavorited, isFavorite)
+ .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
+ .in(WaylineFileEntity::getWaylineId, waylineIds)) > 0;
+ }
+
+ @Override
+ public List<String> getDuplicateNames(String workspaceId, List<String> names) {
+ return mapper.selectList(new LambdaQueryWrapper<WaylineFileEntity>()
+ .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
+ .in(WaylineFileEntity::getName, names))
+ .stream()
+ .map(WaylineFileEntity::getName)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Convert database entity objects into wayline data transfer object.
+ * @param entity
+ * @return
+ */
+ private WaylineFileDTO entityConvertToDTO(WaylineFileEntity entity) {
+ WaylineFileDTO.WaylineFileDTOBuilder builder = WaylineFileDTO.builder();
+
+ if (entity != null) {
+ builder.droneModelKey(entity.getDroneModelKey())
+ .favorited(entity.getFavorited())
+ .name(entity.getName())
+ .payloadModelKeys(entity.getPayloadModelKeys() != null ?
+ Arrays.asList(entity.getPayloadModelKeys().split(",")) : null)
+ .templateTypes(Arrays.stream(entity.getTemplateTypes().split(","))
+ .map(Integer::parseInt)
+ .collect(Collectors.toList()))
+ .username(entity.getUsername())
+ .objectKey(entity.getObjectKey())
+ .updateTime(entity.getUpdateTime())
+ .waylineId(entity.getWaylineId());
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Convert the received wayline object into a database entity object.
+ * @param file
+ * @return
+ */
+ private WaylineFileEntity dtoConvertToEntity(WaylineFileDTO file) {
+ WaylineFileEntity.WaylineFileEntityBuilder builder = WaylineFileEntity.builder();
+
+ if (file != null) {
+ builder.droneModelKey(file.getDroneModelKey())
+ .name(file.getName())
+ .username(file.getUsername())
+ .objectKey(file.getObjectKey())
+ // Separate multiple payload data with ",".
+ .payloadModelKeys(String.join(",", file.getPayloadModelKeys()))
+ .templateTypes(file.getTemplateTypes().stream()
+ .map(String::valueOf)
+ .collect(Collectors.joining(",")))
+ .favorited(file.getFavorited())
+ .build();
+ }
+
+ return builder.build();
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..57357e6
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,86 @@
+server:
+ port: 6789
+spring:
+ application:
+ name: cloud-api-sample
+ datasource:
+ druid:
+ type: com.alibaba.druid.pool.DruidDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/cloud_sample?useSSL=false
+ username: root
+ password: root
+ initial-size: 10
+ min-idle: 10
+ max-active: 20
+ max-wait: 60000
+
+ jackson:
+ property-naming-strategy: SNAKE_CASE
+ date-format: yyyy-MM-dd HH:mm:ss
+ default-property-inclusion: NON_NULL
+ deserialization:
+ FAIL_ON_UNKNOWN_PROPERTIES: false
+
+jwt:
+ issuer: DJI
+ subject: CloudApiSample
+ secret: CloudApiSample
+ age: 86400
+
+mqtt:
+ protocol: tcp
+ host: Please enter your IP.
+ port: 1883
+ username: JavaServer
+ password: 123456
+ client-id: 123456
+ # Topics that need to be subscribed when initially connecting to mqtt, multiple topics are divided by ",".
+ inbound-topic: sys/product/+/status
+
+url:
+ manage:
+ prefix: /manage
+ version: /api/v1
+ map:
+ prefix: /map
+ version: /api/v1
+ media:
+ prefix: /media
+ version: /api/v1
+ wayline:
+ prefix: /wayline
+ version: /api/v1
+ storage:
+ prefix: /storage
+ version: /api/v1
+
+aliyun:
+ oss:
+ enable: true
+ endpoint: Please enter your endpoint. #Example: https://oss-cn-shenzhen.aliyuncs.com
+ access-key: Please enter your access key.
+ secret-key: Please enter your secret key.
+ expire: 3600
+ region: Please enter oss region.
+ role-session-name: Please enter session name.
+ role-arn: Please enter role arn.
+ bucket: Please enter bucket name.
+ object-dir-prefix: Please enter object prefix.
+
+# MinIO is temporarily unavailable.
+minio:
+ enable: false
+ endpoint: Please enter your endpoint. #http://192.168.1.1:9000/
+ access-key: minioadmin
+ secret-key: minioadmin
+ bucket: Please enter bucket name.
+ expire: 3600
+ region: Please enter minio region.
+ object-dir-prefix: Please enter object prefix.
+
+logging:
+ level:
+ com.dji.sample: debug
+ file:
+ name: logs/cloud-api-sample.log
--
Gitblit v1.9.3