From 17835b967b48f2676cce3bbed081c6580602ee1c Mon Sep 17 00:00:00 2001 From: "sean.zhou" Date: Mon, 21 Mar 2022 22:17:00 +0800 Subject: [PATCH] 1.0.0 initial --- .gitignore | 38 ++ pom.xml | 148 +++++ sql/cloud_sample.sql | 324 ++++++++++ .../dji/sample/CloudApiSampleApplication.java | 17 + .../sample/common/error/CommonErrorEnum.java | 40 ++ .../dji/sample/common/error/IErrorInfo.java | 22 + .../sample/common/error/LiveErrorEnum.java | 78 +++ .../dji/sample/common/model/CustomClaim.java | 88 +++ .../dji/sample/common/model/Pagination.java | 36 ++ .../sample/common/model/PaginationData.java | 27 + .../sample/common/model/ResponseResult.java | 69 +++ .../com/dji/sample/common/util/JwtUtil.java | 103 ++++ .../component/ApplicationBootInitial.java | 40 ++ .../dji/sample/component/AuthInterceptor.java | 60 ++ .../com/dji/sample/component/CorsFilter.java | 35 ++ .../component/GlobalExceptionHandler.java | 33 ++ .../component/GlobalScheduleService.java | 58 ++ .../mqtt/config/InboundMessageRouter.java | 95 +++ .../mqtt/config/MqttConfiguration.java | 63 ++ .../mqtt/config/MqttInboundConfiguration.java | 69 +++ .../mqtt/config/MqttMessageChannel.java | 90 +++ .../config/MqttOutboundConfiguration.java | 47 ++ .../component/mqtt/model/ChannelName.java | 46 ++ .../mqtt/model/CommonTopicReceiver.java | 29 + .../mqtt/model/CommonTopicResponse.java | 33 ++ .../component/mqtt/model/TopicConst.java | 29 + .../mqtt/model/TopicStateReceiver.java | 29 + .../mqtt/service/IMessageSenderService.java | 27 + .../mqtt/service/IMqttMessageGateway.java | 33 ++ .../mqtt/service/IMqttTopicService.java | 38 ++ .../impl/MessageSenderServiceImpl.java | 50 ++ .../service/impl/MqttTopicServiceImpl.java | 44 ++ .../mybatis/MybatisPlusConfiguration.java | 24 + .../mybatis/MybatisPlusMetaObjectHandler.java | 37 ++ .../oss/model/AliyunOSSConfiguration.java | 104 ++++ .../oss/model/MinIOConfiguration.java | 93 +++ .../component/oss/service/IOssService.java | 27 + .../service/impl/AliyunOssServiceImpl.java | 68 +++ .../oss/service/impl/MinIOServiceImpl.java | 66 +++ .../config/AuthPrincipalHandler.java | 70 +++ .../config/ConcurrentWebSocketSession.java | 25 + .../config/WebSocketDefaultFactory.java | 20 + .../config/WebSocketDefaultHandler.java | 60 ++ .../config/WebSocketMessageConfiguration.java | 39 ++ .../websocket/model/BizCodeEnum.java | 37 ++ .../model/CustomWebSocketMessage.java | 30 + .../websocket/model/WebSocketManager.java | 112 ++++ .../service/ISendMessageService.java | 28 + .../service/impl/SendMessageServiceImpl.java | 72 +++ .../configuration/GlobalMVCConfigurer.java | 35 ++ .../GlobalThreadPoolConfiguration.java | 42 ++ .../manage/controller/DeviceController.java | 138 +++++ .../controller/DevicePayloadController.java | 66 +++ .../controller/LiveStreamController.java | 113 ++++ .../manage/controller/LoginController.java | 47 ++ .../manage/controller/TopologyController.java | 40 ++ .../manage/controller/UserController.java | 29 + .../controller/WorkspaceController.java | 41 ++ .../sample/manage/dao/ICameraVideoMapper.java | 12 + .../manage/dao/ICapacityCameraMapper.java | 12 + .../manage/dao/IDeviceDictionaryMapper.java | 13 + .../dji/sample/manage/dao/IDeviceMapper.java | 14 + .../manage/dao/IDevicePayloadMapper.java | 12 + .../dji/sample/manage/dao/IUserMapper.java | 8 + .../sample/manage/dao/IWorkspaceMapper.java | 8 + .../handler/AbstractStateTopicHandler.java | 35 ++ .../manage/handler/StateDefaultHandler.java | 25 + .../handler/StateDeviceBasicHandler.java | 34 ++ .../handler/StateLiveCapacityHandler.java | 39 ++ .../manage/handler/StatePayloadHandler.java | 55 ++ .../sample/manage/handler/StateRouter.java | 92 +++ .../sample/manage/handler/StateSplitter.java | 59 ++ .../sample/manage/handler/StatusRouter.java | 70 +++ .../com/dji/sample/manage/model/Chan.java | 45 ++ .../manage/model/DeviceStatusManager.java | 20 + .../manage/model/dto/CapacityCameraDTO.java | 34 ++ .../manage/model/dto/CapacityDeviceDTO.java | 26 + .../manage/model/dto/CapacityVideoDTO.java | 24 + .../sample/manage/model/dto/DeviceDTO.java | 47 ++ .../manage/model/dto/DeviceDictionaryDTO.java | 28 + .../manage/model/dto/DeviceModelDTO.java | 27 + .../manage/model/dto/DevicePayloadDTO.java | 29 + .../sample/manage/model/dto/IconUrlDTO.java | 25 + .../dji/sample/manage/model/dto/LiveDTO.java | 20 + .../sample/manage/model/dto/LiveTypeDTO.java | 26 + .../manage/model/dto/LiveUrlAgoraDTO.java | 20 + .../manage/model/dto/LiveUrlGB28181DTO.java | 27 + .../manage/model/dto/LiveUrlRTSPDTO.java | 18 + .../sample/manage/model/dto/TelemetryDTO.java | 22 + .../manage/model/dto/TelemetryDeviceDTO.java | 32 + .../sample/manage/model/dto/TopologyDTO.java | 24 + .../manage/model/dto/TopologyDeviceDTO.java | 35 ++ .../dji/sample/manage/model/dto/UserDTO.java | 37 ++ .../sample/manage/model/dto/UserLoginDTO.java | 14 + .../sample/manage/model/dto/WorkspaceDTO.java | 33 ++ .../model/entity/CameraVideoEntity.java | 37 ++ .../model/entity/CapacityCameraEntity.java | 46 ++ .../model/entity/DeviceDictionaryEntity.java | 45 ++ .../manage/model/entity/DeviceEntity.java | 69 +++ .../model/entity/DevicePayloadEntity.java | 56 ++ .../manage/model/entity/UserEntity.java | 41 ++ .../manage/model/entity/WorkspaceEntity.java | 33 ++ .../manage/model/enums/DeviceDomainEnum.java | 63 ++ .../manage/model/enums/IconUrlEnum.java | 37 ++ .../manage/model/enums/LiveMethodEnum.java | 27 + .../manage/model/enums/LiveUrlTypeEnum.java | 41 ++ .../model/enums/LiveVideoQualityEnum.java | 51 ++ .../manage/model/enums/StateDataEnum.java | 26 + .../manage/model/enums/UserTypeEnum.java | 24 + .../manage/model/param/DeviceQueryParam.java | 32 + .../model/receiver/BatteryReceiver.java | 27 + .../model/receiver/BatteryStateReceiver.java | 34 ++ .../receiver/CapacityCameraReceiver.java | 28 + .../receiver/CapacityDeviceReceiver.java | 25 + .../model/receiver/CapacityVideoReceiver.java | 19 + .../model/receiver/DeviceBasicReceiver.java | 39 ++ .../model/receiver/DevicePayloadReceiver.java | 29 + .../model/receiver/LiveStatusReceiver.java | 23 + .../model/receiver/OsdGatewayReceiver.java | 31 + .../model/receiver/OsdSubDeviceReceiver.java | 55 ++ .../model/receiver/PositionStateReceiver.java | 23 + .../model/receiver/RTKStateReceiver.java | 28 + .../model/receiver/ServiceReplyReceiver.java | 18 + .../model/receiver/StatusGatewayReceiver.java | 43 ++ .../receiver/StatusSubDeviceReceiver.java | 32 + .../receiver/WirelessLinkStateReceiver.java | 22 + .../manage/service/ICameraVideoService.java | 37 ++ .../service/ICapacityCameraService.java | 45 ++ .../service/IDeviceDictionaryService.java | 24 + .../manage/service/IDevicePayloadService.java | 48 ++ .../sample/manage/service/IDeviceService.java | 135 +++++ .../manage/service/ILiveStreamService.java | 51 ++ .../sample/manage/service/ITSAService.java | 17 + .../manage/service/ITopologyService.java | 20 + .../sample/manage/service/IUserService.java | 32 + .../manage/service/IWorkspaceService.java | 23 + .../service/impl/AbstractTSAService.java | 59 ++ .../service/impl/CameraVideoServiceImpl.java | 99 ++++ .../impl/CapacityCameraServiceImpl.java | 172 ++++++ .../impl/DeviceDictionaryServiceImpl.java | 59 ++ .../service/impl/DeviceOSDServiceImpl.java | 69 +++ .../impl/DevicePayloadServiceImpl.java | 157 +++++ .../service/impl/DeviceServiceImpl.java | 553 ++++++++++++++++++ .../service/impl/GatewayOSDServiceImpl.java | 74 +++ .../service/impl/LiveStreamServiceImpl.java | 326 +++++++++++ .../service/impl/TopologyServiceImpl.java | 55 ++ .../manage/service/impl/UserServiceImpl.java | 141 +++++ .../service/impl/WorkspaceServiceImpl.java | 52 ++ .../WorkspaceElementController.java | 184 ++++++ .../map/dao/IElementCoordinateMapper.java | 12 + .../sample/map/dao/IGroupElementMapper.java | 12 + .../com/dji/sample/map/dao/IGroupMapper.java | 12 + .../map/model/dto/ContentPropertyDTO.java | 25 + .../map/model/dto/ElementCoordinateDTO.java | 25 + .../map/model/dto/ElementCreateDTO.java | 18 + .../map/model/dto/ElementLineStringDTO.java | 47 ++ .../sample/map/model/dto/ElementPointDTO.java | 46 ++ .../map/model/dto/ElementPolygonDTO.java | 48 ++ .../map/model/dto/ElementResourceDTO.java | 26 + .../dji/sample/map/model/dto/ElementType.java | 43 ++ .../map/model/dto/ElementUpdateDTO.java | 17 + .../dji/sample/map/model/dto/GroupDTO.java | 38 ++ .../sample/map/model/dto/GroupElementDTO.java | 38 ++ .../map/model/dto/ResourceContentDTO.java | 25 + .../map/model/dto/WebSocketElementDelDTO.java | 25 + .../model/entity/ElementCoordinateEntity.java | 41 ++ .../map/model/entity/GroupElementEntity.java | 55 ++ .../sample/map/model/entity/GroupEntity.java | 49 ++ .../map/model/enums/ElementTypeEnum.java | 69 +++ .../service/IElementCoordinateService.java | 35 ++ .../map/service/IGroupElementService.java | 54 ++ .../dji/sample/map/service/IGroupService.java | 24 + .../map/service/IWorkspaceElementService.java | 71 +++ .../impl/ElementCoordinateServiceImpl.java | 94 +++ .../service/impl/GroupElementServiceImpl.java | 199 +++++++ .../map/service/impl/GroupServiceImpl.java | 66 +++ .../impl/WorkspaceElementServiceImpl.java | 109 ++++ .../media/controller/FileController.java | 36 ++ .../media/controller/MediaController.java | 73 +++ .../com/dji/sample/media/dao/IFileMapper.java | 12 + .../sample/media/model/CredentialsDTO.java | 40 ++ .../sample/media/model/FileExtensionDTO.java | 26 + .../sample/media/model/FileMetadataDTO.java | 31 + .../dji/sample/media/model/FileUploadDTO.java | 26 + .../dji/sample/media/model/MediaFileDTO.java | 34 ++ .../sample/media/model/MediaFileEntity.java | 62 ++ .../dji/sample/media/model/PositionDTO.java | 16 + .../sample/media/model/StsCredentialsDTO.java | 33 ++ .../sample/media/service/IFileService.java | 37 ++ .../sample/media/service/IMediaService.java | 36 ++ .../media/service/impl/FileServiceImpl.java | 112 ++++ .../media/service/impl/MediaServiceImpl.java | 41 ++ .../storage/controller/StorageController.java | 51 ++ .../storage/service/IStorageService.java | 17 + .../impl/AliyunStorageServiceImpl.java | 32 + .../service/impl/MinIOStorageServiceImpl.java | 34 ++ .../controller/WaylineFileController.java | 150 +++++ .../wayline/dao/IWaylineFileMapper.java | 12 + .../sample/wayline/model/WaylineFileDTO.java | 41 ++ .../wayline/model/WaylineFileEntity.java | 57 ++ .../wayline/model/WaylineFileUploadDTO.java | 18 + .../wayline/model/WaylineQueryParam.java | 30 + .../wayline/service/IWaylineFileService.java | 65 ++ .../service/impl/WaylineFileServiceImpl.java | 179 ++++++ src/main/resources/application.yml | 86 +++ 205 files changed, 10748 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 sql/cloud_sample.sql create mode 100644 src/main/java/com/dji/sample/CloudApiSampleApplication.java create mode 100644 src/main/java/com/dji/sample/common/error/CommonErrorEnum.java create mode 100644 src/main/java/com/dji/sample/common/error/IErrorInfo.java create mode 100644 src/main/java/com/dji/sample/common/error/LiveErrorEnum.java create mode 100644 src/main/java/com/dji/sample/common/model/CustomClaim.java create mode 100644 src/main/java/com/dji/sample/common/model/Pagination.java create mode 100644 src/main/java/com/dji/sample/common/model/PaginationData.java create mode 100644 src/main/java/com/dji/sample/common/model/ResponseResult.java create mode 100644 src/main/java/com/dji/sample/common/util/JwtUtil.java create mode 100644 src/main/java/com/dji/sample/component/ApplicationBootInitial.java create mode 100644 src/main/java/com/dji/sample/component/AuthInterceptor.java create mode 100644 src/main/java/com/dji/sample/component/CorsFilter.java create mode 100644 src/main/java/com/dji/sample/component/GlobalExceptionHandler.java create mode 100644 src/main/java/com/dji/sample/component/GlobalScheduleService.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java create mode 100644 src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java create mode 100644 src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java create mode 100644 src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java create mode 100644 src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java create mode 100644 src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java create mode 100644 src/main/java/com/dji/sample/component/oss/service/IOssService.java create mode 100644 src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java create mode 100644 src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java create mode 100644 src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java create mode 100644 src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java create mode 100644 src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java create mode 100644 src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java create mode 100644 src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java create mode 100644 src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java create mode 100644 src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java create mode 100644 src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java create mode 100644 src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java create mode 100644 src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java create mode 100644 src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java create mode 100644 src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java create mode 100644 src/main/java/com/dji/sample/manage/controller/DeviceController.java create mode 100644 src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java create mode 100644 src/main/java/com/dji/sample/manage/controller/LiveStreamController.java create mode 100644 src/main/java/com/dji/sample/manage/controller/LoginController.java create mode 100644 src/main/java/com/dji/sample/manage/controller/TopologyController.java create mode 100644 src/main/java/com/dji/sample/manage/controller/UserController.java create mode 100644 src/main/java/com/dji/sample/manage/controller/WorkspaceController.java create mode 100644 src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java create mode 100644 src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java create mode 100644 src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java create mode 100644 src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java create mode 100644 src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java create mode 100644 src/main/java/com/dji/sample/manage/dao/IUserMapper.java create mode 100644 src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java create mode 100644 src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java create mode 100644 src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java create mode 100644 src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java create mode 100644 src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java create mode 100644 src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java create mode 100644 src/main/java/com/dji/sample/manage/handler/StateRouter.java create mode 100644 src/main/java/com/dji/sample/manage/handler/StateSplitter.java create mode 100644 src/main/java/com/dji/sample/manage/handler/StatusRouter.java create mode 100644 src/main/java/com/dji/sample/manage/model/Chan.java create mode 100644 src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/UserDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java create mode 100644 src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java create mode 100644 src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java create mode 100644 src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java create mode 100644 src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java create mode 100644 src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java create mode 100644 src/main/java/com/dji/sample/manage/model/entity/UserEntity.java create mode 100644 src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java create mode 100644 src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java create mode 100644 src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java create mode 100644 src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java create mode 100644 src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java create mode 100644 src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java create mode 100644 src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java create mode 100644 src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java create mode 100644 src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java create mode 100644 src/main/java/com/dji/sample/manage/service/ICameraVideoService.java create mode 100644 src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java create mode 100644 src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java create mode 100644 src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java create mode 100644 src/main/java/com/dji/sample/manage/service/IDeviceService.java create mode 100644 src/main/java/com/dji/sample/manage/service/ILiveStreamService.java create mode 100644 src/main/java/com/dji/sample/manage/service/ITSAService.java create mode 100644 src/main/java/com/dji/sample/manage/service/ITopologyService.java create mode 100644 src/main/java/com/dji/sample/manage/service/IUserService.java create mode 100644 src/main/java/com/dji/sample/manage/service/IWorkspaceService.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java create mode 100644 src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java create mode 100644 src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java create mode 100644 src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java create mode 100644 src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java create mode 100644 src/main/java/com/dji/sample/map/dao/IGroupMapper.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ElementType.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/GroupDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java create mode 100644 src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java create mode 100644 src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java create mode 100644 src/main/java/com/dji/sample/map/model/entity/GroupEntity.java create mode 100644 src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java create mode 100644 src/main/java/com/dji/sample/map/service/IElementCoordinateService.java create mode 100644 src/main/java/com/dji/sample/map/service/IGroupElementService.java create mode 100644 src/main/java/com/dji/sample/map/service/IGroupService.java create mode 100644 src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java create mode 100644 src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java create mode 100644 src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java create mode 100644 src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java create mode 100644 src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java create mode 100644 src/main/java/com/dji/sample/media/controller/FileController.java create mode 100644 src/main/java/com/dji/sample/media/controller/MediaController.java create mode 100644 src/main/java/com/dji/sample/media/dao/IFileMapper.java create mode 100644 src/main/java/com/dji/sample/media/model/CredentialsDTO.java create mode 100644 src/main/java/com/dji/sample/media/model/FileExtensionDTO.java create mode 100644 src/main/java/com/dji/sample/media/model/FileMetadataDTO.java create mode 100644 src/main/java/com/dji/sample/media/model/FileUploadDTO.java create mode 100644 src/main/java/com/dji/sample/media/model/MediaFileDTO.java create mode 100644 src/main/java/com/dji/sample/media/model/MediaFileEntity.java create mode 100644 src/main/java/com/dji/sample/media/model/PositionDTO.java create mode 100644 src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java create mode 100644 src/main/java/com/dji/sample/media/service/IFileService.java create mode 100644 src/main/java/com/dji/sample/media/service/IMediaService.java create mode 100644 src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java create mode 100644 src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java create mode 100644 src/main/java/com/dji/sample/storage/controller/StorageController.java create mode 100644 src/main/java/com/dji/sample/storage/service/IStorageService.java create mode 100644 src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java create mode 100644 src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java create mode 100644 src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java create mode 100644 src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java create mode 100644 src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java create mode 100644 src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java create mode 100644 src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java create mode 100644 src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java create mode 100644 src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java create mode 100644 src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java create mode 100644 src/main/resources/application.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b53c3c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +/.mvn/ +/mvnw +/mvnw.cmd +/logs/ +/src/test/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5cbe4c6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.11 + + + + com.dji + cloud-api-sample + 1.0.0 + cloud-api-sample + + + 11 + 3.4.2 + 1.2.6 + 3.12.1 + 5.5.5 + 8.3.7 + 4.9.1 + 3.1.0 + 3.12.0 + 1.1.1 + 2.3.3 + 2.15.0 + 2.3.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + mysql + mysql-connector-java + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + com.auth0 + java-jwt + ${jwt.version} + + + + org.springframework.integration + spring-integration-mqtt + ${mqtt.version} + + + + org.springframework.boot + spring-boot-starter-websocket + + + org.jetbrains + annotations + RELEASE + compile + + + + io.minio + minio + ${minio.version} + + + + com.squareup.okhttp3 + okhttp + ${okhttp3.version} + + + + com.aliyun + aliyun-java-sdk-sts + ${aliyun-sdk-sts.version} + + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun-oss.version} + + + + javax.xml.bind + jaxb-api + + + javax.activation + activation + ${javax-activation.version} + + + + org.glassfish.jaxb + jaxb-runtime + ${glassfish-jaxb.version} + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/sql/cloud_sample.sql b/sql/cloud_sample.sql new file mode 100644 index 0000000..4cfe7fd --- /dev/null +++ b/sql/cloud_sample.sql @@ -0,0 +1,324 @@ +CREATE DATABASE IF NOT EXISTS `cloud_sample` /*!40100 DEFAULT CHARACTER SET utf8 */; +USE `cloud_sample`; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +SET NAMES utf8mb4; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE='NO_AUTO_VALUE_ON_ZERO', SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +# manage_camera_video +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `manage_camera_video`; + +CREATE TABLE `manage_camera_video` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `camera_id` int NOT NULL, + `video_index` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `video_type` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + + + +# manage_capacity_camera +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `manage_capacity_camera`; + +CREATE TABLE `manage_capacity_camera` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined', + `description` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `camera_index` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `coexist_video_number_max` int NOT NULL, + `available_video_number` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + + + +# manage_device +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `manage_device`; + +CREATE TABLE `manage_device` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined', + `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `device_type` smallint NOT NULL, + `sub_type` smallint NOT NULL, + `domain` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `version` smallint NOT NULL, + `device_index` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `child_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `create_time` bigint NOT NULL, + `update_time` bigint NOT NULL, + `device_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `url_normal` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `url_select` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `product_sn_UNIQUE` (`device_sn`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + + + +# manage_device_dictionary +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `manage_device_dictionary`; + +CREATE TABLE `manage_device_dictionary` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `domain` int NOT NULL, + `device_type` int NOT NULL, + `sub_type` int NOT NULL, + `device_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `device_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + +LOCK TABLES `manage_device_dictionary` WRITE; +/*!40000 ALTER TABLE `manage_device_dictionary` DISABLE KEYS */; + +INSERT INTO `manage_device_dictionary` (`id`, `domain`, `device_type`, `sub_type`, `device_name`, `device_desc`) +VALUES + (1,0,60,0,'Matrice 300 RTK',NULL), + (16,2,56,0,'DJI Smart Controller','Remote control for M300'), + (17,1,20,0,'Z30',NULL), + (18,1,26,0,'XT2',NULL), + (19,1,39,0,'FPV',NULL), + (20,1,41,0,'XTS',NULL), + (21,1,42,0,'H20',NULL), + (22,1,43,0,'H20T',NULL), + (24,1,90742,0,'L1',NULL), + (27,1,50,0,'P1 24mm lens',NULL), + (28,1,50,1,'P1 35mm lens',NULL), + (29,1,50,2,'P1 50mm lens',NULL), + (30,0,67,0,'Matrice 30',NULL), + (31,0,67,1,'Matrice 30T',NULL), + (32,2,119,0,'DJI RC Plus','Remote control for M30'), + (33,1,52,0,'M30',NULL), + (34,1,53,1,'M30T',NULL); + +/*!40000 ALTER TABLE `manage_device_dictionary` ENABLE KEYS */; +UNLOCK TABLES; + + +# manage_device_payload +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `manage_device_payload`; + +CREATE TABLE `manage_device_payload` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `payload_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `payload_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined', + `payload_type` smallint NOT NULL, + `sub_type` smallint NOT NULL, + `version` smallint DEFAULT NULL, + `payload_index` smallint NOT NULL, + `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `payload_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `create_time` bigint NOT NULL, + `update_time` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `payload_sn_UNIQUE` (`payload_sn`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + + + +# manage_user +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `manage_user`; + +CREATE TABLE `manage_user` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `user_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `workspace_id` int NOT NULL, + `user_type` smallint NOT NULL, + `mqtt_username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `mqtt_password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `create_time` bigint NOT NULL, + `update_time` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `user_id_UNIQUE` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + +LOCK TABLES `manage_user` WRITE; +/*!40000 ALTER TABLE `manage_user` DISABLE KEYS */; + +INSERT INTO `manage_user` (`id`, `user_id`, `username`, `password`, `workspace_id`, `user_type`, `mqtt_username`, `mqtt_password`, `create_time`, `update_time`) +VALUES + (1,'a1559e7c-8dd8-4780-b952-100cc4797da2','adminPC','adminPC',1,1,'admin','admin',1634898410751,1634898410751), + (2,'be7c6c3d-afe9-4be4-b9eb-c55066c0914e','pilot','pilot123',1,2,'pilot','pilot123',1634898410751,1634898410751); + +/*!40000 ALTER TABLE `manage_user` ENABLE KEYS */; +UNLOCK TABLES; + + +# manage_workspace +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `manage_workspace`; + +CREATE TABLE `manage_workspace` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `workspace_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `workspace_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `platform_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `create_time` bigint NOT NULL, + `update_time` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `workspace_id_UNIQUE` (`workspace_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + +LOCK TABLES `manage_workspace` WRITE; +/*!40000 ALTER TABLE `manage_workspace` DISABLE KEYS */; + +INSERT INTO `manage_workspace` (`id`, `workspace_id`, `workspace_name`, `workspace_desc`, `platform_name`, `create_time`, `update_time`) +VALUES + (1,'e3dea0f5-37f2-4d79-ae58-490af3228069','Test Group One','Cloud Sample Test Platform','Cloud Api Platform',1634898410751,1634898410751); + +/*!40000 ALTER TABLE `manage_workspace` ENABLE KEYS */; +UNLOCK TABLES; + + +# map_element_coordinate +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `map_element_coordinate`; + +CREATE TABLE `map_element_coordinate` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `element_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `longitude` decimal(18,14) NOT NULL, + `latitude` decimal(17,14) NOT NULL, + `altitude` decimal(17,14) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + + + +# map_group +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `map_group`; + +CREATE TABLE `map_group` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `group_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `group_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `group_type` int NOT NULL, + `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `is_distributed` tinyint(1) NOT NULL DEFAULT '1', + `is_lock` tinyint(1) NOT NULL DEFAULT '0', + `create_time` bigint NOT NULL, + `update_time` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `group_id_UNIQUE` (`group_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin; + +LOCK TABLES `map_group` WRITE; +/*!40000 ALTER TABLE `map_group` DISABLE KEYS */; + +INSERT INTO `map_group` (`id`, `group_id`, `group_name`, `group_type`, `workspace_id`, `is_distributed`, `is_lock`, `create_time`, `update_time`) +VALUES + (1,'e3dea0f5-37f2-4d79-ae58-490af3228060','Pilot Share Layer',2,'e3dea0f5-37f2-4d79-ae58-490af3228069',1,0,1638330077356,1638330077356), + (2,'e3dea0f5-37f2-4d79-ae58-490af3228011','Default Layer',1,'e3dea0f5-37f2-4d79-ae58-490af3228069',1,0,1638330077356,1638330077356); + +/*!40000 ALTER TABLE `map_group` ENABLE KEYS */; +UNLOCK TABLES; + + +# map_group_element +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `map_group_element`; + +CREATE TABLE `map_group_element` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `element_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `element_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `display` smallint NOT NULL DEFAULT '1', + `group_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `element_type` smallint NOT NULL, + `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `color` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `clamp_to_ground` tinyint(1) NOT NULL DEFAULT '0', + `create_time` bigint NOT NULL, + `update_time` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `element_id_UNIQUE` (`element_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin; + + + +# media_file +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `media_file`; + +CREATE TABLE `media_file` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `file_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `file_path` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `fingerprint` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `tinny_fingerprint` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `object_key` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `sub_file_type` int NOT NULL, + `is_original` tinyint(1) NOT NULL, + `drone` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined', + `payload` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined', + `create_time` bigint NOT NULL, + `update_time` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `fingerprint_UNIQUE` (`fingerprint`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + + + +# wayline_file +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `wayline_file`; + +CREATE TABLE `wayline_file` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `wayline_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `drone_model_key` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `payload_model_keys` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `favorited` tinyint(1) NOT NULL DEFAULT '0', + `template_types` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `object_key` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `create_time` bigint NOT NULL, + `update_time` bigint NOT NULL COMMENT 'required, can not modify.', + PRIMARY KEY (`id`), + UNIQUE KEY `wayline_id_UNIQUE` (`wayline_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + + + + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + diff --git a/src/main/java/com/dji/sample/CloudApiSampleApplication.java b/src/main/java/com/dji/sample/CloudApiSampleApplication.java new file mode 100644 index 0000000..14cca32 --- /dev/null +++ b/src/main/java/com/dji/sample/CloudApiSampleApplication.java @@ -0,0 +1,17 @@ +package com.dji.sample; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +@MapperScan("com.dji.sample.*.dao") +@SpringBootApplication +@EnableScheduling +public class CloudApiSampleApplication { + + public static void main(String[] args) { + SpringApplication.run(CloudApiSampleApplication.class, args); + } + +} diff --git a/src/main/java/com/dji/sample/common/error/CommonErrorEnum.java b/src/main/java/com/dji/sample/common/error/CommonErrorEnum.java new file mode 100644 index 0000000..1dab9d1 --- /dev/null +++ b/src/main/java/com/dji/sample/common/error/CommonErrorEnum.java @@ -0,0 +1,40 @@ +package com.dji.sample.common.error; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/25 + */ +public enum CommonErrorEnum implements IErrorInfo { + + SYSTEM_ERROR(600500, "system error"), + + SECRET_INVALID(600100, "secret invalid"), + + NO_TOKEN(600101, "accss_token is null"), + + TOKEN_EXPIRED(600102, "token is expired"), + + TOKEN_INVALID(600103, "token invalid"), + + SIGN_INVALID(600104, "sign invalid"); + + private String msg; + + private int code; + + CommonErrorEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + @Override + public String getErrorMsg() { + return this.msg; + } + + @Override + public Integer getErrorCode() { + return this.code; + } +} diff --git a/src/main/java/com/dji/sample/common/error/IErrorInfo.java b/src/main/java/com/dji/sample/common/error/IErrorInfo.java new file mode 100644 index 0000000..3332dd3 --- /dev/null +++ b/src/main/java/com/dji/sample/common/error/IErrorInfo.java @@ -0,0 +1,22 @@ +package com.dji.sample.common.error; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/25 + */ +public interface IErrorInfo { + + /** + * Get error message. + * @return error message + */ + String getErrorMsg(); + + /** + * Get error code. + * @return error code + */ + Integer getErrorCode(); + +} diff --git a/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java b/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java new file mode 100644 index 0000000..0a65eed --- /dev/null +++ b/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java @@ -0,0 +1,78 @@ +package com.dji.sample.common.error; + +/** + * Live streaming related error codes. When on-demand via mqtt, + * it can be matched with the error code information replied by the pilot. + * @author sean.zhou + * @version 0.1 + * @date 2021/11/25 + */ +public enum LiveErrorEnum implements IErrorInfo { + + NO_AIRCRAFT(613001, "No aircraft."), + + NO_CAMERA(613002, "No camera."), + + LIVE_STREAM_ALREADY_STARTED(613003, "The camera has started live streaming."), + + FUNCTION_NOT_SUPPORT(613004, "The function is not supported."), + + STRATEGY_NOT_SUPPORT(613005, "The strategy is not supported."), + + NOT_IN_CAMERA_INTERFACE(613006, "The current app is not in the camera interface."), + + NO_FLIGHT_CONTROL(613007, "The remote control has no flight control rights and cannot respond to control commands"), + + NO_STREAM_DATA(613008, "The current app has no stream data."), + + TOO_FREQUENT(613009, "The operation is too frequent."), + + ENABLE_FAILED(613010, "Please check whether the live stream service is normal."), + + NO_LIVE_STREAM(613011, "There are no live stream currently."), + + SWITCH_NOT_SUPPORT(613012, "There is already another camera in the live stream. It's not support to switch the stream directly."), + + URL_TYPE_NOT_SUPPORTED(613013, "This url type is not supported."), + + ERROR_PARAMETERS(613014, "The live stream parameters are abnormal or incomplete."), + + NO_REPLY(613098, "No live reply received."), + + UNKNOWN(613099, "UNKNOWN"); + + + private String msg; + + private int code; + + LiveErrorEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + @Override + public String getErrorMsg() { + return this.msg; + } + + @Override + public Integer getErrorCode() { + return this.code; + } + + /** + * Get the corresponding enumeration object based on the error code. + * @param code error code + * @return enumeration object + */ + public static LiveErrorEnum find(int code) { + + for (LiveErrorEnum errorEnum : LiveErrorEnum.class.getEnumConstants()) { + if (errorEnum.code == code) { + return errorEnum; + } + } + return UNKNOWN; + } +} diff --git a/src/main/java/com/dji/sample/common/model/CustomClaim.java b/src/main/java/com/dji/sample/common/model/CustomClaim.java new file mode 100644 index 0000000..85c16a9 --- /dev/null +++ b/src/main/java/com/dji/sample/common/model/CustomClaim.java @@ -0,0 +1,88 @@ +package com.dji.sample.common.model; + +import com.auth0.jwt.interfaces.Claim; +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A custom claim for storing custom information in the token. + * @author sean.zhou + * @date 2021/11/16 + * @version 0.1 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Slf4j +public class CustomClaim { + + /** + * The id of the account. + */ + private String id; + + private String username; + + @JsonAlias("user_type") + private Integer userType; + + @JsonAlias("workspace_id") + private String workspaceId; + + /** + * Convert the custom claim data type to the Map type. + * @return map + */ + public ConcurrentHashMap convertToMap() { + ConcurrentHashMap map = new ConcurrentHashMap<>(4); + try { + Field[] declaredFields = this.getClass().getDeclaredFields(); + for (Field field : declaredFields) { + JsonAlias annotation = field.getAnnotation(JsonAlias.class); + field.setAccessible(true); + // The value of key is named underscore. + map.put(annotation != null ? annotation.value()[0] : field.getName(), + field.get(this).toString()); + } + } catch (IllegalAccessException e) { + log.info("CustomClaim converts failed. {}", this.toString()); + e.printStackTrace(); + } + return map; + } + + /** + * Convert the data in Map into a custom claim object. + * @param claimMap + */ + public CustomClaim (Map claimMap) { + Field[] declaredFields = this.getClass().getDeclaredFields(); + for (Field field : declaredFields) { + field.setAccessible(true); + JsonAlias annotation = field.getAnnotation(JsonAlias.class); + + Claim value = claimMap.get(annotation == null ? field.getName() : annotation.value()[0]); + try { + Class type = field.getType(); + if (Integer.class.equals(type)) { + field.set(this, Integer.valueOf(value.asString())); + continue; + } + if (String.class.equals(type)) { + field.set(this, value.asString()); + continue; + } + } catch (IllegalAccessException e) { + log.info("Claim parses failed. {}", claimMap.toString()); + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/common/model/Pagination.java b/src/main/java/com/dji/sample/common/model/Pagination.java new file mode 100644 index 0000000..74d1e8a --- /dev/null +++ b/src/main/java/com/dji/sample/common/model/Pagination.java @@ -0,0 +1,36 @@ +package com.dji.sample.common.model; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.Data; + +/** + * Used for paging display in the wayline. These field names cannot be changed. + * Because they need to be the same as the pilot. + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +@Data +public class Pagination { + + /** + * The current page number. + */ + private long page; + + /** + * The amount of data displayed per page. + */ + private long pageSize; + + /** + * The total amount of all data. + */ + private long total; + + public Pagination(Page page) { + this.page = page.getCurrent(); + this.pageSize = page.getSize(); + this.total = page.getTotal(); + } +} diff --git a/src/main/java/com/dji/sample/common/model/PaginationData.java b/src/main/java/com/dji/sample/common/model/PaginationData.java new file mode 100644 index 0000000..95e2442 --- /dev/null +++ b/src/main/java/com/dji/sample/common/model/PaginationData.java @@ -0,0 +1,27 @@ +package com.dji.sample.common.model; + +import lombok.Data; + +import java.util.List; + +/** + * The format of the data response when a paginated display is required. + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +@Data +public class PaginationData { + + /** + * The collection in which the data list is stored. + */ + private List list; + + private Pagination pagination; + + public PaginationData(List list, Pagination pagination) { + this.list = list; + this.pagination = pagination; + } +} diff --git a/src/main/java/com/dji/sample/common/model/ResponseResult.java b/src/main/java/com/dji/sample/common/model/ResponseResult.java new file mode 100644 index 0000000..8ad8e1b --- /dev/null +++ b/src/main/java/com/dji/sample/common/model/ResponseResult.java @@ -0,0 +1,69 @@ +package com.dji.sample.common.model; + +import com.dji.sample.common.error.IErrorInfo; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.http.HttpStatus; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JsonInclude +public class ResponseResult { + + public static final int CODE_SUCCESS = 0; + public static final String MESSAGE_SUCCESS = "success"; + + private int code; + + private String message; + + private T data; + + public static ResponseResult success(T data) { + return ResponseResult.builder() + .code(CODE_SUCCESS) + .message(MESSAGE_SUCCESS) + .data(data) + .build(); + } + + public static ResponseResult success() { + return ResponseResult.builder() + .code(0) + .message(MESSAGE_SUCCESS) + .build(); + } + + public static ResponseResult error() { + return ResponseResult.builder() + .code(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .message(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()) + .build(); + } + + public static ResponseResult error(String message) { + return ResponseResult.builder() + .code(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .message(message) + .build(); + } + + public static ResponseResult error(int code, String message) { + return ResponseResult.builder() + .code(code) + .message(message) + .build(); + } + + public static ResponseResult error(IErrorInfo errorInfo) { + return ResponseResult.builder() + .code(errorInfo.getErrorCode()) + .message(errorInfo.getErrorMsg()) + .build(); + } +} diff --git a/src/main/java/com/dji/sample/common/util/JwtUtil.java b/src/main/java/com/dji/sample/common/util/JwtUtil.java new file mode 100644 index 0000000..9c2cb9a --- /dev/null +++ b/src/main/java/com/dji/sample/common/util/JwtUtil.java @@ -0,0 +1,103 @@ +package com.dji.sample.common.util; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.dji.sample.common.model.CustomClaim; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Component +public class JwtUtil { + + private static String issuer; + + private static String subject; + + private static long age; + + private static String secret; + + private static Algorithm algorithm; + + @Value("${jwt.issuer: DJI}") + private void setIssuer(String issuer) { + JwtUtil.issuer = issuer; + } + + @Value("${jwt.subject: CloudApiSample}") + private void setSubject(String subject) { + JwtUtil.subject = subject; + } + + @Value("${jwt.age: 86400}") + private void setAge(long age) { + JwtUtil.age = age * 1000; + } + + @Value("${jwt.secret: CloudApiSample}") + private void setSecret(String secret) { + JwtUtil.secret = secret; + setAlgorithm(); + } + + private void setAlgorithm() { + JwtUtil.algorithm = Algorithm.HMAC256(secret); + } + + /** + * Create a token based on custom information. + * @param claims custom information + * @return token + */ + public static String createToken(Map claims) { + Date now = new Date(); + JWTCreator.Builder builder = JWT.create(); + // Add custom information to the token's payload segment. + claims.forEach(builder::withClaim); + String token = builder.withIssuer(issuer) + .withSubject(subject) + .withIssuedAt(now) + .withExpiresAt(new Date(now.getTime() + age)) + .withNotBefore(now) + .sign(algorithm); + log.debug("token created. " + token); + return token; + } + + /** + * Verify that the token is valid. + * @param token + * @return + * @throws TokenExpiredException + */ + public static DecodedJWT verifyToken(String token) { + try { + JWTVerifier verifier = JWT.require(algorithm).build(); + return verifier.verify(token); + } catch (Exception e) { + log.error(e.getMessage()); + e.printStackTrace(); + return null; + } + } + + /** + * Parses the custom information in the token into a CustomClaim object. + * @param token + * @return custom claim + */ + public static Optional parseToken(String token) { + DecodedJWT jwt = verifyToken(token); + return jwt == null ? Optional.empty() : Optional.of(new CustomClaim(jwt.getClaims())); + } +} diff --git a/src/main/java/com/dji/sample/component/ApplicationBootInitial.java b/src/main/java/com/dji/sample/component/ApplicationBootInitial.java new file mode 100644 index 0000000..226fe1a --- /dev/null +++ b/src/main/java/com/dji/sample/component/ApplicationBootInitial.java @@ -0,0 +1,40 @@ +package com.dji.sample.component; + +import com.dji.sample.manage.model.DeviceStatusManager; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.param.DeviceQueryParam; +import com.dji.sample.manage.service.IDeviceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * @author sean.zhou + * @date 2021/11/24 + * @version 0.1 + */ +@Component +public class ApplicationBootInitial implements CommandLineRunner { + + @Autowired + private IDeviceService deviceService; + + /** + * Subscribe to the devices that exist in the database when the program starts, + * to prevent the data from being different from the pilot side due to program interruptions. + * @param args + * @throws Exception + */ + @Override + public void run(String... args) throws Exception { + deviceService.getDevicesByParams(DeviceQueryParam.builder().build()) + .forEach(device -> { + deviceService.subscribeTopicOnline(device.getDeviceSn()); + DeviceStatusManager.STATUS_MANAGER.put( + DeviceDomainEnum.getVal(device.getDomain()) + "/" + + device.getDeviceSn(), LocalDateTime.now()); + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/AuthInterceptor.java b/src/main/java/com/dji/sample/component/AuthInterceptor.java new file mode 100644 index 0000000..0d3b8f7 --- /dev/null +++ b/src/main/java/com/dji/sample/component/AuthInterceptor.java @@ -0,0 +1,60 @@ +package com.dji.sample.component; + +import com.dji.sample.common.error.CommonErrorEnum; +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.util.JwtUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; + +@Slf4j +@Component +public class AuthInterceptor implements HandlerInterceptor { + + public static final String PARAM_TOKEN = "x-auth-token"; + + public static final String TOKEN_CLAIM = "customClaim"; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String uri = request.getRequestURI(); + log.debug("request uri: {}", uri); + // The options method is passed directly. + if (HttpMethod.OPTIONS.matches(request.getMethod())) { + response.setStatus(HttpStatus.OK.value()); + return false; + } + String token = request.getHeader(PARAM_TOKEN); + // Check if the token exists. + if (!StringUtils.hasText(token)) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + log.error(CommonErrorEnum.NO_TOKEN.getErrorMsg()); + return false; + } + + // Check if the current token is valid. + Optional customClaimOpt = JwtUtil.parseToken(token); + if (customClaimOpt.isEmpty()) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + return false; + } + + // Put the custom data from the token into the request. + request.setAttribute(TOKEN_CLAIM, customClaimOpt.get()); + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + // Delete the custom data in the request after the request ends. + request.removeAttribute(TOKEN_CLAIM); + } +} diff --git a/src/main/java/com/dji/sample/component/CorsFilter.java b/src/main/java/com/dji/sample/component/CorsFilter.java new file mode 100644 index 0000000..a4c245f --- /dev/null +++ b/src/main/java/com/dji/sample/component/CorsFilter.java @@ -0,0 +1,35 @@ +package com.dji.sample.component; + +import org.springframework.stereotype.Component; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static com.dji.sample.component.AuthInterceptor.PARAM_TOKEN; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/22 + */ +@Component +public class CorsFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { + HttpServletResponse res = (HttpServletResponse) response; + res.addHeader("Access-Control-Allow-Credentials", "true"); + res.addHeader("Access-Control-Allow-Origin", "*"); + res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT"); + res.addHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Headers," + + "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, "+ + "Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive," + + " User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma," + PARAM_TOKEN); + if (((HttpServletRequest) request).getMethod().equals("OPTIONS")) { + return; + } + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java new file mode 100644 index 0000000..1d5eb14 --- /dev/null +++ b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java @@ -0,0 +1,33 @@ +package com.dji.sample.component; + +import com.dji.sample.common.model.ResponseResult; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/1 + */ +@ControllerAdvice +@ResponseBody +public class GlobalExceptionHandler { + + /** + * Please do not return directly like this, there is a risk. + * @param e + * @return + */ + @ExceptionHandler(Exception.class) + public ResponseResult exceptionHandler(Exception e) { + e.printStackTrace(); + return ResponseResult.error(e.getLocalizedMessage()); + } + + @ExceptionHandler(NullPointerException.class) + public ResponseResult nullPointerExceptionHandler(NullPointerException e) { + e.printStackTrace(); + return ResponseResult.error("A null object appeared."); + } +} diff --git a/src/main/java/com/dji/sample/component/GlobalScheduleService.java b/src/main/java/com/dji/sample/component/GlobalScheduleService.java new file mode 100644 index 0000000..ea44a62 --- /dev/null +++ b/src/main/java/com/dji/sample/component/GlobalScheduleService.java @@ -0,0 +1,58 @@ +package com.dji.sample.component; + +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.service.IDeviceService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static com.dji.sample.manage.model.DeviceStatusManager.DEFAULT_ALIVE_SECOND; +import static com.dji.sample.manage.model.DeviceStatusManager.STATUS_MANAGER; + +/** + * @author sean.zhou + * @date 2021/11/24 + * @version 0.1 + */ +@Component +@Slf4j +public class GlobalScheduleService { + + @Autowired + private IDeviceService deviceService; + + /** + * Check the status of the devices every 30 seconds. It is recommended to use cache. + */ + @Scheduled(fixedRate = 30, timeUnit = TimeUnit.SECONDS) + private void deviceStatusListen() { + for (Map.Entry entry : STATUS_MANAGER.entrySet()) { + if (entry.getValue().isAfter( + LocalDateTime.now().minusSeconds(DEFAULT_ALIVE_SECOND))) { + continue; + } + + String device = entry.getKey(); + int index = device.indexOf("/"); + + STATUS_MANAGER.remove(device); + + int type = Integer.parseInt(device.substring(0, index)); + String sn = device.substring(index + 1); + // Determine whether it is a gateway device. + if (DeviceDomainEnum.GATEWAY.getVal() == type) { + deviceService.deviceOffline(sn); + deviceService.unsubscribeTopicOffline(sn); + continue; + } + + deviceService.subDeviceOffline(sn); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java b/src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java new file mode 100644 index 0000000..1b2bdf2 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java @@ -0,0 +1,95 @@ +package com.dji.sample.component.mqtt.config; + +import com.dji.sample.component.mqtt.model.ChannelName; +import lombok.extern.slf4j.Slf4j; +import org.springframework.integration.annotation.Router; +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.integration.router.AbstractMessageRouter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHeaders; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Pattern; + +import static com.dji.sample.component.mqtt.model.TopicConst.*; + +/** + * + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Component +@Slf4j +public class InboundMessageRouter extends AbstractMessageRouter { + + @Resource(name = ChannelName.INBOUND) + private MessageChannel inboundChannel; + + @Resource(name = ChannelName.INBOUND_STATUS) + private MessageChannel statusChannel; + + @Resource(name = ChannelName.INBOUND_STATE) + private MessageChannel stateChannel; + + @Resource(name = ChannelName.DEFAULT) + private MessageChannel defaultChannel; + + @Resource(name = ChannelName.INBOUND_SERVICE_REPLY) + private MessageChannel serviceReplyChannel; + + @Resource(name = ChannelName.INBOUND_OSD) + private MessageChannel osdChannel; + + private static final Pattern PATTERN_TOPIC_STATUS = + Pattern.compile("^" + BASIC_PRE + PRODUCT + REGEX_SN + STATUS_SUF + "$"); + + private static final Pattern PATTERN_TOPIC_STATE = + Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + STATE_SUF + "$"); + + private static final Pattern PATTERN_TOPIC_SERVICE_REPLY = + Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + SERVICES_SUF + _REPLY_SUF + "$"); + + private static final Pattern PATTERN_TOPIC_OSD = + Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + OSD_SUF); + + /** + * All mqtt broker messages will arrive here before distributing them to different channels. + * @param message message from mqtt broker + * @return channel + */ + @Override + @Router(inputChannel = ChannelName.INBOUND) + protected Collection determineTargetChannels(Message message) { + MessageHeaders headers = message.getHeaders(); + String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString(); + byte[] payload = (byte[])message.getPayload(); + + // osd + if (PATTERN_TOPIC_OSD.matcher(topic).matches()) { + return Collections.singleton(osdChannel); + } + + log.debug("received topic :{} \t payload :{}", topic, new String(payload)); + + // status + if (PATTERN_TOPIC_STATUS.matcher(topic).matches()) { + return Collections.singleton(statusChannel); + } + + // state + if (PATTERN_TOPIC_STATE.matcher(topic).matches()) { + return Collections.singleton(stateChannel); + } + + // services_reply + if (PATTERN_TOPIC_SERVICE_REPLY.matcher(topic).matches()) { + return Collections.singleton(serviceReplyChannel); + } + return Collections.singleton(defaultChannel); + } +} diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java new file mode 100644 index 0000000..750ba58 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java @@ -0,0 +1,63 @@ +package com.dji.sample.component.mqtt.config; + +import lombok.Data; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; +import org.springframework.integration.mqtt.core.MqttPahoClientFactory; + +/** + * + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Configuration +@Data +@ConfigurationProperties(prefix = "mqtt") +public class MqttConfiguration { + + private String protocol; + + private String host; + + private Integer port; + + private String username; + + private String password; + + private String clientId; + + /** + * The topic to subscribe to immediately when client connects. + */ + private String inboundTopic; + + @Bean + public MqttConnectOptions mqttConnectOptions() { + MqttConnectOptions mqttConnectOptions = new MqttConnectOptions(); + mqttConnectOptions.setServerURIs(new String[]{ + new StringBuilder() + .append(protocol.trim()) + .append("://") + .append(host.trim()) + .append(":") + .append(port) + .toString()}); + mqttConnectOptions.setUserName(username); + mqttConnectOptions.setPassword(password.toCharArray()); + mqttConnectOptions.setAutomaticReconnect(true); + mqttConnectOptions.setKeepAliveInterval(10); + return mqttConnectOptions; + } + + @Bean + public MqttPahoClientFactory mqttClientFactory() { + DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); + factory.setConnectionOptions(mqttConnectOptions()); + return factory; + } +} diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java new file mode 100644 index 0000000..df6f04f --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java @@ -0,0 +1,69 @@ +package com.dji.sample.component.mqtt.config; + +import com.dji.sample.component.mqtt.model.ChannelName; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.endpoint.MessageProducerSupport; +import org.springframework.integration.mqtt.core.MqttPahoClientFactory; +import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; +import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; + +import javax.annotation.Resource; + +/** + * Client configuration for inbound messages. + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Slf4j +@Configuration +@IntegrationComponentScan +public class MqttInboundConfiguration { + + @Autowired + private MqttConfiguration mqttConfiguration; + + @Autowired + private MqttPahoClientFactory mqttClientFactory; + + @Resource(name = ChannelName.INBOUND) + private MessageChannel inboundChannel; + + /** + * Clients of inbound message channels. + * @return + */ + @Bean(name = "adapter") + public MessageProducerSupport mqttInbound() { + MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter( + mqttConfiguration.getClientId() + "_consumer_" + System.currentTimeMillis(), + mqttClientFactory, mqttConfiguration.getInboundTopic().split(",")); + DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter(); + // use byte types uniformly + converter.setPayloadAsBytes(true); + adapter.setConverter(converter); + adapter.setQos(1); + adapter.setOutputChannel(inboundChannel); + return adapter; + } + + /** + * Define a default channel to handle messages that have no effect. + * @return + */ + @Bean + @ServiceActivator(inputChannel = ChannelName.DEFAULT) + public MessageHandler defaultInboundHandler() { + return message -> { + log.info("The default channel does not handle messages."); + }; + } + +} diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java new file mode 100644 index 0000000..2f06938 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java @@ -0,0 +1,90 @@ +package com.dji.sample.component.mqtt.config; + +import com.dji.sample.component.mqtt.model.ChannelName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.ExecutorChannel; +import org.springframework.messaging.MessageChannel; + +import java.util.concurrent.Executor; + +/** + * Definition classes for all channels + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Configuration +public class MqttMessageChannel { + + @Autowired + private Executor threadPool; + + @Bean(name = ChannelName.INBOUND) + public MessageChannel inboundChannel() { + return new ExecutorChannel(threadPool); + } + + @Bean(name = ChannelName.INBOUND_STATUS) + public MessageChannel statusChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_STATUS_ONLINE) + public MessageChannel statusOnlineChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_STATUS_OFFLINE) + public MessageChannel statusOffChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_STATE) + public MessageChannel stateChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_STATE_BASIC) + public MessageChannel stateBasicChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_STATE_PAYLOAD) + public MessageChannel statePayloadChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_SERVICE_REPLY) + public MessageChannel serviceReplyChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_STATE_CAPACITY) + public MessageChannel stateCapacityChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE) + public MessageChannel statePayloadUpdateChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_OSD) + public MessageChannel osdChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.DEFAULT) + public MessageChannel defaultChannel() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.OUTBOUND) + public MessageChannel outboundChannel() { + return new DirectChannel(); + } + +} diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java new file mode 100644 index 0000000..69b919b --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java @@ -0,0 +1,47 @@ +package com.dji.sample.component.mqtt.config; + +import com.dji.sample.component.mqtt.model.ChannelName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.mqtt.core.MqttPahoClientFactory; +import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; +import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; +import org.springframework.messaging.MessageHandler; + +/** + * Client configuration for outbound messages. + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Configuration +public class MqttOutboundConfiguration { + + @Autowired + private MqttConfiguration mqttConfiguration; + + @Autowired + private MqttPahoClientFactory mqttClientFactory; + + /** + * Clients of outbound message channels. + * @return + */ + @Bean + @ServiceActivator(inputChannel = ChannelName.OUTBOUND) + public MessageHandler mqttOutbound() { + MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler( + mqttConfiguration.getClientId() + "_producer_" + System.currentTimeMillis(), + mqttClientFactory); + DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter(); + // use byte types uniformly + converter.setPayloadAsBytes(true); + + messageHandler.setAsync(true); + messageHandler.setDefaultQos(0); + messageHandler.setConverter(converter); + return messageHandler; + } +} diff --git a/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java b/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java new file mode 100644 index 0000000..c8f5f01 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java @@ -0,0 +1,46 @@ +package com.dji.sample.component.mqtt.model; + +/** + * The name of all channels. + * + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +public class ChannelName { + + public static final String INBOUND = "inbound"; + + public static final String INBOUND_STATUS = "inboundStatus"; + + public static final String INBOUND_STATUS_ROUTER = "inboundStatusRouter"; + + public static final String INBOUND_STATUS_ONLINE = "inboundStatusOnline"; + + public static final String INBOUND_STATUS_OFFLINE = "inboundStatusOffline"; + + public static final String INBOUND_STATE = "inboundState"; + + public static final String INBOUND_STATE_SPLITTER = "inboundStateSplitter"; + + public static final String INBOUND_STATE_ROUTER = "inboundStateRouter"; + + public static final String INBOUND_STATE_BASIC = "inboundStateBasic"; + + public static final String INBOUND_STATE_PAYLOAD = "inboundStatePayload"; + + public static final String INBOUND_STATE_PAYLOAD_UPDATE = "inboundStatePayloadUpdate"; + + public static final String INBOUND_STATE_CAPACITY = "inboundStateCapacity"; + + public static final String INBOUND_STATE_LIST = "inboundStateList"; + + public static final String INBOUND_SERVICE_REPLY = "inboundStateServiceReply"; + + public static final String INBOUND_OSD = "inboundOsd"; + + public static final String DEFAULT = "default"; + + public static final String OUTBOUND = "outbound"; + +} diff --git a/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java new file mode 100644 index 0000000..5a6fe45 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java @@ -0,0 +1,29 @@ +package com.dji.sample.component.mqtt.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * Unified topic receiving format. + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class CommonTopicReceiver { + + /** + * The command is sent and the response is matched by the tid and bid fields in the message, + * and the reply should keep the tid and bid the same. + */ + private String tid; + + private String bid; + + private String method; + + private Long timestamp; + + private T data; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java new file mode 100644 index 0000000..effd5c1 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java @@ -0,0 +1,33 @@ +package com.dji.sample.component.mqtt.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Unified Topic response format + * @author sean.zhou + * @date 2021/11/15 + * @version 0.1 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class CommonTopicResponse { + + /** + * The command is sent and the response is matched by the tid and bid fields in the message, + * and the reply should keep the tid and bid the same. + */ + private String tid; + + private String bid; + + private String method; + + private T data; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java b/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java new file mode 100644 index 0000000..667c969 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java @@ -0,0 +1,29 @@ +package com.dji.sample.component.mqtt.model; + +/** + * All the topics that need to be used in the project. + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +public class TopicConst { + + public static final String BASIC_PRE = "sys/"; + + public static final String THING_MODEL_PRE = "thing/"; + + public static final String PRODUCT = "product/"; + + public static final String STATUS_SUF = "/status"; + + public static final String _REPLY_SUF = "_reply"; + + public static final String STATE_SUF = "/state"; + + public static final String SERVICES_SUF = "/services"; + + public static final String OSD_SUF = "/osd"; + + public static final String REGEX_SN = "[A-Za-z0-9]+"; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java b/src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java new file mode 100644 index 0000000..522a4f8 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java @@ -0,0 +1,29 @@ +package com.dji.sample.component.mqtt.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * The data format of the state topic. + * @author sean.zhou + * @date 2021/11/17 + * @version 0.1 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TopicStateReceiver { + + private String tid; + + private String bid; + + private Long timestamp; + + /** + * The sn of the gateway device. + */ + private String gateway; + + private T data; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java b/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java new file mode 100644 index 0000000..7930d41 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java @@ -0,0 +1,27 @@ +package com.dji.sample.component.mqtt.service; + +import com.dji.sample.component.mqtt.model.CommonTopicResponse; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/25 + */ +public interface IMessageSenderService { + + /** + * Publish a message to a specific topic. + * @param topic target + * @param response message + */ + void publish(String topic, CommonTopicResponse response); + + /** + * Use a specific qos to push messages to a specific topic. + * @param topic target + * @param qos qos + * @param response message + */ + void publish(String topic, int qos, CommonTopicResponse response); + +} diff --git a/src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java b/src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java new file mode 100644 index 0000000..cb50b12 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java @@ -0,0 +1,33 @@ +package com.dji.sample.component.mqtt.service; + +import com.dji.sample.component.mqtt.model.ChannelName; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.stereotype.Component; + +/** + * + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Component +@MessagingGateway(defaultRequestChannel = ChannelName.OUTBOUND) +public interface IMqttMessageGateway { + + /** + * Publish a message to a specific topic. + * @param topic target + * @param payload message + */ + void publish(@Header(MqttHeaders.TOPIC) String topic, byte[] payload); + + /** + * Use a specific qos to push messages to a specific topic. + * @param topic target + * @param payload message + * @param qos qos + */ + void publish(@Header(MqttHeaders.TOPIC) String topic, byte[] payload, @Header(MqttHeaders.QOS) int qos); +} diff --git a/src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java b/src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java new file mode 100644 index 0000000..6983109 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java @@ -0,0 +1,38 @@ +package com.dji.sample.component.mqtt.service; + +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.messaging.handler.annotation.Header; + +/** + * + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +public interface IMqttTopicService { + + /** + * Subscribe to a specific topic. + * @param topic target + */ + void subscribe(@Header(MqttHeaders.TOPIC) String topic); + + /** + * Subscribe to a specific topic using a specific qos. + * @param topic target + * @param qos qos + */ + void subscribe(@Header(MqttHeaders.TOPIC) String topic, int qos); + + /** + * Unsubscribe from a specific topic. + * @param topic target + */ + void unsubscribe(@Header(MqttHeaders.TOPIC) String topic); + + /** + * Get all the subscribed topics. + * @return topics + */ + String[] getSubscribedTopic(); +} diff --git a/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java b/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java new file mode 100644 index 0000000..2510ead --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java @@ -0,0 +1,50 @@ +package com.dji.sample.component.mqtt.service.impl; + +import com.dji.sample.component.mqtt.model.CommonTopicResponse; +import com.dji.sample.component.mqtt.service.IMessageSenderService; +import com.dji.sample.component.mqtt.service.IMqttMessageGateway; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author sean.zhou + * @date 2021/11/16 + * @version 0.1 + */ +@Service +@Slf4j +public class MessageSenderServiceImpl implements IMessageSenderService { + + @Autowired + private IMqttMessageGateway messageGateway; + + public void publish(String topic, CommonTopicResponse response) { + try { + ObjectMapper mapper = new ObjectMapper(); + // Only parameters whose value is not null will be serialised. + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + messageGateway.publish(topic, mapper.writeValueAsBytes(response)); + } catch (JsonProcessingException e) { + log.info("Failed to publish the message. {}", response.toString()); + e.printStackTrace(); + } + } + + public void publish(String topic, int qos, CommonTopicResponse response) { + try { + ObjectMapper mapper = new ObjectMapper(); + // Only parameters whose value is not null will be serialised. + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + messageGateway.publish(topic, mapper.writeValueAsBytes(response), qos); + } catch (JsonProcessingException e) { + log.info("Failed to publish the message. {}", response.toString()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java b/src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java new file mode 100644 index 0000000..6880ecf --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java @@ -0,0 +1,44 @@ +package com.dji.sample.component.mqtt.service.impl; + +import com.dji.sample.component.mqtt.service.IMqttTopicService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Component +@Slf4j +public class MqttTopicServiceImpl implements IMqttTopicService { + + @Resource + private MqttPahoMessageDrivenChannelAdapter adapter; + + @Override + public void subscribe(String topic) { + log.debug("subscribe topic: {}", topic); + adapter.addTopic(topic); + } + + @Override + public void subscribe(String topic, int qos) { + log.debug("subscribe topic: {}", topic); + adapter.addTopic(topic, qos); + } + + @Override + public void unsubscribe(String topic) { + log.debug("unsubscribe topic: {}", topic); + adapter.removeTopic(topic); + } + + public String[] getSubscribedTopic() { + return adapter.getTopic(); + } +} diff --git a/src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java b/src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java new file mode 100644 index 0000000..2992641 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java @@ -0,0 +1,24 @@ +package com.dji.sample.component.mybatis; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +@Configuration +public class MybatisPlusConfiguration { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // select database + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} diff --git a/src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java b/src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java new file mode 100644 index 0000000..88170ed --- /dev/null +++ b/src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java @@ -0,0 +1,37 @@ +package com.dji.sample.component.mybatis; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +/** + * Automatic filling for set values + */ +@Component +public class MybatisPlusMetaObjectHandler implements MetaObjectHandler { + + /** + * Automatic filling when inserting into the database. + * @param metaObject + */ + @Override + public void insertFill(MetaObject metaObject) { + this.strictInsertFill(metaObject, "createTime", Long.class, + LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli()); + this.strictInsertFill(metaObject, "updateTime", Long.class, + LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli()); + } + + /** + * Automatic filling when updating the data. + * @param metaObject + */ + @Override + public void updateFill(MetaObject metaObject) { + this.strictUpdateFill(metaObject, "updateTime", Long.class, + LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli()); + } +} diff --git a/src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java b/src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java new file mode 100644 index 0000000..32624ea --- /dev/null +++ b/src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java @@ -0,0 +1,104 @@ +package com.dji.sample.component.oss.model; + +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +@Configuration +public class AliyunOSSConfiguration { + + /** + * default + */ + public static final String PROVIDER = "ali"; + + /** + * Whether to use the current storage service. + */ + public static boolean enable; + + /** + * The protocol needs to be included at the beginning of the address. + */ + public static String endpoint; + + public static String accessKey; + + public static String secretKey; + + public static String region; + + public static Long expire; + + public static String roleSessionName; + + public static String roleArn; + + public static String bucket; + + public static String objectDirPrefix; + + @Value("${aliyun.oss.endpoint}") + private void setEndpoint(String endpoint) { + AliyunOSSConfiguration.endpoint = endpoint; + } + + @Value("${aliyun.oss.access-key}") + private void setAccessKey(String accessKey) { + AliyunOSSConfiguration.accessKey = accessKey; + } + + @Value("${aliyun.oss.secret-key}") + private void setSecretKey(String secretKey) { + AliyunOSSConfiguration.secretKey = secretKey; + } + + @Value("${aliyun.oss.region}") + private void setRegion(String region) { + AliyunOSSConfiguration.region = region; + } + + @Value("${aliyun.oss.expire: 3600}") + private void setExpire(Long expire) { + AliyunOSSConfiguration.expire = expire; + } + + @Value("${aliyun.oss.enable: false}") + private void setEnable(boolean enable) { + AliyunOSSConfiguration.enable = enable; + } + + @Value("${aliyun.oss.role-session-name}") + private void setRoleSessionName(String roleSessionName) { + AliyunOSSConfiguration.roleSessionName = roleSessionName; + } + + @Value("${aliyun.oss.role-arn}") + private void setRoleArn(String roleArn) { + AliyunOSSConfiguration.roleArn = roleArn; + } + + @Value("${aliyun.oss.bucket}") + private void setBucket(String bucket) { + AliyunOSSConfiguration.bucket = bucket; + } + + @Value("${aliyun.oss.object-dir-prefix: wayline}") + private void setObjectDir(String objectDirPrefix) { + AliyunOSSConfiguration.objectDirPrefix = objectDirPrefix; + } + + @Bean + @Lazy + public OSS ossClient() { + return new OSSClientBuilder().build(endpoint, accessKey, secretKey); + } +} diff --git a/src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java b/src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java new file mode 100644 index 0000000..5768023 --- /dev/null +++ b/src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java @@ -0,0 +1,93 @@ +package com.dji.sample.component.oss.model; + +import io.minio.MinioClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.util.StringUtils; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/7 + */ +@Configuration +public class MinIOConfiguration { + + /** + * default + */ + public static final String PROVIDER = "aws"; + + /** + * Whether to use the current storage service. + */ + public static boolean enable; + + public static String endpoint; + + public static String accessKey; + + public static String secretKey; + + public static String region; + + public static String bucket; + + public static Integer expire; + + public static String objectDirPrefix; + + @Value("${minio.endpoint: http://localhost:9000/}") + private void setEndpoint(String endpoint) { + MinIOConfiguration.endpoint = endpoint; + } + + @Value("${minio.access-key: minioadmin}") + private void setAccessKey(String accessKey) { + MinIOConfiguration.accessKey = accessKey; + } + + @Value("${minio.secret-key: minioadmin}") + private void setSecretKey(String secretKey) { + MinIOConfiguration.secretKey = secretKey; + } + + @Value("${minio.region: }") + private void setRegion(String region) { + MinIOConfiguration.region = region; + } + + @Value("${minio.bucket: test}") + private void setBucket(String bucket) { + MinIOConfiguration.bucket = bucket; + } + + @Value("${minio.expire: 3600}") + private void setExpire(Integer expire) { + MinIOConfiguration.expire = expire; + } + + @Value("${minio.enable: false}") + private void setEnable(boolean enable) { + MinIOConfiguration.enable = enable; + } + + @Value("${minio.object-dir-prefix: wayline}") + private void setObjectDir(String objectDirPrefix) { + MinIOConfiguration.objectDirPrefix = objectDirPrefix; + } + + @Bean + @Lazy + public MinioClient minioClient() { + MinioClient.Builder builder = MinioClient.builder() + .endpoint(endpoint) + .credentials(accessKey, secretKey); + if (StringUtils.hasText(region)) { + builder.region(MinIOConfiguration.region); + } + return builder.build(); + } +} diff --git a/src/main/java/com/dji/sample/component/oss/service/IOssService.java b/src/main/java/com/dji/sample/component/oss/service/IOssService.java new file mode 100644 index 0000000..beb9db6 --- /dev/null +++ b/src/main/java/com/dji/sample/component/oss/service/IOssService.java @@ -0,0 +1,27 @@ +package com.dji.sample.component.oss.service; + +import com.dji.sample.media.model.CredentialsDTO; + +import java.net.URL; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/23 + */ +public interface IOssService { + + /** + * Get temporary credentials. + * @return + */ + CredentialsDTO getCredentials(); + + /** + * Get the address of the object based on the bucket name and the object name. + * @param bucket bucket name + * @param objectKey object name + * @return download link + */ + URL getObjectUrl(String bucket, String objectKey); +} diff --git a/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java b/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java new file mode 100644 index 0000000..f9f92d3 --- /dev/null +++ b/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java @@ -0,0 +1,68 @@ +package com.dji.sample.component.oss.service.impl; + +import com.aliyun.oss.OSS; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.profile.DefaultProfile; +import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest; +import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse; +import com.dji.sample.component.oss.model.AliyunOSSConfiguration; +import com.dji.sample.component.oss.service.IOssService; +import com.dji.sample.media.model.CredentialsDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.net.URL; +import java.util.Date; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/23 + */ +@Service +@Slf4j +public class AliyunOssServiceImpl implements IOssService { + + @Autowired + private OSS ossClient; + + @Override + public CredentialsDTO getCredentials() { + + try { + DefaultProfile profile = DefaultProfile.getProfile( + AliyunOSSConfiguration.region, AliyunOSSConfiguration.accessKey, AliyunOSSConfiguration.secretKey); + IAcsClient client = new DefaultAcsClient(profile); + + AssumeRoleRequest request = new AssumeRoleRequest(); + request.setDurationSeconds(AliyunOSSConfiguration.expire); + request.setRoleArn(AliyunOSSConfiguration.roleArn); + request.setRoleSessionName(AliyunOSSConfiguration.roleSessionName); + + AssumeRoleResponse response = client.getAcsResponse(request); + return new CredentialsDTO(response.getCredentials(), AliyunOSSConfiguration.expire); + + } catch (ClientException e) { + log.debug("Failed to obtain sts."); + e.printStackTrace(); + } + return null; + } + + @Override + public URL getObjectUrl(String bucket, String objectKey) { + if (!StringUtils.hasText(bucket) || !StringUtils.hasText(objectKey)) { + return null; + } + // First check if the object can be fetched. + ossClient.getObject(bucket, objectKey); + + return ossClient.generatePresignedUrl(bucket, objectKey, + new Date(System.currentTimeMillis() + AliyunOSSConfiguration.expire * 1000)); + } + +} diff --git a/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java b/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java new file mode 100644 index 0000000..1b5e74e --- /dev/null +++ b/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java @@ -0,0 +1,66 @@ +package com.dji.sample.component.oss.service.impl; + +import com.dji.sample.component.oss.model.MinIOConfiguration; +import com.dji.sample.component.oss.service.IOssService; +import com.dji.sample.media.model.CredentialsDTO; +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.MinioClient; +import io.minio.credentials.AssumeRoleProvider; +import io.minio.errors.*; +import io.minio.http.Method; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.net.URL; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/23 + */ +@Service +@Slf4j +public class MinIOServiceImpl implements IOssService { + + @Autowired + private MinioClient client; + + @Override + public CredentialsDTO getCredentials() { + try { + AssumeRoleProvider provider = new AssumeRoleProvider(MinIOConfiguration.endpoint, MinIOConfiguration.accessKey, + MinIOConfiguration.secretKey, MinIOConfiguration.expire, + null, null, null, null, null, null); + return new CredentialsDTO(provider.fetch(), MinIOConfiguration.expire); + } catch (NoSuchAlgorithmException e) { + log.debug("Failed to obtain sts."); + e.printStackTrace(); + } + return null; + } + + @Override + public URL getObjectUrl(String bucket, String objectKey) { + try { + return new URL( + client.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucket) + .object(objectKey) + .expiry(MinIOConfiguration.expire) + .build())); + } catch (ErrorResponseException | InsufficientDataException | InternalException | + InvalidKeyException | InvalidResponseException | IOException | + NoSuchAlgorithmException | XmlParserException | ServerException e) { + log.error("The file does not exist on the oss."); + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java b/src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java new file mode 100644 index 0000000..68f070e --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java @@ -0,0 +1,70 @@ +package com.dji.sample.component.websocket.config; + +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.util.JwtUtil; +import com.dji.sample.component.AuthInterceptor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.support.DefaultHandshakeHandler; + +import javax.servlet.http.HttpServletRequest; +import java.security.Principal; +import java.util.Map; +import java.util.Optional; + +/** + * @author sean.zhou + * @date 2021/11/16 + * @version 0.1 + */ +@Slf4j +@Component +public class AuthPrincipalHandler extends DefaultHandshakeHandler { + + @Override + protected boolean isValidOrigin(ServerHttpRequest request) { + + if (request instanceof ServletServerHttpRequest) { + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + String token = servletRequest.getParameter(AuthInterceptor.PARAM_TOKEN); + + if (!StringUtils.hasText(token)) { + return false; + } + + Optional customClaim = JwtUtil.parseToken(token); + if (customClaim.isEmpty()) { + return false; + } + + servletRequest.setAttribute(AuthInterceptor.TOKEN_CLAIM, customClaim.get()); + return true; + } + return false; + + } + + /** + * The principal's name: {workspaceId}/{userType}/{userId} + * @param request + * @param wsHandler + * @param attributes + * @return + */ + @Override + protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map attributes) { + if (request instanceof ServletServerHttpRequest) { + + // get the custom claim + CustomClaim claim = (CustomClaim) ((ServletServerHttpRequest) request).getServletRequest() + .getAttribute(AuthInterceptor.TOKEN_CLAIM); + + return () -> claim.getWorkspaceId() + "/" + claim.getUserType() + "/" + claim.getId(); + } + return () -> null; + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java b/src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java new file mode 100644 index 0000000..cd13365 --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java @@ -0,0 +1,25 @@ +package com.dji.sample.component.websocket.config; + +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/24 + */ +public class ConcurrentWebSocketSession extends ConcurrentWebSocketSessionDecorator { + + private static final int SEND_BUFFER_SIZE_LIMIT = 1024 * 1024; + + private static final int SEND_TIME_LIMIT = 1000; + + private ConcurrentWebSocketSession(WebSocketSession delegate, int sendTimeLimit, int bufferSizeLimit) { + super(delegate, sendTimeLimit, bufferSizeLimit); + } + + ConcurrentWebSocketSession(WebSocketSession delegate) { + this(delegate, SEND_TIME_LIMIT, SEND_BUFFER_SIZE_LIMIT); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java b/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java new file mode 100644 index 0000000..4fdd774 --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java @@ -0,0 +1,20 @@ +package com.dji.sample.component.websocket.config; + +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory; + +/** + * + * @author sean.zhou + * @date 2021/11/16 + * @version 0.1 + */ +@Component +public class WebSocketDefaultFactory implements WebSocketHandlerDecoratorFactory { + + @Override + public WebSocketHandler decorate(WebSocketHandler handler) { + return new WebSocketDefaultHandler(handler); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java b/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java new file mode 100644 index 0000000..d4c7e7a --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java @@ -0,0 +1,60 @@ +package com.dji.sample.component.websocket.config; + +import com.dji.sample.component.websocket.model.WebSocketManager; +import com.dji.sample.component.websocket.service.ISendMessageService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.WebSocketHandlerDecorator; + +import java.security.Principal; + +/** + * + * @author sean.zhou + * @date 2021/11/16 + * @version 0.1 + */ +@Slf4j +public class WebSocketDefaultHandler extends WebSocketHandlerDecorator { + + @Autowired + private ISendMessageService sendMessageService; + + WebSocketDefaultHandler(WebSocketHandler delegate) { + super(delegate); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + Principal principal = session.getPrincipal(); + if (StringUtils.hasText(principal.getName())) { + WebSocketManager.put(principal.getName(), new ConcurrentWebSocketSession(session)); + log.debug("{} is connected. ID: {}. WebSocketSession[current count: {}]", + principal.getName(), session.getId(), WebSocketManager.getConnectedCount()); + return; + } + session.close(); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { + Principal principal = session.getPrincipal(); + if (StringUtils.hasText(principal.getName())) { + WebSocketManager.remove(principal.getName(), session.getId()); + log.debug("{} is disconnected. ID: {}. WebSocketSession[current count: {}]", + principal.getName(), session.getId(), WebSocketManager.getConnectedCount()); + } + + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { + log.debug("received message: {}", message.getPayload()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java b/src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java new file mode 100644 index 0000000..85a696c --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java @@ -0,0 +1,39 @@ +package com.dji.sample.component.websocket.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration; + +/** + * + * @author sean.zhou + * @date 2021/11/17 + * @version 0.1 + */ +@EnableWebSocketMessageBroker +@Configuration +public class WebSocketMessageConfiguration implements WebSocketMessageBrokerConfigurer { + + @Autowired + private AuthPrincipalHandler authPrincipalHandler; + + @Autowired + private WebSocketDefaultFactory webSocketDefaultFactory; + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + // Set the WebSocket connection address + registry.addEndpoint("/api/v1/ws").setAllowedOriginPatterns("*") + .setHandshakeHandler(authPrincipalHandler); + } + + @Override + public void configureWebSocketTransport(WebSocketTransportRegistration registry) { + registry.addDecoratorFactory(webSocketDefaultFactory); + registry.setTimeToFirstMessage(60000 * 60 * 24 * 10); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java b/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java new file mode 100644 index 0000000..6ac35c2 --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java @@ -0,0 +1,37 @@ +package com.dji.sample.component.websocket.model; + +/** + * @author sean + * @version 0.1 + * @date 2021/11/26 + */ +public enum BizCodeEnum { + + DEVICE_ONLINE("device_online"), + + DEVICE_OFFLINE("device_offline"), + + DEVICE_UPDATE_TOPO("device_update_topo"), + + DEVICE_OSD("device_osd"), + + GATEWAY_OSD("gateway_osd"), + + MAP_ELEMENT_CREATE("map_element_create"), + + MAP_ELEMENT_UPDATE("map_element_update"), + + MAP_ELEMENT_DELETE("map_element_delete"), + + MAP_GROUP_REFRESH("map_group_refresh"); + + private String code; + + BizCodeEnum(String code) { + this.code = code; + } + + public String getCode() { + return code; + } +} diff --git a/src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java b/src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java new file mode 100644 index 0000000..c1d162d --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java @@ -0,0 +1,30 @@ +package com.dji.sample.component.websocket.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +/** + * The format of WebSocket messages that the pilot can receive. + * @author sean.zhou + * @date 2021/11/17 + * @version 0.1 + */ +@Data +@Builder +public class CustomWebSocketMessage { + + /** + * @see BizCodeEnum + * specific value + */ + @JsonProperty("biz_code") + private String bizCode; + + @Builder.Default + private String version = "1.0"; + + private Long timestamp; + + private T data; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java b/src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java new file mode 100644 index 0000000..da48ae1 --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java @@ -0,0 +1,112 @@ +package com.dji.sample.component.websocket.model; + +import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; +import com.dji.sample.manage.model.enums.UserTypeEnum; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manage all WebSocket connection objects. + * @author sean.zhou + * @date 2021/11/16 + * @version 0.1 + */ +@Slf4j +public class WebSocketManager { + + private static final ConcurrentHashMap>> MANAGER = new ConcurrentHashMap<>(16); + + /** + * WebSocket connection from the pilot. + */ + private static final Set PILOT_SESSION = ConcurrentHashMap.newKeySet(16); + + /** + * WebSocket connection from the web. + */ + private static final Set WEB_SESSION = ConcurrentHashMap.newKeySet(16); + + public static void put(String key, ConcurrentWebSocketSession val) { + + String[] name = key.split("/"); + if (name.length != 3) { + log.debug("The key is out of format. [{workspaceId}/{userType}/{userId}]"); + return; + } + + ConcurrentHashMap> workspaceSessions = + MANAGER.getOrDefault(name[0], new ConcurrentHashMap<>(16)); + + ConcurrentHashMap userSessions = workspaceSessions.getOrDefault( + name[2], new ConcurrentHashMap<>(16)); + userSessions.put(val.getId(), val); + workspaceSessions.put(name[2], userSessions); + MANAGER.put(name[0], workspaceSessions); + + getSetByUserType(Integer.valueOf(name[1])).add(val); + + } + + public static void remove(String key, String sessionId) { + String[] name = key.split("/"); + if (name.length != 3) { + log.debug("The key is out of format. [{workspaceId}/{userType}/{userId}]"); + return; + } + ConcurrentHashMap userSession = MANAGER.get(name[0]).get(name[2]); + + Set typeSession = getSetByUserType(Integer.valueOf(name[1])); + + ConcurrentWebSocketSession session = userSession.get(sessionId); + typeSession.remove(session); + userSession.remove(sessionId); + } + + public static int getConnectedCount() { + return PILOT_SESSION.size() + WEB_SESSION.size(); + } + + public static Collection getValueWithWorkspace(String workspaceId) { + Set sessions = ConcurrentHashMap.newKeySet(); + + MANAGER.get(workspaceId) + .forEach((userId, userSessions) -> { + sessions.addAll(userSessions.values()); + }); + return sessions; + } + + public static Collection getValueWithWorkspaceAndUserType(String workspaceId, Integer userType) { + Set sessions = ConcurrentHashMap.newKeySet(); + Set typeSessions = getSetByUserType(userType); + + MANAGER.getOrDefault(workspaceId, new ConcurrentHashMap<>()) + .forEach((userId, userSessions) -> { + Collection sessionList = userSessions.values(); + if (!sessionList.isEmpty()) { + ConcurrentWebSocketSession session = sessionList.iterator().next(); + if (typeSessions.contains(session)) { + sessions.addAll(sessionList); + } + } + }); + return sessions; + } + + private static Set getSetByUserType(Integer userType) { + if (UserTypeEnum.PILOT.getVal() == userType) { + return PILOT_SESSION; + } + + if (UserTypeEnum.WEB.getVal() == userType) { + return WEB_SESSION; + } + return new HashSet<>(); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java b/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java new file mode 100644 index 0000000..e1d67e7 --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java @@ -0,0 +1,28 @@ +package com.dji.sample.component.websocket.service; + +import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; +import com.dji.sample.component.websocket.model.CustomWebSocketMessage; + +import java.util.Collection; + +/** + * @author sean.zhou + * @date 2021/11/24 + * @version 0.1 + */ +public interface ISendMessageService { + + /** + * Send a message to the specific connection. + * @param session A WebSocket connection object + * @param message message + */ + void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message); + + /** + * Send the same message to specific connection. + * @param sessions A collection of WebSocket connection objects. + * @param message message + */ + void sendBatch(Collection sessions, CustomWebSocketMessage message); +} diff --git a/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java b/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java new file mode 100644 index 0000000..8ee6131 --- /dev/null +++ b/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java @@ -0,0 +1,72 @@ +package com.dji.sample.component.websocket.service.impl; + +import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; +import com.dji.sample.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.component.websocket.service.ISendMessageService; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.socket.TextMessage; + +import java.io.IOException; +import java.util.Collection; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/24 + */ +@Service +@Slf4j +public class SendMessageServiceImpl implements ISendMessageService { + + @Override + public void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message) { + if (session == null) { + return; + } + + try { + if (!session.isOpen()) { + session.close(); + log.debug("This session is closed."); + return; + } + + ObjectMapper mapper = new ObjectMapper(); + + session.sendMessage(new TextMessage(mapper.writeValueAsBytes(message))); + } catch (IOException e) { + log.info("Failed to publish the message. {}", message.toString()); + e.printStackTrace(); + } + } + + @Override + public void sendBatch(Collection sessions, CustomWebSocketMessage message) { + if (sessions.isEmpty()) { + return; + } + + try { + + ObjectMapper mapper = new ObjectMapper(); + TextMessage data = new TextMessage(mapper.writeValueAsBytes(message)); + + for (ConcurrentWebSocketSession session : sessions) { + if (!session.isOpen()) { + session.close(); + log.debug("This session is closed."); + return; + } + session.sendMessage(data); + + } + + } catch (IOException e) { + log.info("Failed to publish the message. {}", message.toString()); + + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java b/src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java new file mode 100644 index 0000000..0dfc05c --- /dev/null +++ b/src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java @@ -0,0 +1,35 @@ +package com.dji.sample.configuration; + +import com.dji.sample.component.AuthInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +public class GlobalMVCConfigurer implements WebMvcConfigurer { + + @Autowired + private AuthInterceptor authInterceptor; + + private static List excludePaths = new ArrayList<>(); + + @Value("${url.manage.prefix}") + private String managePrefix; + + @Value("${url.manage.version}") + private String manageVersion; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // Exclude the login interface. + excludePaths.add(managePrefix + manageVersion + "/login"); + excludePaths.add(managePrefix + manageVersion + "/token/refresh"); + // Intercept for all request interfaces. + registry.addInterceptor(authInterceptor).addPathPatterns("/**").excludePathPatterns(excludePaths); + } +} diff --git a/src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java b/src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java new file mode 100644 index 0000000..af7a92d --- /dev/null +++ b/src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java @@ -0,0 +1,42 @@ +package com.dji.sample.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.*; + +/** + * + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +@Configuration +public class GlobalThreadPoolConfiguration { + + @Value("${thread.pool.core-pool-size: 10}") + private int corePoolSize; + + @Value("${thread.pool.maximum-pool-size: 20}") + private int maximumPoolSize; + + @Value("${thread.pool.keep-alive-time: 60}") + private long keepAliveTime; + + @Value("${thread.pool.queue.capacity: 1000}") + private int capacity; + + /** + * A custom thread pool. + * @return + */ + @Bean + public Executor threadPool() { + return new ThreadPoolExecutor(corePoolSize, + maximumPoolSize, keepAliveTime, + TimeUnit.SECONDS, new LinkedBlockingQueue<>(capacity), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); + } + +} diff --git a/src/main/java/com/dji/sample/manage/controller/DeviceController.java b/src/main/java/com/dji/sample/manage/controller/DeviceController.java new file mode 100644 index 0000000..08a245d --- /dev/null +++ b/src/main/java/com/dji/sample/manage/controller/DeviceController.java @@ -0,0 +1,138 @@ +package com.dji.sample.manage.controller; + +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.AuthInterceptor; +import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.dji.sample.component.mqtt.model.CommonTopicResponse; +import com.dji.sample.component.websocket.model.BizCodeEnum; +import com.dji.sample.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.component.websocket.model.WebSocketManager; +import com.dji.sample.component.websocket.service.ISendMessageService; +import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.dto.WorkspaceDTO; +import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.model.param.DeviceQueryParam; +import com.dji.sample.manage.model.receiver.StatusGatewayReceiver; +import com.dji.sample.manage.service.IDeviceService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.messaging.Message; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/15 + */ +@RestController +@Slf4j +@RequestMapping("${url.manage.prefix}${url.manage.version}/devices") +public class DeviceController { + + @Autowired + private IDeviceService deviceService; + + @Autowired + private ISendMessageService sendMessageService; + + /** + * Handles the message that the drone goes online. + * @param receiver The drone information is not empty. + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS_ONLINE, outputChannel = ChannelName.OUTBOUND) + public void deviceOnline(CommonTopicReceiver receiver) { + boolean online = deviceService.deviceOnline(receiver.getData()); + if (online) { + // Notify pilot that the drone is online successfully. + deviceService.publishStatusReply(receiver.getData().getSn(), + CommonTopicResponse.builder() + .tid(receiver.getTid()) + .bid(receiver.getBid()) + .build()); + + // Publish the latest device topology information in the current workspace to the pilot. + deviceService.pushDeviceOnlineTopo(WorkspaceDTO.DEFAULT_WORKSPACE_ID, + receiver.getData().getSn(), receiver.getData().getSubDevices().get(0).getSn()); + } + } + + /** + * Handles the message that the drone goes offline. + * @param receiver The drone information is empty. + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS_OFFLINE, outputChannel = ChannelName.OUTBOUND) + public void deviceOffline(CommonTopicReceiver receiver) { + + boolean offline = deviceService.deviceOffline(receiver.getData().getSn()); + if (offline) { + // Notify pilot that the device is offline successfully. + deviceService.publishStatusReply(receiver.getData().getSn(), + CommonTopicResponse.builder() + .tid(receiver.getTid()) + .bid(receiver.getBid()) + .build()); + + // Publish the latest device topology information in the current workspace to the pilot. + deviceService.pushDeviceOfflineTopo(WorkspaceDTO.DEFAULT_WORKSPACE_ID, receiver.getData().getSn()); + } + } + + /** + * Get the topology list of all devices in the current user workspace. + * @param request + * @return + */ + @GetMapping("/devices") + public ResponseResult> getDevices(HttpServletRequest request) { + // Get information about the current user. + CustomClaim claim = (CustomClaim)request.getAttribute(AuthInterceptor.TOKEN_CLAIM); + String workspaceId = claim.getWorkspaceId(); + // Get information about the devices in the current user's workspace. + List devicesList = deviceService.getDevicesTopoForWeb(workspaceId); + + return ResponseResult.success(devicesList); + } + + @ServiceActivator(inputChannel = ChannelName.INBOUND_OSD) + public void osdRealTime(Message message) { + String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString(); + byte[] payload = (byte[])message.getPayload(); + deviceService.handleOSD(topic, payload); + } + + /** + * Handles the payloads data of the drone. + * @param deviceSn drone's sn + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE) + public void pushWebSocketDevices(String deviceSn) { + List devicesList = deviceService.getDevicesByParams( + DeviceQueryParam.builder() + .deviceSn(deviceSn) + .build()); + // Get drone information based on the sn of the drone. The sn of the drone is unique. + DeviceDTO device = devicesList.get(0); + // Set the remote controller and payloads information of the drone. + deviceService.spliceDeviceTopo(device); + + CustomWebSocketMessage wsMessage = CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.DEVICE_UPDATE_TOPO.getCode()) + .data(device) + .build(); + // Update the topology of the drone via WebSocket notifications to the web side. + sendMessageService.sendBatch(WebSocketManager + .getValueWithWorkspaceAndUserType( + device.getWorkspaceId(), UserTypeEnum.WEB.getVal()), + wsMessage); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java b/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java new file mode 100644 index 0000000..0c0ba2c --- /dev/null +++ b/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java @@ -0,0 +1,66 @@ +package com.dji.sample.manage.controller; + +import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.manage.model.receiver.DeviceBasicReceiver; +import com.dji.sample.manage.model.receiver.DevicePayloadReceiver; +import com.dji.sample.manage.service.IDevicePayloadService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +@RestController +@Slf4j +public class DevicePayloadController { + + @Autowired + private IDevicePayloadService devicePayloadService; + + /** + * Handles the data for the payload messages in the state topic. + * @param payloadsList List of payload information. + * @return drone's sn + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_PAYLOAD, + outputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE) + public String statePayload(List payloadsList) { + // Delete all payload information for the drone based on the drone's sn. + devicePayloadService.deletePayloadsByDeviceSn(List.of(payloadsList.get(0).getDeviceSn())); + + // Save the new payload information. + devicePayloadService.savePayloadDTOs(payloadsList); + + log.debug("The result of saving the payload is successful."); + + return payloadsList.get(0).getDeviceSn(); + } + + /** + * Handles messages in the state topic about basic drone data. + * + * Note: Only the data of the drone payload is handled here. You can handle other data from the drone + * according to your business needs. + * @param deviceBasic basic drone data + * @return drone's sn + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC, + outputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE) + public String stateBasic(DeviceBasicReceiver deviceBasic) { + // Delete all payload information for the drone based on the drone's sn. + devicePayloadService.deletePayloadsByDeviceSn(List.of(deviceBasic.getDeviceSn())); + + // Save the new payload information. + boolean isSave = devicePayloadService.savePayloadDTOs(deviceBasic.getPayloads()); + + log.debug("The result of saving the payloads is {}.", isSave); + + return deviceBasic.getDeviceSn(); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java b/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java new file mode 100644 index 0000000..ef89f34 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java @@ -0,0 +1,113 @@ +package com.dji.sample.manage.controller; + +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.dji.sample.manage.model.Chan; +import com.dji.sample.manage.model.dto.CapacityDeviceDTO; +import com.dji.sample.manage.model.dto.LiveTypeDTO; +import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver; +import com.dji.sample.manage.model.receiver.ServiceReplyReceiver; +import com.dji.sample.manage.service.ILiveStreamService; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.messaging.Message; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.List; + +import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/19 + */ + +@RestController +@Slf4j +@RequestMapping("${url.manage.prefix}${url.manage.version}/live") +public class LiveStreamController { + + @Autowired + private ILiveStreamService liveStreamService; + + /** + * Analyze the live streaming capabilities of drones. + * This data is necessary if drones are required for live streaming. + * @param device the capacity of drone + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_CAPACITY) + public void stateCapacity(CapacityDeviceReceiver device) { + boolean parseCapacity = liveStreamService.saveLiveCapacity(device); + log.debug("The result of parsing the live capacity is {}.", parseCapacity); + } + + /** + * Get live capability data of all drones in the current user's workspace from the database. + * @param request + * @return live capability + */ + @GetMapping("/capacity") + public ResponseResult> getLiveCapacity(HttpServletRequest request) { + // Get information about the current user. + CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM); + + List liveCapacity = liveStreamService.getLiveCapacity(customClaim.getWorkspaceId()); + + return ResponseResult.success(liveCapacity); + } + + /** + * Live streaming according to the parameters passed in from the web side. + * @param liveParam Live streaming parameters. + * @return + */ + @PostMapping("/streams/start") + public ResponseResult liveStart(@RequestBody LiveTypeDTO liveParam) { + return liveStreamService.liveStart(liveParam); + } + + /** + * Stop live streaming according to the parameters passed in from the web side. + * @param liveParam Live streaming parameters. + * @return + */ + @PostMapping("/streams/stop") + public ResponseResult liveStop(@RequestBody LiveTypeDTO liveParam) { + return liveStreamService.liveStop(liveParam.getVideoId()); + } + + /** + * Set the quality of the live streaming according to the parameters passed in from the web side. + * @param liveParam Live streaming parameters. + * @return + */ + @PostMapping("/streams/update") + public ResponseResult liveSetQuality(@RequestBody LiveTypeDTO liveParam) { + return liveStreamService.liveSetQuality(liveParam); + } + + /** + * Handle the reply message from the pilot side to the on-demand video. + * @param message reply message + * @throws IOException + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_SERVICE_REPLY) + public void serviceReply(Message message) throws IOException { + byte[] payload = (byte[])message.getPayload(); + ObjectMapper mapper = new ObjectMapper(); + CommonTopicReceiver receiver = mapper.readValue(payload, + new TypeReference>() { + }); + Chan chan = Chan.getInstance(); + // Put the message to the chan object. + chan.put(receiver); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/controller/LoginController.java b/src/main/java/com/dji/sample/manage/controller/LoginController.java new file mode 100644 index 0000000..b0cacb4 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/controller/LoginController.java @@ -0,0 +1,47 @@ +package com.dji.sample.manage.controller; + +import com.dji.sample.common.error.CommonErrorEnum; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.manage.model.dto.UserDTO; +import com.dji.sample.manage.model.dto.UserLoginDTO; +import com.dji.sample.manage.service.IUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; + +import static com.dji.sample.component.AuthInterceptor.PARAM_TOKEN; + +@RestController +@RequestMapping("${url.manage.prefix}${url.manage.version}") +public class LoginController { + + @Autowired + private IUserService userService; + + @PostMapping("/login") + public ResponseResult login(@RequestBody UserLoginDTO loginDTO) { + String username = loginDTO.getUsername(); + String password = loginDTO.getPassword(); + return userService.userLogin(username, password); + } + + @PostMapping("/token/refresh") + public ResponseResult refreshToken(HttpServletRequest request, HttpServletResponse response) { + String token = request.getHeader(PARAM_TOKEN); + Optional user = userService.refreshToken(token); + + if (user.isEmpty()) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + return ResponseResult.error(CommonErrorEnum.NO_TOKEN.getErrorMsg()); + } + + return ResponseResult.success(user); + } +} diff --git a/src/main/java/com/dji/sample/manage/controller/TopologyController.java b/src/main/java/com/dji/sample/manage/controller/TopologyController.java new file mode 100644 index 0000000..3f2b80d --- /dev/null +++ b/src/main/java/com/dji/sample/manage/controller/TopologyController.java @@ -0,0 +1,40 @@ +package com.dji.sample.manage.controller; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.manage.model.dto.TopologyDTO; +import com.dji.sample.manage.service.ITopologyService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/8 + */ +@RestController +@RequestMapping("${url.manage.prefix}${url.manage.version}/workspaces") +public class TopologyController { + + @Autowired + private ITopologyService topologyService; + + /** + * Get the topology list of all devices in the current user workspace for pilot display. + * @param workspaceId + * @return + */ + @GetMapping("/{workspace_id}/devices/topologies") + public ResponseResult>> getDevicesTopologiesForPilot( + @PathVariable(name = "workspace_id") String workspaceId) { + List topologyList = topologyService.getDeviceTopology(workspaceId); + return ResponseResult.success(new ConcurrentHashMap<>(Map.of("list", topologyList))); + } + +} diff --git a/src/main/java/com/dji/sample/manage/controller/UserController.java b/src/main/java/com/dji/sample/manage/controller/UserController.java new file mode 100644 index 0000000..06cfb4d --- /dev/null +++ b/src/main/java/com/dji/sample/manage/controller/UserController.java @@ -0,0 +1,29 @@ +package com.dji.sample.manage.controller; + +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.manage.service.IUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; + + +@RestController +@RequestMapping("${url.manage.prefix}${url.manage.version}/users") +public class UserController { + + @Autowired + private IUserService userService; + + @GetMapping("/current") + public ResponseResult getCurrentUserInfo(HttpServletRequest request) { + CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM); + return userService.getUserByUsername(customClaim.getUsername(), customClaim.getWorkspaceId()); + } + +} diff --git a/src/main/java/com/dji/sample/manage/controller/WorkspaceController.java b/src/main/java/com/dji/sample/manage/controller/WorkspaceController.java new file mode 100644 index 0000000..e2f5f43 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/controller/WorkspaceController.java @@ -0,0 +1,41 @@ +package com.dji.sample.manage.controller; + +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.manage.model.dto.WorkspaceDTO; +import com.dji.sample.manage.service.IWorkspaceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; + +import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@RestController +@RequestMapping("${url.manage.prefix}${url.manage.version}/workspaces") +public class WorkspaceController { + + @Autowired + private IWorkspaceService workspaceService; + + /** + * Gets information about the workspace that the current user is in. + * @param request + * @return + */ + @GetMapping("/current") + public ResponseResult getCurrentWorkspace(HttpServletRequest request) { + CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM); + Optional workspaceOpt = workspaceService.getWorkspaceByWorkspaceId(customClaim.getWorkspaceId()); + + return workspaceOpt.isEmpty() ? ResponseResult.error() : ResponseResult.success(workspaceOpt.get()); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java b/src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java new file mode 100644 index 0000000..0784954 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java @@ -0,0 +1,12 @@ +package com.dji.sample.manage.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.manage.model.entity.CameraVideoEntity; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/19 + */ +public interface ICameraVideoMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java b/src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java new file mode 100644 index 0000000..777d6d0 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java @@ -0,0 +1,12 @@ +package com.dji.sample.manage.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.manage.model.entity.CapacityCameraEntity; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +public interface ICapacityCameraMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java b/src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java new file mode 100644 index 0000000..cce8f7e --- /dev/null +++ b/src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java @@ -0,0 +1,13 @@ +package com.dji.sample.manage.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.manage.model.entity.DeviceDictionaryEntity; + +/** + * + * @author sean.zhou + * @date 2021/11/15 + * @version 0.1 + */ +public interface IDeviceDictionaryMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java b/src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java new file mode 100644 index 0000000..758d8e5 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java @@ -0,0 +1,14 @@ +package com.dji.sample.manage.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.manage.model.entity.DeviceEntity; + +/** + * + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +public interface IDeviceMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java b/src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java new file mode 100644 index 0000000..f90231c --- /dev/null +++ b/src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java @@ -0,0 +1,12 @@ +package com.dji.sample.manage.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.manage.model.entity.DevicePayloadEntity; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +public interface IDevicePayloadMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/manage/dao/IUserMapper.java b/src/main/java/com/dji/sample/manage/dao/IUserMapper.java new file mode 100644 index 0000000..a6179ae --- /dev/null +++ b/src/main/java/com/dji/sample/manage/dao/IUserMapper.java @@ -0,0 +1,8 @@ +package com.dji.sample.manage.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.manage.model.entity.UserEntity; + +public interface IUserMapper extends BaseMapper { + +} diff --git a/src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java b/src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java new file mode 100644 index 0000000..79bc240 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java @@ -0,0 +1,8 @@ +package com.dji.sample.manage.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.manage.model.entity.WorkspaceEntity; + +public interface IWorkspaceMapper extends BaseMapper { + +} diff --git a/src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java b/src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java new file mode 100644 index 0000000..5c51c42 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java @@ -0,0 +1,35 @@ +package com.dji.sample.manage.handler; + +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; + +/** + * @author sean + * @version 0.3 + * @date 2022/2/21 + */ +public abstract class AbstractStateTopicHandler { + + protected AbstractStateTopicHandler handler; + protected static ObjectMapper mapper = new ObjectMapper();; + + protected AbstractStateTopicHandler(AbstractStateTopicHandler handler){ + this.handler = handler; + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + /** + * Passing dataNode data, using different processing methods depending on the data selection. + * @param dataNode + * @param stateReceiver + * @param sn + * @return + * @throws JsonProcessingException + */ + public abstract TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException; +} diff --git a/src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java b/src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java new file mode 100644 index 0000000..42140b6 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java @@ -0,0 +1,25 @@ +package com.dji.sample.manage.handler; + +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.stereotype.Service; + +/** + * @author sean + * @version 0.3 + * @date 2022/3/21 + */ +@Service +public class StateDefaultHandler extends AbstractStateTopicHandler { + + protected StateDefaultHandler() { + super(null); + } + + @Override + public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException { + // If no suitable handler is found for the data, it is not processed. + return stateReceiver; + } +} diff --git a/src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java b/src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java new file mode 100644 index 0000000..cb773eb --- /dev/null +++ b/src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java @@ -0,0 +1,34 @@ +package com.dji.sample.manage.handler; + +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.manage.model.receiver.DeviceBasicReceiver; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +/** + * @author sean + * @version 0.3 + * @date 2022/2/21 + */ +@Service +public class StateDeviceBasicHandler extends AbstractStateTopicHandler { + + public StateDeviceBasicHandler(@Autowired @Qualifier("statePayloadHandler") AbstractStateTopicHandler handler) { + super(handler); + } + + @Override + public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException { + // handle device basic data + if (dataNode.size() != 1) { + DeviceBasicReceiver data = mapper.treeToValue(dataNode, DeviceBasicReceiver.class); + data.setDeviceSn(sn); + stateReceiver.setData(data); + return stateReceiver; + } + return handler.handleState(dataNode, stateReceiver, sn); + } +} diff --git a/src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java b/src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java new file mode 100644 index 0000000..9e79d6d --- /dev/null +++ b/src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java @@ -0,0 +1,39 @@ +package com.dji.sample.manage.handler; + +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.manage.model.enums.StateDataEnum; +import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +/** + * @author sean + * @version 0.3 + * @date 2022/2/21 + */ +@Service +public class StateLiveCapacityHandler extends AbstractStateTopicHandler { + + private static final String DEVICE_LIST = "device_list"; + + protected StateLiveCapacityHandler(@Autowired @Qualifier("stateDefaultHandler") AbstractStateTopicHandler handler) { + super(handler); + } + + @Override + public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException { + String name = dataNode.fieldNames().next(); + JsonNode childNode = dataNode.findPath(name); + // Determine if it is live capacity data based on name. + if (name.equals(StateDataEnum.LIVE_CAPACITY.getDesc())) { + JsonNode deviceNode = childNode.findPath(DEVICE_LIST); + stateReceiver.setData( + mapper.treeToValue(deviceNode.get(0), CapacityDeviceReceiver.class)); + return stateReceiver; + } + return handler.handleState(dataNode, stateReceiver, sn); + } +} diff --git a/src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java b/src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java new file mode 100644 index 0000000..a14e0d0 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java @@ -0,0 +1,55 @@ +package com.dji.sample.manage.handler; + +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.manage.model.enums.StateDataEnum; +import com.dji.sample.manage.model.receiver.DevicePayloadReceiver; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author sean + * @version 0.3 + * @date 2022/2/21 + */ +@Service +public class StatePayloadHandler extends AbstractStateTopicHandler { + + protected StatePayloadHandler(@Autowired @Qualifier("stateLiveCapacityHandler") AbstractStateTopicHandler handler) { + super(handler); + } + + @Override + public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException { + String name = dataNode.fieldNames().next(); + JsonNode childNode = dataNode.findPath(name); + // Determine if it is payload data based on name. + if (name.equals(StateDataEnum.PAYLOADS.getDesc())) { + List payloadsList = new ArrayList<>(); + + Iterator payloadsNode = childNode.elements(); + while (payloadsNode.hasNext()) { + DevicePayloadReceiver payloadReceiver = mapper.treeToValue( + payloadsNode.next(), DevicePayloadReceiver.class); + payloadReceiver.setDeviceSn(sn); + payloadsList.add(payloadReceiver); + } + + if (payloadsList.isEmpty()) { + DevicePayloadReceiver payloadReceiver = new DevicePayloadReceiver(); + payloadReceiver.setDeviceSn(sn); + payloadsList.add(payloadReceiver); + } + + stateReceiver.setData(payloadsList); + return stateReceiver; + } + return handler.handleState(dataNode, stateReceiver, sn); + } +} diff --git a/src/main/java/com/dji/sample/manage/handler/StateRouter.java b/src/main/java/com/dji/sample/manage/handler/StateRouter.java new file mode 100644 index 0000000..3fe3404 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/handler/StateRouter.java @@ -0,0 +1,92 @@ +package com.dji.sample.manage.handler; + +import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver; +import com.dji.sample.manage.model.receiver.DeviceBasicReceiver; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.Router; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.integration.router.MessageRouter; +import org.springframework.integration.router.PayloadTypeRouter; +import org.springframework.messaging.Message; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static com.dji.sample.component.mqtt.model.TopicConst.*; + +/** + * + * @author sean.zhou + * @date 2021/11/17 + * @version 0.1 + */ +@MessageEndpoint +@Slf4j +@Configuration +public class StateRouter { + + @Resource(name = "stateDeviceBasicHandler") + private AbstractStateTopicHandler handler; + + /** + * Handles the routing of state topic messages. Depending on the data, it is assigned to different channels for handling. + * @param message + * @return + * @throws IOException + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE, outputChannel = ChannelName.INBOUND_STATE_SPLITTER) + public TopicStateReceiver resolveStateData(Message message) throws IOException { + byte[] payload = (byte[])message.getPayload(); + String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString(); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + TopicStateReceiver stateReceiver = mapper.readValue(payload, TopicStateReceiver.class); + // Get the sn of the topic source. + String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(), + topic.indexOf(STATE_SUF)); + + try { + JsonNode dataNode = mapper.readTree(payload).findPath("data"); + return handler.handleState(dataNode, stateReceiver, from); + + } catch (UnrecognizedPropertyException e) { + log.info("The {} data is not processed.", e.getPropertyName()); + } + return stateReceiver; + } + + @Bean + @Router(inputChannel = ChannelName.INBOUND_STATE_ROUTER) + public MessageRouter resolveStateRouter() { + PayloadTypeRouter router = new PayloadTypeRouter(); + // // Channel mapping for basic data. + router.setChannelMapping(DeviceBasicReceiver.class.getName(), + ChannelName.INBOUND_STATE_BASIC); + // Channel mapping for live streaming capabilities. + router.setChannelMapping(CapacityDeviceReceiver.class.getName(), + ChannelName.INBOUND_STATE_CAPACITY); + // Channel mapping for payload data. + router.setChannelMapping(List.class.getName(), + ChannelName.INBOUND_STATE_PAYLOAD); + router.setChannelMapping(Map.class.getName(), + ChannelName.DEFAULT); + return router; + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/handler/StateSplitter.java b/src/main/java/com/dji/sample/manage/handler/StateSplitter.java new file mode 100644 index 0000000..e47fd53 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/handler/StateSplitter.java @@ -0,0 +1,59 @@ +package com.dji.sample.manage.handler; + +import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.manage.model.receiver.DevicePayloadReceiver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.Splitter; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + * @author sean.zhou + * @date 2021/11/17 + * @version 0.1 + */ +@MessageEndpoint +@Configuration +public class StateSplitter { + + /** + * Split the state message data to different channels for handling according to their different types. + * @param receiver state message + * @return + */ + @Splitter(inputChannel = ChannelName.INBOUND_STATE_SPLITTER, outputChannel = ChannelName.INBOUND_STATE_ROUTER) + public Collection splitState(TopicStateReceiver receiver) { + ArrayList type = new ArrayList<>(); + type.add(receiver.getData()); + return type; + } + + /** + * Split according to the different types in the list. + * @return + */ + @Bean + public IntegrationFlow splitList() { + return IntegrationFlows + .from(ChannelName.INBOUND_STATE_LIST) + .split() + . route(dataType -> { + Class clazz = dataType.getClass(); + if (DevicePayloadReceiver.class.isAssignableFrom(clazz)) { + return ChannelName.INBOUND_STATE_PAYLOAD; + } + return null; + }, mapping -> { + mapping.channelMapping(ChannelName.INBOUND_STATE_PAYLOAD, + ChannelName.INBOUND_STATE_PAYLOAD); + }) + .get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/handler/StatusRouter.java b/src/main/java/com/dji/sample/manage/handler/StatusRouter.java new file mode 100644 index 0000000..3fa6bc8 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/handler/StatusRouter.java @@ -0,0 +1,70 @@ +package com.dji.sample.manage.handler; + +import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.receiver.StatusGatewayReceiver; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.Router; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.messaging.Message; +import org.springframework.util.CollectionUtils; + +import static com.dji.sample.component.mqtt.model.TopicConst.*; + +/** + * + * @author sean.zhou + * @date 2021/11/12 + * @version 0.1 + */ +@MessageEndpoint +public class StatusRouter { + + /** + * Converts the status data sent by the gateway device into an object. + * @param message + * @return + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS, outputChannel = ChannelName.INBOUND_STATUS_ROUTER) + public CommonTopicReceiver resolveStatus(Message message) { + + CommonTopicReceiver statusReceiver = new CommonTopicReceiver<>(); + ObjectMapper mapper = new ObjectMapper(); + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + try { + statusReceiver = mapper.readValue( + (byte[])message.getPayload(), + new TypeReference>() {}); + + String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString(); + + // set gateway's domain + statusReceiver.getData().setDomain(DeviceDomainEnum.GATEWAY.getVal()); + // set gateway's sn + statusReceiver.getData().setSn( + topic.substring((BASIC_PRE + PRODUCT).length(), + topic.indexOf(STATUS_SUF))); + } catch (Exception e) { + e.printStackTrace(); + } + return statusReceiver; + } + + /** + * Handles the routing of status topic messages. Depending on the data, it is assigned to different channels for handling. + * @param receiver + * @return + */ + @Router(inputChannel = ChannelName.INBOUND_STATUS_ROUTER) + public String resolveStatusRouter(CommonTopicReceiver receiver) { + // Determine whether the drone is online or offline according to whether the data of the sub-device is empty. + return CollectionUtils.isEmpty(receiver.getData().getSubDevices()) ? + ChannelName.INBOUND_STATUS_OFFLINE : ChannelName.INBOUND_STATUS_ONLINE; + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/Chan.java b/src/main/java/com/dji/sample/manage/model/Chan.java new file mode 100644 index 0000000..5aa7961 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/Chan.java @@ -0,0 +1,45 @@ +package com.dji.sample.manage.model; + +import java.util.concurrent.locks.LockSupport; + +/** + * The demo is only for functional closure, which is not recommended. + * @author sean.zhou + * @date 2021/11/22 + * @version 0.1 + */ +public class Chan { + + private static final long THREAD_WAIT_TIME = 1000_000 * 500; + + private volatile T data; + + private volatile Thread t; + + private Chan () { + + } + + public static Chan getInstance() { + return ChanSingleton.INSTANCE; + } + + public T get(Object blocker) { + this.t = Thread.currentThread(); + LockSupport.parkNanos(blocker, THREAD_WAIT_TIME); + this.t = null; + return data; + } + + public void put(T data) { + this.data = data; + if (t == null) { + return; + } + LockSupport.unpark(t); + } + + private static class ChanSingleton { + private static final Chan INSTANCE = new Chan<>(); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java b/src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java new file mode 100644 index 0000000..2bc6960 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.model; + +import java.time.LocalDateTime; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The demo is only for functional closure, which is not recommended, + * and it is recommended to use caching for handling. + * @author sean.zhou + * @version 0.1 + * @date 2021/11/25 + */ +public class DeviceStatusManager { + + public static final ConcurrentHashMap STATUS_MANAGER = + new ConcurrentHashMap<>(16); + + public static final Integer DEFAULT_ALIVE_SECOND = 30; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java b/src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java new file mode 100644 index 0000000..dbc6481 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java @@ -0,0 +1,34 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/22 + * @version 0.1 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CapacityCameraDTO { + + private Integer id; + + private String deviceSn; + + private String name; + + private String description; + + private String index; + + private String type; + + private List videosList; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java new file mode 100644 index 0000000..efa0bcc --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java @@ -0,0 +1,26 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/22 + * @version 0.1 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CapacityDeviceDTO { + + private String sn; + + private String name; + + private List camerasList; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java b/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java new file mode 100644 index 0000000..1d9dc3a --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java @@ -0,0 +1,24 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean.zhou + * @date 2021/11/22 + * @version 0.1 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CapacityVideoDTO { + + private Integer id; + + private String index; + + private String type; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java new file mode 100644 index 0000000..2571dff --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java @@ -0,0 +1,47 @@ +package com.dji.sample.manage.model.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class DeviceDTO { + + private String deviceSn; + + private String deviceName; + + private String workspaceId; + + private String deviceIndex; + + private String deviceDesc; + + private String childDeviceSn; + + private String domain; + + private Integer type; + + private Integer subType; + + private List gatewaysList; + + private List payloadsList; + + private IconUrlDTO iconUrl; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java new file mode 100644 index 0000000..9c0170d --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java @@ -0,0 +1,28 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class DeviceDictionaryDTO { + + private Integer domain; + + private Integer deviceType; + + private Integer subType; + + private String deviceName; + + private String deviceDesc; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java new file mode 100644 index 0000000..d7559ff --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java @@ -0,0 +1,27 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/8 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DeviceModelDTO { + + private String key; + + private String domain; + + private String type; + + private String subType; + +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java new file mode 100644 index 0000000..bb16641 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java @@ -0,0 +1,29 @@ +package com.dji.sample.manage.model.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class DevicePayloadDTO { + + private String payloadSn; + + private String payloadName; + + private Integer payloadIndex; + + private String payloadDesc; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java b/src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java new file mode 100644 index 0000000..effbf1c --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java @@ -0,0 +1,25 @@ +package com.dji.sample.manage.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.3 + * @date 2022/1/5 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class IconUrlDTO { + + @JsonProperty("normal_icon_url") + private String normalUrl; + + @JsonProperty("selected_icon_url") + private String selectUrl; +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java new file mode 100644 index 0000000..29841c2 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.model.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LiveDTO { + + private String url; + + private String username; + + private String password; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java new file mode 100644 index 0000000..ffe341e --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java @@ -0,0 +1,26 @@ +package com.dji.sample.manage.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * Receive live parameters. + * @author sean.zhou + * @version 0.1 + * @date 2021/11/22 + */ +@Data +public class LiveTypeDTO { + + @JsonProperty("url_type") + private Integer urlType; + + private String url; + + @JsonProperty("video_id") + private String videoId; + + @JsonProperty("video_quality") + private Integer videoQuality; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java new file mode 100644 index 0000000..1f9f781 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.model.dto; + +import lombok.Data; + +/** + * @author sean.zhou + * @date 2021/11/23 + * @version 0.1 + */ +@Data +public class LiveUrlAgoraDTO { + + private String channel; + + private String sn; + + private String token; + + private Integer uid; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java new file mode 100644 index 0000000..d57f06a --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java @@ -0,0 +1,27 @@ +package com.dji.sample.manage.model.dto; + +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@Data +public class LiveUrlGB28181DTO { + + private String serverIP; + + private Integer serverPort; + + private String serverID; + + private String agentID; + + private String agentPassword; + + private Integer localPort; + + private String channel; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java new file mode 100644 index 0000000..8e57734 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java @@ -0,0 +1,18 @@ +package com.dji.sample.manage.model.dto; + +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@Data +public class LiveUrlRTSPDTO { + + private String userName; + + private String password; + + private Integer port; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java b/src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java new file mode 100644 index 0000000..8f7f9d5 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java @@ -0,0 +1,22 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/8 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TelemetryDTO { + + private TelemetryDeviceDTO host; + + private String sn; +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java new file mode 100644 index 0000000..1905804 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java @@ -0,0 +1,32 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/8 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TelemetryDeviceDTO { + + private Double latitude; + + private Double longitude; + + private Double altitude; + + private Float attitudeHead; + + private Double elevation; + + private Float horizontalSpeed; + + private Float verticalSpeed; +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java b/src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java new file mode 100644 index 0000000..d91cd3a --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java @@ -0,0 +1,24 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/8 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TopologyDTO { + + private List hosts; + + private List parents; +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java new file mode 100644 index 0000000..c36e88d --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java @@ -0,0 +1,35 @@ +package com.dji.sample.manage.model.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/8 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TopologyDeviceDTO { + + private String sn; + + private DeviceModelDTO deviceModel; + + @Builder.Default + private Boolean onlineStatus = true; + + private String deviceCallsign; + + private String userId; + + private String userCallsign; + + private IconUrlDTO iconUrls; +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/UserDTO.java b/src/main/java/com/dji/sample/manage/model/dto/UserDTO.java new file mode 100644 index 0000000..1e67c72 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/UserDTO.java @@ -0,0 +1,37 @@ +package com.dji.sample.manage.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserDTO { + + @JsonProperty("user_id") + private String userId; + + private String username; + + @JsonProperty("workspace_id") + private String workspaceId; + + @JsonProperty("user_type") + private Integer userType; + + @JsonProperty("mqtt_username") + private String mqttUsername; + + @JsonProperty("mqtt_password") + private String mqttPassword; + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("mqtt_addr") + private String mqttAddr; +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java b/src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java new file mode 100644 index 0000000..b82ca44 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java @@ -0,0 +1,14 @@ +package com.dji.sample.manage.model.dto; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class UserLoginDTO { + + @NonNull + private String username; + + @NonNull + private String password; +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java new file mode 100644 index 0000000..0ce7d15 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java @@ -0,0 +1,33 @@ +package com.dji.sample.manage.model.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean.zhou + * @date 2021/11/22 + * @version 0.1 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class WorkspaceDTO { + + public static final String DEFAULT_WORKSPACE_ID = "e3dea0f5-37f2-4d79-ae58-490af3228069"; + + private Integer id; + + private String workspaceId; + + private String workspaceName; + + private String workspaceDesc; + + private String platformName; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java b/src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java new file mode 100644 index 0000000..a2e7269 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java @@ -0,0 +1,37 @@ +package com.dji.sample.manage.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +@TableName(value = "manage_camera_video") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CameraVideoEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField(value = "camera_id") + private Integer cameraId; + + @TableField(value = "video_index") + private String videoIndex; + + @TableField(value = "video_type") + private String videoType; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java b/src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java new file mode 100644 index 0000000..c57f496 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java @@ -0,0 +1,46 @@ +package com.dji.sample.manage.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "manage_capacity_camera") +public class CapacityCameraEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField(value = "device_sn") + private String deviceSn; + + @TableField(value = "name") + private String name; + + @TableField(value = "description") + private String description; + + @TableField(value = "camera_index") + private String cameraIndex; + + @TableField(value = "coexist_video_number_max") + private Integer coexistVideoNumberMax; + + @TableField(value = "available_video_number") + private Integer availableVideoNumber; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java b/src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java new file mode 100644 index 0000000..c01ec6f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java @@ -0,0 +1,45 @@ +package com.dji.sample.manage.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * + * @author sean.zhou + * @date 2021/11/15 + * @version 0.1 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("manage_device_dictionary") +public class DeviceDictionaryEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField(value = "domain") + private Integer domain; + + @TableField(value = "device_type") + private Integer deviceType; + + @TableField(value = "sub_type") + private Integer subType; + + @TableField(value = "device_name") + private String deviceName; + + @TableField(value = "device_desc") + private String deviceDesc; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java b/src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java new file mode 100644 index 0000000..f019a01 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java @@ -0,0 +1,69 @@ +package com.dji.sample.manage.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * The entity class of the device + * + * @author sean.zhou + * @version 0.1 + * @date 2021/11/10 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@TableName(value = "manage_device") +public class DeviceEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField(value = "device_sn") + private String deviceSn; + + @TableField(value = "device_name") + private String deviceName; + + @TableField(value = "workspace_id") + private String workspaceId; + + @TableField(value = "device_type") + private Integer deviceType; + + @TableField(value = "sub_type") + private Integer subType; + + @TableField(value = "domain") + private Integer domain; + + @TableField(value = "version") + private Integer version; + + @TableField(value = "device_index") + private String deviceIndex; + + @TableField(value = "child_sn") + private String childSn; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Long createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Long updateTime; + + @TableField(value = "device_desc") + private String deviceDesc; + + @TableField(value = "url_normal") + private String urlNormal; + + @TableField(value = "url_select") + private String urlSelect; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java b/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java new file mode 100644 index 0000000..184fb0f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java @@ -0,0 +1,56 @@ +package com.dji.sample.manage.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "manage_device_payload") +public class DevicePayloadEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField(value = "payload_sn") + private String payloadSn; + + @TableField(value = "payload_name") + private String payloadName; + + @TableField(value = "payload_type") + private Integer payloadType; + + @TableField(value = "sub_type") + private Integer subType; + + @TableField(value = "version") + private Integer version; + + @TableField(value = "payload_index") + private Integer payloadIndex; + + @TableField(value = "device_sn") + private String deviceSn; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Long createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Long updateTime; + + @TableField(value = "payload_desc") + private String payloadDesc; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/entity/UserEntity.java b/src/main/java/com/dji/sample/manage/model/entity/UserEntity.java new file mode 100644 index 0000000..67edecf --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/entity/UserEntity.java @@ -0,0 +1,41 @@ +package com.dji.sample.manage.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; + +@TableName(value = "manage_user") +@Data +public class UserEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField(value = "user_id") + private String userId; + + @TableField(value = "username") + private String username; + + @TableField(value = "password") + private String password; + + @TableField(value = "workspace_id") + private Integer workspaceId; + + @TableField(value = "user_type") + private Integer userType; + + @TableField(value = "mqtt_username") + private String mqttUsername; + + @TableField(value = "mqtt_password") + private String mqttPassword; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Long createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Long updateTime; +} diff --git a/src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java b/src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java new file mode 100644 index 0000000..8a84ff0 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java @@ -0,0 +1,33 @@ +package com.dji.sample.manage.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; + +@TableName(value = "manage_workspace") +@Data +public class WorkspaceEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField(value = "workspace_id") + private String workspaceId; + + @TableField(value = "workspace_name") + private String workspaceName; + + @TableField(value = "workspace_desc") + private String workspaceDesc; + + @TableField(value = "platform_name") + private String platformName; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Long createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Long updateTime; + +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java new file mode 100644 index 0000000..cd8fd39 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java @@ -0,0 +1,63 @@ +package com.dji.sample.manage.model.enums; + +/** + * + * @author sean.zhou + * @date 2021/11/15 + * @version 0.1 + */ +public enum DeviceDomainEnum { + + SUB_DEVICE(0, "sub-device"), + + GATEWAY(2, "gateway"), + + PAYLOAD(1, "payload"), + + UNKNOWN(-1, "unknown"); + + private int val; + + private String desc; + + DeviceDomainEnum(int val, String desc) { + this.val = val; + this.desc = desc; + } + + public int getVal() { + return val; + } + + public static String getDesc(int val) { + if (SUB_DEVICE.val == val) { + return SUB_DEVICE.desc; + } + + if (GATEWAY.val == val) { + return GATEWAY.desc; + } + + if (PAYLOAD.val == val) { + return PAYLOAD.desc; + } + return UNKNOWN.desc; + } + + public static int getVal(String desc) { + if (SUB_DEVICE.desc.equals(desc)) { + return SUB_DEVICE.val; + } + + if (GATEWAY.desc.equals(desc)) { + return GATEWAY.val; + } + + if (PAYLOAD.desc.equals(desc)) { + return PAYLOAD.val; + } + return UNKNOWN.val; + } + + +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java b/src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java new file mode 100644 index 0000000..3803d2f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java @@ -0,0 +1,37 @@ +package com.dji.sample.manage.model.enums; + +/** + * The system icon that comes with the pilot. + * @author sean + * @version 0.3 + * @date 2022/1/5 + */ +public enum IconUrlEnum { + + SELECT_CAR("resource://pilot/drawable/tsa_car_select"), + + NORMAL_CAR("resource://pilot/drawable/tsa_car_normal"), + + SELECT_PERSON("resource://pilot/drawable/tsa_person_select"), + + NORMAL_PERSON("resource://pilot/drawable/tsa_person_normal"), + + SELECT_EQUIPMENT("resource://pilot/drawable/tsa_equipment_select"), + + NORMAL_EQUIPMENT("resource://pilot/drawable/tsa_equipment_normal"); + + /** + * You can use icons from the web, and the App internally downloads and caches these icons and + * loads them at a fixed size (28dp) to display on the map. + * Example: http://r56978dr7.hn-bkt.clouddn.com/tsa_equipment_normal.png + */ + private String url; + + IconUrlEnum(String url) { + this.url = url; + } + + public String getUrl() { + return url; + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java b/src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java new file mode 100644 index 0000000..eebe35c --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java @@ -0,0 +1,27 @@ +package com.dji.sample.manage.model.enums; + +/** + * @author sean.zhou + * @date 2021/11/22 + * @version 0.1 + */ +public enum LiveMethodEnum { + + LIVE_START_PUSH("live_start_push"), + + LIVE_STOP_PUSH("live_stop_push"), + + LIVE_SET_QUALITY("live_set_quality"), + + UNKNOWN("unknown"); + + private String method; + + LiveMethodEnum(String method) { + this.method = method; + } + + public String getMethod() { + return method; + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java b/src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java new file mode 100644 index 0000000..163b520 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java @@ -0,0 +1,41 @@ +package com.dji.sample.manage.model.enums; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/22 + */ +public enum LiveUrlTypeEnum { + + AGORA(0), + + RTMP(1), + + RTSP(2), + + GB28181(3), + + UNKNOWN(-1); + + private int val; + + LiveUrlTypeEnum(int val) { + this.val = val; + } + + public static LiveUrlTypeEnum find(Integer val) { + if (AGORA.val == val) { + return AGORA; + } + if (RTMP.val == val) { + return RTMP; + } + if (RTSP.val == val) { + return RTSP; + } + if (GB28181.val == val) { + return GB28181; + } + return UNKNOWN; + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java b/src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java new file mode 100644 index 0000000..3ad42d3 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java @@ -0,0 +1,51 @@ +package com.dji.sample.manage.model.enums; + +/** + * @author sean + * @version 0.1 + * @date 2021/11/26 + */ +public enum LiveVideoQualityEnum { + + AUTO (0), + + SMOOTH(1), + + STANDARD_DEFINITION(2), + + HIGH_DEFINITION(3), + + ULTRA_HD(4), + + UNKNOWN(-1); + + private int val; + + LiveVideoQualityEnum(int val) { + this.val = val; + } + + public static LiveVideoQualityEnum find(int val) { + if (AUTO.val == val) { + return AUTO; + } + + if (SMOOTH.val == val) { + return SMOOTH; + } + + if (STANDARD_DEFINITION.val == val) { + return STANDARD_DEFINITION; + } + + if (HIGH_DEFINITION.val == val) { + return HIGH_DEFINITION; + } + + if (ULTRA_HD.val == val) { + return ULTRA_HD; + } + + return UNKNOWN; + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java b/src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java new file mode 100644 index 0000000..5a93cf3 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.manage.model.enums; + +/** + * + * @author sean.zhou + * @date 2021/11/18 + * @version 0.1 + */ +public enum StateDataEnum { + + BATTERIES("batteries"), + + LIVE_CAPACITY("live_capacity"), + + PAYLOADS("payloads"); + + private String desc; + + StateDataEnum(String desc) { + this.desc = desc; + } + + public String getDesc() { + return this.desc; + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java b/src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java new file mode 100644 index 0000000..311669e --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java @@ -0,0 +1,24 @@ +package com.dji.sample.manage.model.enums; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/2 + */ +public enum UserTypeEnum { + + WEB(1), + + PILOT(2); + + private int val; + + + UserTypeEnum(int val) { + this.val = val; + } + + public int getVal() { + return val; + } +} diff --git a/src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java b/src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java new file mode 100644 index 0000000..9233121 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java @@ -0,0 +1,32 @@ +package com.dji.sample.manage.model.param; + +import lombok.Builder; +import lombok.Data; + +/** + * The object of the device query field. + * + * @author sean.zhou + * @date 2021/11/16 + * @version 0.1 + */ +@Data +@Builder +public class DeviceQueryParam { + + private String deviceSn; + + private String workspaceId; + + private Integer deviceType; + + private Integer subType; + + private Integer domain; + + private String childSn; + + private boolean orderBy; + + private boolean isAsc; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java new file mode 100644 index 0000000..dc0162f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java @@ -0,0 +1,27 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.List; + +/** + * @author sean + * @version 0.3 + * @date 2022/1/27 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class BatteryReceiver { + + private List batteries; + + private Integer capacityPercent; + + private Integer landingPower; + + private Integer remainFlightTime; + + private Integer returnHomePower; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java new file mode 100644 index 0000000..275adc1 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java @@ -0,0 +1,34 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/24 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class BatteryStateReceiver { + + private String firmwareVersion; + + private Integer index; + + private Integer loopTimes; + + private Integer capacityPercent; + + private String sn; + + private Integer subType; + + private Float temperature; + + private Integer type; + + private Integer voltage; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java new file mode 100644 index 0000000..e5321fb --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java @@ -0,0 +1,28 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/18 + * @version 0.1 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class CapacityCameraReceiver { + + private Integer availableVideoNumber; + + private Integer coexistVideoNumberMax; + + private String cameraIndex; + + @JsonProperty(value = "video_list") + private List videosList; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java new file mode 100644 index 0000000..750db59 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java @@ -0,0 +1,25 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/18 + * @version 0.1 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class CapacityDeviceReceiver { + + private String sn; + + private Integer availableVideoNumber; + + private Integer coexistVideoNumberMax; + + private List cameraList; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java new file mode 100644 index 0000000..616b5a9 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java @@ -0,0 +1,19 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean.zhou + * @date 2021/11/18 + * @version 0.1 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class CapacityVideoReceiver { + + private String videoIndex; + + private String videoType; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java new file mode 100644 index 0000000..1ae46d8 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java @@ -0,0 +1,39 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/18 + * @version 0.1 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class DeviceBasicReceiver { + + private String controlSource; + + private String deviceSn; + + private Integer flightMode; + + private Integer flightStatus; + + private Double homeLatitude; + + private Double homeLongitude; + + private Integer lowBatteryWarningThreshold; + + private Integer positionMode; + + private Integer seriousLowBatteryWarningThreshold; + + private List payloads; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java new file mode 100644 index 0000000..2b3f998 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java @@ -0,0 +1,29 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean.zhou + * @date 2021/11/18 + * @version 0.1 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class DevicePayloadReceiver { + + private String deviceSn; + + private String controlSource; + + private String payloadIndex; + + private String sn; + + private Integer version; + + private Integer workMode; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java new file mode 100644 index 0000000..463cbf8 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java @@ -0,0 +1,23 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class LiveStatusReceiver { + + private Long liveTime; + + private Integer liveTrendline; + + private String videoId; + + private Integer videoQuality; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java new file mode 100644 index 0000000..4e0a299 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java @@ -0,0 +1,31 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class OsdGatewayReceiver { + + private Double latitude; + + private Double longitude; + + @JsonProperty("capacity_percent") + private Integer remainPower; + + private Integer transmissionSignalQuality; + + private LiveStatusReceiver liveStatus; + + private WirelessLinkStateReceiver wirelessLinkState; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java new file mode 100644 index 0000000..24d101e --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java @@ -0,0 +1,55 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class OsdSubDeviceReceiver { + + private Float attitudeHead; + + private Double attitudePitch; + + private Double attitudeRoll; + + private Double elevation; + + private BatteryReceiver battery; + + private String firmwareVersion; + + private Integer gear; + + private Double height; + + private Double homeDistance; + + private Float horizontalSpeed; + + private Double latitude; + + private Double longitude; + + private Integer modeCode; + + private Double totalFlightDistance; + + private Double totalFlightTime; + + private Float verticalSpeed; + + private Double windDirection; + + private Double windSpeed; + + private PositionStateReceiver positionState; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java new file mode 100644 index 0000000..5c6787f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java @@ -0,0 +1,23 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean + * @version 0.3 + * @date 2022/1/27 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class PositionStateReceiver { + + private Integer gpsNumber; + + private Integer isFixed; + + private Integer quality; + + private Integer rtkNumber; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java new file mode 100644 index 0000000..c877c0c --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java @@ -0,0 +1,28 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/24 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class RTKStateReceiver { + + private Integer bdsNumber; + + private Integer galNumber; + + private Integer gloNumber; + + private Integer gpsNumber; + + private Boolean isFixed; + + private Integer quality; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java new file mode 100644 index 0000000..ece269f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java @@ -0,0 +1,18 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/22 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ServiceReplyReceiver { + + private Integer result; + + private String info; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java new file mode 100644 index 0000000..0cee5ed --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java @@ -0,0 +1,43 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * + * @author sean.zhou + * @version 0.1 + * @date 2021/11/12 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class StatusGatewayReceiver { + + private String sn; + + private Integer domain; + + private Integer type; + + @JsonProperty(value = "sub_type") + private Integer subType; + + @JsonProperty(value = "device_secret") + private String deviceSecret; + + private String nonce; + + private Integer version; + + @JsonProperty(value = "sub_devices") + private List subDevices; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java new file mode 100644 index 0000000..4f0d47a --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java @@ -0,0 +1,32 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * + * @author sean.zhou + * @version 0.1 + * @date 2021/11/12 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class StatusSubDeviceReceiver { + + private String sn; + + private Integer type; + + @JsonProperty(value = "sub_type") + private Integer subType; + + private String index; + + @JsonProperty(value = "device_secret") + private String deviceSecret; + + private String nonce; + + private Integer version; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java new file mode 100644 index 0000000..5dc8bd5 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java @@ -0,0 +1,22 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/23 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class WirelessLinkStateReceiver { + + private Integer downloadQuality; + + private Integer frequencyBand; + + private Integer upwardQuality; + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/ICameraVideoService.java b/src/main/java/com/dji/sample/manage/service/ICameraVideoService.java new file mode 100644 index 0000000..808d6dd --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/ICameraVideoService.java @@ -0,0 +1,37 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.manage.model.dto.CapacityVideoDTO; +import com.dji.sample.manage.model.receiver.CapacityVideoReceiver; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +public interface ICameraVideoService { + + /** + * Queries all lens data contained in the camera based on camera id. + * @param cameraId + * @return + */ + List 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 ids); + + /** + * Save the live capability of all lenses of this camera. + * @param capacityVideoReceivers live capability of lens + * @param cameraId + * @return + */ + Boolean saveCameraVideoDTOList(List capacityVideoReceivers, Integer cameraId); + +} diff --git a/src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java b/src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java new file mode 100644 index 0000000..02ab494 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java @@ -0,0 +1,45 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.manage.model.dto.CapacityCameraDTO; +import com.dji.sample.manage.model.receiver.CapacityCameraReceiver; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +public interface ICapacityCameraService { + + /** + * Query all camera data that can be live streamed from this device based on the device sn. + * @param deviceSn + * @return + */ + List getCapacityCameraByDeviceSn(String deviceSn); + + /** + * Query whether this camera data has been saved based on the device sn and camera location. + * @param deviceSn + * @param cameraIndex + * @return + */ + Boolean checkExist(String deviceSn, String cameraIndex); + + /** + * Delete all live capability data for this device based on the device sn. + * @param deviceSn + * @return + */ + Boolean deleteCapacityCameraByDeviceSn(String deviceSn); + + /** + * Save the live capability data of the device. + * @param capacityCameraReceivers + * @param deviceSn + * @return + */ + Boolean saveCapacityCameraReceiverList(List capacityCameraReceivers, String deviceSn); + +} diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java b/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java new file mode 100644 index 0000000..65324ac --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java @@ -0,0 +1,24 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.manage.model.dto.DeviceDictionaryDTO; + +import java.util.Optional; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/15 + */ +public interface IDeviceDictionaryService { + + /** + * Query the type data of the device based on domain, device type and sub type. + * @param domain + * @param deviceType + * @param subType + * @return + */ + Optional getOneDictionaryInfoByDomainTypeSubType( + Integer domain, Integer deviceType, Integer subType); + +} diff --git a/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java b/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java new file mode 100644 index 0000000..2eb5c0e --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java @@ -0,0 +1,48 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.manage.model.dto.DevicePayloadDTO; +import com.dji.sample.manage.model.receiver.DevicePayloadReceiver; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +public interface IDevicePayloadService { + + /** + * Query if the payload has been saved based on the sn of the payload. + * @param payloadSn + * @return + */ + Integer checkPayloadExist(String payloadSn); + + /** + * Save all payload data. + * @param payloadReceiverList + * @return + */ + Boolean savePayloadDTOs(List payloadReceiverList); + + /** + * Save a payload data. + * @param payloadReceiver + * @return + */ + Integer saveOnePayloadDTO(DevicePayloadReceiver payloadReceiver); + + /** + * Query all payload data on this device based on the device sn. + * @param deviceSn + * @return + */ + List getDevicePayloadEntitiesByDeviceSn(String deviceSn); + + /** + * Delete all payload data on these devices based on the collection of device sns. + * @param deviceSns + */ + void deletePayloadsByDeviceSn(List deviceSns); +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceService.java b/src/main/java/com/dji/sample/manage/service/IDeviceService.java new file mode 100644 index 0000000..444d49d --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/IDeviceService.java @@ -0,0 +1,135 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.component.mqtt.model.CommonTopicResponse; +import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; +import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.dto.TopologyDeviceDTO; +import com.dji.sample.manage.model.param.DeviceQueryParam; +import com.dji.sample.manage.model.receiver.StatusGatewayReceiver; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * @author sean.zhou + * @date 2021/11/10 + * @version 0.1 + */ +public interface IDeviceService { + + /** + * The device goes online. + * @param deviceGateway gateway + * @return Whether the online is successful. + */ + Boolean deviceOnline(StatusGatewayReceiver deviceGateway); + + /** + * The device goes offline. + * @param gatewaySn + * @return Whether the offline is successful. + */ + Boolean deviceOffline(String gatewaySn); + + /** + * The aircraft goes offline. + * @param deviceSn aircraft's SN + * @return Whether the offline is successful. + */ + Boolean subDeviceOffline(String deviceSn); + + /** + * When the device goes online, it needs to subscribe to topics. + * @param sn device's SN + */ + void subscribeTopicOnline(String sn); + + /** + * When the device goes offine, it needs to cancel the subscribed topics. + * @param sn device's SN + */ + void unsubscribeTopicOffline(String sn); + + /** + * Delete all device data according to the SN of the device. + * @param ids device's SN + * @return + */ + Boolean delDeviceByDeviceSns(List ids); + + /** + * Obtain device data according to different query conditions. + * @param param query parameters + * @return + */ + List getDevicesByParams(DeviceQueryParam param); + + /** + * When you receive a status topic message, you need to reply to it. + * @param sn the target of sn + * @param response + */ + void publishStatusReply(String sn, CommonTopicResponse response); + + /** + * The business interface on the web side. Get all information about all devices in this workspace. + * @param workspaceId + * @return + */ + List getDevicesTopoForWeb(String workspaceId); + + /** + * Set the remote controller and payloads information of the drone. + * @param device + */ + void spliceDeviceTopo(DeviceDTO device); + + /** + * Push the topology information to the pilot after one device is online. + * @param sessions The collection of connection objects on the pilot side. + * @param sn + */ + void pushDeviceOnlineTopo(Collection sessions, String sn); + + /** + * Query the information of the device according to the sn of the device. + * @param sn device sn + * @return + */ + Optional getDeviceTopoForPilot(String sn); + + /** + * Convert individual device information into topology objects. + * @param device + * @return + */ + TopologyDeviceDTO deviceConvertToTopologyDTO(DeviceDTO device); + + /** + * When the server receives the request of any device online, offline and topology update in the same workspace, + * it also broadcasts a push of device online, offline and topology update to PILOT via websocket, + * and PILOT will get the device topology list again after receiving the push. + * @param workspaceId + * @param gatewaySn + */ + void pushDeviceOfflineTopo(String workspaceId, String gatewaySn); + + /** + * When the server receives the request of any device online, offline and topology update in the same workspace, + * it also broadcasts a push of device online, offline and topology update to PILOT via websocket, + * and PILOT will get the device topology list again after receiving the push. + * @param workspaceId + * @param deviceSn + * @param gatewaySn + */ + void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn); + + /** + * Handle messages from the osd topic. + * @param topic osd + * @param payload + */ + void handleOSD(String topic, byte[] payload); + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java b/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java new file mode 100644 index 0000000..09cba9e --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java @@ -0,0 +1,51 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.manage.model.dto.CapacityDeviceDTO; +import com.dji.sample.manage.model.dto.LiveTypeDTO; +import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver; + +import java.util.List; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +public interface ILiveStreamService { + + /** + * Get all the drone data that can be broadcast live in this workspace. + * @param workspaceId + * @return + */ + List getLiveCapacity(String workspaceId); + + /** + * Save live capability data from drone. + * @param capacityDeviceReceiver + * @return + */ + Boolean saveLiveCapacity(CapacityDeviceReceiver capacityDeviceReceiver); + + /** + * Initiate a live streaming by publishing mqtt message. + * @param liveParam Parameters needed for on-demand. + * @return + */ + ResponseResult liveStart(LiveTypeDTO liveParam); + + /** + * Stop the live streaming by publishing mqtt message. + * @param videoId + * @return + */ + ResponseResult liveStop(String videoId); + + /** + * Readjust the clarity of the live streaming by publishing mqtt messages. + * @param liveParam + * @return + */ + ResponseResult liveSetQuality(LiveTypeDTO liveParam); +} diff --git a/src/main/java/com/dji/sample/manage/service/ITSAService.java b/src/main/java/com/dji/sample/manage/service/ITSAService.java new file mode 100644 index 0000000..a444f92 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/ITSAService.java @@ -0,0 +1,17 @@ +package com.dji.sample.manage.service; + +/** + * @author sean + * @version 0.3 + * @date 2022/2/21 + */ +public interface ITSAService { + + /** + * Real-time push telemetry data. + * @param workspaceId + * @param osdData + * @param sn + */ + void pushTelemetryData(String workspaceId, Object osdData, String sn); +} diff --git a/src/main/java/com/dji/sample/manage/service/ITopologyService.java b/src/main/java/com/dji/sample/manage/service/ITopologyService.java new file mode 100644 index 0000000..e75a857 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/ITopologyService.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.manage.model.dto.TopologyDTO; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/8 + */ +public interface ITopologyService { + + /** + * Get the topology list of all devices in the workspace for pilot display. + * @param workspaceId + * @return + */ + List getDeviceTopology(String workspaceId); +} diff --git a/src/main/java/com/dji/sample/manage/service/IUserService.java b/src/main/java/com/dji/sample/manage/service/IUserService.java new file mode 100644 index 0000000..2ceff96 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/IUserService.java @@ -0,0 +1,32 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.manage.model.dto.UserDTO; + +import java.util.Optional; + +public interface IUserService { + + /** + * Query user's details based on username. + * @param username + * @param workspaceId + * @return + */ + ResponseResult getUserByUsername(String username, String workspaceId); + + /** + * Verify the username and password to log in. + * @param username + * @param password + * @return + */ + ResponseResult userLogin(String username, String password); + + /** + * Create a user object containing a new token. + * @param token + * @return + */ + Optional refreshToken(String token); +} diff --git a/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java b/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java new file mode 100644 index 0000000..5defa10 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java @@ -0,0 +1,23 @@ +package com.dji.sample.manage.service; + + +import com.dji.sample.manage.model.dto.WorkspaceDTO; + +import java.util.Optional; + +public interface IWorkspaceService { + + /** + * Query the workspace information based on the primary key id of the database. + * @param id primary key id + * @return + */ + Optional getWorkspaceById(int id); + + /** + * Query the information of a workspace based on its workspace id. + * @param workspaceId + * @return + */ + Optional getWorkspaceByWorkspaceId(String workspaceId); +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java b/src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java new file mode 100644 index 0000000..35597fa --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java @@ -0,0 +1,59 @@ +package com.dji.sample.manage.service.impl; + +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; +import com.dji.sample.component.websocket.model.BizCodeEnum; +import com.dji.sample.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.component.websocket.model.WebSocketManager; +import com.dji.sample.component.websocket.service.ISendMessageService; +import com.dji.sample.manage.model.dto.TelemetryDTO; +import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.service.ITSAService; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Collection; + +/** + * @author sean + * @version 0.3 + * @date 2022/2/21 + */ +public abstract class AbstractTSAService implements ITSAService { + + protected AbstractTSAService tsaService; + + public AbstractTSAService(AbstractTSAService tsaService) { + this.tsaService = tsaService; + } + + @Autowired + protected ISendMessageService sendMessageService; + + @Override + public void pushTelemetryData(String workspaceId, Object osdData, String sn) { + // All connected accounts on the pilot side of this workspace. + Collection pilotSessions = WebSocketManager + .getValueWithWorkspaceAndUserType( + workspaceId, UserTypeEnum.PILOT.getVal()); + + TelemetryDTO telemetry = TelemetryDTO.builder() + .sn(sn) + .build(); + CustomWebSocketMessage pilotMessage = CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.DEVICE_OSD.getCode()) + .data(telemetry) + .build(); + + this.pushTelemetryData(pilotSessions, pilotMessage, osdData); + } + + public abstract void pushTelemetryData(Collection sessions, + CustomWebSocketMessage message, Object Object); + + protected abstract void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode, + Collection webSessions, CustomWebSocketMessage wsMessage) + throws JsonProcessingException; +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java new file mode 100644 index 0000000..a2196e4 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java @@ -0,0 +1,99 @@ +package com.dji.sample.manage.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.manage.dao.ICameraVideoMapper; +import com.dji.sample.manage.model.dto.CapacityVideoDTO; +import com.dji.sample.manage.model.entity.CameraVideoEntity; +import com.dji.sample.manage.model.receiver.CapacityVideoReceiver; +import com.dji.sample.manage.service.ICameraVideoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/19 + */ +@Service +@Transactional +public class CameraVideoServiceImpl implements ICameraVideoService { + + @Autowired + private ICameraVideoMapper mapper; + + @Override + public List getCameraVideosByCameraId(Integer cameraId) { + return mapper.selectList( + new LambdaQueryWrapper() + .eq(CameraVideoEntity::getCameraId, cameraId)) + .stream() + .map(this::entityConvertToDto) + .collect(Collectors.toList()); + } + + @Override + public Boolean deleteCameraVideosById(List ids) { + if (ids.isEmpty()) { + return true; + } + return mapper.deleteBatchIds(ids) > 0; + } + + @Override + public Boolean saveCameraVideoDTOList(List capacityVideoReceivers, Integer cameraId) { + for (CapacityVideoReceiver videoDTO : capacityVideoReceivers) { + CameraVideoEntity videoEntity = videoDTOConvertToEntity(videoDTO); + videoEntity.setCameraId(cameraId); + int saveId = this.saveOneCameraVideoEntity(videoEntity); + if (saveId <= 0) { + return false; + } + } + return true; + } + + /** + * Save the live capability of the lens of this camera. + * @param entity lens data + * @return + */ + private Integer saveOneCameraVideoEntity(CameraVideoEntity entity) { + return mapper.insert(entity) > 0 ? entity.getId() : 0; + } + + /** + * Convert the received lens capability object into a database entity object. + * @param dto received lens object + * @return entity + */ + private CameraVideoEntity videoDTOConvertToEntity(CapacityVideoReceiver dto) { + CameraVideoEntity.CameraVideoEntityBuilder builder = CameraVideoEntity.builder(); + if (dto != null) { + builder + .videoIndex(dto.getVideoIndex()) + .videoType(dto.getVideoType()); + } + return builder.build(); + } + + /** + * Convert database entity objects into lens data transfer object. + * @param entity + * @return data transfer object + */ + private CapacityVideoDTO entityConvertToDto(CameraVideoEntity entity) { + CapacityVideoDTO.CapacityVideoDTOBuilder builder = CapacityVideoDTO.builder(); + + if (entity != null) { + builder + .id(entity.getId()) + .index(entity.getVideoIndex()) + .type(entity.getVideoType()); + } + return builder.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java new file mode 100644 index 0000000..7ac4fc4 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java @@ -0,0 +1,172 @@ +package com.dji.sample.manage.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.manage.dao.ICapacityCameraMapper; +import com.dji.sample.manage.model.dto.CapacityCameraDTO; +import com.dji.sample.manage.model.dto.CapacityVideoDTO; +import com.dji.sample.manage.model.dto.DeviceDictionaryDTO; +import com.dji.sample.manage.model.entity.CapacityCameraEntity; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.receiver.CapacityCameraReceiver; +import com.dji.sample.manage.service.ICameraVideoService; +import com.dji.sample.manage.service.ICapacityCameraService; +import com.dji.sample.manage.service.IDeviceDictionaryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author sean.zhou + * @date 2021/11/19 + * @version 0.1 + */ +@Service +@Transactional +public class CapacityCameraServiceImpl implements ICapacityCameraService { + + @Autowired + private ICapacityCameraMapper mapper; + + @Autowired + private ICameraVideoService cameraVideoService; + + @Autowired + private IDeviceDictionaryService dictionaryService; + + @Override + public List getCapacityCameraByDeviceSn(String deviceSn) { + List capacityCamerasList = mapper.selectList( + new LambdaQueryWrapper() + .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() + .eq(CapacityCameraEntity::getDeviceSn, deviceSn) + .eq(CapacityCameraEntity::getCameraIndex, cameraIndex) + .last(" limit 1")) != null; + } + + @Override + public Boolean deleteCapacityCameraByDeviceSn(String deviceSn) { + + List capacityCamerasList = this.getCapacityCameraByDeviceSn(deviceSn); + // Return directly if no data exists in the database. + if (capacityCamerasList.isEmpty()) { + return true; + } + + List cameraIds = capacityCamerasList + .stream() + .map(CapacityCameraDTO::getId) + .collect(Collectors.toList()); + + List videoIds = capacityCamerasList + .stream() + .flatMap(camera -> camera.getVideosList().stream()) + .map(CapacityVideoDTO::getId) + .collect(Collectors.toList()); + + return mapper.deleteBatchIds(cameraIds) > 0 && cameraVideoService.deleteCameraVideosById(videoIds); + } + + @Override + public Boolean saveCapacityCameraReceiverList(List capacityCameraReceivers, String deviceSn) { + for (CapacityCameraReceiver cameraDTO : capacityCameraReceivers) { + CapacityCameraEntity cameraEntity = receiverConvertToEntity(cameraDTO); + cameraEntity.setDeviceSn(deviceSn); + int cameraId = this.saveOneCapacityCameraEntity(cameraEntity); + if (cameraId <= 0) { + continue; + } + boolean saveVideo = cameraVideoService.saveCameraVideoDTOList( + cameraDTO.getVideosList(), cameraId); + + if (!saveVideo) { + return false; + } + } + return true; + } + + /** + * Save the camera live capability data of the device. + * @param cameraEntity + * @return + */ + private Integer saveOneCapacityCameraEntity(CapacityCameraEntity cameraEntity) { + boolean exist = checkExist( + cameraEntity.getDeviceSn(), cameraEntity.getCameraIndex()); + if (exist) { + return -1; + } + return mapper.insert(cameraEntity) > 0 ? cameraEntity.getId() : 0; + } + + /** + * Convert the received camera capability object into a database entity object. + * @param receiver + * @return + */ + private CapacityCameraEntity receiverConvertToEntity(CapacityCameraReceiver receiver) { + CapacityCameraEntity.CapacityCameraEntityBuilder builder = CapacityCameraEntity.builder(); + if (receiver == null) { + return builder.build(); + } + int[] indexArr = Arrays.stream(receiver.getCameraIndex().split("-")) + .map(Integer::valueOf) + .mapToInt(Integer::intValue) + .toArray(); + // The cameraIndex consists of type and subType and the index of the payload hanging on the drone. + // type-subType-index + if (indexArr.length == 3) { + Optional dictionaryOpt = dictionaryService + .getOneDictionaryInfoByDomainTypeSubType( + DeviceDomainEnum.PAYLOAD.getVal(), indexArr[0], indexArr[1]); + dictionaryOpt.ifPresent(dictionary -> + builder.name(dictionary.getDeviceName())); + } + return builder + .availableVideoNumber(receiver.getAvailableVideoNumber()) + .coexistVideoNumberMax(receiver.getCoexistVideoNumberMax()) + .cameraIndex(receiver.getCameraIndex()) + .build(); + } + + /** + * Convert database entity objects into camera data transfer object. + * @param entity + * @return + */ + private CapacityCameraDTO entityConvertToDto(CapacityCameraEntity entity) { + CapacityCameraDTO.CapacityCameraDTOBuilder builder = CapacityCameraDTO.builder(); + + if (entity != null) { + builder + .id(entity.getId()) + .name(entity.getName()) + .description(entity.getDescription()) + .deviceSn(entity.getDeviceSn()) + .index(entity.getCameraIndex()) + .build(); + } + return builder.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java new file mode 100644 index 0000000..60da3d5 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java @@ -0,0 +1,59 @@ +package com.dji.sample.manage.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.manage.dao.IDeviceDictionaryMapper; +import com.dji.sample.manage.model.dto.DeviceDictionaryDTO; +import com.dji.sample.manage.model.entity.DeviceDictionaryEntity; +import com.dji.sample.manage.service.IDeviceDictionaryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +/** + * + * @author sean.zhou + * @version 0.1 + * @date 2021/11/15 + */ +@Service +@Transactional +public class DeviceDictionaryServiceImpl implements IDeviceDictionaryService { + + @Autowired + private IDeviceDictionaryMapper mapper; + + @Override + public Optional getOneDictionaryInfoByDomainTypeSubType( + Integer domain, Integer deviceType, Integer subType) { + if (domain == null || deviceType == null || subType == null) { + return Optional.empty(); + } + return Optional.ofNullable( + entityConvertToDTO( + mapper.selectOne( + new LambdaQueryWrapper() + .eq(DeviceDictionaryEntity::getDomain, domain) + .eq(DeviceDictionaryEntity::getDeviceType, deviceType) + .eq(DeviceDictionaryEntity::getSubType, subType)))); + } + + /** + * Convert database entity objects into dictionary data transfer object. + * @param entity + * @return + */ + private DeviceDictionaryDTO entityConvertToDTO(DeviceDictionaryEntity entity) { + DeviceDictionaryDTO.DeviceDictionaryDTOBuilder builder = DeviceDictionaryDTO.builder(); + + if (entity != null) { + builder.deviceName(entity.getDeviceName()) + .deviceDesc(entity.getDeviceDesc()) + .deviceType(entity.getDeviceType()) + .domain(entity.getDomain()) + .subType(entity.getSubType()); + } + return builder.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java new file mode 100644 index 0000000..606343c --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java @@ -0,0 +1,69 @@ +package com.dji.sample.manage.service.impl; + +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; +import com.dji.sample.component.websocket.model.BizCodeEnum; +import com.dji.sample.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.manage.model.DeviceStatusManager; +import com.dji.sample.manage.model.dto.TelemetryDTO; +import com.dji.sample.manage.model.dto.TelemetryDeviceDTO; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collection; + +/** + * @author sean + * @version 0.3 + * @date 2022/2/21 + */ +@Service +public class DeviceOSDServiceImpl extends AbstractTSAService { + + protected DeviceOSDServiceImpl() { + super(null); + } + + @Override + public void pushTelemetryData(Collection sessions, + CustomWebSocketMessage message, Object osdData) { + if (osdData instanceof OsdSubDeviceReceiver) { + OsdSubDeviceReceiver data = (OsdSubDeviceReceiver) osdData; + TelemetryDTO telemetry = message.getData(); + telemetry.setHost(TelemetryDeviceDTO.builder() + .latitude(data.getLatitude()) + .longitude(data.getLongitude()) + .altitude(data.getElevation()) + .attitudeHead(data.getAttitudeHead()) + .elevation(data.getElevation()) + .horizontalSpeed(data.getHorizontalSpeed()) + .verticalSpeed(data.getVerticalSpeed()) + .build()); + + this.sendMessageService.sendBatch(sessions, message); + } + } + @Override + protected void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode, + Collection webSessions, CustomWebSocketMessage wsMessage) throws JsonProcessingException { + // Real-time update of device status in memory + DeviceStatusManager.STATUS_MANAGER.put( + DeviceDomainEnum.SUB_DEVICE.getVal() + "/" + sn, LocalDateTime.now()); + wsMessage.setBizCode(BizCodeEnum.DEVICE_OSD.getCode()); + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + OsdSubDeviceReceiver data = mapper.treeToValue(hostNode, OsdSubDeviceReceiver.class); + + wsMessage.setData(data); + + sendMessageService.sendBatch(webSessions, wsMessage); + this.pushTelemetryData(workspaceId, data, sn); + } +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java new file mode 100644 index 0000000..a88504f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java @@ -0,0 +1,157 @@ +package com.dji.sample.manage.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.manage.dao.IDevicePayloadMapper; +import com.dji.sample.manage.model.dto.DeviceDictionaryDTO; +import com.dji.sample.manage.model.dto.DevicePayloadDTO; +import com.dji.sample.manage.model.entity.DevicePayloadEntity; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.receiver.DevicePayloadReceiver; +import com.dji.sample.manage.service.ICapacityCameraService; +import com.dji.sample.manage.service.IDeviceDictionaryService; +import com.dji.sample.manage.service.IDevicePayloadService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author sean.zhou + * @version 0.1 + * @date 2021/11/19 + */ +@Service +@Transactional +public class DevicePayloadServiceImpl implements IDevicePayloadService { + + @Autowired + private IDevicePayloadMapper mapper; + + @Autowired + private IDeviceDictionaryService dictionaryService; + + @Autowired + private ICapacityCameraService capacityCameraService; + + @Override + public Integer checkPayloadExist(String payloadSn) { + DevicePayloadEntity devicePayload = mapper.selectOne( + new LambdaQueryWrapper() + .eq(DevicePayloadEntity::getPayloadSn, payloadSn)); + return devicePayload != null ? devicePayload.getId() : -1; + } + + private Integer saveOnePayloadEntity(DevicePayloadEntity entity) { + int id = this.checkPayloadExist(entity.getPayloadSn()); + // If it already exists, update the data directly. + if (id > 0) { + entity.setId(id); + return mapper.updateById(entity); + } + return mapper.insert(entity) > 0 ? entity.getId() : 0; + } + + @Override + public Boolean savePayloadDTOs(List payloadReceiverList) { + for (DevicePayloadReceiver payloadReceiver : payloadReceiverList) { + int payloadId = this.saveOnePayloadDTO(payloadReceiver); + if (payloadId <= 0) { + return false; + } + } + return true; + } + + @Override + public Integer saveOnePayloadDTO(DevicePayloadReceiver payloadReceiver) { + return this.saveOnePayloadEntity(payloadDTOConvertToEntity(payloadReceiver)); + } + + @Override + public List getDevicePayloadEntitiesByDeviceSn(String deviceSn) { + return mapper.selectList( + new LambdaQueryWrapper() + .eq(DevicePayloadEntity::getDeviceSn, deviceSn)) + .stream() + .map(this::payloadEntityConvertToDTO) + .collect(Collectors.toList()); + } + + @Override + public void deletePayloadsByDeviceSn(List deviceSns) { + deviceSns.forEach(deviceSn -> { + mapper.delete( + new LambdaQueryWrapper() + .eq(DevicePayloadEntity::getDeviceSn, deviceSn)); + capacityCameraService.deleteCapacityCameraByDeviceSn(deviceSn); + }); + } + + /** + * Convert database entity objects into payload data transfer object. + * @param entity + * @return + */ + private DevicePayloadDTO payloadEntityConvertToDTO(DevicePayloadEntity entity) { + DevicePayloadDTO.DevicePayloadDTOBuilder builder = DevicePayloadDTO.builder(); + if (entity != null) { + builder.payloadSn(entity.getPayloadSn()) + .payloadName(entity.getPayloadName()) + .payloadDesc(entity.getPayloadDesc()) + .payloadIndex(entity.getPayloadIndex()); + } + return builder.build(); + } + + /** + * Convert the received payload object into a database entity object. + * @param dto payload + * @return + */ + private DevicePayloadEntity payloadDTOConvertToEntity(DevicePayloadReceiver dto) { + if (dto == null) { + return new DevicePayloadEntity(); + } + DevicePayloadEntity.DevicePayloadEntityBuilder builder = DevicePayloadEntity.builder(); + + // The cameraIndex consists of type and subType and the index of the payload hanging on the drone. + // type-subType-index + String[] payloadIndexArr = dto.getPayloadIndex().split("-"); + try { + int[] arr = Arrays.stream(payloadIndexArr) + .map(Integer::valueOf) + .mapToInt(Integer::intValue) + .toArray(); + + if (arr.length == 3) { + Optional dictionaryOpt = dictionaryService + .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), + arr[0], arr[1]); + dictionaryOpt.ifPresent(dictionary -> + builder.payloadName(dictionary.getDeviceName()) + .payloadDesc(dictionary.getDeviceDesc())); + + } + builder.payloadType(arr[0]) + .subType(arr[1]) + .payloadIndex(arr[2]); + } catch (NumberFormatException e) { + builder.payloadType(Integer.valueOf(payloadIndexArr[0])) + .subType(-1) + .payloadIndex(Integer.valueOf(payloadIndexArr[2])); + } + + return builder + .payloadSn(dto.getSn()) + .version(dto.getVersion()) + .deviceSn(dto.getSn() + .substring(0, + dto.getSn().indexOf("-"))) + .build(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java new file mode 100644 index 0000000..80e34df --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java @@ -0,0 +1,553 @@ +package com.dji.sample.manage.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.component.mqtt.model.CommonTopicResponse; +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.component.mqtt.service.IMessageSenderService; +import com.dji.sample.component.mqtt.service.IMqttTopicService; +import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; +import com.dji.sample.component.websocket.model.BizCodeEnum; +import com.dji.sample.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.component.websocket.model.WebSocketManager; +import com.dji.sample.component.websocket.service.ISendMessageService; +import com.dji.sample.manage.dao.IDeviceMapper; +import com.dji.sample.manage.model.dto.*; +import com.dji.sample.manage.model.entity.DeviceEntity; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.enums.IconUrlEnum; +import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.model.param.DeviceQueryParam; +import com.dji.sample.manage.model.receiver.StatusGatewayReceiver; +import com.dji.sample.manage.model.receiver.StatusSubDeviceReceiver; +import com.dji.sample.manage.service.IDeviceDictionaryService; +import com.dji.sample.manage.service.IDevicePayloadService; +import com.dji.sample.manage.service.IDeviceService; +import com.dji.sample.manage.service.IWorkspaceService; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import static com.dji.sample.component.mqtt.model.TopicConst.*; + +/** + * + * @author sean.zhou + * @version 0.1 + * @date 2021/11/10 + */ +@Service +@Slf4j +@Transactional +public class DeviceServiceImpl implements IDeviceService { + + @Autowired + private IMessageSenderService messageSender; + + @Autowired + private IDeviceMapper mapper; + + @Autowired + private IDeviceDictionaryService dictionaryService; + + @Autowired + private IMqttTopicService topicService; + + @Autowired + private IWorkspaceService workspaceService; + + @Autowired + private IDevicePayloadService payloadService; + + @Autowired + private ISendMessageService sendMessageService; + + @Autowired + @Qualifier("gatewayOSDServiceImpl") + private AbstractTSAService tsaService; + + @Override + public Boolean deviceOffline(String gatewaySn) { + List gatewaysList = this.getDevicesByParams( + DeviceQueryParam.builder() + .deviceSn(gatewaySn) + .build()); + + // If no information about this gateway device exists in the database, the drone is considered to be offline. + if (gatewaysList.isEmpty()) { + log.debug("The drone is already offline."); + return true; + } + // Handle the drone connected to the gateway device offline. + return this.subDeviceOffline(gatewaysList.get(0).getChildDeviceSn()); + } + + @Override + public Boolean subDeviceOffline(String deviceSn) { + // Cancel drone-related subscriptions. + this.unsubscribeTopicOffline(deviceSn); + + List devicesList = this.getDevicesByParams( + DeviceQueryParam.builder() + .deviceSn(deviceSn) + .build()); + + // If no information about this drone exists in the database, the drone is considered to be offline. + if (devicesList.isEmpty()) { + log.debug("{} is already offline.", deviceSn); + return true; + } + + List ids = devicesList.stream() + .map(DeviceDTO::getDeviceSn) + .collect(Collectors.toList()); + + // Delete all data related to the drone. + boolean isDel = this.delDeviceByDeviceSns(ids); + payloadService.deletePayloadsByDeviceSn(ids); + + log.debug("{} offline status: {}.", deviceSn, isDel); + return isDel; + } + + @Override + public Boolean deviceOnline(StatusGatewayReceiver deviceGateway) { + String deviceSn = deviceGateway.getSubDevices().get(0).getSn(); + + List devicesList = this.getDevicesByParams( + DeviceQueryParam.builder() + .deviceSn(deviceSn) + .build()); + // If the information about this drone exists in the database, the drone is considered to be online. + if (!devicesList.isEmpty()) { + log.warn("{} is already online.", deviceSn); + // Subscribe to topic related to drone and gateway devices. + this.subscribeTopicOnline(deviceGateway.getSn()); + this.subscribeTopicOnline(deviceSn); + return true; + } + + // Delete the gateway device information that was previously bound to the drone. + this.delDeviceByDeviceSns( + this.getDevicesByParams( + DeviceQueryParam.builder() + .childSn(deviceSn) + .build()) + .stream() + .map(DeviceDTO::getDeviceSn) + .collect(Collectors.toList())); + + DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway); + gateway.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID); + // Set the icon of the gateway device displayed in the pilot's map, required in the TSA module. + gateway.setUrlNormal(IconUrlEnum.NORMAL_PERSON.getUrl()); + // Set the icon of the gateway device displayed in the pilot's map when it is selected, required in the TSA module. + gateway.setUrlSelect(IconUrlEnum.SELECT_PERSON.getUrl()); + + DeviceEntity subDevice = subDeviceConvertToDeviceEntity(deviceGateway.getSubDevices().get(0)); + subDevice.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID); + // Set the icon of the drone device displayed in the pilot's map, required in the TSA module. + subDevice.setUrlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl()); + // Set the icon of the drone device displayed in the pilot's map when it is selected, required in the TSA module. + subDevice.setUrlNormal(IconUrlEnum.NORMAL_EQUIPMENT.getUrl()); + + gateway.setChildSn(subDevice.getDeviceSn()); + + boolean isSave = this.saveDevice(gateway) > 0 && this.saveDevice(subDevice) > 0; + + log.debug(subDevice.getDeviceSn() + " online status: {}", isSave); + if (isSave) { + // Subscribe to topic related to drone and gateway devices. + this.subscribeTopicOnline(subDevice.getDeviceSn()); + this.subscribeTopicOnline(gateway.getDeviceSn()); + + } + return isSave; + } + + @Override + public void subscribeTopicOnline(String sn) { + String[] subscribedTopic = topicService.getSubscribedTopic(); + for (String s : subscribedTopic) { + // If you have already subscribed to the topic of the device, you do not need to subscribe again. + if (s.contains(sn)) { + return; + } + } + topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF); + topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF); + topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF); + } + + @Override + public void unsubscribeTopicOffline(String sn) { + topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF); + topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF); + topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF); + } + + @Override + public Boolean delDeviceByDeviceSns(List ids) { + if (CollectionUtils.isEmpty(ids)) { + return true; + } + return mapper.delete(new LambdaQueryWrapper() + .in(DeviceEntity::getDeviceSn, ids)) + > 0; + } + + @Override + public void publishStatusReply(String sn, CommonTopicResponse response) { + Map result = new ConcurrentHashMap<>(1); + result.put("result", 0); + response.setData(result); + + messageSender.publish( + new StringBuilder() + .append(BASIC_PRE) + .append(PRODUCT) + .append(sn) + .append(STATUS_SUF) + .append(_REPLY_SUF) + .toString(), + response); + } + + @Override + public List getDevicesByParams(DeviceQueryParam param) { + return mapper.selectList( + new LambdaQueryWrapper() + .eq(StringUtils.hasText(param.getDeviceSn()), + DeviceEntity::getDeviceSn, param.getDeviceSn()) + .eq(param.getDeviceType() != null, + DeviceEntity::getDeviceType, param.getDeviceType()) + .eq(param.getSubType() != null, + DeviceEntity::getSubType, param.getSubType()) + .eq(StringUtils.hasText(param.getChildSn()), + DeviceEntity::getChildSn, param.getChildSn()) + .eq(param.getDomain() != null, + DeviceEntity::getDomain, param.getDomain()) + .eq(StringUtils.hasText(param.getWorkspaceId()), + DeviceEntity::getWorkspaceId, param.getWorkspaceId()) + .orderBy(param.isOrderBy(), + param.isAsc(), DeviceEntity::getId)) + .stream() + .map(this::deviceEntityConvertToDTO) + .collect(Collectors.toList()); + } + + @Override + public List getDevicesTopoForWeb(String workspaceId) { + List devicesList = this.getDevicesByParams( + DeviceQueryParam.builder() + .workspaceId(workspaceId) + .domain(DeviceDomainEnum.SUB_DEVICE.getVal()) + .build()); + + devicesList.forEach(device -> { + this.spliceDeviceTopo(device); + device.setWorkspaceId(workspaceId); + + }); + return devicesList; + } + + @Override + public void spliceDeviceTopo(DeviceDTO device) { + + // remote controller + List gatewaysList = getDevicesByParams( + DeviceQueryParam.builder() + .childSn(device.getDeviceSn()) + .build()); + + // payloads + List payloadsList = payloadService + .getDevicePayloadEntitiesByDeviceSn(device.getDeviceSn()); + + + device.setGatewaysList(gatewaysList); + device.setPayloadsList(payloadsList); + + } + + @Override + public Optional getDeviceTopoForPilot(String sn) { + List topologyDeviceList = this.getDevicesByParams( + DeviceQueryParam.builder() + .deviceSn(sn) + .build()) + .stream() + .map(this::deviceConvertToTopologyDTO) + .collect(Collectors.toList()); + if (topologyDeviceList.isEmpty()) { + return Optional.empty(); + } + return Optional.of(topologyDeviceList.get(0)); + } + + @Override + public void pushDeviceOnlineTopo(Collection sessions, String sn) { + + CustomWebSocketMessage pilotMessage = + CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.DEVICE_ONLINE.getCode()) + .data(new TopologyDeviceDTO()) + .build(); + + this.getDeviceTopoForPilot(sn) + .ifPresent(pilotMessage::setData); + + sendMessageService.sendBatch(sessions, pilotMessage); + } + + @Override + public TopologyDeviceDTO deviceConvertToTopologyDTO(DeviceDTO device) { + TopologyDeviceDTO.TopologyDeviceDTOBuilder builder = TopologyDeviceDTO.builder(); + + if (device != null) { + String domain = String.valueOf(DeviceDomainEnum.getVal(device.getDomain())); + String subType = String.valueOf(device.getSubType()); + String type = String.valueOf(device.getType()); + + builder.sn(device.getDeviceSn()) + .deviceCallsign(device.getDeviceName()) + .deviceModel(DeviceModelDTO.builder() + .domain(domain) + .subType(subType) + .type(type) + .key(domain + "-" + type + "-" + subType) + .build()) + .iconUrls(device.getIconUrl()) + .build(); + } + return builder.build(); + } + + @Override + public void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn) { + + // All connected accounts on the pilot side of this workspace. + Collection pilotSessions = WebSocketManager + .getValueWithWorkspaceAndUserType( + workspaceId, UserTypeEnum.PILOT.getVal()); + + this.pushDeviceOnlineTopo(pilotSessions, deviceSn); + this.pushDeviceOnlineTopo(pilotSessions, gatewaySn); + this.pushDeviceUpdateTopo(pilotSessions, deviceSn); + this.pushDeviceUpdateTopo(pilotSessions, gatewaySn); + } + + @Override + public void pushDeviceOfflineTopo(String workspaceId, String gatewaySn) { + // All connected accounts on the pilot side of this workspace. + Collection pilotSessions = WebSocketManager + .getValueWithWorkspaceAndUserType( + workspaceId, UserTypeEnum.PILOT.getVal()); + + + List gatewaysList = this.getDevicesByParams( + DeviceQueryParam.builder() + .deviceSn(gatewaySn) + .build()); + + if (!gatewaysList.isEmpty()) { + String deviceSn = gatewaysList.get(0).getChildDeviceSn(); + this.pushDeviceOfflineTopo(pilotSessions, deviceSn); + this.pushDeviceUpdateTopo(pilotSessions, deviceSn); + } + + this.pushDeviceOfflineTopo(pilotSessions, gatewaySn); + this.pushDeviceUpdateTopo(pilotSessions, gatewaySn); + } + + @Override + public void handleOSD(String topic, byte[] payload) { + TopicStateReceiver receiver; + try { + String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(), + topic.indexOf(OSD_SUF)); + + List deviceList = this.getDevicesByParams( + DeviceQueryParam.builder().deviceSn(from).build()); + if (deviceList.isEmpty()) { + return; + } + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + receiver = mapper.readValue(payload, TopicStateReceiver.class); + + CustomWebSocketMessage wsMessage = CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()).build(); + + JsonNode hostNode = mapper.readTree(payload).findPath("data"); + + String workspaceId = deviceList.get(0).getWorkspaceId(); + Collection webSessions = WebSocketManager + .getValueWithWorkspaceAndUserType( + workspaceId, UserTypeEnum.WEB.getVal()); + + + tsaService.handleOSD(receiver, from, workspaceId, hostNode, webSessions, wsMessage); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Notify the pilot side that there is an update of the device topology. + * @param sessions + * @param deviceSn + */ + private void pushDeviceUpdateTopo(Collection sessions, String deviceSn) { + + CustomWebSocketMessage pilotMessage = + CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.DEVICE_UPDATE_TOPO.getCode()) + .data(new TopologyDeviceDTO()) + .build(); + sendMessageService.sendBatch(sessions, pilotMessage); + } + + /** + * Notify the pilot side that device is offline and needs to reacquire topology information. + * @param sessions + * @param sn + */ + private void pushDeviceOfflineTopo(Collection sessions, String sn) { + CustomWebSocketMessage pilotMessage = + CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.DEVICE_OFFLINE.getCode()) + .data(TopologyDeviceDTO.builder() + .sn(sn) + .onlineStatus(false) + .build()) + .build(); + sendMessageService.sendBatch(sessions, pilotMessage); + } + + /** + * Save the device information and update the information directly if the device already exists. + * @param entity + * @return + */ + private Integer saveDevice(DeviceEntity entity) { + DeviceEntity deviceEntity = mapper.selectOne( + new LambdaQueryWrapper() + .eq(DeviceEntity::getDeviceSn, entity.getDeviceSn())); + // Update the information directly if the device already exists. + if (deviceEntity != null) { + entity.setId(deviceEntity.getId()); + mapper.updateById(entity); + return deviceEntity.getId(); + } + return mapper.insert(entity) > 0 ? entity.getId() : 0; + } + + /** + * Convert the received gateway device object into a database entity object. + * @param gateway + * @return + */ + private DeviceEntity deviceGatewayConvertToDeviceEntity(StatusGatewayReceiver gateway) { + if (gateway == null) { + return new DeviceEntity(); + } + DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder(); + + // Query the model information of this gateway device. + Optional dictionaryOpt = dictionaryService + .getOneDictionaryInfoByDomainTypeSubType(gateway.getDomain(), + gateway.getType(), gateway.getSubType()); + + dictionaryOpt.ifPresent(entity -> + builder.deviceName(entity.getDeviceName()) + .deviceDesc(entity.getDeviceDesc())); + + return builder + .deviceSn(gateway.getSn()) + .domain(gateway.getDomain()) + .subType(gateway.getSubType()) + .deviceType(gateway.getType()) + .version(gateway.getVersion()) + .build(); + } + + /** + * Convert the received drone device object into a database entity object. + * @param device + * @return + */ + private DeviceEntity subDeviceConvertToDeviceEntity(StatusSubDeviceReceiver device) { + if (device == null) { + return new DeviceEntity(); + } + DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder(); + + // Query the model information of this drone device. + Optional dictionaryOpt = dictionaryService + .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.SUB_DEVICE.getVal(), + device.getType(), device.getSubType()); + + dictionaryOpt.ifPresent(dictionary -> + builder.deviceName(dictionary.getDeviceName()) + .deviceDesc(dictionary.getDeviceDesc())); + + return builder + .deviceSn(device.getSn()) + .deviceType(device.getType()) + .subType(device.getSubType()) + .domain(DeviceDomainEnum.SUB_DEVICE.getVal()) + .version(device.getVersion()) + .deviceIndex(device.getIndex()) + .build(); + } + + /** + * Convert database entity objects into device data transfer object. + * @param entity + * @return + */ + private DeviceDTO deviceEntityConvertToDTO(DeviceEntity entity) { + DeviceDTO.DeviceDTOBuilder builder = DeviceDTO.builder(); + + if (entity != null) { + builder.deviceSn(entity.getDeviceSn()) + .childDeviceSn(entity.getChildSn()) + .deviceName(entity.getDeviceName()) + .deviceDesc(entity.getDeviceDesc()) + .deviceIndex(entity.getDeviceIndex()) + .workspaceId(entity.getWorkspaceId()) + .type(entity.getDeviceType()) + .subType(entity.getSubType()) + .domain(DeviceDomainEnum.getDesc(entity.getDomain())) + .iconUrl(IconUrlDTO.builder() + .normalUrl(entity.getUrlNormal()) + .selectUrl(entity.getUrlSelect()) + .build()) + .build(); + } + return builder.build(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java new file mode 100644 index 0000000..a69b9fe --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java @@ -0,0 +1,74 @@ +package com.dji.sample.manage.service.impl; + +import com.dji.sample.component.mqtt.model.TopicStateReceiver; +import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; +import com.dji.sample.component.websocket.model.BizCodeEnum; +import com.dji.sample.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.manage.model.DeviceStatusManager; +import com.dji.sample.manage.model.dto.TelemetryDTO; +import com.dji.sample.manage.model.dto.TelemetryDeviceDTO; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.receiver.OsdGatewayReceiver; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collection; + +/** + * @author sean + * @version 0.3 + * @date 2022/2/21 + */ +@Service +public class GatewayOSDServiceImpl extends AbstractTSAService { + + public GatewayOSDServiceImpl(@Autowired @Qualifier("deviceOSDServiceImpl") AbstractTSAService tsaService) { + super(tsaService); + } + + @Override + public void pushTelemetryData(Collection sessions, + CustomWebSocketMessage message, Object osdData) { + if (osdData instanceof OsdGatewayReceiver) { + OsdGatewayReceiver data = (OsdGatewayReceiver) osdData; + TelemetryDTO telemetry = message.getData(); + telemetry.setHost(TelemetryDeviceDTO.builder() + .latitude(data.getLatitude()) + .longitude(data.getLongitude()) + .build()); + this.sendMessageService.sendBatch(sessions, message); + return; + } + tsaService.pushTelemetryData(sessions, message, osdData); + } + + @Override + protected void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode, + Collection webSessions, CustomWebSocketMessage wsMessage) throws JsonProcessingException { + if (sn.equals(receiver.getGateway())) { + // Real-time update of device status in memory + DeviceStatusManager.STATUS_MANAGER.put(DeviceDomainEnum.GATEWAY.getVal() + "/" + sn, + LocalDateTime.now()); + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + wsMessage.setBizCode(BizCodeEnum.GATEWAY_OSD.getCode()); + OsdGatewayReceiver data = mapper.treeToValue(hostNode, OsdGatewayReceiver.class); + wsMessage.setData(data); + + this.sendMessageService.sendBatch(webSessions, wsMessage); + + this.pushTelemetryData(workspaceId, data, sn); + return; + } + + tsaService.handleOSD(receiver, sn, workspaceId, hostNode, webSessions, wsMessage); + } +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java new file mode 100644 index 0000000..371ad97 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java @@ -0,0 +1,326 @@ +package com.dji.sample.manage.service.impl; + +import com.dji.sample.common.error.LiveErrorEnum; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.dji.sample.component.mqtt.model.CommonTopicResponse; +import com.dji.sample.component.mqtt.service.IMessageSenderService; +import com.dji.sample.manage.model.Chan; +import com.dji.sample.manage.model.dto.*; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.enums.LiveMethodEnum; +import com.dji.sample.manage.model.enums.LiveUrlTypeEnum; +import com.dji.sample.manage.model.enums.LiveVideoQualityEnum; +import com.dji.sample.manage.model.param.DeviceQueryParam; +import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver; +import com.dji.sample.manage.model.receiver.ServiceReplyReceiver; +import com.dji.sample.manage.service.ICapacityCameraService; +import com.dji.sample.manage.service.IDeviceService; +import com.dji.sample.manage.service.ILiveStreamService; +import com.dji.sample.manage.service.IWorkspaceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.dji.sample.component.mqtt.model.TopicConst.*; + +/** + * @author sean.zhou + * @date 2021/11/22 + * @version 0.1 + */ +@Service +@Transactional +public class LiveStreamServiceImpl implements ILiveStreamService { + + @Autowired + private ICapacityCameraService capacityCameraService; + + @Autowired + private IDeviceService deviceService; + + @Autowired + private IWorkspaceService workspaceService; + + @Autowired + private IMessageSenderService messageSender; + + @Override + public List getLiveCapacity(String workspaceId) { + + // Query all drone data in this workspace. + List devicesList = deviceService.getDevicesByParams( + DeviceQueryParam.builder() + .workspaceId(workspaceId) + .domain(DeviceDomainEnum.SUB_DEVICE.getVal()) + .build()); + + List capacityDevicesList = new ArrayList<>(); + // Query the live capability of each drone. + devicesList.forEach(device -> capacityDevicesList.add(CapacityDeviceDTO.builder() + .name(device.getDeviceName()) + .sn(device.getDeviceSn()) + .camerasList(capacityCameraService.getCapacityCameraByDeviceSn(device.getDeviceSn())) + .build())); + + return capacityDevicesList; + } + + @Override + public Boolean saveLiveCapacity(CapacityDeviceReceiver capacityDeviceReceiver) { + return capacityCameraService.saveCapacityCameraReceiverList( + capacityDeviceReceiver.getCameraList(), + capacityDeviceReceiver.getSn()); + } + + @Override + public ResponseResult liveStart(LiveTypeDTO liveParam) { + // Check if this lens is available live. + ResponseResult responseResult = this.checkBeforeLive(liveParam.getVideoId()); + if (responseResult.getCode() != 0) { + return responseResult; + } + + List data = (List)responseResult.getData(); + // target topic + String respTopic = THING_MODEL_PRE + PRODUCT + + data.get(0).getDeviceSn() + SERVICES_SUF; + Optional receiveReplyOpt = this.publishLiveStart(respTopic, liveParam); + + if (receiveReplyOpt.isEmpty()) { + return ResponseResult.error(LiveErrorEnum.NO_REPLY); + } + if (receiveReplyOpt.get().getResult() != 0) { + return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult())); + } + + LiveUrlTypeEnum urlType = LiveUrlTypeEnum.find(liveParam.getUrlType()); + LiveDTO live = new LiveDTO(); + + switch (urlType) { + case RTMP: + live.setUrl(liveParam.getUrl().replace("rtmp", "webrtc")); + break; + case GB28181: + LiveUrlGB28181DTO gb28181 = urlToGB28181(liveParam.getUrl()); + live.setUrl(new StringBuilder() + .append("webrtc://") + .append(gb28181.getServerIP()) + .append("/live/") + .append(gb28181.getAgentID()) + .append("@") + .append(gb28181.getChannel()) + .toString()); + break; + case RTSP: + String url = receiveReplyOpt.get().getInfo(); + this.resolveUrlUser(url, live); + break; + case UNKNOWN: + return ResponseResult.error(LiveErrorEnum.URL_TYPE_NOT_SUPPORTED); + } + + return ResponseResult.success(live); + } + + @Override + public ResponseResult liveStop(String videoId) { + ResponseResult> responseResult = this.checkBeforeLive(videoId); + if (responseResult.getCode() != 0) { + return responseResult; + } + + String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().get(0).getDeviceSn() + SERVICES_SUF; + + Optional receiveReplyOpt = this.publishLiveStop(respTopic, videoId); + if (receiveReplyOpt.isEmpty()) { + return ResponseResult.error(LiveErrorEnum.NO_REPLY); + } + if (receiveReplyOpt.get().getResult() != 0) { + return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult())); + } + + return ResponseResult.success(); + } + + @Override + public ResponseResult liveSetQuality(LiveTypeDTO liveParam) { + if (liveParam.getVideoQuality() == null || + LiveVideoQualityEnum.UNKNOWN == LiveVideoQualityEnum.find(liveParam.getVideoQuality())) { + return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS); + } + + ResponseResult> responseResult = this.checkBeforeLive(liveParam.getVideoId()); + + if (responseResult.getCode() != 0) { + return responseResult; + } + + String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().get(0).getDeviceSn() + SERVICES_SUF; + + Optional receiveReplyOpt = this.publishLiveSetQuality(respTopic, liveParam); + if (receiveReplyOpt.isEmpty()) { + return ResponseResult.error(LiveErrorEnum.NO_REPLY); + } + if (receiveReplyOpt.get().getResult() != 0) { + return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult())); + } + + return ResponseResult.success(); + } + + /** + * Check if this lens is available live. + * @param videoId + * @return + */ + private ResponseResult checkBeforeLive(String videoId) { + String[] videoIdArr = videoId.split("/"); + // drone sn / enumeration value of the location where the payload is mounted / payload lens + if (videoIdArr.length != 3) { + return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS); + } + + List gatewayList = deviceService.getDevicesByParams( + DeviceQueryParam.builder() + .childSn(videoIdArr[0]) + .build()); + // Check if the gateway device connected to this drone exists + if (gatewayList.isEmpty()) { + return ResponseResult.error(LiveErrorEnum.NO_FLIGHT_CONTROL); + } + return ResponseResult.success(gatewayList); + } + + /** + * When using rtsp live, the account and password are parsed from the information returned by the pilot. + * @param url + * @param live + */ + private void resolveUrlUser(String url, LiveDTO live) { + if (!StringUtils.hasText(url)) { + return; + } + int start = url.indexOf("//"); + int end = url.lastIndexOf("@"); + String user = url.substring(start + 2, end); + + url = url.replace(user + "@", ""); + String[] userArr = user.split(":"); + live.setUsername(userArr[0]); + live.setPassword(userArr[1]); + live.setUrl(url); + } + + /** + * When using GB28181 live, url parameters are resolved into objects. + * @param url + * @return + */ + private LiveUrlGB28181DTO urlToGB28181(String url) { + String[] arr = url.split("\\=|\\&"); + LiveUrlGB28181DTO gb28181 = new LiveUrlGB28181DTO(); + try { + Class clazz = LiveUrlGB28181DTO.class; + for (int i = 0; i < arr.length - 1; i += 2) { + Field field = clazz.getDeclaredField(arr[i]); + field.setAccessible(true); + + if (field.getType().equals(Integer.class)) { + field.set(gb28181, Integer.valueOf(arr[i + 1])); + continue; + } + field.set(gb28181, arr[i + 1]); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + return gb28181; + } + + /** + * Send a message to the pilot via mqtt to start the live streaming. + * @param topic + * @param liveParam + * @return + */ + private Optional publishLiveStart(String topic, LiveTypeDTO liveParam) { + CommonTopicResponse response = new CommonTopicResponse<>(); + response.setTid(UUID.randomUUID().toString()); + response.setBid(UUID.randomUUID().toString()); + response.setData(liveParam); + response.setMethod(LiveMethodEnum.LIVE_START_PUSH.getMethod()); + + return this.publishLive(topic, response); + } + + /** + * Send a message to the pilot via mqtt to set quality. + * @param respTopic + * @param liveParam + * @return + */ + private Optional publishLiveSetQuality(String respTopic, LiveTypeDTO liveParam) { + Map data = new ConcurrentHashMap<>(Map.of( + "video_id", liveParam.getVideoId(), + "video_quality", liveParam.getVideoQuality())); + CommonTopicResponse> response = new CommonTopicResponse<>(); + response.setTid(UUID.randomUUID().toString()); + response.setBid(UUID.randomUUID().toString()); + response.setMethod(LiveMethodEnum.LIVE_SET_QUALITY.getMethod()); + response.setData(data); + + return this.publishLive(respTopic, response); + } + + /** + * Send a message to the pilot via mqtt to stop the live streaming. + * @param topic + * @param videoId + * @return + */ + private Optional publishLiveStop(String topic, String videoId) { + Map data = new ConcurrentHashMap<>(Map.of("video_id", videoId)); + CommonTopicResponse> response = new CommonTopicResponse<>(); + response.setTid(UUID.randomUUID().toString()); + response.setBid(UUID.randomUUID().toString()); + response.setData(data); + response.setMethod(LiveMethodEnum.LIVE_STOP_PUSH.getMethod()); + + return this.publishLive(topic, response); + } + + /** + * Send live streaming start message and receive a response at the same time + * @param topic + * @param response notification of whether the start is successful. + * @return + */ + private Optional publishLive(String topic, CommonTopicResponse response) { + AtomicInteger time = new AtomicInteger(0); + // Retry three times + while (time.getAndIncrement() < 3) { + messageSender.publish(topic, response); + + Chan> chan = Chan.getInstance(); + // If the message is not received in 0.5 seconds then resend it again. + CommonTopicReceiver receiver = chan.get(response.getMethod()); + if (receiver == null) { + continue; + } + // Need to match tid and bid. + if (receiver.getTid().equals(response.getTid()) && + receiver.getBid().equals(response.getBid())) { + return Optional.ofNullable(receiver.getData()); + } + } + return Optional.empty(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java new file mode 100644 index 0000000..aefc059 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java @@ -0,0 +1,55 @@ +package com.dji.sample.manage.service.impl; + +import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.dto.TopologyDTO; +import com.dji.sample.manage.model.dto.TopologyDeviceDTO; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.param.DeviceQueryParam; +import com.dji.sample.manage.service.IDeviceService; +import com.dji.sample.manage.service.ITopologyService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/8 + */ +@Service +public class TopologyServiceImpl implements ITopologyService { + + @Autowired + private IDeviceService deviceService; + + @Override + public List getDeviceTopology(String workspaceId) { + // Query the information of all gateway devices in the workspace. + List gatewayList = deviceService.getDevicesByParams( + DeviceQueryParam.builder() + .workspaceId(workspaceId) + .domain(DeviceDomainEnum.GATEWAY.getVal()) + .build()); + + List topologyList = new ArrayList<>(); + + gatewayList.forEach(device -> { + List parents = new ArrayList<>(); + TopologyDeviceDTO gateway = deviceService.deviceConvertToTopologyDTO(device); + parents.add(gateway); + + // Query the topology data of the drone based on the drone sn. + Optional deviceTopo = deviceService.getDeviceTopoForPilot(device.getChildDeviceSn()); + List deviceTopoList = new ArrayList<>(); + deviceTopo.ifPresent(deviceTopoList::add); + + topologyList.add(TopologyDTO.builder().parents(parents).hosts(deviceTopoList).build()); + }); + + return topologyList; + } + +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..c3c9a1b --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java @@ -0,0 +1,141 @@ +package com.dji.sample.manage.service.impl; + +import com.auth0.jwt.JWT; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.common.util.JwtUtil; +import com.dji.sample.component.mqtt.config.MqttConfiguration; +import com.dji.sample.manage.dao.IUserMapper; +import com.dji.sample.manage.model.dto.UserDTO; +import com.dji.sample.manage.model.dto.WorkspaceDTO; +import com.dji.sample.manage.model.entity.UserEntity; +import com.dji.sample.manage.service.IUserService; +import com.dji.sample.manage.service.IWorkspaceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.Optional; + +@Service +@Transactional +public class UserServiceImpl implements IUserService { + + @Autowired + private IUserMapper mapper; + + @Autowired + private MqttConfiguration mqttConfiguration; + + @Autowired + private IWorkspaceService workspaceService; + + @Override + public ResponseResult getUserByUsername(String username, String workspaceId) { + + UserEntity userEntity = this.getUserByUsername(username); + if (userEntity == null) { + return ResponseResult.builder() + .code(HttpStatus.UNAUTHORIZED.value()) + .message("invalid username") + .build(); + } + + UserDTO user = this.entityConvertToDTO(userEntity); + user.setWorkspaceId(workspaceId); + + return ResponseResult.success(user); + } + + @Override + public ResponseResult userLogin(String username, String password) { + // check user + UserEntity userEntity = this.getUserByUsername(username); + if (userEntity == null) { + return ResponseResult.builder() + .code(HttpStatus.UNAUTHORIZED.value()) + .message("invalid username") + .build(); + } + if (!password.equals(userEntity.getPassword())) { + return ResponseResult.builder() + .code(HttpStatus.UNAUTHORIZED.value()) + .message("invalid password") + .build(); + } + + Optional workspaceOpt = workspaceService.getWorkspaceById(userEntity.getWorkspaceId()); + if (workspaceOpt.isEmpty()) { + return ResponseResult.builder() + .code(HttpStatus.UNAUTHORIZED.value()) + .message("invalid workspace id") + .build(); + } + + CustomClaim customClaim = new CustomClaim(userEntity.getUserId(), + userEntity.getUsername(), userEntity.getUserType(), + workspaceOpt.get().getWorkspaceId()); + + // create token + String token = JwtUtil.createToken(customClaim.convertToMap()); + + UserDTO userDTO = entityConvertToDTO(userEntity); + userDTO.setMqttAddr(new StringBuilder() + .append(mqttConfiguration.getProtocol().trim()) + .append("://") + .append(mqttConfiguration.getHost().trim()) + .append(":") + .append(mqttConfiguration.getPort()) + .toString()); + userDTO.setAccessToken(token); + userDTO.setWorkspaceId(workspaceOpt.get().getWorkspaceId()); + return ResponseResult.success(userDTO); + } + + @Override + public Optional refreshToken(String token) { + if (!StringUtils.hasText(token)) { + return Optional.empty(); + } + CustomClaim customClaim = new CustomClaim(JWT.decode(token).getClaims()); + String refreshToken = JwtUtil.createToken(customClaim.convertToMap()); + + UserDTO user = entityConvertToDTO(this.getUserByUsername(customClaim.getUsername())); + user.setWorkspaceId(customClaim.getWorkspaceId()); + user.setAccessToken(refreshToken); + return Optional.of(user); + } + + /** + * Query a user by username. + * @param username + * @return + */ + private UserEntity getUserByUsername(String username) { + return mapper.selectOne(new QueryWrapper() + .eq("username", username)); + } + + private UserDTO entityConvertToDTO(UserEntity entity) { + if (entity == null) { + return new UserDTO(); + } + return UserDTO.builder() + .userId(entity.getUserId()) + .username(entity.getUsername()) + .userType(entity.getUserType()) + .mqttUsername(entity.getMqttUsername()) + .mqttPassword(entity.getMqttPassword()) + .mqttAddr(new StringBuilder() + .append(mqttConfiguration.getProtocol().trim()) + .append("://") + .append(mqttConfiguration.getHost().trim()) + .append(":") + .append(mqttConfiguration.getPort()) + .toString()) + .build(); + } +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java new file mode 100644 index 0000000..d6ed3ab --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java @@ -0,0 +1,52 @@ +package com.dji.sample.manage.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.manage.dao.IWorkspaceMapper; +import com.dji.sample.manage.model.dto.WorkspaceDTO; +import com.dji.sample.manage.model.entity.WorkspaceEntity; +import com.dji.sample.manage.service.IWorkspaceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Service +@Transactional +public class WorkspaceServiceImpl implements IWorkspaceService { + + @Autowired + private IWorkspaceMapper mapper; + + @Override + public Optional getWorkspaceById(int id) { + return Optional.ofNullable(entityConvertToDto(mapper.selectById(id))); + } + + @Override + public Optional getWorkspaceByWorkspaceId(String workspaceId) { + return Optional.ofNullable(entityConvertToDto( + mapper.selectOne( + new LambdaQueryWrapper() + .eq(WorkspaceEntity::getWorkspaceId, workspaceId)))); + } + + /** + * Convert database entity objects into workspace data transfer object. + * @param entity + * @return + */ + private WorkspaceDTO entityConvertToDto(WorkspaceEntity entity) { + WorkspaceDTO.WorkspaceDTOBuilder builder = WorkspaceDTO.builder(); + if (entity == null) { + return builder.build(); + } + return builder + .id(entity.getId()) + .workspaceId(entity.getWorkspaceId()) + .platformName(entity.getPlatformName()) + .workspaceDesc(entity.getWorkspaceDesc()) + .workspaceName(entity.getWorkspaceName()) + .build(); + } +} diff --git a/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java b/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java new file mode 100644 index 0000000..1ad52b8 --- /dev/null +++ b/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java @@ -0,0 +1,184 @@ +package com.dji.sample.map.controller; + +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.websocket.model.BizCodeEnum; +import com.dji.sample.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.component.websocket.model.WebSocketManager; +import com.dji.sample.component.websocket.service.ISendMessageService; +import com.dji.sample.map.model.dto.*; +import com.dji.sample.map.service.IWorkspaceElementService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@RestController +@RequestMapping("${url.map.prefix}${url.map.version}/workspaces") +public class WorkspaceElementController { + + @Autowired + private IWorkspaceElementService elementService; + + @Autowired + private ISendMessageService sendMessageService; + + /** + * In the first connection, pilot will send out this http request to obtain the group element list. + * Also, if pilot receives a group refresh instruction from WebSocket, + * it needs the same interface to request the group element list. + * @param workspaceId + * @param groupId + * @param isDistributed + * @return + */ + @GetMapping("/{workspace_id}/element-groups") + public ResponseResult> getAllElements(@PathVariable(name = "workspace_id") String workspaceId, + @RequestParam(name = "group_id", required = false) String groupId, + @RequestParam(name = "is_distributed", required = false) Boolean isDistributed) { + List groupsList = elementService.getAllGroupsByWorkspaceId(workspaceId, groupId, isDistributed); + return ResponseResult.success(groupsList); + } + + /** + * When user draws a point, line or polygon on the PILOT/Web side. + * Save the element information to the database. + * @param request + * @param workspaceId + * @param groupId + * @param elementCreate + * @return + */ + @PostMapping("/{workspace_id}/element-groups/{group_id}/elements") + public ResponseResult saveElement(HttpServletRequest request, + @PathVariable(name = "workspace_id") String workspaceId, + @PathVariable(name = "group_id") String groupId, + @RequestBody ElementCreateDTO elementCreate) { + CustomClaim claims = (CustomClaim) request.getAttribute(TOKEN_CLAIM); + // Set the creator of the element + elementCreate.getResource().setUsername(claims.getUsername()); + + ResponseResult response = elementService.saveElement(groupId, elementCreate); + if (response.getCode() != ResponseResult.CODE_SUCCESS) { + return response; + } + + // Notify all WebSocket connections in this workspace to be updated when an element is created. + elementService.getElementByElementId(elementCreate.getId()) + .ifPresent(groupElement -> + sendMessageService.sendBatch( + WebSocketManager.getValueWithWorkspace(workspaceId), + CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.MAP_ELEMENT_CREATE.getCode()) + .data(groupElement) + .build())); + + return ResponseResult.success(new ConcurrentHashMap<>(Map.of("id", elementCreate.getId()))); + } + + /** + * When user edits a point, line or polygon on the PILOT/Web side. + * Update the element information to the database. + * @param request + * @param workspaceId + * @param elementId + * @param elementUpdate + * @return + */ + @PutMapping("/{workspace_id}/elements/{element_id}") + public ResponseResult updateElement(HttpServletRequest request, + @PathVariable(name = "workspace_id") String workspaceId, + @PathVariable(name = "element_id") String elementId, + @RequestBody ElementUpdateDTO elementUpdate) { + + CustomClaim claims = (CustomClaim) request.getAttribute(TOKEN_CLAIM); + + ResponseResult response = elementService.updateElement(elementId, elementUpdate, claims.getUsername()); + if (response.getCode() != ResponseResult.CODE_SUCCESS) { + return response; + } + + // Notify all WebSocket connections in this workspace to update when there is an element update. + elementService.getElementByElementId(elementId) + .ifPresent(groupElement -> + sendMessageService.sendBatch( + WebSocketManager.getValueWithWorkspace(workspaceId), + CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.MAP_ELEMENT_UPDATE.getCode()) + .data(groupElement) + .build())); + return response; + } + + /** + * When user delete a point, line or polygon on the PILOT/Web side, + * Delete the element information in the database. + * @param workspaceId + * @param elementId + * @return + */ + @DeleteMapping("/{workspace_id}/elements/{element_id}") + public ResponseResult deleteElement(@PathVariable(name = "workspace_id") String workspaceId, + @PathVariable(name = "element_id") String elementId) { + + Optional elementOpt = elementService.getElementByElementId(elementId); + + ResponseResult response = elementService.deleteElement(elementId); + + // Notify all WebSocket connections in this workspace to update when an element is deleted. + if (ResponseResult.CODE_SUCCESS == response.getCode()) { + elementOpt.ifPresent(element -> + sendMessageService.sendBatch( + WebSocketManager.getValueWithWorkspace(workspaceId), + CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.MAP_ELEMENT_DELETE.getCode()) + .data(WebSocketElementDelDTO.builder() + .elementId(elementId) + .groupId(element.getGroupId()) + .build()) + .build())); + } + return response; + } + + /** + * Delete all the element information in this group based on the group id. + * @param workspaceId + * @param groupId + * @return + */ + @DeleteMapping("/{workspace_id}/element-groups/{group_id}/elements") + public ResponseResult deleteAllElementByGroupId(@PathVariable(name = "workspace_id") String workspaceId, + @PathVariable(name = "group_id") String groupId) { + + ResponseResult response = elementService.deleteAllElementByGroupId(groupId); + + // Notify all WebSocket connections in this workspace to update when elements are deleted. + if (ResponseResult.CODE_SUCCESS == response.getCode()) { + + sendMessageService.sendBatch( + WebSocketManager.getValueWithWorkspace(workspaceId), + CustomWebSocketMessage.builder() + .timestamp(System.currentTimeMillis()) + .bizCode(BizCodeEnum.MAP_GROUP_REFRESH.getCode()) + // Group ids that need to re-request data + .data(new ConcurrentHashMap(Map.of("ids", new String[]{groupId}))) + .build()); + } + return response; + } +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java b/src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java new file mode 100644 index 0000000..5073ce5 --- /dev/null +++ b/src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java @@ -0,0 +1,12 @@ +package com.dji.sample.map.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.map.model.entity.ElementCoordinateEntity; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +public interface IElementCoordinateMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java b/src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java new file mode 100644 index 0000000..4b572c7 --- /dev/null +++ b/src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java @@ -0,0 +1,12 @@ +package com.dji.sample.map.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.map.model.entity.GroupElementEntity; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +public interface IGroupElementMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/map/dao/IGroupMapper.java b/src/main/java/com/dji/sample/map/dao/IGroupMapper.java new file mode 100644 index 0000000..747f7fd --- /dev/null +++ b/src/main/java/com/dji/sample/map/dao/IGroupMapper.java @@ -0,0 +1,12 @@ +package com.dji.sample.map.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.map.model.entity.GroupEntity; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +public interface IGroupMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java b/src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java new file mode 100644 index 0000000..e8fe1d2 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java @@ -0,0 +1,25 @@ +package com.dji.sample.map.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ContentPropertyDTO { + + private String color; + + @JsonProperty("clampToGround") + private Boolean clampToGround; + +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java new file mode 100644 index 0000000..e0799c4 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java @@ -0,0 +1,25 @@ +package com.dji.sample.map.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ElementCoordinateDTO { + + private Double longitude; + + private Double latitude; + + private Double altitude; + +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java new file mode 100644 index 0000000..a594bba --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java @@ -0,0 +1,18 @@ +package com.dji.sample.map.model.dto; + +import lombok.Data; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Data +public class ElementCreateDTO { + + private String id; + + private String name; + + private ElementResourceDTO resource; +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java new file mode 100644 index 0000000..117790b --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java @@ -0,0 +1,47 @@ +package com.dji.sample.map.model.dto; + +import com.dji.sample.map.model.enums.ElementTypeEnum; +import lombok.Data; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Data +public class ElementLineStringDTO extends ElementType { + + private Double[][] coordinates; + + public ElementLineStringDTO() { + super(ElementTypeEnum.LINE_STRING.getDesc()); + } + + @Override + public List convertToList() { + List coordinateList = new ArrayList<>(); + for (Double[] coordinate : this.coordinates) { + coordinateList.add(ElementCoordinateDTO.builder() + .longitude(coordinate[0]) + .latitude(coordinate[1]) + .build()); + } + return coordinateList; + } + + @Override + public void adapterCoordinateType(List coordinateList) { + if (CollectionUtils.isEmpty(coordinateList)) { + return; + } + this.coordinates = new Double[coordinateList.size()][2]; + for (int i = 0; i < this.coordinates.length; i++) { + this.coordinates[i][0] = coordinateList.get(i).getLongitude(); + this.coordinates[i][1] = coordinateList.get(i).getLatitude(); + } + } +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java new file mode 100644 index 0000000..3eeee7d --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java @@ -0,0 +1,46 @@ +package com.dji.sample.map.model.dto; + +import com.dji.sample.map.model.enums.ElementTypeEnum; +import lombok.Data; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Data +public class ElementPointDTO extends ElementType { + + private Double[] coordinates; + + public ElementPointDTO() { + super(ElementTypeEnum.POINT.getDesc()); + } + + @Override + public List convertToList() { + List coordinateList = new ArrayList<>(); + coordinateList.add(ElementCoordinateDTO.builder() + .longitude(this.coordinates[0]) + .latitude(this.coordinates[1]) + .altitude(this.coordinates.length == 3 ? this.coordinates[2] : null) + .build()); + return coordinateList; + } + + @Override + public void adapterCoordinateType(List coordinateList) { + if (CollectionUtils.isEmpty(coordinateList)) { + return; + } + this.coordinates = new Double[]{ + coordinateList.get(0).getLongitude(), + coordinateList.get(0).getLatitude(), + coordinateList.get(0).getAltitude() + }; + } +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java new file mode 100644 index 0000000..35a8d35 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java @@ -0,0 +1,48 @@ +package com.dji.sample.map.model.dto; + +import com.dji.sample.map.model.enums.ElementTypeEnum; +import lombok.Data; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Data +public class ElementPolygonDTO extends ElementType { + + private Double[][][] coordinates; + + public ElementPolygonDTO() { + super(ElementTypeEnum.POLYGON.getDesc()); + } + + @Override + public List convertToList() { + List coordinateList = new ArrayList<>(); + + for (Double[] coordinate : this.coordinates[0]) { + coordinateList.add(ElementCoordinateDTO.builder() + .longitude(coordinate[0]) + .latitude(coordinate[1]) + .build()); + } + return coordinateList; + } + + @Override + public void adapterCoordinateType(List coordinateList) { + if (CollectionUtils.isEmpty(coordinateList)) { + return; + } + this.coordinates = new Double[1][coordinateList.size()][2]; + for (int i = 0; i < this.coordinates[0].length; i++) { + this.coordinates[0][i][0] = coordinateList.get(i).getLongitude(); + this.coordinates[0][i][1] = coordinateList.get(i).getLatitude(); + } + } +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java new file mode 100644 index 0000000..b89df70 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java @@ -0,0 +1,26 @@ +package com.dji.sample.map.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ElementResourceDTO { + + private Integer type; + + @JsonProperty(value = "user_name") + private String username; + + private ResourceContentDTO content; +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementType.java b/src/main/java/com/dji/sample/map/model/dto/ElementType.java new file mode 100644 index 0000000..1fdf3f6 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ElementType.java @@ -0,0 +1,43 @@ +package com.dji.sample.map.model.dto; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Data +@NoArgsConstructor +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", + include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = ElementType.class) +@JsonSubTypes({ + @JsonSubTypes.Type(value = ElementPointDTO.class, name = "Point"), + @JsonSubTypes.Type(value = ElementLineStringDTO.class, name = "LineString"), + @JsonSubTypes.Type(value = ElementPolygonDTO.class, name = "Polygon") +}) +public abstract class ElementType { + + private String type; + + ElementType(String type) { + this.type = type; + } + + /** + * Convert coordinate data into objects and then add them to the collection. + * @return + */ + public abstract List convertToList(); + + /** + * Converts coordinates in a collection of objects to a specific type. + * @param coordinateList + */ + public abstract void adapterCoordinateType(List coordinateList); +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java b/src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java new file mode 100644 index 0000000..58897e2 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java @@ -0,0 +1,17 @@ +package com.dji.sample.map.model.dto; + +import lombok.Data; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/1 + */ +@Data +public class ElementUpdateDTO { + + private String name; + + private ResourceContentDTO content; + +} diff --git a/src/main/java/com/dji/sample/map/model/dto/GroupDTO.java b/src/main/java/com/dji/sample/map/model/dto/GroupDTO.java new file mode 100644 index 0000000..1c75e67 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/GroupDTO.java @@ -0,0 +1,38 @@ +package com.dji.sample.map.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Data +public class GroupDTO { + + private String id; + + private String name; + + private Integer type; + + private List elements; + + @JsonProperty(value = "is_distributed") + private Boolean isDistributed; + + @JsonProperty(value = "is_lock") + private Boolean isLock; + + @JsonProperty(value = "create_time") + private Long createTime; +} diff --git a/src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java b/src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java new file mode 100644 index 0000000..b48d1b6 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java @@ -0,0 +1,38 @@ +package com.dji.sample.map.model.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GroupElementDTO { + private Integer display; + + @JsonProperty("id") + private String elementId; + + private String name; + + @JsonProperty(value = "create_time") + private Long createTime; + + @JsonProperty(value = "update_time") + private Long updateTime; + + private ElementResourceDTO resource; + + @JsonProperty("group_id") + private String groupId; +} diff --git a/src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java b/src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java new file mode 100644 index 0000000..6ae3c9a --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java @@ -0,0 +1,25 @@ +package com.dji.sample.map.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ResourceContentDTO { + + @Builder.Default + private String type = "Feature"; + + private ContentPropertyDTO properties; + + private ElementType geometry; +} diff --git a/src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java b/src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java new file mode 100644 index 0000000..5d99272 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java @@ -0,0 +1,25 @@ +package com.dji.sample.map.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/2 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WebSocketElementDelDTO { + + @JsonProperty("id") + private String elementId; + + @JsonProperty("group_id") + private String groupId; +} diff --git a/src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java b/src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java new file mode 100644 index 0000000..26d2732 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java @@ -0,0 +1,41 @@ +package com.dji.sample.map.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@TableName(value = "map_element_coordinate") +public class ElementCoordinateEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField("element_id") + private String elementId; + + @TableField("longitude") + private Double longitude; + + @TableField("latitude") + private Double latitude; + + @TableField("altitude") + private Double altitude; + +} diff --git a/src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java b/src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java new file mode 100644 index 0000000..7c6748a --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java @@ -0,0 +1,55 @@ +package com.dji.sample.map.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@TableName(value = "map_group_element") +public class GroupElementEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField("group_id") + private String groupId; + + @TableField("element_id") + private String elementId; + + @TableField("element_name") + private String elementName; + + @TableField("display") + private Integer display; + + @TableField("element_type") + private Integer elementType; + + @TableField("username") + private String username; + + @TableField("color") + private String color; + + @TableField("clamp_to_ground") + private Boolean clampToGround; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Long createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Long updateTime; +} diff --git a/src/main/java/com/dji/sample/map/model/entity/GroupEntity.java b/src/main/java/com/dji/sample/map/model/entity/GroupEntity.java new file mode 100644 index 0000000..c639a9b --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/entity/GroupEntity.java @@ -0,0 +1,49 @@ +package com.dji.sample.map.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@TableName(value = "map_group") +public class GroupEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField("group_id") + private String groupId; + + @TableField("group_name") + private String groupName; + + @TableField("group_type") + private Integer groupType; + + @TableField("workspace_id") + private String workspaceId; + + @TableField("is_distributed") + private Boolean isDistributed; + + @TableField("is_lock") + private Boolean isLock; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Long createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Long updateTime; +} diff --git a/src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java b/src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java new file mode 100644 index 0000000..886ebe2 --- /dev/null +++ b/src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java @@ -0,0 +1,69 @@ +package com.dji.sample.map.model.enums; + +import com.dji.sample.map.model.dto.ElementLineStringDTO; +import com.dji.sample.map.model.dto.ElementPointDTO; +import com.dji.sample.map.model.dto.ElementPolygonDTO; +import com.dji.sample.map.model.dto.ElementType; + +import java.util.Optional; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +public enum ElementTypeEnum { + + POINT(0, "Point"), + + LINE_STRING(1, "LineString"), + + POLYGON(2, "Polygon"), + + UNKNOWN(-1, "Unknown"); + + private int val; + + private String desc; + + ElementTypeEnum(int val, String desc) { + this.val = val; + this.desc = desc; + } + + public static Optional findType(int val) { + if (POINT.val == val) { + return Optional.of(new ElementPointDTO()); + } + + if (LINE_STRING.val == val) { + return Optional.of(new ElementLineStringDTO()); + } + + if (POLYGON.val == val) { + return Optional.of(new ElementPolygonDTO()); + } + + return Optional.empty(); + } + + public String getDesc() { + return desc; + } + + public static int findVal(String desc) { + if (POINT.desc.equals(desc)) { + return POINT.val; + } + + if (LINE_STRING.desc.equals(desc)) { + return LINE_STRING.val; + } + + if (POLYGON.desc.equals(desc)) { + return POLYGON.val; + } + + return UNKNOWN.val; + } +} diff --git a/src/main/java/com/dji/sample/map/service/IElementCoordinateService.java b/src/main/java/com/dji/sample/map/service/IElementCoordinateService.java new file mode 100644 index 0000000..7668205 --- /dev/null +++ b/src/main/java/com/dji/sample/map/service/IElementCoordinateService.java @@ -0,0 +1,35 @@ +package com.dji.sample.map.service; + +import com.dji.sample.map.model.dto.ElementCoordinateDTO; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +public interface IElementCoordinateService { + + /** + * Query all the coordinates of the element according to its id. + * @param elementId + * @return + */ + List getCoordinateByElementId(String elementId); + + /** + * Save all the coordinate data of this element. + * @param coordinate + * @param elementId + * @return + */ + Boolean saveCoordinate(List coordinate, String elementId); + + /** + * Delete all the coordinates of the element according to its id. + * @param elementId + * @return + */ + Boolean deleteCoordinateByElementId(String elementId); +} diff --git a/src/main/java/com/dji/sample/map/service/IGroupElementService.java b/src/main/java/com/dji/sample/map/service/IGroupElementService.java new file mode 100644 index 0000000..d6513fa --- /dev/null +++ b/src/main/java/com/dji/sample/map/service/IGroupElementService.java @@ -0,0 +1,54 @@ +package com.dji.sample.map.service; + +import com.dji.sample.map.model.dto.ElementCreateDTO; +import com.dji.sample.map.model.dto.ElementUpdateDTO; +import com.dji.sample.map.model.dto.GroupElementDTO; + +import java.util.List; +import java.util.Optional; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +public interface IGroupElementService { + + /** + * Query all the elements in this group based on the group id. + * @param groupId + * @return + */ + List getElementsByGroupId(String groupId); + + /** + * Save all the elements. + * @param groupId + * @param elementCreate + * @return + */ + Boolean saveElement(String groupId, ElementCreateDTO elementCreate); + + /** + * Query the element information based on the element id and update element. + * @param elementId + * @param elementUpdate + * @param username + * @return + */ + Boolean updateElement(String elementId, ElementUpdateDTO elementUpdate, String username); + + /** + * Delete the element based on the element id. + * @param elementId + * @return + */ + Boolean deleteElement(String elementId); + + /** + * Query an element based on the element id, including the coordinate information in the elements. + * @param elementId + * @return + */ + Optional getElementByElementId(String elementId); +} diff --git a/src/main/java/com/dji/sample/map/service/IGroupService.java b/src/main/java/com/dji/sample/map/service/IGroupService.java new file mode 100644 index 0000000..bead14b --- /dev/null +++ b/src/main/java/com/dji/sample/map/service/IGroupService.java @@ -0,0 +1,24 @@ +package com.dji.sample.map.service; + +import com.dji.sample.map.model.dto.GroupDTO; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +public interface IGroupService { + + /** + * Query all groups in the workspace based on the workspace's id. + * If the group id does not exist, do not add this filter condition. + * @param workspaceId + * @param groupId + * @param isDistributed Used to define if the group needs to be distributed. Default is true. + * @return + */ + List getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed); + +} diff --git a/src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java b/src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java new file mode 100644 index 0000000..4d24769 --- /dev/null +++ b/src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java @@ -0,0 +1,71 @@ +package com.dji.sample.map.service; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.map.model.dto.ElementCreateDTO; +import com.dji.sample.map.model.dto.ElementUpdateDTO; +import com.dji.sample.map.model.dto.GroupDTO; +import com.dji.sample.map.model.dto.GroupElementDTO; + +import java.util.List; +import java.util.Optional; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +public interface IWorkspaceElementService { + + /** + * Query all groups in the workspace based on the workspace's id, + * including the information of the elements in the group, and the coordinate information in the elements. + * @param workspaceId + * @param groupId + * @param isDistributed + * @return + */ + List getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed); + + /** + * Save all the elements, including the information of the elements in the group, + * and the coordinate information in the elements. + * @param groupId + * @param elementCreate + * @return + */ + ResponseResult saveElement(String groupId, ElementCreateDTO elementCreate); + + /** + * Update the element information based on the element id, + * including the information of the elements in the group, and the coordinate information in the elements. + * @param elementId + * @param elementUpdate + * @param username + * @return + */ + ResponseResult updateElement(String elementId, ElementUpdateDTO elementUpdate, String username); + + /** + * Delete the element information based on the element id, + * including the information of the elements in the group, and the coordinate information in the elements. + * @param elementId + * @return + */ + ResponseResult deleteElement(String elementId); + + /** + * Query an element based on the element id, + * including the information of the elements in the group, and the coordinate information in the elements. + * @param elementId + * @return + */ + Optional getElementByElementId(String elementId); + + /** + * Delete all the elements information based on the group id, + * including the information of the elements in the group, and the coordinate information in the elements. + * @param groupId + * @return + */ + ResponseResult deleteAllElementByGroupId(String groupId); +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java b/src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java new file mode 100644 index 0000000..bab9775 --- /dev/null +++ b/src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java @@ -0,0 +1,94 @@ +package com.dji.sample.map.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.dji.sample.map.dao.IElementCoordinateMapper; +import com.dji.sample.map.model.dto.ElementCoordinateDTO; +import com.dji.sample.map.model.entity.ElementCoordinateEntity; +import com.dji.sample.map.service.IElementCoordinateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@Service +@Transactional +public class ElementCoordinateServiceImpl implements IElementCoordinateService { + + @Autowired + private IElementCoordinateMapper mapper; + + @Override + public List getCoordinateByElementId(String elementId) { + return mapper.selectList( + new LambdaQueryWrapper() + .eq(ElementCoordinateEntity::getElementId, elementId)) + .stream() + .map(this::entityConvertToDto) + .collect(Collectors.toList()); + } + + @Override + public Boolean saveCoordinate(List coordinateList, String elementId) { + for (ElementCoordinateDTO coordinate : coordinateList) { + ElementCoordinateEntity entity = this.dtoConvertToEntity(coordinate); + entity.setElementId(elementId); + + int insert = mapper.insert(entity); + if (insert <= 0) { + return false; + } + } + return true; + } + + @Override + public Boolean deleteCoordinateByElementId(String elementId) { + return mapper.delete(new LambdaUpdateWrapper() + .eq(ElementCoordinateEntity::getElementId, elementId)) > 0; + } + + /** + * Convert database entity objects into coordinate data transfer object. + * @param entity + * @return + */ + private ElementCoordinateDTO entityConvertToDto(ElementCoordinateEntity entity) { + ElementCoordinateDTO.ElementCoordinateDTOBuilder builder = ElementCoordinateDTO.builder(); + if (entity == null) { + return builder.build(); + } + + return builder + .longitude(entity.getLongitude()) + .latitude(entity.getLatitude()) + .altitude(entity.getAltitude()) + .build(); + } + + /** + * Convert the received coordinate object into a database entity object. + * @param coordinate + * @return + */ + private ElementCoordinateEntity dtoConvertToEntity(ElementCoordinateDTO coordinate) { + ElementCoordinateEntity.ElementCoordinateEntityBuilder builder = ElementCoordinateEntity.builder(); + + if (coordinate == null) { + return builder.build(); + } + + return builder + .altitude(coordinate.getAltitude()) + .latitude(coordinate.getLatitude()) + .longitude(coordinate.getLongitude()) + .build(); + } +} diff --git a/src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java b/src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java new file mode 100644 index 0000000..7ee4ce9 --- /dev/null +++ b/src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java @@ -0,0 +1,199 @@ +package com.dji.sample.map.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.map.dao.IGroupElementMapper; +import com.dji.sample.map.model.dto.*; +import com.dji.sample.map.model.entity.GroupElementEntity; +import com.dji.sample.map.model.enums.ElementTypeEnum; +import com.dji.sample.map.service.IElementCoordinateService; +import com.dji.sample.map.service.IGroupElementService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@Service +@Transactional +public class GroupElementServiceImpl implements IGroupElementService { + + @Autowired + private IGroupElementMapper mapper; + + @Autowired + private IElementCoordinateService elementCoordinateService; + + @Override + public List getElementsByGroupId(String groupId) { + List elementList = mapper.selectList( + new LambdaQueryWrapper() + .eq(GroupElementEntity::getGroupId, groupId)); + + List groupElementList = new ArrayList<>(); + for (GroupElementEntity elementEntity : elementList) { + + GroupElementDTO groupElement = this.entityConvertToDto(elementEntity); + groupElementList.add(groupElement); + + this.addCoordinateToElement(groupElement, elementEntity); + } + return groupElementList; + } + + @Override + public Boolean saveElement(String groupId, ElementCreateDTO elementCreate) { + Optional groupElementOpt = this.getEntityByElementId(elementCreate.getId()); + + if (groupElementOpt.isPresent()) { + return false; + } + GroupElementEntity groupElement = this.createDtoConvertToEntity(elementCreate); + groupElement.setGroupId(groupId); + + return mapper.insert(groupElement) > 0; + } + + @Override + public Boolean updateElement(String elementId, ElementUpdateDTO elementUpdate, String username) { + Optional groupElementOpt = this.getEntityByElementId(elementId); + if (groupElementOpt.isEmpty()) { + return false; + } + + GroupElementEntity groupElement = groupElementOpt.get(); + groupElement.setUsername(username); + this.updateEntityWithDto(elementUpdate, groupElement); + + return mapper.updateById(groupElement) > 0; + } + + @Override + public Boolean deleteElement(String elementId) { + Optional groupElementOpt = this.getEntityByElementId(elementId); + if (groupElementOpt.isEmpty()) { + return true; + } + + GroupElementEntity groupElement = groupElementOpt.get(); + return mapper.deleteById(groupElement.getId()) > 0; + } + + + @Override + public Optional getElementByElementId(String elementId) { + Optional elementEntityOpt = this.getEntityByElementId(elementId); + if (elementEntityOpt.isEmpty()) { + return Optional.empty(); + } + GroupElementEntity elementEntity = elementEntityOpt.get(); + GroupElementDTO groupElement = this.entityConvertToDto(elementEntity); + + this.addCoordinateToElement(groupElement, elementEntity); + + return Optional.ofNullable(groupElement); + } + + /** + * Adds the received coordinate data to the element object. + * @param element + * @param elementEntity + */ + private void addCoordinateToElement(GroupElementDTO element, GroupElementEntity elementEntity) { + Optional coordinateOpt = ElementTypeEnum.findType(elementEntity.getElementType()); + if (coordinateOpt.isEmpty()) { + return; + } + + ElementType elementType = coordinateOpt.get(); + + element.getResource().setContent( + ResourceContentDTO.builder() + .properties(ContentPropertyDTO.builder() + .clampToGround(elementEntity.getClampToGround()) + .color(elementEntity.getColor()) + .build()) + .geometry(elementType) + .build()); + + elementType.adapterCoordinateType( + elementCoordinateService.getCoordinateByElementId(elementEntity.getElementId())); + } + + /** + * Query an element based on the element id。 + * @param elementId + * @return + */ + private Optional getEntityByElementId(String elementId) { + return Optional.ofNullable(mapper.selectOne( + new LambdaQueryWrapper() + .eq(GroupElementEntity::getElementId, elementId))); + } + + /** + * Convert database entity objects into element data transfer object. + * @param entity + * @return + */ + private GroupElementDTO entityConvertToDto(GroupElementEntity entity) { + GroupElementDTO.GroupElementDTOBuilder builder = GroupElementDTO.builder(); + if (entity == null) { + return builder.build(); + } + + return builder + .display(entity.getDisplay()) + .groupId(entity.getGroupId()) + .elementId(entity.getElementId()) + .name(entity.getElementName()) + .createTime(entity.getCreateTime()) + .updateTime(entity.getUpdateTime()) + .resource(ElementResourceDTO.builder() + .type(entity.getElementType()) + .username(entity.getUsername()) + .build()) + .build(); + } + + /** + * Convert the received element object into a database entity object. + * @param elementCreate + * @return + */ + private GroupElementEntity createDtoConvertToEntity(ElementCreateDTO elementCreate) { + ContentPropertyDTO properties = elementCreate.getResource().getContent().getProperties(); + return GroupElementEntity.builder() + .elementId(elementCreate.getId()) + .elementName(elementCreate.getName()) + .username(elementCreate.getResource().getUsername()) + .elementType(ElementTypeEnum.findVal(elementCreate.getResource().getContent().getGeometry().getType())) + .clampToGround(properties.getClampToGround() != null && properties.getClampToGround()) + .color(properties.getColor()) + .build(); + } + + /** + * Add the content that needs to be updated to the entity object to be updated. + * @param elementUpdate + * @param groupElement + */ + private void updateEntityWithDto(ElementUpdateDTO elementUpdate, GroupElementEntity groupElement) { + if (elementUpdate == null || groupElement == null) { + return; + } + + groupElement.setElementName(elementUpdate.getName()); + groupElement.setElementType(ElementTypeEnum.findVal(elementUpdate.getContent().getGeometry().getType())); + groupElement.setColor(elementUpdate.getContent().getProperties().getColor()); + + boolean clampToGround = elementUpdate.getContent().getProperties().getClampToGround(); + groupElement.setClampToGround(clampToGround); + } +} diff --git a/src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java b/src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java new file mode 100644 index 0000000..07ac7f1 --- /dev/null +++ b/src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java @@ -0,0 +1,66 @@ +package com.dji.sample.map.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.map.dao.IGroupMapper; +import com.dji.sample.map.model.dto.GroupDTO; +import com.dji.sample.map.model.entity.GroupEntity; +import com.dji.sample.map.service.IGroupElementService; +import com.dji.sample.map.service.IGroupService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/29 + */ +@Service +@Transactional +public class GroupServiceImpl implements IGroupService { + + @Autowired + private IGroupMapper mapper; + + @Autowired + private IGroupElementService groupElementService; + + @Override + public List getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed) { + + return mapper.selectList( + new LambdaQueryWrapper() + .eq(GroupEntity::getWorkspaceId, workspaceId) + .eq(StringUtils.hasText(groupId), GroupEntity::getGroupId, groupId) + .eq(isDistributed != null, GroupEntity::getIsDistributed, isDistributed)) + .stream() + .map(this::entityConvertToDto) + .collect(Collectors.toList()); + } + + /** + * Convert database entity objects into group data transfer object. + * @param entity + * @return + */ + private GroupDTO entityConvertToDto(GroupEntity entity) { + GroupDTO.GroupDTOBuilder builder = GroupDTO.builder(); + + if (entity == null) { + return builder.build(); + } + + return builder + .id(entity.getGroupId()) + .name(entity.getGroupName()) + .type(entity.getGroupType()) + .isLock(entity.getIsLock()) + .isDistributed(entity.getIsDistributed()) + .createTime(entity.getCreateTime()) + .build(); + } +} diff --git a/src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java b/src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java new file mode 100644 index 0000000..49b5805 --- /dev/null +++ b/src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java @@ -0,0 +1,109 @@ +package com.dji.sample.map.service.impl; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.map.model.dto.ElementCreateDTO; +import com.dji.sample.map.model.dto.ElementUpdateDTO; +import com.dji.sample.map.model.dto.GroupDTO; +import com.dji.sample.map.model.dto.GroupElementDTO; +import com.dji.sample.map.service.IElementCoordinateService; +import com.dji.sample.map.service.IGroupElementService; +import com.dji.sample.map.service.IGroupService; +import com.dji.sample.map.service.IWorkspaceElementService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +/** + * @author sean + * @version 0.2 + * @date 2021/11/30 + */ +@Transactional +@Service +public class WorkspaceElementServiceImpl implements IWorkspaceElementService { + + @Autowired + private IGroupService groupService; + + @Autowired + private IGroupElementService groupElementService; + + @Autowired + private IElementCoordinateService elementCoordinateService; + + @Override + public List getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed) { + List groupList = groupService.getAllGroupsByWorkspaceId(workspaceId, groupId, isDistributed); + groupList.forEach(group -> group.setElements( + groupElementService.getElementsByGroupId(group.getId()) + )); + return groupList; + } + + @Override + public ResponseResult saveElement(String groupId, ElementCreateDTO elementCreate) { + boolean saveElement = groupElementService.saveElement(groupId, elementCreate); + if (!saveElement) { + return ResponseResult.error("Failed to save the element."); + } + + // save coordinate + boolean saveCoordinate = elementCoordinateService.saveCoordinate( + elementCreate.getResource().getContent().getGeometry().convertToList(), elementCreate.getId()); + + return saveCoordinate ? + ResponseResult.success() : ResponseResult.error("Failed to save the coordinate."); + } + + + @Override + public ResponseResult updateElement(String elementId, ElementUpdateDTO elementUpdate, String username) { + boolean updElement = groupElementService.updateElement(elementId, elementUpdate, username); + if (!updElement) { + return ResponseResult.error("Failed to update the element."); + } + + // delete all coordinates according to element id. + boolean delCoordinate = elementCoordinateService.deleteCoordinateByElementId(elementId); + // save coordinate + boolean saveCoordinate = elementCoordinateService.saveCoordinate( + elementUpdate.getContent().getGeometry().convertToList(), elementId); + + return delCoordinate && saveCoordinate ? + ResponseResult.success() : ResponseResult.error("Failed to update the coordinate."); + } + + @Override + public ResponseResult deleteElement(String elementId) { + boolean delElement = groupElementService.deleteElement(elementId); + if (!delElement) { + return ResponseResult.error("Failed to delete the element."); + } + + // delete all coordinates according to element id. + boolean delCoordinate = elementCoordinateService.deleteCoordinateByElementId(elementId); + + return delCoordinate ? + ResponseResult.success() : ResponseResult.error("Failed to delete the coordinate."); + } + + @Override + public Optional getElementByElementId(String elementId) { + return groupElementService.getElementByElementId(elementId); + } + + @Override + public ResponseResult deleteAllElementByGroupId(String groupId) { + List groupElementList = groupElementService.getElementsByGroupId(groupId); + for (GroupElementDTO groupElement : groupElementList) { + ResponseResult response = this.deleteElement(groupElement.getElementId()); + if (ResponseResult.CODE_SUCCESS != response.getCode()) { + return response; + } + } + return ResponseResult.success(); + } +} diff --git a/src/main/java/com/dji/sample/media/controller/FileController.java b/src/main/java/com/dji/sample/media/controller/FileController.java new file mode 100644 index 0000000..25a6015 --- /dev/null +++ b/src/main/java/com/dji/sample/media/controller/FileController.java @@ -0,0 +1,36 @@ +package com.dji.sample.media.controller; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.media.model.MediaFileDTO; +import com.dji.sample.media.service.IFileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +@RestController +@RequestMapping("${url.media.prefix}${url.media.version}/files") +public class FileController { + + @Autowired + private IFileService fileService; + + /** + * Get information about all the media files in this workspace based on the workspace id. + * @param workspaceId + * @return + */ + @GetMapping("/{workspace_id}/files") + public ResponseResult> getFilesList(@PathVariable(name = "workspace_id") String workspaceId) { + List filesList = fileService.getAllFilesByWorkspaceId(workspaceId); + return ResponseResult.success(filesList); + } +} diff --git a/src/main/java/com/dji/sample/media/controller/MediaController.java b/src/main/java/com/dji/sample/media/controller/MediaController.java new file mode 100644 index 0000000..0e73743 --- /dev/null +++ b/src/main/java/com/dji/sample/media/controller/MediaController.java @@ -0,0 +1,73 @@ +package com.dji.sample.media.controller; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.media.model.FileUploadDTO; +import com.dji.sample.media.service.IMediaService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/7 + */ +@Slf4j +@RestController +@RequestMapping("${url.media.prefix}${url.media.version}/workspaces") +public class MediaController { + + @Autowired + private IMediaService mediaService; + + /** + * Check if the file has been uploaded by the fingerprint. + * @param workspaceId + * @param file + * @return + */ + @PostMapping("/{workspace_id}/fast-upload") + public ResponseResult fastUpload(@PathVariable(name = "workspace_id") String workspaceId, @RequestBody FileUploadDTO file) { + + boolean isExist = mediaService.fastUpload(workspaceId, file.getFingerprint()); + + return isExist ? ResponseResult.success() : ResponseResult.error(file.getFingerprint() + "already exists."); + } + + /** + * When the file is uploaded to the storage server by pilot, + * the basic information of the file is reported through this interface. + * @param workspaceId + * @param file + * @return + */ + @PostMapping("/{workspace_id}/upload-callback") + public ResponseResult uploadCallback(@PathVariable(name = "workspace_id") String workspaceId, @RequestBody FileUploadDTO file) { + mediaService.saveMediaFile(workspaceId, file); + return ResponseResult.success(file.getObjectKey()); + + } + + /** + * Query the files that already exist in this workspace based on the workspace id and the collection of tiny fingerprints. + * @param workspaceId + * @param tinyFingerprints + * @return + */ + @GetMapping("/{workspace_id}/files/tiny-fingerprints") + public ResponseResult>> uploadCallback(@PathVariable(name = "workspace_id") String workspaceId, + @RequestParam(value = "tiny_fingerprint") List tinyFingerprints) { + List tinyFingerprintList = mediaService.getAllTinyFingerprintsByWorkspaceId(workspaceId); + List existingList = tinyFingerprints + .stream() + .filter(tinyFingerprintList::contains) + .collect(Collectors.toList()); + return ResponseResult.success(new ConcurrentHashMap<>(Map.of("tiny_fingerprints", existingList))); + } + +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/media/dao/IFileMapper.java b/src/main/java/com/dji/sample/media/dao/IFileMapper.java new file mode 100644 index 0000000..ec01af1 --- /dev/null +++ b/src/main/java/com/dji/sample/media/dao/IFileMapper.java @@ -0,0 +1,12 @@ +package com.dji.sample.media.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.media.model.MediaFileEntity; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +public interface IFileMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/media/model/CredentialsDTO.java b/src/main/java/com/dji/sample/media/model/CredentialsDTO.java new file mode 100644 index 0000000..2662254 --- /dev/null +++ b/src/main/java/com/dji/sample/media/model/CredentialsDTO.java @@ -0,0 +1,40 @@ +package com.dji.sample.media.model; + +import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse; +import io.minio.credentials.Credentials; +import lombok.Data; + + +/** + * @author sean + * @version 0.2 + * @date 2021/12/7 + */ +@Data +public class CredentialsDTO { + + private String accessKeyId; + + private String accessKeySecret; + + private Integer expire; + + private String securityToken; + + public CredentialsDTO(Credentials credentials, int expire) { + this.accessKeyId = credentials.accessKey(); + this.accessKeySecret = credentials.secretKey(); + this.securityToken = credentials.sessionToken(); + this.expire = expire; + } + + public CredentialsDTO(AssumeRoleResponse.Credentials credentials, long expire) { + this.accessKeyId = credentials.getAccessKeyId(); + this.accessKeySecret = credentials.getAccessKeySecret(); + this.securityToken = credentials.getSecurityToken(); + this.expire = Math.toIntExact(expire); + } + + public CredentialsDTO() { + } +} diff --git a/src/main/java/com/dji/sample/media/model/FileExtensionDTO.java b/src/main/java/com/dji/sample/media/model/FileExtensionDTO.java new file mode 100644 index 0000000..5a33bc6 --- /dev/null +++ b/src/main/java/com/dji/sample/media/model/FileExtensionDTO.java @@ -0,0 +1,26 @@ +package com.dji.sample.media.model; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/7 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class FileExtensionDTO { + + private String droneModelKey; + + private Boolean isOriginal; + + private String payloadModelKey; + + private String tinnyFingerprint; + + private String sn; + +} diff --git a/src/main/java/com/dji/sample/media/model/FileMetadataDTO.java b/src/main/java/com/dji/sample/media/model/FileMetadataDTO.java new file mode 100644 index 0000000..bab4b5a --- /dev/null +++ b/src/main/java/com/dji/sample/media/model/FileMetadataDTO.java @@ -0,0 +1,31 @@ +package com.dji.sample.media.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.Date; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/7 + */ +@Data +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class FileMetadataDTO { + + private Double absoluteAltitude; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssX") + private Date createdTime; + + private Double gimbalYawDegree; + + private PositionDTO photoedPosition; + + private PositionDTO shootPosition; + + private Double relativeAltitude; +} diff --git a/src/main/java/com/dji/sample/media/model/FileUploadDTO.java b/src/main/java/com/dji/sample/media/model/FileUploadDTO.java new file mode 100644 index 0000000..acfa910 --- /dev/null +++ b/src/main/java/com/dji/sample/media/model/FileUploadDTO.java @@ -0,0 +1,26 @@ +package com.dji.sample.media.model; + +import lombok.Data; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/7 + */ +@Data +public class FileUploadDTO { + + private FileExtensionDTO ext; + + private String fingerprint; + + private String name; + + private String path; + + private FileMetadataDTO metadata; + + private String objectKey; + + private Integer subFileType; +} diff --git a/src/main/java/com/dji/sample/media/model/MediaFileDTO.java b/src/main/java/com/dji/sample/media/model/MediaFileDTO.java new file mode 100644 index 0000000..b42acf8 --- /dev/null +++ b/src/main/java/com/dji/sample/media/model/MediaFileDTO.java @@ -0,0 +1,34 @@ +package com.dji.sample.media.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MediaFileDTO { + + private String fileName; + + private String filePath; + + private String objectKey; + + private String subFileType; + + private Boolean isOriginal; + + private String drone; + + private String payload; + + private String tinnyFingerprint; +} diff --git a/src/main/java/com/dji/sample/media/model/MediaFileEntity.java b/src/main/java/com/dji/sample/media/model/MediaFileEntity.java new file mode 100644 index 0000000..a3dd2aa --- /dev/null +++ b/src/main/java/com/dji/sample/media/model/MediaFileEntity.java @@ -0,0 +1,62 @@ +package com.dji.sample.media.model; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +@TableName(value = "media_file") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MediaFileEntity implements Serializable { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField("file_name") + private String fileName; + + @TableField("file_path") + private String filePath; + + @TableField("workspace_id") + private String workspaceId; + + @TableField("fingerprint") + private String fingerprint; + + @TableField("tinny_fingerprint") + private String tinnyFingerprint; + + @TableField("object_key") + private String objectKey; + + @TableField("sub_file_type") + private Integer subFileType; + + @TableField("is_original") + private Boolean isOriginal; + + @TableField("drone") + private String drone; + + @TableField("payload") + private String payload; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Long createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Long updateTime; +} + diff --git a/src/main/java/com/dji/sample/media/model/PositionDTO.java b/src/main/java/com/dji/sample/media/model/PositionDTO.java new file mode 100644 index 0000000..1b5cda4 --- /dev/null +++ b/src/main/java/com/dji/sample/media/model/PositionDTO.java @@ -0,0 +1,16 @@ +package com.dji.sample.media.model; + +import lombok.Data; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/7 + */ +@Data +public class PositionDTO { + + private Double lat; + + private Double lng; +} diff --git a/src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java b/src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java new file mode 100644 index 0000000..a4da2ab --- /dev/null +++ b/src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java @@ -0,0 +1,33 @@ +package com.dji.sample.media.model; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/7 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +public class StsCredentialsDTO { + + private String bucket; + + private CredentialsDTO credentials; + + private String endpoint; + + private String objectKeyPrefix; + + private String provider; + + private String region; +} diff --git a/src/main/java/com/dji/sample/media/service/IFileService.java b/src/main/java/com/dji/sample/media/service/IFileService.java new file mode 100644 index 0000000..9347ccf --- /dev/null +++ b/src/main/java/com/dji/sample/media/service/IFileService.java @@ -0,0 +1,37 @@ +package com.dji.sample.media.service; + +import com.dji.sample.media.model.FileUploadDTO; +import com.dji.sample.media.model.MediaFileDTO; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +public interface IFileService { + + /** + * Query if the file already exists based on the workspace id and the fingerprint of the file. + * @param workspaceId + * @param fingerprint + * @return + */ + Boolean checkExist(String workspaceId, String fingerprint); + + /** + * Save the basic information of the file to the database. + * @param workspaceId + * @param file + * @return + */ + Integer saveFile(String workspaceId, FileUploadDTO file); + + /** + * Query information about all files in this workspace based on the workspace id. + * @param workspaceId + * @return + */ + List getAllFilesByWorkspaceId(String workspaceId); +} diff --git a/src/main/java/com/dji/sample/media/service/IMediaService.java b/src/main/java/com/dji/sample/media/service/IMediaService.java new file mode 100644 index 0000000..f8fc42d --- /dev/null +++ b/src/main/java/com/dji/sample/media/service/IMediaService.java @@ -0,0 +1,36 @@ +package com.dji.sample.media.service; + +import com.dji.sample.media.model.FileUploadDTO; + +import java.util.List; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +public interface IMediaService { + + /** + * Check if the file has been uploaded by the fingerprint. + * @param workspaceId + * @param fingerprint + * @return + */ + Boolean fastUpload(String workspaceId, String fingerprint); + + /** + * Save the basic information of the file to the database. + * @param workspaceId + * @param file + * @return + */ + Integer saveMediaFile(String workspaceId, FileUploadDTO file); + + /** + * Query tiny fingerprints about all files in this workspace based on the workspace id. + * @param workspaceId + * @return + */ + List getAllTinyFingerprintsByWorkspaceId(String workspaceId); +} diff --git a/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java b/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java new file mode 100644 index 0000000..744d9d7 --- /dev/null +++ b/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java @@ -0,0 +1,112 @@ +package com.dji.sample.media.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dji.sample.manage.model.dto.DeviceDictionaryDTO; +import com.dji.sample.manage.service.IDeviceDictionaryService; +import com.dji.sample.media.dao.IFileMapper; +import com.dji.sample.media.model.FileUploadDTO; +import com.dji.sample.media.model.MediaFileDTO; +import com.dji.sample.media.model.MediaFileEntity; +import com.dji.sample.media.service.IFileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +@Service +@Transactional +public class FileServiceImpl implements IFileService { + + + @Autowired + private IFileMapper mapper; + + @Autowired + private IDeviceDictionaryService deviceDictionaryService; + + @Override + public Boolean checkExist(String workspaceId, String fingerprint) { + MediaFileEntity fileEntity = mapper.selectOne(new LambdaQueryWrapper() + .eq(MediaFileEntity::getWorkspaceId, workspaceId) + .eq(MediaFileEntity::getFingerprint, fingerprint)); + return fileEntity != null; + } + + @Override + public Integer saveFile(String workspaceId, FileUploadDTO file) { + MediaFileEntity fileEntity = this.fileUploadConvertToEntity(file); + fileEntity.setWorkspaceId(workspaceId); + return mapper.insert(fileEntity); + } + + @Override + public List getAllFilesByWorkspaceId(String workspaceId) { + return mapper.selectList(new LambdaQueryWrapper() + .eq(MediaFileEntity::getWorkspaceId, workspaceId)) + .stream() + .map(this::entityConvertToDto) + .collect(Collectors.toList()); + } + + /** + * Convert the received file object into a database entity object. + * @param file + * @return + */ + private MediaFileEntity fileUploadConvertToEntity(FileUploadDTO file) { + MediaFileEntity.MediaFileEntityBuilder builder = MediaFileEntity.builder(); + + if (file != null) { + builder.fileName(file.getName()) + .filePath(file.getPath()) + .fingerprint(file.getFingerprint()) + .objectKey(file.getObjectKey()) + .subFileType(file.getSubFileType()) + .isOriginal(file.getExt().getIsOriginal()) + .drone(file.getExt().getSn()) + .tinnyFingerprint(file.getExt().getTinnyFingerprint()); + + // domain-type-subType + int[] payloadModel = Arrays.stream(file.getExt().getPayloadModelKey().split("-")) + .map(Integer::valueOf) + .mapToInt(Integer::intValue) + .toArray(); + Optional payloadDict = deviceDictionaryService + .getOneDictionaryInfoByDomainTypeSubType(payloadModel[0], payloadModel[1], payloadModel[2]); + payloadDict.ifPresent(payload -> builder.payload(payload.getDeviceName())); + } + return builder.build(); + } + + /** + * Convert database entity objects into file data transfer object. + * @param entity + * @return + */ + private MediaFileDTO entityConvertToDto(MediaFileEntity entity) { + MediaFileDTO.MediaFileDTOBuilder builder = MediaFileDTO.builder(); + + if (entity != null) { + builder.fileName(entity.getFileName()) + .filePath(entity.getFilePath()) + .isOriginal(entity.getIsOriginal()) + .objectKey(entity.getObjectKey()) + .tinnyFingerprint(entity.getTinnyFingerprint()) + .payload(entity.getPayload()) + .drone(entity.getDrone()); + + } + + return builder.build(); + } + +} diff --git a/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java b/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java new file mode 100644 index 0000000..f7758d8 --- /dev/null +++ b/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java @@ -0,0 +1,41 @@ +package com.dji.sample.media.service.impl; + +import com.dji.sample.media.model.FileUploadDTO; +import com.dji.sample.media.model.MediaFileDTO; +import com.dji.sample.media.service.IFileService; +import com.dji.sample.media.service.IMediaService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author sean + * @version 0.2 + * @date 2021/12/9 + */ +@Service +public class MediaServiceImpl implements IMediaService { + + @Autowired + private IFileService fileService; + + @Override + public Boolean fastUpload(String workspaceId, String fingerprint) { + return fileService.checkExist(workspaceId, fingerprint); + } + + @Override + public Integer saveMediaFile(String workspaceId, FileUploadDTO file) { + return fileService.saveFile(workspaceId, file); + } + + @Override + public List getAllTinyFingerprintsByWorkspaceId(String workspaceId) { + return fileService.getAllFilesByWorkspaceId(workspaceId) + .stream() + .map(MediaFileDTO::getTinnyFingerprint) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/dji/sample/storage/controller/StorageController.java b/src/main/java/com/dji/sample/storage/controller/StorageController.java new file mode 100644 index 0000000..801146c --- /dev/null +++ b/src/main/java/com/dji/sample/storage/controller/StorageController.java @@ -0,0 +1,51 @@ +package com.dji.sample.storage.controller; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.oss.model.AliyunOSSConfiguration; +import com.dji.sample.component.oss.model.MinIOConfiguration; +import com.dji.sample.media.model.StsCredentialsDTO; +import com.dji.sample.storage.service.IStorageService; +import com.dji.sample.storage.service.impl.AliyunStorageServiceImpl; +import com.dji.sample.storage.service.impl.MinIOStorageServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/29 + */ +@RestController +@RequestMapping("${url.storage.prefix}${url.storage.version}/workspaces/") +public class StorageController { + + private IStorageService storageService; + + @Autowired + private void setOssService(@Autowired AliyunStorageServiceImpl aliyunStorageService, + @Autowired MinIOStorageServiceImpl minIOStorageService) { + if (AliyunOSSConfiguration.enable) { + this.storageService = aliyunStorageService; + return; + } + if (MinIOConfiguration.enable) { + this.storageService = minIOStorageService; + return; + } + throw new NullPointerException("storageService is null."); + } + /** + * Get temporary credentials for uploading the media and wayline in DJI Pilot. + * @param workspaceId + * @return + */ + @PostMapping("/{workspace_id}/sts") + public ResponseResult getSTSCredentials(@PathVariable(name = "workspace_id") String workspaceId) { + + StsCredentialsDTO stsCredentials = storageService.getSTSCredentials(); + return ResponseResult.success(stsCredentials); + } +} diff --git a/src/main/java/com/dji/sample/storage/service/IStorageService.java b/src/main/java/com/dji/sample/storage/service/IStorageService.java new file mode 100644 index 0000000..0dc98c9 --- /dev/null +++ b/src/main/java/com/dji/sample/storage/service/IStorageService.java @@ -0,0 +1,17 @@ +package com.dji.sample.storage.service; + +import com.dji.sample.media.model.StsCredentialsDTO; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/29 + */ +public interface IStorageService { + + /** + * Get custom temporary credentials object for uploading the media and wayline. + * @return temporary credentials object + */ + StsCredentialsDTO getSTSCredentials(); +} diff --git a/src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java b/src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java new file mode 100644 index 0000000..46366a5 --- /dev/null +++ b/src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java @@ -0,0 +1,32 @@ +package com.dji.sample.storage.service.impl; + +import com.dji.sample.component.oss.model.AliyunOSSConfiguration; +import com.dji.sample.component.oss.service.impl.AliyunOssServiceImpl; +import com.dji.sample.media.model.StsCredentialsDTO; +import com.dji.sample.storage.service.IStorageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author sean + * @version 0.3 + * @date 2022/3/9 + */ +@Service +public class AliyunStorageServiceImpl implements IStorageService { + + @Autowired + private AliyunOssServiceImpl ossService; + + @Override + public StsCredentialsDTO getSTSCredentials() { + return StsCredentialsDTO.builder() + .endpoint(AliyunOSSConfiguration.endpoint) + .bucket(AliyunOSSConfiguration.bucket) + .credentials(ossService.getCredentials()) + .provider(AliyunOSSConfiguration.PROVIDER) + .objectKeyPrefix(AliyunOSSConfiguration.objectDirPrefix) + .region(AliyunOSSConfiguration.region) + .build(); + } +} diff --git a/src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java b/src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java new file mode 100644 index 0000000..b03e856 --- /dev/null +++ b/src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java @@ -0,0 +1,34 @@ +package com.dji.sample.storage.service.impl; + +import com.dji.sample.component.oss.model.MinIOConfiguration; +import com.dji.sample.component.oss.service.impl.MinIOServiceImpl; +import com.dji.sample.media.model.StsCredentialsDTO; +import com.dji.sample.storage.service.IStorageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/31 + */ +@Service +public class MinIOStorageServiceImpl implements IStorageService { + + @Autowired + private MinIOServiceImpl ossService; + + @Override + public StsCredentialsDTO getSTSCredentials() { + return StsCredentialsDTO.builder() + .endpoint(MinIOConfiguration.endpoint) + .bucket(MinIOConfiguration.bucket) + .credentials(ossService.getCredentials()) + .provider(MinIOConfiguration.PROVIDER) + .objectKeyPrefix(MinIOConfiguration.objectDirPrefix) + .region(MinIOConfiguration.region) + .build(); + } + + +} diff --git a/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java b/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java new file mode 100644 index 0000000..dbd9025 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java @@ -0,0 +1,150 @@ +package com.dji.sample.wayline.controller; + +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.model.PaginationData; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.oss.model.AliyunOSSConfiguration; +import com.dji.sample.wayline.model.WaylineFileDTO; +import com.dji.sample.wayline.model.WaylineFileUploadDTO; +import com.dji.sample.wayline.model.WaylineQueryParam; +import com.dji.sample.wayline.service.IWaylineFileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URL; +import java.util.List; + +import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +@RestController +@RequestMapping("${url.wayline.prefix}${url.wayline.version}/workspaces") +public class WaylineFileController { + + @Autowired + private IWaylineFileService waylineFileService; + + /** + * Query the basic data of the wayline file according to the query conditions. + * The query condition field in pilot is fixed. + * @param orderBy Sorted fields. Spliced at the end of the sql statement. + * @param favorited Whether the wayline file is favorited or not. + * @param page + * @param pageSize + * @param templateType + * @param workspaceId + * @return + */ + @GetMapping("/{workspace_id}/waylines") + public ResponseResult> getWaylinesPagination(@RequestParam(name = "order_by") String orderBy, + @RequestParam(required = false) boolean favorited, @RequestParam Integer page, + @RequestParam(name = "page_size", defaultValue = "10") Integer pageSize, + @RequestParam(name = "template_type", required = false) Integer[] templateType, + @PathVariable(name = "workspace_id") String workspaceId) { + WaylineQueryParam param = WaylineQueryParam.builder() + .favorited(favorited) + .page(page) + .pageSize(pageSize) + .orderBy(orderBy) + .templateType(templateType) + .build(); + PaginationData data = waylineFileService.getWaylinesByParam(workspaceId, param); + return ResponseResult.success(data); + } + + /** + * Query the download address of the file according to the wayline file id, + * and redirect to this address directly for download. + * @param workspaceId + * @param waylineId + * @param response + */ + @GetMapping("/{workspace_id}/waylines/{wayline_id}/url") + public void getFileUrl(@PathVariable(name = "workspace_id") String workspaceId, + @PathVariable(name = "wayline_id") String waylineId, HttpServletResponse response) { + + WaylineFileDTO wayline = waylineFileService.getWaylineByWaylineId(workspaceId, waylineId); + URL url = waylineFileService.getObjectUrl(AliyunOSSConfiguration.bucket, wayline.getObjectKey()); + + try { + response.sendRedirect(url.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * When the wayline file is uploaded to the storage server by pilot, + * the basic information of the file is reported through this interface. + * @param request + * @param workspaceId + * @param uploadFile + * @return + */ + @PostMapping("/{workspace_id}/upload-callback") + public ResponseResult uploadCallBack(HttpServletRequest request, + @PathVariable(name = "workspace_id") String workspaceId, + @RequestBody WaylineFileUploadDTO uploadFile) { + + CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM); + + WaylineFileDTO metadata = uploadFile.getMetadata(); + metadata.setUsername(customClaim.getUsername()); + metadata.setObjectKey(uploadFile.getObjectKey()); + metadata.setName(uploadFile.getName()); + + int id = waylineFileService.saveWaylineFile(workspaceId, metadata); + + return id <= 0 ? ResponseResult.error() : ResponseResult.success(); + } + + /** + * Favorite the wayline file according to the wayline file id. + * @param workspaceId + * @param ids wayline file id + * @return + */ + @PostMapping("/{workspace_id}/favorites") + public ResponseResult markFavorite(@PathVariable(name = "workspace_id") String workspaceId, + @RequestParam(name = "id") List ids) { + boolean isMark = waylineFileService.markFavorite(workspaceId, ids, true); + + return isMark ? ResponseResult.success() : ResponseResult.error(); + } + + /** + * Delete the favorites of this wayline file based on the wayline file id. + * @param workspaceId + * @param ids wayline file id + * @return + */ + @DeleteMapping("/{workspace_id}/favorites") + public ResponseResult unmarkFavorite(@PathVariable(name = "workspace_id") String workspaceId, + @RequestParam(name = "id") List ids) { + boolean isMark = waylineFileService.markFavorite(workspaceId, ids, false); + + return isMark ? ResponseResult.success() : ResponseResult.error(); + } + + /** + * Checking whether the name already exists according to the wayline name must ensure the uniqueness of the wayline name. + * This interface will be called when uploading waylines and must be available. + * @param workspaceId + * @param names + * @return + */ + @GetMapping("/{workspace_id}/waylines/duplicate-names") + public ResponseResult checkDuplicateNames(@PathVariable(name = "workspace_id") String workspaceId, + @RequestParam(name = "name") List names) { + List existNamesList = waylineFileService.getDuplicateNames(workspaceId, names); + + return ResponseResult.success(existNamesList); + } +} diff --git a/src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java b/src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java new file mode 100644 index 0000000..10a0520 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java @@ -0,0 +1,12 @@ +package com.dji.sample.wayline.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dji.sample.wayline.model.WaylineFileEntity; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +public interface IWaylineFileMapper extends BaseMapper { +} diff --git a/src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java b/src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java new file mode 100644 index 0000000..9b044db --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java @@ -0,0 +1,41 @@ +package com.dji.sample.wayline.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WaylineFileDTO { + + private String name; + + @JsonProperty("id") + private String waylineId; + + private String droneModelKey; + + private List payloadModelKeys; + + private Boolean favorited; + + private List templateTypes; + + private String objectKey; + + @JsonProperty("user_name") + private String username; + + private Long updateTime; +} diff --git a/src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java b/src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java new file mode 100644 index 0000000..34295b3 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java @@ -0,0 +1,57 @@ +package com.dji.sample.wayline.model; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +@Data +@TableName("wayline_file") +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WaylineFileEntity { + + @TableId(type = IdType.AUTO) + private Integer id; + + @TableField("name") + private String name; + + @TableField("wayline_id") + private String waylineId; + + @TableField("drone_model_key") + private String droneModelKey; + + @TableField("payload_model_keys") + private String payloadModelKeys; + + @TableField("workspace_id") + private String workspaceId; + + @TableField("favorited") + private Boolean favorited; + + @TableField("template_types") + private String templateTypes; + + @TableField("object_key") + private String objectKey; + + @TableField("user_name") + private String username; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Long createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Long updateTime; + +} diff --git a/src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java b/src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java new file mode 100644 index 0000000..53b7996 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java @@ -0,0 +1,18 @@ +package com.dji.sample.wayline.model; + +import lombok.Data; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/23 + */ +@Data +public class WaylineFileUploadDTO { + + private String objectKey; + + private String name; + + private WaylineFileDTO metadata; +} diff --git a/src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java b/src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java new file mode 100644 index 0000000..21db9fe --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java @@ -0,0 +1,30 @@ +package com.dji.sample.wayline.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WaylineQueryParam { + + private boolean favorited; + + @Builder.Default + private int page = 1; + + @Builder.Default + private int pageSize = 10; + + private String orderBy; + + private Integer[] templateType; +} diff --git a/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java b/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java new file mode 100644 index 0000000..941890d --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java @@ -0,0 +1,65 @@ +package com.dji.sample.wayline.service; + +import com.dji.sample.common.model.PaginationData; +import com.dji.sample.wayline.model.WaylineFileDTO; +import com.dji.sample.wayline.model.WaylineQueryParam; + +import java.net.URL; +import java.util.List; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +public interface IWaylineFileService { + + /** + * Perform paging queries based on query parameters. + * @param workspaceId + * @param param + * @return + */ + PaginationData getWaylinesByParam(String workspaceId, WaylineQueryParam param); + + /** + * Query the information of this wayline file according to the wayline file id. + * @param workspaceId + * @param waylineId + * @return + */ + WaylineFileDTO getWaylineByWaylineId(String workspaceId, String waylineId); + + /** + * Get the download address of the file object. + * @param bucket bucket name + * @param objectKey object name + * @return + */ + URL getObjectUrl(String bucket, String objectKey); + + /** + * Save the basic information of the wayline file. + * @param workspaceId + * @param metadata + * @return + */ + Integer saveWaylineFile(String workspaceId, WaylineFileDTO metadata); + + /** + * Updates whether the file is collected or not based on the passed parameters. + * @param workspaceId + * @param ids wayline id + * @param isFavorite Whether the wayline file is favorited or not. + * @return + */ + Boolean markFavorite(String workspaceId, List ids, Boolean isFavorite); + + /** + * Batch query for duplicate file names in workspace. + * @param workspaceId + * @param names + * @return + */ + List getDuplicateNames(String workspaceId, List names); +} diff --git a/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java new file mode 100644 index 0000000..eb6bc5e --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java @@ -0,0 +1,179 @@ +package com.dji.sample.wayline.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.dji.sample.common.model.Pagination; +import com.dji.sample.common.model.PaginationData; +import com.dji.sample.component.oss.model.AliyunOSSConfiguration; +import com.dji.sample.component.oss.model.MinIOConfiguration; +import com.dji.sample.component.oss.service.IOssService; +import com.dji.sample.component.oss.service.impl.AliyunOssServiceImpl; +import com.dji.sample.component.oss.service.impl.MinIOServiceImpl; +import com.dji.sample.wayline.dao.IWaylineFileMapper; +import com.dji.sample.wayline.model.WaylineFileDTO; +import com.dji.sample.wayline.model.WaylineFileEntity; +import com.dji.sample.wayline.model.WaylineQueryParam; +import com.dji.sample.wayline.service.IWaylineFileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author sean + * @version 0.3 + * @date 2021/12/22 + */ +@Service +@Transactional +public class WaylineFileServiceImpl implements IWaylineFileService { + + @Autowired + private IWaylineFileMapper mapper; + + private IOssService ossService; + + @Autowired + private void setOssService(@Autowired AliyunOssServiceImpl aliyunOssService, @Autowired MinIOServiceImpl minIOService) { + if (AliyunOSSConfiguration.enable) { + this.ossService = aliyunOssService; + return; + } + if (MinIOConfiguration.enable) { + this.ossService = minIOService; + return; + } + throw new NullPointerException("ossService is null."); + } + + @Override + public PaginationData getWaylinesByParam(String workspaceId, WaylineQueryParam param) { + // Paging Query + Page page = mapper.selectPage( + new Page(param.getPage(), param.getPageSize()), + new LambdaQueryWrapper() + .eq(WaylineFileEntity::getWorkspaceId, workspaceId) + .eq(param.isFavorited(), WaylineFileEntity::getFavorited, param.isFavorited()) + .and(param.getTemplateType() != null, wrapper -> { + for (Integer type : param.getTemplateType()) { + wrapper.like(WaylineFileEntity::getTemplateTypes, type).or(); + } + }) + // There is a risk of SQL injection + .last(StringUtils.hasText(param.getOrderBy()), " order by " + param.getOrderBy())); + + // Wrap the results of a paging query into a custom paging object. + List records = page.getRecords() + .stream() + .map(this::entityConvertToDTO) + .collect(Collectors.toList()); + + return new PaginationData<>(records, new Pagination(page)); + } + + @Override + public WaylineFileDTO getWaylineByWaylineId(String workspaceId, String waylineId) { + return this.entityConvertToDTO( + mapper.selectOne( + new LambdaQueryWrapper() + .eq(WaylineFileEntity::getWorkspaceId, workspaceId) + .eq(WaylineFileEntity::getWaylineId, waylineId))); + } + + @Override + public URL getObjectUrl(String bucket, String objectKey) { + return ossService.getObjectUrl(bucket, objectKey); + } + + @Override + public Integer saveWaylineFile(String workspaceId, WaylineFileDTO metadata) { + WaylineFileEntity file = this.dtoConvertToEntity(metadata); + file.setWaylineId(UUID.randomUUID().toString()); + file.setWorkspaceId(workspaceId); + + int insertId = mapper.insert(file); + return insertId > 0 ? file.getId() : insertId; + } + + @Override + public Boolean markFavorite(String workspaceId, List waylineIds, Boolean isFavorite) { + if (waylineIds.isEmpty()) { + return false; + } + if (isFavorite == null) { + return true; + } + return mapper.update(null, new LambdaUpdateWrapper() + .set(WaylineFileEntity::getFavorited, isFavorite) + .eq(WaylineFileEntity::getWorkspaceId, workspaceId) + .in(WaylineFileEntity::getWaylineId, waylineIds)) > 0; + } + + @Override + public List getDuplicateNames(String workspaceId, List names) { + return mapper.selectList(new LambdaQueryWrapper() + .eq(WaylineFileEntity::getWorkspaceId, workspaceId) + .in(WaylineFileEntity::getName, names)) + .stream() + .map(WaylineFileEntity::getName) + .collect(Collectors.toList()); + } + + /** + * Convert database entity objects into wayline data transfer object. + * @param entity + * @return + */ + private WaylineFileDTO entityConvertToDTO(WaylineFileEntity entity) { + WaylineFileDTO.WaylineFileDTOBuilder builder = WaylineFileDTO.builder(); + + if (entity != null) { + builder.droneModelKey(entity.getDroneModelKey()) + .favorited(entity.getFavorited()) + .name(entity.getName()) + .payloadModelKeys(entity.getPayloadModelKeys() != null ? + Arrays.asList(entity.getPayloadModelKeys().split(",")) : null) + .templateTypes(Arrays.stream(entity.getTemplateTypes().split(",")) + .map(Integer::parseInt) + .collect(Collectors.toList())) + .username(entity.getUsername()) + .objectKey(entity.getObjectKey()) + .updateTime(entity.getUpdateTime()) + .waylineId(entity.getWaylineId()); + } + + return builder.build(); + } + + /** + * Convert the received wayline object into a database entity object. + * @param file + * @return + */ + private WaylineFileEntity dtoConvertToEntity(WaylineFileDTO file) { + WaylineFileEntity.WaylineFileEntityBuilder builder = WaylineFileEntity.builder(); + + if (file != null) { + builder.droneModelKey(file.getDroneModelKey()) + .name(file.getName()) + .username(file.getUsername()) + .objectKey(file.getObjectKey()) + // Separate multiple payload data with ",". + .payloadModelKeys(String.join(",", file.getPayloadModelKeys())) + .templateTypes(file.getTemplateTypes().stream() + .map(String::valueOf) + .collect(Collectors.joining(","))) + .favorited(file.getFavorited()) + .build(); + } + + return builder.build(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..57357e6 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,86 @@ +server: + port: 6789 +spring: + application: + name: cloud-api-sample + datasource: + druid: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/cloud_sample?useSSL=false + username: root + password: root + initial-size: 10 + min-idle: 10 + max-active: 20 + max-wait: 60000 + + jackson: + property-naming-strategy: SNAKE_CASE + date-format: yyyy-MM-dd HH:mm:ss + default-property-inclusion: NON_NULL + deserialization: + FAIL_ON_UNKNOWN_PROPERTIES: false + +jwt: + issuer: DJI + subject: CloudApiSample + secret: CloudApiSample + age: 86400 + +mqtt: + protocol: tcp + host: Please enter your IP. + port: 1883 + username: JavaServer + password: 123456 + client-id: 123456 + # Topics that need to be subscribed when initially connecting to mqtt, multiple topics are divided by ",". + inbound-topic: sys/product/+/status + +url: + manage: + prefix: /manage + version: /api/v1 + map: + prefix: /map + version: /api/v1 + media: + prefix: /media + version: /api/v1 + wayline: + prefix: /wayline + version: /api/v1 + storage: + prefix: /storage + version: /api/v1 + +aliyun: + oss: + enable: true + endpoint: Please enter your endpoint. #Example: https://oss-cn-shenzhen.aliyuncs.com + access-key: Please enter your access key. + secret-key: Please enter your secret key. + expire: 3600 + region: Please enter oss region. + role-session-name: Please enter session name. + role-arn: Please enter role arn. + bucket: Please enter bucket name. + object-dir-prefix: Please enter object prefix. + +# MinIO is temporarily unavailable. +minio: + enable: false + endpoint: Please enter your endpoint. #http://192.168.1.1:9000/ + access-key: minioadmin + secret-key: minioadmin + bucket: Please enter bucket name. + expire: 3600 + region: Please enter minio region. + object-dir-prefix: Please enter object prefix. + +logging: + level: + com.dji.sample: debug + file: + name: logs/cloud-api-sample.log