sean.zhou
2022-07-22 9b2eedb85d53ca32610c32c6e50b5230ab3b16cf
V1.1.0 for dock
91 files modified
82 files added
7129 ■■■■ changed files
README.md 6 ●●●● patch | view | raw | blame | history
api/Cloud API Demo.postman_collection.json 328 ●●●●● patch | view | raw | blame | history
api/Cloud API Demo.postman_environment.json 6 ●●●● patch | view | raw | blame | history
pom.xml 31 ●●●●● patch | view | raw | blame | history
sql/cloud_sample.sql 157 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/CloudApiSampleApplication.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/error/CommonErrorEnum.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/error/StorageErrorEnum.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/util/JwtUtil.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/ApplicationBootInitial.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/GlobalScheduleService.java 53 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java 52 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/AbstractStateTopicHandler.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java 48 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/InboundMessageRouter.java 118 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/RequestsRouter.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/StateDefaultHandler.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/StateDeviceBasicHandler.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/StateFirmwareVersionHandler.java 62 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/StateLiveCapacityHandler.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/StateRouter.java 104 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/StatusRouter.java 67 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/Chan.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/ErrorInfoReply.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/RequestsMethodEnum.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/RequestsReply.java 44 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/ServiceReply.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/ServicesMethodEnum.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/StateDataEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java 38 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/model/OssConfiguration.java 94 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/model/enums/OssTypeEnum.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/IOssService.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java 73 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java 135 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java 54 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/OssAspectHandler.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/OssServiceContext.java 58 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/redis/RedisConfiguration.java 63 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/redis/RedisConst.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/redis/RedisOpsUtils.java 187 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java 7 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/service/IWebSocketManageService.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/service/impl/WebSocketManageServiceImpl.java 90 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DeviceController.java 106 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DeviceHmsController.java 53 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/LiveStreamController.java 21 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/LoginController.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/UserController.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/IDeviceHmsMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/common/HmsJsonUtil.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/common/HmsMessage.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java 22 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceHmsDTO.java 57 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/UserListDTO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DeviceHmsEntity.java 76 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/UserEntity.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java 155 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/PayloadModelEnum.java 61 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/param/DeviceHmsQueryParam.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/AlternateLandPointReceiver.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/BindDeviceReceiver.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/BindStatusReceiver.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DockMediaFileDetailReceiver.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DockSdrReceiver.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DockSubDeviceReceiver.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DroneChargeStateReceiver.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/FirmwareVersionReceiver.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/HmsArgsReceiver.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/LiveCapacityReceiver.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/NetworkStateReceiver.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OrganizationGetReceiver.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java 67 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdDockTransmissionReceiver.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdPayloadReceiver.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/StorageReceiver.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ICameraVideoService.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceHmsService.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceService.java 71 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ILiveStreamService.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ITSAService.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IUserService.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IWorkspaceService.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java 83 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java 136 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java 133 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java 58 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java 53 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java 676 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java 50 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java 129 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java 78 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java 60 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/controller/FileController.java 35 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/controller/MediaController.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/CredentialsDTO.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/FileExtensionDTO.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/FileUploadCallback.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/MediaFileDTO.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/MediaFileEntity.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/IFileService.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/IMediaService.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/storage/controller/StorageController.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/storage/service/IStorageService.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/storage/service/impl/StorageServiceImpl.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java 27 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java 74 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/dao/IWaylineJobMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/FLightTaskProgress.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/FlightTaskCreateDTO.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/FlightTaskFileDTO.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/FlightTaskProgressExt.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/FlightTaskProgressReceiver.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/WaylineFileDTO.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/WaylineFileUploadDTO.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/entity/WaylineFileEntity.java 62 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/entity/WaylineJobEntity.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/param/WaylineQueryParam.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java 21 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java 84 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java 112 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java 212 ●●●●● patch | view | raw | blame | history
src/main/resources/application.yml 80 ●●●●● patch | view | raw | blame | history
README.md
@@ -6,15 +6,15 @@
## Docker
If you don't want to install the development environment, you can try deploying with docker. [Click the link to download.](https://terra-sz-hc1pro-cloudapi.oss-cn-shenzhen.aliyuncs.com/c0af9fe0d7eb4f35a8fe5b695e4d0b96/docker/cloud_api_sample_docker_1.0.0.zip)
If you don't want to install the development environment, you can try deploying with docker. [Click the link to download.](https://terra-sz-hc1pro-cloudapi.oss-cn-shenzhen.aliyuncs.com/c0af9fe0d7eb4f35a8fe5b695e4d0b96/docker/cloud_api_sample_docker.zip)
## Usage
For more documentation, please visit the [DJI Developer Documentation](https://developer.dji.com/cn/document/209883f1-f2ad-406e-b99c-be7498df7f10).
For more documentation, please visit the [DJI Developer Documentation](https://developer.dji.com/doc/cloud-api-tutorial/cn/).
## Latest Release
Cloud API 1.0.0 was released on 21 March 2022. For more information, please visit the [Release Note](https://developer.dji.com/cn/document/87026f9b-e906-4809-9aba-870f569061b5).
Cloud API 1.1.0 was released on 22 July 2022. For more information, please visit the [Release Note](https://developer.dji.com/doc/cloud-api-tutorial/cn/).
## License
api/Cloud API Demo.postman_collection.json
@@ -18,7 +18,7 @@
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"username\": \"adminPC\",\r\n    \"password\": \"adminPC\"\r\n}",
                            "raw": "{\r\n    \"username\": \"adminPC\",\r\n    \"password\": \"adminPC\",\r\n    \"flag\": 1\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
@@ -100,12 +100,13 @@
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/devices",
                            "raw": "{{base_url}}{{manage_version}}/devices/{{workspace_id}}/devices",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{workspace_id}}",
                                "devices"
                            ]
                        }
@@ -132,12 +133,23 @@
                },
                {
                    "name": "Start Livestream",
                    "event": [
                        {
                            "listen": "test",
                            "script": {
                                "exec": [
                                    ""
                                ],
                                "type": "text/javascript"
                            }
                        }
                    ],
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"url_type\": 0,\r\n    \"url\": \"\",\r\n    \"video_id\": \"\",\r\n    \"video_quality\": 0\r\n}",
                            "raw": "{\r\n    \"url\": \"rtmp://192.168.1.1/live/1651053434895\",\r\n    \"url_type\": 1,\r\n    \"video_id\": \"1581F4BN/52-0-0/zoom-0\",\r\n    \"video_quality\": 0\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
@@ -165,7 +177,7 @@
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"video_id\": \"\"\r\n}",
                            "raw": "{\r\n    \"video_id\": \"1581F4BNDQ/39-0-7/normal-0\"\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
@@ -213,19 +225,177 @@
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get All Users Info",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/users/{{workspace_id}}/users?page=1&page_size=10",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "users",
                                "{{workspace_id}}",
                                "users"
                            ],
                            "query": [
                                {
                                    "key": "page",
                                    "value": "1"
                                },
                                {
                                    "key": "page_size",
                                    "value": "10"
                                }
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Update User Info",
                    "request": {
                        "method": "PUT",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"mqtt_username\": \"admin\",\r\n    \"mqtt_password\": \"admin\"\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/users/{{workspace_id}}/users/a1559e7c-8dd8-4780-b952-100cc4797da2",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "users",
                                "{{workspace_id}}",
                                "users",
                                "a1559e7c-8dd8-4780-b952-100cc4797da2"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Bind Device",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"user_id\": \"be7c6c3d-afe9-4be4-b9eb-c55066c0914e\",\r\n    \"workspace_id\": \"e3dea0f5-37f2-4d79-ae58-490af3228069\",\r\n    \"device_sn\": \"1ZMDG009\",\r\n    \"child_device_sn\": \"\"\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/binding",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "binding"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get Binding Devices",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/{{workspace_id}}/devices/bound?page=1&page_size=10&domain=sub-device",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{workspace_id}}",
                                "devices",
                                "bound"
                            ],
                            "query": [
                                {
                                    "key": "page",
                                    "value": "1"
                                },
                                {
                                    "key": "page_size",
                                    "value": "10"
                                },
                                {
                                    "key": "domain",
                                    "value": "sub-device"
                                }
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get Device",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/{{workspace_id}}/devices/{{device_sn}}",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{workspace_id}}",
                                "devices",
                                "{{device_sn}}"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Unbind Device",
                    "request": {
                        "method": "DELETE",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/{{device_sn}}/unbinding",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{device_sn}}",
                                "unbinding"
                            ]
                        }
                    },
                    "response": []
                }
            ],
            "auth": {
                "type": "apikey",
                "apikey": [
                    {
                        "key": "key",
                        "value": "x-auth-token",
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTI2OTUxOTcsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTI3ODE1OTcsImlhdCI6MTY1MjY5NTE5NywidXNlcm5hbWUiOiJhZG1pblBDIn0.BHTwW8imw5ab0GUypRyJ2gkoz5av9q99NrxoFlL53dA",
                        "type": "string"
                    },
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NDg1MjI5ODAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NDg2MDkzODAsImlhdCI6MTY0ODUyMjk4MCwidXNlcm5hbWUiOiJhZG1pblBDIn0.QEDpB_60G4YLLXR_6rLZHWZNVbA1j162Xs1fZx8wEOM",
                        "key": "key",
                        "value": "x-auth-token",
                        "type": "string"
                    }
                ]
@@ -390,7 +560,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NDg1MjI5ODAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NDg2MDkzODAsImlhdCI6MTY0ODUyMjk4MCwidXNlcm5hbWUiOiJhZG1pblBDIn0.QEDpB_60G4YLLXR_6rLZHWZNVbA1j162Xs1fZx8wEOM",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTMzNzI3NDUsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTM0NTkxNDUsImlhdCI6MTY1MzM3Mjc0NSwidXNlcm5hbWUiOiJhZG1pblBDIn0.Zyb_f4umcGY2-WDaQKA1LHGOs9qYfJuPc3rQeIS-4hY",
                        "type": "string"
                    },
                    {
@@ -520,15 +690,12 @@
                },
                {
                    "name": "Check Tiny Fingerprints",
                    "protocolProfileBehavior": {
                        "disableBodyPruning": true
                    },
                    "request": {
                        "method": "GET",
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"tiny_fingerprints\":[\r\n        \r\n    ]\r\n}",
                            "raw": "{\r\n    \"tiny_fingerprints\":[\r\n        \"4a3a67101ffb81d079338d4729315a8c_2022_3_3_11_38_58\",\r\n        \"8e0fedb981be23dd034cf7927919da51_2022_3_3_11_45_26\"\r\n    ]\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
@@ -536,7 +703,7 @@
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{media_version}}/workspaces/{{workspace_id}}/files/tiny-fingerprints?tiny_fingerprint=045040860fb014916c81082407e9ff8b_2021_12_2_16_17_8,922aca3dee753f2f9ee1ad0565967334_2021_12_8_22_13_12",
                            "raw": "{{base_url}}{{media_version}}/workspaces/{{workspace_id}}/files/tiny-fingerprints",
                            "host": [
                                "{{base_url}}{{media_version}}"
                            ],
@@ -545,12 +712,6 @@
                                "{{workspace_id}}",
                                "files",
                                "tiny-fingerprints"
                            ],
                            "query": [
                                {
                                    "key": "tiny_fingerprint",
                                    "value": "045040860fb014916c81082407e9ff8b_2021_12_2_16_17_8,922aca3dee753f2f9ee1ad0565967334_2021_12_8_22_13_12"
                                }
                            ]
                        }
                    },
@@ -562,7 +723,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NDg1MjI5ODAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NDg2MDkzODAsImlhdCI6MTY0ODUyMjk4MCwidXNlcm5hbWUiOiJhZG1pblBDIn0.QEDpB_60G4YLLXR_6rLZHWZNVbA1j162Xs1fZx8wEOM",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTgzOTIyNzksImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTg0Nzg2NzksImlhdCI6MTY1ODM5MjI3OSwidXNlcm5hbWUiOiJhZG1pblBDIn0.ErClyQS1YzoQVBcLYxpFEiyFTb1L-eg2-vsudlL9WJU",
                        "type": "string"
                    },
                    {
@@ -622,7 +783,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NDg1MjI5ODAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NDg2MDkzODAsImlhdCI6MTY0ODUyMjk4MCwidXNlcm5hbWUiOiJhZG1pblBDIn0.QEDpB_60G4YLLXR_6rLZHWZNVbA1j162Xs1fZx8wEOM",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTI2OTUxOTcsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTI3ODE1OTcsImlhdCI6MTY1MjY5NTE5NywidXNlcm5hbWUiOiJhZG1pblBDIn0.BHTwW8imw5ab0GUypRyJ2gkoz5av9q99NrxoFlL53dA",
                        "type": "string"
                    },
                    {
@@ -848,7 +1009,124 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NDg1MjI5ODAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NDg2MDkzODAsImlhdCI6MTY0ODUyMjk4MCwidXNlcm5hbWUiOiJhZG1pblBDIn0.QEDpB_60G4YLLXR_6rLZHWZNVbA1j162Xs1fZx8wEOM",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTU0NDk2MDIsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTU1MzYwMDIsImlhdCI6MTY1NTQ0OTYwMiwidXNlcm5hbWUiOiJhZG1pblBDIn0.YZWHJ65Pl_DT2Ampxk0WC01KD_fNTm_rYVUBIHAZD-4",
                        "type": "string"
                    },
                    {
                        "key": "key",
                        "value": "x-auth-token",
                        "type": "string"
                    }
                ]
            },
            "event": [
                {
                    "listen": "prerequest",
                    "script": {
                        "type": "text/javascript",
                        "exec": [
                            ""
                        ]
                    }
                },
                {
                    "listen": "test",
                    "script": {
                        "type": "text/javascript",
                        "exec": [
                            ""
                        ]
                    }
                }
            ]
        },
        {
            "name": "job",
            "item": [
                {
                    "name": "Create Flight Job",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"name\": \"\",\r\n    \"fild_id\": \"\",\r\n    \"dock_sn\": \"\",\r\n    \"type\": \"\",\r\n    \"immediate\": false\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/flight-tasks",
                            "host": [
                                "{{base_url}}{{wayline_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "flight-tasks"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get Jobs",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/jobs?page=1&pageSize=10",
                            "host": [
                                "{{base_url}}{{wayline_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "jobs"
                            ],
                            "query": [
                                {
                                    "key": "page",
                                    "value": "1"
                                },
                                {
                                    "key": "pageSize",
                                    "value": "10"
                                }
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Execute Job",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/jobs/{{plan_id}}",
                            "host": [
                                "{{base_url}}{{wayline_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "jobs",
                                "{{plan_id}}"
                            ]
                        }
                    },
                    "response": []
                }
            ],
            "auth": {
                "type": "apikey",
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTU4OTA5NTQsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTU5NzczNTQsImlhdCI6MTY1NTg5MDk1NCwidXNlcm5hbWUiOiJhZG1pblBDIn0.fd0iIzCd71LDUE6ixexUJvo-YqtnSCqRx-790snCyBI",
                        "type": "string"
                    },
                    {
@@ -885,7 +1163,7 @@
        "apikey": [
            {
                "key": "value",
                "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Mzg5MzY2OTEsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2MzkwMjMwOTEsImlhdCI6MTYzODkzNjY5MSwidXNlcm5hbWUiOiJhZG1pblBDIn0.W_v88rbhVKivl61MnJ0Cz_0Eq6Gw0RotiHCdLj0UPSQ",
                "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTA0MjY3MzAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTA1MTMxMzAsImlhdCI6MTY1MDQyNjczMCwidXNlcm5hbWUiOiJhZG1pblBDIn0.-FBpi0ktuZ68jV6-HQ7mwm8iC07YH-Hw2aRiREzJ8hs",
                "type": "string"
            },
            {
api/Cloud API Demo.postman_environment.json
@@ -4,7 +4,7 @@
    "values": [
        {
            "key": "ip",
            "value": "192.168.1.1",
            "value": "localhost",
            "type": "default",
            "enabled": true
        },
@@ -55,6 +55,6 @@
        }
    ],
    "_postman_variable_scope": "environment",
    "_postman_exported_at": "2022-03-29T03:52:14.600Z",
    "_postman_exported_using": "Postman/9.7.1"
    "_postman_exported_at": "2022-07-21T08:35:48.441Z",
    "_postman_exported_using": "Postman/9.19.0"
}
pom.xml
@@ -11,7 +11,7 @@
    <groupId>com.dji</groupId>
    <artifactId>cloud-api-sample</artifactId>
    <version>1.0.0</version>
    <version>1.1.0</version>
    <name>cloud-api-sample</name>
    <properties>
@@ -127,6 +127,35 @@
            <version>${glassfish-jaxb.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
            <version>1.12.200</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-sts</artifactId>
            <version>1.12.200</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>
    <build>
sql/cloud_sample.sql
@@ -10,40 +10,8 @@
/*!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
#  manage_device
# ------------------------------------------------------------
DROP TABLE IF EXISTS `manage_device`;
@@ -52,25 +20,31 @@
  `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,
  `user_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `nickname` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `device_type` smallint NOT NULL DEFAULT '-1',
  `sub_type` smallint NOT NULL DEFAULT '-1',
  `domain` smallint NOT NULL DEFAULT '-1',
  `firmware_version` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `version` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `device_index` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `child_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `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,
  `bound_time` bigint DEFAULT NULL,
  `bound_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0:bund; 1:not bound',
  `login_time` bigint DEFAULT NULL,
  `device_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `url_normal` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `url_select` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `product_sn_UNIQUE` (`device_sn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
# manage_device_dictionary
#  manage_device_dictionary
# ------------------------------------------------------------
DROP TABLE IF EXISTS `manage_device_dictionary`;
@@ -106,15 +80,37 @@
    (14,1,165,0,'DJI Dock Camera',NULL),
    (15,1,90742,0,'L1',NULL),
    (16,2,56,0,'DJI Smart Controller','Remote control for M300'),
    (17,2,119,0,'Matrice 30 Smart Controller','Remote control for M30'),
    (18,3,1,0,'DJI Dock','DJI Airport');
    (17,2,119,0,'DJI RC Plus','Remote control for M30'),
    (18,3,1,0,'DJI Dock','');
/*!40000 ALTER TABLE `manage_device_dictionary` ENABLE KEYS */;
UNLOCK TABLES;
# manage_device_payload
# manage_device_hms
# ------------------------------------------------------------
DROP TABLE IF EXISTS `manage_device_hms`;
CREATE TABLE `manage_device_hms` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `hms_id` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `tid` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `bid` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `sn` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `level` smallint NOT NULL,
  `module` tinyint NOT NULL,
  `hms_key` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `message_zh` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `message_en` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `create_time` bigint NOT NULL,
  `update_time` bigint NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UNIQUE_hms_id` (`hms_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
#  manage_device_payload
# ------------------------------------------------------------
DROP TABLE IF EXISTS `manage_device_payload`;
@@ -125,7 +121,7 @@
  `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,
  `firmware_version` varchar(32) 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,
@@ -137,7 +133,7 @@
# manage_user
#  manage_user
# ------------------------------------------------------------
DROP TABLE IF EXISTS `manage_user`;
@@ -147,7 +143,7 @@
  `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,
  `workspace_id` varchar(64) NOT NULL DEFAULT '',
  `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 '',
@@ -162,14 +158,14 @@
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);
    (1,'a1559e7c-8dd8-4780-b952-100cc4797da2','adminPC','adminPC','e3dea0f5-37f2-4d79-ae58-490af3228069',1,'admin','admin',1634898410751,1650880112310),
    (2,'be7c6c3d-afe9-4be4-b9eb-c55066c0914e','pilot','pilot123','e3dea0f5-37f2-4d79-ae58-490af3228069',2,'pilot','pilot123',1634898410751,1634898410751);
/*!40000 ALTER TABLE `manage_user` ENABLE KEYS */;
UNLOCK TABLES;
# manage_workspace
#  manage_workspace
# ------------------------------------------------------------
DROP TABLE IF EXISTS `manage_workspace`;
@@ -182,22 +178,24 @@
  `platform_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `create_time` bigint NOT NULL,
  `update_time` bigint NOT NULL,
  `bind_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `workspace_id_UNIQUE` (`workspace_id`)
  UNIQUE KEY `workspace_id_UNIQUE` (`workspace_id`),
  UNIQUE KEY `bind_code_UNIQUE` (`bind_code`)
) 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`)
INSERT INTO `manage_workspace` (`id`, `workspace_id`, `workspace_name`, `workspace_desc`, `platform_name`, `create_time`, `update_time`, `bind_code`)
VALUES
    (1,'e3dea0f5-37f2-4d79-ae58-490af3228069','Test Group One','Cloud Sample Test Platform','Cloud Api Platform',1634898410751,1634898410751);
    (1,'e3dea0f5-37f2-4d79-ae58-490af3228069','Test Group One','Cloud Sample Test Platform','Cloud Api Platform',1634898410751,1634898410751,'qwe');
/*!40000 ALTER TABLE `manage_workspace` ENABLE KEYS */;
UNLOCK TABLES;
# map_element_coordinate
#  map_element_coordinate
# ------------------------------------------------------------
DROP TABLE IF EXISTS `map_element_coordinate`;
@@ -213,7 +211,7 @@
# map_group
#  map_group
# ------------------------------------------------------------
DROP TABLE IF EXISTS `map_group`;
@@ -244,7 +242,7 @@
UNLOCK TABLES;
# map_group_element
#  map_group_element
# ------------------------------------------------------------
DROP TABLE IF EXISTS `map_group_element`;
@@ -267,7 +265,7 @@
# media_file
#  media_file
# ------------------------------------------------------------
DROP TABLE IF EXISTS `media_file`;
@@ -277,22 +275,22 @@
  `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 '',
  `fingerprint` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci 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,
  `object_key` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `sub_file_type` int DEFAULT 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',
  `job_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
  `create_time` bigint NOT NULL,
  `update_time` bigint NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `fingerprint_UNIQUE` (`fingerprint`)
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
# wayline_file
#  wayline_file
# ------------------------------------------------------------
DROP TABLE IF EXISTS `wayline_file`;
@@ -304,18 +302,41 @@
  `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 '',
  `sign` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'MD5',
  `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.',
  `update_time` bigint NOT NULL COMMENT 'required, can''t modify.',
  PRIMARY KEY (`id`),
  UNIQUE KEY `wayline_id_UNIQUE` (`wayline_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
#  wayline_job
# ------------------------------------------------------------
DROP TABLE IF EXISTS `wayline_job`;
CREATE TABLE `wayline_job` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `job_id` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `file_id` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `dock_sn` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `workspace_id` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `bid` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `type` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `username` 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 `job_id_UNIQUE` (`job_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
src/main/java/com/dji/sample/CloudApiSampleApplication.java
@@ -8,6 +8,7 @@
@MapperScan("com.dji.sample.*.dao")
@SpringBootApplication
@EnableScheduling
//@EnableConfigurationProperties(OssConfiguration.class)
public class CloudApiSampleApplication {
    public static void main(String[] args) {
src/main/java/com/dji/sample/common/error/CommonErrorEnum.java
@@ -7,6 +7,16 @@
 */
public enum CommonErrorEnum implements IErrorInfo {
    ILLEGAL_ARGUMENT(200001, "illegal argument"),
    GET_ORGANIZATION_FAILED(210230, "Failed to get organization."),
    DEVICE_BINDING_FAILED(210231, "Failed to bind device."),
    NON_REPEATABLE_BINDING(210232, "The device has been bound to another organization and can't be bound repeatedly."),
    GET_DEVICE_BINDING_STATUS_FAILED(210233, "Failed to get device binding status."),
    SYSTEM_ERROR(600500, "system error"),
    SECRET_INVALID(600100, "secret invalid"),
src/main/java/com/dji/sample/common/error/StorageErrorEnum.java
New file
@@ -0,0 +1,38 @@
package com.dji.sample.common.error;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/25
 */
public enum StorageErrorEnum implements IErrorInfo {
    GENERATE_CREDENTIALS_ERROR(217001, "Failed to generate temporary credentials."),
    NO_BUCKET(217002, "The bucket does not exist."),
    ILLEGAL_PATH_FORMAT(217006, "Illegal path format."),
    FILE_CREATION_FAILED(217007, "File creation failed."),
    DIR_CREATION_FAILED(217008, "Directory creation failed");
    private String msg;
    private int code;
    StorageErrorEnum(int code, String msg) {
        this.msg = msg;
        this.code = code;
    }
    @Override
    public String getErrorMsg() {
        return msg;
    }
    @Override
    public Integer getErrorCode() {
        return code;
    }
}
src/main/java/com/dji/sample/common/util/JwtUtil.java
@@ -54,6 +54,10 @@
        JwtUtil.algorithm = Algorithm.HMAC256(secret);
    }
    private JwtUtil() {
    }
    /**
     * Create a token based on custom information.
     * @param claims custom information
src/main/java/com/dji/sample/component/ApplicationBootInitial.java
@@ -1,14 +1,11 @@
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.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
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
@@ -21,20 +18,21 @@
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private RedisOpsUtils redisOps;
    /**
     * Subscribe to the devices that exist in the database when the program starts,
     * Subscribe to the devices that exist in the redis 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());
                });
        int start = RedisConst.DEVICE_ONLINE_PREFIX.length();
        redisOps.getAllKeys(RedisConst.DEVICE_ONLINE_PREFIX + "*")
                .forEach(key -> deviceService.subscribeTopicOnline(key.substring(start)));
    }
}
src/main/java/com/dji/sample/component/GlobalScheduleService.java
@@ -1,5 +1,9 @@
package com.dji.sample.component;
import com.dji.sample.component.mqtt.service.IMqttTopicService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.service.IDeviceService;
import lombok.extern.slf4j.Slf4j;
@@ -7,12 +11,8 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Arrays;
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
@@ -26,33 +26,34 @@
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private RedisOpsUtils redisOps;
    @Autowired
    private IMqttTopicService topicService;
    /**
     * Check the status of the devices every 30 seconds. It is recommended to use cache.
     */
    @Scheduled(fixedRate = 30, timeUnit = TimeUnit.SECONDS)
    @Scheduled(initialDelay = 30, 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;
        int start = RedisConst.DEVICE_ONLINE_PREFIX.length();
        redisOps.getAllKeys(RedisConst.DEVICE_ONLINE_PREFIX + "*").forEach(key -> {
            long expire = redisOps.getExpire(key);
            if (expire <= 30) {
                DeviceDTO device = (DeviceDTO) redisOps.get(key);
                if (device.getDomain().equals(DeviceDomainEnum.SUB_DEVICE.getDesc())) {
                    deviceService.subDeviceOffline(key.substring(start));
                } else {
                    deviceService.unsubscribeTopicOffline(key.substring(start));
                    deviceService.pushDeviceOfflineTopo(device.getWorkspaceId(), device.getDeviceSn());
                }
                redisOps.del(key);
            }
        });
            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);
        }
        log.info("Subscriptions: {}", Arrays.toString(topicService.getSubscribedTopic()));
    }
}
src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java
@@ -11,6 +11,7 @@
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@@ -62,7 +63,9 @@
    @ServiceActivator(inputChannel = ChannelName.DEFAULT)
    public MessageHandler defaultInboundHandler() {
        return message -> {
            log.info("The default channel does not handle messages.");
            log.info("The default channel does not handle messages." +
                    "\nTopic: " + message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC) +
                    "\nPayload: " + message.getPayload());
        };
    }
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java
@@ -74,7 +74,7 @@
    @Bean(name = ChannelName.INBOUND_OSD)
    public MessageChannel osdChannel() {
        return new DirectChannel();
        return new ExecutorChannel(threadPool);
    }
    @Bean(name = ChannelName.DEFAULT)
@@ -87,4 +87,54 @@
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_STATE_FIRMWARE_VERSION)
    public MessageChannel stateFirmwareVersionChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_REQUESTS)
    public MessageChannel requestsChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_REQUESTS_STORAGE_CONFIG_GET)
    public MessageChannel requestsConfigGetChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS)
    public MessageChannel eventsChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS)
    public MessageChannel eventsFlightTaskProgressChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK)
    public MessageChannel eventsFileUploadCallbackChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_REQUESTS_AIRPORT_BIND_STATUS)
    public MessageChannel requestsAirportBindStatusChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_GET)
    public MessageChannel requestsAirportOrganizationGetChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND)
    public MessageChannel requestsAirportOrganizationBindChannel() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_HMS)
    public MessageChannel eventsHms() {
        return new DirectChannel();
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/AbstractStateTopicHandler.java
New file
@@ -0,0 +1,39 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Map;
/**
 * @author sean
 * @version 0.3
 * @date 2022/2/21
 */
public abstract class AbstractStateTopicHandler {
    protected AbstractStateTopicHandler handler;
    @Autowired
    protected ObjectMapper mapper;
    @Autowired
    protected RedisOpsUtils redisOps;
    protected AbstractStateTopicHandler(AbstractStateTopicHandler handler) {
        this.handler = handler;
    }
    /**
     * Passing dataNode data, using different processing methods depending on the data selection.
     * @param dataNode
     * @param stateReceiver
     * @param sn
     * @return
     * @throws JsonProcessingException
     */
    public abstract CommonTopicReceiver handleState(Map<String, Object> dataNode, CommonTopicReceiver stateReceiver, String sn) throws JsonProcessingException;
}
src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java
New file
@@ -0,0 +1,48 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.EventsMethodEnum;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import java.io.IOException;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
@Configuration
public class EventsRouter {
    @Autowired
    private ObjectMapper mapper;
    @Bean
    public IntegrationFlow eventsMethodRouterFlow() {
        return IntegrationFlows
                .from(ChannelName.INBOUND_EVENTS)
                .<byte[], CommonTopicReceiver>transform(payload -> {
                    try {
                        return mapper.readValue(payload, CommonTopicReceiver.class);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return new CommonTopicReceiver();
                })
                .<CommonTopicReceiver, EventsMethodEnum>route(
                        receiver -> EventsMethodEnum.find(receiver.getMethod()),
                        mapping -> {
                            mapping.channelMapping(EventsMethodEnum.FILE_UPLOAD_CALLBACK, ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK);
                            mapping.channelMapping(EventsMethodEnum.FLIGHT_TASK_PROGRESS, ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS);
                            mapping.channelMapping(EventsMethodEnum.HMS, ChannelName.INBOUND_EVENTS_HMS);
                            mapping.channelMapping(EventsMethodEnum.UNKNOWN, ChannelName.DEFAULT);
                        })
                .get();
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/InboundMessageRouter.java
New file
@@ -0,0 +1,118 @@
package com.dji.sample.component.mqtt.handler;
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;
    @Resource(name = ChannelName.INBOUND_REQUESTS)
    private MessageChannel requestsChannel;
    @Resource(name = ChannelName.INBOUND_EVENTS)
    private MessageChannel eventsChannel;
    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 + "$");
    private static final Pattern PATTERN_TOPIC_REQUESTS =
            Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + REQUESTS_SUF + "$");
    private static final Pattern PATTERN_TOPIC_EVENTS =
            Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + EVENTS_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);
        }
        // requests
        if (PATTERN_TOPIC_REQUESTS.matcher(topic).matches()) {
            return Collections.singleton(requestsChannel);
        }
        // events
        if (PATTERN_TOPIC_EVENTS.matcher(topic).matches()) {
            return Collections.singleton(eventsChannel);
        }
        return Collections.singleton(defaultChannel);
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/RequestsRouter.java
New file
@@ -0,0 +1,49 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.RequestsMethodEnum;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import java.io.IOException;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/25
 */
