sean.zhou
2022-03-21 17835b967b48f2676cce3bbed081c6580602ee1c
1.0.0 initial
205 files added
10748 ■■■■■ changed files
.gitignore 38 ●●●●● patch | view | raw | blame | history
pom.xml 148 ●●●●● patch | view | raw | blame | history
sql/cloud_sample.sql 324 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/CloudApiSampleApplication.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/error/CommonErrorEnum.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/error/IErrorInfo.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/error/LiveErrorEnum.java 78 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/model/CustomClaim.java 88 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/model/Pagination.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/model/PaginationData.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/model/ResponseResult.java 69 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/common/util/JwtUtil.java 103 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/ApplicationBootInitial.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/AuthInterceptor.java 60 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/CorsFilter.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/GlobalExceptionHandler.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/GlobalScheduleService.java 58 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java 95 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java 63 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java 69 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java 90 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java 46 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java 50 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java 44 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java 104 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java 93 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/IOssService.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java 68 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java 66 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java 70 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java 60 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java 112 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java 72 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java 42 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DeviceController.java 138 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java 66 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/LiveStreamController.java 113 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/LoginController.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/TopologyController.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/UserController.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/WorkspaceController.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/IUserMapper.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/handler/StateRouter.java 92 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/handler/StateSplitter.java 59 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/handler/StatusRouter.java 70 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/Chan.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/UserDTO.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java 46 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java 69 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/UserEntity.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java 63 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java 51 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ICameraVideoService.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java 48 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceService.java 135 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ILiveStreamService.java 51 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ITSAService.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ITopologyService.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IUserService.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IWorkspaceService.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java 59 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java 99 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java 172 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java 59 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java 69 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java 157 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java 553 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java 74 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java 326 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java 141 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java 52 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java 184 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/dao/IGroupMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java 46 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java 48 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ElementType.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/GroupDTO.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/entity/GroupEntity.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java 69 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/IElementCoordinateService.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/IGroupElementService.java 54 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/IGroupService.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java 71 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java 94 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java 199 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java 66 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java 109 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/controller/FileController.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/controller/MediaController.java 73 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/dao/IFileMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/CredentialsDTO.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/FileExtensionDTO.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/FileMetadataDTO.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/FileUploadDTO.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/MediaFileDTO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/MediaFileEntity.java 62 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/PositionDTO.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/IFileService.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/IMediaService.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java 112 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/storage/controller/StorageController.java 51 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/storage/service/IStorageService.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java 150 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java 57 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java 65 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java 179 ●●●●● patch | view | raw | blame | history
src/main/resources/application.yml 86 ●●●●● patch | view | raw | blame | history
.gitignore
New file
@@ -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/
pom.xml
New file
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.11</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.dji</groupId>
    <artifactId>cloud-api-sample</artifactId>
    <version>1.0.0</version>
    <name>cloud-api-sample</name>
    <properties>
        <java.version>11</java.version>
        <mybatis-plus.version>3.4.2</mybatis-plus.version>
        <druid.version>1.2.6</druid.version>
        <jwt.version>3.12.1</jwt.version>
        <mqtt.version>5.5.5</mqtt.version>
        <minio.version>8.3.7</minio.version>
        <okhttp3.version>4.9.1</okhttp3.version>
        <aliyun-sdk-sts.version>3.1.0</aliyun-sdk-sts.version>
        <aliyun-oss.version>3.12.0</aliyun-oss.version>
        <javax-activation.version>1.1.1</javax-activation.version>
        <glassfish-jaxb.version>2.3.3</glassfish-jaxb.version>
        <log4j2.version>2.15.0</log4j2.version>
        <javax-jaxb.version>2.3.0</javax-jaxb.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-mqtt</artifactId>
            <version>${mqtt.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains</groupId>
            <artifactId>annotations</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>${minio.version}</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>${okhttp3.version}</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-sts</artifactId>
            <version>${aliyun-sdk-sts.version}</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>${aliyun-oss.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>${javax-activation.version}</version>
        </dependency>
        <!-- no more than 2.3.3-->
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>${glassfish-jaxb.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
sql/cloud_sample.sql
New file
@@ -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 */;
src/main/java/com/dji/sample/CloudApiSampleApplication.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/common/error/CommonErrorEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/common/error/IErrorInfo.java
New file
@@ -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();
}
src/main/java/com/dji/sample/common/error/LiveErrorEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/common/model/CustomClaim.java
New file
@@ -0,0 +1,88 @@
package com.dji.sample.common.model;
import com.auth0.jwt.interfaces.Claim;
import com.fasterxml.jackson.annotation.JsonAlias;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * A custom claim for storing custom information in the token.
 * @author sean.zhou
 * @date 2021/11/16
 * @version 0.1
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
@Slf4j
public class CustomClaim {
    /**
     * The id of the account.
     */
    private String id;
    private String username;
    @JsonAlias("user_type")
    private Integer userType;
    @JsonAlias("workspace_id")
    private String workspaceId;
    /**
     * Convert the custom claim data type to the Map type.
     * @return map
     */
    public ConcurrentHashMap<String, String> convertToMap() {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(4);
        try {
            Field[] declaredFields = this.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                JsonAlias annotation = field.getAnnotation(JsonAlias.class);
                field.setAccessible(true);
                // The value of key is named underscore.
                map.put(annotation != null ? annotation.value()[0] : field.getName(),
                        field.get(this).toString());
            }
        } catch (IllegalAccessException e) {
            log.info("CustomClaim converts failed. {}", this.toString());
            e.printStackTrace();
        }
        return map;
    }
    /**
     * Convert the data in Map into a custom claim object.
     * @param claimMap
     */
    public CustomClaim (Map<String, Claim> claimMap) {
        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            field.setAccessible(true);
            JsonAlias annotation = field.getAnnotation(JsonAlias.class);
            Claim value = claimMap.get(annotation == null ? field.getName() : annotation.value()[0]);
            try {
                Class<?> type = field.getType();
                if (Integer.class.equals(type)) {
                    field.set(this, Integer.valueOf(value.asString()));
                    continue;
                }
                if (String.class.equals(type)) {
                    field.set(this, value.asString());
                    continue;
                }
            } catch (IllegalAccessException e) {
                log.info("Claim parses failed. {}", claimMap.toString());
                e.printStackTrace();
            }
        }
    }
}
src/main/java/com/dji/sample/common/model/Pagination.java
New file
@@ -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();
    }
}
src/main/java/com/dji/sample/common/model/PaginationData.java
New file
@@ -0,0 +1,27 @@
package com.dji.sample.common.model;
import lombok.Data;
import java.util.List;
/**
 * The format of the data response when a paginated display is required.
 * @author sean
 * @version 0.3
 * @date 2021/12/22
 */
@Data
public class PaginationData<T> {
    /**
     * The collection in which the data list is stored.
     */
    private List<T> list;
    private Pagination pagination;
    public PaginationData(List<T> list, Pagination pagination) {
        this.list = list;
        this.pagination = pagination;
    }
}
src/main/java/com/dji/sample/common/model/ResponseResult.java
New file
@@ -0,0 +1,69 @@
package com.dji.sample.common.model;
import com.dji.sample.common.error.IErrorInfo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonInclude
public class ResponseResult<T> {
    public static final int CODE_SUCCESS = 0;
    public static final String MESSAGE_SUCCESS = "success";
    private int code;
    private String message;
    private T data;
    public static <T> ResponseResult<T> success(T data) {
        return ResponseResult.<T>builder()
                .code(CODE_SUCCESS)
                .message(MESSAGE_SUCCESS)
                .data(data)
                .build();
    }
    public static ResponseResult success() {
        return ResponseResult.builder()
                .code(0)
                .message(MESSAGE_SUCCESS)
                .build();
    }
    public static ResponseResult error() {
        return ResponseResult.builder()
                .code(HttpStatus.INTERNAL_SERVER_ERROR.value())
                .message(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
                .build();
    }
    public static ResponseResult error(String message) {
        return ResponseResult.builder()
                .code(HttpStatus.INTERNAL_SERVER_ERROR.value())
                .message(message)
                .build();
    }
    public static ResponseResult error(int code, String message) {
        return ResponseResult.builder()
                .code(code)
                .message(message)
                .build();
    }
    public static ResponseResult error(IErrorInfo errorInfo) {
        return ResponseResult.builder()
                .code(errorInfo.getErrorCode())
                .message(errorInfo.getErrorMsg())
                .build();
    }
}
src/main/java/com/dji/sample/common/util/JwtUtil.java
New file
@@ -0,0 +1,103 @@
package com.dji.sample.common.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.dji.sample.common.model.CustomClaim;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
@Slf4j
@Component
public class JwtUtil {
    private static String issuer;
    private static String subject;
    private static long age;
    private static String secret;
    private static Algorithm algorithm;
    @Value("${jwt.issuer: DJI}")
    private void setIssuer(String issuer) {
        JwtUtil.issuer = issuer;
    }
    @Value("${jwt.subject: CloudApiSample}")
    private void setSubject(String subject) {
        JwtUtil.subject = subject;
    }
    @Value("${jwt.age: 86400}")
    private void setAge(long age) {
        JwtUtil.age = age * 1000;
    }
    @Value("${jwt.secret: CloudApiSample}")
    private void setSecret(String secret) {
        JwtUtil.secret = secret;
        setAlgorithm();
    }
    private void setAlgorithm() {
        JwtUtil.algorithm = Algorithm.HMAC256(secret);
    }
    /**
     * Create a token based on custom information.
     * @param claims custom information
     * @return token
     */
    public static String createToken(Map<String, String> claims) {
        Date now = new Date();
        JWTCreator.Builder builder = JWT.create();
        // Add custom information to the token's payload segment.
        claims.forEach(builder::withClaim);
        String token = builder.withIssuer(issuer)
                .withSubject(subject)
                .withIssuedAt(now)
                .withExpiresAt(new Date(now.getTime() + age))
                .withNotBefore(now)
                .sign(algorithm);
        log.debug("token created. " + token);
        return token;
    }
    /**
     * Verify that the token is valid.
     * @param token
     * @return
     * @throws TokenExpiredException
     */
    public static DecodedJWT verifyToken(String token) {
        try {
            JWTVerifier verifier = JWT.require(algorithm).build();
            return verifier.verify(token);
        } catch (Exception e) {
            log.error(e.getMessage());
            e.printStackTrace();
            return null;
        }
    }
    /**
     * Parses the custom information in the token into a CustomClaim object.
     * @param token
     * @return custom claim
     */
    public static Optional<CustomClaim> parseToken(String token) {
        DecodedJWT jwt = verifyToken(token);
        return jwt == null ? Optional.empty() : Optional.of(new CustomClaim(jwt.getClaims()));
    }
}
src/main/java/com/dji/sample/component/ApplicationBootInitial.java
New file
@@ -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());
                });
    }
}
src/main/java/com/dji/sample/component/AuthInterceptor.java
New file
@@ -0,0 +1,60 @@
package com.dji.sample.component;
import com.dji.sample.common.error.CommonErrorEnum;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {
    public static final String PARAM_TOKEN = "x-auth-token";
    public static final String TOKEN_CLAIM = "customClaim";
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        log.debug("request uri: {}", uri);
        // The options method is passed directly.
        if (HttpMethod.OPTIONS.matches(request.getMethod())) {
            response.setStatus(HttpStatus.OK.value());
            return false;
        }
        String token = request.getHeader(PARAM_TOKEN);
        // Check if the token exists.
        if (!StringUtils.hasText(token)) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            log.error(CommonErrorEnum.NO_TOKEN.getErrorMsg());
            return false;
        }
        // Check if the current token is valid.
        Optional<CustomClaim> customClaimOpt = JwtUtil.parseToken(token);
        if (customClaimOpt.isEmpty()) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        // Put the custom data from the token into the request.
        request.setAttribute(TOKEN_CLAIM, customClaimOpt.get());
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // Delete the custom data in the request after the request ends.
        request.removeAttribute(TOKEN_CLAIM);
    }
}
src/main/java/com/dji/sample/component/CorsFilter.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/component/GlobalExceptionHandler.java
New file
@@ -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.");
    }
}
src/main/java/com/dji/sample/component/GlobalScheduleService.java
New file
@@ -0,0 +1,58 @@
package com.dji.sample.component;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.service.IDeviceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.dji.sample.manage.model.DeviceStatusManager.DEFAULT_ALIVE_SECOND;
import static com.dji.sample.manage.model.DeviceStatusManager.STATUS_MANAGER;
/**
 * @author sean.zhou
 * @date 2021/11/24
 * @version 0.1
 */
@Component
@Slf4j
public class GlobalScheduleService {
    @Autowired
    private IDeviceService deviceService;
    /**
     * Check the status of the devices every 30 seconds. It is recommended to use cache.
     */
    @Scheduled(fixedRate = 30, timeUnit = TimeUnit.SECONDS)
    private void deviceStatusListen() {
        for (Map.Entry<String, LocalDateTime> entry : STATUS_MANAGER.entrySet()) {
            if (entry.getValue().isAfter(
                    LocalDateTime.now().minusSeconds(DEFAULT_ALIVE_SECOND))) {
                continue;
            }
            String device = entry.getKey();
            int index = device.indexOf("/");
            STATUS_MANAGER.remove(device);
            int type = Integer.parseInt(device.substring(0, index));
            String sn = device.substring(index + 1);
            // Determine whether it is a gateway device.
            if (DeviceDomainEnum.GATEWAY.getVal() == type) {
                deviceService.deviceOffline(sn);
                deviceService.unsubscribeTopicOffline(sn);
                continue;
            }
            deviceService.subDeviceOffline(sn);
        }
    }
}
src/main/java/com/dji/sample/component/mqtt/config/InboundMessageRouter.java
New file
@@ -0,0 +1,95 @@
package com.dji.sample.component.mqtt.config;
import com.dji.sample.component.mqtt.model.ChannelName;
import lombok.extern.slf4j.Slf4j;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.integration.router.AbstractMessageRouter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import static com.dji.sample.component.mqtt.model.TopicConst.*;
/**
 *
 * @author sean.zhou
 * @date 2021/11/10
 * @version 0.1
 */
@Component
@Slf4j
public class InboundMessageRouter extends AbstractMessageRouter {
    @Resource(name = ChannelName.INBOUND)
    private MessageChannel inboundChannel;
    @Resource(name = ChannelName.INBOUND_STATUS)
    private MessageChannel statusChannel;
    @Resource(name = ChannelName.INBOUND_STATE)
    private MessageChannel stateChannel;
    @Resource(name = ChannelName.DEFAULT)
    private MessageChannel defaultChannel;
    @Resource(name = ChannelName.INBOUND_SERVICE_REPLY)
    private MessageChannel serviceReplyChannel;
    @Resource(name = ChannelName.INBOUND_OSD)
    private MessageChannel osdChannel;
    private static final Pattern PATTERN_TOPIC_STATUS =
            Pattern.compile("^" + BASIC_PRE + PRODUCT + REGEX_SN + STATUS_SUF + "$");
    private static final Pattern PATTERN_TOPIC_STATE =
            Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + STATE_SUF + "$");
    private static final Pattern PATTERN_TOPIC_SERVICE_REPLY =
            Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + SERVICES_SUF + _REPLY_SUF + "$");
    private static final Pattern PATTERN_TOPIC_OSD =
            Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + OSD_SUF);
    /**
     * All mqtt broker messages will arrive here before distributing them to different channels.
     * @param message message from mqtt broker
     * @return channel
     */
    @Override
    @Router(inputChannel = ChannelName.INBOUND)
    protected Collection<MessageChannel> determineTargetChannels(Message<?> message) {
        MessageHeaders headers = message.getHeaders();
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString();
        byte[] payload = (byte[])message.getPayload();
        // osd
        if (PATTERN_TOPIC_OSD.matcher(topic).matches()) {
            return Collections.singleton(osdChannel);
        }
        log.debug("received topic :{} \t payload :{}", topic, new String(payload));
        // status
        if (PATTERN_TOPIC_STATUS.matcher(topic).matches()) {
            return Collections.singleton(statusChannel);
        }
        // state
        if (PATTERN_TOPIC_STATE.matcher(topic).matches()) {
            return Collections.singleton(stateChannel);
        }
        // services_reply
        if (PATTERN_TOPIC_SERVICE_REPLY.matcher(topic).matches()) {
            return Collections.singleton(serviceReplyChannel);
        }
        return Collections.singleton(defaultChannel);
    }
}
src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java
New file
@@ -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.");
        };
    }
}
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java
New file
@@ -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();
    }
}
src/main/java/com/dji/sample/component/mqtt/config/MqttOutboundConfiguration.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java
New file
@@ -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";
}
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java
New file
@@ -0,0 +1,29 @@
package com.dji.sample.component.mqtt.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
/**
 * Unified topic receiving format.
 * @author sean.zhou
 * @date 2021/11/10
 * @version 0.1
 */
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class CommonTopicReceiver<T> {
    /**
     * The command is sent and the response is matched by the tid and bid fields in the message,
     * and the reply should keep the tid and bid the same.
     */
    private String tid;
    private String bid;
    private String method;
    private Long timestamp;
    private T data;
}
src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java
New file
@@ -0,0 +1,33 @@
package com.dji.sample.component.mqtt.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * Unified Topic response format
 * @author sean.zhou
 * @date 2021/11/15
 * @version 0.1
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public class CommonTopicResponse<T> {
    /**
     * The command is sent and the response is matched by the tid and bid fields in the message,
     * and the reply should keep the tid and bid the same.
     */
    private String tid;
    private String bid;
    private String method;
    private T data;
}
src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java
New file
@@ -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]+";
}
src/main/java/com/dji/sample/component/mqtt/model/TopicStateReceiver.java
New file
@@ -0,0 +1,29 @@
package com.dji.sample.component.mqtt.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
/**
 * The data format of the state topic.
 * @author sean.zhou
 * @date 2021/11/17
 * @version 0.1
 */
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class TopicStateReceiver<T> {
    private String tid;
    private String bid;
    private Long timestamp;
    /**
     * The sn of the gateway device.
     */
    private String gateway;
    private T data;
}
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java
New file
@@ -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);
}
src/main/java/com/dji/sample/component/mqtt/service/IMqttMessageGateway.java
New file
@@ -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);
}
src/main/java/com/dji/sample/component/mqtt/service/IMqttTopicService.java
New file
@@ -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();
}
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java
New file
@@ -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();
        }
    }
}
src/main/java/com/dji/sample/component/mqtt/service/impl/MqttTopicServiceImpl.java
New file
@@ -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();
    }
}
src/main/java/com/dji/sample/component/mybatis/MybatisPlusConfiguration.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java
New file
@@ -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());
    }
}
src/main/java/com/dji/sample/component/oss/model/AliyunOSSConfiguration.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/component/oss/model/MinIOConfiguration.java
New file
@@ -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();
    }
}
src/main/java/com/dji/sample/component/oss/service/IOssService.java
New file
@@ -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);
}
src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java
New file
@@ -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));
    }
}
src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java
New file
@@ -0,0 +1,70 @@
package com.dji.sample.component.websocket.config;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.util.JwtUtil;
import com.dji.sample.component.AuthInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Map;
import java.util.Optional;
/**
 * @author sean.zhou
 * @date 2021/11/16
 * @version 0.1
 */
@Slf4j
@Component
public class AuthPrincipalHandler extends DefaultHandshakeHandler {
    @Override
    protected boolean isValidOrigin(ServerHttpRequest request) {
        if (request instanceof ServletServerHttpRequest) {
            HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
            String token = servletRequest.getParameter(AuthInterceptor.PARAM_TOKEN);
            if (!StringUtils.hasText(token)) {
                return false;
            }
            Optional<CustomClaim> customClaim = JwtUtil.parseToken(token);
            if (customClaim.isEmpty()) {
                return false;
            }
            servletRequest.setAttribute(AuthInterceptor.TOKEN_CLAIM, customClaim.get());
            return true;
        }
        return false;
    }
    /**
     * The principal's name: {workspaceId}/{userType}/{userId}
     * @param request
     * @param wsHandler
     * @param attributes
     * @return
     */
    @Override
    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
        if (request instanceof ServletServerHttpRequest) {
            // get the custom claim
            CustomClaim claim = (CustomClaim) ((ServletServerHttpRequest) request).getServletRequest()
                    .getAttribute(AuthInterceptor.TOKEN_CLAIM);
            return () -> claim.getWorkspaceId() + "/" + claim.getUserType() + "/" + claim.getId();
        }
        return () -> null;
    }
}
src/main/java/com/dji/sample/component/websocket/config/ConcurrentWebSocketSession.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java
New file
@@ -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());
    }
}
src/main/java/com/dji/sample/component/websocket/config/WebSocketMessageConfiguration.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/component/websocket/model/CustomWebSocketMessage.java
New file
@@ -0,0 +1,30 @@
package com.dji.sample.component.websocket.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
/**
 * The format of WebSocket messages that the pilot can receive.
 * @author sean.zhou
 * @date 2021/11/17
 * @version 0.1
 */
@Data
@Builder
public class CustomWebSocketMessage<T> {
    /**
     * @see BizCodeEnum
     * specific value
     */
    @JsonProperty("biz_code")
    private String bizCode;
    @Builder.Default
    private String version = "1.0";
    private Long timestamp;
    private T data;
}
src/main/java/com/dji/sample/component/websocket/model/WebSocketManager.java
New file
@@ -0,0 +1,112 @@
package com.dji.sample.component.websocket.model;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import lombok.extern.slf4j.Slf4j;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
 * Manage all WebSocket connection objects.
 * @author sean.zhou
 * @date 2021/11/16
 * @version 0.1
 */
@Slf4j
public class WebSocketManager {
    private static final ConcurrentHashMap<String,
            ConcurrentHashMap<String,
                    ConcurrentHashMap<String, ConcurrentWebSocketSession>>> MANAGER = new ConcurrentHashMap<>(16);
    /**
     * WebSocket connection from the pilot.
     */
    private static final Set<ConcurrentWebSocketSession> PILOT_SESSION = ConcurrentHashMap.newKeySet(16);
    /**
     * WebSocket connection from the web.
     */
    private static final Set<ConcurrentWebSocketSession> WEB_SESSION = ConcurrentHashMap.newKeySet(16);
    public static void put(String key, ConcurrentWebSocketSession val) {
        String[] name = key.split("/");
        if (name.length != 3) {
            log.debug("The key is out of format. [{workspaceId}/{userType}/{userId}]");
            return;
        }
        ConcurrentHashMap<String, ConcurrentHashMap<String, ConcurrentWebSocketSession>> workspaceSessions =
                MANAGER.getOrDefault(name[0], new ConcurrentHashMap<>(16));
        ConcurrentHashMap<String, ConcurrentWebSocketSession> userSessions = workspaceSessions.getOrDefault(
                name[2], new ConcurrentHashMap<>(16));
        userSessions.put(val.getId(), val);
        workspaceSessions.put(name[2], userSessions);
        MANAGER.put(name[0], workspaceSessions);
        getSetByUserType(Integer.valueOf(name[1])).add(val);
    }
    public static void remove(String key, String sessionId) {
        String[] name = key.split("/");
        if (name.length != 3) {
            log.debug("The key is out of format. [{workspaceId}/{userType}/{userId}]");
            return;
        }
        ConcurrentHashMap<String, ConcurrentWebSocketSession> userSession = MANAGER.get(name[0]).get(name[2]);
        Set<ConcurrentWebSocketSession> typeSession = getSetByUserType(Integer.valueOf(name[1]));
        ConcurrentWebSocketSession session = userSession.get(sessionId);
        typeSession.remove(session);
        userSession.remove(sessionId);
    }
    public static int getConnectedCount() {
        return PILOT_SESSION.size() + WEB_SESSION.size();
    }
    public static Collection<ConcurrentWebSocketSession> getValueWithWorkspace(String workspaceId) {
        Set<ConcurrentWebSocketSession> sessions = ConcurrentHashMap.newKeySet();
        MANAGER.get(workspaceId)
                .forEach((userId, userSessions) -> {
                    sessions.addAll(userSessions.values());
                });
        return sessions;
    }
    public static Collection<ConcurrentWebSocketSession> getValueWithWorkspaceAndUserType(String workspaceId, Integer userType) {
        Set<ConcurrentWebSocketSession> sessions = ConcurrentHashMap.newKeySet();
        Set<ConcurrentWebSocketSession> typeSessions = getSetByUserType(userType);
        MANAGER.getOrDefault(workspaceId, new ConcurrentHashMap<>())
                .forEach((userId, userSessions) -> {
                    Collection<ConcurrentWebSocketSession> sessionList = userSessions.values();
                    if (!sessionList.isEmpty()) {
                        ConcurrentWebSocketSession session = sessionList.iterator().next();
                        if (typeSessions.contains(session)) {
                            sessions.addAll(sessionList);
                        }
                    }
                });
        return sessions;
    }
    private static Set<ConcurrentWebSocketSession> getSetByUserType(Integer userType) {
        if (UserTypeEnum.PILOT.getVal() == userType) {
            return PILOT_SESSION;
        }
        if (UserTypeEnum.WEB.getVal() == userType) {
            return WEB_SESSION;
        }
        return new HashSet<>();
    }
}
src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java
New file
@@ -0,0 +1,28 @@
package com.dji.sample.component.websocket.service;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import java.util.Collection;
/**
 * @author sean.zhou
 * @date 2021/11/24
 * @version 0.1
 */
public interface ISendMessageService {
    /**
     * Send a message to the specific connection.
     * @param session   A WebSocket connection object
     * @param message   message
     */
    void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message);
    /**
     * Send the same message to specific connection.
     * @param sessions  A collection of WebSocket connection objects.
     * @param message   message
     */
    void sendBatch(Collection<ConcurrentWebSocketSession> sessions, CustomWebSocketMessage message);
}
src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java
New file
@@ -0,0 +1,72 @@
package com.dji.sample.component.websocket.service.impl;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import java.io.IOException;
import java.util.Collection;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/24
 */
@Service
@Slf4j
public class SendMessageServiceImpl implements ISendMessageService {
    @Override
    public void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message) {
        if (session == null) {
            return;
        }
        try {
            if (!session.isOpen()) {
                session.close();
                log.debug("This session is closed.");
                return;
            }
            ObjectMapper mapper = new ObjectMapper();
            session.sendMessage(new TextMessage(mapper.writeValueAsBytes(message)));
        } catch (IOException e) {
            log.info("Failed to publish the message. {}", message.toString());
            e.printStackTrace();
        }
    }
    @Override
    public void sendBatch(Collection<ConcurrentWebSocketSession> sessions, CustomWebSocketMessage message) {
        if (sessions.isEmpty()) {
            return;
        }
        try {
            ObjectMapper mapper = new ObjectMapper();
            TextMessage data = new TextMessage(mapper.writeValueAsBytes(message));
            for (ConcurrentWebSocketSession session : sessions) {
                if (!session.isOpen()) {
                    session.close();
                    log.debug("This session is closed.");
                    return;
                }
                session.sendMessage(data);
            }
        } catch (IOException e) {
            log.info("Failed to publish the message. {}", message.toString());
            e.printStackTrace();
        }
    }
}
src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java
New file
@@ -0,0 +1,35 @@
package com.dji.sample.configuration;
import com.dji.sample.component.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class GlobalMVCConfigurer implements WebMvcConfigurer {
    @Autowired
    private AuthInterceptor authInterceptor;
    private static List<String> excludePaths = new ArrayList<>();
    @Value("${url.manage.prefix}")
    private String managePrefix;
    @Value("${url.manage.version}")
    private String manageVersion;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Exclude the login interface.
        excludePaths.add(managePrefix + manageVersion + "/login");
        excludePaths.add(managePrefix + manageVersion + "/token/refresh");
        // Intercept for all request interfaces.
        registry.addInterceptor(authInterceptor).addPathPatterns("/**").excludePathPatterns(excludePaths);
    }
}
src/main/java/com/dji/sample/configuration/GlobalThreadPoolConfiguration.java
New file
@@ -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());
    }
}
src/main/java/com/dji/sample/manage/controller/DeviceController.java
New file
@@ -0,0 +1,138 @@
package com.dji.sample.manage.controller;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.AuthInterceptor;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.model.WebSocketManager;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.WorkspaceDTO;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import com.dji.sample.manage.service.IDeviceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/15
 */
@RestController
@Slf4j
@RequestMapping("${url.manage.prefix}${url.manage.version}/devices")
public class DeviceController {
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private ISendMessageService sendMessageService;
    /**
     * Handles the message that the drone goes online.
     * @param receiver  The drone information is not empty.
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS_ONLINE, outputChannel = ChannelName.OUTBOUND)
    public void deviceOnline(CommonTopicReceiver<StatusGatewayReceiver> receiver) {
        boolean online = deviceService.deviceOnline(receiver.getData());
        if (online) {
            // Notify pilot that the drone is online successfully.
            deviceService.publishStatusReply(receiver.getData().getSn(),
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .build());
            // Publish the latest device topology information in the current workspace to the pilot.
            deviceService.pushDeviceOnlineTopo(WorkspaceDTO.DEFAULT_WORKSPACE_ID,
                    receiver.getData().getSn(), receiver.getData().getSubDevices().get(0).getSn());
        }
    }
    /**
     * Handles the message that the drone goes offline.
     * @param receiver  The drone information is empty.
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS_OFFLINE, outputChannel = ChannelName.OUTBOUND)
    public void deviceOffline(CommonTopicReceiver<StatusGatewayReceiver> receiver) {
        boolean offline = deviceService.deviceOffline(receiver.getData().getSn());
        if (offline) {
            // Notify pilot that the device is offline successfully.
            deviceService.publishStatusReply(receiver.getData().getSn(),
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .build());
            // Publish the latest device topology information in the current workspace to the pilot.
            deviceService.pushDeviceOfflineTopo(WorkspaceDTO.DEFAULT_WORKSPACE_ID, receiver.getData().getSn());
        }
    }
    /**
     * Get the topology list of all devices in the current user workspace.
     * @param request
     * @return
     */
    @GetMapping("/devices")
    public ResponseResult<List<DeviceDTO>> getDevices(HttpServletRequest request) {
        // Get information about the current user.
        CustomClaim claim = (CustomClaim)request.getAttribute(AuthInterceptor.TOKEN_CLAIM);
        String workspaceId = claim.getWorkspaceId();
        // Get information about the devices in the current user's workspace.
        List<DeviceDTO> devicesList = deviceService.getDevicesTopoForWeb(workspaceId);
        return ResponseResult.success(devicesList);
    }
    @ServiceActivator(inputChannel = ChannelName.INBOUND_OSD)
    public void osdRealTime(Message<?> message) {
        String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
        byte[] payload = (byte[])message.getPayload();
        deviceService.handleOSD(topic, payload);
    }
    /**
     * Handles the payloads data of the drone.
     * @param deviceSn  drone's sn
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE)
    public void pushWebSocketDevices(String deviceSn) {
        List<DeviceDTO> devicesList = deviceService.getDevicesByParams(
                DeviceQueryParam.builder()
                        .deviceSn(deviceSn)
                        .build());
        // Get drone information based on the sn of the drone. The sn of the drone is unique.
        DeviceDTO device = devicesList.get(0);
        // Set the remote controller and payloads information of the drone.
        deviceService.spliceDeviceTopo(device);
        CustomWebSocketMessage wsMessage = CustomWebSocketMessage.builder()
                .timestamp(System.currentTimeMillis())
                .bizCode(BizCodeEnum.DEVICE_UPDATE_TOPO.getCode())
                .data(device)
                .build();
        // Update the topology of the drone via WebSocket notifications to the web side.
        sendMessageService.sendBatch(WebSocketManager
                .getValueWithWorkspaceAndUserType(
                        device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                wsMessage);
    }
}
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java
New file
@@ -0,0 +1,66 @@
package com.dji.sample.manage.controller;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.manage.model.receiver.DeviceBasicReceiver;
import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
import com.dji.sample.manage.service.IDevicePayloadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
@RestController
@Slf4j
public class DevicePayloadController {
    @Autowired
    private IDevicePayloadService devicePayloadService;
    /**
     * Handles the data for the payload messages in the state topic.
     * @param payloadsList  List of payload information.
     * @return  drone's sn
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_PAYLOAD,
            outputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE)
    public String statePayload(List<DevicePayloadReceiver> payloadsList) {
        // Delete all payload information for the drone based on the drone's sn.
        devicePayloadService.deletePayloadsByDeviceSn(List.of(payloadsList.get(0).getDeviceSn()));
        // Save the new payload information.
        devicePayloadService.savePayloadDTOs(payloadsList);
        log.debug("The result of saving the payload is successful.");
        return payloadsList.get(0).getDeviceSn();
    }
    /**
     * Handles messages in the state topic about basic drone data.
     *
     * Note: Only the data of the drone payload is handled here. You can handle other data from the drone
     * according to your business needs.
     * @param deviceBasic   basic drone data
     * @return  drone's sn
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC,
            outputChannel = ChannelName.INBOUND_STATE_PAYLOAD_UPDATE)
    public String stateBasic(DeviceBasicReceiver deviceBasic) {
        // Delete all payload information for the drone based on the drone's sn.
        devicePayloadService.deletePayloadsByDeviceSn(List.of(deviceBasic.getDeviceSn()));
        // Save the new payload information.
        boolean isSave = devicePayloadService.savePayloadDTOs(deviceBasic.getPayloads());
        log.debug("The result of saving the payloads is {}.", isSave);
        return deviceBasic.getDeviceSn();
    }
}
src/main/java/com/dji/sample/manage/controller/LiveStreamController.java
New file
@@ -0,0 +1,113 @@
package com.dji.sample.manage.controller;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.Chan;
import com.dji.sample.manage.model.dto.CapacityDeviceDTO;
import com.dji.sample.manage.model.dto.LiveTypeDTO;
import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
import com.dji.sample.manage.model.receiver.ServiceReplyReceiver;
import com.dji.sample.manage.service.ILiveStreamService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/19
 */
@RestController
@Slf4j
@RequestMapping("${url.manage.prefix}${url.manage.version}/live")
public class LiveStreamController {
    @Autowired
    private ILiveStreamService liveStreamService;
    /**
     * Analyze the live streaming capabilities of drones.
     * This data is necessary if drones are required for live streaming.
     * @param device    the capacity of drone
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_CAPACITY)
    public void stateCapacity(CapacityDeviceReceiver device) {
        boolean parseCapacity = liveStreamService.saveLiveCapacity(device);
        log.debug("The result of parsing the live capacity is {}.", parseCapacity);
    }
    /**
     * Get live capability data of all drones in the current user's workspace from the database.
     * @param request
     * @return  live capability
     */
    @GetMapping("/capacity")
    public ResponseResult<List<CapacityDeviceDTO>> getLiveCapacity(HttpServletRequest request) {
        // Get information about the current user.
        CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
        List<CapacityDeviceDTO> liveCapacity = liveStreamService.getLiveCapacity(customClaim.getWorkspaceId());
        return ResponseResult.success(liveCapacity);
    }
    /**
     * Live streaming according to the parameters passed in from the web side.
     * @param liveParam Live streaming parameters.
     * @return
     */
    @PostMapping("/streams/start")
    public ResponseResult liveStart(@RequestBody LiveTypeDTO liveParam) {
        return liveStreamService.liveStart(liveParam);
    }
    /**
     * Stop live streaming according to the parameters passed in from the web side.
     * @param liveParam Live streaming parameters.
     * @return
     */
    @PostMapping("/streams/stop")
    public ResponseResult liveStop(@RequestBody LiveTypeDTO liveParam) {
        return liveStreamService.liveStop(liveParam.getVideoId());
    }
    /**
     * Set the quality of the live streaming according to the parameters passed in from the web side.
     * @param liveParam Live streaming parameters.
     * @return
     */
    @PostMapping("/streams/update")
    public ResponseResult liveSetQuality(@RequestBody LiveTypeDTO liveParam) {
        return liveStreamService.liveSetQuality(liveParam);
    }
    /**
     * Handle the reply message from the pilot side to the on-demand video.
     * @param message   reply message
     * @throws IOException
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_SERVICE_REPLY)
    public void serviceReply(Message<?> message) throws IOException {
        byte[] payload = (byte[])message.getPayload();
        ObjectMapper mapper = new ObjectMapper();
        CommonTopicReceiver<ServiceReplyReceiver> receiver = mapper.readValue(payload,
                new TypeReference<CommonTopicReceiver<ServiceReplyReceiver>>() {
        });
        Chan<CommonTopicReceiver> chan = Chan.getInstance();
        // Put the message to the chan object.
        chan.put(receiver);
    }
}
src/main/java/com/dji/sample/manage/controller/LoginController.java
New file
@@ -0,0 +1,47 @@
package com.dji.sample.manage.controller;
import com.dji.sample.common.error.CommonErrorEnum;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.UserDTO;
import com.dji.sample.manage.model.dto.UserLoginDTO;
import com.dji.sample.manage.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
import static com.dji.sample.component.AuthInterceptor.PARAM_TOKEN;
@RestController
@RequestMapping("${url.manage.prefix}${url.manage.version}")
public class LoginController {
    @Autowired
    private IUserService userService;
    @PostMapping("/login")
    public ResponseResult login(@RequestBody UserLoginDTO loginDTO) {
        String username = loginDTO.getUsername();
        String password = loginDTO.getPassword();
        return userService.userLogin(username, password);
    }
    @PostMapping("/token/refresh")
    public ResponseResult refreshToken(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getHeader(PARAM_TOKEN);
        Optional<UserDTO> user = userService.refreshToken(token);
        if (user.isEmpty()) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return ResponseResult.error(CommonErrorEnum.NO_TOKEN.getErrorMsg());
        }
        return ResponseResult.success(user);
    }
}
src/main/java/com/dji/sample/manage/controller/TopologyController.java
New file
@@ -0,0 +1,40 @@
package com.dji.sample.manage.controller;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.TopologyDTO;
import com.dji.sample.manage.service.ITopologyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/8
 */
@RestController
@RequestMapping("${url.manage.prefix}${url.manage.version}/workspaces")
public class TopologyController {
    @Autowired
    private ITopologyService topologyService;
    /**
     * Get the topology list of all devices in the current user workspace for pilot display.
     * @param workspaceId
     * @return
     */
    @GetMapping("/{workspace_id}/devices/topologies")
    public ResponseResult<Map<String, List<TopologyDTO>>> getDevicesTopologiesForPilot(
            @PathVariable(name = "workspace_id") String workspaceId) {
        List<TopologyDTO> topologyList = topologyService.getDeviceTopology(workspaceId);
        return ResponseResult.success(new ConcurrentHashMap<>(Map.of("list", topologyList)));
    }
}
src/main/java/com/dji/sample/manage/controller/UserController.java
New file
@@ -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());
    }
}
src/main/java/com/dji/sample/manage/controller/WorkspaceController.java
New file
@@ -0,0 +1,41 @@
package com.dji.sample.manage.controller;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.WorkspaceDTO;
import com.dji.sample.manage.service.IWorkspaceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/23
 */
@RestController
@RequestMapping("${url.manage.prefix}${url.manage.version}/workspaces")
public class WorkspaceController {
    @Autowired
    private IWorkspaceService workspaceService;
    /**
     * Gets information about the workspace that the current user is in.
     * @param request
     * @return
     */
    @GetMapping("/current")
    public ResponseResult getCurrentWorkspace(HttpServletRequest request) {
        CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
        Optional<WorkspaceDTO> workspaceOpt = workspaceService.getWorkspaceByWorkspaceId(customClaim.getWorkspaceId());
        return workspaceOpt.isEmpty() ? ResponseResult.error() : ResponseResult.success(workspaceOpt.get());
    }
}
src/main/java/com/dji/sample/manage/dao/ICameraVideoMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.CameraVideoEntity;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/19
 */
public interface ICameraVideoMapper extends BaseMapper<CameraVideoEntity> {
}
src/main/java/com/dji/sample/manage/dao/ICapacityCameraMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.CapacityCameraEntity;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
public interface ICapacityCameraMapper extends BaseMapper<CapacityCameraEntity> {
}
src/main/java/com/dji/sample/manage/dao/IDeviceDictionaryMapper.java
New file
@@ -0,0 +1,13 @@
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.DeviceDictionaryEntity;
/**
 *
 * @author sean.zhou
 * @date 2021/11/15
 * @version 0.1
 */
public interface IDeviceDictionaryMapper extends BaseMapper<DeviceDictionaryEntity> {
}
src/main/java/com/dji/sample/manage/dao/IDeviceMapper.java
New file
@@ -0,0 +1,14 @@
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.DeviceEntity;
/**
 *
 * @author sean.zhou
 * @date 2021/11/10
 * @version 0.1
 */
public interface IDeviceMapper extends BaseMapper<DeviceEntity> {
}
src/main/java/com/dji/sample/manage/dao/IDevicePayloadMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.DevicePayloadEntity;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
public interface IDevicePayloadMapper extends BaseMapper<DevicePayloadEntity> {
}
src/main/java/com/dji/sample/manage/dao/IUserMapper.java
New file
@@ -0,0 +1,8 @@
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.UserEntity;
public interface IUserMapper extends BaseMapper<UserEntity> {
}
src/main/java/com/dji/sample/manage/dao/IWorkspaceMapper.java
New file
@@ -0,0 +1,8 @@
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.WorkspaceEntity;
public interface IWorkspaceMapper extends BaseMapper<WorkspaceEntity> {
}
src/main/java/com/dji/sample/manage/handler/AbstractStateTopicHandler.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/handler/StateDefaultHandler.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/handler/StateDeviceBasicHandler.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/manage/handler/StateLiveCapacityHandler.java
New file
@@ -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);
    }
}
src/main/java/com/dji/sample/manage/handler/StatePayloadHandler.java
New file
@@ -0,0 +1,55 @@
package com.dji.sample.manage.handler;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.manage.model.enums.StateDataEnum;
import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
 * @author sean
 * @version 0.3
 * @date 2022/2/21
 */
@Service
public class StatePayloadHandler extends AbstractStateTopicHandler {
    protected StatePayloadHandler(@Autowired @Qualifier("stateLiveCapacityHandler") AbstractStateTopicHandler handler) {
        super(handler);
    }
    @Override
    public TopicStateReceiver handleState(JsonNode dataNode, TopicStateReceiver stateReceiver, String sn) throws JsonProcessingException {
        String name = dataNode.fieldNames().next();
        JsonNode childNode = dataNode.findPath(name);
        // Determine if it is payload data based on name.
        if (name.equals(StateDataEnum.PAYLOADS.getDesc())) {
            List<DevicePayloadReceiver> payloadsList = new ArrayList<>();
            Iterator<JsonNode> payloadsNode = childNode.elements();
            while (payloadsNode.hasNext()) {
                DevicePayloadReceiver payloadReceiver = mapper.treeToValue(
                        payloadsNode.next(), DevicePayloadReceiver.class);
                payloadReceiver.setDeviceSn(sn);
                payloadsList.add(payloadReceiver);
            }
            if (payloadsList.isEmpty()) {
                DevicePayloadReceiver payloadReceiver = new DevicePayloadReceiver();
                payloadReceiver.setDeviceSn(sn);
                payloadsList.add(payloadReceiver);
            }
            stateReceiver.setData(payloadsList);
            return stateReceiver;
        }
        return handler.handleState(dataNode, stateReceiver, sn);
    }
}
src/main/java/com/dji/sample/manage/handler/StateRouter.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/handler/StateSplitter.java
New file
@@ -0,0 +1,59 @@
package com.dji.sample.manage.handler;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import java.util.ArrayList;
import java.util.Collection;
/**
 *
 * @author sean.zhou
 * @date 2021/11/17
 * @version 0.1
 */
@MessageEndpoint
@Configuration
public class StateSplitter {
    /**
     * Split the state message data to different channels for handling according to their different types.
     * @param receiver state message
     * @return
     */
    @Splitter(inputChannel = ChannelName.INBOUND_STATE_SPLITTER, outputChannel = ChannelName.INBOUND_STATE_ROUTER)
    public Collection<Object> splitState(TopicStateReceiver receiver) {
        ArrayList<Object> type = new ArrayList<>();
        type.add(receiver.getData());
        return type;
    }
    /**
     * Split according to the different types in the list.
     * @return
     */
    @Bean
    public IntegrationFlow splitList() {
        return IntegrationFlows
                .from(ChannelName.INBOUND_STATE_LIST)
                .split()
                .<Object, String> route(dataType -> {
                    Class<?> clazz = dataType.getClass();
                    if (DevicePayloadReceiver.class.isAssignableFrom(clazz)) {
                        return ChannelName.INBOUND_STATE_PAYLOAD;
                    }
                    return null;
                }, mapping -> {
                    mapping.channelMapping(ChannelName.INBOUND_STATE_PAYLOAD,
                            ChannelName.INBOUND_STATE_PAYLOAD);
                })
                .get();
    }
}
src/main/java/com/dji/sample/manage/handler/StatusRouter.java
New file
@@ -0,0 +1,70 @@
package com.dji.sample.manage.handler;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.util.CollectionUtils;
import static com.dji.sample.component.mqtt.model.TopicConst.*;
/**
 *
 * @author sean.zhou
 * @date 2021/11/12
 * @version 0.1
 */
@MessageEndpoint
public class StatusRouter {
    /**
     * Converts the status data sent by the gateway device into an object.
     * @param message
     * @return
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS, outputChannel = ChannelName.INBOUND_STATUS_ROUTER)
    public CommonTopicReceiver<StatusGatewayReceiver> resolveStatus(Message<?> message) {
        CommonTopicReceiver<StatusGatewayReceiver> statusReceiver = new CommonTopicReceiver<>();
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        try {
            statusReceiver = mapper.readValue(
                    (byte[])message.getPayload(),
                    new TypeReference<CommonTopicReceiver<StatusGatewayReceiver>>() {});
            String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
            // set gateway's domain
            statusReceiver.getData().setDomain(DeviceDomainEnum.GATEWAY.getVal());
            // set gateway's sn
            statusReceiver.getData().setSn(
                    topic.substring((BASIC_PRE + PRODUCT).length(),
                            topic.indexOf(STATUS_SUF)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statusReceiver;
    }
    /**
     * Handles the routing of status topic messages. Depending on the data, it is assigned to different channels for handling.
     * @param receiver
     * @return
     */
    @Router(inputChannel = ChannelName.INBOUND_STATUS_ROUTER)
    public String resolveStatusRouter(CommonTopicReceiver<StatusGatewayReceiver> receiver) {
        // Determine whether the drone is online or offline according to whether the data of the sub-device is empty.
        return CollectionUtils.isEmpty(receiver.getData().getSubDevices()) ?
                ChannelName.INBOUND_STATUS_OFFLINE : ChannelName.INBOUND_STATUS_ONLINE;
    }
}
src/main/java/com/dji/sample/manage/model/Chan.java
New file
@@ -0,0 +1,45 @@
package com.dji.sample.manage.model;
import java.util.concurrent.locks.LockSupport;
/**
 * The demo is only for functional closure, which is not recommended.
 * @author sean.zhou
 * @date 2021/11/22
 * @version 0.1
 */
public class Chan<T> {
    private static final long THREAD_WAIT_TIME = 1000_000 * 500;
    private volatile T data;
    private volatile Thread t;
    private Chan () {
    }
    public static Chan getInstance() {
        return ChanSingleton.INSTANCE;
    }
    public T get(Object blocker) {
        this.t = Thread.currentThread();
        LockSupport.parkNanos(blocker, THREAD_WAIT_TIME);
        this.t = null;
        return data;
    }
    public void put(T data) {
        this.data = data;
        if (t == null) {
            return;
        }
        LockSupport.unpark(t);
    }
    private static class ChanSingleton {
        private static final Chan<?> INSTANCE = new Chan<>();
    }
}
src/main/java/com/dji/sample/manage/model/DeviceStatusManager.java
New file
@@ -0,0 +1,20 @@
package com.dji.sample.manage.model;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;
/**
 * The demo is only for functional closure, which is not recommended,
 * and it is recommended to use caching for handling.
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/25
 */
public class DeviceStatusManager {
    public static final ConcurrentHashMap<String, LocalDateTime> STATUS_MANAGER =
            new ConcurrentHashMap<>(16);
    public static final Integer DEFAULT_ALIVE_SECOND = 30;
}
src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java
New file
@@ -0,0 +1,34 @@
package com.dji.sample.manage.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/22
 * @version 0.1
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CapacityCameraDTO {
    private Integer id;
    private String deviceSn;
    private String name;
    private String description;
    private String index;
    private String type;
    private List<CapacityVideoDTO> videosList;
}
src/main/java/com/dji/sample/manage/model/dto/CapacityDeviceDTO.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.manage.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/22
 * @version 0.1
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CapacityDeviceDTO {
    private String sn;
    private String name;
    private List<CapacityCameraDTO> camerasList;
}
src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java
New file
@@ -0,0 +1,47 @@
package com.dji.sample.manage.model.dto;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class DeviceDTO {
    private String deviceSn;
    private String deviceName;
    private String workspaceId;
    private String deviceIndex;
    private String deviceDesc;
    private String childDeviceSn;
    private String domain;
    private Integer type;
    private Integer subType;
    private List<DeviceDTO> gatewaysList;
    private List<DevicePayloadDTO> payloadsList;
    private IconUrlDTO iconUrl;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceDictionaryDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceModelDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/IconUrlDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/LiveDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/LiveUrlAgoraDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/LiveUrlGB28181DTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/LiveUrlRTSPDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/TelemetryDeviceDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/TopologyDTO.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.manage.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/8
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TopologyDTO {
    private List<TopologyDeviceDTO> hosts;
    private List<TopologyDeviceDTO> parents;
}
src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/UserDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/entity/CameraVideoEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/entity/CapacityCameraEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/entity/DeviceDictionaryEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/entity/UserEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/model/enums/IconUrlEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/model/enums/LiveMethodEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/model/enums/LiveUrlTypeEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/model/enums/LiveVideoQualityEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/model/enums/StateDataEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java
New file
@@ -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;
    }
}
src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/BatteryReceiver.java
New file
@@ -0,0 +1,27 @@
package com.dji.sample.manage.model.receiver;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import java.util.List;
/**
 * @author sean
 * @version 0.3
 * @date 2022/1/27
 */
@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class BatteryReceiver {
    private List<BatteryStateReceiver> batteries;
    private Integer capacityPercent;
    private Integer landingPower;
    private Integer remainFlightTime;
    private Integer returnHomePower;
}
src/main/java/com/dji/sample/manage/model/receiver/BatteryStateReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/CapacityCameraReceiver.java
New file
@@ -0,0 +1,28 @@
package com.dji.sample.manage.model.receiver;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/18
 * @version 0.1
 */
@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class CapacityCameraReceiver {
    private Integer availableVideoNumber;
    private Integer coexistVideoNumberMax;
    private String cameraIndex;
    @JsonProperty(value = "video_list")
    private List<CapacityVideoReceiver> videosList;
}
src/main/java/com/dji/sample/manage/model/receiver/CapacityDeviceReceiver.java
New file
@@ -0,0 +1,25 @@
package com.dji.sample.manage.model.receiver;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/18
 * @version 0.1
 */
@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class CapacityDeviceReceiver {
    private String sn;
    private Integer availableVideoNumber;
    private Integer coexistVideoNumberMax;
    private List<CapacityCameraReceiver> cameraList;
}
src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java
New file
@@ -0,0 +1,39 @@
package com.dji.sample.manage.model.receiver;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/18
 * @version 0.1
 */
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class DeviceBasicReceiver {
    private String controlSource;
    private String deviceSn;
    private Integer flightMode;
    private Integer flightStatus;
    private Double homeLatitude;
    private Double homeLongitude;
    private Integer lowBatteryWarningThreshold;
    private Integer positionMode;
    private Integer seriousLowBatteryWarningThreshold;
    private List<DevicePayloadReceiver> payloads;
}
src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/LiveStatusReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdGatewayReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/RTKStateReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/ServiceReplyReceiver.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.manage.model.receiver;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/22
 */
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ServiceReplyReceiver<T> {
    private Integer result;
    private String info;
}
src/main/java/com/dji/sample/manage/model/receiver/StatusGatewayReceiver.java
New file
@@ -0,0 +1,43 @@
package com.dji.sample.manage.model.receiver;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 *
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/12
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class StatusGatewayReceiver {
    private String sn;
    private Integer domain;
    private Integer type;
    @JsonProperty(value = "sub_type")
    private Integer subType;
    @JsonProperty(value = "device_secret")
    private String deviceSecret;
    private String nonce;
    private Integer version;
    @JsonProperty(value = "sub_devices")
    private List<StatusSubDeviceReceiver> subDevices;
}
src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java
New file
@@ -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;
}
src/main/java/com/dji/sample/manage/service/ICameraVideoService.java
New file
@@ -0,0 +1,37 @@
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.CapacityVideoDTO;
import com.dji.sample.manage.model.receiver.CapacityVideoReceiver;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
public interface ICameraVideoService {
    /**
     * Queries all lens data contained in the camera based on camera id.
     * @param cameraId
     * @return
     */
    List<CapacityVideoDTO> getCameraVideosByCameraId(Integer cameraId);
    /**
     * Deletes all the data of this lens, according to the lens id.
     * @param ids   A collection of lens ids.
     * @return true
     */
    Boolean deleteCameraVideosById(List<Integer> ids);
    /**
     * Save the live capability of all lenses of this camera.
     * @param capacityVideoReceivers live capability of lens
     * @param cameraId
     * @return
     */
    Boolean saveCameraVideoDTOList(List<CapacityVideoReceiver> capacityVideoReceivers, Integer cameraId);
}
src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java
New file
@@ -0,0 +1,45 @@
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.CapacityCameraDTO;
import com.dji.sample.manage.model.receiver.CapacityCameraReceiver;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
public interface ICapacityCameraService {
    /**
     * Query all camera data that can be live streamed from this device based on the device sn.
     * @param deviceSn
     * @return
     */
    List<CapacityCameraDTO> getCapacityCameraByDeviceSn(String deviceSn);
    /**
     * Query whether this camera data has been saved based on the device sn and camera location.
     * @param deviceSn
     * @param cameraIndex
     * @return
     */
    Boolean checkExist(String deviceSn, String cameraIndex);
    /**
     * Delete all live capability data for this device based on the device sn.
     * @param deviceSn
     * @return
     */
    Boolean deleteCapacityCameraByDeviceSn(String deviceSn);
    /**
     * Save the live capability data of the device.
     * @param capacityCameraReceivers
     * @param deviceSn
     * @return
     */
    Boolean saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn);
}
src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
import java.util.Optional;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/15
 */
public interface IDeviceDictionaryService {
    /**
     * Query the type data of the device based on domain, device type and sub type.
     * @param domain
     * @param deviceType
     * @param subType
     * @return
     */
    Optional<DeviceDictionaryDTO> getOneDictionaryInfoByDomainTypeSubType(
            Integer domain, Integer deviceType, Integer subType);
}
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java
New file
@@ -0,0 +1,48 @@
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.DevicePayloadDTO;
import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
public interface IDevicePayloadService {
    /**
     * Query if the payload has been saved based on the sn of the payload.
     * @param payloadSn
     * @return
     */
    Integer checkPayloadExist(String payloadSn);
    /**
     * Save all payload data.
     * @param payloadReceiverList
     * @return
     */
    Boolean savePayloadDTOs(List<DevicePayloadReceiver> payloadReceiverList);
    /**
     * Save a payload data.
     * @param payloadReceiver
     * @return
     */
    Integer saveOnePayloadDTO(DevicePayloadReceiver payloadReceiver);
    /**
     * Query all payload data on this device based on the device sn.
     * @param deviceSn
     * @return
     */
    List<DevicePayloadDTO> getDevicePayloadEntitiesByDeviceSn(String deviceSn);
    /**
     * Delete all payload data on these devices based on the collection of device sns.
     * @param deviceSns
     */
    void deletePayloadsByDeviceSn(List<String> deviceSns);
}
src/main/java/com/dji/sample/manage/service/IDeviceService.java
New file
@@ -0,0 +1,135 @@
package com.dji.sample.manage.service;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.TopologyDeviceDTO;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
/**
 * @author sean.zhou
 * @date 2021/11/10
 * @version 0.1
 */
public interface IDeviceService {
    /**
     * The device goes online.
     * @param deviceGateway gateway
     * @return Whether the online is successful.
     */
    Boolean deviceOnline(StatusGatewayReceiver deviceGateway);
    /**
     * The device goes offline.
     * @param gatewaySn
     * @return Whether the offline is successful.
     */
    Boolean deviceOffline(String gatewaySn);
    /**
     * The aircraft goes offline.
     * @param deviceSn aircraft's SN
     * @return Whether the offline is successful.
     */
    Boolean subDeviceOffline(String deviceSn);
    /**
     * When the device goes online, it needs to subscribe to topics.
     * @param sn device's SN
     */
    void subscribeTopicOnline(String sn);
    /**
     * When the device goes offine, it needs to cancel the subscribed topics.
     * @param sn device's SN
     */
    void unsubscribeTopicOffline(String sn);
    /**
     * Delete all device data according to the SN of the device.
     * @param ids device's SN
     * @return
     */
    Boolean delDeviceByDeviceSns(List<String> ids);
    /**
     * Obtain device data according to different query conditions.
     * @param param query parameters
     * @return
     */
    List<DeviceDTO> getDevicesByParams(DeviceQueryParam param);
    /**
     * When you receive a status topic message, you need to reply to it.
     * @param sn   the target of sn
     * @param response
     */
    void publishStatusReply(String sn, CommonTopicResponse<Object> response);
    /**
     * The business interface on the web side. Get all information about all devices in this workspace.
     * @param workspaceId
     * @return
     */
    List<DeviceDTO> getDevicesTopoForWeb(String workspaceId);
    /**
     * Set the remote controller and payloads information of the drone.
     * @param device
     */
    void spliceDeviceTopo(DeviceDTO device);
    /**
     * Push the topology information to the pilot after one device is online.
     * @param sessions  The collection of connection objects on the pilot side.
     * @param sn
     */
    void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn);
    /**
     * Query the information of the device according to the sn of the device.
     * @param sn device sn
     * @return
     */
    Optional<TopologyDeviceDTO> getDeviceTopoForPilot(String sn);
    /**
     * Convert individual device information into topology objects.
     * @param device
     * @return
     */
    TopologyDeviceDTO deviceConvertToTopologyDTO(DeviceDTO device);
    /**
     * When the server receives the request of any device online, offline and topology update in the same workspace,
     * it also broadcasts a push of device online, offline and topology update to PILOT via websocket,
     * and PILOT will get the device topology list again after receiving the push.
     * @param workspaceId
     * @param gatewaySn
     */
    void pushDeviceOfflineTopo(String workspaceId, String gatewaySn);
    /**
     * When the server receives the request of any device online, offline and topology update in the same workspace,
     * it also broadcasts a push of device online, offline and topology update to PILOT via websocket,
     * and PILOT will get the device topology list again after receiving the push.
     * @param workspaceId
     * @param deviceSn
     * @param gatewaySn
     */
    void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn);
    /**
     * Handle messages from the osd topic.
     * @param topic     osd
     * @param payload
     */
    void handleOSD(String topic, byte[] payload);
}
src/main/java/com/dji/sample/manage/service/ILiveStreamService.java
New file
@@ -0,0 +1,51 @@
package com.dji.sample.manage.service;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.CapacityDeviceDTO;
import com.dji.sample.manage.model.dto.LiveTypeDTO;
import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
import java.util.List;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
public interface ILiveStreamService {
    /**
     * Get all the drone data that can be broadcast live in this workspace.
     * @param workspaceId
     * @return
     */
    List<CapacityDeviceDTO> getLiveCapacity(String workspaceId);
    /**
     * Save live capability data from drone.
     * @param capacityDeviceReceiver
     * @return
     */
    Boolean saveLiveCapacity(CapacityDeviceReceiver capacityDeviceReceiver);
    /**
     * Initiate a live streaming by publishing mqtt message.
     * @param liveParam Parameters needed for on-demand.
     * @return
     */
    ResponseResult liveStart(LiveTypeDTO liveParam);
    /**
     * Stop the live streaming by publishing mqtt message.
     * @param videoId
     * @return
     */
    ResponseResult liveStop(String videoId);
    /**
     * Readjust the clarity of the live streaming by publishing mqtt messages.
     * @param liveParam
     * @return
     */
    ResponseResult liveSetQuality(LiveTypeDTO liveParam);
}
src/main/java/com/dji/sample/manage/service/ITSAService.java
New file
@@ -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);
}
src/main/java/com/dji/sample/manage/service/ITopologyService.java
New file
@@ -0,0 +1,20 @@
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.TopologyDTO;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/8
 */
public interface ITopologyService {
    /**
     * Get the topology list of all devices in the workspace for pilot display.
     * @param workspaceId
     * @return
     */
    List<TopologyDTO> getDeviceTopology(String workspaceId);
}
src/main/java/com/dji/sample/manage/service/IUserService.java
New file
@@ -0,0 +1,32 @@
package com.dji.sample.manage.service;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.UserDTO;
import java.util.Optional;
public interface IUserService {
    /**
     * Query user's details based on username.
     * @param username
     * @param workspaceId
     * @return
     */
    ResponseResult getUserByUsername(String username, String workspaceId);
    /**
     * Verify the username and password to log in.
     * @param username
     * @param password
     * @return
     */
    ResponseResult userLogin(String username, String password);
    /**
     * Create a user object containing a new token.
     * @param token
     * @return
     */
    Optional<UserDTO> refreshToken(String token);
}
src/main/java/com/dji/sample/manage/service/IWorkspaceService.java
New file
@@ -0,0 +1,23 @@
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.WorkspaceDTO;
import java.util.Optional;
public interface IWorkspaceService {
    /**
     * Query the workspace information based on the primary key id of the database.
     * @param id primary key id
     * @return
     */
    Optional<WorkspaceDTO> getWorkspaceById(int id);
    /**
     * Query the information of a workspace based on its workspace id.
     * @param workspaceId
     * @return
     */
    Optional<WorkspaceDTO> getWorkspaceByWorkspaceId(String workspaceId);
}
src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java
New file
@@ -0,0 +1,59 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.model.WebSocketManager;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.manage.model.dto.TelemetryDTO;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.service.ITSAService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
/**
 * @author sean
 * @version 0.3
 * @date 2022/2/21
 */
public abstract class AbstractTSAService implements ITSAService {
    protected AbstractTSAService tsaService;
    public AbstractTSAService(AbstractTSAService tsaService) {
        this.tsaService = tsaService;
    }
    @Autowired
    protected ISendMessageService sendMessageService;
    @Override
    public void pushTelemetryData(String workspaceId, Object osdData, String sn) {
        // All connected accounts on the pilot side of this workspace.
        Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
                .getValueWithWorkspaceAndUserType(
                        workspaceId, UserTypeEnum.PILOT.getVal());
        TelemetryDTO telemetry = TelemetryDTO.builder()
                .sn(sn)
                .build();
        CustomWebSocketMessage<TelemetryDTO> pilotMessage = CustomWebSocketMessage.<TelemetryDTO>builder()
                .timestamp(System.currentTimeMillis())
                .bizCode(BizCodeEnum.DEVICE_OSD.getCode())
                .data(telemetry)
                .build();
        this.pushTelemetryData(pilotSessions, pilotMessage, osdData);
    }
    public abstract void pushTelemetryData(Collection<ConcurrentWebSocketSession> sessions,
                                           CustomWebSocketMessage<TelemetryDTO> message, Object Object);
    protected abstract void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode,
                                      Collection<ConcurrentWebSocketSession> webSessions, CustomWebSocketMessage wsMessage)
            throws JsonProcessingException;
}
src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java
New file
@@ -0,0 +1,99 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.manage.dao.ICameraVideoMapper;
import com.dji.sample.manage.model.dto.CapacityVideoDTO;
import com.dji.sample.manage.model.entity.CameraVideoEntity;
import com.dji.sample.manage.model.receiver.CapacityVideoReceiver;
import com.dji.sample.manage.service.ICameraVideoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/19
 */
@Service
@Transactional
public class CameraVideoServiceImpl implements ICameraVideoService {
    @Autowired
    private ICameraVideoMapper mapper;
    @Override
    public List<CapacityVideoDTO> getCameraVideosByCameraId(Integer cameraId) {
        return mapper.selectList(
                new LambdaQueryWrapper<CameraVideoEntity>()
                        .eq(CameraVideoEntity::getCameraId, cameraId))
                .stream()
                .map(this::entityConvertToDto)
                .collect(Collectors.toList());
    }
    @Override
    public Boolean deleteCameraVideosById(List<Integer> ids) {
        if (ids.isEmpty()) {
            return true;
        }
        return mapper.deleteBatchIds(ids) > 0;
    }
    @Override
    public Boolean saveCameraVideoDTOList(List<CapacityVideoReceiver> capacityVideoReceivers, Integer cameraId) {
        for (CapacityVideoReceiver videoDTO : capacityVideoReceivers) {
            CameraVideoEntity videoEntity = videoDTOConvertToEntity(videoDTO);
            videoEntity.setCameraId(cameraId);
            int saveId = this.saveOneCameraVideoEntity(videoEntity);
            if (saveId <= 0) {
                return false;
            }
        }
        return true;
    }
    /**
     * Save the live capability of the lens of this camera.
     * @param entity lens data
     * @return
     */
    private Integer saveOneCameraVideoEntity(CameraVideoEntity entity) {
        return mapper.insert(entity) > 0 ? entity.getId() : 0;
    }
    /**
     * Convert the received lens capability object into a database entity object.
     * @param dto received lens object
     * @return entity
     */
    private CameraVideoEntity videoDTOConvertToEntity(CapacityVideoReceiver dto) {
        CameraVideoEntity.CameraVideoEntityBuilder builder = CameraVideoEntity.builder();
        if (dto != null) {
            builder
                    .videoIndex(dto.getVideoIndex())
                    .videoType(dto.getVideoType());
        }
        return builder.build();
    }
    /**
     * Convert database entity objects into lens data transfer object.
     * @param entity
     * @return  data transfer object
     */
    private CapacityVideoDTO entityConvertToDto(CameraVideoEntity entity) {
        CapacityVideoDTO.CapacityVideoDTOBuilder builder = CapacityVideoDTO.builder();
        if (entity != null) {
            builder
                    .id(entity.getId())
                    .index(entity.getVideoIndex())
                    .type(entity.getVideoType());
        }
        return builder.build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java
New file
@@ -0,0 +1,172 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.manage.dao.ICapacityCameraMapper;
import com.dji.sample.manage.model.dto.CapacityCameraDTO;
import com.dji.sample.manage.model.dto.CapacityVideoDTO;
import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
import com.dji.sample.manage.model.entity.CapacityCameraEntity;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.receiver.CapacityCameraReceiver;
import com.dji.sample.manage.service.ICameraVideoService;
import com.dji.sample.manage.service.ICapacityCameraService;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
 * @author sean.zhou
 * @date 2021/11/19
 * @version 0.1
 */
@Service
@Transactional
public class CapacityCameraServiceImpl implements ICapacityCameraService {
    @Autowired
    private ICapacityCameraMapper mapper;
    @Autowired
    private ICameraVideoService cameraVideoService;
    @Autowired
    private IDeviceDictionaryService dictionaryService;
    @Override
    public List<CapacityCameraDTO> getCapacityCameraByDeviceSn(String deviceSn) {
        List<CapacityCameraDTO> capacityCamerasList = mapper.selectList(
                new LambdaQueryWrapper<CapacityCameraEntity>()
                        .eq(CapacityCameraEntity::getDeviceSn, deviceSn))
                .stream()
                .map(this::entityConvertToDto)
                .collect(Collectors.toList());
        capacityCamerasList.forEach(capacityCamera -> {
            // Set the lens data for this camera.
            capacityCamera.setVideosList(
                    cameraVideoService.getCameraVideosByCameraId(capacityCamera.getId()));
        });
        return capacityCamerasList;
    }
    @Override
    public Boolean checkExist(String deviceSn, String cameraIndex) {
        return mapper.selectOne(
                new LambdaQueryWrapper<CapacityCameraEntity>()
                        .eq(CapacityCameraEntity::getDeviceSn, deviceSn)
                        .eq(CapacityCameraEntity::getCameraIndex, cameraIndex)
                        .last(" limit 1")) != null;
    }
    @Override
    public Boolean deleteCapacityCameraByDeviceSn(String deviceSn) {
        List<CapacityCameraDTO> capacityCamerasList = this.getCapacityCameraByDeviceSn(deviceSn);
        // Return directly if no data exists in the database.
        if (capacityCamerasList.isEmpty()) {
            return true;
        }
        List<Integer> cameraIds = capacityCamerasList
                .stream()
                .map(CapacityCameraDTO::getId)
                .collect(Collectors.toList());
        List<Integer> videoIds = capacityCamerasList
                .stream()
                .flatMap(camera -> camera.getVideosList().stream())
                .map(CapacityVideoDTO::getId)
                .collect(Collectors.toList());
        return mapper.deleteBatchIds(cameraIds) > 0 && cameraVideoService.deleteCameraVideosById(videoIds);
    }
    @Override
    public Boolean saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn) {
        for (CapacityCameraReceiver cameraDTO : capacityCameraReceivers) {
            CapacityCameraEntity cameraEntity = receiverConvertToEntity(cameraDTO);
            cameraEntity.setDeviceSn(deviceSn);
            int cameraId = this.saveOneCapacityCameraEntity(cameraEntity);
            if (cameraId <= 0) {
                continue;
            }
            boolean saveVideo = cameraVideoService.saveCameraVideoDTOList(
                    cameraDTO.getVideosList(), cameraId);
            if (!saveVideo) {
                return false;
            }
        }
        return true;
    }
    /**
     * Save the camera live capability data of the device.
     * @param cameraEntity
     * @return
     */
    private Integer saveOneCapacityCameraEntity(CapacityCameraEntity cameraEntity) {
        boolean exist = checkExist(
                cameraEntity.getDeviceSn(), cameraEntity.getCameraIndex());
        if (exist) {
            return -1;
        }
        return mapper.insert(cameraEntity) > 0 ? cameraEntity.getId() : 0;
    }
    /**
     *  Convert the received camera capability object into a database entity object.
     * @param receiver
     * @return
     */
    private CapacityCameraEntity receiverConvertToEntity(CapacityCameraReceiver receiver) {
        CapacityCameraEntity.CapacityCameraEntityBuilder builder = CapacityCameraEntity.builder();
        if (receiver == null) {
            return builder.build();
        }
        int[] indexArr = Arrays.stream(receiver.getCameraIndex().split("-"))
                .map(Integer::valueOf)
                .mapToInt(Integer::intValue)
                .toArray();
        // The cameraIndex consists of type and subType and the index of the payload hanging on the drone.
        // type-subType-index
        if (indexArr.length == 3) {
            Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                    .getOneDictionaryInfoByDomainTypeSubType(
                            DeviceDomainEnum.PAYLOAD.getVal(), indexArr[0], indexArr[1]);
            dictionaryOpt.ifPresent(dictionary ->
                    builder.name(dictionary.getDeviceName()));
        }
        return builder
                .availableVideoNumber(receiver.getAvailableVideoNumber())
                .coexistVideoNumberMax(receiver.getCoexistVideoNumberMax())
                .cameraIndex(receiver.getCameraIndex())
                .build();
    }
    /**
     * Convert database entity objects into camera data transfer object.
     * @param entity
     * @return
     */
    private CapacityCameraDTO entityConvertToDto(CapacityCameraEntity entity) {
        CapacityCameraDTO.CapacityCameraDTOBuilder builder = CapacityCameraDTO.builder();
        if (entity != null) {
            builder
                .id(entity.getId())
                .name(entity.getName())
                .description(entity.getDescription())
                .deviceSn(entity.getDeviceSn())
                .index(entity.getCameraIndex())
                .build();
        }
        return builder.build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java
New file
@@ -0,0 +1,59 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.manage.dao.IDeviceDictionaryMapper;
import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
import com.dji.sample.manage.model.entity.DeviceDictionaryEntity;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
/**
 *
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/15
 */
@Service
@Transactional
public class DeviceDictionaryServiceImpl implements IDeviceDictionaryService {
    @Autowired
    private IDeviceDictionaryMapper mapper;
    @Override
    public Optional<DeviceDictionaryDTO> getOneDictionaryInfoByDomainTypeSubType(
            Integer domain, Integer deviceType, Integer subType) {
        if (domain == null || deviceType == null || subType == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(
                entityConvertToDTO(
                        mapper.selectOne(
                                new LambdaQueryWrapper<DeviceDictionaryEntity>()
                                        .eq(DeviceDictionaryEntity::getDomain, domain)
                                        .eq(DeviceDictionaryEntity::getDeviceType, deviceType)
                                        .eq(DeviceDictionaryEntity::getSubType, subType))));
    }
    /**
     * Convert database entity objects into dictionary data transfer object.
     * @param entity
     * @return
     */
    private DeviceDictionaryDTO entityConvertToDTO(DeviceDictionaryEntity entity) {
        DeviceDictionaryDTO.DeviceDictionaryDTOBuilder builder = DeviceDictionaryDTO.builder();
        if (entity != null) {
            builder.deviceName(entity.getDeviceName())
                    .deviceDesc(entity.getDeviceDesc())
                    .deviceType(entity.getDeviceType())
                    .domain(entity.getDomain())
                    .subType(entity.getSubType());
        }
        return builder.build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java
New file
@@ -0,0 +1,69 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.manage.model.DeviceStatusManager;
import com.dji.sample.manage.model.dto.TelemetryDTO;
import com.dji.sample.manage.model.dto.TelemetryDeviceDTO;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Collection;
/**
 * @author sean
 * @version 0.3
 * @date 2022/2/21
 */
@Service
public class DeviceOSDServiceImpl extends AbstractTSAService {
    protected DeviceOSDServiceImpl() {
        super(null);
    }
    @Override
    public void pushTelemetryData(Collection<ConcurrentWebSocketSession> sessions,
                                  CustomWebSocketMessage<TelemetryDTO> message, Object osdData) {
        if (osdData instanceof OsdSubDeviceReceiver) {
            OsdSubDeviceReceiver data = (OsdSubDeviceReceiver) osdData;
            TelemetryDTO telemetry = message.getData();
            telemetry.setHost(TelemetryDeviceDTO.builder()
                    .latitude(data.getLatitude())
                    .longitude(data.getLongitude())
                    .altitude(data.getElevation())
                    .attitudeHead(data.getAttitudeHead())
                    .elevation(data.getElevation())
                    .horizontalSpeed(data.getHorizontalSpeed())
                    .verticalSpeed(data.getVerticalSpeed())
                    .build());
            this.sendMessageService.sendBatch(sessions, message);
        }
    }
    @Override
    protected void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode,
                             Collection<ConcurrentWebSocketSession> webSessions, CustomWebSocketMessage wsMessage) throws JsonProcessingException {
        // Real-time update of device status in memory
        DeviceStatusManager.STATUS_MANAGER.put(
                DeviceDomainEnum.SUB_DEVICE.getVal() + "/" + sn, LocalDateTime.now());
        wsMessage.setBizCode(BizCodeEnum.DEVICE_OSD.getCode());
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        OsdSubDeviceReceiver data = mapper.treeToValue(hostNode, OsdSubDeviceReceiver.class);
        wsMessage.setData(data);
        sendMessageService.sendBatch(webSessions, wsMessage);
        this.pushTelemetryData(workspaceId, data, sn);
    }
}
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java
New file
@@ -0,0 +1,157 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.manage.dao.IDevicePayloadMapper;
import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
import com.dji.sample.manage.model.dto.DevicePayloadDTO;
import com.dji.sample.manage.model.entity.DevicePayloadEntity;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
import com.dji.sample.manage.service.ICapacityCameraService;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import com.dji.sample.manage.service.IDevicePayloadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/19
 */
@Service
@Transactional
public class DevicePayloadServiceImpl implements IDevicePayloadService {
    @Autowired
    private IDevicePayloadMapper mapper;
    @Autowired
    private IDeviceDictionaryService dictionaryService;
    @Autowired
    private ICapacityCameraService capacityCameraService;
    @Override
    public Integer checkPayloadExist(String payloadSn) {
        DevicePayloadEntity devicePayload = mapper.selectOne(
                new LambdaQueryWrapper<DevicePayloadEntity>()
                        .eq(DevicePayloadEntity::getPayloadSn, payloadSn));
        return devicePayload != null ? devicePayload.getId() : -1;
    }
    private Integer saveOnePayloadEntity(DevicePayloadEntity entity) {
        int id = this.checkPayloadExist(entity.getPayloadSn());
        // If it already exists, update the data directly.
        if (id > 0) {
            entity.setId(id);
            return mapper.updateById(entity);
        }
        return mapper.insert(entity) > 0 ? entity.getId() : 0;
    }
    @Override
    public Boolean savePayloadDTOs(List<DevicePayloadReceiver> payloadReceiverList) {
        for (DevicePayloadReceiver payloadReceiver : payloadReceiverList) {
            int payloadId = this.saveOnePayloadDTO(payloadReceiver);
            if (payloadId <= 0) {
                return false;
            }
        }
        return true;
    }
    @Override
    public Integer saveOnePayloadDTO(DevicePayloadReceiver payloadReceiver) {
        return this.saveOnePayloadEntity(payloadDTOConvertToEntity(payloadReceiver));
    }
    @Override
    public List<DevicePayloadDTO> getDevicePayloadEntitiesByDeviceSn(String deviceSn) {
        return mapper.selectList(
                new LambdaQueryWrapper<DevicePayloadEntity>()
                        .eq(DevicePayloadEntity::getDeviceSn, deviceSn))
                .stream()
                .map(this::payloadEntityConvertToDTO)
                .collect(Collectors.toList());
    }
    @Override
    public void deletePayloadsByDeviceSn(List<String> deviceSns) {
        deviceSns.forEach(deviceSn -> {
            mapper.delete(
                    new LambdaQueryWrapper<DevicePayloadEntity>()
                            .eq(DevicePayloadEntity::getDeviceSn, deviceSn));
            capacityCameraService.deleteCapacityCameraByDeviceSn(deviceSn);
        });
    }
    /**
     * Convert database entity objects into payload data transfer object.
     * @param entity
     * @return
     */
    private DevicePayloadDTO payloadEntityConvertToDTO(DevicePayloadEntity entity) {
        DevicePayloadDTO.DevicePayloadDTOBuilder builder = DevicePayloadDTO.builder();
        if (entity != null) {
            builder.payloadSn(entity.getPayloadSn())
                    .payloadName(entity.getPayloadName())
                    .payloadDesc(entity.getPayloadDesc())
                    .payloadIndex(entity.getPayloadIndex());
        }
        return builder.build();
    }
    /**
     * Convert the received payload object into a database entity object.
     * @param dto   payload
     * @return
     */
    private DevicePayloadEntity payloadDTOConvertToEntity(DevicePayloadReceiver dto) {
        if (dto == null) {
            return new DevicePayloadEntity();
        }
        DevicePayloadEntity.DevicePayloadEntityBuilder builder = DevicePayloadEntity.builder();
        // The cameraIndex consists of type and subType and the index of the payload hanging on the drone.
        // type-subType-index
        String[] payloadIndexArr = dto.getPayloadIndex().split("-");
        try {
            int[] arr = Arrays.stream(payloadIndexArr)
                    .map(Integer::valueOf)
                    .mapToInt(Integer::intValue)
                    .toArray();
            if (arr.length == 3) {
                Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                        .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(),
                                arr[0], arr[1]);
                dictionaryOpt.ifPresent(dictionary ->
                        builder.payloadName(dictionary.getDeviceName())
                                .payloadDesc(dictionary.getDeviceDesc()));
            }
            builder.payloadType(arr[0])
                    .subType(arr[1])
                    .payloadIndex(arr[2]);
        } catch (NumberFormatException e) {
            builder.payloadType(Integer.valueOf(payloadIndexArr[0]))
                    .subType(-1)
                    .payloadIndex(Integer.valueOf(payloadIndexArr[2]));
        }
        return builder
                .payloadSn(dto.getSn())
                .version(dto.getVersion())
                .deviceSn(dto.getSn()
                        .substring(0,
                                dto.getSn().indexOf("-")))
                .build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
New file
@@ -0,0 +1,553 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.mqtt.service.IMqttTopicService;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.model.WebSocketManager;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.manage.dao.IDeviceMapper;
import com.dji.sample.manage.model.dto.*;
import com.dji.sample.manage.model.entity.DeviceEntity;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.enums.IconUrlEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import com.dji.sample.manage.model.receiver.StatusSubDeviceReceiver;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import com.dji.sample.manage.service.IDevicePayloadService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.manage.service.IWorkspaceService;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static com.dji.sample.component.mqtt.model.TopicConst.*;
/**
 *
 * @author sean.zhou
 * @version 0.1
 * @date 2021/11/10
 */
@Service
@Slf4j
@Transactional
public class DeviceServiceImpl implements IDeviceService {
    @Autowired
    private IMessageSenderService messageSender;
    @Autowired
    private IDeviceMapper mapper;
    @Autowired
    private IDeviceDictionaryService dictionaryService;
    @Autowired
    private IMqttTopicService topicService;
    @Autowired
    private IWorkspaceService workspaceService;
    @Autowired
    private IDevicePayloadService payloadService;
    @Autowired
    private ISendMessageService sendMessageService;
    @Autowired
    @Qualifier("gatewayOSDServiceImpl")
    private AbstractTSAService tsaService;
    @Override
    public Boolean deviceOffline(String gatewaySn) {
        List<DeviceDTO> gatewaysList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .deviceSn(gatewaySn)
                        .build());
        // If no information about this gateway device exists in the database, the drone is considered to be offline.
        if (gatewaysList.isEmpty()) {
            log.debug("The drone is already offline.");
            return true;
        }
        // Handle the drone connected to the gateway device offline.
        return this.subDeviceOffline(gatewaysList.get(0).getChildDeviceSn());
    }
    @Override
    public Boolean subDeviceOffline(String deviceSn) {
        // Cancel drone-related subscriptions.
        this.unsubscribeTopicOffline(deviceSn);
        List<DeviceDTO> devicesList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .deviceSn(deviceSn)
                        .build());
        // If no information about this drone exists in the database, the drone is considered to be offline.
        if (devicesList.isEmpty()) {
            log.debug("{} is already offline.", deviceSn);
            return true;
        }
        List<String> ids = devicesList.stream()
                .map(DeviceDTO::getDeviceSn)
                .collect(Collectors.toList());
        // Delete all data related to the drone.
        boolean isDel = this.delDeviceByDeviceSns(ids);
        payloadService.deletePayloadsByDeviceSn(ids);
        log.debug("{} offline status: {}.", deviceSn, isDel);
        return isDel;
    }
    @Override
    public Boolean deviceOnline(StatusGatewayReceiver deviceGateway) {
        String deviceSn = deviceGateway.getSubDevices().get(0).getSn();
        List<DeviceDTO> devicesList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .deviceSn(deviceSn)
                        .build());
        // If the information about this drone exists in the database, the drone is considered to be online.
        if (!devicesList.isEmpty()) {
            log.warn("{} is already online.", deviceSn);
            // Subscribe to topic related to drone and gateway devices.
            this.subscribeTopicOnline(deviceGateway.getSn());
            this.subscribeTopicOnline(deviceSn);
            return true;
        }
        // Delete the gateway device information that was previously bound to the drone.
        this.delDeviceByDeviceSns(
                this.getDevicesByParams(
                        DeviceQueryParam.builder()
                                .childSn(deviceSn)
                                .build())
                        .stream()
                        .map(DeviceDTO::getDeviceSn)
                        .collect(Collectors.toList()));
        DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway);
        gateway.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID);
        // Set the icon of the gateway device displayed in the pilot's map, required in the TSA module.
        gateway.setUrlNormal(IconUrlEnum.NORMAL_PERSON.getUrl());
        // Set the icon of the gateway device displayed in the pilot's map when it is selected, required in the TSA module.
        gateway.setUrlSelect(IconUrlEnum.SELECT_PERSON.getUrl());
        DeviceEntity subDevice = subDeviceConvertToDeviceEntity(deviceGateway.getSubDevices().get(0));
        subDevice.setWorkspaceId(WorkspaceDTO.DEFAULT_WORKSPACE_ID);
        // Set the icon of the drone device displayed in the pilot's map, required in the TSA module.
        subDevice.setUrlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl());
        // Set the icon of the drone device displayed in the pilot's map when it is selected, required in the TSA module.
        subDevice.setUrlNormal(IconUrlEnum.NORMAL_EQUIPMENT.getUrl());
        gateway.setChildSn(subDevice.getDeviceSn());
        boolean isSave = this.saveDevice(gateway) > 0 && this.saveDevice(subDevice) > 0;
        log.debug(subDevice.getDeviceSn() + " online status: {}", isSave);
        if (isSave) {
            // Subscribe to topic related to drone and gateway devices.
            this.subscribeTopicOnline(subDevice.getDeviceSn());
            this.subscribeTopicOnline(gateway.getDeviceSn());
        }
        return isSave;
    }
    @Override
    public void subscribeTopicOnline(String sn) {
        String[] subscribedTopic = topicService.getSubscribedTopic();
        for (String s : subscribedTopic) {
            // If you have already subscribed to the topic of the device, you do not need to subscribe again.
            if (s.contains(sn)) {
                return;
            }
        }
        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF);
        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF);
        topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF);
    }
    @Override
    public void unsubscribeTopicOffline(String sn) {
        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF);
        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF);
        topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF);
    }
    @Override
    public Boolean delDeviceByDeviceSns(List<String> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return true;
        }
        return mapper.delete(new LambdaQueryWrapper<DeviceEntity>()
                .in(DeviceEntity::getDeviceSn, ids))
                > 0;
    }
    @Override
    public void publishStatusReply(String sn, CommonTopicResponse<Object> response) {
        Map<String, Integer> result = new ConcurrentHashMap<>(1);
        result.put("result", 0);
        response.setData(result);
        messageSender.publish(
                new StringBuilder()
                        .append(BASIC_PRE)
                        .append(PRODUCT)
                        .append(sn)
                        .append(STATUS_SUF)
                        .append(_REPLY_SUF)
                        .toString(),
                response);
    }
    @Override
    public List<DeviceDTO> getDevicesByParams(DeviceQueryParam param) {
        return mapper.selectList(
                new LambdaQueryWrapper<DeviceEntity>()
                        .eq(StringUtils.hasText(param.getDeviceSn()),
                                    DeviceEntity::getDeviceSn, param.getDeviceSn())
                        .eq(param.getDeviceType() != null,
                                DeviceEntity::getDeviceType, param.getDeviceType())
                        .eq(param.getSubType() != null,
                                DeviceEntity::getSubType, param.getSubType())
                        .eq(StringUtils.hasText(param.getChildSn()),
                                DeviceEntity::getChildSn, param.getChildSn())
                        .eq(param.getDomain() != null,
                                DeviceEntity::getDomain, param.getDomain())
                        .eq(StringUtils.hasText(param.getWorkspaceId()),
                                DeviceEntity::getWorkspaceId, param.getWorkspaceId())
                        .orderBy(param.isOrderBy(),
                                param.isAsc(), DeviceEntity::getId))
                .stream()
                .map(this::deviceEntityConvertToDTO)
                .collect(Collectors.toList());
    }
    @Override
    public List<DeviceDTO> getDevicesTopoForWeb(String workspaceId) {
        List<DeviceDTO> devicesList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .workspaceId(workspaceId)
                        .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                        .build());
        devicesList.forEach(device -> {
            this.spliceDeviceTopo(device);
            device.setWorkspaceId(workspaceId);
        });
        return devicesList;
    }
    @Override
    public void spliceDeviceTopo(DeviceDTO device) {
        // remote controller
        List<DeviceDTO> gatewaysList = getDevicesByParams(
                DeviceQueryParam.builder()
                        .childSn(device.getDeviceSn())
                        .build());
        // payloads
        List<DevicePayloadDTO> payloadsList = payloadService
                .getDevicePayloadEntitiesByDeviceSn(device.getDeviceSn());
        device.setGatewaysList(gatewaysList);
        device.setPayloadsList(payloadsList);
    }
    @Override
    public Optional<TopologyDeviceDTO> getDeviceTopoForPilot(String sn) {
        List<TopologyDeviceDTO> topologyDeviceList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .deviceSn(sn)
                        .build())
                .stream()
                .map(this::deviceConvertToTopologyDTO)
                .collect(Collectors.toList());
        if (topologyDeviceList.isEmpty()) {
            return Optional.empty();
        }
        return Optional.of(topologyDeviceList.get(0));
    }
    @Override
    public void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn) {
        CustomWebSocketMessage<TopologyDeviceDTO> pilotMessage =
                CustomWebSocketMessage.<TopologyDeviceDTO>builder()
                        .timestamp(System.currentTimeMillis())
                        .bizCode(BizCodeEnum.DEVICE_ONLINE.getCode())
                        .data(new TopologyDeviceDTO())
                        .build();
        this.getDeviceTopoForPilot(sn)
                .ifPresent(pilotMessage::setData);
        sendMessageService.sendBatch(sessions, pilotMessage);
    }
    @Override
    public TopologyDeviceDTO deviceConvertToTopologyDTO(DeviceDTO device) {
        TopologyDeviceDTO.TopologyDeviceDTOBuilder builder = TopologyDeviceDTO.builder();
        if (device != null) {
            String domain = String.valueOf(DeviceDomainEnum.getVal(device.getDomain()));
            String subType = String.valueOf(device.getSubType());
            String type = String.valueOf(device.getType());
            builder.sn(device.getDeviceSn())
                    .deviceCallsign(device.getDeviceName())
                    .deviceModel(DeviceModelDTO.builder()
                            .domain(domain)
                            .subType(subType)
                            .type(type)
                            .key(domain + "-" + type + "-" + subType)
                            .build())
                    .iconUrls(device.getIconUrl())
                    .build();
        }
        return builder.build();
    }
    @Override
    public void pushDeviceOnlineTopo(String workspaceId, String deviceSn, String gatewaySn) {
        // All connected accounts on the pilot side of this workspace.
        Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
                .getValueWithWorkspaceAndUserType(
                        workspaceId, UserTypeEnum.PILOT.getVal());
        this.pushDeviceOnlineTopo(pilotSessions, deviceSn);
        this.pushDeviceOnlineTopo(pilotSessions, gatewaySn);
        this.pushDeviceUpdateTopo(pilotSessions, deviceSn);
        this.pushDeviceUpdateTopo(pilotSessions, gatewaySn);
    }
    @Override
    public void pushDeviceOfflineTopo(String workspaceId, String gatewaySn) {
        // All connected accounts on the pilot side of this workspace.
        Collection<ConcurrentWebSocketSession> pilotSessions = WebSocketManager
                .getValueWithWorkspaceAndUserType(
                        workspaceId, UserTypeEnum.PILOT.getVal());
        List<DeviceDTO> gatewaysList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .deviceSn(gatewaySn)
                        .build());
        if (!gatewaysList.isEmpty()) {
            String deviceSn = gatewaysList.get(0).getChildDeviceSn();
            this.pushDeviceOfflineTopo(pilotSessions, deviceSn);
            this.pushDeviceUpdateTopo(pilotSessions, deviceSn);
        }
        this.pushDeviceOfflineTopo(pilotSessions, gatewaySn);
        this.pushDeviceUpdateTopo(pilotSessions, gatewaySn);
    }
    @Override
    public void handleOSD(String topic, byte[] payload) {
        TopicStateReceiver receiver;
        try {
            String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(),
                    topic.indexOf(OSD_SUF));
            List<DeviceDTO> deviceList = this.getDevicesByParams(
                    DeviceQueryParam.builder().deviceSn(from).build());
            if (deviceList.isEmpty()) {
                return;
            }
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            receiver = mapper.readValue(payload, TopicStateReceiver.class);
            CustomWebSocketMessage wsMessage = CustomWebSocketMessage.builder()
                    .timestamp(System.currentTimeMillis()).build();
            JsonNode hostNode = mapper.readTree(payload).findPath("data");
            String workspaceId = deviceList.get(0).getWorkspaceId();
            Collection<ConcurrentWebSocketSession> webSessions = WebSocketManager
                    .getValueWithWorkspaceAndUserType(
                            workspaceId, UserTypeEnum.WEB.getVal());
            tsaService.handleOSD(receiver, from, workspaceId, hostNode, webSessions, wsMessage);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * Notify the pilot side that there is an update of the device topology.
     * @param sessions
     * @param deviceSn
     */
    private void pushDeviceUpdateTopo(Collection<ConcurrentWebSocketSession> sessions, String deviceSn) {
        CustomWebSocketMessage pilotMessage =
                CustomWebSocketMessage.builder()
                        .timestamp(System.currentTimeMillis())
                        .bizCode(BizCodeEnum.DEVICE_UPDATE_TOPO.getCode())
                        .data(new TopologyDeviceDTO())
                        .build();
        sendMessageService.sendBatch(sessions, pilotMessage);
    }
    /**
     * Notify the pilot side that device is offline and needs to reacquire topology information.
     * @param sessions
     * @param sn
     */
    private void pushDeviceOfflineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn) {
        CustomWebSocketMessage<TopologyDeviceDTO> pilotMessage =
                CustomWebSocketMessage.<TopologyDeviceDTO>builder()
                        .timestamp(System.currentTimeMillis())
                        .bizCode(BizCodeEnum.DEVICE_OFFLINE.getCode())
                        .data(TopologyDeviceDTO.builder()
                                .sn(sn)
                                .onlineStatus(false)
                                .build())
                        .build();
        sendMessageService.sendBatch(sessions, pilotMessage);
    }
    /**
     * Save the device information and update the information directly if the device already exists.
     * @param entity
     * @return
     */
    private Integer saveDevice(DeviceEntity entity) {
        DeviceEntity deviceEntity = mapper.selectOne(
                new LambdaQueryWrapper<DeviceEntity>()
                        .eq(DeviceEntity::getDeviceSn, entity.getDeviceSn()));
        // Update the information directly if the device already exists.
        if (deviceEntity != null) {
            entity.setId(deviceEntity.getId());
            mapper.updateById(entity);
            return deviceEntity.getId();
        }
        return mapper.insert(entity) > 0 ? entity.getId() : 0;
    }
    /**
     * Convert the received gateway device object into a database entity object.
     * @param gateway
     * @return
     */
    private DeviceEntity deviceGatewayConvertToDeviceEntity(StatusGatewayReceiver gateway) {
        if (gateway == null) {
            return new DeviceEntity();
        }
        DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
        // Query the model information of this gateway device.
        Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                .getOneDictionaryInfoByDomainTypeSubType(gateway.getDomain(),
                        gateway.getType(), gateway.getSubType());
        dictionaryOpt.ifPresent(entity ->
                builder.deviceName(entity.getDeviceName())
                        .deviceDesc(entity.getDeviceDesc()));
        return builder
                .deviceSn(gateway.getSn())
                .domain(gateway.getDomain())
                .subType(gateway.getSubType())
                .deviceType(gateway.getType())
                .version(gateway.getVersion())
                .build();
    }
    /**
     * Convert the received drone device object into a database entity object.
     * @param device
     * @return
     */
    private DeviceEntity subDeviceConvertToDeviceEntity(StatusSubDeviceReceiver device) {
        if (device == null) {
            return new DeviceEntity();
        }
        DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
        // Query the model information of this drone device.
        Optional<DeviceDictionaryDTO> dictionaryOpt = dictionaryService
                .getOneDictionaryInfoByDomainTypeSubType(DeviceDomainEnum.SUB_DEVICE.getVal(),
                        device.getType(), device.getSubType());
        dictionaryOpt.ifPresent(dictionary ->
                builder.deviceName(dictionary.getDeviceName())
                        .deviceDesc(dictionary.getDeviceDesc()));
        return builder
                .deviceSn(device.getSn())
                .deviceType(device.getType())
                .subType(device.getSubType())
                .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                .version(device.getVersion())
                .deviceIndex(device.getIndex())
                .build();
    }
    /**
     * Convert database entity objects into device data transfer object.
     * @param entity
     * @return
     */
    private DeviceDTO deviceEntityConvertToDTO(DeviceEntity entity) {
        DeviceDTO.DeviceDTOBuilder builder = DeviceDTO.builder();
        if (entity != null) {
            builder.deviceSn(entity.getDeviceSn())
                    .childDeviceSn(entity.getChildSn())
                    .deviceName(entity.getDeviceName())
                    .deviceDesc(entity.getDeviceDesc())
                    .deviceIndex(entity.getDeviceIndex())
                    .workspaceId(entity.getWorkspaceId())
                    .type(entity.getDeviceType())
                    .subType(entity.getSubType())
                    .domain(DeviceDomainEnum.getDesc(entity.getDomain()))
                    .iconUrl(IconUrlDTO.builder()
                            .normalUrl(entity.getUrlNormal())
                            .selectUrl(entity.getUrlSelect())
                            .build())
                    .build();
        }
        return builder.build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java
New file
@@ -0,0 +1,74 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.TopicStateReceiver;
import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.manage.model.DeviceStatusManager;
import com.dji.sample.manage.model.dto.TelemetryDTO;
import com.dji.sample.manage.model.dto.TelemetryDeviceDTO;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.receiver.OsdGatewayReceiver;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Collection;
/**
 * @author sean
 * @version 0.3
 * @date 2022/2/21
 */
@Service
public class GatewayOSDServiceImpl extends AbstractTSAService {
    public GatewayOSDServiceImpl(@Autowired @Qualifier("deviceOSDServiceImpl") AbstractTSAService tsaService) {
        super(tsaService);
    }
    @Override
    public void pushTelemetryData(Collection<ConcurrentWebSocketSession> sessions,
                                  CustomWebSocketMessage<TelemetryDTO> message, Object osdData) {
        if (osdData instanceof OsdGatewayReceiver) {
            OsdGatewayReceiver data = (OsdGatewayReceiver) osdData;
            TelemetryDTO telemetry = message.getData();
            telemetry.setHost(TelemetryDeviceDTO.builder()
                    .latitude(data.getLatitude())
                    .longitude(data.getLongitude())
                    .build());
            this.sendMessageService.sendBatch(sessions, message);
            return;
        }
        tsaService.pushTelemetryData(sessions, message, osdData);
    }
    @Override
    protected void handleOSD(TopicStateReceiver receiver, String sn, String workspaceId, JsonNode hostNode,
                             Collection<ConcurrentWebSocketSession> webSessions, CustomWebSocketMessage wsMessage) throws JsonProcessingException {
        if (sn.equals(receiver.getGateway())) {
            // Real-time update of device status in memory
            DeviceStatusManager.STATUS_MANAGER.put(DeviceDomainEnum.GATEWAY.getVal() + "/" + sn,
                    LocalDateTime.now());
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            wsMessage.setBizCode(BizCodeEnum.GATEWAY_OSD.getCode());
            OsdGatewayReceiver data = mapper.treeToValue(hostNode, OsdGatewayReceiver.class);
            wsMessage.setData(data);
            this.sendMessageService.sendBatch(webSessions, wsMessage);
            this.pushTelemetryData(workspaceId, data, sn);
            return;
        }
        tsaService.handleOSD(receiver, sn, workspaceId, hostNode, webSessions, wsMessage);
    }
}
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
New file
@@ -0,0 +1,326 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.common.error.LiveErrorEnum;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.manage.model.Chan;
import com.dji.sample.manage.model.dto.*;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.enums.LiveMethodEnum;
import com.dji.sample.manage.model.enums.LiveUrlTypeEnum;
import com.dji.sample.manage.model.enums.LiveVideoQualityEnum;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver;
import com.dji.sample.manage.model.receiver.ServiceReplyReceiver;
import com.dji.sample.manage.service.ICapacityCameraService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.manage.service.ILiveStreamService;
import com.dji.sample.manage.service.IWorkspaceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import static com.dji.sample.component.mqtt.model.TopicConst.*;
/**
 * @author sean.zhou
 * @date 2021/11/22
 * @version 0.1
 */
@Service
@Transactional
public class LiveStreamServiceImpl implements ILiveStreamService {
    @Autowired
    private ICapacityCameraService capacityCameraService;
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private IWorkspaceService workspaceService;
    @Autowired
    private IMessageSenderService messageSender;
    @Override
    public List<CapacityDeviceDTO> getLiveCapacity(String workspaceId) {
        // Query all drone data in this workspace.
        List<DeviceDTO> devicesList = deviceService.getDevicesByParams(
                DeviceQueryParam.builder()
                        .workspaceId(workspaceId)
                        .domain(DeviceDomainEnum.SUB_DEVICE.getVal())
                        .build());
        List<CapacityDeviceDTO> capacityDevicesList = new ArrayList<>();
        // Query the live capability of each drone.
        devicesList.forEach(device -> capacityDevicesList.add(CapacityDeviceDTO.builder()
                .name(device.getDeviceName())
                .sn(device.getDeviceSn())
                .camerasList(capacityCameraService.getCapacityCameraByDeviceSn(device.getDeviceSn()))
                .build()));
        return capacityDevicesList;
    }
    @Override
    public Boolean saveLiveCapacity(CapacityDeviceReceiver capacityDeviceReceiver) {
        return capacityCameraService.saveCapacityCameraReceiverList(
                capacityDeviceReceiver.getCameraList(),
                capacityDeviceReceiver.getSn());
    }
    @Override
    public ResponseResult liveStart(LiveTypeDTO liveParam) {
        // Check if this lens is available live.
        ResponseResult responseResult = this.checkBeforeLive(liveParam.getVideoId());
        if (responseResult.getCode() != 0) {
            return responseResult;
        }
        List<DeviceDTO> data = (List<DeviceDTO>)responseResult.getData();
        // target topic
        String respTopic = THING_MODEL_PRE + PRODUCT +
                data.get(0).getDeviceSn() + SERVICES_SUF;
        Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveStart(respTopic, liveParam);
        if (receiveReplyOpt.isEmpty()) {
            return ResponseResult.error(LiveErrorEnum.NO_REPLY);
        }
        if (receiveReplyOpt.get().getResult() != 0) {
            return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult()));
        }
        LiveUrlTypeEnum urlType = LiveUrlTypeEnum.find(liveParam.getUrlType());
        LiveDTO live = new LiveDTO();
        switch (urlType) {
            case RTMP:
                live.setUrl(liveParam.getUrl().replace("rtmp", "webrtc"));
                break;
            case GB28181:
                LiveUrlGB28181DTO gb28181 = urlToGB28181(liveParam.getUrl());
                live.setUrl(new StringBuilder()
                        .append("webrtc://")
                        .append(gb28181.getServerIP())
                        .append("/live/")
                        .append(gb28181.getAgentID())
                        .append("@")
                        .append(gb28181.getChannel())
                        .toString());
                break;
            case RTSP:
                String url = receiveReplyOpt.get().getInfo();
                this.resolveUrlUser(url, live);
                break;
            case UNKNOWN:
                return ResponseResult.error(LiveErrorEnum.URL_TYPE_NOT_SUPPORTED);
        }
        return ResponseResult.success(live);
    }
    @Override
    public ResponseResult liveStop(String videoId) {
        ResponseResult<List<DeviceDTO>> responseResult = this.checkBeforeLive(videoId);
        if (responseResult.getCode() != 0) {
            return responseResult;
        }
        String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().get(0).getDeviceSn() + SERVICES_SUF;
        Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveStop(respTopic, videoId);
        if (receiveReplyOpt.isEmpty()) {
            return ResponseResult.error(LiveErrorEnum.NO_REPLY);
        }
        if (receiveReplyOpt.get().getResult() != 0) {
            return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult()));
        }
        return ResponseResult.success();
    }
    @Override
    public ResponseResult liveSetQuality(LiveTypeDTO liveParam) {
        if (liveParam.getVideoQuality() == null ||
                LiveVideoQualityEnum.UNKNOWN == LiveVideoQualityEnum.find(liveParam.getVideoQuality())) {
            return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS);
        }
        ResponseResult<List<DeviceDTO>> responseResult = this.checkBeforeLive(liveParam.getVideoId());
        if (responseResult.getCode() != 0) {
            return responseResult;
        }
        String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().get(0).getDeviceSn() + SERVICES_SUF;
        Optional<ServiceReplyReceiver> receiveReplyOpt = this.publishLiveSetQuality(respTopic, liveParam);
        if (receiveReplyOpt.isEmpty()) {
            return ResponseResult.error(LiveErrorEnum.NO_REPLY);
        }
        if (receiveReplyOpt.get().getResult() != 0) {
            return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult()));
        }
        return ResponseResult.success();
    }
    /**
     * Check if this lens is available live.
     * @param videoId
     * @return
     */
    private ResponseResult checkBeforeLive(String videoId) {
        String[] videoIdArr = videoId.split("/");
        // drone sn / enumeration value of the location where the payload is mounted / payload lens
        if (videoIdArr.length != 3) {
            return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS);
        }
        List<DeviceDTO> gatewayList = deviceService.getDevicesByParams(
                DeviceQueryParam.builder()
                        .childSn(videoIdArr[0])
                        .build());
        // Check if the gateway device connected to this drone exists
        if (gatewayList.isEmpty()) {
            return ResponseResult.error(LiveErrorEnum.NO_FLIGHT_CONTROL);
        }
        return ResponseResult.success(gatewayList);
    }
    /**
     * When using rtsp live, the account and password are parsed from the information returned by the pilot.
     * @param url
     * @param live
     */
    private void resolveUrlUser(String url, LiveDTO live) {
        if (!StringUtils.hasText(url)) {
            return;
        }
        int start = url.indexOf("//");
        int end = url.lastIndexOf("@");
        String user = url.substring(start + 2, end);
        url = url.replace(user + "@", "");
        String[] userArr = user.split(":");
        live.setUsername(userArr[0]);
        live.setPassword(userArr[1]);
        live.setUrl(url);
    }
    /**
     * When using GB28181 live, url parameters are resolved into objects.
     * @param url
     * @return
     */
    private LiveUrlGB28181DTO urlToGB28181(String url) {
        String[] arr = url.split("\\=|\\&");
        LiveUrlGB28181DTO gb28181 = new LiveUrlGB28181DTO();
        try {
            Class<LiveUrlGB28181DTO> clazz = LiveUrlGB28181DTO.class;
            for (int i = 0; i < arr.length - 1; i += 2) {
                Field field = clazz.getDeclaredField(arr[i]);
                field.setAccessible(true);
                if (field.getType().equals(Integer.class)) {
                    field.set(gb28181, Integer.valueOf(arr[i + 1]));
                    continue;
                }
                field.set(gb28181, arr[i + 1]);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return gb28181;
    }
    /**
     * Send a message to the pilot via mqtt to start the live streaming.
     * @param topic
     * @param liveParam
     * @return
     */
    private Optional<ServiceReplyReceiver> publishLiveStart(String topic, LiveTypeDTO liveParam) {
        CommonTopicResponse<LiveTypeDTO> response = new CommonTopicResponse<>();
        response.setTid(UUID.randomUUID().toString());
        response.setBid(UUID.randomUUID().toString());
        response.setData(liveParam);
        response.setMethod(LiveMethodEnum.LIVE_START_PUSH.getMethod());
        return this.publishLive(topic, response);
    }
    /**
     * Send a message to the pilot via mqtt to set quality.
     * @param respTopic
     * @param liveParam
     * @return
     */
    private Optional<ServiceReplyReceiver> publishLiveSetQuality(String respTopic, LiveTypeDTO liveParam) {
        Map<String, Object> data = new ConcurrentHashMap<>(Map.of(
                "video_id", liveParam.getVideoId(),
                "video_quality", liveParam.getVideoQuality()));
        CommonTopicResponse<Map<String, Object>> response = new CommonTopicResponse<>();
        response.setTid(UUID.randomUUID().toString());
        response.setBid(UUID.randomUUID().toString());
        response.setMethod(LiveMethodEnum.LIVE_SET_QUALITY.getMethod());
        response.setData(data);
        return this.publishLive(respTopic, response);
    }
    /**
     * Send a message to the pilot via mqtt to stop the live streaming.
     * @param topic
     * @param videoId
     * @return
     */
    private Optional<ServiceReplyReceiver> publishLiveStop(String topic, String videoId) {
        Map<String, String> data = new ConcurrentHashMap<>(Map.of("video_id", videoId));
        CommonTopicResponse<Map<String, String>> response = new CommonTopicResponse<>();
        response.setTid(UUID.randomUUID().toString());
        response.setBid(UUID.randomUUID().toString());
        response.setData(data);
        response.setMethod(LiveMethodEnum.LIVE_STOP_PUSH.getMethod());
        return this.publishLive(topic, response);
    }
    /**
     * Send live streaming start message and receive a response at the same time
     * @param topic
     * @param response  notification of whether the start is successful.
     * @return
     */
    private Optional<ServiceReplyReceiver> publishLive(String topic, CommonTopicResponse response) {
        AtomicInteger time = new AtomicInteger(0);
        // Retry three times
        while (time.getAndIncrement() < 3) {
            messageSender.publish(topic, response);
            Chan<CommonTopicReceiver<ServiceReplyReceiver>> chan = Chan.getInstance();
            // If the message is not received in 0.5 seconds then resend it again.
            CommonTopicReceiver<ServiceReplyReceiver> receiver = chan.get(response.getMethod());
            if (receiver == null) {
                continue;
            }
            // Need to match tid and bid.
            if (receiver.getTid().equals(response.getTid()) &&
                    receiver.getBid().equals(response.getBid())) {
                return Optional.ofNullable(receiver.getData());
            }
        }
        return Optional.empty();
    }
}
src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java
New file
@@ -0,0 +1,55 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.TopologyDTO;
import com.dji.sample.manage.model.dto.TopologyDeviceDTO;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.manage.service.ITopologyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/8
 */
@Service
public class TopologyServiceImpl implements ITopologyService {
    @Autowired
    private IDeviceService deviceService;
    @Override
    public List<TopologyDTO> getDeviceTopology(String workspaceId) {
        // Query the information of all gateway devices in the workspace.
        List<DeviceDTO> gatewayList = deviceService.getDevicesByParams(
                DeviceQueryParam.builder()
                        .workspaceId(workspaceId)
                        .domain(DeviceDomainEnum.GATEWAY.getVal())
                        .build());
        List<TopologyDTO> topologyList = new ArrayList<>();
        gatewayList.forEach(device -> {
            List<TopologyDeviceDTO> parents = new ArrayList<>();
            TopologyDeviceDTO gateway = deviceService.deviceConvertToTopologyDTO(device);
            parents.add(gateway);
            // Query the topology data of the drone based on the drone sn.
            Optional<TopologyDeviceDTO> deviceTopo = deviceService.getDeviceTopoForPilot(device.getChildDeviceSn());
            List<TopologyDeviceDTO> deviceTopoList = new ArrayList<>();
            deviceTopo.ifPresent(deviceTopoList::add);
            topologyList.add(TopologyDTO.builder().parents(parents).hosts(deviceTopoList).build());
        });
        return topologyList;
    }
}
src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java
New file
@@ -0,0 +1,141 @@
package com.dji.sample.manage.service.impl;
import com.auth0.jwt.JWT;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.common.util.JwtUtil;
import com.dji.sample.component.mqtt.config.MqttConfiguration;
import com.dji.sample.manage.dao.IUserMapper;
import com.dji.sample.manage.model.dto.UserDTO;
import com.dji.sample.manage.model.dto.WorkspaceDTO;
import com.dji.sample.manage.model.entity.UserEntity;
import com.dji.sample.manage.service.IUserService;
import com.dji.sample.manage.service.IWorkspaceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.Optional;
@Service
@Transactional
public class UserServiceImpl implements IUserService {
    @Autowired
    private IUserMapper mapper;
    @Autowired
    private MqttConfiguration mqttConfiguration;
    @Autowired
    private IWorkspaceService workspaceService;
    @Override
    public ResponseResult getUserByUsername(String username, String workspaceId) {
        UserEntity userEntity = this.getUserByUsername(username);
        if (userEntity == null) {
            return ResponseResult.builder()
                    .code(HttpStatus.UNAUTHORIZED.value())
                    .message("invalid username")
                    .build();
        }
        UserDTO user = this.entityConvertToDTO(userEntity);
        user.setWorkspaceId(workspaceId);
        return ResponseResult.success(user);
    }
    @Override
    public ResponseResult userLogin(String username, String password) {
        // check user
        UserEntity userEntity = this.getUserByUsername(username);
        if (userEntity == null) {
            return ResponseResult.builder()
                    .code(HttpStatus.UNAUTHORIZED.value())
                    .message("invalid username")
                    .build();
        }
        if (!password.equals(userEntity.getPassword())) {
            return ResponseResult.builder()
                    .code(HttpStatus.UNAUTHORIZED.value())
                    .message("invalid password")
                    .build();
        }
        Optional<WorkspaceDTO> workspaceOpt = workspaceService.getWorkspaceById(userEntity.getWorkspaceId());
        if (workspaceOpt.isEmpty()) {
            return ResponseResult.builder()
                    .code(HttpStatus.UNAUTHORIZED.value())
                    .message("invalid workspace id")
                    .build();
        }
        CustomClaim customClaim = new CustomClaim(userEntity.getUserId(),
                userEntity.getUsername(), userEntity.getUserType(),
                workspaceOpt.get().getWorkspaceId());
        // create token
        String token = JwtUtil.createToken(customClaim.convertToMap());
        UserDTO userDTO = entityConvertToDTO(userEntity);
        userDTO.setMqttAddr(new StringBuilder()
                .append(mqttConfiguration.getProtocol().trim())
                .append("://")
                .append(mqttConfiguration.getHost().trim())
                .append(":")
                .append(mqttConfiguration.getPort())
                .toString());
        userDTO.setAccessToken(token);
        userDTO.setWorkspaceId(workspaceOpt.get().getWorkspaceId());
        return ResponseResult.success(userDTO);
    }
    @Override
    public Optional<UserDTO> refreshToken(String token) {
        if (!StringUtils.hasText(token)) {
            return Optional.empty();
        }
        CustomClaim customClaim = new CustomClaim(JWT.decode(token).getClaims());
        String refreshToken = JwtUtil.createToken(customClaim.convertToMap());
        UserDTO user = entityConvertToDTO(this.getUserByUsername(customClaim.getUsername()));
        user.setWorkspaceId(customClaim.getWorkspaceId());
        user.setAccessToken(refreshToken);
        return Optional.of(user);
    }
    /**
     * Query a user by username.
     * @param username
     * @return
     */
    private UserEntity getUserByUsername(String username) {
        return mapper.selectOne(new QueryWrapper<UserEntity>()
                .eq("username", username));
    }
    private UserDTO entityConvertToDTO(UserEntity entity) {
        if (entity == null) {
            return new UserDTO();
        }
        return UserDTO.builder()
                .userId(entity.getUserId())
                .username(entity.getUsername())
                .userType(entity.getUserType())
                .mqttUsername(entity.getMqttUsername())
                .mqttPassword(entity.getMqttPassword())
                .mqttAddr(new StringBuilder()
                        .append(mqttConfiguration.getProtocol().trim())
                        .append("://")
                        .append(mqttConfiguration.getHost().trim())
                        .append(":")
                        .append(mqttConfiguration.getPort())
                        .toString())
                .build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java
New file
@@ -0,0 +1,52 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.manage.dao.IWorkspaceMapper;
import com.dji.sample.manage.model.dto.WorkspaceDTO;
import com.dji.sample.manage.model.entity.WorkspaceEntity;
import com.dji.sample.manage.service.IWorkspaceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
@Transactional
public class WorkspaceServiceImpl implements IWorkspaceService {
    @Autowired
    private IWorkspaceMapper mapper;
    @Override
    public Optional<WorkspaceDTO> getWorkspaceById(int id) {
        return Optional.ofNullable(entityConvertToDto(mapper.selectById(id)));
    }
    @Override
    public Optional<WorkspaceDTO> getWorkspaceByWorkspaceId(String workspaceId) {
        return Optional.ofNullable(entityConvertToDto(
                mapper.selectOne(
                        new LambdaQueryWrapper<WorkspaceEntity>()
                                .eq(WorkspaceEntity::getWorkspaceId, workspaceId))));
    }
    /**
     * Convert database entity objects into workspace data transfer object.
     * @param entity
     * @return
     */
    private WorkspaceDTO entityConvertToDto(WorkspaceEntity entity) {
        WorkspaceDTO.WorkspaceDTOBuilder builder = WorkspaceDTO.builder();
        if (entity == null) {
            return builder.build();
        }
        return builder
                .id(entity.getId())
                .workspaceId(entity.getWorkspaceId())
                .platformName(entity.getPlatformName())
                .workspaceDesc(entity.getWorkspaceDesc())
                .workspaceName(entity.getWorkspaceName())
                .build();
    }
}
src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java
New file
@@ -0,0 +1,184 @@
package com.dji.sample.map.controller;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.model.WebSocketManager;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.map.model.dto.*;
import com.dji.sample.map.service.IWorkspaceElementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
@RestController
@RequestMapping("${url.map.prefix}${url.map.version}/workspaces")
public class WorkspaceElementController {
    @Autowired
    private IWorkspaceElementService elementService;
    @Autowired
    private ISendMessageService sendMessageService;
    /**
     * In the first connection, pilot will send out this http request to obtain the group element list.
     * Also, if pilot receives a group refresh instruction from WebSocket,
     * it needs the same interface to request the group element list.
     * @param workspaceId
     * @param groupId
     * @param isDistributed
     * @return
     */
    @GetMapping("/{workspace_id}/element-groups")
    public ResponseResult<List<GroupDTO>> getAllElements(@PathVariable(name = "workspace_id") String workspaceId,
                               @RequestParam(name = "group_id", required = false) String groupId,
                               @RequestParam(name = "is_distributed", required = false) Boolean isDistributed) {
        List<GroupDTO> groupsList = elementService.getAllGroupsByWorkspaceId(workspaceId, groupId, isDistributed);
        return ResponseResult.success(groupsList);
    }
    /**
     * When user draws a point, line or polygon on the PILOT/Web side.
     * Save the element information to the database.
     * @param request
     * @param workspaceId
     * @param groupId
     * @param elementCreate
     * @return
     */
    @PostMapping("/{workspace_id}/element-groups/{group_id}/elements")
    public ResponseResult saveElement(HttpServletRequest request,
                            @PathVariable(name = "workspace_id") String workspaceId,
                            @PathVariable(name = "group_id") String groupId,
                            @RequestBody ElementCreateDTO elementCreate) {
        CustomClaim claims = (CustomClaim) request.getAttribute(TOKEN_CLAIM);
        // Set the creator of the element
        elementCreate.getResource().setUsername(claims.getUsername());
        ResponseResult response = elementService.saveElement(groupId, elementCreate);
        if (response.getCode() != ResponseResult.CODE_SUCCESS) {
            return response;
        }
        // Notify all WebSocket connections in this workspace to be updated when an element is created.
        elementService.getElementByElementId(elementCreate.getId())
                .ifPresent(groupElement ->
                        sendMessageService.sendBatch(
                                WebSocketManager.getValueWithWorkspace(workspaceId),
                                CustomWebSocketMessage.<GroupElementDTO>builder()
                                        .timestamp(System.currentTimeMillis())
                                        .bizCode(BizCodeEnum.MAP_ELEMENT_CREATE.getCode())
                                        .data(groupElement)
                                        .build()));
        return ResponseResult.success(new ConcurrentHashMap<>(Map.of("id", elementCreate.getId())));
    }
    /**
     * When user edits a point, line or polygon on the PILOT/Web side.
     * Update the element information to the database.
     * @param request
     * @param workspaceId
     * @param elementId
     * @param elementUpdate
     * @return
     */
    @PutMapping("/{workspace_id}/elements/{element_id}")
    public ResponseResult updateElement(HttpServletRequest request,
                              @PathVariable(name = "workspace_id") String workspaceId,
                              @PathVariable(name = "element_id") String elementId,
                              @RequestBody ElementUpdateDTO elementUpdate) {
        CustomClaim claims = (CustomClaim) request.getAttribute(TOKEN_CLAIM);
        ResponseResult response = elementService.updateElement(elementId, elementUpdate, claims.getUsername());
        if (response.getCode() != ResponseResult.CODE_SUCCESS) {
            return response;
        }
        // Notify all WebSocket connections in this workspace to update when there is an element update.
        elementService.getElementByElementId(elementId)
                .ifPresent(groupElement ->
                        sendMessageService.sendBatch(
                                WebSocketManager.getValueWithWorkspace(workspaceId),
                                CustomWebSocketMessage.<GroupElementDTO>builder()
                                        .timestamp(System.currentTimeMillis())
                                        .bizCode(BizCodeEnum.MAP_ELEMENT_UPDATE.getCode())
                                        .data(groupElement)
                                        .build()));
        return response;
    }
    /**
     * When user delete a point, line or polygon on the PILOT/Web side,
     * Delete the element information in the database.
     * @param workspaceId
     * @param elementId
     * @return
     */
    @DeleteMapping("/{workspace_id}/elements/{element_id}")
    public ResponseResult deleteElement(@PathVariable(name = "workspace_id") String workspaceId,
                              @PathVariable(name = "element_id") String elementId) {
        Optional<GroupElementDTO> elementOpt = elementService.getElementByElementId(elementId);
        ResponseResult response = elementService.deleteElement(elementId);
        // Notify all WebSocket connections in this workspace to update when an element is deleted.
        if (ResponseResult.CODE_SUCCESS == response.getCode()) {
            elementOpt.ifPresent(element ->
                    sendMessageService.sendBatch(
                    WebSocketManager.getValueWithWorkspace(workspaceId),
                            CustomWebSocketMessage.<WebSocketElementDelDTO>builder()
                                    .timestamp(System.currentTimeMillis())
                                    .bizCode(BizCodeEnum.MAP_ELEMENT_DELETE.getCode())
                                    .data(WebSocketElementDelDTO.builder()
                                            .elementId(elementId)
                                            .groupId(element.getGroupId())
                                            .build())
                                    .build()));
        }
        return response;
    }
    /**
     * Delete all the element information in this group based on the group id.
     * @param workspaceId
     * @param groupId
     * @return
     */
    @DeleteMapping("/{workspace_id}/element-groups/{group_id}/elements")
    public ResponseResult deleteAllElementByGroupId(@PathVariable(name = "workspace_id") String workspaceId,
                                          @PathVariable(name = "group_id") String groupId) {
        ResponseResult response = elementService.deleteAllElementByGroupId(groupId);
        // Notify all WebSocket connections in this workspace to update when elements are deleted.
        if (ResponseResult.CODE_SUCCESS == response.getCode()) {
            sendMessageService.sendBatch(
                    WebSocketManager.getValueWithWorkspace(workspaceId),
                    CustomWebSocketMessage.builder()
                            .timestamp(System.currentTimeMillis())
                            .bizCode(BizCodeEnum.MAP_GROUP_REFRESH.getCode())
                            // Group ids that need to re-request data
                            .data(new ConcurrentHashMap<String, String[]>(Map.of("ids", new String[]{groupId})))
                            .build());
        }
        return response;
    }
}
src/main/java/com/dji/sample/map/dao/IElementCoordinateMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.map.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.map.model.entity.ElementCoordinateEntity;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
public interface IElementCoordinateMapper extends BaseMapper<ElementCoordinateEntity> {
}
src/main/java/com/dji/sample/map/dao/IGroupElementMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.map.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.map.model.entity.GroupElementEntity;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
public interface IGroupElementMapper extends BaseMapper<GroupElementEntity> {
}
src/main/java/com/dji/sample/map/dao/IGroupMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.map.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.map.model.entity.GroupEntity;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
public interface IGroupMapper extends BaseMapper<GroupEntity> {
}
src/main/java/com/dji/sample/map/model/dto/ContentPropertyDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/dto/ElementCoordinateDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/dto/ElementCreateDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/dto/ElementLineStringDTO.java
New file
@@ -0,0 +1,47 @@
package com.dji.sample.map.model.dto;
import com.dji.sample.map.model.enums.ElementTypeEnum;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/30
 */
@Data
public class ElementLineStringDTO extends ElementType {
    private Double[][] coordinates;
    public ElementLineStringDTO() {
        super(ElementTypeEnum.LINE_STRING.getDesc());
    }
    @Override
    public List<ElementCoordinateDTO> convertToList() {
        List<ElementCoordinateDTO> coordinateList = new ArrayList<>();
        for (Double[] coordinate : this.coordinates) {
            coordinateList.add(ElementCoordinateDTO.builder()
                    .longitude(coordinate[0])
                    .latitude(coordinate[1])
                    .build());
        }
        return coordinateList;
    }
    @Override
    public void adapterCoordinateType(List<ElementCoordinateDTO> coordinateList) {
        if (CollectionUtils.isEmpty(coordinateList)) {
            return;
        }
        this.coordinates = new Double[coordinateList.size()][2];
        for (int i = 0; i < this.coordinates.length; i++) {
            this.coordinates[i][0] = coordinateList.get(i).getLongitude();
            this.coordinates[i][1] = coordinateList.get(i).getLatitude();
        }
    }
}
src/main/java/com/dji/sample/map/model/dto/ElementPointDTO.java
New file
@@ -0,0 +1,46 @@
package com.dji.sample.map.model.dto;
import com.dji.sample.map.model.enums.ElementTypeEnum;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/30
 */
@Data
public class ElementPointDTO extends ElementType {
    private Double[] coordinates;
    public ElementPointDTO() {
        super(ElementTypeEnum.POINT.getDesc());
    }
    @Override
    public List<ElementCoordinateDTO> convertToList() {
        List<ElementCoordinateDTO> coordinateList = new ArrayList<>();
        coordinateList.add(ElementCoordinateDTO.builder()
                .longitude(this.coordinates[0])
                .latitude(this.coordinates[1])
                .altitude(this.coordinates.length == 3 ? this.coordinates[2] : null)
                .build());
        return coordinateList;
    }
    @Override
    public void adapterCoordinateType(List<ElementCoordinateDTO> coordinateList) {
        if (CollectionUtils.isEmpty(coordinateList)) {
            return;
        }
        this.coordinates = new Double[]{
                coordinateList.get(0).getLongitude(),
                coordinateList.get(0).getLatitude(),
                coordinateList.get(0).getAltitude()
        };
    }
}
src/main/java/com/dji/sample/map/model/dto/ElementPolygonDTO.java
New file
@@ -0,0 +1,48 @@
package com.dji.sample.map.model.dto;
import com.dji.sample.map.model.enums.ElementTypeEnum;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/30
 */
@Data
public class ElementPolygonDTO extends ElementType {
    private Double[][][] coordinates;
    public ElementPolygonDTO() {
        super(ElementTypeEnum.POLYGON.getDesc());
    }
    @Override
    public List<ElementCoordinateDTO> convertToList() {
        List<ElementCoordinateDTO> coordinateList = new ArrayList<>();
        for (Double[] coordinate : this.coordinates[0]) {
            coordinateList.add(ElementCoordinateDTO.builder()
                    .longitude(coordinate[0])
                    .latitude(coordinate[1])
                    .build());
        }
        return coordinateList;
    }
    @Override
    public void adapterCoordinateType(List<ElementCoordinateDTO> coordinateList) {
        if (CollectionUtils.isEmpty(coordinateList)) {
            return;
        }
        this.coordinates = new Double[1][coordinateList.size()][2];
        for (int i = 0; i < this.coordinates[0].length; i++) {
            this.coordinates[0][i][0] = coordinateList.get(i).getLongitude();
            this.coordinates[0][i][1] = coordinateList.get(i).getLatitude();
        }
    }
}
src/main/java/com/dji/sample/map/model/dto/ElementResourceDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/dto/ElementType.java
New file
@@ -0,0 +1,43 @@
package com.dji.sample.map.model.dto;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/30
 */
@Data
@NoArgsConstructor
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type",
        include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = ElementType.class)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ElementPointDTO.class, name = "Point"),
        @JsonSubTypes.Type(value = ElementLineStringDTO.class, name = "LineString"),
        @JsonSubTypes.Type(value = ElementPolygonDTO.class, name = "Polygon")
})
public abstract class ElementType {
    private String type;
    ElementType(String type) {
        this.type = type;
    }
    /**
     * Convert coordinate data into objects and then add them to the collection.
     * @return
     */
    public abstract List<ElementCoordinateDTO> convertToList();
    /**
     * Converts coordinates in a collection of objects to a specific type.
     * @param coordinateList
     */
    public abstract void adapterCoordinateType(List<ElementCoordinateDTO> coordinateList);
}
src/main/java/com/dji/sample/map/model/dto/ElementUpdateDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/dto/GroupDTO.java
New file
@@ -0,0 +1,38 @@
package com.dji.sample.map.model.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class GroupDTO {
    private String id;
    private String name;
    private Integer type;
    private List<GroupElementDTO> elements;
    @JsonProperty(value = "is_distributed")
    private Boolean isDistributed;
    @JsonProperty(value = "is_lock")
    private Boolean isLock;
    @JsonProperty(value = "create_time")
    private Long createTime;
}
src/main/java/com/dji/sample/map/model/dto/GroupElementDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/dto/ResourceContentDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/dto/WebSocketElementDelDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/entity/ElementCoordinateEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/entity/GroupElementEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/entity/GroupEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/map/model/enums/ElementTypeEnum.java
New file
@@ -0,0 +1,69 @@
package com.dji.sample.map.model.enums;
import com.dji.sample.map.model.dto.ElementLineStringDTO;
import com.dji.sample.map.model.dto.ElementPointDTO;
import com.dji.sample.map.model.dto.ElementPolygonDTO;
import com.dji.sample.map.model.dto.ElementType;
import java.util.Optional;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/30
 */
public enum ElementTypeEnum {
    POINT(0, "Point"),
    LINE_STRING(1, "LineString"),
    POLYGON(2, "Polygon"),
    UNKNOWN(-1, "Unknown");
    private int val;
    private String desc;
    ElementTypeEnum(int val, String desc) {
        this.val = val;
        this.desc = desc;
    }
    public static Optional<ElementType> findType(int val) {
        if (POINT.val == val) {
            return Optional.of(new ElementPointDTO());
        }
        if (LINE_STRING.val == val) {
            return Optional.of(new ElementLineStringDTO());
        }
        if (POLYGON.val == val) {
            return Optional.of(new ElementPolygonDTO());
        }
        return Optional.empty();
    }
    public String getDesc() {
        return desc;
    }
    public static int findVal(String desc) {
        if (POINT.desc.equals(desc)) {
            return POINT.val;
        }
        if (LINE_STRING.desc.equals(desc)) {
            return LINE_STRING.val;
        }
        if (POLYGON.desc.equals(desc)) {
            return POLYGON.val;
        }
        return UNKNOWN.val;
    }
}
src/main/java/com/dji/sample/map/service/IElementCoordinateService.java
New file
@@ -0,0 +1,35 @@
package com.dji.sample.map.service;
import com.dji.sample.map.model.dto.ElementCoordinateDTO;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
public interface IElementCoordinateService {
    /**
     * Query all the coordinates of the element according to its id.
     * @param elementId
     * @return
     */
    List<ElementCoordinateDTO> getCoordinateByElementId(String elementId);
    /**
     * Save all the coordinate data of this element.
     * @param coordinate
     * @param elementId
     * @return
     */
    Boolean saveCoordinate(List<ElementCoordinateDTO> coordinate, String elementId);
    /**
     * Delete all the coordinates of the element according to its id.
     * @param elementId
     * @return
     */
    Boolean deleteCoordinateByElementId(String elementId);
}
src/main/java/com/dji/sample/map/service/IGroupElementService.java
New file
@@ -0,0 +1,54 @@
package com.dji.sample.map.service;
import com.dji.sample.map.model.dto.ElementCreateDTO;
import com.dji.sample.map.model.dto.ElementUpdateDTO;
import com.dji.sample.map.model.dto.GroupElementDTO;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
public interface IGroupElementService {
    /**
     * Query all the elements in this group based on the group id.
     * @param groupId
     * @return
     */
    List<GroupElementDTO> getElementsByGroupId(String groupId);
    /**
     * Save all the elements.
     * @param groupId
     * @param elementCreate
     * @return
     */
    Boolean saveElement(String groupId, ElementCreateDTO elementCreate);
    /**
     * Query the element information based on the element id and update element.
     * @param elementId
     * @param elementUpdate
     * @param username
     * @return
     */
    Boolean updateElement(String elementId, ElementUpdateDTO elementUpdate, String username);
    /**
     * Delete the element based on the element id.
     * @param elementId
     * @return
     */
    Boolean deleteElement(String elementId);
    /**
     * Query an element based on the element id, including the coordinate information in the elements.
     * @param elementId
     * @return
     */
    Optional<GroupElementDTO> getElementByElementId(String elementId);
}
src/main/java/com/dji/sample/map/service/IGroupService.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.map.service;
import com.dji.sample.map.model.dto.GroupDTO;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
public interface IGroupService {
    /**
     * Query all groups in the workspace based on the workspace's id.
     * If the group id does not exist, do not add this filter condition.
     * @param workspaceId
     * @param groupId
     * @param isDistributed Used to define if the group needs to be distributed. Default is true.
     * @return
     */
    List<GroupDTO> getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed);
}
src/main/java/com/dji/sample/map/service/IWorkspaceElementService.java
New file
@@ -0,0 +1,71 @@
package com.dji.sample.map.service;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.map.model.dto.ElementCreateDTO;
import com.dji.sample.map.model.dto.ElementUpdateDTO;
import com.dji.sample.map.model.dto.GroupDTO;
import com.dji.sample.map.model.dto.GroupElementDTO;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/30
 */
public interface IWorkspaceElementService {
    /**
     * Query all groups in the workspace based on the workspace's id,
     * including the information of the elements in the group, and the coordinate information in the elements.
     * @param workspaceId
     * @param groupId
     * @param isDistributed
     * @return
     */
    List<GroupDTO> getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed);
    /**
     * Save all the elements, including the information of the elements in the group,
     * and the coordinate information in the elements.
     * @param groupId
     * @param elementCreate
     * @return
     */
    ResponseResult saveElement(String groupId, ElementCreateDTO elementCreate);
    /**
     * Update the element information based on the element id,
     * including the information of the elements in the group, and the coordinate information in the elements.
     * @param elementId
     * @param elementUpdate
     * @param username
     * @return
     */
    ResponseResult updateElement(String elementId, ElementUpdateDTO elementUpdate, String username);
    /**
     * Delete the element information based on the element id,
     * including the information of the elements in the group, and the coordinate information in the elements.
     * @param elementId
     * @return
     */
    ResponseResult deleteElement(String elementId);
    /**
     * Query an element based on the element id,
     * including the information of the elements in the group, and the coordinate information in the elements.
     * @param elementId
     * @return
     */
    Optional<GroupElementDTO> getElementByElementId(String elementId);
    /**
     * Delete all the elements information based on the group id,
     * including the information of the elements in the group, and the coordinate information in the elements.
     * @param groupId
     * @return
     */
    ResponseResult deleteAllElementByGroupId(String groupId);
}
src/main/java/com/dji/sample/map/service/impl/ElementCoordinateServiceImpl.java
New file
@@ -0,0 +1,94 @@
package com.dji.sample.map.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.dji.sample.map.dao.IElementCoordinateMapper;
import com.dji.sample.map.model.dto.ElementCoordinateDTO;
import com.dji.sample.map.model.entity.ElementCoordinateEntity;
import com.dji.sample.map.service.IElementCoordinateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
@Service
@Transactional
public class ElementCoordinateServiceImpl implements IElementCoordinateService {
    @Autowired
    private IElementCoordinateMapper mapper;
    @Override
    public List<ElementCoordinateDTO> getCoordinateByElementId(String elementId) {
        return mapper.selectList(
                new LambdaQueryWrapper<ElementCoordinateEntity>()
                        .eq(ElementCoordinateEntity::getElementId, elementId))
                .stream()
                .map(this::entityConvertToDto)
                .collect(Collectors.toList());
    }
    @Override
    public Boolean saveCoordinate(List<ElementCoordinateDTO> coordinateList, String elementId) {
        for (ElementCoordinateDTO coordinate : coordinateList) {
            ElementCoordinateEntity entity = this.dtoConvertToEntity(coordinate);
            entity.setElementId(elementId);
            int insert = mapper.insert(entity);
            if (insert <= 0) {
                return false;
            }
        }
        return true;
    }
    @Override
    public Boolean deleteCoordinateByElementId(String elementId) {
        return mapper.delete(new LambdaUpdateWrapper<ElementCoordinateEntity>()
                .eq(ElementCoordinateEntity::getElementId, elementId)) > 0;
    }
    /**
     * Convert database entity objects into coordinate data transfer object.
     * @param entity
     * @return
     */
    private ElementCoordinateDTO entityConvertToDto(ElementCoordinateEntity entity) {
        ElementCoordinateDTO.ElementCoordinateDTOBuilder builder = ElementCoordinateDTO.builder();
        if (entity == null) {
            return builder.build();
        }
        return builder
                .longitude(entity.getLongitude())
                .latitude(entity.getLatitude())
                .altitude(entity.getAltitude())
                .build();
    }
    /**
     * Convert the received coordinate object into a database entity object.
     * @param coordinate
     * @return
     */
    private ElementCoordinateEntity dtoConvertToEntity(ElementCoordinateDTO coordinate) {
        ElementCoordinateEntity.ElementCoordinateEntityBuilder builder = ElementCoordinateEntity.builder();
        if (coordinate == null) {
            return builder.build();
        }
        return builder
                .altitude(coordinate.getAltitude())
                .latitude(coordinate.getLatitude())
                .longitude(coordinate.getLongitude())
                .build();
    }
}
src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java
New file
@@ -0,0 +1,199 @@
package com.dji.sample.map.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.map.dao.IGroupElementMapper;
import com.dji.sample.map.model.dto.*;
import com.dji.sample.map.model.entity.GroupElementEntity;
import com.dji.sample.map.model.enums.ElementTypeEnum;
import com.dji.sample.map.service.IElementCoordinateService;
import com.dji.sample.map.service.IGroupElementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
@Service
@Transactional
public class GroupElementServiceImpl implements IGroupElementService {
    @Autowired
    private IGroupElementMapper mapper;
    @Autowired
    private IElementCoordinateService elementCoordinateService;
    @Override
    public List<GroupElementDTO> getElementsByGroupId(String groupId) {
        List<GroupElementEntity> elementList = mapper.selectList(
                new LambdaQueryWrapper<GroupElementEntity>()
                        .eq(GroupElementEntity::getGroupId, groupId));
        List<GroupElementDTO> groupElementList = new ArrayList<>();
        for (GroupElementEntity elementEntity : elementList) {
            GroupElementDTO groupElement = this.entityConvertToDto(elementEntity);
            groupElementList.add(groupElement);
            this.addCoordinateToElement(groupElement, elementEntity);
        }
        return groupElementList;
    }
    @Override
    public Boolean saveElement(String groupId, ElementCreateDTO elementCreate) {
        Optional<GroupElementEntity> groupElementOpt = this.getEntityByElementId(elementCreate.getId());
        if (groupElementOpt.isPresent()) {
            return false;
        }
        GroupElementEntity groupElement = this.createDtoConvertToEntity(elementCreate);
        groupElement.setGroupId(groupId);
        return mapper.insert(groupElement) > 0;
    }
    @Override
    public Boolean updateElement(String elementId, ElementUpdateDTO elementUpdate, String username) {
        Optional<GroupElementEntity> groupElementOpt = this.getEntityByElementId(elementId);
        if (groupElementOpt.isEmpty()) {
            return false;
        }
        GroupElementEntity groupElement = groupElementOpt.get();
        groupElement.setUsername(username);
        this.updateEntityWithDto(elementUpdate, groupElement);
        return mapper.updateById(groupElement) > 0;
    }
    @Override
    public Boolean deleteElement(String elementId) {
        Optional<GroupElementEntity> groupElementOpt = this.getEntityByElementId(elementId);
        if (groupElementOpt.isEmpty()) {
            return true;
        }
        GroupElementEntity groupElement = groupElementOpt.get();
        return mapper.deleteById(groupElement.getId()) > 0;
    }
    @Override
    public Optional<GroupElementDTO> getElementByElementId(String elementId) {
        Optional<GroupElementEntity> elementEntityOpt = this.getEntityByElementId(elementId);
        if (elementEntityOpt.isEmpty()) {
            return Optional.empty();
        }
        GroupElementEntity elementEntity = elementEntityOpt.get();
        GroupElementDTO groupElement = this.entityConvertToDto(elementEntity);
        this.addCoordinateToElement(groupElement, elementEntity);
        return Optional.ofNullable(groupElement);
    }
    /**
     * Adds the received coordinate data to the element object.
     * @param element
     * @param elementEntity
     */
    private void addCoordinateToElement(GroupElementDTO element, GroupElementEntity elementEntity) {
        Optional<ElementType> coordinateOpt = ElementTypeEnum.findType(elementEntity.getElementType());
        if (coordinateOpt.isEmpty()) {
            return;
        }
        ElementType elementType = coordinateOpt.get();
        element.getResource().setContent(
                ResourceContentDTO.builder()
                        .properties(ContentPropertyDTO.builder()
                                .clampToGround(elementEntity.getClampToGround())
                                .color(elementEntity.getColor())
                                .build())
                        .geometry(elementType)
                        .build());
        elementType.adapterCoordinateType(
                elementCoordinateService.getCoordinateByElementId(elementEntity.getElementId()));
    }
    /**
     * Query an element based on the element id。
     * @param elementId
     * @return
     */
    private Optional<GroupElementEntity> getEntityByElementId(String elementId) {
        return Optional.ofNullable(mapper.selectOne(
                new LambdaQueryWrapper<GroupElementEntity>()
                        .eq(GroupElementEntity::getElementId, elementId)));
    }
    /**
     * Convert database entity objects into element data transfer object.
     * @param entity
     * @return
     */
    private GroupElementDTO entityConvertToDto(GroupElementEntity entity) {
        GroupElementDTO.GroupElementDTOBuilder builder = GroupElementDTO.builder();
        if (entity == null) {
            return builder.build();
        }
        return builder
                .display(entity.getDisplay())
                .groupId(entity.getGroupId())
                .elementId(entity.getElementId())
                .name(entity.getElementName())
                .createTime(entity.getCreateTime())
                .updateTime(entity.getUpdateTime())
                .resource(ElementResourceDTO.builder()
                        .type(entity.getElementType())
                        .username(entity.getUsername())
                        .build())
                .build();
    }
    /**
     * Convert the received element object into a database entity object.
     * @param elementCreate
     * @return
     */
    private GroupElementEntity createDtoConvertToEntity(ElementCreateDTO elementCreate) {
        ContentPropertyDTO properties = elementCreate.getResource().getContent().getProperties();
        return GroupElementEntity.builder()
                .elementId(elementCreate.getId())
                .elementName(elementCreate.getName())
                .username(elementCreate.getResource().getUsername())
                .elementType(ElementTypeEnum.findVal(elementCreate.getResource().getContent().getGeometry().getType()))
                .clampToGround(properties.getClampToGround() != null && properties.getClampToGround())
                .color(properties.getColor())
                .build();
    }
    /**
     * Add the content that needs to be updated to the entity object to be updated.
     * @param elementUpdate
     * @param groupElement
     */
    private void updateEntityWithDto(ElementUpdateDTO elementUpdate, GroupElementEntity groupElement) {
        if (elementUpdate == null || groupElement == null) {
            return;
        }
        groupElement.setElementName(elementUpdate.getName());
        groupElement.setElementType(ElementTypeEnum.findVal(elementUpdate.getContent().getGeometry().getType()));
        groupElement.setColor(elementUpdate.getContent().getProperties().getColor());
        boolean clampToGround = elementUpdate.getContent().getProperties().getClampToGround();
        groupElement.setClampToGround(clampToGround);
    }
}
src/main/java/com/dji/sample/map/service/impl/GroupServiceImpl.java
New file
@@ -0,0 +1,66 @@
package com.dji.sample.map.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.map.dao.IGroupMapper;
import com.dji.sample.map.model.dto.GroupDTO;
import com.dji.sample.map.model.entity.GroupEntity;
import com.dji.sample.map.service.IGroupElementService;
import com.dji.sample.map.service.IGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/29
 */
@Service
@Transactional
public class GroupServiceImpl implements IGroupService {
    @Autowired
    private IGroupMapper mapper;
    @Autowired
    private IGroupElementService groupElementService;
    @Override
    public List<GroupDTO> getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed) {
        return mapper.selectList(
                new LambdaQueryWrapper<GroupEntity>()
                        .eq(GroupEntity::getWorkspaceId, workspaceId)
                        .eq(StringUtils.hasText(groupId), GroupEntity::getGroupId, groupId)
                        .eq(isDistributed != null, GroupEntity::getIsDistributed, isDistributed))
                .stream()
                .map(this::entityConvertToDto)
                .collect(Collectors.toList());
    }
    /**
     * Convert database entity objects into group data transfer object.
     * @param entity
     * @return
     */
    private GroupDTO entityConvertToDto(GroupEntity entity) {
        GroupDTO.GroupDTOBuilder builder = GroupDTO.builder();
        if (entity == null) {
            return builder.build();
        }
        return builder
                .id(entity.getGroupId())
                .name(entity.getGroupName())
                .type(entity.getGroupType())
                .isLock(entity.getIsLock())
                .isDistributed(entity.getIsDistributed())
                .createTime(entity.getCreateTime())
                .build();
    }
}
src/main/java/com/dji/sample/map/service/impl/WorkspaceElementServiceImpl.java
New file
@@ -0,0 +1,109 @@
package com.dji.sample.map.service.impl;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.map.model.dto.ElementCreateDTO;
import com.dji.sample.map.model.dto.ElementUpdateDTO;
import com.dji.sample.map.model.dto.GroupDTO;
import com.dji.sample.map.model.dto.GroupElementDTO;
import com.dji.sample.map.service.IElementCoordinateService;
import com.dji.sample.map.service.IGroupElementService;
import com.dji.sample.map.service.IGroupService;
import com.dji.sample.map.service.IWorkspaceElementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 0.2
 * @date 2021/11/30
 */
@Transactional
@Service
public class WorkspaceElementServiceImpl implements IWorkspaceElementService {
    @Autowired
    private IGroupService groupService;
    @Autowired
    private IGroupElementService groupElementService;
    @Autowired
    private IElementCoordinateService elementCoordinateService;
    @Override
    public List<GroupDTO> getAllGroupsByWorkspaceId(String workspaceId, String groupId, Boolean isDistributed) {
        List<GroupDTO> groupList = groupService.getAllGroupsByWorkspaceId(workspaceId, groupId, isDistributed);
        groupList.forEach(group -> group.setElements(
                groupElementService.getElementsByGroupId(group.getId())
        ));
        return groupList;
    }
    @Override
    public ResponseResult saveElement(String groupId, ElementCreateDTO elementCreate) {
        boolean saveElement = groupElementService.saveElement(groupId, elementCreate);
        if (!saveElement) {
            return ResponseResult.error("Failed to save the element.");
        }
        // save coordinate
        boolean saveCoordinate = elementCoordinateService.saveCoordinate(
                        elementCreate.getResource().getContent().getGeometry().convertToList(), elementCreate.getId());
        return saveCoordinate ?
                ResponseResult.success() : ResponseResult.error("Failed to save the coordinate.");
    }
    @Override
    public ResponseResult updateElement(String elementId, ElementUpdateDTO elementUpdate, String username) {
        boolean updElement = groupElementService.updateElement(elementId, elementUpdate, username);
        if (!updElement) {
            return ResponseResult.error("Failed to update the element.");
        }
        // delete all coordinates according to element id.
        boolean delCoordinate = elementCoordinateService.deleteCoordinateByElementId(elementId);
        // save coordinate
        boolean saveCoordinate = elementCoordinateService.saveCoordinate(
                elementUpdate.getContent().getGeometry().convertToList(), elementId);
        return delCoordinate && saveCoordinate ?
                ResponseResult.success() : ResponseResult.error("Failed to update the coordinate.");
    }
    @Override
    public ResponseResult deleteElement(String elementId) {
        boolean delElement = groupElementService.deleteElement(elementId);
        if (!delElement) {
            return ResponseResult.error("Failed to delete the element.");
        }
        // delete all coordinates according to element id.
        boolean delCoordinate = elementCoordinateService.deleteCoordinateByElementId(elementId);
        return delCoordinate ?
                ResponseResult.success() : ResponseResult.error("Failed to delete the coordinate.");
    }
    @Override
    public Optional<GroupElementDTO> getElementByElementId(String elementId) {
        return groupElementService.getElementByElementId(elementId);
    }
    @Override
    public ResponseResult deleteAllElementByGroupId(String groupId) {
        List<GroupElementDTO> groupElementList = groupElementService.getElementsByGroupId(groupId);
        for (GroupElementDTO groupElement : groupElementList) {
            ResponseResult response = this.deleteElement(groupElement.getElementId());
            if (ResponseResult.CODE_SUCCESS != response.getCode()) {
                return response;
            }
        }
        return ResponseResult.success();
    }
}
src/main/java/com/dji/sample/media/controller/FileController.java
New file
@@ -0,0 +1,36 @@
package com.dji.sample.media.controller;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.media.model.MediaFileDTO;
import com.dji.sample.media.service.IFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/9
 */
@RestController
@RequestMapping("${url.media.prefix}${url.media.version}/files")
public class FileController {
    @Autowired
    private IFileService fileService;
    /**
     * Get information about all the media files in this workspace based on the workspace id.
     * @param workspaceId
     * @return
     */
    @GetMapping("/{workspace_id}/files")
    public ResponseResult<List<MediaFileDTO>> getFilesList(@PathVariable(name = "workspace_id") String workspaceId) {
        List<MediaFileDTO> filesList = fileService.getAllFilesByWorkspaceId(workspaceId);
        return ResponseResult.success(filesList);
    }
}
src/main/java/com/dji/sample/media/controller/MediaController.java
New file
@@ -0,0 +1,73 @@
package com.dji.sample.media.controller;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.media.model.FileUploadDTO;
import com.dji.sample.media.service.IMediaService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/7
 */
@Slf4j
@RestController
@RequestMapping("${url.media.prefix}${url.media.version}/workspaces")
public class MediaController {
    @Autowired
    private IMediaService mediaService;
    /**
     * Check if the file has been uploaded by the fingerprint.
     * @param workspaceId
     * @param file
     * @return
     */
    @PostMapping("/{workspace_id}/fast-upload")
    public ResponseResult fastUpload(@PathVariable(name = "workspace_id") String workspaceId, @RequestBody FileUploadDTO file) {
        boolean isExist = mediaService.fastUpload(workspaceId, file.getFingerprint());
        return isExist ? ResponseResult.success() : ResponseResult.error(file.getFingerprint() + "already exists.");
    }
    /**
     * When the file is uploaded to the storage server by pilot,
     * the basic information of the file is reported through this interface.
     * @param workspaceId
     * @param file
     * @return
     */
    @PostMapping("/{workspace_id}/upload-callback")
    public ResponseResult<String> uploadCallback(@PathVariable(name = "workspace_id") String workspaceId, @RequestBody FileUploadDTO file) {
        mediaService.saveMediaFile(workspaceId, file);
        return ResponseResult.success(file.getObjectKey());
    }
    /**
     * Query the files that already exist in this workspace based on the workspace id and the collection of tiny fingerprints.
     * @param workspaceId
     * @param tinyFingerprints
     * @return
     */
    @GetMapping("/{workspace_id}/files/tiny-fingerprints")
    public ResponseResult<Map<String, List<String>>> uploadCallback(@PathVariable(name = "workspace_id") String workspaceId,
                               @RequestParam(value = "tiny_fingerprint") List<String> tinyFingerprints) {
        List<String> tinyFingerprintList = mediaService.getAllTinyFingerprintsByWorkspaceId(workspaceId);
        List<String> existingList = tinyFingerprints
                .stream()
                .filter(tinyFingerprintList::contains)
                .collect(Collectors.toList());
        return ResponseResult.success(new ConcurrentHashMap<>(Map.of("tiny_fingerprints", existingList)));
    }
}
src/main/java/com/dji/sample/media/dao/IFileMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.media.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.media.model.MediaFileEntity;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/9
 */
public interface IFileMapper extends BaseMapper<MediaFileEntity> {
}
src/main/java/com/dji/sample/media/model/CredentialsDTO.java
New file
@@ -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() {
    }
}
src/main/java/com/dji/sample/media/model/FileExtensionDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/media/model/FileMetadataDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/media/model/FileUploadDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/media/model/MediaFileDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/media/model/MediaFileEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/media/model/PositionDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/media/model/StsCredentialsDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/media/service/IFileService.java
New file
@@ -0,0 +1,37 @@
package com.dji.sample.media.service;
import com.dji.sample.media.model.FileUploadDTO;
import com.dji.sample.media.model.MediaFileDTO;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/9
 */
public interface IFileService {
    /**
     * Query if the file already exists based on the workspace id and the fingerprint of the file.
     * @param workspaceId
     * @param fingerprint
     * @return
     */
    Boolean checkExist(String workspaceId, String fingerprint);
    /**
     * Save the basic information of the file to the database.
     * @param workspaceId
     * @param file
     * @return
     */
    Integer saveFile(String workspaceId, FileUploadDTO file);
    /**
     * Query information about all files in this workspace based on the workspace id.
     * @param workspaceId
     * @return
     */
    List<MediaFileDTO> getAllFilesByWorkspaceId(String workspaceId);
}
src/main/java/com/dji/sample/media/service/IMediaService.java
New file
@@ -0,0 +1,36 @@
package com.dji.sample.media.service;
import com.dji.sample.media.model.FileUploadDTO;
import java.util.List;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/9
 */
public interface IMediaService {
    /**
     * Check if the file has been uploaded by the fingerprint.
     * @param workspaceId
     * @param fingerprint
     * @return
     */
    Boolean fastUpload(String workspaceId, String fingerprint);
    /**
     * Save the basic information of the file to the database.
     * @param workspaceId
     * @param file
     * @return
     */
    Integer saveMediaFile(String workspaceId, FileUploadDTO file);
    /**
     * Query tiny fingerprints about all files in this workspace based on the workspace id.
     * @param workspaceId
     * @return
     */
    List<String> getAllTinyFingerprintsByWorkspaceId(String workspaceId);
}
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java
New file
@@ -0,0 +1,112 @@
package com.dji.sample.media.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import com.dji.sample.media.dao.IFileMapper;
import com.dji.sample.media.model.FileUploadDTO;
import com.dji.sample.media.model.MediaFileDTO;
import com.dji.sample.media.model.MediaFileEntity;
import com.dji.sample.media.service.IFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/9
 */
@Service
@Transactional
public class FileServiceImpl implements IFileService {
    @Autowired
    private IFileMapper mapper;
    @Autowired
    private IDeviceDictionaryService deviceDictionaryService;
    @Override
    public Boolean checkExist(String workspaceId, String fingerprint) {
        MediaFileEntity fileEntity = mapper.selectOne(new LambdaQueryWrapper<MediaFileEntity>()
                .eq(MediaFileEntity::getWorkspaceId, workspaceId)
                .eq(MediaFileEntity::getFingerprint, fingerprint));
        return fileEntity != null;
    }
    @Override
    public Integer saveFile(String workspaceId, FileUploadDTO file) {
        MediaFileEntity fileEntity = this.fileUploadConvertToEntity(file);
        fileEntity.setWorkspaceId(workspaceId);
        return mapper.insert(fileEntity);
    }
    @Override
    public List<MediaFileDTO> getAllFilesByWorkspaceId(String workspaceId) {
        return mapper.selectList(new LambdaQueryWrapper<MediaFileEntity>()
                .eq(MediaFileEntity::getWorkspaceId, workspaceId))
                .stream()
                .map(this::entityConvertToDto)
                .collect(Collectors.toList());
    }
    /**
     * Convert the received file object into a database entity object.
     * @param file
     * @return
     */
    private MediaFileEntity fileUploadConvertToEntity(FileUploadDTO file) {
        MediaFileEntity.MediaFileEntityBuilder builder = MediaFileEntity.builder();
        if (file != null) {
            builder.fileName(file.getName())
                    .filePath(file.getPath())
                    .fingerprint(file.getFingerprint())
                    .objectKey(file.getObjectKey())
                    .subFileType(file.getSubFileType())
                    .isOriginal(file.getExt().getIsOriginal())
                    .drone(file.getExt().getSn())
                    .tinnyFingerprint(file.getExt().getTinnyFingerprint());
            // domain-type-subType
            int[] payloadModel = Arrays.stream(file.getExt().getPayloadModelKey().split("-"))
                    .map(Integer::valueOf)
                    .mapToInt(Integer::intValue)
                    .toArray();
            Optional<DeviceDictionaryDTO> payloadDict = deviceDictionaryService
                    .getOneDictionaryInfoByDomainTypeSubType(payloadModel[0], payloadModel[1], payloadModel[2]);
            payloadDict.ifPresent(payload -> builder.payload(payload.getDeviceName()));
        }
        return builder.build();
    }
    /**
     * Convert database entity objects into file data transfer object.
     * @param entity
     * @return
     */
    private MediaFileDTO entityConvertToDto(MediaFileEntity entity) {
        MediaFileDTO.MediaFileDTOBuilder builder = MediaFileDTO.builder();
        if (entity != null) {
            builder.fileName(entity.getFileName())
                    .filePath(entity.getFilePath())
                    .isOriginal(entity.getIsOriginal())
                    .objectKey(entity.getObjectKey())
                    .tinnyFingerprint(entity.getTinnyFingerprint())
                    .payload(entity.getPayload())
                    .drone(entity.getDrone());
        }
        return builder.build();
    }
}
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java
New file
@@ -0,0 +1,41 @@
package com.dji.sample.media.service.impl;
import com.dji.sample.media.model.FileUploadDTO;
import com.dji.sample.media.model.MediaFileDTO;
import com.dji.sample.media.service.IFileService;
import com.dji.sample.media.service.IMediaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 0.2
 * @date 2021/12/9
 */
@Service
public class MediaServiceImpl implements IMediaService {
    @Autowired
    private IFileService fileService;
    @Override
    public Boolean fastUpload(String workspaceId, String fingerprint) {
        return fileService.checkExist(workspaceId, fingerprint);
    }
    @Override
    public Integer saveMediaFile(String workspaceId, FileUploadDTO file) {
        return fileService.saveFile(workspaceId, file);
    }
    @Override
    public List<String> getAllTinyFingerprintsByWorkspaceId(String workspaceId) {
        return fileService.getAllFilesByWorkspaceId(workspaceId)
                .stream()
                .map(MediaFileDTO::getTinnyFingerprint)
                .collect(Collectors.toList());
    }
}
src/main/java/com/dji/sample/storage/controller/StorageController.java
New file
@@ -0,0 +1,51 @@
package com.dji.sample.storage.controller;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.oss.model.AliyunOSSConfiguration;
import com.dji.sample.component.oss.model.MinIOConfiguration;
import com.dji.sample.media.model.StsCredentialsDTO;
import com.dji.sample.storage.service.IStorageService;
import com.dji.sample.storage.service.impl.AliyunStorageServiceImpl;
import com.dji.sample.storage.service.impl.MinIOStorageServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author sean
 * @version 0.3
 * @date 2021/12/29
 */
@RestController
@RequestMapping("${url.storage.prefix}${url.storage.version}/workspaces/")
public class StorageController {
    private IStorageService storageService;
    @Autowired
    private void setOssService(@Autowired AliyunStorageServiceImpl aliyunStorageService,
                              @Autowired MinIOStorageServiceImpl minIOStorageService) {
        if (AliyunOSSConfiguration.enable) {
            this.storageService = aliyunStorageService;
            return;
        }
        if (MinIOConfiguration.enable) {
            this.storageService = minIOStorageService;
            return;
        }
        throw new NullPointerException("storageService is null.");
    }
    /**
     * Get temporary credentials for uploading the media and wayline in DJI Pilot.
     * @param workspaceId
     * @return
     */
    @PostMapping("/{workspace_id}/sts")
    public ResponseResult<StsCredentialsDTO> getSTSCredentials(@PathVariable(name = "workspace_id") String workspaceId) {
        StsCredentialsDTO stsCredentials = storageService.getSTSCredentials();
        return ResponseResult.success(stsCredentials);
    }
}
src/main/java/com/dji/sample/storage/service/IStorageService.java
New file
@@ -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();
}
src/main/java/com/dji/sample/storage/service/impl/AliyunStorageServiceImpl.java
New file
@@ -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();
    }
}
src/main/java/com/dji/sample/storage/service/impl/MinIOStorageServiceImpl.java
New file
@@ -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();
    }
}
src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java
New file
@@ -0,0 +1,150 @@
package com.dji.sample.wayline.controller;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.oss.model.AliyunOSSConfiguration;
import com.dji.sample.wayline.model.WaylineFileDTO;
import com.dji.sample.wayline.model.WaylineFileUploadDTO;
import com.dji.sample.wayline.model.WaylineQueryParam;
import com.dji.sample.wayline.service.IWaylineFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
/**
 * @author sean
 * @version 0.3
 * @date 2021/12/22
 */
@RestController
@RequestMapping("${url.wayline.prefix}${url.wayline.version}/workspaces")
public class WaylineFileController {
    @Autowired
    private IWaylineFileService waylineFileService;
    /**
     * Query the basic data of the wayline file according to the query conditions.
     * The query condition field in pilot is fixed.
     * @param orderBy   Sorted fields. Spliced at the end of the sql statement.
     * @param favorited Whether the wayline file is favorited or not.
     * @param page
     * @param pageSize
     * @param templateType
     * @param workspaceId
     * @return
     */
    @GetMapping("/{workspace_id}/waylines")
    public ResponseResult<PaginationData<WaylineFileDTO>> getWaylinesPagination(@RequestParam(name = "order_by") String orderBy,
                                      @RequestParam(required = false) boolean favorited, @RequestParam Integer page,
                                      @RequestParam(name = "page_size", defaultValue = "10") Integer pageSize,
                                      @RequestParam(name = "template_type", required = false) Integer[] templateType,
                                      @PathVariable(name = "workspace_id") String workspaceId) {
        WaylineQueryParam param = WaylineQueryParam.builder()
                .favorited(favorited)
                .page(page)
                .pageSize(pageSize)
                .orderBy(orderBy)
                .templateType(templateType)
                .build();
        PaginationData<WaylineFileDTO> data = waylineFileService.getWaylinesByParam(workspaceId, param);
        return ResponseResult.success(data);
    }
    /**
     * Query the download address of the file according to the wayline file id,
     * and redirect to this address directly for download.
     * @param workspaceId
     * @param waylineId
     * @param response
     */
    @GetMapping("/{workspace_id}/waylines/{wayline_id}/url")
    public void getFileUrl(@PathVariable(name = "workspace_id") String workspaceId,
                                @PathVariable(name = "wayline_id") String waylineId, HttpServletResponse response) {
        WaylineFileDTO wayline = waylineFileService.getWaylineByWaylineId(workspaceId, waylineId);
        URL url = waylineFileService.getObjectUrl(AliyunOSSConfiguration.bucket, wayline.getObjectKey());
        try {
            response.sendRedirect(url.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * When the wayline file is uploaded to the storage server by pilot,
     * the basic information of the file is reported through this interface.
     * @param request
     * @param workspaceId
     * @param uploadFile
     * @return
     */
    @PostMapping("/{workspace_id}/upload-callback")
    public ResponseResult uploadCallBack(HttpServletRequest request,
                                         @PathVariable(name = "workspace_id") String workspaceId,
                                         @RequestBody WaylineFileUploadDTO uploadFile) {
        CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
        WaylineFileDTO metadata = uploadFile.getMetadata();
        metadata.setUsername(customClaim.getUsername());
        metadata.setObjectKey(uploadFile.getObjectKey());
        metadata.setName(uploadFile.getName());
        int id = waylineFileService.saveWaylineFile(workspaceId, metadata);
        return id <= 0 ? ResponseResult.error() : ResponseResult.success();
    }
    /**
     * Favorite the wayline file according to the wayline file id.
     * @param workspaceId
     * @param ids   wayline file id
     * @return
     */
    @PostMapping("/{workspace_id}/favorites")
    public ResponseResult markFavorite(@PathVariable(name = "workspace_id") String workspaceId,
                             @RequestParam(name = "id") List<String> ids) {
        boolean isMark = waylineFileService.markFavorite(workspaceId, ids, true);
        return isMark ? ResponseResult.success() : ResponseResult.error();
    }
    /**
     * Delete the favorites of this wayline file based on the wayline file id.
     * @param workspaceId
     * @param ids wayline file id
     * @return
     */
    @DeleteMapping("/{workspace_id}/favorites")
    public ResponseResult unmarkFavorite(@PathVariable(name = "workspace_id") String workspaceId,
                             @RequestParam(name = "id") List<String> ids) {
        boolean isMark = waylineFileService.markFavorite(workspaceId, ids, false);
        return isMark ? ResponseResult.success() : ResponseResult.error();
    }
    /**
     * Checking whether the name already exists according to the wayline name must ensure the uniqueness of the wayline name.
     * This interface will be called when uploading waylines and must be available.
     * @param workspaceId
     * @param names
     * @return
     */
    @GetMapping("/{workspace_id}/waylines/duplicate-names")
    public ResponseResult checkDuplicateNames(@PathVariable(name = "workspace_id") String workspaceId,
                                              @RequestParam(name = "name") List<String> names) {
        List<String> existNamesList = waylineFileService.getDuplicateNames(workspaceId, names);
        return ResponseResult.success(existNamesList);
    }
}
src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java
New file
@@ -0,0 +1,12 @@
package com.dji.sample.wayline.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.wayline.model.WaylineFileEntity;
/**
 * @author sean
 * @version 0.3
 * @date 2021/12/22
 */
public interface IWaylineFileMapper extends BaseMapper<WaylineFileEntity> {
}
src/main/java/com/dji/sample/wayline/model/WaylineFileDTO.java
New file
@@ -0,0 +1,41 @@
package com.dji.sample.wayline.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean
 * @version 0.3
 * @date 2021/12/22
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WaylineFileDTO {
    private String name;
    @JsonProperty("id")
    private String waylineId;
    private String droneModelKey;
    private List<String> payloadModelKeys;
    private Boolean favorited;
    private List<Integer> templateTypes;
    private String objectKey;
    @JsonProperty("user_name")
    private String username;
    private Long updateTime;
}
src/main/java/com/dji/sample/wayline/model/WaylineFileEntity.java
New file
@@ -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;
}
src/main/java/com/dji/sample/wayline/model/WaylineFileUploadDTO.java
New file
@@ -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;
}
src/main/java/com/dji/sample/wayline/model/WaylineQueryParam.java
New file
@@ -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;
}
src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java
New file
@@ -0,0 +1,65 @@
package com.dji.sample.wayline.service;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.wayline.model.WaylineFileDTO;
import com.dji.sample.wayline.model.WaylineQueryParam;
import java.net.URL;
import java.util.List;
/**
 * @author sean
 * @version 0.3
 * @date 2021/12/22
 */
public interface IWaylineFileService {
    /**
     * Perform paging queries based on query parameters.
     * @param workspaceId
     * @param param
     * @return
     */
    PaginationData<WaylineFileDTO> getWaylinesByParam(String workspaceId, WaylineQueryParam param);
    /**
     * Query the information of this wayline file according to the wayline file id.
     * @param workspaceId
     * @param waylineId
     * @return
     */
    WaylineFileDTO getWaylineByWaylineId(String workspaceId, String waylineId);
    /**
     * Get the download address of the file object.
     * @param bucket    bucket name
     * @param objectKey object name
     * @return
     */
    URL getObjectUrl(String bucket, String objectKey);
    /**
     * Save the basic information of the wayline file.
     * @param workspaceId
     * @param metadata
     * @return
     */
    Integer saveWaylineFile(String workspaceId, WaylineFileDTO metadata);
    /**
     * Updates whether the file is collected or not based on the passed parameters.
     * @param workspaceId
     * @param ids          wayline id
     * @param isFavorite   Whether the wayline file is favorited or not.
     * @return
     */
    Boolean markFavorite(String workspaceId, List<String> ids, Boolean isFavorite);
    /**
     * Batch query for duplicate file names in workspace.
     * @param workspaceId
     * @param names
     * @return
     */
    List<String> getDuplicateNames(String workspaceId, List<String> names);
}
src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java
New file
@@ -0,0 +1,179 @@
package com.dji.sample.wayline.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.common.model.Pagination;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.component.oss.model.AliyunOSSConfiguration;
import com.dji.sample.component.oss.model.MinIOConfiguration;
import com.dji.sample.component.oss.service.IOssService;
import com.dji.sample.component.oss.service.impl.AliyunOssServiceImpl;
import com.dji.sample.component.oss.service.impl.MinIOServiceImpl;
import com.dji.sample.wayline.dao.IWaylineFileMapper;
import com.dji.sample.wayline.model.WaylineFileDTO;
import com.dji.sample.wayline.model.WaylineFileEntity;
import com.dji.sample.wayline.model.WaylineQueryParam;
import com.dji.sample.wayline.service.IWaylineFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 0.3
 * @date 2021/12/22
 */
@Service
@Transactional
public class WaylineFileServiceImpl implements IWaylineFileService {
    @Autowired
    private IWaylineFileMapper mapper;
    private IOssService ossService;
    @Autowired
    private void setOssService(@Autowired AliyunOssServiceImpl aliyunOssService, @Autowired MinIOServiceImpl minIOService) {
        if (AliyunOSSConfiguration.enable) {
            this.ossService = aliyunOssService;
            return;
        }
        if (MinIOConfiguration.enable) {
            this.ossService = minIOService;
            return;
        }
        throw new NullPointerException("ossService is null.");
    }
    @Override
    public PaginationData<WaylineFileDTO> getWaylinesByParam(String workspaceId, WaylineQueryParam param) {
        // Paging Query
        Page<WaylineFileEntity> page = mapper.selectPage(
                new Page<WaylineFileEntity>(param.getPage(), param.getPageSize()),
                new LambdaQueryWrapper<WaylineFileEntity>()
                        .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
                        .eq(param.isFavorited(), WaylineFileEntity::getFavorited, param.isFavorited())
                        .and(param.getTemplateType() != null, wrapper ->  {
                                for (Integer type : param.getTemplateType()) {
                                    wrapper.like(WaylineFileEntity::getTemplateTypes, type).or();
                                }
                        })
                        // There is a risk of SQL injection
                        .last(StringUtils.hasText(param.getOrderBy()), " order by " + param.getOrderBy()));
        // Wrap the results of a paging query into a custom paging object.
        List<WaylineFileDTO> records = page.getRecords()
                .stream()
                .map(this::entityConvertToDTO)
                .collect(Collectors.toList());
        return new PaginationData<>(records, new Pagination(page));
    }
    @Override
    public WaylineFileDTO getWaylineByWaylineId(String workspaceId, String waylineId) {
        return this.entityConvertToDTO(
                mapper.selectOne(
                        new LambdaQueryWrapper<WaylineFileEntity>()
                                .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
                                .eq(WaylineFileEntity::getWaylineId, waylineId)));
    }
    @Override
    public URL getObjectUrl(String bucket, String objectKey) {
        return ossService.getObjectUrl(bucket, objectKey);
    }
    @Override
    public Integer saveWaylineFile(String workspaceId, WaylineFileDTO metadata) {
        WaylineFileEntity file = this.dtoConvertToEntity(metadata);
        file.setWaylineId(UUID.randomUUID().toString());
        file.setWorkspaceId(workspaceId);
        int insertId = mapper.insert(file);
        return insertId > 0 ? file.getId() : insertId;
    }
    @Override
    public Boolean markFavorite(String workspaceId, List<String> waylineIds, Boolean isFavorite) {
        if (waylineIds.isEmpty()) {
            return false;
        }
        if (isFavorite == null) {
            return true;
        }
        return mapper.update(null, new LambdaUpdateWrapper<WaylineFileEntity>()
                .set(WaylineFileEntity::getFavorited, isFavorite)
                .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
                .in(WaylineFileEntity::getWaylineId, waylineIds)) > 0;
    }
    @Override
    public List<String> getDuplicateNames(String workspaceId, List<String> names) {
        return mapper.selectList(new LambdaQueryWrapper<WaylineFileEntity>()
                .eq(WaylineFileEntity::getWorkspaceId, workspaceId)
                .in(WaylineFileEntity::getName, names))
                .stream()
                .map(WaylineFileEntity::getName)
                .collect(Collectors.toList());
    }
    /**
     * Convert database entity objects into wayline data transfer object.
     * @param entity
     * @return
     */
    private WaylineFileDTO entityConvertToDTO(WaylineFileEntity entity) {
        WaylineFileDTO.WaylineFileDTOBuilder builder = WaylineFileDTO.builder();
        if (entity != null) {
            builder.droneModelKey(entity.getDroneModelKey())
                    .favorited(entity.getFavorited())
                    .name(entity.getName())
                    .payloadModelKeys(entity.getPayloadModelKeys() != null ?
                            Arrays.asList(entity.getPayloadModelKeys().split(",")) : null)
                    .templateTypes(Arrays.stream(entity.getTemplateTypes().split(","))
                            .map(Integer::parseInt)
                            .collect(Collectors.toList()))
                    .username(entity.getUsername())
                    .objectKey(entity.getObjectKey())
                    .updateTime(entity.getUpdateTime())
                    .waylineId(entity.getWaylineId());
        }
        return builder.build();
    }
    /**
     * Convert the received wayline object into a database entity object.
     * @param file
     * @return
     */
    private WaylineFileEntity dtoConvertToEntity(WaylineFileDTO file) {
        WaylineFileEntity.WaylineFileEntityBuilder builder = WaylineFileEntity.builder();
        if (file != null) {
            builder.droneModelKey(file.getDroneModelKey())
                    .name(file.getName())
                    .username(file.getUsername())
                    .objectKey(file.getObjectKey())
                    // Separate multiple payload data with ",".
                    .payloadModelKeys(String.join(",", file.getPayloadModelKeys()))
                    .templateTypes(file.getTemplateTypes().stream()
                            .map(String::valueOf)
                            .collect(Collectors.joining(",")))
                    .favorited(file.getFavorited())
                    .build();
        }
        return builder.build();
    }
}
src/main/resources/application.yml
New file
@@ -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