sean.zhou
2022-09-23 a3360878835e4606c968441e432b0c3d4bd921ae
V1.2.0
50 files modified
1 files renamed
50 files added
21913 ■■■■■ changed files
api/Cloud API Demo.postman_collection.json 392 ●●●●● patch | view | raw | blame | history
api/Cloud API Demo.postman_environment.json 16 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java 46 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/Chan.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java 45 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/EventsResultStatusEnum.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/OutputProgressReceiver.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/model/ServicesMethodEnum.java 72 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/model/enums/OssTypeEnum.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/redis/RedisConst.java 18 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/component/websocket/service/impl/WebSocketManageServiceImpl.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/configuration/mvc/GetSnakeArgumentProcessor.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/configuration/mvc/GetSnakeDataBinder.java 53 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/configuration/mvc/GlobalMVCConfigurer.java 9 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/controller/DockController.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/IControlService.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java 133 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DeviceController.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DeviceFirmwareController.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DeviceHmsController.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DeviceLogsController.java 122 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java 17 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/controller/LiveStreamController.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/IDeviceFirmwareMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/IDeviceLogsMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/ILogsFileIndexMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/dao/ILogsFileMapper.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/common/HmsJsonUtil.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceFirmwareDTO.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceFirmwareNoteDTO.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceFirmwareUpgradeDTO.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/DeviceLogsDTO.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LogsFileDTO.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LogsOutputProgressDTO.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LogsProgressDTO.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/dto/LogsUploadCredentialsDTO.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DeviceFirmwareEntity.java 64 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DeviceHmsEntity.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/DeviceLogsEntity.java 52 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/LogsFileEntity.java 58 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/entity/LogsFileIndexEntity.java 54 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DeviceFirmwareStatusEnum.java 62 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/DeviceLogsStatusEnum.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/enums/LogsFileUpdateMethodEnum.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/param/DeviceHmsQueryParam.java 9 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/param/DeviceLogsCreateParam.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/param/DeviceLogsQueryParam.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/param/DeviceOtaCreateParam.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/param/LogsFileUpdateParam.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/LogsExtFileReceiver.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/LogsFile.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/LogsFileUpload.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/LogsFileUploadList.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/LogsProgressReceiver.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OutputLogsExtReceiver.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/model/receiver/OutputLogsProgressReceiver.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java 48 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceHmsService.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java 94 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/IDeviceService.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ILiveStreamService.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ILogsFileIndexService.java 46 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ILogsFileService.java 66 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/ITopologyService.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java 218 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java 172 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java 376 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java 50 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java 113 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/LogsFileIndexServiceImpl.java 108 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/LogsFileServiceImpl.java 163 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/controller/FileController.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/CredentialsDTO.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/MediaFileDTO.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/MediaFileEntity.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/IFileService.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java 14 ●●●● patch | view | raw | blame | history
src/main/resources/application.yml 23 ●●●● patch | view | raw | blame | history
src/main/resources/hms.json 17924 ●●●●● patch | view | raw | blame | history
api/Cloud API Demo.postman_collection.json
@@ -149,7 +149,7 @@
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"url\": \"rtmp://192.168.1.1/live/1651053434895\",\r\n    \"url_type\": 1,\r\n    \"video_id\": \"1581F4BN/52-0-0/zoom-0\",\r\n    \"video_quality\": 0\r\n}",
                            "raw": "{\r\n    \"url\": \"rtmp://192.168.1.1/live/1651053434895\",\r\n    \"url_type\": 1,\r\n    \"video_id\": \"1581F5B0159/53-0-0/normal-0\",\r\n    \"video_quality\": 0\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
@@ -383,6 +383,326 @@
                        }
                    },
                    "response": []
                },
                {
                    "name": "Firmware Upgrad",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "[\r\n    {\r\n        \"sn\": \"1581F5B9\",\r\n        \"device_name\": \"Matrice 30\",\r\n        \"product_version\": \"04.01.0020\",\r\n        \"firmware_upgrade_type\": 3\r\n    }\r\n]\r\n",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/{{workspace_id}}/devices/ota",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{workspace_id}}",
                                "devices",
                                "ota"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get Latest Release Note",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/workspaces/firmware-release-notes/latest?device_name=DJI Dock",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "firmware-release-notes",
                                "latest"
                            ],
                            "query": [
                                {
                                    "key": "device_name",
                                    "value": "DJI Dock"
                                }
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get HMS Info",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/{{workspace_id}}/devices/hms?device_sn=&language=en&page=1&page_size=2",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{workspace_id}}",
                                "devices",
                                "hms"
                            ],
                            "query": [
                                {
                                    "key": "device_sn",
                                    "value": "",
                                    "description": "Type: List"
                                },
                                {
                                    "key": "begin_time",
                                    "value": null,
                                    "disabled": true
                                },
                                {
                                    "key": "end_time",
                                    "value": null,
                                    "disabled": true
                                },
                                {
                                    "key": "language",
                                    "value": "en",
                                    "description": "zh or en"
                                },
                                {
                                    "key": "message",
                                    "value": null,
                                    "disabled": true
                                },
                                {
                                    "key": "page",
                                    "value": "1"
                                },
                                {
                                    "key": "page_size",
                                    "value": "2"
                                },
                                {
                                    "key": "level",
                                    "value": null,
                                    "disabled": true
                                },
                                {
                                    "key": "update_time",
                                    "value": null,
                                    "description": "Type: Long",
                                    "disabled": true
                                }
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Update Unread HMS",
                    "request": {
                        "method": "PUT",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/{{workspace_id}}/devices/hms/{{device_sn}}",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{workspace_id}}",
                                "devices",
                                "hms",
                                "{{device_sn}}"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get Unread Hms",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/devices/{{workspace_id}}/devices/hms/{{device_sn}}",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "devices",
                                "{{workspace_id}}",
                                "devices",
                                "hms",
                                "{{device_sn}}"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get Uploaded Logs",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/workspaces/{{workspace_id}}/devices/{{device_sn}}/logs-uploaded?page=1&page_size=12&begin_time=123123&end_time=123123",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "devices",
                                "{{device_sn}}",
                                "logs-uploaded"
                            ],
                            "query": [
                                {
                                    "key": "page",
                                    "value": "1"
                                },
                                {
                                    "key": "page_size",
                                    "value": "12"
                                },
                                {
                                    "key": "status",
                                    "value": null,
                                    "disabled": true
                                },
                                {
                                    "key": "begin_time",
                                    "value": "123123"
                                },
                                {
                                    "key": "end_time",
                                    "value": "123123"
                                },
                                {
                                    "key": "logs_information",
                                    "value": "",
                                    "disabled": true
                                }
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Get Real Time Logs",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/workspaces/{{workspace_id}}/devices/{{device_sn}}/logs?domain_list=0,3",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "devices",
                                "{{device_sn}}",
                                "logs"
                            ],
                            "query": [
                                {
                                    "key": "domain_list",
                                    "value": "0,3"
                                }
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Upload Logs",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"logs_information\":\"\",\r\n    \"happen_time\":1659326597,\r\n    \"files\":[\r\n        {\r\n            \"list\":[\r\n                {\r\n                    \"boot_index\":1053,\r\n                    \"end_time\":1662070409,\r\n                    \"size\":1083590875,\r\n                    \"start_time\":1662066809\r\n                }\r\n            ],\r\n            \"device_sn\":\"4TADK1D\",\r\n            \"module\":\"3\"\r\n        },\r\n        {\r\n            \"list\":[\r\n                {\r\n                    \"boot_index\":222,\r\n                    \"end_time\":1661833032,\r\n                    \"size\":1782411613,\r\n                    \"start_time\":1661830103\r\n                }\r\n            ],\r\n            \"device_sn\":\"1581259\",\r\n            \"module\":\"0\"\r\n        }\r\n    ]\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/workspaces/{{workspace_id}}/devices/{{device_sn}}/logs",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "devices",
                                "{{device_sn}}",
                                "logs"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Cancel File Upload",
                    "request": {
                        "method": "DELETE",
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"status\":\"cancel\",\r\n    \"module_list\":[\r\n        \"0\",\r\n        \"3\"\r\n    ]\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
                                }
                            }
                        },
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/workspaces/{{workspace_id}}/devices/{{device_sn}}/logs",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "devices",
                                "{{device_sn}}",
                                "logs"
                            ]
                        }
                    },
                    "response": []
                },
                {
                    "name": "Delete Upload History",
                    "request": {
                        "method": "DELETE",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{manage_version}}/workspaces/{{workspace_id}}/devices/{{device_sn}}/logs/{{logs_id}}",
                            "host": [
                                "{{base_url}}{{manage_version}}"
                            ],
                            "path": [
                                "workspaces",
                                "{{workspace_id}}",
                                "devices",
                                "{{device_sn}}",
                                "logs",
                                "{{logs_id}}"
                            ]
                        }
                    },
                    "response": []
                }
            ],
            "auth": {
@@ -390,7 +710,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTI2OTUxOTcsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTI3ODE1OTcsImlhdCI6MTY1MjY5NTE5NywidXNlcm5hbWUiOiJhZG1pblBDIn0.BHTwW8imw5ab0GUypRyJ2gkoz5av9q99NrxoFlL53dA",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NjM1NTkxMTAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NjM2NDU1MTAsImlhdCI6MTY2MzU1OTExMCwidXNlcm5hbWUiOiJhZG1pblBDIn0.LG1JXZkuTdMaqnXn5WMJvnysNkHHbc4HLe_qZPWz_nM",
                        "type": "string"
                    },
                    {
@@ -723,7 +1043,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTgzOTIyNzksImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTg0Nzg2NzksImlhdCI6MTY1ODM5MjI3OSwidXNlcm5hbWUiOiJhZG1pblBDIn0.ErClyQS1YzoQVBcLYxpFEiyFTb1L-eg2-vsudlL9WJU",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NjI2OTYwNjMsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NjI3ODI0NjMsImlhdCI6MTY2MjY5NjA2MywidXNlcm5hbWUiOiJhZG1pblBDIn0.9XEd-Zspb_a-2WhtcHxbQ4GdHbBj9wfmUbHBkZgSS0c",
                        "type": "string"
                    },
                    {
@@ -783,7 +1103,7 @@
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTI2OTUxOTcsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTI3ODE1OTcsImlhdCI6MTY1MjY5NTE5NywidXNlcm5hbWUiOiJhZG1pblBDIn0.BHTwW8imw5ab0GUypRyJ2gkoz5av9q99NrxoFlL53dA",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTg3MTgxNTMsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTg4MDQ1NTMsImlhdCI6MTY1ODcxODE1MywidXNlcm5hbWUiOiJhZG1pblBDIn0._Xw3rnnDhs32JW9pD_FBtRWDuwLZgX_ys3GNmZxuHsk",
                        "type": "string"
                    },
                    {
@@ -1050,7 +1370,7 @@
                        "header": [],
                        "body": {
                            "mode": "raw",
                            "raw": "{\r\n    \"name\": \"\",\r\n    \"fild_id\": \"\",\r\n    \"dock_sn\": \"\",\r\n    \"type\": \"\",\r\n    \"immediate\": false\r\n}",
                            "raw": "{\r\n    \"name\": \"\",\r\n    \"file_id\": \"\",\r\n    \"dock_sn\": \"\",\r\n    \"type\": \"\",\r\n    \"immediate\": false\r\n}",
                            "options": {
                                "raw": {
                                    "language": "json"
@@ -1156,6 +1476,66 @@
                    }
                }
            ]
        },
        {
            "name": "control",
            "item": [
                {
                    "name": "Create Control Job",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": {
                            "raw": "{{base_url}}{{control_version}}/devices/4TADK7E000000H/jobs/debug_mode_close",
                            "host": [
                                "{{base_url}}{{control_version}}"
                            ],
                            "path": [
                                "devices",
                                "4TADK7E000000H",
                                "jobs",
                                "debug_mode_close"
                            ]
                        }
                    },
                    "response": []
                }
            ],
            "auth": {
                "type": "apikey",
                "apikey": [
                    {
                        "key": "value",
                        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NjE5NTQwMTQsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NjIwNDA0MTQsImlhdCI6MTY2MTk1NDAxNCwidXNlcm5hbWUiOiJhZG1pblBDIn0.GgCh575h2-HvYvdGZIKBW50r0F6CPACQn4ceAVzJfCU",
                        "type": "string"
                    },
                    {
                        "key": "key",
                        "value": "x-auth-token",
                        "type": "string"
                    }
                ]
            },
            "event": [
                {
                    "listen": "prerequest",
                    "script": {
                        "type": "text/javascript",
                        "exec": [
                            ""
                        ]
                    }
                },
                {
                    "listen": "test",
                    "script": {
                        "type": "text/javascript",
                        "exec": [
                            ""
                        ]
                    }
                }
            ]
        }
    ],
    "auth": {
@@ -1163,7 +1543,7 @@
        "apikey": [
            {
                "key": "value",
                "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTA0MjY3MzAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTA1MTMxMzAsImlhdCI6MTY1MDQyNjczMCwidXNlcm5hbWUiOiJhZG1pblBDIn0.-FBpi0ktuZ68jV6-HQ7mwm8iC07YH-Hw2aRiREzJ8hs",
                "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NjMyMjM1MTAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NjMzMDk5MTAsImlhdCI6MTY2MzIyMzUxMCwidXNlcm5hbWUiOiJhZG1pblBDIn0.6gzNuuX2f0YBoddoD61NFnwABQ_X4LFtQhEaLWSmTW8",
                "type": "string"
            },
            {
api/Cloud API Demo.postman_environment.json
@@ -52,9 +52,21 @@
            "key": "storage_version",
            "value": "/storage/api/v1",
            "enabled": true
        },
        {
            "key": "control_version",
            "value": "/control/api/v1",
            "type": "default",
            "enabled": true
        },
        {
            "key": "device_sn",
            "value": "xxxxxxxxxx",
            "type": "default",
            "enabled": true
        }
    ],
    "_postman_variable_scope": "environment",
    "_postman_exported_at": "2022-07-21T08:35:48.441Z",
    "_postman_exported_using": "Postman/9.19.0"
    "_postman_exported_at": "2022-09-23T03:53:25.999Z",
    "_postman_exported_using": "Postman/9.31.0"
}
src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java
@@ -137,4 +137,19 @@
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS)
    public MessageChannel eventsControlProgress() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_OTA_PROGRESS)
    public MessageChannel eventsOtaProgress() {
        return new DirectChannel();
    }
    @Bean(name = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_PROGRESS)
    public MessageChannel eventsFileUploadProgress() {
        return new DirectChannel();
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java
@@ -11,6 +11,7 @@
import org.springframework.integration.dsl.IntegrationFlows;
import java.io.IOException;
import java.util.Arrays;
/**
 * @author sean
@@ -37,12 +38,8 @@
                })
                .<CommonTopicReceiver, EventsMethodEnum>route(
                        receiver -> EventsMethodEnum.find(receiver.getMethod()),
                        mapping -> {
                            mapping.channelMapping(EventsMethodEnum.FILE_UPLOAD_CALLBACK, ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK);
                            mapping.channelMapping(EventsMethodEnum.FLIGHT_TASK_PROGRESS, ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS);
                            mapping.channelMapping(EventsMethodEnum.HMS, ChannelName.INBOUND_EVENTS_HMS);
                            mapping.channelMapping(EventsMethodEnum.UNKNOWN, ChannelName.DEFAULT);
                        })
                        mapping -> Arrays.stream(EventsMethodEnum.values()).forEach(
                                methodEnum -> mapping.channelMapping(methodEnum, methodEnum.getChannelName())))
                .get();
    }
}
src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java
New file
@@ -0,0 +1,46 @@
package com.dji.sample.component.mqtt.handler;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.manage.model.receiver.LogsFileUploadList;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
@Component
public class ServicesReplyHandler {
    @Autowired
    private ObjectMapper mapper;
    /**
     * 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();
        CommonTopicReceiver receiver = mapper.readValue(payload, new TypeReference<CommonTopicReceiver>() {});
        if (ServicesMethodEnum.FILE_UPLOAD_LIST.getMethod().equals(receiver.getMethod())) {
            LogsFileUploadList list = mapper.convertValue(receiver.getData(), new TypeReference<LogsFileUploadList>() {});
            receiver.setData(list);
        } else {
            ServiceReply reply = mapper.convertValue(receiver.getData(), new TypeReference<ServiceReply>() {});
            receiver.setData(reply);
        }
        Chan<CommonTopicReceiver<?>> chan = Chan.getInstance();
        // Put the message to the chan object.
        chan.put(receiver);
    }
}
src/main/java/com/dji/sample/component/mqtt/model/Chan.java
@@ -10,7 +10,7 @@
 */
public class Chan<T> {
    private static final long THREAD_WAIT_TIME = 1000_000 * 2000;
    private static final long THREAD_WAIT_TIME = 1000_000L * 10_000;
    private volatile T data;
src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java
@@ -62,4 +62,10 @@
    public static final String INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND = "inboundRequestsAirportOrganizationBind";
    public static final String INBOUND_EVENTS_HMS = "inboundEventsHms";
    public static final String INBOUND_EVENTS_CONTROL_PROGRESS = "inboundEventsControlProgress";
    public static final String INBOUND_EVENTS_OTA_PROGRESS = "inboundEventsOtaProgress";
    public static final String INBOUND_EVENTS_FILE_UPLOAD_PROGRESS = "inboundEventsFileUploadProgress";
}
src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java
@@ -9,24 +9,59 @@
 */
public enum EventsMethodEnum {
    FLIGHT_TASK_PROGRESS("flighttask_progress"),
    FLIGHT_TASK_PROGRESS("flighttask_progress", ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS),
    FILE_UPLOAD_CALLBACK("file_upload_callback"),
    FILE_UPLOAD_CALLBACK("file_upload_callback", ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK),
    HMS("hms"),
    HMS("hms", ChannelName.INBOUND_EVENTS_HMS),
    UNKNOWN("Unknown");
    DEVICE_REBOOT("device_reboot", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    DRONE_OPEN("drone_open", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    DRONE_CLOSE("drone_close", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    DEVICE_CHECK("device_check", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    DRONE_FORMAT("drone_format", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    DEVICE_FORMAT("device_format", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    COVER_OPEN("cover_open", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    COVER_CLOSE("cover_close", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    PUTTER_OPEN("putter_open", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    PUTTER_CLOSE("putter_close", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    CHARGE_OPEN("charge_open", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    CHARGE_CLOSE("charge_close", ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS),
    OTA_PROGRESS("ota_progress", ChannelName.INBOUND_EVENTS_OTA_PROGRESS),
    FILE_UPLOAD_PROGRESS("fileupload_progress", ChannelName.INBOUND_EVENTS_FILE_UPLOAD_PROGRESS),
    UNKNOWN("Unknown", ChannelName.DEFAULT);
    private String method;
    EventsMethodEnum(String method) {
    private String channelName;
    EventsMethodEnum(String method, String channelName) {
        this.method = method;
        this.channelName = channelName;
    }
    public String getMethod() {
        return method;
    }
    public String getChannelName() {
        return channelName;
    }
    public static EventsMethodEnum find(String method) {
        return Arrays.stream(EventsMethodEnum.values())
                .filter(methodEnum -> methodEnum.method.equals(method))
src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.component.mqtt.model;
import lombok.Data;
/**
 * @author sean
 * @version 1.2
 * @date 2022/7/29
 */
@Data
public class EventsOutputReceiver {
    private String status;
    private OutputProgressReceiver progress;
}
src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java
@@ -18,4 +18,6 @@
    private String bid;
    private String sn;
}
src/main/java/com/dji/sample/component/mqtt/model/EventsResultStatusEnum.java
New file
@@ -0,0 +1,47 @@
package com.dji.sample.component.mqtt.model;
import lombok.Getter;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/17
 */
@Getter
public enum EventsResultStatusEnum {
    SENT("sent", false),
    IN_PROGRESS("in_progress", false),
    OK("ok", true),
    PAUSED("paused", false),
    REJECTED("rejected", true),
    FAILED("failed", true),
    CANCELED("canceled", true),
    TIMEOUT("timeout", true),
    UNKNOWN("unknown", false);
    String desc;
    Boolean end;
    EventsResultStatusEnum(String desc, Boolean end) {
        this.desc = desc;
        this.end = end;
    }
    public static EventsResultStatusEnum find(String desc) {
        return Arrays.stream(EventsResultStatusEnum.values())
                .filter(status -> status.desc.equals(desc))
                .findFirst().orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java
@@ -26,4 +26,6 @@
    public static final String BIND_STATUS = "bind_status";
    public static final String LIST = "list";
    public static final String MODULE_LIST = "module_list";
}
src/main/java/com/dji/sample/component/mqtt/model/OutputProgressReceiver.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.component.mqtt.model;
import lombok.Data;
/**
 * @author sean
 * @version 1.2
 * @date 2022/7/29
 */
@Data
public class OutputProgressReceiver {
    private Integer percent;
    private String stepKey;
    private Integer stepResult;
}
src/main/java/com/dji/sample/component/mqtt/model/ServicesMethodEnum.java
@@ -1,5 +1,7 @@
package com.dji.sample.component.mqtt.model;
import java.util.Arrays;
/**
 * @author sean.zhou
 * @date 2021/11/22
@@ -7,23 +9,81 @@
 */
public enum ServicesMethodEnum {
    LIVE_START_PUSH("live_start_push"),
    LIVE_START_PUSH("live_start_push", false),
    LIVE_STOP_PUSH("live_stop_push"),
    LIVE_STOP_PUSH("live_stop_push", false),
    LIVE_SET_QUALITY("live_set_quality"),
    LIVE_SET_QUALITY("live_set_quality", false),
    FLIGHTTASK_CREATE("flighttask_create"),
    FLIGHTTASK_CREATE("flighttask_create", false),
    UNKNOWN("unknown");
    DEBUG_MODE_OPEN("debug_mode_open", false),
    DEBUG_MODE_CLOSE("debug_mode_close", false),
    SUPPLEMENT_LIGHT_OPEN("supplement_light_open", false),
    SUPPLEMENT_LIGHT_CLOSE("supplement_light_close", false),
    RETURN_HOME("return_home", false),
    SDR_WORKMODE_SWITCH("sdr_workmode_switch", false),
    DEVICE_REBOOT("device_reboot", true),
    DRONE_OPEN("drone_open", true),
    DRONE_CLOSE("drone_close", true),
    DEVICE_CHECK("device_check", true),
    DRONE_FORMAT("drone_format", true),
    DEVICE_FORMAT("device_format", true),
    COVER_OPEN("cover_open", true),
    COVER_CLOSE("cover_close", true),
    PUTTER_OPEN("putter_open", true),
    PUTTER_CLOSE("putter_close", true),
    CHARGE_OPEN("charge_open", true),
    CHARGE_CLOSE("charge_close", true),
    OTA_CREATE("ota_create", true),
    FILE_UPLOAD_LIST("fileupload_list", false),
    FILE_UPLOAD_START("fileupload_start", true),
    FILE_UPLOAD_UPDATE("fileupload_update", false),
    UNKNOWN("unknown", false);
    private String method;
    ServicesMethodEnum(String method) {
    private Boolean progress;
    ServicesMethodEnum(String method, Boolean progress) {
        this.method = method;
        this.progress = progress;
    }
    public static ServicesMethodEnum find(String method) {
        return Arrays.stream(ServicesMethodEnum.values())
                .filter(methodEnum -> methodEnum.method.equals(method))
                .findAny()
                .orElse(UNKNOWN);
    }
    public String getMethod() {
        return method;
    }
    public Boolean getProgress() {
        return progress;
    }
}
src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java
@@ -28,10 +28,21 @@
    void publish(String topic, int qos, CommonTopicResponse response);
    /**
     * Send live streaming start message and receive a response at the same time
     * Send live streaming start message and receive a response at the same time.
     * @param topic
     * @param response  notification of whether the start is successful.
     * @return
     */
    Optional<ServiceReply> publishWithReply(String topic, CommonTopicResponse response);
    /**
     * Send live streaming start message and receive a response at the same time.
     * @param clazz
     * @param topic
     * @param response
     * @param retryTime
     * @param <T>
     * @return
     */
    <T> Optional<T> publishWithReply(Class<T> clazz, String topic, CommonTopicResponse response, int retryTime);
}
src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java
@@ -50,14 +50,18 @@
    }
    public Optional<ServiceReply> publishWithReply(String topic, CommonTopicResponse response) {
        return this.publishWithReply(ServiceReply.class, topic, response, 2);
    }
    public <T> Optional<T> publishWithReply(Class<T> clazz, String topic, CommonTopicResponse response, int retryTime) {
        AtomicInteger time = new AtomicInteger(0);
        // Retry three times
        while (time.getAndIncrement() < 3) {
        while (time.getAndIncrement() < retryTime) {
            this.publish(topic, response);
            Chan<CommonTopicReceiver<ServiceReply>> chan = Chan.getInstance();
            Chan<CommonTopicReceiver<T>> chan = Chan.getInstance();
            // If the message is not received in 0.5 seconds then resend it again.
            CommonTopicReceiver<ServiceReply> receiver = chan.get(response.getMethod());
            CommonTopicReceiver<T> receiver = chan.get(response.getMethod());
            if (receiver == null) {
                continue;
            }
src/main/java/com/dji/sample/component/oss/model/enums/OssTypeEnum.java
@@ -11,9 +11,6 @@
    AWS("aws"),
    /*
     * MinIO is temporarily unavailable.
    */
    MINIO("minio");
    private String type;
src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java
@@ -2,6 +2,7 @@
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.OSSObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
@@ -70,7 +71,10 @@
        }
        OSS ossClient = this.createClient();
        // First check if the object can be fetched.
        ossClient.getObject(bucket, objectKey);
        boolean isExist = ossClient.doesObjectExist(bucket, objectKey);
        if (!isExist) {
            throw new OSSException("The object does not exist.");
        }
        return ossClient.generatePresignedUrl(bucket, objectKey,
                new Date(System.currentTimeMillis() + configuration.getExpire() * 1000));
@@ -79,6 +83,10 @@
    @Override
    public Boolean deleteObject(String bucket, String objectKey) {
        OSS ossClient = this.createClient();
        if (!ossClient.doesObjectExist(bucket, objectKey)) {
            ossClient.shutdown();
            return true;
        }
        ossClient.deleteObject(bucket, objectKey);
        ossClient.shutdown();
        return true;
@@ -88,19 +96,13 @@
    public byte[] getObject(String bucket, String objectKey) {
        OSS ossClient = this.createClient();
        OSSObject object = ossClient.getObject(bucket, objectKey);
        InputStream stream = object.getObjectContent();
        try {
        try (InputStream stream = object.getObjectContent()) {
            return stream.readAllBytes();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                stream.close();
                ossClient.shutdown();
            } catch (IOException e) {
                e.printStackTrace();
            }
            ossClient.shutdown();
        }
        return new byte[0];
    }
src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java
@@ -74,6 +74,10 @@
    @Override
    public Boolean deleteObject(String bucket, String objectKey) {
        AmazonS3 client = this.createClient();
        if (!client.doesObjectExist(bucket, objectKey)) {
            client.shutdown();
            return true;
        }
        client.deleteObject(bucket, objectKey);
        client.shutdown();
        return true;
@@ -82,18 +86,13 @@
    public byte[] getObject(String bucket, String objectKey) {
        AmazonS3 client = this.createClient();
        S3Object object = client.getObject(bucket, objectKey);
        InputStream stream = object.getObjectContent().getDelegateStream();
        try {
        try (InputStream stream = object.getObjectContent().getDelegateStream()) {
            return stream.readAllBytes();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                stream.close();
                client.shutdown();
            } catch (IOException e) {
                e.printStackTrace();
            }
            client.shutdown();
        }
        return new byte[0];
    }
src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java
@@ -4,8 +4,10 @@
import com.dji.sample.component.oss.model.enums.OssTypeEnum;
import com.dji.sample.component.oss.service.IOssService;
import com.dji.sample.media.model.CredentialsDTO;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.RemoveObjectArgs;
import io.minio.credentials.AssumeRoleProvider;
import io.minio.errors.*;
import io.minio.http.Method;
@@ -14,6 +16,7 @@
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -41,7 +44,7 @@
            AssumeRoleProvider provider = new AssumeRoleProvider(configuration.getEndpoint(), configuration.getAccessKey(),
                    configuration.getSecretKey(), Math.toIntExact(configuration.getExpire()),
                    null, configuration.getRegion(), null, null, null, null);
            return new CredentialsDTO(provider.fetch(), Math.toIntExact(configuration.getExpire()));
            return new CredentialsDTO(provider.fetch(), configuration.getExpire());
        } catch (NoSuchAlgorithmException e) {
            log.debug("Failed to obtain sts.");
            e.printStackTrace();
@@ -72,11 +75,25 @@
    @Override
    public Boolean deleteObject(String bucket, String objectKey) {
        return null;
        MinioClient client = this.createClient();
        try {
            client.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectKey).build());
        } catch (MinioException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
            log.error("Failed to delete file.");
            e.printStackTrace();
            return false;
        }
        return true;
    }
    @Override
    public byte[] getObject(String bucket, String objectKey) {
        MinioClient client = this.createClient();
        try (InputStream objectResponse = client.getObject(GetObjectArgs.builder().bucket(bucket).object(objectKey).build())) {
            return objectResponse.readAllBytes();
        } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return new byte[0];
    }
src/main/java/com/dji/sample/component/redis/RedisConst.java
@@ -13,17 +13,25 @@
    }
    public static final String DELIMITER = ":";
    public static final Integer DEVICE_ALIVE_SECOND = 60;
    public static final Integer WEBSOCKET_ALIVE_SECOND = 60 * 60 * 24;
    public static final String ONLINE_PREFIX = "online:";
    public static final String ONLINE_PREFIX = "online" + DELIMITER;
    public static final String DEVICE_ONLINE_PREFIX = ONLINE_PREFIX + DeviceDomainEnum.SUB_DEVICE + ":";
    public static final String DEVICE_ONLINE_PREFIX = ONLINE_PREFIX + DeviceDomainEnum.SUB_DEVICE + DELIMITER;
    public static final String WEBSOCKET_PREFIX = "webSocket:";
    public static final String WEBSOCKET_PREFIX = "webSocket" + DELIMITER;
    public static final String WEBSOCKET_ALL = "webSocket:all";
    public static final String WEBSOCKET_ALL = WEBSOCKET_PREFIX + "all";
    public static final String HMS_PREFIX = "hms:";
    public static final String HMS_PREFIX = "hms" + DELIMITER;
    public static final String FIRMWARE_UPGRADING_PREFIX = "upgrading" + DELIMITER;
    public static final String STATE_PAYLOAD_PREFIX = "payload" + DELIMITER;
    public static final String LOGS_FILE_PREFIX = "logs_file" + DELIMITER;
}
src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java
@@ -29,7 +29,31 @@
    FLIGHT_TASK_PROGRESS("flighttask_progress"),
    DEVICE_HMS("device_hms");
    DEVICE_HMS("device_hms"),
    DEVICE_REBOOT("device_reboot"),
    DRONE_OPEN("drone_open"),
    DRONE_CLOSE("drone_close"),
    DEVICE_CHECK("device_check"),
    DRONE_FORMAT("drone_format"),
    DEVICE_FORMAT("device_format"),
    COVER_OPEN("cover_open"),
    COVER_CLOSE("cover_close"),
    PUTTER_OPEN("putter_open"),
    PUTTER_CLOSE("putter_close"),
    CHARGE_OPEN("charge_open"),
    CHARGE_CLOSE("charge_close");
    private String code;
src/main/java/com/dji/sample/component/websocket/service/impl/WebSocketManageServiceImpl.java
@@ -55,7 +55,7 @@
            return;
        }
        redisOps.hashDel(RedisConst.WEBSOCKET_PREFIX + name[0], new String[] {sessionId});
        redisOps.hashDel(RedisConst.WEBSOCKET_PREFIX + UserTypeEnum.find(Integer.parseInt(name[1])), new String[] {sessionId});
        redisOps.hashDel(RedisConst.WEBSOCKET_PREFIX + UserTypeEnum.find(Integer.parseInt(name[1])).getDesc(), new String[] {sessionId});
        SESSIONS.remove(sessionId);
    }
src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java
@@ -7,7 +7,6 @@
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@@ -20,7 +19,7 @@
public class SpringBeanConfiguration {
    @Bean
    @ConditionalOnMissingBean(ObjectMapper.class)
//    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
@@ -38,6 +37,7 @@
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
src/main/java/com/dji/sample/configuration/mvc/GetSnakeArgumentProcessor.java
New file
@@ -0,0 +1,33 @@
package com.dji.sample.configuration.mvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/16
 */
public class GetSnakeArgumentProcessor extends ServletModelAttributeMethodProcessor {
    @Autowired
    private GetSnakeDataBinder snakeDataBinder;
    /**
     * Class constructor.
     *
     * @param annotationNotRequired if "true", non-simple method arguments and
     *                              return values are considered model attributes with or without a
     *                              {@code @ModelAttribute} annotation
     */
    public GetSnakeArgumentProcessor(boolean annotationNotRequired) {
        super(annotationNotRequired);
    }
    @Override
    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
        super.bindRequestParameters(new GetSnakeDataBinder(binder.getTarget(), binder.getObjectName()), request);
    }
}
src/main/java/com/dji/sample/configuration/mvc/GetSnakeDataBinder.java
New file
@@ -0,0 +1,53 @@
package com.dji.sample.configuration.mvc;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;
import javax.servlet.ServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
public class GetSnakeDataBinder extends ExtendedServletRequestDataBinder {
    public GetSnakeDataBinder(Object target, String objectName) {
        super(target, objectName);
    }
    @Override
    protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
        List<PropertyValue> propertyValueList = mpvs.getPropertyValueList();
        List<PropertyValue> values = new ArrayList<>(propertyValueList);
        for (PropertyValue property : values) {
            String name = convertSnake(property.getName());
            if (!property.getName().equals(name)) {
                mpvs.addPropertyValue(new PropertyValue(name, property.getValue()));
                propertyValueList.remove(property);
            }
        }
        super.addBindValues(mpvs, request);
    }
    private String convertSnake(String key) {
        StringBuilder sb = new StringBuilder();
        boolean isChange = false;
        for (char c : key.toCharArray()) {
            if (c == '_') {
                isChange = true;
                continue;
            }
            if (isChange) {
                sb.append((char) (c - 32));
                isChange = false;
                continue;
            }
            sb.append(c);
        }
        return sb.toString();
    }
}
src/main/java/com/dji/sample/configuration/mvc/GlobalMVCConfigurer.java
File was renamed from src/main/java/com/dji/sample/configuration/GlobalMVCConfigurer.java
@@ -1,9 +1,10 @@
package com.dji.sample.configuration;
package com.dji.sample.configuration.mvc;
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.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -24,6 +25,7 @@
    @Value("${url.manage.version}")
    private String manageVersion;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Exclude the login interface.
@@ -32,4 +34,9 @@
        // Intercept for all request interfaces.
        registry.addInterceptor(authInterceptor).addPathPatterns("/**").excludePathPatterns(excludePaths);
    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new GetSnakeArgumentProcessor(true));
    }
}
src/main/java/com/dji/sample/control/controller/DockController.java
New file
@@ -0,0 +1,30 @@
package com.dji.sample.control.controller;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.control.service.IControlService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author sean
 * @version 1.2
 * @date 2022/7/29
 */
@RestController
@Slf4j
@RequestMapping("${url.control.prefix}${url.control.version}/devices")
public class DockController {
    @Autowired
    private IControlService controlService;
    @PostMapping("/{sn}/jobs/{service_identifier}")
    public ResponseResult createControlJob(@PathVariable String sn,
                                           @PathVariable("service_identifier") String serviceIdentifier) {
        return controlService.controlDock(sn, serviceIdentifier);
    }
}
src/main/java/com/dji/sample/control/service/IControlService.java
New file
@@ -0,0 +1,18 @@
package com.dji.sample.control.service;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import org.springframework.messaging.MessageHeaders;
/**
 * @author sean
 * @version 1.2
 * @date 2022/7/29
 */
public interface IControlService {
    ResponseResult controlDock(String sn, String serviceIdentifier);
    void handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers);
}
src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java
New file
@@ -0,0 +1,133 @@
package com.dji.sample.control.service.impl;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.control.service.IControlService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.UUID;
/**
 * @author sean
 * @version 1.2
 * @date 2022/7/29
 */
@Service
@Slf4j
public class ControlServiceImpl implements IControlService {
    @Autowired
    private RedisOpsUtils redisOps;
    @Autowired
    private IMessageSenderService messageSenderService;
    @Autowired
    private ISendMessageService webSocketMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Autowired
    private ObjectMapper mapper;
    @Override
    public ResponseResult controlDock(String sn, String serviceIdentifier) {
        ServicesMethodEnum servicesMethodEnum = ServicesMethodEnum.find(serviceIdentifier);
        if (servicesMethodEnum == ServicesMethodEnum.UNKNOWN) {
            return ResponseResult.error("The " + serviceIdentifier + " method does not exist.");
        }
        boolean isExist = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + sn) > 0;
        if (!isExist) {
            return ResponseResult.error("The dock is offline.");
        }
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + sn + TopicConst.SERVICES_SUF;
        String bid = UUID.randomUUID().toString();
        Optional<ServiceReply> serviceReplyOpt = messageSenderService.publishWithReply(
                topic, CommonTopicResponse.builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .method(serviceIdentifier)
                        .timestamp(System.currentTimeMillis())
                        .data("")
                        .build());
        if (serviceReplyOpt.isEmpty()) {
            return ResponseResult.error("No message reply received.");
        }
        ServiceReply<EventsOutputReceiver> serviceReply = mapper.convertValue(
                serviceReplyOpt.get(), new TypeReference<ServiceReply<EventsOutputReceiver>>() {});
        if (serviceReply.getResult() != ResponseResult.CODE_SUCCESS) {
            return ResponseResult.error(serviceReply.getResult(), serviceReply.getOutput().getStatus());
        }
        if (servicesMethodEnum.getProgress()) {
            redisOps.setWithExpire(serviceIdentifier + RedisConst.DELIMITER +  bid, sn,
                    RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND);
        }
        return ResponseResult.success();
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS, outputChannel = ChannelName.OUTBOUND)
    public void handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String key = receiver.getMethod() + RedisConst.DELIMITER + receiver.getBid();
        if (redisOps.getExpire(key) <= 0) {
            return;
        }
        String sn = redisOps.get(key).toString();
        EventsReceiver<EventsOutputReceiver> eventsReceiver = mapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<EventsOutputReceiver>>(){});
        eventsReceiver.setBid(receiver.getBid());
        eventsReceiver.setSn(sn);
        log.info("SN: {}, {} ===> Control progress: {}",
                sn, receiver.getMethod(), eventsReceiver.getOutput().getProgress().toString());
        if (eventsReceiver.getResult() != ResponseResult.CODE_SUCCESS) {
            log.error("SN: {}, {} ===> Error code: {}", sn, receiver.getMethod(), eventsReceiver.getResult());
        }
        if (eventsReceiver.getOutput().getProgress().getPercent() == 100 ||
                EventsResultStatusEnum.find(eventsReceiver.getOutput().getStatus()).getEnd()) {
            redisOps.del(key);
        }
        DeviceDTO device = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        webSocketMessageService.sendBatch(
                webSocketManageService.getValueWithWorkspaceAndUserType(
                        device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .data(eventsReceiver)
                        .timestamp(System.currentTimeMillis())
                        .bizCode(receiver.getMethod())
                        .build());
        if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) {
            String topic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF;
            messageSenderService.publish(topic,
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .method(receiver.getMethod())
                            .timestamp(System.currentTimeMillis())
                            .data(ResponseResult.success())
                            .build());
        }
    }
}
src/main/java/com/dji/sample/manage/controller/DeviceController.java
@@ -7,6 +7,7 @@
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO;
import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver;
import com.dji.sample.manage.model.receiver.StatusGatewayReceiver;
import com.dji.sample.manage.service.IDeviceService;
@@ -84,6 +85,10 @@
        return ResponseResult.success(devicesList);
    }
    /**
     * Handle osd topic messages.
     * @param message
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_OSD)
    public void osdRealTime(Message<?> message) {
        String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
@@ -91,11 +96,21 @@
        deviceService.handleOSD(topic, payload);
    }
    /**
     * Receive the reported firmware version data.
     * @param receiver
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_FIRMWARE_VERSION)
    public void updateFirmwareVersion(FirmwareVersionReceiver receiver) {
        deviceService.updateFirmwareVersion(receiver);
    }
    /**
     * After binding the device to the workspace, the device data can only be seen on the web.
     * @param device
     * @param deviceSn
     * @return
     */
    @PostMapping("/{device_sn}/binding")
    public ResponseResult bindDevice(@RequestBody DeviceDTO device, @PathVariable("device_sn") String deviceSn) {
        device.setDeviceSn(deviceSn);
@@ -103,6 +118,12 @@
        return isUpd ? ResponseResult.success() : ResponseResult.error();
    }
    /**
     * Obtain device information according to device sn.
     * @param workspaceId
     * @param deviceSn
     * @return
     */
    @GetMapping("/{workspace_id}/devices/{device_sn}")
    public ResponseResult getDevice(@PathVariable("workspace_id") String workspaceId,
                                               @PathVariable("device_sn") String deviceSn) {
@@ -127,12 +148,24 @@
        return ResponseResult.success(devices);
    }
    /**
     * Removing the binding state of the device.
     * @param deviceSn
     * @return
     */
    @DeleteMapping("/{device_sn}/unbinding")
    public ResponseResult unbindingDevice(@PathVariable("device_sn") String deviceSn) {
        deviceService.unbindDevice(deviceSn);
        return ResponseResult.success();
    }
    /**
     * Update device information.
     * @param device
     * @param workspaceId
     * @param deviceSn
     * @return
     */
    @PutMapping("/{workspace_id}/devices/{device_sn}")
    public ResponseResult updateDevice(@RequestBody DeviceDTO device,
                                       @PathVariable("workspace_id") String workspaceId,
@@ -141,4 +174,16 @@
        boolean isUpd = deviceService.updateDevice(device);
        return isUpd ? ResponseResult.success() : ResponseResult.error();
    }
    /**
     * Delivers offline firmware upgrade tasks.
     * @param workspaceId
     * @param upgradeDTOS
     * @return
     */
    @PostMapping("/{workspace_id}/devices/ota")
    public ResponseResult createOtaJob(@PathVariable("workspace_id") String workspaceId,
                                       @RequestBody List<DeviceFirmwareUpgradeDTO> upgradeDTOS) {
        return deviceService.createDeviceOtaJob(workspaceId, upgradeDTOS);
    }
}
src/main/java/com/dji/sample/manage/controller/DeviceFirmwareController.java
New file
@@ -0,0 +1,43 @@
package com.dji.sample.manage.controller;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.DeviceFirmwareNoteDTO;
import com.dji.sample.manage.service.IDeviceFirmwareService;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/16
 */
@RestController
@RequestMapping("${url.manage.prefix}${url.manage.version}/workspaces")
public class DeviceFirmwareController {
    @Autowired
    private IDeviceFirmwareService service;
    /**
     * Get the latest firmware version information for this device model.
     * @param deviceNames
     * @return
     */
    @GetMapping("/firmware-release-notes/latest")
    public ResponseResult<List<DeviceFirmwareNoteDTO>> getLatestFirmwareNote(@RequestParam("device_name") List<String> deviceNames) {
        List<DeviceFirmwareNoteDTO> releaseNotes = new ArrayList<>();
        deviceNames.forEach(deviceName -> {
            Optional<DeviceFirmwareNoteDTO> latestFirmware = service.getLatestFirmwareReleaseNote(deviceName);
            latestFirmware.ifPresent(releaseNotes::add);
        });
        return ResponseResult.success(releaseNotes);
    }
}
src/main/java/com/dji/sample/manage/controller/DeviceHmsController.java
@@ -27,20 +27,36 @@
    @Autowired
    private IDeviceHmsService deviceHmsService;
    /**
     * Page to query the hms information of the device.
     * @param param
     * @param workspaceId
     * @return
     */
    @GetMapping("/{workspace_id}/devices/hms")
    public ResponseResult<PaginationData<DeviceHmsDTO>> getBoundDevicesWithDomain(DeviceHmsQueryParam param,
    public ResponseResult<PaginationData<DeviceHmsDTO>> getHmsInformation(DeviceHmsQueryParam param,
                                                          @PathVariable("workspace_id") String workspaceId) {
        PaginationData<DeviceHmsDTO> devices = deviceHmsService.getDeviceHmsByParam(param);
        return ResponseResult.success(devices);
    }
    /**
     * Update unread hms messages to read status.
     * @param deviceSn
     * @return
     */
    @PutMapping("/{workspace_id}/devices/hms/{device_sn}")
    public ResponseResult updateReadHmsByDeviceSn(@PathVariable("device_sn") String deviceSn) {
        deviceHmsService.updateUnreadHms(deviceSn);
        return ResponseResult.success();
    }
    /**
     * Get hms messages for a single device.
     * @param deviceSn
     * @return
     */
    @GetMapping("/{workspace_id}/devices/hms/{device_sn}")
    public ResponseResult<List<DeviceHmsDTO>> getUnreadHmsByDeviceSn(@PathVariable("device_sn") String deviceSn) {
        PaginationData<DeviceHmsDTO> paginationData = deviceHmsService.getDeviceHmsByParam(
src/main/java/com/dji/sample/manage/controller/DeviceLogsController.java
New file
@@ -0,0 +1,122 @@
package com.dji.sample.manage.controller;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.manage.model.dto.DeviceLogsDTO;
import com.dji.sample.manage.model.param.DeviceLogsCreateParam;
import com.dji.sample.manage.model.param.DeviceLogsQueryParam;
import com.dji.sample.manage.model.param.LogsFileUpdateParam;
import com.dji.sample.manage.service.IDeviceLogsService;
import lombok.extern.slf4j.Slf4j;
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.net.URL;
import java.util.List;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
@RestController
@Slf4j
@RequestMapping("${url.manage.prefix}${url.manage.version}/workspaces")
public class DeviceLogsController {
    @Autowired
    private IDeviceLogsService deviceLogsService;
    /**
     * Obtain the device upload log list by paging according to the query parameters.
     * @param workspaceId
     * @param deviceSn
     * @param param
     * @return
     */
    @GetMapping("/{workspace_id}/devices/{device_sn}/logs-uploaded")
    public ResponseResult getUploadedLogs(DeviceLogsQueryParam param, @PathVariable("workspace_id") String workspaceId,
                                          @PathVariable("device_sn") String deviceSn) {
        PaginationData<DeviceLogsDTO> data = deviceLogsService.getUploadedLogs(deviceSn, param);
        return ResponseResult.success(data);
    }
    /**
     * Get a list of log files that can be uploaded in real time.
     * @param workspaceId
     * @param deviceSn
     * @param domainList
     * @return
     */
    @GetMapping("/{workspace_id}/devices/{device_sn}/logs")
    public ResponseResult getLogsBySn(@PathVariable("workspace_id") String workspaceId,
                                      @PathVariable("device_sn") String deviceSn,
                                      @RequestParam("domain_list") List<String> domainList) {
        return deviceLogsService.getRealTimeLogs(deviceSn, domainList);
    }
    /**
     * Initiate a log upload request to the gateway.
     * @return
     */
    @PostMapping("/{workspace_id}/devices/{device_sn}/logs")
    public ResponseResult uploadLogs(@PathVariable("workspace_id") String workspaceId,
                                     @PathVariable("device_sn") String deviceSn,
                                     HttpServletRequest request, @RequestBody DeviceLogsCreateParam param) {
        CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
        return deviceLogsService.pushFileUpload(customClaim.getUsername(), deviceSn, param);
    }
    /**
     * Cancel logs file upload.
     * @return
     */
    @DeleteMapping("/{workspace_id}/devices/{device_sn}/logs")
    public ResponseResult cancelUploadedLogs(@PathVariable("workspace_id") String workspaceId,
                                             @PathVariable("device_sn") String deviceSn,
                                             @RequestBody LogsFileUpdateParam param) {
        return deviceLogsService.pushUpdateFile(deviceSn, param);
    }
    /**
     * Delete upload history.
     * @return
     */
    @DeleteMapping("/{workspace_id}/devices/{device_sn}/logs/{logs_id}")
    public ResponseResult deleteUploadedLogs(@PathVariable("workspace_id") String workspaceId,
                                             @PathVariable("device_sn") String deviceSn,
                                             @PathVariable("logs_id") String logsId) {
        deviceLogsService.deleteLogs(deviceSn, logsId);
        return ResponseResult.success();
    }
    /**
     * Query the download address of the file according to the wayline file id,
     * and redirect to this address directly for download.
     * @param workspaceId
     * @param fileId
     * @param logsId
     * @param response
     */
    @GetMapping("/{workspace_id}/logs/{logs_id}/url/{file_id}")
    public ResponseResult getFileUrl(@PathVariable(name = "workspace_id") String workspaceId,
                            @PathVariable(name = "file_id") String fileId,
                           @PathVariable(name = "logs_id") String logsId, HttpServletResponse response) {
        try {
            URL url = deviceLogsService.getLogsFileUrl(logsId, fileId);
            return ResponseResult.success(url.toString());
        } catch (Exception e) {
            log.error("Failed to get the logs file download address.");
            e.printStackTrace();
        }
        return ResponseResult.error("Failed to get the logs file download address.");
    }
}
src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java
@@ -3,12 +3,10 @@
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.manage.model.receiver.DeviceBasicReceiver;
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.messaging.MessageHeaders;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author sean.zhou
@@ -16,7 +14,6 @@
 * @version 0.1
 */
@RestController
@Slf4j
public class DevicePayloadController {
    @Autowired
@@ -28,17 +25,11 @@
     * 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
     * @param headers
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC)
    public void stateBasic(DeviceBasicReceiver deviceBasic) {
        // Delete all payload information for the drone based on the drone's sn.
        devicePayloadService.deletePayloadsByDeviceSn(List.of(deviceBasic.getDeviceSn()));
    public void stateBasic(DeviceBasicReceiver deviceBasic, MessageHeaders headers) {
        // Save the new payload information.
        boolean isSave = devicePayloadService.savePayloadDTOs(deviceBasic.getPayloads());
        log.debug("The result of saving the payloads is {}.", isSave);
        devicePayloadService.saveDeviceBasicPayload(deviceBasic.getPayloads(), headers.getTimestamp());
    }
}
src/main/java/com/dji/sample/manage/controller/LiveStreamController.java
@@ -2,24 +2,19 @@
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.Chan;
import com.dji.sample.component.mqtt.model.ChannelName;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.component.mqtt.model.ServiceReply;
import com.dji.sample.manage.model.dto.CapacityDeviceDTO;
import com.dji.sample.manage.model.dto.LiveTypeDTO;
import com.dji.sample.manage.model.receiver.LiveCapacityReceiver;
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.messaging.MessageHeaders;
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;
@@ -47,8 +42,8 @@
     * @param liveCapacity    the capacity of drone and dock
     */
    @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_CAPACITY)
    public void stateCapacity(LiveCapacityReceiver liveCapacity) {
        liveStreamService.saveLiveCapacity(liveCapacity);
    public void stateCapacity(LiveCapacityReceiver liveCapacity, MessageHeaders headers) {
        liveStreamService.saveLiveCapacity(liveCapacity, headers.getTimestamp());
    }
    /**
@@ -96,19 +91,4 @@
        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();
        CommonTopicReceiver<ServiceReply> receiver = mapper.readValue(payload,
                new TypeReference<CommonTopicReceiver<ServiceReply>>() {
        });
        Chan<CommonTopicReceiver> chan = Chan.getInstance();
        // Put the message to the chan object.
        chan.put(receiver);
    }
}
src/main/java/com/dji/sample/manage/dao/IDeviceFirmwareMapper.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.DeviceFirmwareEntity;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/16
 */
public interface IDeviceFirmwareMapper extends BaseMapper<DeviceFirmwareEntity> {
}
src/main/java/com/dji/sample/manage/dao/IDeviceLogsMapper.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.DeviceLogsEntity;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
public interface IDeviceLogsMapper extends BaseMapper<DeviceLogsEntity> {
}
src/main/java/com/dji/sample/manage/dao/ILogsFileIndexMapper.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.LogsFileIndexEntity;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/8
 */
public interface ILogsFileIndexMapper extends BaseMapper<LogsFileIndexEntity> {
}
src/main/java/com/dji/sample/manage/dao/ILogsFileMapper.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.LogsFileEntity;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
public interface ILogsFileMapper extends BaseMapper<LogsFileEntity> {
}
src/main/java/com/dji/sample/manage/model/common/HmsJsonUtil.java
@@ -44,6 +44,9 @@
    }
    public static HmsMessage get(String key) {
        if (nodes.get(key) == null) {
            return new HmsMessage();
        }
        return mapper.convertValue(nodes.get(key), HmsMessage.class);
    }
}
src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java
@@ -60,4 +60,8 @@
    private String workspaceName;
    private DeviceDTO children;
    private Integer firmwareStatus;
    private Integer firmwareProgress;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceFirmwareDTO.java
New file
@@ -0,0 +1,40 @@
package com.dji.sample.manage.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/16
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeviceFirmwareDTO {
    private String firmwareId;
    private String fileName;
    private String productVersion;
    private String fileUrl;
    private Long fileSize;
    private String fileMd5;
    private String deviceName;
    private String releaseNote;
    private LocalDate releasedTime;
    private Boolean firmwareStatus;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceFirmwareNoteDTO.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;
import java.time.LocalDate;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/16
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DeviceFirmwareNoteDTO {
    private String deviceName;
    private String productVersion;
    private String releaseNote;
    private LocalDate releasedTime;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceFirmwareUpgradeDTO.java
New file
@@ -0,0 +1,20 @@
package com.dji.sample.manage.model.dto;
import lombok.Data;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/16
 */
@Data
public class DeviceFirmwareUpgradeDTO {
    private String deviceName;
    private String sn;
    private String productVersion;
    private Integer firmwareUpgradeType;
}
src/main/java/com/dji/sample/manage/model/dto/DeviceLogsDTO.java
New file
@@ -0,0 +1,41 @@
package com.dji.sample.manage.model.dto;
import com.dji.sample.manage.model.receiver.LogsFileUploadList;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DeviceLogsDTO {
    private String logsId;
    private LocalDateTime happenTime;
    private String userName;
    private String logsInformation;
    private LocalDateTime createTime;
    private Integer status;
    private TopologyDTO deviceTopo;
    private List<LogsProgressDTO> logsProgress;
    private LogsFileUploadList deviceLogs;
}
src/main/java/com/dji/sample/manage/model/dto/LogsFileDTO.java
New file
@@ -0,0 +1,35 @@
package com.dji.sample.manage.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LogsFileDTO {
    private String fileId;
    private String name;
    private Long size;
    private String logsId;
    private String deviceSn;
    private String fingerprint;
    private String objectKey;
    private Boolean status;
}
src/main/java/com/dji/sample/manage/model/dto/LogsOutputProgressDTO.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
 * @version 1.2
 * @date 2022/9/9
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LogsOutputProgressDTO {
    private String logsId;
    private String status;
    private List<LogsProgressDTO> files;
}
src/main/java/com/dji/sample/manage/model/dto/LogsProgressDTO.java
New file
@@ -0,0 +1,31 @@
package com.dji.sample.manage.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LogsProgressDTO {
    private String deviceSn;
    private String deviceModelDomain;
    private Integer progress;
    private Integer result;
    private Float uploadRate;
    private String status;
}
src/main/java/com/dji/sample/manage/model/dto/LogsUploadCredentialsDTO.java
New file
@@ -0,0 +1,47 @@
package com.dji.sample.manage.model.dto;
import com.dji.sample.manage.model.receiver.LogsFileUploadList;
import com.dji.sample.media.model.CredentialsDTO;
import com.dji.sample.media.model.StsCredentialsDTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/8
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LogsUploadCredentialsDTO {
    private String bucket;
    private CredentialsDTO credentials;
    private String endpoint;
    @JsonProperty("file_store_dir")
    private String objectKeyPrefix;
    private String provider;
    private String fileType = "text_log";
    private LogsFileUploadList params;
    public LogsUploadCredentialsDTO(StsCredentialsDTO sts) {
        this.bucket = sts.getBucket();
        Long expire = sts.getCredentials().getExpire();
        sts.getCredentials().setExpire(System.currentTimeMillis() + (expire - 60) * 1000);
        this.credentials = sts.getCredentials();
        this.endpoint = sts.getEndpoint();
        this.objectKeyPrefix = sts.getObjectKeyPrefix();
        this.provider = sts.getProvider();
    }
}
src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java
@@ -76,6 +76,9 @@
    @TableField(value = "firmware_version")
    private String firmwareVersion;
    @TableField(value = "compatible_status")
    private Boolean compatibleStatus;
    @TableField(value = "bound_status")
    private Boolean boundStatus;
src/main/java/com/dji/sample/manage/model/entity/DeviceFirmwareEntity.java
New file
@@ -0,0 +1,64 @@
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
 * @version 1.2
 * @date 2022/8/16
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName("manage_device_firmware")
public class DeviceFirmwareEntity implements Serializable {
    private static final long serialVersionUID = -12L;
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("firmware_id")
    private String firmwareId;
    @TableField("file_name")
    private String fileName;
    @TableField("firmware_version")
    private String firmwareVersion;
    @TableField("file_url")
    private String fileUrl;
    @TableField("file_size")
    private Long fileSize;
    @TableField("file_md5")
    private String fileMd5;
    @TableField("device_name")
    private String deviceName;
    @TableField("release_note")
    private String releaseNote;
    @TableField("release_date")
    private Long releaseDate;
    @TableField("status")
    private Boolean status;
    @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/DeviceHmsEntity.java
@@ -23,6 +23,8 @@
@TableName(value = "manage_device_hms")
public class DeviceHmsEntity implements Serializable, Cloneable {
    private static final long serialVersionUID = -12L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
src/main/java/com/dji/sample/manage/model/entity/DeviceLogsEntity.java
New file
@@ -0,0 +1,52 @@
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
 * @version 1.2
 * @date 2022/9/7
 */
@TableName("manage_device_logs")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DeviceLogsEntity implements Serializable {
    private static final long serialVersionUID = -12L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @TableField("logs_id")
    private String logsId;
    @TableField("username")
    private String username;
    @TableField("logs_info")
    private String logsInfo;
    @TableField("device_sn")
    private String deviceSn;
    @TableField("happen_time")
    private Long happenTime;
    @TableField("status")
    private Integer status;
    @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/LogsFileEntity.java
New file
@@ -0,0 +1,58 @@
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
 * @version 1.2
 * @date 2022/9/7
 */
@TableName("logs_file")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LogsFileEntity implements Serializable {
    private static final long serialVersionUID = -12L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @TableField("file_id")
    private String fileId;
    @TableField("name")
    private String name;
    @TableField("size")
    private Long size;
    @TableField("logs_id")
    private String logsId;
    @TableField("device_sn")
    private String deviceSn;
    @TableField("fingerprint")
    private String fingerprint;
    @TableField("object_key")
    private String objectKey;
    @TableField("status")
    private Boolean status;
    @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/LogsFileIndexEntity.java
New file
@@ -0,0 +1,54 @@
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
 * @version 1.2
 * @date 2022/9/8
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName("logs_file_index")
public class LogsFileIndexEntity implements Serializable {
    private static final long serialVersionUID = -12L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @TableField("boot_index")
    private Integer bootIndex;
    @TableField("file_id")
    private String fileId;
    @TableField("start_time")
    private Long startTime;
    @TableField("end_time")
    private Long endTime;
    @TableField("size")
    private Long size;
    @TableField("device_sn")
    private String deviceSn;
    @TableField("domain")
    private Integer domain;
    @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/DeviceFirmwareStatusEnum.java
New file
@@ -0,0 +1,62 @@
package com.dji.sample.manage.model.enums;
import lombok.Getter;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/15
 */
@Getter
public enum DeviceFirmwareStatusEnum {
    /**
     * no need to upgrade
     */
    NOT_UPGRADE(1),
    /**
     *  to upgraded
     */
    NORMAL_UPGRADE(2),
    /**
     * A consistency upgrade is required.
     */
    CONSISTENT_UPGRADE(3),
    /**
     * during upgrade
     */
    UPGRADING(4),
    UNKNOWN(-1);
    int val;
    DeviceFirmwareStatusEnum(int val) {
        this.val = val;
    }
    public static DeviceFirmwareStatusEnum find(int val) {
        return Arrays.stream(DeviceFirmwareStatusEnum.values())
                .filter(firmwareStatus -> firmwareStatus.val == val)
                .findFirst().orElse(UNKNOWN);
    }
    @Getter
    public enum CompatibleStatusEnum {
        INCONSISTENT(1),
        CONSISTENT(0);
        int val;
        CompatibleStatusEnum(int val) {
            this.val = val;
        }
    }
}
src/main/java/com/dji/sample/manage/model/enums/DeviceLogsStatusEnum.java
New file
@@ -0,0 +1,41 @@
package com.dji.sample.manage.model.enums;
import com.dji.sample.component.mqtt.model.EventsResultStatusEnum;
import lombok.Getter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/8
 */
@Getter
public enum DeviceLogsStatusEnum {
    UPLOADING(1, EventsResultStatusEnum.IN_PROGRESS),
    DONE(2, EventsResultStatusEnum.OK),
    CANCELED(3, EventsResultStatusEnum.CANCELED),
    FAILED(4, EventsResultStatusEnum.FAILED, EventsResultStatusEnum.REJECTED, EventsResultStatusEnum.TIMEOUT),
    UNKNOWN(-1);
    int val;
    HashSet<EventsResultStatusEnum> status;
    DeviceLogsStatusEnum(int val, EventsResultStatusEnum... status) {
        this.status = new HashSet<>();
        Collections.addAll(this.status, status);
        this.val = val;
    }
    public static DeviceLogsStatusEnum find(EventsResultStatusEnum status) {
        return Arrays.stream(DeviceLogsStatusEnum.values()).filter(element -> element.status.contains(status)).findAny().orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java
@@ -54,6 +54,8 @@
    @Getter
    public enum HmsFaqIdEnum {
        DOCK_TIP("dock_tip_"),
        FPV_TIP("fpv_tip_");
        private String text;
@@ -152,4 +154,38 @@
                    .orElse(UNKNOWN);
        }
    }
    @Getter
    public enum FormatKeyEnum {
        ALARM_ID("alarmid", 0),
        COMPONENT_INDEX("component_index", 1),
        INDEX("index", 2),
        BATTERY_INDEX("battery_index", 3),
        DOCK_COVER_INDEX("dock_cover_index", 4),
        CHARGING_ROD_INDEX("charging_rod_index", 5),
        UNKNOWN("unknown", -1);
        public static final char KEY_START = '%';
        String key;
        int index;
        FormatKeyEnum(String key, int index) {
            this.key = key;
            this.index = index;
        }
        public static FormatKeyEnum find(String key) {
            return Arrays.stream(FormatKeyEnum.values())
                    .filter(format -> format.getKey().equals(key))
                    .findAny().orElse(UNKNOWN);
        }
    }
}
src/main/java/com/dji/sample/manage/model/enums/LogsFileUpdateMethodEnum.java
New file
@@ -0,0 +1,28 @@
package com.dji.sample.manage.model.enums;
import lombok.Getter;
import java.util.Arrays;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
@Getter
public enum LogsFileUpdateMethodEnum {
    CANCEL("cancel"),
    UNKNOWN("unknown");
    String method;
    LogsFileUpdateMethodEnum(String method) {
        this.method = method;
    }
    public static LogsFileUpdateMethodEnum find(String method) {
        return Arrays.stream(LogsFileUpdateMethodEnum.values()).filter(e -> e.method.equals(method)).findAny().orElse(UNKNOWN);
    }
}
src/main/java/com/dji/sample/manage/model/param/DeviceHmsQueryParam.java
@@ -1,12 +1,10 @@
package com.dji.sample.manage.model.param;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Set;
/**
@@ -18,15 +16,12 @@
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DeviceHmsQueryParam implements Serializable {
public class DeviceHmsQueryParam {
    @JsonProperty("device_sn")
    private Set<String> deviceSn;
    @JsonProperty("begin_time")
    private Long beginTime;
    @JsonProperty("end_time")
    private Long endTime;
    private String language;
@@ -35,11 +30,9 @@
    private Long page;
    @JsonProperty("page_size")
    private Long pageSize;
    private Integer level;
    @JsonProperty("update_time")
    private Long updateTime;
}
src/main/java/com/dji/sample/manage/model/param/DeviceLogsCreateParam.java
New file
@@ -0,0 +1,21 @@
package com.dji.sample.manage.model.param;
import com.dji.sample.manage.model.receiver.LogsFileUpload;
import lombok.Data;
import java.util.List;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/8
 */
@Data
public class DeviceLogsCreateParam {
    private String logsInformation;
    private Long happenTime;
    private List<LogsFileUpload> files;
}
src/main/java/com/dji/sample/manage/model/param/DeviceLogsQueryParam.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.manage.model.param;
import lombok.Data;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
@Data
public class DeviceLogsQueryParam {
    private Long page;
    private Long pageSize;
    private Integer status;
    private Long beginTime;
    private Long endTime;
    private String logsInformation;
}
src/main/java/com/dji/sample/manage/model/param/DeviceOtaCreateParam.java
New file
@@ -0,0 +1,32 @@
package com.dji.sample.manage.model.param;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/16
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DeviceOtaCreateParam {
    private String sn;
    private String productVersion;
    private String fileUrl;
    private String md5;
    private Long fileSize;
    private Integer firmwareUpgradeType;
    private String fileName;
}
src/main/java/com/dji/sample/manage/model/param/LogsFileUpdateParam.java
New file
@@ -0,0 +1,21 @@
package com.dji.sample.manage.model.param;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
@Data
public class LogsFileUpdateParam {
    private String status;
    @JsonProperty("module_list")
    private List<String> deviceModelDomainList;
}
src/main/java/com/dji/sample/manage/model/receiver/LogsExtFileReceiver.java
New file
@@ -0,0 +1,27 @@
package com.dji.sample.manage.model.receiver;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
@Data
public class LogsExtFileReceiver {
    @JsonProperty("module")
    private String deviceModelDomain;
    private Long size;
    private String deviceSn;
    private String key;
    private String fingerprint;
    private LogsProgressReceiver progress;
}
src/main/java/com/dji/sample/manage/model/receiver/LogsFile.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.manage.model.receiver;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LogsFile {
    private Integer bootIndex;
    private Long endTime;
    private Long startTime;
    private Long size;
}
src/main/java/com/dji/sample/manage/model/receiver/LogsFileUpload.java
New file
@@ -0,0 +1,34 @@
package com.dji.sample.manage.model.receiver;
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 1.2
 * @date 2022/9/7
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LogsFileUpload {
    private String deviceSn;
    private List<LogsFile> list;
    @JsonProperty("module")
    private String deviceModelDomain;
    private String objectKey;
    private Integer result;
    private String fileId;
}
src/main/java/com/dji/sample/manage/model/receiver/LogsFileUploadList.java
New file
@@ -0,0 +1,24 @@
package com.dji.sample.manage.model.receiver;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LogsFileUploadList {
    private List<LogsFileUpload> files;
    private Integer result;
}
src/main/java/com/dji/sample/manage/model/receiver/LogsProgressReceiver.java
New file
@@ -0,0 +1,26 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
@Data
public class LogsProgressReceiver {
    private Integer currentStep;
    private Integer totalStep;
    private Integer progress;
    private Long finishTime;
    private Float uploadRate;
    private String status;
    private Integer result;
}
src/main/java/com/dji/sample/manage/model/receiver/OutputLogsExtReceiver.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
import java.util.List;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
@Data
public class OutputLogsExtReceiver {
    private List<LogsExtFileReceiver> files;
}
src/main/java/com/dji/sample/manage/model/receiver/OutputLogsProgressReceiver.java
New file
@@ -0,0 +1,16 @@
package com.dji.sample.manage.model.receiver;
import lombok.Data;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/9
 */
@Data
public class OutputLogsProgressReceiver {
    private OutputLogsExtReceiver ext;
    private String status;
}
src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java
@@ -30,8 +30,9 @@
     * Save the live capability data of the device.
     * @param capacityCameraReceivers
     * @param deviceSn
     * @param timestamp
     */
    void saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn);
    void saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn, Long timestamp);
    /**
     *  Convert the received camera capability object into camera data transfer object.
src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java
New file
@@ -0,0 +1,48 @@
package com.dji.sample.manage.service;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.dto.DeviceFirmwareDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareNoteDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO;
import com.dji.sample.manage.model.param.DeviceOtaCreateParam;
import org.springframework.messaging.MessageHeaders;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/16
 */
public interface IDeviceFirmwareService {
    /**
     * Query specific firmware information based on the device model and firmware version.
     * @param deviceName
     * @param version
     * @return
     */
    Optional<DeviceFirmwareDTO> getFirmware(String deviceName, String version);
    /**
     * Get the latest firmware release note for this device model.
     * @param deviceName
     * @return
     */
    Optional<DeviceFirmwareNoteDTO> getLatestFirmwareReleaseNote(String deviceName);
    /**
     * Get the firmware information that the device needs to update.
     * @param upgradeDTOS
     * @return
     */
    List<DeviceOtaCreateParam> getDeviceOtaFirmware(List<DeviceFirmwareUpgradeDTO> upgradeDTOS);
    /**
     * Interface to handle device firmware update progress.
     * @param receiver
     * @param headers
     */
    void handleOtaProgress(CommonTopicReceiver receiver, MessageHeaders headers);
}
src/main/java/com/dji/sample/manage/service/IDeviceHmsService.java
@@ -13,9 +13,23 @@
 */
public interface IDeviceHmsService {
    /**
     * Handle hms messages.
     * @param receiver
     * @param headers
     */
    void handleHms(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Query hms data by paging according to query parameters.
     * @param param
     * @return
     */
    PaginationData<DeviceHmsDTO> getDeviceHmsByParam(DeviceHmsQueryParam param);
    /**
     * Read message handling.
     * @param deviceSn
     */
    void updateUnreadHms(String deviceSn);
}
src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java
New file
@@ -0,0 +1,94 @@
package com.dji.sample.manage.service;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.CommonTopicReceiver;
import com.dji.sample.manage.model.dto.DeviceLogsDTO;
import com.dji.sample.manage.model.param.DeviceLogsCreateParam;
import com.dji.sample.manage.model.param.DeviceLogsQueryParam;
import com.dji.sample.manage.model.param.LogsFileUpdateParam;
import org.springframework.messaging.MessageHeaders;
import java.net.URL;
import java.util.List;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
public interface IDeviceLogsService {
    /**
     * Obtain the device upload log list by paging according to the query parameters.
     * @param deviceSn
     * @param param
     * @return
     */
    PaginationData<DeviceLogsDTO> getUploadedLogs(String deviceSn, DeviceLogsQueryParam param);
    /**
     * Get a list of log files that can be uploaded in real time.
     * @param deviceSn
     * @param domainList
     * @return
     */
    ResponseResult getRealTimeLogs(String deviceSn, List<String> domainList);
    /**
     * Add device logs.
     *
     * @param bid
     * @param username
     * @param deviceSn
     * @param param
     * @return logs id
     */
    String insertDeviceLogs(String bid, String username, String deviceSn, DeviceLogsCreateParam param);
    /**
     * Initiate a log upload request to the gateway.
     * @param username
     * @param deviceSn
     * @param param
     * @return
     */
    ResponseResult pushFileUpload(String username, String deviceSn, DeviceLogsCreateParam param);
    /**
     * Push a request to modify the  status of the logs file.
     * @param deviceSn
     * @param param
     * @return
     */
    ResponseResult pushUpdateFile(String deviceSn, LogsFileUpdateParam param);
    /**
     * Delete log records.
     * @param deviceSn
     * @param logsId
     */
    void deleteLogs(String deviceSn, String logsId);
    /**
     * Handle logs file upload progress.
     * @param receiver
     * @param headers
     */
    void handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers);
    /**
     * Update status, which is updated when the logs upload succeeds or fails.
     * @param logsId
     * @param value
     */
    void updateLogsStatus(String logsId, Integer value);
    /**
     * Get the file address.
     * @param logsId
     * @param fileId
     * @return
     */
    URL getLogsFileUrl(String logsId, String fileId);
}
src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java
@@ -4,6 +4,7 @@
import com.dji.sample.manage.model.receiver.DevicePayloadReceiver;
import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver;
import java.util.Collection;
import java.util.List;
/**
@@ -52,4 +53,17 @@
     * @param receiver
     */
    void updateFirmwareVersion(FirmwareVersionReceiver receiver);
    /**
     * Handle the topic that contains the payloads field in the state, and save the payloads data.
     * @param payloadReceiverList
     * @param timestamp
     */
    void saveDeviceBasicPayload(List<DevicePayloadReceiver> payloadReceiverList, Long timestamp);
    /**
     * Delete payload data based on payload sn.
     * @param payloadSns
     */
    void deletePayloadsByPayloadsSn(Collection<String> payloadSns);
}
src/main/java/com/dji/sample/manage/service/IDeviceService.java
@@ -1,10 +1,12 @@
package com.dji.sample.manage.service;
import com.dji.sample.common.model.PaginationData;
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.websocket.config.ConcurrentWebSocketSession;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO;
import com.dji.sample.manage.model.dto.TopologyDeviceDTO;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver;
@@ -195,4 +197,12 @@
     * @param receiver
     */
    void updateFirmwareVersion(FirmwareVersionReceiver receiver);
    /**
     * Create job for device firmware updates.
     * @param workspaceId
     * @param upgradeDTOS
     * @return
     */
    ResponseResult createDeviceOtaJob(String workspaceId, List<DeviceFirmwareUpgradeDTO> upgradeDTOS);
}
src/main/java/com/dji/sample/manage/service/ILiveStreamService.java
@@ -24,8 +24,9 @@
    /**
     * Save live capability data.
     * @param liveCapacityReceiver
     * @param timestamp
     */
    void saveLiveCapacity(LiveCapacityReceiver liveCapacityReceiver);
    void saveLiveCapacity(LiveCapacityReceiver liveCapacityReceiver, Long timestamp);
    /**
     * Initiate a live streaming by publishing mqtt message.
src/main/java/com/dji/sample/manage/service/ILogsFileIndexService.java
New file
@@ -0,0 +1,46 @@
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.LogsFileDTO;
import com.dji.sample.manage.model.receiver.LogsFile;
import com.dji.sample.manage.model.receiver.LogsFileUpload;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/8
 */
public interface ILogsFileIndexService {
    /**
     * Insert the index of the device logs.
     * @param file
     * @param deviceSn
     * @param domain
     * @param fileId
     * @return
     */
    Boolean insertFileIndex(LogsFile file, String deviceSn, Integer domain, String fileId);
    /**
     * Query logs file upload information based on the file id.
     * @param fileId
     * @return
     */
    Optional<LogsFileUpload> getFileIndexByFileId(String fileId);
    /**
     * Batch query logs file upload information.
     * @param fileIds
     * @return
     */
    List<LogsFileUpload> getFileIndexByFileIds(List<LogsFileDTO> fileIds);
    /**
     * Delete log index data based on file id.
     * @param fileIds
     */
    void deleteFileIndexByFileIds(List<String> fileIds);
}
src/main/java/com/dji/sample/manage/service/ILogsFileService.java
New file
@@ -0,0 +1,66 @@
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.LogsFileDTO;
import com.dji.sample.manage.model.receiver.LogsExtFileReceiver;
import com.dji.sample.manage.model.receiver.LogsFileUpload;
import java.net.URL;
import java.util.List;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
public interface ILogsFileService {
    /**
     * Query the uploaded log file information based on the log id.
     * @param logsId
     * @return
     */
    List<LogsFileDTO> getLogsFileInfoByLogsId(String logsId);
    /**
     * Query the uploaded log file structure information based on the log id.
     * @param logsId
     * @return
     */
    List<LogsFileUpload> getLogsFileByLogsId(String logsId);
    /**
     * Added logs file.
     * @param file
     * @param logsId
     * @return
     */
    Boolean insertFile(LogsFileUpload file, String logsId);
    /**
     * Delete logs files.
     * @param logsId
     */
    void deleteFileByLogsId(String logsId);
    /**
     * Update file information.
     * @param logsId
     * @param fileReceiver
     */
    void updateFile(String logsId, LogsExtFileReceiver fileReceiver);
    /**
     * Update file upload status.
     * @param logsId
     * @param isUploaded
     */
    void updateFileUploadStatus(String logsId, Boolean isUploaded);
    /**
     * Get the file address.
     * @param logsId
     * @param fileId
     * @return
     */
    URL getLogsFileUrl(String logsId, String fileId);
}
src/main/java/com/dji/sample/manage/service/ITopologyService.java
@@ -3,6 +3,7 @@
import com.dji.sample.manage.model.dto.TopologyDTO;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
@@ -17,4 +18,11 @@
     * @return
     */
    List<TopologyDTO> getDeviceTopology(String workspaceId);
    /**
     * Query the topology according to the gateway sn.
     * @param gatewaySn
     * @return
     */
    Optional<TopologyDTO> getDeviceTopologyByGatewaySn(String gatewaySn);
}
src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java
@@ -1,6 +1,7 @@
package com.dji.sample.manage.service.impl;
import com.dji.sample.component.mqtt.model.StateDataEnum;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.manage.model.dto.CapacityCameraDTO;
import com.dji.sample.manage.model.dto.DeviceDictionaryDTO;
@@ -46,10 +47,11 @@
    }
    @Override
    public void saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn) {
    public void saveCapacityCameraReceiverList(List<CapacityCameraReceiver> capacityCameraReceivers, String deviceSn, Long timestamp) {
        List<CapacityCameraDTO> capacity = capacityCameraReceivers.stream()
                .map(this::receiver2Dto).collect(Collectors.toList());
        redisOps.hashSet(StateDataEnum.LIVE_CAPACITY.getDesc(), deviceSn, capacity);
        redisOps.setWithExpire(StateDataEnum.LIVE_CAPACITY + RedisConst.DELIMITER + deviceSn, timestamp, RedisConst.DEVICE_ALIVE_SECOND);
    }
    @Override
src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java
New file
@@ -0,0 +1,218 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.impl.MessageSenderServiceImpl;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.component.websocket.service.impl.SendMessageServiceImpl;
import com.dji.sample.manage.dao.IDeviceFirmwareMapper;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareNoteDTO;
import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO;
import com.dji.sample.manage.model.entity.DeviceFirmwareEntity;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.param.DeviceOtaCreateParam;
import com.dji.sample.manage.service.IDeviceFirmwareService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
 * @author sean
 * @version 1.2
 * @date 2022/8/16
 */
@Service
@Slf4j
public class DeviceFirmwareServiceImpl implements IDeviceFirmwareService {
    @Autowired
    private IDeviceFirmwareMapper mapper;
    @Autowired
    private RedisOpsUtils redisOps;
    @Autowired
    private MessageSenderServiceImpl messageSenderService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private SendMessageServiceImpl webSocketMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Override
    public Optional<DeviceFirmwareDTO> getFirmware(String deviceName, String version) {
        return Optional.ofNullable(entity2Dto(mapper.selectOne(
                new LambdaQueryWrapper<DeviceFirmwareEntity>()
                        .eq(DeviceFirmwareEntity::getDeviceName, deviceName)
                        .eq(DeviceFirmwareEntity::getFirmwareVersion, version))));
    }
    @Override
    public Optional<DeviceFirmwareNoteDTO> getLatestFirmwareReleaseNote(String deviceName) {
        return Optional.ofNullable(entity2NoteDto(mapper.selectOne(
                new LambdaQueryWrapper<DeviceFirmwareEntity>()
                        .eq(DeviceFirmwareEntity::getDeviceName, deviceName)
                        .eq(DeviceFirmwareEntity::getStatus, true)
                        .orderByDesc(DeviceFirmwareEntity::getReleaseDate)
                        .last(" limit 1 "))));
    }
    @Override
    public List<DeviceOtaCreateParam> getDeviceOtaFirmware(List<DeviceFirmwareUpgradeDTO> upgradeDTOS) {
        List<DeviceOtaCreateParam> deviceOtaList = new ArrayList<>();
        upgradeDTOS.forEach(upgradeDevice -> {
            boolean exist = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + upgradeDevice.getSn()) > 0;
            if (!exist) {
                throw new IllegalArgumentException("Device is offline.");
            }
            Optional<DeviceFirmwareDTO> firmwareOpt = this.getFirmware(
                    upgradeDevice.getDeviceName(), upgradeDevice.getProductVersion());
            if (firmwareOpt.isEmpty()) {
                throw new IllegalArgumentException("This firmware version does not exist.");
            }
            if (!firmwareOpt.get().getFirmwareStatus()) {
                throw new IllegalArgumentException("This firmware version is not available.");
            }
            DeviceOtaCreateParam ota = dto2OtaCreateDto(firmwareOpt.get());
            ota.setSn(upgradeDevice.getSn());
            ota.setFirmwareUpgradeType(upgradeDevice.getFirmwareUpgradeType());
            deviceOtaList.add(ota);
        });
        return deviceOtaList;
    }
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_OTA_PROGRESS, outputChannel = ChannelName.OUTBOUND)
    public void handleOtaProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString();
        String sn  = topic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(),
                topic.indexOf(TopicConst.EVENTS_SUF));
        EventsReceiver<EventsOutputReceiver> eventsReceiver = objectMapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<EventsOutputReceiver>>(){});
        eventsReceiver.setBid(receiver.getBid());
        eventsReceiver.setSn(sn);
        EventsOutputReceiver output = eventsReceiver.getOutput();
        log.info("SN: {}, {} ===> Upgrading progress: {}",
                sn, receiver.getMethod(), output.getProgress().toString());
        if (eventsReceiver.getResult() != ResponseResult.CODE_SUCCESS) {
            log.error("SN: {}, {} ===> Error code: {}", sn, receiver.getMethod(), eventsReceiver.getResult());
        }
        DeviceDTO device = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        String childDeviceSn = device.getChildDeviceSn();
        boolean upgrade = redisOps.getExpire(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn) > 0;
        boolean childUpgrade = redisOps.getExpire(RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn) > 0;
        // Determine whether it is the ending state, delete the update state key in redis after the job ends.
        EventsResultStatusEnum statusEnum = EventsResultStatusEnum.find(output.getStatus());
        if (upgrade) {
            if (statusEnum.getEnd()) {
                // Delete the cache after the update is complete.
                redisOps.del(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn);
            } else {
                // Update the update progress of the dock in redis.
                redisOps.setWithExpire(
                        RedisConst.FIRMWARE_UPGRADING_PREFIX + sn, output.getProgress().getPercent(),
                        RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND);
            }
        }
        if (childUpgrade) {
            if (statusEnum.getEnd()) {
                redisOps.del(RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn);
            } else {
                // Update the update progress of the drone in redis.
                redisOps.setWithExpire(
                        RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn, output.getProgress().getPercent(),
                        RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND);
            }
        }
        webSocketMessageService.sendBatch(
                webSocketManageService.getValueWithWorkspaceAndUserType(
                        device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .data(eventsReceiver)
                        .timestamp(System.currentTimeMillis())
                        .bizCode(receiver.getMethod())
                        .build());
        if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) {
            String replyTopic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF;
            messageSenderService.publish(replyTopic,
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .method(receiver.getMethod())
                            .timestamp(System.currentTimeMillis())
                            .data(ResponseResult.success())
                            .build());
        }
    }
    private DeviceFirmwareNoteDTO entity2NoteDto (DeviceFirmwareEntity entity) {
        if (entity == null) {
            return null;
        }
        return DeviceFirmwareNoteDTO.builder()
                .deviceName(entity.getDeviceName())
                .productVersion(entity.getFirmwareVersion())
                .releasedTime(LocalDate.ofInstant(Instant.ofEpochMilli(entity.getReleaseDate()), ZoneId.systemDefault()))
                .releaseNote(entity.getReleaseNote())
                .build();
    }
    private DeviceFirmwareDTO entity2Dto (DeviceFirmwareEntity entity) {
        if (entity == null) {
            return null;
        }
        return DeviceFirmwareDTO.builder()
                .deviceName(entity.getDeviceName())
                .fileMd5(entity.getFileMd5())
                .fileSize(entity.getFileSize())
                .fileUrl(entity.getFileUrl())
                .firmwareId(entity.getFirmwareId())
                .fileName(entity.getFileName())
                .productVersion(entity.getFirmwareVersion())
                .releasedTime(LocalDate.ofInstant(Instant.ofEpochMilli(entity.getReleaseDate()), ZoneId.systemDefault()))
                .firmwareStatus(entity.getStatus())
                .build();
    }
    private DeviceOtaCreateParam dto2OtaCreateDto(DeviceFirmwareDTO dto) {
        if (dto == null) {
            return null;
        }
        return DeviceOtaCreateParam.builder()
                .fileSize(dto.getFileSize())
                .fileUrl(dto.getFileUrl())
                .fileName(dto.getFileName())
                .md5(dto.getFileMd5())
                .productVersion(dto.getProductVersion())
                .build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java
@@ -43,6 +43,8 @@
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@@ -69,9 +71,61 @@
    @Autowired
    private WebSocketManageServiceImpl webSocketManageService;
    private static final Pattern PATTERN_KEY = Pattern.compile(
            HmsEnum.FormatKeyEnum.KEY_START +
                    "(" +
                    Arrays.stream(HmsEnum.FormatKeyEnum.values())
                            .map(HmsEnum.FormatKeyEnum::getKey)
                            .collect(Collectors.joining("|")) +
                    ")");
    @Override
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_HMS)
    public void handleHms(CommonTopicReceiver receiver, MessageHeaders headers) {
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString();
        String sn  = topic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(),
                topic.indexOf(TopicConst.EVENTS_SUF));
        DeviceHmsEntity entity = DeviceHmsEntity.builder()
                .bid(receiver.getBid())
                .tid(receiver.getTid())
                .createTime(receiver.getTimestamp())
                .updateTime(0L)
                .sn(sn)
                .build();
        String key = RedisConst.HMS_PREFIX + sn;
        // Query all unread hms messages of the device in redis.
        Set<String> hmsMap = redisOps.listGetAll(key).stream().map(String::valueOf).collect(Collectors.toSet());
        DeviceDTO device = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        List<DeviceHmsDTO> unReadList = new ArrayList<>();
        objectMapper.convertValue(((Map) (receiver.getData())).get(MapKeyConst.LIST),
                new TypeReference<List<DeviceHmsReceiver>>() {})
                .forEach(hmsReceiver -> {
                    final DeviceHmsEntity hms = entity.clone();
                    this.fillEntity(hms, hmsReceiver);
                    // The same unread hms are no longer incremented.
                    if (hmsMap.contains(hms.getHmsKey())) {
                        return;
                    }
                    this.fillMessage(hms, hmsReceiver.getArgs());
                    unReadList.add(entity2Dto(hms));
                    mapper.insert(hms);
                });
        if (unReadList.isEmpty()) {
            return;
        }
        redisOps.listRPush(key, unReadList.stream().map(DeviceHmsDTO::getKey).toArray(String[]::new));
        // push to the web
        Collection<ConcurrentWebSocketSession> sessions = webSocketManageService.getValueWithWorkspaceAndUserType(
                device.getWorkspaceId(), UserTypeEnum.WEB.getVal());
        sendMessageService.sendBatch(sessions, CustomWebSocketMessage.builder()
                .bizCode(BizCodeEnum.DEVICE_HMS.getCode())
                .data(TelemetryDTO.<List<DeviceHmsDTO>>builder().sn(sn).host(unReadList).build())
                .timestamp(System.currentTimeMillis())
                .build());
    }
    @Override
@@ -107,6 +161,7 @@
                new LambdaUpdateWrapper<DeviceHmsEntity>()
                        .eq(DeviceHmsEntity::getSn, deviceSn)
                        .eq(DeviceHmsEntity::getUpdateTime, 0L));
        // Delete unread messages cached in redis.
        redisOps.del(RedisConst.HMS_PREFIX + deviceSn);
    }
@@ -130,4 +185,121 @@
                .build();
    }
    /**
     * Populate the received data into the entity. Please refer to the documentation for splicing rules.
     * @param dto
     * @param receiver
     */
    private void fillEntity(DeviceHmsEntity dto, DeviceHmsReceiver receiver) {
        dto.setLevel(receiver.getLevel());
        dto.setModule(receiver.getModule());
        dto.setHmsId(UUID.randomUUID().toString());
        if (HmsEnum.DomainType.DRONE_NEST.getDomain().equals(receiver.getDomainType())) {
            dto.setHmsKey(HmsEnum.HmsFaqIdEnum.DOCK_TIP.getText() + receiver.getCode());
            return;
        }
        StringBuilder key = new StringBuilder(HmsEnum.HmsFaqIdEnum.FPV_TIP.getText()).append(receiver.getCode());
        if (receiver.getInTheSky() == HmsEnum.IN_THE_SKY.getVal()) {
            key.append(HmsEnum.IN_THE_SKY.getText());
        }
        dto.setHmsKey(key.toString());
    }
    /**
     * Replace wildcards in messages according to the relevant rules.
     * Please refer to the documentation for splicing rules.
     * @param dto
     * @param args
     */
    private void fillMessage(DeviceHmsEntity dto, HmsArgsReceiver args) {
        HmsMessage hmsMessage = HmsJsonUtil.get(dto.getHmsKey());
        String zh = StringUtils.hasText(hmsMessage.getZh()) ? hmsMessage.getZh() : String.format("未知错误(%s)", dto.getHmsKey());
        String en = StringUtils.hasText(hmsMessage.getEn()) ? hmsMessage.getEn() : String.format("Unknown(%s)", dto.getHmsKey());//
        dto.setMessageZh(format(Locale.CHINESE.getLanguage(), zh, args));
        dto.setMessageEn(format(Locale.ENGLISH.getLanguage(), en, args));
    }
    /**
     * Set the matching parameters for key.
     * @param l     language: zh or en
     * @param hmsArgs
     * @return
     */
    private List<String> fillKeyArgs(String l, HmsArgsReceiver hmsArgs) {
        List<String> args = new ArrayList<>();
        args.add(Objects.nonNull(hmsArgs.getAlarmId()) ? Long.toHexString(hmsArgs.getAlarmId()) : null);
        args.add(Objects.nonNull(hmsArgs.getComponentIndex()) ? String.valueOf(hmsArgs.getComponentIndex() + 1) : null);
        if (Objects.nonNull(hmsArgs.getSensorIndex())) {
            args.add(String.valueOf(hmsArgs.getSensorIndex() + 1));
            HmsEnum.HmsBatteryIndexEnum hmsBatteryIndexEnum = HmsEnum.HmsBatteryIndexEnum.find(hmsArgs.getSensorIndex());
            HmsEnum.HmsDockCoverIndexEnum hmsDockCoverIndexEnum = HmsEnum.HmsDockCoverIndexEnum.find(hmsArgs.getSensorIndex());
            HmsEnum.HmsChargingRodIndexEnum hmsChargingRodIndexEnum = HmsEnum.HmsChargingRodIndexEnum.find(hmsArgs.getSensorIndex());
            switch (l) {
                case "zh":
                    args.add(hmsBatteryIndexEnum.getZh());
                    args.add(hmsDockCoverIndexEnum.getZh());
                    args.add(hmsChargingRodIndexEnum.getZh());
                    break;
                case "en":
                    args.add(hmsBatteryIndexEnum.getEn());
                    args.add(hmsDockCoverIndexEnum.getEn());
                    args.add(hmsChargingRodIndexEnum.getEn());
                    break;
                default:
                    break;
            }
        }
        return args;
    }
    /**
     * Returns a formatted string using the specified locale, format string, and arguments.
     * @param l language: zh or en
     * @param format
     * @param hmsArgs
     * @return
     */
    private String format(String l, String format, HmsArgsReceiver hmsArgs) {
        List<String> args = fillKeyArgs(l, hmsArgs);
        List<String> list = parse(format);
        StringBuilder sb = new StringBuilder();
        for (String word : list) {
            if (!StringUtils.hasText(word)) {
                continue;
            }
            HmsEnum.FormatKeyEnum keyEnum = HmsEnum.FormatKeyEnum.find(word.substring(1));
            sb.append(HmsEnum.FormatKeyEnum.KEY_START != word.charAt(0) || HmsEnum.FormatKeyEnum.UNKNOWN == keyEnum ?
                    word : args.get(keyEnum.getIndex()));
        }
        return sb.toString();
    }
    /**
     * Finds format specifiers in the format string.
     * @param s
     * @return
     */
    private List<String> parse(String s) {
        List<String> list = new ArrayList<>();
        Matcher matcher = PATTERN_KEY.matcher(s);
        for (int i = 0; i < s.length(); ) {
            if (matcher.find(i)) {
                if (matcher.start() != i) {
                    list.add(s.substring(i, matcher.start()));
                }
                list.add(matcher.group());
                i = matcher.end();
            } else {
                list.add(s.substring(i));
                break;
            }
        }
        return list;
    }
}
src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java
New file
@@ -0,0 +1,376 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.common.model.Pagination;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.component.websocket.model.CustomWebSocketMessage;
import com.dji.sample.component.websocket.service.ISendMessageService;
import com.dji.sample.component.websocket.service.IWebSocketManageService;
import com.dji.sample.manage.dao.IDeviceLogsMapper;
import com.dji.sample.manage.model.dto.*;
import com.dji.sample.manage.model.entity.DeviceLogsEntity;
import com.dji.sample.manage.model.enums.DeviceDomainEnum;
import com.dji.sample.manage.model.enums.DeviceLogsStatusEnum;
import com.dji.sample.manage.model.enums.LogsFileUpdateMethodEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.param.DeviceLogsCreateParam;
import com.dji.sample.manage.model.param.DeviceLogsQueryParam;
import com.dji.sample.manage.model.param.LogsFileUpdateParam;
import com.dji.sample.manage.model.receiver.*;
import com.dji.sample.manage.service.IDeviceLogsService;
import com.dji.sample.manage.service.ILogsFileService;
import com.dji.sample.manage.service.ITopologyService;
import com.dji.sample.media.model.StsCredentialsDTO;
import com.dji.sample.storage.service.IStorageService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
@Service
@Transactional
@Slf4j
public class DeviceLogsServiceImpl implements IDeviceLogsService {
    private static final String LOGS_FILE_SUFFIX = ".tar";
    @Autowired
    private IDeviceLogsMapper mapper;
    @Autowired
    private ITopologyService topologyService;
    @Autowired
    private IMessageSenderService messageSenderService;
    @Autowired
    private ILogsFileService logsFileService;
    @Autowired
    private RedisOpsUtils redisOpsUtils;
    @Autowired
    private IStorageService storageService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private ISendMessageService webSocketMessageService;
    @Autowired
    private IWebSocketManageService webSocketManageService;
    @Override
    public PaginationData<DeviceLogsDTO> getUploadedLogs(String deviceSn, DeviceLogsQueryParam param) {
        LambdaQueryWrapper<DeviceLogsEntity> queryWrapper = new LambdaQueryWrapper<DeviceLogsEntity>()
                .eq(DeviceLogsEntity::getDeviceSn, deviceSn)
                .between(Objects.nonNull(param.getBeginTime()) && Objects.nonNull(param.getEndTime()),
                        DeviceLogsEntity::getCreateTime, param.getBeginTime(), param.getEndTime())
                .eq(Objects.nonNull(param.getStatus()), DeviceLogsEntity::getStatus, param.getStatus())
                .like(StringUtils.hasText(param.getLogsInformation()),
                        DeviceLogsEntity::getLogsInfo, param.getLogsInformation())
                .orderByDesc(DeviceLogsEntity::getCreateTime);
        Page<DeviceLogsEntity> pagination = mapper.selectPage(new Page<>(param.getPage(), param.getPageSize()), queryWrapper);
        List<DeviceLogsDTO> deviceLogsList = pagination.getRecords().stream().map(this::entity2Dto).collect(Collectors.toList());
        return new PaginationData<DeviceLogsDTO>(deviceLogsList, new Pagination(pagination));
    }
    @Override
    public ResponseResult getRealTimeLogs(String deviceSn, List<String> domainList) {
        boolean exist = redisOpsUtils.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceSn) > 0;
        if (!exist) {
            return ResponseResult.error("Device is offline.");
        }
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF;
        Optional<LogsFileUploadList> serviceReplyOpt = messageSenderService.publishWithReply(
                LogsFileUploadList.class,
                topic,
                CommonTopicResponse.builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(UUID.randomUUID().toString())
                        .method(ServicesMethodEnum.FILE_UPLOAD_LIST.getMethod())
                        .timestamp(System.currentTimeMillis())
                        .data(Map.of(MapKeyConst.MODULE_LIST, domainList))
                        .build(), 1);
        if (serviceReplyOpt.isEmpty()) {
            return ResponseResult.error("No message reply received.");
        }
        LogsFileUploadList data = serviceReplyOpt.get();
        for (LogsFileUpload file : data.getFiles()) {
            if (file.getDeviceSn().isBlank()) {
                file.setDeviceSn(deviceSn);
            }
        }
        return ResponseResult.success(data);
    }
    @Override
    public String insertDeviceLogs(String bid, String username, String deviceSn, DeviceLogsCreateParam param) {
        DeviceLogsEntity entity = DeviceLogsEntity.builder()
                .deviceSn(deviceSn)
                .username(username)
                .happenTime(param.getHappenTime())
                .logsInfo(Objects.requireNonNullElse(param.getLogsInformation(), ""))
                .logsId(bid)
                .status(DeviceLogsStatusEnum.UPLOADING.getVal())
                .build();
        boolean insert = mapper.insert(entity) > 0;
        if (!insert) {
            return "";
        }
        for (LogsFileUpload file : param.getFiles()) {
            insert = logsFileService.insertFile(file, entity.getLogsId());
            if (!insert) {
                return "";
            }
        }
        return bid;
    }
    @Override
    public ResponseResult pushFileUpload(String username, String deviceSn, DeviceLogsCreateParam param) {
        StsCredentialsDTO stsCredentials = storageService.getSTSCredentials();
        LogsUploadCredentialsDTO credentialsDTO = new LogsUploadCredentialsDTO(stsCredentials);
        // Set the storage name of the file.
        List<LogsFileUpload> files = param.getFiles();
        files.forEach(file -> file.setObjectKey(credentialsDTO.getObjectKeyPrefix() + "/" + UUID.randomUUID().toString() + LOGS_FILE_SUFFIX));
        credentialsDTO.setParams(LogsFileUploadList.builder().files(files).build());
        String bid = UUID.randomUUID().toString();
        Optional<ServiceReply> serviceReply = messageSenderService.publishWithReply(
                TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF,
                CommonTopicResponse.<LogsUploadCredentialsDTO>builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .timestamp(System.currentTimeMillis())
                        .method(ServicesMethodEnum.FILE_UPLOAD_START.getMethod())
                        .data(credentialsDTO)
                        .build());
        if (serviceReply.isEmpty()) {
            return ResponseResult.error("No message reply received.");
        }
        ServiceReply reply = serviceReply.get();
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            return ResponseResult.error(String.valueOf(reply.getResult()));
        }
        String logsId = this.insertDeviceLogs(bid, username, deviceSn, param);
        if (!bid.equals(logsId)) {
            return ResponseResult.error("Database insert failed.");
        }
        // Save the status of the log upload.
        redisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + deviceSn, bid, LogsOutputProgressDTO.builder().logsId(logsId).build());
        return ResponseResult.success();
    }
    @Override
    public ResponseResult pushUpdateFile(String deviceSn, LogsFileUpdateParam param) {
        LogsFileUpdateMethodEnum method = LogsFileUpdateMethodEnum.find(param.getStatus());
        if (LogsFileUpdateMethodEnum.UNKNOWN == method) {
            return ResponseResult.error("Illegal param");
        }
        String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF;
        String bid = UUID.randomUUID().toString();
        Optional<ServiceReply> serviceReply = messageSenderService.publishWithReply(topic,
                CommonTopicResponse.<LogsFileUpdateParam>builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .timestamp(System.currentTimeMillis())
                        .method(ServicesMethodEnum.FILE_UPLOAD_UPDATE.getMethod())
                        .data(param)
                        .build());
        if (serviceReply.isEmpty()) {
            return ResponseResult.error("No message reply received.");
        }
        ServiceReply reply = serviceReply.get();
        if (ResponseResult.CODE_SUCCESS != reply.getResult()) {
            return ResponseResult.error("Error Code : " + reply.getResult());
        }
        return ResponseResult.success();
    }
    @Override
    public void deleteLogs(String deviceSn, String logsId) {
        mapper.delete(new LambdaUpdateWrapper<DeviceLogsEntity>()
                .eq(DeviceLogsEntity::getLogsId, logsId).eq(DeviceLogsEntity::getDeviceSn, deviceSn));
        logsFileService.deleteFileByLogsId(logsId);
    }
    @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_PROGRESS, outputChannel = ChannelName.OUTBOUND)
    @Override
    public void handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers) {
        String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString();
        String sn  = topic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(),
                topic.indexOf(TopicConst.EVENTS_SUF));
        if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) {
            String replyTopic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF;
            messageSenderService.publish(replyTopic,
                    CommonTopicResponse.builder()
                            .tid(receiver.getTid())
                            .bid(receiver.getBid())
                            .method(receiver.getMethod())
                            .timestamp(System.currentTimeMillis())
                            .data(ResponseResult.success())
                            .build());
        }
        EventsReceiver<OutputLogsProgressReceiver> eventsReceiver = objectMapper.convertValue(receiver.getData(),
                new TypeReference<EventsReceiver<OutputLogsProgressReceiver>>(){});
        EventsReceiver<LogsOutputProgressDTO> webSocketData = new EventsReceiver<>();
        webSocketData.setBid(receiver.getBid());
        webSocketData.setSn(sn);
        DeviceDTO device = (DeviceDTO) redisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn);
        try {
            OutputLogsProgressReceiver output = eventsReceiver.getOutput();
            EventsResultStatusEnum statusEnum = EventsResultStatusEnum.find(output.getStatus());
            log.info("Logs upload progress: {}", output.toString());
            String key = RedisConst.LOGS_FILE_PREFIX + sn;
            LogsOutputProgressDTO progress;
            boolean exist = redisOpsUtils.checkExist(key);
            if (!exist && !statusEnum.getEnd()) {
                progress = LogsOutputProgressDTO.builder().logsId(receiver.getBid()).build();
                redisOpsUtils.hashSet(key, receiver.getBid(), progress);
            } else if (exist) {
                progress = (LogsOutputProgressDTO) redisOpsUtils.hashGet(key, receiver.getBid());
            } else {
                progress = LogsOutputProgressDTO.builder().build();
            }
            progress.setStatus(output.getStatus());
            // If the logs file is empty, delete the cache of this task.
            List<LogsExtFileReceiver> fileReceivers = output.getExt().getFiles();
            if (CollectionUtils.isEmpty(fileReceivers)) {
                redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
                return;
            }
            // refresh cache.
            List<LogsProgressDTO> fileProgressList = new ArrayList<>();
            fileReceivers.forEach(file -> {
                LogsProgressReceiver logsProgress = file.getProgress();
                if (!StringUtils.hasText(file.getDeviceSn())) {
                    if (String.valueOf(DeviceDomainEnum.DOCK.getVal()).equals(file.getDeviceModelDomain())) {
                        file.setDeviceSn(sn);
                    } else if (String.valueOf(DeviceDomainEnum.SUB_DEVICE.getVal()).equals(file.getDeviceModelDomain())) {
                        file.setDeviceSn(device.getChildDeviceSn());
                    }
                }
                fileProgressList.add(LogsProgressDTO.builder()
                        .deviceSn(file.getDeviceSn())
                        .deviceModelDomain(file.getDeviceModelDomain())
                        .result(logsProgress.getResult())
                        .status(logsProgress.getStatus())
                        .uploadRate(logsProgress.getUploadRate())
                        .progress(((logsProgress.getCurrentStep() - 1) * 100 + logsProgress.getProgress()) / logsProgress.getTotalStep())
                        .build());
            });
            progress.setFiles(fileProgressList);
            webSocketData.setOutput(progress);
            redisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + sn, receiver.getBid(), progress);
            // Delete the cache at the end of the task.
            if (statusEnum.getEnd()) {
                redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
                this.updateLogsStatus(receiver.getBid(), DeviceLogsStatusEnum.find(statusEnum).getVal());
                fileReceivers.forEach(file -> logsFileService.updateFile(receiver.getBid(), file));
            }
        } catch (NullPointerException e) {
            this.updateLogsStatus(receiver.getBid(), DeviceLogsStatusEnum.FAILED.getVal());
            redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn);
        }
        webSocketMessageService.sendBatch(
                webSocketManageService.getValueWithWorkspaceAndUserType(
                        device.getWorkspaceId(), UserTypeEnum.WEB.getVal()),
                CustomWebSocketMessage.builder()
                        .data(webSocketData)
                        .timestamp(System.currentTimeMillis())
                        .bizCode(receiver.getMethod())
                        .build());
    }
    @Override
    public void updateLogsStatus(String logsId, Integer value) {
        mapper.update(DeviceLogsEntity.builder().status(value).build(),
                new LambdaUpdateWrapper<DeviceLogsEntity>().eq(DeviceLogsEntity::getLogsId, logsId));
        if (DeviceLogsStatusEnum.DONE.getVal() == value) {
            logsFileService.updateFileUploadStatus(logsId, true);
        }
    }
    @Override
    public URL getLogsFileUrl(String logsId, String fileId) {
        return logsFileService.getLogsFileUrl(logsId, fileId);
    }
    private DeviceLogsDTO entity2Dto(DeviceLogsEntity entity) {
        if (Objects.isNull(entity)) {
            return null;
        }
        String key = RedisConst.LOGS_FILE_PREFIX + entity.getDeviceSn();
        LogsOutputProgressDTO progress = new LogsOutputProgressDTO();
        if (redisOpsUtils.hashCheck(key, entity.getLogsId())) {
            progress = (LogsOutputProgressDTO) redisOpsUtils.hashGet(key, entity.getLogsId());
        }
        return DeviceLogsDTO.builder()
                .logsId(entity.getLogsId())
                .createTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getCreateTime()), ZoneId.systemDefault()))
                .happenTime(Objects.isNull(entity.getHappenTime()) ?
                        null : LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getHappenTime()), ZoneId.systemDefault()))
                .status(entity.getStatus())
                .logsInformation(entity.getLogsInfo())
                .userName(entity.getUsername())
                .deviceLogs(LogsFileUploadList.builder().files(logsFileService.getLogsFileByLogsId(entity.getLogsId())).build())
                .logsProgress(progress.getFiles())
                .deviceTopo(topologyService.getDeviceTopologyByGatewaySn(entity.getDeviceSn()).orElse(null))
                .build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java
@@ -14,14 +14,13 @@
import com.dji.sample.manage.service.ICapacityCameraService;
import com.dji.sample.manage.service.IDeviceDictionaryService;
import com.dji.sample.manage.service.IDevicePayloadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -29,6 +28,7 @@
 * @version 0.1
 * @date 2021/11/19
 */
@Slf4j
@Service
@Transactional
public class DevicePayloadServiceImpl implements IDevicePayloadService {
@@ -124,6 +124,48 @@
                        .eq(DevicePayloadEntity::getDeviceSn, receiver.getSn()));
    }
    @Override
    public void saveDeviceBasicPayload(List<DevicePayloadReceiver> payloadReceiverList, Long timestamp) {
        if (payloadReceiverList.isEmpty()) {
            return;
        }
        String deviceSn = payloadReceiverList.stream().findAny().get().getDeviceSn();
        String key = RedisConst.STATE_PAYLOAD_PREFIX + deviceSn;
        // Solve timing problems
        long last = (long) Objects.requireNonNullElse(redisOps.get(key), 0L);
        if (last > timestamp) {
            return;
        }
        // Filter unsaved payload information.
        Set<String> payloadSns = this.getDevicePayloadEntitiesByDeviceSn(payloadReceiverList.get(0).getDeviceSn())
                .stream().map(DevicePayloadDTO::getPayloadSn).collect(Collectors.toSet());
        Set<String> newPayloadSns = payloadReceiverList.stream().map(DevicePayloadReceiver::getSn).collect(Collectors.toSet());
        Set<String> needToDel = payloadSns.stream().filter(sn -> !newPayloadSns.contains(sn)).collect(Collectors.toSet());
        this.deletePayloadsByPayloadsSn(needToDel);
        List<DevicePayloadReceiver> needToSave = payloadReceiverList.stream()
                .filter(payload -> !payloadSns.contains(payload.getSn())).collect(Collectors.toList());
        // Save the new payload information.
        boolean isSave = this.savePayloadDTOs(needToSave);
        if (isSave) {
            redisOps.setWithExpire(key, timestamp, RedisConst.DEVICE_ALIVE_SECOND);
        }
        log.debug("The result of saving the payloads is {}.", isSave);
    }
    @Override
    public void deletePayloadsByPayloadsSn(Collection<String> payloadSns) {
        if (CollectionUtils.isEmpty(payloadSns)) {
            return;
        }
        mapper.delete(new LambdaUpdateWrapper<DevicePayloadEntity>()
                .or(wrapper -> payloadSns.forEach(sn -> wrapper.eq(DevicePayloadEntity::getPayloadSn, sn))));
    }
    /**
     * Convert database entity objects into payload data transfer object.
     * @param entity
src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java
@@ -6,6 +6,7 @@
import com.dji.sample.common.error.CommonErrorEnum;
import com.dji.sample.common.model.Pagination;
import com.dji.sample.common.model.PaginationData;
import com.dji.sample.common.model.ResponseResult;
import com.dji.sample.component.mqtt.model.*;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.mqtt.service.IMqttTopicService;
@@ -20,8 +21,10 @@
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.DeviceFirmwareStatusEnum;
import com.dji.sample.manage.model.enums.IconUrlEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.param.DeviceOtaCreateParam;
import com.dji.sample.manage.model.param.DeviceQueryParam;
import com.dji.sample.manage.model.receiver.*;
import com.dji.sample.manage.service.*;
@@ -90,6 +93,9 @@
    private IWebSocketManageService webSocketManageService;
    @Autowired
    private IDeviceFirmwareService deviceFirmwareService;
    @Autowired
    @Qualifier("gatewayOSDServiceImpl")
    private ITSAService tsaService;
@@ -120,13 +126,6 @@
            gatewayOpt.map(DeviceDTO::getWorkspaceId).ifPresent(gateway::setWorkspaceId);
            redisOps.setWithExpire(key, gateway, RedisConst.DEVICE_ALIVE_SECOND);
            this.pushDeviceOnlineTopo(gateway.getWorkspaceId(), gatewaySn, gatewaySn);
            return true;
        }
        long expire = redisOps.getExpire(key);
        // If the key about the device in redis has expired, the remote control is considered to be offline.
        if (expire <= 0) {
            log.debug("The remote control is already offline.");
            return true;
        }
@@ -165,10 +164,10 @@
        String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn;
        // change log:  Use redis instead of
        long time = redisOps.getExpire(key);
        long gatewayTime = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn());
        long now = System.currentTimeMillis();
        if (time > 0) {
            redisOps.expireKey(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn(), RedisConst.DEVICE_ALIVE_SECOND);
        if (time > 0 && gatewayTime > 0) {
            redisOps.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND);
            DeviceDTO device = DeviceDTO.builder().loginTime(LocalDateTime.now()).deviceSn(deviceSn).build();
            DeviceDTO gateway = DeviceDTO.builder()
@@ -372,6 +371,9 @@
    @Override
    public Optional<TopologyDeviceDTO> getDeviceTopoForPilot(String sn) {
        if (sn.isBlank()) {
            return Optional.empty();
        }
        List<TopologyDeviceDTO> topologyDeviceList = this.getDevicesByParams(
                DeviceQueryParam.builder()
                        .deviceSn(sn)
@@ -468,8 +470,18 @@
            DeviceDTO device = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + from);
            if (device == null || !StringUtils.hasText(device.getWorkspaceId())) {
                return;
            if (device == null) {
                Optional<DeviceDTO> deviceOpt = this.getDeviceBySn(from);
                if (deviceOpt.isEmpty()) {
                    return;
                }
                device = deviceOpt.get();
                if (!StringUtils.hasText(device.getWorkspaceId())) {
                    return;
                }
                redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + from, device,
                        RedisConst.DEVICE_ALIVE_SECOND);
                this.subscribeTopicOnline(from);
            }
            receiver = objectMapper.readValue(payload, CommonTopicReceiver.class);
@@ -613,7 +625,7 @@
        if (entity == null) {
            return null;
        }
        return DeviceDTO.builder()
        DeviceDTO.DeviceDTOBuilder deviceDTOBuilder = DeviceDTO.builder()
                .deviceSn(entity.getDeviceSn())
                .childDeviceSn(entity.getChildSn())
                .deviceName(entity.getDeviceName())
@@ -638,8 +650,31 @@
                .firmwareVersion(entity.getFirmwareVersion())
                .workspaceName(entity.getWorkspaceId() != null ?
                        workspaceService.getWorkspaceByWorkspaceId(entity.getWorkspaceId())
                        .map(WorkspaceDTO::getWorkspaceName).orElse("") : "")
                .build();
                                .map(WorkspaceDTO::getWorkspaceName).orElse("") : "");
        if (!StringUtils.hasText(entity.getFirmwareVersion())) {
            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
        }
        // Query whether the device is updating firmware.
        Object progress = redisOps.get(RedisConst.FIRMWARE_UPGRADING_PREFIX + entity.getDeviceSn());
        if (Objects.nonNull(progress)) {
            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.UPGRADING.getVal()).firmwareProgress((int)progress).build();
        }
        // First query the latest firmware version of the device model and compare it with the current firmware version
        // to see if it needs to be upgraded.
        Optional<DeviceFirmwareNoteDTO> firmwareReleaseNoteOpt = deviceFirmwareService.getLatestFirmwareReleaseNote(entity.getDeviceName());
        if (firmwareReleaseNoteOpt.isPresent()) {
            DeviceFirmwareNoteDTO firmwareNoteDTO = firmwareReleaseNoteOpt.get();
            if (firmwareNoteDTO.getProductVersion().equals(entity.getFirmwareVersion())) {
                return deviceDTOBuilder.firmwareStatus(entity.getCompatibleStatus() ?
                        DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal() :
                        DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal()).build();
            }
            return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NORMAL_UPGRADE.getVal()).build();
        }
        return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build();
    }
    @Override
@@ -822,13 +857,57 @@
    @Override
    public void updateFirmwareVersion(FirmwareVersionReceiver receiver) {
        if (receiver.getDomain() == DeviceDomainEnum.SUB_DEVICE) {
            this.updateDevice(DeviceDTO.builder()
            final DeviceDTO device = DeviceDTO.builder()
                    .deviceSn(receiver.getSn())
                    .firmwareVersion(receiver.getFirmwareVersion())
                    .build());
                    .firmwareStatus(receiver.getCompatibleStatus() == null ?
                            null : DeviceFirmwareStatusEnum.CompatibleStatusEnum.INCONSISTENT.getVal() != receiver.getCompatibleStatus() ?
                            DeviceFirmwareStatusEnum.UNKNOWN.getVal() : DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal())
                    .build();
            this.updateDevice(device);
            return;
        }
        payloadService.updateFirmwareVersion(receiver);
    }
    @Override
    public ResponseResult createDeviceOtaJob(String workspaceId, List<DeviceFirmwareUpgradeDTO> upgradeDTOS) {
        List<DeviceOtaCreateParam> deviceOtaFirmwares = deviceFirmwareService.getDeviceOtaFirmware(upgradeDTOS);
        if (deviceOtaFirmwares.isEmpty()) {
            return ResponseResult.error();
        }
        DeviceOtaCreateParam deviceOtaFirmware = deviceOtaFirmwares.get(0);
        List<DeviceDTO> devices = getDevicesByParams(DeviceQueryParam.builder().childSn(deviceOtaFirmware.getSn()).build());
        String gatewaySn = devices.isEmpty() ? deviceOtaFirmware.getSn() : devices.get(0).getDeviceSn();
        String topic = THING_MODEL_PRE + PRODUCT + gatewaySn + SERVICES_SUF;
        // The bids in the progress messages reported subsequently are the same.
        String bid = UUID.randomUUID().toString();
        Optional<ServiceReply> serviceReplyOpt = messageSender.publishWithReply(
                topic, CommonTopicResponse.<Map<String, List<DeviceOtaCreateParam>>>builder()
                        .tid(UUID.randomUUID().toString())
                        .bid(bid)
                        .timestamp(System.currentTimeMillis())
                        .method(ServicesMethodEnum.OTA_CREATE.getMethod())
                        .data(Map.of(MapKeyConst.DEVICES, deviceOtaFirmwares))
                        .build());
        if (serviceReplyOpt.isEmpty()) {
            return ResponseResult.error("No message reply received.");
        }
        ServiceReply serviceReply = serviceReplyOpt.get();
        if (serviceReply.getResult() != ResponseResult.CODE_SUCCESS) {
            return ResponseResult.error(serviceReply.getResult(), "Firmware Error Code: " + serviceReply.getResult());
        }
        if (ServicesMethodEnum.OTA_CREATE.getProgress()) {
            // Record the device state that needs to be updated.
            deviceOtaFirmwares.forEach(deviceOta -> redisOps.setWithExpire(
                    RedisConst.FIRMWARE_UPGRADING_PREFIX + deviceOta.getSn(),
                    bid,
                    RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND));
        }
        return ResponseResult.success();
    }
    /**
@@ -854,6 +933,8 @@
                .childSn(dto.getChildDeviceSn())
                .domain(StringUtils.hasText(dto.getDomain()) ? DeviceDomainEnum.getVal(dto.getDomain()) : null)
                .firmwareVersion(dto.getFirmwareVersion())
                .compatibleStatus(dto.getFirmwareStatus() == null ? null :
                        DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE != DeviceFirmwareStatusEnum.find(dto.getFirmwareStatus()))
                .build();
    }
src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java
@@ -5,6 +5,7 @@
import com.dji.sample.component.mqtt.model.CommonTopicResponse;
import com.dji.sample.component.mqtt.model.ServiceReply;
import com.dji.sample.component.mqtt.model.ServicesMethodEnum;
import com.dji.sample.component.mqtt.model.StateDataEnum;
import com.dji.sample.component.mqtt.service.IMessageSenderService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
@@ -25,10 +26,7 @@
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -80,11 +78,17 @@
    }
    @Override
    public void saveLiveCapacity(LiveCapacityReceiver liveCapacityReceiver) {
    public void saveLiveCapacity(LiveCapacityReceiver liveCapacityReceiver, Long timestamp) {
        // Solve timing problems
        for (CapacityDeviceReceiver capacityDeviceReceiver : liveCapacityReceiver.getDeviceList()) {
            long last = (long) Objects.requireNonNullElse(
                    redisOps.get(StateDataEnum.LIVE_CAPACITY + RedisConst.DELIMITER + capacityDeviceReceiver.getSn()), 0L);
            if (last > timestamp) {
                return;
            }
            capacityCameraService.saveCapacityCameraReceiverList(
                    capacityDeviceReceiver.getCameraList(),
                    capacityDeviceReceiver.getSn());
                    capacityDeviceReceiver.getSn(), timestamp);
        }
    }
src/main/java/com/dji/sample/manage/service/impl/LogsFileIndexServiceImpl.java
New file
@@ -0,0 +1,108 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.dji.sample.manage.dao.ILogsFileIndexMapper;
import com.dji.sample.manage.model.dto.LogsFileDTO;
import com.dji.sample.manage.model.entity.LogsFileIndexEntity;
import com.dji.sample.manage.model.receiver.LogsFile;
import com.dji.sample.manage.model.receiver.LogsFileUpload;
import com.dji.sample.manage.service.ILogsFileIndexService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/8
 */
@Service
@Transactional
public class LogsFileIndexServiceImpl implements ILogsFileIndexService {
    @Autowired
    private ILogsFileIndexMapper mapper;
    @Override
    public Boolean insertFileIndex(LogsFile file, String deviceSn, Integer domain, String fileId) {
        if (Objects.isNull(file)) {
            return false;
        }
        LogsFileIndexEntity entity = this.logsFile2Entity(file);
        entity.setDomain(domain);
        entity.setDeviceSn(deviceSn);
        entity.setFileId(fileId);
        return mapper.insert(entity) > 0;
    }
    @Override
    public List<LogsFileUpload> getFileIndexByFileIds(List<LogsFileDTO> files) {
        List<LogsFileUpload> list = new ArrayList<>();
        files.forEach(file -> {
            Optional<LogsFileUpload> fileOpt = this.getFileIndexByFileId(file.getFileId());
            fileOpt.ifPresent(fileUpload -> {
                fileUpload.setObjectKey(file.getStatus() ? file.getObjectKey() : "");
                list.add(fileUpload);
            });
        });
        return list;
    }
    @Override
    public void deleteFileIndexByFileIds(List<String> fileIds) {
        mapper.delete(new LambdaUpdateWrapper<LogsFileIndexEntity>()
                .or(wrapper -> fileIds.forEach(fileId -> wrapper.eq(LogsFileIndexEntity::getFileId, fileId))));
    }
    @Override
    public Optional<LogsFileUpload> getFileIndexByFileId(String fileId) {
        List<LogsFileIndexEntity> logsFileIndexList = mapper.selectList(
                new LambdaQueryWrapper<LogsFileIndexEntity>().eq(LogsFileIndexEntity::getFileId, fileId));
        if (CollectionUtils.isEmpty(logsFileIndexList)) {
            return Optional.empty();
        }
        LogsFileIndexEntity entity = logsFileIndexList.get(0);
        List<LogsFile> logsFileList = logsFileIndexList.stream().map(this::entity2LogsFile).collect(Collectors.toList());
        return Optional.of(LogsFileUpload.builder()
                .deviceSn(entity.getDeviceSn())
                .deviceModelDomain(String.valueOf(entity.getDomain()))
                .list(logsFileList)
                .fileId(fileId)
                .build());
    }
    private LogsFile entity2LogsFile(LogsFileIndexEntity entity) {
        if (Objects.isNull(entity)) {
            return null;
        }
        return LogsFile.builder()
                .bootIndex(entity.getBootIndex())
                .startTime(entity.getStartTime())
                .endTime(entity.getEndTime())
                .size(entity.getSize())
                .build();
    }
    private LogsFileIndexEntity logsFile2Entity(LogsFile file) {
        if (Objects.isNull(file)) {
            return null;
        }
        return LogsFileIndexEntity.builder()
                .bootIndex(file.getBootIndex())
                .size(file.getSize())
                .startTime(file.getStartTime())
                .endTime(file.getEndTime())
                .build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/LogsFileServiceImpl.java
New file
@@ -0,0 +1,163 @@
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.dji.sample.component.mqtt.model.EventsResultStatusEnum;
import com.dji.sample.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.service.impl.OssServiceContext;
import com.dji.sample.manage.dao.ILogsFileMapper;
import com.dji.sample.manage.model.dto.LogsFileDTO;
import com.dji.sample.manage.model.entity.LogsFileEntity;
import com.dji.sample.manage.model.receiver.LogsExtFileReceiver;
import com.dji.sample.manage.model.receiver.LogsFile;
import com.dji.sample.manage.model.receiver.LogsFileUpload;
import com.dji.sample.manage.service.ILogsFileIndexService;
import com.dji.sample.manage.service.ILogsFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
/**
 * @author sean
 * @version 1.2
 * @date 2022/9/7
 */
@Service
@Transactional
public class LogsFileServiceImpl implements ILogsFileService {
    @Autowired
    private ILogsFileMapper mapper;
    @Autowired
    private ILogsFileIndexService logsFileIndexService;
    @Autowired
    private OssServiceContext ossService;
    @Autowired
    private OssConfiguration configuration;
    @Autowired
    private OssServiceContext ossServiceContext;
    @Override
    public List<LogsFileDTO> getLogsFileInfoByLogsId(String logsId) {
        return mapper.selectList(
                new LambdaQueryWrapper<LogsFileEntity>()
                        .eq(LogsFileEntity::getLogsId, logsId)).stream()
                .map(this::entity2Dto).collect(Collectors.toList());
    }
    private LogsFileDTO entity2Dto(LogsFileEntity entity) {
        if (Objects.isNull(entity)) {
            return null;
        }
        return LogsFileDTO.builder()
                .deviceSn(entity.getDeviceSn())
                .fileId(entity.getFileId())
                .fingerprint(entity.getFingerprint())
                .logsId(entity.getLogsId())
                .name(entity.getName())
                .objectKey(entity.getObjectKey())
                .size(entity.getSize())
                .status(entity.getStatus())
                .build();
    }
    @Override
    public List<LogsFileUpload> getLogsFileByLogsId(String logsId) {
        List<LogsFileDTO> logsFiles = this.getLogsFileInfoByLogsId(logsId);
        if (CollectionUtils.isEmpty(logsFiles)) {
            return new ArrayList<>();
        }
        return logsFileIndexService.getFileIndexByFileIds(logsFiles);
    }
    @Override
    public Boolean insertFile(LogsFileUpload file, String logsId) {
        LogsFileEntity entity = LogsFileEntity.builder()
                .logsId(logsId)
                .fileId(UUID.randomUUID().toString())
                .objectKey(file.getObjectKey())
                .status(false)
                .deviceSn(file.getDeviceSn())
                .build();
        boolean insert = mapper.insert(entity) > 0;
        if (!insert) {
            return false;
        }
        for (LogsFile logsFile : file.getList()) {
            insert = logsFileIndexService.insertFileIndex(logsFile, file.getDeviceSn(), Integer.valueOf(file.getDeviceModelDomain()), entity.getFileId());
            if (!insert) {
                return false;
            }
        }
        return true;
    }
    @Override
    public void deleteFileByLogsId(String logsId) {
        List<LogsFileDTO> logsFiles = this.getLogsFileInfoByLogsId(logsId);
        if (CollectionUtils.isEmpty(logsFiles)) {
            return;
        }
        mapper.delete(new LambdaUpdateWrapper<LogsFileEntity>().eq(LogsFileEntity::getLogsId, logsId));
        List<String> fileIds = new ArrayList<>();
        logsFiles.forEach(file -> {
            if (file.getStatus()) {
                ossService.deleteObject(configuration.getBucket(), file.getObjectKey());
            }
            fileIds.add(file.getFileId());
        });
        logsFileIndexService.deleteFileIndexByFileIds(fileIds);
    }
    @Override
    public void updateFile(String logsId, LogsExtFileReceiver fileReceiver) {
        List<LogsFileDTO> logsFiles = this.getLogsFileInfoByLogsId(logsId);
        if (CollectionUtils.isEmpty(logsFiles)) {
            return;
        }
        mapper.update(receiver2Entity(fileReceiver),
                new LambdaUpdateWrapper<LogsFileEntity>().eq(LogsFileEntity::getLogsId, logsId)
                        .eq(LogsFileEntity::getDeviceSn, fileReceiver.getDeviceSn()));
    }
    @Override
    public void updateFileUploadStatus(String logsId, Boolean isUploaded) {
        mapper.update(LogsFileEntity.builder().status(isUploaded).build(),
                new LambdaUpdateWrapper<LogsFileEntity>().eq(LogsFileEntity::getLogsId, logsId));
    }
    @Override
    public URL getLogsFileUrl(String logsId, String fileId) {
        LogsFileEntity logsFile = mapper.selectOne(new LambdaQueryWrapper<LogsFileEntity>()
                .eq(LogsFileEntity::getLogsId, logsId).eq(LogsFileEntity::getFileId, fileId));
        if (Objects.isNull(logsFile)) {
            return null;
        }
        return ossService.getObjectUrl(configuration.getBucket(), logsFile.getObjectKey());
    }
    private LogsFileEntity receiver2Entity(LogsExtFileReceiver receiver) {
        if (Objects.isNull(receiver)) {
            return null;
        }
        return LogsFileEntity.builder()
                .fingerprint(receiver.getFingerprint())
                .size(receiver.getSize())
                .status(Objects.nonNull(receiver.getProgress()) &&
                        EventsResultStatusEnum.OK.getDesc().equals(receiver.getProgress().getStatus()))
                .name(receiver.getKey().substring(receiver.getKey().lastIndexOf("/") + 1)).build();
    }
}
src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java
@@ -36,20 +36,27 @@
        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());
        });
        gatewayList.forEach(device -> this.getDeviceTopologyByGatewaySn(device.getDeviceSn())
                .ifPresent(topologyList::add));
        return topologyList;
    }
    public Optional<TopologyDTO> getDeviceTopologyByGatewaySn(String gatewaySn) {
        Optional<DeviceDTO> dtoOptional = deviceService.getDeviceBySn(gatewaySn);
        if (dtoOptional.isEmpty()) {
            return Optional.empty();
        }
        List<TopologyDeviceDTO> parents = new ArrayList<>();
        DeviceDTO device = dtoOptional.get();
        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);
        return Optional.ofNullable(TopologyDTO.builder().parents(parents).hosts(deviceTopoList).build());
    }
}
src/main/java/com/dji/sample/map/service/impl/GroupElementServiceImpl.java
@@ -193,7 +193,7 @@
        groupElement.setElementType(ElementTypeEnum.findVal(elementUpdate.getContent().getGeometry().getType()));
        groupElement.setColor(elementUpdate.getContent().getProperties().getColor());
        boolean clampToGround = elementUpdate.getContent().getProperties().getClampToGround();
        Boolean clampToGround = elementUpdate.getContent().getProperties().getClampToGround();
        groupElement.setClampToGround(clampToGround);
    }
}
src/main/java/com/dji/sample/media/controller/FileController.java
@@ -37,18 +37,18 @@
    }
    /**
     * Query the download address of the file according to the media file fingerprint,
     * Query the download address of the file according to the media file id,
     * and redirect to this address directly for download.
     * @param workspaceId
     * @param fingerprint
     * @param fileId
     * @param response
     */
    @GetMapping("/{workspace_id}/file/{fingerprint}/url")
    @GetMapping("/{workspace_id}/file/{file_id}/url")
    public void getFileUrl(@PathVariable(name = "workspace_id") String workspaceId,
                           @PathVariable String fingerprint, HttpServletResponse response) {
                           @PathVariable(name = "file_id") String fileId, HttpServletResponse response) {
        try {
            URL url = fileService.getObjectUrl(workspaceId, fingerprint);
            URL url = fileService.getObjectUrl(workspaceId, fileId);
            response.sendRedirect(url.toString());
        } catch (IOException e) {
            e.printStackTrace();
src/main/java/com/dji/sample/media/model/CredentialsDTO.java
@@ -21,11 +21,11 @@
    private String accessKeySecret;
    private Integer expire;
    private Long expire;
    private String securityToken;
    public CredentialsDTO(Credentials credentials, int expire) {
    public CredentialsDTO(Credentials credentials, long expire) {
        this.accessKeyId = credentials.accessKey();
        this.accessKeySecret = credentials.secretKey();
        this.securityToken = credentials.sessionToken();
@@ -36,13 +36,13 @@
        this.accessKeyId = credentials.getAccessKeyId();
        this.accessKeySecret = credentials.getAccessKeySecret();
        this.securityToken = credentials.getSecurityToken();
        this.expire = Math.toIntExact(expire);
        this.expire = expire;
    }
    public CredentialsDTO(com.amazonaws.services.securitytoken.model.Credentials credentials) {
        this.accessKeyId = credentials.getAccessKeyId();
        this.accessKeySecret = credentials.getSecretAccessKey();
        this.securityToken = credentials.getSessionToken();
        this.expire = Math.toIntExact((credentials.getExpiration().getTime() - System.currentTimeMillis()) / 1000);
        this.expire = (credentials.getExpiration().getTime() - System.currentTimeMillis()) / 1000;
    }
}
src/main/java/com/dji/sample/media/model/MediaFileDTO.java
@@ -18,6 +18,8 @@
@AllArgsConstructor
public class MediaFileDTO {
    private String fileId;
    private String fileName;
    private String filePath;
src/main/java/com/dji/sample/media/model/MediaFileEntity.java
@@ -23,6 +23,9 @@
    @TableId(type = IdType.AUTO)
    private Integer id;
    @TableField("file_id")
    private String fileId;
    @TableField("file_name")
    private String fileName;
src/main/java/com/dji/sample/media/service/IFileService.java
@@ -49,8 +49,8 @@
    /**
     * Get the download address of the file.
     * @param workspaceId
     * @param fingerprint
     * @param fileId
     * @return
     */
    URL getObjectUrl(String workspaceId, String fingerprint);
    URL getObjectUrl(String workspaceId, String fileId);
}
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java
@@ -24,6 +24,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@@ -54,6 +55,13 @@
        return Optional.ofNullable(fileEntity);
    }
    private Optional<MediaFileEntity> getMediaByFileId(String workspaceId, String fileId) {
        MediaFileEntity fileEntity = mapper.selectOne(new LambdaQueryWrapper<MediaFileEntity>()
                .eq(MediaFileEntity::getWorkspaceId, workspaceId)
                .eq(MediaFileEntity::getFileId, fileId));
        return Optional.ofNullable(fileEntity);
    }
    @Override
    public Boolean checkExist(String workspaceId, String fingerprint) {
        return this.getMediaByFingerprint(workspaceId, fingerprint).isPresent();
@@ -63,6 +71,7 @@
    public Integer saveFile(String workspaceId, FileUploadDTO file) {
        MediaFileEntity fileEntity = this.fileUploadConvertToEntity(file);
        fileEntity.setWorkspaceId(workspaceId);
        fileEntity.setFileId(UUID.randomUUID().toString());
        return mapper.insert(fileEntity);
    }
@@ -90,8 +99,8 @@
    }
    @Override
    public URL getObjectUrl(String workspaceId, String fingerprint) {
        Optional<MediaFileEntity> mediaFileOpt = getMediaByFingerprint(workspaceId, fingerprint);
    public URL getObjectUrl(String workspaceId, String fileId) {
        Optional<MediaFileEntity> mediaFileOpt = getMediaByFileId(workspaceId, fileId);
        if (mediaFileOpt.isEmpty()) {
            throw new IllegalArgumentException("{} doesn't exist.");
        }
@@ -140,6 +149,7 @@
        if (entity != null) {
            builder.fileName(entity.getFileName())
                    .fileId(entity.getFileId())
                    .filePath(entity.getFilePath())
                    .isOriginal(entity.getIsOriginal())
                    .fingerprint(entity.getFingerprint())
src/main/resources/application.yml
@@ -79,26 +79,25 @@
#  enable: true
#  provider: aws
#  endpoint: https://s3.us-east-1.amazonaws.com
#  access-key:
#  secret-key:
#  access-key:
#  secret-key:
#  expire: 3600
#  region: us-east-1
#  role-session-name: cloudApi
#  role-arn:
#  role-arn:
#  bucket: cloudapi-bucket
#  object-dir-prefix: wayline
# MinIO is temporarily unavailable.
#oss:
#  enable: false
#  enable: true
#  provider: minio
#  endpoint:
#  access-key:
#  secret-key:
#  bucket:
#  expire:
#  region:
#  object-dir-prefix:
#  endpoint: http://192.168.1.1:9000
#  access-key: minioadmin
#  secret-key: minioadmin
#  bucket: cloud-bucket
#  expire: 3600
#  region: us-east-1
#  object-dir-prefix: wayline
logging:
  level:
src/main/resources/hms.json
New file
Diff too large