@Configuration
public class RequestsRouter {
    @Autowired
    private ObjectMapper mapper;
    @Bean
    public IntegrationFlow requestsMethodRouterFlow() {
        return IntegrationFlows
                .from(ChannelName.INBOUND_REQUESTS)
                .<byte[], CommonTopicReceiver>transform(payload -> {
                    try {
                        return mapper.readValue(payload, CommonTopicReceiver.class);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return new CommonTopicReceiver();
                })
                .<CommonTopicReceiver, RequestsMethodEnum>route(
                        receiver -> RequestsMethodEnum.find(receiver.getMethod()),
                        mapping -> {
                            mapping.channelMapping(RequestsMethodEnum.STORAGE_CONFIG_GET, ChannelName.INBOUND_REQUESTS_STORAGE_CONFIG_GET);
                            mapping.channelMapping(RequestsMethodEnum.AIRPORT_BIND_STATUS, ChannelName.INBOUND_REQUESTS_AIRPORT_BIND_STATUS);
                            mapping.channelMapping(RequestsMethodEnum.AIRPORT_ORGANIZATION_GET, ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_GET);
                            mapping.channelMapping(RequestsMethodEnum.AIRPORT_ORGANIZATION_BIND, ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND);
                            mapping.channelMapping(RequestsMethodEnum.UNKNOWN, ChannelName.DEFAULT);
                        })
                .get();
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/StateDefaultHandler.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
 * @author sean
 * @version 0.3
 * @date 2022/3/21
 */
@Service
public class StateDefaultHandler extends AbstractStateTopicHandler {
    protected StateDefaultHandler() {
        super(null);
    }
    @Override
    public CommonTopicReceiver handleState(Map<String, Object> dataNode, CommonTopicReceiver stateReceiver, String sn) throws JsonProcessingException {
        // If no suitable handler is found for the data, it is not processed.
        return stateReceiver;
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/StateDeviceBasicHandler.java
New file
@@ -0,0 +1,38 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.StateDataEnum;
import com.dji.sample.manage.model.receiver.DeviceBasicReceiver;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
 * @author sean
 * @version 0.3
 * @date 2022/2/21
 */
@Service
public class StateDeviceBasicHandler extends AbstractStateTopicHandler {
    public StateDeviceBasicHandler(@Autowired @Qualifier("stateLiveCapacityHandler") AbstractStateTopicHandler handler) {
        super(handler);
    }
    @Override
    public CommonTopicReceiver handleState(Map<String, Object> dataNode, CommonTopicReceiver stateReceiver, String sn) throws JsonProcessingException {
        // handle device basic data
        if (dataNode.containsKey(StateDataEnum.PAYLOADS.getDesc())) {
            DeviceBasicReceiver data = mapper.convertValue(stateReceiver.getData(), DeviceBasicReceiver.class);
            data.setDeviceSn(sn);
            data.getPayloads().forEach(payload -> payload.setDeviceSn(sn));
            stateReceiver.setData(data);
            return stateReceiver;
        }
        return handler.handleState(dataNode, stateReceiver, sn);
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/StateFirmwareVersionHandler.java
New file
@@ -0,0 +1,62 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.StateDataEnum;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.enums.PayloadModelEnum;
import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
 * @author sean
 * @version 0.3
 * @date 2022/2/21
 */
@Service
public class StateFirmwareVersionHandler extends AbstractStateTopicHandler {
    protected StateFirmwareVersionHandler(@Autowired @Qualifier("stateDefaultHandler") AbstractStateTopicHandler handler) {
        super(handler);
    }
    @Override
    public CommonTopicReceiver handleState(Map<String, Object> dataNode, CommonTopicReceiver stateReceiver, String sn) throws JsonProcessingException {
        // Parse the firmware version of the device.
        if (dataNode.containsKey(StateDataEnum.FIRMWARE_VERSION.getDesc())) {
            FirmwareVersionReceiver firmware = mapper.convertValue(dataNode, FirmwareVersionReceiver.class);
            firmware.setSn(sn);
            firmware.setDomain(DeviceDomainEnum.SUB_DEVICE);
            stateReceiver.setData(firmware);
            return stateReceiver;
        }
        // Parse the firmware version of the payload.
        List<String> payloads = PayloadModelEnum.getAllModel();
        long count = dataNode.keySet()
                .stream()
                .map(key -> {
                    int end = key.indexOf("-");
                    return end == -1 ? key : key.substring(0, end);
                })
                .filter(payloads::contains)
                .count();
        if (count > 0) {
            FirmwareVersionReceiver firmware = FirmwareVersionReceiver.builder()
                    .firmwareVersion(((Map<String, String>)(dataNode.values().iterator().next()))
                            .get(StateDataEnum.FIRMWARE_VERSION.getDesc()))
                    .sn(sn)
                    .domain(DeviceDomainEnum.PAYLOAD)
                    .build();
            stateReceiver.setData(firmware);
            return stateReceiver;
        }
        return handler.handleState(dataNode, stateReceiver, sn);
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/StateLiveCapacityHandler.java
New file
@@ -0,0 +1,39 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.StateDataEnum;
import com.dji.sample.manage.model.receiver.LiveCapacityReceiver;
import com.fasterxml.jackson.core.JsonProcessingException;
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 java.util.Map;
/**
 * @author sean
 * @version 0.3
 * @date 2022/2/21
 */
@Service
@Slf4j
public class StateLiveCapacityHandler extends AbstractStateTopicHandler {
    protected StateLiveCapacityHandler(@Autowired @Qualifier("stateFirmwareVersionHandler") AbstractStateTopicHandler handler) {
        super(handler);
    }
    @Override
    public CommonTopicReceiver handleState(Map<String, Object> dataNode, CommonTopicReceiver stateReceiver, String sn) throws JsonProcessingException {
        // Determine if it is live capacity data based on name.
        if (dataNode.containsKey(StateDataEnum.LIVE_CAPACITY.getDesc())) {
            stateReceiver.setData(mapper.convertValue(
                    dataNode.get(StateDataEnum.LIVE_CAPACITY.getDesc()),
                    LiveCapacityReceiver.class));
            log.info("Analyze live stream capabilities.");
            return stateReceiver;
        }
        return handler.handleState(dataNode, stateReceiver, sn);
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/StateRouter.java
New file
@@ -0,0 +1,104 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.receiver.DeviceBasicReceiver;
import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver;
import com.dji.sample.manage.model.receiver.LiveCapacityReceiver;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
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.MessageEndpoint;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Splitter;
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.ArrayList;
import java.util.Collection;
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;
    @Autowired
    private ObjectMapper mapper;
    /**
     * 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 CommonTopicReceiver<?> resolveStateData(Message<?> message) throws IOException {
        byte[] payload = (byte[])message.getPayload();
        String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
        CommonTopicReceiver stateReceiver = mapper.readValue(payload, CommonTopicReceiver.class);
        // Get the sn of the topic source.
        String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(),
                topic.indexOf(STATE_SUF));
        try {
            Map<String, Object> data = (Map<String, Object>) (stateReceiver.getData());
            return handler.handleState(data, stateReceiver, from);
        } catch (UnrecognizedPropertyException e) {
            log.info("The {} data is not processed.", e.getPropertyName());
        }
        return stateReceiver;
    }
    /**
     * 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(CommonTopicReceiver receiver) {
        ArrayList<Object> type = new ArrayList<>();
        type.add(receiver.getData());
        return type;
    }
    @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(LiveCapacityReceiver.class.getName(),
                ChannelName.INBOUND_STATE_CAPACITY);
        router.setChannelMapping(FirmwareVersionReceiver.class.getName(),
                ChannelName.INBOUND_STATE_FIRMWARE_VERSION);
        router.setChannelMapping(Map.class.getName(),
                ChannelName.DEFAULT);
        return router;
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/StatusRouter.java
New file
@@ -0,0 +1,67 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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 {
    @Autowired
    private ObjectMapper mapper;
    /**
     * 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<>();
        try {
            statusReceiver = mapper.readValue(
                    (byte[])message.getPayload(),
                    new TypeReference<CommonTopicReceiver<StatusGatewayReceiver>>() {});
            String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
            // 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;
    }
}
src/main/java/com/dji/sample/component/mqtt/model/Chan.java
New file
@@ -0,0 +1,45 @@
package com.dji.sample.component.mqtt.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 * 2000;
    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<>();
    }
}
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java
@@ -43,4 +43,23 @@
    public static final String OUTBOUND = "outbound";
    public static final String INBOUND_STATE_FIRMWARE_VERSION = "inboundStateFirmwareVersion";
    public static final String INBOUND_REQUESTS = "inboundRequests";
    public static final String INBOUND_REQUESTS_STORAGE_CONFIG_GET = "inboundRequestsConfigGet";
    public static final String INBOUND_EVENTS = "inboundEvents";
    public static final String INBOUND_EVENTS_FLIGHT_TASK_PROGRESS = "inboundEventsFlightTaskProgress";
    public static final String INBOUND_EVENTS_FILE_UPLOAD_CALLBACK = "inboundEventsFileUploadCallback";
    public static final String INBOUND_REQUESTS_AIRPORT_BIND_STATUS = "inboundRequestsAirportBindStatus";
    public static final String INBOUND_REQUESTS_AIRPORT_ORGANIZATION_GET = "inboundRequestsAirportOrganizationGet";
    public static final String INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND = "inboundRequestsAirportOrganizationBind";
    public static final String INBOUND_EVENTS_HMS = "inboundEventsHms";
}
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java
@@ -26,4 +26,10 @@
    private Long timestamp;
    private T data;
    private String gateway;
    private Integer needReply;
    private String from;
}
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java
@@ -30,4 +30,6 @@
    private String method;
    private T data;
    private Long timestamp;
}
src/main/java/com/dji/sample/component/mqtt/model/ErrorInfoReply.java
New file
@@ -0,0 +1,23 @@
package com.dji.sample.component.mqtt.model;
import com.dji.sample.common.model.ResponseResult;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/14
 */
@Data
@AllArgsConstructor
public class ErrorInfoReply {
    private String sn;
    private Integer errCode;
    public static ErrorInfoReply success(String sn) {
        return new ErrorInfoReply(sn, ResponseResult.CODE_SUCCESS);
    }
}
src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java
New file
@@ -0,0 +1,36 @@
package com.dji.sample.component.mqtt.model;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
public enum EventsMethodEnum {
    FLIGHT_TASK_PROGRESS("flighttask_progress"),
    FILE_UPLOAD_CALLBACK("file_upload_callback"),
    HMS("hms"),
    UNKNOWN("Unknown");
    private String method;
    EventsMethodEnum(String method) {
        this.method = method;
    }
    public String getMethod() {
        return method;
    }
    public static EventsMethodEnum find(String method) {
        return Arrays.stream(EventsMethodEnum.values())
                .filter(methodEnum -> methodEnum.method.equals(method))
                .findAny()
                .orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java
New file
@@ -0,0 +1,21 @@
package com.dji.sample.component.mqtt.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/9
 */
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class EventsReceiver<T> {
    private Integer result;
    private T output;
    private String bid;
}
src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java
New file
@@ -0,0 +1,29 @@
package com.dji.sample.component.mqtt.model;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/14
 */
public final class MapKeyConst {
    private MapKeyConst(){
    }
    public static final String ORGANIZATION_NAME = "organization_name";
    public static final String DEVICES = "devices";
    public static final String SN = "sn";
    public static final String BIND_DEVICES = "bind_devices";
    public static final String ERR_INFOS = "err_infos";
    public static final String TINY_FINGERPRINTS = "tiny_fingerprints";
    public static final String BIND_STATUS = "bind_status";
    public static final String LIST = "list";
}
src/main/java/com/dji/sample/component/mqtt/model/RequestsMethodEnum.java
New file
@@ -0,0 +1,38 @@
package com.dji.sample.component.mqtt.model;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/25
 */
public enum RequestsMethodEnum {
    STORAGE_CONFIG_GET("storage_config_get"),
    AIRPORT_BIND_STATUS("airport_bind_status"),
    AIRPORT_ORGANIZATION_BIND("airport_organization_bind"),
    AIRPORT_ORGANIZATION_GET("airport_organization_get"),
    UNKNOWN("Unknown");
    private String method;
    RequestsMethodEnum(String method) {
        this.method = method;
    }
    public String getMethod() {
        return method;
    }
    public static RequestsMethodEnum find(String method) {
        return Arrays.stream(RequestsMethodEnum.values())
                .filter(methodEnum -> methodEnum.method.equals(method))
                .findAny()
                .orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/component/mqtt/model/RequestsReply.java
New file
@@ -0,0 +1,44 @@
package com.dji.sample.component.mqtt.model;
import com.dji.sample.common.error.IErrorInfo;
import com.dji.sample.common.model.ResponseResult;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/13
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RequestsReply<T> {
    private Integer result;
    private T output;
    public static RequestsReply error(IErrorInfo errorInfo) {
        return RequestsReply.builder()
                .result(errorInfo.getErrorCode())
                .output(errorInfo.getErrorMsg())
                .build();
    }
    public static <T> RequestsReply success(T data) {
        return RequestsReply.builder()
                .result(ResponseResult.CODE_SUCCESS)
                .output(data)
                .build();
    }
    public static RequestsReply success() {
        return RequestsReply.builder()
                .result(ResponseResult.CODE_SUCCESS)
                .build();
    }
}
src/main/java/com/dji/sample/component/mqtt/model/ServiceReply.java
New file
@@ -0,0 +1,20 @@
package com.dji.sample.component.mqtt.model;
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 ServiceReply<T> {
    private Integer result;
    private T info;
    private T output;
}
src/main/java/com/dji/sample/component/mqtt/model/ServicesMethodEnum.java
New file
@@ -0,0 +1,29 @@
package com.dji.sample.component.mqtt.model;
/**
 * @author sean.zhou
 * @date 2021/11/22
 * @version 0.1
 */
public enum ServicesMethodEnum {
    LIVE_START_PUSH("live_start_push"),
    LIVE_STOP_PUSH("live_stop_push"),
    LIVE_SET_QUALITY("live_set_quality"),
    FLIGHTTASK_CREATE("flighttask_create"),
    UNKNOWN("unknown");
    private String method;
    ServicesMethodEnum(String method) {
        this.method = method;
    }
    public String getMethod() {
        return method;
    }
}
src/main/java/com/dji/sample/component/mqtt/model/StateDataEnum.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.component.mqtt.model;
/**
 *
 * @author sean.zhou
 * @date 2021/11/18
 * @version 0.1
 */
public enum StateDataEnum {
    FIRMWARE_VERSION("firmware_version"),
    LIVE_CAPACITY("live_capacity"),
    PAYLOADS("payloads");
    private String desc;
    StateDataEnum(String desc) {
        this.desc = desc;
    }
    public String getDesc() {
        return this.desc;
    }
}
src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java
@@ -24,6 +24,10 @@
    public static final String OSD_SUF = "/osd";
    public static final String REQUESTS_SUF = "/requests";
    public static final String EVENTS_SUF = "/events";
    public static final String REGEX_SN = "[A-Za-z0-9]+";
}
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java
@@ -1,6 +1,9 @@
package com.dji.sample.component.mqtt.service;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.mqtt.model.ServiceReply;
import java.util.Optional;
/**
 * @author sean.zhou
@@ -24,4 +27,11 @@
     */
    void publish(String topic, int qos, CommonTopicResponse 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
     */
    Optional<ServiceReply> publishWithReply(String topic, CommonTopicResponse response);
}
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java
@@ -1,14 +1,19 @@
package com.dji.sample.component.mqtt.service.impl;
import com.dji.sample.component.mqtt.model.Chan;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.mqtt.model.ServiceReply;
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;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @author sean.zhou
@@ -22,11 +27,11 @@
    @Autowired
    private IMqttMessageGateway messageGateway;
    @Autowired
    private ObjectMapper mapper;
    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) {
@@ -37,14 +42,31 @@
    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();
        }
    }
    public Optional<ServiceReply> publishWithReply(String topic, CommonTopicResponse response) {
        AtomicInteger time = new AtomicInteger(0);
        // Retry three times
        while (time.getAndIncrement() < 3) {
            this.publish(topic, response);
            Chan<CommonTopicReceiver<ServiceReply>> chan = Chan.getInstance();
            // If the message is not received in 0.5 seconds then resend it again.
            CommonTopicReceiver<ServiceReply> 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();
    }
}
src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java
@@ -5,7 +5,7 @@
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZoneId;
/**
 * Automatic filling for set values
@@ -20,9 +20,9 @@
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Long.class,
                LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
                LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        this.strictInsertFill(metaObject, "updateTime", Long.class,
                LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
                LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    }
    /**
@@ -32,6 +32,6 @@
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Long.class,
                LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
                LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    }
}
src/main/java/com/dji/sample/component/oss/model/OssConfiguration.java
New file
@@ -0,0 +1,94 @@
package com.dji.sample.component.oss.model;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/9
 */
@ConfigurationProperties(prefix = "oss")
@Component
@Data
public class OssConfiguration {
    /**
     * @see com.dji.sample.component.oss.model.enums.OssTypeEnum
     */
    private String provider;
    /**
     * Whether to use the object storage service.
     */
    private boolean enable;
    /**
     * The protocol needs to be included at the beginning of the address.
     */
    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String region;
    private Long expire;
    private String roleSessionName;
    private String roleArn;
    private String bucket;
    private String objectDirPrefix;
    public void setProvider(String provider) {
        this.provider = provider;
    }
    public void setEnable(boolean enable) {
        this.enable = enable;
    }
    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }
    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }
    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }
    public void setRegion(String region) {
        this.region = region;
    }
    public void setExpire(Long expire) {
        this.expire = expire;
    }
    public void setRoleSessionName(String roleSessionName) {
        this.roleSessionName = roleSessionName;
    }
    public void setRoleArn(String roleArn) {
        this.roleArn = roleArn;
    }
    public void setBucket(String bucket) {
        this.bucket = bucket;
    }
    public void setObjectDirPrefix(String objectDirPrefix) {
        this.objectDirPrefix = objectDirPrefix;
    }
}
src/main/java/com/dji/sample/component/oss/model/enums/OssTypeEnum.java
New file
@@ -0,0 +1,28 @@
package com.dji.sample.component.oss.model.enums;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/30
 */
public enum OssTypeEnum {
    ALIYUN("ali"),
    AWS("aws"),
    /*
     * MinIO is temporarily unavailable.
    */
    MINIO("minio");
    private String type;
    OssTypeEnum(String type) {
        this.type = type;
    }
    public String getType() {
        return type;
    }
}
src/main/java/com/dji/sample/component/oss/service/IOssService.java
@@ -11,6 +11,8 @@
 */
public interface IOssService {
    String getOssType();
    /**
     * Get temporary credentials.
     * @return
@@ -24,4 +26,20 @@
     * @return download link
     */
    URL getObjectUrl(String bucket, String objectKey);
    /**
     * Deletes the object in the storage bucket.
     * @param bucket
     * @param objectKey
     * @return
     */
    Boolean deleteObject(String bucket, String objectKey);
    /**
     * Get the contents of an object.
     * @param bucket
     * @param objectKey
     * @return
     */
    byte[] getObject(String bucket, String objectKey);
}
src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java
@@ -1,13 +1,16 @@
package com.dji.sample.component.oss.service.impl;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.OSSObject;
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.model.OssConfiguration;
import com.dji.sample.component.oss.model.enums.OssTypeEnum;
import com.dji.sample.component.oss.service.IOssService;
import com.dji.sample.media.model.CredentialsDTO;
import lombok.extern.slf4j.Slf4j;
@@ -15,6 +18,8 @@
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
@@ -27,24 +32,29 @@
@Slf4j
public class AliyunOssServiceImpl implements IOssService {
    @Autowired(required = false)
    private OSS ossClient;
    @Autowired
    public OssConfiguration configuration;
    @Override
    public String getOssType() {
        return OssTypeEnum.ALIYUN.getType();
    }
    @Override
    public CredentialsDTO getCredentials() {
        try {
            DefaultProfile profile = DefaultProfile.getProfile(
                    AliyunOSSConfiguration.region, AliyunOSSConfiguration.accessKey, AliyunOSSConfiguration.secretKey);
                    configuration.getRegion(), configuration.getAccessKey(), configuration.getSecretKey());
            IAcsClient client = new DefaultAcsClient(profile);
            AssumeRoleRequest request = new AssumeRoleRequest();
            request.setDurationSeconds(AliyunOSSConfiguration.expire);
            request.setRoleArn(AliyunOSSConfiguration.roleArn);
            request.setRoleSessionName(AliyunOSSConfiguration.roleSessionName);
            request.setDurationSeconds(configuration.getExpire());
            request.setRoleArn(configuration.getRoleArn());
            request.setRoleSessionName(configuration.getRoleSessionName());
            AssumeRoleResponse response = client.getAcsResponse(request);
            return new CredentialsDTO(response.getCredentials(), AliyunOSSConfiguration.expire);
            return new CredentialsDTO(response.getCredentials(), configuration.getExpire());
        } catch (ClientException e) {
            log.debug("Failed to obtain sts.");
@@ -58,16 +68,45 @@
        if (!StringUtils.hasText(bucket) || !StringUtils.hasText(objectKey)) {
            return null;
        }
        try {
            // First check if the object can be fetched.
            ossClient.getObject(bucket, objectKey);
        OSS ossClient = this.createClient();
        // First check if the object can be fetched.
        ossClient.getObject(bucket, objectKey);
            return ossClient.generatePresignedUrl(bucket, objectKey,
                    new Date(System.currentTimeMillis() + AliyunOSSConfiguration.expire * 1000));
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
        return null;
        return ossClient.generatePresignedUrl(bucket, objectKey,
                new Date(System.currentTimeMillis() + configuration.getExpire() * 1000));
    }
    @Override
    public Boolean deleteObject(String bucket, String objectKey) {
        OSS ossClient = this.createClient();
        ossClient.deleteObject(bucket, objectKey);
        ossClient.shutdown();
        return true;
    }
    @Override
    public byte[] getObject(String bucket, String objectKey) {
        OSS ossClient = this.createClient();
        OSSObject object = ossClient.getObject(bucket, objectKey);
        InputStream stream = object.getObjectContent();
        try {
            return stream.readAllBytes();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                stream.close();
                ossClient.shutdown();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return new byte[0];
    }
    private OSS createClient() {
        return new OSSClientBuilder()
                .build(configuration.getEndpoint(), configuration.getAccessKey(), configuration.getSecretKey());
    }
}
src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java
New file
@@ -0,0 +1,135 @@
package com.dji.sample.component.oss.service.impl;
import com.amazonaws.HttpMethod;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.BucketCrossOriginConfiguration;
import com.amazonaws.services.s3.model.CORSRule;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder;
import com.amazonaws.services.securitytoken.model.AssumeRoleRequest;
import com.amazonaws.services.securitytoken.model.AssumeRoleResult;
import com.amazonaws.services.securitytoken.model.Credentials;
import com.dji.sample.component.AuthInterceptor;
import com.dji.sample.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.model.enums.OssTypeEnum;
import com.dji.sample.component.oss.service.IOssService;
import com.dji.sample.media.model.CredentialsDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/27
 */
@Service
public class AmazonS3ServiceImpl implements IOssService {
    @Autowired
    private OssConfiguration configuration;
    @Override
    public String getOssType() {
        return OssTypeEnum.AWS.getType();
    }
    @Override
    public CredentialsDTO getCredentials() {
        AWSSecurityTokenService stsClient = AWSSecurityTokenServiceClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(
                        new BasicAWSCredentials(configuration.getAccessKey(), configuration.getSecretKey())))
                .withRegion(configuration.getRegion()).build();
        AssumeRoleRequest request = new AssumeRoleRequest()
                .withRoleArn(configuration.getRoleArn())
                .withRoleSessionName(configuration.getRoleSessionName())
                .withDurationSeconds(Math.toIntExact(configuration.getExpire()));
        AssumeRoleResult result = stsClient.assumeRole(request);
        Credentials credentials = result.getCredentials();
        stsClient.shutdown();
        return new CredentialsDTO(credentials);
    }
    @Override
    public URL getObjectUrl(String bucket, String objectKey) {
        AmazonS3 client = this.createClient();
        URL url = client.generatePresignedUrl(bucket, objectKey,
                new Date(System.currentTimeMillis() + configuration.getExpire() * 1000), HttpMethod.GET);
        client.shutdown();
        return url;
    }
    @Override
    public Boolean deleteObject(String bucket, String objectKey) {
        AmazonS3 client = this.createClient();
        client.deleteObject(bucket, objectKey);
        client.shutdown();
        return true;
    }
    public byte[] getObject(String bucket, String objectKey) {
        AmazonS3 client = this.createClient();
        S3Object object = client.getObject(bucket, objectKey);
        InputStream stream = object.getObjectContent().getDelegateStream();
        try {
            return stream.readAllBytes();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                stream.close();
                client.shutdown();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return new byte[0];
    }
    private AmazonS3 createClient() {
        return AmazonS3ClientBuilder.standard()
                .withCredentials(
                        new AWSStaticCredentialsProvider(
                                new BasicAWSCredentials(configuration.getAccessKey(), configuration.getSecretKey())))
                .withRegion(configuration.getRegion())
                .build();
    }
    /**
     * Configuring cross-origin resource sharing
     */
    @PostConstruct
    private void configCORS() {
        if (!configuration.isEnable() || !OssTypeEnum.AWS.getType().equals(configuration.getProvider())) {
            return;
        }
        List<CORSRule.AllowedMethods> allowedMethods = new ArrayList<>();
        allowedMethods.add(CORSRule.AllowedMethods.GET);
        allowedMethods.add(CORSRule.AllowedMethods.POST);
        allowedMethods.add(CORSRule.AllowedMethods.DELETE);
        CORSRule rule = new CORSRule()
                .withId("CORSAccessRule")
                .withAllowedOrigins(List.of("*"))
                .withAllowedHeaders(List.of(AuthInterceptor.PARAM_TOKEN))
                .withAllowedMethods(allowedMethods);
        AmazonS3 client = this.createClient();
        client.setBucketCrossOriginConfiguration(this.configuration.getBucket(),
                new BucketCrossOriginConfiguration().withRules(rule));
        client.shutdown();
    }
}
src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java
@@ -1,6 +1,7 @@
package com.dji.sample.component.oss.service.impl;
import com.dji.sample.component.oss.model.MinIOConfiguration;
import com.dji.sample.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.model.enums.OssTypeEnum;
import com.dji.sample.component.oss.service.IOssService;
import com.dji.sample.media.model.CredentialsDTO;
import io.minio.GetPresignedObjectUrlArgs;
@@ -26,16 +27,21 @@
@Slf4j
public class MinIOServiceImpl implements IOssService {
    @Autowired(required = false)
    private MinioClient client;
    @Autowired
    private OssConfiguration configuration;
    @Override
    public String getOssType() {
        return OssTypeEnum.MINIO.getType();
    }
    @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);
            AssumeRoleProvider provider = new AssumeRoleProvider(configuration.getEndpoint(), configuration.getAccessKey(),
                    configuration.getSecretKey(), Math.toIntExact(configuration.getExpire()),
                    null, configuration.getRegion(), null, null, null, null);
            return new CredentialsDTO(provider.fetch(), Math.toIntExact(configuration.getExpire()));
        } catch (NoSuchAlgorithmException e) {
            log.debug("Failed to obtain sts.");
            e.printStackTrace();
@@ -47,20 +53,38 @@
    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()));
                    this.createClient()
                            .getPresignedObjectUrl(
                                    GetPresignedObjectUrlArgs.builder()
                                            .method(Method.GET)
                                            .bucket(bucket)
                                            .object(objectKey)
                                            .expiry(Math.toIntExact(configuration.getExpire()))
                                            .build()));
        } catch (ErrorResponseException | InsufficientDataException | InternalException |
                InvalidKeyException | InvalidResponseException | IOException |
                NoSuchAlgorithmException | XmlParserException | ServerException e) {
            log.error("The file does not exist on the oss.");
            log.error("The file does not exist on the OssConfiguration.");
            e.printStackTrace();
        }
        return null;
    }
    @Override
    public Boolean deleteObject(String bucket, String objectKey) {
        return null;
    }
    @Override
    public byte[] getObject(String bucket, String objectKey) {
        return new byte[0];
    }
    private MinioClient createClient() {
        return MinioClient.builder()
                .endpoint(configuration.getEndpoint())
                .credentials(configuration.getAccessKey(), configuration.getSecretKey())
                .region(configuration.getRegion())
                .build();
    }
}
src/main/java/com/dji/sample/component/oss/service/impl/OssAspectHandler.java
New file
@@ -0,0 +1,33 @@
package com.dji.sample.component.oss.service.impl;
import com.dji.sample.component.oss.model.OssConfiguration;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/20
 */
@Component
@Aspect
public class OssAspectHandler {
    @Autowired
    private OssServiceContext ossServiceContext;
    @Autowired
    private OssConfiguration configuration;
    @Before("execution(public * com.dji.sample.component.oss.service.impl.OssServiceContext.*(..))")
    public void before() {
        if (!this.configuration.isEnable()) {
            throw new IllegalArgumentException("Please enable OssConfiguration.");
        }
        if (this.ossServiceContext.getOssService() == null) {
            throw new IllegalArgumentException("Please check the OssConfiguration configuration.");
        }
    }
}
src/main/java/com/dji/sample/component/oss/service/impl/OssServiceContext.java
New file
@@ -0,0 +1,58 @@
package com.dji.sample.component.oss.service.impl;
import com.dji.sample.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.model.enums.OssTypeEnum;
import com.dji.sample.component.oss.service.IOssService;
import com.dji.sample.media.model.CredentialsDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/30
 */
@Service
public class OssServiceContext {
    private IOssService ossService;
    private OssConfiguration configuration;
    @Autowired
    public OssServiceContext(List<IOssService> ossServices, OssConfiguration configuration) {
        this.configuration = configuration;
        if (!configuration.isEnable()) {
            return;
        }
        this.ossService = ossServices.stream()
                .filter(ossService -> ossService.getOssType().equals(configuration.getProvider()))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Oss provider is illegal. Optional: " +
                        Arrays.toString(Arrays.stream(OssTypeEnum.values()).map(OssTypeEnum::getType).toArray())));
    }
    IOssService getOssService() {
        return this.ossService;
    }
    public CredentialsDTO getCredentials() {
        return this.ossService.getCredentials();
    }
    public URL getObjectUrl(String bucket, String objectKey) {
        return this.ossService.getObjectUrl(bucket, objectKey);
    }
    public Boolean deleteObject(String bucket, String objectKey) {
        return this.ossService.deleteObject(bucket, objectKey);
    }
    public byte[] getObject(String bucket, String objectKey) {
        return this.ossService.getObject(bucket, objectKey);
    }
}
src/main/java/com/dji/sample/component/redis/RedisConfiguration.java
New file
@@ -0,0 +1,63 @@
package com.dji.sample.component.redis;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/19
 */
@Configuration
@EnableRedisRepositories
public class RedisConfiguration {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        ObjectMapper objectMapper = new ObjectMapper();
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        objectMapper.disable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS);
        objectMapper.registerModules(timeModule);
        objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        StringRedisSerializer serializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(serializer);
        redisTemplate.setHashKeySerializer(serializer);
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
src/main/java/com/dji/sample/component/redis/RedisConst.java
New file
@@ -0,0 +1,29 @@
package com.dji.sample.component.redis;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/21
 */
public final class RedisConst {
    private RedisConst() {
    }
    public static final Integer DEVICE_ALIVE_SECOND = 60;
    public static final Integer WEBSOCKET_ALIVE_SECOND = 60 * 60 * 24;
    public static final String ONLINE_PREFIX = "online:";
    public static final String DEVICE_ONLINE_PREFIX = ONLINE_PREFIX + DeviceDomainEnum.SUB_DEVICE + ":";
    public static final String WEBSOCKET_PREFIX = "webSocket:";
    public static final String WEBSOCKET_ALL = "webSocket:all";
    public static final String HMS_PREFIX = "hms:";
}
src/main/java/com/dji/sample/component/redis/RedisOpsUtils.java
New file
@@ -0,0 +1,187 @@
package com.dji.sample.component.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/19
 */
@Component
public class RedisOpsUtils {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * HSET
     * @param key
     * @param field
     * @param value
     */
    public void hashSet(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }
    /**
     * HGET
     * @param key
     * @param field
     * @return
     */
    public Object hashGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }
    /**
     * HKEYS
     * @param key
     * @return
     */
    public Set<Object> hashKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }
    /**
     * HEXISTS
     * @param key
     * @param field
     * @return
     */
    public boolean hashCheck(String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }
    /**
     * HDEL
     * @param key
     * @param fields
     * @return
     */
    public boolean hashDel(String key, Object[] fields) {
        return redisTemplate.opsForHash().delete(key, fields) > 0;
    }
    /**
     * EXPIRE
     * @param key
     * @param timeout
     * @return
     */
    public boolean expireKey(String key, long timeout) {
        return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }
    /**
     * SET
     * @param key
     * @param value
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
    /**
     * GET
     * @param key
     * @return
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    /**
     * SETEX
     * @param key
     * @param value
     * @param expire
     */
    public void setWithExpire(String key, Object value, long expire) {
        redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
    }
    /**
     * TTL
     * @param key
     * @return
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    /**
     * EXISTS
     * @param key
     * @return
     */
    public boolean checkExist(String key) {
        return redisTemplate.hasKey(key);
    }
    /**
     * DEL
     * @param key
     * @return
     */
    public boolean del(String key) {
        return this.checkExist(key) && redisTemplate.delete(key);
    }
    /**
     * KEYS
     * @param pattern
     * @return
     */
    public Set<String> getAllKeys(String pattern) {
        return redisTemplate.keys(pattern);
    }
    /**
     * RPUSH
     * @param key
     * @param value
     */
    public void listRPush(String key, Object... value) {
        if (value.length == 0) {
            return;
        }
        for (Object val : value) {
            redisTemplate.opsForList().rightPush(key, val);
        }
    }
    /**
     * LRANGE
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<Object> listGet(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
    /**
     * LRANGE
     * @param key
     * @return
     */
    public List<Object> listGetAll(String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
    /**
     * LLen
     * @param key
     * @return
     */
    public Long listLen(String key) {
        return redisTemplate.opsForList().size(key);
    }
}
src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java
@@ -35,7 +35,7 @@
            if (!StringUtils.hasText(token)) {
                return false;
            }
            log.debug("token:" + token);
            Optional<CustomClaim> customClaim = JwtUtil.parseToken(token);
            if (customClaim.isEmpty()) {
                return false;
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java
@@ -1,5 +1,7 @@
package com.dji.sample.component.websocket.config;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
@@ -13,8 +15,11 @@
@Component
public class WebSocketDefaultFactory implements WebSocketHandlerDecoratorFactory {
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Override
    public WebSocketHandler decorate(WebSocketHandler handler) {
        return new WebSocketDefaultHandler(handler);
        return new WebSocketDefaultHandler(handler, webSocketManageService);
    }
}
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java
@@ -1,9 +1,7 @@
package com.dji.sample.component.websocket.config;
import com.dji.sample.component.websocket.model.WebSocketManager;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
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;
@@ -22,20 +20,20 @@
@Slf4j
public class WebSocketDefaultHandler extends WebSocketHandlerDecorator {
    @Autowired
    private ISendMessageService sendMessageService;
    private IWebSocketManageService webSocketManageService;
    WebSocketDefaultHandler(WebSocketHandler delegate) {
    WebSocketDefaultHandler(WebSocketHandler delegate, IWebSocketManageService webSocketManageService) {
        super(delegate);
        this.webSocketManageService = webSocketManageService;
    }
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Principal principal = session.getPrincipal();
        if (StringUtils.hasText(principal.getName())) {
            WebSocketManager.put(principal.getName(), new ConcurrentWebSocketSession(session));
            webSocketManageService.put(principal.getName(), new ConcurrentWebSocketSession(session));
            log.debug("{} is connected. ID: {}. WebSocketSession[current count: {}]",
                    principal.getName(), session.getId(), WebSocketManager.getConnectedCount());
                    principal.getName(), session.getId(), webSocketManageService.getConnectedCount());
            return;
        }
        session.close();
@@ -45,9 +43,9 @@
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        Principal principal = session.getPrincipal();
        if (StringUtils.hasText(principal.getName())) {
            WebSocketManager.remove(principal.getName(), session.getId());
            webSocketManageService.remove(principal.getName(), session.getId());
            log.debug("{} is disconnected. ID: {}. WebSocketSession[current count: {}]",
                    principal.getName(), session.getId(), WebSocketManager.getConnectedCount());
                    principal.getName(), session.getId(), webSocketManageService.getConnectedCount());
        }
    }
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java
@@ -17,13 +17,19 @@
    GATEWAY_OSD("gateway_osd"),
    DOCK_OSD("dock_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");
    MAP_GROUP_REFRESH("map_group_refresh"),
    FLIGHT_TASK_PROGRESS("flighttask_progress"),
    DEVICE_HMS("device_hms");
    private String code;
src/main/java/com/dji/sample/component/websocket/service/IWebSocketManageService.java
New file
@@ -0,0 +1,23 @@
package com.dji.sample.component.websocket.service;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import java.util.Collection;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/25
 */
public interface IWebSocketManageService {
    void put(String key, ConcurrentWebSocketSession val);
    void remove(String key, String sessionId);
    Collection<ConcurrentWebSocketSession> getValueWithWorkspace(String workspaceId);
    Collection<ConcurrentWebSocketSession> getValueWithWorkspaceAndUserType(String workspaceId, Integer userType);
    Long getConnectedCount();
}
src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java
@@ -5,6 +5,7 @@
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
@@ -20,6 +21,9 @@
@Slf4j
public class SendMessageServiceImpl implements ISendMessageService {
    @Autowired
    private ObjectMapper mapper;
    @Override
    public void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message) {
        if (session == null) {
@@ -33,7 +37,6 @@
                return;
            }
            ObjectMapper mapper = new ObjectMapper();
            session.sendMessage(new TextMessage(mapper.writeValueAsBytes(message)));
        } catch (IOException e) {
@@ -50,7 +53,6 @@
        try {
            ObjectMapper mapper = new ObjectMapper();
            TextMessage data = new TextMessage(mapper.writeValueAsBytes(message));
            for (ConcurrentWebSocketSession session : sessions) {
@@ -60,7 +62,6 @@
                    return;
                }
                session.sendMessage(data);
            }
        } catch (IOException e) {
src/main/java/com/dji/sample/component/websocket/service/impl/WebSocketManageServiceImpl.java
New file
@@ -0,0 +1,90 @@
package com.dji.sample.component.websocket.service.impl;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/25
 */
@Slf4j
@Service
public class WebSocketManageServiceImpl implements IWebSocketManageService {
    private static final ConcurrentHashMap<String, ConcurrentWebSocketSession> SESSIONS = new ConcurrentHashMap<>(16);
    @Autowired
    private RedisOpsUtils redisOps;
    @Override
    public 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;
        }
        String sessionId = val.getId();
        String workspaceKey = RedisConst.WEBSOCKET_PREFIX + name[0];
        String userTypeKey = RedisConst.WEBSOCKET_PREFIX + UserTypeEnum.find(Integer.parseInt(name[1])).getDesc();
        redisOps.hashSet(workspaceKey, sessionId, name[2]);
        redisOps.hashSet(userTypeKey, sessionId, name[2]);
        SESSIONS.put(sessionId, val);
        redisOps.expireKey(workspaceKey, RedisConst.WEBSOCKET_ALIVE_SECOND);
        redisOps.expireKey(userTypeKey, RedisConst.WEBSOCKET_ALIVE_SECOND);
    }
    @Override
    public 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;
        }
        redisOps.hashDel(RedisConst.WEBSOCKET_PREFIX + name[0], new String[] {sessionId});
        redisOps.hashDel(RedisConst.WEBSOCKET_PREFIX + UserTypeEnum.find(Integer.parseInt(name[1])), new String[] {sessionId});
        SESSIONS.remove(sessionId);
    }
    @Override
    public Collection<ConcurrentWebSocketSession> getValueWithWorkspace(String workspaceId) {
        if (!StringUtils.hasText(workspaceId)) {
            return Collections.emptySet();
        }
        String key = RedisConst.WEBSOCKET_PREFIX + workspaceId;
        return redisOps.hashKeys(key)
                .stream()
                .map(SESSIONS::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
    }
    @Override
    public Collection<ConcurrentWebSocketSession> getValueWithWorkspaceAndUserType(String workspaceId, Integer userType) {
        String key = RedisConst.WEBSOCKET_PREFIX + UserTypeEnum.find(userType).getDesc();
        return redisOps.hashKeys(key)
                .stream()
                .map(SESSIONS::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
    }
    @Override
    public Long getConnectedCount() {
        return SESSIONS.mappingCount();
    }
}
src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java
New file
@@ -0,0 +1,49 @@
package com.dji.sample.configuration;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class SpringBeanConfiguration {
    @Bean
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        objectMapper.disable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS);
        objectMapper.registerModules(timeModule);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                gen.writeString("");
            }
        });
        return objectMapper;
    }
}
src/main/java/com/dji/sample/manage/controller/DeviceController.java
@@ -1,19 +1,13 @@
package com.dji.sample.manage.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.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.FirmwareVersionReceiver;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import com.dji.sample.manage.service.IDeviceService;
import lombok.extern.slf4j.Slf4j;
@@ -21,12 +15,10 @@
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 org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;
/**
 * @author sean.zhou
@@ -58,10 +50,6 @@
                            .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());
        }
    }
@@ -81,22 +69,16 @@
                            .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
     * Get the topology list of all online devices in one workspace.
     * @param workspaceId
     * @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.
    @GetMapping("/{workspace_id}/devices")
    public ResponseResult<List<DeviceDTO>> getDevices(@PathVariable("workspace_id") String workspaceId) {
        List<DeviceDTO> devicesList = deviceService.getDevicesTopoForWeb(workspaceId);
        return ResponseResult.success(devicesList);
@@ -109,30 +91,54 @@
        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);
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_FIRMWARE_VERSION)
    public void updateFirmwareVersion(FirmwareVersionReceiver receiver) {
        deviceService.updateFirmwareVersion(receiver);
    }
        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);
    @PostMapping("/{device_sn}/binding")
    public ResponseResult bindDevice(@RequestBody DeviceDTO device, @PathVariable("device_sn") String deviceSn) {
        device.setDeviceSn(deviceSn);
        boolean isUpd = deviceService.bindDevice(device);
        return isUpd ? ResponseResult.success() : ResponseResult.error();
    }
    @GetMapping("/{workspace_id}/devices/{device_sn}")
    public ResponseResult getDevice(@PathVariable("workspace_id") String workspaceId,
                                               @PathVariable("device_sn") String deviceSn) {
        Optional<DeviceDTO> deviceOpt = deviceService.getDeviceBySn(deviceSn);
        return deviceOpt.isEmpty() ? ResponseResult.error("device not found.") : ResponseResult.success(deviceOpt.get());
    }
    /**
     * Get the binding devices list in one workspace.
     * @param workspaceId
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/{workspace_id}/devices/bound")
    public ResponseResult<PaginationData<DeviceDTO>> getBoundDevicesWithDomain(
            @PathVariable("workspace_id") String workspaceId, String domain,
            @RequestParam(defaultValue = "1") Long page,
            @RequestParam(value = "page_size", defaultValue = "50") Long pageSize) {
        PaginationData<DeviceDTO> devices = deviceService.getBoundDevicesWithDomain(workspaceId, page, pageSize, domain);
        return ResponseResult.success(devices);
    }
    @DeleteMapping("/{device_sn}/unbinding")
    public ResponseResult unbindingDevice(@PathVariable("device_sn") String deviceSn) {
        deviceService.unbindDevice(deviceSn);
        return ResponseResult.success();
    }
    @PutMapping("/{workspace_id}/devices/{device_sn}")
    public ResponseResult updateDevice(@RequestBody DeviceDTO device,
                                       @PathVariable("workspace_id") String workspaceId,
                                       @PathVariable("device_sn") String deviceSn) {
        device.setDeviceSn(deviceSn);
        boolean isUpd = deviceService.updateDevice(device);
        return isUpd ? ResponseResult.success() : ResponseResult.error();
    }
}
src/main/java/com/dji/sample/manage/controller/DeviceHmsController.java
New file
@@ -0,0 +1,53 @@
package com.dji.sample.manage.controller;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.DeviceHmsDTO;
import com.dji.sample.manage.model.param.DeviceHmsQueryParam;
import com.dji.sample.manage.service.IDeviceHmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/7
 */
@RestController
@Slf4j
@RequestMapping("${url.manage.prefix}${url.manage.version}/devices")
public class DeviceHmsController {
    @Autowired
    private IDeviceHmsService deviceHmsService;
    @GetMapping("/{workspace_id}/devices/hms")
    public ResponseResult<PaginationData<DeviceHmsDTO>> getBoundDevicesWithDomain(DeviceHmsQueryParam param,
                                                          @PathVariable("workspace_id") String workspaceId) {
        PaginationData<DeviceHmsDTO> devices = deviceHmsService.getDeviceHmsByParam(param);
        return ResponseResult.success(devices);
    }
    @PutMapping("/{workspace_id}/devices/hms/{device_sn}")
    public ResponseResult updateReadHmsByDeviceSn(@PathVariable("device_sn") String deviceSn) {
        deviceHmsService.updateUnreadHms(deviceSn);
        return ResponseResult.success();
    }
    @GetMapping("/{workspace_id}/devices/hms/{device_sn}")
    public ResponseResult<List<DeviceHmsDTO>> getUnreadHmsByDeviceSn(@PathVariable("device_sn") String deviceSn) {
        PaginationData<DeviceHmsDTO> paginationData = deviceHmsService.getDeviceHmsByParam(
                DeviceHmsQueryParam.builder()
                        .deviceSn(new HashSet<>(Set.of(deviceSn)))
                        .updateTime(0L)
                        .build());
        return ResponseResult.success(paginationData.getList());
    }
}
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java
@@ -2,7 +2,6 @@
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;
@@ -24,25 +23,6 @@
    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
@@ -50,9 +30,8 @@
     * @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) {
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC)
    public void stateBasic(DeviceBasicReceiver deviceBasic) {
        // Delete all payload information for the drone based on the drone's sn.
        devicePayloadService.deletePayloadsByDeviceSn(List.of(deviceBasic.getDeviceSn()));
@@ -61,6 +40,5 @@
        log.debug("The result of saving the payloads is {}.", isSave);
        return deviceBasic.getDeviceSn();
    }
}
src/main/java/com/dji/sample/manage/controller/LiveStreamController.java
@@ -2,13 +2,13 @@
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.Chan;
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.component.mqtt.model.ServiceReply;
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.model.receiver.LiveCapacityReceiver;
import com.dji.sample.manage.service.ILiveStreamService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -38,15 +38,17 @@
    @Autowired
    private ILiveStreamService liveStreamService;
    @Autowired
    private ObjectMapper mapper;
    /**
     * Analyze the live streaming capabilities of drones.
     * This data is necessary if drones are required for live streaming.
     * @param device    the capacity of drone
     * @param liveCapacity    the capacity of drone and dock
     */
    @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);
    public void stateCapacity(LiveCapacityReceiver liveCapacity) {
        liveStreamService.saveLiveCapacity(liveCapacity);
    }
    /**
@@ -102,9 +104,8 @@
    @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>>() {
        CommonTopicReceiver<ServiceReply> receiver = mapper.readValue(payload,
                new TypeReference<CommonTopicReceiver<ServiceReply>>() {
        });
        Chan<CommonTopicReceiver> chan = Chan.getInstance();
        // Put the message to the chan object.
src/main/java/com/dji/sample/manage/controller/LoginController.java
@@ -27,9 +27,10 @@
    @PostMapping("/login")
    public ResponseResult login(@RequestBody UserLoginDTO loginDTO) {
        String username = loginDTO.getUsername();
        String password = loginDTO.getPassword();
        return userService.userLogin(username, password);
        return userService.userLogin(username, password, loginDTO.getFlag());
    }
    @PostMapping("/token/refresh")
@@ -42,6 +43,6 @@
            return ResponseResult.error(CommonErrorEnum.NO_TOKEN.getErrorMsg());
        }
        return ResponseResult.success(user);
        return ResponseResult.success(user.get());
    }
}
src/main/java/com/dji/sample/manage/controller/UserController.java
@@ -1,12 +1,12 @@
package com.dji.sample.manage.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.manage.model.dto.UserListDTO;
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 org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@@ -20,10 +20,45 @@
    @Autowired
    private IUserService userService;
    /**
     * Query the information of the current user.
     * @param request
     * @return
     */
    @GetMapping("/current")
    public ResponseResult getCurrentUserInfo(HttpServletRequest request) {
        CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
        return userService.getUserByUsername(customClaim.getUsername(), customClaim.getWorkspaceId());
    }
    /**
     * Paging to query all users in a workspace.
     * @param page      current page
     * @param pageSize
     * @param workspaceId
     * @return
     */
    @GetMapping("/{workspace_id}/users")
    public ResponseResult<PaginationData<UserListDTO>> getUsers(@RequestParam(defaultValue = "1") Long page,
                                    @RequestParam(value = "page_size", defaultValue = "50") Long pageSize,
                                    @PathVariable("workspace_id") String workspaceId) {
        PaginationData<UserListDTO> paginationData = userService.getUsersByWorkspaceId(page, pageSize, workspaceId);
        return ResponseResult.success(paginationData);
    }
    /**
     * Modify user information. Only mqtt account information is included, nothing else can be modified.
     * @param user
     * @param workspaceId
     * @param userId
     * @return
     */
    @PutMapping("/{workspace_id}/users/{user_id}")
    public ResponseResult updateUser(@RequestBody UserListDTO user,
                                  @PathVariable("workspace_id") String workspaceId,
                                  @PathVariable("user_id") String userId) {
        userService.updateUser(workspaceId, userId, user);
        return ResponseResult.success();
    }
}
src/main/java/com/dji/sample/manage/dao/IDeviceHmsMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.DeviceHmsEntity;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/6
 */
public interface IDeviceHmsMapper extends BaseMapper<DeviceHmsEntity> {
}
src/main/java/com/dji/sample/manage/model/common/HmsJsonUtil.java
New file
@@ -0,0 +1,49 @@
package com.dji.sample.manage.model.common;
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.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/7
 */
@Slf4j
@Component
public class HmsJsonUtil {
    private static ObjectMapper mapper;
    @Autowired
    public void setMapper(ObjectMapper mapper) {
        HmsJsonUtil.mapper = mapper;
    }
    private static JsonNode nodes;
    private HmsJsonUtil(){
    }
    @PostConstruct
    private void loadJsonFile() {
        try (InputStream inputStream = new ClassPathResource("hms.json").getInputStream()){
            nodes = mapper.readTree(inputStream);
        } catch (IOException e) {
            log.error("hms.json failed to load.");
            e.printStackTrace();
        }
    }
    public static HmsMessage get(String key) {
        return mapper.convertValue(nodes.get(key), HmsMessage.class);
    }
}
src/main/java/com/dji/sample/manage/model/common/HmsMessage.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.manage.model.common;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/7
 */
@Data
public class HmsMessage {
    private String zh;
    private String en;
}
src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java
@@ -18,13 +18,11 @@
@NoArgsConstructor
public class CapacityCameraDTO {
    private Integer id;
    private String id;
    private String deviceSn;
    private String name;
    private String description;
    private String index;
src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java
@@ -16,7 +16,7 @@
@NoArgsConstructor
public class CapacityVideoDTO {
    private Integer id;
    private String id;
    private String index;
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java
@@ -1,12 +1,11 @@
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.time.LocalDateTime;
import java.util.List;
/**
@@ -18,7 +17,6 @@
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class DeviceDTO {
    private String deviceSn;
@@ -44,4 +42,22 @@
    private List<DevicePayloadDTO> payloadsList;
    private IconUrlDTO iconUrl;
    private Boolean status;
    private Boolean boundStatus;
    private LocalDateTime loginTime;
    private LocalDateTime boundTime;
    private String nickname;
    private String userId;
    private String firmwareVersion;
    private String workspaceName;
    private DeviceDTO children;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceHmsDTO.java
New file
@@ -0,0 +1,57 @@
package com.dji.sample.manage.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/8
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DeviceHmsDTO implements Cloneable {
    private String hmsId;
    private String tid;
    private String bid;
    private String sn;
    private Integer level;
    private Integer module;
    private String key;
    private String messageZh;
    private String messageEn;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    @Override
    public DeviceHmsDTO clone() {
        try {
            return (DeviceHmsDTO) super.clone();
        } catch (CloneNotSupportedException e) {
            return DeviceHmsDTO.builder()
                    .sn(this.sn)
                    .bid(this.bid)
                    .tid(this.tid)
                    .createTime(this.createTime)
                    .updateTime(this.updateTime)
                    .build();
        }
    }
}
src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java
@@ -14,9 +14,9 @@
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TelemetryDTO {
public class TelemetryDTO<T> {
    private TelemetryDeviceDTO host;
    private T host;
    private String sn;
}
src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java
@@ -22,8 +22,7 @@
    private DeviceModelDTO deviceModel;
    @Builder.Default
    private Boolean onlineStatus = true;
    private Boolean onlineStatus;
    private String deviceCallsign;
@@ -32,4 +31,12 @@
    private String userCallsign;
    private IconUrlDTO iconUrls;
    private String model;
    private Boolean boundStatus;
    private String gatewaySn;
    private String domain;
}
src/main/java/com/dji/sample/manage/model/dto/UserListDTO.java
New file
@@ -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.time.LocalDateTime;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/18
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserListDTO {
    private String userId;
    private String username;
    private String workspaceName;
    private String userType;
    private String mqttUsername;
    private String mqttPassword;
    private LocalDateTime createTime;
}
src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java
@@ -11,4 +11,7 @@
    @NonNull
    private String password;
    @NonNull
    private Integer flag;
}
src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java
@@ -19,8 +19,6 @@
@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;
@@ -30,4 +28,6 @@
    private String workspaceDesc;
    private String platformName;
    private String bindCode;
}
src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java
@@ -66,4 +66,23 @@
    @TableField(value = "url_select")
    private String urlSelect;
    @TableField(value = "user_id")
    private String userId;
    @TableField(value = "nickname")
    private String nickname;
    @TableField(value = "firmware_version")
    private String firmwareVersion;
    @TableField(value = "bound_status")
    private Boolean boundStatus;
    @TableField(value = "bound_time")
    private Long boundTime;
    @TableField(value = "login_time")
    private Long loginTime;
}
src/main/java/com/dji/sample/manage/model/entity/DeviceHmsEntity.java
New file
@@ -0,0 +1,76 @@
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
 * @version 1.1
 * @date 2022/7/6
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "manage_device_hms")
public class DeviceHmsEntity implements Serializable, Cloneable {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @TableField("hms_id")
    private String hmsId;
    @TableField("tid")
    private String tid;
    @TableField("bid")
    private String bid;
    @TableField("sn")
    private String sn;
    @TableField("level")
    private Integer level;
    @TableField("module")
    private Integer module;
    @TableField("hms_key")
    private String hmsKey;
    @TableField("message_zh")
    private String messageZh;
    @TableField("message_en")
    private String messageEn;
    @TableField("create_time")
    private Long createTime;
    @TableField("update_time")
    private Long updateTime;
    @Override
    public DeviceHmsEntity clone() {
        try {
            return (DeviceHmsEntity) super.clone();
        } catch (CloneNotSupportedException e) {
            return DeviceHmsEntity.builder()
                    .bid(this.getBid())
                    .tid(this.getTid())
                    .createTime(this.getCreateTime())
                    .updateTime(this.getUpdateTime())
                    .sn(this.getSn())
                    .build();
        }
    }
}
src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java
@@ -35,8 +35,8 @@
    @TableField(value = "sub_type")
    private Integer subType;
    @TableField(value = "version")
    private Integer version;
    @TableField(value = "firmware_version")
    private String firmwareVersion;
    @TableField(value = "payload_index")
    private Integer payloadIndex;
src/main/java/com/dji/sample/manage/model/entity/UserEntity.java
@@ -22,7 +22,7 @@
    private String password;
    @TableField(value = "workspace_id")
    private Integer workspaceId;
    private String workspaceId;
    @TableField(value = "user_type")
    private Integer userType;
src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java
@@ -30,4 +30,6 @@
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private Long updateTime;
    @TableField(value = "bind_code")
    private String bindCode;
}
src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java
@@ -14,6 +14,8 @@
    PAYLOAD(1, "payload"),
    DOCK (3, "dock"),
    UNKNOWN(-1, "unknown");
    private int val;
@@ -29,6 +31,10 @@
        return val;
    }
    public String getDesc() {
        return desc;
    }
    public static String getDesc(int val) {
        if (SUB_DEVICE.val == val) {
            return SUB_DEVICE.desc;
@@ -40,6 +46,10 @@
        if (PAYLOAD.val == val) {
            return PAYLOAD.desc;
        }
        if (DOCK.val == val) {
            return DOCK.desc;
        }
        return UNKNOWN.desc;
    }
@@ -56,6 +66,10 @@
        if (PAYLOAD.desc.equals(desc)) {
            return PAYLOAD.val;
        }
        if (DOCK.desc.equals(desc)) {
            return DOCK.val;
        }
        return UNKNOWN.val;
    }
src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java
New file
@@ -0,0 +1,155 @@
package com.dji.sample.manage.model.enums;
import lombok.Getter;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/7
 */
@Getter
public enum HmsEnum {
    IN_THE_SKY("_in_the_sky", 1);
    private int val;
    private String text;
    HmsEnum(String text, int val) {
        this.text = text;
        this.val = val;
    }
    @Getter
    public enum MessageLanguage {
        EN("en"),
        ZH("zh");
        String language;
        MessageLanguage(String language) {
            this.language = language;
        }
    }
    @Getter
    public enum DomainType {
        DRONE_NEST("drone_nest"),
        DRONE("drone");
        private String domain;
        DomainType(String domain) {
            this.domain = domain;
        }
    }
    @Getter
    public enum HmsFaqIdEnum {
        FPV_TIP("fpv_tip_");
        private String text;
        HmsFaqIdEnum(String text) {
            this.text = text;
        }
    }
    @Getter
    public enum HmsBatteryIndexEnum {
        LEFT(0, "left", "左"),
        RIGHT(1, "right", "右"),
        UNKNOWN(-1, "unknown", "未知");
        private int val;
        private String en;
        private String zh;
        HmsBatteryIndexEnum(int val, String en, String zh) {
            this.val = val;
            this.en = en;
            this.zh = zh;
        }
        public static HmsBatteryIndexEnum find(int val) {
            return Arrays.stream(HmsBatteryIndexEnum.values())
                    .filter(battery -> battery.val == val)
                    .findAny()
                    .orElse(UNKNOWN);
        }
    }
    @Getter
    public enum HmsDockCoverIndexEnum {
        LEFT(0, "left", "左"),
        RIGHT(1, "right", "右"),
        UNKNOWN(-1, "unknown", "未知");
        private int val;
        private String en;
        private String zh;
        HmsDockCoverIndexEnum(int val, String en, String zh) {
            this.val = val;
            this.en = en;
            this.zh = zh;
        }
        public static HmsDockCoverIndexEnum find(int val) {
            return Arrays.stream(HmsDockCoverIndexEnum.values())
                    .filter(dockCover -> dockCover.val == val)
                    .findAny()
                    .orElse(UNKNOWN);
        }
    }
    @Getter
    public enum HmsChargingRodIndexEnum {
        FRONT(0, "front", "前"),
        BACK(1, "back", "后"),
        LEFT(2, "left", "左"),
        RIGHT(3, "right", "右"),
        UNKNOWN(-1, "unknown", "未知");
        private int val;
        private String en;
        private String zh;
        HmsChargingRodIndexEnum(int val, String en, String zh) {
            this.val = val;
            this.en = en;
            this.zh = zh;
        }
        public static HmsChargingRodIndexEnum find(int val) {
            return Arrays.stream(HmsChargingRodIndexEnum.values())
                    .filter(rod -> rod.val == val)
                    .findAny()
                    .orElse(UNKNOWN);
        }
    }
}
src/main/java/com/dji/sample/manage/model/enums/PayloadModelEnum.java
New file
@@ -0,0 +1,61 @@
package com.dji.sample.manage.model.enums;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/29
 */
public enum PayloadModelEnum {
    Z30("Z30", "20-0"),
    XT2("XT2", "26-0"),
    XTS("XTS", "41-0"),
    H20("H20", "42-0"),
    H20T("H20T", "43-0"),
    P1("P1", "50-65535"),
    M30("M30", "52-0"),
    M30T("M30T", "53-0"),
    H20N("H20N", "61-0"),
    DOCK("DOCK", "165-0"),
    L1("L1", "90742-0");
    private String model;
    private String index;
    PayloadModelEnum(String model, String index) {
        this.model = model;
        this.index = index;
    }
    public String getModel() {
        return model;
    }
    public String getIndex() {
        return index;
    }
    public static List<String> getAllModel() {
        return Arrays.stream(PayloadModelEnum.values()).map(PayloadModelEnum::getModel).collect(Collectors.toList());
    }
    public static List<String> getAllIndex() {
        return Arrays.stream(PayloadModelEnum.values()).map(PayloadModelEnum::getIndex).collect(Collectors.toList());
    }
}
src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java
@@ -7,18 +7,36 @@
 */
public enum UserTypeEnum {
    WEB(1),
    WEB(1, "Web"),
    PILOT(2);
    PILOT(2, "Pilot"),
    UNKNOWN(-1, "Unknown");
    private int val;
    private String desc;
    UserTypeEnum(int val) {
    UserTypeEnum(int val, String desc) {
        this.val = val;
        this.desc = desc;
    }
    public int getVal() {
        return val;
        return this.val;
    }
    public String getDesc() {
        return this.desc;
    }
    public static UserTypeEnum find(int val) {
        if (val == WEB.val) {
            return WEB;
        }
        if (val == PILOT.val) {
            return PILOT;
        }
        return UNKNOWN;
    }
}
src/main/java/com/dji/sample/manage/model/param/DeviceHmsQueryParam.java
New file
@@ -0,0 +1,45 @@
package com.dji.sample.manage.model.param;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Set;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/8
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DeviceHmsQueryParam implements Serializable {
    @JsonProperty("device_sn")
    private Set<String> deviceSn;
    @JsonProperty("begin_time")
    private Long beginTime;
    @JsonProperty("end_time")
    private Long endTime;
    private String language;
    private String message;
    private Long page;
    @JsonProperty("page_size")
    private Long pageSize;
    private Integer level;
    @JsonProperty("update_time")
    private Long updateTime;
}
src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java
@@ -3,6 +3,8 @@
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
 * The object of the device query field.
 *
@@ -22,10 +24,12 @@
    private Integer subType;
    private Integer domain;
    private List<Integer> domains;
    private String childSn;
    private Boolean boundStatus;
    private boolean orderBy;
    private boolean isAsc;
src/main/java/com/dji/sample/manage/model/receiver/AlternateLandPointReceiver.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/11
 */
@Data
public class AlternateLandPointReceiver {
    private Double latitude;
    private Double longitude;
    private Double safeLandHeight;
}
src/main/java/com/dji/sample/manage/model/receiver/BindDeviceReceiver.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/13
 */
@Data
public class BindDeviceReceiver {
    private String deviceBindingCode;
    private String organizationId;
    private String deviceCallsign;
    private String sn;
    private String deviceModelKey;
}
src/main/java/com/dji/sample/manage/model/receiver/BindStatusReceiver.java
New file
@@ -0,0 +1,30 @@
package com.dji.sample.manage.model.receiver;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/14
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude
public class BindStatusReceiver {
    private String sn;
    private Boolean isDeviceBindOrganization;
    private String organizationId;
    private String organizationName;
    private String deviceCallsign;
}
src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java
@@ -21,17 +21,11 @@
    private String deviceSn;
    private Integer flightMode;
    private Integer flightStatus;
    private Double homeLatitude;
    private Double homeLongitude;
    private Integer lowBatteryWarningThreshold;
    private Integer positionMode;
    private Integer seriousLowBatteryWarningThreshold;
src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java
New file
@@ -0,0 +1,28 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/6
 */
@Data
public class DeviceHmsReceiver {
    private String code;
    private String deviceType;
    private String domainType;
    private Integer imminent;
    private Integer inTheSky;
    private Integer level;
    private Integer module;
    private HmsArgsReceiver args;
}
src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java
@@ -23,7 +23,4 @@
    private String sn;
    private Integer version;
    private Integer workMode;
}
src/main/java/com/dji/sample/manage/model/receiver/DockMediaFileDetailReceiver.java
New file
@@ -0,0 +1,14 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/17
 */
@Data
public class DockMediaFileDetailReceiver {
    private Integer remainUpload;
}
src/main/java/com/dji/sample/manage/model/receiver/DockSdrReceiver.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/17
 */
@Data
public class DockSdrReceiver {
    private Integer downQuality;
    private Double frequencyBand;
    private Integer upQuality;
}
src/main/java/com/dji/sample/manage/model/receiver/DockSubDeviceReceiver.java
New file
@@ -0,0 +1,20 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/11
 */
@Data
public class DockSubDeviceReceiver {
    private String deviceSn;
    private Integer deviceOnlineStatus;
    private Integer devicePaired;
    private String deviceModelKey;
}
src/main/java/com/dji/sample/manage/model/receiver/DroneChargeStateReceiver.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/11
 */
@Data
public class DroneChargeStateReceiver {
    private Integer state;
    private Integer capacityPercent;
}
src/main/java/com/dji/sample/manage/model/receiver/FirmwareVersionReceiver.java
New file
@@ -0,0 +1,29 @@
package com.dji.sample.manage.model.receiver;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.0
 * @date 2022/4/28
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FirmwareVersionReceiver {
    private String firmwareVersion;
    private Integer compatibleStatus;
    private Integer firmwareUpgradeStatus;
    private String sn;
    private DeviceDomainEnum domain;
}
src/main/java/com/dji/sample/manage/model/receiver/HmsArgsReceiver.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/6
 */
@Data
public class HmsArgsReceiver {
    private Long componentIndex;
    private Integer sensorIndex;
    private Integer alarmId;
}
src/main/java/com/dji/sample/manage/model/receiver/LiveCapacityReceiver.java
New file
@@ -0,0 +1,20 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
import java.util.List;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/24
 */
@Data
public class LiveCapacityReceiver {
    private Integer availableVideoNumber;
    private Integer coexistVideoNumberMax;
    private List<CapacityDeviceReceiver> deviceList;
}
src/main/java/com/dji/sample/manage/model/receiver/NetworkStateReceiver.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/11
 */
@Data
public class NetworkStateReceiver {
    private Integer type;
    private Integer quality;
    private float rate;
}
src/main/java/com/dji/sample/manage/model/receiver/OrganizationGetReceiver.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/13
 */
@Data
public class OrganizationGetReceiver {
    private String deviceBindingCode;
    private String organizationId;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java
New file
@@ -0,0 +1,67 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/11
 */
@Data
public class OsdDockReceiver {
    private NetworkStateReceiver networkState;
    private Integer droneInDock;
    private DroneChargeStateReceiver droneChargeState;
    private Integer rainfall;
    private Float windSpeed;
    private Float environmentTemperature;
    private Integer environmentHumidity;
    private Float temperature;
    private Integer humidity;
    private Double latitude;
    private Double longitude;
    private Double height;
    private AlternateLandPointReceiver alternateLandPoint;
    private Integer jobNumber;
    private Integer accTime;
    private Long firstPowerOn;
    private PositionStateReceiver positionState;
    private StorageReceiver storage;
    private Integer electricSupplyVoltage;
    private Integer workingVoltage;
    private Integer workingCurrent;
    private Integer backupBatteryVoltage;
    private Integer modeCode;
    private Integer coverState;
    private Integer supplementLightState;
    private Integer putterState;
    private DockSubDeviceReceiver subDevice;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdDockTransmissionReceiver.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/17
 */
@Data
public class OsdDockTransmissionReceiver {
    private Integer flighttaskStepCode;
    private DockMediaFileDetailReceiver mediaFileDetail;
    private DockSdrReceiver sdr;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdPayloadReceiver.java
New file
@@ -0,0 +1,33 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/6
 */
@Data
public class OsdPayloadReceiver {
    private String payloadIndex;
    private Double gimbalPitch;
    private Double gimbalRoll;
    private Double gimbalYaw;
    private Double measureTargetAltitude;
    private Double measureTargetDistance;
    private Double measureTargetLatitude;
    private Double measureTargetLongitude;
    private Integer measureTargetErrorState;
    private Integer version;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java
@@ -5,6 +5,8 @@
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import java.util.List;
/**
 * @author sean.zhou
 * @version 0.1
@@ -49,7 +51,11 @@
    private Double windDirection;
    private Double windSpeed;
    private Float windSpeed;
    private PositionStateReceiver positionState;
    private List<OsdPayloadReceiver> payloads;
    private StorageReceiver storage;
}
src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java
@@ -13,6 +13,8 @@
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class PositionStateReceiver {
    private Integer isCalibration;
    private Integer gpsNumber;
    private Integer isFixed;
src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java
@@ -16,6 +16,8 @@
    private String sn;
    private Integer domain;
    private Integer type;
    @JsonProperty(value = "sub_type")
src/main/java/com/dji/sample/manage/model/receiver/StorageReceiver.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/11
 */
@Data
public class StorageReceiver {
    private Long total;
    private Long used;
}
src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java
@@ -15,7 +15,7 @@
    private Integer downloadQuality;
    private Integer frequencyBand;
    private Double frequencyBand;
    private Integer upwardQuality;
src/main/java/com/dji/sample/manage/service/ICameraVideoService.java
@@ -3,8 +3,6 @@
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
@@ -13,25 +11,9 @@
public interface ICameraVideoService {
    /**
     * Queries all lens data contained in the camera based on camera id.
     * @param cameraId
     * @return
     * Convert the received lens capability object into lens data transfer object.
     * @param receiver
     * @return  data transfer object
     */
    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);
    CapacityVideoDTO receiver2Dto(CapacityVideoReceiver receiver);
}
src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java
@@ -20,14 +20,6 @@
    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
@@ -38,8 +30,13 @@
     * Save the live capability data of the device.
     * @param capacityCameraReceivers
     * @param deviceSn
     */
    void saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn);
    /**
     *  Convert the received camera capability object into camera data transfer object.
     * @param receiver
     * @return
     */
    Boolean saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn);
    CapacityCameraDTO receiver2Dto(CapacityCameraReceiver receiver);
}
src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java
@@ -13,12 +13,10 @@
    /**
     * 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);
    Optional<DeviceDictionaryDTO> getOneDictionaryInfoByTypeSubType(Integer deviceType, Integer subType);
}
src/main/java/com/dji/sample/manage/service/IDeviceHmsService.java
New file
@@ -0,0 +1,21 @@
package com.dji.sample.manage.service;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.dto.DeviceHmsDTO;
import com.dji.sample.manage.model.param.DeviceHmsQueryParam;
import org.springframework.messaging.MessageHeaders;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/6
 */
public interface IDeviceHmsService {
    void handleHms(CommonTopicReceiver receiver, MessageHeaders headers);
    PaginationData<DeviceHmsDTO> getDeviceHmsByParam(DeviceHmsQueryParam param);
    void updateUnreadHms(String deviceSn);
}
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java
@@ -2,6 +2,7 @@
import com.dji.sample.manage.model.dto.DevicePayloadDTO;
import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver;
import java.util.List;
@@ -45,4 +46,10 @@
     * @param deviceSns
     */
    void deletePayloadsByDeviceSn(List<String> deviceSns);
    /**
     * Update the firmware version information of the payload.
     * @param receiver
     */
    void updateFirmwareVersion(FirmwareVersionReceiver receiver);
}
src/main/java/com/dji/sample/manage/service/IDeviceService.java
@@ -1,11 +1,15 @@
package com.dji.sample.manage.service;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
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.FirmwareVersionReceiver;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import org.springframework.messaging.MessageHeaders;
import java.util.Collection;
import java.util.List;
@@ -90,7 +94,7 @@
     * @param sessions  The collection of connection objects on the pilot side.
     * @param sn
     */
    void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn);
    void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn, String gatewaySn);
    /**
     * Query the information of the device according to the sn of the device.
@@ -111,9 +115,9 @@
     * 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
     * @param sn
     */
    void pushDeviceOfflineTopo(String workspaceId, String gatewaySn);
    void pushDeviceOfflineTopo(String workspaceId, String sn);
    /**
     * When the server receives the request of any device online, offline and topology update in the same workspace,
@@ -123,7 +127,7 @@
     * @param deviceSn
     * @param gatewaySn
     */
    void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn);
    void pushDeviceOnlineTopo(String workspaceId, String gatewaySn, String deviceSn);
    /**
     * Handle messages from the osd topic.
@@ -132,4 +136,63 @@
     */
    void handleOSD(String topic, byte[] payload);
    /**
     * Update the device information.
     * @param deviceDTO
     * @return
     */
    Boolean updateDevice(DeviceDTO deviceDTO);
    /**
     * Bind devices to organizations and people.
     * @param device
     */
    Boolean bindDevice(DeviceDTO device);
    /**
     * Handle dock binding status requests.
     * Note: If your business does not need to bind the dock to the organization,
     *       you can directly reply to the successful message without implementing business logic.
     * @param receiver
     * @param headers
     */
    void bindStatus(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Handle dock binding requests.
     * Note: If your business does not need to bind the dock to the organization,
     *       you can directly reply to the successful message without implementing business logic.
     * @param receiver
     * @param headers
     */
    void bindDevice(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Get the binding devices list in one workspace.
     * @param workspaceId
     * @param page
     * @param pageSize
     * @param domain
     * @return
     */
    PaginationData<DeviceDTO> getBoundDevicesWithDomain(String workspaceId, Long page, Long pageSize, String domain);
    /**
     * Unbind device base on device's sn.
     * @param deviceSn
     */
    void unbindDevice(String deviceSn);
    /**
     * Get device information based on device's sn.
     * @param sn device's sn
     * @return device
     */
    Optional<DeviceDTO> getDeviceBySn(String sn);
    /**
     * Update the firmware version information of the device or payload.
     * @param receiver
     */
    void updateFirmwareVersion(FirmwareVersionReceiver receiver);
}
src/main/java/com/dji/sample/manage/service/ILiveStreamService.java
@@ -3,7 +3,7 @@
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 com.dji.sample.manage.model.receiver.LiveCapacityReceiver;
import java.util.List;
@@ -22,11 +22,10 @@
    List<CapacityDeviceDTO> getLiveCapacity(String workspaceId);
    /**
     * Save live capability data from drone.
     * @param capacityDeviceReceiver
     * @return
     * Save live capability data.
     * @param liveCapacityReceiver
     */
    Boolean saveLiveCapacity(CapacityDeviceReceiver capacityDeviceReceiver);
    void saveLiveCapacity(LiveCapacityReceiver liveCapacityReceiver);
    /**
     * Initiate a live streaming by publishing mqtt message.
src/main/java/com/dji/sample/manage/service/ITSAService.java
@@ -1,5 +1,13 @@
package com.dji.sample.manage.service;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.TelemetryDTO;
import java.util.Collection;
/**
 * @author sean
 * @version 0.3
@@ -14,4 +22,14 @@
     * @param sn
     */
    void pushTelemetryData(String workspaceId, Object osdData, String sn);
    /**
     * Handle device's osd data.
     * @param receiver
     * @param webSessions
     * @param wsMessage
     */
    void handleOSD(CommonTopicReceiver receiver, DeviceDTO device,
                   Collection<ConcurrentWebSocketSession> webSessions, CustomWebSocketMessage<TelemetryDTO> wsMessage);
}
src/main/java/com/dji/sample/manage/service/IUserService.java
@@ -1,7 +1,9 @@
package com.dji.sample.manage.service;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.UserDTO;
import com.dji.sample.manage.model.dto.UserListDTO;
import java.util.Optional;
@@ -19,9 +21,10 @@
     * Verify the username and password to log in.
     * @param username
     * @param password
     * @param flag
     * @return
     */
    ResponseResult userLogin(String username, String password);
    ResponseResult userLogin(String username, String password, Integer flag);
    /**
     * Create a user object containing a new token.
@@ -29,4 +32,13 @@
     * @return
     */
    Optional<UserDTO> refreshToken(String token);
    /**
     * Query information about all users in a workspace.
     * @param workspaceId   uuid
     * @return
     */
    PaginationData<UserListDTO> getUsersByWorkspaceId(long page, long pageSize, String workspaceId);
    Boolean updateUser(String workspaceId, String userId, UserListDTO user);
}
src/main/java/com/dji/sample/manage/service/IWorkspaceService.java
@@ -1,18 +1,13 @@
package com.dji.sample.manage.service;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.dto.WorkspaceDTO;
import org.springframework.messaging.MessageHeaders;
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.
@@ -20,4 +15,19 @@
     * @return
     */
    Optional<WorkspaceDTO> getWorkspaceByWorkspaceId(String workspaceId);
    /**
     * Query the workspace of a workspace based on bind code.
     * @param bindCode
     * @return
     */
    Optional<WorkspaceDTO> getWorkspaceNameByBindCode(String bindCode);
    /**
     * Handle the request for obtaining the organization information corresponding to the device binding.
     * Note: If your business does not need to bind the dock to the organization,
     *       you can directly reply to the successful message without implementing business logic.
     * @param receiver
     */
    void replyOrganizationGet(CommonTopicReceiver receiver, MessageHeaders headers);
}
src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java
@@ -1,16 +1,17 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.redis.RedisOpsUtils;
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.component.websocket.service.IWebSocketManageService;
import com.dji.sample.manage.model.dto.DeviceDTO;
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 com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
@@ -24,6 +25,15 @@
    protected AbstractTSAService tsaService;
    @Autowired
    protected ObjectMapper mapper;
    @Autowired
    protected RedisOpsUtils redisOps;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    public AbstractTSAService(AbstractTSAService tsaService) {
        this.tsaService = tsaService;
    }
