Ver código fonte

Merge branch 'bind'

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
kindring 1 ano atrás
pai
commit
3990f8d7cf
100 arquivos alterados com 5555 adições e 1533 exclusões
  1. 9 1
      README.md
  2. 1 1
      olDoc/_content/ability/proxy.md
  3. 1 1
      olDoc/_content/introduction/config.md
  4. 1 1
      package/my.cnf
  5. 767 284
      package/mysqlDocker/db.sql
  6. 12 12
      package/redisDocker/redis.conf
  7. 7 0
      pom.xml
  8. 29 21
      sql/mysql.sql
  9. 29 21
      sql/初始化.sql
  10. 31 30
      sql/新版数据结构 1.11.sql
  11. 3 0
      src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
  12. 26 4
      src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
  13. 2 2
      src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java
  14. 86 0
      src/main/java/com/genersoft/iot/vmp/conf/GlobalExceptionHandler.java
  15. 8 8
      src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java
  16. 27 0
      src/main/java/com/genersoft/iot/vmp/conf/security/SaTokenConfigure.java
  17. 6 6
      src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java
  18. 55 0
      src/main/java/com/genersoft/iot/vmp/conf/security/StpInterfaceImpl.java
  19. 46 43
      src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
  20. 9 9
      src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java
  21. 24 0
      src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaAdminCheckLogin.java
  22. 41 0
      src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaAdminCheckRole.java
  23. 40 0
      src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaAdminPermission.java
  24. 24 0
      src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaUserCheckLogin.java
  25. 41 0
      src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaUserCheckRole.java
  26. 41 0
      src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaUserPermission.java
  27. 37 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  28. 126 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipUserConfig.java
  29. 1 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java
  30. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java
  31. 19 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
  32. 31 15
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java
  33. 57 36
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  34. 114 73
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
  35. 165 78
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  36. 38 22
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  37. 79 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/HfyCodeResponseMessageHandler.java
  38. 1 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  39. 84 0
      src/main/java/com/genersoft/iot/vmp/service/IAccountService.java
  40. 61 0
      src/main/java/com/genersoft/iot/vmp/service/IAdminService.java
  41. 2 0
      src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java
  42. 24 0
      src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java
  43. 5 1
      src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
  44. 52 0
      src/main/java/com/genersoft/iot/vmp/service/ISipConfigService.java
  45. 0 31
      src/main/java/com/genersoft/iot/vmp/service/IUserService.java
  46. 2 0
      src/main/java/com/genersoft/iot/vmp/service/bean/BroadcastCallback.java
  47. 95 0
      src/main/java/com/genersoft/iot/vmp/service/impl/AccountServiceImpl.java
  48. 128 0
      src/main/java/com/genersoft/iot/vmp/service/impl/AdminServiceImpl.java
  49. 6 0
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java
  50. 93 3
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
  51. 7 4
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  52. 385 148
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  53. 64 0
      src/main/java/com/genersoft/iot/vmp/service/impl/SipConfigService.java
  54. 0 92
      src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java
  55. 8 1
      src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
  56. 56 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/AccountMapper.java
  57. 55 16
      src/main/java/com/genersoft/iot/vmp/storager/dao/AdminMapper.java
  58. 3 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  59. 20 1
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
  60. 0 1
      src/main/java/com/genersoft/iot/vmp/storager/dao/RoleMapper.java
  61. 89 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/SipConfigMapper.java
  62. 1 1
      src/main/java/com/genersoft/iot/vmp/storager/dao/dto/AdminAccount.java
  63. 70 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/dto/UserAccount.java
  64. 51 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/dto/deviceBind.java
  65. 24 8
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  66. 27 22
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  67. 33 0
      src/main/java/com/genersoft/iot/vmp/utils/AuthorUtil.java
  68. 60 0
      src/main/java/com/genersoft/iot/vmp/utils/DeviceHelper.java
  69. 487 0
      src/main/java/com/genersoft/iot/vmp/utils/StpAdminUtil.java
  70. 496 0
      src/main/java/com/genersoft/iot/vmp/utils/StpUserUtil.java
  71. 12 10
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
  72. 102 0
      src/main/java/com/genersoft/iot/vmp/vmanager/account/AccountController.java
  73. 160 0
      src/main/java/com/genersoft/iot/vmp/vmanager/account/AccountDeviceControl.java
  74. 4 1
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorCode.java
  75. 6 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorHook.java
  76. 38 9
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java
  77. 2 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/SseController/SseController.java
  78. 2 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/aiLib/AiApi.java
  79. 4 2
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/aiLib/AiControl.java
  80. 2 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java
  81. 2 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java
  82. 2 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java
  83. 42 30
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
  84. 2 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/gbStream/GbStreamController.java
  85. 2 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java
  86. 2 0
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java
  87. 87 137
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
  88. 76 53
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java
  89. 110 131
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java
  90. 59 49
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
  91. 1 8
      src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java
  92. 105 0
      src/main/java/com/genersoft/iot/vmp/vmanager/server/sipController.java
  93. 65 78
      src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
  94. 4 3
      src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java
  95. 10 9
      src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java
  96. 7 7
      src/main/java/com/genersoft/iot/vmp/web/gb28181/AuthController.java
  97. 1 1
      src/main/resources/all-application.yml
  98. 19 2
      src/main/resources/application.yml
  99. 1 0
      web_src/src/assets/base.css
  100. 103 0
      web_src/src/components/account_com/bindDevice.vue

+ 9 - 1
README.md

@@ -1,12 +1,20 @@
 # 合方圆国标平台
+
 > 本项目基于开源项目 [wvp](https://github.com/648540858/wvp-GB28181-pro)
 > 定制开发特色功能.
+
 ## 项目开发环境
 
+## 关键数据
+
+### 权限验证 `sa-token`
+
 ## 数据库设计
+
 ### 预置位表
+
 > 设备预置位通过预置位国标预置位查询指令实现.
-> 配合数据表存储预置位信息,从而实现调用功能指定预置位功能  
+> 配合数据表存储预置位信息,从而实现调用功能指定预置位功能
 
 #### preset
 

+ 1 - 1
olDoc/_content/ability/proxy.md

@@ -3,7 +3,7 @@
 不是所有的摄像机都支持国标或者推流的,但是这些设备可以得到一个视频播放地址,通常为rtsp协议,
 以大华为例:
 ```text
-rtsp://{user}:{passwd}@{ipc_ip}:{rtsp_port}/cam/realmonitor?channel=1&subtype=0
+rtsp://{adminAccount}:{passwd}@{ipc_ip}:{rtsp_port}/cam/realmonitor?channel=1&subtype=0
 ```
 可以得到这样一个流地址,可以直接用vlc进行播放,此时我们可以通过拉流代理功能将这个设备推送给其他国标平台了。
 流程如下:

+ 1 - 1
olDoc/_content/introduction/config.md

@@ -84,7 +84,7 @@ media:
 ### 2.4 个性化定制信息配置
 ```yaml
 # [根据业务需求配置]
-user-settings:
+adminAccount-settings:
     # [可选] 服务ID,不写则为000000
     server-id:
     # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true

+ 1 - 1
package/my.cnf

@@ -28,7 +28,7 @@ skip-name-resolve
 datadir=/var/lib/mysql
 socket=/var/run/mysqld/mysqld.sock
 secure-file-priv=/var/lib/mysql-files
-user=mysql
+adminAccount=mysql
 
 pid-file=/var/run/mysqld/mysqld.pid
 [client]

Diferenças do arquivo suprimidas por serem muito extensas
+ 767 - 284
package/mysqlDocker/db.sql


+ 12 - 12
package/redisDocker/redis.conf

@@ -220,7 +220,7 @@ save 60 10000
 
 # By default Redis will stop accepting writes if RDB snapshots are enabled
 # (at least one save point) and the latest background save failed.
-# This will make the user aware (in a hard way) that data is not persisting
+# This will make the adminAccount aware (in a hard way) that data is not persisting
 # on disk properly, otherwise chances are that no one will notice and some
 # disaster will happen.
 #
@@ -278,7 +278,7 @@ dir /data/redis
 #    master if the replication link is lost for a relatively small amount of
 #    time. You may want to configure the replication backlog size (see the next
 #    sections of this file) with a sensible value depending on your needs.
-# 3) Replication is automatic and does not need user intervention. After a
+# 3) Replication is automatic and does not need adminAccount intervention. After a
 #    network partition replicas automatically try to reconnect to masters
 #    and resynchronize with them.
 #
@@ -499,7 +499,7 @@ replica-priority 100
 # This should stay commented out for backward compatibility and because most
 # people do not need auth (e.g. they run their own servers).
 #
-# Warning: since Redis is pretty fast an outside user can try up to
+# Warning: since Redis is pretty fast an outside adminAccount can try up to
 # 150k passwords per second against a good box. This means that you should
 # use a very strong password otherwise it will be very easy to break.
 #
@@ -643,11 +643,11 @@ requirepass hfyredis28181
 # are executed in constant time. Another thread will incrementally free the
 # object in the background as fast as possible.
 #
-# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled.
+# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are adminAccount-controlled.
 # It's up to the design of the application to understand when it is a good
 # idea to use one or the other. However the Redis server sometimes has to
 # delete keys or flush the whole database as a side effect of other operations.
-# Specifically Redis deletes objects independently of a user call in the
+# Specifically Redis deletes objects independently of a adminAccount call in the
 # following scenarios:
 #
 # 1) On eviction, because of the maxmemory and maxmemory policy configurations,
@@ -781,9 +781,9 @@ auto-aof-rewrite-min-size 64mb
 # to be truncated at the end. The following option controls this behavior.
 #
 # If aof-load-truncated is set to yes, a truncated AOF file is loaded and
-# the Redis server starts emitting a log to inform the user of the event.
+# the Redis server starts emitting a log to inform the adminAccount of the event.
 # Otherwise if the option is set to no, the server aborts with an error
-# and refuses to start. When the option is set to no, the user requires
+# and refuses to start. When the option is set to no, the adminAccount requires
 # to fix the AOF file using the "redis-check-aof" utility before to restart
 # the server.
 #
@@ -816,7 +816,7 @@ aof-use-rdb-preamble yes
 # SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
 # used to stop a script that did not yet called write commands. The second
 # is the only way to shut down the server in the case a write command was
-# already issued by the script but the user doesn't want to wait for the natural
+# already issued by the script but the adminAccount doesn't want to wait for the natural
 # termination of the script.
 #
 # Set it to 0 or a negative value for unlimited execution without warnings.
@@ -863,7 +863,7 @@ lua-time-limit 5000
 #    If the last interaction is too old, the replica will not try to failover
 #    at all.
 #
-# The point "2" can be tuned by user. Specifically a replica will not perform
+# The point "2" can be tuned by adminAccount. Specifically a replica will not perform
 # the failover if, since the last interaction with the master, the time
 # elapsed is greater than:
 #
@@ -997,7 +997,7 @@ slowlog-max-len 128
 # at runtime in order to collect data related to possible sources of
 # latency of a Redis instance.
 #
-# Via the LATENCY command this information is available to the user that can
+# Via the LATENCY command this information is available to the adminAccount that can
 # print graphs and obtain reports.
 #
 # The system only logs operations that were performed in a time equal or
@@ -1053,7 +1053,7 @@ latency-monitor-threshold 0
 #
 #  notify-keyspace-events Ex
 #
-#  By default all notifications are disabled because most users don't need
+#  By default all notifications are disabled because most adminAccounts don't need
 #  this feature and the feature has some overhead. Note that if you don't
 #  specify at least one of K or E, no events will be delivered.
 notify-keyspace-events ""
@@ -1219,7 +1219,7 @@ client-output-buffer-limit pubsub 32mb 8mb 60
 # handled with more precision.
 #
 # The range is between 1 and 500, however a value over 100 is usually not
-# a good idea. Most users should use the default of 10 and raise this up to
+# a good idea. Most adminAccounts should use the default of 10 and raise this up to
 # 100 only in environments where very low latency is required.
 hz 10
 

+ 7 - 0
pom.xml

@@ -252,6 +252,13 @@
 			<version>3.1.1</version>
 		</dependency>
 
+<!--		sa-Token 权限验证-->
+		<dependency>
+			<groupId>cn.dev33</groupId>
+			<artifactId>sa-token-spring-boot-starter</artifactId>
+			<version>1.37.0</version>
+		</dependency>
+
 		<!-- 获取系统信息 -->
 		<dependency>
 			<groupId>com.github.oshi</groupId>

+ 29 - 21
sql/mysql.sql

@@ -499,40 +499,47 @@ CREATE TABLE `stream_push` (
 -- Dumping data for table `stream_push`
 --
 
-LOCK TABLES `stream_push` WRITE;
+LOCK
+TABLES `stream_push` WRITE;
 /*!40000 ALTER TABLE `stream_push` DISABLE KEYS */;
 /*!40000 ALTER TABLE `stream_push` ENABLE KEYS */;
-UNLOCK TABLES;
+UNLOCK
+TABLES;
 
 --
--- Table structure for table `user`
+-- Table structure for table `adminAccount`
 --
 
-DROP TABLE IF EXISTS `user`;
+DROP TABLE IF EXISTS `adminAccount`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!50503 SET character_set_client = utf8mb4 */;
-CREATE TABLE `user` (
-                        `id` int NOT NULL AUTO_INCREMENT,
-                        `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `roleId` int NOT NULL,
-                        `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `pushKey` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                        PRIMARY KEY (`id`) USING BTREE,
-                        UNIQUE KEY `user_username_uindex` (`username`) USING BTREE
+CREATE TABLE `adminAccount`
+(
+    `id`         int                                                           NOT NULL AUTO_INCREMENT,
+    `username`   varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+    `password`   varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+    `roleId`     int                                                           NOT NULL,
+    `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL,
+    `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL,
+    `pushKey`    varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+    PRIMARY KEY (`id`) USING BTREE,
+    UNIQUE KEY `user_username_uindex` (`username`) USING BTREE
 ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
--- Dumping data for table `user`
+-- Dumping data for table `adminAccount`
 --
 
-LOCK TABLES `user` WRITE;
-/*!40000 ALTER TABLE `user` DISABLE KEYS */;
-INSERT INTO `user` VALUES (1,'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021 - 04 - 13 14:14:57','2021 - 04 - 13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
-/*!40000 ALTER TABLE `user` ENABLE KEYS */;
-UNLOCK TABLES;
+LOCK
+TABLES `adminAccount` WRITE;
+/*!40000 ALTER TABLE `adminAccount` DISABLE KEYS */;
+INSERT INTO `adminAccount`
+VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1, '2021 - 04 - 13 14:14:57', '2021 - 04 - 13 14:14:57',
+        '3e80d1762a324d5b0ff636e0bd16f1e3');
+/*!40000 ALTER TABLE `adminAccount` ENABLE KEYS */;
+UNLOCK
+TABLES;
 
 --
 -- Table structure for table `user_role`
@@ -541,7 +548,8 @@ UNLOCK TABLES;
 DROP TABLE IF EXISTS `user_role`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!50503 SET character_set_client = utf8mb4 */;
-CREATE TABLE `user_role` (
+CREATE TABLE `user_role`
+(
                              `id` int NOT NULL AUTO_INCREMENT,
                              `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                              `authority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

+ 29 - 21
sql/初始化.sql

@@ -505,40 +505,47 @@ CREATE TABLE `stream_push` (
 -- Dumping data for table `stream_push`
 --
 
-LOCK TABLES `stream_push` WRITE;
+LOCK
+TABLES `stream_push` WRITE;
 /*!40000 ALTER TABLE `stream_push` DISABLE KEYS */;
 /*!40000 ALTER TABLE `stream_push` ENABLE KEYS */;
-UNLOCK TABLES;
+UNLOCK
+TABLES;
 
 --
--- Table structure for table `user`
+-- Table structure for table `adminAccount`
 --
 
-DROP TABLE IF EXISTS `user`;
+DROP TABLE IF EXISTS `adminAccount`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!50503 SET character_set_client = utf8mb4 */;
-CREATE TABLE `user` (
-                        `id` int NOT NULL AUTO_INCREMENT,
-                        `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `roleId` int NOT NULL,
-                        `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-                        `pushKey` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
-                        PRIMARY KEY (`id`) USING BTREE,
-                        UNIQUE KEY `user_username_uindex` (`username`) USING BTREE
+CREATE TABLE `adminAccount`
+(
+    `id`         int                                                           NOT NULL AUTO_INCREMENT,
+    `username`   varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+    `password`   varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+    `roleId`     int                                                           NOT NULL,
+    `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL,
+    `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL,
+    `pushKey`    varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+    PRIMARY KEY (`id`) USING BTREE,
+    UNIQUE KEY `user_username_uindex` (`username`) USING BTREE
 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
--- Dumping data for table `user`
+-- Dumping data for table `adminAccount`
 --
 
-LOCK TABLES `user` WRITE;
-/*!40000 ALTER TABLE `user` DISABLE KEYS */;
-INSERT INTO `user` VALUES (1,'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
-/*!40000 ALTER TABLE `user` ENABLE KEYS */;
-UNLOCK TABLES;
+LOCK
+TABLES `adminAccount` WRITE;
+/*!40000 ALTER TABLE `adminAccount` DISABLE KEYS */;
+INSERT INTO `adminAccount`
+VALUES (1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 1, '2021-04-13 14:14:57', '2021-04-13 14:14:57',
+        '3e80d1762a324d5b0ff636e0bd16f1e3');
+/*!40000 ALTER TABLE `adminAccount` ENABLE KEYS */;
+UNLOCK
+TABLES;
 
 --
 -- Table structure for table `user_role`
@@ -547,7 +554,8 @@ UNLOCK TABLES;
 DROP TABLE IF EXISTS `user_role`;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!50503 SET character_set_client = utf8mb4 */;
-CREATE TABLE `user_role` (
+CREATE TABLE `user_role`
+(
                              `id` int NOT NULL AUTO_INCREMENT,
                              `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                              `authority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

+ 31 - 30
sql/新版数据结构 1.11.sql

@@ -433,39 +433,40 @@ CREATE TABLE `stream_proxy`  (
 -- ----------------------------
 DROP TABLE IF EXISTS `stream_push`;
 CREATE TABLE `stream_push`  (
-  `id` int NOT NULL AUTO_INCREMENT,
-  `app` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-  `stream` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-  `totalReaderCount` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
-  `originType` int NULL DEFAULT NULL,
-  `originTypeStr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
-  `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
-  `aliveSecond` int NULL DEFAULT NULL,
-  `mediaServerId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
-  `serverId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-  `pushTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
-  `status` int NULL DEFAULT NULL,
-  `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
-  `pushIng` int NULL DEFAULT NULL,
-  `self` int NULL DEFAULT NULL,
-  PRIMARY KEY (`id`) USING BTREE,
-  UNIQUE INDEX `stream_push_pk`(`app` ASC, `stream` ASC) USING BTREE
+                                `id` int NOT NULL AUTO_INCREMENT,
+                                `app` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+                                `stream`           varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+                                `totalReaderCount` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+                                `originType`       int NULL DEFAULT NULL,
+                                `originTypeStr`    varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+                                `createTime`       varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+                                `aliveSecond`      int NULL DEFAULT NULL,
+                                `mediaServerId`    varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+                                `serverId`         varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+                                `pushTime`         varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+                                `status`           int NULL DEFAULT NULL,
+                                `updateTime`       varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+                                `pushIng`          int NULL DEFAULT NULL,
+                                `self`             int NULL DEFAULT NULL,
+                                PRIMARY KEY (`id`) USING BTREE,
+                                UNIQUE INDEX `stream_push_pk`(`app` ASC, `stream` ASC) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 161 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
 
 -- ----------------------------
--- Table structure for user
--- ----------------------------
-DROP TABLE IF EXISTS `user`;
-CREATE TABLE `user`  (
-  `id` int NOT NULL AUTO_INCREMENT,
-  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-  `roleId` int NOT NULL,
-  `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-  `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
-  `pushKey` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
-  PRIMARY KEY (`id`) USING BTREE,
-  UNIQUE INDEX `user_username_uindex`(`username` ASC) USING BTREE
+-- Table structure for adminAccount
+-- ----------------------------
+DROP TABLE IF EXISTS `adminAccount`;
+CREATE TABLE `adminAccount`
+(
+    `id`         int                                                           NOT NULL AUTO_INCREMENT,
+    `username`   varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+    `password`   varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+    `roleId`     int                                                           NOT NULL,
+    `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL,
+    `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL,
+    `pushKey`    varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+    PRIMARY KEY (`id`) USING BTREE,
+    UNIQUE INDEX `user_username_uindex`(`username` ASC) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
 
 -- ----------------------------

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp;
 
+import cn.dev33.satoken.SaManager;
 import com.genersoft.iot.vmp.conf.druid.EnableDruidSupport;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.utils.GitUtil;
@@ -13,6 +14,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.web.servlet.ServletComponentScan;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
 import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.scheduling.annotation.EnableScheduling;
 
 import javax.servlet.ServletContext;
@@ -45,6 +47,7 @@ public class VManageBootstrap extends SpringBootServletInitializer {
 		logger.info("构建版本: {}", gitUtil1.getBuildVersion());
 		logger.info("构建时间: {}", gitUtil1.getBuildDate());
 		logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime());
+		logger.info("SaManager.getConfig: {}", SaManager.getConfig());
 	}
 	// 项目重启
 	public static void restart() {

+ 26 - 4
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java

@@ -19,6 +19,12 @@ public class StreamInfo implements Serializable, Cloneable{
     @Schema(description = "IP")
     private String ip;
 
+    @Schema(description = "ssrc")
+    private String ssrc;
+
+    @Schema(description = "isBack")
+    private Boolean isBack;
+
     @Schema(description = "HTTP-FLV流地址")
     private StreamURL flv;
 
@@ -312,8 +318,16 @@ public class StreamInfo implements Serializable, Cloneable{
         }
     }
 
+    public Boolean getBack() {
+        return isBack;
+    }
+
+    public void setBack(Boolean back) {
+        isBack = back;
+    }
+
 
-    public static class TransactionInfo{
+    public static class TransactionInfo {
         public String callId;
         public String localTag;
         public String remoteTag;
@@ -514,12 +528,20 @@ public class StreamInfo implements Serializable, Cloneable{
         this.transactionInfo = transactionInfo;
     }
 
+    public String getSsrc() {
+        return ssrc;
+    }
+
+    public void setSsrc(String ssrc) {
+        this.ssrc = ssrc;
+    }
+
     @Override
     public StreamInfo clone() {
         StreamInfo instance = null;
-        try{
-            instance = (StreamInfo)super.clone();
-        }catch(CloneNotSupportedException e) {
+        try {
+            instance = (StreamInfo) super.clone();
+        } catch (CloneNotSupportedException e) {
             e.printStackTrace();
         }
         return instance;

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java

@@ -24,8 +24,8 @@ import java.io.IOException;
 /**
  * @author lin
  */
-@WebFilter(filterName = "ApiAccessFilter", urlPatterns = "/api/*", asyncSupported=true)
-@Component
+//@WebFilter(filterName = "ApiAccessFilter", urlPatterns = "/api/*", asyncSupported=true)
+//@Component
 public class ApiAccessFilter extends OncePerRequestFilter {
 
     private final static Logger logger = LoggerFactory.getLogger(ApiAccessFilter.class);

+ 86 - 0
src/main/java/com/genersoft/iot/vmp/conf/GlobalExceptionHandler.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.conf;
 
+import cn.dev33.satoken.exception.*;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -8,6 +9,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseStatus;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -40,6 +42,7 @@ public class GlobalExceptionHandler {
     @ExceptionHandler(IllegalStateException.class)
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     public WVPResult<String> exceptionHandler(IllegalStateException e) {
+        logger.error("[400异常]: 400", e);
         return WVPResult.fail(ErrorCode.ERROR400);
     }
 
@@ -57,6 +60,7 @@ public class GlobalExceptionHandler {
 
     /**
      * 登陆失败
+     *
      * @param e 异常
      * @return 统一返回结果
      */
@@ -65,4 +69,86 @@ public class GlobalExceptionHandler {
     public ResponseEntity<WVPResult<String>> exceptionHandler(BadCredentialsException e) {
         return new ResponseEntity<>(WVPResult.fail(ErrorCode.ERROR100.getCode(), e.getMessage()), HttpStatus.OK);
     }
+
+    /**
+     * 全局异常处理
+     */
+    @RestControllerAdvice
+    public static class AuthException {
+
+        private Logger logger = LoggerFactory.getLogger(AuthException.class);
+
+        // 拦截:未登录异常
+        @ExceptionHandler(NotLoginException.class)
+        public ResponseEntity<WVPResult> handlerException(NotLoginException e) {
+            logger.warn("[未登录异常]: {}", e.getMessage());
+            // 打印堆栈,以供调试
+//            e.printStackTrace();
+            // 构造包含401状态码和对应数据的WVPResult对象
+            WVPResult result = WVPResult.fail(ErrorCode.ERROR401);
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
+        }
+
+        // 拦截:缺少权限异常
+        @ExceptionHandler(NotPermissionException.class)
+        public ResponseEntity<WVPResult> handlerException(NotPermissionException e) {
+            logger.warn("[缺少权限异常] {}", e.getMessage());
+//            e.printStackTrace();
+            WVPResult result = WVPResult.fail(ErrorCode.ERROR403);
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
+        }
+
+        // 拦截:缺少角色异常
+        @ExceptionHandler(NotRoleException.class)
+        public ResponseEntity<WVPResult> handlerException(NotRoleException e) {
+            logger.warn("[缺少角色异常] {}", e.getMessage());
+//            e.printStackTrace();
+            WVPResult result = WVPResult.fail(ErrorCode.ERROR403);
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
+        }
+
+        // 拦截:二级认证校验失败异常
+        @ExceptionHandler(NotSafeException.class)
+        public ResponseEntity<WVPResult> handlerException(NotSafeException e) {
+            logger.warn("二级认证校验失败异常");
+            e.printStackTrace();
+            WVPResult result = WVPResult.fail(ErrorCode.ERROR403);
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
+        }
+
+        // 拦截:服务封禁异常
+        @ExceptionHandler(DisableServiceException.class)
+        public WVPResult handlerException(DisableServiceException e) {
+            logger.error("当前服务已被封禁");
+            e.printStackTrace();
+            return WVPResult.fail(ErrorCode.ERROR403, "当前服务已被封禁");
+        }
+
+        // 拦截:Http Basic 校验失败异常
+        @ExceptionHandler(NotBasicAuthException.class)
+        public WVPResult handlerException(NotBasicAuthException e) {
+            logger.error("Http Basic 校验失败异常");
+            e.printStackTrace();
+            return WVPResult.fail(ErrorCode.ERROR401);
+        }
+
+        @ExceptionHandler(MissingServletRequestParameterException.class)
+        public ResponseEntity<String> handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
+            // 确实请求参数
+            // 处理缺少请求参数异常
+            return new ResponseEntity<>("缺少请求参数: " + ex.getParameterName(), HttpStatus.BAD_REQUEST);
+        }
+
+
+        // 拦截:其它所有异常
+        @ExceptionHandler(Exception.class)
+        public ResponseEntity<WVPResult> handlerException(Exception e) {
+
+            logger.error("其它所有异常: {}", e.getMessage());
+            e.printStackTrace();
+            WVPResult result = WVPResult.fail(ErrorCode.ERROR500);
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
+
+        }
+    }
 }

+ 8 - 8
src/main/java/com/genersoft/iot/vmp/conf/security/DefaultUserDetailsServiceImpl.java

@@ -2,8 +2,8 @@ package com.genersoft.iot.vmp.conf.security;
 
 import com.alibaba.excel.util.StringUtils;
 import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
-import com.genersoft.iot.vmp.service.IUserService;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
+import com.genersoft.iot.vmp.service.IAdminService;
+import com.genersoft.iot.vmp.storager.dao.dto.AdminAccount;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -23,7 +23,7 @@ public class DefaultUserDetailsServiceImpl implements UserDetailsService {
     private final static Logger logger = LoggerFactory.getLogger(DefaultUserDetailsServiceImpl.class);
 
     @Autowired
-    private IUserService userService;
+    private IAdminService userService;
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
@@ -33,14 +33,14 @@ public class DefaultUserDetailsServiceImpl implements UserDetailsService {
         }
 
         // 查出密码
-        User user = userService.getUserByUsername(username);
-        if (user == null) {
+        AdminAccount adminAccount = userService.getUserByUsername(username);
+        if (adminAccount == null) {
             logger.info("登录用户:{} 不存在", username);
             throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
         }
-        String password = SecurityUtils.encryptPassword(user.getPassword());
-        user.setPassword(password);
-        return new LoginUser(user, LocalDateTime.now());
+        String password = SecurityUtils.encryptPassword(adminAccount.getPassword());
+        adminAccount.setPassword(password);
+        return new LoginUser(adminAccount, LocalDateTime.now());
     }
 
 

+ 27 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/SaTokenConfigure.java

@@ -0,0 +1,27 @@
+package com.genersoft.iot.vmp.conf.security;
+
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.strategy.SaStrategy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class SaTokenConfigure implements WebMvcConfigurer {
+    // 注册 Sa-Token 拦截器,打开注解式鉴权功能
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 注册 Sa-Token 拦截器,打开注解式鉴权功能
+        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
+    }
+
+    @Autowired
+    public void rewriteSaStrategy() {
+        // 重写Sa-Token的注解处理器,增加注解合并功能
+        SaStrategy.instance.getAnnotation = (element, annotationClass) -> {
+            return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
+        };
+    }
+}

+ 6 - 6
src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java

@@ -1,7 +1,7 @@
 package com.genersoft.iot.vmp.conf.security;
 
 import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
+import com.genersoft.iot.vmp.storager.dao.dto.AdminAccount;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
@@ -53,15 +53,15 @@ public class SecurityUtils {
         Authentication authentication = getAuthentication();
         if(authentication!=null){
             Object principal = authentication.getPrincipal();
-            if(principal!=null && !"anonymousUser".equals(principal)){
+            if(principal!=null && !"anonymousUser".equals(principal)) {
 //                LoginUser user = (LoginUser) authentication.getPrincipal();
 
                 String username = (String) principal;
                 String password = (String) authentication.getCredentials();
-                User user = new User();
-                user.setUsername(username);
-                user.setPassword(password);
-                LoginUser loginUser = new LoginUser(user, LocalDateTime.now());
+                AdminAccount adminAccount = new AdminAccount();
+                adminAccount.setUsername(username);
+                adminAccount.setPassword(password);
+                LoginUser loginUser = new LoginUser(adminAccount, LocalDateTime.now());
                 return loginUser;
             }
         }

+ 55 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/StpInterfaceImpl.java

@@ -0,0 +1,55 @@
+package com.genersoft.iot.vmp.conf.security;
+
+import cn.dev33.satoken.stp.StpInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 自定义权限验证接口扩展
+ */
+@Component    // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
+public class StpInterfaceImpl implements StpInterface {
+    private Logger logger = LoggerFactory.getLogger(StpInterfaceImpl.class);
+    /**
+     * 返回一个账号所拥有的权限码集合
+     */
+    @Override
+    public List<String> getPermissionList(Object loginId, String loginKey) {
+        logger.debug("[用户权限] 获取用户权限 {}: {}", loginId, loginKey);
+        // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
+        List<String> list = new ArrayList<>();
+        list.add("101");
+        list.add("user-add");
+        list.add("user-delete");
+        list.add("user-update");
+        list.add("user-get");
+        list.add("article-get");
+        return list;
+    }
+    /**
+     * 返回一个账号所拥有的角色标识集合
+     */
+    @Override
+    public List<String> getRoleList(Object loginId, String loginKey) {
+        logger.debug("[用户权限] 获取用户角色 {}: {}", loginId, loginKey);
+        // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
+        List<String> list = new ArrayList<>();
+        switch (loginKey) {
+            case "admin":
+//                StpAdminUtil.checkLogin();
+                list.add("admin");
+                break;
+            case "user":
+//                StpUserUtil.checkLogin();
+                list.add("user");
+                break;
+            default:
+                break;
+        }
+        return list;
+    }
+}

+ 46 - 43
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java

@@ -87,13 +87,14 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
             "/aiLib/",
             "/aiLib/mFile/",
             "/api/device/query/share/info",
+            "/account"
     };
 
     public static String[] matchShareUrl = new String[]{
             "/api/ptz",
             "/api/play",
             "/api/device/query/devices",
-            "/zlm/"
+            "/zlm/",
     };
 
 
@@ -130,36 +131,36 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         }
     }
 
-    /**
-     * 配置认证方式
-     *
-     * @param auth
-     * @throws Exception
-     */
-    @Override
-    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
-        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        // 设置不隐藏 未找到用户异常
-        provider.setHideUserNotFoundExceptions(true);
-        // 用户认证service - 查询数据库的逻辑
-        provider.setUserDetailsService(userDetailsService);
-        // 设置密码加密算法
-        provider.setPasswordEncoder(passwordEncoder());
-        auth.authenticationProvider(provider);
-    }
+//    /**
+//     * 配置认证方式
+//     *
+//     * @param auth
+//     * @throws Exception
+//     */
+//    @Override
+//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+//        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+//        // 设置不隐藏 未找到用户异常
+//        provider.setHideUserNotFoundExceptions(true);
+//        // 用户认证service - 查询数据库的逻辑
+//        provider.setUserDetailsService(userDetailsService);
+//        // 设置密码加密算法
+//        provider.setPasswordEncoder(passwordEncoder());
+//        auth.authenticationProvider(provider);
+//    }
 
 
     // 投票模式鉴权链接
-    @Bean
-    public AccessDecisionManager accessDecisionManager() {
-        List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList(
-                new JwtAccess<>(),
-                new CustomAccess<>(deviceShare, matchShareUrl),
-                new queryAccess<>(matchUrl)
-        );
-        logger.info("decision {}", decisionVoters.size());
-        return new AffirmativeBased(decisionVoters);
-    }
+//    @Bean
+//    public AccessDecisionManager accessDecisionManager() {
+//        List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList(
+//                new JwtAccess<>(),
+//                new CustomAccess<>(deviceShare, matchShareUrl),
+//                new queryAccess<>(matchUrl)
+//        );
+//        logger.info("decision {}", decisionVoters.size());
+//        return new AffirmativeBased(decisionVoters);
+//    }
 
     @Override
     protected void configure(HttpSecurity http) throws Exception {
@@ -168,21 +169,23 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 //                .and().cors().configurationSource(configurationSource())
                 .and().cors()
                 .and().csrf().disable()
-                .sessionManagement()
-                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
-
-                // 配置拦截规则
-                .and()
-                .authorizeRequests()
-                // 分享码验证
-                .accessDecisionManager(accessDecisionManager())
-                .anyRequest().authenticated()
-                // 异常处理器
-                .and()
-                .exceptionHandling()
-                .authenticationEntryPoint(anonymousAuthenticationEntryPoint)
-                .and().logout().logoutUrl("/api/user/logout").permitAll()
-                .logoutSuccessHandler(logoutHandler)
+//                .anyRequest()
+//                .permitAll()
+//                .sessionManagement()
+//                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+
+        // 配置拦截规则
+//                .and()
+//                .authorizeRequests()
+//                // 分享码验证
+////                .accessDecisionManager(accessDecisionManager())
+//                .anyRequest().authenticated()
+//                // 异常处理器
+//                .and()
+//                .exceptionHandling()
+//                .authenticationEntryPoint(anonymousAuthenticationEntryPoint)
+//                .and().logout().logoutUrl("/api/user/logout").permitAll()
+//                .logoutSuccessHandler(logoutHandler)
         ;
 //        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
 

+ 9 - 9
src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java

@@ -1,7 +1,7 @@
 package com.genersoft.iot.vmp.conf.security.dto;
 
+import com.genersoft.iot.vmp.storager.dao.dto.AdminAccount;
 import com.genersoft.iot.vmp.storager.dao.dto.Role;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
 import org.springframework.security.core.CredentialsContainer;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.SpringSecurityCoreVersion;
@@ -17,7 +17,7 @@ public class LoginUser implements UserDetails, CredentialsContainer {
     /**
      * 用户
      */
-    private User user;
+    private AdminAccount adminAccount;
 
     private String accessToken;
 
@@ -27,8 +27,8 @@ public class LoginUser implements UserDetails, CredentialsContainer {
      */
     private LocalDateTime loginTime;
 
-    public LoginUser(User user, LocalDateTime loginTime) {
-        this.user = user;
+    public LoginUser(AdminAccount adminAccount, LocalDateTime loginTime) {
+        this.adminAccount = adminAccount;
         this.loginTime = loginTime;
     }
 
@@ -40,12 +40,12 @@ public class LoginUser implements UserDetails, CredentialsContainer {
 
     @Override
     public String getPassword() {
-        return user.getPassword();
+        return adminAccount.getPassword();
     }
 
     @Override
     public String getUsername() {
-        return user.getUsername();
+        return adminAccount.getUsername();
     }
 
     /**
@@ -88,16 +88,16 @@ public class LoginUser implements UserDetails, CredentialsContainer {
      */
     @Override
     public void eraseCredentials() {
-        user.setPassword(null);
+        adminAccount.setPassword(null);
     }
 
 
     public int getId() {
-        return user.getId();
+        return adminAccount.getId();
     }
 
     public Role getRole() {
-        return user.getRole();
+        return adminAccount.getRole();
     }
 
 

+ 24 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaAdminCheckLogin.java

@@ -0,0 +1,24 @@
+package com.genersoft.iot.vmp.conf.security.saToken;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 登录认证(User版):只有登录之后才能进入该方法
+ * <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author click33
+ */
+@SaCheckLogin(type = StpAdminUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SaAdminCheckLogin {
+
+}
+

+ 41 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaAdminCheckRole.java

@@ -0,0 +1,41 @@
+package com.genersoft.iot.vmp.conf.security.saToken;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 角色认证(User版):必须具有指定角色标识才能进入该方法
+ * <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author click33
+ */
+@SaCheckRole(type = StpAdminUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SaAdminCheckRole {
+    /**
+     * 需要校验的角色标识
+     *
+     * @return 需要校验的角色标识
+     */
+    @AliasFor(annotation = SaCheckRole.class)
+    String[] value() default {};
+
+    /**
+     * 验证模式:AND | OR,默认AND
+     *
+     * @return 验证模式
+     */
+    @AliasFor(annotation = SaCheckRole.class)
+    SaMode mode() default SaMode.AND;
+}
+
+

+ 40 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaAdminPermission.java

@@ -0,0 +1,40 @@
+package com.genersoft.iot.vmp.conf.security.saToken;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaMode;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 权限认证(User版):必须具有指定权限才能进入该方法
+ * <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author click33
+ */
+@SaCheckPermission(type = StpAdminUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SaAdminPermission {
+
+    /**
+     * 需要校验的权限码
+     *
+     * @return 需要校验的权限码
+     */
+    @AliasFor(annotation = SaCheckPermission.class)
+    String[] value() default {};
+
+    /**
+     * 验证模式:AND | OR,默认AND
+     *
+     * @return 验证模式
+     */
+    @AliasFor(annotation = SaCheckPermission.class)
+    SaMode mode() default SaMode.AND;
+
+}

+ 24 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaUserCheckLogin.java

@@ -0,0 +1,24 @@
+package com.genersoft.iot.vmp.conf.security.saToken;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 登录认证(User版):只有登录之后才能进入该方法
+ * <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author click33
+ */
+@SaCheckLogin(type = StpUserUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SaUserCheckLogin {
+
+}
+

+ 41 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaUserCheckRole.java

@@ -0,0 +1,41 @@
+package com.genersoft.iot.vmp.conf.security.saToken;
+
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 角色认证(User版):必须具有指定角色标识才能进入该方法
+ * <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author click33
+ */
+@SaCheckRole(type = StpUserUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SaUserCheckRole {
+    /**
+     * 需要校验的角色标识
+     *
+     * @return 需要校验的角色标识
+     */
+    @AliasFor(annotation = SaCheckRole.class)
+    String[] value() default {};
+
+    /**
+     * 验证模式:AND | OR,默认AND
+     *
+     * @return 验证模式
+     */
+    @AliasFor(annotation = SaCheckRole.class)
+    SaMode mode() default SaMode.AND;
+}
+
+

+ 41 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/saToken/SaUserPermission.java

@@ -0,0 +1,41 @@
+package com.genersoft.iot.vmp.conf.security.saToken;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaMode;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 权限认证(User版):必须具有指定权限才能进入该方法
+ * <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ *
+ * @author click33
+ */
+@SaCheckPermission(type = StpUserUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SaUserPermission {
+
+    /**
+     * 需要校验的权限码
+     *
+     * @return 需要校验的权限码
+     */
+    @AliasFor(annotation = SaCheckPermission.class)
+    String[] value() default {};
+
+    /**
+     * 验证模式:AND | OR,默认AND
+     *
+     * @return 验证模式
+     */
+    @AliasFor(annotation = SaCheckPermission.class)
+    SaMode mode() default SaMode.AND;
+
+}

+ 37 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java

@@ -11,6 +11,12 @@ import io.swagger.v3.oas.annotations.media.Schema;
 public class Device {
 
 	/**
+     * 设备表id, 用于绑定设备
+     */
+    @Schema(description = "设备id, 数据库Id")
+    private String id;
+
+	/**
 	 * 设备国标编号
 	 */
 	@Schema(description = "设备国标编号")
@@ -192,6 +198,9 @@ public class Device {
 	@Schema(description = "SIP交互IP(设备访问平台的IP)")
 	private String localIp;
 
+	@Schema(description = "设备连接的域")
+	private String domain;
+
 
 	@Schema(description = "是否作为消息通道")
 	private boolean asMessageChannel;
@@ -214,6 +223,10 @@ public class Device {
 	@Schema(description = "分享中的播放通道")
 	private String playChannel;
 
+	@Schema(description = "设备绑定码")
+	private String bindCode;
+
+
 	public String getDeviceId() {
 		return deviceId;
 	}
@@ -515,5 +528,29 @@ public class Device {
 
 	public void setPlayChannel(String playChannel) {
 		this.playChannel = playChannel;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+	}
+
+	public String getDomain() {
+		return domain;
+	}
+
+	public void setDomain(String domain) {
+		this.domain = domain;
+	}
+
+	public String getBindCode() {
+		return bindCode;
+	}
+
+	public void setBindCode(String bindCode) {
+		this.bindCode = bindCode;
 	}
 }

+ 126 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipUserConfig.java

@@ -0,0 +1,126 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+/**
+ * | 字段          | 类型           | 值介绍   | 默认 | 备注                  |
+ * |-------------|--------------|-------|----|---------------------|
+ * | id          | int          | 主键    | 无  | 无                   |
+ * | adminId   | int          | 管理员id | 无  | 创建者id, 在注册时不发挥作用    |
+ * | sipDomain   | varchar(100) | sip域名 | 无  | sip域 唯一,根据域来获取对应的密码 |
+ * | serverId    | varchar(100) | 服务器id | 无  | 服务器id               |
+ * | password    | varchar(100) | 服务器密码 | 无  | 密码                  |
+ * | description | varchar(255) | 描述    | 无  | 描述                  |
+ * | enable      | char(1)      | 是否启用 0 不启用 1 启用 | 1  | 只有启用的情况下才会被允许注册     |
+ * | createTime  | datetime     | 创建时间  | 无  | 创建时间                |
+ */
+public class SipUserConfig {
+    @Schema(description = "配置id")
+    private String id;
+    @Schema(description = "管理员id")
+    private String adminId;
+    @Schema(description = "sip域名")
+    private String sipDomain;
+    @Schema(description = "服务器id")
+    private String serverId;
+    @Schema(description = "服务器密码")
+    private String password;
+    @Schema(description = "描述")
+    private String description;
+    @Schema(description = "是否启用 0 不启用 1 启用")
+    private String enable;
+
+    @Schema(description = "是否有绑定机制")
+    private String enableBind;
+
+    @Schema(description = "创建时间")
+    private String createTime;
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setAdminId(String adminId) {
+        this.adminId = adminId;
+    }
+
+    public void setSipDomain(String sipDomain) {
+        this.sipDomain = sipDomain;
+    }
+
+    public void setServerId(String serverId) {
+        this.serverId = serverId;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public void setEnable(String enable) {
+        this.enable = enable;
+    }
+
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getAdminId() {
+        return adminId;
+    }
+
+    public String getSipDomain() {
+        return sipDomain;
+    }
+
+    public String getServerId() {
+        return serverId;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getEnable() {
+        return enable;
+    }
+
+    public Boolean isEnableFlag() {
+        if (enable == null) {
+            return false;
+        }
+        return enable.equals("1");
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public String getEnableBind() {
+        return enableBind;
+    }
+
+    public void setEnableBind(String enableBind) {
+        this.enableBind = enableBind;
+    }
+
+    public Boolean isEnableBindFlag() {
+        // 为空判断
+        if (enableBind == null) {
+            return false;
+        }
+        return enableBind.equals("1");
+    }
+}

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java

@@ -84,6 +84,7 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
         // 获取设备ID 34020000002000000123 通过正则
         try{
             if (fromHeader != null) {
+                logger.info("fromHeader: {}", fromHeader);
                 deviceId = fromHeader.getAddress().getURI().toString().split(":")[1].split("@")[0];
             }
         }catch (Exception e){

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java

@@ -49,7 +49,7 @@ public class SIPSender {
     public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException {
         try {
             ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME);
-            logger.info("[server ---> device] ip:{} \n{}",ip,message);
+            logger.info("[server ---> device] transmitRequest ip:{} \n{}",ip,message);
             String transport = "UDP";
             if (viaHeader == null) {
                 logger.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据");

+ 19 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java

@@ -1,6 +1,9 @@
 package com.genersoft.iot.vmp.gb28181.transmit.callback;
 
 import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
+import com.genersoft.iot.vmp.vmanager.gb28181.play.PlayController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.context.request.async.DeferredResult;
@@ -18,8 +21,8 @@ import java.util.concurrent.ConcurrentHashMap;
 @SuppressWarnings(value = {"rawtypes", "unchecked"})
 @Component
 public class DeferredResultHolder {
-	
-	public static final String CALLBACK_CMD_DEVICESTATUS = "CALLBACK_DEVICESTATUS";
+    private final static Logger logger = LoggerFactory.getLogger(DeferredResultHolder.class);
+    public static final String CALLBACK_CMD_DEVICESTATUS = "CALLBACK_DEVICESTATUS";
 	
 	public static final String CALLBACK_CMD_DEVICEINFO = "CALLBACK_DEVICEINFO";
 	
@@ -55,10 +58,14 @@ public class DeferredResultHolder {
 	public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
 
 	public static final String CALLBACK_CMD_BROADCAST_INVITE = "CALLBACK_BROADCAST_INVITE";
+
+	public static final String CALLBACK_CMD_HFY_CODE = "CALLBACK_HFY_CODE";
+
 	private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>();
 
 	/**
 	 * 获取所有的key
+	 *
 	 * @return
 	 */
 	public Set<String> getKeys() {
@@ -77,7 +84,8 @@ public class DeferredResultHolder {
 	public void put(String key, String id, DeferredResult result) {
 		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
 		if (deferredResultMap == null) {
-			deferredResultMap = new ConcurrentHashMap<>();
+            logger.info("put key: " + key + " id: " + id + " result: " + result);
+            deferredResultMap = new ConcurrentHashMap<>();
 			map.put(key, deferredResultMap);
 		}
 		deferredResultMap.put(id, new DeferredResultEx(result));
@@ -116,8 +124,10 @@ public class DeferredResultHolder {
 	 * @param msg
 	 */
 	public void invokeResult(RequestMessage msg) {
+        logger.info("release msg");
 		Map<String, DeferredResultEx> deferredResultMap = map.get(msg.getKey());
 		if (deferredResultMap == null) {
+            logger.warn("DeferredResultHolder invokeResult not found key: " + msg.getKey());
 			return;
 		}
 		if(msg.getId() == null) {
@@ -125,13 +135,16 @@ public class DeferredResultHolder {
 				result.getDeferredResult().setResult(msg.getData());
 			});
 			deferredResultMap.clear();
-			map.remove(msg.getKey());
+            map.remove(msg.getKey());
+            logger.warn("DeferredResultHolder msg id not found id: " + msg.getId());
 			return;
 		}
 		DeferredResultEx result = deferredResultMap.get(msg.getId());
 		if (result == null) {
+            logger.warn("DeferredResultHolder result not found id: " + msg.getId());
 			return;
 		}
+        // 设置结果
 		result.getDeferredResult().setResult(msg.getData());
 		deferredResultMap.remove(msg.getId());
 		if (deferredResultMap.size() == 0) {
@@ -170,4 +183,6 @@ public class DeferredResultHolder {
 			map.remove(msg.getKey());
 		}
 	}
+
+
 }

+ 31 - 15
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java

@@ -1,21 +1,36 @@
 package com.genersoft.iot.vmp.gb28181.transmit.callback;
 
-/**    
- * @description: 请求信息定义   
- * @author: swwheihei
- * @date:   2020年5月8日 下午1:09:18     
- */
+
 public class RequestMessage {
-	
-	private String id;
+    public RequestMessage() {
+    }
 
-	private String key;
 
-	private Object data;
+    public RequestMessage(String key, String id) {
+        this.id = id;
+        this.key = key;
+    }
 
-	public String getId() {
-		return id;
-	}
+    public RequestMessage(String key, Object data) {
+        this.key = key;
+        this.data = data;
+    }
+
+    public RequestMessage(String id, String key, Object data) {
+        this.id = id;
+        this.key = key;
+        this.data = data;
+    }
+
+    private String id;
+
+    private String key;
+
+    private Object data;
+
+    public String getId() {
+        return id;
+    }
 
 	public void setId(String id) {
 		this.id = id;
@@ -33,7 +48,8 @@ public class RequestMessage {
 		return data;
 	}
 
-	public void setData(Object data) {
-		this.data = data;
-	}
+    public RequestMessage setData(Object data) {
+        this.data = data;
+        return this;
+    }
 }

+ 57 - 36
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -10,6 +10,7 @@ import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorHook;
 import gov.nist.javax.sip.message.SIPRequest;
 
 import javax.sip.InvalidArgumentException;
@@ -95,25 +96,33 @@ public interface ISIPCommander {
 	 * @param device		控制设备
 	 * @param channelId		预览通道
 	 * @param cmdString		前端控制指令串
-	 */
-	void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+     */
+    void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 
-	/**
-	 * 请求预览视频流
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 */
-	void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,int isUsePs, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+    /**
+     * 请求预览视频流
+     *
+     * @param device    视频设备
+     * @param channelId 预览通道
+     */
+    void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, int isUsePs, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
-	/**
-	 * 请求回放视频流
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 */
-	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+    void sendPlayCmd(
+            MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,
+            Device device, String channelId, int isUsePs,
+            ErrorHook errorHook
+    ) throws InvalidArgumentException, SipException, ParseException;
+
+    /**
+     * 请求回放视频流
+     *
+     * @param device    视频设备
+     * @param channelId 预览通道
+     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param endTime   结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+     */
+    void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
 
 	/**
 	 * 请求历史媒体下载
@@ -283,35 +292,47 @@ public interface ISIPCommander {
 	
 	/**
 	 * 查询报警信息
-	 * 
-	 * @param device		视频设备
-	 * @param startPriority	报警起始级别(可选)
-	 * @param endPriority	报警终止级别(可选)
-	 * @param alarmMethod	报警方式条件(可选)
-	 * @param alarmType		报警类型
-	 * @param startTime		报警发生起始时间(可选)
-	 * @param endTime		报警发生终止时间(可选)
-	 * @return				true = 命令发送成功
+	 *
+	 * @param device        视频设备
+	 * @param startPriority    报警起始级别(可选)
+	 * @param endPriority    报警终止级别(可选)
+	 * @param alarmMethod    报警方式条件(可选)
+	 * @param alarmType        报警类型
+	 * @param startTime        报警发生起始时间(可选)
+	 * @param endTime        报警发生终止时间(可选)
+	 * @return true = 命令发送成功
 	 */
 	void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod,
-							String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
+						String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
 	/**
 	 * 查询设备配置
-	 * 
-	 * @param device 		视频设备
-	 * @param channelId		通道编码(可选)
-	 * @param configType	配置类型:
+	 *
+	 * @param device     视频设备
+	 * @param channelId  通道编码(可选)
+	 * @param configType 配置类型:
 	 */
-	void deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
+	void deviceConfigQuery(Device device, String channelId, String configType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+
+	/**
+	 * 查询设备绑定码
+	 *
+	 * @param device
+	 * @param errorHook
+	 * @throws InvalidArgumentException
+	 * @throws SipException
+	 * @throws ParseException
+	 */
+	void queryBindCode(Device device, ErrorHook errorHook) throws InvalidArgumentException, SipException, ParseException;
+
 	/**
 	 * 查询设备预置位置
-	 * 
+	 *
 	 * @param device 视频设备
 	 */
 	void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
+
 	/**
 	 * 查询移动设备位置数据
 	 * 

+ 114 - 73
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java

@@ -4,12 +4,17 @@ import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
+import com.genersoft.iot.vmp.gb28181.bean.SipUserConfig;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.service.ISipConfigService;
+import com.genersoft.iot.vmp.service.impl.PlayServiceImpl;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.utils.GitUtil;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -30,9 +35,9 @@ import java.util.ArrayList;
  */
 @Component
 public class SIPRequestHeaderProvider {
-
-	@Autowired
-	private SipConfig sipConfig;
+    private final static Logger logger = LoggerFactory.getLogger(SIPRequestHeaderProvider.class);
+    @Autowired
+    private SipConfig sipConfig;
 	
 	@Autowired
 	private SipLayer sipLayer;
@@ -45,18 +50,27 @@ public class SIPRequestHeaderProvider {
 
 	@Autowired
 	private VideoStreamSessionManager streamSession;
-	
+
+	@Autowired
+	private ISipConfigService sipConfigService;
+
 	public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		// sipuri
-		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		// from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+        Request request;
+        SipUserConfig sipUserConfig = sipConfigService.getSipConfigByDomain(device.getDomain());
+        if (sipUserConfig == null) {
+            logger.error("无法获取到对应的sip配置信息,domain:" + device.getDomain());
+            return null;
+        }
+        // sipuri
+        SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+        // via
+        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+        ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+        viaHeader.setRPort();
+        viaHeaders.add(viaHeader);
+
+        // from
+        SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipUserConfig.getServerId(), sipUserConfig.getSipDomain());
 		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
 		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag);
 		// to
@@ -80,18 +94,23 @@ public class SIPRequestHeaderProvider {
 	}
 	
 	public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		//via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		HeaderFactory headerFactory = sipLayer.getSipFactory().createHeaderFactory();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-
-		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+        Request request;
+        SipUserConfig sipUserConfig = sipConfigService.getSipConfigByDomain(device.getDomain());
+        if (sipUserConfig == null) {
+            logger.error("无法获取到对应的sip配置信息,domain:" + device.getDomain());
+            return null;
+        }
+        //请求行
+        SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+        //via
+        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+        HeaderFactory headerFactory = sipLayer.getSipFactory().createHeaderFactory();
+        ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+        viaHeader.setRPort();
+        viaHeaders.add(viaHeader);
+
+        //from
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipUserConfig.getServerId(), sipUserConfig.getSipDomain());
 		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
 		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
 		//to
@@ -120,17 +139,22 @@ public class SIPRequestHeaderProvider {
 	}
 	
 	public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+        Request request;
+        SipUserConfig sipUserConfig = sipConfigService.getSipConfigByDomain(device.getDomain());
+        if (sipUserConfig == null) {
+            logger.error("无法获取到对应的sip配置信息,domain:" + device.getDomain());
+            return null;
+        }
+        //请求行
+        SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+        // via
+        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+        ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+        viaHeader.setRPort();
+        viaHeaders.add(viaHeader);
+        //from
+        SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipUserConfig.getServerId(), sipUserConfig.getSipDomain());
+        Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
 		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
 		//to
 		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
@@ -160,17 +184,22 @@ public class SIPRequestHeaderProvider {
 	}
 
 	public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+        Request request = null;
+        SipUserConfig sipUserConfig = sipConfigService.getSipConfigByDomain(device.getDomain());
+        if (sipUserConfig == null) {
+            logger.error("无法获取到对应的sip配置信息,domain:" + device.getDomain());
+            return null;
+        }
+        //请求行
+        SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+        // via
+        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+        ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+        viaHeaders.add(viaHeader);
+        //from
+        SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipUserConfig.getServerId(), sipUserConfig.getSipDomain());
+        Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+        FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
 		//to
 		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId,device.getHostAddress());
 		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
@@ -194,18 +223,25 @@ public class SIPRequestHeaderProvider {
 		return request;
 	}
 
-	public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		// sipuri
-		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
-				device.getTransport(), SipUtils.getNewViaTag());
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		// from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+
+    public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+        Request request = null;
+        SipUserConfig sipUserConfig = sipConfigService.getSipConfigByDomain(device.getDomain());
+        if (sipUserConfig == null) {
+            logger.error("无法获取到对应的sip配置信息,domain:" + device.getDomain());
+            return null;
+        }
+
+        // sipuri
+        SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+        // via
+        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+        ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
+                device.getTransport(), SipUtils.getNewViaTag());
+        viaHeader.setRPort();
+        viaHeaders.add(viaHeader);
+        // from
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipUserConfig.getServerId(), sipUserConfig.getSipDomain());
 		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
 		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
 		// to
@@ -247,19 +283,24 @@ public class SIPRequestHeaderProvider {
 
 	public SIPRequest createInfoRequest(Device device, String channelId, String content, SipTransactionInfo transactionInfo)
 			throws SipException, ParseException, InvalidArgumentException {
-		if (device == null || transactionInfo == null) {
-			return null;
-		}
-		SIPRequest request = null;
-		//请求行
-		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+        if (device == null || transactionInfo == null) {
+            return null;
+        }
+        SipUserConfig sipUserConfig = sipConfigService.getSipConfigByDomain(device.getDomain());
+        if (sipUserConfig == null) {
+            logger.error("无法获取到对应的sip配置信息,domain:" + device.getDomain());
+            return null;
+        }
+        SIPRequest request = null;
+        //请求行
+        SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+        // via
+        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+        ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+        viaHeaders.add(viaHeader);
+        //from
+        SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipUserConfig.getServerId(), sipUserConfig.getSipDomain());
+        Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
 		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
 		//to
 		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId,device.getHostAddress());

+ 165 - 78
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@@ -25,6 +25,9 @@ import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorHook;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 import org.slf4j.Logger;
@@ -35,6 +38,7 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 
 import javax.sip.InvalidArgumentException;
+import javax.sip.PeerUnavailableException;
 import javax.sip.ResponseEvent;
 import javax.sip.SipException;
 import javax.sip.header.CallIdHeader;
@@ -203,10 +207,13 @@ public class SIPCommander implements ISIPCommander {
         ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
         ptzXml.append("</Info>\r\n");
         ptzXml.append("</Control>\r\n");
-        
-        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
 
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
+        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()));
+        if (request == null) {
+            SipException e = new SipException("无法获取到对应的sip配置信息,domain:" + device.getDomain());
+            throw e;
+        }
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
     }
 
 
@@ -329,10 +336,52 @@ public class SIPCommander implements ISIPCommander {
                 subscribe.removeSubscribe(hookSubscribe);
             }
         });
+
+        // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
+//			content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备
+        StringBuffer content = buildPlayCmd(mediaServerItem, device, channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc(), isUsePs, userSetting.isSeniorSdp());
+
+        Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(), sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()));
+        if (request == null) {
+            SipException e = new SipException("无法获取到对应的sip配置信息,domain:" + device.getDomain());
+            SipSubscribe.EventResult responseEvent = new SipSubscribe.EventResult(e);
+            errorEvent.response(responseEvent);
+            return;
+        }
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request,
+                (e -> {
+                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                    errorEvent.response(e);
+                }), e -> {
+                    // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
+                    ResponseEvent responseEvent = (ResponseEvent) e.event;
+                    SIPResponse response = (SIPResponse) responseEvent.getResponse();
+                    streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
+                    okEvent.response(e);
+                });
+    }
+
+    /**
+     * 构建播放 sip invite 信息
+     *
+     * @param mediaServerItem
+     * @param device
+     * @param channelId
+     * @param port
+     * @param ssrc
+     * @param isUsePs
+     * @param isSeniorSdp
+     * @return
+     */
+    private StringBuffer buildPlayCmd(
+            MediaServerItem mediaServerItem, Device device,
+            String channelId, int port, String ssrc,
+            int isUsePs, Boolean isSeniorSdp) {
         String sdpIp;
         if (!ObjectUtils.isEmpty(device.getSdpIp())) {
             sdpIp = device.getSdpIp();
-        }else {
+        } else {
             sdpIp = mediaServerItem.getSdpIp();
         }
         StringBuffer content = new StringBuffer(200);
@@ -341,14 +390,13 @@ public class SIPCommander implements ISIPCommander {
         content.append("s=Play\r\n");
         content.append("c=IN IP4 " + sdpIp + "\r\n");
         content.append("t=0 0\r\n");
-
         if (userSetting.isSeniorSdp()) {
             if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+                content.append("m=video " + port + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
             } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+                content.append("m=video " + port + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
             } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
+                content.append("m=video " + port + " RTP/AVP 96 126 125 99 34 98 97\r\n");
             }
             content.append("a=recvonly\r\n");
             content.append("a=rtpmap:96 PS/90000\r\n");
@@ -368,14 +416,14 @@ public class SIPCommander implements ISIPCommander {
             }
         } else {
             if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+                content.append("m=video " + port + " TCP/RTP/AVP 96 97 98 99\r\n");
             } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+                content.append("m=video " + port + " TCP/RTP/AVP 96 97 98 99\r\n");
             } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
+                content.append("m=video " + port + " RTP/AVP 96 97 98 99\r\n");
             }
             content.append("a=recvonly\r\n");
-            if(isUsePs == 1){
+            if (isUsePs == 1) {
                 // ps 改为手动启用
                 content.append("a=rtpmap:96 PS/90000\r\n");
             }
@@ -391,25 +439,40 @@ public class SIPCommander implements ISIPCommander {
                 content.append("a=connection:new\r\n");
             }
         }
-        logger.info("------------ invik : {} ", content.toString());
-        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
-        // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
-//			content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备
-
-
+        content.append("y=" + ssrc + "\r\n");//ssrc
+        return content;
+    }
 
-        Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> {
-            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
-            errorEvent.response(e);
-        }), e -> {
-            // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
-            ResponseEvent responseEvent = (ResponseEvent) e.event;
-            SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
-            okEvent.response(e);
-        });
+    public void sendPlayCmd(
+            MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,
+            Device device, String channelId, int isUsePs,
+            ErrorHook errorHook
+    ) throws InvalidArgumentException, SipException, ParseException {
+        StringBuffer content = buildPlayCmd(mediaServerItem, device, channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc(), isUsePs, userSetting.isSeniorSdp());
+        Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(), sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()));
+        if (request == null) {
+            errorHook.run(WVPResult.fail(
+                    ErrorCode.ERROR_DATA,
+                    "无法获取到对应的sip配置信息,domain:" + device.getDomain()
+            ));
+            return;
+        }
+        // 发送请求
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request,
+                (e -> {
+                    errorHook.run(WVPResult.fail(
+                            ErrorCode.ERR_Invite_fail,
+                            "命令发送失败: "
+                    ));
+                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                }), e -> {
+                    // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
+                    ResponseEvent responseEvent = (ResponseEvent) e.event;
+                    SIPResponse response = (SIPResponse) responseEvent.getResponse();
+                    streamSession.put(device.getDeviceId(), channelId, "play", ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
+                    errorHook.run(WVPResult.success());
+                });
     }
 
     /**
@@ -741,16 +804,31 @@ public class SIPCommander implements ISIPCommander {
         mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
         mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
         streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
-//        if(ssrcTransaction.getApp){
-//            //        JSONObject result = mediaServerService.closeStreams(mediaServerItem, param.getApp(), param.getStream());
-//        }
         Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
         sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
     }
 
-    public void streamByeBroadcastCmd(Device device,String channelId){
-
+    public void stopPlayCmd(Device device, String channelId, ErrorHook errorHook) throws InvalidArgumentException, SipException, ParseException {
+        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, null, null);
+        if (ssrcTransaction == null) {
+            // 无法找到
+            errorHook.run(WVPResult.fail(ErrorCode.ERROR100, "无法找到该设备的流信息"));
+            return;
+        }
         // 关闭推流通道
+        Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest,
+                (e -> {
+                    errorHook.run(WVPResult.fail(
+                            ErrorCode.ERR_Invite_fail,
+                            "命令发送失败: "
+                    ));
+                }), e -> {
+                    // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
+                    ResponseEvent responseEvent = (ResponseEvent) e.event;
+                    SIPResponse response = (SIPResponse) responseEvent.getResponse();
+                    errorHook.run(WVPResult.success(response));
+                });
     }
 
 
@@ -897,20 +975,9 @@ public class SIPCommander implements ISIPCommander {
      */
     @Override
     public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
+        StringBuffer cmdXml = devControlHeaderXml(device, channelId);
         cmdXml.append("<RecordCmd>" + recordCmdStr + "</RecordCmd>\r\n");
         cmdXml.append("</Control>\r\n");
-
         
 
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
@@ -1009,6 +1076,17 @@ public class SIPCommander implements ISIPCommander {
     @Override
     public void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException {
 
+
+        StringBuffer cmdXml = devControlHeaderXml(device, channelId);
+        cmdXml.append("<IFameCmd>Send</IFameCmd>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
+    }
+
+    private StringBuffer devControlHeaderXml(Device device, String channelId) {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1020,13 +1098,7 @@ public class SIPCommander implements ISIPCommander {
         } else {
             cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
         }
-        cmdXml.append("<IFameCmd>Send</IFameCmd>\r\n");
-        cmdXml.append("</Control>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
+        return cmdXml;
     }
 
     /**
@@ -1041,17 +1113,7 @@ public class SIPCommander implements ISIPCommander {
     @Override
     public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
+        StringBuffer cmdXml = devControlHeaderXml(device, channelId);
         cmdXml.append("<HomePosition>\r\n");
         if (NumericUtil.isInteger(enabled) && (!enabled.equals("0"))) {
             cmdXml.append("<Enabled>1</Enabled>\r\n");
@@ -1303,12 +1365,47 @@ public class SIPCommander implements ISIPCommander {
         }
         cmdXml.append("</Query>\r\n");
 
-        
 
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()));
         sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
     }
 
+
+    public void queryBindCode(Device device, ErrorHook errorHook) throws InvalidArgumentException, SipException, ParseException {
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>HfyCode</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("</Query>\r\n");
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()));
+        if (request == null) {
+            errorHook.run(WVPResult.fail(
+                    ErrorCode.ERROR_DATA,
+                    "无法获取到对应的sip配置信息,domain:" + device.getDomain()
+            ));
+            return;
+        }
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request,
+                (err -> {
+                    logger.warn("查询合方圆专属code失败 {}", err.msg);
+                    errorHook.run(WVPResult.fail(
+                            ErrorCode.ERR_Invite_fail,
+                            "命令发送失败: "
+                    ));
+                }), e -> {
+                    // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
+                    ResponseEvent responseEvent = (ResponseEvent) e.event;
+                    SIPResponse response = (SIPResponse) responseEvent.getResponse();
+                    errorHook.run(WVPResult.success());
+                });
+        errorHook.run(WVPResult.success());
+
+    }
+
     /**
      * 查询设备配置
      *
@@ -1556,17 +1653,7 @@ public class SIPCommander implements ISIPCommander {
     @Override
     public void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException {
 
-        StringBuffer dragXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        dragXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        dragXml.append("<Control>\r\n");
-        dragXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        dragXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            dragXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            dragXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
+        StringBuffer dragXml = devControlHeaderXml(device, channelId);
         dragXml.append(cmdString);
         dragXml.append("</Control>\r\n");
         

+ 38 - 22
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java

@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
+import com.genersoft.iot.vmp.gb28181.bean.SipUserConfig;
 import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
@@ -12,6 +13,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.service.ISipConfigService;
+import com.genersoft.iot.vmp.storager.dao.SipConfigMapper;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import gov.nist.javax.sip.RequestEventExt;
 import gov.nist.javax.sip.address.AddressImpl;
@@ -62,6 +65,9 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
     @Autowired
     private UserSetting userSetting;
 
+    @Autowired
+    private ISipConfigService sipConfigService;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         // 添加消息处理的订阅
@@ -78,21 +84,8 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
         try {
             RequestEventExt evtExt = (RequestEventExt) evt;
             String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort();
-            logger.info("[注册请求] 开始处理: {}", requestAddress);
-//            MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
-//            QueryExp protocol = Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"));
-////            ObjectName name = new ObjectName("*:type=Connector,*");
-//            ObjectName name = new ObjectName("*:*");
-//            Set<ObjectName> objectNames = beanServer.queryNames(name, protocol);
-//            for (ObjectName objectName : objectNames) {
-//                String catalina = objectName.getDomain();
-//                if ("Catalina".equals(catalina)) {
-//                    System.out.println(objectName.getKeyProperty("port"));
-//                }
-//            }
-
-//            System.out.println(ServiceInfo.getServerPort());
-            SIPRequest request = (SIPRequest)evt.getRequest();
+            logger.info("[sip注册] 开始处理: {}", requestAddress);
+            SIPRequest request = (SIPRequest) evt.getRequest();
             Response response = null;
             boolean passwordCorrect = false;
             // 注册标志
@@ -102,12 +95,27 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             SipUri uri = (SipUri) address.getURI();
             String deviceId = uri.getUser();
             Device device = deviceService.getDevice(deviceId);
-            String password = (device != null && !ObjectUtils.isEmpty(device.getPassword()))? device.getPassword() : sipConfig.getPassword();
+
+            // 获取对应的sip配置
+            logger.info("[sip 注册信息] host" + uri.getHost());
+            SipUserConfig sipUserConfig = sipConfigService.getSipConfigByDomain(uri.getHost());
+            if (sipUserConfig == null || !sipUserConfig.isEnableFlag()) {
+                logger.error("[sip注册] 不受支持的sip域, 回复404: {}", uri.getHost());
+                response = getMessageFactory().createResponse(Response.NOT_FOUND, request);
+                response.setReasonPhrase("wrong domain");
+                logger.info("[sip注册] 不受支持的sip域: {}", requestAddress);
+                sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
+                return;
+            }
+
+            // 获取密码
+            String password = (device != null && !ObjectUtils.isEmpty(device.getPassword())) ? device.getPassword() : sipUserConfig.getPassword();
+
             AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
             if (authHead == null && !ObjectUtils.isEmpty(password)) {
-                logger.info("[注册请求] 回复401: {}", requestAddress);
+                logger.info("[sip注册] 回复401: {}", requestAddress);
                 response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
-                new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
+                new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipUserConfig.getSipDomain());
                 sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
                 return;
             }
@@ -120,7 +128,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
                 // 注册失败
                 response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
                 response.setReasonPhrase("wrong password");
-                logger.info("[注册请求] 密码/SIP服务器ID错误, 回复403: {}", requestAddress);
+                logger.info("[sip注册] 密码/SIP服务器ID错误, 回复403: {}", requestAddress);
                 sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
                 return;
             }
@@ -160,6 +168,8 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             device.setPort(remoteAddressInfo.getPort());
             device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
             device.setLocalIp(request.getLocalAddress().getHostAddress());
+            device.setDomain(sipUserConfig.getSipDomain());
+
             if (request.getExpires().getExpires() == 0) {
                 // 注销成功
                 registerFlag = false;
@@ -177,15 +187,21 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             // 注册成功
             // 保存到redis
             if (registerFlag) {
-                logger.info("[注册成功] deviceId: {}->{}",  deviceId, requestAddress);
+                logger.info("[sip注册成功] deviceId: {}->{}", deviceId, requestAddress);
                 device.setRegisterTime(DateUtil.getNow());
                 deviceService.online(device);
+
+                if (sipUserConfig.isEnableBindFlag()) {
+                    logger.info("[sip注册成功] 设备:{} 连接的域启用绑定, 尝试获取设备绑定码中");
+
+                    deviceService.loadBindCode(device);
+                }
             } else {
-                logger.info("[注销成功] deviceId: {}->{}" ,deviceId, requestAddress);
+                logger.info("[sip注销成功] deviceId: {}->{}", deviceId, requestAddress);
                 deviceService.offline(deviceId, "主动注销");
             }
         } catch (SipException | NoSuchAlgorithmException | ParseException e) {
-            logger.error("未处理的异常 ", e);
+            logger.error("sip注册 发现 未处理的异常 ", e);
         }
     }
 }

+ 79 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/HfyCodeResponseMessageHandler.java

@@ -0,0 +1,79 @@
+package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
+import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
+import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import gov.nist.javax.sip.message.SIPRequest;
+import org.dom4j.Element;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
+import javax.sip.message.Response;
+import java.text.ParseException;
+
+@Component
+public class HfyCodeResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
+
+    private Logger logger = LoggerFactory.getLogger(HfyCodeResponseMessageHandler.class);
+    private final String cmdType = "HfyCode";
+
+    @Autowired
+    private ResponseMessageHandler responseMessageHandler;
+
+    @Autowired
+    private DeferredResultHolder deferredResultHolder;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        responseMessageHandler.addHandler(cmdType, this);
+    }
+
+    @Override
+    public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
+
+        try {
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
+            Element deviceIdElement = rootElement.element("DeviceID");
+            Element hfyCodeElement = rootElement.element("HfyCode");
+
+            if (hfyCodeElement == null) {
+                // 设备码为空的处理逻辑
+                logger.info("[绑定码回复] 无法解析到设备绑定码");
+                return;
+            }
+            logger.info("[绑定码回复] 收到设备绑定码: {}", hfyCodeElement.getText());
+            String key = DeferredResultHolder.CALLBACK_CMD_ALARM + device.getDeviceId();
+            // 获取 hfyCode
+            RequestMessage msg = new RequestMessage(
+                    key, WVPResult.success(hfyCodeElement)
+            );
+            deferredResultHolder.invokeAllResult(msg);
+            // 保存数据
+            deviceService.updateBindCode(device, hfyCodeElement.getText());
+        } catch (ParseException | SipException | InvalidArgumentException e) {
+            logger.error("[绑定码回复] 解析或返回 ok 失败: {}", e.getMessage());
+        }
+    }
+
+    @Override
+    public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element element) {
+
+    }
+}

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -96,7 +96,7 @@ public class ZLMHttpHookListener {
     private UserSetting userSetting;
 
     @Autowired
-    private IUserService userService;
+    private IAdminService userService;
 
     @Autowired
     private VideoStreamSessionManager sessionManager;

+ 84 - 0
src/main/java/com/genersoft/iot/vmp/service/IAccountService.java

@@ -0,0 +1,84 @@
+package com.genersoft.iot.vmp.service;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.storager.dao.dto.UserAccount;
+import com.github.pagehelper.PageInfo;
+
+import java.util.List;
+
+public interface IAccountService {
+
+    /**
+     * 注册账号
+     * @param name
+     * @param account
+     * @param password
+     * @return
+     */
+    boolean registerAccount(String name, String account, String password);
+
+    /**
+     * 检查账号是否允许注册
+     * @param account
+     * @return
+     */
+    UserAccount checkAccount(String account);
+
+    /**
+     * 登录账号
+     * @param account
+     * @param password
+     * @return
+     */
+    UserAccount login(String account, String password);
+
+    /**
+     * 绑定设备
+     *
+     * @param id
+     * @param devId
+     * @param devCode
+     * @return
+     */
+    int bindDevice(String id, String devId, String devCode);
+
+    /**
+     * 解绑设备
+     *
+     * @param userId
+     * @param devId
+     * @return
+     */
+    int unBindDevice(String userId, String devId);
+
+    /**
+     * 检查绑定码是否能够被绑定, 能够绑定则 允许 true
+     *
+     * @param bindCode
+     * @return
+     */
+    Boolean checkIsAllowBind(String bindCode);
+
+    /**
+     * 使用绑定码检索设备
+     */
+
+    /**
+     * 获取用户设备
+     *
+     * @param accountId 用户id
+     * @param page      页码
+     * @param limit     账户
+     * @return
+     */
+    PageInfo<Device> getAccountDevices(String accountId, int page, int limit);
+
+    /**
+     * 获取指定用户的指定设备
+     *
+     * @param accountId 用户id
+     * @param deviceId  设备id,非国标id
+     * @return Device
+     */
+    Device getAccountDevice(String accountId, String deviceId);
+}

+ 61 - 0
src/main/java/com/genersoft/iot/vmp/service/IAdminService.java

@@ -0,0 +1,61 @@
+package com.genersoft.iot.vmp.service;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.storager.dao.dto.AdminAccount;
+import com.github.pagehelper.PageInfo;
+
+import java.util.List;
+
+public interface IAdminService {
+
+    AdminAccount getUser(String username, String password);
+
+    boolean changePassword(int id, String password);
+
+    AdminAccount getUserByUsername(String username);
+
+    AdminAccount getUserById(String adminId);
+
+    int addUser(AdminAccount adminAccount);
+
+    int deleteUser(int id);
+
+    List<AdminAccount> getAllUsers();
+
+    int updateUsers(AdminAccount adminAccount);
+
+    boolean checkPushAuthority(String callId, String sign);
+
+    PageInfo<AdminAccount> getUsers(int page, int count);
+
+    int changePushKey(int id, String pushKey);
+
+    String getPushKey(int id);
+
+    /**
+     * 获取设备, 根据管理员ID和设备ID
+     *
+     * @param adminId
+     * @param devId   设备表id
+     * @return
+     */
+    Device getAdminDevice(String adminId, String devId);
+
+    /**
+     * 获取设备, 根据管理员ID和设备国标id
+     *
+     * @param adminId
+     * @param deviceSipId
+     * @return
+     */
+    Device getAdminDeviceBySipId(String adminId, String deviceSipId);
+
+    /**
+     * 获取设备列表, 根据管理员ID
+     *
+     * @param adminId
+     * @return
+     */
+    List<Device> getAdminDevices(String adminId);
+
+}

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java

@@ -56,4 +56,6 @@ public interface IDeviceChannelService {
      * 查询通道所属的设备
      */
     List<Device> getDeviceByChannelId(String channelId);
+
+    List<DeviceChannel> getChannelListByDeviceId(String deviceId);
 }

+ 24 - 0
src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java

@@ -167,4 +167,28 @@ public interface IDeviceService {
      * 获取所有设备
      */
     List<Device> getAll();
+
+    /**
+     * 获取能够绑定的设备
+     *
+     * @param bindCode 绑定码
+     * @return
+     */
+    Device getBindDevice(String bindCode);
+
+    /**
+     * 加载设备绑定码
+     *
+     * @param device
+     */
+    public void loadBindCode(Device device);
+
+    /**
+     * 设备绑定码更新
+     *
+     * @param device
+     * @param bindCode
+     * @return
+     */
+    public boolean updateBindCode(Device device, String bindCode);
 }

+ 5 - 1
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java

@@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback;
 import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.bean.*;
@@ -22,7 +23,6 @@ import java.text.ParseException;
  */
 public interface IPlayService {
 
-    void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId);
 
     void play(MediaServerItem mediaServerItem,
               SSRCInfo ssrcInfo,
@@ -41,11 +41,15 @@ public interface IPlayService {
             Runnable timeoutCallback);
 
 
+    void startPlay(RequestMessage requestMsg, MediaServerItem mediaServerItem, Device device, String channelId, int isUsePs);
+
+    void stopPlay(Device device, String channelId);
 
     void openBroadcast(MediaServerItem mediaServerItem,
                        Device device,
                        int waitTime,
                        BroadcastCallback callback);
+
     MediaServerItem getNewMediaServerItem(Device device);
 
     /**

+ 52 - 0
src/main/java/com/genersoft/iot/vmp/service/ISipConfigService.java

@@ -0,0 +1,52 @@
+package com.genersoft.iot.vmp.service;
+
+import com.genersoft.iot.vmp.gb28181.bean.SipUserConfig;
+import com.github.pagehelper.PageInfo;
+
+public interface ISipConfigService {
+    /**
+     * 获取对应的sip配置信息
+     *
+     * @param domain
+     * @return
+     */
+    SipUserConfig getSipConfigByDomain(String domain);
+
+    /**
+     * 获取指定管理员账户下的所有国标配置信息
+     *
+     * @param adminId
+     * @param page
+     * @param limit
+     * @return
+     */
+    PageInfo<SipUserConfig> getSipConfigsById(String adminId, int page, int limit);
+
+
+    /**
+     * 获取指定配置信息
+     *
+     * @param adminId
+     * @param sipId
+     * @return
+     */
+    SipUserConfig getSipConfigById(String adminId, String sipId);
+
+    /**
+     * 编辑国标配置
+     *
+     * @param sipId
+     * @param sipConfig
+     * @return
+     */
+    boolean editSipConfig(String sipId, SipUserConfig sipConfig);
+
+    /**
+     * 新增国标配置信息
+     */
+    boolean addSipConfig(String adminId, SipUserConfig sipConfig);
+
+
+    boolean deleteSipConfig(String adminId, String sipId);
+
+}

+ 0 - 31
src/main/java/com/genersoft/iot/vmp/service/IUserService.java

@@ -1,31 +0,0 @@
-package com.genersoft.iot.vmp.service;
-
-import com.genersoft.iot.vmp.storager.dao.dto.User;
-import com.github.pagehelper.PageInfo;
-
-import java.util.List;
-
-public interface IUserService {
-
-    User getUser(String username, String password);
-
-    boolean changePassword(int id, String password);
-
-    User getUserByUsername(String username);
-
-    int addUser(User user);
-
-    int deleteUser(int id);
-
-    List<User> getAllUsers();
-
-    int updateUsers(User user);
-
-    boolean checkPushAuthority(String callId, String sign);
-
-    PageInfo<User> getUsers(int page, int count);
-
-    int changePushKey(int id, String pushKey);
-
-    String getPushKey(int id);
-}

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/BroadcastCallback.java

@@ -11,3 +11,5 @@ public interface BroadcastCallback {
      */
     void run(int code, JSONObject json, SIPRequest request);
 }
+
+

+ 95 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/AccountServiceImpl.java

@@ -0,0 +1,95 @@
+package com.genersoft.iot.vmp.service.impl;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.service.IAccountService;
+import com.genersoft.iot.vmp.storager.dao.AccountMapper;
+import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
+import com.genersoft.iot.vmp.storager.dao.dto.UserAccount;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class AccountServiceImpl implements IAccountService {
+
+    private Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
+
+    @Autowired
+    private AccountMapper accountMapper;
+
+    @Autowired
+    private DeviceMapper deviceMapper;
+
+    /**
+     * 注册用户
+     *
+     * @param name
+     * @param account
+     * @param password
+     * @return
+     */
+    public boolean registerAccount(String name, String account, String password) {
+       UserAccount userAccount = accountMapper.findByAccount(account);
+       if (userAccount != null) {
+           return false;
+       }
+        // 注册用户
+       return accountMapper.register(name, account, password) > 0;
+    }
+
+    /**
+     * 获取设备
+     * @param account
+     * @return
+     */
+    public UserAccount checkAccount(String account) {
+        return accountMapper.findByAccount(account);
+    }
+
+    /**
+     * 登录
+     * @param account
+     * @param password
+     * @return
+     */
+    public UserAccount login(String account, String password) {
+        return accountMapper.login(account, password);
+    }
+
+    public int bindDevice(String id, String devId, String devCode) {
+        return accountMapper.bindDevice(id, devId, devCode);
+    }
+
+    public int unBindDevice(String userId, String devId) {
+        return accountMapper.unBindDevice(userId, devId);
+    }
+
+
+    public Boolean checkIsAllowBind(String bindCode) {
+        if (deviceMapper.getDeviceByBindCode(bindCode) == null) {
+            logger.warn("not find device");
+            return false;
+        }
+        Device device = accountMapper.findDeviceByDevCode(bindCode);
+        logger.info("device: {}", device);
+        return device == null;
+    }
+
+
+    public PageInfo<Device> getAccountDevices(String accountId, int page, int count) {
+        PageHelper.startPage(page, count);
+        List<Device> devices = accountMapper.getAccountDevices(accountId);
+        return new PageInfo<>(devices);
+    }
+
+    public Device getAccountDevice(String accountId, String deviceId) {
+        return accountMapper.getAccountDevice(accountId, deviceId);
+    }
+
+
+}

+ 128 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/AdminServiceImpl.java

@@ -0,0 +1,128 @@
+package com.genersoft.iot.vmp.service.impl;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.service.IAdminService;
+import com.genersoft.iot.vmp.storager.dao.AdminMapper;
+import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
+import com.genersoft.iot.vmp.storager.dao.dto.AdminAccount;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.ObjectUtils;
+
+import java.util.List;
+
+@Service
+public class AdminServiceImpl implements IAdminService {
+
+    private final static Logger logger = LoggerFactory.getLogger(AdminServiceImpl.class);
+
+    @Autowired
+    private AdminMapper adminMapper;
+
+    @Autowired
+    private DeviceMapper deviceMapper;
+
+    @Override
+    public AdminAccount getUser(String username, String password) {
+        return adminMapper.select(username, password);
+    }
+
+    @Override
+    public boolean changePassword(int id, String password) {
+
+        AdminAccount adminAccount = adminMapper.selectById(id);
+        adminAccount.setPassword(password);
+        return adminMapper.update(adminAccount) > 0;
+    }
+
+    @Override
+    public AdminAccount getUserByUsername(String username) {
+        return adminMapper.getUserByUsername(username);
+    }
+
+    @Override
+    public AdminAccount getUserById(String adminId) {
+        return adminMapper.getUserById(adminId);
+    }
+
+    @Override
+    public int addUser(AdminAccount adminAccount) {
+        AdminAccount adminAccountByUsername = adminMapper.getUserByUsername(adminAccount.getUsername());
+        if (adminAccountByUsername != null) {
+            return 0;
+        }
+        return adminMapper.add(adminAccount);
+    }
+
+    @Override
+    public int deleteUser(int id) {
+        return adminMapper.delete(id);
+    }
+
+    @Override
+    public List<AdminAccount> getAllUsers() {
+        return adminMapper.selectAll();
+    }
+
+    @Override
+    public int updateUsers(AdminAccount adminAccount) {
+        return adminMapper.update(adminAccount);
+    }
+
+
+    @Override
+    public boolean checkPushAuthority(String callId, String sign) {
+        if (ObjectUtils.isEmpty(callId)) {
+            return adminMapper.checkPushAuthorityByCallId(sign).size() > 0;
+        }else {
+            return adminMapper.checkPushAuthorityByCallIdAndSign(callId, sign).size() > 0;
+        }
+    }
+
+    @Override
+    public PageInfo<AdminAccount> getUsers(int page, int count) {
+        PageHelper.startPage(page, count);
+        List<AdminAccount> adminAccounts = adminMapper.getUsers();
+        return new PageInfo<>(adminAccounts);
+    }
+
+    @Override
+    public int changePushKey(int id, String pushKey) {
+        return adminMapper.changePushKey(id, pushKey);
+    }
+
+    @Override
+    public String getPushKey(int id) {
+        List<AdminAccount> adminAccounts = adminMapper.getPushKey(id);
+        if (!adminAccounts.isEmpty()) {
+            return adminAccounts.get(0).getPushKey();
+        }
+        return null;
+    }
+
+    // 获取当前管理员的设备
+    public Device getAdminDevice(String adminId, String devId) {
+        return adminMapper.getDeviceByAdminIdAndDevId(adminId, devId);
+    }
+
+    // 获取某个管理员账户下的所有设备
+    public List<Device> getAdminDevices(String adminId) {
+        return adminMapper.getDevicesByAdminId(adminId);
+    }
+
+    //    获取某个管理员设备,根据设备国标id
+    public Device getAdminDeviceBySipId(String adminId, String deviceSipId) {
+//        logger.info("getAdminDeviceBySipId get admin:{} device:{}", adminId, deviceSipId);
+        Device device = adminMapper.getAdminDeviceBySipId(adminId, deviceSipId);
+        if (device == null) {
+            return null;
+        }
+        return device;
+    }
+
+
+}

+ 6 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java

@@ -209,4 +209,10 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
     public List<Device> getDeviceByChannelId(String channelId) {
         return channelMapper.getDeviceByChannelId(channelId);
     }
+
+    @Override
+    public List<DeviceChannel> getChannelListByDeviceId(String deviceId) {
+        return channelMapper.getChannelsByDeviceId(deviceId);
+    }
+
 }

+ 93 - 3
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java

@@ -1,6 +1,9 @@
 package com.genersoft.iot.vmp.service.impl;
 
 import com.genersoft.iot.vmp.common.HfyAiInfo;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
+import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.dao.HfyDevAiMapper;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
@@ -21,16 +24,17 @@ import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
 import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
 import com.genersoft.iot.vmp.storager.dao.PlatformChannelMapper;
 import com.genersoft.iot.vmp.utils.DateUtil;
-import com.genersoft.iot.vmp.vmanager.bean.BaseTree;
-import com.genersoft.iot.vmp.vmanager.bean.ResourceBaceInfo;
+import com.genersoft.iot.vmp.vmanager.bean.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.util.ObjectUtils;
+import org.springframework.web.context.request.async.DeferredResult;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
@@ -39,7 +43,9 @@ import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
 
@@ -57,6 +63,9 @@ public class DeviceServiceImpl implements IDeviceService {
     @Autowired
     private ISIPCommander sipCommander;
 
+    @Autowired
+    private SIPCommander cmder;
+
     @Autowired
     private CatalogResponseMessageHandler catalogResponseMessageHandler;
 
@@ -99,10 +108,16 @@ public class DeviceServiceImpl implements IDeviceService {
     @Autowired
     private SipConfig sipConfig;
 
+    @Autowired
+    private DeferredResultHolder resultHolder;
+
+    @Autowired
+    private DeferredResultHolder deferredResultHolder;
+
     @Override
     public void online(Device device) {
         logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
-        logger.info("[test] sipBaseUrl={}:{}",sipConfig.getIp(),sipConfig.getPort());
+        logger.info("[test] sipBaseUrl={}:{}", sipConfig.getIp(), sipConfig.getPort());
         Device deviceInRedis = redisCatchStorage.getDevice(device.getDeviceId());
         Device deviceInDb = deviceMapper.getDeviceByDeviceId(device.getDeviceId());
 
@@ -684,4 +699,79 @@ public class DeviceServiceImpl implements IDeviceService {
     public List<Device> getAll() {
         return deviceMapper.getAll();
     }
+
+
+    public Device getBindDevice(String bindCode) {
+        return deviceMapper.getDeviceByBindCode(bindCode);
+    }
+
+
+    public void loadBindCode(Device device) {
+
+        ErrorHook cmdSendHook = wvpResult ->
+        {
+
+            if (wvpResult.getCode() != ErrorCode.SUCCESS.getCode()) {
+                logger.warn("[查询设备绑定码] 异常 {}:{}", wvpResult.getCode(), wvpResult.getMsg());
+                return;
+            }
+            logger.info("[查询设备绑定码] 命令发送成功 {}", wvpResult);
+        };
+
+        try {
+            sipCommander.queryBindCode(device, cmdSendHook);
+        } catch (Exception e) {
+            logger.error("[查询设备绑定码] 严重异常 err:{}", e.getMessage());
+        }
+    }
+
+
+    /**
+     * 创建 DeferredResult 的
+     *
+     * @param keyStr
+     * @param timeout
+     * @param eventHook
+     * @param timeoutMessage
+     * @param successMessage
+     * @return
+     */
+    public RequestMessage createDeferredResultAndCallback(String keyStr, int timeout, ErrorHook eventHook, String timeoutMessage, String successMessage) {
+        DeferredResult<WVPResult> result =
+                new DeferredResult<>(timeout * 10L);
+        DeferredResultEx<WVPResult> deferredResultEx = new DeferredResultEx<>(result);
+        String uuid = UUID.randomUUID().toString();
+        RequestMessage msg = new RequestMessage();
+        msg.setId(uuid);
+        msg.setKey(keyStr);
+        result.onTimeout(() -> {
+            logger.warn(timeoutMessage);
+            msg.setData(WVPResult.fail(ErrorCode.ERR_TIMEOUT, timeoutMessage));
+            resultHolder.invokeResult(msg);
+        });
+
+        result.onCompletion(() -> {
+            logger.warn(successMessage);
+            eventHook.run((WVPResult) result.getResult());
+        });
+
+        resultHolder.put(keyStr, uuid, deferredResultEx);
+        return msg;
+    }
+
+    // 更新设备 绑定码
+    public boolean updateBindCode(Device device, String bindCode) {
+        if (device == null || bindCode == null || bindCode.equals("")) {
+            logger.error("[绑定码更新] 设备或者绑定码错误");
+            return false;
+        }
+        Device oldDevice = deviceMapper.getDeviceByBindCode(bindCode);
+        if (oldDevice != null) {
+            oldDevice.setBindCode(null);
+            deviceMapper.updateBindCode(oldDevice.getDeviceId(), "");
+        }
+        return deviceMapper.updateBindCode(device.getDeviceId(), bindCode) > 0;
+    }
+
+
 }

+ 7 - 4
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -121,7 +121,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
 
     @Override
     public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback) {
-        return openRTPServer(mediaServerItem, streamId, null, ssrcCheck,isPlayback);
+        return openRTPServer(mediaServerItem, streamId, null, ssrcCheck, isPlayback);
     }
 
 
@@ -129,7 +129,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
     @Override
     public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, boolean isPlayback, Integer port) {
         if (mediaServerItem == null || mediaServerItem.getId() == null) {
-            logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
+            logger.warn("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
             return null;
         }
         // 获取mediaServer可用的ssrc
@@ -137,7 +137,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
 
         SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
         if (ssrcConfig == null) {
-            logger.info("media server [ {} ] ssrcConfig is null", mediaServerItem.getId());
+            logger.warn("media server [ {} ] ssrcConfig is null", mediaServerItem.getId());
             return null;
         }else {
             String ssrc;
@@ -213,7 +213,10 @@ public class MediaServerServiceImpl implements IMediaServerService {
 //            JSONObject result = zlmrtpServerFactory.startSendRtpStream(mediaServerItem,rtpParam);
             logger.info("zlm start send {}", result.toJSONString());
             Integer code = (Integer) result.get("code");
-            if(code != null && code.intValue() != 0){ logger.error("[语音广播 流媒体异常] {}", result.get("msg")); return null; }
+            if (code != null && code.intValue() != 0) {
+                logger.error("[语音广播 流媒体异常] {}", result.get("msg"));
+                return null;
+            }
             RedisUtil.set(key, mediaServerItem);
             rtpServerPort = (int) result.get("local_port");
             return new SSRCInfo(rtpServerPort, ssrc, recv_stream_id);

+ 385 - 148
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -14,6 +14,7 @@ import com.genersoft.iot.vmp.gb28181.GBHookSubscribeFactory;
 import com.genersoft.iot.vmp.gb28181.HookSubscribeForKey;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
@@ -35,6 +36,8 @@ import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorHook;
+import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
@@ -53,6 +56,7 @@ import javax.sip.SipException;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.ParseException;
+import java.util.EventObject;
 import java.util.List;
 import java.util.UUID;
 
@@ -117,14 +121,14 @@ public class PlayServiceImpl implements IPlayService {
 
     @Override
     public void play(MediaServerItem mediaServerItem, String deviceId,
-                                String channelId,int isUsePs,
-                                 ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
-                                 Runnable timeoutCallback) {
+                     String channelId, int isUsePs,
+                     ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
+                     Runnable timeoutCallback) {
 
         String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
         WVPResult wvpResult = new WVPResult();
         RequestMessage msg = new RequestMessage();
-        Device device = redisCatchStorage.getDevice(deviceId);
+        Device device = storager.queryVideoDevice(deviceId);
         StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
 
         msg.setKey(key);
@@ -209,26 +213,25 @@ public class PlayServiceImpl implements IPlayService {
             }
             SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
             logger.info(JSONObject.toJSONString(ssrcInfo));
-			if (ssrcInfo == null) {
+            if (ssrcInfo == null) {
                 wvpResult.setCode(ErrorCode.ERROR100.getCode());
                 wvpResult.setMsg("开启收流失败");
                 msg.setData(wvpResult);
                 resultHolder.invokeAllResult(msg);
                 return;
             }
-            play(mediaServerItem, ssrcInfo, device, channelId,
-                    isUsePs,
+            play(mediaServerItem, ssrcInfo, device, channelId, isUsePs,
                     (mediaServerItemInUse, response) -> {
-                if (hookEvent != null) {
-                    hookEvent.response(mediaServerItem, response);
-                }
-            }, event -> {
-                // sip error错误
-               logger.warn("sip 错误,点播失败");
-                wvpResult.setCode(ErrorCode.ERROR100.getCode());
-                wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg));
-                msg.setData(wvpResult);
-                resultHolder.invokeAllResult(msg);
+                        if (hookEvent != null) {
+                            hookEvent.response(mediaServerItem, response);
+                        }
+                    }, event -> {
+                        // sip error错误
+                        logger.warn("sip 错误,点播失败");
+                        wvpResult.setCode(ErrorCode.ERROR100.getCode());
+                        wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg));
+                        msg.setData(wvpResult);
+                        resultHolder.invokeAllResult(msg);
                 if (errorEvent != null) {
                     errorEvent.response(event);
                 }
@@ -280,6 +283,8 @@ public class PlayServiceImpl implements IPlayService {
                 }
             }
         }, userSetting.getPlayTimeout());
+        RequestMessage msg = new RequestMessage();
+        msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + device.getDeviceId() + channelId);
         //端口获取失败的ssrcInfo 没有必要发送点播指令
         if (ssrcInfo.getPort() <= 0) {
             logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
@@ -287,88 +292,87 @@ public class PlayServiceImpl implements IPlayService {
             // 释放ssrc
             mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
             streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-
-            RequestMessage msg = new RequestMessage();
-            msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + device.getDeviceId() + channelId);
             msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "点播端口分配异常"));
             resultHolder.invokeAllResult(msg);
             return;
         }
         try {
-            cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId,isUsePs, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
-                logger.info("收到订阅消息: " + response.toJSONString());
-                logger.info("停止超时任务: " + timeOutTaskKey);
-                dynamicTask.stop(timeOutTaskKey);
-                // hook响应
-                onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId);
-                hookEvent.response(mediaServerItemInuse, response);
-                logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
-                String streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.flv", mediaServerItemInuse.getHttpPort(), "rtp",  ssrcInfo.getStream());
-                String path = "snap";
-                String fileName = device.getDeviceId() + "_" + channelId + ".jpg";
-                // 请求截图
-                logger.info("[请求截图]: " + fileName);
-                zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
-
-            }, (event) -> {
-                ResponseEvent responseEvent = (ResponseEvent) event.event;
-                String contentString = new String(responseEvent.getResponse().getRawContent());
-                // 获取ssrc
-                int ssrcIndex = contentString.indexOf("y=");
-                // 检查是否有y字段
-                if (ssrcIndex >= 0) {
-                    //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
-                    String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
-                    // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
-                    if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
-                        return;
-                    }
-                    logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
-                    if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
-                        logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
-
-                        if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
-                            // ssrc 不可用
-                            // 释放ssrc
-                            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
-                            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-                            event.msg = "下级自定义了ssrc,但是此ssrc不可用";
-                            event.statusCode = 400;
-                            errorEvent.response(event);
-                            return;
-                        }
+            cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, isUsePs,
+                    (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
+                        logger.info("收到订阅消息: " + response.toJSONString());
+                        logger.info("停止超时任务: " + timeOutTaskKey);
+                        dynamicTask.stop(timeOutTaskKey);
+                        // hook响应
+                        onPublishHandlerForPlay(msg, mediaServerItemInuse, response, device.getDeviceId(), channelId, ssrcInfo.getSsrc());
+                        hookEvent.response(mediaServerItemInuse, response);
+                        logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
+                        String streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.flv", mediaServerItemInuse.getHttpPort(), "rtp", ssrcInfo.getStream());
+                        String path = "snap";
+                        String fileName = device.getDeviceId() + "_" + channelId + ".jpg";
+                        // 请求截图
+                        logger.info("[请求截图]: " + fileName);
+                        zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
+
+                    },
+                    (okEvent) -> {
+                        ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
+                        String contentString = new String(responseEvent.getResponse().getRawContent());
+                        // 获取ssrc
+                        int ssrcIndex = contentString.indexOf("y=");
+                        // 检查是否有y字段
+                        if (ssrcIndex >= 0) {
+                            //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
+                            String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+                            // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
+                            if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
+                                return;
+                            }
+                            logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
+                            if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
+                                logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
+
+                                if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
+                                    // ssrc 不可用
+                                    // 释放ssrc
+                                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                                    okEvent.msg = "下级自定义了ssrc,但是此ssrc不可用";
+                                    okEvent.statusCode = 400;
+                                    errorEvent.response(okEvent);
+                                    return;
+                                }
 
-                        // 单端口模式streamId也有变化,需要重新设置监听
-                        if (!mediaServerItem.isRtpEnable()) {
-                            // 添加订阅
-                            HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
-                            subscribe.removeSubscribe(hookSubscribe);
-                            hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
-                            subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
-                                logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
-                                dynamicTask.stop(timeOutTaskKey);
-                                // hook响应
-                                onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId);
-                                hookEvent.response(mediaServerItemInUse, response);
-                            });
-                        }
-                        // 关闭rtp server
-                        mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
-                        // 重新开启ssrc server
-                        mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, ssrcInfo.getPort());
+                                // 单端口模式streamId也有变化,需要重新设置监听
+                                if (!mediaServerItem.isRtpEnable()) {
+                                    // 添加订阅
+                                    HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+                                    subscribe.removeSubscribe(hookSubscribe);
+                                    hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+                                    subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
+                                        logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
+                                        dynamicTask.stop(timeOutTaskKey);
+                                        // hook响应
+                                        onPublishHandlerForPlay(msg, mediaServerItemInUse, response, device.getDeviceId(), channelId, ssrcInfo.getSsrc());
+                                        hookEvent.response(mediaServerItemInUse, response);
+                                    });
+                                }
+                                // 关闭rtp server
+                                mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                                // 重新开启ssrc server
+                                mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, ssrcInfo.getPort());
 
+                            }
+                        }
                     }
-                }
-            }
-            , (event) -> {
-                dynamicTask.stop(timeOutTaskKey);
-                mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
-                // 释放ssrc
-                mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                    , (errEvent) -> {
+                        dynamicTask.stop(timeOutTaskKey);
+                        mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                        // 释放ssrc
+                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 
-                streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-                errorEvent.response(event);
-            });
+                        streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                        errorEvent.response(errEvent);
+                    });
         } catch (InvalidArgumentException | SipException | ParseException e) {
 
             logger.error("[命令发送失败] 点播消息: {}", e.getMessage());
@@ -378,86 +382,300 @@ public class PlayServiceImpl implements IPlayService {
             mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 
             streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-            SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
-            eventResult.msg = "命令发送失败";
+//            有什么大病一般的代码
+//            SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
+//            eventResult.msg = "命令发送失败";
+            EventObject eventObject = new EventObject("命令发送失败");
+            SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(eventObject);
             errorEvent.response(eventResult);
+//
+//            RequestMessage msg = new RequestMessage();
+//            msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + device.getDeviceId() + channelId);
+//            msg.setData(WVPResult.fail(ErrorCode.ERR_Invite_fail, "点播命令发送失败: " + e.getMessage()));
+//            resultHolder.invokeAllResult(msg);
+        }
+    }
 
-            RequestMessage msg = new RequestMessage();
-            msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + device.getDeviceId() + channelId);
-            msg.setData(WVPResult.fail(ErrorCode.ERR_Invite_fail.getCode(), "点播命令发送至设备异常"));
-            resultHolder.invokeAllResult(msg);
+    /**
+     * 从缓存中获取流信息
+     *
+     * @param deviceId
+     * @param channelId
+     * @return
+     */
+    private StreamInfo getStreamInfo(String deviceId, String channelId, WVPResult wvpResult) {
+        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
+        // 两种情况, 一种是已经在推流中了. 一种是还没有开始推流
+        if (streamInfo == null) {
+            // 未开始推流, 开启新推流
+            return null;
+        }
+
+        // 能够读取到视频缓存
+        String streamId = streamInfo.getStream();
+        // 应该是不可能出现的异常情况,
+        if (streamId == null) {
+            logger.warn("[读取推流信息] 失败, redis缓存异常 streamId 为空");
+            // 说明缓存的流信息有问题
+            // 移除redis 缓存
+            redisCatchStorage.stopPlay(streamInfo);
+            redisCatchStorage.deleteDeviceStream(deviceId, channelId);
+            storager.stopPlay(deviceId, channelId);
+            return null;
+        }
+        String mediaServerId = streamInfo.getMediaServerId();
+        // 获取视频推流通道对应的流媒体信息
+        MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
+        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
+        if (rtpInfo == null) {
+            logger.warn("[读取推流信息] 失败, rtpInfo为null");
+            redisCatchStorage.stopPlay(streamInfo);
+            redisCatchStorage.deleteDeviceStream(deviceId, channelId);
+            storager.stopPlay(deviceId, channelId);
+            return null;
+        }
+        if (rtpInfo.getInteger("code") != 0) {
+            redisCatchStorage.stopPlay(streamInfo);
+            redisCatchStorage.deleteDeviceStream(deviceId, channelId);
+            storager.stopPlay(deviceId, channelId);
+            return null;
+        }
+        if (!rtpInfo.getBoolean("exist")) {
+            // 说明流已经停止
+            redisCatchStorage.stopPlay(streamInfo);
+            redisCatchStorage.deleteDeviceStream(deviceId, channelId);
+            storager.stopPlay(deviceId, channelId);
+            return null;
+        }
+        int localPort = rtpInfo.getInteger("local_port");
+        if (localPort == 0) {
+            logger.warn("[读取推流信息] 失败, rtpServer存在,但是尚未开始推流");
+            // 此时说明rtpServer已经创建但是流还没有推上来, 所以稍微等待一下重试即可
+            // 使用 wvpResult 返回对应信息提示
+            wvpResult.setCode(ErrorCode.ERROR_Retry.getCode());
+            wvpResult.setMsg("点播已经在进行中,请稍后进行重试");
         }
+        return streamInfo;
     }
 
+    // 直接抛弃原有的播放 函数 创建新地播放函数, 重新梳理播放逻辑
+    public void startPlay(RequestMessage requestMsg, MediaServerItem mediaServerItem, Device device, String channelId, int isUsePs) {
+        // 获取缓存的流信息
+        WVPResult wvpResult = new WVPResult();
+        StreamInfo streamInfo = getStreamInfo(device.getDeviceId(), channelId, wvpResult);
 
+        if (streamInfo != null) {
+            logger.info("已经启用视频流 {}", wvpResult.toString());
+            // 说明已经在推流中了
+            if (wvpResult.getCode() != ErrorCode.ERROR_Retry.getCode()) {
+                // 在获取流信息没有更改 wvpResult 时将返回值设置为成功
+                logger.warn("{} 已经在推流, 并且无异常", device.getDeviceId(), channelId);
+                // 数据类型转换, 懒得改前端了
+                wvpResult = WVPResult.success(
+                        new StreamContent(streamInfo)
+                );
+            }
+            resultHolder.invokeResult(requestMsg.setData(wvpResult));
+            return;
+        }
+        // 说明还没有开始推流
+        if (!mediaServerItem.isRtpEnable()) {
+            logger.warn("设备应用的流媒体未启用");
+            resultHolder.invokeAllResult(
+                    requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "设备应用的流媒体未启用"))
+            );
+            return;
+        }
+        // 生成streamId
+        String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
+        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
+        if (ssrcInfo == null) {
+            logger.warn("开启收流失败");
+            resultHolder.invokeAllResult(
+                    requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "开启收流失败"))
+            );
+            return;
+        }
+        // 尝试启用收流
+        if (ssrcInfo.getPort() <= 0) {
+            logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
+            resultHolder.invokeAllResult(
+                    requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "点播端口分配异常, 请重试"))
+            );
+            return;
+        }
+        executePlayLive(requestMsg, mediaServerItem, ssrcInfo, device, channelId, isUsePs);
+    }
+
+    /**
+     * 执行推流相关操作 发送命令
+     *
+     * @param requestMsg
+     * @param mediaServerItem
+     * @param device
+     * @param channelId
+     * @param isUsePs
+     */
+    private void executePlayLive(RequestMessage requestMsg, MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, int isUsePs) {
+        logger.info("[支持点播推流] 收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
+                ssrcInfo.getPort(), device.getStreamMode(),
+                ssrcInfo.getSsrc(), device.isSsrcCheck());
+        String stream = ssrcInfo.getStream();
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+
+        // 构建errorHook 回调函数
+        ErrorHook errorHook = wvpResult ->
+        {
+            logger.info("收到点播命令回调 {}", wvpResult);
+            if (wvpResult.getCode() != ErrorCode.SUCCESS.getCode()) {
+                logger.error("[发送点播命令] 失败 {}", wvpResult.getMsg());
+                requestMsg.setData(wvpResult);
+                stopPlay(device, channelId);
+//            resultHolder.invokeAllResult(requestMsg);
+            }
+            // 监听zlm的流改变事件, 在流改变时直接回复 http 信息
+            addHookSubscribeForStreamChange(requestMsg, mediaServerItem, device, channelId, ssrcInfo);
+        };
+
+        try {
+            // 发送命令
+            cmder.sendPlayCmd(mediaServerItem, ssrcInfo, device, channelId, isUsePs, errorHook);
+        } catch (Exception e) {
+            e.printStackTrace();
+            logger.error("[发送点播命令] 失败 {}", e.getMessage());
+            // 未知的错误, 打印错误堆栈
+            requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "发送点播命令失败"));
+            resultHolder.invokeAllResult(requestMsg);
+
+        }
+    }
+
+    // 停止推流
+    public void stopPlay(Device device, String channelId) {
+        WVPResult wvpResult = new WVPResult();
+        StreamInfo streamInfo = getStreamInfo(device.getDeviceId(), channelId, wvpResult);
+        // 获取 流媒体
 
 
+        // 发送bye给设备
+        ErrorHook errorHook = wvpResult1 ->
+        {
+            logger.info("关闭推流回调 {}", wvpResult1);
+            if (wvpResult1.getCode() != ErrorCode.SUCCESS.getCode()) {
+                logger.error("[关闭推流回调] 失败 {}", wvpResult1.getMsg());
+                return;
+            }
+            //
+            if (streamInfo != null) {
+                logger.info("找到正在推流的视频流 尝试停止 {}", wvpResult.toString());
+                MediaServerItem mediaServerItem = mediaServerService.getOne(
+                        streamInfo.getMediaServerId()
+                );
+                // 获取ssrc
+                logger.info("移除并关闭推流通道");
+                redisCatchStorage.stopPlay(streamInfo);
+                redisCatchStorage.deleteDeviceStream(device.getDeviceId(), channelId);
+                mediaServerService.releaseSsrc(streamInfo.getMediaServerId(), streamInfo.getSsrc());
+                mediaServerService.closeRTPServer(streamInfo.getMediaServerId(), streamInfo.getStream());
+            } else {
+                logger.info("在没有服务端缓存的情况下关闭推流通道");
+            }
+        };
+        try {
+            // 发送命令
+            cmder.stopPlayCmd(device, channelId, errorHook);
+        } catch (Exception e) {
+            e.printStackTrace();
+            logger.error("[关闭推流] 失败 {}", e.getMessage());
+            // 未知的错误, 打印错误堆栈
+        }
+    }
+
+    /**
+     * 截取视频快照
+     *
+     * @param mediaServerItemInUse
+     * @param deviceId
+     * @param channelId
+     * @param ssrcInfo
+     */
+    public void screenshot(MediaServerItem mediaServerItemInUse, String deviceId, String channelId, SSRCInfo ssrcInfo) {
+        String streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.flv",
+                mediaServerItemInUse.getHttpPort(), "rtp", ssrcInfo.getStream());
+        String path = "snap";
+        String fileName = deviceId + "_" + channelId + ".jpg";
+        // 请求截图
+        logger.info("[尝试截取视频快照]: " + fileName);
+        zlmresTfulUtils.getSnap(mediaServerItemInUse, streamUrl, 15, 1, path, fileName);
+    }
+
     public void openBroadcast(MediaServerItem mediaServerItem,
                               Device device,
                               int waitTime,
-                              BroadcastCallback callback){
-            logger.warn("[语音广播] 开语音广播 新");
-            JSONObject errJson = new JSONObject();
-            try {
-                cmder.audioBroadcastCmd(device);
-            } catch (InvalidArgumentException | SipException | ParseException e) {
+                              BroadcastCallback callback) {
+        logger.warn("[语音广播] 开语音广播 新");
+        JSONObject errJson = new JSONObject();
+        try {
+            cmder.audioBroadcastCmd(device);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
                 logger.error("[命令发送失败] 发送broadcast中 errorMsg: {}", e.getMessage());
                 errJson.put("msg", "[命令发送失败] 无法发送broadcast消息");
                 callback.run(2, errJson, null);
             }
             logger.warn("等待设备返回invite");
-            HookSubscribeForKey broadcastForInviteHook = GBHookSubscribeFactory.on_broadcast_invite(device.getDeviceId());
-            // 创建计时器,计时结束未收到invite则自动进行失败处理
-            String timeOutTaskKey = UUID.randomUUID().toString();
-            dynamicTask.startDelay(timeOutTaskKey, () -> {
-                // todo 发送 bye 通知给设备?
-                logger.warn("invite超时");
-                errJson.put("msg", "等待设备语音invite信息超时");
-                callback.run(1, errJson, null);
-            }, waitTime);
-
-            GBHookSubscribe.addInviteSubscribe(broadcastForInviteHook,
-                    (int code, JSONObject json, SIPRequest request) -> {
-                        // invite信息返回
-                        logger.info("[语音广播] 接收到设备invite信息___订阅事件触发 JSONDATA: {}", json.toJSONString());
-                        // 取消计时器
-                        dynamicTask.stop(timeOutTaskKey);
-                        callback.run(0, json, request);
-                    });
-    };
-    @Override
-    public void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId) {
+        HookSubscribeForKey broadcastForInviteHook = GBHookSubscribeFactory.on_broadcast_invite(device.getDeviceId());
+        // 创建计时器,计时结束未收到invite则自动进行失败处理
+        String timeOutTaskKey = UUID.randomUUID().toString();
+        dynamicTask.startDelay(timeOutTaskKey, () -> {
+            // todo 发送 bye 通知给设备?
+            logger.warn("invite超时");
+            errJson.put("msg", "等待设备语音invite信息超时");
+            callback.run(1, errJson, null);
+        }, waitTime);
+
+        GBHookSubscribe.addInviteSubscribe(
+                broadcastForInviteHook,
+                (int code, JSONObject json, SIPRequest request) -> {
+                    // invite信息返回
+                    logger.info("[语音广播] 接收到设备invite信息___订阅事件触发 JSONDATA: {}", json.toJSONString());
+                    // 取消计时器
+                    dynamicTask.stop(timeOutTaskKey);
+                    callback.run(0, json, request);
+                });
+    }
+
+    ;
+
+    private void onPublishHandlerForPlay(
+            RequestMessage requestMsg, MediaServerItem mediaServerItem,
+            JSONObject response, String deviceId, String channelId, String ssrc) {
         StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
-        RequestMessage msg = new RequestMessage();
-        msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId);
         if (streamInfo != null) {
+            streamInfo.setBack(false);
+            streamInfo.setSsrc(ssrc);
             DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
             if (deviceChannel != null) {
                 deviceChannel.setStreamId(streamInfo.getStream());
                 storager.startPlay(deviceId, channelId, streamInfo.getStream());
             }
             redisCatchStorage.startPlay(streamInfo);
-
-            WVPResult wvpResult = new WVPResult();
-            wvpResult.setCode(ErrorCode.SUCCESS.getCode());
-            wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
-            wvpResult.setData(streamInfo);
-
-            msg.setData(wvpResult);
-            resultHolder.invokeAllResult(msg);
-
+            // 流信息转换
+            requestMsg.setData(WVPResult.success(new StreamContent(streamInfo)));
+            resultHolder.invokeAllResult(requestMsg);
         } else {
             logger.warn("设备预览API调用失败!");
-            msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "设备预览API调用失败!"));
-            resultHolder.invokeAllResult(msg);
+            requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "设备预览API调用失败!"));
+            resultHolder.invokeAllResult(requestMsg);
         }
     }
 
-    private void onPublishHandlerForPlayback(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, PlayBackCallback playBackCallback) {
+    private void onPublishHandlerForPlayback(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, String ssrc, PlayBackCallback playBackCallback) {
 
         StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
         PlayBackResult<StreamInfo> playBackResult = new PlayBackResult<>();
         if (streamInfo != null) {
+            streamInfo.setBack(true);
+            streamInfo.setSsrc(ssrc);
             DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
             if (deviceChannel != null) {
                 deviceChannel.setStreamId(streamInfo.getStream());
@@ -489,7 +707,6 @@ public class PlayServiceImpl implements IPlayService {
         } else {
             // 尝试获取device配置的zlm服务
             mediaServerItem = mediaServerService.getOne(device.getMediaServerId());
-
             // 如果默认zlm无法找到则随机分配一个zlm
             if(mediaServerItem == null){
                 logger.warn("无法找到设备默认流媒体服务,即将使用默认流媒体服务");
@@ -638,7 +855,7 @@ public class PlayServiceImpl implements IPlayService {
                                             logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
                                             dynamicTask.stop(playBackTimeOutTaskKey);
                                             // hook响应
-                                            onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, playBackCallback);
+                                            onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, ssrcInfo.getSsrc(), playBackCallback);
                                             hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
                                         });
                                     }
@@ -777,7 +994,7 @@ public class PlayServiceImpl implements IPlayService {
                                             logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
                                             dynamicTask.stop(downLoadTimeOutTaskKey);
                                             // hook响应
-                                            onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, hookCallBack);
+                                            onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, ssrcInfo.getSsrc(), hookCallBack);
                                             hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
                                         });
                                     }
@@ -847,7 +1064,8 @@ public class PlayServiceImpl implements IPlayService {
         RequestMessage msg = new RequestMessage();
         msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId);
         msg.setId(uuid);
-        StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
+        StreamInfo streamInfo = onPublishHandler(
+                inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
         if (streamInfo != null) {
             redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
             msg.setData(JSON.toJSONString(streamInfo));
@@ -860,9 +1078,9 @@ public class PlayServiceImpl implements IPlayService {
     }
 
 
-    public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId) {
-        String streamId = resonse.getString("stream");
-        JSONArray tracks = resonse.getJSONArray("tracks");
+    public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject json, String deviceId, String channelId) {
+        String streamId = json.getString("stream");
+        JSONArray tracks = json.getJSONArray("tracks");
         StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, "rtp", streamId, tracks, null);
         streamInfo.setDeviceID(deviceId);
         streamInfo.setChannelId(channelId);
@@ -1052,9 +1270,9 @@ public class PlayServiceImpl implements IPlayService {
                     null,
                     null);
 
-            callback.run(0, null,null);
-        }catch(InvalidArgumentException |  SipException | ParseException e){
-            logger.error("[下发audio拉流invite失败]",e);
+            callback.run(0, null, null);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[下发audio拉流invite失败]", e);
             logger.error("[zlm控制异常] 创建媒体流失败");
             errJson.put("msg", "[zlm控制异常] 创建媒体流失败");
             callback.run(2, errJson, null);
@@ -1062,5 +1280,24 @@ public class PlayServiceImpl implements IPlayService {
 //                    eventResult.msg = "命令发送失败";
 //                    errorEvent.response(eventResult);
         }
-    };
+    }
+
+    ;
+
+
+    private void addHookSubscribeForStreamChange(
+            RequestMessage requestMsg, MediaServerItem mediaServerItem,
+            Device device, String channelId, SSRCInfo ssrcInfo) {
+        String stream = ssrcInfo.getStream();
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
+        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
+            // zlm 事件触发. 流改变事件
+            logger.info("[ZLM HOOK] 收到ZLM流 编号信息: {}", json.toJSONString());
+            logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
+            // 处理结果
+            onPublishHandlerForPlay(requestMsg, mediaServerItemInUse, json, device.getDeviceId(), channelId, ssrcInfo.getSsrc());
+            // 请求截图
+            screenshot(mediaServerItemInUse, device.getDeviceId(), channelId, ssrcInfo);
+        });
+    }
 }

+ 64 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/SipConfigService.java

@@ -0,0 +1,64 @@
+package com.genersoft.iot.vmp.service.impl;
+
+import com.genersoft.iot.vmp.gb28181.bean.SipUserConfig;
+import com.genersoft.iot.vmp.service.ISipConfigService;
+import com.genersoft.iot.vmp.storager.dao.SipConfigMapper;
+import com.genersoft.iot.vmp.utils.DateUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class SipConfigService implements ISipConfigService {
+    private final static Logger logger = LoggerFactory.getLogger(SipConfigService.class);
+
+    @Autowired
+    private SipConfigMapper sipConfigMapper;
+
+    @Override
+    public SipUserConfig getSipConfigByDomain(String domain) {
+        logger.info("查询sip配置信息,domain:" + domain);
+        List<SipUserConfig> sipConfigs = sipConfigMapper.querySipConfigByDomain(domain);
+        // 如果有的话返回第一个
+        if (sipConfigs == null) {
+            logger.warn("无法找到对应的sip配置信息 {}", domain);
+            return null;
+        }
+        return sipConfigs.get(0);
+    }
+
+    @Override
+    public PageInfo<SipUserConfig> getSipConfigsById(String adminId, int page, int limit) {
+        PageHelper.startPage(page, limit);
+        logger.info("获取管理员{}, 的sip配置信息", adminId);
+        List<SipUserConfig> sipConfigs = sipConfigMapper.querySipConfigsByAdminId(adminId);
+        return new PageInfo<>(sipConfigs);
+    }
+
+    @Override
+    public SipUserConfig getSipConfigById(String adminId, String sipId) {
+        return sipConfigMapper.querySipConfigsById(adminId, sipId);
+    }
+
+    public boolean editSipConfig(String sipId, SipUserConfig sipConfig) {
+        return sipConfigMapper.updateSipConfig(sipConfig, sipId) > 0;
+    }
+
+    public boolean addSipConfig(String adminId, SipUserConfig sipConfig) {
+        String createTime = "";
+        sipConfig.setAdminId(adminId);
+        sipConfig.setEnable("1");
+        sipConfig.setCreateTime(DateUtil.getNow());
+        return sipConfigMapper.addSipConfig(sipConfig) > 0;
+    }
+
+    public boolean deleteSipConfig(String adminId, String sipId) {
+        return sipConfigMapper.deleteSipConfig(sipId, adminId) > 0;
+    }
+
+}

+ 0 - 92
src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java

@@ -1,92 +0,0 @@
-package com.genersoft.iot.vmp.service.impl;
-
-import com.genersoft.iot.vmp.service.IUserService;
-import com.genersoft.iot.vmp.storager.dao.UserMapper;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
-import com.github.pagehelper.PageHelper;
-import com.github.pagehelper.PageInfo;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
-
-import java.util.List;
-
-@Service
-public class UserServiceImpl implements IUserService {
-
-    @Autowired
-    private UserMapper userMapper;
-
-    @Override
-    public User getUser(String username, String password) {
-        return userMapper.select(username, password);
-    }
-
-    @Override
-    public boolean changePassword(int id, String password) {
-
-        User user = userMapper.selectById(id);
-        user.setPassword(password);
-        return userMapper.update(user) > 0;
-    }
-
-    @Override
-    public User getUserByUsername(String username) {
-        return userMapper.getUserByUsername(username);
-    }
-
-    @Override
-    public int addUser(User user) {
-        User userByUsername = userMapper.getUserByUsername(user.getUsername());
-        if (userByUsername != null) {
-            return 0;
-        }
-        return userMapper.add(user);
-    }
-    @Override
-    public int deleteUser(int id) {
-        return userMapper.delete(id);
-    }
-
-    @Override
-    public List<User> getAllUsers() {
-        return userMapper.selectAll();
-    }
-
-    @Override
-    public int updateUsers(User user) {
-        return userMapper.update(user);
-    }
-
-
-    @Override
-    public boolean checkPushAuthority(String callId, String sign) {
-        if (ObjectUtils.isEmpty(callId)) {
-            return userMapper.checkPushAuthorityByCallId(sign).size() > 0;
-        }else {
-            return userMapper.checkPushAuthorityByCallIdAndSign(callId, sign).size() > 0;
-        }
-    }
-
-    @Override
-    public PageInfo<User> getUsers(int page, int count) {
-        PageHelper.startPage(page, count);
-        List<User> users = userMapper.getUsers();
-        return new PageInfo<>(users);
-    }
-
-    @Override
-    public int changePushKey(int id, String pushKey) {
-        return userMapper.changePushKey(id,pushKey);
-    }
-
-    @Override
-    public String getPushKey(int id) {
-        List<User> users = userMapper.getPushKey(id);
-        if (!users.isEmpty()) {
-            return users.get(0).getPushKey();
-        }
-        return null;
-    }
-}

+ 8 - 1
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java

@@ -32,7 +32,6 @@ public interface IRedisCatchStorage {
      */
     boolean startPlay(StreamInfo stream);
 
-
     /**
      * 停止播放时删除
      *
@@ -291,5 +290,13 @@ public interface IRedisCatchStorage {
 
     boolean isBroadcastItem(String deviceId);
 
+    /**
+     * 移除设备通道所有的视频流缓存
+     *
+     * @param deviceId
+     * @param channelId
+     */
+    void deleteDeviceStream(String deviceId, String channelId);
+
 
 }

+ 56 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/AccountMapper.java

@@ -0,0 +1,56 @@
+package com.genersoft.iot.vmp.storager.dao;
+
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.storager.dao.dto.UserAccount;
+import org.apache.ibatis.annotations.*;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Mapper
+@Repository
+public interface AccountMapper {
+
+    @Insert("INSERT INTO account (name, account, password) VALUES" +
+            "(#{name}, #{account}, #{password})")
+    int register(String name, String account, String password);
+
+    @Update(value = {" <script>" +
+            "UPDATE account " +
+            "SET reginCode=#{reginCode} ," +
+            "phone=#{phone} " +
+            "WHERE id=#{id}" +
+            "</script>"})
+    int bindPhone(int id, String phone, String reginCode);
+
+
+    @Insert("INSERT INTO device_bind " +
+            "(userId, devId, devCode) VALUES" +
+            "(#{id}, #{deviceId}, #{devCode})")
+    int bindDevice(String id, String deviceId, String devCode);
+
+    @Delete("DELETE FROM device_bind WHERE userId=#{userId} AND devId=#{deviceId}")
+    int unBindDevice(String userId, String deviceId);
+
+    @Select("SELECT * FROM device_bind where devCode = #{bindCode}")
+    Device findDeviceByDevCode(String devCode);
+
+    // 查询用户设备
+    //    SELECT * FROM `device_bind` as b LEFT JOIN `device` as d ON b.deviceId = d.id where b.userId = 1
+    @Select("SELECT * FROM `device_bind` as b LEFT JOIN `device` as d ON b.devId = d.id where b.userId = #{id}")
+    List<Device> getAccountDevices(String id);
+
+    @Select("SELECT * FROM `device_bind` as b LEFT JOIN `device` as d ON b.devId = d.id where b.userId = #{userId} AND b.devId = #{devId}")
+    Device getAccountDevice(String userId, String devId);
+
+    @Select("SELECT * FROM account WHERE id = #{id}")
+    UserAccount selectById(String id);
+
+    @Select("SELECT * FROM account WHERE account = #{account}")
+    UserAccount findByAccount(String account);
+
+    @Select("SELECT * FROM account WHERE account = #{account} AND password = #{password}")
+    UserAccount login(String account, String password);
+
+
+}

+ 55 - 16
src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java → src/main/java/com/genersoft/iot/vmp/storager/dao/AdminMapper.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.storager.dao;
 
-import com.genersoft.iot.vmp.storager.dao.dto.User;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.storager.dao.dto.AdminAccount;
 import org.apache.ibatis.annotations.*;
 import org.springframework.stereotype.Repository;
 
@@ -8,11 +9,11 @@ import java.util.List;
 
 @Mapper
 @Repository
-public interface UserMapper {
+public interface AdminMapper {
 
     @Insert("INSERT INTO user (username, password, roleId, pushKey, createTime, updateTime) VALUES" +
             "(#{username}, #{password}, #{role.id}, #{pushKey}, #{createTime}, #{updateTime})")
-    int add(User user);
+    int add(AdminAccount adminAccount);
 
     @Update(value = {" <script>" +
             "UPDATE user " +
@@ -23,7 +24,7 @@ public interface UserMapper {
             "<if test=\"username != null\">, username=#{username}</if>" +
             "WHERE id=#{id}" +
             " </script>"})
-    int update(User user);
+    int update(AdminAccount adminAccount);
 
     @Delete("DELETE FROM user WHERE id != 1 and id=#{id}")
     int delete(int id);
@@ -36,33 +37,71 @@ public interface UserMapper {
             @Result(column = "roleCreateTime", property = "role.createTime"),
             @Result(column = "roleUpdateTime", property = "role.updateTime")
     })
-    User select(String username, String password);
+    AdminAccount select(String username, String password);
 
     @Select("select u.*, r.id as roleID, r.name as roleName, r.authority as roleAuthority , r.createTime as roleCreateTime , r.updateTime as roleUpdateTime FROM user u, user_role r WHERE u.roleId=r.id and u.id=#{id}")
-    @ResultMap(value="roleMap")
-    User selectById(int id);
+    @ResultMap(value = "roleMap")
+    AdminAccount selectById(int id);
+
+    @Select("select u.*, r.id as roleID, r.name as roleName, r.authority as roleAuthority , r.createTime as roleCreateTime , r.updateTime as roleUpdateTime FROM user u, user_role r WHERE u.roleId=r.id and u.id=#{id}")
+    @ResultMap(value = "roleMap")
+    AdminAccount getUserById(String id);
 
     @Select("select u.*, r.id as roleID, r.name as roleName, r.authority as roleAuthority , r.createTime as roleCreateTime , r.updateTime as roleUpdateTime FROM user u, user_role r WHERE u.roleId=r.id and u.username=#{username}")
-    @ResultMap(value="roleMap")
-    User getUserByUsername(String username);
+    @ResultMap(value = "roleMap")
+    AdminAccount getUserByUsername(String username);
 
     @Select("select u.*, r.id as roleID, r.name as roleName, r.authority as roleAuthority , r.createTime as roleCreateTime , r.updateTime as roleUpdateTime FROM user u, user_role r WHERE u.roleId=r.id")
-    @ResultMap(value="roleMap")
-    List<User> selectAll();
+    @ResultMap(value = "roleMap")
+    List<AdminAccount> selectAll();
 
     @Select("select * from (select user.*, concat(concat(#{callId}, '_'), pushKey) as str1 from user) as u where md5(u.str1) = #{sign}")
-    List<User> checkPushAuthorityByCallIdAndSign(String callId, String sign);
+    List<AdminAccount> checkPushAuthorityByCallIdAndSign(String callId, String sign);
 
     @Select("select * from user where md5(pushKey) = #{sign}")
-    List<User> checkPushAuthorityByCallId(String sign);
+    List<AdminAccount> checkPushAuthorityByCallId(String sign);
 
     @Select("select u.id, u.username,u.pushKey,u.roleId, r.id as roleID, r.name as roleName, r.authority as roleAuthority , r.createTime as roleCreateTime , r.updateTime as roleUpdateTime FROM user u join user_role r on u.roleId=r.id")
-    @ResultMap(value="roleMap")
-    List<User> getUsers();
+    @ResultMap(value = "roleMap")
+    List<AdminAccount> getUsers();
 
     @Update("update user set pushKey=#{pushKey} where id=#{id}")
     int changePushKey(int id, String pushKey);
 
     @Select("select u.id,u.pushKey from user as u where id=#{id}")
-    List<User> getPushKey(int id);
+    List<AdminAccount> getPushKey(int id);
+
+//    SELECT dev.*
+//    FROM sip_config as sip
+//    JOIN device as dev ON sip.sipDomain = dev.domain
+//    WHERE sip.adminId = 1 AND dev.id = 15;
+
+    @Select("SELECT dev.* FROM sip_config as sip JOIN device as dev ON sip.sipDomain = dev.domain WHERE sip.adminId = #{adminId} AND dev.id = #{devId}")
+    /**
+     * 获取某个管理员设备,根据设备表id
+     * @param adminId 管理员id
+     * @param devId 设备表id
+     */
+    Device getDeviceByAdminIdAndDevId(String adminId, String devId);
+
+    @Select("SELECT dev.* FROM sip_config as sip JOIN device as dev ON sip.sipDomain = dev.domain WHERE sip.adminId = #{adminId} AND dev.deviceId = #{deviceSipId}")
+    /**
+     * 获取某个管理员设备,根据设备国标id
+     * @param adminId 管理员id
+     * @param deviceSipId 设备国标id
+     */
+    Device getAdminDeviceBySipId(String adminId, String deviceSipId);
+
+    @Select("SELECT dev.* FROM sip_config as sip JOIN device as dev ON sip.sipDomain = dev.domain WHERE sip.adminId = #{adminId}")
+    /**
+     * 获取某个管理员账户下的所有设备
+     */
+    List<Device> getDevicesByAdminId(String adminId);
+
+    @Select("SELECT * FROM  device as dev  WHERE dev.domain = #{domain}")
+    /**
+     * 获取某个国标域的所有设备
+     * @param domain 国标域
+     */
+    List<Device> getDevicesByDomain(String domain);
 }

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java

@@ -403,4 +403,7 @@ public interface DeviceChannelMapper {
 
     @Select("select de.* from device de left join device_channel dc on de.deviceId = dc.deviceId where dc.channelId=#{channelId}")
     List<Device> getDeviceByChannelId(String channelId);
+
+    @Select("select * from device_channel where deviceId = #{deviceId}")
+    List<DeviceChannel> getChannelsByDeviceId(String deviceId);
 }

+ 20 - 1
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java

@@ -50,7 +50,8 @@ public interface DeviceMapper {
             "shareTime," +
             "isShare," +
             "playChannel," +
-            "autoUpdate" +
+            "autoUpdate," +
+            "domain" +
             " FROM device WHERE deviceId = #{deviceId}")
     Device getDeviceByDeviceId(String deviceId);
 
@@ -82,6 +83,7 @@ public interface DeviceMapper {
             "asMessageChannel," +
             "geoCoordSys," +
             "treeType," +
+            "domain," +
             "online" +
             ") VALUES (" +
             "#{deviceId}," +
@@ -111,6 +113,7 @@ public interface DeviceMapper {
             "#{asMessageChannel}," +
             "#{geoCoordSys}," +
             "#{treeType}," +
+            "#{domain}," +
             "#{online}" +
             ")")
     int add(Device device);
@@ -139,6 +142,7 @@ public interface DeviceMapper {
             "<if test=\"shareTime != null\">, shareTime=#{shareTime}</if>" +
             "<if test=\"isShare != null\">, isShare=#{isShare}</if>" +
             "<if test=\"autoUpdate != null\">, autoUpdate=#{autoUpdate}</if>" +
+            "<if test=\"domain != null\">, domain=#{domain}</if>" +
             "WHERE deviceId=#{deviceId}" +
             " </script>"})
     int update(Device device);
@@ -249,6 +253,7 @@ public interface DeviceMapper {
                     "shareExpires," +
                     "shareTime," +
                     "isShare," +
+                    "domain," +
                     "playChannel," +
                     "(SELECT count(0) FROM device_channel WHERE deviceId=de.deviceId) as channelCount  FROM device de" +
                     "<if test=\"online != null\"> where online=${online}</if>" +
@@ -298,6 +303,7 @@ public interface DeviceMapper {
             "shareTime," +
             "isShare," +
             "playChannel," +
+            "domain," +
             "autoUpdate" +
             " FROM device WHERE online = 1")
     List<Device> getOnlineDevices();
@@ -334,6 +340,7 @@ public interface DeviceMapper {
             "mediaServerId," +
             "audioEncodePt," +
             "playChannel," +
+            "domain," +
             "autoUpdate" +
             " FROM device WHERE ip = #{host} AND port=#{port}")
     Device getDeviceByHostAndPort(String host, int port);
@@ -364,6 +371,7 @@ public interface DeviceMapper {
             "<if test=\"shareTime != null\">, shareTime=#{shareTime}</if>" +
             "<if test=\"isShare != null\">, isShare=#{isShare}</if>" +
             "<if test=\"playChannel != null\">, playChannel=#{playChannel}</if>" +
+            "<if test=\"domain != null\">, domain=#{domain}</if>" +
             "WHERE deviceId=#{deviceId}" +
             " </script>"})
     int updateCustom(Device device);
@@ -380,6 +388,7 @@ public interface DeviceMapper {
             "asMessageChannel," +
             "geoCoordSys," +
             "treeType," +
+            "domain," +
             "online" +
             ") VALUES (" +
             "#{deviceId}," +
@@ -393,6 +402,7 @@ public interface DeviceMapper {
             "#{asMessageChannel}," +
             "#{geoCoordSys}," +
             "#{treeType}," +
+            "#{domain}," +
             "#{online}" +
             ")")
     void addCustomDevice(Device device);
@@ -409,4 +419,13 @@ public interface DeviceMapper {
     // 根据设备id查询autoUpdate字段
     @Select("select autoUpdate from device where deviceId = #{deviceId}")
     Integer getAutoUpdateByDeviceId(String deviceId);
+
+    @Select("select * from device where bindCode = #{bindCode}")
+    Device getDeviceByBindCode(String bindCode);
+
+    // 设备绑定码更新
+    @Update("UPDATE device SET bindCode=#{bindCode} WHERE deviceId=#{deviceId}")
+    int updateBindCode(String deviceId, String bindCode);
+
+
 }

+ 0 - 1
src/main/java/com/genersoft/iot/vmp/storager/dao/RoleMapper.java

@@ -1,7 +1,6 @@
 package com.genersoft.iot.vmp.storager.dao;
 
 import com.genersoft.iot.vmp.storager.dao.dto.Role;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
 import org.apache.ibatis.annotations.*;
 import org.springframework.stereotype.Repository;
 

+ 89 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/SipConfigMapper.java

@@ -0,0 +1,89 @@
+package com.genersoft.iot.vmp.storager.dao;
+// class SipUserConfig
+//private int id;
+//private int adminId;
+//private String sipDomain;
+//private String serverId;
+//private String password;
+//private String description;
+//private Boolean enable;
+//private String createTime;
+
+import com.genersoft.iot.vmp.gb28181.bean.SipUserConfig;
+import org.apache.ibatis.annotations.*;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * sip配置信息 sipConfig 表
+ */
+@Mapper
+@Repository
+public interface SipConfigMapper {
+
+    // 新增sip规则
+    @Insert("INSERT INTO sip_config (" +
+            "adminId, " +
+            "sipDomain, " +
+            "serverId, " +
+            "password," +
+            "description," +
+            "enable," +
+            "enableBind," +
+            "createTime" +
+            ") VALUES (" +
+            "#{adminId}, " +
+            "#{sipDomain}, " +
+            "#{serverId}, " +
+            "#{password}, " +
+            "#{description}, " +
+            "#{enable}, " +
+            "#{enableBind}, " +
+            "#{createTime} " +
+            ")"
+    )
+    int addSipConfig(SipUserConfig sipUserConfig);
+
+    // 禁用规则
+    @Update("UPDATE sip_config SET enable = 1 WHERE id = ${id}")
+    int enableSipConfig(SipUserConfig sipUserConfig);
+
+    // 禁用服务
+    @Update("UPDATE sip_config SET enable = 0 WHERE id = ${id}")
+    int disableSipConfig(SipUserConfig sipUserConfig);
+
+    // 更新sip规则
+    @Update("UPDATE sip_config SET " +
+            "sipDomain = #{sipUserConfig.sipDomain}, " +
+            "serverId = #{sipUserConfig.serverId}, " +
+            "password = #{sipUserConfig.password}, " +
+            "description = #{sipUserConfig.description}, " +
+            "enable = #{sipUserConfig.enable}, " +
+            "enableBind = #{sipUserConfig.enableBind} " +
+            "WHERE id = #{SipId}"
+    )
+    int updateSipConfig(@Param("sipUserConfig") SipUserConfig sipUserConfig, @Param("SipId") String SipId);
+
+
+    // 删除sip规则
+    @Delete("DELETE FROM sip_config WHERE id = ${sipId} adminId = ${adminId}")
+    int deleteSipConfig(String sipId, String adminId);
+
+    // 查询根据域获取对应的sip配置
+    @Select("SELECT * FROM sip_config WHERE sipDomain = ${sipDomain}")
+    List<SipUserConfig> querySipConfigByDomain(String sipDomain);
+
+    // 获取指定管理员创建的sip规则
+    @Select("SELECT * FROM sip_config WHERE adminId = ${adminId}")
+    List<SipUserConfig> querySipConfigsByAdminId(String adminId);
+
+    @Select("SELECT * FROM sip_config WHERE adminId = ${adminId} and id =${sipId}")
+    SipUserConfig querySipConfigsById(String adminId, String sipId);
+
+
+    // 获取所有sip规则
+    @Select("SELECT * FROM sip_config")
+    List<SipUserConfig> queryAllSipConfig();
+
+}

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/storager/dao/dto/User.java → src/main/java/com/genersoft/iot/vmp/storager/dao/dto/AdminAccount.java

@@ -1,6 +1,6 @@
 package com.genersoft.iot.vmp.storager.dao.dto;
 
-public class User {
+public class AdminAccount {
 
     private int id;
     private String username;

+ 70 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/dto/UserAccount.java

@@ -0,0 +1,70 @@
+package com.genersoft.iot.vmp.storager.dao.dto;
+
+public class UserAccount {
+    private int id;
+    private String account;
+    private String name;
+    private String password;
+    private String createTime;
+    private String phone;
+    private String reginCode;
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getAccount() {
+        return account;
+    }
+
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getReginCode() {
+        return reginCode;
+    }
+
+    public void setReginCode(String reginCode) {
+        this.reginCode = reginCode;
+    }
+
+
+
+
+}

+ 51 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/dto/deviceBind.java

@@ -0,0 +1,51 @@
+package com.genersoft.iot.vmp.storager.dao.dto;
+
+public class deviceBind {
+    private int id;
+    private int userId;
+    private int deviceId;
+    private String devCode;
+    private String createTime;
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public int getUserId() {
+        return userId;
+    }
+
+    public void setUserId(int userId) {
+        this.userId = userId;
+    }
+
+    public int getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(int deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public String getDevCode() {
+        return devCode;
+    }
+
+    public void setDevCode(String devCode) {
+        this.devCode = devCode;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+
+
+}

+ 24 - 8
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -91,9 +91,10 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
      */
     @Override
     public boolean startPlay(StreamInfo stream) {
-
-        return RedisUtil.set(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, userSetting.getServerId(),
-                        stream.getMediaServerId(), stream.getStream(), stream.getDeviceID(), stream.getChannelId()),
+        return RedisUtil.set(String.format("%S_%s_%s_%s_%s_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, userSetting.getServerId(),
+                        stream.getMediaServerId(), stream.getStream(), stream.getDeviceID(), stream.getChannelId(),
+                        stream.getSsrc(),
+                        stream.getBack()),
                 stream);
     }
 
@@ -157,14 +158,15 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
 
     @Override
     public StreamInfo queryPlayByDevice(String deviceId, String channelId) {
-        List<Object> playLeys = RedisUtil.scan(String.format("%S_%s_*_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
-                userSetting.getServerId(),
-                deviceId,
-                channelId));
+        List<Object> playLeys =
+                RedisUtil.scan(String.format("%S_%s_*_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
+                        userSetting.getServerId(),
+                        deviceId,
+                        channelId));
         if (playLeys == null || playLeys.size() == 0) {
             return null;
         }
-        return (StreamInfo)RedisUtil.get(playLeys.get(0).toString());
+        return (StreamInfo) RedisUtil.get(playLeys.get(0).toString());
     }
 
     @Override
@@ -952,5 +954,19 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         return broadcastItem != null && broadcastItem.getIpcAudioPort() != null;
     }
 
+    // 移除设备视频流信息
+    @Override
+    public void deleteDeviceStream(String deviceId, String channelId) {
+        String searchKey = String.format("%S_%s_*_*_%s_%s",
+                VideoManagerConstants.PLAYER_PREFIX,
+                userSetting.getServerId(),
+                deviceId,
+                channelId);
+        List<Object> keys = RedisUtil.scan(searchKey);
+        for (Object key : keys) {
+            RedisUtil.del((String) key);
+        }
+    }
+
 
 }

+ 27 - 22
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -635,28 +635,33 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		return libData.getVersion();
 	}
 
-	public AiLib queryItemInfoByItemId(int itemId){
-		List<AiLib> aiLibItemArr = HfyDevAiMapper.queryItemInfoByItemId(itemId);
-		if(aiLibItemArr.isEmpty()){throw new ControllerException(ErrorCode.ERROR400.getCode(),"无法找到该数据,请检查id");}
-		return aiLibItemArr.stream().findFirst().get();
-	}
-	@Override
-	public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, Boolean catalogUnderDevice, int page, int count) {
-		// 获取到所有正在播放的流
-		PageHelper.startPage(page, count);
-		List<DeviceChannel> all;
-		if (catalogUnderDevice != null && catalogUnderDevice) {
-			all = deviceChannelMapper.queryChannels(deviceId, deviceId, query, hasSubChannel, online,null);
-			// 海康设备的parentId是SIP id
-			List<DeviceChannel> deviceChannels = deviceChannelMapper.queryChannels(deviceId, sipConfig.getId(), query, hasSubChannel, online,null);
-			all.addAll(deviceChannels);
-		}else {
-			all = deviceChannelMapper.queryChannels(deviceId, null, query, hasSubChannel, online,null);
-		}
-		return new PageInfo<>(all);
-	}
-
-	@Override
+	public AiLib queryItemInfoByItemId(int itemId) {
+        List<AiLib> aiLibItemArr = HfyDevAiMapper.queryItemInfoByItemId(itemId);
+        if (aiLibItemArr.isEmpty()) {
+            throw new ControllerException(ErrorCode.ERROR400.getCode(), "无法找到该数据,请检查id");
+        }
+        return aiLibItemArr.stream().findFirst().get();
+    }
+
+    @Override
+    public PageInfo<DeviceChannel> queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, Boolean catalogUnderDevice, int page, int count) {
+        PageHelper.startPage(page, count);
+        List<DeviceChannel> allChannels;
+        // 海康设备的parentId是SIP id
+        if (catalogUnderDevice != null && catalogUnderDevice) {
+            List<DeviceChannel> channels1 = deviceChannelMapper.queryChannels(deviceId, deviceId, query, hasSubChannel, online, null);
+            List<DeviceChannel> channels2 = deviceChannelMapper.queryChannels(deviceId, sipConfig.getId(), query, hasSubChannel, online, null);
+            allChannels = new ArrayList<>(channels1);
+            allChannels.addAll(channels2);
+        } else {
+            allChannels = deviceChannelMapper.queryChannels(deviceId, null, query, hasSubChannel, online, null);
+        }
+
+        return new PageInfo<>(allChannels);
+    }
+
+
+    @Override
 	public List<DeviceChannelExtend> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, List<String> channelIds, String query, Boolean hasSubChannel, Boolean online, int start, int limit) {
 		return deviceChannelMapper.queryChannelsByDeviceIdWithStartAndLimit(deviceId, channelIds, null, query, hasSubChannel, online, start, limit);
 	}

+ 33 - 0
src/main/java/com/genersoft/iot/vmp/utils/AuthorUtil.java

@@ -0,0 +1,33 @@
+package com.genersoft.iot.vmp.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AuthorUtil {
+    private final static Logger logger = LoggerFactory.getLogger(AuthorUtil.class);
+
+    static public String getAdminId() {
+        try {
+            String adminId = StpAdminUtil.getLoginId().toString();
+            return adminId;
+        } catch (Exception e) {
+            logger.warn("[admin] 获取管理员id: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    static public String getUserId() {
+        try {
+            String userId = StpUserUtil.getLoginId().toString();
+            return userId;
+        } catch (Exception e) {
+            logger.warn("[user] 获取用户id: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 判断设备id 是否能够和 用户id 或者 管理员id 对应
+     */
+
+}

+ 60 - 0
src/main/java/com/genersoft/iot/vmp/utils/DeviceHelper.java

@@ -0,0 +1,60 @@
+package com.genersoft.iot.vmp.utils;
+
+import com.genersoft.iot.vmp.gb28181.DeviceShare;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.service.IAccountService;
+import com.genersoft.iot.vmp.service.IAdminService;
+import com.genersoft.iot.vmp.service.IDeviceService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DeviceHelper {
+    private final static Logger logger = LoggerFactory.getLogger(DeviceHelper.class);
+    @Autowired
+    private IAdminService adminService;
+
+    @Autowired
+    private IAccountService accountService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private DeviceShare deviceShare;
+
+    /**
+     * 获取设备
+     *
+     * @param deviceId
+     * @param shareCode
+     * @return
+     */
+    public Device getDevice(String deviceId, String shareCode) {
+        Device device = null;
+        String userId = AuthorUtil.getUserId();
+        String adminId = AuthorUtil.getAdminId();
+        if (adminId != null) {
+            logger.info("get admin:{} device:{}", adminId, deviceId);
+            // 管理员用户目前基本使用 国标id 进行设备操作
+            device = adminService.getAdminDeviceBySipId(adminId, deviceId);
+        } else if (userId != null) {
+            device = accountService.getAccountDevice(userId, deviceId);
+        } else if (shareCode != null) {
+            // 判断是否为分享码访问
+            device = deviceService.getDevice(deviceId);
+            if (!deviceShare.shareCheck(device, shareCode, false)) {
+                // 判断设备分享是否过期,如果过期则自动进行关闭分享
+                if (deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0) {
+                    // 设备还在分享中, 延长分享时间
+                    deviceShare.updateTime(device);
+                } else {
+                    deviceShare.closeShare(device);
+                }
+            }
+        }
+        return device;
+    }
+}

+ 487 - 0
src/main/java/com/genersoft/iot/vmp/utils/StpAdminUtil.java

@@ -0,0 +1,487 @@
+//
+// Source code recreated from a .class file by IntelliJ IDEA
+// (powered by FernFlower decompiler)
+//
+
+package com.genersoft.iot.vmp.utils;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.fun.SaFunction;
+import cn.dev33.satoken.listener.SaTokenEventCenter;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.session.TokenSign;
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.SaTokenInfo;
+import cn.dev33.satoken.stp.StpLogic;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class StpAdminUtil {
+
+    public static final String TYPE = "admin";
+
+    public static StpLogic stpLogic = new StpLogic(TYPE);
+
+    private StpAdminUtil() {
+    }
+
+    public static String getLoginType() {
+        return stpLogic.getLoginType();
+    }
+
+    public static void setStpLogic(StpLogic newStpLogic) {
+        stpLogic = newStpLogic;
+        SaManager.putStpLogic(newStpLogic);
+        SaTokenEventCenter.doSetStpLogic(stpLogic);
+    }
+
+    public static StpLogic getStpLogic() {
+        return stpLogic;
+    }
+
+    public static String getTokenName() {
+        return stpLogic.getTokenName();
+    }
+
+    public static void setTokenValue(String tokenValue) {
+        stpLogic.setTokenValue(tokenValue);
+    }
+
+    public static void setTokenValue(String tokenValue, int cookieTimeout) {
+        stpLogic.setTokenValue(tokenValue, cookieTimeout);
+    }
+
+    public static void setTokenValue(String tokenValue, SaLoginModel loginModel) {
+        stpLogic.setTokenValue(tokenValue, loginModel);
+    }
+
+    public static String getTokenValue() {
+        return stpLogic.getTokenValue();
+    }
+
+    public static String getTokenValueNotCut() {
+        return stpLogic.getTokenValueNotCut();
+    }
+
+    public static SaTokenInfo getTokenInfo() {
+        return stpLogic.getTokenInfo();
+    }
+
+    public static void login(Object id) {
+        stpLogic.login(id);
+    }
+
+    public static void login(Object id, String device) {
+        stpLogic.login(id, device);
+    }
+
+    public static void login(Object id, boolean isLastingCookie) {
+        stpLogic.login(id, isLastingCookie);
+    }
+
+    public static void login(Object id, long timeout) {
+        stpLogic.login(id, timeout);
+    }
+
+    public static void login(Object id, SaLoginModel loginModel) {
+        stpLogic.login(id, loginModel);
+    }
+
+    public static String createLoginSession(Object id) {
+        return stpLogic.createLoginSession(id);
+    }
+
+    public static String createLoginSession(Object id, SaLoginModel loginModel) {
+        return stpLogic.createLoginSession(id, loginModel);
+    }
+
+    public static void logout() {
+        stpLogic.logout();
+    }
+
+    public static void logout(Object loginId) {
+        stpLogic.logout(loginId);
+    }
+
+    public static void logout(Object loginId, String device) {
+        stpLogic.logout(loginId, device);
+    }
+
+    public static void logoutByTokenValue(String tokenValue) {
+        stpLogic.logoutByTokenValue(tokenValue);
+    }
+
+    public static void kickout(Object loginId) {
+        stpLogic.kickout(loginId);
+    }
+
+    public static void kickout(Object loginId, String device) {
+        stpLogic.kickout(loginId, device);
+    }
+
+    public static void kickoutByTokenValue(String tokenValue) {
+        stpLogic.kickoutByTokenValue(tokenValue);
+    }
+
+    public static void replaced(Object loginId, String device) {
+        stpLogic.replaced(loginId, device);
+    }
+
+    public static boolean isLogin() {
+        return stpLogic.isLogin();
+    }
+
+    public static boolean isLogin(Object loginId) {
+        return stpLogic.isLogin(loginId);
+    }
+
+    public static void checkLogin() {
+        stpLogic.checkLogin();
+    }
+
+    public static Object getLoginId() {
+        return stpLogic.getLoginId();
+    }
+
+    public static <T> T getLoginId(T defaultValue) {
+        return stpLogic.getLoginId(defaultValue);
+    }
+
+    public static Object getLoginIdDefaultNull() {
+        return stpLogic.getLoginIdDefaultNull();
+    }
+
+    public static String getLoginIdAsString() {
+        return stpLogic.getLoginIdAsString();
+    }
+
+    public static int getLoginIdAsInt() {
+        return stpLogic.getLoginIdAsInt();
+    }
+
+    public static long getLoginIdAsLong() {
+        return stpLogic.getLoginIdAsLong();
+    }
+
+    public static Object getLoginIdByToken(String tokenValue) {
+        return stpLogic.getLoginIdByToken(tokenValue);
+    }
+
+    public static Object getExtra(String key) {
+        return stpLogic.getExtra(key);
+    }
+
+    public static Object getExtra(String tokenValue, String key) {
+        return stpLogic.getExtra(tokenValue, key);
+    }
+
+    public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
+        return stpLogic.getSessionByLoginId(loginId, isCreate);
+    }
+
+    public static SaSession getSessionBySessionId(String sessionId) {
+        return stpLogic.getSessionBySessionId(sessionId);
+    }
+
+    public static SaSession getSessionByLoginId(Object loginId) {
+        return stpLogic.getSessionByLoginId(loginId);
+    }
+
+    public static SaSession getSession(boolean isCreate) {
+        return stpLogic.getSession(isCreate);
+    }
+
+    public static SaSession getSession() {
+        return stpLogic.getSession();
+    }
+
+    public static SaSession getTokenSessionByToken(String tokenValue) {
+        return stpLogic.getTokenSessionByToken(tokenValue);
+    }
+
+    public static SaSession getTokenSession() {
+        return stpLogic.getTokenSession();
+    }
+
+    public static SaSession getAnonTokenSession() {
+        return stpLogic.getAnonTokenSession();
+    }
+
+    public static void updateLastActiveToNow() {
+        stpLogic.updateLastActiveToNow();
+    }
+
+    public static void checkActiveTimeout() {
+        stpLogic.checkActiveTimeout();
+    }
+
+    public static long getTokenTimeout() {
+        return stpLogic.getTokenTimeout();
+    }
+
+    public static long getTokenTimeout(String token) {
+        return stpLogic.getTokenTimeout(token);
+    }
+
+    public static long getSessionTimeout() {
+        return stpLogic.getSessionTimeout();
+    }
+
+    public static long getTokenSessionTimeout() {
+        return stpLogic.getTokenSessionTimeout();
+    }
+
+    public static long getTokenActiveTimeout() {
+        return stpLogic.getTokenActiveTimeout();
+    }
+
+    public static void renewTimeout(long timeout) {
+        stpLogic.renewTimeout(timeout);
+    }
+
+    public static void renewTimeout(String tokenValue, long timeout) {
+        stpLogic.renewTimeout(tokenValue, timeout);
+    }
+
+    public static List<String> getRoleList() {
+        return stpLogic.getRoleList();
+    }
+
+    public static List<String> getRoleList(Object loginId) {
+        return stpLogic.getRoleList(loginId);
+    }
+
+    public static boolean hasRole(String role) {
+        return stpLogic.hasRole(role);
+    }
+
+    public static boolean hasRole(Object loginId, String role) {
+        return stpLogic.hasRole(loginId, role);
+    }
+
+    public static boolean hasRoleAnd(String... roleArray) {
+        return stpLogic.hasRoleAnd(roleArray);
+    }
+
+    public static boolean hasRoleOr(String... roleArray) {
+        return stpLogic.hasRoleOr(roleArray);
+    }
+
+    public static void checkRole(String role) {
+        stpLogic.checkRole(role);
+    }
+
+    public static void checkRoleAnd(String... roleArray) {
+        stpLogic.checkRoleAnd(roleArray);
+    }
+
+    public static void checkRoleOr(String... roleArray) {
+        stpLogic.checkRoleOr(roleArray);
+    }
+
+    public static List<String> getPermissionList() {
+        return stpLogic.getPermissionList();
+    }
+
+    public static List<String> getPermissionList(Object loginId) {
+        return stpLogic.getPermissionList(loginId);
+    }
+
+    public static boolean hasPermission(String permission) {
+        return stpLogic.hasPermission(permission);
+    }
+
+    public static boolean hasPermission(Object loginId, String permission) {
+        return stpLogic.hasPermission(loginId, permission);
+    }
+
+    public static boolean hasPermissionAnd(String... permissionArray) {
+        return stpLogic.hasPermissionAnd(permissionArray);
+    }
+
+    public static boolean hasPermissionOr(String... permissionArray) {
+        return stpLogic.hasPermissionOr(permissionArray);
+    }
+
+    public static void checkPermission(String permission) {
+        stpLogic.checkPermission(permission);
+    }
+
+    public static void checkPermissionAnd(String... permissionArray) {
+        stpLogic.checkPermissionAnd(permissionArray);
+    }
+
+    public static void checkPermissionOr(String... permissionArray) {
+        stpLogic.checkPermissionOr(permissionArray);
+    }
+
+    public static String getTokenValueByLoginId(Object loginId) {
+        return stpLogic.getTokenValueByLoginId(loginId);
+    }
+
+    public static String getTokenValueByLoginId(Object loginId, String device) {
+        return stpLogic.getTokenValueByLoginId(loginId, device);
+    }
+
+    public static List<String> getTokenValueListByLoginId(Object loginId) {
+        return stpLogic.getTokenValueListByLoginId(loginId);
+    }
+
+    public static List<String> getTokenValueListByLoginId(Object loginId, String device) {
+        return stpLogic.getTokenValueListByLoginId(loginId, device);
+    }
+
+    public static List<TokenSign> getTokenSignListByLoginId(Object loginId, String device) {
+        return stpLogic.getTokenSignListByLoginId(loginId, device);
+    }
+
+    public static String getLoginDevice() {
+        return stpLogic.getLoginDevice();
+    }
+
+    public static List<String> searchTokenValue(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchTokenValue(keyword, start, size, sortType);
+    }
+
+    public static List<String> searchSessionId(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchSessionId(keyword, start, size, sortType);
+    }
+
+    public static List<String> searchTokenSessionId(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchTokenSessionId(keyword, start, size, sortType);
+    }
+
+    public static void disable(Object loginId, long time) {
+        stpLogic.disable(loginId, time);
+    }
+
+    public static boolean isDisable(Object loginId) {
+        return stpLogic.isDisable(loginId);
+    }
+
+    public static void checkDisable(Object loginId) {
+        stpLogic.checkDisable(loginId);
+    }
+
+    public static long getDisableTime(Object loginId) {
+        return stpLogic.getDisableTime(loginId);
+    }
+
+    public static void untieDisable(Object loginId) {
+        stpLogic.untieDisable(loginId);
+    }
+
+    public static void disable(Object loginId, String service, long time) {
+        stpLogic.disable(loginId, service, time);
+    }
+
+    public static boolean isDisable(Object loginId, String service) {
+        return stpLogic.isDisable(loginId, service);
+    }
+
+    public static void checkDisable(Object loginId, String... services) {
+        stpLogic.checkDisable(loginId, services);
+    }
+
+    public static long getDisableTime(Object loginId, String service) {
+        return stpLogic.getDisableTime(loginId, service);
+    }
+
+    public static void untieDisable(Object loginId, String... services) {
+        stpLogic.untieDisable(loginId, services);
+    }
+
+    public static void disableLevel(Object loginId, int level, long time) {
+        stpLogic.disableLevel(loginId, level, time);
+    }
+
+    public static void disableLevel(Object loginId, String service, int level, long time) {
+        stpLogic.disableLevel(loginId, service, level, time);
+    }
+
+    public static boolean isDisableLevel(Object loginId, int level) {
+        return stpLogic.isDisableLevel(loginId, level);
+    }
+
+    public static boolean isDisableLevel(Object loginId, String service, int level) {
+        return stpLogic.isDisableLevel(loginId, service, level);
+    }
+
+    public static void checkDisableLevel(Object loginId, int level) {
+        stpLogic.checkDisableLevel(loginId, level);
+    }
+
+    public static void checkDisableLevel(Object loginId, String service, int level) {
+        stpLogic.checkDisableLevel(loginId, service, level);
+    }
+
+    public static int getDisableLevel(Object loginId) {
+        return stpLogic.getDisableLevel(loginId);
+    }
+
+    public static int getDisableLevel(Object loginId, String service) {
+        return stpLogic.getDisableLevel(loginId, service);
+    }
+
+    public static void switchTo(Object loginId) {
+        stpLogic.switchTo(loginId);
+    }
+
+    public static void endSwitch() {
+        stpLogic.endSwitch();
+    }
+
+    public static boolean isSwitch() {
+        return stpLogic.isSwitch();
+    }
+
+    public static void switchTo(Object loginId, SaFunction function) {
+        stpLogic.switchTo(loginId, function);
+    }
+
+    public static void openSafe(long safeTime) {
+        stpLogic.openSafe(safeTime);
+    }
+
+    public static void openSafe(String service, long safeTime) {
+        stpLogic.openSafe(service, safeTime);
+    }
+
+    public static boolean isSafe() {
+        return stpLogic.isSafe();
+    }
+
+    public static boolean isSafe(String service) {
+        return stpLogic.isSafe(service);
+    }
+
+    public static boolean isSafe(String tokenValue, String service) {
+        return stpLogic.isSafe(tokenValue, service);
+    }
+
+    public static void checkSafe() {
+        stpLogic.checkSafe();
+    }
+
+    public static void checkSafe(String service) {
+        stpLogic.checkSafe(service);
+    }
+
+    public static long getSafeTime() {
+        return stpLogic.getSafeTime();
+    }
+
+    public static long getSafeTime(String service) {
+        return stpLogic.getSafeTime(service);
+    }
+
+    public static void closeSafe() {
+        stpLogic.closeSafe();
+    }
+
+    public static void closeSafe(String service) {
+        stpLogic.closeSafe(service);
+    }
+}

+ 496 - 0
src/main/java/com/genersoft/iot/vmp/utils/StpUserUtil.java

@@ -0,0 +1,496 @@
+//
+// Source code recreated from a .class file by IntelliJ IDEA
+// (powered by FernFlower decompiler)
+//
+
+package com.genersoft.iot.vmp.utils;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.fun.SaFunction;
+import cn.dev33.satoken.listener.SaTokenEventCenter;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.session.TokenSign;
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.SaTokenInfo;
+import cn.dev33.satoken.stp.StpLogic;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class StpUserUtil {
+
+    public static final String TYPE = "user";
+
+    // 使用匿名子类 重写`stpLogic对象`的一些方法
+    public static StpLogic stpLogic = new StpLogic(TYPE) {
+        // 重写 StpLogic 类下的 `splicingKeyTokenName` 函数,返回一个与 `StpUtil` 不同的token名称, 防止冲突
+        @Override
+        public String splicingKeyTokenName() {
+            return super.splicingKeyTokenName() + "-user";
+        }
+        // 同理你可以按需重写一些其它方法 ...
+    };
+
+    private StpUserUtil() {
+    }
+
+
+    public static String getLoginType() {
+        return stpLogic.getLoginType();
+    }
+
+    public static void setStpLogic(StpLogic newStpLogic) {
+        stpLogic = newStpLogic;
+        SaManager.putStpLogic(newStpLogic);
+        SaTokenEventCenter.doSetStpLogic(stpLogic);
+    }
+
+    public static StpLogic getStpLogic() {
+        return stpLogic;
+    }
+
+    public static String getTokenName() {
+        return stpLogic.getTokenName();
+    }
+
+    public static void setTokenValue(String tokenValue) {
+        stpLogic.setTokenValue(tokenValue);
+    }
+
+    public static void setTokenValue(String tokenValue, int cookieTimeout) {
+        stpLogic.setTokenValue(tokenValue, cookieTimeout);
+    }
+
+    public static void setTokenValue(String tokenValue, SaLoginModel loginModel) {
+        stpLogic.setTokenValue(tokenValue, loginModel);
+    }
+
+    public static String getTokenValue() {
+        return stpLogic.getTokenValue();
+    }
+
+    public static String getTokenValueNotCut() {
+        return stpLogic.getTokenValueNotCut();
+    }
+
+    public static SaTokenInfo getTokenInfo() {
+        return stpLogic.getTokenInfo();
+    }
+
+    public static void login(Object id) {
+        stpLogic.login(id);
+    }
+
+    public static void login(Object id, String device) {
+        stpLogic.login(id, device);
+    }
+
+    public static void login(Object id, boolean isLastingCookie) {
+        stpLogic.login(id, isLastingCookie);
+    }
+
+    public static void login(Object id, long timeout) {
+        stpLogic.login(id, timeout);
+    }
+
+    public static void login(Object id, SaLoginModel loginModel) {
+        stpLogic.login(id, loginModel);
+    }
+
+    public static String createLoginSession(Object id) {
+        return stpLogic.createLoginSession(id);
+    }
+
+    public static String createLoginSession(Object id, SaLoginModel loginModel) {
+        return stpLogic.createLoginSession(id, loginModel);
+    }
+
+    public static void logout() {
+        stpLogic.logout();
+    }
+
+    public static void logout(Object loginId) {
+        stpLogic.logout(loginId);
+    }
+
+    public static void logout(Object loginId, String device) {
+        stpLogic.logout(loginId, device);
+    }
+
+    public static void logoutByTokenValue(String tokenValue) {
+        stpLogic.logoutByTokenValue(tokenValue);
+    }
+
+    public static void kickout(Object loginId) {
+        stpLogic.kickout(loginId);
+    }
+
+    public static void kickout(Object loginId, String device) {
+        stpLogic.kickout(loginId, device);
+    }
+
+    public static void kickoutByTokenValue(String tokenValue) {
+        stpLogic.kickoutByTokenValue(tokenValue);
+    }
+
+    public static void replaced(Object loginId, String device) {
+        stpLogic.replaced(loginId, device);
+    }
+
+    public static boolean isLogin() {
+        return stpLogic.isLogin();
+    }
+
+    public static boolean isLogin(Object loginId) {
+        return stpLogic.isLogin(loginId);
+    }
+
+    public static void checkLogin() {
+        stpLogic.checkLogin();
+    }
+
+    public static Object getLoginId() {
+        return stpLogic.getLoginId();
+    }
+
+    public static <T> T getLoginId(T defaultValue) {
+        return stpLogic.getLoginId(defaultValue);
+    }
+
+    public static Object getLoginIdDefaultNull() {
+        return stpLogic.getLoginIdDefaultNull();
+    }
+
+    public static String getLoginIdAsString() {
+        return stpLogic.getLoginIdAsString();
+    }
+
+    public static int getLoginIdAsInt() {
+        return stpLogic.getLoginIdAsInt();
+    }
+
+    public static long getLoginIdAsLong() {
+        return stpLogic.getLoginIdAsLong();
+    }
+
+    public static Object getLoginIdByToken(String tokenValue) {
+        return stpLogic.getLoginIdByToken(tokenValue);
+    }
+
+    public static Object getExtra(String key) {
+        return stpLogic.getExtra(key);
+    }
+
+    public static Object getExtra(String tokenValue, String key) {
+        return stpLogic.getExtra(tokenValue, key);
+    }
+
+    public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
+        return stpLogic.getSessionByLoginId(loginId, isCreate);
+    }
+
+    public static SaSession getSessionBySessionId(String sessionId) {
+        return stpLogic.getSessionBySessionId(sessionId);
+    }
+
+    public static SaSession getSessionByLoginId(Object loginId) {
+        return stpLogic.getSessionByLoginId(loginId);
+    }
+
+    public static SaSession getSession(boolean isCreate) {
+        return stpLogic.getSession(isCreate);
+    }
+
+    public static SaSession getSession() {
+        return stpLogic.getSession();
+    }
+
+    public static SaSession getTokenSessionByToken(String tokenValue) {
+        return stpLogic.getTokenSessionByToken(tokenValue);
+    }
+
+    public static SaSession getTokenSession() {
+        return stpLogic.getTokenSession();
+    }
+
+    public static SaSession getAnonTokenSession() {
+        return stpLogic.getAnonTokenSession();
+    }
+
+    public static void updateLastActiveToNow() {
+        stpLogic.updateLastActiveToNow();
+    }
+
+    public static void checkActiveTimeout() {
+        stpLogic.checkActiveTimeout();
+    }
+
+    public static long getTokenTimeout() {
+        return stpLogic.getTokenTimeout();
+    }
+
+    public static long getTokenTimeout(String token) {
+        return stpLogic.getTokenTimeout(token);
+    }
+
+    public static long getSessionTimeout() {
+        return stpLogic.getSessionTimeout();
+    }
+
+    public static long getTokenSessionTimeout() {
+        return stpLogic.getTokenSessionTimeout();
+    }
+
+    public static long getTokenActiveTimeout() {
+        return stpLogic.getTokenActiveTimeout();
+    }
+
+    public static void renewTimeout(long timeout) {
+        stpLogic.renewTimeout(timeout);
+    }
+
+    public static void renewTimeout(String tokenValue, long timeout) {
+        stpLogic.renewTimeout(tokenValue, timeout);
+    }
+
+    public static List<String> getRoleList() {
+        return stpLogic.getRoleList();
+    }
+
+    public static List<String> getRoleList(Object loginId) {
+        return stpLogic.getRoleList(loginId);
+    }
+
+    public static boolean hasRole(String role) {
+        return stpLogic.hasRole(role);
+    }
+
+    public static boolean hasRole(Object loginId, String role) {
+        return stpLogic.hasRole(loginId, role);
+    }
+
+    public static boolean hasRoleAnd(String... roleArray) {
+        return stpLogic.hasRoleAnd(roleArray);
+    }
+
+    public static boolean hasRoleOr(String... roleArray) {
+        return stpLogic.hasRoleOr(roleArray);
+    }
+
+    public static void checkRole(String role) {
+        stpLogic.checkRole(role);
+    }
+
+    public static void checkRoleAnd(String... roleArray) {
+        stpLogic.checkRoleAnd(roleArray);
+    }
+
+    public static void checkRoleOr(String... roleArray) {
+        stpLogic.checkRoleOr(roleArray);
+    }
+
+    public static List<String> getPermissionList() {
+        return stpLogic.getPermissionList();
+    }
+
+    public static List<String> getPermissionList(Object loginId) {
+        return stpLogic.getPermissionList(loginId);
+    }
+
+    public static boolean hasPermission(String permission) {
+        return stpLogic.hasPermission(permission);
+    }
+
+    public static boolean hasPermission(Object loginId, String permission) {
+        return stpLogic.hasPermission(loginId, permission);
+    }
+
+    public static boolean hasPermissionAnd(String... permissionArray) {
+        return stpLogic.hasPermissionAnd(permissionArray);
+    }
+
+    public static boolean hasPermissionOr(String... permissionArray) {
+        return stpLogic.hasPermissionOr(permissionArray);
+    }
+
+    public static void checkPermission(String permission) {
+        stpLogic.checkPermission(permission);
+    }
+
+    public static void checkPermissionAnd(String... permissionArray) {
+        stpLogic.checkPermissionAnd(permissionArray);
+    }
+
+    public static void checkPermissionOr(String... permissionArray) {
+        stpLogic.checkPermissionOr(permissionArray);
+    }
+
+    public static String getTokenValueByLoginId(Object loginId) {
+        return stpLogic.getTokenValueByLoginId(loginId);
+    }
+
+    public static String getTokenValueByLoginId(Object loginId, String device) {
+        return stpLogic.getTokenValueByLoginId(loginId, device);
+    }
+
+    public static List<String> getTokenValueListByLoginId(Object loginId) {
+        return stpLogic.getTokenValueListByLoginId(loginId);
+    }
+
+    public static List<String> getTokenValueListByLoginId(Object loginId, String device) {
+        return stpLogic.getTokenValueListByLoginId(loginId, device);
+    }
+
+    public static List<TokenSign> getTokenSignListByLoginId(Object loginId, String device) {
+        return stpLogic.getTokenSignListByLoginId(loginId, device);
+    }
+
+    public static String getLoginDevice() {
+        return stpLogic.getLoginDevice();
+    }
+
+    public static List<String> searchTokenValue(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchTokenValue(keyword, start, size, sortType);
+    }
+
+    public static List<String> searchSessionId(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchSessionId(keyword, start, size, sortType);
+    }
+
+    public static List<String> searchTokenSessionId(String keyword, int start, int size, boolean sortType) {
+        return stpLogic.searchTokenSessionId(keyword, start, size, sortType);
+    }
+
+    public static void disable(Object loginId, long time) {
+        stpLogic.disable(loginId, time);
+    }
+
+    public static boolean isDisable(Object loginId) {
+        return stpLogic.isDisable(loginId);
+    }
+
+    public static void checkDisable(Object loginId) {
+        stpLogic.checkDisable(loginId);
+    }
+
+    public static long getDisableTime(Object loginId) {
+        return stpLogic.getDisableTime(loginId);
+    }
+
+    public static void untieDisable(Object loginId) {
+        stpLogic.untieDisable(loginId);
+    }
+
+    public static void disable(Object loginId, String service, long time) {
+        stpLogic.disable(loginId, service, time);
+    }
+
+    public static boolean isDisable(Object loginId, String service) {
+        return stpLogic.isDisable(loginId, service);
+    }
+
+    public static void checkDisable(Object loginId, String... services) {
+        stpLogic.checkDisable(loginId, services);
+    }
+
+    public static long getDisableTime(Object loginId, String service) {
+        return stpLogic.getDisableTime(loginId, service);
+    }
+
+    public static void untieDisable(Object loginId, String... services) {
+        stpLogic.untieDisable(loginId, services);
+    }
+
+    public static void disableLevel(Object loginId, int level, long time) {
+        stpLogic.disableLevel(loginId, level, time);
+    }
+
+    public static void disableLevel(Object loginId, String service, int level, long time) {
+        stpLogic.disableLevel(loginId, service, level, time);
+    }
+
+    public static boolean isDisableLevel(Object loginId, int level) {
+        return stpLogic.isDisableLevel(loginId, level);
+    }
+
+    public static boolean isDisableLevel(Object loginId, String service, int level) {
+        return stpLogic.isDisableLevel(loginId, service, level);
+    }
+
+    public static void checkDisableLevel(Object loginId, int level) {
+        stpLogic.checkDisableLevel(loginId, level);
+    }
+
+    public static void checkDisableLevel(Object loginId, String service, int level) {
+        stpLogic.checkDisableLevel(loginId, service, level);
+    }
+
+    public static int getDisableLevel(Object loginId) {
+        return stpLogic.getDisableLevel(loginId);
+    }
+
+    public static int getDisableLevel(Object loginId, String service) {
+        return stpLogic.getDisableLevel(loginId, service);
+    }
+
+    public static void switchTo(Object loginId) {
+        stpLogic.switchTo(loginId);
+    }
+
+    public static void endSwitch() {
+        stpLogic.endSwitch();
+    }
+
+    public static boolean isSwitch() {
+        return stpLogic.isSwitch();
+    }
+
+    public static void switchTo(Object loginId, SaFunction function) {
+        stpLogic.switchTo(loginId, function);
+    }
+
+    public static void openSafe(long safeTime) {
+        stpLogic.openSafe(safeTime);
+    }
+
+    public static void openSafe(String service, long safeTime) {
+        stpLogic.openSafe(service, safeTime);
+    }
+
+    public static boolean isSafe() {
+        return stpLogic.isSafe();
+    }
+
+    public static boolean isSafe(String service) {
+        return stpLogic.isSafe(service);
+    }
+
+    public static boolean isSafe(String tokenValue, String service) {
+        return stpLogic.isSafe(tokenValue, service);
+    }
+
+    public static void checkSafe() {
+        stpLogic.checkSafe();
+    }
+
+    public static void checkSafe(String service) {
+        stpLogic.checkSafe(service);
+    }
+
+    public static long getSafeTime() {
+        return stpLogic.getSafeTime();
+    }
+
+    public static long getSafeTime(String service) {
+        return stpLogic.getSafeTime(service);
+    }
+
+    public static void closeSafe() {
+        stpLogic.closeSafe();
+    }
+
+    public static void closeSafe(String service) {
+        stpLogic.closeSafe(service);
+    }
+}

+ 12 - 10
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java

@@ -868,16 +868,18 @@ public class RedisUtil {
         if (redisTemplate == null) {
             redisTemplate = SpringBeanFactory.getBean("redisTemplate");
         }
-        Set<String> resultKeys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
-            ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + query + "*").count(1000).build();
-            Cursor<byte[]> scan = connection.scan(scanOptions);
-            Set<String> keys = new HashSet<>();
-            while (scan.hasNext()) {
-                byte[] next = scan.next();
-                keys.add(new String(next));
-            }
-            return keys;
-        });
+        Set<String> resultKeys = (Set<String>) redisTemplate.execute(
+                (RedisCallback<Set<String>>) connection ->
+                {
+                    ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + query + "*").count(1000).build();
+                    Cursor<byte[]> scan = connection.scan(scanOptions);
+                    Set<String> keys = new HashSet<>();
+                    while (scan.hasNext()) {
+                        byte[] next = scan.next();
+                        keys.add(new String(next));
+                    }
+                    return keys;
+                });
 
         return new ArrayList<>(resultKeys);
     }

+ 102 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/account/AccountController.java

@@ -0,0 +1,102 @@
+package com.genersoft.iot.vmp.vmanager.account;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.dev33.satoken.stp.SaTokenInfo;
+import com.genersoft.iot.vmp.conf.security.saToken.SaUserCheckRole;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.service.IAccountService;
+import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.storager.dao.dto.UserAccount;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Tag(name = "普通用户管理")
+@CrossOrigin(origins = "*")
+@RestController
+@RequestMapping("/account")
+@SaUserCheckRole("user")
+public class AccountController {
+    private Logger logger = LoggerFactory.getLogger(AccountController.class);
+
+    @Autowired
+    private IAccountService accountService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @PostMapping("/register")
+    @SaIgnore
+    @Operation(summary = "注册用户")
+    @Parameter(name = "name", description = "用户名", required = true)
+    @Parameter(name = "account", description = "登陆账号", required = true)
+    @Parameter(name = "password", description = "密码", required = true)
+    public WVPResult<String> register(HttpServletRequest request,
+                                      @Parameter String name,
+                                      @Parameter String account,
+                                      @Parameter String password) {
+        logger.info("[注册账户] name:{} account:{} ",
+                name, account);
+        if (name == null || account == null || password == null) {
+            return WVPResult.fail(
+                    ErrorCode.ERROR400,
+                    "缺少参数");
+        }
+        if (accountService.checkAccount(account) != null) {
+            return WVPResult.fail(
+                    ErrorCode.ERROR100,
+                    "账号已存在");
+        }
+        if (!accountService.registerAccount(name, account, password)) {
+            return WVPResult.fail(
+                    ErrorCode.ERROR100,
+                    "注册失败");
+        }
+        logger.info("[注册账户] 用户: {} 注册:{}成功", name, account);
+        return WVPResult.success();
+    }
+
+    @PostMapping("/login")
+    @SaIgnore
+    @Operation(summary = "登录")
+    @Parameter(name = "account", description = "登陆账号", required = true)
+    @Parameter(name = "password", description = "密码", required = true)
+    public WVPResult<SaTokenInfo> login(@RequestParam String account,
+                                        @RequestParam String password) {
+        logger.info("[登录账号] account {}", account);
+        UserAccount userAccount = accountService.login(account, password);
+        if (userAccount == null) {
+            return WVPResult.fail(
+                    ErrorCode.ERROR404,
+                    "账号或者密码错误");
+        }
+        StpUserUtil.login(userAccount.getId());
+
+        return WVPResult.success(StpUserUtil.getTokenInfo());
+    }
+
+    @GetMapping("/load/bind")
+    @Operation(summary = "获取绑定码对应的设备")
+    @Parameter(name = "bindCode", description = "绑定码", required = true)
+    public WVPResult<Device> getBindDevice(@RequestParam String devCode) {
+        Device device = deviceService.getBindDevice(devCode);
+        if (device == null) {
+            return WVPResult.fail(ErrorCode.ERROR404, "无法找到对应设备");
+        }
+        return WVPResult.success(device);
+    }
+
+
+
+
+
+}

+ 160 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/account/AccountDeviceControl.java

@@ -0,0 +1,160 @@
+package com.genersoft.iot.vmp.vmanager.account;
+
+import com.genersoft.iot.vmp.conf.security.saToken.SaUserCheckRole;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.service.IAccountService;
+import com.genersoft.iot.vmp.service.IDeviceChannelService;
+import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import com.github.pagehelper.PageInfo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.commons.compress.utils.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.List;
+
+@Tag(name = "普通用户设备管理")
+@CrossOrigin(origins = "*")
+@RestController
+@RequestMapping("/account/device")
+@SaUserCheckRole("user")
+public class AccountDeviceControl {
+    private Logger logger = LoggerFactory.getLogger(AccountDeviceControl.class);
+
+    @Autowired
+    private IAccountService accountService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private IDeviceChannelService deviceChannelService;
+
+    @GetMapping("/bind")
+    @Operation(summary = "绑定设备")
+    @Parameter(name = "bindCode", description = "设备码", required = true)
+    public WVPResult<String> bindDevice(@RequestParam String bindCode) {
+        logger.info("[绑定设备] bind account {}", bindCode);
+        String userId = StpUserUtil.getLoginId().toString();
+        Device device = deviceService.getBindDevice(bindCode);
+        if (device == null) {
+            return WVPResult.fail(ErrorCode.ERROR404, "无法找到对应设备");
+        }
+        // 判断设备是否已经绑定
+        if (!accountService.checkIsAllowBind(bindCode)) {
+            return WVPResult.fail(
+                    ErrorCode.ERROR403,
+                    "设备已经绑定, 无法重新绑定");
+        }
+        if (accountService.bindDevice(
+                userId,
+                device.getId(),
+                bindCode) < 1) {
+            logger.warn("[绑定设备] 绑定失败");
+            return WVPResult.fail(ErrorCode.ERROR500, "绑定失败");
+        }
+        ;
+
+        return WVPResult.success();
+    }
+
+    @GetMapping("/unbind")
+    @Operation(summary = "解绑设备")
+    @Parameter(name = "deviceId", description = "设备id", required = true)
+    public WVPResult<String> unbindDevice(
+            @RequestParam String deviceId) {
+        String userId = StpUserUtil.getLoginId().toString();
+        accountService.unBindDevice(userId, deviceId);
+        return WVPResult.success("ok");
+    }
+
+    // 获取当前登录的用户设备
+    @GetMapping("/search")
+    @Operation(summary = "获取设备列表")
+    @Parameter(name = "p", description = "当前页", required = true)
+    @Parameter(name = "l", description = "每页查询数量", required = true)
+    public WVPResult<List<Device>> getDevices(
+            @RequestParam(value = "p", required = false, defaultValue = "1") int page,
+            @RequestParam(value = "l", required = false, defaultValue = "20") int limit) {
+        String userId = StpUserUtil.getLoginId().toString();
+        PageInfo<Device> pageResult = accountService.getAccountDevices(
+                userId,
+                page,
+                limit);
+        return WVPResult.success(
+                pageResult.getList(),
+                page,
+                pageResult.getPageSize(),
+                pageResult.getTotal());
+    }
+
+    // 获取设备通道列表
+    @GetMapping("/channels")
+    @Operation(summary = "获取设备通道列表")
+    @Parameter(name = "deviceId", description = "设备id", required = true)
+    public WVPResult<List<DeviceChannel>> getChannels(
+            @RequestParam(value = "deviceId", required = true) String deviceId) {
+        String userId = StpUserUtil.getLoginId().toString();
+        // 判断当前用户是否有该设备
+        Device device = accountService.getAccountDevice(userId, deviceId);
+        if (device == null) {
+            return WVPResult.fail(ErrorCode.ERROR403, "当前用户无权限访问此设备");
+        }
+        logger.info(" 用户{} 获取设备{}:{} 通道", userId, deviceId, device.getDeviceId());
+        List<DeviceChannel> channels = deviceChannelService.getChannelListByDeviceId(device.getDeviceId());
+        return WVPResult.success(channels);
+    }
+
+    @GetMapping("/info")
+    @Operation(summary = "获取设备信息")
+    @Parameter(name = "deviceId", description = "设备id", required = true)
+    public WVPResult<Device> getDeviceInfo(
+            @RequestParam(value = "deviceId", required = true) String deviceId) {
+        logger.info("[web api] /device/info 获取设备信息");
+        String userId = StpUserUtil.getLoginId().toString();
+        // 判断当前用户是否有该设备
+        Device device = accountService.getAccountDevice(userId, deviceId);
+        if (device == null) {
+            return WVPResult.fail(ErrorCode.ERROR403, "当前用户无权限访问此设备");
+        }
+        return WVPResult.success(device);
+    }
+
+
+    // 获取通道截图
+    @GetMapping("/snap")
+    @Operation(summary = "请求截图")
+    @Parameter(name = "deviceId", description = "设备国标编号", required = true)
+    @Parameter(name = "channelId", description = "通道国标编号", required = true)
+    public void getSnap(HttpServletResponse resp, @RequestParam String deviceId, @RequestParam String channelId) {
+        logger.info("[web api] /device/snap?({}, {}) 请求设备截图",
+                deviceId, channelId);
+        String userId = StpUserUtil.getLoginId().toString();
+        // 判断当前用户是否有该设备
+        Device device = accountService.getAccountDevice(userId, deviceId);
+        if (device == null) {
+            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
+        }
+        try {
+            final InputStream in = Files.newInputStream(new File("snap" + File.separator + device.getDeviceId() + "_" + channelId + ".jpg").toPath());
+            resp.setContentType(MediaType.IMAGE_PNG_VALUE);
+            IOUtils.copy(in, resp.getOutputStream());
+        } catch (IOException e) {
+            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
+        }
+    }
+}

+ 4 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorCode.java

@@ -16,7 +16,10 @@ public enum ErrorCode {
     ERR_MEDIA(600, "流媒体服务异常"),
     ERR_NOTFOUND_STREAM(601, "无法找到流媒体"),
     ERR_Invite_fail(602, "流媒体交互异常"),
-    ERROR_Device_Busy(603, "设备繁忙");
+    ERROR_Device_Busy(603, "设备繁忙"),
+    ERROR_Retry(604, "稍后重试"),
+    // 数据异常
+    ERROR_DATA(1000, "数据异常");
 //    void ERROR200(200, "系统异常1");
 
     private final int code;

+ 6 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/ErrorHook.java

@@ -0,0 +1,6 @@
+package com.genersoft.iot.vmp.vmanager.bean;
+
+// 可以触发回调函数
+public interface ErrorHook {
+    void run(WVPResult wvpResult);
+}

+ 38 - 9
src/main/java/com/genersoft/iot/vmp/vmanager/bean/WVPResult.java

@@ -18,12 +18,12 @@ public class WVPResult<T> implements Cloneable{
         this.data = data;
     }
 
-    // todo 返回结果携带搜索结果等页数
-    public WVPResult(int code, String msg, T data, int page, int limit, int total) {
+
+    public WVPResult(int code, String msg, T data, int page, int limit, long total) {
         this.code = code;
         this.msg = msg;
         this.data = data;
-        this.page =  page;
+        this.page = page;
         this.limit = limit;
         this.total = total;
     }
@@ -50,7 +50,7 @@ public class WVPResult<T> implements Cloneable{
 
     @JsonInclude(JsonInclude.Include.NON_NULL)
     @Schema(description = "总条数")
-    private int total;
+    private long total;
 
     public static <T> WVPResult<T> success(T t, String msg) {
         return new WVPResult<>(ErrorCode.SUCCESS.getCode(), msg, t);
@@ -60,6 +60,17 @@ public class WVPResult<T> implements Cloneable{
         return new WVPResult<>(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), null);
     }
 
+    // 返回成功带数据和页码的返回
+    public static <T> WVPResult<T> success(T data, int page, int limit, long total) {
+        return new WVPResult<>(
+                ErrorCode.SUCCESS.getCode(),
+                ErrorCode.SUCCESS.getMsg(), data,
+                page,
+                limit,
+                total
+        );
+    }
+
     public static <T> WVPResult<T> success(T t) {
         return success(t, ErrorCode.SUCCESS.getMsg());
     }
@@ -68,6 +79,10 @@ public class WVPResult<T> implements Cloneable{
         return new WVPResult<>(code, msg, null);
     }
 
+    public static <T> WVPResult<T> fail(ErrorCode errorCode, String msg) {
+        return new WVPResult<>(errorCode.getCode(), msg, null);
+    }
+
     public static <T> WVPResult<T> fail(ErrorCode errorCode) {
         return fail(errorCode.getCode(), errorCode.getMsg());
     }
@@ -109,19 +124,33 @@ public class WVPResult<T> implements Cloneable{
     }
 
     public void setPage(int page) {
-        this.page =  page;
+        this.page = page;
     }
-    public int getTotal() {
+
+    public long getTotal() {
         return total;
     }
 
     public void setTotal(int total) {
-        this.total =  total;
+        this.total = total;
     }
-	
-	@Override
+
+    @Override
     public Object clone() throws CloneNotSupportedException {
         return super.clone();
     }
 
+    // toString
+    @Override
+    public String toString() {
+        return "WVPResult{" +
+                "code=" + code +
+                ", msg='" + msg + '\'' +
+                ", data=" + data +
+                ", limit=" + limit +
+                ", page=" + page +
+                ", total=" + total +
+                '}';
+    }
+
 }

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/SseController/SseController.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.SseController;
 
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEventListener;
 
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -20,6 +21,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 @CrossOrigin
 @Controller
 @RequestMapping("/api")
+@SaAdminCheckRole("admin")
 public class SseController {
     @Autowired
     AlarmEventListener alarmEventListener;

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/aiLib/AiApi.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.aiLib;
 
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.storager.IAiControlStorage;
 import com.genersoft.iot.vmp.vmanager.bean.*;
 import com.genersoft.iot.vmp.vmanager.bean.AiAlarm;
@@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
 @Tag(name = "ai数据接口")
 @RestController
 @RequestMapping("/ai")
+@SaAdminCheckRole("admin")
 public class AiApi {
     private final static Logger logger = LoggerFactory.getLogger(AiControl.class);
     @Autowired

+ 4 - 2
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/aiLib/AiControl.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.aiLib;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.vmanager.bean.*;
 import com.github.pagehelper.PageInfo;
@@ -29,6 +30,7 @@ import java.util.Objects;
 @CrossOrigin
 @RestController
 @RequestMapping("/aiLib")
+@SaAdminCheckRole("admin")
 public class AiControl {
     private final static Logger logger = LoggerFactory.getLogger(AiControl.class);
     @Autowired
@@ -193,7 +195,7 @@ public class AiControl {
             result.setCode(ErrorCode.ERROR400.getCode());
             result.setMsg("未接收到上传文件.待处理");
         }else{
-            // todo 制作存储预警信息的 controller
+            //
             storager.saveAlarm(deviceId,channelId,arithmetic, results_recoInfos,uploads,
                     firmware_version,timestamp,battery,signal,temp_env,temp_cpu,ccid);
             result.setCode(ErrorCode.SUCCESS.getCode());
@@ -216,5 +218,5 @@ public class AiControl {
         if(upload1!=null&&!upload1.isEmpty()){ uploads.add(upload1); }
         if(upload2!=null&&!upload2.isEmpty()){ uploads.add(upload2); }
     }
-    // todo ai库特征码上传
+    //
 }

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.alarm;
 
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
@@ -37,6 +38,7 @@ import java.util.List;
 @CrossOrigin
 @RestController
 @RequestMapping("/api/alarm")
+@SaAdminCheckRole("admin")
 public class AlarmController {
 
     private final static Logger logger = LoggerFactory.getLogger(AlarmController.class);

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java

@@ -9,6 +9,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.device;
 
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.bean.AiConfig;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
@@ -39,6 +40,7 @@ import java.util.UUID;
 @CrossOrigin
 @RestController
 @RequestMapping("/api/device/config")
+@SaAdminCheckRole("admin")
 public class DeviceConfig {
 
     private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java

@@ -9,6 +9,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.device;
 
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
@@ -37,6 +38,7 @@ import java.util.UUID;
 @CrossOrigin
 @RestController
 @RequestMapping("/api/device/control")
+@SaAdminCheckRole("admin")
 public class DeviceControl {
 
     private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);

+ 42 - 30
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java

@@ -1,8 +1,12 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.device;
 
+import cn.dev33.satoken.annotation.SaCheckRole;
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckLogin;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
+import com.genersoft.iot.vmp.conf.security.saToken.SaUserCheckRole;
 import com.genersoft.iot.vmp.gb28181.DeviceShare;
 import com.genersoft.iot.vmp.gb28181.bean.AiConfig;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
@@ -18,6 +22,10 @@ import com.genersoft.iot.vmp.service.IDeviceChannelService;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.utils.AuthorUtil;
+import com.genersoft.iot.vmp.utils.DeviceHelper;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
 import com.genersoft.iot.vmp.vmanager.bean.AiLib;
 import com.genersoft.iot.vmp.vmanager.bean.BaseTree;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
@@ -26,6 +34,7 @@ import com.github.pagehelper.PageInfo;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jdk.nashorn.internal.ir.annotations.Ignore;
 import org.apache.commons.compress.utils.IOUtils;
 import org.apache.ibatis.annotations.Options;
 import org.slf4j.Logger;
@@ -50,10 +59,11 @@ import java.text.ParseException;
 import java.util.*;
 
 @Tag(name = "国标设备查询", description = "国标设备查询")
-@SuppressWarnings("rawtypes")
-@CrossOrigin
+//@SuppressWarnings("rawtypes")
+@CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/device/query")
+@SaAdminCheckRole("admin")
 public class DeviceQuery {
 
     private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);
@@ -82,6 +92,9 @@ public class DeviceQuery {
     @Autowired
     private DeviceShare deviceShare;
 
+    @Autowired
+    private DeviceHelper deviceHelper;
+
     /**
      * 使用ID查询国标设备
      *
@@ -92,6 +105,7 @@ public class DeviceQuery {
     @Parameter(name = "deviceId", description = "设备国标编号", required = true)
     @GetMapping("/devices/{deviceId}")
     public Device devices(@PathVariable String deviceId) {
+
         return storager.queryVideoDevice(deviceId);
     }
 
@@ -285,11 +299,12 @@ public class DeviceQuery {
     public PageInfo<Device> devices(int page,
                                     int count,
                                     @RequestParam(value = "online", defaultValue = "false", required = false) boolean online) {
-
-//		if (page == null) page = 0;
-//		if (count == null) count = 20;
+        String adminId = AuthorUtil.getAdminId();
+        String userId = AuthorUtil.getUserId();
+        logger.info("[设备查询] 查询所有设备 {}", adminId);
+        //
         if (online) {
-            return storager.queryVideoDeviceList(page, count, online);
+            return storager.queryVideoDeviceList(page, count, true);
         }
         return storager.queryVideoDeviceList(page, count, null);
     }
@@ -316,6 +331,7 @@ public class DeviceQuery {
     @Parameter(name = "channelType", description = "设备/子目录-> false/true")
     @Parameter(name = "shareCode", description = "分享码", required = false)
     @Parameter(name = "catalogUnderDevice", description = "是否直属与设备的目录")
+    @Ignore
     public PageInfo<DeviceChannel> channels(
             @PathVariable String deviceId,
             int page, int count,
@@ -328,17 +344,14 @@ public class DeviceQuery {
         if (ObjectUtils.isEmpty(query)) {
             query = null;
         }
-        Device device = storager.queryVideoDevice(deviceId);
-        if(!deviceShare.shareCheck(device, shareCode, false)){
-            // 判断设备分享是否过期,如果过期则自动进行关闭分享
-            if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-                // 设备还在分享中, 延长分享时间
-                deviceShare.updateTime(device);
-            }else{
-                deviceShare.closeShare(device);
-            }
+        logger.info("[查询通道] device:{}", deviceId);
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        if (device == null) {
+            logger.warn("设备不存在,或者无权限访问. 设备ID:" + deviceId);
+            // 无权限
+            throw new ControllerException(ErrorCode.ERROR403.getCode(), "无法找到设备");
         }
-        return storager.queryChannelsByDeviceId(deviceId, query, channelType, online, catalogUnderDevice, page, count);
+        return storager.queryChannelsByDeviceId(device.getDeviceId(), query, channelType, online, catalogUnderDevice, page, count);
     }
 
     /**
@@ -612,6 +625,7 @@ public class DeviceQuery {
             logger.error("[命令发送失败] 设备报警查询: {}", e.getMessage());
             throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
         }
+
         DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
         result.onTimeout(() -> {
             logger.warn(String.format("设备报警查询超时"));
@@ -779,6 +793,7 @@ public class DeviceQuery {
 
     @Operation(summary = "获取分享信息")
     @Parameter(name = "shareCode", description = "分享码", required = true)
+    @Ignore
     @GetMapping("/share/info")
     public WVPResult<Device> getShareInfo(@RequestParam String shareCode) {
         logger.info("获取分享信息: {}", shareCode);
@@ -787,11 +802,7 @@ public class DeviceQuery {
             throw new ControllerException(ErrorCode.ERROR100.getCode(), "无法找到对应的分享设备");
         }
         logger.info("设备 id: {}", device.getDeviceId());
-        WVPResult wvpResult = new WVPResult();
-        wvpResult.setCode(ErrorCode.SUCCESS.getCode());
-        wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
-        wvpResult.setData(device);
-        return wvpResult;
+        return WVPResult.success(device);
     }
 
     @Operation(summary = "分享设备通道")
@@ -799,10 +810,12 @@ public class DeviceQuery {
     @Parameter(name = "channelId", description = "通道国标编号", required = true)
     @GetMapping("/share/open")
     public WVPResult<String> shareOpen(@RequestParam String deviceId, @RequestParam String channelId) {
-        logger.info("[设备分享] 开启分享: {} {}", deviceId, channelId);
-        Device device = storager.queryVideoDevice(deviceId);
+        logger.info("[开启分享] {} {}", deviceId, channelId);
+        Device device = deviceHelper.getDevice(deviceId, null);
         if (device == null) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "无法找到对应的分享设备");
+            logger.warn("[开启分享] 设备不存在,或者无权限访问. 设备ID:" + deviceId);
+            // 无权限
+            throw new ControllerException(ErrorCode.ERROR403.getCode(), "无法找到设备");
         }
 
         DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
@@ -825,14 +838,13 @@ public class DeviceQuery {
     @GetMapping("/share/close")
     public WVPResult<String> shareClose(@RequestParam String deviceId) {
         logger.info("[设备分享] 关闭分享: {}", deviceId);
-        Device device = storager.queryVideoDevice(deviceId);
+        Device device = deviceHelper.getDevice(deviceId, null);
         if (device == null) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "无法找到设备");
+            logger.warn("[关闭分享] 设备不存在,或者无权限访问. 设备ID:" + deviceId);
+            // 无权限
+            return WVPResult.fail(ErrorCode.ERROR403, "无法找到设备");
         }
         deviceShare.closeShare(device);
-        WVPResult wvpResult = new WVPResult();
-        wvpResult.setCode(ErrorCode.SUCCESS.getCode());
-        wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
-        return wvpResult;
+        return WVPResult.success();
     }
 }

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/gbStream/GbStreamController.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.vmanager.gb28181.gbStream;
 
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
 import com.genersoft.iot.vmp.service.IGbStreamService;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
@@ -20,6 +21,7 @@ import java.util.List;
 @CrossOrigin
 @RestController
 @RequestMapping("/api/gbStream")
+@SaAdminCheckRole("admin")
 public class GbStreamController {
 
     private final static Logger logger = LoggerFactory.getLogger(GbStreamController.class);

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java

@@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.conf.security.SecurityUtils;
 import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
 import com.genersoft.iot.vmp.service.IStreamProxyService;
 import com.genersoft.iot.vmp.service.IMediaService;
@@ -26,6 +27,7 @@ import javax.servlet.http.HttpServletRequest;
 @Controller
 @CrossOrigin
 @RequestMapping(value = "/api/media")
+@SaAdminCheckRole("admin")
 public class MediaController {
 
     private final static Logger logger = LoggerFactory.getLogger(MediaController.class);

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java

@@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.PlatformCatalog;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
@@ -40,6 +41,7 @@ import java.util.List;
 @CrossOrigin
 @RestController
 @RequestMapping("/api/platform")
+@SaAdminCheckRole("admin")
 public class PlatformController {
 
     private final static Logger logger = LoggerFactory.getLogger(PlatformController.class);

+ 87 - 137
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java

@@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.conf.security.SecurityUtils;
 import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.*;
 import com.genersoft.iot.vmp.gb28181.bean.BroadcastItem;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
@@ -18,13 +19,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
-import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
-import com.genersoft.iot.vmp.service.IMediaServerService;
-import com.genersoft.iot.vmp.service.IMediaService;
-import com.genersoft.iot.vmp.service.IPlayService;
-import com.genersoft.iot.vmp.service.IUserService;
+import com.genersoft.iot.vmp.service.*;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.utils.AuthorUtil;
+import com.genersoft.iot.vmp.utils.DeviceHelper;
 import com.genersoft.iot.vmp.utils.Md5Utils;
 import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
@@ -88,7 +87,10 @@ public class PlayController {
     private GBEventSubscribe GBHookSubscribe;
 
     @Autowired
-    private IUserService userService;
+    private IAdminService adminService;
+
+    @Autowired
+    private IAccountService accountService;
 
     @Autowired
     private GBStore gbStore;
@@ -96,6 +98,10 @@ public class PlayController {
     @Autowired
     private DeviceShare deviceShare;
 
+    @Autowired
+    private DeviceHelper deviceHelper;
+
+
     @Operation(summary = "开始点播")
     @Parameter(name = "deviceId", description = "设备国标编号", required = true)
     @Parameter(name = "channelId", description = "通道国标编号", required = true)
@@ -110,79 +116,51 @@ public class PlayController {
             @RequestParam(required = false) String shareCode
     ) {
         logger.info("[视频点播] {}-{}开始点播视频", deviceId, channelId);
-        // 获取可用的zlm
-        Device device = storager.queryVideoDevice(deviceId);
-        // 数据校验
-        if(!deviceShare.shareCheck(device, shareCode, false)){
-            // 判断设备分享是否过期,如果过期则自动进行关闭分享
-            if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-                // 设备还在分享中, 延长分享时间
-                deviceShare.updateTime(device);
-            }else{
-                deviceShare.closeShare(device);
-            }
+        // 判断用户权限
+
+        // 只要有一个权限即可
+        DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
+        DeferredResultEx<WVPResult<StreamContent>> deferredResultEx = new DeferredResultEx<>(result);
+
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        String errorText = "";
+        if (device == null) {
+            errorText = "无法操控此设备";
+            logger.warn(errorText);
+            result.setResult(WVPResult.fail(ErrorCode.ERROR403, errorText));
+            return result;
         }
-        MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
 
-        RequestMessage msg = new RequestMessage();
+        deviceId = device.getDeviceId();
+
+        String uuid = UUID.randomUUID().toString();
         String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
+        RequestMessage msg = new RequestMessage(key, uuid);
         boolean exist = resultHolder.exist(key, null);
-        msg.setKey(key);
-        String uuid = UUID.randomUUID().toString();
-        msg.setId(uuid);
-        DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
-        DeferredResultEx<WVPResult<StreamContent>> deferredResultEx = new DeferredResultEx<>(result);
+        resultHolder.put(key, uuid, deferredResultEx);
 
+        // 获取流媒体服务器
+        MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
+        if (newMediaServerItem == null) {
+            logger.warn("无法找到可用的流媒体服务器");
+            msg.setData(WVPResult.fail(ErrorCode.ERR_MEDIA, "无法找到可用的流媒体服务器"));
+            resultHolder.invokeResult(msg);
+        }
+        if (exist) {
+            logger.warn("当前设备通道在推流中");
+            msg.setData(WVPResult.fail(ErrorCode.ERROR100, "当前设备通道在推流中,请先停止(如遇非正常停止,请等待一分钟后重试)"));
+            resultHolder.invokeResult(msg);
+        }
         result.onTimeout(() -> {
             logger.info("点播接口等待超时");
-            // 释放rtpserver
-            WVPResult<StreamInfo> wvpResult = new WVPResult<>();
-            wvpResult.setCode(ErrorCode.ERROR100.getCode());
-            wvpResult.setMsg("点播超时");
-            msg.setData(wvpResult);
+            msg.setData(WVPResult.fail(ErrorCode.ERROR100, "点播接口等待超时"));
             resultHolder.invokeResult(msg);
-            // 强行移除 resultHolder 中的 DeferredResult
-        });
-        // TODO 在点播未成功的情况下在此调用接口点播会导致返回的流地址ip错误
-        deferredResultEx.setFilter(result1 -> {
-            logger.info("点播成功");
-
-            WVPResult<StreamInfo> wvpResult1 = (WVPResult<StreamInfo>) result1;
-            WVPResult<StreamContent> resultStream = new WVPResult<>();
-            resultStream.setCode(wvpResult1.getCode());
-            resultStream.setMsg(wvpResult1.getMsg());
-            if (wvpResult1.getCode() == ErrorCode.SUCCESS.getCode()) {
-                StreamInfo data = wvpResult1.getData().clone();
-                if (userSetting.getUseSourceIpAsStreamIp()) {
-                    data.channgeStreamIp(request.getLocalName());
-                }
-                resultStream.setData(new StreamContent(wvpResult1.getData()));
-            }
-//			resultHolder.invokeResult(msg);
-            return resultStream;
+            // 向设备发送 bye
+            playService.stopPlay(device, channelId);
         });
 
 
-        // 录像查询以channelId作为deviceId查询
-        resultHolder.put(key, uuid, deferredResultEx);
-
-        if (!exist) {
-            playService.play(newMediaServerItem, deviceId, channelId, isUsePs, null, null, null);
-        } else {
-            logger.info("当前设备通道在推流中");
-            // 释放rtpserver
-            WVPResult<StreamInfo> wvpResult = new WVPResult<>();
-            wvpResult.setCode(ErrorCode.ERROR100.getCode());
-            wvpResult.setMsg("当前设备通道在推流中,请先停止(如遇非正常停止,请等待一分钟后重试)");
-            msg.setData(wvpResult);
-            resultHolder.invokeResult(msg);
-            try {
-                resultHolder.invokeAllResult(msg);
-            } catch (Exception e) {
-                logger.error("关闭所有视频通道失败");
-            }
-
-        }
+        playService.startPlay(msg, newMediaServerItem, device, channelId, isUsePs);
         return result;
     }
 
@@ -198,23 +176,13 @@ public class PlayController {
 
         logger.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId));
 
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
 
-        if (deviceId == null || channelId == null) {
+        if (device == null || channelId == null) {
+            logger.warn("设备预览/回放停止API调用失败,设备:{} 或通道:{} 不存在", deviceId, channelId);
             throw new ControllerException(ErrorCode.ERROR400);
         }
 
-        Device device = storager.queryVideoDevice(deviceId);
-
-        // 数据校验
-        if(!deviceShare.shareCheck(device, shareCode, false)){
-            // 判断设备分享是否过期,如果过期则自动进行关闭分享
-            if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-                // 设备还在分享中, 延长分享时间
-                deviceShare.updateTime(device);
-            }else{
-                deviceShare.closeShare(device);
-            }
-        }
 
         StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
         if (streamInfo == null) {
@@ -331,51 +299,41 @@ public class PlayController {
                                             defaultValue = "5000") int waitTimeStr,
                                     @RequestParam(required = false) String shareCode) {
 
-        WVPResult wvpResult = new WVPResult();
+        logger.info("[语音广播 step1] 尝试开启语音对讲");
 
-        Device device = storager.queryVideoDevice(deviceId);
-        // 数据校验
-        if(!deviceShare.shareCheck(device, shareCode, false)){
-            // 判断设备分享是否过期,如果过期则自动进行关闭分享
-            if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-                // 设备还在分享中, 延长分享时间
-                deviceShare.updateTime(device);
-            }else{
-                deviceShare.closeShare(device);
-            }
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+
+        if (device == null || channelId == null) {
+            logger.warn("[语音广播 step1] 设备:{} 或通道:{} 不存在", deviceId, channelId);
+            return WVPResult.fail(ErrorCode.ERROR400, "设备或通道不存在");
         }
+
         MediaServerItem mediaServerItem = playService.getNewMediaServerItem(device);
 
         // 流媒体服务异常处理
         if (mediaServerItem == null) {
-            wvpResult.setCode(ErrorCode.ERR_MEDIA.getCode());
-            wvpResult.setMsg("暂时无法找到可用的zlm流媒体服务");
-            return wvpResult;
+            logger.error("[语音广播 step1] 无法连接至ZLM服务器");
+            return WVPResult.fail(ErrorCode.ERR_MEDIA, "暂时无法找到可用的zlm流媒体服务");
         }
 
         String app = "audio";
         String stream = "rtc_" + deviceId + "_" + channelId;
         String type = "push";
-        Map<String, Object> resultData = new HashMap<>(16);
-        LoginUser userInfo = SecurityUtils.getUserInfo();
-        String sign = Md5Utils.hash(userService.getUserByUsername(userInfo.getUsername()).getPushKey()); //获取推流鉴权密钥
+
+//        LoginUser userInfo = SecurityUtils.getUserInfo();
+        // todo 生成设备推流鉴权密钥, 无论是什么用户登录都可以正常使用
+        String sign = Md5Utils.hash(adminService.getUserById("1").getPushKey()); //获取推流鉴权密钥
         String webRtcPushUrl = String.format("https://%s:%s/index/api/webrtc?app=%s&stream=%s&type=%s&sign=%s", mediaServerItem.getIp(), mediaServerItem.getHttpSSlPort(), app, stream, type, sign);
 
         //首先判断设备是否正在对讲
         if (redisCatchStorage.isBroadcastItem(deviceId)) {
             // 设备正在进行语音对讲
-            wvpResult.setCode(ErrorCode.ERROR_Device_Busy.getCode());
-            wvpResult.setMsg(ErrorCode.ERROR_Device_Busy.getMsg());
-            return wvpResult;
-        }
-
-        if (mediaServerItem == null) {
-            logger.error("流媒体未找到");
-            wvpResult.setCode(ErrorCode.ERR_MEDIA.getCode());
-            wvpResult.setMsg(ErrorCode.ERR_MEDIA.getMsg());
-            return wvpResult;
+            logger.error("[语音广播 step1] 设备正在进行语音对讲");
+            return WVPResult.fail(ErrorCode.ERROR_Device_Busy, "设备正在进行语音对讲");
         }
 
+        // 构建返回数据
+        Map<String, Object> resultData = new HashMap<>(16);
         resultData.put("mediaId", mediaServerItem.getId());
         resultData.put("app", app);
         resultData.put("stream", stream);
@@ -383,10 +341,8 @@ public class PlayController {
         resultData.put("sign", sign);
         resultData.put("webRtcPushUrl", webRtcPushUrl);
         resultData.put("audioEncodePt", device.getAudioEncodePt());
-        wvpResult.setCode(ErrorCode.SUCCESS.getCode());
-        wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
-        wvpResult.setData(resultData);
-        return wvpResult;
+
+        return WVPResult.success(resultData);
     }
 
     @Operation(summary = "开始建立语音广播连接")
@@ -405,20 +361,19 @@ public class PlayController {
             @RequestParam(value = "stream") String stream,
             @RequestParam(required = false) String shareCode
     ) {
-        logger.info("[语音对讲] web端已经开启推流 {} - {}", app, stream);
-
-        // 检查设备是否存在
-        Device device = storager.queryVideoDevice(deviceId);
-        // 数据校验
-        if(!deviceShare.shareCheck(device, shareCode, false)){
-            // 判断设备分享是否过期,如果过期则自动进行关闭分享
-            if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-                // 设备还在分享中, 延长分享时间
-                deviceShare.updateTime(device);
-            }else{
-                deviceShare.closeShare(device);
-            }
+        logger.info("[语音对讲 step2] 音频流已经建立 app:{} stream:{}", app, stream);
+
+        // 检查是否有权限调用此设备
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        if (device == null) {
+            logger.warn("[语音对讲 step2] 无权限 deviceId:{}", deviceId);
+            DeferredResult<WVPResult<String>> deferredResult = new DeferredResult<>();
+            deferredResult.setResult(
+                    WVPResult.fail(ErrorCode.ERROR400, "接口调用失败, 没有权限或者无法找到设备")
+            );
+            return deferredResult;
         }
+
         RequestMessage msg = new RequestMessage();
         // 返回invite信息给设备
         String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST_INVITE + deviceId;
@@ -527,17 +482,14 @@ public class PlayController {
             @RequestParam(required = false) String shareCode) {
 
         WVPResult wvpResult = new WVPResult();
-        Device device = storager.queryVideoDevice(deviceId);
-        // 数据校验
-        if(!deviceShare.shareCheck(device, shareCode, false)){
-            // 判断设备分享是否过期,如果过期则自动进行关闭分享
-            if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-                // 设备还在分享中, 延长分享时间
-                deviceShare.updateTime(device);
-            }else{
-                deviceShare.closeShare(device);
-            }
+        // 检查是否有权限调用此设备
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        if (device == null) {
+            logger.warn("[语音对讲 step2] 无权限 deviceId:{} channelId:{}", deviceId, channelId);
+            // 检查是否有权限调用此设备
+            return WVPResult.fail(ErrorCode.ERROR400, "接口调用失败, 没有权限或者无法找到设备");
         }
+
         // 停止音频流,给设备发送bye
         try {
             logger.warn("[停止点播] {}/{}", deviceId, channelId);
@@ -558,8 +510,6 @@ public class PlayController {
             // 清除redis中的语音广播信息
             logger.error("[停止对讲] 移除语音广播缓存信息");
             gbStore.delBroadcastStore("broadcast_" + deviceId);
-            logger.error("[停止对讲] 移除语音广播redis缓存信息");
-            redisCatchStorage.deleteBroadcastItem(deviceId);
         }
         return wvpResult;
     }

+ 76 - 53
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java

@@ -5,11 +5,14 @@ import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.conf.exception.ServiceException;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.service.IPlayService;
+import com.genersoft.iot.vmp.utils.DeviceHelper;
+import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -59,37 +62,50 @@ public class PlaybackController {
 	@Autowired
 	private IRedisCatchStorage redisCatchStorage;
 
-	@Autowired
-	private IPlayService playService;
-
-	@Autowired
-	private DeferredResultHolder resultHolder;
-
-	@Autowired
-	private UserSetting userSetting;
-
-	@Operation(summary = "开始视频回放")
-	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
-	@Parameter(name = "channelId", description = "通道国标编号", required = true)
-	@Parameter(name = "startTime", description = "开始时间", required = true)
-	@Parameter(name = "endTime", description = "结束时间", required = true)
-	@GetMapping("/start/{deviceId}/{channelId}")
-	public DeferredResult<WVPResult<StreamContent>> start(@PathVariable String deviceId, @PathVariable String channelId,
-														 String startTime, String endTime) {
-
-		if (logger.isDebugEnabled()) {
-			logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId));
-		}
-
-		String uuid = UUID.randomUUID().toString();
-		String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId;
-		DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
-		resultHolder.put(key, uuid, result);
-
-		WVPResult<StreamContent> wvpResult = new WVPResult<>();
-
-		RequestMessage msg = new RequestMessage();
-		msg.setKey(key);
+    @Autowired
+    private IPlayService playService;
+
+    @Autowired
+    private DeferredResultHolder resultHolder;
+
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private DeviceHelper deviceHelper; // 注入DeviceHelper
+
+
+    @Operation(summary = "开始视频回放")
+    @Parameter(name = "deviceId", description = "设备国标编号", required = true)
+    @Parameter(name = "channelId", description = "通道国标编号", required = true)
+    @Parameter(name = "startTime", description = "开始时间", required = true)
+    @Parameter(name = "endTime", description = "结束时间", required = true)
+    @GetMapping("/start/{deviceId}/{channelId}")
+    public DeferredResult<WVPResult<StreamContent>> start(@PathVariable String deviceId, @PathVariable String channelId,
+                                                          String startTime, String endTime) {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId));
+        }
+        logger.warn("[录像回放] deviceId:{} channelId:{} {}-{}", deviceId, channelId, startTime, endTime);
+        Device device = deviceHelper.getDevice(deviceId, null);
+        if (device == null) {
+            logger.warn("[录像回放] 无权限 deviceId:{} channelId:{}", deviceId, channelId);
+            // 检查是否有权限调用此设备
+            DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
+            result.setResult(WVPResult.fail(ErrorCode.ERROR400, "接口调用失败, 没有权限或者无法找到设备"));
+            return result;
+        }
+
+        String uuid = UUID.randomUUID().toString();
+        String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId;
+        DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
+        resultHolder.put(key, uuid, result);
+
+        WVPResult<StreamContent> wvpResult = new WVPResult<>();
+
+        RequestMessage msg = new RequestMessage();
+        msg.setKey(key);
 		msg.setId(uuid);
 
 		playService.playBack(deviceId, channelId, startTime, endTime, null,
@@ -108,28 +124,35 @@ public class PlaybackController {
 	}
 
 
-	@Operation(summary = "停止视频回放")
-	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
-	@Parameter(name = "channelId", description = "通道国标编号", required = true)
-	@Parameter(name = "stream", description = "流ID", required = true)
-	@GetMapping("/stop/{deviceId}/{channelId}/{stream}")
-	public void playStop(
-			@PathVariable String deviceId,
-			@PathVariable String channelId,
-			@PathVariable String stream) {
-		if (ObjectUtils.isEmpty(deviceId) || ObjectUtils.isEmpty(channelId) || ObjectUtils.isEmpty(stream)) {
-			throw new ControllerException(ErrorCode.ERROR400);
-		}
-		Device device = storager.queryVideoDevice(deviceId);
-		if (device == null) {
-			throw new ControllerException(ErrorCode.ERROR400.getCode(), "设备:" + deviceId + " 未找到");
-		}
-		try {
-			cmder.streamByeCmd(device, channelId, stream, null);
-		} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "发送bye失败: " + e.getMessage());
-		}
-	}
+    @Operation(summary = "停止视频回放")
+    @Parameter(name = "deviceId", description = "设备国标编号", required = true)
+    @Parameter(name = "channelId", description = "通道国标编号", required = true)
+    @Parameter(name = "stream", description = "流ID", required = true)
+    @GetMapping("/stop/{deviceId}/{channelId}/{stream}")
+    public WVPResult playStop(
+            @PathVariable String deviceId,
+            @PathVariable String channelId,
+            @PathVariable String stream) {
+
+        if (ObjectUtils.isEmpty(deviceId) || ObjectUtils.isEmpty(channelId) || ObjectUtils.isEmpty(stream)) {
+            return WVPResult.fail(ErrorCode.ERROR400, "参数异常");
+        }
+
+        Device device = deviceHelper.getDevice(deviceId, null);
+        if (device == null) {
+            logger.warn("[停止视频回放] 无权限 deviceId:{} channelId:{}", deviceId, channelId);
+            // 检查是否有权限调用此设备
+            DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
+            return WVPResult.fail(ErrorCode.ERROR403, "接口调用失败, 没有权限或者无法找到设备");
+        }
+        try {
+            cmder.streamByeCmd(device, channelId, stream, null);
+        } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
+            logger.warn("[停止视频回放] 停止视频回放失败 {}", e.getMessage());
+            return WVPResult.fail(ErrorCode.ERROR100, e.getMessage());
+        }
+        return WVPResult.success();
+    }
 
 
 	@Operation(summary = "回放暂停")

+ 110 - 131
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.ptz;
 
  
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.DeviceShare;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.PresetQuerySipReq;
@@ -9,6 +10,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.utils.DeviceHelper;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import io.swagger.v3.oas.annotations.Operation;
@@ -39,24 +41,27 @@ public class PtzController {
 	@Autowired
 	private SIPCommander cmder;
 
-	@Autowired
-	private IVideoManagerStorage storager;
+    @Autowired
+    private IVideoManagerStorage storager;
 
-	@Autowired
-	private DeferredResultHolder resultHolder;
+    @Autowired
+    private DeferredResultHolder resultHolder;
 
-	@Autowired
-	private DeviceShare deviceShare;
+    @Autowired
+    private DeviceShare deviceShare;
 
-	/***
-	 * 云台控制
-	 * @param deviceId 设备id
-	 * @param channelId 通道id
-	 * @param command	控制指令
-	 * @param horizonSpeed	水平移动速度
-	 * @param verticalSpeed	垂直移动速度
-	 * @param zoomSpeed	    缩放速度
-	 */
+    @Autowired
+    private DeviceHelper deviceHelper;
+
+    /***
+     * 云台控制
+     * @param deviceId 设备id
+     * @param channelId 通道id
+     * @param command    控制指令
+     * @param horizonSpeed    水平移动速度
+     * @param verticalSpeed    垂直移动速度
+     * @param zoomSpeed        缩放速度
+     */
 
 	@Operation(summary = "云台控制")
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
@@ -73,31 +78,26 @@ public class PtzController {
 					int horizonSpeed,
 					int verticalSpeed,
 					int zoomSpeed,
-					@RequestParam(required = false) String shareCode){
+					@RequestParam(required = false) String shareCode) {
 
-		if (logger.isDebugEnabled()) {
-			logger.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,command:%s ,horizonSpeed:%d ,verticalSpeed:%d ,zoomSpeed:%d",deviceId, channelId, command, horizonSpeed, verticalSpeed, zoomSpeed));
-		}
-		Device device = storager.queryVideoDevice(deviceId);
-		// 数据校验
-		if(!deviceShare.shareCheck(device, shareCode, false)){
-			// 判断设备分享是否过期,如果过期则自动进行关闭分享
-			if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-				// 设备还在分享中, 延长分享时间
-				deviceShare.updateTime(device);
-			}else{
-				deviceShare.closeShare(device);
-			}
-		}
-		int cmdCode = 0;
-		switch (command){
-			case "left":
-				cmdCode = 2;
-				break;
-			case "right":
-				cmdCode = 1;
-				break;
-			case "up":
+
+        logger.info("[云台控制] deviceId:{} ,channelId:{} ,command:{} ,horizonSpeed:{} ,verticalSpeed:{} ,zoomSpeed:{} ", deviceId, channelId, command, horizonSpeed, verticalSpeed, zoomSpeed);
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        if (device == null) {
+            logger.warn("[云台控制] 设备不存在,设备ID:" + deviceId);
+            // 无权限
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "无法找到设备");
+        }
+
+        int cmdCode = 0;
+        switch (command) {
+            case "left":
+                cmdCode = 2;
+                break;
+            case "right":
+                cmdCode = 1;
+                break;
+            case "up":
 				cmdCode = 8;
 				break;
 			case "down":
@@ -147,31 +147,24 @@ public class PtzController {
 					   String c,
 					   String step,
 					   @RequestParam(required = false) String shareCode
-					   ){
-		if (logger.isDebugEnabled()) {
-			logger.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,c:%s ,speed:%d ",deviceId, channelId, c, step));
-		}
-		logger.info("设备云台控制 API调用,deviceId:{} ,channelId:{} ,command:{} ,speed:{} ",deviceId, channelId, c, step);
-		Device device = storager.queryVideoDevice(deviceId);
-		// 数据校验
-		if(!deviceShare.shareCheck(device, shareCode, false)){
-			// 判断设备分享是否过期,如果过期则自动进行关闭分享
-			if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-				// 设备还在分享中, 延长分享时间
-				deviceShare.updateTime(device);
-			}else{
-				deviceShare.closeShare(device);
-			}
-		}
-		int cmdCode = 0;
-		switch (c){
-			case "left":
-				cmdCode = 3;
-				break;
-			case "right":
-				cmdCode = 4;
-				break;
-			case "up":
+					   ) {
+        logger.info("设备云台控制 API调用,deviceId:{} ,channelId:{} ,command:{} ,speed:{} ", deviceId, channelId, c, step);
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        if (device == null) {
+            logger.warn("设备不存在,设备ID:" + deviceId);
+            // 无权限
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "无法找到设备");
+        }
+
+        int cmdCode = 0;
+        switch (c) {
+            case "left":
+                cmdCode = 3;
+                break;
+            case "right":
+                cmdCode = 4;
+                break;
+            case "up":
 				cmdCode = 1;
 				break;
 			case "down":
@@ -205,29 +198,24 @@ public class PtzController {
 	public void focus(@PathVariable String deviceId,
 					  @PathVariable String channelId,
 					  @RequestParam(required = false) String shareCode
-					  ){
-		if (logger.isDebugEnabled()) {
-			logger.debug(String.format("设备云台控制聚焦 API调用,deviceId:%s ,channelId:%s ",deviceId, channelId));
-		}
-		logger.info("控制设备  {} - {} 通道聚焦",deviceId, channelId);
-		Device device = storager.queryVideoDevice(deviceId);
-		// 数据校验
-		if(!deviceShare.shareCheck(device, shareCode, false)){
-			// 判断设备分享是否过期,如果过期则自动进行关闭分享
-			if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-				// 设备还在分享中, 延长分享时间
-				deviceShare.updateTime(device);
-			}else{
-				deviceShare.closeShare(device);
-			}
-		}
-		try {
-			cmder.ptzFocus(device, channelId);
-		} catch (SipException | InvalidArgumentException | ParseException e) {
-			logger.error("[命令发送失败] 云台控制: {}", e.getMessage());
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
-		}
-	}
+					  ) {
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("设备云台控制聚焦 API调用,deviceId:%s ,channelId:%s ", deviceId, channelId));
+        }
+        logger.info("控制设备  {} - {} 通道聚焦", deviceId, channelId);
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        if (device == null) {
+            logger.warn("设备不存在,设备ID:" + deviceId);
+            // 无权限
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "无法找到设备");
+        }
+        try {
+            cmder.ptzFocus(device, channelId);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 云台控制: {}", e.getMessage());
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+        }
+    }
 
 	@Operation(summary = "通用前端控制命令")
 	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
@@ -245,29 +233,25 @@ public class PtzController {
 								int parameter2,
 								int combindCode2,
 								@RequestParam(required = false) String shareCode
-								){
+								) {
 
-		if (logger.isDebugEnabled()) {
-			logger.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,cmdCode:%d parameter1:%d parameter2:%d",deviceId, channelId, cmdCode, parameter1, parameter2));
-		}
-		Device device = storager.queryVideoDevice(deviceId);
-		// 数据校验
-		if(!deviceShare.shareCheck(device, shareCode, false)){
-			// 判断设备分享是否过期,如果过期则自动进行关闭分享
-			if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-				// 设备还在分享中, 延长分享时间
-				deviceShare.updateTime(device);
-			}else{
-				deviceShare.closeShare(device);
-			}
-		}
-		try {
-			cmder.frontEndCmd(device, channelId, cmdCode, parameter1, parameter2, combindCode2);
-		} catch (SipException | InvalidArgumentException | ParseException e) {
-			logger.error("[命令发送失败] 前端控制: {}", e.getMessage());
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
-		}
-	}
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("设备云台控制 API调用,deviceId:%s ,channelId:%s ,cmdCode:%d parameter1:%d parameter2:%d", deviceId, channelId, cmdCode, parameter1, parameter2));
+        }
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        if (device == null) {
+            logger.warn("设备不存在,设备ID:" + deviceId);
+            // 无权限
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "无法找到设备");
+        }
+
+        try {
+            cmder.frontEndCmd(device, channelId, cmdCode, parameter1, parameter2, combindCode2);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 前端控制: {}", e.getMessage());
+            throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
+        }
+    }
 
 
 	@Operation(summary = "预置位查询")
@@ -280,32 +264,27 @@ public class PtzController {
 			@PathVariable String channelId,
 			@RequestParam(required = false) String shareCode
 			) {
-		if (logger.isDebugEnabled()) {
-			logger.debug("设备预置位查询API调用");
-		}
-		logger.info("[预置位查询] 获取设备预置位信息");
-		Device device = storager.queryVideoDevice(deviceId);
+        logger.info("[预置位查询] 获取设备预置位信息");
 
-		// 分享码数据校验
-		if(!deviceShare.shareCheck(device, shareCode, false)){
-			// 判断设备分享是否过期,如果过期则自动进行关闭分享
-			if(deviceShare.checkShareCodeIsExpires(device, device.getShareCode()) == 0){
-				// 设备还在分享中, 延长分享时间
-				deviceShare.updateTime(device);
-			}else{
-				deviceShare.closeShare(device);
-			}
-		}
-		String uuid =  UUID.randomUUID().toString();
-		// 此处一开始使用的是channelId,但是发现有些设备没有channelId,所以改成deviceId
-		String key =  DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + deviceId;
-		DeferredResult<WVPResult<List<PresetQuerySipReq>>> result = new DeferredResult<WVPResult<List<PresetQuerySipReq>>> (15 * 1000L);
-		WVPResult wvpResult = new WVPResult();
+        Device device = deviceHelper.getDevice(deviceId, shareCode);
+        DeferredResult<WVPResult<List<PresetQuerySipReq>>> result = new DeferredResult<WVPResult<List<PresetQuerySipReq>>>(15 * 1000L);
+
+        if (device == null) {
+            logger.warn("设备不存在,设备ID:" + deviceId);
+            // 无权限
+            result.setResult(WVPResult.fail(ErrorCode.ERROR100, "无法找到设备"));
+            return result;
+        }
+
+        String uuid = UUID.randomUUID().toString();
+        // 此处一开始使用的是channelId,但是发现有些设备没有channelId,所以改成deviceId
+        String key = DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + deviceId;
 
+        WVPResult wvpResult = new WVPResult();
 
 
-		result.onTimeout(()->{
-			logger.warn(String.format("获取设备预置位超时"));
+        result.onTimeout(() -> {
+            logger.warn(String.format("获取设备预置位超时"));
 			// 释放rtpserver
 			RequestMessage msg = new RequestMessage();
 			msg.setId(uuid);

+ 59 - 49
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java

@@ -4,11 +4,13 @@ import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IPlayService;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.utils.DeviceHelper;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -51,39 +53,50 @@ public class GBRecordController {
 	@Autowired
 	private IVideoManagerStorage storager;
 
-	@Autowired
-	private DeferredResultHolder resultHolder;
-
-	@Autowired
-	private IPlayService playService;
-
-	@Autowired
-	private IDeviceService deviceService;
-
+    @Autowired
+    private DeferredResultHolder resultHolder;
+
+    @Autowired
+    private IPlayService playService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private DeviceHelper deviceHelper;
+
+
+    @Operation(summary = "录像查询")
+    @Parameter(name = "deviceId", description = "设备国标编号", required = true)
+    @Parameter(name = "channelId", description = "通道国标编号", required = true)
+    @Parameter(name = "startTime", description = "开始时间", required = true)
+    @Parameter(name = "endTime", description = "结束时间", required = true)
+    @GetMapping("/query/{deviceId}/{channelId}")
+    public DeferredResult<WVPResult<RecordInfo>> recordinfo(@PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime) {
+
+        logger.info("[设备录像查询] {}-{}", deviceId, channelId);
+        Device device = deviceHelper.getDevice(deviceId, null);
+        DeferredResult<WVPResult<RecordInfo>> result = new DeferredResult<>();
+        if (device == null) {
+            // 无权限
+            result.setResult(WVPResult.fail(ErrorCode.ERROR403, "无法操控此设备"));
+            return result;
+        }
+
+        // 时间转换
+        if (!DateUtil.verification(startTime, DateUtil.formatter)) {
+            result.setResult(
+                    WVPResult.fail(ErrorCode.ERROR100, "startTime error, format is " + DateUtil.PATTERN)
+            );
+            return result;
+        }
+        if (!DateUtil.verification(endTime, DateUtil.formatter)) {
+            result.setResult(
+                    WVPResult.fail(ErrorCode.ERROR100, "endTime error, format is " + DateUtil.PATTERN)
+            );
+            return result;
+        }
 
-
-
-	@Operation(summary = "录像查询")
-	@Parameter(name = "deviceId", description = "设备国标编号", required = true)
-	@Parameter(name = "channelId", description = "通道国标编号", required = true)
-	@Parameter(name = "startTime", description = "开始时间", required = true)
-	@Parameter(name = "endTime", description = "结束时间", required = true)
-	@GetMapping("/query/{deviceId}/{channelId}")
-	public DeferredResult<WVPResult<RecordInfo>> recordinfo(@PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime){
-
-		if (logger.isDebugEnabled()) {
-			logger.debug(String.format("录像信息查询 API调用,deviceId:%s ,startTime:%s, endTime:%s",deviceId, startTime, endTime));
-		}
-		logger.info("[设备录像查询] {}-{}",deviceId,channelId);
-		DeferredResult<WVPResult<RecordInfo>> result = new DeferredResult<>();
-		if (!DateUtil.verification(startTime, DateUtil.formatter)){
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "startTime error, format is " + DateUtil.PATTERN);
-		}
-		if (!DateUtil.verification(endTime, DateUtil.formatter)){
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "endTime error, format is " + DateUtil.PATTERN);
-		}
-
-		Device device = storager.queryVideoDevice(deviceId);
 		// 指定超时时间 1分钟30秒
 		String uuid = UUID.randomUUID().toString();
 		int sn  =  (int)((Math.random()*9+1)*100000);
@@ -92,27 +105,24 @@ public class GBRecordController {
 		msg.setId(uuid);
 		msg.setKey(key);
 		try {
-			cmder.recordInfoQuery(device, channelId,false, startTime, endTime, sn, null, null, null, (eventResult -> {
-				WVPResult<RecordInfo> wvpResult = new WVPResult<>();
-				wvpResult.setCode(ErrorCode.ERROR100.getCode());
-				wvpResult.setMsg("查询录像失败, status: " +  eventResult.statusCode + ", message: " + eventResult.msg);
-				msg.setData(wvpResult);
-				resultHolder.invokeResult(msg);
-			}));
-		} catch (InvalidArgumentException | SipException | ParseException e) {
-			logger.error("[命令发送失败] 查询录像: {}", e.getMessage());
-			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " +  e.getMessage());
-		}
+            cmder.recordInfoQuery(device, channelId, false, startTime, endTime, sn, null, null, null,
+                    (eventResult -> {
+                        String errMsg = "查询录像失败, status: " + eventResult.statusCode + ", message: " + eventResult.msg;
+                        logger.error(errMsg);
+                        msg.setData(WVPResult.fail(ErrorCode.ERROR100, errMsg));
+                        resultHolder.invokeResult(msg);
+                    }));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 查询录像: {}", e.getMessage());
+            msg.setData(WVPResult.fail(ErrorCode.ERROR100, "[命令发送失败] 查询录像"));
+            resultHolder.invokeResult(msg);
+        }
 
 		// 录像查询以channelId作为deviceId查询
 		resultHolder.put(key, uuid, result);
 		result.onTimeout(()->{
-			msg.setData("timeout");
-			WVPResult<RecordInfo> wvpResult = new WVPResult<>();
-			wvpResult.setCode(ErrorCode.ERROR100.getCode());
-			wvpResult.setMsg("timeout");
-			msg.setData(wvpResult);
-			resultHolder.invokeResult(msg);
+            msg.setData(WVPResult.fail(ErrorCode.ERROR100, "[查询超时] 查询录像超时"));
+            resultHolder.invokeResult(msg);
 		});
         return result;
 	}

+ 1 - 8
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java

@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.vmanager.server;
 
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
-import com.genersoft.iot.vmp.VManageBootstrap;
 import com.genersoft.iot.vmp.common.SystemAllInfo;
 import com.genersoft.iot.vmp.common.VersionPo;
 import com.genersoft.iot.vmp.conf.MediaConfig;
@@ -17,12 +16,10 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.*;
 import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.utils.SpringBeanFactory;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.ResourceBaceInfo;
 import com.genersoft.iot.vmp.vmanager.bean.ResourceInfo;
 import com.genersoft.iot.vmp.vmanager.bean.SystemConfigInfo;
-import gov.nist.javax.sip.SipStackImpl;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -33,11 +30,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.*;
 
-import javax.sip.ListeningPoint;
-import javax.sip.ObjectInUseException;
-import javax.sip.SipProvider;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -84,7 +77,7 @@ public class ServerController {
     private int serverPort;
 
     @Autowired
-    private IUserService userService;
+    private IAdminService userService;
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;

+ 105 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/server/sipController.java

@@ -0,0 +1,105 @@
+package com.genersoft.iot.vmp.vmanager.server;
+
+
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
+import com.genersoft.iot.vmp.gb28181.bean.SipUserConfig;
+import com.genersoft.iot.vmp.service.ISipConfigService;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import com.github.pagehelper.PageInfo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "平台sip信息管理")
+@CrossOrigin(origins = "*")
+@RestController
+@RequestMapping("/api/sip")
+@SaAdminCheckRole("admin")
+public class sipController {
+    private Logger logger = LoggerFactory.getLogger(sipController.class);
+
+    @Autowired
+    private ISipConfigService sipConfigService;
+
+    @GetMapping("/search")
+    @Parameter(name = "p", description = "当前页", required = true)
+    @Parameter(name = "l", description = "每页查询数量", required = true)
+    @Operation(summary = "搜索sip列表")
+    public WVPResult<List<SipUserConfig>> getSipConfigs(
+            @RequestParam(value = "p", required = false, defaultValue = "1") int page,
+            @RequestParam(value = "l", required = false, defaultValue = "20") int limit) {
+        logger.info("[sip配置检索]");
+        String adminId = StpAdminUtil.getLoginId().toString();
+        PageInfo<SipUserConfig> pageResult = sipConfigService.getSipConfigsById(
+                adminId,
+                page,
+                limit);
+        return WVPResult.success(
+                pageResult.getList(),
+                page,
+                pageResult.getPageSize(),
+                pageResult.getTotal());
+    }
+
+    @PostMapping("/edit")
+    @Parameter(name = "id", description = "sipId", required = true)
+    @Operation(summary = "编辑sip配置")
+    public WVPResult<String> editSipConfig(
+            @RequestParam String id,
+            @RequestBody SipUserConfig sipConfig) {
+        logger.info("[编辑sip配置信息] id:{}", id);
+        String adminId = StpAdminUtil.getLoginId().toString();
+        // 判断当前用户是否有该域
+        SipUserConfig oldSipConfig = sipConfigService.getSipConfigById(adminId, id);
+        if (oldSipConfig == null) {
+            return WVPResult.fail(ErrorCode.ERROR404, "用户无法编辑指定sip配置");
+        }
+        if (!sipConfigService.editSipConfig(id, sipConfig)) {
+            logger.error("[编辑sip配置失败]");
+            return WVPResult.fail(ErrorCode.ERROR100, "编辑sip配置失败");
+        }
+        return WVPResult.success();
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "新增sip配置")
+    public WVPResult<String> editSipConfig(
+            @RequestBody SipUserConfig sipConfig) {
+        String adminId = StpAdminUtil.getLoginId().toString();
+        logger.info("[新增sip配置信息] admin:{}", adminId);
+        if (!sipConfigService.addSipConfig(adminId, sipConfig)) {
+            logger.error("[新增sip配置失败]");
+            return WVPResult.fail(ErrorCode.ERROR100, "新增sip配置失败");
+        }
+        return WVPResult.success();
+    }
+
+    @PostMapping("/del")
+    @Parameter(name = "id", description = "sipId", required = true)
+    @Operation(summary = "删除sip配置")
+    public WVPResult<String> delSipConfig(
+            @RequestParam String id,
+            @RequestBody SipUserConfig sipConfig) {
+        logger.info("[删除sip配置信息] id:{}", id);
+        String adminId = StpAdminUtil.getLoginId().toString();
+        // 判断当前用户是否有该域
+        SipUserConfig oldSipConfig = sipConfigService.getSipConfigById(adminId, id);
+        if (oldSipConfig == null) {
+            return WVPResult.fail(ErrorCode.ERROR404, "用户无法删除指定sip配置");
+        }
+        if (!sipConfigService.deleteSipConfig(id, adminId)) {
+            logger.error("[删除sip配置失败]");
+            return WVPResult.fail(ErrorCode.ERROR100, "删除sip配置失败");
+        }
+        return WVPResult.success();
+    }
+
+}

+ 65 - 78
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java

@@ -1,15 +1,17 @@
 package com.genersoft.iot.vmp.vmanager.user;
 
+import cn.dev33.satoken.annotation.SaIgnore;
 import com.genersoft.iot.vmp.conf.exception.ControllerException;
-import com.genersoft.iot.vmp.conf.security.JwtUtils;
 import com.genersoft.iot.vmp.conf.security.SecurityUtils;
 import com.genersoft.iot.vmp.conf.security.dto.LoginUser;
-import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd.KeepaliveNotifyMessageHandler;
+import com.genersoft.iot.vmp.conf.security.saToken.SaAdminCheckRole;
+import com.genersoft.iot.vmp.service.IAdminService;
 import com.genersoft.iot.vmp.service.IRoleService;
-import com.genersoft.iot.vmp.service.IUserService;
+import com.genersoft.iot.vmp.storager.dao.dto.AdminAccount;
 import com.genersoft.iot.vmp.storager.dao.dto.Role;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.utils.StpAdminUtil;
+import com.genersoft.iot.vmp.utils.StpUserUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import com.github.pagehelper.PageInfo;
@@ -32,6 +34,7 @@ import java.util.List;
 @Tag(name  = "用户管理")
 @CrossOrigin(origins = "*")
 @RestController
+@SaAdminCheckRole("admin")
 @RequestMapping("/api/user")
 public class UserController {
     private Logger logger = LoggerFactory.getLogger(UserController.class);
@@ -39,34 +42,27 @@ public class UserController {
     private AuthenticationManager authenticationManager;
 
     @Autowired
-    private IUserService userService;
+    private IAdminService userService;
 
     @Autowired
     private IRoleService roleService;
 
-//    @GetMapping("/login")
+    //    @GetMapping("/login")
     @PostMapping("/login")
+    @SaIgnore
     @Operation(summary = "登录", description = "登录成功后返回AccessToken, 可以从返回值获取到也可以从响应头中获取到," +
             "后续的请求需要添加请求头 'access-token'或者放在参数里")
 
     @Parameter(name = "username", description = "用户名", required = true)
     @Parameter(name = "password", description = "密码(32位md5加密)", required = true)
-    public LoginUser login(HttpServletRequest request, HttpServletResponse response, @RequestParam String username, @RequestParam String password){
-        LoginUser user;
-        try {
-            user = SecurityUtils.login(username, password, authenticationManager);
-            logger.info("[用户管理] - [登录] - [{} - {} -{}] - 登录成功", user.getId(), user.getUsername(), user.getRole().getId());
-        } catch (AuthenticationException e) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
-        }
-        if (user == null) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), "用户名或密码错误");
-        }else {
-            String jwt = JwtUtils.createToken(username, password);
-            response.setHeader(JwtUtils.getHeader(), jwt);
-            user.setAccessToken(jwt);
+    public WVPResult login(HttpServletRequest request, HttpServletResponse response, @RequestParam String username, @RequestParam String password) {
+
+        AdminAccount adminAccount = userService.getUser(username, password);
+        if (adminAccount == null) {
+            return WVPResult.fail(ErrorCode.ERROR100);
         }
-        return user;
+        StpAdminUtil.login(adminAccount.getId());
+        return WVPResult.success();
     }
 
 
@@ -110,41 +106,32 @@ public class UserController {
     @Parameter(name = "roleId", description = "角色ID", required = true)
     public void add(@RequestParam String username,
                                                  @RequestParam String password,
-                                                 @RequestParam Integer roleId){
-        try {
-            if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password) || roleId == null) {
-                throw new ControllerException(ErrorCode.ERROR400.getCode(), "参数不可为空");
-            }
-            LoginUser loginUser = SecurityUtils.getUserInfo();
-            String _username = loginUser.getUsername();
-            String _passwordMd5 = loginUser.getPassword();
-            LoginUser _user = SecurityUtils.login(_username, _passwordMd5, authenticationManager);
-            // 获取当前登录用户id
-            int currenRoleId = _user.getRole().getId();
-            logger.info("[用户管理] 添加用户,当前用户角色id:" + currenRoleId);
-            if (currenRoleId != 1) {
-                // 只用角色id为1才可以删除和添加用户
-                throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户无权限");
-            }
-            User user = new User();
-            user.setUsername(username);
-            user.setPassword(DigestUtils.md5DigestAsHex(password.getBytes()));
-            //新增用户的pushKey的生成规则为md5(时间戳+用户名)
-            user.setPushKey(DigestUtils.md5DigestAsHex((System.currentTimeMillis() + password).getBytes()));
-            Role role = roleService.getRoleById(roleId);
-
-            if (role == null) {
-                throw new ControllerException(ErrorCode.ERROR400.getCode(), "角色不存在");
-            }
-            user.setRole(role);
-            user.setCreateTime(DateUtil.getNow());
-            user.setUpdateTime(DateUtil.getNow());
-            int addResult = userService.addUser(user);
-            if (addResult <= 0) {
-                throw new ControllerException(ErrorCode.ERROR100);
-            }
-        }catch (AuthenticationException e) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
+                                                 @RequestParam Integer roleId) {
+
+        if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password) || roleId == null) {
+            throw new ControllerException(ErrorCode.ERROR400.getCode(), "参数不可为空");
+        }
+
+        // 获取当前登录用户id
+        String accountId = StpUserUtil.getLoginId().toString();
+        logger.info("[用户管理] 添加用户,当前用户id:" + accountId);
+
+        AdminAccount adminAccount = new AdminAccount();
+        adminAccount.setUsername(username);
+        adminAccount.setPassword(DigestUtils.md5DigestAsHex(password.getBytes()));
+        //新增用户的pushKey的生成规则为md5(时间戳+用户名)
+        adminAccount.setPushKey(DigestUtils.md5DigestAsHex((System.currentTimeMillis() + password).getBytes()));
+        Role role = roleService.getRoleById(roleId);
+
+        if (role == null) {
+            throw new ControllerException(ErrorCode.ERROR400.getCode(), "角色不存在");
+        }
+        adminAccount.setRole(role);
+        adminAccount.setCreateTime(DateUtil.getNow());
+        adminAccount.setUpdateTime(DateUtil.getNow());
+        int addResult = userService.addUser(adminAccount);
+        if (addResult <= 0) {
+            throw new ControllerException(ErrorCode.ERROR100);
         }
     }
 
@@ -174,11 +161,23 @@ public class UserController {
 
     @GetMapping("/all")
     @Operation(summary = "查询用户")
-    public List<User> all(){
+    public List<AdminAccount> all() {
         // 获取当前登录用户id
         return userService.getAllUsers();
     }
 
+    public void register(String username, String password) {
+        AdminAccount adminAccount = new AdminAccount();
+        adminAccount.setUsername(username);
+        adminAccount.setPassword(DigestUtils.md5DigestAsHex(password.getBytes()));
+        adminAccount.setCreateTime(DateUtil.getNow());
+        adminAccount.setUpdateTime(DateUtil.getNow());
+        int addResult = userService.addUser(adminAccount);
+        if (addResult <= 0) {
+            throw new ControllerException(ErrorCode.ERROR100);
+        }
+    }
+
     /**
      * 分页查询用户
      *
@@ -190,34 +189,22 @@ public class UserController {
     @Operation(summary = "分页查询用户")
     @Parameter(name = "page", description = "当前页", required = true)
     @Parameter(name = "count", description = "每页查询数量", required = true)
-    public PageInfo<User> users(int page, int count) {
+    public PageInfo<AdminAccount> users(int page, int count) {
         return userService.getUsers(page, count);
     }
 
     @RequestMapping("/changePushKey")
     @Operation(summary = "修改pushkey")
-    @Parameter(name = "userId", description = "用户Id", required = true)
     @Parameter(name = "pushKey", description = "新的pushKey", required = true)
-    public void changePushKey(@RequestParam Integer userId,@RequestParam String pushKey) {
-        try{
-            // 获取当前登录用户id
-            LoginUser loginUser = SecurityUtils.getUserInfo();
-            String _username = loginUser.getUsername();
-            String _passwordMd5 = loginUser.getPassword();
-            LoginUser _user = SecurityUtils.login(_username, _passwordMd5, authenticationManager);
-            int currenRoleId = _user.getRole().getId();
-            WVPResult<String> result = new WVPResult<>();
-            if (currenRoleId != 1) {
-                // 只用角色id为0才可以删除和添加用户
-                throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户无权限");
-            }
-            int resetPushKeyResult = userService.changePushKey(userId,pushKey);
-            if (resetPushKeyResult <= 0) {
-                throw new ControllerException(ErrorCode.ERROR100);
-            }
-        } catch (AuthenticationException e) {
-            throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
+    public WVPResult changePushKey(@RequestParam String pushKey) {
+        // 获取当前登录用户id
+        String accountId = StpUserUtil.getLoginId().toString();
+        logger.info("[用户管理] 修改pushKey,当前用户id:" + accountId);
+        int resetPushKeyResult = userService.changePushKey(Integer.parseInt(accountId), pushKey);
+        if (resetPushKeyResult <= 0) {
+            return WVPResult.fail(ErrorCode.ERROR100);
         }
+        return WVPResult.success();
     }
 
     @PostMapping("/changePasswordForAdmin")

+ 4 - 3
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java

@@ -44,7 +44,8 @@ public class ApiDeviceController {
     // private DeviceOffLineDetector offLineDetector;
 
     /**
-     * 分页获取设备列表 TODO 现在直接返回,尚未实现分页
+     * 分页获取设备列表
+     *
      * @param start
      * @param limit
      * @param q
@@ -140,11 +141,11 @@ public class ApiDeviceController {
             deviceJOSNChannel.put("DeviceID", deviceChannelExtend.getDeviceId());
             deviceJOSNChannel.put("DeviceName", deviceChannelExtend.getDeviceName());
             deviceJOSNChannel.put("DeviceOnline", deviceChannelExtend.getDeviceOnline() == 1);
-            deviceJOSNChannel.put("Channel", 0); // TODO 自定义序号
+            deviceJOSNChannel.put("Channel", 0);
             deviceJOSNChannel.put("Name", deviceChannelExtend.getName());
             deviceJOSNChannel.put("Custom", false);
             deviceJOSNChannel.put("CustomName", "");
-            deviceJOSNChannel.put("SubCount", deviceChannelExtend.getSubCount()); // TODO ? 子节点数, SubCount > 0 表示该通道为子目录
+            deviceJOSNChannel.put("SubCount", deviceChannelExtend.getSubCount());
             deviceJOSNChannel.put("SnapURL", "");
             deviceJOSNChannel.put("Manufacturer ", deviceChannelExtend.getManufacture());
             deviceJOSNChannel.put("Model", deviceChannelExtend.getModel());

+ 10 - 9
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java

@@ -54,15 +54,16 @@ public class ApiStreamController {
 
     /**
      * 实时直播 - 开始直播
-     * @param serial 设备编号
-     * @param channel 通道序号 默认值: 1
-     * @param code 通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可
-     * @param cdn TODO 转推 CDN 地址, 形如: [rtmp|rtsp]://xxx, encodeURIComponent
-     * @param audio TODO 是否开启音频, 默认 开启
-     * @param transport 流传输模式, 默认 UDP
-     * @param checkchannelstatus TODO 是否检查通道状态, 默认 false, 表示 拉流前不检查通道状态是否在线
-     * @param transportmode TODO 当 transport=TCP 时有效, 指示流传输主被动模式, 默认被动
-     * @param timeout TODO 拉流超时(秒),
+     *
+     * @param serial             设备编号
+     * @param channel            通道序号 默认值: 1
+     * @param code               通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可
+     * @param cdn                转推 CDN 地址, 形如: [rtmp|rtsp]://xxx, encodeURIComponent
+     * @param audio              是否开启音频, 默认 开启
+     * @param transport          流传输模式, 默认 UDP
+     * @param checkchannelstatus 是否检查通道状态, 默认 false, 表示 拉流前不检查通道状态是否在线
+     * @param transportmode      当 transport=TCP 时有效, 指示流传输主被动模式, 默认被动
+     * @param timeout            拉流超时(秒),
      * @return
      */
     @RequestMapping(value = "/start")

+ 7 - 7
src/main/java/com/genersoft/iot/vmp/web/gb28181/AuthController.java

@@ -1,7 +1,7 @@
 package com.genersoft.iot.vmp.web.gb28181;
 
-import com.genersoft.iot.vmp.service.IUserService;
-import com.genersoft.iot.vmp.storager.dao.dto.User;
+import com.genersoft.iot.vmp.service.IAdminService;
+import com.genersoft.iot.vmp.storager.dao.dto.AdminAccount;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -11,14 +11,14 @@ import org.springframework.web.bind.annotation.*;
 public class AuthController {
 
     @Autowired
-    private IUserService userService;
+    private IAdminService userService;
 
     @RequestMapping("/login")
-    public String devices(String name, String passwd){
-        User user = userService.getUser(name, passwd);
-        if (user != null) {
+    public String devices(String name, String passwd) {
+        AdminAccount adminAccount = userService.getUser(name, passwd);
+        if (adminAccount != null) {
             return "success";
-        }else {
+        } else {
             return "fail";
         }
     }

+ 1 - 1
src/main/resources/all-application.yml

@@ -111,7 +111,7 @@ sip:
     register-time-interval: 60
     # [可选] 云台控制速度
     ptz-speed: 50
-    # TODO [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。
+    #  [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。
     # keepalliveToOnline: false
     # 是否存储alarm信息
     alarm: false

+ 19 - 2
src/main/resources/application.yml

@@ -59,6 +59,23 @@ spring:
             connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000
             #stat-view-servlet.url-pattern: /admin/druid/*
 
+############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
+sa-token:
+    # token 名称(同时也是 cookie 名称)
+    token-name: hfy
+    # token 有效期(单位:秒) 默认30天,-1 代表永久有效
+    timeout: 2592000
+    # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
+    active-timeout: -1
+    # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
+    is-concurrent: true
+    # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
+    is-share: true
+    # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+    token-style: uuid
+    # 是否输出操作日志
+    is-log: true
+
 # druid管理监控页面的一些配置
 rj-druid-manage:
     allow:                        # 访问druid监控页面的IP白名单
@@ -118,7 +135,7 @@ sip:
     register-time-interval: 60
     # [可选] 云台控制速度
     ptz-speed: 50
-    # TODO [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。
+    #  [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。
     # keepalliveToOnline: false
     # 是否存储alarm信息
     alarm: false
@@ -163,4 +180,4 @@ springdoc:
     api-docs:
         enabled: true
     swagger-ui:
-        enabled: false
+        enabled: true

+ 1 - 0
web_src/src/assets/base.css

@@ -1,4 +1,5 @@
 *{
     padding: 0;
     margin: 0;
+    box-sizing: border-box;
 }

+ 103 - 0
web_src/src/components/account_com/bindDevice.vue

@@ -0,0 +1,103 @@
+<script>
+import {FormVerify} from "kind-form-verify";
+import fieldCheck from "@/until/FieldCheck";
+import handle from "@/until/handle";
+
+
+let fieldVerify = null;
+export default {
+  name: "bindDevice",
+  data() {
+    return {
+      loading: false,
+      form: {
+        bindCode: {
+          val: "",
+          oldVal: "",
+          init: "",
+          msg: "",
+          showText: ""
+        }
+      }
+    }
+  },
+  beforeMount() {
+    fieldVerify = new FormVerify(this.form, fieldCheck.fieldCheck)
+  },
+  methods: {
+
+    formBlurHandle(key) {
+      console.log("formBlurHandle")
+      if (!this.form[key]) {
+        return console.warn("error field")
+      }
+      fieldVerify.checkItem(key)
+    },
+    formFocusHandle(type, key) {
+      let formObject = this.form;
+      if (!formObject[key]) {
+        return console.warn("error field")
+      }
+      formObject[key].msg = ""
+    },
+    bindHandle() {
+      console.log("bindHandle")
+      if (!fieldVerify.check()) {
+        return this.$message.warning("绑定码不合法")
+      }
+      let formData = fieldVerify.getFormData()
+      this.executeBindDevice(formData)
+
+    },
+    async executeBindDevice(formData) {
+      this.loading = true
+      let url = `/account/device/bind`
+      url += `?bindCode=${formData.bindCode}`
+      let [err, res] = await handle(this.$axios.get(
+          url,
+      ));
+      this.loading = false
+      if (err) {
+        console.log(err);
+        this.$message.error(`设备绑定失败 ${err.message}`);
+        return
+      }
+      let response = res.data
+      if (response.code !== 0) {
+        this.$message.error(`设备绑定失败 ${response.msg}`)
+        return
+      }
+      console.log(response);
+      this.$emit('exitBind')
+
+    }
+  }
+}
+</script>
+
+<template>
+  <div class="full-box">
+    <!--  输入绑定码 -->
+    <div class="bind-box" :loading="loading">
+      <el-form>
+        <el-form-item class="form-item" prop="bindCode" :error="form.bindCode.msg">
+          <el-input v-model="form.bindCode.val"
+                    placeholder="设备绑定码"
+                    auto-complete="new-password"
+                    @blur="formBlurHandle('bindCode')"
+                    @focus="formFocusHandle('register', 'bindCode')"
+          />
+        </el-form-item>
+      </el-form>
+      <el-button @click="bindHandle">
+        绑定
+      </el-button>
+
+    </div>
+    <div class="result-box"></div>
+  </div>
+</template>
+
+<style scoped>
+
+</style>

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff