From 9b2eedb85d53ca32610c32c6e50b5230ab3b16cf Mon Sep 17 00:00:00 2001
From: sean.zhou <sean.zhou@dji.com>
Date: Fri, 22 Jul 2022 20:16:03 +0800
Subject: [PATCH] V1.1.0 for dock

---
 src/main/java/com/dji/sample/manage/model/param/DeviceQueryParam.java                         |    6 
 src/main/java/com/dji/sample/manage/model/dto/UserLoginDTO.java                               |    3 
 src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java               |  136 
 src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultHandler.java          |   18 
 src/main/java/com/dji/sample/media/model/MediaFileDTO.java                                    |    6 
 src/main/java/com/dji/sample/wayline/model/dto/FLightTaskProgress.java                        |   16 
 src/main/java/com/dji/sample/storage/service/impl/StorageServiceImpl.java                     |   56 
 src/main/java/com/dji/sample/manage/service/impl/WorkspaceServiceImpl.java                    |   60 
 src/main/java/com/dji/sample/component/mqtt/handler/StatusRouter.java                         |   67 
 src/main/java/com/dji/sample/wayline/model/dto/FlightTaskFileDTO.java                         |   22 
 src/main/java/com/dji/sample/manage/service/IUserService.java                                 |   14 
 src/main/java/com/dji/sample/component/mqtt/handler/RequestsRouter.java                       |   49 
 src/main/java/com/dji/sample/component/mybatis/MybatisPlusMetaObjectHandler.java              |    8 
 src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java                       |   36 
 src/main/java/com/dji/sample/component/mqtt/handler/InboundMessageRouter.java                 |  118 
 src/main/java/com/dji/sample/manage/controller/LoginController.java                           |    5 
 src/main/java/com/dji/sample/component/mqtt/model/StateDataEnum.java                          |   26 
 src/main/java/com/dji/sample/component/mqtt/handler/StateRouter.java                          |  104 
 src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java                    |    6 
 src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java     |    7 
 src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java                  |  212 +
 src/main/java/com/dji/sample/media/model/CredentialsDTO.java                                  |   10 
 src/main/java/com/dji/sample/manage/controller/DeviceController.java                          |  106 
 src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java                             |   43 
 src/main/java/com/dji/sample/wayline/model/entity/WaylineJobEntity.java                       |   56 
 src/main/java/com/dji/sample/component/mqtt/model/Chan.java                                   |   45 
 src/main/java/com/dji/sample/component/websocket/service/impl/WebSocketManageServiceImpl.java |   90 
 src/main/java/com/dji/sample/wayline/model/entity/WaylineFileEntity.java                      |   62 
 src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java                    |   52 
 src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java                          |   56 
 src/main/java/com/dji/sample/wayline/model/dto/WaylineFileUploadDTO.java                      |   18 
 src/main/java/com/dji/sample/media/model/MediaFileEntity.java                                 |    3 
 src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java                 |  112 
 src/main/java/com/dji/sample/media/service/IFileService.java                                  |   19 
 src/main/java/com/dji/sample/manage/model/entity/UserEntity.java                              |    2 
 src/main/java/com/dji/sample/wayline/model/dto/FlightTaskProgressExt.java                     |   16 
 src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java                          |   18 
 src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java                  |   83 
 src/main/java/com/dji/sample/component/mqtt/handler/StateLiveCapacityHandler.java             |   39 
 src/main/java/com/dji/sample/component/mqtt/model/RequestsMethodEnum.java                     |   38 
 src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java                     |    4 
 src/main/java/com/dji/sample/common/error/CommonErrorEnum.java                                |   10 
 src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java                        |    7 
 src/main/java/com/dji/sample/component/websocket/config/WebSocketDefaultFactory.java          |    7 
 src/main/java/com/dji/sample/manage/service/ILiveStreamService.java                           |    9 
 src/main/java/com/dji/sample/wayline/model/param/WaylineQueryParam.java                       |   30 
 src/main/java/com/dji/sample/manage/controller/DeviceHmsController.java                       |   53 
 src/main/java/com/dji/sample/manage/model/enums/UserTypeEnum.java                             |   26 
 src/main/java/com/dji/sample/manage/controller/LiveStreamController.java                      |   21 
 src/main/java/com/dji/sample/manage/model/receiver/BindStatusReceiver.java                    |   30 
 src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java                         |   21 
 pom.xml                                                                                       |   31 
 src/main/java/com/dji/sample/component/ApplicationBootInitial.java                            |   24 
 src/main/java/com/dji/sample/manage/controller/UserController.java                            |   41 
 src/main/java/com/dji/sample/component/websocket/service/IWebSocketManageService.java         |   23 
 src/main/java/com/dji/sample/common/util/JwtUtil.java                                         |    4 
 src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java             |   73 
 src/main/java/com/dji/sample/manage/model/receiver/StorageReceiver.java                       |   16 
 src/main/java/com/dji/sample/manage/model/receiver/DockSdrReceiver.java                       |   18 
 src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java                       |   67 
 src/main/java/com/dji/sample/manage/model/receiver/PositionStateReceiver.java                 |    2 
 src/main/java/com/dji/sample/manage/model/receiver/LiveCapacityReceiver.java                  |   20 
 src/main/java/com/dji/sample/component/mqtt/model/ErrorInfoReply.java                         |   23 
 src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java        |   38 
 src/main/java/com/dji/sample/component/mqtt/model/ServiceReply.java                           |   20 
 src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java                 |   54 
 src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java                |   10 
 src/main/java/com/dji/sample/component/mqtt/model/CommonTopicResponse.java                    |    2 
 src/main/java/com/dji/sample/media/service/IMediaService.java                                 |   16 
 src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java                  |   84 
 src/main/java/com/dji/sample/component/mqtt/handler/StateDeviceBasicHandler.java              |   38 
 src/main/java/com/dji/sample/component/mqtt/model/ServicesMethodEnum.java                     |   29 
 src/main/java/com/dji/sample/manage/dao/IDeviceHmsMapper.java                                 |   12 
 src/main/java/com/dji/sample/manage/model/receiver/WirelessLinkStateReceiver.java             |    2 
 src/main/java/com/dji/sample/wayline/model/dto/FlightTaskCreateDTO.java                       |   24 
 src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java                                  |  155 +
 src/main/java/com/dji/sample/manage/model/entity/DeviceEntity.java                            |   19 
 src/main/java/com/dji/sample/manage/service/impl/AbstractTSAService.java                      |   28 
 src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java                         |   14 
 src/main/java/com/dji/sample/manage/model/receiver/OsdPayloadReceiver.java                    |   33 
 src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java                   |   13 
 src/main/java/com/dji/sample/manage/service/ITSAService.java                                  |   18 
 src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java                  |    8 
 src/main/resources/application.yml                                                            |   80 
 src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java                   |  129 
 src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java                     |   28 
 src/main/java/com/dji/sample/manage/service/impl/GatewayOSDServiceImpl.java                   |   30 
 src/main/java/com/dji/sample/component/oss/service/IOssService.java                           |   18 
 src/main/java/com/dji/sample/component/mqtt/handler/StateDefaultHandler.java                  |   26 
 src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java                            |   19 
 src/main/java/com/dji/sample/component/oss/model/enums/OssTypeEnum.java                       |   28 
 sql/cloud_sample.sql                                                                          |  157 
 src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java                       |  676 ++++-
 src/main/java/com/dji/sample/CloudApiSampleApplication.java                                   |    1 
 src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java                     |    4 
 src/main/java/com/dji/sample/storage/controller/StorageController.java                        |   21 
 src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java                          |   22 
 api/Cloud API Demo.postman_collection.json                                                    |  328 ++
 src/main/java/com/dji/sample/wayline/model/dto/WaylineFileDTO.java                            |   43 
 src/main/java/com/dji/sample/manage/model/dto/UserListDTO.java                                |   34 
 src/main/java/com/dji/sample/wayline/dao/IWaylineJobMapper.java                               |   12 
 src/main/java/com/dji/sample/component/websocket/config/AuthPrincipalHandler.java             |    2 
 src/main/java/com/dji/sample/manage/model/common/HmsMessage.java                              |   16 
 README.md                                                                                     |    6 
 src/main/java/com/dji/sample/manage/service/ICameraVideoService.java                          |   26 
 src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java                                  |   22 
 src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java              |  135 +
 src/main/java/com/dji/sample/component/oss/service/impl/OssServiceContext.java                |   58 
 src/main/java/com/dji/sample/component/redis/RedisConfiguration.java                          |   63 
 src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java                    |   58 
 src/main/java/com/dji/sample/component/mqtt/handler/StateFirmwareVersionHandler.java          |   62 
 src/main/java/com/dji/sample/manage/model/receiver/DevicePayloadReceiver.java                 |    3 
 src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java                          |   56 
 src/main/java/com/dji/sample/common/error/StorageErrorEnum.java                               |   38 
 src/main/java/com/dji/sample/component/GlobalScheduleService.java                             |   53 
 src/main/java/com/dji/sample/manage/model/receiver/NetworkStateReceiver.java                  |   18 
 src/main/java/com/dji/sample/component/redis/RedisConst.java                                  |   29 
 src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java                         |   21 
 src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java                    |   27 
 src/main/java/com/dji/sample/manage/model/receiver/FirmwareVersionReceiver.java               |   29 
 src/main/java/com/dji/sample/media/controller/FileController.java                             |   35 
 src/main/java/com/dji/sample/manage/model/receiver/AlternateLandPointReceiver.java            |   18 
 src/main/java/com/dji/sample/media/controller/MediaController.java                            |   24 
 src/main/java/com/dji/sample/component/mqtt/config/MqttInboundConfiguration.java              |    5 
 src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java                             |    4 
 src/main/java/com/dji/sample/manage/model/receiver/HmsArgsReceiver.java                       |   18 
 src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java                      |   50 
 src/main/java/com/dji/sample/manage/model/receiver/OrganizationGetReceiver.java               |   16 
 src/main/java/com/dji/sample/media/model/FileExtensionDTO.java                                |    2 
 src/main/java/com/dji/sample/manage/model/dto/CapacityCameraDTO.java                          |    4 
 src/main/java/com/dji/sample/manage/service/ICapacityCameraService.java                       |   17 
 src/main/java/com/dji/sample/manage/model/receiver/BindDeviceReceiver.java                    |   22 
 src/main/java/com/dji/sample/component/redis/RedisOpsUtils.java                               |  187 +
 src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java                |   53 
 src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java                         |   78 
 src/main/java/com/dji/sample/manage/model/entity/DeviceHmsEntity.java                         |   76 
 src/main/java/com/dji/sample/manage/model/receiver/DroneChargeStateReceiver.java              |   16 
 src/main/java/com/dji/sample/component/mqtt/model/RequestsReply.java                          |   44 
 src/main/java/com/dji/sample/component/oss/model/OssConfiguration.java                        |   94 
 src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java                         |   48 
 src/main/java/com/dji/sample/manage/service/IWorkspaceService.java                            |   24 
 src/main/java/com/dji/sample/component/mqtt/handler/AbstractStateTopicHandler.java            |   39 
 src/main/java/com/dji/sample/manage/service/IDeviceService.java                               |   71 
 src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java                    |  133 +
 src/main/java/com/dji/sample/media/model/FileUploadCallback.java                              |   18 
 api/Cloud API Demo.postman_environment.json                                                   |    6 
 src/main/java/com/dji/sample/manage/model/receiver/DockMediaFileDetailReceiver.java           |   14 
 src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java                           |    2 
 src/main/java/com/dji/sample/manage/model/receiver/DockSubDeviceReceiver.java                 |   20 
 src/main/java/com/dji/sample/manage/model/entity/WorkspaceEntity.java                         |    2 
 src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java                       |   49 
 src/main/java/com/dji/sample/manage/model/receiver/StatusSubDeviceReceiver.java               |    2 
 src/main/java/com/dji/sample/manage/model/common/HmsJsonUtil.java                             |   49 
 src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java                            |   29 
 src/main/java/com/dji/sample/manage/model/dto/WorkspaceDTO.java                               |    4 
 src/main/java/com/dji/sample/manage/model/enums/PayloadModelEnum.java                         |   61 
 src/main/java/com/dji/sample/manage/model/receiver/DeviceBasicReceiver.java                   |    6 
 src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java                   |   26 
 src/main/java/com/dji/sample/manage/model/dto/DeviceHmsDTO.java                               |   57 
 src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java                         |   56 
 src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java             |    6 
 src/main/java/com/dji/sample/wayline/model/dto/FlightTaskProgressReceiver.java                |   19 
 src/main/java/com/dji/sample/manage/model/dto/TopologyDeviceDTO.java                          |   11 
 src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java                     |   74 
 src/main/java/com/dji/sample/component/oss/service/impl/OssAspectHandler.java                 |   33 
 src/main/java/com/dji/sample/manage/model/param/DeviceHmsQueryParam.java                      |   45 
 src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java                       |    8 
 src/main/java/com/dji/sample/manage/model/dto/TelemetryDTO.java                               |    4 
 src/main/java/com/dji/sample/manage/model/receiver/OsdDockTransmissionReceiver.java           |   18 
 src/main/java/com/dji/sample/wayline/dao/IWaylineFileMapper.java                              |    2 
 src/main/java/com/dji/sample/manage/service/impl/TopologyServiceImpl.java                     |    2 
 src/main/java/com/dji/sample/manage/service/IDeviceHmsService.java                            |   21 
 src/main/java/com/dji/sample/storage/service/IStorageService.java                             |    9 
 173 files changed, 6,127 insertions(+), 1,002 deletions(-)

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

--
Gitblit v1.9.3