@@ -34,9 +44,8 @@
    @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());
        Collection<ConcurrentWebSocketSession> pilotSessions = webSocketManageService
                .getValueWithWorkspaceAndUserType(workspaceId, UserTypeEnum.PILOT.getVal());
        TelemetryDTO telemetry = TelemetryDTO.builder()
                .sn(sn)
@@ -53,7 +62,6 @@
    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;
    public abstract void handleOSD(CommonTopicReceiver receiver, DeviceDTO device,
                                   Collection<ConcurrentWebSocketSession> webSessions, CustomWebSocketMessage<TelemetryDTO> wsMessage);
}
src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java
@@ -1,17 +1,11 @@
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;
import java.util.UUID;
/**
 * @author sean.zhou
@@ -19,80 +13,17 @@
 * @date 2021/11/19
 */
@Service
@Transactional
//@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) {
    public CapacityVideoDTO receiver2Dto(CapacityVideoReceiver receiver) {
        CapacityVideoDTO.CapacityVideoDTOBuilder builder = CapacityVideoDTO.builder();
        if (entity != null) {
            builder
                    .id(entity.getId())
                    .index(entity.getVideoIndex())
                    .type(entity.getVideoType());
        if (receiver != null) {
            builder.id(UUID.randomUUID().toString())
                    .index(receiver.getVideoIndex())
                    .type(receiver.getVideoType());
        }
        return builder.build();
    }
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java
@@ -1,23 +1,20 @@
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.component.mqtt.model.StateDataEnum;
import com.dji.sample.component.redis.RedisOpsUtils;
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.UUID;
import java.util.stream.Collectors;
/**
@@ -26,11 +23,8 @@
 * @version 0.1
 */
@Service
@Transactional
//@Transactional
public class CapacityCameraServiceImpl implements ICapacityCameraService {
    @Autowired
    private ICapacityCameraMapper mapper;
    @Autowired
    private ICameraVideoService cameraVideoService;
@@ -38,95 +32,29 @@
    @Autowired
    private IDeviceDictionaryService dictionaryService;
    @Autowired
    private RedisOpsUtils redisOps;
    @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;
        return (List<CapacityCameraDTO>) redisOps.hashGet(StateDataEnum.LIVE_CAPACITY.getDesc(), deviceSn);
    }
    @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);
        return redisOps.hashDel(StateDataEnum.LIVE_CAPACITY.getDesc(), new String[]{deviceSn});
    }
    @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;
    public void saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn) {
        List<CapacityCameraDTO> capacity = capacityCameraReceivers.stream()
                .map(this::receiver2Dto).collect(Collectors.toList());
        redisOps.hashSet(StateDataEnum.LIVE_CAPACITY.getDesc(), deviceSn, capacity);
    }
    /**
     * 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();
    @Override
    public CapacityCameraDTO receiver2Dto(CapacityCameraReceiver receiver) {
        CapacityCameraDTO.CapacityCameraDTOBuilder builder = CapacityCameraDTO.builder();
        if (receiver == null) {
            return builder.build();
        }
@@ -138,35 +66,17 @@
        // type-subType-index
        if (indexArr.length == 3) {
            Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                    .getOneDictionaryInfoByDomainTypeSubType(
                            DeviceDomainEnum.PAYLOAD.getVal(), indexArr[0], indexArr[1]);
                    .getOneDictionaryInfoByTypeSubType(indexArr[0], indexArr[1]);
            dictionaryOpt.ifPresent(dictionary ->
                    builder.name(dictionary.getDeviceName()));
        }
        return builder
                .availableVideoNumber(receiver.getAvailableVideoNumber())
                .coexistVideoNumberMax(receiver.getCoexistVideoNumberMax())
                .cameraIndex(receiver.getCameraIndex())
                .id(UUID.randomUUID().toString())
                .videosList(receiver.getVideosList()
                        .stream()
                        .map(cameraVideoService::receiver2Dto)
                        .collect(Collectors.toList()))
                .index(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();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java
@@ -25,16 +25,14 @@
    private IDeviceDictionaryMapper mapper;
    @Override
    public Optional<DeviceDictionaryDTO> getOneDictionaryInfoByDomainTypeSubType(
            Integer domain, Integer deviceType, Integer subType) {
        if (domain == null || deviceType == null || subType == null) {
    public Optional<DeviceDictionaryDTO> getOneDictionaryInfoByTypeSubType(Integer deviceType, Integer subType) {
        if (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))));
    }
src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java
New file
@@ -0,0 +1,133 @@
package com.dji.sample.manage.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.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.MapKeyConst;
import com.dji.sample.component.mqtt.model.TopicConst;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
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.service.impl.SendMessageServiceImpl;
import com.dji.sample.component.websocket.service.impl.WebSocketManageServiceImpl;
import com.dji.sample.manage.dao.IDeviceHmsMapper;
import com.dji.sample.manage.model.common.HmsJsonUtil;
import com.dji.sample.manage.model.common.HmsMessage;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.DeviceHmsDTO;
import com.dji.sample.manage.model.dto.TelemetryDTO;
import com.dji.sample.manage.model.entity.DeviceHmsEntity;
import com.dji.sample.manage.model.enums.HmsEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.param.DeviceHmsQueryParam;
import com.dji.sample.manage.model.receiver.DeviceHmsReceiver;
import com.dji.sample.manage.model.receiver.HmsArgsReceiver;
import com.dji.sample.manage.service.IDeviceHmsService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 1.1
 * @date 2022/7/6
 */
@Service
@Transactional
public class DeviceHmsServiceImpl implements IDeviceHmsService {
    @Autowired
    private IDeviceHmsMapper mapper;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RedisOpsUtils redisOps;
    @Autowired
    private SendMessageServiceImpl sendMessageService;
    @Autowired
    private WebSocketManageServiceImpl webSocketManageService;
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_HMS)
    public void handleHms(CommonTopicReceiver receiver, MessageHeaders headers) {
    }
    @Override
    public PaginationData<DeviceHmsDTO> getDeviceHmsByParam(DeviceHmsQueryParam param) {
        LambdaQueryWrapper<DeviceHmsEntity> queryWrapper = new LambdaQueryWrapper<DeviceHmsEntity>()
                .and(wrapper -> param.getDeviceSn().forEach(sn -> wrapper.eq(DeviceHmsEntity::getSn, sn).or()))
                .between(param.getBeginTime() != null && param.getEndTime() != null,
                        DeviceHmsEntity::getCreateTime, param.getBeginTime(), param.getEndTime())
                .eq(param.getUpdateTime() != null, DeviceHmsEntity::getUpdateTime, param.getUpdateTime())
                .eq(param.getLevel() != null, DeviceHmsEntity::getLevel, param.getLevel())
                .like(StringUtils.hasText(param.getMessage()) &&
                                HmsEnum.MessageLanguage.ZH.getLanguage().equals(param.getLanguage()),
                        DeviceHmsEntity::getMessageZh, param.getMessage())
                .like(StringUtils.hasText(param.getMessage()) &&
                                HmsEnum.MessageLanguage.EN.getLanguage().equals(param.getLanguage()),
                        DeviceHmsEntity::getMessageEn, param.getMessage())
                .orderByDesc(DeviceHmsEntity::getCreateTime);
        if (param.getPage() == null || param.getPageSize() == null) {
            param.setPage(1L);
            param.setPageSize(Long.valueOf(mapper.selectCount(queryWrapper)));
        }
        Page<DeviceHmsEntity> pagination = mapper.selectPage(new Page<>(param.getPage(), param.getPageSize()), queryWrapper);
        List<DeviceHmsDTO> deviceHmsList = pagination.getRecords().stream().map(this::entity2Dto).collect(Collectors.toList());
        return new PaginationData<DeviceHmsDTO>(deviceHmsList, new Pagination(pagination));
    }
    @Override
    public void updateUnreadHms(String deviceSn) {
        mapper.update(DeviceHmsEntity.builder().updateTime(System.currentTimeMillis()).build(),
                new LambdaUpdateWrapper<DeviceHmsEntity>()
                        .eq(DeviceHmsEntity::getSn, deviceSn)
                        .eq(DeviceHmsEntity::getUpdateTime, 0L));
        redisOps.del(RedisConst.HMS_PREFIX + deviceSn);
    }
    private DeviceHmsDTO entity2Dto(DeviceHmsEntity entity) {
        if (entity == null) {
            return null;
        }
        return DeviceHmsDTO.builder()
                .bid(entity.getBid())
                .tid(entity.getTid())
                .createTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getCreateTime()), ZoneId.systemDefault()))
                .updateTime(entity.getUpdateTime().intValue() == 0 ?
                        null : LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault()))
                .sn(entity.getSn())
                .hmsId(entity.getHmsId())
                .key(entity.getHmsKey())
                .level(entity.getLevel())
                .module(entity.getModule())
                .messageEn(entity.getMessageEn())
                .messageZh(entity.getMessageZh())
                .build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java
@@ -1,22 +1,25 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
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.DeviceDTO;
import com.dji.sample.manage.model.dto.DevicePayloadDTO;
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.OsdPayloadReceiver;
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 lombok.extern.slf4j.Slf4j;
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;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * @author sean
@@ -24,10 +27,11 @@
 * @date 2022/2/21
 */
@Service
@Slf4j
public class DeviceOSDServiceImpl extends AbstractTSAService {
    protected DeviceOSDServiceImpl() {
        super(null);
    protected DeviceOSDServiceImpl(@Autowired @Qualifier("dockOSDServiceImpl") AbstractTSAService tsaService) {
        super(tsaService);
    }
    @Override
@@ -50,20 +54,32 @@
        }
    }
    @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());
    public void handleOSD(CommonTopicReceiver receiver, DeviceDTO device,
                          Collection<ConcurrentWebSocketSession> webSessions,
                          CustomWebSocketMessage<TelemetryDTO> wsMessage) {
        if (DeviceDomainEnum.SUB_DEVICE.getDesc().equals(device.getDomain())) {
            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);
            OsdSubDeviceReceiver data = mapper.convertValue(receiver.getData(), OsdSubDeviceReceiver.class);
            List<DevicePayloadDTO> payloadsList = device.getPayloadsList();
            try {
                Map<String, Object> receiverData = (Map<String, Object>) receiver.getData();
                data.setPayloads(payloadsList.stream()
                        .map(payload -> mapper.convertValue(
                                receiverData.getOrDefault(payload.getPayloadName(), Map.of()),
                                OsdPayloadReceiver.class))
                        .collect(Collectors.toList()));
        wsMessage.setData(data);
            } catch (NullPointerException e) {
                log.warn("Please remount the payload, or restart the drone. Otherwise the data of the payload will not be received.");
            }
        sendMessageService.sendBatch(webSessions, wsMessage);
        this.pushTelemetryData(workspaceId, data, sn);
            wsMessage.getData().setHost(data);
            sendMessageService.sendBatch(webSessions, wsMessage);
            this.pushTelemetryData(device.getWorkspaceId(), data, device.getDeviceSn());
        }
        tsaService.handleOSD(receiver, device, webSessions, wsMessage);
    }
}
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java
@@ -1,12 +1,16 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.manage.dao.IDevicePayloadMapper;
import com.dji.sample.manage.model.dto.DeviceDTO;
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.model.receiver.FirmwareVersionReceiver;
import com.dji.sample.manage.service.ICapacityCameraService;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import com.dji.sample.manage.service.IDevicePayloadService;
@@ -14,6 +18,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -37,6 +42,9 @@
    @Autowired
    private ICapacityCameraService capacityCameraService;
    @Autowired
    private RedisOpsUtils redisOps;
    @Override
    public Integer checkPayloadExist(String payloadSn) {
        DevicePayloadEntity devicePayload = mapper.selectOne(
@@ -57,12 +65,28 @@
    @Override
    public Boolean savePayloadDTOs(List<DevicePayloadReceiver> payloadReceiverList) {
        if (payloadReceiverList.isEmpty()) {
            return true;
        }
        String deviceSn = payloadReceiverList.get(0).getDeviceSn();
        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
        DeviceDTO device = (DeviceDTO) redisOps.get(key);
        List<DevicePayloadDTO> payloads = new ArrayList<>();
        for (DevicePayloadReceiver payloadReceiver : payloadReceiverList) {
            int payloadId = this.saveOnePayloadDTO(payloadReceiver);
            if (payloadId <= 0) {
                return false;
            }
            payloads.add(this.receiver2Dto(payloadReceiver));
        }
        if (payloads.isEmpty()) {
            payloads = this.getDevicePayloadEntitiesByDeviceSn(deviceSn);
        }
        device.setPayloadsList(payloads);
        redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(), device, RedisConst.DEVICE_ALIVE_SECOND);
        return true;
    }
@@ -89,6 +113,15 @@
                            .eq(DevicePayloadEntity::getDeviceSn, deviceSn));
            capacityCameraService.deleteCapacityCameraByDeviceSn(deviceSn);
        });
    }
    @Override
    public void updateFirmwareVersion(FirmwareVersionReceiver receiver) {
        mapper.update(DevicePayloadEntity.builder()
                        .firmwareVersion(receiver.getFirmwareVersion())
                        .build()
                , new LambdaUpdateWrapper<DevicePayloadEntity>()
                        .eq(DevicePayloadEntity::getDeviceSn, receiver.getSn()));
    }
    /**
@@ -129,8 +162,7 @@
            if (arr.length == 3) {
                Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                        .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(),
                                arr[0], arr[1]);
                        .getOneDictionaryInfoByTypeSubType(arr[0], arr[1]);
                dictionaryOpt.ifPresent(dictionary ->
                        builder.payloadName(dictionary.getDeviceName())
                                .payloadDesc(dictionary.getDeviceDesc()));
@@ -147,10 +179,17 @@
        return builder
                .payloadSn(dto.getSn())
                .version(dto.getVersion())
                .deviceSn(dto.getSn()
                        .substring(0,
                                dto.getSn().indexOf("-")))
                .deviceSn(dto.getDeviceSn())
                .build();
    }
    private DevicePayloadDTO receiver2Dto(DevicePayloadReceiver receiver) {
        DevicePayloadDTO.DevicePayloadDTOBuilder builder = DevicePayloadDTO.builder();
        if (receiver == null) {
            return builder.build();
        }
        return builder.payloadSn(receiver.getSn())
                .payloadName(receiver.getPayloadIndex())
                .build();
    }
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
@@ -1,15 +1,21 @@
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.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.common.error.CommonErrorEnum;
import com.dji.sample.common.model.Pagination;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.mqtt.service.IMqttTopicService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
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.component.websocket.service.IWebSocketManageService;
import com.dji.sample.manage.dao.IDeviceMapper;
import com.dji.sample.manage.model.dto.*;
import com.dji.sample.manage.model.entity.DeviceEntity;
@@ -17,28 +23,26 @@
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.dji.sample.manage.model.receiver.*;
import com.dji.sample.manage.service.*;
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.beans.factory.annotation.Qualifier;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
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.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -77,23 +81,61 @@
    private ISendMessageService sendMessageService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RedisOpsUtils redisOps;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Autowired
    @Qualifier("gatewayOSDServiceImpl")
    private AbstractTSAService tsaService;
    private ITSAService tsaService;
    @Override
    public Boolean deviceOffline(String gatewaySn) {
        List<DeviceDTO> gatewaysList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .deviceSn(gatewaySn)
                        .build());
        this.subscribeTopicOnline(gatewaySn);
        // 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.");
        // Only the remote controller is logged in and the aircraft is not connected.
        String key = RedisConst.DEVICE_ONLINE_PREFIX + gatewaySn;
        boolean exist = redisOps.checkExist(key);
        if (!exist) {
            Optional<DeviceDTO> gatewayOpt = this.getDeviceBySn(gatewaySn);
            if (gatewayOpt.isPresent()) {
                DeviceDTO value = gatewayOpt.get();
                value.setChildDeviceSn(value.getDeviceSn());
                value.setBoundTime(null);
                value.setLoginTime(null);
                redisOps.setWithExpire(key, value, RedisConst.DEVICE_ALIVE_SECOND);
                this.pushDeviceOnlineTopo(value.getWorkspaceId(), gatewaySn, gatewaySn);
                return true;
            }
            DeviceDTO gateway = DeviceDTO.builder()
                    .deviceSn(gatewaySn)
                    .childDeviceSn(gatewaySn)
                    .domain(DeviceDomainEnum.GATEWAY.getDesc())
                    .build();
            gatewayOpt.map(DeviceDTO::getWorkspaceId).ifPresent(gateway::setWorkspaceId);
            redisOps.setWithExpire(key, gateway, RedisConst.DEVICE_ALIVE_SECOND);
            this.pushDeviceOnlineTopo(gateway.getWorkspaceId(), gatewaySn, gatewaySn);
            return true;
        }
        // Handle the drone connected to the gateway device offline.
        return this.subDeviceOffline(gatewaysList.get(0).getChildDeviceSn());
        long expire = redisOps.getExpire(key);
        // If the key about the device in redis has expired, the remote control is considered to be offline.
        if (expire <= 0) {
            log.debug("The remote control is already offline.");
            return true;
        }
        String deviceSn = ((DeviceDTO)(redisOps.get(key))).getChildDeviceSn();
        if (deviceSn.equals(gatewaySn)) {
            return true;
        }
        return subDeviceOffline(deviceSn);
    }
    @Override
@@ -101,82 +143,117 @@
        // 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);
        payloadService.deletePayloadsByDeviceSn(new ArrayList<>(List.of(deviceSn)));
        // If no information about this gateway device exists in the database, the drone is considered to be offline.
        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
        if (!redisOps.checkExist(key) || redisOps.getExpire(key) <= 0) {
            log.debug("The drone is already offline.");
            return true;
        }
        DeviceDTO device = (DeviceDTO) redisOps.get(key);
        // Publish the latest device topology information in the current workspace.
        this.pushDeviceOfflineTopo(device.getWorkspaceId(), deviceSn);
        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;
        redisOps.del(key);
        log.debug("{} offline.", deviceSn);
        return true;
    }
    @Override
    public Boolean deviceOnline(StatusGatewayReceiver deviceGateway) {
        String deviceSn = deviceGateway.getSubDevices().get(0).getSn();
        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
        // change log:  Use redis instead of
        long time = redisOps.getExpire(key);
        long now = System.currentTimeMillis();
        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()) {
        if (time > 0) {
            redisOps.expireKey(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn(), RedisConst.DEVICE_ALIVE_SECOND);
            redisOps.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND);
            DeviceDTO device = DeviceDTO.builder().loginTime(LocalDateTime.now()).deviceSn(deviceSn).build();
            DeviceDTO gateway = DeviceDTO.builder()
                    .loginTime(LocalDateTime.now())
                    .deviceSn(deviceGateway.getSn())
                    .childDeviceSn(deviceSn).build();
            this.updateDevice(gateway);
            this.updateDevice(device);
            String workspaceId = ((DeviceDTO)(redisOps.get(key))).getWorkspaceId();
            if (StringUtils.hasText(workspaceId)) {
                this.subscribeTopicOnline(deviceSn);
                this.subscribeTopicOnline(deviceGateway.getSn());
            }
            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()));
        List<DeviceDTO> gatewaysList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .childSn(deviceSn)
                        .build());
        gatewaysList.stream().filter(
                gateway -> !gateway.getDeviceSn().equals(deviceGateway.getSn()))
                .findAny()
                .ifPresent(gateway -> {
                    gateway.setChildDeviceSn("");
                    this.updateDevice(gateway);
                });
        DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway);
        gateway.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID);
        gateway.setChildSn(deviceSn);
        // 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());
        gateway.setLoginTime(now);
        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());
        // Set the icon of the drone device displayed in the pilot's map, required in the TSA module.
        subDevice.setUrlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl());
        subDevice.setLoginTime(now);
        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());
        // dock go online
        if (deviceGateway.getDomain() != null && DeviceDomainEnum.DOCK.getVal() == deviceGateway.getDomain()) {
            Optional<DeviceDTO> deviceOpt = this.getDeviceBySn(deviceGateway.getSn());
            if (deviceOpt.isEmpty()) {
                log.info("The dock is not bound and cannot go online.");
                return false;
            }
            gateway.setNickname(null);
            subDevice.setNickname(null);
        }
        return isSave;
        Optional<DeviceEntity> gatewayOpt = this.saveDevice(gateway);
        String workspaceId = this.saveDevice(subDevice).orElse(subDevice).getWorkspaceId();
        redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceSn,
                DeviceDTO.builder()
                        .deviceSn(deviceSn)
                        .domain(DeviceDomainEnum.SUB_DEVICE.getDesc())
                        .workspaceId(workspaceId)
                        .build(),
                RedisConst.DEVICE_ALIVE_SECOND);
        redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn(),
                DeviceDTO.builder()
                        .deviceSn(gateway.getDeviceSn())
                        .workspaceId(gatewayOpt.orElse(gateway).getWorkspaceId())
                        .childDeviceSn(deviceSn)
                        .domain(deviceGateway.getDomain() != null ?
                                DeviceDomainEnum.getDesc(deviceGateway.getDomain()) :
                                DeviceDomainEnum.GATEWAY.getDesc())
                        .build(),
                RedisConst.DEVICE_ALIVE_SECOND);
        log.debug("{} online.", subDevice.getDeviceSn());
        if (StringUtils.hasText(workspaceId)) {
            this.pushDeviceOnlineTopo(workspaceId, deviceGateway.getSn(), deviceSn);
        }
        // Subscribe to topic related to drone devices.
        this.subscribeTopicOnline(deviceSn);
        this.subscribeTopicOnline(deviceGateway.getSn());
        return true;
    }
    @Override
@@ -191,6 +268,8 @@
        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);
        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + REQUESTS_SUF);
        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + EVENTS_SUF);
    }
    @Override
@@ -198,6 +277,8 @@
        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);
        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + REQUESTS_SUF);
        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + EVENTS_SUF);
    }
    @Override
@@ -232,17 +313,21 @@
        return mapper.selectList(
                new LambdaQueryWrapper<DeviceEntity>()
                        .eq(StringUtils.hasText(param.getDeviceSn()),
                                    DeviceEntity::getDeviceSn, 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())
                        .and(!CollectionUtils.isEmpty(param.getDomains()), wrapper -> {
                            for (Integer domain : param.getDomains()) {
                                wrapper.eq(DeviceEntity::getDomain, domain).or();
                            }
                        })
                        .eq(StringUtils.hasText(param.getWorkspaceId()),
                                DeviceEntity::getWorkspaceId, param.getWorkspaceId())
                        .eq(param.getBoundStatus() != null, DeviceEntity::getBoundStatus, param.getBoundStatus())
                        .orderBy(param.isOrderBy(),
                                param.isAsc(), DeviceEntity::getId))
                .stream()
@@ -255,13 +340,13 @@
        List<DeviceDTO> devicesList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .workspaceId(workspaceId)
                        .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                        .domains(List.of(DeviceDomainEnum.SUB_DEVICE.getVal()))
                        .build());
        devicesList.forEach(device -> {
            this.spliceDeviceTopo(device);
            device.setWorkspaceId(workspaceId);
            device.setStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
        });
        return devicesList;
    }
@@ -301,7 +386,7 @@
    }
    @Override
    public void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn) {
    public void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn, String gatewaySn) {
        CustomWebSocketMessage<TopologyDeviceDTO> pilotMessage =
                CustomWebSocketMessage.<TopologyDeviceDTO>builder()
@@ -312,6 +397,9 @@
        this.getDeviceTopoForPilot(sn)
                .ifPresent(pilotMessage::setData);
        boolean exist = redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        pilotMessage.getData().setOnlineStatus(exist);
        pilotMessage.getData().setGatewaySn(gatewaySn.equals(sn) ? "" : gatewaySn);
        sendMessageService.sendBatch(sessions, pilotMessage);
    }
@@ -321,91 +409,82 @@
        TopologyDeviceDTO.TopologyDeviceDTOBuilder builder = TopologyDeviceDTO.builder();
        if (device != null) {
            String domain = String.valueOf(DeviceDomainEnum.getVal(device.getDomain()));
            int domain = DeviceDomainEnum.getVal(device.getDomain());
            String subType = String.valueOf(device.getSubType());
            String type = String.valueOf(device.getType());
            builder.sn(device.getDeviceSn())
                    .deviceCallsign(device.getDeviceName())
                    .deviceCallsign(device.getNickname())
                    .deviceModel(DeviceModelDTO.builder()
                            .domain(domain)
                            .domain(String.valueOf(domain))
                            .subType(subType)
                            .type(type)
                            .key(domain + "-" + type + "-" + subType)
                            .build())
                    .iconUrls(device.getIconUrl())
                    .onlineStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()))
                    .boundStatus(device.getBoundStatus())
                    .model(device.getDeviceName())
                    .userId(device.getUserId())
                    .domain(DeviceDomainEnum.getDesc(domain))
                    .build();
        }
        return builder.build();
    }
    @Override
    public void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn) {
    public void pushDeviceOnlineTopo(String workspaceId, String gatewaySn, String deviceSn) {
        // All connected accounts on the pilot side of this workspace.
        Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
                .getValueWithWorkspaceAndUserType(
                        workspaceId, UserTypeEnum.PILOT.getVal());
        // All connected accounts in this workspace.
        Collection<ConcurrentWebSocketSession> allSessions = webSocketManageService.getValueWithWorkspace(workspaceId);
        this.pushDeviceOnlineTopo(pilotSessions, deviceSn);
        this.pushDeviceOnlineTopo(pilotSessions, gatewaySn);
        this.pushDeviceUpdateTopo(pilotSessions, deviceSn);
        this.pushDeviceUpdateTopo(pilotSessions, gatewaySn);
        if (!gatewaySn.equals(deviceSn)) {
            this.pushDeviceOnlineTopo(allSessions, deviceSn, gatewaySn);
            this.pushDeviceUpdateTopo(allSessions, deviceSn);
        }
        this.pushDeviceOnlineTopo(allSessions, gatewaySn, gatewaySn);
        this.pushDeviceUpdateTopo(allSessions, 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());
    public void pushDeviceOfflineTopo(String workspaceId, String sn) {
        // All connected accounts of this workspace.
        Collection<ConcurrentWebSocketSession> allSessions = webSocketManageService
                .getValueWithWorkspace(workspaceId);
        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);
        this.pushDeviceOfflineTopo(allSessions, sn);
        this.pushDeviceUpdateTopo(allSessions, sn);
    }
    @Override
    public void handleOSD(String topic, byte[] payload) {
        TopicStateReceiver receiver;
        CommonTopicReceiver 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()) {
            // Real-time update of device status in memory
            redisOps.expireKey(RedisConst.DEVICE_ONLINE_PREFIX + from, RedisConst.DEVICE_ALIVE_SECOND);
            DeviceDTO device = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + from);
            if (device == null || !StringUtils.hasText(device.getWorkspaceId())) {
                return;
            }
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            receiver = objectMapper.readValue(payload, CommonTopicReceiver.class);
            receiver = mapper.readValue(payload, TopicStateReceiver.class);
            CustomWebSocketMessage<TelemetryDTO> wsMessage = CustomWebSocketMessage.<TelemetryDTO>builder()
                    .timestamp(System.currentTimeMillis())
                    .data(TelemetryDTO.builder().sn(from).build())
                    .build();
            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
            Collection<ConcurrentWebSocketSession> webSessions = webSocketManageService
                    .getValueWithWorkspaceAndUserType(
                            workspaceId, UserTypeEnum.WEB.getVal());
                            device.getWorkspaceId(), UserTypeEnum.WEB.getVal());
            tsaService.handleOSD(receiver, from, workspaceId, hostNode, webSessions, wsMessage);
            tsaService.handleOSD(receiver, device, webSessions, wsMessage);
        } catch (IOException e) {
            e.printStackTrace();
@@ -451,7 +530,7 @@
     * @param entity
     * @return
     */
    private Integer saveDevice(DeviceEntity entity) {
    private Optional<DeviceEntity> saveDevice(DeviceEntity entity) {
        DeviceEntity deviceEntity = mapper.selectOne(
                new LambdaQueryWrapper<DeviceEntity>()
                        .eq(DeviceEntity::getDeviceSn, entity.getDeviceSn()));
@@ -459,9 +538,9 @@
        if (deviceEntity != null) {
            entity.setId(deviceEntity.getId());
            mapper.updateById(entity);
            return deviceEntity.getId();
            return Optional.of(deviceEntity);
        }
        return mapper.insert(entity) > 0 ? entity.getId() : 0;
        return mapper.insert(entity) > 0 ? Optional.of(entity) : Optional.empty();
    }
    /**
@@ -477,19 +556,20 @@
        // Query the model information of this gateway device.
        Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                .getOneDictionaryInfoByDomainTypeSubType(gateway.getDomain(),
                        gateway.getType(), gateway.getSubType());
                .getOneDictionaryInfoByTypeSubType(gateway.getType(), gateway.getSubType());
        dictionaryOpt.ifPresent(entity ->
                builder.deviceName(entity.getDeviceName())
                        .nickname(entity.getDeviceName())
                        .deviceDesc(entity.getDeviceDesc()));
        return builder
                .deviceSn(gateway.getSn())
                .domain(gateway.getDomain())
                .subType(gateway.getSubType())
                .deviceType(gateway.getType())
                .version(gateway.getVersion())
                .domain(gateway.getDomain() != null ?
                        gateway.getDomain() : DeviceDomainEnum.GATEWAY.getVal())
                .build();
    }
@@ -506,48 +586,330 @@
        // Query the model information of this drone device.
        Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.SUB_DEVICE.getVal(),
                        device.getType(), device.getSubType());
                .getOneDictionaryInfoByTypeSubType(device.getType(), device.getSubType());
        dictionaryOpt.ifPresent(dictionary ->
                builder.deviceName(dictionary.getDeviceName())
                        .nickname(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())
                .domain(device.getDomain() != null ?
                        device.getDomain() : DeviceDomainEnum.SUB_DEVICE.getVal())
                .build();
    }
    /**
     * Convert database entity objects into device data transfer object.
     * Convert database entity object 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();
        if (entity == null) {
            return null;
        }
        return builder.build();
        return DeviceDTO.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())
                .boundStatus(entity.getBoundStatus())
                .loginTime(entity.getLoginTime() != null ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getLoginTime()), ZoneId.systemDefault())
                        : null)
                .boundTime(entity.getBoundTime() != null ?
                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getBoundTime()), ZoneId.systemDefault())
                        : null)
                .nickname(entity.getNickname())
                .firmwareVersion(entity.getFirmwareVersion())
                .workspaceName(entity.getWorkspaceId() != null ?
                        workspaceService.getWorkspaceByWorkspaceId(entity.getWorkspaceId())
                        .map(WorkspaceDTO::getWorkspaceName).orElse("") : "")
                .build();
    }
    @Override
    public Boolean updateDevice(DeviceDTO deviceDTO) {
        int update = mapper.update(this.deviceDTO2Entity(deviceDTO),
                new LambdaUpdateWrapper<DeviceEntity>().eq(DeviceEntity::getDeviceSn, deviceDTO.getDeviceSn()));
        return update > 0;
    }
    @Override
    public Boolean bindDevice(DeviceDTO device) {
        device.setBoundStatus(true);
        device.setBoundTime(LocalDateTime.now());
        boolean isUpd = this.saveDevice(this.deviceDTO2Entity(device)).isPresent();
        if (DeviceDomainEnum.DOCK.getDesc().equals(device.getDomain())) {
            return isUpd;
        }
        if (!isUpd) {
            return false;
        }
        String key = RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn();
        DeviceDTO redisDevice = (DeviceDTO)redisOps.get(key);
        redisDevice.setWorkspaceId(device.getWorkspaceId());
        redisOps.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
        if (DeviceDomainEnum.GATEWAY.getDesc().equals(redisDevice.getDomain())) {
            this.pushDeviceOnlineTopo(webSocketManageService.getValueWithWorkspace(device.getWorkspaceId()),
                    device.getDeviceSn(), device.getDeviceSn());
        }
        if (DeviceDomainEnum.SUB_DEVICE.getDesc().equals(redisDevice.getDomain())) {
            DeviceDTO subDevice = this.getDevicesByParams(DeviceQueryParam.builder()
                    .childSn(device.getChildDeviceSn())
                    .build()).get(0);
            this.pushDeviceOnlineTopo(webSocketManageService.getValueWithWorkspace(device.getWorkspaceId()),
                    device.getDeviceSn(), subDevice.getDeviceSn());
        }
        this.subscribeTopicOnline(device.getDeviceSn());
        return true;
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_AIRPORT_BIND_STATUS, outputChannel = ChannelName.OUTBOUND)
    public void bindStatus(CommonTopicReceiver receiver, MessageHeaders headers) {
        List<Map<String, String>> data = ((Map<String, List<Map<String, String>>>) receiver.getData()).get(MapKeyConst.DEVICES);
        String dockSn = data.get(0).get(MapKeyConst.SN);
        String droneSn = data.size() > 1 ? data.get(1).get(MapKeyConst.SN) : "null";
        Optional<DeviceDTO> dockOpt = this.getDeviceBySn(dockSn);
        Optional<DeviceDTO> droneOpt = this.getDeviceBySn(droneSn);
        List<BindStatusReceiver> bindStatusResult = new ArrayList<>();
        bindStatusResult.add(dockOpt.isPresent() ? this.dto2BindStatus(dockOpt.get()) :
                BindStatusReceiver.builder().sn(dockSn).isDeviceBindOrganization(false).build());
        if (data.size() > 1) {
            bindStatusResult.add(droneOpt.isPresent() ? this.dto2BindStatus(droneOpt.get()) :
                    BindStatusReceiver.builder().sn(droneSn).isDeviceBindOrganization(false).build());
        }
        messageSender.publish(headers.get(MqttHeaders.RECEIVED_TOPIC) + _REPLY_SUF,
                CommonTopicResponse.builder()
                        .tid(receiver.getTid())
                        .bid(receiver.getBid())
                        .timestamp(System.currentTimeMillis())
                        .method(RequestsMethodEnum.AIRPORT_BIND_STATUS.getMethod())
                        .data(RequestsReply.success(Map.of(MapKeyConst.BIND_STATUS, bindStatusResult)))
                        .build());
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND)
    public void bindDevice(CommonTopicReceiver receiver, MessageHeaders headers) {
        Map<String, List<BindDeviceReceiver>> data = objectMapper.convertValue(receiver.getData(),
                new TypeReference<Map<String, List<BindDeviceReceiver>>>() {});
        List<BindDeviceReceiver> devices = data.get(MapKeyConst.BIND_DEVICES);
        BindDeviceReceiver dock = null;
        BindDeviceReceiver drone = null;
        for (BindDeviceReceiver device : devices) {
            int val = Integer.parseInt(device.getDeviceModelKey().split("-")[0]);
            if (val == DeviceDomainEnum.DOCK.getVal()) {
                dock = device;
            }
            if (val == DeviceDomainEnum.SUB_DEVICE.getVal()) {
                drone = device;
            }
        }
        assert dock != null;
        Optional<DeviceEntity> dockEntityOpt = this.bindDevice2Entity(dock);
        Optional<DeviceEntity> droneEntityOpt = this.bindDevice2Entity(drone);
        List<ErrorInfoReply> bindResult = new ArrayList<>();
        droneEntityOpt.ifPresent(droneEntity -> {
            dockEntityOpt.get().setChildSn(droneEntity.getDeviceSn());
            Optional<DeviceEntity> deviceEntityOpt = this.saveDevice(droneEntity);
            bindResult.add(
                    deviceEntityOpt.isPresent() ?
                        ErrorInfoReply.success(droneEntity.getDeviceSn()) :
                        new ErrorInfoReply(droneEntity.getDeviceSn(),
                                CommonErrorEnum.DEVICE_BINDING_FAILED.getErrorCode())
            );
        });
        Optional<DeviceEntity> dockOpt = this.saveDevice(dockEntityOpt.get());
        bindResult.add(dockOpt.isPresent() ?
                ErrorInfoReply.success(dock.getSn()) :
                    new ErrorInfoReply(dock.getSn(),
                            CommonErrorEnum.DEVICE_BINDING_FAILED.getErrorCode()));
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC) + _REPLY_SUF;
        messageSender.publish(topic,
                CommonTopicResponse.builder()
                        .tid(receiver.getTid())
                        .bid(receiver.getBid())
                        .method(RequestsMethodEnum.AIRPORT_ORGANIZATION_BIND.getMethod())
                        .timestamp(System.currentTimeMillis())
                        .data(RequestsReply.success(Map.of(MapKeyConst.ERR_INFOS, bindResult)))
                        .build());
    }
    @Override
    public PaginationData<DeviceDTO> getBoundDevicesWithDomain(String workspaceId, Long page,
                                                               Long pageSize, String domain) {
        Page<DeviceEntity> pagination = mapper.selectPage(new Page<>(page, pageSize),
                new LambdaQueryWrapper<DeviceEntity>()
                        .eq(DeviceEntity::getDomain, DeviceDomainEnum.getVal(domain))
                        .eq(DeviceEntity::getWorkspaceId, workspaceId)
                        .eq(DeviceEntity::getBoundStatus, true));
        List<DeviceDTO> devicesList = pagination.getRecords().stream().map(this::deviceEntityConvertToDTO)
                .peek(device -> {
                    device.setStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
                    if (StringUtils.hasText(device.getChildDeviceSn())) {
                        Optional<DeviceDTO> childOpt = this.getDeviceBySn(device.getChildDeviceSn());
                        childOpt.ifPresent(child -> {
                            child.setStatus(
                                    redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + child.getDeviceSn()));
                            child.setWorkspaceName(device.getWorkspaceName());
                            device.setChildren(child);
                        });
                    }
                })
                .collect(Collectors.toList());
        return new PaginationData<DeviceDTO>(devicesList, new Pagination(pagination));
    }
    @Override
    public void unbindDevice(String deviceSn) {
        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
        DeviceDTO redisDevice = (DeviceDTO) redisOps.get(key);
        redisDevice.setWorkspaceId("");
        redisOps.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND);
        DeviceDTO device = DeviceDTO.builder()
                .deviceSn(deviceSn)
                .workspaceId("")
                .userId("")
                .boundStatus(false)
                .build();
        this.updateDevice(device);
    }
    @Override
    public Optional<DeviceDTO> getDeviceBySn(String sn) {
        List<DeviceDTO> devicesList = this.getDevicesByParams(DeviceQueryParam.builder().deviceSn(sn).build());
        if (devicesList.isEmpty()) {
            return Optional.empty();
        }
        DeviceDTO device = devicesList.get(0);
        device.setStatus(redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn));
        return Optional.of(device);
    }
    @Override
    public void updateFirmwareVersion(FirmwareVersionReceiver receiver) {
        if (receiver.getDomain() == DeviceDomainEnum.SUB_DEVICE) {
            this.updateDevice(DeviceDTO.builder()
                    .deviceSn(receiver.getSn())
                    .firmwareVersion(receiver.getFirmwareVersion())
                    .build());
            return;
        }
        payloadService.updateFirmwareVersion(receiver);
    }
    /**
     * Convert device data transfer object into database entity object.
     * @param dto
     * @return
     */
    private DeviceEntity deviceDTO2Entity(DeviceDTO dto) {
        DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
        if (dto == null) {
            return builder.build();
        }
        return builder.deviceSn(dto.getDeviceSn())
                .userId(dto.getUserId())
                .nickname(dto.getNickname())
                .workspaceId(dto.getWorkspaceId())
                .boundStatus(dto.getBoundStatus())
                .loginTime(dto.getLoginTime() != null ?
                        dto.getLoginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
                .boundTime(dto.getBoundTime() != null ?
                        dto.getBoundTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null)
                .childSn(dto.getChildDeviceSn())
                .domain(StringUtils.hasText(dto.getDomain()) ? DeviceDomainEnum.getVal(dto.getDomain()) : null)
                .firmwareVersion(dto.getFirmwareVersion())
                .build();
    }
    /**
     * Convert device binding data object into database entity object.
     * @param receiver
     * @return
     */
    private Optional<DeviceEntity> bindDevice2Entity(BindDeviceReceiver receiver) {
        if (receiver == null) {
            return Optional.empty();
        }
        int[] droneKey = Arrays.stream(receiver.getDeviceModelKey().split("-")).mapToInt(Integer::parseInt).toArray();
        Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService.getOneDictionaryInfoByTypeSubType(droneKey[1], droneKey[2]);
        DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
        dictionaryOpt.ifPresent(entity ->
                builder.deviceName(entity.getDeviceName())
                        .nickname(entity.getDeviceName())
                        .deviceDesc(entity.getDeviceDesc()));
        Optional<WorkspaceDTO> workspace = workspaceService.getWorkspaceNameByBindCode(receiver.getDeviceBindingCode());
        DeviceEntity entity = builder
                .workspaceId(workspace.isPresent() ? workspace.get().getWorkspaceId() : receiver.getOrganizationId())
                .domain(droneKey[0])
                .deviceType(droneKey[1])
                .subType(droneKey[2])
                .deviceSn(receiver.getSn())
                .boundStatus(true)
                .loginTime(System.currentTimeMillis())
                .boundTime(System.currentTimeMillis())
                .urlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl())
                .urlNormal(IconUrlEnum.NORMAL_EQUIPMENT.getUrl())
                .build();
        if (StringUtils.hasText(receiver.getDeviceCallsign())) {
            entity.setNickname(receiver.getDeviceCallsign());
        }
        return Optional.of(entity);
    }
    /**
     * Convert device data transfer object into device binding status data object.
     * @param device
     * @return
     */
    private BindStatusReceiver dto2BindStatus(DeviceDTO device) {
        if (device == null) {
            return null;
        }
        return BindStatusReceiver.builder()
                .sn(device.getDeviceSn())
                .deviceCallsign(device.getNickname())
                .isDeviceBindOrganization(device.getBoundStatus())
                .organizationId(device.getWorkspaceId())
                .organizationName(device.getWorkspaceName())
                .build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java
New file
@@ -0,0 +1,50 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
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.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.TelemetryDTO;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.receiver.OsdDockReceiver;
import com.dji.sample.manage.model.receiver.OsdDockTransmissionReceiver;
import org.springframework.stereotype.Service;
import java.util.Collection;
/**
 * @author sean
 * @version 1.0
 * @date 2022/5/11
 */
@Service
public class DockOSDServiceImpl extends AbstractTSAService {
    public DockOSDServiceImpl() {
        super(null);
    }
    @Override
    public void pushTelemetryData(Collection<ConcurrentWebSocketSession> sessions,
                                  CustomWebSocketMessage<TelemetryDTO> message, Object Object) {
    }
    @Override
    public void handleOSD(CommonTopicReceiver receiver, DeviceDTO device,
                          Collection<ConcurrentWebSocketSession> webSessions,
                          CustomWebSocketMessage<TelemetryDTO> wsMessage) {
        if (DeviceDomainEnum.DOCK.getDesc().equals(device.getDomain())) {
            wsMessage.setBizCode(BizCodeEnum.DOCK_OSD.getCode());
            OsdDockReceiver data = mapper.convertValue(receiver.getData(), OsdDockReceiver.class);
            wsMessage.getData().setHost(data);
            if (data.getSubDevice() == null) {
                OsdDockTransmissionReceiver transmission = mapper.convertValue(receiver.getData(), OsdDockTransmissionReceiver.class);
                wsMessage.getData().setHost(transmission);
            }
            sendMessageService.sendBatch(webSessions, wsMessage);
        }
    }
}
src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java
@@ -1,23 +1,18 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
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.DeviceDTO;
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;
/**
@@ -49,26 +44,21 @@
    }
    @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);
    public void handleOSD(CommonTopicReceiver receiver, DeviceDTO device,
                          Collection<ConcurrentWebSocketSession> webSessions,
                          CustomWebSocketMessage<TelemetryDTO> wsMessage) {
        if (DeviceDomainEnum.GATEWAY.getDesc().equals(device.getDomain())) {
            wsMessage.setBizCode(BizCodeEnum.GATEWAY_OSD.getCode());
            OsdGatewayReceiver data = mapper.treeToValue(hostNode, OsdGatewayReceiver.class);
            wsMessage.setData(data);
            OsdGatewayReceiver data = mapper.convertValue(receiver.getData(), OsdGatewayReceiver.class);
            wsMessage.getData().setHost(data);
            this.sendMessageService.sendBatch(webSessions, wsMessage);
            this.pushTelemetryData(workspaceId, data, sn);
            this.pushTelemetryData(device.getWorkspaceId(), data, device.getDeviceSn());
            return;
        }
        tsaService.handleOSD(receiver, sn, workspaceId, hostNode, webSessions, wsMessage);
        tsaService.handleOSD(receiver, device, webSessions, wsMessage);
    }
}
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
@@ -2,18 +2,19 @@
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.model.ServiceReply;
import com.dji.sample.component.mqtt.model.ServicesMethodEnum;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.manage.model.Chan;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
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.model.receiver.LiveCapacityReceiver;
import com.dji.sample.manage.service.ICapacityCameraService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.manage.service.ILiveStreamService;
@@ -24,9 +25,12 @@
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static com.dji.sample.component.mqtt.model.TopicConst.*;
@@ -51,32 +55,37 @@
    @Autowired
    private IMessageSenderService messageSender;
    @Autowired
    private RedisOpsUtils redisOps;
    @Override
    public List<CapacityDeviceDTO> getLiveCapacity(String workspaceId) {
        // Query all drone data in this workspace.
        // Query all devices in this workspace.
        List<DeviceDTO> devicesList = deviceService.getDevicesByParams(
                DeviceQueryParam.builder()
                        .workspaceId(workspaceId)
                        .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                        .domains(List.of(DeviceDomainEnum.SUB_DEVICE.getVal(), DeviceDomainEnum.DOCK.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;
        return devicesList.stream()
                .filter(device -> redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()))
                .map(device -> CapacityDeviceDTO.builder()
                        .name(device.getDeviceName())
                        .sn(device.getDeviceSn())
                        .camerasList(capacityCameraService.getCapacityCameraByDeviceSn(device.getDeviceSn()))
                        .build())
                .collect(Collectors.toList());
    }
    @Override
    public Boolean saveLiveCapacity(CapacityDeviceReceiver capacityDeviceReceiver) {
        return capacityCameraService.saveCapacityCameraReceiverList(
                capacityDeviceReceiver.getCameraList(),
                capacityDeviceReceiver.getSn());
    public void saveLiveCapacity(LiveCapacityReceiver liveCapacityReceiver) {
        for (CapacityDeviceReceiver capacityDeviceReceiver : liveCapacityReceiver.getDeviceList()) {
            capacityCameraService.saveCapacityCameraReceiverList(
                    capacityDeviceReceiver.getCameraList(),
                    capacityDeviceReceiver.getSn());
        }
    }
    @Override
@@ -87,11 +96,11 @@
            return responseResult;
        }
        List<DeviceDTO> data = (List<DeviceDTO>)responseResult.getData();
        DeviceDTO data = (DeviceDTO)responseResult.getData();
        // target topic
        String respTopic = THING_MODEL_PRE + PRODUCT +
                data.get(0).getDeviceSn() + SERVICES_SUF;
        Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveStart(respTopic, liveParam);
                data.getDeviceSn() + SERVICES_SUF;
        Optional<ServiceReply> receiveReplyOpt = this.publishLiveStart(respTopic, liveParam);
        if (receiveReplyOpt.isEmpty()) {
            return ResponseResult.error(LiveErrorEnum.NO_REPLY);
@@ -119,7 +128,7 @@
                        .toString());
                break;
            case RTSP:
                String url = receiveReplyOpt.get().getInfo();
                String url = receiveReplyOpt.get().getInfo().toString();
                this.resolveUrlUser(url, live);
                break;
            case UNKNOWN:
@@ -131,14 +140,14 @@
    @Override
    public ResponseResult liveStop(String videoId) {
        ResponseResult<List<DeviceDTO>> responseResult = this.checkBeforeLive(videoId);
        ResponseResult<DeviceDTO> responseResult = this.checkBeforeLive(videoId);
        if (responseResult.getCode() != 0) {
            return responseResult;
        }
        String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().get(0).getDeviceSn() + SERVICES_SUF;
        String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().getDeviceSn() + SERVICES_SUF;
        Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveStop(respTopic, videoId);
        Optional<ServiceReply> receiveReplyOpt = this.publishLiveStop(respTopic, videoId);
        if (receiveReplyOpt.isEmpty()) {
            return ResponseResult.error(LiveErrorEnum.NO_REPLY);
        }
@@ -156,15 +165,15 @@
            return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS);
        }
        ResponseResult<List<DeviceDTO>> responseResult = this.checkBeforeLive(liveParam.getVideoId());
        ResponseResult<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;
        String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().getDeviceSn() + SERVICES_SUF;
        Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveSetQuality(respTopic, liveParam);
        Optional<ServiceReply> receiveReplyOpt = this.publishLiveSetQuality(respTopic, liveParam);
        if (receiveReplyOpt.isEmpty()) {
            return ResponseResult.error(LiveErrorEnum.NO_REPLY);
        }
@@ -180,22 +189,31 @@
     * @param videoId
     * @return
     */
    private ResponseResult checkBeforeLive(String videoId) {
    private ResponseResult<DeviceDTO> 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);
        }
        Optional<DeviceDTO> deviceOpt = deviceService.getDeviceBySn(videoIdArr[0]);
        // Check if the gateway device connected to this drone exists
        if (deviceOpt.isEmpty()) {
            return ResponseResult.error(LiveErrorEnum.NO_AIRCRAFT);
        }
        if (deviceOpt.get().getDomain().equals(DeviceDomainEnum.DOCK.getDesc())) {
            return ResponseResult.success(deviceOpt.get());
        }
        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);
        return ResponseResult.success(gatewayList.get(0));
    }
    /**
@@ -250,14 +268,14 @@
     * @param liveParam
     * @return
     */
    private Optional<ServiceReplyReceiver> publishLiveStart(String topic, LiveTypeDTO liveParam) {
    private Optional<ServiceReply> 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());
        response.setMethod(ServicesMethodEnum.LIVE_START_PUSH.getMethod());
        return this.publishLive(topic, response);
        return messageSender.publishWithReply(topic, response);
    }
    /**
@@ -266,17 +284,17 @@
     * @param liveParam
     * @return
     */
    private Optional<ServiceReplyReceiver> publishLiveSetQuality(String respTopic, LiveTypeDTO liveParam) {
    private Optional<ServiceReply> 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.setMethod(ServicesMethodEnum.LIVE_SET_QUALITY.getMethod());
        response.setData(data);
        return this.publishLive(respTopic, response);
        return messageSender.publishWithReply(respTopic, response);
    }
    /**
@@ -285,42 +303,15 @@
     * @param videoId
     * @return
     */
    private Optional<ServiceReplyReceiver> publishLiveStop(String topic, String videoId) {
    private Optional<ServiceReply> 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());
        response.setMethod(ServicesMethodEnum.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();
        return messageSender.publishWithReply(topic, response);
    }
}
src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java
@@ -31,7 +31,7 @@
        List<DeviceDTO> gatewayList = deviceService.getDevicesByParams(
                DeviceQueryParam.builder()
                        .workspaceId(workspaceId)
                        .domain(DeviceDomainEnum.GATEWAY.getVal())
                        .domains(List.of(DeviceDomainEnum.GATEWAY.getVal()))
                        .build());
        List<TopologyDTO> topologyList = new ArrayList<>();
src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java
@@ -1,15 +1,22 @@
package com.dji.sample.manage.service.impl;
import com.auth0.jwt.JWT;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.Pagination;
import com.dji.sample.common.model.PaginationData;
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.UserListDTO;
import com.dji.sample.manage.model.dto.WorkspaceDTO;
import com.dji.sample.manage.model.entity.UserEntity;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.service.IUserService;
import com.dji.sample.manage.service.IWorkspaceService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -18,7 +25,12 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Transactional
@@ -51,7 +63,7 @@
    }
    @Override
    public ResponseResult userLogin(String username, String password) {
    public ResponseResult userLogin(String username, String password, Integer flag) {
        // check user
        UserEntity userEntity = this.getUserByUsername(username);
        if (userEntity == null) {
@@ -60,6 +72,9 @@
                    .message("invalid username")
                    .build();
        }
        if (flag.intValue() != userEntity.getUserType().intValue()) {
            return ResponseResult.error("The account type does not match.");
        }
        if (!password.equals(userEntity.getPassword())) {
            return ResponseResult.builder()
                    .code(HttpStatus.UNAUTHORIZED.value())
@@ -67,7 +82,7 @@
                    .build();
        }
        Optional<WorkspaceDTO> workspaceOpt = workspaceService.getWorkspaceById(userEntity.getWorkspaceId());
        Optional<WorkspaceDTO> workspaceOpt = workspaceService.getWorkspaceByWorkspaceId(userEntity.getWorkspaceId());
        if (workspaceOpt.isEmpty()) {
            return ResponseResult.builder()
                    .code(HttpStatus.UNAUTHORIZED.value())
@@ -109,6 +124,60 @@
        return Optional.of(user);
    }
    @Override
    public PaginationData<UserListDTO> getUsersByWorkspaceId(long page, long pageSize, String workspaceId) {
        Page<UserEntity> userEntityPage = mapper.selectPage(
                new Page<>(page, pageSize),
                new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getWorkspaceId, workspaceId));
        List<UserListDTO> usersList = userEntityPage.getRecords()
                .stream()
                .map(this::entity2UserListDTO)
                .collect(Collectors.toList());
        return new PaginationData<>(usersList, new Pagination(userEntityPage));
    }
    @Override
    public Boolean updateUser(String workspaceId, String userId, UserListDTO user) {
        UserEntity userEntity = mapper.selectOne(
                new LambdaQueryWrapper<UserEntity>()
                        .eq(UserEntity::getUserId, userId)
                        .eq(UserEntity::getWorkspaceId, workspaceId));
        if (userEntity == null) {
            return false;
        }
        userEntity.setMqttUsername(user.getMqttUsername());
        userEntity.setMqttPassword(user.getMqttPassword());
        userEntity.setUpdateTime(System.currentTimeMillis());
        int id = mapper.update(userEntity, new LambdaUpdateWrapper<UserEntity>()
                .eq(UserEntity::getUserId, userId)
                .eq(UserEntity::getWorkspaceId, workspaceId));
        return id > 0;
    }
    /**
     * Convert database entity objects into user data transfer object.
     * @param entity
     * @return
     */
    private UserListDTO entity2UserListDTO(UserEntity entity) {
        UserListDTO.UserListDTOBuilder builder = UserListDTO.builder();
        if (entity != null) {
            builder.userId(entity.getUserId())
                    .username(entity.getUsername())
                    .mqttUsername(entity.getMqttUsername())
                    .mqttPassword(entity.getMqttPassword())
                    .userType(UserTypeEnum.find(entity.getUserType()).getDesc())
                    .createTime(LocalDateTime.ofInstant(
                            Instant.ofEpochMilli(entity.getCreateTime()), ZoneId.systemDefault()));
            Optional<WorkspaceDTO> workspaceOpt = workspaceService.getWorkspaceByWorkspaceId(entity.getWorkspaceId());
            workspaceOpt.ifPresent(workspace -> builder.workspaceName(workspace.getWorkspaceName()));
        }
        return builder.build();
    }
    /**
     * Query a user by username.
     * @param username
@@ -119,6 +188,11 @@
                .eq("username", username));
    }
    /**
     * Convert database entity objects into user data transfer object.
     * @param entity
     * @return
     */
    private UserDTO entityConvertToDTO(UserEntity entity) {
        if (entity == null) {
            return new UserDTO();
src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java
@@ -1,14 +1,24 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.common.error.CommonErrorEnum;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
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.model.receiver.OrganizationGetReceiver;
import com.dji.sample.manage.service.IWorkspaceService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.Optional;
@Service
@@ -18,10 +28,11 @@
    @Autowired
    private IWorkspaceMapper mapper;
    @Override
    public Optional<WorkspaceDTO> getWorkspaceById(int id) {
        return Optional.ofNullable(entityConvertToDto(mapper.selectById(id)));
    }
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private IMessageSenderService messageSenderService;
    @Override
    public Optional<WorkspaceDTO> getWorkspaceByWorkspaceId(String workspaceId) {
@@ -31,22 +42,57 @@
                                .eq(WorkspaceEntity::getWorkspaceId, workspaceId))));
    }
    @Override
    public Optional<WorkspaceDTO> getWorkspaceNameByBindCode(String bindCode) {
        return Optional.ofNullable(entityConvertToDto(
                mapper.selectOne(new LambdaQueryWrapper<WorkspaceEntity>().eq(WorkspaceEntity::getBindCode, bindCode))));
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_GET, outputChannel = ChannelName.OUTBOUND)
    public void replyOrganizationGet(CommonTopicReceiver receiver, MessageHeaders headers) {
        OrganizationGetReceiver organizationGet = objectMapper.convertValue(receiver.getData(), OrganizationGetReceiver.class);
        CommonTopicResponse.CommonTopicResponseBuilder<RequestsReply> builder = CommonTopicResponse.<RequestsReply>builder()
                .tid(receiver.getTid())
                .bid(receiver.getBid())
                .method(RequestsMethodEnum.AIRPORT_ORGANIZATION_GET.getMethod())
                .timestamp(System.currentTimeMillis());
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString() + TopicConst._REPLY_SUF;
        if (!StringUtils.hasText(organizationGet.getDeviceBindingCode())) {
            builder.data(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT));
            messageSenderService.publish(topic, builder.build());
            return;
        }
        Optional<WorkspaceDTO> workspace = this.getWorkspaceNameByBindCode(organizationGet.getDeviceBindingCode());
        if (workspace.isEmpty()) {
            builder.data(RequestsReply.error(CommonErrorEnum.GET_ORGANIZATION_FAILED));
            messageSenderService.publish(topic, builder.build());
            return;
        }
        builder.data(RequestsReply.success(Map.of(MapKeyConst.ORGANIZATION_NAME, workspace.get().getWorkspaceName())));
        messageSenderService.publish(topic, builder.build());
    }
    /**
     * 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 null;
        }
        return builder
        return WorkspaceDTO.builder()
                .id(entity.getId())
                .workspaceId(entity.getWorkspaceId())
                .platformName(entity.getPlatformName())
                .workspaceDesc(entity.getWorkspaceDesc())
                .workspaceName(entity.getWorkspaceName())
                .bindCode(entity.getBindCode())
                .build();
    }
}
src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java
@@ -4,8 +4,8 @@
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.component.websocket.service.IWebSocketManageService;
import com.dji.sample.map.model.dto.*;
import com.dji.sample.map.service.IWorkspaceElementService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +33,9 @@
    @Autowired
    private ISendMessageService sendMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    /**
     * In the first connection, pilot will send out this http request to obtain the group element list.
@@ -78,7 +81,7 @@
        elementService.getElementByElementId(elementCreate.getId())
                .ifPresent(groupElement ->
                        sendMessageService.sendBatch(
                                WebSocketManager.getValueWithWorkspace(workspaceId),
                                webSocketManageService.getValueWithWorkspace(workspaceId),
                                CustomWebSocketMessage.<GroupElementDTO>builder()
                                        .timestamp(System.currentTimeMillis())
                                        .bizCode(BizCodeEnum.MAP_ELEMENT_CREATE.getCode())
@@ -114,7 +117,7 @@
        elementService.getElementByElementId(elementId)
                .ifPresent(groupElement ->
                        sendMessageService.sendBatch(
                                WebSocketManager.getValueWithWorkspace(workspaceId),
                                webSocketManageService.getValueWithWorkspace(workspaceId),
                                CustomWebSocketMessage.<GroupElementDTO>builder()
                                        .timestamp(System.currentTimeMillis())
                                        .bizCode(BizCodeEnum.MAP_ELEMENT_UPDATE.getCode())
@@ -142,7 +145,7 @@
        if (ResponseResult.CODE_SUCCESS == response.getCode()) {
            elementOpt.ifPresent(element ->
                    sendMessageService.sendBatch(
                    WebSocketManager.getValueWithWorkspace(workspaceId),
                    webSocketManageService.getValueWithWorkspace(workspaceId),
                            CustomWebSocketMessage.<WebSocketElementDelDTO>builder()
                                    .timestamp(System.currentTimeMillis())
                                    .bizCode(BizCodeEnum.MAP_ELEMENT_DELETE.getCode())
@@ -171,7 +174,7 @@
        if (ResponseResult.CODE_SUCCESS == response.getCode()) {
            sendMessageService.sendBatch(
                    WebSocketManager.getValueWithWorkspace(workspaceId),
                    webSocketManageService.getValueWithWorkspace(workspaceId),
                    CustomWebSocketMessage.builder()
                            .timestamp(System.currentTimeMillis())
                            .bizCode(BizCodeEnum.MAP_GROUP_REFRESH.getCode())
src/main/java/com/dji/sample/media/controller/FileController.java
@@ -1,15 +1,15 @@
package com.dji.sample.media.controller;
import com.dji.sample.common.model.PaginationData;
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 org.springframework.web.bind.annotation.*;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URL;
/**
 * @author sean
@@ -29,8 +29,29 @@
     * @return
     */
    @GetMapping("/{workspace_id}/files")
    public ResponseResult<List<MediaFileDTO>> getFilesList(@PathVariable(name = "workspace_id") String workspaceId) {
        List<MediaFileDTO> filesList = fileService.getAllFilesByWorkspaceId(workspaceId);
    public ResponseResult<PaginationData<MediaFileDTO>> getFilesList(@RequestParam(defaultValue = "1") Long page,
                               @RequestParam(name = "page_size", defaultValue = "10") Long pageSize,
                               @PathVariable(name = "workspace_id") String workspaceId) {
        PaginationData<MediaFileDTO> filesList = fileService.getJobsPaginationByWorkspaceId(workspaceId, page, pageSize);
        return ResponseResult.success(filesList);
    }
    /**
     * Query the download address of the file according to the media file fingerprint,
     * and redirect to this address directly for download.
     * @param workspaceId
     * @param fingerprint
     * @param response
     */
    @GetMapping("/{workspace_id}/file/{fingerprint}/url")
    public void getFileUrl(@PathVariable(name = "workspace_id") String workspaceId,
                           @PathVariable String fingerprint, HttpServletResponse response) {
        try {
            URL url = fileService.getObjectUrl(workspaceId, fingerprint);
            response.sendRedirect(url.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
src/main/java/com/dji/sample/media/controller/MediaController.java
@@ -1,8 +1,10 @@
package com.dji.sample.media.controller;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.MapKeyConst;
import com.dji.sample.media.model.FileUploadDTO;
import com.dji.sample.media.service.IMediaService;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -10,7 +12,6 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
 * @author sean
@@ -36,7 +37,7 @@
        boolean isExist = mediaService.fastUpload(workspaceId, file.getFingerprint());
        return isExist ? ResponseResult.success() : ResponseResult.error(file.getFingerprint() + "already exists.");
        return isExist ? ResponseResult.success() : ResponseResult.error(file.getFingerprint() + "don't exist.");
    }
    /**
@@ -56,18 +57,17 @@
    /**
     * Query the files that already exist in this workspace based on the workspace id and the collection of tiny fingerprints.
     * @param workspaceId
     * @param tinyFingerprints
     * @param tinyFingerprints  There is only one tiny_fingerprint parameter in the body.
     *                          But it is not recommended to use Map to receive the parameter.
     * @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)));
    @PostMapping("/{workspace_id}/files/tiny-fingerprints")
    public ResponseResult<Map<String, List<String>>> uploadCallback(
                                @PathVariable(name = "workspace_id") String workspaceId,
                               @RequestBody Map<String, List<String>> tinyFingerprints) throws JsonProcessingException {
        List<String> existingList = mediaService.getExistTinyFingerprints(workspaceId, tinyFingerprints.get(MapKeyConst.TINY_FINGERPRINTS));
        return ResponseResult.success(new ConcurrentHashMap<>(Map.of(MapKeyConst.TINY_FINGERPRINTS, existingList)));
    }
}
src/main/java/com/dji/sample/media/model/CredentialsDTO.java
@@ -2,7 +2,9 @@
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
import io.minio.credentials.Credentials;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
@@ -11,6 +13,8 @@
 * @date 2021/12/7
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CredentialsDTO {
    private String accessKeyId;
@@ -35,6 +39,10 @@
        this.expire = Math.toIntExact(expire);
    }
    public CredentialsDTO() {
    public CredentialsDTO(com.amazonaws.services.securitytoken.model.Credentials credentials) {
        this.accessKeyId = credentials.getAccessKeyId();
        this.accessKeySecret = credentials.getSecretAccessKey();
        this.securityToken = credentials.getSessionToken();
        this.expire = Math.toIntExact((credentials.getExpiration().getTime() - System.currentTimeMillis()) / 1000);
    }
}
src/main/java/com/dji/sample/media/model/FileExtensionDTO.java
@@ -23,4 +23,6 @@
    private String sn;
    private String flightId;
}
src/main/java/com/dji/sample/media/model/FileUploadCallback.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.media.model;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/9
 */
@Data
public class FileUploadCallback {
    private Integer result;
    private Integer progress;
    private FileUploadDTO file;
}
src/main/java/com/dji/sample/media/model/MediaFileDTO.java
@@ -5,6 +5,8 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
 * @author sean
 * @version 0.2
@@ -31,4 +33,8 @@
    private String payload;
    private String tinnyFingerprint;
    private String fingerprint;
    private LocalDateTime createTime;
}
src/main/java/com/dji/sample/media/model/MediaFileEntity.java
@@ -53,6 +53,9 @@
    @TableField("payload")
    private String payload;
    @TableField("job_id")
    private String jobId;
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Long createTime;
src/main/java/com/dji/sample/media/service/IFileService.java
@@ -1,8 +1,10 @@
package com.dji.sample.media.service;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.media.model.FileUploadDTO;
import com.dji.sample.media.model.MediaFileDTO;
import java.net.URL;
import java.util.List;
/**
@@ -34,4 +36,21 @@
     * @return
     */
    List<MediaFileDTO> getAllFilesByWorkspaceId(String workspaceId);
    /**
     * Paginate through all media files in this workspace.
     * @param workspaceId
     * @param page
     * @param pageSize
     * @return
     */
    PaginationData<MediaFileDTO> getJobsPaginationByWorkspaceId(String workspaceId, long page, long pageSize);
    /**
     * Get the download address of the file.
     * @param workspaceId
     * @param fingerprint
     * @return
     */
    URL getObjectUrl(String workspaceId, String fingerprint);
}
src/main/java/com/dji/sample/media/service/IMediaService.java
@@ -1,5 +1,6 @@
package com.dji.sample.media.service;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.media.model.FileUploadDTO;
import java.util.List;
@@ -33,4 +34,19 @@
     * @return
     */
    List<String> getAllTinyFingerprintsByWorkspaceId(String workspaceId);
    /**
     * Query the fingerprints that already exist in it based on the incoming tiny fingerprints data.
     * @param workspaceId
     * @param tinyFingerprints
     * @return
     */
    List<String> getExistTinyFingerprints(String workspaceId, List<String> tinyFingerprints);
    /**
     * Handle media files messages reported by dock.
     * @param receiver
     * @return
     */
    void handleFileUploadCallBack(CommonTopicReceiver receiver);
}
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java
@@ -1,6 +1,11 @@
package com.dji.sample.media.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.OssConfiguration;
import com.dji.sample.component.oss.service.impl.OssServiceContext;
import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import com.dji.sample.media.dao.IFileMapper;
@@ -12,6 +17,10 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -26,19 +35,28 @@
@Transactional
public class FileServiceImpl implements IFileService {
    @Autowired
    private IFileMapper mapper;
    @Autowired
    private IDeviceDictionaryService deviceDictionaryService;
    @Override
    public Boolean checkExist(String workspaceId, String fingerprint) {
    @Autowired
    private OssServiceContext ossService;
    @Autowired
    private OssConfiguration configuration;
    private Optional<MediaFileEntity> getMediaByFingerprint(String workspaceId, String fingerprint) {
        MediaFileEntity fileEntity = mapper.selectOne(new LambdaQueryWrapper<MediaFileEntity>()
                .eq(MediaFileEntity::getWorkspaceId, workspaceId)
                .eq(MediaFileEntity::getFingerprint, fingerprint));
        return fileEntity != null;
        return Optional.ofNullable(fileEntity);
    }
    @Override
    public Boolean checkExist(String workspaceId, String fingerprint) {
        return this.getMediaByFingerprint(workspaceId, fingerprint).isPresent();
    }
    @Override
@@ -57,6 +75,30 @@
                .collect(Collectors.toList());
    }
    @Override
    public PaginationData<MediaFileDTO> getJobsPaginationByWorkspaceId(String workspaceId, long page, long pageSize) {
        Page<MediaFileEntity> pageData = mapper.selectPage(
                new Page<MediaFileEntity>(page, pageSize),
                new LambdaQueryWrapper<MediaFileEntity>()
                        .eq(MediaFileEntity::getWorkspaceId, workspaceId));
        List<MediaFileDTO> records = pageData.getRecords()
                .stream()
                .map(this::entityConvertToDto)
                .collect(Collectors.toList());
        return new PaginationData<MediaFileDTO>(records, new Pagination(pageData));
    }
    @Override
    public URL getObjectUrl(String workspaceId, String fingerprint) {
        Optional<MediaFileEntity> mediaFileOpt = getMediaByFingerprint(workspaceId, fingerprint);
        if (mediaFileOpt.isEmpty()) {
            throw new IllegalArgumentException("{} doesn't exist.");
        }
        return ossService.getObjectUrl(configuration.getBucket(), mediaFileOpt.get().getObjectKey());
    }
    /**
     * Convert the received file object into a database entity object.
     * @param file
@@ -72,6 +114,7 @@
                    .objectKey(file.getObjectKey())
                    .subFileType(file.getSubFileType())
                    .isOriginal(file.getExt().getIsOriginal())
                    .jobId(file.getExt().getFlightId())
                    .drone(file.getExt().getSn())
                    .tinnyFingerprint(file.getExt().getTinnyFingerprint());
@@ -81,7 +124,7 @@
                    .mapToInt(Integer::intValue)
                    .toArray();
            Optional<DeviceDictionaryDTO> payloadDict = deviceDictionaryService
                    .getOneDictionaryInfoByDomainTypeSubType(payloadModel[0], payloadModel[1], payloadModel[2]);
                    .getOneDictionaryInfoByTypeSubType(payloadModel[1], payloadModel[2]);
            payloadDict.ifPresent(payload -> builder.payload(payload.getDeviceName()));
        }
        return builder.build();
@@ -99,9 +142,12 @@
            builder.fileName(entity.getFileName())
                    .filePath(entity.getFilePath())
                    .isOriginal(entity.getIsOriginal())
                    .fingerprint(entity.getFingerprint())
                    .objectKey(entity.getObjectKey())
                    .tinnyFingerprint(entity.getTinnyFingerprint())
                    .payload(entity.getPayload())
                    .createTime(LocalDateTime.ofInstant(
                            Instant.ofEpochMilli(entity.getCreateTime()), ZoneId.systemDefault()))
                    .drone(entity.getDrone());
        }
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java
@@ -1,13 +1,22 @@
package com.dji.sample.media.service.impl;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.media.model.FileUploadCallback;
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 com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.service.IWaylineJobService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -20,6 +29,15 @@
    @Autowired
    private IFileService fileService;
    @Autowired
    private IWaylineJobService waylineJobService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private IMessageSenderService messageSenderService;
    @Override
    public Boolean fastUpload(String workspaceId, String fingerprint) {
@@ -38,4 +56,42 @@
                .map(MediaFileDTO::getTinnyFingerprint)
                .collect(Collectors.toList());
    }
    @Override
    public List<String> getExistTinyFingerprints(String workspaceId, List<String> tinyFingerprints) {
        List<String> tinyFingerprintList = this.getAllTinyFingerprintsByWorkspaceId(workspaceId);
        return tinyFingerprints
                .stream()
                .filter(tinyFingerprintList::contains)
                .collect(Collectors.toList());
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK, outputChannel = ChannelName.OUTBOUND)
    public void handleFileUploadCallBack(CommonTopicReceiver receiver) {
        FileUploadCallback callback = objectMapper.convertValue(receiver.getData(), FileUploadCallback.class);
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + receiver.getGateway()
                + TopicConst.EVENTS_SUF + TopicConst._REPLY_SUF;
        CommonTopicResponse<Object> data = CommonTopicResponse.builder()
                .timestamp(System.currentTimeMillis())
                .method(EventsMethodEnum.FILE_UPLOAD_CALLBACK.getMethod())
                .data(ResponseResult.success())
                .tid(receiver.getTid())
                .bid(receiver.getBid())
                .build();
        if (callback.getResult() == ResponseResult.CODE_SUCCESS) {
            String jobId = callback.getFile().getExt().getFlightId();
            Optional<WaylineJobDTO> jobOpt = waylineJobService.getJobByJobId(jobId);
            if (jobOpt.isPresent()) {
                int id = fileService.saveFile(jobOpt.get().getWorkspaceId(), callback.getFile());
                if (id <= 0) {
                    data.setData(ResponseResult.error());
                }
            }
        }
        messageSenderService.publish(topic, data);
    }
}
src/main/java/com/dji/sample/storage/controller/StorageController.java
@@ -1,13 +1,8 @@
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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -21,24 +16,11 @@
 */
@RestController
@RequestMapping("${url.storage.prefix}${url.storage.version}/workspaces/")
@Slf4j
public class StorageController {
    @Autowired
    private IStorageService storageService;
    @Autowired
    private void setOssService(@Autowired(required = false) AliyunStorageServiceImpl aliyunStorageService,
                              @Autowired(required = false) MinIOStorageServiceImpl minIOStorageService) {
        if (AliyunOSSConfiguration.enable) {
            this.storageService = aliyunStorageService;
            return;
        }
        if (MinIOConfiguration.enable) {
            this.storageService = minIOStorageService;
            return;
        }
        log.error("storageService is null.");
    }
    /**
     * Get temporary credentials for uploading the media and wayline in DJI Pilot.
     * @param workspaceId
@@ -50,4 +32,5 @@
        StsCredentialsDTO stsCredentials = storageService.getSTSCredentials();
        return ResponseResult.success(stsCredentials);
    }
}
src/main/java/com/dji/sample/storage/service/IStorageService.java
@@ -1,6 +1,8 @@
package com.dji.sample.storage.service;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.media.model.StsCredentialsDTO;
import org.springframework.messaging.MessageHeaders;
/**
 * @author sean
@@ -14,4 +16,11 @@
     * @return temporary credentials object
     */
    StsCredentialsDTO getSTSCredentials();
    /**
     * Handles requests from the dock to obtain temporary credentials.
     * @param receiver
     * @param headers
     */
    void replyConfigGet(CommonTopicReceiver receiver, MessageHeaders headers);
}
src/main/java/com/dji/sample/storage/service/impl/StorageServiceImpl.java
New file
@@ -0,0 +1,56 @@
package com.dji.sample.storage.service.impl;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.service.impl.OssServiceContext;
import com.dji.sample.media.model.StsCredentialsDTO;
import com.dji.sample.storage.service.IStorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
/**
 * @author sean
 * @version 0.3
 * @date 2022/3/9
 */
@Service
public class StorageServiceImpl implements IStorageService {
    @Autowired
    private IMessageSenderService messageSender;
    @Autowired
    private OssServiceContext ossService;
    @Autowired
    private OssConfiguration configuration;
    @Override
    public StsCredentialsDTO getSTSCredentials() {
        return StsCredentialsDTO.builder()
                .endpoint(configuration.getEndpoint())
                .bucket(configuration.getBucket())
                .credentials(ossService.getCredentials())
                .provider(configuration.getProvider())
                .objectKeyPrefix(configuration.getObjectDirPrefix())
                .region(configuration.getRegion())
                .build();
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_STORAGE_CONFIG_GET, outputChannel = ChannelName.OUTBOUND)
    public void replyConfigGet(CommonTopicReceiver receiver, MessageHeaders headers) {
        CommonTopicResponse<RequestsReply> response = CommonTopicResponse.<RequestsReply>builder()
                .tid(receiver.getTid())
                .bid(receiver.getBid())
                .data(RequestsReply.success(this.getSTSCredentials()))
                .timestamp(System.currentTimeMillis())
                .method(receiver.getMethod())
                .build();
        messageSender.publish(headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF, response);
    }
}
src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java
@@ -3,10 +3,9 @@
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.model.dto.WaylineFileDTO;
import com.dji.sample.wayline.model.dto.WaylineFileUploadDTO;
import com.dji.sample.wayline.model.param.WaylineQueryParam;
import com.dji.sample.wayline.service.IWaylineFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -15,6 +14,7 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URL;
import java.sql.SQLException;
import java.util.List;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
@@ -70,12 +70,10 @@
    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 {
            URL url = waylineFileService.getObjectUrl(workspaceId, waylineId);
            response.sendRedirect(url.toString());
        } catch (Exception e) {
        } catch (IOException | SQLException e) {
            e.printStackTrace();
        }
    }
@@ -147,4 +145,17 @@
        return ResponseResult.success(existNamesList);
    }
    /**
     * Delete the wayline file in the workspace according to the wayline id.
     * @param workspaceId
     * @param waylineId
     * @return
     */
    @DeleteMapping("/{workspace_id}/waylines/{wayline_id}")
    public ResponseResult deleteWayline(@PathVariable(name = "workspace_id") String workspaceId,
                                        @PathVariable(name = "wayline_id") String waylineId) {
        boolean isDel = waylineFileService.deleteByWaylineId(workspaceId, waylineId);
        return isDel ? ResponseResult.success() : ResponseResult.error("Failed to delete wayline.");
    }
}
src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java
New file
@@ -0,0 +1,74 @@
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.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.param.CreateJobParam;
import com.dji.sample.wayline.service.IWaylineJobService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.sql.SQLException;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
@RequestMapping("${url.wayline.prefix}${url.wayline.version}/workspaces")
@RestController
public class WaylineJobController {
    @Autowired
    private IWaylineJobService waylineJobService;
    /**
     * Create a wayline task for the Dock.
     * @param request
     * @param param
     * @param workspaceId
     * @return
     * @throws SQLException
     */
    @PostMapping("/{workspace_id}/flight-tasks")
    public ResponseResult createJob(HttpServletRequest request, @RequestBody CreateJobParam param,
                                    @PathVariable(name = "workspace_id") String workspaceId) throws SQLException {
        CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
        customClaim.setWorkspaceId(workspaceId);
        boolean isCreate = waylineJobService.createJob(param, customClaim);
        return isCreate ? ResponseResult.success() : ResponseResult.error();
    }
    /**
     * Paginate through all jobs in this workspace.
     * @param page
     * @param pageSize
     * @param workspaceId
     * @return
     */
    @GetMapping("/{workspace_id}/jobs")
    public ResponseResult<PaginationData<WaylineJobDTO>> getJobs(@RequestParam(defaultValue = "1") Long page,
                     @RequestParam(name = "page_size", defaultValue = "10") Long pageSize,
                     @PathVariable(name = "workspace_id") String workspaceId) {
        PaginationData<WaylineJobDTO> data = waylineJobService.getJobsByWorkspaceId(workspaceId, page, pageSize);
        return ResponseResult.success(data);
    }
    /**
     * Issue wayline mission to the dock for execution.
     * @param jobId
     * @param workspaceId
     * @return
     * @throws SQLException
     */
    @PostMapping("/{workspace_id}/jobs/{job_id}")
    public ResponseResult publishJob(@PathVariable(name = "job_id") String jobId,
                                     @PathVariable(name = "workspace_id") String workspaceId) throws SQLException {
        waylineJobService.publishFlightTask(workspaceId, jobId);
        return ResponseResult.success();
    }
}
src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java
@@ -1,7 +1,7 @@
package com.dji.sample.wayline.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.wayline.model.WaylineFileEntity;
import com.dji.sample.wayline.model.entity.WaylineFileEntity;
/**
 * @author sean
src/main/java/com/dji/sample/wayline/dao/IWaylineJobMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.wayline.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.wayline.model.entity.WaylineJobEntity;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
public interface IWaylineJobMapper extends BaseMapper<WaylineJobEntity> {
}
src/main/java/com/dji/sample/wayline/model/dto/FLightTaskProgress.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.wayline.model.dto;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/9
 */
@Data
public class FLightTaskProgress {
    private Integer currentStep;
    private Integer percent;
}
src/main/java/com/dji/sample/wayline/model/dto/FlightTaskCreateDTO.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.wayline.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FlightTaskCreateDTO {
    private String flightId;
    private String type;
    private FlightTaskFileDTO file;
}
src/main/java/com/dji/sample/wayline/model/dto/FlightTaskFileDTO.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.wayline.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FlightTaskFileDTO {
    private String url;
    private String sign;
}
src/main/java/com/dji/sample/wayline/model/dto/FlightTaskProgressExt.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.wayline.model.dto;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/9
 */
@Data
public class FlightTaskProgressExt {
    private Integer currentWaypointIndex;
    private Integer mediaCount;
}
src/main/java/com/dji/sample/wayline/model/dto/FlightTaskProgressReceiver.java
New file
@@ -0,0 +1,19 @@
package com.dji.sample.wayline.model.dto;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/9
 */
@Data
public class FlightTaskProgressReceiver {
    private FlightTaskProgressExt ext;
    private FLightTaskProgress progress;
    private String status;
}
src/main/java/com/dji/sample/wayline/model/dto/WaylineFileDTO.java
New file
@@ -0,0 +1,43 @@
package com.dji.sample.wayline.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.3
 * @date 2021/12/22
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WaylineFileDTO {
    private String name;
    @JsonProperty("id")
    private String waylineId;
    private String droneModelKey;
    private String sign;
    private List<String> payloadModelKeys;
    private Boolean favorited;
    private List<Integer> templateTypes;
    private String objectKey;
    @JsonProperty("user_name")
    private String username;
    private Long updateTime;
}
src/main/java/com/dji/sample/wayline/model/dto/WaylineFileUploadDTO.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.wayline.model.dto;
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;
}
src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java
New file
@@ -0,0 +1,43 @@
package com.dji.sample.wayline.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class WaylineJobDTO {
    private String jobId;
    private String jobName;
    private String fileId;
    private String fileName;
    private String dockSn;
    private String dockName;
    private String workspaceId;
    private String bid;
    private String type;
    private String username;
    private LocalDateTime updateTime;
}
src/main/java/com/dji/sample/wayline/model/entity/WaylineFileEntity.java
New file
@@ -0,0 +1,62 @@
package com.dji.sample.wayline.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.3
 * @date 2021/12/22
 */
@Data
@TableName("wayline_file")
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WaylineFileEntity implements Serializable {
    @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("sign")
    private String sign;
    @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;
}
src/main/java/com/dji/sample/wayline/model/entity/WaylineJobEntity.java
New file
@@ -0,0 +1,56 @@
package com.dji.sample.wayline.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 1.1
 * @date 2022/6/1
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("wayline_job")
public class WaylineJobEntity implements Serializable {
    @TableId(type = IdType.AUTO)
    private Integer id;
    @TableField("job_id")
    private String jobId;
    @TableField("name")
    private String name;
    @TableField("file_id")
    private String fileId;
    @TableField("dock_sn")
    private String dockSn;
    @TableField("workspace_id")
    private String workspaceId;
    @TableField("bid")
    private String bid;
    @TableField("type")
    private String type;
    @TableField("username")
    private String username;
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Long createTime;
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private Long updateTime;
}
src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java
New file
@@ -0,0 +1,22 @@
package com.dji.sample.wayline.model.param;
import lombok.Data;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
@Data
public class CreateJobParam {
    private String name;
    private String fileId;
    private String dockSn;
    private String type;
    private boolean immediate;
}
src/main/java/com/dji/sample/wayline/model/param/WaylineQueryParam.java
New file
@@ -0,0 +1,30 @@
package com.dji.sample.wayline.model.param;
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;
}
src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.wayline.service;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import org.springframework.messaging.MessageHeaders;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/9
 */
public interface IFlightTaskService {
    /**
     * Handle the progress messages of the flight tasks reported by the dock.
     * @param receiver
     */
    void handleProgress(CommonTopicReceiver receiver, MessageHeaders headers);
}
src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java
@@ -1,11 +1,13 @@
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 com.dji.sample.wayline.model.dto.WaylineFileDTO;
import com.dji.sample.wayline.model.param.WaylineQueryParam;
import java.net.URL;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
@@ -28,15 +30,15 @@
     * @param waylineId
     * @return
     */
    WaylineFileDTO getWaylineByWaylineId(String workspaceId, String waylineId);
    Optional<WaylineFileDTO> getWaylineByWaylineId(String workspaceId, String waylineId);
    /**
     * Get the download address of the file object.
     * @param bucket    bucket name
     * @param objectKey object name
     * @param workspaceId
     * @param waylineId
     * @return
     */
    URL getObjectUrl(String bucket, String objectKey);
    URL getObjectUrl(String workspaceId, String waylineId) throws SQLException;
    /**
     * Save the basic information of the wayline file.
@@ -62,4 +64,11 @@
     * @return
     */
    List<String> getDuplicateNames(String workspaceId, List<String> names);
    /**
     * Delete the wayline file based on the wayline id.
     * @param workspaceId
     * @param waylineId
     */
    Boolean deleteByWaylineId(String workspaceId, String waylineId);
}
src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java
New file
@@ -0,0 +1,56 @@
package com.dji.sample.wayline.service;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.param.CreateJobParam;
import java.sql.SQLException;
import java.util.Optional;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
public interface IWaylineJobService {
    /**
     * Create a wayline mission for the dock.
     * @param param
     * @param customClaim user info
     * @return
     */
    Boolean createJob(CreateJobParam param, CustomClaim customClaim) throws SQLException;
    /**
     * Issue wayline mission to the dock for execution.
     * @param workspaceId
     * @param jobId
     * @return
     */
    void publishFlightTask(String workspaceId, String jobId) throws SQLException;
    /**
     * Query job information based on job id.
     * @param jobId
     * @return job information
     */
    Optional<WaylineJobDTO> getJobByJobId(String jobId);
    /**
     * Update job data.
     * @param dto
     * @return
     */
    Boolean updateJob(WaylineJobDTO dto);
    /**
     * Paginate through all jobs in this workspace.
     * @param workspaceId
     * @param page
     * @param pageSize
     * @return
     */
    PaginationData<WaylineJobDTO> getJobsByWorkspaceId(String workspaceId, long page, long pageSize);
}
src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java
New file
@@ -0,0 +1,84 @@
package com.dji.sample.wayline.service.impl;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.wayline.model.dto.FlightTaskProgressReceiver;
import com.dji.sample.wayline.service.IFlightTaskService;
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.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/9
 */
@Service
@Slf4j
public class FlightTaskServiceImpl implements IFlightTaskService {
    @Autowired
    private IMessageSenderService messageSender;
    @Autowired
    private ObjectMapper mapper;
    @Autowired
    private ISendMessageService websocketMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Autowired
    private RedisOpsUtils redisOps;
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS, outputChannel = ChannelName.OUTBOUND)
    public void handleProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        EventsReceiver<FlightTaskProgressReceiver> eventsReceiver = mapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<FlightTaskProgressReceiver>>(){});
        eventsReceiver.setBid(receiver.getBid());
        log.info("Task progress: " + eventsReceiver.getOutput().getProgress().toString());
        if (eventsReceiver.getResult() != ResponseResult.CODE_SUCCESS) {
            log.error("Error code: " + eventsReceiver.getResult());
        }
        DeviceDTO device = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway());
        websocketMessageService.sendBatch(
                webSocketManageService.getValueWithWorkspaceAndUserType(
                        device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .data(eventsReceiver)
                        .timestamp(System.currentTimeMillis())
                        .bizCode(BizCodeEnum.FLIGHT_TASK_PROGRESS.getCode())
                        .build());
        if (receiver.getNeedReply() == 1) {
            String topic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF;
            messageSender.publish(topic,
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .method(EventsMethodEnum.FLIGHT_TASK_PROGRESS.getMethod())
                            .timestamp(System.currentTimeMillis())
                            .data(ResponseResult.success())
                            .build());
        }
    }
}
src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java
@@ -5,25 +5,24 @@
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.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.service.impl.OssServiceContext;
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.model.dto.WaylineFileDTO;
import com.dji.sample.wayline.model.entity.WaylineFileEntity;
import com.dji.sample.wayline.model.param.WaylineQueryParam;
import com.dji.sample.wayline.service.IWaylineFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.net.URL;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -34,27 +33,16 @@
 */
@Service
@Transactional
@Slf4j
public class WaylineFileServiceImpl implements IWaylineFileService {
    @Autowired
    private IWaylineFileMapper mapper;
    private IOssService ossService;
    @Autowired
    private OssServiceContext ossService;
    @Autowired
    private void setOssService(@Autowired(required = false) AliyunOssServiceImpl aliyunOssService,
                               @Autowired(required = false) MinIOServiceImpl minIOService) {
        if (AliyunOSSConfiguration.enable) {
            this.ossService = aliyunOssService;
            return;
        }
        if (MinIOConfiguration.enable) {
            this.ossService = minIOService;
            return;
        }
        log.error("ossService is null.");
    }
    private OssConfiguration configuration;
    @Override
    public PaginationData<WaylineFileDTO> getWaylinesByParam(String workspaceId, WaylineQueryParam param) {
@@ -82,17 +70,22 @@
    }
    @Override
    public WaylineFileDTO getWaylineByWaylineId(String workspaceId, String waylineId) {
        return this.entityConvertToDTO(
                mapper.selectOne(
                        new LambdaQueryWrapper<WaylineFileEntity>()
                                .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
                                .eq(WaylineFileEntity::getWaylineId, waylineId)));
    public Optional<WaylineFileDTO> getWaylineByWaylineId(String workspaceId, String waylineId) {
        return Optional.ofNullable(
                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);
    public URL getObjectUrl(String workspaceId, String waylineId) throws SQLException {
        Optional<WaylineFileDTO> waylineOpt = this.getWaylineByWaylineId(workspaceId, waylineId);
        if (waylineOpt.isEmpty()) {
            throw new SQLException(waylineId + " does not exist.");
        }
        return ossService.getObjectUrl(configuration.getBucket(), waylineOpt.get().getObjectKey());
    }
    @Override
@@ -101,6 +94,13 @@
        file.setWaylineId(UUID.randomUUID().toString());
        file.setWorkspaceId(workspaceId);
        byte[] object = ossService.getObject(configuration.getBucket(), metadata.getObjectKey());
        if (object.length == 0) {
            throw new RuntimeException("The file " + metadata.getObjectKey() +
                    " does not exist in the bucket[" + configuration.getBucket() + "].");
        }
        file.setSign(DigestUtils.md5DigestAsHex(object));
        int insertId = mapper.insert(file);
        return insertId > 0 ? file.getId() : insertId;
    }
@@ -129,30 +129,48 @@
                .collect(Collectors.toList());
    }
    @Override
    public Boolean deleteByWaylineId(String workspaceId, String waylineId) {
        Optional<WaylineFileDTO> waylineOpt = this.getWaylineByWaylineId(workspaceId, waylineId);
        if (waylineOpt.isEmpty()) {
            return true;
        }
        WaylineFileDTO wayline = waylineOpt.get();
        boolean isDel = mapper.delete(new LambdaUpdateWrapper<WaylineFileEntity>()
                    .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
                    .eq(WaylineFileEntity::getWaylineId, waylineId))
                > 0;
        if (!isDel) {
            return false;
        }
        return ossService.deleteObject(configuration.getBucket(), wayline.getObjectKey());
    }
    /**
     * 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());
        if (entity == null) {
            return null;
        }
        return WaylineFileDTO.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())
                .sign(entity.getSign())
                .updateTime(entity.getUpdateTime())
                .waylineId(entity.getWaylineId())
                .build();
        return builder.build();
    }
    /**
src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java
New file
@@ -0,0 +1,212 @@
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.CustomClaim;
import com.dji.sample.common.model.Pagination;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.mqtt.model.ServiceReply;
import com.dji.sample.component.mqtt.model.ServicesMethodEnum;
import com.dji.sample.component.mqtt.model.TopicConst;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.wayline.dao.IWaylineJobMapper;
import com.dji.sample.wayline.model.dto.FlightTaskCreateDTO;
import com.dji.sample.wayline.model.dto.FlightTaskFileDTO;
import com.dji.sample.wayline.model.dto.WaylineFileDTO;
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.entity.WaylineJobEntity;
import com.dji.sample.wayline.model.param.CreateJobParam;
import com.dji.sample.wayline.service.IWaylineFileService;
import com.dji.sample.wayline.service.IWaylineJobService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.URL;
import java.sql.SQLException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 1.1
 * @date 2022/6/1
 */
@Service
@Transactional
@Slf4j
public class WaylineJobServiceImpl implements IWaylineJobService {
    @Autowired
    private IWaylineJobMapper mapper;
    @Autowired
    private IWaylineFileService waylineFileService;
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private IMessageSenderService messageSender;
    @Autowired
    private RedisOpsUtils redisOps;
    @Override
    public Boolean createJob(CreateJobParam param, CustomClaim customClaim) throws SQLException {
        if (param == null) {
            return false;
        }
        WaylineJobEntity jobEntity = WaylineJobEntity.builder()
                .name(param.getName())
                .dockSn(param.getDockSn())
                .fileId(param.getFileId())
                .username(customClaim.getUsername())
                .workspaceId(customClaim.getWorkspaceId())
                .jobId(UUID.randomUUID().toString())
                .type(param.getType())
                .build();
        int id = mapper.insert(jobEntity);
        if (id <= 0) {
            return false;
        }
        if (param.isImmediate()) {
            publishFlightTask(jobEntity.getWorkspaceId(), jobEntity.getJobId());
        }
        return true;
    }
    @Override
    public void publishFlightTask(String workspaceId, String jobId) throws SQLException {
        // get job
        Optional<WaylineJobDTO> waylineJob = this.getJobByJobId(jobId);
        if (waylineJob.isEmpty()) {
            throw new IllegalArgumentException("Job doesn't exist.");
        }
        long expire = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + waylineJob.get().getDockSn());
        if (expire < 0) {
            throw new RuntimeException("Dock is offline.");
        }
        // get wayline file
        Optional<WaylineFileDTO> waylineFile = waylineFileService.getWaylineByWaylineId(workspaceId, waylineJob.get().getFileId());
        if (waylineFile.isEmpty()) {
            throw new IllegalArgumentException("Wayline file doesn't exist.");
        }
        // get file url
        URL url = waylineFileService.getObjectUrl(workspaceId, waylineFile.get().getWaylineId());
        WaylineJobDTO job = waylineJob.get();
        FlightTaskCreateDTO flightTask = FlightTaskCreateDTO.builder()
                .flightId(jobId)
                .type(job.getType())
                .file(FlightTaskFileDTO.builder()
                        .url(url.toString())
                        .sign(waylineFile.get().getSign())
                        .build())
                .build();
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT +
                job.getDockSn() + TopicConst.SERVICES_SUF;
        CommonTopicResponse<Object> response = CommonTopicResponse.builder()
                .tid(UUID.randomUUID().toString())
                .bid(UUID.randomUUID().toString())
                .timestamp(System.currentTimeMillis())
                .data(flightTask)
                .method(ServicesMethodEnum.FLIGHTTASK_CREATE.getMethod())
                .build();
        Optional<ServiceReply> serviceReplyOpt = messageSender.publishWithReply(topic, response);
        if (serviceReplyOpt.isEmpty()) {
            log.info("Timeout to receive reply.");
            throw new RuntimeException("Timeout to receive reply.");
        }
        if (serviceReplyOpt.get().getResult() != 0) {
            log.info("Error code: {}", serviceReplyOpt.get().getResult());
            throw new RuntimeException("Error code: " + serviceReplyOpt.get().getResult());
        }
        job.setBid(response.getBid());
        boolean isUpd = this.updateJob(job);
        if (!isUpd) {
            throw new SQLException("Failed to update data.");
        }
    }
    @Override
    public Optional<WaylineJobDTO> getJobByJobId(String jobId) {
        WaylineJobEntity jobEntity = mapper.selectOne(
                new LambdaQueryWrapper<WaylineJobEntity>()
                        .eq(WaylineJobEntity::getJobId, jobId));
        return Optional.ofNullable(entity2Dto(jobEntity));
    }
    @Override
    public Boolean updateJob(WaylineJobDTO dto) {
        return mapper.update(this.dto2Entity(dto),
                new LambdaUpdateWrapper<WaylineJobEntity>()
                        .eq(WaylineJobEntity::getWorkspaceId, dto.getWorkspaceId())
                        .eq(WaylineJobEntity::getJobId, dto.getJobId()))
                > 0;
    }
    @Override
    public PaginationData<WaylineJobDTO> getJobsByWorkspaceId(String workspaceId, long page, long pageSize) {
        Page<WaylineJobEntity> pageData = mapper.selectPage(
                new Page<WaylineJobEntity>(page, pageSize),
                new LambdaQueryWrapper<WaylineJobEntity>()
                        .eq(WaylineJobEntity::getWorkspaceId, workspaceId));
        List<WaylineJobDTO> records = pageData.getRecords()
                .stream()
                .map(this::entity2Dto)
                .collect(Collectors.toList());
        return new PaginationData<WaylineJobDTO>(records, new Pagination(pageData));
    }
    private WaylineJobEntity dto2Entity(WaylineJobDTO dto) {
        WaylineJobEntity.WaylineJobEntityBuilder builder = WaylineJobEntity.builder();
        if (dto == null) {
            return builder.build();
        }
        return builder.type(dto.getType())
                .bid(dto.getBid())
                .name(dto.getJobName())
                .build();
    }
    private WaylineJobDTO entity2Dto(WaylineJobEntity entity) {
        if (entity == null) {
            return null;
        }
        return WaylineJobDTO.builder()
                .jobId(entity.getJobId())
                .bid(entity.getBid())
                .updateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault()))
                .jobName(entity.getName())
                .fileId(entity.getFileId())
                .fileName(waylineFileService.getWaylineByWaylineId(entity.getWorkspaceId(), entity.getFileId())
                        .orElse(WaylineFileDTO.builder().build()).getName())
                .dockSn(entity.getDockSn())
                .dockName(deviceService.getDeviceBySn(entity.getDockSn())
                        .orElse(DeviceDTO.builder().build()).getNickname())
                .username(entity.getUsername())
                .workspaceId(entity.getWorkspaceId())
                .type(entity.getType())
                .build();
    }
}
src/main/resources/application.yml
@@ -7,7 +7,7 @@
    druid:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/cloud_sample?useSSL=false&allowPublicKeyRetrieval=true
      url: jdbc:mysql://cloud_api_sample_mysql:3306/cloud_sample?useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: root
      initial-size: 10
@@ -15,12 +15,18 @@
      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
  redis:
    host: cloud_api_sample_redis
    port: 6379
    database: 0
    username: # if you enable
    password:
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
jwt:
  issuer: DJI
@@ -30,13 +36,13 @@
mqtt:
  protocol: tcp
  host: Please enter the address of the emqx server. # Example: 192.168.1.1
  host: Please enter your ip. # 192.168.1.1
  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
  inbound-topic: sys/product/+/status,thing/product/+/requests
url:
  manage:
@@ -56,29 +62,43 @@
    version: /api/v1
# Tutorial: https://help.aliyun.com/document_detail/100624.htm?spm=a2c4g.11186623.0.0.74075e34eIhK7T#concept-xzh-nzk-2gb
aliyun:
  oss:
    enable: false
    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.    # Example: cn-shenzhen
    role-session-name: Please enter session name.   # A custom role session name to distinguish the different tokens, for example it could be filled in as SessionTest.
    role-arn: Please enter role ARN.    # Example: acs:ram::123456789:role/oss
    bucket: Please enter bucket name.
    object-dir-prefix: Please enter object prefix.
oss:
  enable: true
  provider: ali # @see com.dji.sample.component.OssConfiguration.model.enums.OssTypeEnum
  endpoint: https://oss-cn-hangzhou.aliyuncs.com
  access-key: Please enter your access key.
  secret-key: Please enter your secret key.
  expire: 3600
  region: Please enter your oss region. # cn-hangzhou
  role-session-name: cloudApi
  role-arn: Please enter your role arn. # acs:ram::123456789:role/stsrole
  bucket: Please enter your bucket name.
  object-dir-prefix: Please enter a folder name.
#oss:
#  enable: true
#  provider: aws
#  endpoint: https://s3.us-east-1.amazonaws.com
#  access-key:
#  secret-key:
#  expire: 3600
#  region: us-east-1
#  role-session-name: cloudApi
#  role-arn:
#  bucket: cloudapi-bucket
#  object-dir-prefix: wayline
# 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.
#oss:
#  enable: false
#  provider: minio
#  endpoint:
#  access-key:
#  secret-key:
#  bucket:
#  expire:
#  region:
#  object-dir-prefix:
logging:
  level: